110 lines
3.4 KiB
JavaScript
110 lines
3.4 KiB
JavaScript
import { Backup } from '@xen-orchestra/backups/Backup.js'
|
|
import { v4 as generateUuid } from 'uuid'
|
|
|
|
export default class MigrateVm {
|
|
constructor(app) {
|
|
this._app = app
|
|
}
|
|
|
|
// Backup should be reinstentiated each time
|
|
#createWarmBackup(sourceVmId, srId, jobId) {
|
|
const app = this._app
|
|
const config = {
|
|
snapshotNameLabelTpl: '[XO warm migration {job.name}] {vm.name_label}',
|
|
}
|
|
const job = {
|
|
type: 'backup',
|
|
id: jobId,
|
|
mode: 'delta',
|
|
vms: { id: sourceVmId },
|
|
name: `Warm migration`,
|
|
srs: { id: srId },
|
|
settings: {
|
|
'': {
|
|
// mandatory for delta replication writer
|
|
copyRetention: 1,
|
|
},
|
|
},
|
|
}
|
|
const schedule = { id: 'one-time' }
|
|
|
|
// for now we only support this from the main OA, no proxy
|
|
return new Backup({
|
|
config,
|
|
job,
|
|
schedule,
|
|
getAdapter: async remoteId => app.getBackupsRemoteAdapter(await app.getRemoteWithCredentials(remoteId)),
|
|
|
|
// `@xen-orchestra/backups/Backup` expect that `getConnectedRecord` returns a promise
|
|
getConnectedRecord: async (xapiType, uuid) => app.getXapiObject(uuid),
|
|
})
|
|
}
|
|
|
|
async warmMigrateVm(sourceVmId, srId, startDestVm = true, deleteSource = false) {
|
|
// we'll use a one time use continuous replication job with the VM to migrate
|
|
const jobId = generateUuid()
|
|
const app = this._app
|
|
const sourceVm = app.getXapiObject(sourceVmId)
|
|
let backup = this.#createWarmBackup(sourceVmId, srId, jobId)
|
|
await backup.run()
|
|
const xapi = sourceVm.$xapi
|
|
const ref = sourceVm.$ref
|
|
|
|
// stop the source VM before
|
|
try {
|
|
await xapi.callAsync('VM.clean_shutdown', ref)
|
|
} catch (error) {
|
|
await xapi.callAsync('VM.hard_shutdown', ref)
|
|
}
|
|
// make it so it can't be restarted by error
|
|
const message =
|
|
'This VM has been migrated somewhere else and might not be up to date, check twice before starting it.'
|
|
await sourceVm.update_blocked_operations({
|
|
start: message,
|
|
start_on: message,
|
|
})
|
|
|
|
// run the transfer again to transfer the changed parts
|
|
// since the source is stopped, there won't be any new change after
|
|
backup = this.#createWarmBackup(sourceVmId, srId, jobId)
|
|
await backup.run()
|
|
// find the destination Vm
|
|
const targets = Object.keys(
|
|
app.getObjects({
|
|
filter: obj => {
|
|
return (
|
|
'other' in obj &&
|
|
obj.other['xo:backup:job'] === jobId &&
|
|
obj.other['xo:backup:sr'] === srId &&
|
|
obj.other['xo:backup:vm'] === sourceVm.uuid &&
|
|
'start' in obj.blockedOperations
|
|
)
|
|
},
|
|
})
|
|
)
|
|
if (targets.length === 0) {
|
|
throw new Error(`Vm target of warm migration not found for ${sourceVmId} on SR ${srId} `)
|
|
}
|
|
if (targets.length > 1) {
|
|
throw new Error(`Multiple target of warm migration found for ${sourceVmId} on SR ${srId} `)
|
|
}
|
|
const targetVm = app.getXapiObject(targets[0])
|
|
|
|
// new vm is ready to start
|
|
// delta replication writer as set this as blocked
|
|
await targetVm.update_blocked_operations({ start: null, start_on: null })
|
|
|
|
if (startDestVm) {
|
|
// boot it
|
|
await targetVm.$xapi.startVm(targetVm.$ref)
|
|
// wait for really started
|
|
// delete source
|
|
if (deleteSource) {
|
|
sourceVm.$xapi.VM_destroy(sourceVm.$ref)
|
|
} else {
|
|
// @todo should we delete the snapshot if we keep the source vm ?
|
|
}
|
|
}
|
|
}
|
|
}
|