@xen-orchestra/backups 0.36.1 → 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} +8 -8
  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 +5 -5
  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
@@ -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,61 @@
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
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 stream = this._throttleStream(
33
+ await this._xapi.VM_export(this.exportedVm.$ref, {
34
+ compress: Boolean(compression) && (compression === 'native' ? 'gzip' : 'zstd'),
35
+ useSnapshot: false,
36
+ })
37
+ )
38
+ const sizeContainer = watchStreamSize(stream)
39
+
40
+ const timestamp = Date.now()
41
+
42
+ await this._callWriters(
43
+ writer =>
44
+ writer.run({
45
+ sizeContainer,
46
+ stream: forkStreamUnpipe(stream),
47
+ timestamp,
48
+ }),
49
+ 'writer.run()'
50
+ )
51
+
52
+ const { size } = sizeContainer
53
+ const end = Date.now()
54
+ const duration = end - timestamp
55
+ debug('transfer complete', {
56
+ duration,
57
+ speed: duration !== 0 ? (size * 1e3) / 1024 / 1024 / duration : 0,
58
+ size,
59
+ })
60
+ }
61
+ }
@@ -0,0 +1,163 @@
1
+ 'use strict'
2
+
3
+ const findLast = require('lodash/findLast.js')
4
+ const keyBy = require('lodash/keyBy.js')
5
+ const mapValues = require('lodash/mapValues.js')
6
+ const vhdStreamValidator = require('vhd-lib/vhdStreamValidator.js')
7
+ const { asyncMap } = require('@xen-orchestra/async-map')
8
+ const { createLogger } = require('@xen-orchestra/log')
9
+ const { pipeline } = require('node:stream')
10
+
11
+ const { IncrementalRemoteWriter } = require('../_writers/IncrementalRemoteWriter.js')
12
+ const { IncrementalXapiWriter } = require('../_writers/IncrementalXapiWriter.js')
13
+ const { exportIncrementalVm } = require('../../_incrementalVm.js')
14
+ const { Task } = require('../../Task.js')
15
+ const { watchStreamSize } = require('../../_watchStreamSize.js')
16
+ const { AbstractXapi } = require('./_AbstractXapi.js')
17
+ const { forkDeltaExport } = require('./_forkDeltaExport.js')
18
+
19
+ const { debug } = createLogger('xo:backups:IncrementalXapiVmBackup')
20
+
21
+ const noop = Function.prototype
22
+
23
+ exports.IncrementalXapi = class IncrementalXapiVmBackupRunner extends AbstractXapi {
24
+ _getWriters() {
25
+ return [IncrementalRemoteWriter, IncrementalXapiWriter]
26
+ }
27
+
28
+ _mustDoSnapshot() {
29
+ return true
30
+ }
31
+
32
+ async _copy() {
33
+ const { exportedVm } = this
34
+ const baseVm = this._baseVm
35
+ const fullVdisRequired = this._fullVdisRequired
36
+
37
+ const isFull = fullVdisRequired === undefined || fullVdisRequired.size !== 0
38
+
39
+ await this._callWriters(writer => writer.prepare({ isFull }), 'writer.prepare()')
40
+
41
+ const deltaExport = await exportIncrementalVm(exportedVm, baseVm, {
42
+ fullVdisRequired,
43
+ })
44
+ // since NBD is network based, if one disk use nbd , all the disk use them
45
+ // except the suspended VDI
46
+ if (Object.values(deltaExport.streams).some(({ _nbd }) => _nbd)) {
47
+ Task.info('Transfer data using NBD')
48
+ }
49
+ const sizeContainers = mapValues(deltaExport.streams, stream => watchStreamSize(stream))
50
+
51
+ if (this._settings.validateVhdStreams) {
52
+ deltaExport.streams = mapValues(deltaExport.streams, stream => pipeline(stream, vhdStreamValidator, noop))
53
+ }
54
+
55
+ deltaExport.streams = mapValues(deltaExport.streams, this._throttleStream)
56
+
57
+ const timestamp = Date.now()
58
+
59
+ await this._callWriters(
60
+ writer =>
61
+ writer.transfer({
62
+ deltaExport: forkDeltaExport(deltaExport),
63
+ sizeContainers,
64
+ timestamp,
65
+ }),
66
+ 'writer.transfer()'
67
+ )
68
+
69
+ this._baseVm = exportedVm
70
+
71
+ if (baseVm !== undefined) {
72
+ await exportedVm.update_other_config(
73
+ 'xo:backup:deltaChainLength',
74
+ String(+(baseVm.other_config['xo:backup:deltaChainLength'] ?? 0) + 1)
75
+ )
76
+ }
77
+
78
+ // not the case if offlineBackup
79
+ if (exportedVm.is_a_snapshot) {
80
+ await exportedVm.update_other_config('xo:backup:exported', 'true')
81
+ }
82
+
83
+ const size = Object.values(sizeContainers).reduce((sum, { size }) => sum + size, 0)
84
+ const end = Date.now()
85
+ const duration = end - timestamp
86
+ debug('transfer complete', {
87
+ duration,
88
+ speed: duration !== 0 ? (size * 1e3) / 1024 / 1024 / duration : 0,
89
+ size,
90
+ })
91
+
92
+ await this._callWriters(writer => writer.cleanup(), 'writer.cleanup()')
93
+ }
94
+
95
+ async _selectBaseVm() {
96
+ const xapi = this._xapi
97
+
98
+ let baseVm = findLast(this._jobSnapshots, _ => 'xo:backup:exported' in _.other_config)
99
+ if (baseVm === undefined) {
100
+ debug('no base VM found')
101
+ return
102
+ }
103
+
104
+ const fullInterval = this._settings.fullInterval
105
+ const deltaChainLength = +(baseVm.other_config['xo:backup:deltaChainLength'] ?? 0) + 1
106
+ if (!(fullInterval === 0 || fullInterval > deltaChainLength)) {
107
+ debug('not using base VM becaust fullInterval reached')
108
+ return
109
+ }
110
+
111
+ const srcVdis = keyBy(await xapi.getRecords('VDI', await this.vm.$getDisks()), '$ref')
112
+
113
+ // resolve full record
114
+ baseVm = await xapi.getRecord('VM', baseVm.$ref)
115
+
116
+ const baseUuidToSrcVdi = new Map()
117
+ await asyncMap(await baseVm.$getDisks(), async baseRef => {
118
+ const [baseUuid, snapshotOf] = await Promise.all([
119
+ xapi.getField('VDI', baseRef, 'uuid'),
120
+ xapi.getField('VDI', baseRef, 'snapshot_of'),
121
+ ])
122
+ const srcVdi = srcVdis[snapshotOf]
123
+ if (srcVdi !== undefined) {
124
+ baseUuidToSrcVdi.set(baseUuid, srcVdi)
125
+ } else {
126
+ debug('ignore snapshot VDI because no longer present on VM', {
127
+ vdi: baseUuid,
128
+ })
129
+ }
130
+ })
131
+
132
+ const presentBaseVdis = new Map(baseUuidToSrcVdi)
133
+ await this._callWriters(
134
+ writer => presentBaseVdis.size !== 0 && writer.checkBaseVdis(presentBaseVdis, baseVm),
135
+ 'writer.checkBaseVdis()',
136
+ false
137
+ )
138
+
139
+ if (presentBaseVdis.size === 0) {
140
+ debug('no base VM found')
141
+ return
142
+ }
143
+
144
+ const fullVdisRequired = new Set()
145
+ baseUuidToSrcVdi.forEach((srcVdi, baseUuid) => {
146
+ if (presentBaseVdis.has(baseUuid)) {
147
+ debug('found base VDI', {
148
+ base: baseUuid,
149
+ vdi: srcVdi.uuid,
150
+ })
151
+ } else {
152
+ debug('missing base VDI', {
153
+ base: baseUuid,
154
+ vdi: srcVdi.uuid,
155
+ })
156
+ fullVdisRequired.add(srcVdi.uuid)
157
+ }
158
+ })
159
+
160
+ this._baseVm = baseVm
161
+ this._fullVdisRequired = fullVdisRequired
162
+ }
163
+ }
@@ -0,0 +1,87 @@
1
+ 'use strict'
2
+
3
+ const { asyncMap } = require('@xen-orchestra/async-map')
4
+ const { createLogger } = require('@xen-orchestra/log')
5
+ const { Task } = require('../../Task.js')
6
+
7
+ const { debug, warn } = createLogger('xo:backups:AbstractVmRunner')
8
+
9
+ class AggregateError extends Error {
10
+ constructor(errors, message) {
11
+ super(message)
12
+ this.errors = errors
13
+ }
14
+ }
15
+
16
+ const asyncEach = async (iterable, fn, thisArg = iterable) => {
17
+ for (const item of iterable) {
18
+ await fn.call(thisArg, item)
19
+ }
20
+ }
21
+
22
+ exports.Abstract = class AbstractVmBackupRunner {
23
+ // calls fn for each function, warns of any errors, and throws only if there are no writers left
24
+ async _callWriters(fn, step, parallel = true) {
25
+ const writers = this._writers
26
+ const n = writers.size
27
+ if (n === 0) {
28
+ return
29
+ }
30
+
31
+ async function callWriter(writer) {
32
+ const { name } = writer.constructor
33
+ try {
34
+ debug('writer step starting', { step, writer: name })
35
+ await fn(writer)
36
+ debug('writer step succeeded', { duration: step, writer: name })
37
+ } catch (error) {
38
+ writers.delete(writer)
39
+
40
+ warn('writer step failed', { error, step, writer: name })
41
+
42
+ // these two steps are the only one that are not already in their own sub tasks
43
+ if (step === 'writer.checkBaseVdis()' || step === 'writer.beforeBackup()') {
44
+ Task.warning(
45
+ `the writer ${name} has failed the step ${step} with error ${error.message}. It won't be used anymore in this job execution.`
46
+ )
47
+ }
48
+
49
+ throw error
50
+ }
51
+ }
52
+ if (n === 1) {
53
+ const [writer] = writers
54
+ return callWriter(writer)
55
+ }
56
+
57
+ const errors = []
58
+ await (parallel ? asyncMap : asyncEach)(writers, async function (writer) {
59
+ try {
60
+ await callWriter(writer)
61
+ } catch (error) {
62
+ errors.push(error)
63
+ }
64
+ })
65
+ if (writers.size === 0) {
66
+ throw new AggregateError(errors, 'all targets have failed, step: ' + step)
67
+ }
68
+ }
69
+
70
+ async _healthCheck() {
71
+ const settings = this._settings
72
+
73
+ if (this._healthCheckSr === undefined) {
74
+ return
75
+ }
76
+
77
+ // check if current VM has tags
78
+ const { tags } = this.vm
79
+ const intersect = settings.healthCheckVmsWithTags.some(t => tags.includes(t))
80
+
81
+ if (settings.healthCheckVmsWithTags.length !== 0 && !intersect) {
82
+ return
83
+ }
84
+
85
+ await this._callWriters(writer => writer.healthCheck(this._healthCheckSr), 'writer.healthCheck()')
86
+ }
87
+ }