@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.
Files changed (43) hide show
  1. package/Backup.js +263 -0
  2. package/DurablePartition.js +40 -0
  3. package/ImportVmBackup.js +66 -0
  4. package/README.md +28 -0
  5. package/RemoteAdapter.js +552 -0
  6. package/RestoreMetadataBackup.js +24 -0
  7. package/Task.js +151 -0
  8. package/_PoolMetadataBackup.js +75 -0
  9. package/_VmBackup.js +409 -0
  10. package/_XoMetadataBackup.js +62 -0
  11. package/_backupType.js +4 -0
  12. package/_backupWorker.js +155 -0
  13. package/_cancelableMap.js +20 -0
  14. package/_cleanVm.js +378 -0
  15. package/_deltaVm.js +347 -0
  16. package/_extractIdsFromSimplePattern.js +29 -0
  17. package/_filenameDate.js +6 -0
  18. package/_forkStreamUnpipe.js +28 -0
  19. package/_getOldEntries.js +4 -0
  20. package/_getTmpDir.js +20 -0
  21. package/_getVmBackupDir.js +6 -0
  22. package/_isValidXva.js +60 -0
  23. package/_listPartitions.js +52 -0
  24. package/_lvm.js +31 -0
  25. package/_watchStreamSize.js +7 -0
  26. package/formatVmBackups.js +34 -0
  27. package/merge-worker/cli.js +69 -0
  28. package/merge-worker/index.js +25 -0
  29. package/package.json +49 -0
  30. package/parseMetadataBackupId.js +23 -0
  31. package/runBackupWorker.js +38 -0
  32. package/writers/DeltaBackupWriter.js +221 -0
  33. package/writers/DeltaReplicationWriter.js +126 -0
  34. package/writers/FullBackupWriter.js +85 -0
  35. package/writers/FullReplicationWriter.js +88 -0
  36. package/writers/_AbstractDeltaWriter.js +26 -0
  37. package/writers/_AbstractFullWriter.js +12 -0
  38. package/writers/_AbstractWriter.js +10 -0
  39. package/writers/_MixinBackupWriter.js +51 -0
  40. package/writers/_MixinReplicationWriter.js +8 -0
  41. package/writers/_checkVhd.js +5 -0
  42. package/writers/_listReplicatedVms.js +30 -0
  43. 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,10 @@
1
+ exports.AbstractWriter = class AbstractWriter {
2
+ constructor({ backup, settings }) {
3
+ this._backup = backup
4
+ this._settings = settings
5
+ }
6
+
7
+ beforeBackup() {}
8
+
9
+ afterBackup() {}
10
+ }
@@ -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,8 @@
1
+ exports.MixinReplicationWriter = (BaseClass = Object) =>
2
+ class MixinReplicationWriter extends BaseClass {
3
+ constructor({ sr, ...rest }) {
4
+ super(rest)
5
+
6
+ this._sr = sr
7
+ }
8
+ }
@@ -0,0 +1,5 @@
1
+ const Vhd = require('vhd-lib').default
2
+
3
+ exports.checkVhd = async function checkVhd(handler, path) {
4
+ await new Vhd(handler, path).readHeaderAndFooter()
5
+ }
@@ -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
+ }
@@ -0,0 +1,5 @@
1
+ const PARSE_UUID_RE = /-/g
2
+
3
+ exports.packUuid = function packUuid(uuid) {
4
+ return Buffer.from(uuid.replace(PARSE_UUID_RE, ''), 'hex')
5
+ }