Properly remove objects for which xo.id !== xapi.$id.

This commit is contained in:
Julien Fontanet 2015-11-10 22:26:57 +01:00
parent b55accd76f
commit 5ee11c7b6b
19 changed files with 836 additions and 765 deletions

View File

@ -51,13 +51,13 @@ function checkSelf ({ id }, permission) {
// =================================================================== // ===================================================================
const checkAuthorizationByTypes = { const checkAuthorizationByTypes = {
host: or(checkSelf, checkMember('$poolId')), host: or(checkSelf, checkMember('$pool')),
message: checkMember('$object'), 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'), task: checkMember('$host'),

View File

@ -5,7 +5,7 @@ import {parseSize} from '../utils'
export async function create ({name, size, sr}) { export async function create ({name, size, sr}) {
const vdi = await this.getXAPI(sr).createVdi(parseSize(size), { const vdi = await this.getXAPI(sr).createVdi(parseSize(size), {
name_label: name, name_label: name,
sr: sr.id sr: sr._xapiId
}) })
return vdi.$id return vdi.$id
} }

View File

@ -1,5 +1,5 @@
export async function register ({vm}) { export async function register ({vm}) {
await this.getXAPI(vm).registerDockerContainer(vm.id) await this.getXAPI(vm).registerDockerContainer(vm._xapiId)
} }
register.permission = 'admin' register.permission = 'admin'
@ -16,7 +16,7 @@ register.resolve = {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export async function deregister ({vm}) { export async function deregister ({vm}) {
await this.getXAPI(vm).unregisterDockerContainer(vm.id) await this.getXAPI(vm).unregisterDockerContainer(vm._xapiId)
} }
deregister.permission = 'admin' deregister.permission = 'admin'
@ -33,23 +33,23 @@ deregister.resolve = {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
export async function start ({vm, container}) { 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}) { 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}) { 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}) { 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}) { 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]) { for (let fn of [start, stop, restart, pause, unpause]) {

View File

@ -6,23 +6,18 @@ endsWith = require 'lodash.endswith'
got = require('got') got = require('got')
startsWith = require 'lodash.startswith' startsWith = require 'lodash.startswith'
{coroutine: $coroutine} = require 'bluebird' {coroutine: $coroutine} = require 'bluebird'
{parseXml, promisify} = require '../utils' {
extractProperty,
parseXml,
promisify
} = require '../utils'
#===================================================================== #=====================================================================
set = $coroutine (params) -> set = (params) ->
{host} = params host = extractProperty(params, 'host')
xapi = @getXAPI host
for param, field of { return @getXAPI(host).setHostProperties(host._xapiId, params)
'name_label'
'name_description'
}
continue unless param of params
yield xapi.call "host.set_#{field}", host.ref, params[param]
return true
set.description = 'changes the properties of an host' set.description = 'changes the properties of an host'
@ -43,18 +38,19 @@ exports.set = set
#--------------------------------------------------------------------- #---------------------------------------------------------------------
restart = $coroutine ({host}) -> # FIXME: set force to false per default when correctly implemented in
xapi = @getXAPI host # UI.
restart = ({host, force = true}) ->
yield xapi.call 'host.disable', host.ref return @getXAPI(host).rebootHost(host._xapiId, force)
yield xapi.call 'host.reboot', host.ref
return true
restart.description = 'restart the host' restart.description = 'restart the host'
restart.params = { restart.params = {
id: { type: 'string' } id: { type: 'string' },
force: {
type: 'boolean',
optional: true
}
} }
restart.resolve = { restart.resolve = {
@ -65,12 +61,8 @@ exports.restart = restart
#--------------------------------------------------------------------- #---------------------------------------------------------------------
restartAgent = $coroutine ({host}) -> restartAgent = ({host}) ->
xapi = @getXAPI host return @getXAPI(host).restartHostAgent(host._xapiId)
yield xapi.call 'host.restart_agent', host.ref
return true
restartAgent.description = 'restart the Xen agent on the host' restartAgent.description = 'restart the Xen agent on the host'
@ -79,7 +71,7 @@ restartAgent.params = {
} }
restartAgent.resolve = { restartAgent.resolve = {
host: ['id', 'host', 'operate'], host: ['id', 'host', 'administrate'],
} }
# TODO camel case # TODO camel case
@ -87,12 +79,8 @@ exports.restart_agent = restartAgent
#--------------------------------------------------------------------- #---------------------------------------------------------------------
start = $coroutine ({host}) -> start = ({host}) ->
xapi = @getXAPI host return @getXAPI(host).powerOnHost(host._xapiId)
yield xapi.call 'host.power_on', host.ref
return true
start.description = 'start the host' start.description = 'start the host'
@ -108,13 +96,8 @@ exports.start = start
#--------------------------------------------------------------------- #---------------------------------------------------------------------
stop = $coroutine ({host}) -> stop = ({host}) ->
xapi = @getXAPI host return @getXAPI(host).shutdownHost(host._xapiId)
yield xapi.call 'host.disable', host.ref
yield xapi.call 'host.shutdown', host.ref
return true
stop.description = 'stop the host' stop.description = 'stop the host'
@ -130,12 +113,8 @@ exports.stop = stop
#--------------------------------------------------------------------- #---------------------------------------------------------------------
detach = $coroutine ({host}) -> detach = ({host}) ->
xapi = @getXAPI host return @getXAPI(host).ejectHostFromPool(host._xapiId)
yield xapi.call 'pool.eject', host.ref
return true
detach.description = 'eject the host of a pool' detach.description = 'eject the host of a pool'
@ -151,12 +130,8 @@ exports.detach = detach
#--------------------------------------------------------------------- #---------------------------------------------------------------------
enable = $coroutine ({host}) -> enable = ({host}) ->
xapi = @getXAPI host return @getXAPI(host).enableHost(host._xapiId)
yield xapi.call 'host.enable', host.ref
return true
enable.description = 'enable to create VM on the host' enable.description = 'enable to create VM on the host'
@ -172,12 +147,8 @@ exports.enable = enable
#--------------------------------------------------------------------- #---------------------------------------------------------------------
disable = $coroutine ({host}) -> disable = ({host}) ->
xapi = @getXAPI host return @getXAPI(host).disableHost(host._xapiId)
yield xapi.call 'host.disable', host.ref
return true
disable.description = 'disable to create VM on the hsot' 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}) -> createNetwork = $coroutine ({host, name, description, pif, mtu, vlan}) ->
xapi = @getXAPI host xapi = @getXAPI host
@ -208,7 +180,7 @@ createNetwork = $coroutine ({host, name, description, pif, mtu, vlan}) ->
if pif? if pif?
vlan = vlan ? '0' vlan = vlan ? '0'
pif = @getObject pif, 'PIF' 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 return true
@ -233,7 +205,7 @@ exports.createNetwork = createNetwork
# Throws an error if the host is not running the latest XS version # Throws an error if the host is not running the latest XS version
listMissingPatches = ({host}) -> listMissingPatches = ({host}) ->
return @getXAPI(host).listMissingPoolPatchesOnHost(host.id) return @getXAPI(host).listMissingPoolPatchesOnHost(host._xapiId)
listMissingPatches.params = { listMissingPatches.params = {
host: { type: 'string' } host: { type: 'string' }
@ -250,7 +222,7 @@ listMissingPatches.description = 'return an array of missing new patches in the
#--------------------------------------------------------------------- #---------------------------------------------------------------------
installPatch = ({host, patch: patchUuid}) -> 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' installPatch.description = 'install a patch on an host'
@ -268,7 +240,7 @@ exports.installPatch = installPatch
#--------------------------------------------------------------------- #---------------------------------------------------------------------
installAllPatches = ({host}) -> installAllPatches = ({host}) ->
return @getXAPI(host).installAllPoolPatchesOnHost(host.id) return @getXAPI(host).installAllPoolPatchesOnHost(host._xapiId)
installAllPatches.description = 'install all the missing patches on a host' installAllPatches.description = 'install all the missing patches on a host'
@ -284,9 +256,8 @@ exports.installAllPatches = installAllPatches
#--------------------------------------------------------------------- #---------------------------------------------------------------------
stats = $coroutine ({host, granularity}) -> stats = ({host, granularity}) ->
stats = yield @getXapiHostStats(host, granularity) return @getXapiHostStats(host, granularity)
return stats
stats.description = 'returns statistic of the host' stats.description = 'returns statistic of the host'

View File

@ -1,5 +1,5 @@
async function delete_ ({message}) { async function delete_ ({ message }) {
await this.getXAPI(message).call('message.destroy', message.ref) await this.getXAPI(message).call('message.destroy', message._xapiRef)
} }
export {delete_ as delete} export {delete_ as delete}

View File

@ -5,7 +5,7 @@
async function delete_ ({PBD}) { async function delete_ ({PBD}) {
// TODO: check if PBD is attached before // 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} export {delete_ as delete}
@ -22,7 +22,7 @@ delete_.resolve = {
export async function disconnect ({PBD}) { export async function disconnect ({PBD}) {
// TODO: check if PBD is attached before // 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 = { disconnect.params = {
@ -38,7 +38,7 @@ disconnect.resolve = {
export async function connect ({PBD}) { export async function connect ({PBD}) {
// TODO: check if PBD is attached before // 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 = { connect.params = {

View File

@ -1,9 +1,11 @@
// TODO: too low level, move into host.
// =================================================================== // ===================================================================
// Delete // Delete
async function delete_ ({PIF}) { async function delete_ ({PIF}) {
// TODO: check if PIF is attached before // 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} export {delete_ as delete}
@ -20,7 +22,7 @@ delete_.resolve = {
export async function disconnect ({PIF}) { export async function disconnect ({PIF}) {
// TODO: check if PIF is attached before // 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 = { disconnect.params = {
@ -35,7 +37,7 @@ disconnect.resolve = {
export async function connect ({PIF}) { export async function connect ({PIF}) {
// TODO: check if PIF is attached before // 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 = { connect.params = {

View File

@ -82,7 +82,7 @@ export {uploadPatch as patch}
export async function mergeInto ({ source, target, force }) { export async function mergeInto ({ source, target, force }) {
try { try {
await this.mergeXenPools(source.id, target.id, force) await this.mergeXenPools(source._xapiId, target._xapiId, force)
} catch (e) { } catch (e) {
// FIXME: should we expose plain XAPI error messages? // FIXME: should we expose plain XAPI error messages?
throw new JsonRpcError(e.message) throw new JsonRpcError(e.message)

View File

@ -1,5 +1,6 @@
import { import {
ensureArray, ensureArray,
extractProperty,
forEach, forEach,
parseXml parseXml
} from '../utils' } from '../utils'
@ -7,10 +8,9 @@ import {
// =================================================================== // ===================================================================
export async function set (params) { export async function set (params) {
const {sr} = params const sr = extractProperty(params, 'sr')
delete params.sr
await this.getXAPI(sr).setSrProperties(sr.id, params) await this.getXAPI(sr).setSrProperties(sr._xapiId, params)
} }
set.params = { set.params = {
@ -28,7 +28,7 @@ set.resolve = {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
export async function scan ({SR}) { 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 = { scan.params = {
@ -43,7 +43,7 @@ scan.resolve = {
// TODO: find a way to call this "delete" and not destroy // TODO: find a way to call this "delete" and not destroy
export async function destroy ({SR}) { 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 = { destroy.params = {
@ -57,7 +57,7 @@ destroy.resolve = {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
export async function forget ({SR}) { 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 = { forget.params = {
@ -87,7 +87,7 @@ export async function createIso ({
} }
const srRef = await xapi.call( const srRef = await xapi.call(
'SR.create', 'SR.create',
host.ref, host._xapiRef,
deviceConfig, deviceConfig,
'0', // SR size 0 because ISO '0', // SR size 0 because ISO
nameLabel, nameLabel,
@ -140,7 +140,7 @@ export async function createNfs ({
const srRef = await xapi.call( const srRef = await xapi.call(
'SR.create', 'SR.create',
host.ref, host._xapiRef,
deviceConfig, deviceConfig,
'0', '0',
nameLabel, nameLabel,
@ -187,7 +187,7 @@ export async function createLvm ({
const srRef = await xapi.call( const srRef = await xapi.call(
'SR.create', 'SR.create',
host.ref, host._xapiRef,
deviceConfig, deviceConfig,
'0', '0',
nameLabel, nameLabel,
@ -232,7 +232,7 @@ export async function probeNfs ({
try { try {
await xapi.call( await xapi.call(
'SR.probe', 'SR.probe',
host.ref, host._xapiRef,
deviceConfig, deviceConfig,
'nfs', 'nfs',
{} {}
@ -305,7 +305,7 @@ export async function createIscsi ({
const srRef = await xapi.call( const srRef = await xapi.call(
'SR.create', 'SR.create',
host.ref, host._xapiRef,
deviceConfig, deviceConfig,
'0', '0',
nameLabel, nameLabel,
@ -369,7 +369,7 @@ export async function probeIscsiIqns ({
try { try {
await xapi.call( await xapi.call(
'SR.probe', 'SR.probe',
host.ref, host._xapiRef,
deviceConfig, deviceConfig,
'lvmoiscsi', 'lvmoiscsi',
{} {}
@ -447,7 +447,7 @@ export async function probeIscsiLuns ({
try { try {
await xapi.call( await xapi.call(
'SR.probe', 'SR.probe',
host.ref, host._xapiRef,
deviceConfig, deviceConfig,
'lvmoiscsi', 'lvmoiscsi',
{} {}
@ -521,7 +521,7 @@ export async function probeIscsiExists ({
deviceConfig.port = port 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 = [] const srs = []
forEach(ensureArray(xml['SRlist'].SR), sr => { forEach(ensureArray(xml['SRlist'].SR), sr => {
@ -562,7 +562,7 @@ export async function probeNfsExists ({
serverpath: serverPath 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 = [] const srs = []

View File

@ -1,5 +1,5 @@
export async function add ({tag, object}) { 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' add.description = 'add a new tag to an object'
@ -16,7 +16,7 @@ add.params = {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
export async function remove ({tag, object}) { 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' remove.description = 'remove an existing tag from an object'

View File

@ -1,5 +1,5 @@
export async function cancel ({task}) { 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 = { cancel.params = {
@ -13,7 +13,7 @@ cancel.resolve = {
// ------------------------------------------------------------------- // -------------------------------------------------------------------
export async function destroy ({task}) { 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 = { destroy.params = {

View File

@ -8,7 +8,7 @@ delete_ = $coroutine ({vbd}) ->
xapi = @getXAPI vbd xapi = @getXAPI vbd
# TODO: check if VBD is attached before # TODO: check if VBD is attached before
yield xapi.call 'VBD.destroy', vbd.ref yield xapi.call 'VBD.destroy', vbd._xapiRef
return true return true
@ -28,7 +28,7 @@ disconnect = $coroutine ({vbd}) ->
xapi = @getXAPI vbd xapi = @getXAPI vbd
# TODO: check if VBD is attached before # 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 return true
@ -48,7 +48,7 @@ connect = $coroutine ({vbd}) ->
xapi = @getXAPI vbd xapi = @getXAPI vbd
# TODO: check if VBD is attached before # TODO: check if VBD is attached before
yield xapi.call 'VBD.plug', vbd.ref yield xapi.call 'VBD.plug', vbd._xapiRef
return true return true
@ -68,7 +68,7 @@ set = $coroutine (params) ->
{vbd} = params {vbd} = params
xapi = @getXAPI vbd xapi = @getXAPI vbd
{ref} = vbd { _xapiRef: ref } = vbd
# VBD position # VBD position
if 'position' of params if 'position' of params

View File

@ -9,7 +9,7 @@ $isArray = require 'lodash.isarray'
#===================================================================== #=====================================================================
delete_ = $coroutine ({vdi}) -> delete_ = $coroutine ({vdi}) ->
yield @getXAPI(vdi).deleteVdi(vdi.id) yield @getXAPI(vdi).deleteVdi(vdi._xapiId)
return return
@ -30,7 +30,7 @@ set = $coroutine (params) ->
{vdi} = params {vdi} = params
xapi = @getXAPI vdi xapi = @getXAPI vdi
{ref} = vdi {_xapiRef: ref} = vdi
# Size. # Size.
if 'size' of params if 'size' of params
@ -79,7 +79,7 @@ migrate = $coroutine ({vdi, sr}) ->
xapi = @getXAPI vdi xapi = @getXAPI vdi
# TODO: check if VDI is attached before # 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 return true

View File

@ -1,6 +1,6 @@
// TODO: move into vm and rename to removeInterface // TODO: move into vm and rename to removeInterface
async function delete_ ({vif}) { async function delete_ ({vif}) {
await this.getXAPI(vif).deleteVif(vif.id) await this.getXAPI(vif).deleteVif(vif._xapiId)
} }
export {delete_ as delete} export {delete_ as delete}
@ -16,7 +16,7 @@ delete_.resolve = {
// TODO: move into vm and rename to disconnectInterface // TODO: move into vm and rename to disconnectInterface
export async function disconnect ({vif}) { export async function disconnect ({vif}) {
// TODO: check if VIF is attached before // 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 = { disconnect.params = {
@ -31,7 +31,7 @@ disconnect.resolve = {
// TODO: move into vm and rename to connectInterface // TODO: move into vm and rename to connectInterface
export async function connect ({vif}) { export async function connect ({vif}) {
// TODO: check if VIF is attached before // 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 = { connect.params = {

View File

@ -59,7 +59,7 @@ create = $coroutine ({
VDIs VDIs
VIFs VIFs
}) -> }) ->
vm = yield @getXAPI(template).createVm(template.id, { vm = yield @getXAPI(template).createVm(template._xapiId, {
installRepository: installation && installation.repository, installRepository: installation && installation.repository,
nameDescription: name_description, nameDescription: name_description,
nameLabel: name_label, nameLabel: name_label,
@ -138,7 +138,7 @@ exports.create = create
#--------------------------------------------------------------------- #---------------------------------------------------------------------
delete_ = ({vm, delete_disks: deleteDisks}) -> delete_ = ({vm, delete_disks: deleteDisks}) ->
return @getXAPI(vm).deleteVm(vm.id, deleteDisks) return @getXAPI(vm).deleteVm(vm._xapiId, deleteDisks)
delete_.params = { delete_.params = {
id: { type: 'string' } id: { type: 'string' }
@ -157,7 +157,7 @@ exports.delete = delete_
#--------------------------------------------------------------------- #---------------------------------------------------------------------
ejectCd = $coroutine ({vm}) -> ejectCd = $coroutine ({vm}) ->
yield @getXAPI(vm).ejectCdFromVm(vm.id) yield @getXAPI(vm).ejectCdFromVm(vm._xapiId)
return return
ejectCd.params = { ejectCd.params = {
@ -172,7 +172,7 @@ exports.ejectCd = ejectCd
#--------------------------------------------------------------------- #---------------------------------------------------------------------
insertCd = $coroutine ({vm, vdi, force}) -> insertCd = $coroutine ({vm, vdi, force}) ->
yield @getXAPI(vm).insertCdIntoVm(vdi.id, vm.id, {force}) yield @getXAPI(vm).insertCdIntoVm(vdi._xapiId, vm._xapiId, {force})
return return
insertCd.params = { insertCd.params = {
@ -190,7 +190,7 @@ exports.insertCd = insertCd
#--------------------------------------------------------------------- #---------------------------------------------------------------------
migrate = $coroutine ({vm, host}) -> 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 return
migrate.params = { migrate.params = {
@ -217,10 +217,10 @@ migratePool = $coroutine ({
network network
migrationNetwork migrationNetwork
}) -> }) ->
yield @getXAPI(vm).migrateVm(vm.id, @getXAPI(host), host.id, { yield @getXAPI(vm).migrateVm(vm._xapiId, @getXAPI(host), host._xapiId, {
migrationNetworkId: migrationNetwork?.id migrationNetworkId: migrationNetwork?._xapiId
networkId: network?.id, networkId: network?._xapiId,
srId: sr?.id, srId: sr?._xapiId,
}) })
return return
@ -260,7 +260,7 @@ set = $coroutine (params) ->
{VM} = params {VM} = params
xapi = @getXAPI VM xapi = @getXAPI VM
{ref} = VM {_xapiRef: ref} = VM
# Memory. # Memory.
if 'memory' of params if 'memory' of params
@ -371,9 +371,9 @@ restart = $coroutine ({vm, force}) ->
xapi = @getXAPI(vm) xapi = @getXAPI(vm)
if force if force
yield xapi.call 'VM.hard_reboot', vm.ref yield xapi.call 'VM.hard_reboot', vm._xapiRef
else else
yield xapi.call 'VM.clean_reboot', vm.ref yield xapi.call 'VM.clean_reboot', vm._xapiRef
return true return true
@ -391,7 +391,7 @@ exports.restart = restart
#--------------------------------------------------------------------- #---------------------------------------------------------------------
clone = ({vm, name, full_copy}) -> clone = ({vm, name, full_copy}) ->
return @getXAPI(vm).cloneVm(vm.ref, { return @getXAPI(vm).cloneVm(vm._xapiRef, {
nameLabel: name, nameLabel: name,
fast: not full_copy fast: not full_copy
}).then((vm) -> vm.$id) }).then((vm) -> vm.$id)
@ -417,15 +417,15 @@ copy = $coroutine ({
sr, sr,
vm vm
}) -> }) ->
if vm.$poolId == sr.$poolId if vm.$pool == sr.$pool
if vm.power_state is 'Running' if vm.power_state is 'Running'
yield checkPermissionsForSnapshot.call(this, vm) yield checkPermissionsForSnapshot.call(this, vm)
return @getXAPI(vm).copyVm(vm.id, sr.id, { return @getXAPI(vm).copyVm(vm._xapiId, sr._xapiId, {
nameLabel nameLabel
}).then((vm) -> vm.$id) }).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, compress,
nameLabel nameLabel
}).then((vm) -> vm.$id) }).then((vm) -> vm.$id)
@ -454,7 +454,7 @@ exports.copy = copy
# TODO: rename convertToTemplate() # TODO: rename convertToTemplate()
convert = $coroutine ({vm}) -> 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 return true
@ -472,7 +472,7 @@ exports.convert = convert
snapshot = $coroutine ({vm, name}) -> snapshot = $coroutine ({vm, name}) ->
yield checkPermissionsForSnapshot.call(this, vm) yield checkPermissionsForSnapshot.call(this, vm)
snapshot = yield @getXAPI(vm).snapshotVm(vm.ref, name) snapshot = yield @getXAPI(vm).snapshotVm(vm._xapiRef, name)
return snapshot.$id return snapshot.$id
snapshot.params = { snapshot.params = {
@ -559,7 +559,7 @@ exports.rollingBackup = rollingBackup
#--------------------------------------------------------------------- #---------------------------------------------------------------------
rollingDrCopy = ({vm, pool, tag, depth}) -> 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') throw new JsonRpcError('Disaster Recovery attempts to copy on the same pool')
return @rollingDrCopyVm({vm, sr: @getObject(pool.default_SR, 'SR'), tag, depth}) return @rollingDrCopyVm({vm, sr: @getObject(pool.default_SR, 'SR'), tag, depth})
@ -583,7 +583,7 @@ exports.rollingDrCopy = rollingDrCopy
start = $coroutine ({vm}) -> start = $coroutine ({vm}) ->
yield @getXAPI(vm).call( yield @getXAPI(vm).call(
'VM.start', vm.ref 'VM.start', vm._xapiRef
false # Start paused? false # Start paused?
false # Skips the pre-boot checks? false # Skips the pre-boot checks?
) )
@ -611,12 +611,12 @@ stop = $coroutine ({vm, force}) ->
# Hard shutdown # Hard shutdown
if force if force
yield xapi.call 'VM.hard_shutdown', vm.ref yield xapi.call 'VM.hard_shutdown', vm._xapiRef
return true return true
# Clean shutdown # Clean shutdown
try try
yield xapi.call 'VM.clean_shutdown', vm.ref yield xapi.call 'VM.clean_shutdown', vm._xapiRef
catch error catch error
if error.code is 'VM_MISSING_PV_DRIVERS' or error.code is 'VM_LACKS_FEATURE_SHUTDOWN' if error.code is 'VM_MISSING_PV_DRIVERS' or error.code is 'VM_LACKS_FEATURE_SHUTDOWN'
# TODO: Improve reporting: this message is unclear. # TODO: Improve reporting: this message is unclear.
@ -640,7 +640,7 @@ exports.stop = stop
#--------------------------------------------------------------------- #---------------------------------------------------------------------
suspend = $coroutine ({vm}) -> suspend = $coroutine ({vm}) ->
yield @getXAPI(vm).call 'VM.suspend', vm.ref yield @getXAPI(vm).call 'VM.suspend', vm._xapiRef
return true return true
@ -660,7 +660,7 @@ resume = $coroutine ({vm, force}) ->
if not force if not force
force = true force = true
yield @getXAPI(vm).call 'VM.resume', vm.ref, false, force yield @getXAPI(vm).call 'VM.resume', vm._xapiRef, false, force
return true return true
@ -679,7 +679,7 @@ exports.resume = resume
# revert a snapshot to its parent VM # revert a snapshot to its parent VM
revert = $coroutine ({snapshot}) -> revert = $coroutine ({snapshot}) ->
# Attempts a revert from this snapshot to its parent VM # 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 return true
@ -713,7 +713,7 @@ export_ = $coroutine ({vm, compress, onlyMetadata}) ->
if vm.power_state is 'Running' if vm.power_state is 'Running'
yield checkPermissionsForSnapshot.call(this, vm) yield checkPermissionsForSnapshot.call(this, vm)
stream = yield @getXAPI(vm).exportVm(vm.id, { stream = yield @getXAPI(vm).exportVm(vm._xapiId, {
compress: compress ? true, compress: compress ? true,
onlyMetadata: onlyMetadata ? false onlyMetadata: onlyMetadata ? false
}) })
@ -779,7 +779,7 @@ exports.import = import_
# FIXME: if position is used, all other disks after this position # FIXME: if position is used, all other disks after this position
# should be shifted. # should be shifted.
attachDisk = $coroutine ({vm, vdi, position, mode, bootable}) -> attachDisk = $coroutine ({vm, vdi, position, mode, bootable}) ->
yield @getXAPI(vm).attachVdiToVm(vdi.id, vm.id, { yield @getXAPI(vm).attachVdiToVm(vdi._xapiId, vm._xapiId, {
bootable, bootable,
position, position,
readOnly: mode is 'RO' readOnly: mode is 'RO'
@ -808,7 +808,7 @@ exports.attachDisk = attachDisk
# FIXME: position should be optional and default to last. # FIXME: position should be optional and default to last.
createInterface = $coroutine ({vm, network, position, mtu, mac}) -> 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, mac,
mtu, mtu,
position position
@ -835,7 +835,7 @@ exports.createInterface = createInterface
attachPci = $coroutine ({vm, pciId}) -> attachPci = $coroutine ({vm, pciId}) ->
xapi = @getXAPI vm 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 return true
@ -855,7 +855,7 @@ exports.attachPci = attachPci
detachPci = $coroutine ({vm}) -> detachPci = $coroutine ({vm}) ->
xapi = @getXAPI 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 return true
@ -899,7 +899,7 @@ bootOrder = $coroutine ({vm, order}) ->
order = {order: 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 return true

592
src/xapi-object-to-xo.js Normal file
View File

@ -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)
}

View File

@ -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')
}
}

View File

@ -77,7 +77,7 @@ const getNamespaceForType = (type) => typeToNamespace[type] || type
// =================================================================== // ===================================================================
// Format a date (pseudo ISO 8601) from one XenServer get by // 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 formatDateTime = d3TimeFormat.utcFormat('%Y%m%dT%H:%M:%SZ')
export const parseDateTime = formatDateTime.parse 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 ({ async setPoolProperties ({
name_label, name_label,
name_description 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 // Clone a VM: make a fast copy by fast copying each of its VDIs
// (using snapshots where possible) on the same SRs. // (using snapshots where possible) on the same SRs.
async _cloneVm (vm, nameLabel = vm.name_label) { async _cloneVm (vm, nameLabel = vm.name_label) {

129
src/xo.js
View File

@ -21,14 +21,13 @@ import {
verify verify
} from 'hashy' } from 'hashy'
import * as xapiObjectsToXo from './xapi-objects-to-xo'
import checkAuthorization from './acl' import checkAuthorization from './acl'
import Connection from './connection' import Connection from './connection'
import LevelDbLogger from './loggers/leveldb' import LevelDbLogger from './loggers/leveldb'
import Xapi from './xapi' import Xapi from './xapi'
import xapiObjectToXo from './xapi-object-to-xo'
import XapiStats from './xapi-stats' import XapiStats from './xapi-stats'
import {Acls} from './models/acl' import {Acls} from './models/acl'
import {autobind} from './decorators'
import { import {
createRawObject, createRawObject,
forEach, forEach,
@ -110,7 +109,7 @@ export default class Xo extends EventEmitter {
super() super()
this._objects = new XoCollection() this._objects = new XoCollection()
this._objects.createIndex('byRef', new XoUniqueIndex('ref')) this._objects.createIndex('byRef', new XoUniqueIndex('_xapiRef'))
// These will be initialized in start() // These will be initialized in start()
// //
@ -788,7 +787,7 @@ export default class Xo extends EventEmitter {
const targetStream = fs.createWriteStream(pathToFile, { flags: 'wx' }) const targetStream = fs.createWriteStream(pathToFile, { flags: 'wx' })
const promise = eventToPromise(targetStream, 'finish') const promise = eventToPromise(targetStream, 'finish')
const sourceStream = await this.getXAPI(vm).exportVm(vm.id, { const sourceStream = await this.getXAPI(vm).exportVm(vm._xapiId, {
compress, compress,
onlyMetadata: onlyMetadata || false onlyMetadata: onlyMetadata || false
}) })
@ -821,7 +820,7 @@ export default class Xo extends EventEmitter {
async rollingSnapshotVm (vm, tag, depth) { async rollingSnapshotVm (vm, tag, depth) {
const xapi = this.getXAPI(vm) const xapi = this.getXAPI(vm)
vm = xapi.getObject(vm.id) vm = xapi.getObject(vm._xapiId)
const reg = new RegExp('^rollingSnapshot_[^_]+_' + escapeStringRegexp(tag) + '_') const reg = new RegExp('^rollingSnapshot_[^_]+_' + escapeStringRegexp(tag) + '_')
const snapshots = sortBy(filter(vm.$snapshots, snapshot => reg.test(snapshot.name_label)), 'name_label') 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 reg = new RegExp('^' + escapeStringRegexp(`${vm.name_label}_${tag}_`) + '[0-9]{8}T[0-9]{6}Z$')
const targetXapi = this.getXAPI(sr) const targetXapi = this.getXAPI(sr)
sr = targetXapi.getObject(sr.id) sr = targetXapi.getObject(sr._xapiId)
const sourceXapi = this.getXAPI(vm) const sourceXapi = this.getXAPI(vm)
vm = sourceXapi.getObject(vm.id) vm = sourceXapi.getObject(vm._xapiId)
const vms = [] const vms = []
forEach(sr.$VDIs, vdi => { forEach(sr.$VDIs, vdi => {
@ -934,48 +933,28 @@ export default class Xo extends EventEmitter {
return server return server
} }
@autobind _onXenAdd (xapiObjects, xapiIdsToXo) {
_onXenAdd (xapiObjects) {
const {_objects: objects} = this const {_objects: objects} = this
forEach(xapiObjects, (xapiObject, id) => { forEach(xapiObjects, (xapiObject, xapiId) => {
const transform = xapiObjectsToXo[xapiObject.$type] const xoObject = xapiObjectToXo(xapiObject)
if (!transform) {
return
}
const xoObject = transform(xapiObject) if (xoObject) {
if (!xoObject) { xapiIdsToXo[xapiId] = xoObject.id
return
}
if (!xoObject.id) { objects.set(xoObject)
xoObject.id = id
} }
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, xapiIdsToXo) {
_onXenRemove (xapiObjects) {
const {_objects: objects} = this const {_objects: objects} = this
forEach(xapiObjects, (_, id) => { forEach(xapiObjects, (_, xapiId) => {
if (objects.has(id)) { const xoId = xapiIdsToXo[xapiId]
objects.remove(id)
if (xoId) {
delete xapiIdsToXo[xapiId]
objects.unset(xoId)
} }
}) })
} }
@ -992,16 +971,41 @@ export default class Xo extends EventEmitter {
} }
}) })
const {objects} = xapi xapi.xo = (() => {
objects.on('add', this._onXenAdd) const xapiIdsToXo = createRawObject()
objects.on('update', this._onXenAdd) const onAddOrUpdate = objects => {
objects.on('remove', this._onXenRemove) 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 const { objects } = xapi
// the pool identifier.
objects.on('finish', () => { return {
this._xapis[xapi.pool.$id] = xapi 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 { try {
await xapi.connect() await xapi.connect()
@ -1028,6 +1032,7 @@ export default class Xo extends EventEmitter {
delete this._xapis[xapi.pool.id] delete this._xapis[xapi.pool.id]
} }
xapi.xo.uninstall()
return xapi.disconnect() return xapi.disconnect()
} }
@ -1037,7 +1042,7 @@ export default class Xo extends EventEmitter {
object = this.getObject(object, type) object = this.getObject(object, type)
} }
const {$poolId: poolId} = object const { $pool: poolId } = object
if (!poolId) { if (!poolId) {
throw new Error(`object ${object.id} does not belong to a pool`) 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) { getXapiVmStats (vm, granularity) {
const xapi = this.getXAPI(vm) const xapi = this.getXAPI(vm)
return this._xapiStats.getVmPoints(xapi, vm.id, granularity) return this._xapiStats.getVmPoints(xapi, vm._xapiId, granularity)
} }
getXapiHostStats (host, granularity) { getXapiHostStats (host, granularity) {
const xapi = this.getXAPI(host) 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) { 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 // We don't want the events of the source XAPI to interfere with
// the events of the new XAPI. // the events of the new XAPI.
{ sourceXapi.xo.uninstall()
const {objects} = sourceXapi
objects.removeListener('add', this._onXenAdd)
objects.removeListener('update', this._onXenAdd)
objects.removeListener('remove', this._onXenRemove)
this._onXenRemove(objects.all)
}
try { try {
await sourceXapi.joinPool(hostname, user, password, force) await sourceXapi.joinPool(hostname, user, password, force)
} catch (e) { } catch (e) {
const {objects} = sourceXapi sourceXapi.xo.install()
objects.on('add', this._onXenAdd)
objects.on('update', this._onXenAdd)
objects.on('remove', this._onXenRemove)
this._onXenAdd(objects.all)
throw e throw e
} }