@xen-orchestra/backups 0.36.0 → 0.37.0

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.
Files changed (35) hide show
  1. package/Backup.js +11 -302
  2. package/ImportVmBackup.js +6 -6
  3. package/RemoteAdapter.js +20 -13
  4. package/RestoreMetadataBackup.js +1 -1
  5. package/_backupWorker.js +2 -2
  6. package/{_deltaVm.js → _incrementalVm.js} +11 -11
  7. package/_runners/Metadata.js +134 -0
  8. package/_runners/VmsXapi.js +138 -0
  9. package/_runners/_Abstract.js +51 -0
  10. package/{_PoolMetadataBackup.js → _runners/_PoolMetadataBackup.js} +3 -3
  11. package/_runners/_RemoteTimeoutError.js +8 -0
  12. package/{_XoMetadataBackup.js → _runners/_XoMetadataBackup.js} +3 -3
  13. package/_runners/_getAdaptersByRemote.js +9 -0
  14. package/_runners/_runTask.js +6 -0
  15. package/_runners/_vmRunners/FullXapi.js +61 -0
  16. package/_runners/_vmRunners/IncrementalXapi.js +163 -0
  17. package/_runners/_vmRunners/_Abstract.js +87 -0
  18. package/_runners/_vmRunners/_AbstractXapi.js +258 -0
  19. package/_runners/_vmRunners/_forkDeltaExport.js +12 -0
  20. package/{writers/FullBackupWriter.js → _runners/_writers/FullRemoteWriter.js} +5 -5
  21. package/{writers/FullReplicationWriter.js → _runners/_writers/FullXapiWriter.js} +5 -5
  22. package/{writers/DeltaBackupWriter.js → _runners/_writers/IncrementalRemoteWriter.js} +7 -7
  23. package/{writers/DeltaReplicationWriter.js → _runners/_writers/IncrementalXapiWriter.js} +10 -10
  24. package/{writers/_AbstractDeltaWriter.js → _runners/_writers/_AbstractIncrementalWriter.js} +1 -1
  25. package/{writers/_MixinBackupWriter.js → _runners/_writers/_MixinRemoteWriter.js} +10 -10
  26. package/{writers/_MixinReplicationWriter.js → _runners/_writers/_MixinXapiWriter.js} +6 -12
  27. package/package.json +6 -6
  28. package/_VmBackup.js +0 -515
  29. /package/{_createStreamThrottle.js → _runners/_createStreamThrottle.js} +0 -0
  30. /package/{_forkStreamUnpipe.js → _runners/_forkStreamUnpipe.js} +0 -0
  31. /package/{writers → _runners/_writers}/_AbstractFullWriter.js +0 -0
  32. /package/{writers → _runners/_writers}/_AbstractWriter.js +0 -0
  33. /package/{writers → _runners/_writers}/_checkVhd.js +0 -0
  34. /package/{writers → _runners/_writers}/_listReplicatedVms.js +0 -0
  35. /package/{writers → _runners/_writers}/_packUuid.js +0 -0
package/Backup.js CHANGED
@@ -1,307 +1,16 @@
1
1
  'use strict'
2
2
 
