@xen-orchestra/backups 0.46.0 → 0.47.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.
- package/_backupWorker.mjs +26 -1
- package/_runners/_vmRunners/FullRemote.mjs +1 -13
- package/_runners/_vmRunners/IncrementalRemote.mjs +1 -12
- package/_runners/_vmRunners/_AbstractRemote.mjs +13 -1
- package/_runners/_writers/_MixinRemoteWriter.mjs +6 -1
- package/_runners/_writers/_MixinXapiWriter.mjs +16 -5
- package/package.json +4 -2
package/_backupWorker.mjs
CHANGED
|
@@ -2,7 +2,10 @@ import { createLogger } from '@xen-orchestra/log'
|
|
|
2
2
|
import { catchGlobalErrors } from '@xen-orchestra/log/configure'
|
|
3
3
|
|
|
4
4
|
import Disposable from 'promise-toolbox/Disposable'
|
|
5
|
+
import humanFormat from 'human-format'
|
|
5
6
|
import ignoreErrors from 'promise-toolbox/ignoreErrors'
|
|
7
|
+
import mapValues from 'lodash/mapValues.js'
|
|
8
|
+
import ms from 'ms'
|
|
6
9
|
import { compose } from '@vates/compose'
|
|
7
10
|
import { createCachedLookup } from '@vates/cached-dns.lookup'
|
|
8
11
|
import { createDebounceResource } from '@vates/disposable/debounceResource.js'
|
|
@@ -20,7 +23,7 @@ createCachedLookup().patchGlobal()
|
|
|
20
23
|
|
|
21
24
|
const logger = createLogger('xo:backups:worker')
|
|
22
25
|
catchGlobalErrors(logger)
|
|
23
|
-
const { debug } = logger
|
|
26
|
+
const { debug, info } = logger
|
|
24
27
|
|
|
25
28
|
class BackupWorker {
|
|
26
29
|
#config
|
|
@@ -149,6 +152,27 @@ process.on('message', async message => {
|
|
|
149
152
|
debug('message received', { message })
|
|
150
153
|
|
|
151
154
|
if (message.action === 'run') {
|
|
155
|
+
const resourceStart = process.resourceUsage()
|
|
156
|
+
const timeStart = process.hrtime.bigint()
|
|
157
|
+
info('starting backup')
|
|
158
|
+
|
|
159
|
+
process.on('exit', exitCode => {
|
|
160
|
+
const resourceUsage = mapValues(process.resourceUsage(), (end, key) => end - resourceStart[key])
|
|
161
|
+
const cpuTotal = resourceUsage.userCPUTime + resourceUsage.systemCPUTime
|
|
162
|
+
const duration = Number((process.hrtime.bigint() - timeStart) / 1000n) // in μs
|
|
163
|
+
|
|
164
|
+
info('process will exit', {
|
|
165
|
+
duration,
|
|
166
|
+
exitCode,
|
|
167
|
+
resourceUsage,
|
|
168
|
+
summary: {
|
|
169
|
+
duration: ms(duration / 1000),
|
|
170
|
+
cpuUsage: Math.round((100 * cpuTotal) / duration) + '%',
|
|
171
|
+
memoryUsage: humanFormat.bytes(resourceUsage.maxRSS * 1024),
|
|
172
|
+
},
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
152
176
|
const backupWorker = new BackupWorker(message.data)
|
|
153
177
|
try {
|
|
154
178
|
const result = message.runWithLogs
|
|
@@ -177,6 +201,7 @@ process.on('message', async message => {
|
|
|
177
201
|
status: 'failure',
|
|
178
202
|
})
|
|
179
203
|
} finally {
|
|
204
|
+
info('backup has ended')
|
|
180
205
|
await ignoreErrors.call(backupWorker.debounceResource.flushAll())
|
|
181
206
|
process.disconnect()
|
|
182
207
|
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { decorateMethodsWith } from '@vates/decorate-with'
|
|
2
|
-
import { defer } from 'golike-defer'
|
|
3
1
|
import { AbstractRemote } from './_AbstractRemote.mjs'
|
|
4
2
|
import { FullRemoteWriter } from '../_writers/FullRemoteWriter.mjs'
|
|
5
3
|
import { forkStreamUnpipe } from '../_forkStreamUnpipe.mjs'
|
|
@@ -10,15 +8,9 @@ export const FullRemote = class FullRemoteVmBackupRunner extends AbstractRemote
|
|
|
10
8
|
_getRemoteWriter() {
|
|
11
9
|
return FullRemoteWriter
|
|
12
10
|
}
|
|
13
|
-
async _run(
|
|
11
|
+
async _run() {
|
|
14
12
|
const transferList = await this._computeTransferList(({ mode }) => mode === 'full')
|
|
15
13
|
|
|
16
|
-
await this._callWriters(async writer => {
|
|
17
|
-
await writer.beforeBackup()
|
|
18
|
-
$defer(async () => {
|
|
19
|
-
await writer.afterBackup()
|
|
20
|
-
})
|
|
21
|
-
}, 'writer.beforeBackup()')
|
|
22
14
|
if (transferList.length > 0) {
|
|
23
15
|
for (const metadata of transferList) {
|
|
24
16
|
const stream = await this._sourceRemoteAdapter.readFullVmBackup(metadata)
|
|
@@ -46,7 +38,3 @@ export const FullRemote = class FullRemoteVmBackupRunner extends AbstractRemote
|
|
|
46
38
|
}
|
|
47
39
|
}
|
|
48
40
|
}
|
|
49
|
-
|
|
50
|
-
decorateMethodsWith(FullRemote, {
|
|
51
|
-
_run: defer,
|
|
52
|
-
})
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { asyncEach } from '@vates/async-each'
|
|
2
|
-
import { decorateMethodsWith } from '@vates/decorate-with'
|
|
3
|
-
import { defer } from 'golike-defer'
|
|
4
2
|
import assert from 'node:assert'
|
|
5
3
|
import * as UUID from 'uuid'
|
|
6
4
|
import isVhdDifferencingDisk from 'vhd-lib/isVhdDifferencingDisk.js'
|
|
@@ -52,14 +50,8 @@ class IncrementalRemoteVmBackupRunner extends AbstractRemote {
|
|
|
52
50
|
})
|
|
53
51
|
// yeah , let's go
|
|
54
52
|
}
|
|
55
|
-
async _run(
|
|
53
|
+
async _run() {
|
|
56
54
|
const transferList = await this._computeTransferList(({ mode }) => mode === 'delta')
|
|
57
|
-
await this._callWriters(async writer => {
|
|
58
|
-
await writer.beforeBackup()
|
|
59
|
-
$defer(async () => {
|
|
60
|
-
await writer.afterBackup()
|
|
61
|
-
})
|
|
62
|
-
}, 'writer.beforeBackup()')
|
|
63
55
|
|
|
64
56
|
if (transferList.length > 0) {
|
|
65
57
|
for (const metadata of transferList) {
|
|
@@ -110,6 +102,3 @@ class IncrementalRemoteVmBackupRunner extends AbstractRemote {
|
|
|
110
102
|
}
|
|
111
103
|
|
|
112
104
|
export const IncrementalRemote = IncrementalRemoteVmBackupRunner
|
|
113
|
-
decorateMethodsWith(IncrementalRemoteVmBackupRunner, {
|
|
114
|
-
_run: defer,
|
|
115
|
-
})
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { asyncEach } from '@vates/async-each'
|
|
2
|
+
import { decorateMethodsWith } from '@vates/decorate-with'
|
|
3
|
+
import { defer } from 'golike-defer'
|
|
2
4
|
import { Disposable } from 'promise-toolbox'
|
|
3
5
|
|
|
4
6
|
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
|
|
@@ -90,11 +92,21 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
|
|
90
92
|
return chain
|
|
91
93
|
}
|
|
92
94
|
|
|
93
|
-
async run() {
|
|
95
|
+
async run($defer) {
|
|
94
96
|
const handler = this._sourceRemoteAdapter._handler
|
|
95
97
|
await Disposable.use(await handler.lock(getVmBackupDir(this._vmUuid)), async () => {
|
|
98
|
+
await this._callWriters(async writer => {
|
|
99
|
+
await writer.beforeBackup()
|
|
100
|
+
$defer(async () => {
|
|
101
|
+
await writer.afterBackup()
|
|
102
|
+
})
|
|
103
|
+
}, 'writer.beforeBackup()')
|
|
96
104
|
await this._run()
|
|
97
105
|
await this._healthCheck()
|
|
98
106
|
})
|
|
99
107
|
}
|
|
100
108
|
}
|
|
109
|
+
|
|
110
|
+
decorateMethodsWith(AbstractRemote, {
|
|
111
|
+
run: defer,
|
|
112
|
+
})
|
|
@@ -100,7 +100,12 @@ export const MixinRemoteWriter = (BaseClass = Object) =>
|
|
|
100
100
|
additionnalVmTag: 'xo:no-bak=Health Check',
|
|
101
101
|
},
|
|
102
102
|
}).run()
|
|
103
|
-
|
|
103
|
+
let restoredVm
|
|
104
|
+
try {
|
|
105
|
+
restoredVm = xapi.getObject(restoredId)
|
|
106
|
+
} catch (err) {
|
|
107
|
+
restoredVm = await xapi.waitObject(restoredId)
|
|
108
|
+
}
|
|
104
109
|
try {
|
|
105
110
|
await new HealthCheckVmBackup({
|
|
106
111
|
restoredVm,
|
|
@@ -26,7 +26,8 @@ export const MixinXapiWriter = (BaseClass = Object) =>
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
healthCheck() {
|
|
29
|
-
|
|
29
|
+
// the SR that the VM has been replicated on
|
|
30
|
+
const sr = this._sr
|
|
30
31
|
assert.notStrictEqual(sr, undefined, 'SR should be defined before making a health check')
|
|
31
32
|
assert.notEqual(this._targetVmRef, undefined, 'A vm should have been transfered to be health checked')
|
|
32
33
|
// copy VM
|
|
@@ -38,8 +39,12 @@ export const MixinXapiWriter = (BaseClass = Object) =>
|
|
|
38
39
|
const { $xapi: xapi } = sr
|
|
39
40
|
let healthCheckVmRef
|
|
40
41
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
let baseVm
|
|
43
|
+
try {
|
|
44
|
+
baseVm = xapi.getObject(this._targetVmRef)
|
|
45
|
+
} catch (err) {
|
|
46
|
+
baseVm = await xapi.waitObject(this._targetVmRef)
|
|
47
|
+
}
|
|
43
48
|
if (await this.#isAlreadyOnHealthCheckSr(baseVm)) {
|
|
44
49
|
healthCheckVmRef = await Task.run(
|
|
45
50
|
{ name: 'cloning-vm' },
|
|
@@ -53,11 +58,17 @@ export const MixinXapiWriter = (BaseClass = Object) =>
|
|
|
53
58
|
{ name: 'copying-vm' },
|
|
54
59
|
async () =>
|
|
55
60
|
await xapi
|
|
56
|
-
.callAsync(
|
|
61
|
+
.callAsync(
|
|
62
|
+
'VM.copy',
|
|
63
|
+
this._targetVmRef,
|
|
64
|
+
`Health Check - ${baseVm.name_label}`,
|
|
65
|
+
this._healthCheckSr.$ref
|
|
66
|
+
)
|
|
57
67
|
.then(extractOpaqueRef)
|
|
58
68
|
)
|
|
59
69
|
}
|
|
60
|
-
const healthCheckVm =
|
|
70
|
+
const healthCheckVm =
|
|
71
|
+
xapi.getObject(healthCheckVmRef, undefined) ?? (await xapi.waitObject(healthCheckVmRef))
|
|
61
72
|
await healthCheckVm.add_tags('xo:no-bak=Health Check')
|
|
62
73
|
await new HealthCheckVmBackup({
|
|
63
74
|
restoredVm: healthCheckVm,
|
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.
|
|
11
|
+
"version": "0.47.0",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.18"
|
|
14
14
|
},
|
|
@@ -36,15 +36,17 @@
|
|
|
36
36
|
"d3-time-format": "^4.1.0",
|
|
37
37
|
"decorator-synchronized": "^0.6.0",
|
|
38
38
|
"golike-defer": "^0.5.1",
|
|
39
|
+
"human-format": "^1.2.0",
|
|
39
40
|
"limit-concurrency-decorator": "^0.5.0",
|
|
40
41
|
"lodash": "^4.17.20",
|
|
42
|
+
"ms": "^2.1.3",
|
|
41
43
|
"node-zone": "^0.4.0",
|
|
42
44
|
"parse-pairs": "^2.0.0",
|
|
43
45
|
"promise-toolbox": "^0.21.0",
|
|
44
46
|
"proper-lockfile": "^4.1.2",
|
|
45
47
|
"tar": "^6.1.15",
|
|
46
48
|
"uuid": "^9.0.0",
|
|
47
|
-
"vhd-lib": "^4.9.
|
|
49
|
+
"vhd-lib": "^4.9.2",
|
|
48
50
|
"xen-api": "^3.0.0",
|
|
49
51
|
"yazl": "^2.5.1"
|
|
50
52
|
},
|