@xen-orchestra/backups 0.36.1 → 0.38.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 (40) hide show
  1. package/Backup.js +14 -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/VmsRemote.js +98 -0
  9. package/_runners/VmsXapi.js +138 -0
  10. package/_runners/_Abstract.js +51 -0
  11. package/{_PoolMetadataBackup.js → _runners/_PoolMetadataBackup.js} +3 -3
  12. package/_runners/_RemoteTimeoutError.js +8 -0
  13. package/{_XoMetadataBackup.js → _runners/_XoMetadataBackup.js} +3 -3
  14. package/_runners/_getAdaptersByRemote.js +9 -0
  15. package/_runners/_runTask.js +6 -0
  16. package/_runners/_vmRunners/FullRemote.js +53 -0
  17. package/_runners/_vmRunners/FullXapi.js +65 -0
  18. package/_runners/_vmRunners/IncrementalRemote.js +67 -0
  19. package/_runners/_vmRunners/IncrementalXapi.js +175 -0
  20. package/_runners/_vmRunners/_Abstract.js +95 -0
  21. package/_runners/_vmRunners/_AbstractRemote.js +86 -0
  22. package/_runners/_vmRunners/_AbstractXapi.js +257 -0
  23. package/_runners/_vmRunners/_forkDeltaExport.js +12 -0
  24. package/{writers/FullBackupWriter.js → _runners/_writers/FullRemoteWriter.js} +15 -13
  25. package/{writers/FullReplicationWriter.js → _runners/_writers/FullXapiWriter.js} +8 -7
  26. package/{writers/DeltaBackupWriter.js → _runners/_writers/IncrementalRemoteWriter.js} +28 -22
  27. package/{writers/DeltaReplicationWriter.js → _runners/_writers/IncrementalXapiWriter.js} +19 -16
  28. package/{writers → _runners/_writers}/_AbstractFullWriter.js +2 -2
  29. package/{writers/_AbstractDeltaWriter.js → _runners/_writers/_AbstractIncrementalWriter.js} +3 -3
  30. package/_runners/_writers/_AbstractWriter.js +31 -0
  31. package/{writers/_MixinBackupWriter.js → _runners/_writers/_MixinRemoteWriter.js} +30 -16
  32. package/{writers/_MixinReplicationWriter.js → _runners/_writers/_MixinXapiWriter.js} +9 -13
  33. package/package.json +5 -5
  34. package/_VmBackup.js +0 -515
  35. package/writers/_AbstractWriter.js +0 -14
  36. /package/{_createStreamThrottle.js → _runners/_createStreamThrottle.js} +0 -0
  37. /package/{_forkStreamUnpipe.js → _runners/_forkStreamUnpipe.js} +0 -0
  38. /package/{writers → _runners/_writers}/_checkVhd.js +0 -0
  39. /package/{writers → _runners/_writers}/_listReplicatedVms.js +0 -0
  40. /package/{writers → _runners/_writers}/_packUuid.js +0 -0