3
- const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
4
- const Disposable = require('promise-toolbox/Disposable')
5
- const ignoreErrors = require('promise-toolbox/ignoreErrors')
6
- const pTimeout = require('promise-toolbox/timeout')
7
- const { compileTemplate } = require('@xen-orchestra/template')
8
- const { limitConcurrency } = require('limit-concurrency-decorator')
9
-
10
- const { extractIdsFromSimplePattern } = require('./extractIdsFromSimplePattern.js')
11
- const { PoolMetadataBackup } = require('./_PoolMetadataBackup.js')
12
- const { Task } = require('./Task.js')
13
- const { VmBackup } = require('./_VmBackup.js')
14
- const { XoMetadataBackup } = require('./_XoMetadataBackup.js')
15
- const createStreamThrottle = require('./_createStreamThrottle.js')
16
-
17
- const noop = Function.prototype
18
-
19
- const getAdaptersByRemote = adapters => {
20
- const adaptersByRemote = {}
21
- adapters.forEach(({ adapter, remoteId }) => {
22
- adaptersByRemote[remoteId] = adapter
23
- })
24
- return adaptersByRemote
25
- }
26
-
27
- const runTask = (...args) => Task.run(...args).catch(noop) // errors are handled by logs
28
-
29
- const DEFAULT_SETTINGS = {
30
- getRemoteTimeout: 300e3,
31
- reportWhen: 'failure',
32
- }
33
-
34
- const DEFAULT_VM_SETTINGS = {
35
- bypassVdiChainsCheck: false,
36
- checkpointSnapshot: false,
37
- concurrency: 2,
38
- copyRetention: 0,
39
- deleteFirst: false,
40
- exportRetention: 0,
41
- fullInterval: 0,
42
- healthCheckSr: undefined,
43
- healthCheckVmsWithTags: [],
44
- maxExportRate: 0,
45
- maxMergedDeltasPerRun: Infinity,
46
- offlineBackup: false,
47
- offlineSnapshot: false,
48
- snapshotRetention: 0,
49
- timeout: 0,
50
- useNbd: false,
51
- unconditionalSnapshot: false,
52
- validateVhdStreams: false,
53
- vmTimeout: 0,
54
- }
55
-
56
- const DEFAULT_METADATA_SETTINGS = {
57
- retentionPoolMetadata: 0,
58
- retentionXoMetadata: 0,
59
- }
60
-
61
- class RemoteTimeoutError extends Error {
62
- constructor(remoteId) {
63
- super('timeout while getting the remote ' + remoteId)
64
- this.remoteId = remoteId
65
- }
66
- }
67
-
68
- exports.Backup = class Backup {
69
- constructor({ config, getAdapter, getConnectedRecord, job, schedule }) {
70
- this._config = config
71
- this._getRecord = getConnectedRecord
72
- this._job = job
73
- this._schedule = schedule
74
-
75
- this._getSnapshotNameLabel = compileTemplate(config.snapshotNameLabelTpl, {
76
- '{job.name}': job.name,
77
- '{vm.name_label}': vm => vm.name_label,
78
- })
79
-
80
- const { type } = job
81
- const baseSettings = { ...DEFAULT_SETTINGS }
82
- if (type === 'backup') {
83
- Object.assign(baseSettings, DEFAULT_VM_SETTINGS, config.defaultSettings, config.vm?.defaultSettings)
84
- this.run = this._runVmBackup
85
- } else if (type === 'metadataBackup') {
86
- Object.assign(baseSettings, DEFAULT_METADATA_SETTINGS, config.defaultSettings, config.metadata?.defaultSettings)
87
- this.run = this._runMetadataBackup
88
- } else {
3
+ const { Metadata } = require('./_runners/Metadata.js')
4
+ const { VmsXapi } = require('./_runners/VmsXapi.js')
5
+
6
+ exports.createRunner = function createRunner(opts) {
7
+ const { type } = opts.job
8
+ switch (type) {
9
+ case 'backup':
10
+ return new VmsXapi(opts)
11
+ case 'metadataBackup':
12
+ return new Metadata(opts)
13
+ default:
89
14
  throw new Error(`No runner for the backup type ${type}`)
90
- }
91
- Object.assign(baseSettings, job.settings[''])
92
-
93
- this._baseSettings = baseSettings
94
- this._settings = { ...baseSettings, ...job.settings[schedule.id] }
95
-
96
- const { getRemoteTimeout } = this._settings
97
- this._getAdapter = async function (remoteId) {
98
- try {
99
- const disposable = await pTimeout.call(getAdapter(remoteId), getRemoteTimeout, new RemoteTimeoutError(remoteId))
100
-
101
- return new Disposable(() => disposable.dispose(), {
102
- adapter: disposable.value,
103
- remoteId,
104
- })
105
- } catch (error) {
106
- // See https://github.com/vatesfr/xen-orchestra/commit/6aa6cfba8ec939c0288f0fa740f6dfad98c43cbb
107
- runTask(
108
- {
109
- name: 'get remote adapter',
110
- data: { type: 'remote', id: remoteId },
111
- },
112
- () => Promise.reject(error)
113
- )
114
- }
115
- }
116
- }
117
-
118
- async _runMetadataBackup() {
119
- const schedule = this._schedule
120
- const job = this._job
121
- const remoteIds = extractIdsFromSimplePattern(job.remotes)
122
- if (remoteIds.length === 0) {
123
- throw new Error('metadata backup job cannot run without remotes')
124
- }
125
-
126
- const config = this._config
127
- const poolIds = extractIdsFromSimplePattern(job.pools)
128
- const isEmptyPools = poolIds.length === 0
129
- const isXoMetadata = job.xoMetadata !== undefined
130
- if (!isXoMetadata && isEmptyPools) {
131
- throw new Error('no metadata mode found')
132
- }
133
-
134
- const settings = this._settings
135
-
136
- const { retentionPoolMetadata, retentionXoMetadata } = settings
137
-
138
- if (
139
- (retentionPoolMetadata === 0 && retentionXoMetadata === 0) ||
140
- (!isXoMetadata && retentionPoolMetadata === 0) ||
141
- (isEmptyPools && retentionXoMetadata === 0)
142
- ) {
143
- throw new Error('no retentions corresponding to the metadata modes found')
144
- }
145
-
146
- await Disposable.use(
147
- Disposable.all(
148
- poolIds.map(id =>
149
- this._getRecord('pool', id).catch(error => {
150
- // See https://github.com/vatesfr/xen-orchestra/commit/6aa6cfba8ec939c0288f0fa740f6dfad98c43cbb
151
- runTask(
152
- {
153
- name: 'get pool record',
154
- data: { type: 'pool', id },
155
- },
156
- () => Promise.reject(error)
157
- )
158
- })
159
- )
160
- ),
161
- Disposable.all(remoteIds.map(id => this._getAdapter(id))),
162
- async (pools, remoteAdapters) => {
163
- // remove adapters that failed (already handled)
164
- remoteAdapters = remoteAdapters.filter(_ => _ !== undefined)
165
- if (remoteAdapters.length === 0) {
166
- return
167
- }
168
- remoteAdapters = getAdaptersByRemote(remoteAdapters)
169
-
170
- // remove pools that failed (already handled)
171
- pools = pools.filter(_ => _ !== undefined)
172
-
173
- const promises = []
174
- if (pools.length !== 0 && settings.retentionPoolMetadata !== 0) {
175
- promises.push(
176
- asyncMap(pools, async pool =>
177
- runTask(
178
- {
179
- name: `Starting metadata backup for the pool (${pool.$id}). (${job.id})`,
180
- data: {
181
- id: pool.$id,
182
- pool,
183
- poolMaster: await ignoreErrors.call(pool.$xapi.getRecord('host', pool.master)),
184
- type: 'pool',
185
- },
186
- },
187
- () =>
188
- new PoolMetadataBackup({
189
- config,
190
- job,
191
- pool,
192
- remoteAdapters,
193
- schedule,
194
- settings,
195
- }).run()
196
- )
197
- )
198
- )
199
- }
200
-
201
- if (job.xoMetadata !== undefined && settings.retentionXoMetadata !== 0) {
202
- promises.push(
203
- runTask(
204
- {
205
- name: `Starting XO metadata backup. (${job.id})`,
206
- data: {
207
- type: 'xo',
208
- },
209
- },
210
- () =>
211
- new XoMetadataBackup({
212
- config,
213
- job,
214
- remoteAdapters,
215
- schedule,
216
- settings,
217
- }).run()
218
- )
219
- )
220
- }
221
- await Promise.all(promises)
222
- }
223
- )
224
- }
225
-
226
- async _runVmBackup() {
227
- const job = this._job
228
-
229
- // FIXME: proper SimpleIdPattern handling
230
- const getSnapshotNameLabel = this._getSnapshotNameLabel
231
- const schedule = this._schedule
232
- const settings = this._settings
233
-
234
- const throttleStream = createStreamThrottle(settings.maxExportRate)
235
-
236
- const config = this._config
237
- await Disposable.use(
238
- Disposable.all(
239
- extractIdsFromSimplePattern(job.srs).map(id =>
240
- this._getRecord('SR', id).catch(error => {
241
- runTask(
242
- {
243
- name: 'get SR record',
244
- data: { type: 'SR', id },
245
- },
246
- () => Promise.reject(error)
247
- )
248
- })
249
- )
250
- ),
251
- Disposable.all(extractIdsFromSimplePattern(job.remotes).map(id => this._getAdapter(id))),
252
- () => (settings.healthCheckSr !== undefined ? this._getRecord('SR', settings.healthCheckSr) : undefined),
253
- async (srs, remoteAdapters, healthCheckSr) => {
254
- // remove adapters that failed (already handled)
255
- remoteAdapters = remoteAdapters.filter(_ => _ !== undefined)
256
-
257
- // remove srs that failed (already handled)
258
- srs = srs.filter(_ => _ !== undefined)
259
-
260
- if (remoteAdapters.length === 0 && srs.length === 0 && settings.snapshotRetention === 0) {
261
- return
262
- }
263
-
264
- const vmIds = extractIdsFromSimplePattern(job.vms)
265
-
266
- Task.info('vms', { vms: vmIds })
267
-
268
- remoteAdapters = getAdaptersByRemote(remoteAdapters)
269
-
270
- const allSettings = this._job.settings
271
- const baseSettings = this._baseSettings
272
-
273
- const handleVm = vmUuid => {
274
- const taskStart = { name: 'backup VM', data: { type: 'VM', id: vmUuid } }
275
-
276
- return this._getRecord('VM', vmUuid).then(
277
- disposableVm =>
278
- Disposable.use(disposableVm, vm => {
279
- taskStart.data.name_label = vm.name_label
280
- return runTask(taskStart, () =>
281
- new VmBackup({
282
- baseSettings,
283
- config,
284
- getSnapshotNameLabel,
285
- healthCheckSr,
286
- job,
287
- remoteAdapters,
288
- schedule,
289
- settings: { ...settings, ...allSettings[vm.uuid] },
290
- srs,
291
- throttleStream,
292
- vm,
293
- }).run()
294
- )
295
- }),
296
- error =>
297
- runTask(taskStart, () => {
298
- throw error
299
- })
300
- )
301
- }
302
- const { concurrency } = settings
303
- await asyncMapSettled(vmIds, concurrency === 0 ? handleVm : limitConcurrency(concurrency)(handleVm))
304
- }
305
- )
306
15
  }
307
16
  }
package/ImportVmBackup.js CHANGED
@@ -3,14 +3,14 @@
3
3
  const assert = require('assert')
4
4
 
5
5
  const { formatFilenameDate } = require('./_filenameDate.js')
6
- const { importDeltaVm } = require('./_deltaVm.js')
6
+ const { importIncrementalVm } = require('./_incrementalVm.js')
7
7
  const { Task } = require('./Task.js')
8
8
  const { watchStreamSize } = require('./_watchStreamSize.js')
9
9
 
10
10
  exports.ImportVmBackup = class ImportVmBackup {
11
11
  constructor({ adapter, metadata, srUuid, xapi, settings: { newMacAddresses, mapVdisSrs = {} } = {} }) {
12
12
  this._adapter = adapter
13
- this._importDeltaVmSettings = { newMacAddresses, mapVdisSrs }
13
+ this._importIncrementalVmSettings = { newMacAddresses, mapVdisSrs }
14
14
  this._metadata = metadata
15
15
  this._srUuid = srUuid
16
16
  this._xapi = xapi
@@ -31,11 +31,11 @@ exports.ImportVmBackup = class ImportVmBackup {
31
31
  assert.strictEqual(metadata.mode, 'delta')
32
32
 
33
33
  const ignoredVdis = new Set(
34
- Object.entries(this._importDeltaVmSettings.mapVdisSrs)
34
+ Object.entries(this._importIncrementalVmSettings.mapVdisSrs)
35
35
  .filter(([_, srUuid]) => srUuid === null)
36
36
  .map(([vdiUuid]) => vdiUuid)
37
37
  )
38
- backup = await adapter.readDeltaVmBackup(metadata, ignoredVdis)
38
+ backup = await adapter.readIncrementalVmBackup(metadata, ignoredVdis)
39
39
  Object.values(backup.streams).forEach(stream => watchStreamSize(stream, sizeContainer))
40
40
  }
41
41
 
@@ -49,8 +49,8 @@ exports.ImportVmBackup = class ImportVmBackup {
49
49
 
50
50
  const vmRef = isFull
51
51
  ? await xapi.VM_import(backup, srRef)
52
- : await importDeltaVm(backup, await xapi.getRecord('SR', srRef), {
53
- ...this._importDeltaVmSettings,
52
+ : await importIncrementalVm(backup, await xapi.getRecord('SR', srRef), {
53
+ ...this._importIncrementalVmSettings,
54
54
  detectBase: false,
55
55
  })
56
56
 
package/RemoteAdapter.js CHANGED
@@ -333,7 +333,7 @@ class RemoteAdapter {
333
333
  const RE_VHDI = /^vhdi(\d+)$/
334
334
  const handler = this._handler
335
335
 
336
- const diskPath = handler._getFilePath('/' + diskId)
336
+ const diskPath = handler.getFilePath('/' + diskId)
337
337
  const mountDir = yield getTmpDir()
338
338
  await fromCallback(execFile, 'vhdimount', [diskPath, mountDir])
339
339
  try {
@@ -404,20 +404,27 @@ class RemoteAdapter {
404
404
  return `${baseName}.vhd`
405
405
  }
406
406
 
407
- async listAllVmBackups() {
407
+ async listAllVms() {
408
408
  const handler = this._handler
409
-
410
- const backups = { __proto__: null }
411
- await asyncMap(await handler.list(BACKUP_DIR), async entry => {
409
+ const vmsUuids = []
410
+ await asyncEach(await handler.list(BACKUP_DIR), async entry => {
412
411
  // ignore hidden and lock files
413
412
  if (entry[0] !== '.' && !entry.endsWith('.lock')) {
414
- const vmBackups = await this.listVmBackups(entry)
415
- if (vmBackups.length !== 0) {
416
- backups[entry] = vmBackups
417
- }
413
+ vmsUuids.push(entry)
418
414
  }
419
415
  })
416
+ return vmsUuids
417
+ }
420
418
 
419
+ async listAllVmBackups() {
420
+ const vmsUuids = await this.listAllVms()
421
+ const backups = { __proto__: null }
422
+ await asyncEach(vmsUuids, async vmUuid => {
423
+ const vmBackups = await this.listVmBackups(vmUuid)
424
+ if (vmBackups.length !== 0) {
425
+ backups[vmUuid] = vmBackups
426
+ }
427
+ })
421
428
  return backups
422
429
  }
423
430
 
@@ -691,8 +698,8 @@ class RemoteAdapter {
691
698
  }
692
699
 
693
700
  // open the hierarchy of ancestors until we find a full one
694
- async _createSyntheticStream(handler, path) {
695
- const disposableSynthetic = await VhdSynthetic.fromVhdChain(handler, path)
701
+ async _createVhdStream(handler, path, { useChain }) {
702
+ const disposableSynthetic = useChain ? await VhdSynthetic.fromVhdChain(handler, path) : await openVhd(handler, path)
696
703
  // I don't want the vhds to be disposed on return
697
704
  // but only when the stream is done ( or failed )
698
705
 
@@ -717,7 +724,7 @@ class RemoteAdapter {
717
724
  return stream
718
725
  }
719
726
 
720
- async readDeltaVmBackup(metadata, ignoredVdis) {
727
+ async readIncrementalVmBackup(metadata, ignoredVdis, { useChain = true } = {}) {
721
728
  const handler = this._handler
722
729
  const { vbds, vhds, vifs, vm, vmSnapshot } = metadata
723
730
  const dir = dirname(metadata._filename)
@@ -725,7 +732,7 @@ class RemoteAdapter {
725
732
 
726
733
  const streams = {}
727
734
  await asyncMapSettled(Object.keys(vdis), async ref => {
728
- streams[`${ref}.vhd`] = await this._createSyntheticStream(handler, join(dir, vhds[ref]))
735
+ streams[`${ref}.vhd`] = await this._createVhdStream(handler, join(dir, vhds[ref]), { useChain })
729
736
  })
730
737
 
731
738
  return {
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const { DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')
4
- const { PATH_DB_DUMP } = require('./_PoolMetadataBackup.js')
4
+ const { PATH_DB_DUMP } = require('./_runners/_PoolMetadataBackup.js')
5
5
 
6
6
  exports.RestoreMetadataBackup = class RestoreMetadataBackup {
7
7
  constructor({ backupId, handler, xapi }) {
package/_backupWorker.js CHANGED
@@ -13,10 +13,10 @@ const { createDebounceResource } = require('@vates/disposable/debounceResource.j
13
13
  const { decorateMethodsWith } = require('@vates/decorate-with')
14
14
  const { deduped } = require('@vates/disposable/deduped.js')
15
15
  const { getHandler } = require('@xen-orchestra/fs')
16
+ const { createRunner } = require('./Backup.js')
16
17
  const { parseDuration } = require('@vates/parse-duration')
17
18
  const { Xapi } = require('@xen-orchestra/xapi')
18
19
 
19
- const { Backup } = require('./Backup.js')
20
20
  const { RemoteAdapter } = require('./RemoteAdapter.js')
21
21
  const { Task } = require('./Task.js')
22
22
 
@@ -48,7 +48,7 @@ class BackupWorker {
48
48
  }
49
49
 
50
50
  run() {
51
- return new Backup({
51
+ return createRunner({
52
52
  config: this.#config,
53
53
  getAdapter: remoteId => this.getAdapter(this.#remotes[remoteId]),
54
54
  getConnectedRecord: Disposable.factory(async function* getConnectedRecord(type, uuid) {
@@ -33,7 +33,7 @@ const resolveUuid = async (xapi, cache, uuid, type) => {
33
33
  return ref
34
34
  }
35
35
 
36
- exports.exportDeltaVm = async function exportDeltaVm(
36
+ exports.exportIncrementalVm = async function exportIncrementalVm(
37
37
  vm,
38
38
  baseVm,
39
39
  {
@@ -143,18 +143,18 @@ exports.exportDeltaVm = async function exportDeltaVm(
143
143
  )
144
144
  }
145
145
 
146
- exports.importDeltaVm = defer(async function importDeltaVm(
146
+ exports.importIncrementalVm = defer(async function importIncrementalVm(
147
147
  $defer,
148
- deltaVm,
148
+ incrementalVm,
149
149
  sr,
150
150
  { cancelToken = CancelToken.none, detectBase = true, mapVdisSrs = {}, newMacAddresses = false } = {}
151
151
  ) {
152
- const { version } = deltaVm
152
+ const { version } = incrementalVm
153
153
  if (compareVersions(version, '1.0.0') < 0) {
154
154
  throw new Error(`Unsupported delta backup version: ${version}`)
155
155
  }
156
156
 
157
- const vmRecord = deltaVm.vm
157
+ const vmRecord = incrementalVm.vm
158
158
  const xapi = sr.$xapi
159
159
 
160
160
  let baseVm
@@ -183,7 +183,7 @@ exports.importDeltaVm = defer(async function importDeltaVm(
183
183
  baseVdis[vbd.VDI] = vbd.$VDI
184
184
  }
185
185
  })
186
- const vdiRecords = deltaVm.vdis
186
+ const vdiRecords = incrementalVm.vdis
187
187
 
188
188
  // 0. Create suspend_VDI
189
189
  let suspendVdi
@@ -240,7 +240,7 @@ exports.importDeltaVm = defer(async function importDeltaVm(
240
240
  await asyncMap(await xapi.getField('VM', vmRef, 'VBDs'), ref => ignoreErrors.call(xapi.call('VBD.destroy', ref)))
241
241
 
242
242
  // 3. Create VDIs & VBDs.
243
- const vbdRecords = deltaVm.vbds
243
+ const vbdRecords = incrementalVm.vbds
244
244
  const vbds = groupBy(vbdRecords, 'VDI')
245
245
  const newVdis = {}
246
246
  await asyncMap(Object.keys(vdiRecords), async vdiRef => {
@@ -309,7 +309,7 @@ exports.importDeltaVm = defer(async function importDeltaVm(
309
309
  }
310
310
  })
311
311
 
312
- const { streams } = deltaVm
312
+ const { streams } = incrementalVm
313
313
 
314
314
  await Promise.all([
315
315
  // Import VDI contents.
@@ -326,7 +326,7 @@ exports.importDeltaVm = defer(async function importDeltaVm(
326
326
  }),
327
327
 
328
328
  // Create VIFs.
329
- asyncMap(Object.values(deltaVm.vifs), vif => {
329
+ asyncMap(Object.values(incrementalVm.vifs), vif => {
330
330
  let network = vif.$network$uuid && xapi.getObjectByUuid(vif.$network$uuid, undefined)
331
331
 
332
332
  if (network === undefined) {
@@ -358,8 +358,8 @@ exports.importDeltaVm = defer(async function importDeltaVm(
358
358
  ])
359
359
 
360
360
  await Promise.all([
361
- deltaVm.vm.ha_always_run && xapi.setField('VM', vmRef, 'ha_always_run', true),
362
- xapi.setField('VM', vmRef, 'name_label', deltaVm.vm.name_label),
361
+ incrementalVm.vm.ha_always_run && xapi.setField('VM', vmRef, 'ha_always_run', true),
362
+ xapi.setField('VM', vmRef, 'name_label', incrementalVm.vm.name_label),
363
363
  ])
364
364
 
365
365
  return vmRef
@@ -0,0 +1,134 @@
1
+ 'use strict'
2
+
3
+ const { asyncMap } = require('@xen-orchestra/async-map')
4
+ const Disposable = require('promise-toolbox/Disposable')
5
+ const ignoreErrors = require('promise-toolbox/ignoreErrors')
6
+
7
+ const { extractIdsFromSimplePattern } = require('../extractIdsFromSimplePattern.js')
8
+ const { PoolMetadataBackup } = require('./_PoolMetadataBackup.js')
9
+ const { XoMetadataBackup } = require('./_XoMetadataBackup.js')
10
+ const { DEFAULT_SETTINGS, Abstract } = require('./_Abstract.js')
11
+ const { runTask } = require('./_runTask.js')
12
+ const { getAdaptersByRemote } = require('./_getAdaptersByRemote.js')
13
+
14
+ const DEFAULT_METADATA_SETTINGS = {
15
+ retentionPoolMetadata: 0,
16
+ retentionXoMetadata: 0,
17
+ }
18
+
19
+ exports.Metadata = class MetadataBackupRunner extends Abstract {
20
+ _computeBaseSettings(config, job) {
21
+ const baseSettings = { ...DEFAULT_SETTINGS }
22
+ Object.assign(baseSettings, DEFAULT_METADATA_SETTINGS, config.defaultSettings, config.metadata?.defaultSettings)
23
+ Object.assign(baseSettings, job.settings[''])
24
+ return baseSettings
25
+ }
26
+
27
+ async run() {
28
+ const schedule = this._schedule
29
+ const job = this._job
30
+ const remoteIds = extractIdsFromSimplePattern(job.remotes)
31
+ if (remoteIds.length === 0) {
32
+ throw new Error('metadata backup job cannot run without remotes')
33
+ }
34
+
35
+ const config = this._config
36
+ const poolIds = extractIdsFromSimplePattern(job.pools)
37
+ const isEmptyPools = poolIds.length === 0
38
+ const isXoMetadata = job.xoMetadata !== undefined
39
+ if (!isXoMetadata && isEmptyPools) {
40
+ throw new Error('no metadata mode found')
41
+ }
42
+
43
+ const settings = this._settings
44
+
45
+ const { retentionPoolMetadata, retentionXoMetadata } = settings
46
+
47
+ if (
48
+ (retentionPoolMetadata === 0 && retentionXoMetadata === 0) ||
49
+ (!isXoMetadata && retentionPoolMetadata === 0) ||
50
+ (isEmptyPools && retentionXoMetadata === 0)
51
+ ) {
52
+ throw new Error('no retentions corresponding to the metadata modes found')
53
+ }
54
+
55
+ await Disposable.use(
56
+ Disposable.all(
57
+ poolIds.map(id =>
58
+ this._getRecord('pool', id).catch(error => {
59
+ // See https://github.com/vatesfr/xen-orchestra/commit/6aa6cfba8ec939c0288f0fa740f6dfad98c43cbb
60
+ runTask(
61
+ {
62
+ name: 'get pool record',
63
+ data: { type: 'pool', id },
64
+ },
65
+ () => Promise.reject(error)
66
+ )
67
+ })
68
+ )
69
+ ),
70
+ Disposable.all(remoteIds.map(id => this._getAdapter(id))),
71
+ async (pools, remoteAdapters) => {
72
+ // remove adapters that failed (already handled)
73
+ remoteAdapters = remoteAdapters.filter(_ => _ !== undefined)
74
+ if (remoteAdapters.length === 0) {
75
+ return
76
+ }
77
+ remoteAdapters = getAdaptersByRemote(remoteAdapters)
78
+
79
+ // remove pools that failed (already handled)
80
+ pools = pools.filter(_ => _ !== undefined)
81
+
82
+ const promises = []
83
+ if (pools.length !== 0 && settings.retentionPoolMetadata !== 0) {
84
+ promises.push(
85
+ asyncMap(pools, async pool =>
86
+ runTask(
87
+ {
88
+ name: `Starting metadata backup for the pool (${pool.$id}). (${job.id})`,
89
+ data: {
90
+ id: pool.$id,
91
+ pool,
92
+ poolMaster: await ignoreErrors.call(pool.$xapi.getRecord('host', pool.master)),
93
+ type: 'pool',
94
+ },
95
+ },
96
+ () =>
97
+ new PoolMetadataBackup({
98
+ config,
99
+ job,
100
+ pool,
101
+ remoteAdapters,
102
+ schedule,
103
+ settings,
104
+ }).run()
105
+ )
106
+ )
107
+ )
108
+ }
109
+
110
+ if (job.xoMetadata !== undefined && settings.retentionXoMetadata !== 0) {
111
+ promises.push(
112
+ runTask(
113
+ {
114
+ name: `Starting XO metadata backup. (${job.id})`,
115
+ data: {
116
+ type: 'xo',
117
+ },
118
+ },
119
+ () =>
120
+ new XoMetadataBackup({
121
+ config,
122
+ job,
123
+ remoteAdapters,
124
+ schedule,
125
+ settings,
126
+ }).run()
127
+ )
128
+ )
129
+ }
130
+ await Promise.all(promises)
131
+ }
132
+ )
133
+ }
134
+ }