@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 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 { RemoteVhd } from './disks/RemoteVhd.mjs'
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 RemoteVhd({ handler, path })
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, VhdFile } from 'vhd-lib'
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 { mergeVhdChain } from 'vhd-lib/merge.js'
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
- return await mergeVhdChain(handler, chain, {
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 { finalVhdSize } = await limitedMergeVhdChain(handler, chain, {
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) + finalVhdSize
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, vhds, xva } = metadata
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
- // can fail with encrypted remote
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
- _getRemoteWriter() {
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
- _getRemoteWriter() {
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 [IncrementalRemoteWriter, IncrementalXapiWriter]
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 RemoteWriter = this._getRemoteWriter()
49
- extractIdsFromSimplePattern(job.remotes).forEach(remoteId => {
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 RemoteWriter({
57
- adapter,
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
- remoteId,
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] = this._getWriters()
85
+ const [BackupWriter, ReplicationWriter, AggregratedBackupWriter, AggregratedReplicationWriter] =
86
+ this._getWriters()
86
87
 
87
88
  const allSettings = job.settings
88
- Object.entries(remoteAdapters).forEach(([remoteId, adapter]) => {
89
- const targetSettings = {
90
- ...settings,
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 BackupWriter({
96
- adapter,
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
- remoteId,
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
- srs.forEach(sr => {
109
- const targetSettings = {
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 ReplicationWriter({
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
- sr,
122
- settings: targetSettings,
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
  }