@@ -0,0 +1,98 @@
1
+ 'use strict'
2
+
3
+ const { asyncMapSettled } = require('@xen-orchestra/async-map')
4
+ const Disposable = require('promise-toolbox/Disposable')
5
+ const { limitConcurrency } = require('limit-concurrency-decorator')
6
+
7
+ const { extractIdsFromSimplePattern } = require('../extractIdsFromSimplePattern.js')
8
+ const { Task } = require('../Task.js')
9
+ const createStreamThrottle = require('./_createStreamThrottle.js')
10
+ const { DEFAULT_SETTINGS, Abstract } = require('./_Abstract.js')
11
+ const { runTask } = require('./_runTask.js')
12
+ const { getAdaptersByRemote } = require('./_getAdaptersByRemote.js')
13
+ const { FullRemote } = require('./_vmRunners/FullRemote.js')
14
+ const { IncrementalRemote } = require('./_vmRunners/IncrementalRemote.js')
15
+
16
+ const DEFAULT_REMOTE_VM_SETTINGS = {
17
+ concurrency: 2,
18
+ copyRetention: 0,
19
+ deleteFirst: false,
20
+ exportRetention: 0,
21
+ healthCheckSr: undefined,
22
+ healthCheckVmsWithTags: [],
23
+ maxExportRate: 0,
24
+ maxMergedDeltasPerRun: Infinity,
25
+ timeout: 0,
26
+ validateVhdStreams: false,
27
+ vmTimeout: 0,
28
+ }
29
+
30
+ exports.VmsRemote = class RemoteVmsBackupRunner extends Abstract {
31
+ _computeBaseSettings(config, job) {
32
+ const baseSettings = { ...DEFAULT_SETTINGS }
33
+ Object.assign(baseSettings, DEFAULT_REMOTE_VM_SETTINGS, config.defaultSettings, config.vm?.defaultSettings)
34
+ Object.assign(baseSettings, job.settings[''])
35
+ return baseSettings
36
+ }
37
+
38
+ async run() {
39
+ const job = this._job
40
+ const schedule = this._schedule
41
+ const settings = this._settings
42
+
43
+ const throttleStream = createStreamThrottle(settings.maxExportRate)
44
+
45
+ const config = this._config
46
+ await Disposable.use(
47
+ () => this._getAdapter(job.sourceRemote),
48
+ () => (settings.healthCheckSr !== undefined ? this._getRecord('SR', settings.healthCheckSr) : undefined),
49
+ Disposable.all(
50
+ extractIdsFromSimplePattern(job.remotes).map(id => id !== job.sourceRemote && this._getAdapter(id))
51
+ ),
52
+ async ({ adapter: sourceRemoteAdapter }, healthCheckSr, remoteAdapters) => {
53
+ // remove adapters that failed (already handled)
54
+ remoteAdapters = remoteAdapters.filter(_ => !!_)
55
+ if (remoteAdapters.length === 0) {
56
+ return
57
+ }
58
+
59
+ const vmsUuids = await sourceRemoteAdapter.listAllVms()
60
+
61
+ Task.info('vms', { vms: vmsUuids })
62
+
63
+ remoteAdapters = getAdaptersByRemote(remoteAdapters)
64
+ const allSettings = this._job.settings
65
+ const baseSettings = this._baseSettings
66
+
67
+ const handleVm = vmUuid => {
68
+ const taskStart = { name: 'backup VM', data: { type: 'VM', id: vmUuid } }
69
+
70
+ const opts = {
71
+ baseSettings,
72
+ config,
73
+ job,
74
+ healthCheckSr,
75
+ remoteAdapters,
76
+ schedule,
77
+ settings: { ...settings, ...allSettings[vmUuid] },
78
+ sourceRemoteAdapter,
79
+ throttleStream,
80
+ vmUuid,
81
+ }
82
+ let vmBackup
83
+ if (job.mode === 'delta') {
84
+ vmBackup = new IncrementalRemote(opts)
85
+ } else if (job.mode === 'full') {
86
+ vmBackup = new FullRemote(opts)
87
+ } else {
88
+ throw new Error(`Job mode ${job.mode} not implemented for mirror backup`)
89
+ }
90
+
91
+ return runTask(taskStart, () => vmBackup.run())
92
+ }
93
+ const { concurrency } = settings
94
+ await asyncMapSettled(vmsUuids, !concurrency ? handleVm : limitConcurrency(concurrency)(handleVm))
95
+ }
96
+ )
97
+ }
98
+ }
@@ -0,0 +1,138 @@
1
+ 'use strict'
2
+
3
+ const { asyncMapSettled } = require('@xen-orchestra/async-map')
4
+ const Disposable = require('promise-toolbox/Disposable')
5
+ const { limitConcurrency } = require('limit-concurrency-decorator')
6
+
7
+ const { extractIdsFromSimplePattern } = require('../extractIdsFromSimplePattern.js')
8
+ const { Task } = require('../Task.js')
9
+ const createStreamThrottle = require('./_createStreamThrottle.js')
10
+ const { DEFAULT_SETTINGS, Abstract } = require('./_Abstract.js')
11
+ const { runTask } = require('./_runTask.js')
12
+ const { getAdaptersByRemote } = require('./_getAdaptersByRemote.js')
13
+ const { IncrementalXapi } = require('./_vmRunners/IncrementalXapi.js')
14
+ const { FullXapi } = require('./_vmRunners/FullXapi.js')
15
+
16
+ const DEFAULT_XAPI_VM_SETTINGS = {
17
+ bypassVdiChainsCheck: false,
18
+ checkpointSnapshot: false,
19
+ concurrency: 2,
20
+ copyRetention: 0,
21
+ deleteFirst: false,
22
+ exportRetention: 0,
23
+ fullInterval: 0,
24
+ healthCheckSr: undefined,
25
+ healthCheckVmsWithTags: [],
26
+ maxExportRate: 0,
27
+ maxMergedDeltasPerRun: Infinity,
28
+ offlineBackup: false,
29
+ offlineSnapshot: false,
30
+ snapshotRetention: 0,
31
+ timeout: 0,
32
+ useNbd: false,
33
+ unconditionalSnapshot: false,
34
+ validateVhdStreams: false,
35
+ vmTimeout: 0,
36
+ }
37
+
38
+ exports.VmsXapi = class VmsXapiBackupRunner extends Abstract {
39
+ _computeBaseSettings(config, job) {
40
+ const baseSettings = { ...DEFAULT_SETTINGS }
41
+ Object.assign(baseSettings, DEFAULT_XAPI_VM_SETTINGS, config.defaultSettings, config.vm?.defaultSettings)
42
+ Object.assign(baseSettings, job.settings[''])
43
+ return baseSettings
44
+ }
45
+
46
+ async run() {
47
+ const job = this._job
48
+
49
+ // FIXME: proper SimpleIdPattern handling
50
+ const getSnapshotNameLabel = this._getSnapshotNameLabel
51
+ const schedule = this._schedule
52
+ const settings = this._settings
53
+
54
+ const throttleStream = createStreamThrottle(settings.maxExportRate)
55
+
56
+ const config = this._config
57
+ await Disposable.use(
58
+ Disposable.all(
59
+ extractIdsFromSimplePattern(job.srs).map(id =>
60
+ this._getRecord('SR', id).catch(error => {
61
+ runTask(
62
+ {
63
+ name: 'get SR record',
64
+ data: { type: 'SR', id },
65
+ },
66
+ () => Promise.reject(error)
67
+ )
68
+ })
69
+ )
70
+ ),
71
+ Disposable.all(extractIdsFromSimplePattern(job.remotes).map(id => this._getAdapter(id))),
72
+ () => (settings.healthCheckSr !== undefined ? this._getRecord('SR', settings.healthCheckSr) : undefined),
73
+ async (srs, remoteAdapters, healthCheckSr) => {
74
+ // remove adapters that failed (already handled)
75
+ remoteAdapters = remoteAdapters.filter(_ => _ !== undefined)
76
+
77
+ // remove srs that failed (already handled)
78
+ srs = srs.filter(_ => _ !== undefined)
79
+
80
+ if (remoteAdapters.length === 0 && srs.length === 0 && settings.snapshotRetention === 0) {
81
+ return
82
+ }
83
+
84
+ const vmIds = extractIdsFromSimplePattern(job.vms)
85
+
86
+ Task.info('vms', { vms: vmIds })
87
+
88
+ remoteAdapters = getAdaptersByRemote(remoteAdapters)
89
+
90
+ const allSettings = this._job.settings
91
+ const baseSettings = this._baseSettings
92
+
93
+ const handleVm = vmUuid => {
94
+ const taskStart = { name: 'backup VM', data: { type: 'VM', id: vmUuid } }
95
+
96
+ return this._getRecord('VM', vmUuid).then(
97
+ disposableVm =>
98
+ Disposable.use(disposableVm, vm => {
99
+ taskStart.data.name_label = vm.name_label
100
+ return runTask(taskStart, () => {
101
+ const opts = {
102
+ baseSettings,
103
+ config,
104
+ getSnapshotNameLabel,
105
+ healthCheckSr,
106
+ job,
107
+ remoteAdapters,
108
+ schedule,
109
+ settings: { ...settings, ...allSettings[vm.uuid] },
110
+ srs,
111
+ throttleStream,
112
+ vm,
113
+ }
114
+ let vmBackup
115
+ if (job.mode === 'delta') {
116
+ vmBackup = new IncrementalXapi(opts)
117
+ } else {
118
+ if (job.mode === 'full') {
119
+ vmBackup = new FullXapi(opts)
120
+ } else {
121
+ throw new Error(`Job mode ${job.mode} not implemented`)
122
+ }
123
+ }
124
+ return vmBackup.run()
125
+ })
126
+ }),
127
+ error =>
128
+ runTask(taskStart, () => {
129
+ throw error
130
+ })
131
+ )
132
+ }
133
+ const { concurrency } = settings
134
+ await asyncMapSettled(vmIds, concurrency === 0 ? handleVm : limitConcurrency(concurrency)(handleVm))
135
+ }
136
+ )
137
+ }
138
+ }
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const Disposable = require('promise-toolbox/Disposable')
4
+ const pTimeout = require('promise-toolbox/timeout')
5
+ const { compileTemplate } = require('@xen-orchestra/template')
6
+ const { runTask } = require('./_runTask.js')
7
+ const { RemoteTimeoutError } = require('./_RemoteTimeoutError.js')
8
+
9
+ exports.DEFAULT_SETTINGS = {
10
+ getRemoteTimeout: 300e3,
11
+ reportWhen: 'failure',
12
+ }
13
+
14
+ exports.Abstract = class AbstractRunner {
15
+ constructor({ config, getAdapter, getConnectedRecord, job, schedule }) {
16
+ this._config = config
17
+ this._getRecord = getConnectedRecord
18
+ this._job = job
19
+ this._schedule = schedule
20
+
21
+ this._getSnapshotNameLabel = compileTemplate(config.snapshotNameLabelTpl, {
22
+ '{job.name}': job.name,
23
+ '{vm.name_label}': vm => vm.name_label,
24
+ })
25
+
26
+ const baseSettings = this._computeBaseSettings(config, job)
27
+ this._baseSettings = baseSettings
28
+ this._settings = { ...baseSettings, ...job.settings[schedule.id] }
29
+
30
+ const { getRemoteTimeout } = this._settings
31
+ this._getAdapter = async function (remoteId) {
32
+ try {
33
+ const disposable = await pTimeout.call(getAdapter(remoteId), getRemoteTimeout, new RemoteTimeoutError(remoteId))
34
+
35
+ return new Disposable(() => disposable.dispose(), {
36
+ adapter: disposable.value,
37
+ remoteId,
38
+ })
39
+ } catch (error) {
40
+ // See https://github.com/vatesfr/xen-orchestra/commit/6aa6cfba8ec939c0288f0fa740f6dfad98c43cbb
41
+ runTask(
42
+ {
43
+ name: 'get remote adapter',
44
+ data: { type: 'remote', id: remoteId },
45
+ },
46
+ () => Promise.reject(error)
47
+ )
48
+ }
49
+ }
50
+ }
51
+ }
@@ -2,10 +2,10 @@
2
2
 
