feat(xo-server): implement warm migration backend (#6549)
This commit is contained in:
committed by
GitHub
parent
d6192a4a7a
commit
72c69d791a
@@ -596,6 +596,22 @@ migrate.resolve = {
|
||||
migrationNetwork: ['migrationNetwork', 'network', 'administrate'],
|
||||
}
|
||||
|
||||
export async function warmMigration({ vm, sr, startVm, deleteSource }) {
|
||||
await this.warmMigrateVm(vm, sr, startVm, deleteSource)
|
||||
}
|
||||
warmMigration.permission = 'admin'
|
||||
|
||||
warmMigration.params = {
|
||||
vm: {
|
||||
type: 'string',
|
||||
},
|
||||
sr: {
|
||||
type: 'string',
|
||||
},
|
||||
startDestinationVm: { type: 'boolean' },
|
||||
deleteSourceVm: { type: 'boolean' },
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const set = defer(async function ($defer, params) {
|
||||
|
||||
109
packages/xo-server/src/xo-mixins/migrate-vm.mjs
Normal file
109
packages/xo-server/src/xo-mixins/migrate-vm.mjs
Normal file
@@ -0,0 +1,109 @@
|
||||
import { Backup } from '@xen-orchestra/backups/Backup.js'
|
||||
import { uuid } 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 = uuid.v4()
|
||||
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)
|
||||
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 ?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user