feat(xo-server,xo-web): file restore via proxies (#5359)

This commit is contained in:
badrAZ 2020-11-26 17:14:06 +01:00 committed by GitHub
parent ae2a92d229
commit a776eaf61a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 81 additions and 21 deletions

View File

@ -12,6 +12,7 @@
- [SR] Use SR type `zfs` instead of `file` for ZFS storage repositories (PR [5302](https://github.com/vatesfr/xen-orchestra/pull/5330))
- [Dashboard/Health] List VMs with missing or outdated guest tools (PR [#5376](https://github.com/vatesfr/xen-orchestra/pull/5376))
- [VIF] Ability for admins to set any allowed IPs, including IPv6 and IPs that are not in an IP pool [#2535](https://github.com/vatesfr/xen-orchestra/issues/2535) [#1872](https://github.com/vatesfr/xen-orchestra/issues/1872) (PR [#5367](https://github.com/vatesfr/xen-orchestra/pull/5367))
- [Proxy] Ability to restore a file from VM backup (PR [#5359](https://github.com/vatesfr/xen-orchestra/pull/5359))
### Bug fixes

View File

@ -591,7 +591,7 @@ export default class BackupNg {
try {
const logsStream = await app.callProxyMethod(proxyId, 'backup.run', params, {
expectStream: true,
assertType: 'iterator',
})
const localTaskIds = { __proto__: null }

View File

@ -32,12 +32,6 @@ const IGNORED_PARTITION_TYPES = {
0x82: true, // swap
}
const PARTITION_TYPE_NAMES = {
0x07: 'NTFS',
0x0c: 'FAT',
0x83: 'linux',
}
const RE_VHDI = /^vhdi(\d+)$/
async function addDirectory(zip, realPath, metadataPath) {
@ -54,8 +48,7 @@ async function addDirectory(zip, realPath, metadataPath) {
const parsePartxLine = createPairsParser({
keyTransform: key => (key === 'UUID' ? 'id' : key.toLowerCase()),
valueTransform: (value, key) =>
key === 'start' || key === 'size' ? +value : key === 'type' ? PARTITION_TYPE_NAMES[+value] || value : value,
valueTransform: (value, key) => (key === 'start' || key === 'size' || key === 'type' ? +value : value),
})
const listLvmLogicalVolumes = compose(
@ -172,6 +165,25 @@ export default class BackupNgFileRestore {
@defer
async fetchBackupNgPartitionFiles($defer, remoteId, diskId, partitionId, paths) {
const app = this._app
const { proxy, url, options } = await app.getRemoteWithCredentials(remoteId)
if (proxy !== undefined) {
return app.callProxyMethod(
proxy,
'backup.fetchPartitionFiles',
{
disk: diskId,
remote: {
url,
options,
},
partition: partitionId,
paths,
},
{ assertType: 'stream' }
)
}
const disk = await this._mountDisk(remoteId, diskId)
$defer.onFailure(disk.unmount)
@ -190,6 +202,29 @@ export default class BackupNgFileRestore {
@defer
async listBackupNgDiskPartitions($defer, remoteId, diskId) {
const app = this._app
const { proxy, url, options } = await app.getRemoteWithCredentials(remoteId)
if (proxy !== undefined) {
const stream = await app.callProxyMethod(
proxy,
'backup.listDiskPartitions',
{
disk: diskId,
remote: {
url,
options,
},
},
{ assertType: 'iterator' }
)
const partitions = []
for await (const partition of stream) {
partitions.push(partition)
}
return partitions
}
const disk = await this._mountDisk(remoteId, diskId)
$defer(disk.unmount)
return this._listPartitions(disk.path)
@ -197,6 +232,20 @@ export default class BackupNgFileRestore {
@defer
async listBackupNgPartitionFiles($defer, remoteId, diskId, partitionId, path) {
const app = this._app
const { proxy, url, options } = await app.getRemoteWithCredentials(remoteId)
if (proxy !== undefined) {
return app.callProxyMethod(proxy, 'backup.listPartitionFiles', {
disk: diskId,
remote: {
url,
options,
},
partition: partitionId,
path,
})
}
const disk = await this._mountDisk(remoteId, diskId)
$defer(disk.unmount)
@ -237,8 +286,8 @@ export default class BackupNgFileRestore {
const partitions = []
splitLines(stdout).forEach(line => {
const partition = parsePartxLine(line)
let { type } = partition
if (type == null || (type = +type) in IGNORED_PARTITION_TYPES) {
const { type } = partition
if (type == null || type in IGNORED_PARTITION_TYPES) {
return
}

View File

@ -321,7 +321,8 @@ export default class Proxy {
await this.callProxyMethod(id, 'system.getServerVersion')
}
async callProxyMethod(id, method, params, { expectStream = false } = {}) {
// enum assertType {iterator, scalar, stream}
async callProxyMethod(id, method, params, { assertType = 'scalar' } = {}) {
const proxy = await this._getProxy(id)
const request = {
@ -358,6 +359,10 @@ export default class Proxy {
const responseType = contentType.parse(response).type
if (responseType === 'application/octet-stream') {
if (assertType !== 'stream') {
response.destroy()
throw new Error(`expect the result to be ${assertType}`)
}
return response
}
@ -367,13 +372,13 @@ export default class Proxy {
const firstLine = await readChunk(lines)
const result = parse.result(firstLine)
const isStream = result.$responseType === 'ndjson'
if (isStream !== expectStream) {
const isIterator = result.$responseType === 'ndjson'
if (assertType !== (isIterator ? 'iterator' : 'scalar')) {
lines.destroy()
throw new Error(`expect the result ${expectStream ? '' : 'not'} to be a stream`)
throw new Error(`expect the result to be ${assertType}`)
}
if (isStream) {
if (isIterator) {
return lines
}
lines.destroy()

View File

@ -88,10 +88,7 @@ export default class Restore extends Component {
}
_refreshBackupList = async (_remotes = this.props.remotes, jobs = this.props.jobs) => {
const remotes = keyBy(
filter(_remotes, ({ enabled, proxy }) => enabled && proxy === undefined),
'id'
)
const remotes = keyBy(filter(_remotes, 'enabled'), 'id')
const backupsByRemote = await listVmBackups(toArray(remotes))
const backupDataByVm = {}

View File

@ -2,6 +2,7 @@ import _ from 'intl'
import ActionButton from 'action-button'
import ButtonGroup from 'button-group'
import Component from 'base-component'
import defined from '@xen-orchestra/defined'
import Icon from 'icon'
import React from 'react'
import Select from 'form/select'
@ -16,6 +17,12 @@ import { listPartitions, listFiles } from 'xo'
// -----------------------------------------------------------------------------
const PARTITION_TYPE_NAMES = {
0x07: 'NTFS',
0x0c: 'FAT',
0x83: 'LINUX',
}
const BACKUP_RENDERER = getRenderXoItemOfType('backup')
const diskOptionRenderer = disk => (
@ -26,7 +33,8 @@ const diskOptionRenderer = disk => (
const partitionOptionRenderer = partition => (
<span>
{partition.name} {partition.type} {partition.size && `(${formatSize(+partition.size)})`}
{partition.name} {defined(PARTITION_TYPE_NAMES[partition.type], partition.type)}{' '}
{partition.size && `(${formatSize(+partition.size)})`}
</span>
)