@xen-orchestra/backups 0.27.0 → 0.27.3
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/RemoteAdapter.js +1 -1
- package/_cleanVm.js +64 -39
- package/merge-worker/cli.js +1 -1
- package/package.json +4 -4
package/RemoteAdapter.js
CHANGED
|
@@ -279,7 +279,7 @@ class RemoteAdapter {
|
|
|
279
279
|
const dirs = new Set(files.map(file => dirname(file)))
|
|
280
280
|
for (const dir of dirs) {
|
|
281
281
|
// don't merge in main process, unused VHDs will be merged in the next backup run
|
|
282
|
-
await this.cleanVm(dir, { remove: true,
|
|
282
|
+
await this.cleanVm(dir, { remove: true, logWarn: warn })
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
const dedupedVmUuid = new Set(metadatas.map(_ => _.vm.uuid))
|
package/_cleanVm.js
CHANGED
|
@@ -13,6 +13,7 @@ const { limitConcurrency } = require('limit-concurrency-decorator')
|
|
|
13
13
|
|
|
14
14
|
const { Task } = require('./Task.js')
|
|
15
15
|
const { Disposable } = require('promise-toolbox')
|
|
16
|
+
const handlerPath = require('@xen-orchestra/fs/path')
|
|
16
17
|
|
|
17
18
|
// checking the size of a vhd directory is costly
|
|
18
19
|
// 1 Http Query per 1000 blocks
|
|
@@ -55,12 +56,17 @@ async function mergeVhdChain(chain, { handler, logInfo, remove, merge }) {
|
|
|
55
56
|
const children = chainCopy
|
|
56
57
|
|
|
57
58
|
if (merge) {
|
|
58
|
-
logInfo(
|
|
59
|
+
logInfo('will merge children into parent', { children, parent })
|
|
59
60
|
|
|
60
61
|
let done, total
|
|
61
62
|
const handle = setInterval(() => {
|
|
62
63
|
if (done !== undefined) {
|
|
63
|
-
logInfo(
|
|
64
|
+
logInfo('merge in progress', {
|
|
65
|
+
done,
|
|
66
|
+
parent,
|
|
67
|
+
progress: Math.round((100 * done) / total),
|
|
68
|
+
total,
|
|
69
|
+
})
|
|
64
70
|
}
|
|
65
71
|
}, 10e3)
|
|
66
72
|
|
|
@@ -81,7 +87,7 @@ async function mergeVhdChain(chain, { handler, logInfo, remove, merge }) {
|
|
|
81
87
|
const noop = Function.prototype
|
|
82
88
|
|
|
83
89
|
const INTERRUPTED_VHDS_REG = /^\.(.+)\.merge.json$/
|
|
84
|
-
const listVhds = async (handler, vmDir) => {
|
|
90
|
+
const listVhds = async (handler, vmDir, logWarn) => {
|
|
85
91
|
const vhds = new Set()
|
|
86
92
|
const aliases = {}
|
|
87
93
|
const interruptedVhds = new Map()
|
|
@@ -101,12 +107,23 @@ const listVhds = async (handler, vmDir) => {
|
|
|
101
107
|
filter: file => isVhdFile(file) || INTERRUPTED_VHDS_REG.test(file),
|
|
102
108
|
})
|
|
103
109
|
aliases[vdiDir] = list.filter(vhd => isVhdAlias(vhd)).map(file => `${vdiDir}/${file}`)
|
|
104
|
-
|
|
110
|
+
|
|
111
|
+
await asyncMap(list, async file => {
|
|
105
112
|
const res = INTERRUPTED_VHDS_REG.exec(file)
|
|
106
113
|
if (res === null) {
|
|
107
114
|
vhds.add(`${vdiDir}/${file}`)
|
|
108
115
|
} else {
|
|
109
|
-
|
|
116
|
+
try {
|
|
117
|
+
const mergeState = JSON.parse(await handler.readFile(file))
|
|
118
|
+
interruptedVhds.set(`${vdiDir}/${res[1]}`, {
|
|
119
|
+
statePath: `${vdiDir}/${file}`,
|
|
120
|
+
chain: mergeState.chain,
|
|
121
|
+
})
|
|
122
|
+
} catch (error) {
|
|
123
|
+
// fall back to a non resuming merge
|
|
124
|
+
vhds.add(`${vdiDir}/${file}`)
|
|
125
|
+
logWarn('failed to read existing merge state', { path: file, error })
|
|
126
|
+
}
|
|
110
127
|
}
|
|
111
128
|
})
|
|
112
129
|
}
|
|
@@ -122,15 +139,15 @@ async function checkAliases(
|
|
|
122
139
|
{ handler, logInfo = noop, logWarn = console.warn, remove = false }
|
|
123
140
|
) {
|
|
124
141
|
const aliasFound = []
|
|
125
|
-
for (const
|
|
126
|
-
const target = await resolveVhdAlias(handler,
|
|
142
|
+
for (const alias of aliasPaths) {
|
|
143
|
+
const target = await resolveVhdAlias(handler, alias)
|
|
127
144
|
|
|
128
145
|
if (!isVhdFile(target)) {
|
|
129
|
-
logWarn('alias references non VHD target', {
|
|
146
|
+
logWarn('alias references non VHD target', { alias, target })
|
|
130
147
|
if (remove) {
|
|
131
|
-
logInfo('removing alias and non VHD target', {
|
|
148
|
+
logInfo('removing alias and non VHD target', { alias, target })
|
|
132
149
|
await handler.unlink(target)
|
|
133
|
-
await handler.unlink(
|
|
150
|
+
await handler.unlink(alias)
|
|
134
151
|
}
|
|
135
152
|
continue
|
|
136
153
|
}
|
|
@@ -143,13 +160,13 @@ async function checkAliases(
|
|
|
143
160
|
// error during dispose should not trigger a deletion
|
|
144
161
|
}
|
|
145
162
|
} catch (error) {
|
|
146
|
-
logWarn('missing or broken alias target', {
|
|
163
|
+
logWarn('missing or broken alias target', { alias, target, error })
|
|
147
164
|
if (remove) {
|
|
148
165
|
try {
|
|
149
|
-
await VhdAbstract.unlink(handler,
|
|
166
|
+
await VhdAbstract.unlink(handler, alias)
|
|
150
167
|
} catch (error) {
|
|
151
168
|
if (error.code !== 'ENOENT') {
|
|
152
|
-
logWarn('error deleting alias target', {
|
|
169
|
+
logWarn('error deleting alias target', { alias, target, error })
|
|
153
170
|
}
|
|
154
171
|
}
|
|
155
172
|
}
|
|
@@ -159,17 +176,17 @@ async function checkAliases(
|
|
|
159
176
|
aliasFound.push(resolve('/', target))
|
|
160
177
|
}
|
|
161
178
|
|
|
162
|
-
const
|
|
179
|
+
const vhds = await handler.list(targetDataRepository, {
|
|
163
180
|
ignoreMissing: true,
|
|
164
181
|
prependDir: true,
|
|
165
182
|
})
|
|
166
183
|
|
|
167
|
-
|
|
168
|
-
if (!aliasFound.includes(
|
|
169
|
-
logWarn('no alias references VHD', {
|
|
184
|
+
await asyncMap(vhds, async path => {
|
|
185
|
+
if (!aliasFound.includes(path)) {
|
|
186
|
+
logWarn('no alias references VHD', { path })
|
|
170
187
|
if (remove) {
|
|
171
|
-
logInfo('deleting
|
|
172
|
-
await VhdAbstract.unlink(handler,
|
|
188
|
+
logInfo('deleting unused VHD', { path })
|
|
189
|
+
await VhdAbstract.unlink(handler, path)
|
|
173
190
|
}
|
|
174
191
|
}
|
|
175
192
|
})
|
|
@@ -192,7 +209,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
192
209
|
const vhdParents = { __proto__: null }
|
|
193
210
|
const vhdChildren = { __proto__: null }
|
|
194
211
|
|
|
195
|
-
const { vhds, interruptedVhds, aliases } = await listVhds(handler, vmDir)
|
|
212
|
+
const { vhds, interruptedVhds, aliases } = await listVhds(handler, vmDir, logWarn)
|
|
196
213
|
|
|
197
214
|
// remove broken VHDs
|
|
198
215
|
await asyncMap(vhds, async path => {
|
|
@@ -225,10 +242,8 @@ exports.cleanVm = async function cleanVm(
|
|
|
225
242
|
logWarn(`should delete ${duplicate._path}`)
|
|
226
243
|
vhds.delete(duplicate._path)
|
|
227
244
|
} else {
|
|
228
|
-
logWarn(
|
|
245
|
+
logWarn('same ids but different content')
|
|
229
246
|
}
|
|
230
|
-
} else {
|
|
231
|
-
logInfo('not duplicate', UUID.stringify(vhd.footer.uuid), path)
|
|
232
247
|
}
|
|
233
248
|
vhdById.set(UUID.stringify(vhdKept.footer.uuid), vhdKept)
|
|
234
249
|
})
|
|
@@ -236,7 +251,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
236
251
|
vhds.delete(path)
|
|
237
252
|
logWarn('VHD check error', { path, error })
|
|
238
253
|
if (error?.code === 'ERR_ASSERTION' && remove) {
|
|
239
|
-
logInfo('deleting broken
|
|
254
|
+
logInfo('deleting broken VHD', { path })
|
|
240
255
|
return VhdAbstract.unlink(handler, path)
|
|
241
256
|
}
|
|
242
257
|
}
|
|
@@ -245,7 +260,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
245
260
|
// remove interrupted merge states for missing VHDs
|
|
246
261
|
for (const interruptedVhd of interruptedVhds.keys()) {
|
|
247
262
|
if (!vhds.has(interruptedVhd)) {
|
|
248
|
-
const statePath = interruptedVhds.get(interruptedVhd)
|
|
263
|
+
const { statePath } = interruptedVhds.get(interruptedVhd)
|
|
249
264
|
interruptedVhds.delete(interruptedVhd)
|
|
250
265
|
|
|
251
266
|
logWarn('orphan merge state', {
|
|
@@ -284,9 +299,9 @@ exports.cleanVm = async function cleanVm(
|
|
|
284
299
|
if (!vhds.has(parent)) {
|
|
285
300
|
vhds.delete(vhdPath)
|
|
286
301
|
|
|
287
|
-
logWarn('parent VHD is missing', { parent, vhdPath })
|
|
302
|
+
logWarn('parent VHD is missing', { parent, child: vhdPath })
|
|
288
303
|
if (remove) {
|
|
289
|
-
logInfo('deleting orphan VHD', { vhdPath })
|
|
304
|
+
logInfo('deleting orphan VHD', { path: vhdPath })
|
|
290
305
|
deletions.push(VhdAbstract.unlink(handler, vhdPath))
|
|
291
306
|
}
|
|
292
307
|
}
|
|
@@ -337,7 +352,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
337
352
|
try {
|
|
338
353
|
metadata = JSON.parse(await handler.readFile(json))
|
|
339
354
|
} catch (error) {
|
|
340
|
-
logWarn('failed to read metadata
|
|
355
|
+
logWarn('failed to read backup metadata', { path: json, error })
|
|
341
356
|
jsons.delete(json)
|
|
342
357
|
return
|
|
343
358
|
}
|
|
@@ -348,9 +363,9 @@ exports.cleanVm = async function cleanVm(
|
|
|
348
363
|
if (xvas.has(linkedXva)) {
|
|
349
364
|
unusedXvas.delete(linkedXva)
|
|
350
365
|
} else {
|
|
351
|
-
logWarn('
|
|
366
|
+
logWarn('the XVA linked to the backup is missing', { backup: json, xva: linkedXva })
|
|
352
367
|
if (remove) {
|
|
353
|
-
logInfo('deleting incomplete backup', { json })
|
|
368
|
+
logInfo('deleting incomplete backup', { path: json })
|
|
354
369
|
jsons.delete(json)
|
|
355
370
|
await handler.unlink(json)
|
|
356
371
|
}
|
|
@@ -371,9 +386,9 @@ exports.cleanVm = async function cleanVm(
|
|
|
371
386
|
vhdsToJSons[path] = json
|
|
372
387
|
})
|
|
373
388
|
} else {
|
|
374
|
-
logWarn('some
|
|
389
|
+
logWarn('some VHDs linked to the backup are missing', { backup: json, missingVhds })
|
|
375
390
|
if (remove) {
|
|
376
|
-
logInfo('deleting incomplete backup', { json })
|
|
391
|
+
logInfo('deleting incomplete backup', { path: json })
|
|
377
392
|
jsons.delete(json)
|
|
378
393
|
await handler.unlink(json)
|
|
379
394
|
}
|
|
@@ -414,9 +429,9 @@ exports.cleanVm = async function cleanVm(
|
|
|
414
429
|
}
|
|
415
430
|
}
|
|
416
431
|
|
|
417
|
-
logWarn('unused VHD', { vhd })
|
|
432
|
+
logWarn('unused VHD', { path: vhd })
|
|
418
433
|
if (remove) {
|
|
419
|
-
logInfo('deleting unused VHD', { vhd })
|
|
434
|
+
logInfo('deleting unused VHD', { path: vhd })
|
|
420
435
|
unusedVhdsDeletion.push(VhdAbstract.unlink(handler, vhd))
|
|
421
436
|
}
|
|
422
437
|
}
|
|
@@ -427,7 +442,13 @@ exports.cleanVm = async function cleanVm(
|
|
|
427
442
|
|
|
428
443
|
// merge interrupted VHDs
|
|
429
444
|
for (const parent of interruptedVhds.keys()) {
|
|
430
|
-
|
|
445
|
+
// before #6349 the chain wasn't in the mergeState
|
|
446
|
+
const { chain, statePath } = interruptedVhds.get(parent)
|
|
447
|
+
if (chain === undefined) {
|
|
448
|
+
vhdChainsToMerge[parent] = [parent, vhdChildren[parent]]
|
|
449
|
+
} else {
|
|
450
|
+
vhdChainsToMerge[parent] = chain.map(vhdPath => handlerPath.resolveFromFile(statePath, vhdPath))
|
|
451
|
+
}
|
|
431
452
|
}
|
|
432
453
|
|
|
433
454
|
Object.values(vhdChainsToMerge).forEach(chain => {
|
|
@@ -442,7 +463,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
442
463
|
await asyncMap(toMerge, async chain => {
|
|
443
464
|
const merged = await limitedMergeVhdChain(chain, { handler, logInfo, logWarn, remove, merge })
|
|
444
465
|
if (merged !== undefined) {
|
|
445
|
-
const metadataPath = vhdsToJSons[chain[
|
|
466
|
+
const metadataPath = vhdsToJSons[chain[chain.length - 1]] // all the chain should have the same metada file
|
|
446
467
|
metadataWithMergedVhd[metadataPath] = true
|
|
447
468
|
}
|
|
448
469
|
})
|
|
@@ -497,11 +518,15 @@ exports.cleanVm = async function cleanVm(
|
|
|
497
518
|
|
|
498
519
|
// don't warn if the size has changed after a merge
|
|
499
520
|
if (!merged && fileSystemSize !== size) {
|
|
500
|
-
logWarn('incorrect size in metadata', {
|
|
521
|
+
logWarn('incorrect backup size in metadata', {
|
|
522
|
+
path: metadataPath,
|
|
523
|
+
actual: size ?? 'none',
|
|
524
|
+
expected: fileSystemSize,
|
|
525
|
+
})
|
|
501
526
|
}
|
|
502
527
|
}
|
|
503
528
|
} catch (error) {
|
|
504
|
-
logWarn('failed to get
|
|
529
|
+
logWarn('failed to get backup size', { backup: metadataPath, error })
|
|
505
530
|
return
|
|
506
531
|
}
|
|
507
532
|
|
|
@@ -511,7 +536,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
511
536
|
try {
|
|
512
537
|
await handler.writeFile(metadataPath, JSON.stringify(metadata), { flags: 'w' })
|
|
513
538
|
} catch (error) {
|
|
514
|
-
logWarn('
|
|
539
|
+
logWarn('failed to update backup size in metadata', { path: metadataPath, error })
|
|
515
540
|
}
|
|
516
541
|
}
|
|
517
542
|
})
|
package/merge-worker/cli.js
CHANGED
|
@@ -64,7 +64,7 @@ const main = Disposable.wrap(async function* main(args) {
|
|
|
64
64
|
try {
|
|
65
65
|
const vmDir = getVmBackupDir(String(await handler.readFile(taskFile)))
|
|
66
66
|
try {
|
|
67
|
-
await adapter.cleanVm(vmDir, { merge: true,
|
|
67
|
+
await adapter.cleanVm(vmDir, { merge: true, logInfo: info, logWarn: warn, remove: true })
|
|
68
68
|
} catch (error) {
|
|
69
69
|
// consider the clean successful if the VM dir is missing
|
|
70
70
|
if (error.code !== 'ENOENT') {
|
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.27.
|
|
11
|
+
"version": "0.27.3",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.6"
|
|
14
14
|
},
|
|
@@ -22,7 +22,7 @@
|
|
|
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": "^
|
|
25
|
+
"@xen-orchestra/fs": "^2.0.0",
|
|
26
26
|
"@xen-orchestra/log": "^0.3.0",
|
|
27
27
|
"@xen-orchestra/template": "^0.1.0",
|
|
28
28
|
"compare-versions": "^4.0.1",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"promise-toolbox": "^0.21.0",
|
|
39
39
|
"proper-lockfile": "^4.1.2",
|
|
40
40
|
"uuid": "^8.3.2",
|
|
41
|
-
"vhd-lib": "^3.3.
|
|
41
|
+
"vhd-lib": "^3.3.4",
|
|
42
42
|
"yazl": "^2.5.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"tmp": "^0.2.1"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@xen-orchestra/xapi": "^1.4.
|
|
49
|
+
"@xen-orchestra/xapi": "^1.4.1"
|
|
50
50
|
},
|
|
51
51
|
"license": "AGPL-3.0-or-later",
|
|
52
52
|
"author": {
|