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 { readdir, rmdir, stat } from 'fs-extra'
|
||||||
import { ZipFile } from 'yazl'
|
import { ZipFile } from 'yazl'
|
||||||
|
|
||||||
|
import { decorateWith } from '../_decorateWith'
|
||||||
|
import { dedupeUnmount } from '../_dedupeUnmount'
|
||||||
import { lvs, pvs } from '../lvm'
|
import { lvs, pvs } from '../lvm'
|
||||||
import { resolveSubpath, tmpDir } from '../utils'
|
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 = {
|
const IGNORED_PARTITION_TYPES = {
|
||||||
// https://github.com/jhermsmeier/node-mbr/blob/master/lib/partition.js#L38
|
// https://github.com/jhermsmeier/node-mbr/blob/master/lib/partition.js#L38
|
||||||
0x05: true,
|
0x05: true,
|
||||||
@ -60,50 +66,56 @@ const parsePartxLine = createPairsParser({
|
|||||||
: value,
|
: value,
|
||||||
})
|
})
|
||||||
|
|
||||||
const listLvmLogicalVolumes = defer(
|
const listLvmLogicalVolumes = compose(
|
||||||
async ($defer, devicePath, partition, results = []) => {
|
defer,
|
||||||
const pv = await mountLvmPhysicalVolume(devicePath, partition)
|
dedupeUnmountWithArgs
|
||||||
$defer(pv.unmount)
|
)(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 lvs = await pvs(['lv_name', 'lv_path', 'lv_size', 'vg_name'], pv.path)
|
||||||
const partitionId = partition !== undefined ? partition.id : ''
|
const partitionId = partition !== undefined ? partition.id : ''
|
||||||
lvs.forEach((lv, i) => {
|
lvs.forEach((lv, i) => {
|
||||||
const name = lv.lv_name
|
const name = lv.lv_name
|
||||||
if (name !== '') {
|
if (name !== '') {
|
||||||
results.push({
|
results.push({
|
||||||
id: `${partitionId}/${lv.vg_name}/${name}`,
|
id: `${partitionId}/${lv.vg_name}/${name}`,
|
||||||
name,
|
name,
|
||||||
size: lv.lv_size,
|
size: lv.lv_size,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return results
|
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 mountPartition = compose(
|
||||||
const args = []
|
defer,
|
||||||
if (partition !== undefined) {
|
dedupeUnmountWithArgs
|
||||||
args.push('-o', partition.start * 512)
|
)(async ($defer, devicePath, partition) => {
|
||||||
}
|
|
||||||
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 options = ['loop', 'ro']
|
const options = ['loop', 'ro']
|
||||||
|
|
||||||
if (partition !== undefined) {
|
if (partition !== undefined) {
|
||||||
@ -280,6 +292,7 @@ export default class BackupNgFileRestore {
|
|||||||
return partitions
|
return partitions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@decorateWith(dedupeUnmountWithArgs)
|
||||||
@defer
|
@defer
|
||||||
async _mountDisk($defer, remoteId, diskId) {
|
async _mountDisk($defer, remoteId, diskId) {
|
||||||
const handler = await this._app.getRemoteHandler(remoteId)
|
const handler = await this._app.getRemoteHandler(remoteId)
|
||||||
@ -321,6 +334,7 @@ export default class BackupNgFileRestore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@decorateWith(dedupeUnmountWithArgs)
|
||||||
@defer
|
@defer
|
||||||
async _mountPartition($defer, devicePath, partitionId) {
|
async _mountPartition($defer, devicePath, partitionId) {
|
||||||
if (partitionId === undefined) {
|
if (partitionId === undefined) {
|
||||||
|
Loading…
Reference in New Issue
Block a user