@xen-orchestra/backups 0.66.0 → 0.67.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ImportVmBackup.mjs +10 -10
- package/RemoteAdapter.mjs +9 -9
- package/_backupType.mjs +1 -0
- package/_cleanVm.mjs +19 -4
- package/_getOldEntries.mjs +43 -24
- package/_listPartitions.mjs +20 -2
- package/_runners/_vmRunners/_AbstractRemote.mjs +20 -18
- package/_runners/_writers/FullRemoteWriter.mjs +1 -1
- package/_runners/_writers/_MixinRemoteWriter.mjs +1 -1
- package/_runners/_writers/_MixinXapiWriter.mjs +1 -1
- package/formatVmBackups.mjs +1 -0
- package/package.json +6 -6
package/ImportVmBackup.mjs
CHANGED
|
@@ -29,10 +29,10 @@ export class ImportVmBackup {
|
|
|
29
29
|
metadata,
|
|
30
30
|
srUuid,
|
|
31
31
|
xapi,
|
|
32
|
-
settings: {
|
|
32
|
+
settings: { additionalVmTag, newMacAddresses, mapVdisSrs = {}, useDifferentialRestore = false } = {},
|
|
33
33
|
}) {
|
|
34
34
|
this._adapter = adapter
|
|
35
|
-
this._importIncrementalVmSettings = {
|
|
35
|
+
this._importIncrementalVmSettings = { additionalVmTag, newMacAddresses, mapVdisSrs, useDifferentialRestore }
|
|
36
36
|
this._metadata = metadata
|
|
37
37
|
this._srUuid = srUuid
|
|
38
38
|
this._xapi = xapi
|
|
@@ -61,11 +61,11 @@ export class ImportVmBackup {
|
|
|
61
61
|
const { mapVdisSrs } = this._importIncrementalVmSettings
|
|
62
62
|
const { vbds, vhds, vifs, vm, vmSnapshot, vtpms } = metadata
|
|
63
63
|
const disks = {}
|
|
64
|
-
const
|
|
64
|
+
const metadataDir = dirname(metadata._filename)
|
|
65
65
|
const vdis = ignoredVdis === undefined ? metadata.vdis : pickBy(metadata.vdis, vdi => !ignoredVdis.has(vdi.uuid))
|
|
66
66
|
|
|
67
67
|
for (const [vdiRef, vdi] of Object.entries(vdis)) {
|
|
68
|
-
const vhdPath = join(
|
|
68
|
+
const vhdPath = join(metadataDir, vhds[vdiRef])
|
|
69
69
|
|
|
70
70
|
let xapiDisk
|
|
71
71
|
try {
|
|
@@ -77,7 +77,7 @@ export class ImportVmBackup {
|
|
|
77
77
|
|
|
78
78
|
let snapshotCandidate, backupCandidate
|
|
79
79
|
if (xapiDisk !== undefined) {
|
|
80
|
-
debug('found disks,
|
|
80
|
+
debug('found disks, will search its snapshots', { snapshots: xapiDisk.snapshots })
|
|
81
81
|
for (const snapshotRef of xapiDisk.snapshots) {
|
|
82
82
|
const snapshot = await this._xapi.getRecord('VDI', snapshotRef)
|
|
83
83
|
|
|
@@ -96,7 +96,7 @@ export class ImportVmBackup {
|
|
|
96
96
|
// have a corresponding backup more recent than metadata ?
|
|
97
97
|
const pathToSnapshotData = await this.#getPathOfVdiSnapshot(snapshot.uuid)
|
|
98
98
|
if (pathToSnapshotData === undefined) {
|
|
99
|
-
debug('no backup linked to this
|
|
99
|
+
debug('no backup linked to this snapshot')
|
|
100
100
|
continue
|
|
101
101
|
}
|
|
102
102
|
if (snapshot.$SR.uuid !== (mapVdisSrs[vdi.$snapshot_of$uuid] ?? this._srUuid)) {
|
|
@@ -112,7 +112,7 @@ export class ImportVmBackup {
|
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
let disk
|
|
115
|
-
const backupWithSnapshotPath = join(
|
|
115
|
+
const backupWithSnapshotPath = join(metadataDir, backupCandidate ?? '')
|
|
116
116
|
if (vhdPath === backupWithSnapshotPath) {
|
|
117
117
|
// all the data are already on the host
|
|
118
118
|
debug('direct reuse of a snapshot')
|
|
@@ -194,7 +194,7 @@ export class ImportVmBackup {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
async #decorateIncrementalVmMetadata() {
|
|
197
|
-
const {
|
|
197
|
+
const { additionalVmTag, mapVdisSrs, useDifferentialRestore } = this._importIncrementalVmSettings
|
|
198
198
|
|
|
199
199
|
const ignoredVdis = new Set(
|
|
200
200
|
Object.entries(mapVdisSrs)
|
|
@@ -211,8 +211,8 @@ export class ImportVmBackup {
|
|
|
211
211
|
|
|
212
212
|
const cache = new Map()
|
|
213
213
|
const mapVdisSrRefs = {}
|
|
214
|
-
if (
|
|
215
|
-
backup.vm.tags.push(
|
|
214
|
+
if (additionalVmTag !== undefined) {
|
|
215
|
+
backup.vm.tags.push(additionalVmTag)
|
|
216
216
|
}
|
|
217
217
|
for (const [vdiUuid, srUuid] of Object.entries(mapVdisSrs)) {
|
|
218
218
|
mapVdisSrRefs[vdiUuid] = await resolveUuid(xapi, cache, srUuid, 'SR')
|
package/RemoteAdapter.mjs
CHANGED
|
@@ -27,7 +27,7 @@ import { formatFilenameDate } from './_filenameDate.mjs'
|
|
|
27
27
|
import { getTmpDir } from './_getTmpDir.mjs'
|
|
28
28
|
import { isMetadataFile } from './_backupType.mjs'
|
|
29
29
|
import { isValidXva } from './_isValidXva.mjs'
|
|
30
|
-
import { listPartitions,
|
|
30
|
+
import { listPartitions, LVM_PARTITION_TYPE_MBR, LVM_PARTITION_TYPE_GPT } from './_listPartitions.mjs'
|
|
31
31
|
import { lvs, pvs } from './_lvm.mjs'
|
|
32
32
|
import { watchStreamSize } from './_watchStreamSize.mjs'
|
|
33
33
|
|
|
@@ -40,7 +40,7 @@ export const DIR_XO_CONFIG_BACKUPS = 'xo-config-backups'
|
|
|
40
40
|
|
|
41
41
|
export const DIR_XO_POOL_METADATA_BACKUPS = 'xo-pool-metadata-backups'
|
|
42
42
|
|
|
43
|
-
const
|
|
43
|
+
const IMMUTABILITY_METADATA_FILENAME = '/immutability.json'
|
|
44
44
|
|
|
45
45
|
const { debug, warn } = createLogger('xo:backups:RemoteAdapter')
|
|
46
46
|
|
|
@@ -312,8 +312,8 @@ export class RemoteAdapter {
|
|
|
312
312
|
}
|
|
313
313
|
|
|
314
314
|
async deleteVmBackups(files) {
|
|
315
|
-
const
|
|
316
|
-
const { delta, full, ...others } = groupBy(
|
|
315
|
+
const metadata = await asyncMap(files, file => this.readVmBackupMetadata(file))
|
|
316
|
+
const { delta, full, ...others } = groupBy(metadata, 'mode')
|
|
317
317
|
|
|
318
318
|
const unsupportedModes = Object.keys(others)
|
|
319
319
|
if (unsupportedModes.length !== 0) {
|
|
@@ -498,7 +498,7 @@ export class RemoteAdapter {
|
|
|
498
498
|
|
|
499
499
|
const results = []
|
|
500
500
|
await asyncMapSettled(partitions, partition =>
|
|
501
|
-
partition.type ===
|
|
501
|
+
partition.type === LVM_PARTITION_TYPE_MBR || partition.type === LVM_PARTITION_TYPE_GPT
|
|
502
502
|
? this._listLvmLogicalVolumes(devicePath, partition, results)
|
|
503
503
|
: results.push(partition)
|
|
504
504
|
)
|
|
@@ -575,7 +575,7 @@ export class RemoteAdapter {
|
|
|
575
575
|
}
|
|
576
576
|
}
|
|
577
577
|
|
|
578
|
-
async #
|
|
578
|
+
async #getCacheableDataListVmBackups(dir) {
|
|
579
579
|
debug('generating cache', { path: dir })
|
|
580
580
|
|
|
581
581
|
const handler = this._handler
|
|
@@ -622,7 +622,7 @@ export class RemoteAdapter {
|
|
|
622
622
|
}
|
|
623
623
|
|
|
624
624
|
// nothing cached, or cache unreadable => regenerate it
|
|
625
|
-
const backups = await this.#
|
|
625
|
+
const backups = await this.#getCacheableDataListVmBackups(`${BACKUP_DIR}/${vmUuid}`)
|
|
626
626
|
if (backups === undefined) {
|
|
627
627
|
return
|
|
628
628
|
}
|
|
@@ -709,7 +709,7 @@ export class RemoteAdapter {
|
|
|
709
709
|
})
|
|
710
710
|
} else {
|
|
711
711
|
const stream = await toVhdStream(disk)
|
|
712
|
-
await this.outputStream(path, stream, { validator })
|
|
712
|
+
await this.outputStream(path, stream, { validator, checksum: false })
|
|
713
713
|
await validator(path)
|
|
714
714
|
}
|
|
715
715
|
}
|
|
@@ -779,7 +779,7 @@ export class RemoteAdapter {
|
|
|
779
779
|
// if the remote is immutable, check if this metadata is also immutable
|
|
780
780
|
try {
|
|
781
781
|
// this file is not encrypted
|
|
782
|
-
await this._handler._readFile(
|
|
782
|
+
await this._handler._readFile(IMMUTABILITY_METADATA_FILENAME)
|
|
783
783
|
remoteIsImmutable = true
|
|
784
784
|
} catch (error) {
|
|
785
785
|
if (error.code !== 'ENOENT') {
|
package/_backupType.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const isMetadataFile = filename => filename.endsWith('.json')
|
|
2
2
|
export const isVhdFile = filename => filename.endsWith('.vhd')
|
|
3
|
+
export const isVhdSumFile = filename => filename.endsWith('.vhd.checksum')
|
|
3
4
|
export const isXvaFile = filename => filename.endsWith('.xva')
|
|
4
5
|
export const isXvaSumFile = filename => filename.endsWith('.xva.checksum')
|
package/_cleanVm.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { asyncMap } from '@xen-orchestra/async-map'
|
|
|
4
4
|
import { Constants, openVhd, VhdAbstract, VhdFile } from 'vhd-lib'
|
|
5
5
|
import { isVhdAlias, resolveVhdAlias } from 'vhd-lib/aliases.js'
|
|
6
6
|
import { basename, dirname, resolve } from 'node:path'
|
|
7
|
-
import { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } from './_backupType.mjs'
|
|
7
|
+
import { isMetadataFile, isVhdFile, isVhdSumFile, isXvaFile, isXvaSumFile } from './_backupType.mjs'
|
|
8
8
|
import { limitConcurrency } from 'limit-concurrency-decorator'
|
|
9
9
|
import { mergeVhdChain } from 'vhd-lib/merge.js'
|
|
10
10
|
|
|
@@ -68,10 +68,12 @@ async function _mergeVhdChain(handler, chain, { logInfo, remove, mergeBlockConcu
|
|
|
68
68
|
const noop = Function.prototype
|
|
69
69
|
|
|
70
70
|
const INTERRUPTED_VHDS_REG = /^\.(.+)\.merge.json$/
|
|
71
|
+
|
|
71
72
|
const listVhds = async (handler, vmDir, logWarn) => {
|
|
72
73
|
const vhds = new Set()
|
|
73
74
|
const aliases = {}
|
|
74
75
|
const interruptedVhds = new Map()
|
|
76
|
+
const checksums = new Set()
|
|
75
77
|
|
|
76
78
|
await asyncMap(
|
|
77
79
|
await handler.list(`${vmDir}/vdis`, {
|
|
@@ -85,11 +87,15 @@ const listVhds = async (handler, vmDir, logWarn) => {
|
|
|
85
87
|
}),
|
|
86
88
|
async vdiDir => {
|
|
87
89
|
const list = await handler.list(vdiDir, {
|
|
88
|
-
filter: file => isVhdFile(file) || INTERRUPTED_VHDS_REG.test(file),
|
|
90
|
+
filter: file => isVhdFile(file) || INTERRUPTED_VHDS_REG.test(file) || isVhdSumFile(file),
|
|
89
91
|
})
|
|
90
92
|
aliases[vdiDir] = list.filter(vhd => isVhdAlias(vhd)).map(file => `${vdiDir}/${file}`)
|
|
91
93
|
|
|
92
94
|
await asyncMap(list, async file => {
|
|
95
|
+
if (isVhdSumFile(file)) {
|
|
96
|
+
checksums.add(`${vdiDir}/${file}`)
|
|
97
|
+
return
|
|
98
|
+
}
|
|
93
99
|
const res = INTERRUPTED_VHDS_REG.exec(file)
|
|
94
100
|
if (res === null) {
|
|
95
101
|
vhds.add(`${vdiDir}/${file}`)
|
|
@@ -111,7 +117,7 @@ const listVhds = async (handler, vmDir, logWarn) => {
|
|
|
111
117
|
)
|
|
112
118
|
)
|
|
113
119
|
|
|
114
|
-
return { vhds, interruptedVhds, aliases }
|
|
120
|
+
return { vhds, interruptedVhds, aliases, checksums }
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
export async function checkAliases(
|
|
@@ -228,7 +234,16 @@ export async function cleanVm(
|
|
|
228
234
|
const vhdParents = { __proto__: null }
|
|
229
235
|
const vhdChildren = { __proto__: null }
|
|
230
236
|
|
|
231
|
-
const { vhds, interruptedVhds, aliases } = await listVhds(handler, vmDir, logWarn)
|
|
237
|
+
const { vhds, interruptedVhds, aliases, checksums } = await listVhds(handler, vmDir, logWarn)
|
|
238
|
+
|
|
239
|
+
// from 5.110 to 5.113 we computed checksum for vhd file
|
|
240
|
+
// but never used nor removed them
|
|
241
|
+
await asyncMap(checksums, async path => {
|
|
242
|
+
if (remove) {
|
|
243
|
+
logInfo('deleting checksum file ', { path })
|
|
244
|
+
return handler.unlink(path)
|
|
245
|
+
}
|
|
246
|
+
})
|
|
232
247
|
|
|
233
248
|
// remove broken VHDs
|
|
234
249
|
await asyncMap(vhds, async path => {
|
package/_getOldEntries.mjs
CHANGED
|
@@ -97,40 +97,59 @@ export function getOldEntries(minRetentionCount, entries, { longTermRetention =
|
|
|
97
97
|
remaining: retention,
|
|
98
98
|
lastMatchingBucket: null,
|
|
99
99
|
formatter: LTR_DEFINITIONS[duration].makeDateFormatter({ ...settings, dateCreator }),
|
|
100
|
+
entries: {}, // bucket => entry id
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
const nb = entries.length
|
|
103
|
-
const
|
|
104
|
-
|
|
104
|
+
const minDurationEntries = []
|
|
105
|
+
let previousTimestamp = -1
|
|
105
106
|
for (let i = nb - 1; i >= 0; i--) {
|
|
106
107
|
const entry = entries[i]
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
108
|
+
if (entry.timestamp !== undefined) {
|
|
109
|
+
// we go through the entries from the last (most recent) to the first (oldest)
|
|
110
|
+
assert.ok(
|
|
111
|
+
previousTimestamp === -1 || entry.timestamp < previousTimestamp,
|
|
112
|
+
`entries must be sorted in desc order ${new Date(entry.timestamp)} , previous : > ${new Date(previousTimestamp)} `
|
|
113
|
+
)
|
|
114
|
+
previousTimestamp = entry.timestamp
|
|
115
|
+
const entryDate = dateCreator(entry.timestamp)
|
|
116
|
+
for (const [duration, { remaining, lastMatchingBucket, formatter }] of Object.entries(dateBuckets)) {
|
|
117
|
+
const bucket = formatter(entryDate)
|
|
118
|
+
if (lastMatchingBucket !== bucket) {
|
|
119
|
+
if (remaining === 0) {
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
dateBuckets[duration].lastMatchingBucket = bucket
|
|
123
|
+
dateBuckets[duration].remaining -= 1
|
|
121
124
|
}
|
|
122
|
-
|
|
123
|
-
dateBuckets[duration].remaining -= 1
|
|
124
|
-
dateBuckets[duration].lastMatchingBucket = bucket
|
|
125
|
+
dateBuckets[duration].entries[bucket] = entry
|
|
125
126
|
}
|
|
127
|
+
} else {
|
|
128
|
+
// replicated VM entries or snapshot retention don't have a timestamp
|
|
129
|
+
// but also can't have LTR
|
|
130
|
+
assert.deepStrictEqual(
|
|
131
|
+
longTermRetention,
|
|
132
|
+
{},
|
|
133
|
+
"Can't compute long term retention if entries don't have a timestamp"
|
|
134
|
+
)
|
|
126
135
|
}
|
|
136
|
+
|
|
137
|
+
// also keep it on retention
|
|
127
138
|
if (i >= nb - minRetentionCount) {
|
|
128
|
-
|
|
139
|
+
minDurationEntries.push(entry)
|
|
129
140
|
}
|
|
130
|
-
|
|
131
|
-
|
|
141
|
+
}
|
|
142
|
+
const kept = new Set(minDurationEntries)
|
|
143
|
+
for (const { entries } of Object.values(dateBuckets)) {
|
|
144
|
+
for (const entry of Object.values(entries)) {
|
|
145
|
+
kept.add(entry)
|
|
132
146
|
}
|
|
133
147
|
}
|
|
134
|
-
|
|
135
|
-
|
|
148
|
+
|
|
149
|
+
// ensure order is the same as the source
|
|
150
|
+
const oldEntries = entries.filter(entry => {
|
|
151
|
+
return !kept.has(entry)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
return oldEntries
|
|
136
155
|
}
|
package/_listPartitions.mjs
CHANGED
|
@@ -20,13 +20,31 @@ const IGNORED_PARTITION_TYPES = {
|
|
|
20
20
|
0xd5: true,
|
|
21
21
|
|
|
22
22
|
0x82: true, // swap
|
|
23
|
+
// GPT Linux swap GUID
|
|
24
|
+
'0657fd6d-a4ab-43c4-84e5-0933c84b4f4f': true,
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
// MBR LVM type
|
|
28
|
+
export const LVM_PARTITION_TYPE_MBR = 0x8e
|
|
29
|
+
// GPT LVM type
|
|
30
|
+
export const LVM_PARTITION_TYPE_GPT = 'e6d6d379-f507-44c2-a23c-238f2a3df928'
|
|
26
31
|
|
|
27
32
|
const parsePartxLine = createParser({
|
|
28
33
|
keyTransform: key => (key === 'UUID' ? 'id' : key.toLowerCase()),
|
|
29
|
-
valueTransform: (value, key) =>
|
|
34
|
+
valueTransform: (value, key) => {
|
|
35
|
+
if (key === 'start' || key === 'size') {
|
|
36
|
+
return +value
|
|
37
|
+
}
|
|
38
|
+
// For GPT partitions, type is a UUID string
|
|
39
|
+
if (key === 'type' && !value.startsWith('0x')) {
|
|
40
|
+
return value.toLowerCase()
|
|
41
|
+
}
|
|
42
|
+
// For MBR partitions, type is a hex number as string (e.g., "0x8e")
|
|
43
|
+
if (key === 'type' && value.startsWith('0x')) {
|
|
44
|
+
return parseInt(value, 16)
|
|
45
|
+
}
|
|
46
|
+
return value
|
|
47
|
+
},
|
|
30
48
|
})
|
|
31
49
|
|
|
32
50
|
// returns an empty array in case of a non-partitioned disk
|
|
@@ -74,28 +74,28 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
async #computeTransferListPerJob(sourceBackups, remotesBackups) {
|
|
77
|
-
const
|
|
77
|
+
const localMetadata = new Map()
|
|
78
78
|
sourceBackups.forEach(metadata => {
|
|
79
79
|
const timestamp = metadata.timestamp
|
|
80
|
-
|
|
80
|
+
localMetadata.set(timestamp, metadata)
|
|
81
81
|
})
|
|
82
82
|
const nbRemotes = remotesBackups.length
|
|
83
|
-
const
|
|
83
|
+
const remoteMetadata = {}
|
|
84
84
|
remotesBackups.forEach(async remoteBackups => {
|
|
85
85
|
remoteBackups.forEach(metadata => {
|
|
86
86
|
const timestamp = metadata.timestamp
|
|
87
|
-
|
|
87
|
+
remoteMetadata[timestamp] = (remoteMetadata[timestamp] ?? 0) + 1
|
|
88
88
|
})
|
|
89
89
|
})
|
|
90
90
|
|
|
91
91
|
let transferList = []
|
|
92
|
-
const timestamps = [...
|
|
92
|
+
const timestamps = [...localMetadata.keys()]
|
|
93
93
|
timestamps.sort()
|
|
94
94
|
for (const timestamp of timestamps) {
|
|
95
|
-
if (
|
|
95
|
+
if (remoteMetadata[timestamp] !== nbRemotes) {
|
|
96
96
|
// this backup is not present in all the remote
|
|
97
97
|
// should be retransferred if not found later
|
|
98
|
-
transferList.push(
|
|
98
|
+
transferList.push(localMetadata.get(timestamp))
|
|
99
99
|
} else {
|
|
100
100
|
// backup is present in local and remote : the chain has already been transferred
|
|
101
101
|
transferList = []
|
|
@@ -103,17 +103,10 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
|
|
103
103
|
}
|
|
104
104
|
if (transferList.length > 0) {
|
|
105
105
|
const filteredTransferList = this._filterTransferList(transferList)
|
|
106
|
-
|
|
107
|
-
return filteredTransferList
|
|
108
|
-
} else {
|
|
109
|
-
Task.info('This VM is excluded by the job filter')
|
|
110
|
-
return []
|
|
111
|
-
}
|
|
106
|
+
return { transferList: filteredTransferList, filtered: true }
|
|
112
107
|
} else {
|
|
113
|
-
|
|
108
|
+
return { transferList, filtered: false }
|
|
114
109
|
}
|
|
115
|
-
|
|
116
|
-
return []
|
|
117
110
|
}
|
|
118
111
|
|
|
119
112
|
/**
|
|
@@ -127,12 +120,21 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
|
|
127
120
|
Object.values(this.remoteAdapters).map(remoteAdapter => remoteAdapter.listVmBackups(this._vmUuid, vmPredicate))
|
|
128
121
|
)
|
|
129
122
|
const sourceBackupByJobId = groupBy(sourceBackups, 'jobId')
|
|
130
|
-
const
|
|
123
|
+
const transferObjectsList = await Promise.all(
|
|
131
124
|
Object.values(sourceBackupByJobId).map(vmBackupsByJob =>
|
|
132
125
|
this.#computeTransferListPerJob(vmBackupsByJob, remotesBackups)
|
|
133
126
|
)
|
|
134
127
|
)
|
|
135
|
-
|
|
128
|
+
const transferList = transferObjectsList.map(transferObject => transferObject.transferList).flat(1)
|
|
129
|
+
if (transferList.length === 0 && Object.values(sourceBackupByJobId).length > 0) {
|
|
130
|
+
if (transferObjectsList.some(transferObject => transferObject.filtered)) {
|
|
131
|
+
// Happens if any job transfer list wasn't empty before filter and is empty after filter.
|
|
132
|
+
Task.info('This VM is excluded by the job filter')
|
|
133
|
+
} else {
|
|
134
|
+
Task.info('No new data to upload for this VM')
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return transferList
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
async run($defer) {
|
|
@@ -33,7 +33,7 @@ export class FullRemoteWriter extends MixinRemoteWriter(AbstractFullWriter) {
|
|
|
33
33
|
let metadata = await this._isAlreadyTransferred(timestamp)
|
|
34
34
|
if (metadata !== undefined) {
|
|
35
35
|
// @todo : should skip backup while being vigilant to not stuck the forked stream
|
|
36
|
-
Task.info('This backup has already been
|
|
36
|
+
Task.info('This backup has already been transferred')
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const oldBackups = getOldEntries(
|
|
@@ -29,7 +29,7 @@ export const MixinXapiWriter = (BaseClass = Object) =>
|
|
|
29
29
|
// the SR that the VM has been replicated on
|
|
30
30
|
const sr = this._sr
|
|
31
31
|
assert.notStrictEqual(sr, undefined, 'SR should be defined before making a health check')
|
|
32
|
-
assert.notEqual(this._targetVmRef, undefined, 'A vm should have been
|
|
32
|
+
assert.notEqual(this._targetVmRef, undefined, 'A vm should have been transferred to be health checked')
|
|
33
33
|
// copy VM
|
|
34
34
|
return Task.run(
|
|
35
35
|
{
|
package/formatVmBackups.mjs
CHANGED
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.67.1",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.18"
|
|
14
14
|
},
|
|
@@ -26,11 +26,11 @@
|
|
|
26
26
|
"@vates/disposable": "^0.1.6",
|
|
27
27
|
"@vates/fuse-vhd": "^2.1.2",
|
|
28
28
|
"@vates/generator-toolbox": "^1.1.0",
|
|
29
|
-
"@vates/nbd-client": "^3.2.
|
|
29
|
+
"@vates/nbd-client": "^3.2.2",
|
|
30
30
|
"@vates/parse-duration": "^0.1.1",
|
|
31
31
|
"@xen-orchestra/async-map": "^0.1.2",
|
|
32
32
|
"@xen-orchestra/disk-transform": "^1.2.0",
|
|
33
|
-
"@xen-orchestra/fs": "^4.6.
|
|
33
|
+
"@xen-orchestra/fs": "^4.6.5",
|
|
34
34
|
"@xen-orchestra/log": "^0.7.1",
|
|
35
35
|
"@xen-orchestra/qcow2": "^1.1.0",
|
|
36
36
|
"@xen-orchestra/template": "^0.1.0",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"tar": "^6.1.15",
|
|
52
52
|
"uuid": "^9.0.0",
|
|
53
53
|
"value-matcher": "^0.2.0",
|
|
54
|
-
"vhd-lib": "^4.14.
|
|
55
|
-
"xen-api": "^4.7.
|
|
54
|
+
"vhd-lib": "^4.14.5",
|
|
55
|
+
"xen-api": "^4.7.5",
|
|
56
56
|
"yazl": "^2.5.1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"tmp": "^0.2.1"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@xen-orchestra/xapi": "^8.
|
|
65
|
+
"@xen-orchestra/xapi": "^8.6.1"
|
|
66
66
|
},
|
|
67
67
|
"license": "AGPL-3.0-or-later",
|
|
68
68
|
"author": {
|