@xen-orchestra/backups 0.71.2 → 0.72.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/HealthCheckVmBackup.mjs +2 -2
- package/ImportVmBackup.mjs +2 -2
- package/_backupWorker.mjs +3 -3
- package/_cleanVm.mjs +8 -3
- package/_incrementalVm.mjs +1 -1
- package/_runners/Metadata.mjs +18 -13
- package/_runners/VmsRemote.mjs +21 -17
- package/_runners/VmsXapi.mjs +24 -23
- package/_runners/_Abstract.mjs +10 -5
- package/_runners/_PoolMetadataBackup.mjs +3 -3
- package/_runners/_XoMetadataBackup.mjs +3 -3
- package/_runners/_vmRunners/IncrementalXapi.mjs +1 -1
- package/_runners/_vmRunners/_Abstract.mjs +2 -2
- package/_runners/_vmRunners/_AbstractRemote.mjs +1 -1
- package/_runners/_vmRunners/_AbstractXapi.mjs +2 -2
- package/_runners/_writers/FullRemoteWriter.mjs +6 -5
- package/_runners/_writers/FullXapiWriter.mjs +5 -5
- package/_runners/_writers/IncrementalRemoteWriter.mjs +10 -9
- package/_runners/_writers/IncrementalXapiWriter.mjs +26 -12
- package/_runners/_writers/_MixinRemoteWriter.mjs +3 -3
- package/_runners/_writers/_MixinXapiWriter.mjs +4 -4
- package/disks/MergeRemoteDisk.mjs +6 -0
- package/disks/RemoteDisk.mjs +10 -0
- package/disks/RemoteVhdDisk.mjs +15 -1
- package/disks/RemoteVhdDiskChain.mjs +38 -6
- package/package.json +16 -14
- package/Task.mjs +0 -155
- package/_runners/_runTask.mjs +0 -5
package/HealthCheckVmBackup.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Task } from '
|
|
1
|
+
import { Task } from '@vates/task'
|
|
2
2
|
|
|
3
3
|
export class HealthCheckVmBackup {
|
|
4
4
|
#restoredVm
|
|
@@ -14,7 +14,7 @@ export class HealthCheckVmBackup {
|
|
|
14
14
|
async run() {
|
|
15
15
|
return Task.run(
|
|
16
16
|
{
|
|
17
|
-
name: 'vmstart',
|
|
17
|
+
properties: { name: 'vmstart' },
|
|
18
18
|
},
|
|
19
19
|
async () => {
|
|
20
20
|
let restoredVm = this.#restoredVm
|
package/ImportVmBackup.mjs
CHANGED
|
@@ -2,9 +2,9 @@ import assert from 'node:assert'
|
|
|
2
2
|
|
|
3
3
|
import { formatFilenameDate } from './_filenameDate.mjs'
|
|
4
4
|
import { importIncrementalVm } from './_incrementalVm.mjs'
|
|
5
|
-
import { Task } from './Task.mjs'
|
|
6
5
|
import { watchStreamSize } from './_watchStreamSize.mjs'
|
|
7
6
|
import { decorateClass } from '@vates/decorate-with'
|
|
7
|
+
import { Task } from '@vates/task'
|
|
8
8
|
import { createLogger } from '@xen-orchestra/log'
|
|
9
9
|
import { dirname, join } from 'node:path'
|
|
10
10
|
import pickBy from 'lodash/pickBy.js'
|
|
@@ -244,7 +244,7 @@ export class ImportVmBackup {
|
|
|
244
244
|
|
|
245
245
|
return Task.run(
|
|
246
246
|
{
|
|
247
|
-
name: 'transfer',
|
|
247
|
+
properties: { name: 'transfer' },
|
|
248
248
|
},
|
|
249
249
|
async () => {
|
|
250
250
|
const xapi = this._xapi
|
package/_backupWorker.mjs
CHANGED
|
@@ -14,10 +14,10 @@ import { decorateMethodsWith } from '@vates/decorate-with'
|
|
|
14
14
|
import { deduped } from '@vates/disposable/deduped.js'
|
|
15
15
|
import { getHandler } from '@xen-orchestra/fs'
|
|
16
16
|
import { parseDuration } from '@vates/parse-duration'
|
|
17
|
+
import { Task } from '@vates/task'
|
|
17
18
|
import { Xapi } from '@xen-orchestra/xapi'
|
|
18
19
|
|
|
19
20
|
import { RemoteAdapter } from './RemoteAdapter.mjs'
|
|
20
|
-
import { Task } from './Task.mjs'
|
|
21
21
|
|
|
22
22
|
createCachedLookup().patchGlobal()
|
|
23
23
|
|
|
@@ -178,8 +178,8 @@ process.on('message', async message => {
|
|
|
178
178
|
const result = message.runWithLogs
|
|
179
179
|
? await Task.run(
|
|
180
180
|
{
|
|
181
|
-
name: 'backup run',
|
|
182
|
-
|
|
181
|
+
properties: { name: 'backup run', ...message.data.jobData },
|
|
182
|
+
onProgress: data =>
|
|
183
183
|
emitMessage({
|
|
184
184
|
data,
|
|
185
185
|
type: 'log',
|
package/_cleanVm.mjs
CHANGED
|
@@ -9,8 +9,8 @@ import { RemoteVhdDisk } from './disks/RemoteVhdDisk.mjs'
|
|
|
9
9
|
import { RemoteVhdDiskChain } from './disks/RemoteVhdDiskChain.mjs'
|
|
10
10
|
import { MergeRemoteDisk } from './disks/MergeRemoteDisk.mjs'
|
|
11
11
|
|
|
12
|
-
import { Task } from './Task.mjs'
|
|
13
12
|
import { Disposable } from 'promise-toolbox'
|
|
13
|
+
import { Task } from '@vates/task'
|
|
14
14
|
import handlerPath from '@xen-orchestra/fs/path'
|
|
15
15
|
|
|
16
16
|
const { DISK_TYPES } = Constants
|
|
@@ -514,23 +514,27 @@ export async function cleanVm(
|
|
|
514
514
|
})
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
+
let totalMergedDataSize = 0
|
|
517
518
|
const metadataWithMergedVhd = {}
|
|
518
519
|
const doMerge = async () => {
|
|
519
520
|
await asyncMap(toMerge, async chain => {
|
|
520
|
-
const { finalDiskSize } = await limitedMergeVhdChain(handler, chain, {
|
|
521
|
+
const { finalDiskSize, mergedDataSize } = await limitedMergeVhdChain(handler, chain, {
|
|
521
522
|
logInfo,
|
|
522
523
|
logWarn,
|
|
523
524
|
remove,
|
|
524
525
|
mergeBlockConcurrency,
|
|
525
526
|
})
|
|
527
|
+
totalMergedDataSize += mergedDataSize
|
|
526
528
|
const metadataPath = vhdsToJSons[chain[chain.length - 1]] // all the chain should have the same metadata file
|
|
527
529
|
metadataWithMergedVhd[metadataPath] = (metadataWithMergedVhd[metadataPath] ?? 0) + finalDiskSize
|
|
528
530
|
})
|
|
531
|
+
|
|
532
|
+
return { size: totalMergedDataSize }
|
|
529
533
|
}
|
|
530
534
|
|
|
531
535
|
await Promise.all([
|
|
532
536
|
...unusedVhdsDeletion,
|
|
533
|
-
toMerge.length !== 0 && (merge ? Task.run({ name: 'merge' }, doMerge) : () => Promise.resolve()),
|
|
537
|
+
toMerge.length !== 0 && (merge ? Task.run({ properties: { name: 'merge' } }, doMerge) : () => Promise.resolve()),
|
|
534
538
|
asyncMap(unusedXvas, path => {
|
|
535
539
|
logWarn('unused XVA', { path })
|
|
536
540
|
if (remove) {
|
|
@@ -619,5 +623,6 @@ export async function cleanVm(
|
|
|
619
623
|
return {
|
|
620
624
|
// boolean whether some VHDs were merged (or should be merged)
|
|
621
625
|
merge: toMerge.length !== 0,
|
|
626
|
+
size: totalMergedDataSize,
|
|
622
627
|
}
|
|
623
628
|
}
|
package/_incrementalVm.mjs
CHANGED
|
@@ -4,9 +4,9 @@ import { asyncMap } from '@xen-orchestra/async-map'
|
|
|
4
4
|
import { CancelToken } from 'promise-toolbox'
|
|
5
5
|
import { compareVersions } from 'compare-versions'
|
|
6
6
|
import { defer } from 'golike-defer'
|
|
7
|
+
import { Task } from '@vates/task'
|
|
7
8
|
|
|
8
9
|
import { cancelableMap } from './_cancelableMap.mjs'
|
|
9
|
-
import { Task } from './Task.mjs'
|
|
10
10
|
import pick from 'lodash/pick.js'
|
|
11
11
|
import { BASE_DELTA_VDI, CONTENT_KEY, COPY_OF, VM_UUID } from './_otherConfig.mjs'
|
|
12
12
|
|
package/_runners/Metadata.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { asyncMap } from '@xen-orchestra/async-map'
|
|
2
|
+
import { Task } from '@vates/task'
|
|
2
3
|
import Disposable from 'promise-toolbox/Disposable'
|
|
3
4
|
import ignoreErrors from 'promise-toolbox/ignoreErrors'
|
|
4
5
|
|
|
@@ -6,9 +7,10 @@ import { extractIdsFromSimplePattern } from '../extractIdsFromSimplePattern.mjs'
|
|
|
6
7
|
import { PoolMetadataBackup } from './_PoolMetadataBackup.mjs'
|
|
7
8
|
import { XoMetadataBackup } from './_XoMetadataBackup.mjs'
|
|
8
9
|
import { DEFAULT_SETTINGS, Abstract } from './_Abstract.mjs'
|
|
9
|
-
import { runTask } from './_runTask.mjs'
|
|
10
10
|
import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
|
|
11
11
|
|
|
12
|
+
const noop = Function.prototype
|
|
13
|
+
|
|
12
14
|
const DEFAULT_METADATA_SETTINGS = {
|
|
13
15
|
retentionPoolMetadata: 0,
|
|
14
16
|
retentionXoMetadata: 0,
|
|
@@ -55,13 +57,16 @@ export const Metadata = class MetadataBackupRunner extends Abstract {
|
|
|
55
57
|
poolIds.map(id =>
|
|
56
58
|
this._getRecord('pool', id).catch(error => {
|
|
57
59
|
// See https://github.com/vatesfr/xen-orchestra/commit/6aa6cfba8ec939c0288f0fa740f6dfad98c43cbb
|
|
58
|
-
|
|
60
|
+
Task.run(
|
|
59
61
|
{
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
properties: {
|
|
63
|
+
id,
|
|
64
|
+
name: 'get pool record',
|
|
65
|
+
type: 'pool',
|
|
66
|
+
},
|
|
62
67
|
},
|
|
63
68
|
() => Promise.reject(error)
|
|
64
|
-
)
|
|
69
|
+
).catch(noop)
|
|
65
70
|
})
|
|
66
71
|
)
|
|
67
72
|
),
|
|
@@ -81,11 +86,11 @@ export const Metadata = class MetadataBackupRunner extends Abstract {
|
|
|
81
86
|
if (pools.length !== 0 && settings.retentionPoolMetadata !== 0) {
|
|
82
87
|
promises.push(
|
|
83
88
|
asyncMap(pools, async pool =>
|
|
84
|
-
|
|
89
|
+
Task.run(
|
|
85
90
|
{
|
|
86
|
-
|
|
87
|
-
data: {
|
|
91
|
+
properties: {
|
|
88
92
|
id: pool.$id,
|
|
93
|
+
name: `Starting metadata backup for the pool (${pool.$id}). (${job.id})`,
|
|
89
94
|
pool,
|
|
90
95
|
poolMaster: await ignoreErrors.call(pool.$xapi.getRecord('host', pool.master)),
|
|
91
96
|
type: 'pool',
|
|
@@ -100,17 +105,17 @@ export const Metadata = class MetadataBackupRunner extends Abstract {
|
|
|
100
105
|
schedule,
|
|
101
106
|
settings,
|
|
102
107
|
}).run()
|
|
103
|
-
)
|
|
108
|
+
).catch(noop)
|
|
104
109
|
)
|
|
105
110
|
)
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
if (job.xoMetadata !== undefined && settings.retentionXoMetadata !== 0) {
|
|
109
114
|
promises.push(
|
|
110
|
-
|
|
115
|
+
Task.run(
|
|
111
116
|
{
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
properties: {
|
|
118
|
+
name: `Starting XO metadata backup. (${job.id})`,
|
|
114
119
|
type: 'xo',
|
|
115
120
|
},
|
|
116
121
|
},
|
|
@@ -122,7 +127,7 @@ export const Metadata = class MetadataBackupRunner extends Abstract {
|
|
|
122
127
|
schedule,
|
|
123
128
|
settings,
|
|
124
129
|
}).run()
|
|
125
|
-
)
|
|
130
|
+
).catch(noop)
|
|
126
131
|
)
|
|
127
132
|
}
|
|
128
133
|
await Promise.all(promises)
|
package/_runners/VmsRemote.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { asyncMapSettled } from '@xen-orchestra/async-map'
|
|
2
2
|
import Disposable from 'promise-toolbox/Disposable'
|
|
3
3
|
import { limitConcurrency } from 'limit-concurrency-decorator'
|
|
4
|
+
import { Task } from '@vates/task'
|
|
4
5
|
|
|
5
6
|
import { extractIdsFromSimplePattern } from '../extractIdsFromSimplePattern.mjs'
|
|
6
|
-
import { Task } from '../Task.mjs'
|
|
7
7
|
import { DEFAULT_SETTINGS, Abstract } from './_Abstract.mjs'
|
|
8
8
|
import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
|
|
9
9
|
import { FullRemote } from './_vmRunners/FullRemote.mjs'
|
|
@@ -78,7 +78,7 @@ export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
|
|
|
78
78
|
}
|
|
79
79
|
nTriesByVmId[vmUuid]++
|
|
80
80
|
|
|
81
|
-
const taskStart = { name: 'backup VM',
|
|
81
|
+
const taskStart = { properties: { id: vmUuid, name: 'backup VM', type: 'VM' } }
|
|
82
82
|
const vmSettings = { ...settings, ...allSettings[vmUuid] }
|
|
83
83
|
const isLastRun = nTriesByVmId[vmUuid] === vmSettings.nRetriesVmBackupFailures + 1
|
|
84
84
|
|
|
@@ -113,23 +113,27 @@ export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
|
|
|
113
113
|
taskByVmId[vmUuid] = new Task(taskStart)
|
|
114
114
|
}
|
|
115
115
|
const task = taskByVmId[vmUuid]
|
|
116
|
+
// error has to be caught in the task to prevent its failure, but handled outside the task to execute another task.run()
|
|
117
|
+
let taskError
|
|
116
118
|
return task
|
|
117
|
-
.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
} else {
|
|
126
|
-
Task.warning(`Retry the VM mirror backup due to an error`, {
|
|
127
|
-
attempt: nTriesByVmId[vmUuid],
|
|
128
|
-
error: error.message,
|
|
129
|
-
})
|
|
130
|
-
queue.add(vmUuid)
|
|
131
|
-
}
|
|
119
|
+
.runInside(async () =>
|
|
120
|
+
vmBackup.run().catch(error => {
|
|
121
|
+
taskError = error
|
|
122
|
+
})
|
|
123
|
+
)
|
|
124
|
+
.then(result => {
|
|
125
|
+
if (taskError === undefined) {
|
|
126
|
+
return task.success(result)
|
|
132
127
|
}
|
|
128
|
+
if (isLastRun) {
|
|
129
|
+
return task.failure(taskError)
|
|
130
|
+
}
|
|
131
|
+
// don't end the task
|
|
132
|
+
task.warning(`Retry the VM mirror backup due to an error`, {
|
|
133
|
+
attempt: nTriesByVmId[vmUuid],
|
|
134
|
+
error: taskError.message,
|
|
135
|
+
})
|
|
136
|
+
queue.add(vmUuid)
|
|
133
137
|
})
|
|
134
138
|
.catch(noop)
|
|
135
139
|
}
|
package/_runners/VmsXapi.mjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { asyncMapSettled } from '@xen-orchestra/async-map'
|
|
2
2
|
import Disposable from 'promise-toolbox/Disposable'
|
|
3
3
|
import { limitConcurrency } from 'limit-concurrency-decorator'
|
|
4
|
+
import { Task } from '@vates/task'
|
|
4
5
|
|
|
5
6
|
import { extractIdsFromSimplePattern } from '../extractIdsFromSimplePattern.mjs'
|
|
6
|
-
import { Task } from '../Task.mjs'
|
|
7
7
|
import { DEFAULT_SETTINGS, Abstract } from './_Abstract.mjs'
|
|
8
|
-
import { runTask } from './_runTask.mjs'
|
|
9
8
|
import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
|
|
10
9
|
import { IncrementalXapi } from './_vmRunners/IncrementalXapi.mjs'
|
|
11
10
|
import { FullXapi } from './_vmRunners/FullXapi.mjs'
|
|
@@ -65,13 +64,12 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
|
|
|
65
64
|
Disposable.all(
|
|
66
65
|
extractIdsFromSimplePattern(job.srs).map(id =>
|
|
67
66
|
this._getRecord('SR', id).catch(error => {
|
|
68
|
-
|
|
67
|
+
Task.run(
|
|
69
68
|
{
|
|
70
|
-
name: 'get SR record',
|
|
71
|
-
data: { type: 'SR', id },
|
|
69
|
+
properties: { id, name: 'get SR record', type: 'SR' },
|
|
72
70
|
},
|
|
73
71
|
() => Promise.reject(error)
|
|
74
|
-
)
|
|
72
|
+
).catch(noop)
|
|
75
73
|
})
|
|
76
74
|
)
|
|
77
75
|
),
|
|
@@ -108,11 +106,12 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
|
|
|
108
106
|
}
|
|
109
107
|
return taskByVmId[vmUuid]
|
|
110
108
|
}
|
|
111
|
-
const vmBackupFailed = error => {
|
|
109
|
+
const vmBackupFailed = async (error, task) => {
|
|
112
110
|
if (isLastRun) {
|
|
113
|
-
|
|
111
|
+
return task.failure(error)
|
|
114
112
|
} else {
|
|
115
|
-
|
|
113
|
+
// don't end the task
|
|
114
|
+
task.warning(`Retry the VM backup due to an error`, {
|
|
116
115
|
attempt: nTriesByVmId[vmUuid],
|
|
117
116
|
error: error.message,
|
|
118
117
|
})
|
|
@@ -126,19 +125,21 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
|
|
|
126
125
|
nTriesByVmId[vmUuid]++
|
|
127
126
|
|
|
128
127
|
const vmSettings = { ...settings, ...allSettings[vmUuid] }
|
|
129
|
-
const taskStart = { name: 'backup VM',
|
|
128
|
+
const taskStart = { properties: { id: vmUuid, name: 'backup VM', type: 'VM' } }
|
|
130
129
|
const isLastRun = nTriesByVmId[vmUuid] === vmSettings.nRetriesVmBackupFailures + 1
|
|
131
130
|
|
|
132
131
|
return this._getRecord('VM', vmUuid).then(
|
|
133
132
|
disposableVm =>
|
|
134
133
|
Disposable.use(disposableVm, async vm => {
|
|
135
|
-
if (taskStart.
|
|
136
|
-
taskStart.
|
|
134
|
+
if (taskStart.properties.name_label === undefined) {
|
|
135
|
+
taskStart.properties.name_label = vm.name_label
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
const task = getVmTask()
|
|
139
|
+
// error has to be caught in the task to prevent its failure, but handled outside the task to execute another task.run()
|
|
140
|
+
let taskError
|
|
140
141
|
return task
|
|
141
|
-
.
|
|
142
|
+
.runInside(async () => {
|
|
142
143
|
const opts = {
|
|
143
144
|
baseSettings,
|
|
144
145
|
config,
|
|
@@ -164,21 +165,21 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
|
|
|
164
165
|
throw new Error(`Job mode ${job.mode} not implemented`)
|
|
165
166
|
}
|
|
166
167
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
168
|
+
return vmBackup.run().catch(error => {
|
|
169
|
+
taskError = error
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
.then(result => {
|
|
173
|
+
if (taskError === undefined) {
|
|
170
174
|
task.success(result)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
vmBackupFailed(
|
|
175
|
+
} else {
|
|
176
|
+
// ending the task with error or not ending the task
|
|
177
|
+
vmBackupFailed(taskError, task)
|
|
174
178
|
}
|
|
175
179
|
})
|
|
176
180
|
.catch(noop) // errors are handled by logs
|
|
177
181
|
}),
|
|
178
|
-
error =>
|
|
179
|
-
getVmTask().run(() => {
|
|
180
|
-
vmBackupFailed(error)
|
|
181
|
-
})
|
|
182
|
+
error => vmBackupFailed(error, getVmTask())
|
|
182
183
|
)
|
|
183
184
|
}
|
|
184
185
|
const { concurrency } = settings
|
package/_runners/_Abstract.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import Disposable from 'promise-toolbox/Disposable'
|
|
2
2
|
import pTimeout from 'promise-toolbox/timeout'
|
|
3
3
|
import { compileTemplate } from '@xen-orchestra/template'
|
|
4
|
-
import { runTask } from './_runTask.mjs'
|
|
5
4
|
import { RemoteTimeoutError } from './_RemoteTimeoutError.mjs'
|
|
5
|
+
import { Task } from '@vates/task'
|
|
6
|
+
|
|
7
|
+
const noop = Function.prototype
|
|
6
8
|
|
|
7
9
|
export const DEFAULT_SETTINGS = {
|
|
8
10
|
getRemoteTimeout: 300e3,
|
|
@@ -36,13 +38,16 @@ export const Abstract = class AbstractRunner {
|
|
|
36
38
|
})
|
|
37
39
|
} catch (error) {
|
|
38
40
|
// See https://github.com/vatesfr/xen-orchestra/commit/6aa6cfba8ec939c0288f0fa740f6dfad98c43cbb
|
|
39
|
-
|
|
41
|
+
Task.run(
|
|
40
42
|
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
properties: {
|
|
44
|
+
id: remoteId,
|
|
45
|
+
name: 'get remote adapter',
|
|
46
|
+
type: 'remote',
|
|
47
|
+
},
|
|
43
48
|
},
|
|
44
49
|
() => Promise.reject(error)
|
|
45
|
-
)
|
|
50
|
+
).catch(noop)
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { asyncMap } from '@xen-orchestra/async-map'
|
|
2
|
+
import { Task } from '@vates/task'
|
|
2
3
|
|
|
3
4
|
import { DIR_XO_POOL_METADATA_BACKUPS } from '../RemoteAdapter.mjs'
|
|
4
5
|
import { forkStreamUnpipe } from './_forkStreamUnpipe.mjs'
|
|
5
6
|
import { formatFilenameDate } from '../_filenameDate.mjs'
|
|
6
|
-
import { Task } from '../Task.mjs'
|
|
7
7
|
|
|
8
8
|
export const PATH_DB_DUMP = '/pool/xmldbdump'
|
|
9
9
|
|
|
@@ -54,8 +54,8 @@ export class PoolMetadataBackup {
|
|
|
54
54
|
([remoteId, adapter]) =>
|
|
55
55
|
Task.run(
|
|
56
56
|
{
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
properties: {
|
|
58
|
+
name: `Starting metadata backup for the pool (${pool.$id}) for the remote (${remoteId}). (${job.id})`,
|
|
59
59
|
id: remoteId,
|
|
60
60
|
type: 'remote',
|
|
61
61
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { asyncMap } from '@xen-orchestra/async-map'
|
|
2
2
|
import { join } from '@xen-orchestra/fs/path'
|
|
3
|
+
import { Task } from '@vates/task'
|
|
3
4
|
|
|
4
5
|
import { DIR_XO_CONFIG_BACKUPS } from '../RemoteAdapter.mjs'
|
|
5
6
|
import { formatFilenameDate } from '../_filenameDate.mjs'
|
|
6
|
-
import { Task } from '../Task.mjs'
|
|
7
7
|
|
|
8
8
|
export class XoMetadataBackup {
|
|
9
9
|
constructor({ config, job, remoteAdapters, schedule, settings }) {
|
|
@@ -51,8 +51,8 @@ export class XoMetadataBackup {
|
|
|
51
51
|
([remoteId, adapter]) =>
|
|
52
52
|
Task.run(
|
|
53
53
|
{
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
properties: {
|
|
55
|
+
name: `Starting XO metadata backup for the remote (${remoteId}). (${job.id})`,
|
|
56
56
|
id: remoteId,
|
|
57
57
|
type: 'remote',
|
|
58
58
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createLogger } from '@xen-orchestra/log'
|
|
2
|
+
import { Task } from '@vates/task'
|
|
2
3
|
import keyBy from 'lodash/keyBy.js'
|
|
3
4
|
|
|
4
5
|
import { AbstractXapi } from './_AbstractXapi.mjs'
|
|
@@ -6,7 +7,6 @@ import { forkDeltaExport } from './_forkDeltaExport.mjs'
|
|
|
6
7
|
import { exportIncrementalVm } from '../../_incrementalVm.mjs'
|
|
7
8
|
import { IncrementalRemoteWriter } from '../_writers/IncrementalRemoteWriter.mjs'
|
|
8
9
|
import { IncrementalXapiWriter } from '../_writers/IncrementalXapiWriter.mjs'
|
|
9
|
-
import { Task } from '../../Task.mjs'
|
|
10
10
|
import {
|
|
11
11
|
DATETIME,
|
|
12
12
|
DELTA_CHAIN_LENGTH,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { asyncMap } from '@xen-orchestra/async-map'
|
|
2
2
|
import { createLogger } from '@xen-orchestra/log'
|
|
3
|
-
import { Task } from '
|
|
3
|
+
import { Task } from '@vates/task'
|
|
4
4
|
|
|
5
5
|
const { debug, warn } = createLogger('xo:backups:AbstractVmRunner')
|
|
6
6
|
|
|
@@ -83,7 +83,7 @@ export const Abstract = class AbstractVmBackupRunner {
|
|
|
83
83
|
// create a task to have an info in the logs and reports
|
|
84
84
|
return Task.run(
|
|
85
85
|
{
|
|
86
|
-
name: 'health check',
|
|
86
|
+
properties: { name: 'health check' },
|
|
87
87
|
},
|
|
88
88
|
() => {
|
|
89
89
|
Task.info(`This VM doesn't match the health check's tags for this schedule`)
|
|
@@ -4,12 +4,12 @@ import { decorateMethodsWith } from '@vates/decorate-with'
|
|
|
4
4
|
import { defer } from 'golike-defer'
|
|
5
5
|
import { Disposable } from 'promise-toolbox'
|
|
6
6
|
import { createPredicate } from 'value-matcher'
|
|
7
|
+
import { Task } from '@vates/task'
|
|
7
8
|
|
|
8
9
|
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
|
|
9
10
|
|
|
10
11
|
import { Abstract } from './_Abstract.mjs'
|
|
11
12
|
import { extractIdsFromSimplePattern } from '../../extractIdsFromSimplePattern.mjs'
|
|
12
|
-
import { Task } from '../../Task.mjs'
|
|
13
13
|
|
|
14
14
|
export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstract {
|
|
15
15
|
_filterPredicate
|
|
@@ -6,9 +6,9 @@ import { asyncMap } from '@xen-orchestra/async-map'
|
|
|
6
6
|
import { asyncEach } from '@vates/async-each'
|
|
7
7
|
import { decorateMethodsWith } from '@vates/decorate-with'
|
|
8
8
|
import { defer } from 'golike-defer'
|
|
9
|
+
import { Task } from '@vates/task'
|
|
9
10
|
|
|
10
11
|
import { getOldEntries } from '../../_getOldEntries.mjs'
|
|
11
|
-
import { Task } from '../../Task.mjs'
|
|
12
12
|
import { Abstract } from './_Abstract.mjs'
|
|
13
13
|
import {
|
|
14
14
|
COPY_OF,
|
|
@@ -186,7 +186,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
|
|
186
186
|
const settings = this._settings
|
|
187
187
|
|
|
188
188
|
if (await this._mustDoSnapshot()) {
|
|
189
|
-
await Task.run({ name: 'snapshot' }, async () => {
|
|
189
|
+
await Task.run({ properties: { name: 'snapshot' } }, async () => {
|
|
190
190
|
if (!settings.bypassVdiChainsCheck) {
|
|
191
191
|
await vm.$assertHealthyVdiChains()
|
|
192
192
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { Task } from '@vates/task'
|
|
2
|
+
|
|
1
3
|
import { formatFilenameDate } from '../../_filenameDate.mjs'
|
|
2
4
|
import { getOldEntries } from '../../_getOldEntries.mjs'
|
|
3
|
-
import { Task } from '../../Task.mjs'
|
|
4
5
|
|
|
5
6
|
import { MixinRemoteWriter } from './_MixinRemoteWriter.mjs'
|
|
6
7
|
import { AbstractFullWriter } from './_AbstractFullWriter.mjs'
|
|
@@ -9,11 +10,11 @@ export class FullRemoteWriter extends MixinRemoteWriter(AbstractFullWriter) {
|
|
|
9
10
|
constructor(props) {
|
|
10
11
|
super(props)
|
|
11
12
|
|
|
12
|
-
this.run = Task.
|
|
13
|
+
this.run = Task.wrap(
|
|
13
14
|
{
|
|
14
|
-
|
|
15
|
-
data: {
|
|
15
|
+
properties: {
|
|
16
16
|
id: props.remoteId,
|
|
17
|
+
name: 'export',
|
|
17
18
|
type: 'remote',
|
|
18
19
|
|
|
19
20
|
// necessary?
|
|
@@ -64,7 +65,7 @@ export class FullRemoteWriter extends MixinRemoteWriter(AbstractFullWriter) {
|
|
|
64
65
|
await deleteOldBackups()
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
await Task.run({ name: 'transfer' }, async () => {
|
|
68
|
+
await Task.run({ properties: { name: 'transfer' } }, async () => {
|
|
68
69
|
await adapter.outputStream(dataFilename, stream, {
|
|
69
70
|
maxStreamLength,
|
|
70
71
|
streamLength,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import ignoreErrors from 'promise-toolbox/ignoreErrors'
|
|
2
2
|
import { asyncMap, asyncMapSettled } from '@xen-orchestra/async-map'
|
|
3
|
+
import { Task } from '@vates/task'
|
|
3
4
|
|
|
4
5
|
import { formatFilenameDate } from '../../_filenameDate.mjs'
|
|
5
6
|
import { getOldEntries } from '../../_getOldEntries.mjs'
|
|
6
|
-
import { Task } from '../../Task.mjs'
|
|
7
7
|
|
|
8
8
|
import { AbstractFullWriter } from './_AbstractFullWriter.mjs'
|
|
9
9
|
import { MixinXapiWriter } from './_MixinXapiWriter.mjs'
|
|
@@ -14,11 +14,11 @@ export class FullXapiWriter extends MixinXapiWriter(AbstractFullWriter) {
|
|
|
14
14
|
constructor(props) {
|
|
15
15
|
super(props)
|
|
16
16
|
|
|
17
|
-
this.run = Task.
|
|
17
|
+
this.run = Task.wrap(
|
|
18
18
|
{
|
|
19
|
-
|
|
20
|
-
data: {
|
|
19
|
+
properties: {
|
|
21
20
|
id: props.sr.uuid,
|
|
21
|
+
name: 'export',
|
|
22
22
|
name_label: this._sr.name_label,
|
|
23
23
|
type: 'SR',
|
|
24
24
|
|
|
@@ -52,7 +52,7 @@ export class FullXapiWriter extends MixinXapiWriter(AbstractFullWriter) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
let targetVmRef
|
|
55
|
-
await Task.run({ name: 'transfer' }, async () => {
|
|
55
|
+
await Task.run({ properties: { name: 'transfer' } }, async () => {
|
|
56
56
|
targetVmRef = await xapi.VM_import(stream, sr.$ref, vm =>
|
|
57
57
|
Promise.all([
|
|
58
58
|
!_warmMigration && vm.add_tags('Disaster Recovery'),
|
|
@@ -7,10 +7,10 @@ import { createLogger } from '@xen-orchestra/log'
|
|
|
7
7
|
import { decorateClass } from '@vates/decorate-with'
|
|
8
8
|
import { defer } from 'golike-defer'
|
|
9
9
|
import { dirname, basename } from 'node:path'
|
|
10
|
+
import { Task } from '@vates/task'
|
|
10
11
|
|
|
11
12
|
import { formatFilenameDate } from '../../_filenameDate.mjs'
|
|
12
13
|
import { getOldEntries } from '../../_getOldEntries.mjs'
|
|
13
|
-
import { Task } from '../../Task.mjs'
|
|
14
14
|
|
|
15
15
|
import { MixinRemoteWriter } from './_MixinRemoteWriter.mjs'
|
|
16
16
|
import { AbstractIncrementalWriter } from './_AbstractIncrementalWriter.mjs'
|
|
@@ -72,19 +72,20 @@ export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrement
|
|
|
72
72
|
prepare({ isFull }) {
|
|
73
73
|
// create the task related to this export and ensure all methods are called in this context
|
|
74
74
|
const task = new Task({
|
|
75
|
-
|
|
76
|
-
data: {
|
|
75
|
+
properties: {
|
|
77
76
|
id: this._remoteId,
|
|
78
77
|
isFull,
|
|
78
|
+
name: 'export',
|
|
79
79
|
type: 'remote',
|
|
80
80
|
},
|
|
81
81
|
})
|
|
82
|
-
this.
|
|
83
|
-
this.
|
|
84
|
-
this.
|
|
85
|
-
this.
|
|
82
|
+
this._prepare = task.wrapInside(this._prepare)
|
|
83
|
+
this.transfer = task.wrapInside(this.transfer)
|
|
84
|
+
this.healthCheck = task.wrapInside(this.healthCheck)
|
|
85
|
+
this.cleanup = task.wrapInside(this.cleanup)
|
|
86
|
+
this.afterBackup = task.wrap(this.afterBackup)
|
|
86
87
|
|
|
87
|
-
return
|
|
88
|
+
return this._prepare()
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
async _prepare() {
|
|
@@ -223,7 +224,7 @@ export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrement
|
|
|
223
224
|
vtpms: deltaExport.vtpms,
|
|
224
225
|
}
|
|
225
226
|
let size = 0
|
|
226
|
-
await Task.run({ name: 'transfer' }, async () => {
|
|
227
|
+
await Task.run({ properties: { name: 'transfer' } }, async () => {
|
|
227
228
|
await asyncEach(
|
|
228
229
|
Object.entries(deltaExport.disks),
|
|
229
230
|
async ([diskRef, disk]) => {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import humanFormat from 'human-format'
|
|
2
2
|
|
|
3
3
|
import { asyncMapSettled } from '@xen-orchestra/async-map'
|
|
4
|
+
import { Task } from '@vates/task'
|
|
4
5
|
import ignoreErrors from 'promise-toolbox/ignoreErrors'
|
|
5
6
|
|
|
6
7
|
import { getOldEntries } from '../../_getOldEntries.mjs'
|
|
7
8
|
import { importIncrementalVm } from '../../_incrementalVm.mjs'
|
|
8
|
-
import { Task } from '../../Task.mjs'
|
|
9
9
|
|
|
10
10
|
import { AbstractIncrementalWriter } from './_AbstractIncrementalWriter.mjs'
|
|
11
11
|
import { MixinXapiWriter } from './_MixinXapiWriter.mjs'
|
|
@@ -65,8 +65,9 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
65
65
|
snapshotCandidates,
|
|
66
66
|
async snapshot => {
|
|
67
67
|
let diffDisk
|
|
68
|
+
let activeVdi
|
|
68
69
|
try {
|
|
69
|
-
|
|
70
|
+
activeVdi = sr.$xapi.getObject(snapshot.$snapshot_of)
|
|
70
71
|
const userVbds = activeVdi.$VBDs?.filter(vbd => vbd.$VM && !vbd.$VM.is_control_domain) ?? []
|
|
71
72
|
if (userVbds.length !== 1) {
|
|
72
73
|
debug('checkBaseVdis, share vbd ', { ref: snapshot.$ref, userVbds })
|
|
@@ -81,7 +82,12 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
81
82
|
// but it indicates the users played with the blocked operations
|
|
82
83
|
return
|
|
83
84
|
}
|
|
84
|
-
diffDisk = new XapiDiskSource({
|
|
85
|
+
diffDisk = new XapiDiskSource({
|
|
86
|
+
xapi: sr.$xapi,
|
|
87
|
+
vdiRef: activeVdi.$ref,
|
|
88
|
+
baseRef: snapshot.$ref,
|
|
89
|
+
onlyListChangedBlocks: true,
|
|
90
|
+
})
|
|
85
91
|
await diffDisk.init()
|
|
86
92
|
if (diffDisk.getBlockIndexes().length === 0) {
|
|
87
93
|
const sourceUuid = snapshot.other_config?.[COPY_OF]
|
|
@@ -103,6 +109,10 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
103
109
|
return
|
|
104
110
|
} finally {
|
|
105
111
|
await diffDisk?.close().catch(error => debug('checkBaseVdis, error closing', error))
|
|
112
|
+
await sr.$xapi.VDI_disconnectFromControlDomain(snapshot.$ref)
|
|
113
|
+
if (activeVdi !== undefined) {
|
|
114
|
+
await sr.$xapi.VDI_disconnectFromControlDomain(activeVdi.$ref)
|
|
115
|
+
}
|
|
106
116
|
}
|
|
107
117
|
},
|
|
108
118
|
{
|
|
@@ -141,20 +151,24 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
141
151
|
prepare({ isFull }) {
|
|
142
152
|
// create the task related to this export and ensure all methods are called in this context
|
|
143
153
|
const task = new Task({
|
|
144
|
-
|
|
145
|
-
data: {
|
|
154
|
+
properties: {
|
|
146
155
|
id: this._sr.uuid,
|
|
147
156
|
isFull,
|
|
157
|
+
name: 'export',
|
|
148
158
|
name_label: this._sr.name_label,
|
|
149
159
|
type: 'SR',
|
|
150
160
|
},
|
|
151
161
|
})
|
|
152
|
-
|
|
153
|
-
this.transfer = task.
|
|
154
|
-
|
|
155
|
-
|
|
162
|
+
this._prepare = task.wrapInside(this._prepare)
|
|
163
|
+
this.transfer = task.wrapInside(this.transfer)
|
|
164
|
+
if (this._healthCheckSr !== undefined) {
|
|
165
|
+
this.cleanup = task.wrapInside(this.cleanup)
|
|
166
|
+
this.healthCheck = task.wrap(this.healthCheck)
|
|
167
|
+
} else {
|
|
168
|
+
this.cleanup = task.wrap(this.cleanup)
|
|
169
|
+
}
|
|
156
170
|
|
|
157
|
-
return
|
|
171
|
+
return this._prepare(isFull)
|
|
158
172
|
}
|
|
159
173
|
|
|
160
174
|
async _prepare(isFull) {
|
|
@@ -255,7 +269,7 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
255
269
|
const { uuid: srUuid, $xapi: xapi } = sr
|
|
256
270
|
|
|
257
271
|
let targetVmRef
|
|
258
|
-
await Task.run({ name: 'transfer' }, async () => {
|
|
272
|
+
await Task.run({ properties: { name: 'transfer' } }, async () => {
|
|
259
273
|
targetVmRef = await importIncrementalVm(this.#decorateVmMetadata(deltaExport, timestamp), sr, {
|
|
260
274
|
targetRef: this._targetVmRef,
|
|
261
275
|
})
|
|
@@ -279,7 +293,7 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
279
293
|
` -- last replication: ${formatFilenameDate(timestamp)} ${humanFormat.bytes(size)} read`
|
|
280
294
|
)
|
|
281
295
|
// take a snapshot to ensure these data are not modified until next snapshot
|
|
282
|
-
await Task.run({ name: 'target snapshot' }, async () => {
|
|
296
|
+
await Task.run({ properties: { name: 'target snapshot' } }, async () => {
|
|
283
297
|
await xapi.VM_snapshot(targetVmRef, {
|
|
284
298
|
name_label: `${vm.name_label} - ${job.name} / ${schedule.name} ${formatFilenameDate(timestamp)}`,
|
|
285
299
|
})
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createLogger } from '@xen-orchestra/log'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
+
import { Task } from '@vates/task'
|
|
3
4
|
import assert from 'node:assert'
|
|
4
5
|
|
|
5
6
|
import { formatFilenameDate } from '../../_filenameDate.mjs'
|
|
6
7
|
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
|
|
7
8
|
import { HealthCheckVmBackup } from '../../HealthCheckVmBackup.mjs'
|
|
8
9
|
import { ImportVmBackup } from '../../ImportVmBackup.mjs'
|
|
9
|
-
import { Task } from '../../Task.mjs'
|
|
10
10
|
import * as MergeWorker from '../../merge-worker/index.mjs'
|
|
11
11
|
import ms from 'ms'
|
|
12
12
|
import { getEntryStatus } from '../../_getOldEntries.mjs'
|
|
@@ -28,7 +28,7 @@ export const MixinRemoteWriter = (BaseClass = Object) =>
|
|
|
28
28
|
|
|
29
29
|
async _cleanVm(options) {
|
|
30
30
|
try {
|
|
31
|
-
return await Task.run({ name: 'clean-vm' }, () => {
|
|
31
|
+
return await Task.run({ properties: { name: 'clean-vm' } }, () => {
|
|
32
32
|
return this._adapter.cleanVm(this._vmBackupDir, {
|
|
33
33
|
...options,
|
|
34
34
|
fixMetadata: true,
|
|
@@ -100,7 +100,7 @@ export const MixinRemoteWriter = (BaseClass = Object) =>
|
|
|
100
100
|
}
|
|
101
101
|
return Task.run(
|
|
102
102
|
{
|
|
103
|
-
name: 'health check',
|
|
103
|
+
properties: { name: 'health check' },
|
|
104
104
|
},
|
|
105
105
|
async () => {
|
|
106
106
|
const xapi = sr.$xapi
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from 'node:assert/strict'
|
|
2
|
+
import { Task } from '@vates/task'
|
|
2
3
|
|
|
3
4
|
import { HealthCheckVmBackup } from '../../HealthCheckVmBackup.mjs'
|
|
4
|
-
import { Task } from '../../Task.mjs'
|
|
5
5
|
import ms from 'ms'
|
|
6
6
|
|
|
7
7
|
export const MixinXapiWriter = (BaseClass = Object) =>
|
|
@@ -33,7 +33,7 @@ export const MixinXapiWriter = (BaseClass = Object) =>
|
|
|
33
33
|
// copy VM
|
|
34
34
|
return Task.run(
|
|
35
35
|
{
|
|
36
|
-
name: 'health check',
|
|
36
|
+
properties: { name: 'health check' },
|
|
37
37
|
},
|
|
38
38
|
async () => {
|
|
39
39
|
const { $xapi: xapi } = sr
|
|
@@ -47,12 +47,12 @@ export const MixinXapiWriter = (BaseClass = Object) =>
|
|
|
47
47
|
}
|
|
48
48
|
if (await this.#isAlreadyOnHealthCheckSr(baseVm)) {
|
|
49
49
|
healthCheckVmRef = await Task.run(
|
|
50
|
-
{ name: 'cloning-vm' },
|
|
50
|
+
{ properties: { name: 'cloning-vm' } },
|
|
51
51
|
async () => await xapi.callAsync('VM.clone', this._targetVmRef, `Health Check - ${baseVm.name_label}`)
|
|
52
52
|
)
|
|
53
53
|
} else {
|
|
54
54
|
healthCheckVmRef = await Task.run(
|
|
55
|
-
{ name: 'copying-vm' },
|
|
55
|
+
{ properties: { name: 'copying-vm' } },
|
|
56
56
|
async () =>
|
|
57
57
|
await xapi.callAsync(
|
|
58
58
|
'VM.copy',
|
|
@@ -10,6 +10,7 @@ import { createLogger } from '@xen-orchestra/log'
|
|
|
10
10
|
|
|
11
11
|
import { basename, dirname } from 'path'
|
|
12
12
|
import { asyncEach } from '@vates/async-each'
|
|
13
|
+
import { relativeFromFile } from '@xen-orchestra/fs/path'
|
|
13
14
|
|
|
14
15
|
// @ts-ignore
|
|
15
16
|
const { warn } = createLogger('remote-disk:merge')
|
|
@@ -18,6 +19,7 @@ const { warn } = createLogger('remote-disk:merge')
|
|
|
18
19
|
* @typedef {Object} MergeState
|
|
19
20
|
* @property {{ uuid: string }} child
|
|
20
21
|
* @property {{ uuid: string }} parent
|
|
22
|
+
* @property { string[] | undefined} chain
|
|
21
23
|
* @property {number} currentBlock
|
|
22
24
|
* @property {number} mergedDataSize
|
|
23
25
|
* @property {'mergeBlocks' | 'cleanup'} step
|
|
@@ -32,6 +34,7 @@ export class MergeRemoteDisk {
|
|
|
32
34
|
#state = {
|
|
33
35
|
child: { uuid: '0' },
|
|
34
36
|
parent: { uuid: '0' },
|
|
37
|
+
chain: undefined,
|
|
35
38
|
currentBlock: 0,
|
|
36
39
|
mergedDataSize: 0,
|
|
37
40
|
step: 'mergeBlocks',
|
|
@@ -205,6 +208,9 @@ export class MergeRemoteDisk {
|
|
|
205
208
|
} else {
|
|
206
209
|
this.#state.child = { uuid: childDisk.getUuid() ?? undefined }
|
|
207
210
|
this.#state.parent = { uuid: parentDisk.getUuid() ?? undefined }
|
|
211
|
+
this.#state.chain = [parentDisk.getPath(), ...childDisk.getPaths()].map(path =>
|
|
212
|
+
relativeFromFile(this.#statePath, path)
|
|
213
|
+
)
|
|
208
214
|
|
|
209
215
|
// Finds first allocated block for the 2 following loops
|
|
210
216
|
while (this.#state.currentBlock < getMaxBlockCount && !childDisk.hasBlock(this.#state.currentBlock)) {
|
package/disks/RemoteDisk.mjs
CHANGED
|
@@ -55,6 +55,16 @@ export class RemoteDisk extends RandomAccessDisk {
|
|
|
55
55
|
throw new Error(`getPath must be implemented`)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Abstract
|
|
60
|
+
* Returns an array of disk paths.
|
|
61
|
+
*
|
|
62
|
+
* @returns {string[]}
|
|
63
|
+
*/
|
|
64
|
+
getPaths() {
|
|
65
|
+
throw new Error(`getPaths must be implemented`)
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
/**
|
|
59
69
|
* Abstract
|
|
60
70
|
* @returns {string}
|
package/disks/RemoteVhdDisk.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { DISK_TYPES } from 'vhd-lib/_constants.js'
|
|
|
16
16
|
import { isVhdAlias, resolveVhdAlias } from 'vhd-lib/aliases.js'
|
|
17
17
|
import { stringify } from 'uuid'
|
|
18
18
|
import { dirname, join } from 'node:path'
|
|
19
|
+
import { RemoteVhdDiskChain } from './RemoteVhdDiskChain.mjs'
|
|
19
20
|
|
|
20
21
|
export class RemoteVhdDisk extends RemoteDisk {
|
|
21
22
|
/**
|
|
@@ -140,6 +141,15 @@ export class RemoteVhdDisk extends RemoteDisk {
|
|
|
140
141
|
return this.#path
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Returns the disk path in an array.
|
|
146
|
+
*
|
|
147
|
+
* @returns {string[]}
|
|
148
|
+
*/
|
|
149
|
+
getPaths() {
|
|
150
|
+
return [this.getPath()]
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
/**
|
|
144
154
|
* @returns {string}
|
|
145
155
|
*/
|
|
@@ -259,7 +269,11 @@ export class RemoteVhdDisk extends RemoteDisk {
|
|
|
259
269
|
* @returns {Promise<number>} blockSize
|
|
260
270
|
*/
|
|
261
271
|
async mergeBlock(childDisk, index, isResumingMerge) {
|
|
262
|
-
if (
|
|
272
|
+
if (
|
|
273
|
+
(childDisk instanceof RemoteVhdDisk || childDisk instanceof RemoteVhdDiskChain) &&
|
|
274
|
+
(await this.isDirectory()) &&
|
|
275
|
+
(await childDisk.isDirectory())
|
|
276
|
+
) {
|
|
263
277
|
try {
|
|
264
278
|
await this.#handler.rename(childDisk.getBlockPath(index), this.getBlockPath(index))
|
|
265
279
|
|
|
@@ -123,6 +123,15 @@ export class RemoteVhdDiskChain extends RemoteDisk {
|
|
|
123
123
|
return this.#disks[this.#disks.length - 1].getPath()
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Disk chains return an array of disk paths.
|
|
128
|
+
*
|
|
129
|
+
* @returns {string[]}
|
|
130
|
+
*/
|
|
131
|
+
getPaths() {
|
|
132
|
+
return this.#disks.map(disk => disk.getPath())
|
|
133
|
+
}
|
|
134
|
+
|
|
126
135
|
/**
|
|
127
136
|
* @returns {string}
|
|
128
137
|
*/
|
|
@@ -134,12 +143,7 @@ export class RemoteVhdDiskChain extends RemoteDisk {
|
|
|
134
143
|
* @returns {Promise<boolean>} canMergeConcurently
|
|
135
144
|
*/
|
|
136
145
|
async canMergeConcurently() {
|
|
137
|
-
|
|
138
|
-
if (!(await disk.isDirectory())) {
|
|
139
|
-
return true
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return false
|
|
146
|
+
return this.isDirectory()
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
/**
|
|
@@ -221,6 +225,21 @@ export class RemoteVhdDiskChain extends RemoteDisk {
|
|
|
221
225
|
throw new Error(`Can't merge block into a disk chain`)
|
|
222
226
|
}
|
|
223
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Gets a specific block path from the VHD directory disk.
|
|
230
|
+
* @param {number} index
|
|
231
|
+
* @returns {string} blockPath
|
|
232
|
+
*/
|
|
233
|
+
getBlockPath(index) {
|
|
234
|
+
for (const disk of [...this.#disks].reverse()) {
|
|
235
|
+
if (disk.hasBlock(index)) {
|
|
236
|
+
return disk.getBlockPath(index)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw new Error(`Block ${index} not found in chain`)
|
|
241
|
+
}
|
|
242
|
+
|
|
224
243
|
/**
|
|
225
244
|
* @returns {VhdFooter}
|
|
226
245
|
*/
|
|
@@ -269,4 +288,17 @@ export class RemoteVhdDiskChain extends RemoteDisk {
|
|
|
269
288
|
await disk.unlink()
|
|
270
289
|
}
|
|
271
290
|
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Check if all the disks in the chain are VHD directories.
|
|
294
|
+
* @returns {Promise<boolean>}
|
|
295
|
+
*/
|
|
296
|
+
async isDirectory() {
|
|
297
|
+
for (const disk of this.#disks) {
|
|
298
|
+
if (!(await disk.isDirectory())) {
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return true
|
|
303
|
+
}
|
|
272
304
|
}
|
package/package.json
CHANGED
|
@@ -8,32 +8,34 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.72.0",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.18"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"postversion": "npm publish --access public",
|
|
17
|
+
"test": "node --test",
|
|
17
18
|
"test-integration": "node --test *.integ.mjs"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
21
|
"@iarna/toml": "^2.2.5",
|
|
21
22
|
"@kldzj/stream-throttle": "^1.1.1",
|
|
22
|
-
"@vates/async-each": "^1.0.
|
|
23
|
+
"@vates/async-each": "^1.0.3",
|
|
23
24
|
"@vates/cached-dns.lookup": "^1.0.0",
|
|
24
25
|
"@vates/compose": "^2.1.0",
|
|
25
26
|
"@vates/decorate-with": "^2.1.0",
|
|
26
27
|
"@vates/disposable": "^0.1.6",
|
|
27
28
|
"@vates/fuse-vhd": "^2.1.2",
|
|
28
|
-
"@vates/generator-toolbox": "^1.1.
|
|
29
|
-
"@vates/nbd-client": "^3.
|
|
29
|
+
"@vates/generator-toolbox": "^1.1.2",
|
|
30
|
+
"@vates/nbd-client": "^3.4.0",
|
|
30
31
|
"@vates/parse-duration": "^0.1.1",
|
|
31
|
-
"@
|
|
32
|
-
"@xen-orchestra/
|
|
33
|
-
"@xen-orchestra/
|
|
34
|
-
"@xen-orchestra/
|
|
35
|
-
"@xen-orchestra/
|
|
36
|
-
"@xen-orchestra/
|
|
32
|
+
"@vates/task": "^0.7.0",
|
|
33
|
+
"@xen-orchestra/async-map": "^0.1.3",
|
|
34
|
+
"@xen-orchestra/disk-transform": "^1.2.3",
|
|
35
|
+
"@xen-orchestra/fs": "^4.8.0",
|
|
36
|
+
"@xen-orchestra/log": "^0.7.2",
|
|
37
|
+
"@xen-orchestra/qcow2": "^1.3.0",
|
|
38
|
+
"@xen-orchestra/template": "^0.1.1",
|
|
37
39
|
"app-conf": "^3.0.0",
|
|
38
40
|
"compare-versions": "^6.0.0",
|
|
39
41
|
"d3-time-format": "^4.1.0",
|
|
@@ -41,7 +43,7 @@
|
|
|
41
43
|
"golike-defer": "^0.5.1",
|
|
42
44
|
"human-format": "^1.2.0",
|
|
43
45
|
"limit-concurrency-decorator": "^0.6.0",
|
|
44
|
-
"lodash": "^4.
|
|
46
|
+
"lodash": "^4.18.0",
|
|
45
47
|
"moment-timezone": "^0.5.46",
|
|
46
48
|
"ms": "^2.1.3",
|
|
47
49
|
"node-zone": "^0.4.0",
|
|
@@ -51,8 +53,8 @@
|
|
|
51
53
|
"tar": "^7.5.3",
|
|
52
54
|
"uuid": "^9.0.0",
|
|
53
55
|
"value-matcher": "^0.2.0",
|
|
54
|
-
"vhd-lib": "^4.
|
|
55
|
-
"xen-api": "^4.7.
|
|
56
|
+
"vhd-lib": "^4.16.0",
|
|
57
|
+
"xen-api": "^4.7.7",
|
|
56
58
|
"yazl": "^2.5.1"
|
|
57
59
|
},
|
|
58
60
|
"devDependencies": {
|
|
@@ -62,7 +64,7 @@
|
|
|
62
64
|
"tmp": "^0.2.1"
|
|
63
65
|
},
|
|
64
66
|
"peerDependencies": {
|
|
65
|
-
"@xen-orchestra/xapi": "^8.7.
|
|
67
|
+
"@xen-orchestra/xapi": "^8.7.2"
|
|
66
68
|
},
|
|
67
69
|
"license": "AGPL-3.0-or-later",
|
|
68
70
|
"author": {
|
package/Task.mjs
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import CancelToken from 'promise-toolbox/CancelToken'
|
|
2
|
-
import Zone from 'node-zone'
|
|
3
|
-
|
|
4
|
-
const logAfterEnd = log => {
|
|
5
|
-
const error = new Error('task has already ended')
|
|
6
|
-
error.log = log
|
|
7
|
-
throw error
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
const noop = Function.prototype
|
|
11
|
-
|
|
12
|
-
const serializeErrors = errors => (Array.isArray(errors) ? errors.map(serializeError) : errors)
|
|
13
|
-
|
|
14
|
-
// Create a serializable object from an error.
|
|
15
|
-
//
|
|
16
|
-
// Otherwise some fields might be non-enumerable and missing from logs.
|
|
17
|
-
const serializeError = error =>
|
|
18
|
-
error instanceof Error
|
|
19
|
-
? {
|
|
20
|
-
...error, // Copy enumerable properties.
|
|
21
|
-
code: error.code,
|
|
22
|
-
errors: serializeErrors(error.errors), // supports AggregateError
|
|
23
|
-
message: error.message,
|
|
24
|
-
name: error.name,
|
|
25
|
-
stack: error.stack,
|
|
26
|
-
}
|
|
27
|
-
: error
|
|
28
|
-
|
|
29
|
-
const $$task = Symbol('@xen-orchestra/backups/Task')
|
|
30
|
-
|
|
31
|
-
export class Task {
|
|
32
|
-
static get cancelToken() {
|
|
33
|
-
const task = Zone.current.data[$$task]
|
|
34
|
-
return task !== undefined ? task.#cancelToken : CancelToken.none
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
static run(opts, fn) {
|
|
38
|
-
return new this(opts).run(fn, true)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
static wrapFn(opts, fn) {
|
|
42
|
-
// compatibility with @decorateWith
|
|
43
|
-
if (typeof fn !== 'function') {
|
|
44
|
-
;[fn, opts] = [opts, fn]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return function () {
|
|
48
|
-
return Task.run(typeof opts === 'function' ? opts.apply(this, arguments) : opts, () => fn.apply(this, arguments))
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
#cancelToken
|
|
53
|
-
#id = Math.random().toString(36).slice(2)
|
|
54
|
-
#onLog
|
|
55
|
-
#zone
|
|
56
|
-
|
|
57
|
-
constructor({ name, data, onLog }) {
|
|
58
|
-
let parentCancelToken, parentId
|
|
59
|
-
if (onLog === undefined) {
|
|
60
|
-
const parent = Zone.current.data[$$task]
|
|
61
|
-
if (parent === undefined) {
|
|
62
|
-
onLog = noop
|
|
63
|
-
} else {
|
|
64
|
-
onLog = log => parent.#onLog(log)
|
|
65
|
-
parentCancelToken = parent.#cancelToken
|
|
66
|
-
parentId = parent.#id
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const zone = Zone.current.fork('@xen-orchestra/backups/Task')
|
|
71
|
-
zone.data[$$task] = this
|
|
72
|
-
this.#zone = zone
|
|
73
|
-
|
|
74
|
-
const { cancel, token } = CancelToken.source(parentCancelToken && [parentCancelToken])
|
|
75
|
-
this.#cancelToken = token
|
|
76
|
-
this.cancel = cancel
|
|
77
|
-
|
|
78
|
-
this.#onLog = onLog
|
|
79
|
-
|
|
80
|
-
this.#log('start', {
|
|
81
|
-
data,
|
|
82
|
-
message: name,
|
|
83
|
-
parentId,
|
|
84
|
-
})
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
failure(error) {
|
|
88
|
-
this.#end('failure', serializeError(error))
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
info(message, data) {
|
|
92
|
-
this.#log('info', { data, message })
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Run a function in the context of this task
|
|
97
|
-
*
|
|
98
|
-
* In case of error, the task will be failed.
|
|
99
|
-
*
|
|
100
|
-
* @typedef Result
|
|
101
|
-
* @param {() => Result} fn
|
|
102
|
-
* @param {boolean} last - Whether the task should succeed if there is no error
|
|
103
|
-
* @returns Result
|
|
104
|
-
*/
|
|
105
|
-
run(fn, last = false) {
|
|
106
|
-
return this.#zone.run(() => {
|
|
107
|
-
try {
|
|
108
|
-
const result = fn()
|
|
109
|
-
let then
|
|
110
|
-
if (result != null && typeof (then = result.then) === 'function') {
|
|
111
|
-
then.call(result, last && (value => this.success(value)), error => this.failure(error))
|
|
112
|
-
} else if (last) {
|
|
113
|
-
this.success(result)
|
|
114
|
-
}
|
|
115
|
-
return result
|
|
116
|
-
} catch (error) {
|
|
117
|
-
this.failure(error)
|
|
118
|
-
throw error
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
success(value) {
|
|
124
|
-
this.#end('success', value)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
warning(message, data) {
|
|
128
|
-
this.#log('warning', { data, message })
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
wrapFn(fn, last) {
|
|
132
|
-
const task = this
|
|
133
|
-
return function () {
|
|
134
|
-
return task.run(() => fn.apply(this, arguments), last)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
#end(status, result) {
|
|
139
|
-
this.#log('end', { result, status })
|
|
140
|
-
this.#onLog = logAfterEnd
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
#log(event, props) {
|
|
144
|
-
this.#onLog({
|
|
145
|
-
...props,
|
|
146
|
-
event,
|
|
147
|
-
taskId: this.#id,
|
|
148
|
-
timestamp: Date.now(),
|
|
149
|
-
})
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
for (const method of ['info', 'warning']) {
|
|
154
|
-
Task[method] = (...args) => Zone.current.data[$$task]?.[method](...args)
|
|
155
|
-
}
|