chore(xo-server): remove unused {export,import}DeltaVm functions
This commit is contained in:
parent
c92b371d9e
commit
4bed50b4ed
@ -62,17 +62,6 @@ export async function copyVm({ vm, sr }) {
|
|||||||
console.log('import full VM...')
|
console.log('import full VM...')
|
||||||
await tgtXapi.VM_destroy((await tgtXapi.importVm(input, { srId: sr })).$ref)
|
await tgtXapi.VM_destroy((await tgtXapi.importVm(input, { srId: sr })).$ref)
|
||||||
}
|
}
|
||||||
|
|
||||||
// delta
|
|
||||||
{
|
|
||||||
console.log('export delta VM...')
|
|
||||||
const input = await srcXapi.exportDeltaVm(vm)
|
|
||||||
console.log('import delta VM...')
|
|
||||||
const { vm: copyVm } = await tgtXapi.importDeltaVm(input, {
|
|
||||||
srId: sr,
|
|
||||||
})
|
|
||||||
await tgtXapi.VM_destroy(copyVm.$ref)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
copyVm.description = 'export/import full/delta VM'
|
copyVm.description = 'export/import full/delta VM'
|
||||||
|
@ -6,7 +6,6 @@ import filter from 'lodash/filter.js'
|
|||||||
import find from 'lodash/find.js'
|
import find from 'lodash/find.js'
|
||||||
import flatMap from 'lodash/flatMap.js'
|
import flatMap from 'lodash/flatMap.js'
|
||||||
import flatten from 'lodash/flatten.js'
|
import flatten from 'lodash/flatten.js'
|
||||||
import groupBy from 'lodash/groupBy.js'
|
|
||||||
import identity from 'lodash/identity.js'
|
import identity from 'lodash/identity.js'
|
||||||
import includes from 'lodash/includes.js'
|
import includes from 'lodash/includes.js'
|
||||||
import isEmpty from 'lodash/isEmpty.js'
|
import isEmpty from 'lodash/isEmpty.js'
|
||||||
@ -14,9 +13,7 @@ import mapToArray from 'lodash/map.js'
|
|||||||
import mixin from '@xen-orchestra/mixin/legacy.js'
|
import mixin from '@xen-orchestra/mixin/legacy.js'
|
||||||
import ms from 'ms'
|
import ms from 'ms'
|
||||||
import noop from 'lodash/noop.js'
|
import noop from 'lodash/noop.js'
|
||||||
import omit from 'lodash/omit.js'
|
|
||||||
import once from 'lodash/once.js'
|
import once from 'lodash/once.js'
|
||||||
import semver from 'semver'
|
|
||||||
import tarStream from 'tar-stream'
|
import tarStream from 'tar-stream'
|
||||||
import uniq from 'lodash/uniq.js'
|
import uniq from 'lodash/uniq.js'
|
||||||
import { asyncMap } from '@xen-orchestra/async-map'
|
import { asyncMap } from '@xen-orchestra/async-map'
|
||||||
@ -33,9 +30,7 @@ import { Xapi as XapiBase } from '@xen-orchestra/xapi'
|
|||||||
import { Ref } from 'xen-api'
|
import { Ref } from 'xen-api'
|
||||||
import { synchronized } from 'decorator-synchronized'
|
import { synchronized } from 'decorator-synchronized'
|
||||||
|
|
||||||
import ensureArray from '../_ensureArray.mjs'
|
|
||||||
import fatfsBuffer, { init as fatfsBufferInit } from '../fatfs-buffer.mjs'
|
import fatfsBuffer, { init as fatfsBufferInit } from '../fatfs-buffer.mjs'
|
||||||
import { asyncMapValues } from '../_asyncMapValues.mjs'
|
|
||||||
import { camelToSnakeCase, forEach, map, parseSize, pDelay, promisifyAll } from '../utils.mjs'
|
import { camelToSnakeCase, forEach, map, parseSize, pDelay, promisifyAll } from '../utils.mjs'
|
||||||
|
|
||||||
import mixins from './mixins/index.mjs'
|
import mixins from './mixins/index.mjs'
|
||||||
@ -65,11 +60,6 @@ class AggregateError extends Error {
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
const TAG_BASE_DELTA = 'xo:base_delta'
|
|
||||||
export const TAG_COPY_SRC = 'xo:copy_of'
|
|
||||||
|
|
||||||
// ===================================================================
|
|
||||||
|
|
||||||
export * from './utils.mjs'
|
export * from './utils.mjs'
|
||||||
|
|
||||||
// VDI formats. (Raw is not available for delta vdi.)
|
// VDI formats. (Raw is not available for delta vdi.)
|
||||||
@ -593,352 +583,6 @@ export default class Xapi extends XapiBase {
|
|||||||
return writeStream
|
return writeStream
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a snapshot (if necessary) of the VM and returns a delta export
|
|
||||||
// object.
|
|
||||||
@cancelable
|
|
||||||
@decorateWith(deferrable)
|
|
||||||
async exportDeltaVm(
|
|
||||||
$defer,
|
|
||||||
$cancelToken,
|
|
||||||
vmId,
|
|
||||||
baseVmId,
|
|
||||||
{
|
|
||||||
bypassVdiChainsCheck = false,
|
|
||||||
|
|
||||||
// Contains a vdi.$id set of vmId.
|
|
||||||
fullVdisRequired = [],
|
|
||||||
|
|
||||||
disableBaseTags = false,
|
|
||||||
snapshotNameLabel = undefined,
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
let vm = this.getObject(vmId)
|
|
||||||
|
|
||||||
// do not use the snapshot name in the delta export
|
|
||||||
const exportedNameLabel = vm.name_label
|
|
||||||
if (!vm.is_a_snapshot) {
|
|
||||||
if (!bypassVdiChainsCheck) {
|
|
||||||
await this.VM_assertHealthyVdiChains(vm.$ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
vm = await this.getRecord(
|
|
||||||
'VM',
|
|
||||||
await this.VM_snapshot(vm.$ref, { cancelToken: $cancelToken, name_label: snapshotNameLabel })
|
|
||||||
)
|
|
||||||
$defer.onFailure(() => this.VM_destroy(vm.$ref))
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseVm = baseVmId && this.getObject(baseVmId)
|
|
||||||
|
|
||||||
// refs of VM's VDIs → base's VDIs.
|
|
||||||
const baseVdis = {}
|
|
||||||
baseVm &&
|
|
||||||
forEach(baseVm.$VBDs, vbd => {
|
|
||||||
let vdi, snapshotOf
|
|
||||||
if (
|
|
||||||
(vdi = vbd.$VDI) &&
|
|
||||||
(snapshotOf = vdi.$snapshot_of) &&
|
|
||||||
!find(fullVdisRequired, id => snapshotOf.$id === id)
|
|
||||||
) {
|
|
||||||
baseVdis[vdi.snapshot_of] = vdi
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const streams = {}
|
|
||||||
const vdis = {}
|
|
||||||
const vbds = {}
|
|
||||||
forEach(vm.$VBDs, vbd => {
|
|
||||||
let vdi
|
|
||||||
if (vbd.type !== 'Disk' || !(vdi = vbd.$VDI)) {
|
|
||||||
// Ignore this VBD.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the VDI name start with `[NOBAK]`, do not export it.
|
|
||||||
if (vdi.name_label.startsWith('[NOBAK]')) {
|
|
||||||
// FIXME: find a way to not create the VDI snapshot in the
|
|
||||||
// first time.
|
|
||||||
//
|
|
||||||
// The snapshot must not exist otherwise it could break the
|
|
||||||
// next export.
|
|
||||||
vdi.$destroy()::ignoreErrors()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
vbds[vbd.$ref] = vbd
|
|
||||||
|
|
||||||
const vdiRef = vdi.$ref
|
|
||||||
if (vdiRef in vdis) {
|
|
||||||
// This VDI has already been managed.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for a snapshot of this vdi in the base VM.
|
|
||||||
const baseVdi = baseVdis[vdi.snapshot_of]
|
|
||||||
|
|
||||||
vdis[vdiRef] = {
|
|
||||||
...vdi,
|
|
||||||
other_config: {
|
|
||||||
...vdi.other_config,
|
|
||||||
[TAG_BASE_DELTA]: baseVdi && !disableBaseTags ? baseVdi.uuid : undefined,
|
|
||||||
},
|
|
||||||
$SR$uuid: vdi.$SR.uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
streams[`${vdiRef}.vhd`] = () => this._exportVdi($cancelToken, vdi, baseVdi, VDI_FORMAT_VHD)
|
|
||||||
})
|
|
||||||
|
|
||||||
const suspendVdi = vm.$suspend_VDI
|
|
||||||
if (suspendVdi !== undefined) {
|
|
||||||
const vdiRef = suspendVdi.$ref
|
|
||||||
vdis[vdiRef] = {
|
|
||||||
...suspendVdi,
|
|
||||||
$SR$uuid: suspendVdi.$SR.uuid,
|
|
||||||
}
|
|
||||||
streams[`${vdiRef}.vhd`] = () => this._exportVdi($cancelToken, suspendVdi, undefined, VDI_FORMAT_VHD)
|
|
||||||
}
|
|
||||||
|
|
||||||
const vifs = {}
|
|
||||||
forEach(vm.$VIFs, vif => {
|
|
||||||
const network = vif.$network
|
|
||||||
vifs[vif.$ref] = {
|
|
||||||
...vif,
|
|
||||||
$network$uuid: network.uuid,
|
|
||||||
$network$name_label: network.name_label,
|
|
||||||
// https://github.com/babel/babel-eslint/issues/595
|
|
||||||
// eslint-disable-next-line no-undef
|
|
||||||
$network$VLAN: network.$PIFs[0]?.VLAN,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return Object.defineProperty(
|
|
||||||
{
|
|
||||||
version: '1.1.0',
|
|
||||||
vbds,
|
|
||||||
vdis,
|
|
||||||
vifs,
|
|
||||||
vm: {
|
|
||||||
...vm,
|
|
||||||
name_label: exportedNameLabel,
|
|
||||||
other_config:
|
|
||||||
baseVm && !disableBaseTags
|
|
||||||
? {
|
|
||||||
...vm.other_config,
|
|
||||||
[TAG_BASE_DELTA]: baseVm.uuid,
|
|
||||||
}
|
|
||||||
: omit(vm.other_config, TAG_BASE_DELTA),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'streams',
|
|
||||||
{
|
|
||||||
configurable: true,
|
|
||||||
value: streams,
|
|
||||||
writable: true,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@decorateWith(deferrable)
|
|
||||||
async importDeltaVm(
|
|
||||||
$defer,
|
|
||||||
delta,
|
|
||||||
{
|
|
||||||
deleteBase = false,
|
|
||||||
detectBase = true,
|
|
||||||
disableStartAfterImport = true,
|
|
||||||
mapVdisSrs = {},
|
|
||||||
name_label = delta.vm.name_label,
|
|
||||||
srId = this.pool.default_SR,
|
|
||||||
} = {}
|
|
||||||
) {
|
|
||||||
const { version } = delta
|
|
||||||
|
|
||||||
if (!semver.satisfies(version, '^1')) {
|
|
||||||
throw new Error(`Unsupported delta backup version: ${version}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
let baseVm
|
|
||||||
if (detectBase) {
|
|
||||||
const remoteBaseVmUuid = delta.vm.other_config[TAG_BASE_DELTA]
|
|
||||||
if (remoteBaseVmUuid) {
|
|
||||||
baseVm = find(this.objects.all, obj => (obj = obj.other_config) && obj[TAG_COPY_SRC] === remoteBaseVmUuid)
|
|
||||||
|
|
||||||
if (!baseVm) {
|
|
||||||
throw new Error(`could not find the base VM (copy of ${remoteBaseVmUuid})`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseVdis = {}
|
|
||||||
baseVm &&
|
|
||||||
forEach(baseVm.$VBDs, vbd => {
|
|
||||||
const vdi = vbd.$VDI
|
|
||||||
if (vdi !== undefined) {
|
|
||||||
baseVdis[vbd.VDI] = vbd.$VDI
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 0. Create suspend_VDI
|
|
||||||
let suspendVdi
|
|
||||||
if (delta.vm.power_state === 'Suspended') {
|
|
||||||
const vdi = delta.vdis[delta.vm.suspend_VDI]
|
|
||||||
suspendVdi = await this.createVdi({
|
|
||||||
...vdi,
|
|
||||||
other_config: {
|
|
||||||
...vdi.other_config,
|
|
||||||
[TAG_BASE_DELTA]: undefined,
|
|
||||||
[TAG_COPY_SRC]: vdi.uuid,
|
|
||||||
},
|
|
||||||
sr: mapVdisSrs[vdi.uuid] || srId,
|
|
||||||
})
|
|
||||||
$defer.onFailure.call(this, 'VDI_destroy', suspendVdi.$ref)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Create the VMs.
|
|
||||||
const vm = await this._getOrWaitObject(
|
|
||||||
await this._createVmRecord(
|
|
||||||
{
|
|
||||||
...delta.vm,
|
|
||||||
affinity: null,
|
|
||||||
blocked_operations: {
|
|
||||||
...delta.vm.blocked_operations,
|
|
||||||
start: 'Importing…',
|
|
||||||
start_on: 'Importing…',
|
|
||||||
},
|
|
||||||
ha_always_run: false,
|
|
||||||
is_a_template: false,
|
|
||||||
name_label: `[Importing…] ${name_label}`,
|
|
||||||
other_config: {
|
|
||||||
...delta.vm.other_config,
|
|
||||||
[TAG_COPY_SRC]: delta.vm.uuid,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ suspend_VDI: suspendVdi?.$ref }
|
|
||||||
)
|
|
||||||
)
|
|
||||||
$defer.onFailure(() => this.VM_destroy(vm.$ref))
|
|
||||||
|
|
||||||
// 2. Delete all VBDs which may have been created by the import.
|
|
||||||
await asyncMapSettled(vm.$VBDs, vbd => this._deleteVbd(vbd))::ignoreErrors()
|
|
||||||
|
|
||||||
// 3. Create VDIs & VBDs.
|
|
||||||
//
|
|
||||||
// TODO: move all VDIs creation before the VM and simplify the code
|
|
||||||
const vbds = groupBy(delta.vbds, 'VDI')
|
|
||||||
const newVdis = await asyncMapValues(delta.vdis, async (vdi, vdiRef) => {
|
|
||||||
let newVdi
|
|
||||||
|
|
||||||
const remoteBaseVdiUuid = detectBase && vdi.other_config[TAG_BASE_DELTA]
|
|
||||||
if (remoteBaseVdiUuid) {
|
|
||||||
const baseVdi = find(baseVdis, vdi => vdi.other_config[TAG_COPY_SRC] === remoteBaseVdiUuid)
|
|
||||||
if (!baseVdi) {
|
|
||||||
throw new Error(`missing base VDI (copy of ${remoteBaseVdiUuid})`)
|
|
||||||
}
|
|
||||||
|
|
||||||
newVdi = await this._getOrWaitObject(await this._cloneVdi(baseVdi))
|
|
||||||
$defer.onFailure(() => newVdi.$destroy())
|
|
||||||
|
|
||||||
await newVdi.update_other_config(TAG_COPY_SRC, vdi.uuid)
|
|
||||||
} else if (vdiRef === delta.vm.suspend_VDI) {
|
|
||||||
// suspend VDI has been already created
|
|
||||||
newVdi = suspendVdi
|
|
||||||
} else {
|
|
||||||
newVdi = await this.createVdi({
|
|
||||||
...vdi,
|
|
||||||
other_config: {
|
|
||||||
...vdi.other_config,
|
|
||||||
[TAG_BASE_DELTA]: undefined,
|
|
||||||
[TAG_COPY_SRC]: vdi.uuid,
|
|
||||||
},
|
|
||||||
sr: mapVdisSrs[vdi.uuid] || srId,
|
|
||||||
})
|
|
||||||
$defer.onFailure(() => newVdi.$destroy())
|
|
||||||
}
|
|
||||||
|
|
||||||
await asyncMapSettled(vbds[vdiRef], vbd =>
|
|
||||||
this.createVbd({
|
|
||||||
...vbd,
|
|
||||||
vdi: newVdi,
|
|
||||||
vm,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
return newVdi
|
|
||||||
})
|
|
||||||
|
|
||||||
const networksByNameLabelByVlan = {}
|
|
||||||
let defaultNetwork
|
|
||||||
forEach(this.objects.all, object => {
|
|
||||||
if (object.$type === 'network') {
|
|
||||||
const pif = object.$PIFs[0]
|
|
||||||
if (pif === undefined) {
|
|
||||||
// ignore network
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const vlan = pif.VLAN
|
|
||||||
const networksByNameLabel = networksByNameLabelByVlan[vlan] || (networksByNameLabelByVlan[vlan] = {})
|
|
||||||
defaultNetwork = networksByNameLabel[object.name_label] = object
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const { streams } = delta
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
// Import VDI contents.
|
|
||||||
asyncMapSettled(newVdis, async (vdi, id) => {
|
|
||||||
for (let stream of ensureArray(streams[`${id}.vhd`])) {
|
|
||||||
if (typeof stream === 'function') {
|
|
||||||
stream = await stream()
|
|
||||||
}
|
|
||||||
await this._importVdiContent(vdi, stream, VDI_FORMAT_VHD)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
|
|
||||||
// Wait for VDI export tasks (if any) termination.
|
|
||||||
asyncMapSettled(streams, stream => stream.task),
|
|
||||||
|
|
||||||
// Create VIFs.
|
|
||||||
asyncMapSettled(delta.vifs, vif => {
|
|
||||||
let network = vif.$network$uuid && this.getObject(vif.$network$uuid, undefined)
|
|
||||||
|
|
||||||
if (network === undefined) {
|
|
||||||
const { $network$VLAN: vlan = -1 } = vif
|
|
||||||
const networksByNameLabel = networksByNameLabelByVlan[vlan]
|
|
||||||
if (networksByNameLabel !== undefined) {
|
|
||||||
network = networksByNameLabel[vif.$network$name_label]
|
|
||||||
if (network === undefined) {
|
|
||||||
network = networksByNameLabel[Object.keys(networksByNameLabel)[0]]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
network = defaultNetwork
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (network) {
|
|
||||||
return this._createVif(vm, network, vif)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
|
|
||||||
if (deleteBase && baseVm) {
|
|
||||||
this.VM_destroy(baseVm.$ref)::ignoreErrors()
|
|
||||||
}
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
delta.vm.ha_always_run && vm.set_ha_always_run(true),
|
|
||||||
vm.set_name_label(name_label),
|
|
||||||
// FIXME: move
|
|
||||||
asyncMap(['start', 'start_on'], op =>
|
|
||||||
vm.update_blocked_operations(
|
|
||||||
op,
|
|
||||||
disableStartAfterImport ? 'Do not start this VM, clone it if you want to use it.' : null
|
|
||||||
)
|
|
||||||
),
|
|
||||||
])
|
|
||||||
|
|
||||||
return { vm }
|
|
||||||
}
|
|
||||||
|
|
||||||
async _migrateVmWithStorageMotion(
|
async _migrateVmWithStorageMotion(
|
||||||
vm,
|
vm,
|
||||||
hostXapi,
|
hostXapi,
|
||||||
|
Loading…
Reference in New Issue
Block a user