@xen-orchestra/backups 0.68.0 → 0.68.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/RemoteAdapter.mjs CHANGED
@@ -613,6 +613,11 @@ export class RemoteAdapter {
613
613
  // if cache is missing or broken => regenerate it and return
614
614
 
615
615
  async _readCacheListVmBackups(vmUuid) {
616
+ // immutable remote can't use any caching
617
+ // since the cache file may be non modifiable
618
+ if (this._handler.isImmutable()) {
619
+ return this.#getCacheableDataListVmBackups(`${BACKUP_DIR}/${vmUuid}`)
620
+ }
616
621
  const path = this.#getVmBackupsCache(vmUuid)
617
622
 
618
623
  const cache = await this._readCache(path)
@@ -291,10 +291,16 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
291
291
  await xapi.VTPM_create({ VM: vmRef, contents })
292
292
  })
293
293
  )
294
-
294
+ const vm = await xapi.getRecord('VM', vmRef)
295
295
  await Promise.all([
296
- incrementalVm.vm.ha_always_run && xapi.setField('VM', vmRef, 'ha_always_run', true),
297
- xapi.setField('VM', vmRef, 'name_label', incrementalVm.vm.name_label),
296
+ vmRecord.ha_always_run && xapi.setField('VM', vmRef, 'ha_always_run', true),
297
+ xapi.setField('VM', vmRef, 'name_label', vmRecord.name_label),
298
+ // correctly unlock the VM and reapply the target blocked operations
299
+ vm.update_blocked_operations({
300
+ start: null,
301
+ start_on: null,
302
+ ...vmRecord.blocked_operations,
303
+ }),
298
304
  ])
299
305
 
300
306
  return vmRef
@@ -3,6 +3,7 @@ import groupBy from 'lodash/groupBy.js'
3
3
  import { createLogger } from '@xen-orchestra/log'
4
4
  import ignoreErrors from 'promise-toolbox/ignoreErrors'
5
5
  import { asyncMap } from '@xen-orchestra/async-map'
6
+ import { asyncEach } from '@vates/async-each'
6
7
  import { decorateMethodsWith } from '@vates/decorate-with'
7
8
  import { defer } from 'golike-defer'
8
9
 
@@ -13,6 +14,8 @@ import { DATETIME, JOB_ID, SCHEDULE_ID, VM_UUID, resetVmOtherConfig, setVmOtherC
13
14
 
14
15
  const { warn, info } = createLogger('xo:backups:AbstractXapi')
15
16
 
17
+ const TEMP_SNAPSHOT_NAME = 'xo-backup-temp-snapshot-name'
18
+
16
19
  export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
17
20
  constructor({
18
21
  config,
@@ -156,7 +159,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
156
159
 
157
160
  const snapshotRef = await vm[settings.checkpointSnapshot ? '$checkpoint' : '$snapshot']({
158
161
  ignoredVdisTag: '[NOBAK]',
159
- name_label: this._getSnapshotNameLabel(vm),
162
+ name_label: TEMP_SNAPSHOT_NAME,
160
163
  unplugVusbs: true,
161
164
  })
162
165
  this.timestamp = Date.now()
@@ -166,6 +169,9 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
166
169
  scheduleId: this.scheduleId,
167
170
  vmUuid: vm.uuid,
168
171
  })
172
+ const snapshot = await xapi.getRecord('VM', snapshotRef)
173
+ await snapshot.set_name_label(this._getSnapshotNameLabel(vm))
174
+ // reload data to ensure it is up to date with the new name label
169
175
  this._exportedVm = await xapi.getRecord('VM', snapshotRef)
170
176
  return this._exportedVm.uuid
171
177
  })
@@ -371,6 +377,11 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
371
377
  }
372
378
  })
373
379
  })
380
+
381
+ // list and remove the snapshot were the jobs failed between
382
+ // makesnapshot and update_other_config
383
+ const snapshots = this._vm.$snapshots.filter(_ => !!_).filter(({ name_label }) => name_label === TEMP_SNAPSHOT_NAME)
384
+ await asyncEach(snapshots, snapshot => snapshot.$destroy())
374
385
  }
375
386
 