3
3
  const { asyncMap } = require('@xen-orchestra/async-map')
4
4
 
5
- const { DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')
5
+ const { DIR_XO_POOL_METADATA_BACKUPS } = require('../RemoteAdapter.js')
6
6
  const { forkStreamUnpipe } = require('./_forkStreamUnpipe.js')
7
- const { formatFilenameDate } = require('./_filenameDate.js')
8
- const { Task } = require('./Task.js')
7
+ const { formatFilenameDate } = require('../_filenameDate.js')
8
+ const { Task } = require('../Task.js')
9
9
 
10
10
  const PATH_DB_DUMP = '/pool/xmldbdump'
11
11
  exports.PATH_DB_DUMP = PATH_DB_DUMP
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+ class RemoteTimeoutError extends Error {
3
+ constructor(remoteId) {
4
+ super('timeout while getting the remote ' + remoteId)
5
+ this.remoteId = remoteId
6
+ }
7
+ }
8
+ exports.RemoteTimeoutError = RemoteTimeoutError
@@ -2,9 +2,9 @@
2
2
 
3
3
  const { asyncMap } = require('@xen-orchestra/async-map')
4
4
 
5
- const { DIR_XO_CONFIG_BACKUPS } = require('./RemoteAdapter.js')
6
- const { formatFilenameDate } = require('./_filenameDate.js')
7
- const { Task } = require('./Task.js')
5
+ const { DIR_XO_CONFIG_BACKUPS } = require('../RemoteAdapter.js')
6
+ const { formatFilenameDate } = require('../_filenameDate.js')
7
+ const { Task } = require('../Task.js')
8
8
 
9
9
  exports.XoMetadataBackup = class XoMetadataBackup {
10
10
  constructor({ config, job, remoteAdapters, schedule, settings }) {
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+ const getAdaptersByRemote = adapters => {
3
+ const adaptersByRemote = {}
4
+ adapters.forEach(({ adapter, remoteId }) => {
5
+ adaptersByRemote[remoteId] = adapter
6
+ })
7
+ return adaptersByRemote
8
+ }
9
+ exports.getAdaptersByRemote = getAdaptersByRemote
@@ -0,0 +1,6 @@
1
+ 'use strict'
2
+ const { Task } = require('../Task.js')
3
+ const noop = Function.prototype
4
+ const runTask = (...args) => Task.run(...args).catch(noop) // errors are handled by logs
5
+
6
+ exports.runTask = runTask
@@ -0,0 +1,53 @@
1
+ 'use strict'
2
+
3
+ const { decorateMethodsWith } = require('@vates/decorate-with')
4
+ const { defer } = require('golike-defer')
5
+ const { AbstractRemote } = require('./_AbstractRemote')
6
+ const { FullRemoteWriter } = require('../_writers/FullRemoteWriter')
7
+ const { forkStreamUnpipe } = require('../_forkStreamUnpipe')
8
+ const { watchStreamSize } = require('../../_watchStreamSize')
9
+ const { Task } = require('../../Task')
10
+
11
+ class FullRemoteVmBackupRunner extends AbstractRemote {
12
+ _getRemoteWriter() {
13
+ return FullRemoteWriter
14
+ }
15
+ async _run($defer) {
16
+ const transferList = await this._computeTransferList(({ mode }) => mode === 'full')
17
+
18
+ await this._callWriters(async writer => {
19
+ await writer.beforeBackup()
20
+ $defer(async () => {
21
+ await writer.afterBackup()
22
+ })
23
+ }, 'writer.beforeBackup()')
24
+ if (transferList.length > 0) {
25
+ for (const metadata of transferList) {
26
+ const stream = await this._sourceRemoteAdapter.readFullVmBackup(metadata)
27
+ const sizeContainer = watchStreamSize(stream)
28
+
29
+ // @todo shouldn't transfer backup if it will be deleted by retention policy (higher retention on source than destination)
30
+ await this._callWriters(
31
+ writer =>
32
+ writer.run({
33
+ stream: forkStreamUnpipe(stream),
34
+ timestamp: metadata.timestamp,
35
+ vm: metadata.vm,
36
+ vmSnapshot: metadata.vmSnapshot,
37
+ sizeContainer,
38
+ }),
39
+ 'writer.run()'
40
+ )
41
+ // for healthcheck
42
+ this._tags = metadata.vm.tags
43
+ }
44
+ } else {
45
+ Task.info('No new data to upload for this VM')
46
+ }
47
+ }
48
+ }
49
+
50
+ exports.FullRemote = FullRemoteVmBackupRunner
51
+ decorateMethodsWith(FullRemoteVmBackupRunner, {
52
+ _run: defer,
53
+ })
@@ -0,0 +1,65 @@
1
+ 'use strict'
2
+
3
+ const { createLogger } = require('@xen-orchestra/log')
4
+
5
+ const { forkStreamUnpipe } = require('../_forkStreamUnpipe.js')
6
+ const { FullRemoteWriter } = require('../_writers/FullRemoteWriter.js')
7
+ const { FullXapiWriter } = require('../_writers/FullXapiWriter.js')
8
+ const { watchStreamSize } = require('../../_watchStreamSize.js')
9
+ const { AbstractXapi } = require('./_AbstractXapi.js')
10
+
11
+ const { debug } = createLogger('xo:backups:FullXapiVmBackup')
12
+
13
+ exports.FullXapi = class FullXapiVmBackupRunner extends AbstractXapi {
14
+ _getWriters() {
15
+ return [FullRemoteWriter, FullXapiWriter]
16
+ }
17
+
18
+ _mustDoSnapshot() {
19
+ const vm = this._vm
20
+
21
+ const settings = this._settings
22
+ return (
23
+ settings.unconditionalSnapshot ||
24
+ (!settings.offlineBackup && vm.power_state === 'Running') ||
25
+ settings.snapshotRetention !== 0
26
+ )
27
+ }
28
+ _selectBaseVm() {}
29
+
30
+ async _copy() {
31
+ const { compression } = this.job
32
+ const vm = this._vm
33
+ const exportedVm = this._exportedVm
34
+ const stream = this._throttleStream(
35
+ await this._xapi.VM_export(exportedVm.$ref, {
36
+ compress: Boolean(compression) && (compression === 'native' ? 'gzip' : 'zstd'),
37
+ useSnapshot: false,
38
+ })
39
+ )
40
+ const sizeContainer = watchStreamSize(stream)
41
+
42
+ const timestamp = Date.now()
43
+
44
+ await this._callWriters(
45
+ writer =>
46
+ writer.run({
47
+ sizeContainer,
48
+ stream: forkStreamUnpipe(stream),
49
+ timestamp,
50
+ vm,
51
+ vmSnapshot: exportedVm,
52
+ }),
53
+ 'writer.run()'
54
+ )
55
+
56
+ const { size } = sizeContainer
57
+ const end = Date.now()
58
+ const duration = end - timestamp
59
+ debug('transfer complete', {
60
+ duration,
61
+ speed: duration !== 0 ? (size * 1e3) / 1024 / 1024 / duration : 0,
62
+ size,
63
+ })
64
+ }
65
+ }
@@ -0,0 +1,67 @@
1
+ 'use strict'
2
+ const assert = require('node:assert')
3
+
4
+ const { decorateMethodsWith } = require('@vates/decorate-with')
5
+ const { defer } = require('golike-defer')
6
+ const { mapValues } = require('lodash')
7
+ const { Task } = require('../../Task')
8
+ const { AbstractRemote } = require('./_AbstractRemote')
9
+ const { IncrementalRemoteWriter } = require('../_writers/IncrementalRemoteWriter')
10
+ const { forkDeltaExport } = require('./_forkDeltaExport')
11
+ const isVhdDifferencingDisk = require('vhd-lib/isVhdDifferencingDisk')
12
+ const { asyncEach } = require('@vates/async-each')
13
+
14
+ class IncrementalRemoteVmBackupRunner extends AbstractRemote {
15
+ _getRemoteWriter() {
16
+ return IncrementalRemoteWriter
17
+ }
18
+ async _run($defer) {
19
+ const transferList = await this._computeTransferList(({ mode }) => mode === 'delta')
20
+ await this._callWriters(async writer => {
21
+ await writer.beforeBackup()
22
+ $defer(async () => {
23
+ await writer.afterBackup()
24
+ })
25
+ }, 'writer.beforeBackup()')
26
+
27
+ if (transferList.length > 0) {
28
+ for (const metadata of transferList) {
29
+ assert.strictEqual(metadata.mode, 'delta')
30
+
31
+ await this._callWriters(writer => writer.prepare({ isBase: metadata.isBase }), 'writer.prepare()')
32
+ const incrementalExport = await this._sourceRemoteAdapter.readIncrementalVmBackup(metadata, undefined, {
33
+ useChain: false,
34
+ })
35
+
36
+ const differentialVhds = {}
37
+
38
+ await asyncEach(Object.entries(incrementalExport.streams), async ([key, stream]) => {
39
+ differentialVhds[key] = await isVhdDifferencingDisk(stream)
40
+ })
41
+
42
+ incrementalExport.streams = mapValues(incrementalExport.streams, this._throttleStream)
43
+ await this._callWriters(
44
+ writer =>
45
+ writer.transfer({
46
+ deltaExport: forkDeltaExport(incrementalExport),
47
+ differentialVhds,
48
+ timestamp: metadata.timestamp,
49
+ vm: metadata.vm,
50
+ vmSnapshot: metadata.vmSnapshot,
51
+ }),
52
+ 'writer.transfer()'
53
+ )
54
+ await this._callWriters(writer => writer.cleanup(), 'writer.cleanup()')
55
+ // for healthcheck
56
+ this._tags = metadata.vm.tags
57
+ }
58
+ } else {
59
+ Task.info('No new data to upload for this VM')
60
+ }
61
+ }
62
+ }
63
+
64
+ exports.IncrementalRemote = IncrementalRemoteVmBackupRunner
65
+ decorateMethodsWith(IncrementalRemoteVmBackupRunner, {
66
+ _run: defer,
67
+ })