@xen-orchestra/backups 0.14.0 → 0.16.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.js CHANGED
@@ -3,19 +3,19 @@ const Disposable = require('promise-toolbox/Disposable.js')
3
3
  const fromCallback = require('promise-toolbox/fromCallback.js')
4
4
  const fromEvent = require('promise-toolbox/fromEvent.js')
5
5
  const pDefer = require('promise-toolbox/defer.js')
6
- const pump = require('pump')
7
- const { basename, dirname, join, normalize, resolve } = require('path')
6
+ const { dirname, join, normalize, resolve } = require('path')
8
7
  const { createLogger } = require('@xen-orchestra/log')
9
- const { createSyntheticStream, mergeVhd, default: Vhd } = require('vhd-lib')
8
+ const { Constants, createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdSynthetic } = require('vhd-lib')
10
9
  const { deduped } = require('@vates/disposable/deduped.js')
11
10
  const { execFile } = require('child_process')
12
11
  const { readdir, stat } = require('fs-extra')
12
+ const { v4: uuidv4 } = require('uuid')
13
13
  const { ZipFile } = require('yazl')
14
14
 
15
15
  const { BACKUP_DIR } = require('./_getVmBackupDir.js')
16
16
  const { cleanVm } = require('./_cleanVm.js')
17
17
  const { getTmpDir } = require('./_getTmpDir.js')
18
- const { isMetadataFile, isVhdFile } = require('./_backupType.js')
18
+ const { isMetadataFile } = require('./_backupType.js')
19
19
  const { isValidXva } = require('./_isValidXva.js')
20
20
  const { listPartitions, LVM_PARTITION_TYPE } = require('./_listPartitions.js')
21
21
  const { lvs, pvs } = require('./_lvm.js')
@@ -77,48 +77,6 @@ class RemoteAdapter {
77
77
  return this._handler
78
78
  }
79
79
 
