From 53e0f17c55c2f023d1f852411ecaa703ba9a3927 Mon Sep 17 00:00:00 2001 From: Florent BEAUCHAMP Date: Tue, 28 Mar 2023 19:29:34 +0200 Subject: [PATCH] feat(xo-server): import multiple from ESXi (#6708) --- packages/xo-server/src/api/vm.mjs | 103 ++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/packages/xo-server/src/api/vm.mjs b/packages/xo-server/src/api/vm.mjs index 4a538345c2a..6a8aadb5ce2 100644 --- a/packages/xo-server/src/api/vm.mjs +++ b/packages/xo-server/src/api/vm.mjs @@ -1,6 +1,8 @@ import * as multiparty from 'multiparty' import assignWith from 'lodash/assignWith.js' +import { asyncEach } from '@vates/async-each' import asyncMapSettled from '@xen-orchestra/async-map/legacy.js' +import { Task } from '@xen-orchestra/mixins/Tasks.mjs' import concat from 'lodash/concat.js' import getStream from 'get-stream' import hrp from 'http-request-plus' @@ -1327,6 +1329,107 @@ importFromEsxi.params = { vm: { type: 'string' }, } +/** + * on success: returns an object, the keys are the esxi id, and the values are the created vm uuid + * On error: throw an error. If stopOnError is false, continue when an error occurs, throws an error at the end with a 'succeeded' + * property listing the VM properly imported and a 'errors' property with all the errors collected + */ +export async function importMultipleFromEsxi({ + concurrency, + host, + network, + password, + sr, + sslVerify, + stopSource, + stopOnError, + thin, + user, + vms, +}) { + const task = await this.tasks.create({ name: `importing vms ${vms.join(',')}` }) + let done = 0 + return task.run(async () => { + Task.set('total', vms.length) + Task.set('done', 0) + Task.set('progress', 0) + const result = {} + try { + await asyncEach( + vms, + async vm => { + await new Task({ name: `importing vm ${vm}` }).run(async () => { + try { + const vmUuid = await this.migrationfromEsxi({ + host, + user, + password, + sslVerify, + thin, + vm, + sr, + network, + stopSource, + }) + result[vm] = vmUuid + } finally { + done++ + Task.set('done', done) + Task.set('progress', Math.round((done * 100) / vms.length)) + } + }) + }, + { + concurrency, + stopOnError, + } + ) + return result + } catch (error) { + // if stopOnError is true : + // error is the original error , `suceeded` property {[esxi vm id]: xen vm id} contains only the VMs migrated before the error. + // VMs started before the error and finished migration after won't be in + // else + // error is an AggregatedError with an `errors` property containing all the errors + // and a `succeeded` property {[esxi vm id]: xen vm id} containing all the migrated VMs + error.succeeded = result + throw error + } + }) +} + +importMultipleFromEsxi.params = { + concurrency: { + type: 'number', + optional: true, + default: 2, + description: 'number of VM imports in parallel (the disks are imported in parallel in each VM import)', + }, + host: { type: 'string' }, + network: { type: 'string' }, + password: { type: 'string' }, + sr: { type: 'string' }, + sslVerify: { type: 'boolean', optional: true, default: true }, + stopSource: { type: 'boolean', optional: true, default: false }, + stopOnError: { + type: 'boolean', + optional: true, + default: false, + description: 'should the import stop on the first error , default true . Warning, change the response format', + }, + thin: { type: 'boolean', optional: true, default: false }, + user: { type: 'string' }, + vms: { + items: { + type: 'string', + description: 'VM id to be imported, if used from cli use this syntax : vms=json:\'["2","9","18"]\'', + }, + minItems: 1, + type: 'array', + uniqueItems: true, + }, +} + // ------------------------------------------------------------------- // FIXME: if position is used, all other disks after this position