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 = {
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'),

View File

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

View File

@ -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]) {

View File

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

View File

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

View File

@ -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 = {

View File

@ -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 = {

View File

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

View File

@ -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 = []

View File

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

View File

@ -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 = {

View File

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

View File

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

View File

@ -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 = {

View File

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

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
// 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) {

129
src/xo.js
View File

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