@xen-orchestra/backups 0.67.0 → 0.67.2
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 +2 -0
- package/RemoteAdapter.mjs +1 -1
- package/_backupType.mjs +1 -0
- package/_cleanVm.mjs +19 -4
- package/_otherConfig.mjs +2 -1
- package/_runners/_vmRunners/_AbstractRemote.mjs +13 -11
- package/_runners/_vmRunners/_AbstractXapi.mjs +35 -13
- package/package.json +8 -8
package/ImportVmBackup.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import pickBy from 'lodash/pickBy.js'
|
|
|
11
11
|
import { defer } from 'golike-defer'
|
|
12
12
|
import { NegativeDisk } from '@xen-orchestra/disk-transform'
|
|
13
13
|
import { openDiskChain } from './disks/openDiskChain.mjs'
|
|
14
|
+
import { resetVmOtherConfig } from './_otherConfig.mjs'
|
|
14
15
|
|
|
15
16
|
const { debug, info, warn } = createLogger('xo:backups:importVmBackup')
|
|
16
17
|
async function resolveUuid(xapi, cache, uuid, type) {
|
|
@@ -270,6 +271,7 @@ export class ImportVmBackup {
|
|
|
270
271
|
`${metadata.vm.name_label} (${formatFilenameDate(metadata.timestamp)})`
|
|
271
272
|
),
|
|
272
273
|
xapi.call('VM.set_name_description', vmRef, desc),
|
|
274
|
+
resetVmOtherConfig(xapi, vmRef),
|
|
273
275
|
])
|
|
274
276
|
|
|
275
277
|
return {
|
package/RemoteAdapter.mjs
CHANGED
|
@@ -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
|
}
|
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/_otherConfig.mjs
CHANGED
|
@@ -70,7 +70,8 @@ export async function getVmDeltaChainLength(xapi, vmRef) {
|
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
72
|
*
|
|
73
|
-
* Reset the other_config field of a VM and its VDIs
|
|
73
|
+
* Reset the other_config field related to backups of a VM and its VDIs
|
|
74
|
+
*
|
|
74
75
|
*
|
|
75
76
|
* @param {Xapi} xapi
|
|
76
77
|
* @param {String} vmRef
|
|
@@ -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) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import assert from 'node:assert'
|
|
2
2
|
import groupBy from 'lodash/groupBy.js'
|
|
3
|
+
import { createLogger } from '@xen-orchestra/log'
|
|
3
4
|
import ignoreErrors from 'promise-toolbox/ignoreErrors'
|
|
4
5
|
import { asyncMap } from '@xen-orchestra/async-map'
|
|
5
6
|
import { decorateMethodsWith } from '@vates/decorate-with'
|
|
@@ -17,6 +18,8 @@ import {
|
|
|
17
18
|
setVmOtherConfig,
|
|
18
19
|
} from '../../_otherConfig.mjs'
|
|
19
20
|
|
|
21
|
+
const { warn } = createLogger('xo:backups:AbstractXapi')
|
|
22
|
+
|
|
20
23
|
export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
|
21
24
|
constructor({
|
|
22
25
|
config,
|
|
@@ -255,28 +258,47 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
|
|
255
258
|
return
|
|
256
259
|
}
|
|
257
260
|
const vdis = snapshotPerDatetime[datetime]
|
|
258
|
-
let
|
|
261
|
+
let vm
|
|
259
262
|
// if there is an attached VM => destroy the VM (Non CBT backups)
|
|
260
263
|
for (const vdi of vdis) {
|
|
261
264
|
const vbds = vdi.$VBDs.filter(({ $VM }) => $VM.is_control_domain === false)
|
|
262
265
|
if (vbds.length > 0) {
|
|
263
266
|
// only one VM linked to this vdi
|
|
264
267
|
// this will throw error for VDI still attached to control domain
|
|
268
|
+
// since we won't be able to remove an attached VDI
|
|
265
269
|
assert.strictEqual(vbds.length, 1, 'VDI must be free or attached to exactly one VM')
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
)
|
|
275
|
-
|
|
270
|
+
const vdiVm = vbds[0].$VM
|
|
271
|
+
if (!vdiVm.is_a_snapshot) {
|
|
272
|
+
// don't delete a VM (especially a control domain)
|
|
273
|
+
warn(
|
|
274
|
+
`VM ${vdiVm.uuid} (${vdiVm.name_label}) linked to VDI ${vdi.uuid} (${vdi.name_label}) should be a snapshot`
|
|
275
|
+
)
|
|
276
|
+
throw new Error(`VM must be a snapshot`)
|
|
277
|
+
}
|
|
278
|
+
if (vm !== undefined && vm.$ref !== vdiVm.$ref) {
|
|
279
|
+
// this VDI is attached to another VM than the other vdi of
|
|
280
|
+
// this batch
|
|
281
|
+
// in doubt, do not delete anything
|
|
282
|
+
warn("_removeUnusedSnapshots don't handle vdi related to multiple VMs ", {
|
|
283
|
+
vm1: {
|
|
284
|
+
label: vm.name_label,
|
|
285
|
+
id: vm.$id,
|
|
286
|
+
},
|
|
287
|
+
vm2: {
|
|
288
|
+
label: vdiVm.name_label,
|
|
289
|
+
id: vdiVm.$id,
|
|
290
|
+
},
|
|
291
|
+
vdis: vdis.map(({ name_label, $id }) => ({ name_label, $id })),
|
|
292
|
+
})
|
|
293
|
+
throw new Error(
|
|
294
|
+
`_removeUnusedSnapshots don't handle vdi related to multiple VMs ${vm.name_label} and ${vdiVm.name_label}`
|
|
295
|
+
)
|
|
296
|
+
}
|
|
297
|
+
vm = vdiVm
|
|
276
298
|
}
|
|
277
299
|
}
|
|
278
|
-
if (
|
|
279
|
-
return xapi.VM_destroy(
|
|
300
|
+
if (vm?.$ref !== undefined) {
|
|
301
|
+
return xapi.VM_destroy(vm.$ref)
|
|
280
302
|
} else {
|
|
281
303
|
return asyncMap(
|
|
282
304
|
vdis.map(async ({ $ref }) => {
|
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.67.
|
|
11
|
+
"version": "0.67.2",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.18"
|
|
14
14
|
},
|
|
@@ -26,13 +26,13 @@
|
|
|
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
|
-
"@xen-orchestra/disk-transform": "^1.2.
|
|
33
|
-
"@xen-orchestra/fs": "^4.6.
|
|
32
|
+
"@xen-orchestra/disk-transform": "^1.2.1",
|
|
33
|
+
"@xen-orchestra/fs": "^4.6.5",
|
|
34
34
|
"@xen-orchestra/log": "^0.7.1",
|
|
35
|
-
"@xen-orchestra/qcow2": "^1.1.
|
|
35
|
+
"@xen-orchestra/qcow2": "^1.1.1",
|
|
36
36
|
"@xen-orchestra/template": "^0.1.0",
|
|
37
37
|
"app-conf": "^3.0.0",
|
|
38
38
|
"compare-versions": "^6.0.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.6",
|
|
55
|
+
"xen-api": "^4.7.6",
|
|
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.6.
|
|
65
|
+
"@xen-orchestra/xapi": "^8.6.3"
|
|
66
66
|
},
|
|
67
67
|
"license": "AGPL-3.0-or-later",
|
|
68
68
|
"author": {
|