@xen-orchestra/backups 0.14.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 +263 -0
- package/DurablePartition.js +40 -0
- package/ImportVmBackup.js +66 -0
- package/README.md +28 -0
- package/RemoteAdapter.js +552 -0
- package/RestoreMetadataBackup.js +24 -0
- package/Task.js +151 -0
- package/_PoolMetadataBackup.js +75 -0
- package/_VmBackup.js +409 -0
- package/_XoMetadataBackup.js +62 -0
- package/_backupType.js +4 -0
- package/_backupWorker.js +155 -0
- package/_cancelableMap.js +20 -0
- package/_cleanVm.js +378 -0
- package/_deltaVm.js +347 -0
- package/_extractIdsFromSimplePattern.js +29 -0
- package/_filenameDate.js +6 -0
- package/_forkStreamUnpipe.js +28 -0
- package/_getOldEntries.js +4 -0
- package/_getTmpDir.js +20 -0
- package/_getVmBackupDir.js +6 -0
- package/_isValidXva.js +60 -0
- package/_listPartitions.js +52 -0
- package/_lvm.js +31 -0
- package/_watchStreamSize.js +7 -0
- package/formatVmBackups.js +34 -0
- package/merge-worker/cli.js +69 -0
- package/merge-worker/index.js +25 -0
- package/package.json +49 -0
- package/parseMetadataBackupId.js +23 -0
- package/runBackupWorker.js +38 -0
- package/writers/DeltaBackupWriter.js +221 -0
- package/writers/DeltaReplicationWriter.js +126 -0
- package/writers/FullBackupWriter.js +85 -0
- package/writers/FullReplicationWriter.js +88 -0
- package/writers/_AbstractDeltaWriter.js +26 -0
- package/writers/_AbstractFullWriter.js +12 -0
- package/writers/_AbstractWriter.js +10 -0
- package/writers/_MixinBackupWriter.js +51 -0
- package/writers/_MixinReplicationWriter.js +8 -0
- package/writers/_checkVhd.js +5 -0
- package/writers/_listReplicatedVms.js +30 -0
- package/writers/_packUuid.js +5 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
|
|
2
|
+
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
|
|
3
|
+
const { formatDateTime } = require('@xen-orchestra/xapi')
|
|
4
|
+
|
|
5
|
+
const { formatFilenameDate } = require('../_filenameDate.js')
|
|
6
|
+
const { getOldEntries } = require('../_getOldEntries.js')
|
|
7
|
+
const { Task } = require('../Task.js')
|
|
8
|
+
|
|
9
|
+
const { AbstractFullWriter } = require('./_AbstractFullWriter.js')
|
|
10
|
+
const { MixinReplicationWriter } = require('./_MixinReplicationWriter.js')
|
|
11
|
+
const { listReplicatedVms } = require('./_listReplicatedVms.js')
|
|
12
|
+
|
|
13
|
+
exports.FullReplicationWriter = class FullReplicationWriter extends MixinReplicationWriter(AbstractFullWriter) {
|
|
14
|
+
constructor(props) {
|
|
15
|
+
super(props)
|
|
16
|
+
|
|
17
|
+
this.run = Task.wrapFn(
|
|
18
|
+
{
|
|
19
|
+
name: 'export',
|
|
20
|
+
data: {
|
|
21
|
+
id: props.sr.uuid,
|
|
22
|
+
type: 'SR',
|
|
23
|
+
|
|
24
|
+
// necessary?
|
|
25
|
+
isFull: true,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
this.run
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async _run({ timestamp, sizeContainer, stream }) {
|
|
33
|
+
const sr = this._sr
|
|
34
|
+
const settings = this._settings
|
|
35
|
+
const { job, scheduleId, vm } = this._backup
|
|
36
|
+
|
|
37
|
+
const { uuid: srUuid, $xapi: xapi } = sr
|
|
38
|
+
|
|
39
|
+
// delete previous interrupted copies
|
|
40
|
+
ignoreErrors.call(
|
|
41
|
+
asyncMapSettled(listReplicatedVms(xapi, scheduleId, undefined, vm.uuid), vm => xapi.VM_destroy(vm.$ref))
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const oldVms = getOldEntries(settings.copyRetention - 1, listReplicatedVms(xapi, scheduleId, srUuid, vm.uuid))
|
|
45
|
+
|
|
46
|
+
const deleteOldBackups = () => asyncMapSettled(oldVms, vm => xapi.VM_destroy(vm.$ref))
|
|
47
|
+
const { deleteFirst } = settings
|
|
48
|
+
if (deleteFirst) {
|
|
49
|
+
await deleteOldBackups()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let targetVmRef
|
|
53
|
+
await Task.run({ name: 'transfer' }, async () => {
|
|
54
|
+
targetVmRef = await xapi.VM_import(stream, sr.$ref, vm =>
|
|
55
|
+
Promise.all([
|
|
56
|
+
vm.add_tags('Disaster Recovery'),
|
|
57
|
+
vm.ha_restart_priority !== '' && Promise.all([vm.set_ha_restart_priority(''), vm.add_tags('HA disabled')]),
|
|
58
|
+
vm.set_name_label(`${vm.name_label} - ${job.name} - (${formatFilenameDate(timestamp)})`),
|
|
59
|
+
])
|
|
60
|
+
)
|
|
61
|
+
return { size: sizeContainer.size }
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const targetVm = await xapi.getRecord('VM', targetVmRef)
|
|
65
|
+
|
|
66
|
+
await Promise.all([
|
|
67
|
+
asyncMap(['start', 'start_on'], op =>
|
|
68
|
+
targetVm.update_blocked_operations(
|
|
69
|
+
op,
|
|
70
|
+
'Start operation for this vm is blocked, clone it if you want to use it.'
|
|
71
|
+
)
|
|
72
|
+
),
|
|
73
|
+
targetVm.update_other_config({
|
|
74
|
+
'xo:backup:sr': srUuid,
|
|
75
|
+
|
|
76
|
+
// these entries need to be added in case of offline backup
|
|
77
|
+
'xo:backup:datetime': formatDateTime(timestamp),
|
|
78
|
+
'xo:backup:job': job.id,
|
|
79
|
+
'xo:backup:schedule': scheduleId,
|
|
80
|
+
'xo:backup:vm': vm.uuid,
|
|
81
|
+
}),
|
|
82
|
+
])
|
|
83
|
+
|
|
84
|
+
if (!deleteFirst) {
|
|
85
|
+
await deleteOldBackups()
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const { AbstractWriter } = require('./_AbstractWriter.js')
|
|
2
|
+
|
|
3
|
+
exports.AbstractDeltaWriter = class AbstractDeltaWriter extends AbstractWriter {
|
|
4
|
+
checkBaseVdis(baseUuidToSrcVdi, baseVm) {
|
|
5
|
+
throw new Error('Not implemented')
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
cleanup() {
|
|
9
|
+
throw new Error('Not implemented')
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
prepare({ isFull }) {
|
|
13
|
+
throw new Error('Not implemented')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async transfer({ timestamp, deltaExport, sizeContainers }) {
|
|
17
|
+
try {
|
|
18
|
+
return await this._transfer({ timestamp, deltaExport, sizeContainers })
|
|
19
|
+
} finally {
|
|
20
|
+
// ensure all streams are properly closed
|
|
21
|
+
for (const stream of Object.values(deltaExport.streams)) {
|
|
22
|
+
stream.destroy()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const { AbstractWriter } = require('./_AbstractWriter.js')
|
|
2
|
+
|
|
3
|
+
exports.AbstractFullWriter = class AbstractFullWriter extends AbstractWriter {
|
|
4
|
+
async run({ timestamp, sizeContainer, stream }) {
|
|
5
|
+
try {
|
|
6
|
+
return await this._run({ timestamp, sizeContainer, stream })
|
|
7
|
+
} finally {
|
|
8
|
+
// ensure stream is properly closed
|
|
9
|
+
stream.destroy()
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const { createLogger } = require('@xen-orchestra/log')
|
|
2
|
+
const { join } = require('path')
|
|
3
|
+
|
|
4
|
+
const { BACKUP_DIR, getVmBackupDir } = require('../_getVmBackupDir.js')
|
|
5
|
+
const MergeWorker = require('../merge-worker/index.js')
|
|
6
|
+
const { formatFilenameDate } = require('../_filenameDate.js')
|
|
7
|
+
|
|
8
|
+
const { warn } = createLogger('xo:backups:MixinBackupWriter')
|
|
9
|
+
|
|
10
|
+
exports.MixinBackupWriter = (BaseClass = Object) =>
|
|
11
|
+
class MixinBackupWriter extends BaseClass {
|
|
12
|
+
#lock
|
|
13
|
+
#vmBackupDir
|
|
14
|
+
|
|
15
|
+
constructor({ remoteId, ...rest }) {
|
|
16
|
+
super(rest)
|
|
17
|
+
|
|
18
|
+
this._adapter = rest.backup.remoteAdapters[remoteId]
|
|
19
|
+
this._remoteId = remoteId
|
|
20
|
+
|
|
21
|
+
this.#vmBackupDir = getVmBackupDir(this._backup.vm.uuid)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_cleanVm(options) {
|
|
25
|
+
return this._adapter
|
|
26
|
+
.cleanVm(this.#vmBackupDir, { ...options, fixMetadata: true, onLog: warn, lock: false })
|
|
27
|
+
.catch(warn)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async beforeBackup() {
|
|
31
|
+
const { handler } = this._adapter
|
|
32
|
+
const vmBackupDir = this.#vmBackupDir
|
|
33
|
+
await handler.mktree(vmBackupDir)
|
|
34
|
+
this.#lock = await handler.lock(vmBackupDir)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async afterBackup() {
|
|
38
|
+
const { disableMergeWorker } = this._backup.config
|
|
39
|
+
|
|
40
|
+
const { merge } = await this._cleanVm({ remove: true, merge: disableMergeWorker })
|
|
41
|
+
await this.#lock.dispose()
|
|
42
|
+
|
|
43
|
+
// merge worker only compatible with local remotes
|
|
44
|
+
const { handler } = this._adapter
|
|
45
|
+
if (merge && !disableMergeWorker && typeof handler._getRealPath === 'function') {
|
|
46
|
+
await handler.outputFile(join(MergeWorker.CLEAN_VM_QUEUE, formatFilenameDate(new Date())), this._backup.vm.uuid)
|
|
47
|
+
const remotePath = handler._getRealPath()
|
|
48
|
+
await MergeWorker.run(remotePath)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const getReplicatedVmDatetime = vm => {
|
|
2
|
+
const { 'xo:backup:datetime': datetime = vm.name_label.slice(-17, -1) } = vm.other_config
|
|
3
|
+
return datetime
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
const compareReplicatedVmDatetime = (a, b) => (getReplicatedVmDatetime(a) < getReplicatedVmDatetime(b) ? -1 : 1)
|
|
7
|
+
|
|
8
|
+
exports.listReplicatedVms = function listReplicatedVms(xapi, scheduleOrJobId, srUuid, vmUuid) {
|
|
9
|
+
const { all } = xapi.objects
|
|
10
|
+
const vms = {}
|
|
11
|
+
for (const key in all) {
|
|
12
|
+
const object = all[key]
|
|
13
|
+
const oc = object.other_config
|
|
14
|
+
if (
|
|
15
|
+
object.$type === 'VM' &&
|
|
16
|
+
!object.is_a_snapshot &&
|
|
17
|
+
!object.is_a_template &&
|
|
18
|
+
'start' in object.blocked_operations &&
|
|
19
|
+
(oc['xo:backup:job'] === scheduleOrJobId || oc['xo:backup:schedule'] === scheduleOrJobId) &&
|
|
20
|
+
oc['xo:backup:sr'] === srUuid &&
|
|
21
|
+
(oc['xo:backup:vm'] === vmUuid ||
|
|
22
|
+
// 2018-03-28, JFT: to catch VMs replicated before this fix
|
|
23
|
+
oc['xo:backup:vm'] === undefined)
|
|
24
|
+
) {
|
|
25
|
+
vms[object.$id] = object
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return Object.values(vms).sort(compareReplicatedVmDatetime)
|
|
30
|
+
}
|