feat(xo-web/backup/restore): ability to ignore certain VDIs (#6143)

Fixes #4605
This commit is contained in:
Rajaa.BARHTAOUI 2022-03-24 10:51:26 +01:00 committed by GitHub
parent fbb5c47358
commit dfa5009a9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 52 additions and 12 deletions

View File

@ -30,7 +30,12 @@ exports.ImportVmBackup = class ImportVmBackup {
} else {
assert.strictEqual(metadata.mode, 'delta')
backup = await adapter.readDeltaVmBackup(metadata)
const ignoredVdis = new Set(
Object.entries(this._importDeltaVmSettings.mapVdisSrs)
.filter(([_, srUuid]) => srUuid === null)
.map(([vdiUuid]) => vdiUuid)
)
backup = await adapter.readDeltaVmBackup(metadata, ignoredVdis)
Object.values(backup.streams).forEach(stream => watchStreamSize(stream, sizeContainer))
}

View File

@ -6,6 +6,7 @@ const fromCallback = require('promise-toolbox/fromCallback')
const fromEvent = require('promise-toolbox/fromEvent')
const pDefer = require('promise-toolbox/defer')
const groupBy = require('lodash/groupBy.js')
const pickBy = require('lodash/pickBy.js')
const { dirname, join, normalize, resolve } = require('path')
const { createLogger } = require('@xen-orchestra/log')
const { Constants, createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } = require('vhd-lib')
@ -576,14 +577,15 @@ class RemoteAdapter {
return stream
}
async readDeltaVmBackup(metadata) {
async readDeltaVmBackup(metadata, ignoredVdis) {
const handler = this._handler
const { vbds, vdis, vhds, vifs, vm } = metadata
const { vbds, vhds, vifs, vm } = metadata
const dir = dirname(metadata._filename)
const vdis = ignoredVdis === undefined ? metadata.vdis : pickBy(metadata.vdis, vdi => !ignoredVdis.has(vdi.uuid))
const streams = {}
await asyncMapSettled(Object.keys(vdis), async id => {
streams[`${id}.vhd`] = await this._createSyntheticStream(handler, join(dir, vhds[id]))
await asyncMapSettled(Object.keys(vdis), async ref => {
streams[`${ref}.vhd`] = await this._createSyntheticStream(handler, join(dir, vhds[ref]))
})
return {

View File

@ -20,6 +20,9 @@ exports.TAG_COPY_SRC = TAG_COPY_SRC
const ensureArray = value => (value === undefined ? [] : Array.isArray(value) ? value : [value])
const resolveUuid = async (xapi, cache, uuid, type) => {
if (uuid == null) {
return uuid
}
let ref = cache.get(uuid)
if (ref === undefined) {
ref = await xapi.call(`${type}.get_by_uuid`, uuid)

View File

@ -10,6 +10,7 @@
- [REST API] Expose networks, VBDs, VDIs and VIFs
- [Console] Supports host and VM consoles behind HTTP proxies [#6133](https://github.com/vatesfr/xen-orchestra/pull/6133)
- [Install patches] Disable patch installation when `High Availability` is enabled (PR [#6145](https://github.com/vatesfr/xen-orchestra/pull/6145))
- [Delta Backup/Restore] Ability to ignore some VDIs (PR [#6143](https://github.com/vatesfr/xen-orchestra/pull/6143))
### Bug fixes
@ -44,5 +45,5 @@
- vhd-cli minor
- @xen-orchestra/backups minor
- @xen-orchestra/proxy minor
- xo-server patch
- xo-server minor
- xo-web minor

View File

@ -1826,6 +1826,7 @@ const messages = {
'This VM contains a duplicate MAC address or has the same MAC address as another running VM. Do you want to continue?',
vmsWithDuplicatedMacAddressesMessage:
'{nVms, number} VM{nVms, plural, one {} other {s}} contain{nVms, plural, one {s} other {}} duplicate MAC addresses or {nVms, plural, one {has} other {have}} the same MAC addresses as other running VMs. Do you want to continue?',
ignoreVdi: 'Ignore this VDI',
// ----- Servers -----
enableServerErrorTitle: 'Enable server',

View File

@ -1,14 +1,16 @@
import Collapse from 'collapse'
import Component from 'base-component'
import Icon from 'icon'
import PropTypes from 'prop-types'
import React from 'react'
import Tooltip from 'tooltip'
import { Container, Col } from 'grid'
import { isEmpty, map } from 'lodash'
import { isSrWritable } from 'xo'
import { Vdi } from 'render-xo-item'
import _ from '../../intl'
import SingleLineRow from '../../single-line-row'
import { Container, Col } from 'grid'
import { isSrWritable } from 'xo'
import { SelectSr } from '../../select-objects'
const Collapsible = ({ collapsible, children, ...props }) =>
@ -29,6 +31,7 @@ Collapsible.propTypes = {
export default class ChooseSrForEachVdisModal extends Component {
static propTypes = {
ignorableVdis: PropTypes.bool,
mainSrPredicate: PropTypes.func,
onChange: PropTypes.func.isRequired,
srPredicate: PropTypes.func,
@ -53,6 +56,7 @@ export default class ChooseSrForEachVdisModal extends Component {
render() {
const { props } = this
const {
ignorableVdis = false,
mainSrPredicate = isSrWritable,
placeholder,
srPredicate = mainSrPredicate,
@ -64,7 +68,7 @@ export default class ChooseSrForEachVdisModal extends Component {
<div>
<SelectSr
onChange={this._onChangeMainSr}
placeholder={placeholder !== undefined ? placeholder : 'chooseSrForEachVdisModalMainSr'}
placeholder={placeholder !== undefined ? placeholder : _('chooseSrForEachVdisModalMainSr')}
predicate={mainSrPredicate}
required
value={mainSr}
@ -84,7 +88,9 @@ export default class ChooseSrForEachVdisModal extends Component {
</SingleLineRow>
{map(vdis, vdi => (
<SingleLineRow key={vdi.uuid}>
<Col size={6}>{vdi.name !== undefined ? vdi.name : <Vdi id={vdi.id} showSize />}</Col>
<Col size={ignorableVdis ? 5 : 6}>
{vdi.name !== undefined ? vdi.name : <Vdi id={vdi.id} showSize />}
</Col>
<Col size={6}>
<SelectSr
onChange={sr =>
@ -96,6 +102,22 @@ export default class ChooseSrForEachVdisModal extends Component {
value={mapVdisSrs !== undefined && mapVdisSrs[vdi.uuid]}
/>
</Col>
{ignorableVdis && (
<Col size={1}>
<Tooltip content={_('ignoreVdi')}>
<a
role='button'
onClick={() =>
this._onChange({
mapVdisSrs: { ...mapVdisSrs, [vdi.uuid]: null },
})
}
>
<Icon icon='remove' />
</a>
</Tooltip>
</Col>
)}
</SingleLineRow>
))}
<i>{_('optionalEntry')}</i>

View File

@ -19,9 +19,14 @@ export default class RestoreBackupsModalBody extends Component {
_getDisks = createSelector(
() => this.state.backup,
backup =>
() => this.state.targetSrs.mapVdisSrs,
(backup, mapVdisSrs) =>
backup !== undefined && backup.mode === 'delta'
? backup.disks.reduce((vdis, vdi) => ({ ...vdis, [vdi.uuid]: vdi }), {})
? backup.disks.reduce(
(vdis, vdi) =>
mapVdisSrs !== undefined && mapVdisSrs[vdi.uuid] === null ? vdis : { ...vdis, [vdi.uuid]: vdi },
{}
)
: {}
)
@ -40,6 +45,7 @@ export default class RestoreBackupsModalBody extends Component {
<div>
<div className='mb-1'>
<ChooseSrForEachVdisModal
ignorableVdis
onChange={this.linkState('targetSrs')}
placeholder={_('importBackupModalSelectSr')}
required