376
387
  async _removeSnapshotData() {
@@ -1,7 +1,6 @@
1
- import { asyncMap, asyncMapSettled } from '@xen-orchestra/async-map'
1
+ import { asyncMapSettled } from '@xen-orchestra/async-map'
2
2
  import ignoreErrors from 'promise-toolbox/ignoreErrors'
3
3
 
4
- import { formatFilenameDate } from '../../_filenameDate.mjs'
5
4
  import { getOldEntries } from '../../_getOldEntries.mjs'
6
5
  import { importIncrementalVm } from '../../_incrementalVm.mjs'
7
6
  import { Task } from '../../Task.mjs'
@@ -9,8 +8,18 @@ import { Task } from '../../Task.mjs'
9
8
  import { AbstractIncrementalWriter } from './_AbstractIncrementalWriter.mjs'
10
9
  import { MixinXapiWriter } from './_MixinXapiWriter.mjs'
11
10
  import { listReplicatedVms } from './_listReplicatedVms.mjs'
12
- import { COPY_OF, setVmOtherConfig, BASE_DELTA_VDI } from '../../_otherConfig.mjs'
11
+ import {
12
+ COPY_OF,
13
+ setVmOtherConfig,
14
+ BASE_DELTA_VDI,
15
+ JOB_ID,
16
+ SCHEDULE_ID,
17
+ REPLICATED_TO_SR_UUID,
18
+ DATETIME,
19
+ VM_UUID,
20
+ } from '../../_otherConfig.mjs'
13
21
  import assert from 'node:assert'
22
+ import { formatFilenameDate } from '../../_filenameDate.mjs'
14
23
 
15
24
  export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWriter) {
16
25
  async checkBaseVdis(baseUuidToSrcVdi) {
@@ -91,15 +100,31 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
91
100
  }
92
101
 
93
102
  async _deleteOldEntries() {
94
- return asyncMapSettled(this._oldEntries, vm => vm.$destroy())
103
+ return asyncMapSettled(this._oldEntries, vm => vm.$destroy({ bypassBlockedOperation: true }))
95
104
  }
96
105
 
97
- #decorateVmMetadata(backup) {
106
+ #decorateVmMetadata(backup, timestamp) {
98
107
  const { _warmMigration } = this._settings
99
108
  const sr = this._sr
100
109
  const vm = backup.vm
110
+ const job = this._job
111
+ const scheduleId = this._scheduleId
101
112
 
113
+ vm.name_label = `${vm.name_label} - ${job.name} - (${formatFilenameDate(timestamp)})`
114
+ // update other_config data as soon as possible to ensure the next job
115
+ // will be able to detect any partial transfer and lean them
102
116
  vm.other_config[COPY_OF] = vm.uuid
117
+ vm.other_config[JOB_ID] = job.id
118
+ vm.other_config[SCHEDULE_ID] = scheduleId
119
+ vm.other_config[REPLICATED_TO_SR_UUID] = sr.uuid
120
+ // set the timestamp in the past to ensure any incomplete VM will be deleted on next run
121
+ vm.other_config[DATETIME] = formatFilenameDate(0)
122
+
123
+ vm.blocked_operations = {
124
+ start: 'Start operation for this vm is blocked, clone it if you want to use it.',
125
+ start_on: 'Start operation for this vm is blocked, clone it if you want to use it.',
126
+ }
127
+
103
128
  if (!_warmMigration) {
104
129
  vm.tags.push('Continuous Replication')
105
130
  }
@@ -118,6 +143,11 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
118
143
 
119
144
  Object.values(backup.vdis).forEach(vdi => {
120
145
  vdi.other_config[COPY_OF] = vdi.uuid
146
+ vdi.other_config[JOB_ID] = job.id
147
+ vdi.other_config[SCHEDULE_ID] = scheduleId
148
+ vdi.other_config[REPLICATED_TO_SR_UUID] = sr.uuid
149
+ vdi.other_config[VM_UUID] = vm.uuid
150
+
121
151
  if (sourceVdiUuids.length > 0) {
122
152
  const baseReplicatedTo = replicatedVdis.filter(
123
153
  replicatedVdi => replicatedVdi.other_config[COPY_OF] === vdi.other_config[BASE_DELTA_VDI]
@@ -144,12 +174,11 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
144
174
  const sr = this._sr
145
175
  const job = this._job
146
176
  const scheduleId = this._scheduleId
147
-
148
177
  const { uuid: srUuid, $xapi: xapi } = sr
149
178
 
150
179
  let targetVmRef
151
180
  await Task.run({ name: 'transfer' }, async () => {
152
- targetVmRef = await importIncrementalVm(this.#decorateVmMetadata(deltaExport), sr)
181
+ targetVmRef = await importIncrementalVm(this.#decorateVmMetadata(deltaExport, timestamp), sr)
153
182
  // size is mandatory to ensure the task have the right data
154
183
  return {
155
184
  size: Object.values(deltaExport.disks).reduce(
@@ -166,15 +195,8 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
166
195
  !_warmMigration &&
167
196
  targetVm.ha_restart_priority !== '' &&
168
197
  Promise.all([targetVm.set_ha_restart_priority(''), targetVm.add_tags('HA disabled')]),
169
- targetVm.set_name_label(`${vm.name_label} - ${job.name} - (${formatFilenameDate(timestamp)})`),
170
- asyncMap(['start', 'start_on'], op =>
171
- targetVm.update_blocked_operations(
172
- op,
173
- 'Start operation for this vm is blocked, clone it if you want to use it.'
174
- )
175
- ),
176
198
  setVmOtherConfig(xapi, targetVmRef, {
177
- timestamp,
199
+ timestamp, // updated at the end to mark the transfer as complete
178
200
  jobId: job.id,
179
201
  scheduleId,
180
202
  vmUuid: vm.uuid,
@@ -130,7 +130,7 @@ export const MixinRemoteWriter = (BaseClass = Object) =>
130
130
  xapi,
131
131
  }).run()
132
132
  } finally {
133
- await xapi.VM_destroy(restoredVm.$ref)
133
+ await xapi.VM_destroy(restoredVm.$ref, { bypassBlockedOperation: true })
134
134
  }
135
135
  }
136
136
  )
@@ -72,7 +72,7 @@ export const MixinXapiWriter = (BaseClass = Object) =>
72
72
  xapi,
73
73
  }).run()
74
74
  } finally {
75
- healthCheckVmRef && (await xapi.VM_destroy(healthCheckVmRef))
75
+ healthCheckVmRef && (await xapi.VM_destroy(healthCheckVmRef, { bypassBlockedOperation: true }))
76
76
  }
77
77
  }
78
78
  )
@@ -20,9 +20,7 @@ export function listReplicatedVms(xapi, scheduleOrJobId, srUuid, vmUuid) {
20
20
  'start' in object.blocked_operations &&
21
21
  (oc[JOB_ID] === scheduleOrJobId || oc[SCHEDULE_ID] === scheduleOrJobId) &&
22
22
  oc[REPLICATED_TO_SR_UUID] === srUuid &&
23
- (oc[VM_UUID] === vmUuid ||
24
- // 2018-03-28, JFT: to catch VMs replicated before this fix
25
- oc[VM_UUID] === undefined)
23
+ oc[VM_UUID] === vmUuid
26
24
  ) {
27
25
  vms[object.$id] = object
28
26
  }
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.68.0",
11
+ "version": "0.68.2",
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.2",
29
+ "@vates/nbd-client": "^3.2.3",
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.1",
33
- "@xen-orchestra/fs": "^4.6.5",
33
+ "@xen-orchestra/fs": "^4.6.6",
34
34
  "@xen-orchestra/log": "^0.7.1",
35
35
  "@xen-orchestra/qcow2": "^1.1.2",
36
36
  "@xen-orchestra/template": "^0.1.0",
@@ -62,7 +62,7 @@
62
62
  "tmp": "^0.2.1"
63
63
  },
64
64
  "peerDependencies": {
65
- "@xen-orchestra/xapi": "^8.6.4"
65
+ "@xen-orchestra/xapi": "^8.6.6"
66
66
  },
67
67
  "license": "AGPL-3.0-or-later",
68
68
  "author": {