@xen-orchestra/backups 0.23.0 → 0.26.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/Backup.js +52 -26
- package/HealthCheckVmBackup.js +64 -0
- package/RemoteAdapter.js +104 -50
- package/_VmBackup.js +51 -14
- package/_cleanVm.js +67 -83
- package/{_extractIdsFromSimplePattern.js → extractIdsFromSimplePattern.js} +0 -0
- package/merge-worker/cli.js +2 -0
- package/package.json +5 -4
- package/writers/DeltaBackupWriter.js +35 -2
- package/writers/_AbstractWriter.js +2 -0
- package/writers/_MixinBackupWriter.js +14 -6
- package/gitignore-cleanVm-debug.js +0 -516
package/_cleanVm.js
CHANGED
|
@@ -31,71 +31,48 @@ const computeVhdsSize = (handler, vhdPaths) =>
|
|
|
31
31
|
}
|
|
32
32
|
)
|
|
33
33
|
|
|
34
|
-
// chain is
|
|
34
|
+
// chain is [ ancestor, child1, ..., childn]
|
|
35
|
+
// 1. Create a VhdSynthetic from all children
|
|
36
|
+
// 2. Merge the VhdSynthetic into the ancestor
|
|
37
|
+
// 3. Delete all (now) unused VHDs
|
|
38
|
+
// 4. Rename the ancestor with the merged data to the latest child
|
|
35
39
|
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
40
|
+
// VhdSynthetic
|
|
41
|
+
// |
|
|
42
|
+
// /‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\
|
|
43
|
+
// [ ancestor, child1, ...,child n-1, childn ]
|
|
44
|
+
// | \___________________/ ^
|
|
45
|
+
// | | |
|
|
46
|
+
// | unused VHDs |
|
|
47
|
+
// | |
|
|
48
|
+
// \___________rename_____________/
|
|
49
|
+
|
|
50
|
+
async function mergeVhdChain(chain, { handler, logInfo, remove, merge }) {
|
|
39
51
|
assert(chain.length >= 2)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
const children = chain.slice(0, -1).reverse()
|
|
44
|
-
|
|
45
|
-
chain
|
|
46
|
-
.slice(1)
|
|
47
|
-
.reverse()
|
|
48
|
-
.forEach(parent => {
|
|
49
|
-
onLog(`the parent ${parent} of the child ${child} is unused`)
|
|
50
|
-
})
|
|
52
|
+
const chainCopy = [...chain]
|
|
53
|
+
const parent = chainCopy.pop()
|
|
54
|
+
const children = chainCopy
|
|
51
55
|
|
|
52
56
|
if (merge) {
|
|
53
|
-
|
|
54
|
-
// - make it accept a stream
|
|
55
|
-
// - or create synthetic VHD which is not a stream
|
|
56
|
-
if (children.length !== 1) {
|
|
57
|
-
// TODO: implement merging multiple children
|
|
58
|
-
children.length = 1
|
|
59
|
-
child = children[0]
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
onLog(`merging ${child} into ${parent}`)
|
|
57
|
+
logInfo(`merging children into parent`, { childrenCount: children.length, parent })
|
|
63
58
|
|
|
64
59
|
let done, total
|
|
65
60
|
const handle = setInterval(() => {
|
|
66
61
|
if (done !== undefined) {
|
|
67
|
-
|
|
62
|
+
logInfo(`merging children in progress`, { children, parent, doneCount: done, totalCount: total })
|
|
68
63
|
}
|
|
69
64
|
}, 10e3)
|
|
70
65
|
|
|
71
|
-
const mergedSize = await mergeVhd(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
{
|
|
80
|
-
onProgress({ done: d, total: t }) {
|
|
81
|
-
done = d
|
|
82
|
-
total = t
|
|
83
|
-
},
|
|
84
|
-
}
|
|
85
|
-
)
|
|
66
|
+
const mergedSize = await mergeVhd(handler, parent, handler, children, {
|
|
67
|
+
logInfo,
|
|
68
|
+
onProgress({ done: d, total: t }) {
|
|
69
|
+
done = d
|
|
70
|
+
total = t
|
|
71
|
+
},
|
|
72
|
+
remove,
|
|
73
|
+
})
|
|
86
74
|
|
|
87
75
|
clearInterval(handle)
|
|
88
|
-
await Promise.all([
|
|
89
|
-
VhdAbstract.rename(handler, parent, child),
|
|
90
|
-
asyncMap(children.slice(0, -1), child => {
|
|
91
|
-
onLog(`the VHD ${child} is unused`)
|
|
92
|
-
if (remove) {
|
|
93
|
-
onLog(`deleting unused VHD ${child}`)
|
|
94
|
-
return VhdAbstract.unlink(handler, child)
|
|
95
|
-
}
|
|
96
|
-
}),
|
|
97
|
-
])
|
|
98
|
-
|
|
99
76
|
return mergedSize
|
|
100
77
|
}
|
|
101
78
|
}
|
|
@@ -138,14 +115,19 @@ const listVhds = async (handler, vmDir) => {
|
|
|
138
115
|
return { vhds, interruptedVhds, aliases }
|
|
139
116
|
}
|
|
140
117
|
|
|
141
|
-
async function checkAliases(
|
|
118
|
+
async function checkAliases(
|
|
119
|
+
aliasPaths,
|
|
120
|
+
targetDataRepository,
|
|
121
|
+
{ handler, logInfo = noop, logWarn = console.warn, remove = false }
|
|
122
|
+
) {
|
|
142
123
|
const aliasFound = []
|
|
143
124
|
for (const path of aliasPaths) {
|
|
144
125
|
const target = await resolveVhdAlias(handler, path)
|
|
145
126
|
|
|
146
127
|
if (!isVhdFile(target)) {
|
|
147
|
-
|
|
128
|
+
logWarn('alias references non VHD target', { path, target })
|
|
148
129
|
if (remove) {
|
|
130
|
+
logInfo('removing alias and non VHD target', { path, target })
|
|
149
131
|
await handler.unlink(target)
|
|
150
132
|
await handler.unlink(path)
|
|
151
133
|
}
|
|
@@ -160,13 +142,13 @@ async function checkAliases(aliasPaths, targetDataRepository, { handler, onLog =
|
|
|
160
142
|
// error during dispose should not trigger a deletion
|
|
161
143
|
}
|
|
162
144
|
} catch (error) {
|
|
163
|
-
|
|
145
|
+
logWarn('missing or broken alias target', { target, path, error })
|
|
164
146
|
if (remove) {
|
|
165
147
|
try {
|
|
166
148
|
await VhdAbstract.unlink(handler, path)
|
|
167
|
-
} catch (
|
|
168
|
-
if (
|
|
169
|
-
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (error.code !== 'ENOENT') {
|
|
151
|
+
logWarn('error deleting alias target', { target, path, error })
|
|
170
152
|
}
|
|
171
153
|
}
|
|
172
154
|
}
|
|
@@ -183,20 +165,22 @@ async function checkAliases(aliasPaths, targetDataRepository, { handler, onLog =
|
|
|
183
165
|
|
|
184
166
|
entries.forEach(async entry => {
|
|
185
167
|
if (!aliasFound.includes(entry)) {
|
|
186
|
-
|
|
168
|
+
logWarn('no alias references VHD', { entry })
|
|
187
169
|
if (remove) {
|
|
170
|
+
logInfo('deleting unaliased VHD')
|
|
188
171
|
await VhdAbstract.unlink(handler, entry)
|
|
189
172
|
}
|
|
190
173
|
}
|
|
191
174
|
})
|
|
192
175
|
}
|
|
176
|
+
|
|
193
177
|
exports.checkAliases = checkAliases
|
|
194
178
|
|
|
195
179
|
const defaultMergeLimiter = limitConcurrency(1)
|
|
196
180
|
|
|
197
181
|
exports.cleanVm = async function cleanVm(
|
|
198
182
|
vmDir,
|
|
199
|
-
{ fixMetadata, remove, merge, mergeLimiter = defaultMergeLimiter,
|
|
183
|
+
{ fixMetadata, remove, merge, mergeLimiter = defaultMergeLimiter, logInfo = noop, logWarn = console.warn }
|
|
200
184
|
) {
|
|
201
185
|
const limitedMergeVhdChain = mergeLimiter(mergeVhdChain)
|
|
202
186
|
|
|
@@ -227,9 +211,9 @@ exports.cleanVm = async function cleanVm(
|
|
|
227
211
|
})
|
|
228
212
|
} catch (error) {
|
|
229
213
|
vhds.delete(path)
|
|
230
|
-
|
|
214
|
+
logWarn('VHD check error', { path, error })
|
|
231
215
|
if (error?.code === 'ERR_ASSERTION' && remove) {
|
|
232
|
-
|
|
216
|
+
logInfo('deleting broken path', { path })
|
|
233
217
|
return VhdAbstract.unlink(handler, path)
|
|
234
218
|
}
|
|
235
219
|
}
|
|
@@ -241,12 +225,12 @@ exports.cleanVm = async function cleanVm(
|
|
|
241
225
|
const statePath = interruptedVhds.get(interruptedVhd)
|
|
242
226
|
interruptedVhds.delete(interruptedVhd)
|
|
243
227
|
|
|
244
|
-
|
|
228
|
+
logWarn('orphan merge state', {
|
|
245
229
|
mergeStatePath: statePath,
|
|
246
230
|
missingVhdPath: interruptedVhd,
|
|
247
231
|
})
|
|
248
232
|
if (remove) {
|
|
249
|
-
|
|
233
|
+
logInfo('deleting orphan merge state', { statePath })
|
|
250
234
|
await handler.unlink(statePath)
|
|
251
235
|
}
|
|
252
236
|
}
|
|
@@ -255,7 +239,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
255
239
|
// check if alias are correct
|
|
256
240
|
// check if all vhd in data subfolder have a corresponding alias
|
|
257
241
|
await asyncMap(Object.keys(aliases), async dir => {
|
|
258
|
-
await checkAliases(aliases[dir], `${dir}/data`, { handler,
|
|
242
|
+
await checkAliases(aliases[dir], `${dir}/data`, { handler, logInfo, logWarn, remove })
|
|
259
243
|
})
|
|
260
244
|
|
|
261
245
|
// remove VHDs with missing ancestors
|
|
@@ -277,9 +261,9 @@ exports.cleanVm = async function cleanVm(
|
|
|
277
261
|
if (!vhds.has(parent)) {
|
|
278
262
|
vhds.delete(vhdPath)
|
|
279
263
|
|
|
280
|
-
|
|
264
|
+
logWarn('parent VHD is missing', { parent, vhdPath })
|
|
281
265
|
if (remove) {
|
|
282
|
-
|
|
266
|
+
logInfo('deleting orphan VHD', { vhdPath })
|
|
283
267
|
deletions.push(VhdAbstract.unlink(handler, vhdPath))
|
|
284
268
|
}
|
|
285
269
|
}
|
|
@@ -316,7 +300,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
316
300
|
// check is not good enough to delete the file, the best we can do is report
|
|
317
301
|
// it
|
|
318
302
|
if (!(await this.isValidXva(path))) {
|
|
319
|
-
|
|
303
|
+
logWarn('XVA might be broken', { path })
|
|
320
304
|
}
|
|
321
305
|
})
|
|
322
306
|
|
|
@@ -330,7 +314,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
330
314
|
try {
|
|
331
315
|
metadata = JSON.parse(await handler.readFile(json))
|
|
332
316
|
} catch (error) {
|
|
333
|
-
|
|
317
|
+
logWarn('failed to read metadata file', { json, error })
|
|
334
318
|
jsons.delete(json)
|
|
335
319
|
return
|
|
336
320
|
}
|
|
@@ -341,9 +325,9 @@ exports.cleanVm = async function cleanVm(
|
|
|
341
325
|
if (xvas.has(linkedXva)) {
|
|
342
326
|
unusedXvas.delete(linkedXva)
|
|
343
327
|
} else {
|
|
344
|
-
|
|
328
|
+
logWarn('metadata XVA is missing', { json })
|
|
345
329
|
if (remove) {
|
|
346
|
-
|
|
330
|
+
logInfo('deleting incomplete backup', { json })
|
|
347
331
|
jsons.delete(json)
|
|
348
332
|
await handler.unlink(json)
|
|
349
333
|
}
|
|
@@ -364,9 +348,9 @@ exports.cleanVm = async function cleanVm(
|
|
|
364
348
|
vhdsToJSons[path] = json
|
|
365
349
|
})
|
|
366
350
|
} else {
|
|
367
|
-
|
|
351
|
+
logWarn('some metadata VHDs are missing', { json, missingVhds })
|
|
368
352
|
if (remove) {
|
|
369
|
-
|
|
353
|
+
logInfo('deleting incomplete backup', { json })
|
|
370
354
|
jsons.delete(json)
|
|
371
355
|
await handler.unlink(json)
|
|
372
356
|
}
|
|
@@ -407,9 +391,9 @@ exports.cleanVm = async function cleanVm(
|
|
|
407
391
|
}
|
|
408
392
|
}
|
|
409
393
|
|
|
410
|
-
|
|
394
|
+
logWarn('unused VHD', { vhd })
|
|
411
395
|
if (remove) {
|
|
412
|
-
|
|
396
|
+
logInfo('deleting unused VHD', { vhd })
|
|
413
397
|
unusedVhdsDeletion.push(VhdAbstract.unlink(handler, vhd))
|
|
414
398
|
}
|
|
415
399
|
}
|
|
@@ -433,7 +417,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
433
417
|
const metadataWithMergedVhd = {}
|
|
434
418
|
const doMerge = async () => {
|
|
435
419
|
await asyncMap(toMerge, async chain => {
|
|
436
|
-
const merged = await limitedMergeVhdChain(chain, { handler,
|
|
420
|
+
const merged = await limitedMergeVhdChain(chain, { handler, logInfo, logWarn, remove, merge })
|
|
437
421
|
if (merged !== undefined) {
|
|
438
422
|
const metadataPath = vhdsToJSons[chain[0]] // all the chain should have the same metada file
|
|
439
423
|
metadataWithMergedVhd[metadataPath] = true
|
|
@@ -445,18 +429,18 @@ exports.cleanVm = async function cleanVm(
|
|
|
445
429
|
...unusedVhdsDeletion,
|
|
446
430
|
toMerge.length !== 0 && (merge ? Task.run({ name: 'merge' }, doMerge) : doMerge()),
|
|
447
431
|
asyncMap(unusedXvas, path => {
|
|
448
|
-
|
|
432
|
+
logWarn('unused XVA', { path })
|
|
449
433
|
if (remove) {
|
|
450
|
-
|
|
434
|
+
logInfo('deleting unused XVA', { path })
|
|
451
435
|
return handler.unlink(path)
|
|
452
436
|
}
|
|
453
437
|
}),
|
|
454
438
|
asyncMap(xvaSums, path => {
|
|
455
439
|
// no need to handle checksums for XVAs deleted by the script, they will be handled by `unlink()`
|
|
456
440
|
if (!xvas.has(path.slice(0, -'.checksum'.length))) {
|
|
457
|
-
|
|
441
|
+
logInfo('unused XVA checksum', { path })
|
|
458
442
|
if (remove) {
|
|
459
|
-
|
|
443
|
+
logInfo('deleting unused XVA checksum', { path })
|
|
460
444
|
return handler.unlink(path)
|
|
461
445
|
}
|
|
462
446
|
}
|
|
@@ -490,11 +474,11 @@ exports.cleanVm = async function cleanVm(
|
|
|
490
474
|
|
|
491
475
|
// don't warn if the size has changed after a merge
|
|
492
476
|
if (!merged && fileSystemSize !== size) {
|
|
493
|
-
|
|
477
|
+
logWarn('incorrect size in metadata', { size: size ?? 'none', fileSystemSize })
|
|
494
478
|
}
|
|
495
479
|
}
|
|
496
480
|
} catch (error) {
|
|
497
|
-
|
|
481
|
+
logWarn('failed to get metadata size', { metadataPath, error })
|
|
498
482
|
return
|
|
499
483
|
}
|
|
500
484
|
|
|
@@ -504,7 +488,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
504
488
|
try {
|
|
505
489
|
await handler.writeFile(metadataPath, JSON.stringify(metadata), { flags: 'w' })
|
|
506
490
|
} catch (error) {
|
|
507
|
-
|
|
491
|
+
logWarn('metadata size update failed', { metadataPath, error })
|
|
508
492
|
}
|
|
509
493
|
}
|
|
510
494
|
})
|
|
File without changes
|
package/merge-worker/cli.js
CHANGED
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.26.0",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.6"
|
|
14
14
|
},
|
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
"@vates/disposable": "^0.1.1",
|
|
23
23
|
"@vates/parse-duration": "^0.1.1",
|
|
24
24
|
"@xen-orchestra/async-map": "^0.1.2",
|
|
25
|
-
"@xen-orchestra/fs": "^1.0.
|
|
25
|
+
"@xen-orchestra/fs": "^1.0.3",
|
|
26
26
|
"@xen-orchestra/log": "^0.3.0",
|
|
27
27
|
"@xen-orchestra/template": "^0.1.0",
|
|
28
28
|
"compare-versions": "^4.0.1",
|
|
29
29
|
"d3-time-format": "^3.0.0",
|
|
30
|
+
"decorator-synchronized": "^0.6.0",
|
|
30
31
|
"end-of-stream": "^1.4.4",
|
|
31
32
|
"fs-extra": "^10.0.0",
|
|
32
33
|
"golike-defer": "^0.5.1",
|
|
@@ -37,7 +38,7 @@
|
|
|
37
38
|
"promise-toolbox": "^0.21.0",
|
|
38
39
|
"proper-lockfile": "^4.1.2",
|
|
39
40
|
"uuid": "^8.3.2",
|
|
40
|
-
"vhd-lib": "^3.
|
|
41
|
+
"vhd-lib": "^3.2.0",
|
|
41
42
|
"yazl": "^2.5.1"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
@@ -45,7 +46,7 @@
|
|
|
45
46
|
"tmp": "^0.2.1"
|
|
46
47
|
},
|
|
47
48
|
"peerDependencies": {
|
|
48
|
-
"@xen-orchestra/xapi": "^1.
|
|
49
|
+
"@xen-orchestra/xapi": "^1.2.0"
|
|
49
50
|
},
|
|
50
51
|
"license": "AGPL-3.0-or-later",
|
|
51
52
|
"author": {
|
|
@@ -19,6 +19,8 @@ const { AbstractDeltaWriter } = require('./_AbstractDeltaWriter.js')
|
|
|
19
19
|
const { checkVhd } = require('./_checkVhd.js')
|
|
20
20
|
const { packUuid } = require('./_packUuid.js')
|
|
21
21
|
const { Disposable } = require('promise-toolbox')
|
|
22
|
+
const { HealthCheckVmBackup } = require('../HealthCheckVmBackup.js')
|
|
23
|
+
const { ImportVmBackup } = require('../ImportVmBackup.js')
|
|
22
24
|
|
|
23
25
|
const { warn } = createLogger('xo:backups:DeltaBackupWriter')
|
|
24
26
|
|
|
@@ -69,6 +71,35 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
|
|
|
69
71
|
return this._cleanVm({ merge: true })
|
|
70
72
|
}
|
|
71
73
|
|
|
74
|
+
healthCheck(sr) {
|
|
75
|
+
return Task.run(
|
|
76
|
+
{
|
|
77
|
+
name: 'health check',
|
|
78
|
+
},
|
|
79
|
+
async () => {
|
|
80
|
+
const xapi = sr.$xapi
|
|
81
|
+
const srUuid = sr.uuid
|
|
82
|
+
const adapter = this._adapter
|
|
83
|
+
const metadata = await adapter.readVmBackupMetadata(this._metadataFileName)
|
|
84
|
+
const { id: restoredId } = await new ImportVmBackup({
|
|
85
|
+
adapter,
|
|
86
|
+
metadata,
|
|
87
|
+
srUuid,
|
|
88
|
+
xapi,
|
|
89
|
+
}).run()
|
|
90
|
+
const restoredVm = xapi.getObject(restoredId)
|
|
91
|
+
try {
|
|
92
|
+
await new HealthCheckVmBackup({
|
|
93
|
+
restoredVm,
|
|
94
|
+
xapi,
|
|
95
|
+
}).run()
|
|
96
|
+
} finally {
|
|
97
|
+
await xapi.VM_destroy(restoredVm.$ref)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
72
103
|
prepare({ isFull }) {
|
|
73
104
|
// create the task related to this export and ensure all methods are called in this context
|
|
74
105
|
const task = new Task({
|
|
@@ -80,7 +111,9 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
|
|
|
80
111
|
},
|
|
81
112
|
})
|
|
82
113
|
this.transfer = task.wrapFn(this.transfer)
|
|
83
|
-
this.
|
|
114
|
+
this.healthCheck = task.wrapFn(this.healthCheck)
|
|
115
|
+
this.cleanup = task.wrapFn(this.cleanup)
|
|
116
|
+
this.afterBackup = task.wrapFn(this.afterBackup, true)
|
|
84
117
|
|
|
85
118
|
return task.run(() => this._prepare())
|
|
86
119
|
}
|
|
@@ -156,7 +189,7 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
|
|
|
156
189
|
}/${adapter.getVhdFileName(basename)}`
|
|
157
190
|
)
|
|
158
191
|
|
|
159
|
-
const metadataFilename = `${backupDir}/${basename}.json`
|
|
192
|
+
const metadataFilename = (this._metadataFileName = `${backupDir}/${basename}.json`)
|
|
160
193
|
const metadataContent = {
|
|
161
194
|
jobId,
|
|
162
195
|
mode: job.mode,
|
|
@@ -6,8 +6,9 @@ const { join } = require('path')
|
|
|
6
6
|
const { getVmBackupDir } = require('../_getVmBackupDir.js')
|
|
7
7
|
const MergeWorker = require('../merge-worker/index.js')
|
|
8
8
|
const { formatFilenameDate } = require('../_filenameDate.js')
|
|
9
|
+
const { Task } = require('../Task.js')
|
|
9
10
|
|
|
10
|
-
const { warn } = createLogger('xo:backups:MixinBackupWriter')
|
|
11
|
+
const { info, warn } = createLogger('xo:backups:MixinBackupWriter')
|
|
11
12
|
|
|
12
13
|
exports.MixinBackupWriter = (BaseClass = Object) =>
|
|
13
14
|
class MixinBackupWriter extends BaseClass {
|
|
@@ -25,11 +26,17 @@ exports.MixinBackupWriter = (BaseClass = Object) =>
|
|
|
25
26
|
|
|
26
27
|
async _cleanVm(options) {
|
|
27
28
|
try {
|
|
28
|
-
return await
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
return await Task.run({ name: 'clean-vm' }, () => {
|
|
30
|
+
return this._adapter.cleanVm(this.#vmBackupDir, {
|
|
31
|
+
...options,
|
|
32
|
+
fixMetadata: true,
|
|
33
|
+
logInfo: info,
|
|
34
|
+
logWarn: (message, data) => {
|
|
35
|
+
warn(message, data)
|
|
36
|
+
Task.warning(message, data)
|
|
37
|
+
},
|
|
38
|
+
lock: false,
|
|
39
|
+
})
|
|
33
40
|
})
|
|
34
41
|
} catch (error) {
|
|
35
42
|
warn(error)
|
|
@@ -64,5 +71,6 @@ exports.MixinBackupWriter = (BaseClass = Object) =>
|
|
|
64
71
|
const remotePath = handler._getRealPath()
|
|
65
72
|
await MergeWorker.run(remotePath)
|
|
66
73
|
}
|
|
74
|
+
await this._adapter.invalidateVmBackupListCache(this._backup.vm.uuid)
|
|
67
75
|
}
|
|
68
76
|
}
|