@xen-orchestra/backups 0.68.2 → 0.69.1
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.mjs +3 -3
- package/_cleanVm.mjs +25 -43
- package/_runners/_vmRunners/FullRemote.mjs +3 -2
- package/_runners/_vmRunners/FullXapi.mjs +3 -1
- package/_runners/_vmRunners/IncrementalRemote.mjs +3 -2
- package/_runners/_vmRunners/IncrementalXapi.mjs +8 -1
- package/_runners/_vmRunners/_AbstractRemote.mjs +27 -12
- package/_runners/_vmRunners/_AbstractXapi.mjs +58 -22
- package/_runners/_writers/AggregatedFullRemoteWriter.mjs +38 -0
- package/_runners/_writers/AggregatedFullXapiWriter.mjs +31 -0
- package/_runners/_writers/AggregatedIncrementalRemoteWriter.mjs +78 -0
- package/_runners/_writers/AggregatedIncrementalXapiWriter.mjs +85 -0
- package/_runners/_writers/FullRemoteWriter.mjs +3 -3
- package/_runners/_writers/FullXapiWriter.mjs +3 -3
- package/_runners/_writers/IncrementalRemoteWriter.mjs +2 -2
- package/_runners/_writers/IncrementalXapiWriter.mjs +4 -2
- package/_runners/_writers/_AbstractAggregatedRemoteWriter.mjs +157 -0
- package/_runners/_writers/_AbstractAggregatedXapiWriter.mjs +114 -0
- package/_runners/_writers/_listReplicatedVms.mjs +10 -2
- package/disks/MergeRemoteDisk.mjs +317 -0
- package/disks/RemoteDisk.mjs +204 -0
- package/disks/RemoteVhdDisk.mjs +458 -0
- package/disks/RemoteVhdDiskChain.mjs +271 -0
- package/disks/openDiskChain.mjs +8 -3
- package/package.json +2 -2
- package/disks/RemoteVhd.mjs +0 -171
package/RemoteAdapter.mjs
CHANGED
|
@@ -31,7 +31,7 @@ import { listPartitions, LVM_PARTITION_TYPE_MBR, LVM_PARTITION_TYPE_GPT } from '
|
|
|
31
31
|
import { lvs, pvs } from './_lvm.mjs'
|
|
32
32
|
import { watchStreamSize } from './_watchStreamSize.mjs'
|
|
33
33
|
|
|
34
|
-
import {
|
|
34
|
+
import { RemoteVhdDisk } from './disks/RemoteVhdDisk.mjs'
|
|
35
35
|
import { openDiskChain } from './disks/openDiskChain.mjs'
|
|
36
36
|
import { toVhdStream, writeToVhdDirectory } from 'vhd-lib/disk-consumer/index.mjs'
|
|
37
37
|
import { ReadAhead } from '@xen-orchestra/disk-transform'
|
|
@@ -44,7 +44,7 @@ const IMMUTABILITY_METADATA_FILENAME = '/immutability.json'
|
|
|
44
44
|
|
|
45
45
|
const { debug, warn } = createLogger('xo:backups:RemoteAdapter')
|
|
46
46
|
|
|
47
|
-
const compareTimestamp = (a, b) => a.timestamp - b.timestamp
|
|
47
|
+
export const compareTimestamp = (a, b) => a.timestamp - b.timestamp
|
|
48
48
|
|
|
49
49
|
const noop = Function.prototype
|
|
50
50
|
|
|
@@ -744,7 +744,7 @@ export class RemoteAdapter {
|
|
|
744
744
|
if (useChain) {
|
|
745
745
|
disk = await openDiskChain({ handler, path })
|
|
746
746
|
} else {
|
|
747
|
-
disk = new
|
|
747
|
+
disk = new RemoteVhdDisk({ handler, path })
|
|
748
748
|
await disk.init()
|
|
749
749
|
}
|
|
750
750
|
disk = new ReadAhead(disk)
|
package/_cleanVm.mjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as UUID from 'uuid'
|
|
2
|
-
import sum from 'lodash/sum.js'
|
|
3
2
|
import { asyncMap } from '@xen-orchestra/async-map'
|
|
4
|
-
import { Constants, openVhd, VhdAbstract
|
|
3
|
+
import { Constants, openVhd, VhdAbstract } from 'vhd-lib'
|
|
5
4
|
import { isVhdAlias, resolveVhdAlias } from 'vhd-lib/aliases.js'
|
|
6
5
|
import { basename, dirname, resolve } from 'node:path'
|
|
7
6
|
import { isMetadataFile, isVhdFile, isVhdSumFile, isXvaFile, isXvaSumFile } from './_backupType.mjs'
|
|
8
7
|
import { limitConcurrency } from 'limit-concurrency-decorator'
|
|
9
|
-
import {
|
|
8
|
+
import { RemoteVhdDisk } from './disks/RemoteVhdDisk.mjs'
|
|
9
|
+
import { RemoteVhdDiskChain } from './disks/RemoteVhdDiskChain.mjs'
|
|
10
|
+
import { MergeRemoteDisk } from './disks/MergeRemoteDisk.mjs'
|
|
10
11
|
|
|
11
12
|
import { Task } from './Task.mjs'
|
|
12
13
|
import { Disposable } from 'promise-toolbox'
|
|
@@ -14,27 +15,6 @@ import handlerPath from '@xen-orchestra/fs/path'
|
|
|
14
15
|
|
|
15
16
|
const { DISK_TYPES } = Constants
|
|
16
17
|
|
|
17
|
-
// checking the size of a vhd directory is costly
|
|
18
|
-
// 1 Http Query per 1000 blocks
|
|
19
|
-
// we only check size of all the vhd are VhdFiles
|
|
20
|
-
function shouldComputeVhdsSize(handler, vhds) {
|
|
21
|
-
if (handler.isEncrypted) {
|
|
22
|
-
return false
|
|
23
|
-
}
|
|
24
|
-
return vhds.every(vhd => vhd instanceof VhdFile)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const computeVhdsSize = (handler, vhdPaths) =>
|
|
28
|
-
Disposable.use(
|
|
29
|
-
vhdPaths.map(vhdPath => openVhd(handler, vhdPath)),
|
|
30
|
-
async vhds => {
|
|
31
|
-
if (shouldComputeVhdsSize(handler, vhds)) {
|
|
32
|
-
const sizes = await asyncMap(vhds, vhd => vhd.getSize())
|
|
33
|
-
return sum(sizes)
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
)
|
|
37
|
-
|
|
38
18
|
// chain is [ ancestor, child_1, ..., child_n ]
|
|
39
19
|
async function _mergeVhdChain(handler, chain, { logInfo, remove, mergeBlockConcurrency }) {
|
|
40
20
|
logInfo(`merging VHD chain`, { chain })
|
|
@@ -51,7 +31,15 @@ async function _mergeVhdChain(handler, chain, { logInfo, remove, mergeBlockConcu
|
|
|
51
31
|
}
|
|
52
32
|
}, 10e3)
|
|
53
33
|
try {
|
|
54
|
-
|
|
34
|
+
const parentDisk = new RemoteVhdDisk({ handler, path: chain.shift() })
|
|
35
|
+
|
|
36
|
+
const childDisks = []
|
|
37
|
+
for (const path of chain) {
|
|
38
|
+
childDisks.push(new RemoteVhdDisk({ handler, path }))
|
|
39
|
+
}
|
|
40
|
+
const childDiskChain = new RemoteVhdDiskChain({ disks: childDisks })
|
|
41
|
+
|
|
42
|
+
const mergeRemoteDisk = new MergeRemoteDisk(handler, {
|
|
55
43
|
logInfo,
|
|
56
44
|
mergeBlockConcurrency,
|
|
57
45
|
onProgress({ done: d, total: t }) {
|
|
@@ -60,6 +48,14 @@ async function _mergeVhdChain(handler, chain, { logInfo, remove, mergeBlockConcu
|
|
|
60
48
|
},
|
|
61
49
|
removeUnused: remove,
|
|
62
50
|
})
|
|
51
|
+
|
|
52
|
+
const isResumingMerge = await mergeRemoteDisk.isResuming(parentDisk)
|
|
53
|
+
await parentDisk.init({ force: isResumingMerge })
|
|
54
|
+
await childDiskChain.init({ force: isResumingMerge })
|
|
55
|
+
|
|
56
|
+
const result = await mergeRemoteDisk.merge(parentDisk, childDiskChain)
|
|
57
|
+
|
|
58
|
+
return result
|
|
63
59
|
} finally {
|
|
64
60
|
clearInterval(handle)
|
|
65
61
|
}
|
|
@@ -521,14 +517,14 @@ export async function cleanVm(
|
|
|
521
517
|
const metadataWithMergedVhd = {}
|
|
522
518
|
const doMerge = async () => {
|
|
523
519
|
await asyncMap(toMerge, async chain => {
|
|
524
|
-
const {
|
|
520
|
+
const { finalDiskSize } = await limitedMergeVhdChain(handler, chain, {
|
|
525
521
|
logInfo,
|
|
526
522
|
logWarn,
|
|
527
523
|
remove,
|
|
528
524
|
mergeBlockConcurrency,
|
|
529
525
|
})
|
|
530
526
|
const metadataPath = vhdsToJSons[chain[chain.length - 1]] // all the chain should have the same metadata file
|
|
531
|
-
metadataWithMergedVhd[metadataPath] = (metadataWithMergedVhd[metadataPath] ?? 0) +
|
|
527
|
+
metadataWithMergedVhd[metadataPath] = (metadataWithMergedVhd[metadataPath] ?? 0) + finalDiskSize
|
|
532
528
|
})
|
|
533
529
|
}
|
|
534
530
|
|
|
@@ -562,7 +558,7 @@ export async function cleanVm(
|
|
|
562
558
|
let fileSystemSize
|
|
563
559
|
const mergedSize = metadataWithMergedVhd[metadataPath]
|
|
564
560
|
|
|
565
|
-
const { mode, size,
|
|
561
|
+
const { mode, size, xva } = metadata
|
|
566
562
|
|
|
567
563
|
try {
|
|
568
564
|
if (mode === 'full') {
|
|
@@ -578,21 +574,7 @@ export async function cleanVm(
|
|
|
578
574
|
})
|
|
579
575
|
}
|
|
580
576
|
} catch (error) {
|
|
581
|
-
//
|
|
582
|
-
}
|
|
583
|
-
} else if (mode === 'delta') {
|
|
584
|
-
// don't warn if the size has changed after a merge
|
|
585
|
-
if (mergedSize === undefined) {
|
|
586
|
-
const linkedVhds = Object.keys(vhds).map(key => resolve('/', vmDir, vhds[key]))
|
|
587
|
-
fileSystemSize = await computeVhdsSize(handler, linkedVhds)
|
|
588
|
-
// the size is not computed in some cases (e.g. VhdDirectory)
|
|
589
|
-
if (fileSystemSize !== undefined && fileSystemSize !== size) {
|
|
590
|
-
logWarn('cleanVm: incorrect backup size in metadata', {
|
|
591
|
-
path: metadataPath,
|
|
592
|
-
actual: size ?? 'none',
|
|
593
|
-
expected: fileSystemSize,
|
|
594
|
-
})
|
|
595
|
-
}
|
|
577
|
+
// will fail with encrypted remote
|
|
596
578
|
}
|
|
597
579
|
}
|
|
598
580
|
} catch (error) {
|
|
@@ -2,10 +2,11 @@ import { AbstractRemote } from './_AbstractRemote.mjs'
|
|
|
2
2
|
import { FullRemoteWriter } from '../_writers/FullRemoteWriter.mjs'
|
|
3
3
|
import { forkStreamUnpipe } from '../_forkStreamUnpipe.mjs'
|
|
4
4
|
import { watchStreamSize } from '../../_watchStreamSize.mjs'
|
|
5
|
+
import { AggregatedFullRemoteWriter } from '../_writers/AggregatedFullRemoteWriter.mjs'
|
|
5
6
|
|
|
6
7
|
export const FullRemote = class FullRemoteVmBackupRunner extends AbstractRemote {
|
|
7
|
-
|
|
8
|
-
return FullRemoteWriter
|
|
8
|
+
_getRemoteWriters() {
|
|
9
|
+
return [FullRemoteWriter, AggregatedFullRemoteWriter]
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
_filterTransferList(transferList) {
|
|
@@ -5,12 +5,14 @@ import { FullRemoteWriter } from '../_writers/FullRemoteWriter.mjs'
|
|
|
5
5
|
import { FullXapiWriter } from '../_writers/FullXapiWriter.mjs'
|
|
6
6
|
import { watchStreamSize } from '../../_watchStreamSize.mjs'
|
|
7
7
|
import { AbstractXapi } from './_AbstractXapi.mjs'
|
|
8
|
+
import { AggregatedFullRemoteWriter } from '../_writers/AggregatedFullRemoteWriter.mjs'
|
|
9
|
+
import { AggregatedFullXapiWriter } from '../_writers/AggregatedFullXapiWriter.mjs'
|
|
8
10
|
|
|
9
11
|
const { debug } = createLogger('xo:backups:FullXapiVmBackup')
|
|
10
12
|
|
|
11
13
|
export const FullXapi = class FullXapiVmBackupRunner extends AbstractXapi {
|
|
12
14
|
_getWriters() {
|
|
13
|
-
return [FullRemoteWriter, FullXapiWriter]
|
|
15
|
+
return [FullRemoteWriter, FullXapiWriter, AggregatedFullRemoteWriter, AggregatedFullXapiWriter]
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
async _mustDoSnapshot() {
|
|
@@ -11,11 +11,12 @@ import { Disposable } from 'promise-toolbox'
|
|
|
11
11
|
import { openVhd } from 'vhd-lib'
|
|
12
12
|
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
|
|
13
13
|
import { SynchronizedDisk, ThrottledDisk } from '@xen-orchestra/disk-transform'
|
|
14
|
+
import { AggregatedIncrementalRemoteWriter } from '../_writers/AggregatedIncrementalRemoteWriter.mjs'
|
|
14
15
|
|
|
15
16
|
const { warn } = createLogger('xo:backups:Incrementalremote')
|
|
16
17
|
class IncrementalRemoteVmBackupRunner extends AbstractRemote {
|
|
17
|
-
|
|
18
|
-
return IncrementalRemoteWriter
|
|
18
|
+
_getRemoteWriters() {
|
|
19
|
+
return [IncrementalRemoteWriter, AggregatedIncrementalRemoteWriter]
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
// we'll transfer the full list if at least one backup should be transferred
|
|
@@ -15,12 +15,19 @@ import {
|
|
|
15
15
|
markExportSuccessfull,
|
|
16
16
|
} from '../../_otherConfig.mjs'
|
|
17
17
|
import { ThrottledDisk, SynchronizedDisk } from '@xen-orchestra/disk-transform'
|
|
18
|
+
import { AggregatedIncrementalRemoteWriter } from '../_writers/AggregatedIncrementalRemoteWriter.mjs'
|
|
19
|
+
import { AggregatedIncrementalXapiWriter } from '../_writers/AggregatedIncrementalXapiWriter.mjs'
|
|
18
20
|
|
|
19
21
|
const { debug } = createLogger('xo:backups:IncrementalXapiVmBackup')
|
|
20
22
|
|
|
21
23
|
export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends AbstractXapi {
|
|
22
24
|
_getWriters() {
|
|
23
|
-
return [
|
|
25
|
+
return [
|
|
26
|
+
IncrementalRemoteWriter,
|
|
27
|
+
IncrementalXapiWriter,
|
|
28
|
+
AggregatedIncrementalRemoteWriter,
|
|
29
|
+
AggregatedIncrementalXapiWriter,
|
|
30
|
+
]
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
async _mustDoSnapshot() {
|
|
@@ -45,26 +45,41 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
|
|
45
45
|
const writers = new Set()
|
|
46
46
|
this._writers = writers
|
|
47
47
|
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const adapter = remoteAdapters[remoteId]
|
|
51
|
-
const targetSettings = {
|
|
52
|
-
...settings,
|
|
53
|
-
...allSettings[remoteId],
|
|
54
|
-
}
|
|
48
|
+
const [BackupWriter, AggregratedBackupWriter] = this._getRemoteWriters()
|
|
49
|
+
if (settings.distributeBackups) {
|
|
55
50
|
writers.add(
|
|
56
|
-
new
|
|
57
|
-
|
|
51
|
+
new AggregratedBackupWriter({
|
|
52
|
+
adapters: remoteAdapters,
|
|
53
|
+
BackupWriter,
|
|
58
54
|
config,
|
|
59
55
|
healthCheckSr,
|
|
60
56
|
job,
|
|
61
57
|
scheduleId: schedule.id,
|
|
62
58
|
vmUuid,
|
|
63
|
-
|
|
64
|
-
settings: targetSettings,
|
|
59
|
+
settings,
|
|
65
60
|
})
|
|
66
61
|
)
|
|
67
|
-
}
|
|
62
|
+
} else {
|
|
63
|
+
extractIdsFromSimplePattern(job.remotes).forEach(remoteId => {
|
|
64
|
+
const adapter = remoteAdapters[remoteId]
|
|
65
|
+
const targetSettings = {
|
|
66
|
+
...settings,
|
|
67
|
+
...allSettings[remoteId],
|
|
68
|
+
}
|
|
69
|
+
writers.add(
|
|
70
|
+
new BackupWriter({
|
|
71
|
+
adapter,
|
|
72
|
+
config,
|
|
73
|
+
healthCheckSr,
|
|
74
|
+
job,
|
|
75
|
+
scheduleId: schedule.id,
|
|
76
|
+
vmUuid,
|
|
77
|
+
remoteId,
|
|
78
|
+
settings: targetSettings,
|
|
79
|
+
})
|
|
80
|
+
)
|
|
81
|
+
})
|
|
82
|
+
}
|
|
68
83
|
const { filter } = job
|
|
69
84
|
if (filter === undefined) {
|
|
70
85
|
this._filterPredicate = () => true
|
|
@@ -82,48 +82,84 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
|
|
82
82
|
const writers = new Set()
|
|
83
83
|
this._writers = writers
|
|
84
84
|
|
|
85
|
-
const [BackupWriter, ReplicationWriter] =
|
|
85
|
+
const [BackupWriter, ReplicationWriter, AggregratedBackupWriter, AggregratedReplicationWriter] =
|
|
86
|
+
this._getWriters()
|
|
86
87
|
|
|
87
88
|
const allSettings = job.settings
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
...allSettings[remoteId],
|
|
92
|
-
}
|
|
93
|
-
if (targetSettings.exportRetention !== 0) {
|
|
89
|
+
|
|
90
|
+
if (settings.exportRetention > 0) {
|
|
91
|
+
if (settings.distributeBackups) {
|
|
94
92
|
writers.add(
|
|
95
|
-
new
|
|
96
|
-
|
|
93
|
+
new AggregratedBackupWriter({
|
|
94
|
+
adapters: remoteAdapters,
|
|
95
|
+
BackupWriter,
|
|
97
96
|
config,
|
|
98
97
|
healthCheckSr,
|
|
99
98
|
job,
|
|
100
99
|
scheduleId: schedule.id,
|
|
101
100
|
vmUuid: vm.uuid,
|
|
102
|
-
|
|
103
|
-
settings: targetSettings,
|
|
101
|
+
settings,
|
|
104
102
|
})
|
|
105
103
|
)
|
|
104
|
+
} else {
|
|
105
|
+
Object.entries(remoteAdapters).forEach(([remoteId, adapter]) => {
|
|
106
|
+
const targetSettings = {
|
|
107
|
+
...settings,
|
|
108
|
+
...allSettings[remoteId],
|
|
109
|
+
}
|
|
110
|
+
if (targetSettings.exportRetention !== 0) {
|
|
111
|
+
writers.add(
|
|
112
|
+
new BackupWriter({
|
|
113
|
+
adapter,
|
|
114
|
+
config,
|
|
115
|
+
healthCheckSr,
|
|
116
|
+
job,
|
|
117
|
+
scheduleId: schedule.id,
|
|
118
|
+
vmUuid: vm.uuid,
|
|
119
|
+
remoteId,
|
|
120
|
+
settings: targetSettings,
|
|
121
|
+
})
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
})
|
|
106
125
|
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
...settings,
|
|
111
|
-
...allSettings[sr.uuid],
|
|
112
|
-
}
|
|
113
|
-
if (targetSettings.copyRetention !== 0) {
|
|
126
|
+
}
|
|
127
|
+
if (settings.copyRetention) {
|
|
128
|
+
if (settings.distributeReplications) {
|
|
114
129
|
writers.add(
|
|
115
|
-
new
|
|
130
|
+
new AggregratedReplicationWriter({
|
|
116
131
|
config,
|
|
117
132
|
healthCheckSr,
|
|
118
133
|
job,
|
|
134
|
+
ReplicationWriter,
|
|
119
135
|
scheduleId: schedule.id,
|
|
120
136
|
vmUuid: vm.uuid,
|
|
121
|
-
|
|
122
|
-
settings
|
|
137
|
+
srs,
|
|
138
|
+
settings,
|
|
123
139
|
})
|
|
124
140
|
)
|
|
141
|
+
} else {
|
|
142
|
+
srs.forEach(sr => {
|
|
143
|
+
const targetSettings = {
|
|
144
|
+
...settings,
|
|
145
|
+
...allSettings[sr.uuid],
|
|
146
|
+
}
|
|
147
|
+
if (targetSettings.copyRetention !== 0) {
|
|
148
|
+
writers.add(
|
|
149
|
+
new ReplicationWriter({
|
|
150
|
+
config,
|
|
151
|
+
healthCheckSr,
|
|
152
|
+
job,
|
|
153
|
+
scheduleId: schedule.id,
|
|
154
|
+
vmUuid: vm.uuid,
|
|
155
|
+
sr,
|
|
156
|
+
settings: targetSettings,
|
|
157
|
+
})
|
|
158
|
+
)
|
|
159
|
+
}
|
|
160
|
+
})
|
|
125
161
|
}
|
|
126
|
-
}
|
|
162
|
+
}
|
|
127
163
|
}
|
|
128
164
|
}
|
|
129
165
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AbstractAggregatedRemoteWriter } from './_AbstractAggregatedRemoteWriter.mjs'
|
|
2
|
+
|
|
3
|
+
export class AggregatedFullRemoteWriter extends AbstractAggregatedRemoteWriter {
|
|
4
|
+
async deleteOldBackupsOnAdapter(adapter, backups) {
|
|
5
|
+
await adapter.deleteFullVmBackups(backups)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// take the lock on every adapters with this VM
|
|
9
|
+
// cleanup if deleteFirst is selected
|
|
10
|
+
async beforeBackup() {
|
|
11
|
+
await this.setupWriters()
|
|
12
|
+
await Promise.all(this.writers.map(writer => writer.beforeBackup()))
|
|
13
|
+
|
|
14
|
+
await this.setOldBackups()
|
|
15
|
+
if (this.props.settings.deleteFirst) {
|
|
16
|
+
await this.deleteOldBackups()
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async run(props) {
|
|
21
|
+
const mainAdapter = await this.computeAdapterCandidate()
|
|
22
|
+
this.mainWriter = this.getWriterByAdapter(mainAdapter)
|
|
23
|
+
return this.mainWriter.run(props)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// cleanup if deleteFirst is NOT selected
|
|
27
|
+
// release the lock on every adapters with this VM
|
|
28
|
+
async afterBackup() {
|
|
29
|
+
if (!this.props.settings.deleteFirst) {
|
|
30
|
+
await this.deleteOldBackups()
|
|
31
|
+
}
|
|
32
|
+
await Promise.all(this.writers.map(writer => writer.afterBackup()))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
healthCheck() {
|
|
36
|
+
return this.mainWriter.healthCheck()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AbstractAggregatedXapiWriter } from './_AbstractAggregatedXapiWriter.mjs'
|
|
2
|
+
|
|
3
|
+
export class AggregatedFullXapiWriter extends AbstractAggregatedXapiWriter {
|
|
4
|
+
// delete replica if deleteFirst is selected
|
|
5
|
+
async beforeBackup() {
|
|
6
|
+
await this.setupWriters()
|
|
7
|
+
await Promise.all(this.writers.map(writer => writer.beforeBackup()))
|
|
8
|
+
this.setOldReplicaList()
|
|
9
|
+
if (this.props.settings.deleteFirst) {
|
|
10
|
+
await this.deleteOldReplicas()
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async run(props) {
|
|
15
|
+
const mainSr = await this.computeSRCandidate()
|
|
16
|
+
this.mainWriter = this.getWriterBySr(mainSr)
|
|
17
|
+
await this.mainWriter.run(props)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// delete replica if not using deleteFirst
|
|
21
|
+
async afterBackup() {
|
|
22
|
+
if (!this.props.settings.deleteFirst) {
|
|
23
|
+
await this.deleteOldReplicas()
|
|
24
|
+
}
|
|
25
|
+
await Promise.all(this.writers.map(writer => writer.afterBackup()))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
healthCheck() {
|
|
29
|
+
return this.mainWriter.healthCheck()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AbstractAggregatedRemoteWriter } from './_AbstractAggregatedRemoteWriter.mjs'
|
|
2
|
+
export class AggregatedIncrementalRemoteWriter extends AbstractAggregatedRemoteWriter {
|
|
3
|
+
async deleteOldBackupsOnAdapter(adapter, backups) {
|
|
4
|
+
await adapter.deleteDeltaVmBackups(backups)
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @param {Map<string,string>} baseUuidToSrcVdi
|
|
10
|
+
*/
|
|
11
|
+
async checkBaseVdis(baseUuidToSrcVdi) {
|
|
12
|
+
let selectedCopy = new Map()
|
|
13
|
+
for (const writer of this.writers) {
|
|
14
|
+
const copy = new Map(baseUuidToSrcVdi)
|
|
15
|
+
await writer.checkBaseVdis(copy)
|
|
16
|
+
if (copy.size > 0) {
|
|
17
|
+
// there can be multiple candidates
|
|
18
|
+
// we use the last one in this case
|
|
19
|
+
this.mainWriter = writer
|
|
20
|
+
selectedCopy = copy
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// update the parameter with our best candidate
|
|
24
|
+
// that can be an empty map
|
|
25
|
+
baseUuidToSrcVdi.forEach(key => {
|
|
26
|
+
if (!selectedCopy.has(key)) {
|
|
27
|
+
baseUuidToSrcVdi.delete(key)
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// take the lock on every adapters with this VM
|
|
33
|
+
// cleanup if deleteFirst is selected
|
|
34
|
+
async beforeBackup() {
|
|
35
|
+
await this.setupWriters()
|
|
36
|
+
await Promise.all(this.writers.map(writer => writer.beforeBackup()))
|
|
37
|
+
|
|
38
|
+
await this.setOldBackups()
|
|
39
|
+
if (this.props.settings.deleteFirst) {
|
|
40
|
+
await this.deleteOldBackups()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async prepare({ isFull }) {
|
|
45
|
+
if (this.mainWriter === undefined) {
|
|
46
|
+
// use the remote with the most empty space for starting a new chain
|
|
47
|
+
const mainAdapter = await this.computeAdapterCandidate()
|
|
48
|
+
this.mainWriter = this.getWriterByAdapter(mainAdapter)
|
|
49
|
+
}
|
|
50
|
+
await Promise.all(this.writers.map(writer => writer.prepare({ isFull })))
|
|
51
|
+
}
|
|
52
|
+
// write only on the main writer
|
|
53
|
+
transfer({ isVhdDifferencing, timestamp, deltaExport, vm, vmSnapshot }) {
|
|
54
|
+
return this.mainWriter.transfer({ isVhdDifferencing, timestamp, deltaExport, vm, vmSnapshot })
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// chain only on the main writer
|
|
58
|
+
updateUuidAndChain({ isVhdDifferencing, vdis }) {
|
|
59
|
+
return this.mainWriter.updateUuidAndChain({ isVhdDifferencing, vdis })
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// remove the backups and remove the entries
|
|
63
|
+
async cleanup() {
|
|
64
|
+
if (!this.props.settings.deleteFirst) {
|
|
65
|
+
await this.deleteOldBackups()
|
|
66
|
+
}
|
|
67
|
+
await Promise.all(this.writers.map(writer => writer.cleanup()))
|
|
68
|
+
}
|
|
69
|
+
// cleanVM on all, since we may want to delete a chani
|
|
70
|
+
// that is not on the main writer
|
|
71
|
+
async afterBackup() {
|
|
72
|
+
await Promise.all(this.writers.map(writer => writer.afterBackup()))
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
healthCheck() {
|
|
76
|
+
return this.mainWriter.healthCheck()
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { AbstractAggregatedXapiWriter } from './_AbstractAggregatedXapiWriter.mjs'
|
|
2
|
+
import { createLogger } from '@xen-orchestra/log'
|
|
3
|
+
|
|
4
|
+
const { debug } = createLogger('xo:backups:AggregatedIncrementalXapiWriter')
|
|
5
|
+
export class AggregatedIncrementalXapiWriter extends AbstractAggregatedXapiWriter {
|
|
6
|
+
/**
|
|
7
|
+
*
|
|
8
|
+
* @param {Map<string,string>} baseUuidToSrcVdi
|
|
9
|
+
*/
|
|
10
|
+
async checkBaseVdis(baseUuidToSrcVdi) {
|
|
11
|
+
debug('checkBaseVdis', { baseUuidToSrcVdi })
|
|
12
|
+
let selectedCopy = new Map()
|
|
13
|
+
for (const writer of this.writers) {
|
|
14
|
+
const copy = new Map(baseUuidToSrcVdi)
|
|
15
|
+
await writer.checkBaseVdis(copy)
|
|
16
|
+
if (copy.size > 0) {
|
|
17
|
+
debug('checkBaseVdis found a mainwriter candidate ', writer._sr.name_label)
|
|
18
|
+
// there can be multiple candidates
|
|
19
|
+
// we use the last one in this case
|
|
20
|
+
this.mainWriter = writer
|
|
21
|
+
selectedCopy = copy
|
|
22
|
+
} else {
|
|
23
|
+
debug(writer._sr.name_label, 'is not a delta candidate')
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// update the parameter with our best candidate
|
|
27
|
+
// that can be an empty map
|
|
28
|
+
baseUuidToSrcVdi.forEach(key => {
|
|
29
|
+
if (!selectedCopy.has(key)) {
|
|
30
|
+
baseUuidToSrcVdi.delete(key)
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
debug('checkBaseVdis', { baseUuidToSrcVdi })
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// take the lock on every adapters with this VM
|
|
37
|
+
// cleanup if deleteFirst is selected
|
|
38
|
+
async beforeBackup() {
|
|
39
|
+
await this.setupWriters()
|
|
40
|
+
await Promise.all(this.writers.map(writer => writer.beforeBackup()))
|
|
41
|
+
this.setOldReplicaList()
|
|
42
|
+
if (this.props.settings.deleteFirst) {
|
|
43
|
+
await this.deleteOldReplicas()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async prepare(args) {
|
|
48
|
+
if (this.mainWriter === undefined) {
|
|
49
|
+
debug('no mainwriter found, fallback to a new SR')
|
|
50
|
+
// use the SR with the most empty space for starting a new chain
|
|
51
|
+
const mainSr = await this.computeSRCandidate()
|
|
52
|
+
this.mainWriter = this.getWriterBySr(mainSr)
|
|
53
|
+
}
|
|
54
|
+
await Promise.all(this.writers.map(writer => writer.prepare(args)))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// write only on the main writer
|
|
58
|
+
transfer(args) {
|
|
59
|
+
return this.mainWriter.transfer(args)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// chain only on the main writer
|
|
63
|
+
updateUuidAndChain(args) {
|
|
64
|
+
return this.mainWriter.updateUuidAndChain(args)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// remove the backups and remove the entries
|
|
68
|
+
async cleanup() {
|
|
69
|
+
debug('cleanup')
|
|
70
|
+
if (!this.props.settings.deleteFirst) {
|
|
71
|
+
await this.deleteOldReplicas()
|
|
72
|
+
}
|
|
73
|
+
await Promise.all(this.writers.map(writer => writer.cleanup()))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// cleanVM on all, since we may want to delete a chani
|
|
77
|
+
// that is not on the main writer
|
|
78
|
+
async afterBackup() {
|
|
79
|
+
await Promise.all(this.writers.map(writer => writer.afterBackup()))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
healthCheck() {
|
|
83
|
+
return this.mainWriter.healthCheck()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -59,8 +59,8 @@ export class FullRemoteWriter extends MixinRemoteWriter(AbstractFullWriter) {
|
|
|
59
59
|
xva: './' + dataBasename,
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const { deleteFirst } = settings
|
|
63
|
-
if (deleteFirst) {
|
|
62
|
+
const { skipDeleteOldEntries, deleteFirst } = settings
|
|
63
|
+
if (!skipDeleteOldEntries && deleteFirst) {
|
|
64
64
|
await deleteOldBackups()
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -76,7 +76,7 @@ export class FullRemoteWriter extends MixinRemoteWriter(AbstractFullWriter) {
|
|
|
76
76
|
metadata.tags = await this.getLongTermRetentionTags(metadata)
|
|
77
77
|
this._metadataFileName = await adapter.writeVmBackupMetadata(vm.uuid, metadata)
|
|
78
78
|
|
|
79
|
-
if (!deleteFirst) {
|
|
79
|
+
if (!skipDeleteOldEntries && !deleteFirst) {
|
|
80
80
|
await deleteOldBackups()
|
|
81
81
|
}
|
|
82
82
|
|
|
@@ -46,8 +46,8 @@ export class FullXapiWriter extends MixinXapiWriter(AbstractFullWriter) {
|
|
|
46
46
|
const oldVms = getOldEntries(settings.copyRetention - 1, listReplicatedVms(xapi, scheduleId, srUuid, vm.uuid))
|
|
47
47
|
|
|
48
48
|
const deleteOldBackups = () => asyncMapSettled(oldVms, vm => xapi.VM_destroy(vm.$ref))
|
|
49
|
-
const { deleteFirst, _warmMigration } = settings
|
|
50
|
-
if (deleteFirst) {
|
|
49
|
+
const { deleteFirst, skipDeleteOldEntries, _warmMigration } = settings
|
|
50
|
+
if (!skipDeleteOldEntries && deleteFirst) {
|
|
51
51
|
await deleteOldBackups()
|
|
52
52
|
}
|
|
53
53
|
|
|
@@ -85,7 +85,7 @@ export class FullXapiWriter extends MixinXapiWriter(AbstractFullWriter) {
|
|
|
85
85
|
}),
|
|
86
86
|
])
|
|
87
87
|
|
|
88
|
-
if (!deleteFirst) {
|
|
88
|
+
if (!skipDeleteOldEntries && !deleteFirst) {
|
|
89
89
|
await deleteOldBackups()
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -114,13 +114,13 @@ export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrement
|
|
|
114
114
|
oldEntries.length = maxMergedDeltasPerRun
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
if (settings.deleteFirst) {
|
|
117
|
+
if (settings.deleteFirst && !settings.skipDeleteOldEntries) {
|
|
118
118
|
await this._deleteOldEntries()
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
async cleanup() {
|
|
123
|
-
if (!this._settings.deleteFirst) {
|
|
123
|
+
if (!this._settings.deleteFirst && !this._settings.skipDeleteOldEntries) {
|
|
124
124
|
await this._deleteOldEntries()
|
|
125
125
|
}
|
|
126
126
|
}
|