80
- async _deleteVhd(path) {
81
- const handler = this._handler
82
- const vhds = await asyncMapSettled(
83
- await handler.list(dirname(path), {
84
- filter: isVhdFile,
85
- prependDir: true,
86
- }),
87
- async path => {
88
- try {
89
- const vhd = new Vhd(handler, path)
90
- await vhd.readHeaderAndFooter()
91
- return {
92
- footer: vhd.footer,
93
- header: vhd.header,
94
- path,
95
- }
96
- } catch (error) {
97
- // Do not fail on corrupted VHDs (usually uncleaned temporary files),
98
- // they are probably inconsequent to the backup process and should not
99
- // fail it.
100
- warn(`BackupNg#_deleteVhd ${path}`, { error })
101
- }
102
- }
103
- )
104
- const base = basename(path)
105
- const child = vhds.find(_ => _ !== undefined && _.header.parentUnicodeName === base)
106
- if (child === undefined) {
107
- await handler.unlink(path)
108
- return 0
109
- }
110
-
111
- try {
112
- const childPath = child.path
113
- const mergedDataSize = await mergeVhd(handler, path, handler, childPath)
114
- await handler.rename(path, childPath)
115
- return mergedDataSize
116
- } catch (error) {
117
- handler.unlink(path).catch(warn)
118
- throw error
119
- }
120
- }
121
-
122
80
  async _findPartition(devicePath, partitionId) {
123
81
  const partitions = await listPartitions(devicePath)
124
82
  const partition = partitions.find(_ => _.id === partitionId)
@@ -255,7 +213,7 @@ class RemoteAdapter {
255
213
  const handler = this._handler
256
214
 
257
215
  // unused VHDs will be detected by `cleanVm`
258
- await asyncMapSettled(backups, ({ _filename }) => handler.unlink(_filename))
216
+ await asyncMapSettled(backups, ({ _filename }) => VhdAbstract.unlink(handler, _filename))
259
217
  }
260
218
 
261
219
  async deleteMetadataBackup(backupId) {
@@ -354,6 +312,17 @@ class RemoteAdapter {
354
312
  return yield this._getPartition(devicePath, await this._findPartition(devicePath, partitionId))
355
313
  }
356
314
 
315
+ // this function will be the one where we plug the logic of the storage format by fs type/user settings
316
+
317
+ // if the file is named .vhd => vhd
318
+ // if the file is named alias.vhd => alias to a vhd
319
+ getVhdFileName(baseName) {
320
+ if (this._handler.type === 's3') {
321
+ return `${baseName}.alias.vhd` // we want an alias to a vhddirectory
322
+ }
323
+ return `${baseName}.vhd`
324
+ }
325
+
357
326
  async listAllVmBackups() {
358
327
  const handler = this._handler
359
328
 
@@ -498,6 +467,24 @@ class RemoteAdapter {
498
467
  return backups.sort(compareTimestamp)
499
468
  }
500
469
 
470
+ async writeVhd(path, input, { checksum = true, validator = noop } = {}) {
471
+ const handler = this._handler
472
+
473
+ if (path.endsWith('.alias.vhd')) {
474
+ const dataPath = `${dirname(path)}/data/${uuidv4()}.vhd`
475
+ await createVhdDirectoryFromStream(handler, dataPath, input, {
476
+ concurrency: 16,
477
+ async validator() {
478
+ await input.task
479
+ return validator.apply(this, arguments)
480
+ },
481
+ })
482
+ await VhdAbstract.createAlias(handler, path, dataPath)
483
+ } else {
484
+ await this.outputStream(path, input, { checksum, validator })
485
+ }
486
+ }
487
+
501
488
  async outputStream(path, input, { checksum = true, validator = noop } = {}) {
502
489
  await this._handler.outputStream(path, input, {
503
490
  checksum,
@@ -509,6 +496,52 @@ class RemoteAdapter {
509
496
  })
510
497
  }
511
498
 
499
+ async _createSyntheticStream(handler, paths) {
500
+ let disposableVhds = []
501
+
502
+ // if it's a path : open all hierarchy of parent
503
+ if (typeof paths === 'string') {
504
+ let vhd,
505
+ vhdPath = paths
506
+ do {
507
+ const disposable = await openVhd(handler, vhdPath)
508
+ vhd = disposable.value
509
+ disposableVhds.push(disposable)
510
+ vhdPath = resolveRelativeFromFile(vhdPath, vhd.header.parentUnicodeName)
511
+ } while (vhd.footer.diskType !== Constants.DISK_TYPES.DYNAMIC)
512
+ } else {
513
+ // only open the list of path given
514
+ disposableVhds = paths.map(path => openVhd(handler, path))
515
+ }
516
+
517
+ // I don't want the vhds to be disposed on return
518
+ // but only when the stream is done ( or failed )
519
+ const disposables = await Disposable.all(disposableVhds)
520
+ const vhds = disposables.value
521
+
522
+ let disposed = false
523
+ const disposeOnce = async () => {
524
+ if (!disposed) {
525
+ disposed = true
526
+
527
+ try {
528
+ await disposables.dispose()
529
+ } catch (error) {
530
+ warn('_createSyntheticStream: failed to dispose VHDs', { error })
531
+ }
532
+ }
533
+ }
534
+
535
+ const synthetic = new VhdSynthetic(vhds)
536
+ await synthetic.readHeaderAndFooter()
537
+ await synthetic.readBlockAllocationTable()
538
+ const stream = await synthetic.stream()
539
+ stream.on('end', disposeOnce)
540
+ stream.on('close', disposeOnce)
541
+ stream.on('error', disposeOnce)
542
+ return stream
543
+ }
544
+
512
545
  async readDeltaVmBackup(metadata) {
513
546
  const handler = this._handler
514
547
  const { vbds, vdis, vhds, vifs, vm } = metadata
@@ -516,7 +549,7 @@ class RemoteAdapter {
516
549
 
517
550
  const streams = {}
518
551
  await asyncMapSettled(Object.keys(vdis), async id => {
519
- streams[`${id}.vhd`] = await createSyntheticStream(handler, join(dir, vhds[id]))
552
+ streams[`${id}.vhd`] = await this._createSyntheticStream(handler, join(dir, vhds[id]))
520
553
  })
521
554
 
522
555
  return {
package/_VmBackup.js CHANGED
@@ -1,9 +1,10 @@
1
1
  const assert = require('assert')
2
2
  const findLast = require('lodash/findLast.js')
3
+ const groupBy = require('lodash/groupBy.js')
3
4
  const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
4
5
  const keyBy = require('lodash/keyBy.js')
5
6
  const mapValues = require('lodash/mapValues.js')
6
- const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
7
+ const { asyncMap } = require('@xen-orchestra/async-map')
7
8
  const { createLogger } = require('@xen-orchestra/log')
8
9
  const { defer } = require('golike-defer')
9
10
  const { formatDateTime } = require('@xen-orchestra/xapi')
@@ -284,17 +285,28 @@ exports.VmBackup = class VmBackup {
284
285
  }
285
286
 
286
287
  async _removeUnusedSnapshots() {
287
- // TODO: handle all schedules (no longer existing schedules default to 0 retention)
288
-
289
- const { scheduleId } = this
290
- const scheduleSnapshots = this._jobSnapshots.filter(_ => _.other_config['xo:backup:schedule'] === scheduleId)
291
-
288
+ const jobSettings = this.job.settings
292
289
  const baseVmRef = this._baseVm?.$ref
290
+ const { config } = this
291
+ const baseSettings = {
292
+ ...config.defaultSettings,
293
+ ...config.metadata.defaultSettings,
294
+ ...jobSettings[''],
295
+ }
296
+
297
+ const snapshotsPerSchedule = groupBy(this._jobSnapshots, _ => _.other_config['xo:backup:schedule'])
293
298
  const xapi = this._xapi
294
- await asyncMap(getOldEntries(this._settings.snapshotRetention, scheduleSnapshots), ({ $ref }) => {
295
- if ($ref !== baseVmRef) {
296
- return xapi.VM_destroy($ref)
299
+ await asyncMap(Object.entries(snapshotsPerSchedule), ([scheduleId, snapshots]) => {
300
+ const settings = {
301
+ ...baseSettings,
302
+ ...jobSettings[scheduleId],
303
+ ...jobSettings[this.vm.uuid],
297
304
  }
305
+ return asyncMap(getOldEntries(settings.snapshotRetention, snapshots), ({ $ref }) => {
306
+ if ($ref !== baseVmRef) {
307
+ return xapi.VM_destroy($ref)
308
+ }
309
+ })
298
310
  })
299
311
  }
300
312
 
@@ -321,13 +333,16 @@ exports.VmBackup = class VmBackup {
321
333
 
322
334
  const baseUuidToSrcVdi = new Map()
323
335
  await asyncMap(await baseVm.$getDisks(), async baseRef => {
324
- const snapshotOf = await xapi.getField('VDI', baseRef, 'snapshot_of')
336
+ const [baseUuid, snapshotOf] = await Promise.all([
337
+ xapi.getField('VDI', baseRef, 'uuid'),
338
+ xapi.getField('VDI', baseRef, 'snapshot_of'),
339
+ ])
325
340
  const srcVdi = srcVdis[snapshotOf]
326
341
  if (srcVdi !== undefined) {
327
- baseUuidToSrcVdi.set(await xapi.getField('VDI', baseRef, 'uuid'), srcVdi)
342
+ baseUuidToSrcVdi.set(baseUuid, srcVdi)
328
343
  } else {
329
- debug('no base VDI found', {
330
- vdi: srcVdi.uuid,
344
+ debug('ignore snapshot VDI because no longer present on VM', {
345
+ vdi: baseUuid,
331
346
  })
332
347
  }
333
348
  })
package/_cleanVm.js CHANGED
@@ -1,13 +1,14 @@
1
1
  const assert = require('assert')
2
2
  const sum = require('lodash/sum')
3
3
  const { asyncMap } = require('@xen-orchestra/async-map')
4
- const { default: Vhd, mergeVhd } = require('vhd-lib')
4
+ const { Constants, mergeVhd, openVhd, VhdAbstract, VhdFile } = require('vhd-lib')
5
5
  const { dirname, resolve } = require('path')
6
- const { DISK_TYPE_DIFFERENCING } = require('vhd-lib/dist/_constants.js')
6
+ const { DISK_TYPES } = Constants
7
7
  const { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } = require('./_backupType.js')
8
8
  const { limitConcurrency } = require('limit-concurrency-decorator')
9
9
 
10
10
  const { Task } = require('./Task.js')
11
+ const { Disposable } = require('promise-toolbox')
11
12
 
12
13
  // chain is an array of VHDs from child to parent
13
14
  //
@@ -65,12 +66,12 @@ async function mergeVhdChain(chain, { handler, onLog, remove, merge }) {
65
66
  clearInterval(handle)
66
67
 
67
68
  await Promise.all([
68
- handler.rename(parent, child),
69
+ VhdAbstract.rename(handler, parent, child),
69
70
  asyncMap(children.slice(0, -1), child => {
70
71
  onLog(`the VHD ${child} is unused`)
71
72
  if (remove) {
72
73
  onLog(`deleting unused VHD ${child}`)
73
- return handler.unlink(child)
74
+ return VhdAbstract.unlink(handler, child)
74
75
  }
75
76
  }),
76
77
  ])
@@ -124,6 +125,8 @@ exports.cleanVm = async function cleanVm(
124
125
  vmDir,
125
126
  { fixMetadata, remove, merge, mergeLimiter = defaultMergeLimiter, onLog = noop }
126
127
  ) {
128
+ const limitedMergeVhdChain = mergeLimiter(mergeVhdChain)
129
+
127
130
  const handler = this._handler
128
131
 
129
132
  const vhds = new Set()
@@ -135,53 +138,55 @@ exports.cleanVm = async function cleanVm(
135
138
  // remove broken VHDs
136
139
  await asyncMap(vhdsList.vhds, async path => {
137
140
  try {
138
- const vhd = new Vhd(handler, path)
139
- await vhd.readHeaderAndFooter(!vhdsList.interruptedVhds.has(path))
140
- vhds.add(path)
141
- if (vhd.footer.diskType === DISK_TYPE_DIFFERENCING) {
142
- const parent = resolve('/', dirname(path), vhd.header.parentUnicodeName)
143
- vhdParents[path] = parent
144
- if (parent in vhdChildren) {
145
- const error = new Error('this script does not support multiple VHD children')
146
- error.parent = parent
147
- error.child1 = vhdChildren[parent]
148
- error.child2 = path
149
- throw error // should we throw?
141
+ await Disposable.use(openVhd(handler, path, { checkSecondFooter: !vhdsList.interruptedVhds.has(path) }), vhd => {
142
+ vhds.add(path)
143
+ if (vhd.footer.diskType === DISK_TYPES.DIFFERENCING) {
144
+ const parent = resolve('/', dirname(path), vhd.header.parentUnicodeName)
145
+ vhdParents[path] = parent
146
+ if (parent in vhdChildren) {
147
+ const error = new Error('this script does not support multiple VHD children')
148
+ error.parent = parent
149
+ error.child1 = vhdChildren[parent]
150
+ error.child2 = path
151
+ throw error // should we throw?
152
+ }
153
+ vhdChildren[parent] = path
150
154
  }
151
- vhdChildren[parent] = path
152
- }
155
+ })
153
156
  } catch (error) {
154
157
  onLog(`error while checking the VHD with path ${path}`, { error })
155
158
  if (error?.code === 'ERR_ASSERTION' && remove) {
156
159
  onLog(`deleting broken ${path}`)
157
- await handler.unlink(path)
160
+ return VhdAbstract.unlink(handler, path)
158
161
  }
159
162
  }
160
163
  })
161
164
 
165
+ // @todo : add check for data folder of alias not referenced in a valid alias
166
+
162
167
  // remove VHDs with missing ancestors
163
168
  {
164
169
  const deletions = []
165
170
 
166
171
  // return true if the VHD has been deleted or is missing
167
- const deleteIfOrphan = vhd => {
168
- const parent = vhdParents[vhd]
172
+ const deleteIfOrphan = vhdPath => {
173
+ const parent = vhdParents[vhdPath]
169
174
  if (parent === undefined) {
170
175
  return
171
176
  }
172
177
 
173
178
  // no longer needs to be checked
174
- delete vhdParents[vhd]
179
+ delete vhdParents[vhdPath]
175
180
 
176
181
  deleteIfOrphan(parent)
177
182
 
178
183
  if (!vhds.has(parent)) {
179
- vhds.delete(vhd)
184
+ vhds.delete(vhdPath)
180
185
 
181
- onLog(`the parent ${parent} of the VHD ${vhd} is missing`)
186
+ onLog(`the parent ${parent} of the VHD ${vhdPath} is missing`)
182
187
  if (remove) {
183
- onLog(`deleting orphan VHD ${vhd}`)
184
- deletions.push(handler.unlink(vhd))
188
+ onLog(`deleting orphan VHD ${vhdPath}`)
189
+ deletions.push(VhdAbstract.unlink(handler, vhdPath))
185
190
  }
186
191
  }
187
192
  }
@@ -251,15 +256,26 @@ exports.cleanVm = async function cleanVm(
251
256
  const { vhds } = metadata
252
257
  return Object.keys(vhds).map(key => resolve('/', vmDir, vhds[key]))
253
258
  })()
254
-
255
259
  // FIXME: find better approach by keeping as much of the backup as
256
260
  // possible (existing disks) even if one disk is missing
257
261
  if (linkedVhds.every(_ => vhds.has(_))) {
258
262
  linkedVhds.forEach(_ => unusedVhds.delete(_))
259
263
 
260
- size = await asyncMap(linkedVhds, vhd => handler.getSize(vhd)).then(sum, error => {
261
- onLog(`failed to get size of ${json}`, { error })
262
- })
264
+ // checking the size of a vhd directory is costly
265
+ // 1 Http Query per 1000 blocks
266
+ // we only check size of all the vhd are VhdFiles
267
+
268
+ const shouldComputeSize = linkedVhds.every(vhd => vhd instanceof VhdFile)
269
+ if (shouldComputeSize) {
270
+ try {
271
+ await Disposable.use(Disposable.all(linkedVhds.map(vhdPath => openVhd(handler, vhdPath))), async vhds => {
272
+ const sizes = await asyncMap(vhds, vhd => vhd.getSize())
273
+ size = sum(sizes)
274
+ })
275
+ } catch (error) {
276
+ onLog(`failed to get size of ${json}`, { error })
277
+ }
278
+ }
263
279
  } else {
264
280
  onLog(`Some VHDs linked to the metadata ${json} are missing`)
265
281
  if (remove) {
@@ -322,7 +338,7 @@ exports.cleanVm = async function cleanVm(
322
338
  onLog(`the VHD ${vhd} is unused`)
323
339
  if (remove) {
324
340
  onLog(`deleting unused VHD ${vhd}`)
325
- unusedVhdsDeletion.push(handler.unlink(vhd))
341
+ unusedVhdsDeletion.push(VhdAbstract.unlink(handler, vhd))
326
342
  }
327
343
  }
328
344
 
@@ -343,9 +359,7 @@ exports.cleanVm = async function cleanVm(
343
359
  }
344
360
 
345
361
  const doMerge = () => {
346
- const promise = asyncMap(toMerge, async chain => {
347
- mergeVhdChain(chain, { handler, onLog, remove, merge })
348
- })
362
+ const promise = asyncMap(toMerge, async chain => limitedMergeVhdChain(chain, { handler, onLog, remove, merge }))
349
363
  return merge ? promise.then(sizes => ({ size: sum(sizes) })) : promise
350
364
  }
351
365
 
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "type": "git",
9
9
  "url": "https://github.com/vatesfr/xen-orchestra.git"
10
10
  },
11
- "version": "0.14.0",
11
+ "version": "0.16.1",
12
12
  "engines": {
13
13
  "node": ">=14.6"
14
14
  },
@@ -16,14 +16,14 @@
16
16
  "postversion": "npm publish --access public"
17
17
  },
18
18
  "dependencies": {
19
- "@vates/compose": "^2.0.0",
19
+ "@vates/compose": "^2.1.0",
20
20
  "@vates/disposable": "^0.1.1",
21
21
  "@vates/parse-duration": "^0.1.1",
22
22
  "@xen-orchestra/async-map": "^0.1.2",
23
- "@xen-orchestra/fs": "^0.18.0",
23
+ "@xen-orchestra/fs": "^0.19.1",
24
24
  "@xen-orchestra/log": "^0.3.0",
25
25
  "@xen-orchestra/template": "^0.1.0",
26
- "compare-versions": "^3.6.0",
26
+ "compare-versions": "^4.0.1",
27
27
  "d3-time-format": "^3.0.0",
28
28
  "end-of-stream": "^1.4.4",
29
29
  "fs-extra": "^10.0.0",
@@ -35,11 +35,12 @@
35
35
  "promise-toolbox": "^0.20.0",
36
36
  "proper-lockfile": "^4.1.2",
37
37
  "pump": "^3.0.0",
38
- "vhd-lib": "^1.2.0",
38
+ "uuid": "^8.3.2",
39
+ "vhd-lib": "^2.0.2",
39
40
  "yazl": "^2.5.1"
40
41
  },
41
42
  "peerDependencies": {
42
- "@xen-orchestra/xapi": "^0.7.0"
43
+ "@xen-orchestra/xapi": "^0.8.4"
43
44
  },
44
45
  "license": "AGPL-3.0-or-later",
45
46
  "author": {
@@ -3,7 +3,7 @@ const map = require('lodash/map.js')
3
3
  const mapValues = require('lodash/mapValues.js')
4
4
  const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
5
5
  const { asyncMap } = require('@xen-orchestra/async-map')
6
- const { chainVhd, checkVhdChain, default: Vhd } = require('vhd-lib')
6
+ const { chainVhd, checkVhdChain, openVhd, VhdAbstract } = require('vhd-lib')
7
7
  const { createLogger } = require('@xen-orchestra/log')
8
8
  const { dirname } = require('path')
9
9
 
@@ -16,6 +16,7 @@ const { MixinBackupWriter } = require('./_MixinBackupWriter.js')
16
16
  const { AbstractDeltaWriter } = require('./_AbstractDeltaWriter.js')
17
17
  const { checkVhd } = require('./_checkVhd.js')
18
18
  const { packUuid } = require('./_packUuid.js')
19
+ const { Disposable } = require('promise-toolbox')
19
20
 
20
21
  const { warn } = createLogger('xo:backups:DeltaBackupWriter')
21
22
 
@@ -37,13 +38,13 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
37
38
  await asyncMap(vhds, async path => {
38
39
  try {
39
40
  await checkVhdChain(handler, path)
40
-
41
- const vhd = new Vhd(handler, path)
42
- await vhd.readHeaderAndFooter()
43
- found = found || vhd.footer.uuid.equals(packUuid(baseUuid))
41
+ await Disposable.use(
42
+ openVhd(handler, path),
43
+ vhd => (found = found || vhd.footer.uuid.equals(packUuid(baseUuid)))
44
+ )
44
45
  } catch (error) {
45
46
  warn('checkBaseVdis', { error })
46
- await ignoreErrors.call(handler.unlink(path))
47
+ await ignoreErrors.call(VhdAbstract.unlink(handler, path))
47
48
  }
48
49
  })
49
50
  } catch (error) {
@@ -144,7 +145,7 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
144
145
  // don't do delta for it
145
146
  vdi.uuid
146
147
  : vdi.$snapshot_of$uuid
147
- }/${basename}.vhd`
148
+ }/${adapter.getVhdFileName(basename)}`
148
149
  )
149
150
 
150
151
  const metadataFilename = `${backupDir}/${basename}.json`
@@ -188,7 +189,7 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
188
189
  await checkVhd(handler, parentPath)
189
190
  }
190
191
 
191
- await adapter.outputStream(path, deltaExport.streams[`${id}.vhd`], {
192
+ await adapter.writeVhd(path, deltaExport.streams[`${id}.vhd`], {
192
193
  // no checksum for VHDs, because they will be invalidated by
193
194
  // merges and chainings
194
195
  checksum: false,
@@ -200,11 +201,11 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
200
201
  }
201
202
 
202
203
  // set the correct UUID in the VHD
203
- const vhd = new Vhd(handler, path)
204
- await vhd.readHeaderAndFooter()
205
- vhd.footer.uuid = packUuid(vdi.uuid)
206
- await vhd.readBlockAllocationTable() // required by writeFooter()
207
- await vhd.writeFooter()
204
+ await Disposable.use(openVhd(handler, path), async vhd => {
205
+ vhd.footer.uuid = packUuid(vdi.uuid)
206
+ await vhd.readBlockAllocationTable() // required by writeFooter()
207
+ await vhd.writeFooter()
208
+ })
208
209
  })
209
210
  )
210
211
  return {
@@ -1,5 +1,6 @@
1
- const Vhd = require('vhd-lib').default
1
+ const openVhd = require('vhd-lib').openVhd
2
+ const Disposable = require('promise-toolbox/Disposable')
2
3
 
3
4
  exports.checkVhd = async function checkVhd(handler, path) {
4
- await new Vhd(handler, path).readHeaderAndFooter()
5
+ await Disposable.use(openVhd(handler, path), () => {})
5
6
  }