@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.
@@ -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
- if (filteredTransferList.length > 0) {
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
- Task.info('No new data to upload for this VM')
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 transferByJobs = await Promise.all(
123
+ const transferObjectsList = await Promise.all(
131
124
  Object.values(sourceBackupByJobId).map(vmBackupsByJob =>
132
125
  this.#computeTransferListPerJob(vmBackupsByJob, remotesBackups)
133
126
  )
134
127
  )
135
- return transferByJobs.flat(1)
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 vmRef
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 vm = vbds[0].$VM
267
- assert.strictEqual(vm.is_a_snapshot, true, `VM must be a snapshot`) // don't delete a VM (especially a control domain)
268
-
269
- const vmRefVdi = vm.$ref
270
- // same vm than other vdi of the same batch
271
- assert.ok(
272
- vmRef === undefined || vmRef === vmRefVdi,
273
- '_removeUnusedSnapshots don t handle vdi related to multiple VMs '
274
- )
275
- vmRef = vmRefVdi
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 (vmRef !== undefined) {
279
- return xapi.VM_destroy(vmRef)
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.0",
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.1",
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.0",
33
- "@xen-orchestra/fs": "^4.6.4",
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.0",
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.4",
55
- "xen-api": "^4.7.5",
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.1"
65
+ "@xen-orchestra/xapi": "^8.6.3"
66
66
  },
67
67
  "license": "AGPL-3.0-or-later",
68
68
  "author": {