feat(xo-server/file restore): dedupe mount/unmount (#4961)
This commit is contained in:
parent
6555e2c440
commit
48ce7df43a
45
packages/xo-server/src/_dedupeUnmount.js
Normal file
45
packages/xo-server/src/_dedupeUnmount.js
Normal file
@ -0,0 +1,45 @@
|
||||
import assert from 'assert'
|
||||
|
||||
import ensureArray from './_ensureArray'
|
||||
import MultiKeyMap from './_MultiKeyMap'
|
||||
|
||||
function State() {
|
||||
this.i = 0
|
||||
this.value = undefined
|
||||
}
|
||||
|
||||
export const dedupeUnmount = (fn, keyFn) => {
|
||||
const states = new MultiKeyMap()
|
||||
|
||||
return function() {
|
||||
const keys = ensureArray(keyFn.apply(this, arguments))
|
||||
let state = states.get(keys)
|
||||
if (state === undefined) {
|
||||
state = new State()
|
||||
states.set(keys, state)
|
||||
|
||||
const mount = async () => {
|
||||
try {
|
||||
const value = await fn.apply(this, arguments)
|
||||
return {
|
||||
__proto__: value,
|
||||
async unmount() {
|
||||
assert(state.i > 0)
|
||||
if (--state.i === 0) {
|
||||
states.delete(keys)
|
||||
await value.unmount()
|
||||
}
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
states.delete(keys)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
state.value = mount()
|
||||
}
|
||||
++state.i
|
||||
return state.value
|
||||
}
|
||||
}
|
@ -6,9 +6,15 @@ import { normalize } from 'path'
|
||||
import { readdir, rmdir, stat } from 'fs-extra'
|
||||
import { ZipFile } from 'yazl'
|
||||
|
||||
import { decorateWith } from '../_decorateWith'
|
||||
import { dedupeUnmount } from '../_dedupeUnmount'
|
||||
import { lvs, pvs } from '../lvm'
|
||||
import { resolveSubpath, tmpDir } from '../utils'
|
||||
|
||||
const compose = (...fns) => value => fns.reduce((value, fn) => fn(value), value)
|
||||
|
||||
const dedupeUnmountWithArgs = fn => dedupeUnmount(fn, (...args) => args)
|
||||
|
||||
const IGNORED_PARTITION_TYPES = {
|
||||
// https://github.com/jhermsmeier/node-mbr/blob/master/lib/partition.js#L38
|
||||
0x05: true,
|
||||
@ -60,50 +66,56 @@ const parsePartxLine = createPairsParser({
|
||||
: value,
|
||||
})
|
||||
|
||||
const listLvmLogicalVolumes = defer(
|
||||
async ($defer, devicePath, partition, results = []) => {
|
||||
const pv = await mountLvmPhysicalVolume(devicePath, partition)
|
||||
$defer(pv.unmount)
|
||||
const listLvmLogicalVolumes = compose(
|
||||
defer,
|
||||
dedupeUnmountWithArgs
|
||||
)(async ($defer, devicePath, partition, results = []) => {
|
||||
const pv = await mountLvmPhysicalVolume(devicePath, partition)
|
||||
$defer(pv.unmount)
|
||||
|
||||
const lvs = await pvs(['lv_name', 'lv_path', 'lv_size', 'vg_name'], pv.path)
|
||||
const partitionId = partition !== undefined ? partition.id : ''
|
||||
lvs.forEach((lv, i) => {
|
||||
const name = lv.lv_name
|
||||
if (name !== '') {
|
||||
results.push({
|
||||
id: `${partitionId}/${lv.vg_name}/${name}`,
|
||||
name,
|
||||
size: lv.lv_size,
|
||||
})
|
||||
}
|
||||
})
|
||||
return results
|
||||
const lvs = await pvs(['lv_name', 'lv_path', 'lv_size', 'vg_name'], pv.path)
|
||||
const partitionId = partition !== undefined ? partition.id : ''
|
||||
lvs.forEach((lv, i) => {
|
||||
const name = lv.lv_name
|
||||
if (name !== '') {
|
||||
results.push({
|
||||
id: `${partitionId}/${lv.vg_name}/${name}`,
|
||||
name,
|
||||
size: lv.lv_size,
|
||||
})
|
||||
}
|
||||
})
|
||||
return results
|
||||
})
|
||||
|
||||
const mountLvmPhysicalVolume = dedupeUnmountWithArgs(
|
||||
async (devicePath, partition) => {
|
||||
const args = []
|
||||
if (partition !== undefined) {
|
||||
args.push('-o', partition.start * 512)
|
||||
}
|
||||
args.push('--show', '-f', devicePath)
|
||||
const path = (await execa('losetup', args)).stdout.trim()
|
||||
await execa('pvscan', ['--cache', path])
|
||||
|
||||
return {
|
||||
path,
|
||||
unmount: async () => {
|
||||
try {
|
||||
const vgNames = await pvs('vg_name', path)
|
||||
await execa('vgchange', ['-an', ...vgNames])
|
||||
} finally {
|
||||
await execa('losetup', ['-d', path])
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async function mountLvmPhysicalVolume(devicePath, partition) {
|
||||
const args = []
|
||||
if (partition !== undefined) {
|
||||
args.push('-o', partition.start * 512)
|
||||
}
|
||||
args.push('--show', '-f', devicePath)
|
||||
const path = (await execa('losetup', args)).stdout.trim()
|
||||
await execa('pvscan', ['--cache', path])
|
||||
|
||||
return {
|
||||
path,
|
||||
unmount: async () => {
|
||||
try {
|
||||
const vgNames = await pvs('vg_name', path)
|
||||
await execa('vgchange', ['-an', ...vgNames])
|
||||
} finally {
|
||||
await execa('losetup', ['-d', path])
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const mountPartition = defer(async ($defer, devicePath, partition) => {
|
||||
const mountPartition = compose(
|
||||
defer,
|
||||
dedupeUnmountWithArgs
|
||||
)(async ($defer, devicePath, partition) => {
|
||||
const options = ['loop', 'ro']
|
||||
|
||||
if (partition !== undefined) {
|
||||
@ -280,6 +292,7 @@ export default class BackupNgFileRestore {
|
||||
return partitions
|
||||
}
|
||||
|
||||
@decorateWith(dedupeUnmountWithArgs)
|
||||
@defer
|
||||
async _mountDisk($defer, remoteId, diskId) {
|
||||
const handler = await this._app.getRemoteHandler(remoteId)
|
||||
@ -321,6 +334,7 @@ export default class BackupNgFileRestore {
|
||||
}
|
||||
}
|
||||
|
||||
@decorateWith(dedupeUnmountWithArgs)
|
||||
@defer
|
||||
async _mountPartition($defer, devicePath, partitionId) {
|
||||
if (partitionId === undefined) {
|
||||
|
Loading…
Reference in New Issue
Block a user