From 5ee11c7b6ba487e3e99c27abc4fc5bcc50dbe7c1 Mon Sep 17 00:00:00 2001 From: Julien Fontanet Date: Tue, 10 Nov 2015 22:26:57 +0100 Subject: [PATCH] Properly remove objects for which `xo.id !== xapi.$id`. --- src/acl.js | 6 +- src/api/disk.js | 2 +- src/api/docker.js | 14 +- src/api/host.coffee | 103 +++---- src/api/message.js | 4 +- src/api/pbd.js | 6 +- src/api/pif.js | 8 +- src/api/pool.js | 2 +- src/api/sr.js | 30 +- src/api/tag.js | 4 +- src/api/task.js | 4 +- src/api/vbd.coffee | 8 +- src/api/vdi.coffee | 6 +- src/api/vif.js | 6 +- src/api/vm.coffee | 62 ++-- src/xapi-object-to-xo.js | 592 ++++++++++++++++++++++++++++++++++++++ src/xapi-objects-to-xo.js | 549 ----------------------------------- src/xapi.js | 66 ++++- src/xo.js | 129 ++++----- 19 files changed, 836 insertions(+), 765 deletions(-) create mode 100644 src/xapi-object-to-xo.js delete mode 100644 src/xapi-objects-to-xo.js diff --git a/src/acl.js b/src/acl.js index 64a10b1ef..958b14827 100644 --- a/src/acl.js +++ b/src/acl.js @@ -51,13 +51,13 @@ function checkSelf ({ id }, permission) { // =================================================================== const checkAuthorizationByTypes = { - host: or(checkSelf, checkMember('$poolId')), + host: or(checkSelf, checkMember('$pool')), message: checkMember('$object'), - network: or(checkSelf, checkMember('$poolId')), + network: or(checkSelf, checkMember('$pool')), - SR: or(checkSelf, checkMember('$poolId')), + SR: or(checkSelf, checkMember('$pool')), task: checkMember('$host'), diff --git a/src/api/disk.js b/src/api/disk.js index c031f9b65..22fd23d2f 100644 --- a/src/api/disk.js +++ b/src/api/disk.js @@ -5,7 +5,7 @@ import {parseSize} from '../utils' export async function create ({name, size, sr}) { const vdi = await this.getXAPI(sr).createVdi(parseSize(size), { name_label: name, - sr: sr.id + sr: sr._xapiId }) return vdi.$id } diff --git a/src/api/docker.js b/src/api/docker.js index 80d87aac2..2e0cbcfea 100644 --- a/src/api/docker.js +++ b/src/api/docker.js @@ -1,5 +1,5 @@ export async function register ({vm}) { - await this.getXAPI(vm).registerDockerContainer(vm.id) + await this.getXAPI(vm).registerDockerContainer(vm._xapiId) } register.permission = 'admin' @@ -16,7 +16,7 @@ register.resolve = { // ----------------------------------------------------------------------------- export async function deregister ({vm}) { - await this.getXAPI(vm).unregisterDockerContainer(vm.id) + await this.getXAPI(vm).unregisterDockerContainer(vm._xapiId) } deregister.permission = 'admin' @@ -33,23 +33,23 @@ deregister.resolve = { // ----------------------------------------------------------------------------- export async function start ({vm, container}) { - await this.getXAPI(vm).startDockerContainer(vm.id, container) + await this.getXAPI(vm).startDockerContainer(vm._xapiId, container) } export async function stop ({vm, container}) { - await this.getXAPI(vm).stopDockerContainer(vm.id, container) + await this.getXAPI(vm).stopDockerContainer(vm._xapiId, container) } export async function restart ({vm, container}) { - await this.getXAPI(vm).restartDockerContainer(vm.id, container) + await this.getXAPI(vm).restartDockerContainer(vm._xapiId, container) } export async function pause ({vm, container}) { - await this.getXAPI(vm).pauseDockerContainer(vm.id, container) + await this.getXAPI(vm).pauseDockerContainer(vm._xapiId, container) } export async function unpause ({vm, container}) { - await this.getXAPI(vm).unpauseDockerContainer(vm.id, container) + await this.getXAPI(vm).unpauseDockerContainer(vm._xapiId, container) } for (let fn of [start, stop, restart, pause, unpause]) { diff --git a/src/api/host.coffee b/src/api/host.coffee index a777cabb5..550bcc529 100644 --- a/src/api/host.coffee +++ b/src/api/host.coffee @@ -6,23 +6,18 @@ endsWith = require 'lodash.endswith' got = require('got') startsWith = require 'lodash.startswith' {coroutine: $coroutine} = require 'bluebird' -{parseXml, promisify} = require '../utils' +{ + extractProperty, + parseXml, + promisify +} = require '../utils' #===================================================================== -set = $coroutine (params) -> - {host} = params - xapi = @getXAPI host +set = (params) -> + host = extractProperty(params, 'host') - for param, field of { - 'name_label' - 'name_description' - } - continue unless param of params - - yield xapi.call "host.set_#{field}", host.ref, params[param] - - return true + return @getXAPI(host).setHostProperties(host._xapiId, params) set.description = 'changes the properties of an host' @@ -43,18 +38,19 @@ exports.set = set #--------------------------------------------------------------------- -restart = $coroutine ({host}) -> - xapi = @getXAPI host - - yield xapi.call 'host.disable', host.ref - yield xapi.call 'host.reboot', host.ref - - return true +# FIXME: set force to false per default when correctly implemented in +# UI. +restart = ({host, force = true}) -> + return @getXAPI(host).rebootHost(host._xapiId, force) restart.description = 'restart the host' restart.params = { - id: { type: 'string' } + id: { type: 'string' }, + force: { + type: 'boolean', + optional: true + } } restart.resolve = { @@ -65,12 +61,8 @@ exports.restart = restart #--------------------------------------------------------------------- -restartAgent = $coroutine ({host}) -> - xapi = @getXAPI host - - yield xapi.call 'host.restart_agent', host.ref - - return true +restartAgent = ({host}) -> + return @getXAPI(host).restartHostAgent(host._xapiId) restartAgent.description = 'restart the Xen agent on the host' @@ -79,7 +71,7 @@ restartAgent.params = { } restartAgent.resolve = { - host: ['id', 'host', 'operate'], + host: ['id', 'host', 'administrate'], } # TODO camel case @@ -87,12 +79,8 @@ exports.restart_agent = restartAgent #--------------------------------------------------------------------- -start = $coroutine ({host}) -> - xapi = @getXAPI host - - yield xapi.call 'host.power_on', host.ref - - return true +start = ({host}) -> + return @getXAPI(host).powerOnHost(host._xapiId) start.description = 'start the host' @@ -108,13 +96,8 @@ exports.start = start #--------------------------------------------------------------------- -stop = $coroutine ({host}) -> - xapi = @getXAPI host - - yield xapi.call 'host.disable', host.ref - yield xapi.call 'host.shutdown', host.ref - - return true +stop = ({host}) -> + return @getXAPI(host).shutdownHost(host._xapiId) stop.description = 'stop the host' @@ -130,12 +113,8 @@ exports.stop = stop #--------------------------------------------------------------------- -detach = $coroutine ({host}) -> - xapi = @getXAPI host - - yield xapi.call 'pool.eject', host.ref - - return true +detach = ({host}) -> + return @getXAPI(host).ejectHostFromPool(host._xapiId) detach.description = 'eject the host of a pool' @@ -151,12 +130,8 @@ exports.detach = detach #--------------------------------------------------------------------- -enable = $coroutine ({host}) -> - xapi = @getXAPI host - - yield xapi.call 'host.enable', host.ref - - return true +enable = ({host}) -> + return @getXAPI(host).enableHost(host._xapiId) enable.description = 'enable to create VM on the host' @@ -172,12 +147,8 @@ exports.enable = enable #--------------------------------------------------------------------- -disable = $coroutine ({host}) -> - xapi = @getXAPI host - - yield xapi.call 'host.disable', host.ref - - return true +disable = ({host}) -> + return @getXAPI(host).disableHost(host._xapiId) disable.description = 'disable to create VM on the hsot' @@ -193,6 +164,7 @@ exports.disable = disable #--------------------------------------------------------------------- +# TODO: to test and to fix. createNetwork = $coroutine ({host, name, description, pif, mtu, vlan}) -> xapi = @getXAPI host @@ -208,7 +180,7 @@ createNetwork = $coroutine ({host, name, description, pif, mtu, vlan}) -> if pif? vlan = vlan ? '0' pif = @getObject pif, 'PIF' - yield xapi.call 'pool.create_VLAN_from_PIF', pif.ref, network_ref, vlan + yield xapi.call 'pool.create_VLAN_from_PIF', pif._xapiRef, network_ref, vlan return true @@ -233,7 +205,7 @@ exports.createNetwork = createNetwork # Throws an error if the host is not running the latest XS version listMissingPatches = ({host}) -> - return @getXAPI(host).listMissingPoolPatchesOnHost(host.id) + return @getXAPI(host).listMissingPoolPatchesOnHost(host._xapiId) listMissingPatches.params = { host: { type: 'string' } @@ -250,7 +222,7 @@ listMissingPatches.description = 'return an array of missing new patches in the #--------------------------------------------------------------------- installPatch = ({host, patch: patchUuid}) -> - return @getXAPI(host).installPoolPatchOnHost(patchUuid, host.id) + return @getXAPI(host).installPoolPatchOnHost(patchUuid, host._xapiId) installPatch.description = 'install a patch on an host' @@ -268,7 +240,7 @@ exports.installPatch = installPatch #--------------------------------------------------------------------- installAllPatches = ({host}) -> - return @getXAPI(host).installAllPoolPatchesOnHost(host.id) + return @getXAPI(host).installAllPoolPatchesOnHost(host._xapiId) installAllPatches.description = 'install all the missing patches on a host' @@ -284,9 +256,8 @@ exports.installAllPatches = installAllPatches #--------------------------------------------------------------------- -stats = $coroutine ({host, granularity}) -> - stats = yield @getXapiHostStats(host, granularity) - return stats +stats = ({host, granularity}) -> + return @getXapiHostStats(host, granularity) stats.description = 'returns statistic of the host' diff --git a/src/api/message.js b/src/api/message.js index 9f1391dcc..290776372 100644 --- a/src/api/message.js +++ b/src/api/message.js @@ -1,5 +1,5 @@ -async function delete_ ({message}) { - await this.getXAPI(message).call('message.destroy', message.ref) +async function delete_ ({ message }) { + await this.getXAPI(message).call('message.destroy', message._xapiRef) } export {delete_ as delete} diff --git a/src/api/pbd.js b/src/api/pbd.js index a05b3d214..b4b9888e6 100644 --- a/src/api/pbd.js +++ b/src/api/pbd.js @@ -5,7 +5,7 @@ async function delete_ ({PBD}) { // TODO: check if PBD is attached before - await this.getXAPI(PBD).call('PBD.destroy', PBD.ref) + await this.getXAPI(PBD).call('PBD.destroy', PBD._xapiRef) } export {delete_ as delete} @@ -22,7 +22,7 @@ delete_.resolve = { export async function disconnect ({PBD}) { // TODO: check if PBD is attached before - await this.getXAPI(PBD).call('PBD.unplug', PBD.ref) + await this.getXAPI(PBD).call('PBD.unplug', PBD._xapiRef) } disconnect.params = { @@ -38,7 +38,7 @@ disconnect.resolve = { export async function connect ({PBD}) { // TODO: check if PBD is attached before - await this.getXAPI(PBD).call('PBD.plug', PBD.ref) + await this.getXAPI(PBD).call('PBD.plug', PBD._xapiRef) } connect.params = { diff --git a/src/api/pif.js b/src/api/pif.js index ca2e29c84..8a2ab474f 100644 --- a/src/api/pif.js +++ b/src/api/pif.js @@ -1,9 +1,11 @@ +// TODO: too low level, move into host. + // =================================================================== // Delete async function delete_ ({PIF}) { // TODO: check if PIF is attached before - await this.getXAPI(PIF).call('PIF.destroy', PIF.ref) + await this.getXAPI(PIF).call('PIF.destroy', PIF._xapiRef) } export {delete_ as delete} @@ -20,7 +22,7 @@ delete_.resolve = { export async function disconnect ({PIF}) { // TODO: check if PIF is attached before - await this.getXAPI(PIF).call('PIF.unplug', PIF.ref) + await this.getXAPI(PIF).call('PIF.unplug', PIF._xapiRef) } disconnect.params = { @@ -35,7 +37,7 @@ disconnect.resolve = { export async function connect ({PIF}) { // TODO: check if PIF is attached before - await this.getXAPI(PIF).call('PIF.plug', PIF.ref) + await this.getXAPI(PIF).call('PIF.plug', PIF._xapiRef) } connect.params = { diff --git a/src/api/pool.js b/src/api/pool.js index 08e414946..11e83ad43 100644 --- a/src/api/pool.js +++ b/src/api/pool.js @@ -82,7 +82,7 @@ export {uploadPatch as patch} export async function mergeInto ({ source, target, force }) { try { - await this.mergeXenPools(source.id, target.id, force) + await this.mergeXenPools(source._xapiId, target._xapiId, force) } catch (e) { // FIXME: should we expose plain XAPI error messages? throw new JsonRpcError(e.message) diff --git a/src/api/sr.js b/src/api/sr.js index cd10fc6df..c05df379f 100644 --- a/src/api/sr.js +++ b/src/api/sr.js @@ -1,5 +1,6 @@ import { ensureArray, + extractProperty, forEach, parseXml } from '../utils' @@ -7,10 +8,9 @@ import { // =================================================================== export async function set (params) { - const {sr} = params - delete params.sr + const sr = extractProperty(params, 'sr') - await this.getXAPI(sr).setSrProperties(sr.id, params) + await this.getXAPI(sr).setSrProperties(sr._xapiId, params) } set.params = { @@ -28,7 +28,7 @@ set.resolve = { // ------------------------------------------------------------------- export async function scan ({SR}) { - await this.getXAPI(SR).call('SR.scan', SR.ref) + await this.getXAPI(SR).call('SR.scan', SR._xapiRef) } scan.params = { @@ -43,7 +43,7 @@ scan.resolve = { // TODO: find a way to call this "delete" and not destroy export async function destroy ({SR}) { - await this.getXAPI(SR).call('SR.destroy', SR.ref) + await this.getXAPI(SR).call('SR.destroy', SR._xapiRef) } destroy.params = { @@ -57,7 +57,7 @@ destroy.resolve = { // ------------------------------------------------------------------- export async function forget ({SR}) { - await this.getXAPI(SR).call('SR.forget', SR.ref) + await this.getXAPI(SR).call('SR.forget', SR._xapiRef) } forget.params = { @@ -87,7 +87,7 @@ export async function createIso ({ } const srRef = await xapi.call( 'SR.create', - host.ref, + host._xapiRef, deviceConfig, '0', // SR size 0 because ISO nameLabel, @@ -140,7 +140,7 @@ export async function createNfs ({ const srRef = await xapi.call( 'SR.create', - host.ref, + host._xapiRef, deviceConfig, '0', nameLabel, @@ -187,7 +187,7 @@ export async function createLvm ({ const srRef = await xapi.call( 'SR.create', - host.ref, + host._xapiRef, deviceConfig, '0', nameLabel, @@ -232,7 +232,7 @@ export async function probeNfs ({ try { await xapi.call( 'SR.probe', - host.ref, + host._xapiRef, deviceConfig, 'nfs', {} @@ -305,7 +305,7 @@ export async function createIscsi ({ const srRef = await xapi.call( 'SR.create', - host.ref, + host._xapiRef, deviceConfig, '0', nameLabel, @@ -369,7 +369,7 @@ export async function probeIscsiIqns ({ try { await xapi.call( 'SR.probe', - host.ref, + host._xapiRef, deviceConfig, 'lvmoiscsi', {} @@ -447,7 +447,7 @@ export async function probeIscsiLuns ({ try { await xapi.call( 'SR.probe', - host.ref, + host._xapiRef, deviceConfig, 'lvmoiscsi', {} @@ -521,7 +521,7 @@ export async function probeIscsiExists ({ deviceConfig.port = port } - const xml = parseXml(await xapi.call('SR.probe', host.ref, deviceConfig, 'lvmoiscsi', {})) + const xml = parseXml(await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'lvmoiscsi', {})) const srs = [] forEach(ensureArray(xml['SRlist'].SR), sr => { @@ -562,7 +562,7 @@ export async function probeNfsExists ({ serverpath: serverPath } - const xml = parseXml(await xapi.call('SR.probe', host.ref, deviceConfig, 'nfs', {})) + const xml = parseXml(await xapi.call('SR.probe', host._xapiRef, deviceConfig, 'nfs', {})) const srs = [] diff --git a/src/api/tag.js b/src/api/tag.js index bdc874b22..02c9cf3c4 100644 --- a/src/api/tag.js +++ b/src/api/tag.js @@ -1,5 +1,5 @@ export async function add ({tag, object}) { - await this.getXAPI(object).addTag(object.id, tag) + await this.getXAPI(object).addTag(object._xapiId, tag) } add.description = 'add a new tag to an object' @@ -16,7 +16,7 @@ add.params = { // ------------------------------------------------------------------- export async function remove ({tag, object}) { - await this.getXAPI(object).removeTag(object.id, tag) + await this.getXAPI(object).removeTag(object._xapiId, tag) } remove.description = 'remove an existing tag from an object' diff --git a/src/api/task.js b/src/api/task.js index 586285fde..4f03441ff 100644 --- a/src/api/task.js +++ b/src/api/task.js @@ -1,5 +1,5 @@ export async function cancel ({task}) { - await this.getXAPI(task).call('task.cancel', task.ref) + await this.getXAPI(task).call('task.cancel', task._xapiRef) } cancel.params = { @@ -13,7 +13,7 @@ cancel.resolve = { // ------------------------------------------------------------------- export async function destroy ({task}) { - await this.getXAPI(task).call('task.destroy', task.ref) + await this.getXAPI(task).call('task.destroy', task._xapiRef) } destroy.params = { diff --git a/src/api/vbd.coffee b/src/api/vbd.coffee index 16459951a..91680c9e1 100644 --- a/src/api/vbd.coffee +++ b/src/api/vbd.coffee @@ -8,7 +8,7 @@ delete_ = $coroutine ({vbd}) -> xapi = @getXAPI vbd # TODO: check if VBD is attached before - yield xapi.call 'VBD.destroy', vbd.ref + yield xapi.call 'VBD.destroy', vbd._xapiRef return true @@ -28,7 +28,7 @@ disconnect = $coroutine ({vbd}) -> xapi = @getXAPI vbd # TODO: check if VBD is attached before - yield xapi.call 'VBD.unplug_force', vbd.ref + yield xapi.call 'VBD.unplug_force', vbd._xapiRef return true @@ -48,7 +48,7 @@ connect = $coroutine ({vbd}) -> xapi = @getXAPI vbd # TODO: check if VBD is attached before - yield xapi.call 'VBD.plug', vbd.ref + yield xapi.call 'VBD.plug', vbd._xapiRef return true @@ -68,7 +68,7 @@ set = $coroutine (params) -> {vbd} = params xapi = @getXAPI vbd - {ref} = vbd + { _xapiRef: ref } = vbd # VBD position if 'position' of params diff --git a/src/api/vdi.coffee b/src/api/vdi.coffee index 821a0e371..1d731df11 100644 --- a/src/api/vdi.coffee +++ b/src/api/vdi.coffee @@ -9,7 +9,7 @@ $isArray = require 'lodash.isarray' #===================================================================== delete_ = $coroutine ({vdi}) -> - yield @getXAPI(vdi).deleteVdi(vdi.id) + yield @getXAPI(vdi).deleteVdi(vdi._xapiId) return @@ -30,7 +30,7 @@ set = $coroutine (params) -> {vdi} = params xapi = @getXAPI vdi - {ref} = vdi + {_xapiRef: ref} = vdi # Size. if 'size' of params @@ -79,7 +79,7 @@ migrate = $coroutine ({vdi, sr}) -> xapi = @getXAPI vdi # TODO: check if VDI is attached before - yield xapi.call 'VDI.pool_migrate', vdi.ref, sr.ref, {} + yield xapi.call 'VDI.pool_migrate', vdi._xapiRef, sr._xapiRef, {} return true diff --git a/src/api/vif.js b/src/api/vif.js index 266bb251d..8597df3b4 100644 --- a/src/api/vif.js +++ b/src/api/vif.js @@ -1,6 +1,6 @@ // TODO: move into vm and rename to removeInterface async function delete_ ({vif}) { - await this.getXAPI(vif).deleteVif(vif.id) + await this.getXAPI(vif).deleteVif(vif._xapiId) } export {delete_ as delete} @@ -16,7 +16,7 @@ delete_.resolve = { // TODO: move into vm and rename to disconnectInterface export async function disconnect ({vif}) { // TODO: check if VIF is attached before - await this.getXAPI(vif).call('VIF.unplug_force', vif.ref) + await this.getXAPI(vif).call('VIF.unplug_force', vif._xapiRef) } disconnect.params = { @@ -31,7 +31,7 @@ disconnect.resolve = { // TODO: move into vm and rename to connectInterface export async function connect ({vif}) { // TODO: check if VIF is attached before - await this.getXAPI(vif).call('VIF.plug', vif.ref) + await this.getXAPI(vif).call('VIF.plug', vif._xapiRef) } connect.params = { diff --git a/src/api/vm.coffee b/src/api/vm.coffee index f8e2d2152..99a2f5457 100644 --- a/src/api/vm.coffee +++ b/src/api/vm.coffee @@ -59,7 +59,7 @@ create = $coroutine ({ VDIs VIFs }) -> - vm = yield @getXAPI(template).createVm(template.id, { + vm = yield @getXAPI(template).createVm(template._xapiId, { installRepository: installation && installation.repository, nameDescription: name_description, nameLabel: name_label, @@ -138,7 +138,7 @@ exports.create = create #--------------------------------------------------------------------- delete_ = ({vm, delete_disks: deleteDisks}) -> - return @getXAPI(vm).deleteVm(vm.id, deleteDisks) + return @getXAPI(vm).deleteVm(vm._xapiId, deleteDisks) delete_.params = { id: { type: 'string' } @@ -157,7 +157,7 @@ exports.delete = delete_ #--------------------------------------------------------------------- ejectCd = $coroutine ({vm}) -> - yield @getXAPI(vm).ejectCdFromVm(vm.id) + yield @getXAPI(vm).ejectCdFromVm(vm._xapiId) return ejectCd.params = { @@ -172,7 +172,7 @@ exports.ejectCd = ejectCd #--------------------------------------------------------------------- insertCd = $coroutine ({vm, vdi, force}) -> - yield @getXAPI(vm).insertCdIntoVm(vdi.id, vm.id, {force}) + yield @getXAPI(vm).insertCdIntoVm(vdi._xapiId, vm._xapiId, {force}) return insertCd.params = { @@ -190,7 +190,7 @@ exports.insertCd = insertCd #--------------------------------------------------------------------- migrate = $coroutine ({vm, host}) -> - yield @getXAPI(vm).migrateVm(vm.id, @getXAPI(host), host.id) + yield @getXAPI(vm).migrateVm(vm._xapiId, @getXAPI(host), host._xapiId) return migrate.params = { @@ -217,10 +217,10 @@ migratePool = $coroutine ({ network migrationNetwork }) -> - yield @getXAPI(vm).migrateVm(vm.id, @getXAPI(host), host.id, { - migrationNetworkId: migrationNetwork?.id - networkId: network?.id, - srId: sr?.id, + yield @getXAPI(vm).migrateVm(vm._xapiId, @getXAPI(host), host._xapiId, { + migrationNetworkId: migrationNetwork?._xapiId + networkId: network?._xapiId, + srId: sr?._xapiId, }) return @@ -260,7 +260,7 @@ set = $coroutine (params) -> {VM} = params xapi = @getXAPI VM - {ref} = VM + {_xapiRef: ref} = VM # Memory. if 'memory' of params @@ -371,9 +371,9 @@ restart = $coroutine ({vm, force}) -> xapi = @getXAPI(vm) if force - yield xapi.call 'VM.hard_reboot', vm.ref + yield xapi.call 'VM.hard_reboot', vm._xapiRef else - yield xapi.call 'VM.clean_reboot', vm.ref + yield xapi.call 'VM.clean_reboot', vm._xapiRef return true @@ -391,7 +391,7 @@ exports.restart = restart #--------------------------------------------------------------------- clone = ({vm, name, full_copy}) -> - return @getXAPI(vm).cloneVm(vm.ref, { + return @getXAPI(vm).cloneVm(vm._xapiRef, { nameLabel: name, fast: not full_copy }).then((vm) -> vm.$id) @@ -417,15 +417,15 @@ copy = $coroutine ({ sr, vm }) -> - if vm.$poolId == sr.$poolId + if vm.$pool == sr.$pool if vm.power_state is 'Running' yield checkPermissionsForSnapshot.call(this, vm) - return @getXAPI(vm).copyVm(vm.id, sr.id, { + return @getXAPI(vm).copyVm(vm._xapiId, sr._xapiId, { nameLabel }).then((vm) -> vm.$id) - return @getXAPI(vm).remoteCopyVm(vm.id, @getXAPI(sr), sr.id, { + return @getXAPI(vm).remoteCopyVm(vm._xapiId, @getXAPI(sr), sr._xapiId, { compress, nameLabel }).then((vm) -> vm.$id) @@ -454,7 +454,7 @@ exports.copy = copy # TODO: rename convertToTemplate() convert = $coroutine ({vm}) -> - yield @getXAPI(vm).call 'VM.set_is_a_template', vm.ref, true + yield @getXAPI(vm).call 'VM.set_is_a_template', vm._xapiRef, true return true @@ -472,7 +472,7 @@ exports.convert = convert snapshot = $coroutine ({vm, name}) -> yield checkPermissionsForSnapshot.call(this, vm) - snapshot = yield @getXAPI(vm).snapshotVm(vm.ref, name) + snapshot = yield @getXAPI(vm).snapshotVm(vm._xapiRef, name) return snapshot.$id snapshot.params = { @@ -559,7 +559,7 @@ exports.rollingBackup = rollingBackup #--------------------------------------------------------------------- rollingDrCopy = ({vm, pool, tag, depth}) -> - if vm.$poolId is pool.id + if vm.$pool is pool.id throw new JsonRpcError('Disaster Recovery attempts to copy on the same pool') return @rollingDrCopyVm({vm, sr: @getObject(pool.default_SR, 'SR'), tag, depth}) @@ -583,7 +583,7 @@ exports.rollingDrCopy = rollingDrCopy start = $coroutine ({vm}) -> yield @getXAPI(vm).call( - 'VM.start', vm.ref + 'VM.start', vm._xapiRef false # Start paused? false # Skips the pre-boot checks? ) @@ -611,12 +611,12 @@ stop = $coroutine ({vm, force}) -> # Hard shutdown if force - yield xapi.call 'VM.hard_shutdown', vm.ref + yield xapi.call 'VM.hard_shutdown', vm._xapiRef return true # Clean shutdown try - yield xapi.call 'VM.clean_shutdown', vm.ref + yield xapi.call 'VM.clean_shutdown', vm._xapiRef catch error if error.code is 'VM_MISSING_PV_DRIVERS' or error.code is 'VM_LACKS_FEATURE_SHUTDOWN' # TODO: Improve reporting: this message is unclear. @@ -640,7 +640,7 @@ exports.stop = stop #--------------------------------------------------------------------- suspend = $coroutine ({vm}) -> - yield @getXAPI(vm).call 'VM.suspend', vm.ref + yield @getXAPI(vm).call 'VM.suspend', vm._xapiRef return true @@ -660,7 +660,7 @@ resume = $coroutine ({vm, force}) -> if not force force = true - yield @getXAPI(vm).call 'VM.resume', vm.ref, false, force + yield @getXAPI(vm).call 'VM.resume', vm._xapiRef, false, force return true @@ -679,7 +679,7 @@ exports.resume = resume # revert a snapshot to its parent VM revert = $coroutine ({snapshot}) -> # Attempts a revert from this snapshot to its parent VM - yield @getXAPI(snapshot).call 'VM.revert', snapshot.ref + yield @getXAPI(snapshot).call 'VM.revert', snapshot._xapiRef return true @@ -713,7 +713,7 @@ export_ = $coroutine ({vm, compress, onlyMetadata}) -> if vm.power_state is 'Running' yield checkPermissionsForSnapshot.call(this, vm) - stream = yield @getXAPI(vm).exportVm(vm.id, { + stream = yield @getXAPI(vm).exportVm(vm._xapiId, { compress: compress ? true, onlyMetadata: onlyMetadata ? false }) @@ -779,7 +779,7 @@ exports.import = import_ # FIXME: if position is used, all other disks after this position # should be shifted. attachDisk = $coroutine ({vm, vdi, position, mode, bootable}) -> - yield @getXAPI(vm).attachVdiToVm(vdi.id, vm.id, { + yield @getXAPI(vm).attachVdiToVm(vdi._xapiId, vm._xapiId, { bootable, position, readOnly: mode is 'RO' @@ -808,7 +808,7 @@ exports.attachDisk = attachDisk # FIXME: position should be optional and default to last. createInterface = $coroutine ({vm, network, position, mtu, mac}) -> - vif = yield @getXAPI(vm).createVif(vm.id, network.id, { + vif = yield @getXAPI(vm).createVif(vm._xapiId, network._xapiId, { mac, mtu, position @@ -835,7 +835,7 @@ exports.createInterface = createInterface attachPci = $coroutine ({vm, pciId}) -> xapi = @getXAPI vm - yield xapi.call 'VM.add_to_other_config', vm.ref, 'pci', pciId + yield xapi.call 'VM.add_to_other_config', vm._xapiRef, 'pci', pciId return true @@ -855,7 +855,7 @@ exports.attachPci = attachPci detachPci = $coroutine ({vm}) -> xapi = @getXAPI vm - yield xapi.call 'VM.remove_from_other_config', vm.ref, 'pci' + yield xapi.call 'VM.remove_from_other_config', vm._xapiRef, 'pci' return true @@ -899,7 +899,7 @@ bootOrder = $coroutine ({vm, order}) -> order = {order: order} - yield xapi.call 'VM.set_HVM_boot_params', vm.ref, order + yield xapi.call 'VM.set_HVM_boot_params', vm._xapiRef, order return true diff --git a/src/xapi-object-to-xo.js b/src/xapi-object-to-xo.js new file mode 100644 index 000000000..4c71a165c --- /dev/null +++ b/src/xapi-object-to-xo.js @@ -0,0 +1,592 @@ +import isArray from 'lodash.isarray' + +import { + ensureArray, + extractProperty, + forEach, + mapToArray, + parseXml +} from './utils' +import { + isHostRunning, + isVmHvm, + isVmRunning, + parseDateTime +} from './xapi' + +// =================================================================== + +const { + defineProperties, + freeze +} = Object + +function link (obj, prop, idField = '$id') { + const dynamicValue = obj[`$${prop}`] + if (dynamicValue == null) { + return dynamicValue // Properly handles null and undefined. + } + + if (isArray(dynamicValue)) { + return mapToArray(dynamicValue, idField) + } + + return dynamicValue[idField] +} + +// Parse a string date time to a Unix timestamp (in seconds). +// +// If there are no data or if the timestamp is 0, returns null. +function toTimestamp (date) { + if (!date) { + return null + } + + const ms = parseDateTime(date).getTime() + if (!ms) { + return null + } + + return Math.round(ms / 1000) +} + +// =================================================================== + +const TRANSFORMS = { + pool (obj) { + return { + default_SR: link(obj, 'default_SR'), + HA_enabled: Boolean(obj.ha_enabled), + master: link(obj, 'master'), + tags: obj.tags, + name_description: obj.name_description, + name_label: obj.name_label || obj.$master.name_label + + // TODO + // - ? networks = networksByPool.items[pool.id] (network.$pool.id) + // - hosts = hostsByPool.items[pool.id] (host.$pool.$id) + // - patches = poolPatchesByPool.items[pool.id] (poolPatch.$pool.id) + // - SRs = srsByContainer.items[pool.id] (sr.$container.id) + // - templates = vmTemplatesByContainer.items[pool.id] (vmTemplate.$container.$id) + // - VMs = vmsByContainer.items[pool.id] (vm.$container.id) + // - $running_hosts = runningHostsByPool.items[pool.id] (runningHost.$pool.id) + // - $running_VMs = runningVmsByPool.items[pool.id] (runningHost.$pool.id) + // - $VMs = vmsByPool.items[pool.id] (vm.$pool.id) + } + }, + + // ----------------------------------------------------------------- + + host (obj) { + const { + $metrics: metrics, + other_config: otherConfig + } = obj + + const isRunning = isHostRunning(obj) + + return { + address: obj.address, + bios_strings: obj.bios_strings, + build: obj.software_version.build_number, + CPUs: obj.cpu_info, + enabled: Boolean(obj.enabled), + current_operations: obj.current_operations, + hostname: obj.hostname, + iSCSI_name: otherConfig.iscsi_iqn || null, + name_description: obj.name_description, + name_label: obj.name_label, + memory: (function () { + if (metrics) { + const free = +metrics.memory_free + const total = +metrics.memory_total + + return { + usage: total - free, + size: total + } + } + + return { + usage: 0, + total: 0 + } + })(), + patches: link(obj, 'patches'), + powerOnMode: obj.power_on_mode, + power_state: isRunning ? 'Running' : 'Halted', + tags: obj.tags, + version: obj.software_version.product_version, + + // TODO: dedupe. + PIFs: link(obj, 'PIFs'), + $PIFs: link(obj, 'PIFs'), + PCIs: link(obj, 'PCIs'), + $PCIs: link(obj, 'PCIs'), + PGPUs: link(obj, 'PGPUs'), + $PGPUs: link(obj, 'PGPUs'), + + $PBDs: link(obj, 'PBDs') + + // TODO: + // - controller = vmControllersByContainer.items[host.id] + // - SRs = srsByContainer.items[host.id] + // - tasks = tasksByHost.items[host.id] + // - templates = vmTemplatesByContainer.items[host.id] + // - VMs = vmsByContainer.items[host.id] + // - $vCPUs = sum(host.VMs, vm => host.CPUs.number) + } + }, + + // ----------------------------------------------------------------- + + vm (obj) { + const { + $guest_metrics: guestMetrics, + $metrics: metrics, + other_config: otherConfig + } = obj + + const isHvm = isVmHvm(obj) + const isRunning = isVmRunning(obj) + + const vm = { + // type is redefined after for controllers/, templates & + // snapshots. + type: 'VM', + + addresses: guestMetrics && guestMetrics.networks || null, + auto_poweron: Boolean(otherConfig.auto_poweron), + boot: obj.HVM_boot_params, + CPUs: { + max: +obj.VCPUs_max, + number: ( + isRunning && metrics + ? +metrics.VCPUs_number + : +obj.VCPUs_at_startup + ) + }, + current_operations: obj.current_operations, + docker: (function () { + const monitor = otherConfig['xscontainer-monitor'] + if (!monitor) { + return + } + + if (monitor === 'False') { + return { + enabled: false + } + } + + const { + docker_ps: process, + docker_info: info, + docker_version: version + } = otherConfig + + return { + enabled: true, + info: info && parseXml(info).docker_info, + process: process && parseXml(process).docker_ps, + version: version && parseXml(version).docker_version + } + })(), + + // TODO: there is two possible value: "best-effort" and "restart" + high_availability: Boolean(obj.ha_restart_priority), + + memory: (function () { + const dynamicMin = +obj.memory_dynamic_min + const dynamicMax = +obj.memory_dynamic_max + const staticMin = +obj.memory_static_min + const staticMax = +obj.memory_static_max + + const memory = { + dynamic: [ dynamicMin, dynamicMax ], + static: [ staticMin, staticMax ] + } + + const gmMemory = guestMetrics && guestMetrics.memory + + if (!isRunning) { + memory.size = dynamicMax + } else if (gmMemory && gmMemory.used) { + memory.usage = +gmMemory.used + memory.size = +gmMemory.total + } else if (metrics) { + memory.size = +metrics.memory_actual + } else { + memory.size = dynamicMax + } + + return memory + })(), + name_description: obj.name_description, + name_label: obj.name_label, + other: otherConfig, + os_version: guestMetrics && guestMetrics.os_version || null, + power_state: obj.power_state, + snapshot_time: toTimestamp(obj.snapshot_time), + snapshots: link(obj, 'snapshots'), + tags: obj.tags, + VIFs: link(obj, 'VIFs'), + virtualizationMode: isHvm ? 'hvm' : 'pv', + + // <=> Are the Xen Server tools installed? + // + // - undefined: unknown status + // - false: not optimized + // - 'out of date': optimized but drivers should be updated + // - 'up to date': optimized + xenTools: (() => { + if (!isRunning || !metrics) { + // Unknown status, returns nothing. + return + } + + if (!guestMetrics) { + return false + } + + const { PV_drivers_version: { major, minor } } = guestMetrics + if (major === undefined || minor === undefined) { + return false + } + + return guestMetrics.PV_drivers_up_to_date + ? 'up to date' + : 'out of date' + })(), + + $container: ( + isRunning + ? link(obj, 'resident_on') + : link(obj, 'pool') // TODO: handle local VMs (`VM.get_possible_hosts()`). + ), + $VBDs: link(obj, 'VBDs'), + + // TODO: dedupe + VGPUs: link(obj, 'VGPUs'), + $VGPUs: link(obj, 'VGPUs') + } + + if (obj.is_control_domain) { + vm.type += '-controller' + } else if (obj.is_a_snapshot) { + vm.type += '-snapshot' + + vm.$snapshot_of = link(obj, 'snapshot_of') + } else if (obj.is_a_template) { + vm.type += '-template' + + vm.CPUs.number = +obj.VCPUs_at_startup + vm.template_info = { + arch: otherConfig['install-arch'], + disks: (function () { + const {disks: xml} = otherConfig + let data + if (!xml || !(data = parseXml(xml)).provision) { + return [] + } + + const disks = ensureArray(data.provision.disk) + forEach(disks, function normalize (disk) { + disk.bootable = disk.bootable === 'true' + disk.size = +disk.size + disk.SR = extractProperty(disk, 'sr') + }) + + return disks + })(), + install_methods: (function () { + const {['install-methods']: methods} = otherConfig + + return methods ? methods.split(',') : [] + })() + } + } + + if (!isHvm) { + vm.PV_args = obj.PV_args + } + + return vm + }, + + // ----------------------------------------------------------------- + + sr (obj) { + return { + type: 'SR', + + content_type: obj.content_type, + name_description: obj.name_description, + name_label: obj.name_label, + physical_usage: +obj.physical_utilisation, + size: +obj.physical_size, + SR_type: obj.type, + tags: obj.tags, + usage: +obj.virtual_allocation, + VDIs: link(obj, 'VDIs'), + + $container: ( + obj.shared + ? link(obj, 'pool') + : obj.$PBDs[0] && link(obj.$PBDs[0], 'host') + ), + $PBDs: link(obj, 'PBDs') + } + }, + + // ----------------------------------------------------------------- + + pbd (obj) { + return { + type: 'PBD', + + attached: obj.currently_attached, + host: link(obj, 'host'), + SR: link(obj, 'SR') + } + }, + + // ----------------------------------------------------------------- + + pif (obj) { + return { + type: 'PIF', + + attached: Boolean(obj.currently_attached), + device: obj.device, + IP: obj.IP, + MAC: obj.MAC, + management: Boolean(obj.management), // TODO: find a better name. + mode: obj.ip_configuration_mode, + MTU: +obj.MTU, + netmask: obj.netmask, + vlan: +obj.VLAN, + + // TODO: What is it? + // + // Could it mean “is this a physical interface?”. + // How could a PIF not be physical? + // physical: obj.physical, + + $host: link(obj, 'host'), + $network: link(obj, 'network') + } + }, + + // ----------------------------------------------------------------- + + // TODO: should we have a VDI-snapshot type like we have with VMs? + vdi (obj) { + if (!obj.managed) { + return + } + + return { + type: 'VDI', + + name_description: obj.name_description, + name_label: obj.name_label, + size: +obj.virtual_size, + snapshots: link(obj, 'snapshots'), + snapshot_time: toTimestamp(obj.snapshot_time), + tags: obj.tags, + usage: +obj.physical_utilisation, + + $snapshot_of: link(obj, 'snapshot_of'), + $SR: link(obj, 'SR'), + $VBDs: link(obj, 'VBDs') + } + }, + + // ----------------------------------------------------------------- + + vbd (obj) { + return { + type: 'VBD', + + attached: Boolean(obj.currently_attached), + bootable: Boolean(obj.bootable), + is_cd_drive: obj.type === 'CD', + position: obj.userdevice, + read_only: obj.mode === 'RO', + VDI: link(obj, 'VDI'), + VM: link(obj, 'VM') + } + }, + + // ----------------------------------------------------------------- + + vif (obj) { + return { + type: 'VIF', + + attached: Boolean(obj.currently_attached), + device: obj.device, // TODO: should it be cast to a number? + MAC: obj.MAC, + MTU: +obj.MTU, + + $network: link(obj, 'network'), + $VM: link(obj, 'VM') + } + }, + + // ----------------------------------------------------------------- + + network (obj) { + return { + bridge: obj.bridge, + MTU: +obj.MTU, + name_description: obj.name_description, + name_label: obj.name_label, + tags: obj.tags, + PIFs: link(obj, 'PIFs'), + VIFs: link(obj, 'VIFs') + } + }, + + // ----------------------------------------------------------------- + + message (obj) { + return { + body: obj.body, + name: obj.name, + time: toTimestamp(obj.timestamp), + + $object: obj.obj_uuid // Special link as it is already an UUID. + } + }, + + // ----------------------------------------------------------------- + + task (obj) { + return { + created: toTimestamp(obj.created), + current_operations: obj.current_operations, + finished: toTimestamp(obj.finished), + name_description: obj.name_description, + name_label: obj.name_label, + progress: +obj.progress, + result: obj.result, + status: obj.status, + + $host: link(obj, 'resident_on') + } + }, + + // ----------------------------------------------------------------- + + host_patch (obj) { + return { + applied: Boolean(obj.applied), + time: toTimestamp(obj.timestamp_applied), + pool_patch: link(obj, 'pool_patch', '$ref'), + + $host: link(obj, 'host') + } + }, + + // ----------------------------------------------------------------- + + pool_patch (obj) { + return { + id: obj.$ref, + + applied: Boolean(obj.pool_applied), + description: obj.name_description, + guidance: obj.after_apply_guidance, + name: obj.name_label, + size: +obj.size, + uuid: obj.uuid, + + // TODO: what does it mean, should we handle it? + // version: obj.version, + + // TODO: host.[$]pool_patches ←→ pool.[$]host_patches + $host_patches: link(obj, 'host_patches') + } + }, + + // ----------------------------------------------------------------- + + pci (obj) { + return { + type: 'PCI', + + class_name: obj.class_name, + device_name: obj.device_name, + pci_id: obj.pci_id, + + $host: link(obj, 'host') + } + }, + + // ----------------------------------------------------------------- + + pgpu (obj) { + return { + type: 'PGPU', + + pci: link(obj, 'PCI'), + + // TODO: dedupe. + host: link(obj, 'host'), + $host: link(obj, 'host'), + vgpus: link(obj, 'resident_VGPUs'), + $vgpus: link(obj, 'resident_VGPUs') + } + }, + + // ----------------------------------------------------------------- + + vgpu (obj) { + return { + type: 'VGPU', + + currentlyAttached: Boolean(obj.currently_attached), + device: obj.device, + resident_on: link(obj, 'resident_on'), + vm: link(obj, 'VM') + } + } +} + +// =================================================================== + +export default xapiObj => { + const transform = TRANSFORMS[xapiObj.$type.toLowerCase()] + if (!transform) { + return + } + + const xoObj = transform(xapiObj) + if (!xoObj) { + return + } + + if (!('id' in xoObj)) { + xoObj.id = xapiObj.$id + } + if (!('type' in xoObj)) { + xoObj.type = xapiObj.$type + } + xoObj.$pool = xapiObj.$pool.$id + xoObj.$poolId = xoObj.$pool // TODO: deprecated, remove when no longer used in xo-web + + // Internal properties. + defineProperties(xoObj, { + _xapiId: { + value: xapiObj.$id + }, + _xapiRef: { + value: xapiObj.$ref + } + }) + + // Freezes and returns the new object. + return freeze(xoObj) +} diff --git a/src/xapi-objects-to-xo.js b/src/xapi-objects-to-xo.js deleted file mode 100644 index ab28e3bf7..000000000 --- a/src/xapi-objects-to-xo.js +++ /dev/null @@ -1,549 +0,0 @@ -import isArray from 'lodash.isarray' - -import { - ensureArray, - extractProperty, - forEach, - mapToArray, - parseXml -} from './utils' -import { - isHostRunning, - isVmHvm, - isVmRunning, - parseDateTime -} from './xapi' - -// =================================================================== - -function link (obj, prop, idField = '$id') { - const dynamicValue = obj[`$${prop}`] - if (dynamicValue == null) { - return dynamicValue // Properly handles null and undefined. - } - - if (isArray(dynamicValue)) { - return mapToArray(dynamicValue, idField) - } - - return dynamicValue[idField] -} - -// Parse a string date time to a Unix timestamp (in seconds). -// -// If there are no data or if the timestamp is 0, returns null. -function toTimestamp (date) { - if (!date) { - return null - } - - const ms = parseDateTime(date).getTime() - if (!ms) { - return null - } - - return Math.round(ms / 1000) -} - -// =================================================================== - -export function pool (obj) { - return { - default_SR: link(obj, 'default_SR'), - HA_enabled: Boolean(obj.ha_enabled), - master: link(obj, 'master'), - tags: obj.tags, - name_description: obj.name_description, - name_label: obj.name_label || obj.$master.name_label - - // TODO - // - ? networks = networksByPool.items[pool.id] (network.$pool.id) - // - hosts = hostsByPool.items[pool.id] (host.$pool.$id) - // - patches = poolPatchesByPool.items[pool.id] (poolPatch.$pool.id) - // - SRs = srsByContainer.items[pool.id] (sr.$container.id) - // - templates = vmTemplatesByContainer.items[pool.id] (vmTemplate.$container.$id) - // - VMs = vmsByContainer.items[pool.id] (vm.$container.id) - // - $running_hosts = runningHostsByPool.items[pool.id] (runningHost.$pool.id) - // - $running_VMs = runningVmsByPool.items[pool.id] (runningHost.$pool.id) - // - $VMs = vmsByPool.items[pool.id] (vm.$pool.id) - } -} - -// ------------------------------------------------------------------- - -export function host (obj) { - const { - $metrics: metrics, - other_config: otherConfig - } = obj - - const isRunning = isHostRunning(obj) - - return { - address: obj.address, - bios_strings: obj.bios_strings, - build: obj.software_version.build_number, - CPUs: obj.cpu_info, - enabled: Boolean(obj.enabled), - current_operations: obj.current_operations, - hostname: obj.hostname, - iSCSI_name: otherConfig.iscsi_iqn || null, - name_description: obj.name_description, - name_label: obj.name_label, - memory: (function () { - if (metrics) { - const free = +metrics.memory_free - const total = +metrics.memory_total - - return { - usage: total - free, - size: total - } - } - - return { - usage: 0, - total: 0 - } - })(), - patches: link(obj, 'patches'), - powerOnMode: obj.power_on_mode, - power_state: isRunning ? 'Running' : 'Halted', - tags: obj.tags, - version: obj.software_version.product_version, - - // TODO: dedupe. - PIFs: link(obj, 'PIFs'), - $PIFs: link(obj, 'PIFs'), - PCIs: link(obj, 'PCIs'), - $PCIs: link(obj, 'PCIs'), - PGPUs: link(obj, 'PGPUs'), - $PGPUs: link(obj, 'PGPUs'), - - $PBDs: link(obj, 'PBDs') - - // TODO: - // - controller = vmControllersByContainer.items[host.id] - // - SRs = srsByContainer.items[host.id] - // - tasks = tasksByHost.items[host.id] - // - templates = vmTemplatesByContainer.items[host.id] - // - VMs = vmsByContainer.items[host.id] - // - $vCPUs = sum(host.VMs, vm => host.CPUs.number) - } -} - -// ------------------------------------------------------------------- - -export function vm (obj) { - const { - $guest_metrics: guestMetrics, - $metrics: metrics, - other_config: otherConfig - } = obj - - const isHvm = isVmHvm(obj) - const isRunning = isVmRunning(obj) - - const vm = { - // type is redefined after for controllers/, templates & - // snapshots. - type: 'VM', - - addresses: guestMetrics && guestMetrics.networks || null, - auto_poweron: Boolean(otherConfig.auto_poweron), - boot: obj.HVM_boot_params, - CPUs: { - max: +obj.VCPUs_max, - number: ( - isRunning && metrics - ? +metrics.VCPUs_number - : +obj.VCPUs_at_startup - ) - }, - current_operations: obj.current_operations, - docker: (function () { - const monitor = otherConfig['xscontainer-monitor'] - if (!monitor) { - return - } - - if (monitor === 'False') { - return { - enabled: false - } - } - - const { - docker_ps: process, - docker_info: info, - docker_version: version - } = otherConfig - - return { - enabled: true, - info: info && parseXml(info).docker_info, - process: process && parseXml(process).docker_ps, - version: version && parseXml(version).docker_version - } - })(), - - // TODO: there is two possible value: "best-effort" and "restart" - high_availability: Boolean(obj.ha_restart_priority), - - memory: (function () { - const dynamicMin = +obj.memory_dynamic_min - const dynamicMax = +obj.memory_dynamic_max - const staticMin = +obj.memory_static_min - const staticMax = +obj.memory_static_max - - const memory = { - dynamic: [ dynamicMin, dynamicMax ], - static: [ staticMin, staticMax ] - } - - const gmMemory = guestMetrics && guestMetrics.memory - - if (!isRunning) { - memory.size = dynamicMax - } else if (gmMemory && gmMemory.used) { - memory.usage = +gmMemory.used - memory.size = +gmMemory.total - } else if (metrics) { - memory.size = +metrics.memory_actual - } else { - memory.size = dynamicMax - } - - return memory - })(), - name_description: obj.name_description, - name_label: obj.name_label, - other: otherConfig, - os_version: guestMetrics && guestMetrics.os_version || null, - power_state: obj.power_state, - snapshot_time: toTimestamp(obj.snapshot_time), - snapshots: link(obj, 'snapshots'), - tags: obj.tags, - VIFs: link(obj, 'VIFs'), - virtualizationMode: isHvm ? 'hvm' : 'pv', - - // <=> Are the Xen Server tools installed? - // - // - undefined: unknown status - // - false: not optimized - // - 'out of date': optimized but drivers should be updated - // - 'up to date': optimized - xenTools: (() => { - if (!isRunning || !metrics) { - // Unknown status, returns nothing. - return - } - - if (!guestMetrics) { - return false - } - - const { PV_drivers_version: { major, minor } } = guestMetrics - if (major === undefined || minor === undefined) { - return false - } - - return guestMetrics.PV_drivers_up_to_date - ? 'up to date' - : 'out of date' - })(), - - $container: ( - isRunning - ? link(obj, 'resident_on') - : link(obj, 'pool') // TODO: handle local VMs (`VM.get_possible_hosts()`). - ), - $VBDs: link(obj, 'VBDs'), - - // TODO: dedupe - VGPUs: link(obj, 'VGPUs'), - $VGPUs: link(obj, 'VGPUs') - } - - if (obj.is_control_domain) { - vm.type += '-controller' - } else if (obj.is_a_snapshot) { - vm.type += '-snapshot' - - vm.$snapshot_of = link(obj, 'snapshot_of') - } else if (obj.is_a_template) { - vm.type += '-template' - - vm.CPUs.number = +obj.VCPUs_at_startup - vm.template_info = { - arch: otherConfig['install-arch'], - disks: (function () { - const {disks: xml} = otherConfig - let data - if (!xml || !(data = parseXml(xml)).provision) { - return [] - } - - const disks = ensureArray(data.provision.disk) - forEach(disks, function normalize (disk) { - disk.bootable = disk.bootable === 'true' - disk.size = +disk.size - disk.SR = extractProperty(disk, 'sr') - }) - - return disks - })(), - install_methods: (function () { - const {['install-methods']: methods} = otherConfig - - return methods ? methods.split(',') : [] - })() - } - } - - if (!isHvm) { - vm.PV_args = obj.PV_args - } - - return vm -} - -// ------------------------------------------------------------------- - -export function sr (obj) { - return { - type: 'SR', - - content_type: obj.content_type, - name_description: obj.name_description, - name_label: obj.name_label, - physical_usage: +obj.physical_utilisation, - size: +obj.physical_size, - SR_type: obj.type, - tags: obj.tags, - usage: +obj.virtual_allocation, - VDIs: link(obj, 'VDIs'), - - $container: ( - obj.shared - ? link(obj, 'pool') - : obj.$PBDs[0] && link(obj.$PBDs[0], 'host') - ), - $PBDs: link(obj, 'PBDs') - } -} - -// ------------------------------------------------------------------- - -export function pbd (obj) { - return { - type: 'PBD', - - attached: obj.currently_attached, - host: link(obj, 'host'), - SR: link(obj, 'SR') - } -} - -// ------------------------------------------------------------------- - -export function pif (obj) { - return { - type: 'PIF', - - attached: Boolean(obj.currently_attached), - device: obj.device, - IP: obj.IP, - MAC: obj.MAC, - management: Boolean(obj.management), // TODO: find a better name. - mode: obj.ip_configuration_mode, - MTU: +obj.MTU, - netmask: obj.netmask, - vlan: +obj.VLAN, - - // TODO: What is it? - // - // Could it mean “is this a physical interface?”. - // How could a PIF not be physical? - // physical: obj.physical, - - $host: link(obj, 'host'), - $network: link(obj, 'network') - } -} - -// ------------------------------------------------------------------- - -// TODO: should we have a VDI-snapshot type like we have with VMs? -export function vdi (obj) { - if (!obj.managed) { - return - } - - return { - type: 'VDI', - - name_description: obj.name_description, - name_label: obj.name_label, - size: +obj.virtual_size, - snapshots: link(obj, 'snapshots'), - snapshot_time: toTimestamp(obj.snapshot_time), - tags: obj.tags, - usage: +obj.physical_utilisation, - - $snapshot_of: link(obj, 'snapshot_of'), - $SR: link(obj, 'SR'), - $VBDs: link(obj, 'VBDs') - } -} - -// ------------------------------------------------------------------- - -export function vbd (obj) { - return { - type: 'VBD', - - attached: Boolean(obj.currently_attached), - bootable: Boolean(obj.bootable), - is_cd_drive: obj.type === 'CD', - position: obj.userdevice, - read_only: obj.mode === 'RO', - VDI: link(obj, 'VDI'), - VM: link(obj, 'VM') - } -} - -// ------------------------------------------------------------------- - -export function vif (obj) { - return { - type: 'VIF', - - attached: Boolean(obj.currently_attached), - device: obj.device, // TODO: should it be cast to a number? - MAC: obj.MAC, - MTU: +obj.MTU, - - $network: link(obj, 'network'), - $VM: link(obj, 'VM') - } -} - -// ------------------------------------------------------------------- - -export function network (obj) { - return { - bridge: obj.bridge, - MTU: +obj.MTU, - name_description: obj.name_description, - name_label: obj.name_label, - tags: obj.tags, - PIFs: link(obj, 'PIFs'), - VIFs: link(obj, 'VIFs') - } -} - -// ------------------------------------------------------------------- - -export function message (obj) { - return { - body: obj.body, - name: obj.name, - time: toTimestamp(obj.timestamp), - - $object: obj.obj_uuid // Special link as it is already an UUID. - } -} - -// ------------------------------------------------------------------- - -export function task (obj) { - return { - created: toTimestamp(obj.created), - current_operations: obj.current_operations, - finished: toTimestamp(obj.finished), - name_description: obj.name_description, - name_label: obj.name_label, - progress: +obj.progress, - result: obj.result, - status: obj.status, - - $host: link(obj, 'resident_on') - } -} - -// ------------------------------------------------------------------- - -export function host_patch (obj) { - return { - applied: Boolean(obj.applied), - time: toTimestamp(obj.timestamp_applied), - pool_patch: link(obj, 'pool_patch', '$ref'), - - $host: link(obj, 'host') - } -} - -// ------------------------------------------------------------------- - -export function pool_patch (obj) { - return { - id: obj.$ref, - - applied: Boolean(obj.pool_applied), - description: obj.name_description, - guidance: obj.after_apply_guidance, - name: obj.name_label, - size: +obj.size, - uuid: obj.uuid, - - // TODO: what does it mean, should we handle it? - // version: obj.version, - - // TODO: host.[$]pool_patches ←→ pool.[$]host_patches - $host_patches: link(obj, 'host_patches') - } -} - -// ------------------------------------------------------------------- - -export function pci (obj) { - return { - type: 'PCI', - - class_name: obj.class_name, - device_name: obj.device_name, - pci_id: obj.pci_id, - - $host: link(obj, 'host') - } -} - -// ------------------------------------------------------------------- - -export function pgpu (obj) { - return { - type: 'PGPU', - - pci: link(obj, 'PCI'), - - // TODO: dedupe. - host: link(obj, 'host'), - $host: link(obj, 'host'), - vgpus: link(obj, 'resident_VGPUs'), - $vgpus: link(obj, 'resident_VGPUs') - } -} - -// ------------------------------------------------------------------- - -export function vgpu (obj) { - return { - type: 'VGPU', - - currentlyAttached: Boolean(obj.currently_attached), - device: obj.device, - resident_on: link(obj, 'resident_on'), - vm: link(obj, 'VM') - } -} diff --git a/src/xapi.js b/src/xapi.js index 16099a52e..97735b49e 100644 --- a/src/xapi.js +++ b/src/xapi.js @@ -77,7 +77,7 @@ const getNamespaceForType = (type) => typeToNamespace[type] || type // =================================================================== // Format a date (pseudo ISO 8601) from one XenServer get by -// xapi.call('host.get_servertime', host.ref) for example +// xapi.call('host.get_servertime', host.$ref) for example export const formatDateTime = d3TimeFormat.utcFormat('%Y%m%dT%H:%M:%SZ') export const parseDateTime = formatDateTime.parse @@ -232,6 +232,16 @@ export default class Xapi extends XapiBase { })) } + async setHostProperties (id, { + name_label, + name_description + }) { + await this._setObjectProperties(this.getObject(id), { + name_label, + name_description + }) + } + async setPoolProperties ({ name_label, name_description @@ -552,6 +562,60 @@ export default class Xapi extends XapiBase { // ================================================================= + // Disable the host and evacuate all its VMs. + // + // If `force` is false and the evacuation failed, the host is re- + // enabled and the error is thrown. + async _clearHost ({ $ref, ref }, force) { + await this.call('host.disable', ref) + + try { + await this.call('host.evacuate', ref) + } catch (error) { + if (!force) { + await this.call('host.enabled', ref) + + throw error + } + } + } + + async disableHost (hostId) { + await this.call('host.disable', this.getObject(hostId).$ref) + } + + async ejectHostFromPool (hostId) { + await this.call('pool.eject', this.getObject(hostId).$ref) + } + + async enableHost (hostId) { + await this.call('host.enable', this.getObject(hostId).$ref) + } + + async powerOnHost (hostId) { + await this.call('host.power_on', this.getObject(hostId).$ref) + } + + async rebootHost (hostId, force = false) { + const host = this.getObject(hostId) + + await this._clearHost(host, force) + await this.call('host.reboot', host.$ref) + } + + async restartHostAgent (hostId) { + await this.call('host.restart_agent', this.getObject(hostId).$ref) + } + + async shutdownHost (hostId, force = false) { + const host = this.getObject(hostId) + + await this._clearHost(host, force) + await this.call('host.shutdown', host.$ref) + } + + // ================================================================= + // Clone a VM: make a fast copy by fast copying each of its VDIs // (using snapshots where possible) on the same SRs. async _cloneVm (vm, nameLabel = vm.name_label) { diff --git a/src/xo.js b/src/xo.js index 6bfca06ca..e8289a9a7 100644 --- a/src/xo.js +++ b/src/xo.js @@ -21,14 +21,13 @@ import { verify } from 'hashy' -import * as xapiObjectsToXo from './xapi-objects-to-xo' import checkAuthorization from './acl' import Connection from './connection' import LevelDbLogger from './loggers/leveldb' import Xapi from './xapi' +import xapiObjectToXo from './xapi-object-to-xo' import XapiStats from './xapi-stats' import {Acls} from './models/acl' -import {autobind} from './decorators' import { createRawObject, forEach, @@ -110,7 +109,7 @@ export default class Xo extends EventEmitter { super() this._objects = new XoCollection() - this._objects.createIndex('byRef', new XoUniqueIndex('ref')) + this._objects.createIndex('byRef', new XoUniqueIndex('_xapiRef')) // These will be initialized in start() // @@ -788,7 +787,7 @@ export default class Xo extends EventEmitter { const targetStream = fs.createWriteStream(pathToFile, { flags: 'wx' }) const promise = eventToPromise(targetStream, 'finish') - const sourceStream = await this.getXAPI(vm).exportVm(vm.id, { + const sourceStream = await this.getXAPI(vm).exportVm(vm._xapiId, { compress, onlyMetadata: onlyMetadata || false }) @@ -821,7 +820,7 @@ export default class Xo extends EventEmitter { async rollingSnapshotVm (vm, tag, depth) { const xapi = this.getXAPI(vm) - vm = xapi.getObject(vm.id) + vm = xapi.getObject(vm._xapiId) const reg = new RegExp('^rollingSnapshot_[^_]+_' + escapeStringRegexp(tag) + '_') const snapshots = sortBy(filter(vm.$snapshots, snapshot => reg.test(snapshot.name_label)), 'name_label') @@ -842,9 +841,9 @@ export default class Xo extends EventEmitter { const reg = new RegExp('^' + escapeStringRegexp(`${vm.name_label}_${tag}_`) + '[0-9]{8}T[0-9]{6}Z$') const targetXapi = this.getXAPI(sr) - sr = targetXapi.getObject(sr.id) + sr = targetXapi.getObject(sr._xapiId) const sourceXapi = this.getXAPI(vm) - vm = sourceXapi.getObject(vm.id) + vm = sourceXapi.getObject(vm._xapiId) const vms = [] forEach(sr.$VDIs, vdi => { @@ -934,48 +933,28 @@ export default class Xo extends EventEmitter { return server } - @autobind - _onXenAdd (xapiObjects) { + _onXenAdd (xapiObjects, xapiIdsToXo) { const {_objects: objects} = this - forEach(xapiObjects, (xapiObject, id) => { - const transform = xapiObjectsToXo[xapiObject.$type] - if (!transform) { - return - } + forEach(xapiObjects, (xapiObject, xapiId) => { + const xoObject = xapiObjectToXo(xapiObject) - const xoObject = transform(xapiObject) - if (!xoObject) { - return - } + if (xoObject) { + xapiIdsToXo[xapiId] = xoObject.id - if (!xoObject.id) { - xoObject.id = id + objects.set(xoObject) } - xoObject.ref = xapiObject.$ref - if (!xoObject.type) { - xoObject.type = xapiObject.$type - } - - const {$pool: pool} = xapiObject - Object.defineProperties(xoObject, { - poolRef: { value: pool.$ref }, - $poolId: { - enumerable: true, - value: pool.$id - }, - ref: { value: xapiObject.$ref } - }) - - objects.set(xoObject) }) } - @autobind - _onXenRemove (xapiObjects) { + _onXenRemove (xapiObjects, xapiIdsToXo) { const {_objects: objects} = this - forEach(xapiObjects, (_, id) => { - if (objects.has(id)) { - objects.remove(id) + forEach(xapiObjects, (_, xapiId) => { + const xoId = xapiIdsToXo[xapiId] + + if (xoId) { + delete xapiIdsToXo[xapiId] + + objects.unset(xoId) } }) } @@ -992,16 +971,41 @@ export default class Xo extends EventEmitter { } }) - const {objects} = xapi - objects.on('add', this._onXenAdd) - objects.on('update', this._onXenAdd) - objects.on('remove', this._onXenRemove) + xapi.xo = (() => { + const xapiIdsToXo = createRawObject() + const onAddOrUpdate = objects => { + this._onXenAdd(objects, xapiIdsToXo) + } + const onRemove = objects => { + this._onXenRemove(objects, xapiIdsToXo) + } + const onFinish = () => { + this._xapis[xapi.pool.$id] = xapi + } - // Each time objects are refreshed, registers the connection with - // the pool identifier. - objects.on('finish', () => { - this._xapis[xapi.pool.$id] = xapi - }) + const { objects } = xapi + + return { + install () { + objects.on('add', onAddOrUpdate) + objects.on('update', onAddOrUpdate) + objects.on('remove', onRemove) + objects.on('finish', onFinish) + + onAddOrUpdate(objects.all) + }, + uninstall () { + objects.removeListener('add', onAddOrUpdate) + objects.removeListener('update', onAddOrUpdate) + objects.removeListener('remove', onRemove) + objects.removeListener('finish', onFinish) + + onRemove(objects.all) + } + } + })() + + xapi.xo.install() try { await xapi.connect() @@ -1028,6 +1032,7 @@ export default class Xo extends EventEmitter { delete this._xapis[xapi.pool.id] } + xapi.xo.uninstall() return xapi.disconnect() } @@ -1037,7 +1042,7 @@ export default class Xo extends EventEmitter { object = this.getObject(object, type) } - const {$poolId: poolId} = object + const { $pool: poolId } = object if (!poolId) { throw new Error(`object ${object.id} does not belong to a pool`) } @@ -1052,12 +1057,12 @@ export default class Xo extends EventEmitter { getXapiVmStats (vm, granularity) { const xapi = this.getXAPI(vm) - return this._xapiStats.getVmPoints(xapi, vm.id, granularity) + return this._xapiStats.getVmPoints(xapi, vm._xapiId, granularity) } getXapiHostStats (host, granularity) { const xapi = this.getXAPI(host) - return this._xapiStats.getHostPoints(xapi, host.id, granularity) + return this._xapiStats.getHostPoints(xapi, host._xapiId, granularity) } async mergeXenPools (sourceId, targetId, force = false) { @@ -1069,26 +1074,12 @@ export default class Xo extends EventEmitter { // We don't want the events of the source XAPI to interfere with // the events of the new XAPI. - { - const {objects} = sourceXapi - - objects.removeListener('add', this._onXenAdd) - objects.removeListener('update', this._onXenAdd) - objects.removeListener('remove', this._onXenRemove) - - this._onXenRemove(objects.all) - } + sourceXapi.xo.uninstall() try { await sourceXapi.joinPool(hostname, user, password, force) } catch (e) { - const {objects} = sourceXapi - - objects.on('add', this._onXenAdd) - objects.on('update', this._onXenAdd) - objects.on('remove', this._onXenRemove) - - this._onXenAdd(objects.all) + sourceXapi.xo.install() throw e }