Skip to content

Commit

Permalink
feat(xo-server): implement warm migration backend (#6549)
Browse files Browse the repository at this point in the history
  • Loading branch information
fbeauchamp authored Nov 28, 2022
1 parent d6192a4 commit 72c69d7
Show file tree
Hide file tree
Showing 2 changed files with 125 additions and 0 deletions.
16 changes: 16 additions & 0 deletions packages/xo-server/src/api/vm.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
109 changes: 109 additions & 0 deletions packages/xo-server/src/xo-mixins/migrate-vm.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Backup } from '@xen-orchestra/backups/Backup.js'
import { uuid } from 'uuid'

This comment has been minimized.

Copy link
@Danp2

Danp2 Nov 29, 2022

Collaborator

This change causes xo-server to fail --

Nov 28 19:43:52 ubuntuxo xo-server[7652]: file:///opt/xen-orchestra/packages/xo-server/dist/xo-mixins/migrate-vm.mjs:2
Nov 28 19:43:52 ubuntuxo xo-server[7652]: import { uuid } from 'uuid';
Nov 28 19:43:52 ubuntuxo xo-server[7652]:          ^^^^
Nov 28 19:43:52 ubuntuxo xo-server[7652]: SyntaxError: The requested module 'uuid' does not provide an export named 'uuid'
Nov 28 19:43:52 ubuntuxo xo-server[7652]:     at ModuleJob._instantiate (node:internal/modules/esm/module_job:123:21)
Nov 28 19:43:52 ubuntuxo xo-server[7652]:     at async ModuleJob.run (node:internal/modules/esm/module_job:189:5)
Nov 28 19:43:52 ubuntuxo xo-server[7652]:     at async Promise.all (index 0)
Nov 28 19:43:52 ubuntuxo xo-server[7652]:     at async ESMLoader.import (node:internal/modules/esm/loader:530:24)
Nov 28 19:43:52 ubuntuxo xo-server[7652]:     at async loadESM (node:internal/process/esm_loader:91:5)
Nov 28 19:43:52 ubuntuxo xo-server[7652]:     at async handleMainPromise (node:internal/modules/run_main:65:12)
Nov 28 19:43:52 ubuntuxo xo-server[7652]: Node.js v18.12.1
Nov 28 19:43:52 ubuntuxo systemd[1]: xo-server.service: Main process exited, code=exited, status=1/FAILURE
Nov 28 19:43:52 ubuntuxo systemd[1]: xo-server.service: Failed with result 'exit-code'.

This comment has been minimized.

Copy link
@julien-f

julien-f Nov 29, 2022

Member

Thank you, this has been fixed 🙂


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 ?
}
}
}
}

0 comments on commit 72c69d7

Please sign in to comment.