New collection!
This commit is contained in:
parent
94620748ab
commit
5236441be0
@ -80,7 +80,7 @@
|
||||
"source-map-support": "^0.2.10",
|
||||
"then-redis": "~1.3.0",
|
||||
"ws": "~0.7.1",
|
||||
"xen-api": "^0.3.0",
|
||||
"xen-api": "^0.5.4",
|
||||
"xml2js": "~0.4.6",
|
||||
"xo-collection": "^0.3.2"
|
||||
},
|
||||
|
@ -1,457 +0,0 @@
|
||||
{EventEmitter: $EventEmitter} = require 'events'
|
||||
|
||||
$assign = require 'lodash.assign'
|
||||
$filter = require 'lodash.filter'
|
||||
$forEach = require 'lodash.foreach'
|
||||
$getKeys = require 'lodash.keys'
|
||||
$isArray = require 'lodash.isarray'
|
||||
$isEmpty = require 'lodash.isempty'
|
||||
$isFunction = require 'lodash.isfunction'
|
||||
$isObject = require 'lodash.isobject'
|
||||
$isString = require 'lodash.isstring'
|
||||
$map = require 'lodash.map'
|
||||
|
||||
{mapInPlace: $mapInPlace, wrap: $wrap} = require './utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
class $MappedCollection extends $EventEmitter
|
||||
|
||||
# The dispatch function is called whenever a new item has to be
|
||||
# processed and returns the name of the rule to use.
|
||||
#
|
||||
# To change the way it is dispatched, just override this it.
|
||||
dispatch: ->
|
||||
(@genval and (@genval.rule ? @genval.type)) ? 'unknown'
|
||||
|
||||
# This function is called when an item has been dispatched to a
|
||||
# missing rule.
|
||||
#
|
||||
# The default behavior is to throw an error but you may instead
|
||||
# choose to create a rule:
|
||||
#
|
||||
# collection.missingRule = collection.rule
|
||||
missingRule: (name) ->
|
||||
throw new Error "undefined rule “#{name}”"
|
||||
|
||||
# This function is called when the new generator of an existing item has been
|
||||
# matched to a different rule.
|
||||
#
|
||||
# The default behavior is to throw an error as it usually indicates a bug but
|
||||
# you can ignore it.
|
||||
ruleConflict: (rule, item) ->
|
||||
throw new Error "the item “#{item.key}” was of rule “#{item.rule}” "+
|
||||
"but matches to “#{rule}”"
|
||||
|
||||
constructor: ->
|
||||
# Items are stored here indexed by key.
|
||||
#
|
||||
# The prototype of this object is set to `null` to avoid pollution
|
||||
# from enumerable properties of `Object.prototype` and the
|
||||
# performance hit of `hasOwnProperty o`.
|
||||
@_byKey = Object.create null
|
||||
|
||||
# Hooks are stored here indexed by moment.
|
||||
@_hooks = {
|
||||
beforeDispatch: []
|
||||
beforeUpdate: []
|
||||
beforeSave: []
|
||||
afterRule: []
|
||||
}
|
||||
|
||||
# Rules are stored here indexed by name.
|
||||
#
|
||||
# The prototype of this object is set to `null` to avoid pollution
|
||||
# from enumerable properties of `Object.prototype` and to be able
|
||||
# to use the `name of @_rules` syntax.
|
||||
@_rules = Object.create null
|
||||
|
||||
# Register a hook to run at a given point.
|
||||
#
|
||||
# A hook receives as parameter an event object with the following
|
||||
# properties:
|
||||
# - `preventDefault()`: prevents the next default action from
|
||||
# happening;
|
||||
# - `stopPropagation()`: prevents other hooks from being run.
|
||||
#
|
||||
# Note: if a hook throws an exception, `event.stopPropagation()`
|
||||
# then `event.preventDefault()` will be called and the exception
|
||||
# will be forwarded.
|
||||
#
|
||||
# # Item hook
|
||||
#
|
||||
# Valid items related moments are:
|
||||
# - beforeDispatch: even before the item has been dispatched;
|
||||
# - beforeUpdate: after the item has been dispatched but before
|
||||
# updating its value.
|
||||
# - beforeSave: after the item has been updated.
|
||||
#
|
||||
# An item hook is run in the context of the current item.
|
||||
#
|
||||
# # Rule hook
|
||||
#
|
||||
# Valid rules related moments are:
|
||||
# - afterRule: just after a new rule has been defined (even
|
||||
# singleton).
|
||||
#
|
||||
# An item hook is run in the context of the current rule.
|
||||
hook: (name, hook) ->
|
||||
# Allows a nicer syntax for CoffeeScript.
|
||||
if $isObject name
|
||||
# Extracts the name and the value from the first property of the
|
||||
# object.
|
||||
do ->
|
||||
object = name
|
||||
return for own name, hook of object
|
||||
|
||||
hooks = @_hooks[name]
|
||||
|
||||
@_assert(
|
||||
hooks?
|
||||
"invalid hook moment “#{name}”"
|
||||
)
|
||||
|
||||
hooks.push hook
|
||||
|
||||
# Register a new singleton rule.
|
||||
#
|
||||
# See the `rule()` method for more information.
|
||||
item: (name, definition) ->
|
||||
# Creates the corresponding rule.
|
||||
rule = @rule name, definition, true
|
||||
|
||||
# Creates the singleton.
|
||||
item = {
|
||||
rule: rule.name
|
||||
key: rule.key() # No context because there is not generator.
|
||||
val: undefined
|
||||
}
|
||||
@_updateItems [item], true
|
||||
|
||||
# Register a new rule.
|
||||
#
|
||||
# If the definition is a function, it will be run in the context of
|
||||
# an item-like object with the following properties:
|
||||
# - `key`: the definition for the key of this item;
|
||||
# - `val`: the definition for the value of this item.
|
||||
#
|
||||
# Warning: The definition function is run only once!
|
||||
rule: (name, definition, singleton = false) ->
|
||||
# Allows a nicer syntax for CoffeeScript.
|
||||
if $isObject name
|
||||
# Extracts the name and the definition from the first property
|
||||
# of the object.
|
||||
do ->
|
||||
object = name
|
||||
return for own name, definition of object
|
||||
|
||||
@_assert(
|
||||
name not of @_rules
|
||||
"the rule “#{name}” is already defined"
|
||||
)
|
||||
|
||||
# Extracts the rule definition.
|
||||
if $isFunction definition
|
||||
ctx = {
|
||||
name
|
||||
key: undefined
|
||||
data: undefined
|
||||
val: undefined
|
||||
singleton
|
||||
}
|
||||
definition.call ctx
|
||||
else
|
||||
ctx = {
|
||||
name
|
||||
key: definition?.key
|
||||
data: definition?.data
|
||||
val: definition?.val
|
||||
singleton
|
||||
}
|
||||
|
||||
# Runs the `afterRule` hook and returns if the registration has
|
||||
# been prevented.
|
||||
return unless @_runHook 'afterRule', ctx
|
||||
|
||||
{key, data, val} = ctx
|
||||
|
||||
# The default key.
|
||||
key ?= if singleton then -> name else -> @genkey
|
||||
|
||||
# The default value.
|
||||
val ?= -> @genval
|
||||
|
||||
# Makes sure `key` is a function for uniformity.
|
||||
key = $wrap key unless $isFunction key
|
||||
|
||||
# Register the new rule.
|
||||
@_rules[name] = {
|
||||
name
|
||||
key
|
||||
data
|
||||
val
|
||||
singleton
|
||||
}
|
||||
|
||||
#--------------------------------
|
||||
|
||||
get: (keys, ignoreMissingItems = false) ->
|
||||
if keys is undefined
|
||||
items = $map @_byKey, (item) -> item.val
|
||||
else
|
||||
items = @_fetchItems keys, ignoreMissingItems
|
||||
$mapInPlace items, (item) -> item.val
|
||||
|
||||
if $isString keys then items[0] else items
|
||||
|
||||
getRaw: (keys, ignoreMissingItems = false) ->
|
||||
if keys is undefined
|
||||
item for _, item of @_byKey
|
||||
else
|
||||
items = @_fetchItems keys, ignoreMissingItems
|
||||
|
||||
if $isString keys then items[0] else items
|
||||
|
||||
remove: (keys, ignoreMissingItems = false) ->
|
||||
@_removeItems (@_fetchItems keys, ignoreMissingItems)
|
||||
|
||||
removeWithPredicate: (predicate, thisArg) ->
|
||||
items = ($filter @_byKey, predicate, thisArg)
|
||||
@_removeItems items
|
||||
|
||||
set: (items, {add, update, remove} = {}) ->
|
||||
add = true unless add?
|
||||
update = true unless update?
|
||||
remove = false unless remove?
|
||||
|
||||
itemsToAdd = {}
|
||||
itemsToUpdate = {}
|
||||
|
||||
itemsToRemove = {}
|
||||
$assign itemsToRemove, @_byKey if remove
|
||||
|
||||
$forEach items, (genval, genkey) =>
|
||||
item = {
|
||||
rule: undefined
|
||||
key: undefined
|
||||
data: undefined
|
||||
val: undefined
|
||||
genkey
|
||||
genval
|
||||
}
|
||||
|
||||
return unless @_runHook 'beforeDispatch', item
|
||||
|
||||
# Searches for a rule to handle it.
|
||||
ruleName = @dispatch.call item
|
||||
rule = @_rules[ruleName]
|
||||
|
||||
unless rule?
|
||||
@missingRule ruleName
|
||||
|
||||
# If `missingRule()` has not created the rule, just skip this
|
||||
# item.
|
||||
rule = @_rules[ruleName]
|
||||
return unless rule?
|
||||
|
||||
# Checks if this is a singleton.
|
||||
@_assert(
|
||||
not rule.singleton
|
||||
"cannot add items to singleton rule “#{rule.name}”"
|
||||
)
|
||||
|
||||
# Computes its key.
|
||||
key = rule.key.call item
|
||||
|
||||
@_assert(
|
||||
$isString key
|
||||
"the key “#{key}” is not a string"
|
||||
)
|
||||
|
||||
# Updates known values.
|
||||
item.rule = rule.name
|
||||
item.key = key
|
||||
|
||||
if key of @_byKey
|
||||
# Marks this item as not to be removed.
|
||||
delete itemsToRemove[key]
|
||||
|
||||
if update
|
||||
# Fetches the existing entry.
|
||||
prev = @_byKey[key]
|
||||
|
||||
# Checks if there is a conflict in rules.
|
||||
unless item.rule is prev.rule
|
||||
@ruleConflict item.rule, prev
|
||||
item.prevRule = prev.rule
|
||||
else
|
||||
delete item.prevRule
|
||||
|
||||
# Gets its previous data/value.
|
||||
item.data = prev.data
|
||||
item.val = prev.val
|
||||
|
||||
# Registers the item to be updated.
|
||||
itemsToUpdate[key] = item
|
||||
|
||||
# Note: an item will be updated only once per `set()` and
|
||||
# only the last generator will be used.
|
||||
else
|
||||
if add
|
||||
|
||||
# Registers the item to be added.
|
||||
itemsToAdd[key] = item
|
||||
return
|
||||
|
||||
# Adds items.
|
||||
@_updateItems itemsToAdd, true
|
||||
|
||||
# Updates items.
|
||||
@_updateItems itemsToUpdate
|
||||
|
||||
# Removes any items not seen (iff `remove` is true).
|
||||
@_removeItems itemsToRemove
|
||||
|
||||
# Forces items to update their value.
|
||||
touch: (keys) ->
|
||||
@_updateItems (@_fetchItems keys, true)
|
||||
|
||||
#--------------------------------
|
||||
|
||||
_assert: (cond, message) ->
|
||||
throw new Error message unless cond
|
||||
|
||||
# Emits item related event.
|
||||
_emitEvent: (event, items) ->
|
||||
getRule = if event is 'exit'
|
||||
(item) -> item.prevRule or item.rule
|
||||
else
|
||||
(item) -> item.rule
|
||||
|
||||
byRule = Object.create null
|
||||
|
||||
# One per item.
|
||||
$forEach items, (item) =>
|
||||
@emit "key=#{item.key}", event, item
|
||||
|
||||
(byRule[getRule item] ?= []).push item
|
||||
|
||||
return
|
||||
|
||||
# One per rule.
|
||||
@emit "rule=#{rule}", event, byRule[rule] for rule of byRule
|
||||
|
||||
# One for everything.
|
||||
@emit 'any', event, items
|
||||
|
||||
_fetchItems: (keys, ignoreMissingItems = false) ->
|
||||
unless $isArray keys
|
||||
keys = if $isObject keys then $getKeys keys else [keys]
|
||||
|
||||
items = []
|
||||
for key in keys
|
||||
item = @_byKey[key]
|
||||
if item?
|
||||
items.push item
|
||||
else
|
||||
@_assert(
|
||||
ignoreMissingItems
|
||||
"no item with key “#{key}”"
|
||||
)
|
||||
items
|
||||
|
||||
_removeItems: (items) ->
|
||||
return if $isEmpty items
|
||||
|
||||
$forEach items, (item) =>
|
||||
delete @_byKey[item.key]
|
||||
return
|
||||
|
||||
@_emitEvent 'exit', items
|
||||
|
||||
|
||||
# Runs hooks for the moment `name` with the given context and
|
||||
# returns false if the default action has been prevented.
|
||||
_runHook: (name, ctx) ->
|
||||
hooks = @_hooks[name]
|
||||
|
||||
# If no hooks, nothing to do.
|
||||
return true unless hooks? and (n = hooks.length) isnt 0
|
||||
|
||||
# Flags controlling the run.
|
||||
notStopped = true
|
||||
actionNotPrevented = true
|
||||
|
||||
# Creates the event object.
|
||||
event = {
|
||||
stopPropagation: -> notStopped = false
|
||||
|
||||
# TODO: Should `preventDefault()` imply `stopPropagation()`?
|
||||
preventDefault: -> actionNotPrevented = false
|
||||
}
|
||||
|
||||
i = 0
|
||||
while notStopped and i < n
|
||||
hooks[i++].call ctx, event
|
||||
|
||||
# TODO: Is exception handling necessary to have the wanted
|
||||
# behavior?
|
||||
|
||||
return actionNotPrevented
|
||||
|
||||
_updateItems: (items, areNew) ->
|
||||
return if $isEmpty items
|
||||
|
||||
# An update is similar to an exit followed by an enter.
|
||||
@_removeItems items unless areNew
|
||||
|
||||
$forEach items, (item) =>
|
||||
return unless @_runHook 'beforeUpdate', item
|
||||
|
||||
{rule: ruleName} = item
|
||||
|
||||
# Computes its value.
|
||||
do =>
|
||||
# Item is not passed directly to function to avoid direct
|
||||
# modification.
|
||||
#
|
||||
# This is not a true security but better than nothing.
|
||||
proxy = Object.create item
|
||||
|
||||
updateValue = (parent, prop, def) ->
|
||||
if not $isObject def
|
||||
parent[prop] = def
|
||||
else if $isFunction def
|
||||
parent[prop] = def.call proxy, parent[prop]
|
||||
else if $isArray def
|
||||
i = 0
|
||||
n = def.length
|
||||
|
||||
current = parent[prop] ?= new Array n
|
||||
while i < n
|
||||
updateValue current, i, def[i]
|
||||
++i
|
||||
else
|
||||
# It's a plain object.
|
||||
current = parent[prop] ?= {}
|
||||
for i of def
|
||||
updateValue current, i, def[i]
|
||||
|
||||
updateValue item, 'data', @_rules[ruleName].data
|
||||
updateValue item, 'val', @_rules[ruleName].val
|
||||
|
||||
unless @_runHook 'beforeSave', item
|
||||
# FIXME: should not be removed, only not saved.
|
||||
delete @_byKey[item.key]
|
||||
|
||||
return
|
||||
|
||||
# Really inserts the items and trigger events.
|
||||
$forEach items, (item) =>
|
||||
@_byKey[item.key] = item
|
||||
return
|
||||
@_emitEvent 'enter', items
|
||||
|
||||
#=====================================================================
|
||||
|
||||
module.exports = {$MappedCollection}
|
@ -1,121 +0,0 @@
|
||||
{expect: $expect} = require 'chai'
|
||||
|
||||
$sinon = require 'sinon'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
{$MappedCollection} = require './MappedCollection'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
describe '$MappedCollection', ->
|
||||
|
||||
# Shared variables.
|
||||
collection = null
|
||||
|
||||
beforeEach ->
|
||||
collection = new $MappedCollection()
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
describe '#dispatch()', ->
|
||||
|
||||
# Test data.
|
||||
beforeEach ->
|
||||
collection.rule test: {}
|
||||
|
||||
#------------------------------
|
||||
|
||||
it 'should have genkey and genval', ->
|
||||
collection.dispatch = ->
|
||||
$expect(@genkey).to.equal 'a key'
|
||||
$expect(@genval).to.equal 'a value'
|
||||
|
||||
'test'
|
||||
|
||||
collection.set {
|
||||
'a key': 'a value'
|
||||
}
|
||||
|
||||
#------------------------------
|
||||
|
||||
it 'should be used to dispatch an item', ->
|
||||
collection.dispatch = -> 'test'
|
||||
|
||||
collection.set [
|
||||
'any value'
|
||||
]
|
||||
|
||||
$expect(collection.getRaw('0').rule).to.equal 'test'
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
describe 'item hooks', ->
|
||||
|
||||
# Test data.
|
||||
beforeEach ->
|
||||
collection.rule test: {}
|
||||
|
||||
#------------------------------
|
||||
|
||||
it 'should be called in the correct order', ->
|
||||
|
||||
beforeDispatch = $sinon.spy()
|
||||
collection.hook {beforeDispatch}
|
||||
|
||||
dispatcher = $sinon.spy ->
|
||||
$expect(beforeDispatch.called).to.true
|
||||
|
||||
# It still is a dispatcher.
|
||||
'test'
|
||||
collection.dispatch = dispatcher
|
||||
|
||||
beforeUpdate = $sinon.spy ->
|
||||
$expect(dispatcher.called).to.true
|
||||
collection.hook {beforeUpdate}
|
||||
|
||||
beforeSave = $sinon.spy ->
|
||||
$expect(beforeUpdate.called).to.true
|
||||
collection.hook {beforeSave}
|
||||
|
||||
collection.set [
|
||||
'any value'
|
||||
]
|
||||
|
||||
$expect(beforeSave.called).to.be.true
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
describe 'adding new items', ->
|
||||
|
||||
beforeEach ->
|
||||
collection.rule test: {}
|
||||
collection.dispatch = -> 'test'
|
||||
|
||||
#------------------------------
|
||||
|
||||
it 'should trigger three `enter` events', ->
|
||||
keySpy = $sinon.spy()
|
||||
ruleSpy = $sinon.spy()
|
||||
anySpy = $sinon.spy()
|
||||
|
||||
collection.on 'key=a key', keySpy
|
||||
collection.on 'rule=test', ruleSpy
|
||||
collection.on 'any', anySpy
|
||||
|
||||
collection.set {
|
||||
'a key': 'a value'
|
||||
}
|
||||
|
||||
item = collection.getRaw 'a key'
|
||||
|
||||
# TODO: items can be an array or a object (it is not defined).
|
||||
$expect(keySpy.args).to.deep.equal [
|
||||
['enter', item]
|
||||
]
|
||||
$expect(ruleSpy.args).to.deep.equal [
|
||||
['enter', [item]]
|
||||
]
|
||||
$expect(anySpy.args).to.deep.equal [
|
||||
['enter', {'a key': item}]
|
||||
]
|
@ -16,8 +16,8 @@ export class NotImplemented extends JsonRpcError {
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export class NoSuchObject extends JsonRpcError {
|
||||
constructor (data) {
|
||||
super('no such object', 1, data)
|
||||
constructor (id, type) {
|
||||
super('no such object', 1, {id, type})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,12 @@ describe('autobind', function () {
|
||||
expect(getFoo()).to.equal(foo)
|
||||
})
|
||||
|
||||
it('returns the same bound instance each time', function () {
|
||||
const foo = new Foo()
|
||||
|
||||
expect(foo.getFoo).to.equal(foo.getFoo)
|
||||
})
|
||||
|
||||
it('works with multiple instances of the same class', function () {
|
||||
const foo1 = new Foo()
|
||||
const foo2 = new Foo()
|
||||
|
367
src/helpers.js
367
src/helpers.js
@ -1,367 +0,0 @@
|
||||
// FIXME: This file name should reflect what's inside!
|
||||
|
||||
// ===================================================================
|
||||
|
||||
import $clone from 'lodash.clone'
|
||||
import $forEach from 'lodash.foreach'
|
||||
import $isArray from 'lodash.isarray'
|
||||
import $isEmpty from 'lodash.isempty'
|
||||
import $isFunction from 'lodash.isfunction'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const $asArray = (val) => $isArray(val) ? val : [val]
|
||||
const $asFunction = (val) => $isFunction(val) ? val : () => val
|
||||
|
||||
const $first = (collection, defaultValue) => {
|
||||
const {length} = collection
|
||||
if (length == null) {
|
||||
for (let key in collection) {
|
||||
return collection[key]
|
||||
}
|
||||
} else if (length) {
|
||||
return collection[0]
|
||||
}
|
||||
|
||||
// Nothing was found, returns the `def` value.
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
const $removeValue = (array, value) => {
|
||||
const index = array.indexOf(value)
|
||||
if (index === -1) {
|
||||
return false
|
||||
}
|
||||
|
||||
array.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// TODO: currently the watch can be updated multiple times per
|
||||
// “$MappedCollection.set()” which is inefficient: it should be
|
||||
// possible to address that.
|
||||
|
||||
const $watch = (collection, {
|
||||
// Key(s) of the “remote” objects watched.
|
||||
//
|
||||
// If it is a function, it is evaluated in the scope of the “current”
|
||||
// object. (TODO)
|
||||
//
|
||||
// Default: undefined
|
||||
keys,
|
||||
|
||||
// Alias for `keys`.
|
||||
key,
|
||||
|
||||
// Rule(s) of the “remote” objects watched.
|
||||
//
|
||||
// If it is a function, it is evaluated in the scope of the “current”
|
||||
// object. (TODO)
|
||||
//
|
||||
// Note: `key`/`keys` and `rule`/`rules` cannot be used both.
|
||||
//
|
||||
// Default: undefined
|
||||
rules,
|
||||
|
||||
// Alias for `rules`.
|
||||
rule,
|
||||
|
||||
// Value to add to the set.
|
||||
//
|
||||
// If it is a function, it is evaluated in the scope of the “remote”
|
||||
// object.
|
||||
//
|
||||
// Default: -> @val
|
||||
val,
|
||||
|
||||
// Predicates the “remote” object must fulfill to be used.
|
||||
//
|
||||
// Default: -> true
|
||||
if: cond,
|
||||
|
||||
// Function evaluated in the scope of the “remote” object which
|
||||
// returns the key of the object to update (usually the current one).
|
||||
//
|
||||
// TODO: Does it make sense to return an array?
|
||||
//
|
||||
// Default: undefined
|
||||
bind,
|
||||
|
||||
// Initial value.
|
||||
init,
|
||||
|
||||
// Function called when a loop is detected.
|
||||
//
|
||||
// Usually it is used to either throw an exception or do nothing to
|
||||
// stop the loop.
|
||||
//
|
||||
// Note: The function may also returns `true` to force the processing
|
||||
// to continue.
|
||||
loopDetected = () => { throw new Error('loop detected') }
|
||||
}, fn) => {
|
||||
val = val == null ?
|
||||
// The default value is simply the value of the item.
|
||||
function () { return this.val } :
|
||||
$asFunction(val)
|
||||
|
||||
// Method allowing the cleanup when the helper is no longer used.
|
||||
// cleanUp = -> // TODO: noop for now.
|
||||
|
||||
// Keys of items using the current helper.
|
||||
const consumers = Object.create(null)
|
||||
|
||||
// Current values.
|
||||
const values = Object.create(null)
|
||||
values.common = init
|
||||
|
||||
// The number of nested processing for this watcher is counted to
|
||||
// avoid an infinite loop.
|
||||
let loops = 0
|
||||
|
||||
let updating = false
|
||||
|
||||
const process = (event, items) => {
|
||||
if (updating) return
|
||||
|
||||
// Values are grouped by namespace.
|
||||
const valuesByNamespace = Object.create(null)
|
||||
|
||||
$forEach(items, (item) => {
|
||||
if (cond && !cond.call(item)) return
|
||||
|
||||
const namespace = (function () {
|
||||
if (bind) {
|
||||
const key = bind.call(item)
|
||||
|
||||
return key && `$${key}`
|
||||
} else {
|
||||
return 'common'
|
||||
}
|
||||
})()
|
||||
|
||||
// If not namespace, ignore this item.
|
||||
if (!namespace) return
|
||||
|
||||
(
|
||||
valuesByNamespace[namespace] ||
|
||||
(valuesByNamespace[namespace] = [])
|
||||
).push(val.call(item))
|
||||
})
|
||||
|
||||
// Stops here if no values were computed.
|
||||
if ($isEmpty(valuesByNamespace)) return
|
||||
|
||||
if (loops && loopDetected(loops) !== true) return
|
||||
const previousLoops = loops++
|
||||
|
||||
// For each namespace.
|
||||
$forEach(valuesByNamespace, (values_, namespace) => {
|
||||
// Updates the value.
|
||||
const value = values[namespace]
|
||||
|
||||
const ctx = {
|
||||
// TODO: test the $clone
|
||||
value: value == null ? $clone(init) : value
|
||||
}
|
||||
const changed = event === 'enter' ?
|
||||
fn.call(ctx, values_, {}) :
|
||||
fn.call(ctx, {}, values_)
|
||||
|
||||
// Notifies watchers unless it is known the value has not
|
||||
// changed.
|
||||
if (changed !== false) {
|
||||
values[namespace] = ctx.value
|
||||
updating = true
|
||||
if (namespace === 'common') {
|
||||
collection.touch(consumers)
|
||||
} else {
|
||||
collection.touch(namespace.substr(1))
|
||||
}
|
||||
updating = false
|
||||
}
|
||||
})
|
||||
|
||||
loops = previousLoops
|
||||
}
|
||||
|
||||
const processOne = (event, item) => process(event, [item])
|
||||
|
||||
// Sets up the watch based on the provided criteria.
|
||||
//
|
||||
// TODO: provides a way to clean this when no longer used.
|
||||
keys = $asArray(keys || key || [])
|
||||
rules = $asArray(rules || rule || [])
|
||||
if (!$isEmpty(keys)) {
|
||||
// Matching is done on the keys.
|
||||
|
||||
if (!$isEmpty(rules)) {
|
||||
throw new Error('cannot use both keys and rules')
|
||||
}
|
||||
|
||||
$forEach(keys, key => {
|
||||
collection.on(`key=${key}`, processOne)
|
||||
})
|
||||
|
||||
// Handles existing items.
|
||||
process('enter', collection.getRaw(keys, true))
|
||||
} else if (!$isEmpty(rules)) {
|
||||
// Matching is done the rules.
|
||||
|
||||
$forEach(rules, rule => {
|
||||
collection.on(`rule=${rule}`, process)
|
||||
})
|
||||
|
||||
// TODO: Inefficient, is there another way?
|
||||
rules = (function (rules) { // Minor optimization.
|
||||
const tmp = Object.create(null)
|
||||
for (let rule of rules) {
|
||||
tmp[rule] = true
|
||||
}
|
||||
return tmp
|
||||
})(rules)
|
||||
$forEach(collection.getRaw(), item => {
|
||||
if (rules[item.rule]) {
|
||||
processOne('enter', item)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// No matching done.
|
||||
|
||||
collection.on('any', process)
|
||||
|
||||
// Handles existing items.
|
||||
process('enter', collection.getRaw())
|
||||
}
|
||||
|
||||
// Creates the generator: the function which items will used to
|
||||
// register to this watcher and to get the current value.
|
||||
const generator = function () {
|
||||
const {key} = this
|
||||
|
||||
// Register this item has a consumer.
|
||||
consumers[key] = true
|
||||
|
||||
// Returns the value for this item if any or the common value.
|
||||
const namespace = `$${key}`
|
||||
return (namespace in values) ?
|
||||
values[namespace] :
|
||||
values.common
|
||||
}
|
||||
|
||||
// Creates a helper to unregister an item from this watcher.
|
||||
generator.unregister = function () {
|
||||
const {key} = this
|
||||
delete consumers[key]
|
||||
delete values[`$${key}`]
|
||||
}
|
||||
|
||||
// Creates a helper to get the value without using an item.
|
||||
generator.raw = (key) => values[key != null ? `$${key}` : 'common']
|
||||
|
||||
// Returns the generator.
|
||||
return generator
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const $map = function (options) {
|
||||
options.init = Object.create(null)
|
||||
|
||||
return $watch(this, options, function (entered, exited) {
|
||||
let changed = false
|
||||
|
||||
$forEach(entered, ([key, value]) => {
|
||||
if (this.value[key] !== value) {
|
||||
this.value[key] = value
|
||||
changed = true
|
||||
}
|
||||
})
|
||||
$forEach(exited, ([key, value]) => {
|
||||
if (key in this.value) {
|
||||
delete this.value[key]
|
||||
changed = true
|
||||
}
|
||||
})
|
||||
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Creates a set of value from various items.
|
||||
export const $set = function (options) {
|
||||
// Contrary to other helpers, the default value is the key.
|
||||
if (!options.val) {
|
||||
options.val = function () { return this.key }
|
||||
}
|
||||
|
||||
options.init = []
|
||||
|
||||
return $watch(this, options, function (entered, exited) {
|
||||
let changed = false
|
||||
|
||||
$forEach(entered, (value) => {
|
||||
if (this.value.indexOf(value) === -1) {
|
||||
this.value.push(value)
|
||||
changed = true
|
||||
}
|
||||
})
|
||||
|
||||
$forEach(exited, (value) => {
|
||||
if ($removeValue(this.value, value)) {
|
||||
changed = true
|
||||
}
|
||||
})
|
||||
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export const $sum = function (options) {
|
||||
if (!options.init) {
|
||||
options.init = 0
|
||||
}
|
||||
|
||||
return $watch(this, options, function (entered, exited) {
|
||||
const prev = this.value
|
||||
|
||||
$forEach(entered, (value) => { this.value += value })
|
||||
$forEach(exited, (value) => { this.value -= value })
|
||||
|
||||
return this.value !== prev
|
||||
})
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// Uses a value from another item.
|
||||
//
|
||||
// Important note: Behavior is not specified when binding to multiple
|
||||
// items.
|
||||
export const $val = function (options) {
|
||||
// The default value.
|
||||
const def = options.default
|
||||
delete options.default
|
||||
|
||||
if (!options.init) {
|
||||
options.init = def
|
||||
}
|
||||
|
||||
// Should the last value be kept instead of returning to the default
|
||||
// value when no items are available!
|
||||
const keepLast = !!options.keepLast
|
||||
delete options.keepLast
|
||||
|
||||
return $watch(this, options, function (entered, exited) {
|
||||
const prev = this.value
|
||||
|
||||
this.value = $first(entered, keepLast ? this.value : def)
|
||||
|
||||
return this.value !== prev
|
||||
})
|
||||
}
|
@ -1,269 +0,0 @@
|
||||
/* eslint-env mocha */
|
||||
|
||||
import $bind from 'lodash.bind'
|
||||
import {expect as $expect} from 'chai'
|
||||
|
||||
import {$MappedCollection} from './MappedCollection'
|
||||
import * as $nonBindedHelpers from './helpers'
|
||||
|
||||
// Enable source maps support for traces.
|
||||
import sourceMapSupport from 'source-map-support'
|
||||
sourceMapSupport.install()
|
||||
|
||||
// ===================================================================
|
||||
|
||||
describe('Helper', () => {
|
||||
// Shared variables.
|
||||
// let $set
|
||||
// let $val
|
||||
let $sum
|
||||
let collection
|
||||
|
||||
beforeEach(() => {
|
||||
// Creates the collection.
|
||||
collection = new $MappedCollection()
|
||||
|
||||
// Dispatcher used for tests.
|
||||
collection.dispatch = function () { return this.genkey.split('.')[0] }
|
||||
|
||||
// Missing rules should be automatically created.
|
||||
collection.missingRule = collection.rule
|
||||
|
||||
// // Monkey patch the collection to see all emitted events.
|
||||
// {
|
||||
// const {emit} = collection
|
||||
// collection.emit = (...args) => {
|
||||
// console.log(...args)
|
||||
// emit.call(collection, ...args)
|
||||
// }
|
||||
// }
|
||||
|
||||
// Binds helpers to this collection.
|
||||
// $set = $bind($nonBindedHelpers.$set, collection)
|
||||
// $val = $bind($nonBindedHelpers.$val, collection)
|
||||
$sum = $bind($nonBindedHelpers.$sum, collection)
|
||||
})
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
|
||||
// All helpers share the same logical code, we need only to test one
|
||||
// extensively and test the others basically.
|
||||
//
|
||||
// $sum was chosen because it is the simplest helper to test.
|
||||
describe('$sum', () => {
|
||||
it('with single key', () => {
|
||||
collection.set({ foo: 1 })
|
||||
|
||||
collection.item('sum', function () {
|
||||
this.val = $sum({
|
||||
key: 'foo'
|
||||
})
|
||||
})
|
||||
|
||||
$expect(collection.get('sum')).to.equal(1)
|
||||
|
||||
collection.set({ foo: 2 })
|
||||
|
||||
$expect(collection.get('sum')).to.equal(2)
|
||||
|
||||
collection.remove('foo')
|
||||
|
||||
$expect(collection.get('sum')).to.equal(0)
|
||||
})
|
||||
|
||||
it('with multiple keys', () => {
|
||||
collection.set({
|
||||
foo: 1,
|
||||
bar: 2
|
||||
})
|
||||
|
||||
collection.item('sum', function () {
|
||||
this.val = $sum({
|
||||
keys: ['foo', 'bar']
|
||||
})
|
||||
})
|
||||
|
||||
$expect(collection.get('sum')).to.equal(3)
|
||||
|
||||
collection.set({ bar: 3 })
|
||||
|
||||
$expect(collection.get('sum')).to.equal(4)
|
||||
|
||||
collection.remove('foo')
|
||||
|
||||
$expect(collection.get('sum')).to.equal(3)
|
||||
})
|
||||
|
||||
// FIXME: This test fails but this feature is not used.
|
||||
it.skip('with dynamic keys', () => {
|
||||
collection.set({
|
||||
foo: 1,
|
||||
bar: 2
|
||||
})
|
||||
|
||||
collection.rule('sum', function () {
|
||||
this.val = $sum({
|
||||
key: function () { return this.genkey.split('.')[1] }
|
||||
})
|
||||
})
|
||||
collection.set({
|
||||
'sum.foo': null,
|
||||
'sum.bar': null
|
||||
})
|
||||
|
||||
$expect(collection.get('sum.foo')).to.equal(1)
|
||||
$expect(collection.get('sum.bar')).to.equal(2)
|
||||
|
||||
collection.remove('bar')
|
||||
|
||||
$expect(collection.get('sum.foo')).to.equal(1)
|
||||
$expect(collection.get('sum.bar')).to.equal(0)
|
||||
})
|
||||
|
||||
it('with single rule', () => {
|
||||
collection.set({
|
||||
'foo.1': 1,
|
||||
'foo.2': 2
|
||||
})
|
||||
|
||||
collection.item('sum', function () {
|
||||
this.val = $sum({
|
||||
rule: 'foo'
|
||||
})
|
||||
})
|
||||
|
||||
$expect(collection.get('sum')).to.equal(3)
|
||||
|
||||
collection.set({ 'foo.2': 3 })
|
||||
|
||||
$expect(collection.get('sum')).to.equal(4)
|
||||
|
||||
collection.remove('foo.1')
|
||||
|
||||
$expect(collection.get('sum')).to.equal(3)
|
||||
})
|
||||
|
||||
it('with multiple rules', () => {
|
||||
collection.set({
|
||||
'foo': 1,
|
||||
'bar.1': 2,
|
||||
'bar.2': 3
|
||||
})
|
||||
|
||||
collection.item('sum', function () {
|
||||
this.val = $sum({
|
||||
rules: ['foo', 'bar']
|
||||
})
|
||||
})
|
||||
|
||||
$expect(collection.get('sum')).to.equal(6)
|
||||
|
||||
collection.set({ 'bar.1': 3 })
|
||||
|
||||
$expect(collection.get('sum')).to.equal(7)
|
||||
|
||||
collection.remove('bar.2')
|
||||
|
||||
$expect(collection.get('sum')).to.equal(4)
|
||||
})
|
||||
|
||||
it('with bind', () => {
|
||||
collection.set({
|
||||
'foo': {
|
||||
sum: 2, // This item will participate to `sum.2`.
|
||||
val: 1
|
||||
},
|
||||
'bar': {
|
||||
sum: 1, // This item will participate to `sum.1`.
|
||||
val: 2
|
||||
}
|
||||
})
|
||||
|
||||
collection.rule('sum', function () {
|
||||
this.val = $sum({
|
||||
bind: function () {
|
||||
const id = this.val.sum
|
||||
|
||||
return id && `sum.${id}`
|
||||
},
|
||||
val: function () { return this.val.val }
|
||||
})
|
||||
})
|
||||
collection.set({
|
||||
'sum.1': null,
|
||||
'sum.2': null
|
||||
})
|
||||
|
||||
$expect(collection.get('sum.1')).to.equal(2)
|
||||
$expect(collection.get('sum.2')).to.equal(1)
|
||||
|
||||
collection.set({
|
||||
'foo': {
|
||||
sum: 1,
|
||||
val: 3
|
||||
}
|
||||
})
|
||||
|
||||
$expect(collection.get('sum.1')).to.equal(5)
|
||||
$expect(collection.get('sum.2')).to.equal(0)
|
||||
|
||||
collection.remove('bar')
|
||||
|
||||
$expect(collection.get('sum.1')).to.equal(3)
|
||||
$expect(collection.get('sum.2')).to.equal(0)
|
||||
})
|
||||
|
||||
it('with predicate', () => {
|
||||
collection.set({
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
baz: 3
|
||||
})
|
||||
|
||||
collection.item('sum', function () {
|
||||
this.val = $sum({
|
||||
if: function () { return /^b/.test(this.rule) }
|
||||
})
|
||||
})
|
||||
|
||||
$expect(collection.get('sum')).to.equal(5)
|
||||
|
||||
collection.set({ foo: 4 })
|
||||
|
||||
$expect(collection.get('sum')).to.equal(5)
|
||||
|
||||
collection.set({ bar: 5 })
|
||||
|
||||
$expect(collection.get('sum')).to.equal(8)
|
||||
|
||||
collection.remove('baz')
|
||||
|
||||
$expect(collection.get('sum')).to.equal(5)
|
||||
})
|
||||
|
||||
it('with initial value', () => {
|
||||
collection.set({ foo: 1 })
|
||||
|
||||
collection.item('sum', function () {
|
||||
this.val = $sum({
|
||||
key: 'foo',
|
||||
init: 2
|
||||
})
|
||||
})
|
||||
|
||||
$expect(collection.get('sum')).to.equal(3)
|
||||
|
||||
collection.set({ foo: 2 })
|
||||
|
||||
$expect(collection.get('sum')).to.equal(4)
|
||||
|
||||
collection.remove('foo')
|
||||
|
||||
$expect(collection.get('sum')).to.equal(2)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO:
|
||||
// - dynamic keys
|
||||
// - dynamic rules
|
||||
})
|
@ -1,6 +1,5 @@
|
||||
import createLogger from 'debug'
|
||||
const debug = createLogger('xo:main')
|
||||
const debugPlugin = createLogger('xo:plugin')
|
||||
|
||||
import Bluebird from 'bluebird'
|
||||
Bluebird.longStackTraces()
|
||||
@ -81,6 +80,8 @@ async function loadConfiguration () {
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const debugPlugin = createLogger('xo:plugin')
|
||||
|
||||
const loadPlugin = Bluebird.method(function (pluginConf, pluginName) {
|
||||
debugPlugin('loading %s', pluginName)
|
||||
|
||||
|
929
src/spec.coffee
929
src/spec.coffee
@ -1,929 +0,0 @@
|
||||
$forEach = require 'lodash.foreach'
|
||||
$isArray = require 'lodash.isarray'
|
||||
$isObject = require 'lodash.isobject'
|
||||
$xml2js = require 'xml2js'
|
||||
|
||||
$helpers = require './helpers'
|
||||
{parseXml: $parseXML} = require './utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$isVMRunning = ->
|
||||
return switch @val.power_state
|
||||
when 'Paused', 'Running'
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
$isHostRunning = -> @val.power_state is 'Running'
|
||||
|
||||
$isTaskLive = -> @val.status is 'pending' or @val.status is 'cancelling'
|
||||
|
||||
$retrieveTags = -> [] # TODO
|
||||
|
||||
$toTimestamp = (date) ->
|
||||
# Weird behavior from the XAPI.
|
||||
return null if date is '1969-12-31T23:00:00.000Z'
|
||||
|
||||
return if date?
|
||||
Math.round (Date.parse date) / 1000
|
||||
else
|
||||
null
|
||||
|
||||
#=====================================================================
|
||||
|
||||
module.exports = ->
|
||||
|
||||
# Binds the helpers to the collection.
|
||||
{
|
||||
$set
|
||||
$sum
|
||||
$val
|
||||
} = do =>
|
||||
helpers = {}
|
||||
helpers[name] = fn.bind this for name, fn of $helpers
|
||||
helpers
|
||||
|
||||
collection = this
|
||||
# do (emit = collection.emit) ->
|
||||
# collection.emit = (event, items) ->
|
||||
# console.log event
|
||||
# emit.call collection, event, items
|
||||
|
||||
$link = (keyFn, valFn = (-> @val)) ->
|
||||
keyPerItem = Object.create null
|
||||
listenerPerItem = Object.create null
|
||||
valuePerItem = Object.create null
|
||||
updating = false
|
||||
|
||||
->
|
||||
{key} = this
|
||||
|
||||
# Gets the key of the remote object.
|
||||
remoteKey = keyFn.call this
|
||||
|
||||
keyHasChanged = remoteKey isnt keyPerItem[key]
|
||||
|
||||
if keyHasChanged
|
||||
keyPerItem[key] = remoteKey
|
||||
else
|
||||
# Returns the value if already defined.
|
||||
return valuePerItem[key] if key of valuePerItem
|
||||
|
||||
eventName = "key=#{remoteKey}"
|
||||
listener = listenerPerItem[key]
|
||||
|
||||
if listener and keyHasChanged
|
||||
collection.remove eventName, listener
|
||||
listener = null
|
||||
|
||||
# Special case for `OpaqueRef:NULL`.
|
||||
if remoteKey is 'OpaqueRef:NULL'
|
||||
return valuePerItem[key] = null
|
||||
|
||||
unless listener
|
||||
listener = (event, item) ->
|
||||
# If the events are due to an update of this link or if the item is
|
||||
# exiting, just returns.
|
||||
return if updating or event isnt 'enter'
|
||||
|
||||
# Register its value.
|
||||
valuePerItem[key] = valFn.call item
|
||||
|
||||
# Force the object to update.
|
||||
try
|
||||
updating = true
|
||||
collection.touch key
|
||||
finally
|
||||
updating = false
|
||||
collection.on eventName, listener
|
||||
|
||||
# Tries to find the remote object in the collection.
|
||||
try
|
||||
return valuePerItem[key] = valFn.call (collection.getRaw remoteKey)
|
||||
|
||||
# Returns `null` for now.
|
||||
valuePerItem[key] = null
|
||||
|
||||
$map = (valFn) ->
|
||||
map = Object.create null
|
||||
subscribers = Object.create null
|
||||
updating = false
|
||||
|
||||
# First, initializes the map with existing items.
|
||||
$forEach collection.getRaw(), (item) ->
|
||||
val = valFn.call item
|
||||
map[val[0]] = val[1] if val
|
||||
return
|
||||
|
||||
# Listens to any new item.
|
||||
collection.on 'any', (event, items) ->
|
||||
# If the events are due to an update of this map or if items are exiting,
|
||||
# just returns.
|
||||
return if updating or event isnt 'enter'
|
||||
|
||||
# No need to trigger an update if nothing has changed.
|
||||
changed = false
|
||||
|
||||
$forEach items, (item) ->
|
||||
val = valFn.call item
|
||||
if val and map[val[0]] isnt val[1]
|
||||
changed = true
|
||||
map[val[0]] = val[1]
|
||||
return
|
||||
|
||||
if changed
|
||||
try
|
||||
updating = true
|
||||
collection.touch subscribers
|
||||
finally
|
||||
updating = false
|
||||
|
||||
generator = ->
|
||||
subscribers[@key] = true
|
||||
map
|
||||
generator.unsubscribe = ->
|
||||
delete subscribers[@key]
|
||||
|
||||
generator
|
||||
|
||||
# Shared watchers.
|
||||
UUIDsToKeys = $map ->
|
||||
{UUID} = @val
|
||||
return false unless UUID
|
||||
[UUID, "#{@key}"]
|
||||
messages = $set {
|
||||
rule: 'message'
|
||||
bind: -> @val.$object or @val.poolRef
|
||||
}
|
||||
|
||||
# Classes in XAPI are not always delivered with the same case,
|
||||
# therefore a map is needed to make sure they always map to the same
|
||||
# rule.
|
||||
rulesMap = {}
|
||||
|
||||
# Defines which rule should be used for this item.
|
||||
#
|
||||
# Note: If the rule does not exists, a temporary item is created. FIXME
|
||||
@dispatch = ->
|
||||
{$type: type} = @genval
|
||||
|
||||
# Normalizes the type.
|
||||
type = rulesMap[type.toLowerCase()] ? type
|
||||
|
||||
# Subtypes handling for VMs.
|
||||
if type is 'VM'
|
||||
return 'VM-controller' if @genval.is_control_domain
|
||||
return 'VM-snapshot' if @genval.is_a_snapshot
|
||||
return 'VM-template' if @genval.is_a_template
|
||||
|
||||
type
|
||||
|
||||
# Missing rules should be created.
|
||||
@missingRule = (name) ->
|
||||
@rule(name, ->
|
||||
@key = -> @genval.id
|
||||
@val = -> @genval
|
||||
)
|
||||
|
||||
# Rule conflicts are possible (e.g. VM-template to VM).
|
||||
@ruleConflict = ( -> )
|
||||
|
||||
# Used to apply common definition to rules.
|
||||
@hook afterRule: ->
|
||||
# Registers this rule in the map.
|
||||
rulesMap[@name.toLowerCase()] = @name
|
||||
|
||||
# TODO: explain.
|
||||
return unless @val?
|
||||
|
||||
unless $isObject @val
|
||||
throw new Error 'the value should be an object'
|
||||
|
||||
# Injects various common definitions.
|
||||
@val.type = @name
|
||||
if @singleton
|
||||
@val.id = @val.ref = -> @key
|
||||
else
|
||||
# This definition are for non singleton items only.
|
||||
@key = -> @genval.$ref
|
||||
@val.id = -> @genval.$id
|
||||
@val.UUID = -> @genval.uuid
|
||||
@val.ref = -> @genval.$ref
|
||||
@val.poolRef = -> @genval.$pool.$ref
|
||||
@val.$poolId = -> @genval.$pool.$id
|
||||
|
||||
# Main objects all can have associated messages and tags.
|
||||
if @name in ['host', 'pool', 'SR', 'VM', 'VM-controller']
|
||||
@val.messages = messages
|
||||
|
||||
@val.tags = $retrieveTags
|
||||
|
||||
# Helper to create multiple rules with the same definition.
|
||||
rules = (rules, definition) =>
|
||||
@rule rule, definition for rule in rules
|
||||
|
||||
#===================================================================
|
||||
|
||||
# An item is equivalent to a rule but one and only one instance of
|
||||
# this rule is created without any generator.
|
||||
@item xo: ->
|
||||
@val = {
|
||||
|
||||
# TODO: Maybe there should be high-level hosts: those who do not
|
||||
# belong to a pool.
|
||||
|
||||
pools: $set {
|
||||
rule: 'pool'
|
||||
}
|
||||
|
||||
$CPUs: $sum {
|
||||
rule: 'host'
|
||||
val: -> +(@val.CPUs.cpu_count)
|
||||
}
|
||||
|
||||
$vCPUs: $sum {
|
||||
rule: 'VM'
|
||||
val: -> @val.CPUs.number
|
||||
if: $isVMRunning
|
||||
}
|
||||
|
||||
# Do not work due to problem in host rule.
|
||||
$memory: {
|
||||
usage: $sum {
|
||||
rule: 'host'
|
||||
if: $isHostRunning
|
||||
val: -> @val.memory.usage
|
||||
}
|
||||
size: $sum {
|
||||
rule: 'host'
|
||||
if: $isHostRunning
|
||||
val: -> @val.memory.size
|
||||
}
|
||||
}
|
||||
|
||||
# Maps the UUIDs to keys (i.e. opaque references).
|
||||
$UUIDsToKeys: UUIDsToKeys
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule pool: ->
|
||||
@val = {
|
||||
name_label: -> @genval.name_label
|
||||
|
||||
name_description: -> @genval.name_description
|
||||
|
||||
SRs: $set {
|
||||
rule: 'SR'
|
||||
bind: -> @val.$container
|
||||
}
|
||||
|
||||
default_SR: ->
|
||||
SR = @genval.default_SR
|
||||
if SR is 'OpaqueRef:NULL'
|
||||
null
|
||||
else
|
||||
SR
|
||||
|
||||
HA_enabled: -> @genval.ha_enabled
|
||||
|
||||
hosts: $set {
|
||||
rule: 'host'
|
||||
bind: -> @genval.$pool.$ref
|
||||
}
|
||||
|
||||
master: -> @genval.master
|
||||
|
||||
networks: $set {
|
||||
rule: 'network'
|
||||
bind: -> @genval.$pool.$ref
|
||||
}
|
||||
|
||||
templates: $set {
|
||||
rule: 'VM-template'
|
||||
bind: -> @val.$container
|
||||
}
|
||||
|
||||
VMs: $set {
|
||||
rule: 'VM'
|
||||
bind: -> @val.$container
|
||||
}
|
||||
|
||||
$running_hosts: $set {
|
||||
rule: 'host'
|
||||
bind: -> @genval.$pool.$ref
|
||||
if: $isHostRunning
|
||||
}
|
||||
|
||||
$running_VMs: $set {
|
||||
rule: 'VM'
|
||||
bind: -> @genval.$pool.$ref
|
||||
if: $isVMRunning
|
||||
}
|
||||
|
||||
$VMs: $set {
|
||||
rule: 'VM'
|
||||
bind: -> @genval.$pool.$ref
|
||||
}
|
||||
|
||||
patches: $set {
|
||||
rule: 'pool_patch'
|
||||
bind: -> @genval.$pool.$ref
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule host: ->
|
||||
# Private properties used to helps construction.
|
||||
@data = {
|
||||
metrics: $link -> @genval.metrics
|
||||
}
|
||||
|
||||
@val = {
|
||||
name_label: -> @genval.name_label
|
||||
|
||||
name_description: -> @genval.name_description
|
||||
|
||||
address: -> @genval.address
|
||||
|
||||
controller: $val {
|
||||
rule: 'VM-controller'
|
||||
bind: -> @val.$container
|
||||
val: -> @key
|
||||
}
|
||||
|
||||
bios_strings: -> @genval.bios_strings
|
||||
|
||||
CPUs: -> @genval.cpu_info
|
||||
|
||||
enabled: -> @genval.enabled
|
||||
|
||||
current_operations: -> @genval.current_operations
|
||||
|
||||
hostname: -> @genval.hostname
|
||||
|
||||
iSCSI_name: -> @genval.other_config?.iscsi_iqn ? null
|
||||
|
||||
memory: ->
|
||||
{metrics} = @data
|
||||
if metrics
|
||||
{
|
||||
usage: +metrics.memory_total - metrics.memory_free
|
||||
size: +metrics.memory_total
|
||||
}
|
||||
else
|
||||
{
|
||||
usage: 0
|
||||
size: 0
|
||||
}
|
||||
|
||||
patches: -> @genval.patches
|
||||
|
||||
power_state: ->
|
||||
if @data.metrics?.live
|
||||
'Running'
|
||||
else
|
||||
'Halted'
|
||||
|
||||
# Local SRs are handled directly in `SR.$container`.
|
||||
SRs: $set {
|
||||
rule: 'SR'
|
||||
bind: -> @val.$container
|
||||
}
|
||||
|
||||
# What are local templates?
|
||||
templates: $set {
|
||||
rule: 'VM-template'
|
||||
bind: -> @val.$container
|
||||
}
|
||||
|
||||
# Local VMs are handled directly in `VM.$container`.
|
||||
VMs: $set {
|
||||
rule: 'VM'
|
||||
bind: -> @val.$container
|
||||
}
|
||||
|
||||
$PBDs: -> @genval.PBDs
|
||||
|
||||
PIFs: -> @genval.PIFs
|
||||
$PIFs: -> @val.PIFs
|
||||
|
||||
PCIs: -> @genval.PCIs
|
||||
$PCIs: -> @val.PCIs
|
||||
|
||||
PGPUs: -> @genval.PGPUs
|
||||
$PGPUs: -> @val.PGPUs
|
||||
|
||||
tasks: $set {
|
||||
rule: 'task'
|
||||
bind: -> @genval.resident_on
|
||||
if: $isTaskLive
|
||||
}
|
||||
|
||||
$running_VMs: $set {
|
||||
rule: 'VM'
|
||||
bind: -> @val.$container
|
||||
if: $isVMRunning
|
||||
}
|
||||
|
||||
$vCPUs: $sum {
|
||||
rule: 'VM'
|
||||
bind: -> @val.$container
|
||||
if: $isVMRunning
|
||||
val: -> @val.CPUs.number
|
||||
}
|
||||
|
||||
version: -> @genval.software_version.product_version
|
||||
|
||||
build: -> @genval.software_version.build_number
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# This definition is shared.
|
||||
VMdef = ->
|
||||
@data = {
|
||||
metrics: $link -> @genval.metrics
|
||||
guest_metrics: $link -> @genval.guest_metrics
|
||||
}
|
||||
|
||||
@val = {
|
||||
name_label: -> @genval.name_label
|
||||
|
||||
name_description: -> @genval.name_description
|
||||
|
||||
boot: -> @genval.HVM_boot_params
|
||||
|
||||
addresses: ->
|
||||
{guest_metrics} = @data
|
||||
if guest_metrics
|
||||
guest_metrics.networks
|
||||
else
|
||||
null
|
||||
|
||||
consoles: $set {
|
||||
rule: 'console'
|
||||
bind: -> @genval.VM
|
||||
val: -> @val
|
||||
}
|
||||
|
||||
current_operations: -> @genval.current_operations
|
||||
|
||||
docker: ->
|
||||
monitor = @genval.other_config['xscontainer-monitor']
|
||||
return if not monitor?
|
||||
|
||||
if monitor is 'False'
|
||||
return {
|
||||
enabled: false
|
||||
}
|
||||
|
||||
if @genval.power_state is 'Running'
|
||||
if @genval.other_config.docker_ps && @genval.other_config.docker_info && @genval.other_config.docker_version
|
||||
process = ($parseXML @genval.other_config.docker_ps).docker_ps
|
||||
info = ($parseXML @genval.other_config.docker_info).docker_info
|
||||
version = ($parseXML @genval.other_config.docker_version).docker_version
|
||||
|
||||
return {
|
||||
enabled: true
|
||||
process: process
|
||||
info: info
|
||||
version: version
|
||||
}
|
||||
|
||||
return { enabled: true}
|
||||
|
||||
# TODO: there is two possible value: "best-effort" and "restart"
|
||||
high_availability: ->
|
||||
if @genval.ha_restart_priority
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
auto_poweron: ->
|
||||
if @genval.other_config.auto_poweron
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
os_version: ->
|
||||
{guest_metrics} = @data
|
||||
if guest_metrics
|
||||
guest_metrics.os_version
|
||||
else
|
||||
null
|
||||
|
||||
VGPUs: -> @genval.VGPUs
|
||||
$VGPUs: -> @val.VGPUs
|
||||
|
||||
power_state: -> @genval.power_state
|
||||
|
||||
other: -> @genval.other_config
|
||||
|
||||
memory: ->
|
||||
{metrics, guest_metrics} = @data
|
||||
|
||||
memory = {
|
||||
dynamic: [
|
||||
+@genval.memory_dynamic_min
|
||||
+@genval.memory_dynamic_max
|
||||
]
|
||||
static: [
|
||||
+@genval.memory_static_min
|
||||
+@genval.memory_static_max
|
||||
]
|
||||
}
|
||||
|
||||
memory.size = if not $isVMRunning.call this
|
||||
+@genval.memory_dynamic_max
|
||||
else if (gmmemory = guest_metrics?.memory)?.used
|
||||
memory.usage = +gmmemory.used
|
||||
+gmmemory.total
|
||||
else if metrics
|
||||
+metrics.memory_actual
|
||||
else
|
||||
+@genval.memory_dynamic_max
|
||||
|
||||
memory
|
||||
|
||||
PV_drivers: ->
|
||||
{guest_metrics} = @data
|
||||
if guest_metrics
|
||||
guest_metrics.PV_drivers_up_to_date
|
||||
else
|
||||
false
|
||||
|
||||
CPUs: ->
|
||||
{metrics} = @data
|
||||
|
||||
CPUs = {
|
||||
max: +@genval.VCPUs_max
|
||||
number: if ($isVMRunning.call this) and metrics
|
||||
+metrics.VCPUs_number
|
||||
else
|
||||
+@genval.VCPUs_at_startup
|
||||
}
|
||||
|
||||
$CPU_usage: null #TODO
|
||||
|
||||
# FIXME: $container should contains the pool UUID when the VM is
|
||||
# not on a host.
|
||||
$container: ->
|
||||
if $isVMRunning.call this
|
||||
@genval.resident_on
|
||||
else
|
||||
# TODO: Handle local VMs. (`get_possible_hosts()`).
|
||||
@genval.$pool.$ref
|
||||
|
||||
snapshots: -> @genval.snapshots
|
||||
|
||||
snapshot_time: -> $toTimestamp @genval.snapshot_time
|
||||
|
||||
$VBDs: -> @genval.VBDs
|
||||
|
||||
VIFs: -> @genval.VIFs
|
||||
|
||||
}
|
||||
@rule VM: VMdef
|
||||
@rule 'VM-controller': VMdef
|
||||
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# VM-snapshot starts with the same definition but extends it.
|
||||
@rule 'VM-snapshot': ->
|
||||
VMdef.call(this)
|
||||
|
||||
@val.$snapshot_of = -> @genval.snapshot_of
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# VM-template starts with the same definition but extends it.
|
||||
@rule 'VM-template': ->
|
||||
VMdef.call this
|
||||
|
||||
@val.CPUs.number = -> +@genval.VCPUs_at_startup
|
||||
|
||||
@val.template_info = {
|
||||
arch: -> @genval.other_config?['install-arch']
|
||||
disks: ->
|
||||
#console.log @genval.other_config
|
||||
disks = @genval.other_config?.disks
|
||||
return [] unless disks?
|
||||
|
||||
disks = ($parseXML disks)?.provision?.disk
|
||||
return [] unless disks?
|
||||
|
||||
disks = [disks] unless $isArray disks
|
||||
# Normalize entries.
|
||||
for disk in disks
|
||||
disk.bootable = disk.bootable is 'true'
|
||||
disk.size = +disk.size
|
||||
disk.SR = disk.sr
|
||||
delete disk.sr
|
||||
disks
|
||||
install_methods: ->
|
||||
methods = @genval.other_config?['install-methods']
|
||||
return [] unless methods?
|
||||
methods.split ','
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule SR: ->
|
||||
@data = {
|
||||
# Note: not dynamic.
|
||||
host: $link(
|
||||
-> @genval.PBDs[0] ? 'OpaqueRef:NULL'
|
||||
-> @val.host
|
||||
)
|
||||
}
|
||||
|
||||
@val = {
|
||||
name_label: -> @genval.name_label
|
||||
|
||||
name_description: -> @genval.name_description
|
||||
|
||||
SR_type: -> @genval.type
|
||||
|
||||
content_type: -> @genval.content_type
|
||||
|
||||
physical_usage: -> +@genval.physical_utilisation
|
||||
|
||||
usage: -> +@genval.virtual_allocation
|
||||
|
||||
size: -> +@genval.physical_size
|
||||
|
||||
$container: ->
|
||||
if @genval.shared
|
||||
@genval.$pool.$ref
|
||||
else
|
||||
@data.host
|
||||
|
||||
$PBDs: -> @genval.PBDs
|
||||
|
||||
VDIs: -> @genval.VDIs
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule PBD: ->
|
||||
@val = {
|
||||
attached: -> @genval.currently_attached
|
||||
|
||||
host: -> @genval.host
|
||||
|
||||
SR: -> @genval.SR
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule PIF: ->
|
||||
@val = {
|
||||
attached: -> @genval.currently_attached
|
||||
|
||||
device: -> @genval.device
|
||||
|
||||
IP: -> @genval.IP
|
||||
|
||||
$host: -> @genval.host
|
||||
|
||||
MAC: -> @genval.MAC
|
||||
|
||||
# TODO: Find a more meaningful name.
|
||||
management: -> @genval.management
|
||||
|
||||
mode: -> @genval.ip_configuration_mode
|
||||
|
||||
MTU: -> +@genval.MTU
|
||||
|
||||
netmask: -> @genval.netmask
|
||||
|
||||
$network: -> @genval.network
|
||||
|
||||
vlan: -> @genval.VLAN
|
||||
|
||||
# TODO: What is it?
|
||||
#
|
||||
# Could it mean “is this a physical interface?”.
|
||||
# How could a PIF not be physical?
|
||||
#physical: -> @genval.physical
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule VDI: ->
|
||||
@val = {
|
||||
name_label: -> @genval.name_label
|
||||
|
||||
name_description: -> @genval.name_description
|
||||
|
||||
# TODO: determine whether or not tags are required for a VDI.
|
||||
#tags: $retrieveTags
|
||||
|
||||
usage: -> +@genval.physical_utilisation
|
||||
|
||||
size: -> +@genval.virtual_size
|
||||
|
||||
$snapshot_of: ->
|
||||
original = @genval.snapshot_of
|
||||
if original is 'OpaqueRef:NULL'
|
||||
null
|
||||
else
|
||||
original
|
||||
|
||||
snapshots: -> @genval.snapshots
|
||||
|
||||
# TODO: Does the name fit?
|
||||
#snapshot_time: -> @genval.snapshot_time
|
||||
|
||||
$SR: -> @genval.SR
|
||||
|
||||
$VBDs: -> @genval.VBDs
|
||||
|
||||
$VBD: -> # Deprecated
|
||||
{VBDs} = @genval
|
||||
|
||||
if VBDs.length is 0 then null else VBDs[0]
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule VBD: ->
|
||||
@val = {
|
||||
attached: -> @genval.currently_attached
|
||||
|
||||
bootable: -> @genval.bootable
|
||||
|
||||
read_only: -> @genval.mode is 'RO'
|
||||
|
||||
is_cd_drive: -> @genval.type is 'CD'
|
||||
|
||||
position: -> @genval.userdevice
|
||||
|
||||
# null if empty.
|
||||
#
|
||||
# TODO: Is it really equivalent?
|
||||
VDI: ->
|
||||
VDI = @genval.VDI
|
||||
if VDI is 'OpaqueRef:NULL'
|
||||
null
|
||||
else
|
||||
VDI
|
||||
|
||||
VM: -> @genval.VM
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule VIF: ->
|
||||
@val = {
|
||||
attached: -> @genval.currently_attached
|
||||
|
||||
# TODO: Should it be cast to a number?
|
||||
device: -> @genval.device
|
||||
|
||||
MAC: -> @genval.MAC
|
||||
|
||||
MTU: -> +@genval.MTU
|
||||
|
||||
$network: -> @genval.network
|
||||
|
||||
$VM: -> @genval.VM
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule network: ->
|
||||
@val = {
|
||||
name_label: -> @genval.name_label
|
||||
|
||||
name_description: -> @genval.name_description
|
||||
|
||||
# TODO: determine whether or not tags are required for a VDI.
|
||||
#tags: $retrieveTags
|
||||
|
||||
bridge: -> @genval.bridge
|
||||
|
||||
MTU: -> +@genval.MTU
|
||||
|
||||
PIFs: -> @genval.PIFs
|
||||
|
||||
VIFs: -> @genval.VIFs
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule message: ->
|
||||
@val = {
|
||||
time: -> $toTimestamp @genval.timestamp
|
||||
|
||||
$object: ->
|
||||
# If the key of the concerned object has already be resolved
|
||||
# returns the known value.
|
||||
return @val.$object if @val.$object?
|
||||
|
||||
# Tries to resolve the key of the concerned object.
|
||||
object = (UUIDsToKeys.call this)[@genval.obj_uuid]
|
||||
|
||||
# If resolved, unregister from the watcher.
|
||||
UUIDsToKeys.unsubscribe.call this if object?
|
||||
|
||||
object
|
||||
|
||||
# TODO: Are these names meaningful?
|
||||
name: -> @genval.name
|
||||
body: -> @genval.body
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
@rule task: ->
|
||||
@val = {
|
||||
name_label: -> @genval.name_label
|
||||
|
||||
name_description: -> @genval.name_description
|
||||
|
||||
progress: -> +@genval.progress
|
||||
|
||||
result: -> @genval.result
|
||||
|
||||
$host: -> @genval.resident_on
|
||||
|
||||
created: -> @genval.created
|
||||
|
||||
finished: -> @genval.finished
|
||||
|
||||
current_operations: -> @genval.current_operations
|
||||
|
||||
status: -> @genval.status
|
||||
}
|
||||
|
||||
@rule host_patch: ->
|
||||
@val = {
|
||||
applied: -> @genval.applied
|
||||
|
||||
$host: -> @genval.host
|
||||
|
||||
time: -> $toTimestamp @genval.timestamp_applied
|
||||
|
||||
pool_patch: -> @genval.pool_patch
|
||||
|
||||
}
|
||||
|
||||
@rule pool_patch: ->
|
||||
@val = {
|
||||
name_label: -> @genval.name_label
|
||||
|
||||
name_description: -> @genval.name_description
|
||||
|
||||
applied: -> @genval.pool_applied
|
||||
|
||||
version: -> @genval.version
|
||||
|
||||
$host_patches: -> @genval.host_patches
|
||||
|
||||
size: -> @genval.size
|
||||
|
||||
}
|
||||
|
||||
@rule pci: ->
|
||||
@val = {
|
||||
pci_id: -> @genval.pci_id
|
||||
|
||||
class_name: -> @genval.class_name
|
||||
|
||||
device_name: -> @genval.device_name
|
||||
|
||||
$host: -> @genval.host
|
||||
|
||||
}
|
||||
|
||||
@rule pgpu: ->
|
||||
@val = {
|
||||
pci: -> @genval.PCI
|
||||
|
||||
host: -> @genval.host
|
||||
|
||||
vgpus: -> @genval.resident_VGPUs
|
||||
$vgpus: -> @val.vgpus
|
||||
|
||||
$host: -> @genval.host
|
||||
|
||||
}
|
||||
|
||||
@rule vgpu: ->
|
||||
@val = {
|
||||
currentAttached: -> @genval.currently_attached
|
||||
|
||||
vm: -> @genval.VM
|
||||
|
||||
device: -> @genval.device
|
||||
|
||||
resident_on: -> @genval.resident_on
|
||||
|
||||
}
|
||||
|
||||
return
|
File diff suppressed because one or more lines are too long
@ -1,753 +0,0 @@
|
||||
{expect: $expect} = require 'chai'
|
||||
|
||||
$sinon = require 'sinon'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
{$MappedCollection} = require './MappedCollection'
|
||||
|
||||
# Helpers for dealing with fibers.
|
||||
{$coroutine} = require './fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
describe 'spec', ->
|
||||
|
||||
collection = null
|
||||
before $coroutine ->
|
||||
# Creates the collection.
|
||||
collection = new $MappedCollection()
|
||||
|
||||
# Loads the spec.
|
||||
(require './spec').call collection
|
||||
|
||||
# Loads the mockup data.
|
||||
collection.set (require './spec.spec-data')
|
||||
|
||||
#console.log collection.get()
|
||||
|
||||
it 'xo', ->
|
||||
xo = collection.get 'xo'
|
||||
|
||||
#console.log xo
|
||||
|
||||
$expect(xo).to.be.an 'object'
|
||||
|
||||
$expect(xo.type).to.equal 'xo'
|
||||
|
||||
$expect(xo.pools).to.have.members [
|
||||
'OpaqueRef:6462d0b3-8f20-ef76-fddf-002f7af3452e'
|
||||
]
|
||||
|
||||
$expect(xo.$CPUs).to.equal 8
|
||||
|
||||
$expect(xo.$vCPUs).to.equal 10
|
||||
|
||||
$expect(xo.$memory).to.be.an 'object'
|
||||
$expect(xo.$memory.usage).to.equal 15185723392
|
||||
$expect(xo.$memory.size).to.equal 33532379136
|
||||
|
||||
UUIDsToKeys = {}
|
||||
UUIDsToKeys[obj.UUID] = "#{obj.ref}" for obj in collection.get() when obj.UUID?
|
||||
$expect(xo.$UUIDsToKeys).to.deep.equal UUIDsToKeys
|
||||
|
||||
it 'pool', ->
|
||||
pool = collection.get 'OpaqueRef:6462d0b3-8f20-ef76-fddf-002f7af3452e'
|
||||
|
||||
#console.log pool
|
||||
|
||||
$expect(pool).to.be.an 'object'
|
||||
|
||||
$expect(pool.type).to.equal 'pool'
|
||||
|
||||
$expect(pool.name_label).to.equal 'Lab Pool'
|
||||
|
||||
$expect(pool.name_description).to.equal 'Vates dev pool at our HQ'
|
||||
|
||||
$expect(pool.tags).to.have.members []
|
||||
|
||||
$expect(pool.SRs).to.have.members [
|
||||
'OpaqueRef:d6fe49bf-dd48-c929-5aab-b2786a2e7aee'
|
||||
'OpaqueRef:6637b7d7-9e5c-f331-c7e4-a7f68f77a047'
|
||||
'OpaqueRef:557155b2-f092-3417-f509-7ee35b1d42da'
|
||||
]
|
||||
|
||||
$expect(pool.default_SR).to.equal 'OpaqueRef:d6fe49bf-dd48-c929-5aab-b2786a2e7aee'
|
||||
|
||||
$expect(pool.HA_enabled).to.be.false
|
||||
|
||||
$expect(pool.hosts).to.have.members [
|
||||
'OpaqueRef:cd0f68c5-5245-5ae8-f0e1-324e2201c692'
|
||||
'OpaqueRef:bbc98f5e-1a17-2030-28af-0df2393f3145'
|
||||
]
|
||||
|
||||
$expect(pool.master).to.equal 'OpaqueRef:bbc98f5e-1a17-2030-28af-0df2393f3145'
|
||||
|
||||
$expect(pool.networks).to.have.members [
|
||||
'OpaqueRef:dbc93777-f2c0-e888-967d-dd9beeffb3c0'
|
||||
'OpaqueRef:4e265829-7517-3520-6a97-56b6ac0730c9'
|
||||
'OpaqueRef:16013d48-b9eb-84c0-0e62-d809211b0632'
|
||||
]
|
||||
|
||||
$expect(pool.templates).to.have.members [
|
||||
'OpaqueRef:f81c6db6-4227-55a5-0c2f-b670ca5d8d3f'
|
||||
'OpaqueRef:f449b8ec-ac86-1b6d-2347-37ec36c41bc5'
|
||||
'OpaqueRef:f02a3c19-447b-c618-fb51-a9cde79be17c'
|
||||
'OpaqueRef:ee2e2c00-8011-4847-ba7e-c288d5fb01f5'
|
||||
'OpaqueRef:ebc96e49-11d4-471d-c21f-625a95c34ff9'
|
||||
'OpaqueRef:e9fb38c8-acc3-dbb8-cc6f-f1f89b03c1ae'
|
||||
'OpaqueRef:e803bc1b-d3be-b95f-f3cc-a26a174ec93c'
|
||||
'OpaqueRef:e373c644-3576-985e-9c8f-67062c81d0d2'
|
||||
'OpaqueRef:e3035b8b-cd27-3e7c-ecbf-54a18a2da59e'
|
||||
'OpaqueRef:d99a46bf-1b68-072c-00db-444d099466cd'
|
||||
'OpaqueRef:d45b3989-7350-5166-eeaa-7b789a32addd'
|
||||
'OpaqueRef:d18c965e-0cef-48b0-2f8d-d48ef6663c32'
|
||||
'OpaqueRef:d15de0db-1dc5-2a00-331a-c0f7d3c2e123'
|
||||
'OpaqueRef:cfe620f9-5c68-0f35-ce9f-8f5227fda1c8'
|
||||
'OpaqueRef:cb865487-9139-3fbc-4aac-68abdb663925'
|
||||
'OpaqueRef:c8bf31d6-9888-4256-1547-c722016a0079'
|
||||
'OpaqueRef:c651901b-0944-be6b-aabf-a87d9a037edd'
|
||||
'OpaqueRef:c5a9e2de-1916-7f4c-aa2a-ce95d138032b'
|
||||
'OpaqueRef:c22bce1f-16a0-7745-179d-dcbd5c5deab3'
|
||||
'OpaqueRef:be6abc7d-dd7a-5ee6-9c95-8e562a69d992'
|
||||
'OpaqueRef:b9587bb6-6efe-0c71-e01c-2c750c9ab774'
|
||||
'OpaqueRef:b6f58482-8b60-b3b4-2a01-0d6113411bf2'
|
||||
'OpaqueRef:ad21fbbb-6cf9-e6ca-c415-1f428f20da1f'
|
||||
'OpaqueRef:aa2d04ec-0512-c128-8820-c8ecde93baa4'
|
||||
'OpaqueRef:a247a02f-8909-5044-64a0-82460b25e740'
|
||||
'OpaqueRef:9d28dba9-aee6-cafd-06af-54ebdfb1c271'
|
||||
'OpaqueRef:9796cc01-6640-211f-09f9-fee94f9cd720'
|
||||
'OpaqueRef:922b3a98-f238-4cea-8b75-c38e90ac11ee'
|
||||
'OpaqueRef:8e720505-e75b-eda3-3b14-fd1471890cc1'
|
||||
'OpaqueRef:8e3211dc-fdaf-22c7-41b2-c3a892529679'
|
||||
'OpaqueRef:89919714-1184-ce4b-3cb5-67059640b3a7'
|
||||
'OpaqueRef:892768c0-4d15-769f-e760-b781a0291ddb'
|
||||
'OpaqueRef:838ff163-ae6e-d98e-9cef-4d783f81dcb0'
|
||||
'OpaqueRef:8079d64b-fe87-0ecf-e558-7b607b0e1524'
|
||||
'OpaqueRef:773d92c9-898b-bc25-a50d-d868bbf933a4'
|
||||
'OpaqueRef:770d2193-ab69-4fc3-c462-7f75a79d497c'
|
||||
'OpaqueRef:75441e00-55df-85f5-1780-731110df91de'
|
||||
'OpaqueRef:6ee1cc24-ebbb-b02a-88b0-a921c7a5f217'
|
||||
'OpaqueRef:6b5be573-b116-6238-9cff-bde0658d6f18'
|
||||
'OpaqueRef:6a09a6de-e778-a474-4ebd-f617db5b5d5e'
|
||||
'OpaqueRef:616942c0-1e1b-e733-3c4c-7236fd3de158'
|
||||
'OpaqueRef:5e93cf73-a212-a83f-d3f9-a539be98d320'
|
||||
'OpaqueRef:56af2e14-d4bb-20e9-421b-00d75dfb89f2'
|
||||
'OpaqueRef:5059cc2d-b414-97eb-6aac-ce816b72b2bd'
|
||||
'OpaqueRef:4a43ad28-b809-2c8f-aa24-70d8bd4954f2'
|
||||
'OpaqueRef:466d7dc3-f2df-8c8d-685d-eef256fe2b43'
|
||||
'OpaqueRef:4347e9d6-7faf-90e4-4f5f-d513cf44b3cc'
|
||||
'OpaqueRef:3c4558e8-ed88-ce88-81a9-111ac2cc56d6'
|
||||
'OpaqueRef:3b97e45b-aa4e-d175-95e5-e95ceefa0b6b'
|
||||
'OpaqueRef:2e3b5ada-5083-87b1-d6fb-aaa0e5bd862d'
|
||||
'OpaqueRef:2b6e3248-52b0-85d1-7415-4f91a0a90a3a'
|
||||
'OpaqueRef:2a838052-3aa3-d09d-1eae-8293a565fef5'
|
||||
'OpaqueRef:2a092eee-7c6a-058b-0368-b37362328678'
|
||||
'OpaqueRef:2968283f-8656-6e31-816c-e96325e66ebf'
|
||||
'OpaqueRef:27ad4e06-a7b2-20a2-4fd9-7f1b54fdc5a2'
|
||||
'OpaqueRef:217d930f-8e65-14e6-eb20-63d55158093f'
|
||||
'OpaqueRef:20377446-2388-5c8f-d3f2-6e9c883c61d9'
|
||||
'OpaqueRef:201cf416-bfd0-00d3-a4d2-b19226c43c82'
|
||||
'OpaqueRef:1ed4ee31-56e0-98da-65d4-00c776716b9c'
|
||||
'OpaqueRef:1c0b590d-563b-5061-a253-f98535ab8389'
|
||||
'OpaqueRef:1be0fe3b-1944-06db-3734-b6bb888cfe78'
|
||||
'OpaqueRef:12d0dfc0-ce63-a072-3cd0-ccba7bd3c200'
|
||||
'OpaqueRef:039273c3-b4b2-5c68-63e4-c5610a738fe3'
|
||||
'OpaqueRef:030314a2-0909-9e7a-418a-9f38746aaf0c',
|
||||
]
|
||||
|
||||
$expect(pool.VMs).to.have.members [
|
||||
'OpaqueRef:d4fa8fba-ec86-5928-a1bb-dd78b6fb5944'
|
||||
'OpaqueRef:8491f148-3e78-9c74-ab98-84445c5f2861'
|
||||
'OpaqueRef:13b9ec24-04ea-ae04-78e6-6ec4b81a8deb'
|
||||
]
|
||||
|
||||
$expect(pool.$running_hosts).to.have.members [
|
||||
'OpaqueRef:cd0f68c5-5245-5ae8-f0e1-324e2201c692'
|
||||
'OpaqueRef:bbc98f5e-1a17-2030-28af-0df2393f3145'
|
||||
]
|
||||
|
||||
$expect(pool.$running_VMs).to.have.members [
|
||||
'OpaqueRef:fdaba312-c3a5-0190-b1a1-bf389567e620'
|
||||
'OpaqueRef:46fa4c52-5e93-6cf7-32e3-c51fb4ed106d'
|
||||
'OpaqueRef:c0fa9288-2a6b-cd8e-b9a8-cc5afc63b386'
|
||||
'OpaqueRef:be2390b2-cd08-53f5-3fae-b76f6f3725bf'
|
||||
'OpaqueRef:8f9966ea-38ef-ac4c-b634-81e31ef1e7c1'
|
||||
'OpaqueRef:646297e5-4fd6-c70d-6365-ef19b9807f64'
|
||||
'OpaqueRef:1ef43ee8-bc18-6c4f-4919-0e42a3ac6e4b'
|
||||
]
|
||||
|
||||
$expect(pool.$VMs).to.have.members [
|
||||
'OpaqueRef:fdaba312-c3a5-0190-b1a1-bf389567e620'
|
||||
'OpaqueRef:46fa4c52-5e93-6cf7-32e3-c51fb4ed106d'
|
||||
'OpaqueRef:d4fa8fba-ec86-5928-a1bb-dd78b6fb5944'
|
||||
'OpaqueRef:8491f148-3e78-9c74-ab98-84445c5f2861'
|
||||
'OpaqueRef:13b9ec24-04ea-ae04-78e6-6ec4b81a8deb'
|
||||
'OpaqueRef:c0fa9288-2a6b-cd8e-b9a8-cc5afc63b386'
|
||||
'OpaqueRef:be2390b2-cd08-53f5-3fae-b76f6f3725bf'
|
||||
'OpaqueRef:8f9966ea-38ef-ac4c-b634-81e31ef1e7c1'
|
||||
'OpaqueRef:646297e5-4fd6-c70d-6365-ef19b9807f64'
|
||||
'OpaqueRef:1ef43ee8-bc18-6c4f-4919-0e42a3ac6e4b'
|
||||
]
|
||||
|
||||
$expect(pool.messages).to.have.members [
|
||||
'OpaqueRef:0241d2be-fcda-64b7-b95d-550399f22000'
|
||||
'OpaqueRef:08093780-5d87-46f4-400d-fc8406bdd6c2'
|
||||
'OpaqueRef:0c565205-db69-eb0f-b80b-a8e356ae43ae'
|
||||
'OpaqueRef:0f955091-d6e6-ed3e-2bbe-94d914e6efbe'
|
||||
'OpaqueRef:15f61c91-5ac8-6234-78bb-2edbdcf9164f'
|
||||
'OpaqueRef:1b04b4db-3622-4d54-e8fa-a2f6661c6e43'
|
||||
'OpaqueRef:20aadafb-47c8-0796-e3c2-4e497bcb0205'
|
||||
'OpaqueRef:2243e321-e4bd-50dd-1451-f329df240517'
|
||||
'OpaqueRef:226e9274-77d6-9805-a0f3-396d1e54fe72'
|
||||
'OpaqueRef:230d01c6-3e25-b877-9e35-13a707335e23'
|
||||
'OpaqueRef:279e9aed-7d9e-13bc-e4d2-d477abbf9f6a'
|
||||
'OpaqueRef:2c460c86-2e1c-cd0d-cbaf-95bf771af2bc'
|
||||
'OpaqueRef:300a2868-2b8a-4f0c-788d-4e2ba4a160da'
|
||||
'OpaqueRef:323297f9-4a0b-c517-1ff7-eacad80fc796'
|
||||
'OpaqueRef:33d58ecd-d2a4-f63a-46bb-307a7c7762a6'
|
||||
'OpaqueRef:3962ad4b-18e9-53ce-ff72-b2ef3d6692ec'
|
||||
'OpaqueRef:3a8a42d6-f5b3-1479-3ad6-2c7caed94459'
|
||||
'OpaqueRef:3f77ad7a-de22-0b05-4005-7cfdc5d8bc86'
|
||||
'OpaqueRef:4147a60c-2b41-4dc7-491d-3470466abbc7'
|
||||
'OpaqueRef:443c4e46-d98a-87d6-92f5-c35bb5b65a5c'
|
||||
'OpaqueRef:4a3aebd9-e670-c796-4938-e29e178f1959'
|
||||
'OpaqueRef:50f02c5f-b2d0-a42a-a156-7905b78a918a'
|
||||
'OpaqueRef:5f34bfc5-f92f-9830-b3e9-06367ef56a77'
|
||||
'OpaqueRef:69d3511e-ec73-69c9-819e-14b85236059d'
|
||||
'OpaqueRef:6b04d423-8991-c838-d981-aca1b9c7be7d'
|
||||
'OpaqueRef:6e161f6f-df2b-195f-be46-530884a2c24a'
|
||||
'OpaqueRef:6f9b4c87-c7ba-1a87-073d-569051f307a8'
|
||||
'OpaqueRef:72360954-3629-1e09-b1bf-b819732bddfd'
|
||||
'OpaqueRef:79f9e82b-1a0e-75b7-efc5-8689a4cd4aed'
|
||||
'OpaqueRef:844844c6-5e82-4d9c-7ed9-01c46d46e67c'
|
||||
'OpaqueRef:84a7efe6-2a37-d4be-5f9a-aa66adfe3104'
|
||||
'OpaqueRef:9a645810-7308-c296-d9df-cc5d91f8f2a4'
|
||||
'OpaqueRef:a073f53c-557a-fd67-878d-b3a881ebd935'
|
||||
'OpaqueRef:a08f1c9a-34de-5441-b847-18533244910d'
|
||||
'OpaqueRef:a4fd777c-f417-23e9-8338-30d8097a8430'
|
||||
'OpaqueRef:a5296901-25c3-b600-7be7-16a20ba86600'
|
||||
'OpaqueRef:a99badbe-75fa-8bc8-22b3-78c616873b62'
|
||||
'OpaqueRef:ab16dfa7-3c86-56c3-038c-c6bcfe0b64c1'
|
||||
'OpaqueRef:af840b26-91b6-56aa-e2a0-266ce7dd411b'
|
||||
'OpaqueRef:b857ac11-36a0-38e4-4d9c-13586e381f7a'
|
||||
'OpaqueRef:c0b26952-1a46-9dfb-a826-78cbfeaa1b00'
|
||||
'OpaqueRef:cdeda917-3496-c407-95fd-2ef63bf5e79e'
|
||||
'OpaqueRef:d5ab7d13-0ebb-5805-b767-608cb7737690'
|
||||
'OpaqueRef:dae9fbe3-a709-3433-e8e3-491b3a79df84'
|
||||
'OpaqueRef:dd735a0f-d2fd-9475-7dd3-b387251f4426'
|
||||
'OpaqueRef:df07d60e-8a03-6979-3e61-4460bc8197b3'
|
||||
'OpaqueRef:e6a0aa45-f8e0-ae7d-7b3a-d76b95a03c95'
|
||||
'OpaqueRef:eaad760a-0e23-4e2b-3f96-2f65170a1dd7'
|
||||
'OpaqueRef:ebead5cf-4a48-ad28-4241-ad5869fa9752'
|
||||
'OpaqueRef:ecc7b91d-6f50-94c6-6f51-2d609dc3ebe7'
|
||||
'OpaqueRef:f3492f88-e0b0-405a-5723-f83429e016c5'
|
||||
]
|
||||
|
||||
it 'host', ->
|
||||
host = collection.get 'OpaqueRef:bbc98f5e-1a17-2030-28af-0df2393f3145'
|
||||
|
||||
#console.log host
|
||||
|
||||
$expect(host).to.be.an 'object'
|
||||
|
||||
$expect(host.type).to.equal 'host'
|
||||
|
||||
$expect(host.name_label).to.equal 'lab1'
|
||||
|
||||
$expect(host.name_description).to.equal 'Default install of XenServer'
|
||||
|
||||
$expect(host.tags).to.have.members []
|
||||
|
||||
$expect(host.address).to.equal '192.168.1.1'
|
||||
|
||||
$expect(host.controller).to.equal 'OpaqueRef:719e4877-c7ad-68be-6b04-5750c8dcfeed'
|
||||
|
||||
# Burk.
|
||||
$expect(host.CPUs).to.deep.equal {
|
||||
cpu_count: '4'
|
||||
socket_count: '1'
|
||||
vendor: 'GenuineIntel'
|
||||
speed: '3192.858'
|
||||
modelname: 'Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz'
|
||||
family: '6'
|
||||
model: '58'
|
||||
stepping: '9'
|
||||
flags: 'fpu de tsc msr pae mce cx8 apic sep mtrr mca cmov pat clflush acpi mmx fxsr sse sse2 ss ht nx constant_tsc nonstop_tsc aperfmperf pni pclmulqdq vmx est ssse3 sse4_1 sse4_2 x2apic popcnt aes hypervisor ida arat tpr_shadow vnmi flexpriority ept vpid'
|
||||
features: '77bae3ff-bfebfbff-00000001-28100800'
|
||||
features_after_reboot: '77bae3ff-bfebfbff-00000001-28100800'
|
||||
physical_features: '77bae3ff-bfebfbff-00000001-28100800'
|
||||
maskable: 'full'
|
||||
}
|
||||
|
||||
$expect(host.enabled).to.be.true
|
||||
|
||||
$expect(host.hostname).to.equal 'lab1'
|
||||
|
||||
$expect(host.iSCSI_name).to.equal 'iqn.2013-07.com.example:83ba9261'
|
||||
|
||||
$expect(host.memory).to.be.an 'object'
|
||||
$expect(host.memory.usage).to.equal 2564788224
|
||||
$expect(host.memory.size).to.equal 8502759424
|
||||
|
||||
$expect(host.power_state).to.equal 'Running'
|
||||
|
||||
$expect(host.SRs).to.have.members [
|
||||
'OpaqueRef:31be9b5e-882a-a8ae-0edf-bf8942b49b5a'
|
||||
'OpaqueRef:7c88a8c6-fc48-8836-28fa-212f67c42d2f'
|
||||
'OpaqueRef:ec76bd6a-f2c0-636d-ca72-de8fb42d6eea'
|
||||
]
|
||||
|
||||
$expect(host.templates).to.have.members [
|
||||
# TODO
|
||||
]
|
||||
|
||||
$expect(host.VMs).to.have.members [
|
||||
'OpaqueRef:fdaba312-c3a5-0190-b1a1-bf389567e620'
|
||||
'OpaqueRef:46fa4c52-5e93-6cf7-32e3-c51fb4ed106d'
|
||||
]
|
||||
|
||||
$expect(host.$PBDs).to.have.members [
|
||||
'OpaqueRef:ff32de74-138c-9d80-ab58-c631d2aa0e71'
|
||||
'OpaqueRef:f0f98779-5cf8-cabc-edc3-631a2d63d89c'
|
||||
'OpaqueRef:b70f8e06-07a8-a5e7-2856-f221c822e9b2'
|
||||
'OpaqueRef:b641552a-8c92-71b3-c0a2-e4dd3d04c215'
|
||||
'OpaqueRef:93320534-824f-850a-64a2-bcbfdc2e0927'
|
||||
'OpaqueRef:0c1d3862-5a38-e4cc-4a46-d8358a622461'
|
||||
]
|
||||
|
||||
$expect(host.$PIFs).to.have.members [
|
||||
'OpaqueRef:aef57ed4-e4d9-7f72-0376-b781a19bb9d2'
|
||||
'OpaqueRef:06f53e3d-d8de-d4ed-6359-9e20b4fb0d21'
|
||||
]
|
||||
|
||||
$expect(host.messages).to.have.members [
|
||||
'OpaqueRef:cb515b9a-ef8c-13d4-88ea-e0d3ee88d22a'
|
||||
'OpaqueRef:6ba7c244-3b44-2ed2-ec81-4fa13ea82465'
|
||||
'OpaqueRef:0e3fc97f-45ce-26c3-9435-899be96b35c4'
|
||||
'OpaqueRef:6ca16f45-6266-6cff-55cd-19a8ef0acf1a'
|
||||
'OpaqueRef:11452a2a-1ccd-e4df-25d8-ba99bba710db'
|
||||
'OpaqueRef:9ddc8eb2-969f-ba56-757a-efd482da5ce9'
|
||||
'OpaqueRef:68c8d0c6-e5a2-8ade-569a-dfc732e7994d'
|
||||
'OpaqueRef:ddb628ca-24f1-04d2-0b2c-9996aaab59f2'
|
||||
'OpaqueRef:0e7044a7-542b-4dd9-65bc-cded0e41853a'
|
||||
'OpaqueRef:ee26daf0-2ff7-734e-438d-9a521aaaa0c5'
|
||||
'OpaqueRef:40f8459f-1b6b-1625-1284-0f2878c3203d'
|
||||
'OpaqueRef:739ca434-6dca-b633-0097-b3f3183150a7'
|
||||
'OpaqueRef:cf655e45-c8c7-bdb9-e56c-5b67d6952f15'
|
||||
'OpaqueRef:3e33b140-f7e8-7dcc-3475-97dcc2fbfb5b'
|
||||
'OpaqueRef:8f3e2923-e690-e859-4f9e-a3e711a1e230'
|
||||
'OpaqueRef:ed7b1960-1ab7-4f47-8ef1-7a7769e09207'
|
||||
'OpaqueRef:6a0c4183-2f95-661f-9b19-0df0015867ca'
|
||||
'OpaqueRef:8d04b3fa-e81d-c6ae-d072-bd3a1ea22189'
|
||||
'OpaqueRef:dada1bd4-d7ed-429f-0a1a-585a3bfbf7e6'
|
||||
'OpaqueRef:a5648ca1-b37a-0765-9192-ebfb9ff376e8'
|
||||
'OpaqueRef:78c09b42-ad6f-0e66-0349-80b45264120d'
|
||||
'OpaqueRef:9c657a2b-560c-2050-014a-20e8cf5bd235'
|
||||
'OpaqueRef:1d50d25b-41f6-ffd3-5410-0de4fbed8543'
|
||||
'OpaqueRef:cb515b9a-ef8c-13d4-88ea-e0d3ee88d22a'
|
||||
'OpaqueRef:6ba7c244-3b44-2ed2-ec81-4fa13ea82465'
|
||||
'OpaqueRef:0e3fc97f-45ce-26c3-9435-899be96b35c4'
|
||||
'OpaqueRef:6ca16f45-6266-6cff-55cd-19a8ef0acf1a'
|
||||
'OpaqueRef:11452a2a-1ccd-e4df-25d8-ba99bba710db'
|
||||
'OpaqueRef:9ddc8eb2-969f-ba56-757a-efd482da5ce9'
|
||||
'OpaqueRef:68c8d0c6-e5a2-8ade-569a-dfc732e7994d'
|
||||
'OpaqueRef:ddb628ca-24f1-04d2-0b2c-9996aaab59f2'
|
||||
'OpaqueRef:0e7044a7-542b-4dd9-65bc-cded0e41853a'
|
||||
'OpaqueRef:ee26daf0-2ff7-734e-438d-9a521aaaa0c5'
|
||||
'OpaqueRef:40f8459f-1b6b-1625-1284-0f2878c3203d'
|
||||
'OpaqueRef:739ca434-6dca-b633-0097-b3f3183150a7'
|
||||
'OpaqueRef:cf655e45-c8c7-bdb9-e56c-5b67d6952f15'
|
||||
'OpaqueRef:3e33b140-f7e8-7dcc-3475-97dcc2fbfb5b'
|
||||
'OpaqueRef:8f3e2923-e690-e859-4f9e-a3e711a1e230'
|
||||
'OpaqueRef:ed7b1960-1ab7-4f47-8ef1-7a7769e09207'
|
||||
'OpaqueRef:6a0c4183-2f95-661f-9b19-0df0015867ca'
|
||||
'OpaqueRef:8d04b3fa-e81d-c6ae-d072-bd3a1ea22189'
|
||||
'OpaqueRef:dada1bd4-d7ed-429f-0a1a-585a3bfbf7e6'
|
||||
'OpaqueRef:a5648ca1-b37a-0765-9192-ebfb9ff376e8'
|
||||
'OpaqueRef:78c09b42-ad6f-0e66-0349-80b45264120d'
|
||||
'OpaqueRef:9c657a2b-560c-2050-014a-20e8cf5bd235'
|
||||
'OpaqueRef:1d50d25b-41f6-ffd3-5410-0de4fbed8543'
|
||||
]
|
||||
|
||||
$expect(host.tasks).to.have.members [
|
||||
# TODO
|
||||
]
|
||||
|
||||
$expect(host.$running_VMs).to.have.members [
|
||||
'OpaqueRef:fdaba312-c3a5-0190-b1a1-bf389567e620'
|
||||
'OpaqueRef:46fa4c52-5e93-6cf7-32e3-c51fb4ed106d'
|
||||
]
|
||||
|
||||
$expect(host.$vCPUs).to.equal 2
|
||||
|
||||
it 'VM', ->
|
||||
vm = collection.get 'OpaqueRef:fdaba312-c3a5-0190-b1a1-bf389567e620'
|
||||
|
||||
#console.log vm
|
||||
|
||||
$expect(vm).to.be.an 'object'
|
||||
|
||||
$expect(vm.type).to.equal 'VM'
|
||||
|
||||
$expect(vm.name_label).to.equal 'ceph3'
|
||||
|
||||
$expect(vm.name_description).to.equal ''
|
||||
|
||||
$expect(vm.tags).to.have.members []
|
||||
|
||||
$expect(vm.addresses).to.deep.equal {
|
||||
'0/ip': '192.168.1.116'
|
||||
'0/ipv6/0': 'fe80::cc20:2bff:fe38:7ffd'
|
||||
}
|
||||
|
||||
$expect(vm.consoles).to.deep.equal [
|
||||
{
|
||||
uuid: 'b7f85b67-4b8a-0586-b279-6146da76642f'
|
||||
protocol: 'rfb'
|
||||
location: 'https://192.168.1.1/console?uuid=b7f85b67-4b8a-0586-b279-6146da76642f'
|
||||
VM: 'OpaqueRef:fdaba312-c3a5-0190-b1a1-bf389567e620'
|
||||
other_config: {}
|
||||
'$pool': '313624ab-0958-bb1e-45b5-7556a463a10b'
|
||||
'$poolRef': 'OpaqueRef:6462d0b3-8f20-ef76-fddf-002f7af3452e'
|
||||
'$ref': 'OpaqueRef:69b8dbde-161c-b3fa-bd1a-3567e7efdbda'
|
||||
'$type': 'console'
|
||||
}
|
||||
]
|
||||
|
||||
$expect(vm.current_operations).to.deep.equal {
|
||||
# No data for this test.
|
||||
}
|
||||
|
||||
$expect(vm.memory).to.deep.equal {
|
||||
dynamic: [
|
||||
536870912
|
||||
536870912
|
||||
]
|
||||
static: [
|
||||
134217728
|
||||
536870912
|
||||
]
|
||||
size: 536838144
|
||||
}
|
||||
|
||||
$expect(vm.messages).to.have.members [
|
||||
'OpaqueRef:a242799a-03bf-b55e-ecde-ddfe902fa69e'
|
||||
'OpaqueRef:5cec485b-e276-c45b-09cb-dd02bb1d00f3'
|
||||
'OpaqueRef:ff3b6df1-b761-0d75-e80e-4ef137eec9e6'
|
||||
'OpaqueRef:a8d94d7e-7a6e-0cc1-b7a0-8f18940410fd'
|
||||
'OpaqueRef:35585a79-caf7-6522-18ee-8d3e8459441d'
|
||||
'OpaqueRef:68d1102f-eadc-e1f3-7949-3f62248c165c'
|
||||
'OpaqueRef:974bef10-184a-c063-aa32-c318fd39e400'
|
||||
'OpaqueRef:e092c4e1-a211-204a-f773-49cc3a4611be'
|
||||
'OpaqueRef:013a4a12-1981-fbc8-92ac-1fa45d2e9c9c'
|
||||
'OpaqueRef:a77fc714-b5b1-0c37-d006-0935506bb8cd'
|
||||
'OpaqueRef:554ec983-e67a-fc8b-7d2a-00c55be5f266'
|
||||
'OpaqueRef:38404a18-4c1b-0bf5-1d45-c47243bbc69d'
|
||||
'OpaqueRef:0f98e883-a4d5-0fd8-3aa3-92be69adc4e3'
|
||||
'OpaqueRef:b3e9ac53-f6b8-4c49-f096-57f680136477'
|
||||
'OpaqueRef:1aa65d64-a00b-4c0b-be07-95f6eec7fd87'
|
||||
'OpaqueRef:be431f8c-f39b-4a64-5fc2-de9744ced26a'
|
||||
'OpaqueRef:0e571611-6194-6ce6-bae0-94bbe57576c6'
|
||||
'OpaqueRef:114fdd8a-844c-6bb5-0855-e3427bc8f073'
|
||||
'OpaqueRef:a486606c-1c75-e1c3-56de-c6e1bc3df980'
|
||||
'OpaqueRef:b6975094-843e-a19a-6101-ee7953e40580'
|
||||
'OpaqueRef:f15d7d4c-32d1-45e1-5f6f-ddc68733bab6'
|
||||
'OpaqueRef:1b04b1a2-e8b2-df82-6618-0d0a741d8bbb'
|
||||
'OpaqueRef:dcd41e75-47fc-5ae5-1d59-5176a7b76eaa'
|
||||
'OpaqueRef:71ed5eba-33c9-6deb-6dc2-ab670a6c968b'
|
||||
'OpaqueRef:59ee665c-9270-64a4-3829-aef3e045a705'
|
||||
'OpaqueRef:88979f4b-16ef-3b99-a616-aa1e2787bebe'
|
||||
'OpaqueRef:80a3e419-5a81-a7df-103d-5cf60bbde793'
|
||||
'OpaqueRef:38737284-e4e1-5172-2bf3-f9d70dcaadfa'
|
||||
'OpaqueRef:456d4d7f-77f8-ef40-aadd-f56601bc7c2b'
|
||||
'OpaqueRef:4a949518-cc01-a003-f386-b3319db6d7a6'
|
||||
'OpaqueRef:c8834c52-f15b-437d-1e09-958fedbf3c5b'
|
||||
'OpaqueRef:07d40d2c-4f6e-4f5f-0c3e-c2ea028d4fc4'
|
||||
'OpaqueRef:6df45555-1b11-2873-8947-2b6e7c9445be'
|
||||
'OpaqueRef:d3c60e69-2cf8-191f-9679-d6ae0ecdf5f9'
|
||||
'OpaqueRef:ed499671-2c01-3dc9-f6cd-553fef4b6716'
|
||||
]
|
||||
|
||||
$expect(vm.power_state).to.equal 'Running'
|
||||
|
||||
$expect(vm.CPUs).to.deep.equal {
|
||||
max: 1
|
||||
number: 1
|
||||
}
|
||||
|
||||
$expect(vm.$CPU_usage).to.be.null
|
||||
|
||||
$expect(vm.$container).to.equal 'OpaqueRef:bbc98f5e-1a17-2030-28af-0df2393f3145'
|
||||
|
||||
$expect(vm.snapshots).to.have.members []
|
||||
|
||||
$expect(vm.snapshot_time).to.equal null
|
||||
|
||||
$expect(vm.$VBDs).to.have.members [
|
||||
'OpaqueRef:dbb53525-e1a3-741b-4924-9944b845bc0c'
|
||||
'OpaqueRef:1bd20244-01a0-fec3-eb00-79a453a56446'
|
||||
]
|
||||
|
||||
$expect(vm.VIFs).to.have.members [
|
||||
'OpaqueRef:20349ad5-0a0d-4b80-dcc0-0037fa647182'
|
||||
]
|
||||
|
||||
it 'VM-template', ->
|
||||
vm = collection.get 'OpaqueRef:f02a3c19-447b-c618-fb51-a9cde79be17c'
|
||||
|
||||
#console.log vm
|
||||
|
||||
# Only specific VM-templates fields will be tested.
|
||||
|
||||
$expect(vm.type).to.equal 'VM-template'
|
||||
|
||||
$expect(vm.template_info).to.be.an 'object'
|
||||
|
||||
$expect(vm.template_info.arch).to.equal 'amd64'
|
||||
|
||||
$expect(vm.template_info.disks).to.deep.equal [
|
||||
{
|
||||
bootable: true
|
||||
device: '0'
|
||||
size: 8589934592
|
||||
SR: ''
|
||||
type: 'system'
|
||||
}
|
||||
]
|
||||
|
||||
$expect(vm.template_info.install_methods).to.have.members [
|
||||
'cdrom'
|
||||
'http'
|
||||
'ftp'
|
||||
]
|
||||
|
||||
it 'SR', ->
|
||||
sr = collection.get 'OpaqueRef:d6fe49bf-dd48-c929-5aab-b2786a2e7aee'
|
||||
|
||||
#console.log sr
|
||||
|
||||
$expect(sr).to.be.an 'object'
|
||||
|
||||
$expect(sr.type).to.equal 'SR'
|
||||
|
||||
$expect(sr.name_label).to.equal 'Zfs'
|
||||
|
||||
$expect(sr.name_description).to.equal 'iSCSI SR [192.168.0.100 (iqn.1986-03.com.sun:02:ba2ab54c-2d14-eb74-d6f9-ef7c4f28ff1e; LUN 0: A83BCKLAF: 2048 GB (NEXENTA))]'
|
||||
|
||||
$expect(sr.SR_type).to.equal 'lvmoiscsi'
|
||||
|
||||
$expect(sr.content_type).to.equal ''
|
||||
|
||||
$expect(sr.physical_usage).to.equal 205831274496
|
||||
|
||||
$expect(sr.usage).to.equal 202358390784
|
||||
|
||||
$expect(sr.size).to.equal 2199010672640
|
||||
|
||||
$expect(sr.$container).to.equal 'OpaqueRef:6462d0b3-8f20-ef76-fddf-002f7af3452e'
|
||||
|
||||
$expect(sr.$PBDs).to.have.members [
|
||||
'OpaqueRef:ff32de74-138c-9d80-ab58-c631d2aa0e71'
|
||||
'OpaqueRef:200674ae-d9ab-2caa-a283-4fa3d14592fd'
|
||||
]
|
||||
|
||||
$expect(sr.VDIs).to.have.members [
|
||||
'OpaqueRef:b4a1573f-c235-8acd-4625-dfbcb2beb523'
|
||||
'OpaqueRef:098a2155-605b-241e-f775-a05c2133874e'
|
||||
'OpaqueRef:f7d900f9-a4fe-9a3e-ead8-28db301d26e8'
|
||||
'OpaqueRef:f26d2af5-b529-4d16-21d1-a56965e7bfb1'
|
||||
'OpaqueRef:ec5ce10e-023e-9a9f-eef7-a64e4c6d7b28'
|
||||
'OpaqueRef:e0eb5eb1-a485-fcfc-071e-fafa17f9ac48'
|
||||
'OpaqueRef:c4aa5d87-4115-c359-9cdf-c16fbf56cf2c'
|
||||
'OpaqueRef:b06a9d3f-5132-e58f-25c4-ef94d5b38986'
|
||||
'OpaqueRef:a4dd8a73-5393-81ce-abce-fc1502490a6d'
|
||||
'OpaqueRef:83331526-8bd8-9644-0a7d-9f645f5fcd70'
|
||||
'OpaqueRef:693bef17-aa19-63f8-3775-7d3b2dbce9d6'
|
||||
'OpaqueRef:67618138-57df-e90a-74c6-402ad62d657b'
|
||||
'OpaqueRef:5f1d5117-1033-b12a-92a8-99f206c9dbba'
|
||||
'OpaqueRef:287084c1-241a-58df-929a-cbe2e7454a56'
|
||||
'OpaqueRef:1f7f9828-f4e7-41dd-20e6-3bf57c559a78'
|
||||
]
|
||||
|
||||
$expect(sr.messages).to.have.members [
|
||||
# No data for this test.
|
||||
]
|
||||
|
||||
it 'PBD', ->
|
||||
pbd = collection.get 'OpaqueRef:ff32de74-138c-9d80-ab58-c631d2aa0e71'
|
||||
|
||||
#console.log pbd
|
||||
|
||||
$expect(pbd).to.an 'object'
|
||||
|
||||
$expect(pbd.type).to.equal 'PBD'
|
||||
|
||||
$expect(pbd.attached).to.be.true
|
||||
|
||||
$expect(pbd.host).to.equal 'OpaqueRef:bbc98f5e-1a17-2030-28af-0df2393f3145'
|
||||
|
||||
$expect(pbd.SR).to.equal 'OpaqueRef:d6fe49bf-dd48-c929-5aab-b2786a2e7aee'
|
||||
|
||||
it 'PIF', ->
|
||||
pif = collection.get 'OpaqueRef:aef57ed4-e4d9-7f72-0376-b781a19bb9d2'
|
||||
|
||||
#console.log pif
|
||||
|
||||
$expect(pif).to.an 'object'
|
||||
|
||||
$expect(pif.type).to.equal 'PIF'
|
||||
|
||||
$expect(pif.attached).to.be.true
|
||||
|
||||
$expect(pif.device).to.equal 'eth0'
|
||||
|
||||
$expect(pif.IP).to.equal '192.168.1.1'
|
||||
|
||||
$expect(pif.$host).to.equal 'OpaqueRef:bbc98f5e-1a17-2030-28af-0df2393f3145'
|
||||
|
||||
$expect(pif.MAC).to.equal '90:2b:34:d3:ce:75'
|
||||
|
||||
$expect(pif.management).to.be.true
|
||||
|
||||
$expect(pif.mode).to.equal 'Static'
|
||||
|
||||
$expect(pif.MTU).to.equal 1500
|
||||
|
||||
$expect(pif.netmask).to.equal '255.255.255.0'
|
||||
|
||||
$expect(pif.$network).to.equal 'OpaqueRef:dbc93777-f2c0-e888-967d-dd9beeffb3c0'
|
||||
|
||||
it 'VDI', ->
|
||||
vdi = collection.get 'OpaqueRef:1f7f9828-f4e7-41dd-20e6-3bf57c559a78'
|
||||
|
||||
#console.log vdi
|
||||
|
||||
$expect(vdi).to.an 'object'
|
||||
|
||||
$expect(vdi.type).to.equal 'VDI'
|
||||
|
||||
$expect(vdi.name_label).to.equal 'ceph'
|
||||
|
||||
$expect(vdi.name_description).to.equal ''
|
||||
|
||||
$expect(vdi.usage).to.equal 21525168128
|
||||
|
||||
$expect(vdi.size).to.equal 21474836480
|
||||
|
||||
$expect(vdi.$snapshot_of).to.equal null
|
||||
|
||||
$expect(vdi.snapshots).to.have.members [
|
||||
'OpaqueRef:b4a1573f-c235-8acd-4625-dfbcb2beb523'
|
||||
]
|
||||
|
||||
$expect(vdi.$SR).to.equal 'OpaqueRef:d6fe49bf-dd48-c929-5aab-b2786a2e7aee'
|
||||
|
||||
$expect(vdi.$VBDs).to.have.members [
|
||||
'OpaqueRef:9f15200b-3cac-7a61-b3e8-dd2fc0a5572d'
|
||||
]
|
||||
|
||||
it 'VBD', ->
|
||||
vbd = collection.get 'OpaqueRef:9f15200b-3cac-7a61-b3e8-dd2fc0a5572d'
|
||||
|
||||
#console.log vbd
|
||||
|
||||
$expect(vbd).to.an 'object'
|
||||
|
||||
$expect(vbd.type).to.equal 'VBD'
|
||||
|
||||
$expect(vbd.attached).to.be.true
|
||||
|
||||
$expect(vbd.bootable).to.be.false
|
||||
|
||||
$expect(vbd.is_cd_drive).to.be.false
|
||||
|
||||
$expect(vbd.read_only).to.be.false
|
||||
|
||||
$expect(vbd.VDI).to.equal 'OpaqueRef:1f7f9828-f4e7-41dd-20e6-3bf57c559a78'
|
||||
|
||||
$expect(vbd.VM).to.equal 'OpaqueRef:be2390b2-cd08-53f5-3fae-b76f6f3725bf'
|
||||
|
||||
it 'VIF', ->
|
||||
vif = collection.get 'OpaqueRef:20349ad5-0a0d-4b80-dcc0-0037fa647182'
|
||||
|
||||
#console.log vif
|
||||
|
||||
$expect(vif).to.an 'object'
|
||||
|
||||
$expect(vif.type).to.equal 'VIF'
|
||||
|
||||
$expect(vif.attached).to.be.true
|
||||
|
||||
$expect(vif.device).to.equal '0'
|
||||
|
||||
$expect(vif.MAC).to.equal 'ce:20:2b:38:7f:fd'
|
||||
|
||||
$expect(vif.MTU).to.equal 1500
|
||||
|
||||
$expect(vif.$network).to.equal 'OpaqueRef:dbc93777-f2c0-e888-967d-dd9beeffb3c0'
|
||||
|
||||
$expect(vif.$VM).to.equal 'OpaqueRef:fdaba312-c3a5-0190-b1a1-bf389567e620'
|
||||
|
||||
it 'network', ->
|
||||
network = collection.get 'OpaqueRef:dbc93777-f2c0-e888-967d-dd9beeffb3c0'
|
||||
|
||||
#console.log network
|
||||
|
||||
$expect(network).to.be.an 'object'
|
||||
|
||||
$expect(network.type).to.equal 'network'
|
||||
|
||||
$expect(network.name_label).to.equal 'Pool-wide network associated with eth0'
|
||||
|
||||
$expect(network.name_description).to.equal ''
|
||||
|
||||
$expect(network.bridge).to.equal 'xenbr0'
|
||||
|
||||
$expect(network.MTU).to.equal 1500
|
||||
|
||||
$expect(network.PIFs).to.have.members [
|
||||
'OpaqueRef:aef57ed4-e4d9-7f72-0376-b781a19bb9d2'
|
||||
'OpaqueRef:971d6bc5-60f4-a331-bdee-444ee7cbf678'
|
||||
]
|
||||
|
||||
$expect(network.VIFs).to.have.members [
|
||||
'OpaqueRef:fc86d17e-d9d1-5534-69d6-d15edbe36d22'
|
||||
'OpaqueRef:ed2d89ca-1f4e-09ff-f80e-991d6b01de45'
|
||||
'OpaqueRef:c6651d03-cefe-accf-920b-636e32fee23c'
|
||||
'OpaqueRef:c5977d9b-cb50-a615-8488-1dd105d69802'
|
||||
'OpaqueRef:c391575b-168f-e52b-59f7-9f852a2c6854'
|
||||
'OpaqueRef:bf4da755-480b-e3fd-2bfe-f53e7204c8ae'
|
||||
'OpaqueRef:ba41d1a6-724e-aae8-3447-20f74014eb75'
|
||||
'OpaqueRef:b8df4453-542e-6c14-0eb1-174d48373bca'
|
||||
'OpaqueRef:b5980de3-1a74-9f57-1e98-2a74184211dc'
|
||||
'OpaqueRef:aaae3669-faee-4338-3156-0ce8c06c75cf'
|
||||
'OpaqueRef:aa874254-b67c-e9e3-6a08-1c770c2dd8ac'
|
||||
'OpaqueRef:7b8ecb18-5bc5-7650-3ac4-6bc22322e8ba'
|
||||
'OpaqueRef:59b884b0-521f-7b3e-6a91-319ded893e68'
|
||||
'OpaqueRef:20349ad5-0a0d-4b80-dcc0-0037fa647182'
|
||||
]
|
||||
|
||||
it 'message', ->
|
||||
message = collection.get 'OpaqueRef:cb515b9a-ef8c-13d4-88ea-e0d3ee88d22a'
|
||||
|
||||
#console.log message
|
||||
|
||||
$expect(message.type).to.equal 'message'
|
||||
|
||||
$expect(message.time).to.equal 1389449056
|
||||
|
||||
$expect(message.$object).to.equal 'OpaqueRef:bbc98f5e-1a17-2030-28af-0df2393f3145'
|
||||
|
||||
$expect(message.name).to.equal 'PBD_PLUG_FAILED_ON_SERVER_START'
|
||||
|
||||
$expect(message.body).to.equal ''
|
||||
|
||||
it 'task', ->
|
||||
all = collection.get()
|
||||
|
||||
for object in all
|
||||
if object.type is 'task'
|
||||
console.log object
|
||||
|
||||
# FIXME: we need to update the tests data to complete this test.
|
493
src/xapi-objects-to-xo.js
Normal file
493
src/xapi-objects-to-xo.js
Normal file
@ -0,0 +1,493 @@
|
||||
import forEach from 'lodash.foreach'
|
||||
import isArray from 'lodash.isarray'
|
||||
import map from 'lodash.map'
|
||||
|
||||
import {
|
||||
ensureArray,
|
||||
extractProperty,
|
||||
parseXml
|
||||
} from './utils'
|
||||
import {
|
||||
isHostRunning,
|
||||
isVmRunning
|
||||
} from './xapi'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
function link (obj, prop) {
|
||||
const dynamicValue = obj[`$${prop}`]
|
||||
if (dynamicValue == null) {
|
||||
return dynamicValue // Properly handles null and undefined.
|
||||
}
|
||||
|
||||
if (isArray(dynamicValue)) {
|
||||
return map(dynamicValue, '$id')
|
||||
}
|
||||
|
||||
return dynamicValue.$id
|
||||
}
|
||||
|
||||
function toTimestamp (date) {
|
||||
// Weird behavior from the XAPI.
|
||||
if (!date || date === '1969-12-31T23:00:00.000Z') {
|
||||
return null
|
||||
}
|
||||
|
||||
return Math.round(Date.parse(date) / 1000)
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export function pool (obj) {
|
||||
return {
|
||||
default_SR: link(obj, 'default_SR'),
|
||||
HA_enabled: obj.ha_enabled,
|
||||
master: link(obj, 'master'),
|
||||
name_description: obj.name_description,
|
||||
name_label: obj.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: 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'),
|
||||
power_state: isRunning ? 'Running' : 'Halted',
|
||||
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 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).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) {
|
||||
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,
|
||||
PV_drivers: Boolean(guestMetrics && guestMetrics.PV_drivers_up_to_date),
|
||||
snapshot_time: toTimestamp(obj.snapshot_time),
|
||||
snapshots: link(obj, 'snapshots'),
|
||||
VIFs: link(obj, 'VIFs'),
|
||||
|
||||
$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 {xml} = otherConfig
|
||||
if (!xml) {
|
||||
return []
|
||||
}
|
||||
|
||||
const disks = ensureArray(parseXml(xml).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(',') : []
|
||||
})()
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
usage: +obj.virtual_allocation,
|
||||
VDI: link(obj, 'VDIs'),
|
||||
|
||||
$container: (
|
||||
obj.shared ?
|
||||
link(obj, 'pool') :
|
||||
|
||||
// FIXME: find host for which host.PBDs includes obj.$ref
|
||||
null
|
||||
),
|
||||
$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: obj.currently_attached,
|
||||
device: obj.device,
|
||||
IP: obj.IP,
|
||||
MAC: obj.MAC,
|
||||
management: 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) {
|
||||
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),
|
||||
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: obj.currently_attached,
|
||||
bootable: 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: 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,
|
||||
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: obj.applied,
|
||||
time: toTimestamp(obj.timestamp_applied),
|
||||
pool_patch: link(obj, 'pool_patch'),
|
||||
|
||||
$host: link(obj, 'host')
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
export function pool_patch (obj) {
|
||||
return {
|
||||
applied: obj.pool_applied,
|
||||
name_description: obj.name_description,
|
||||
name_label: obj.name_label,
|
||||
size: +obj.size,
|
||||
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: obj.currently_attached,
|
||||
device: obj.device,
|
||||
resident_on: link(obj, 'resident_on'),
|
||||
vm: link(obj, 'VM')
|
||||
}
|
||||
}
|
92
src/xapi.js
92
src/xapi.js
@ -61,11 +61,17 @@ const getNamespaceForType = (type) => typeToNamespace[type] || type
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export const isHostRunning = (host) => {
|
||||
const {$metrics: metrics} = host
|
||||
|
||||
return metrics && metrics.live
|
||||
}
|
||||
|
||||
const VM_RUNNING_POWER_STATES = {
|
||||
Running: true,
|
||||
Paused: true
|
||||
}
|
||||
const isVmRunning = (vm) => VM_RUNNING_POWER_STATES[vm.power_state]
|
||||
export const isVmRunning = (vm) => VM_RUNNING_POWER_STATES[vm.power_state]
|
||||
|
||||
// ===================================================================
|
||||
|
||||
@ -76,23 +82,13 @@ export default class Xapi extends XapiBase {
|
||||
const objectsWatchers = this._objectWatchers = Object.create(null)
|
||||
const taskWatchers = this._taskWatchers = Object.create(null)
|
||||
|
||||
// TODO: This is necessary to get UUIDs for host.patches.
|
||||
//
|
||||
// It will no longer be useful when using xen-api >= 0.5.
|
||||
this._refsToUuids = Object.create(null)
|
||||
|
||||
const onAddOrUpdate = objects => {
|
||||
forEach(objects, object => {
|
||||
const {
|
||||
$id: id,
|
||||
$ref: ref,
|
||||
uuid
|
||||
$ref: ref
|
||||
} = object
|
||||
|
||||
if (ref && uuid) {
|
||||
this._refsToUuids[ref] = uuid
|
||||
}
|
||||
|
||||
// Watched object.
|
||||
if (id in objectsWatchers) {
|
||||
objectsWatchers[id].resolve(object)
|
||||
@ -123,56 +119,6 @@ export default class Xapi extends XapiBase {
|
||||
this.objects.on('update', onAddOrUpdate)
|
||||
}
|
||||
|
||||
// FIXME: remove this backported methods when xen-api >= 0.5
|
||||
getObject (idOrUuidOrRef, defaultValue) {
|
||||
const {_objects: {all: objects}} = this
|
||||
const object = (
|
||||
// if there is an UUID, it is also the $id.
|
||||
objects[idOrUuidOrRef] ||
|
||||
objects[this._refsToUuids[idOrUuidOrRef]]
|
||||
)
|
||||
|
||||
if (object) return object
|
||||
|
||||
if (arguments.length > 1) return defaultValue
|
||||
|
||||
throw new Error('no object can be matched to ' + idOrUuidOrRef)
|
||||
}
|
||||
getObjectByRef (ref, defaultValue) {
|
||||
const {
|
||||
_refsToUuids: refsToUuids,
|
||||
|
||||
// Objects ids are already UUIDs if they have one.
|
||||
_objects: {all: objectsByUuids}
|
||||
} = this
|
||||
|
||||
if (ref in refsToUuids) {
|
||||
return objectsByUuids[refsToUuids[ref]]
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
throw new Error('there is no object with the ref ' + ref)
|
||||
}
|
||||
getObjectByUuid (uuid, defaultValue) {
|
||||
const {
|
||||
// Objects ids are already UUIDs if they have one.
|
||||
_objects: {all: objectsByUuids}
|
||||
} = this
|
||||
|
||||
if (uuid in objectsByUuids) {
|
||||
return objectsByUuids[uuid]
|
||||
}
|
||||
|
||||
if (arguments.length > 1) {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
throw new Error('there is no object with the UUID ' + uuid)
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
||||
// Wait for an object to appear or to be updated.
|
||||
@ -366,10 +312,8 @@ export default class Xapi extends XapiBase {
|
||||
const all = (await this._getXenUpdates()).versions[version].patches
|
||||
|
||||
const installed = Object.create(null)
|
||||
// TODO: simplify when we start to use xen-api >= 0.5
|
||||
forEach(host.patches, ref => {
|
||||
const hostPatch = this.objects.all[this._refsToUuids[ref]]
|
||||
installed[this._refsToUuids[hostPatch.pool_patch]] = true
|
||||
forEach(host.$patches, hostPatch => {
|
||||
installed[hostPatch.$pool_patch.uuid] = true
|
||||
})
|
||||
|
||||
const installable = []
|
||||
@ -395,11 +339,8 @@ export default class Xapi extends XapiBase {
|
||||
async uploadPoolPatch (stream, length) {
|
||||
const task = await this._createTask('Patch upload')
|
||||
|
||||
// TODO: Update when xen-api >= 0.5
|
||||
const poolMaster = this.objects.all[this._refsToUuids[this.pool.master]]
|
||||
|
||||
const [, patchRef] = await Promise.all([
|
||||
gotPromise('http://' + poolMaster.address + '/pool_patch_upload', {
|
||||
gotPromise('http://' + this.pool.$master.address + '/pool_patch_upload', {
|
||||
method: 'put',
|
||||
body: stream,
|
||||
query: {
|
||||
@ -489,10 +430,9 @@ export default class Xapi extends XapiBase {
|
||||
}
|
||||
|
||||
if (deleteDisks) {
|
||||
// TODO: simplify when we start to use xen-api >= 0.5
|
||||
await Promise.all(map(vm.VBDs, ref => {
|
||||
await Promise.all(map(vm.$VBDs, vbd => {
|
||||
try {
|
||||
return this._deleteVdi(this.getObject(ref).VDI).catch(noop)
|
||||
return this._deleteVdi(vbd.$VDI).catch(noop)
|
||||
} catch (_) {}
|
||||
}))
|
||||
}
|
||||
@ -507,10 +447,10 @@ export default class Xapi extends XapiBase {
|
||||
let host
|
||||
let snapshotRef
|
||||
if (isVmRunning(vm)) {
|
||||
host = this.getObject(vm.resident_on)
|
||||
host = vm.$resident_on
|
||||
snapshotRef = await this._snapshotVm(vm)
|
||||
} else {
|
||||
host = this.getObject(this.pool.master)
|
||||
host = this.pool.$master
|
||||
}
|
||||
|
||||
const task = await this._createTask('VM Snapshot', vm.name_label)
|
||||
@ -548,7 +488,7 @@ export default class Xapi extends XapiBase {
|
||||
|
||||
async _doDockerAction (vmId, action, containerId) {
|
||||
const vm = this.getObject(vmId)
|
||||
const host = this.getObject(vm.resident_on)
|
||||
const host = vm.$resident_on
|
||||
|
||||
return await this.call('host.call_plugin', host.$ref, 'xscontainer', action, {
|
||||
vmuuid: vm.uuid,
|
||||
|
279
src/xo.js
279
src/xo.js
@ -1,54 +1,50 @@
|
||||
import Bluebird from 'bluebird'
|
||||
import createDebug from 'debug'
|
||||
import forEach from 'lodash.foreach'
|
||||
import includes from 'lodash.includes'
|
||||
import isEmpty from 'lodash.isempty'
|
||||
import isString from 'lodash.isstring'
|
||||
import pluck from 'lodash.pluck'
|
||||
import proxyRequest from 'proxy-http-request'
|
||||
// import XoCollection from 'xo-collection'
|
||||
import XoCollection from 'xo-collection'
|
||||
import XoUniqueIndex from 'xo-collection/unique-index'
|
||||
// import XoView from 'xo-collection/view'
|
||||
import {createClient as createRedisClient} from 'then-redis'
|
||||
import {EventEmitter} from 'events'
|
||||
import {parse as parseUrl} from 'url'
|
||||
|
||||
import {ModelAlreadyExists} from './collection'
|
||||
import * as xapiObjectsToXo from './xapi-objects-to-xo'
|
||||
import Connection from './connection'
|
||||
import spec from './spec'
|
||||
import User, {Users} from './models/user'
|
||||
import Xapi from './xapi'
|
||||
import {$MappedCollection as MappedCollection} from './MappedCollection'
|
||||
import {Acls} from './models/acl'
|
||||
import {autobind} from './decorators'
|
||||
import {generateToken} from './utils'
|
||||
import {JsonRpcError, NoSuchObject, Unauthorized} from './api-errors'
|
||||
import {ModelAlreadyExists} from './collection'
|
||||
import {Servers} from './models/server'
|
||||
import {Tokens} from './models/token'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
const debug = createDebug('xo:xo')
|
||||
|
||||
// ===================================================================
|
||||
|
||||
class NoSuchAuthenticationToken extends NoSuchObject {
|
||||
constructor (id) {
|
||||
super({
|
||||
type: 'authentication token',
|
||||
id
|
||||
})
|
||||
super(id, 'authentication token')
|
||||
}
|
||||
}
|
||||
|
||||
class NoSuchUser extends NoSuchObject {
|
||||
constructor (id) {
|
||||
super({
|
||||
type: 'user',
|
||||
id
|
||||
})
|
||||
super(id, 'user')
|
||||
}
|
||||
}
|
||||
|
||||
class NoSuchXenServer extends NoSuchObject {
|
||||
constructor (id) {
|
||||
super({
|
||||
type: 'xen server',
|
||||
id
|
||||
})
|
||||
super(id, 'xen server')
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +66,8 @@ export default class Xo extends EventEmitter {
|
||||
constructor () {
|
||||
super()
|
||||
|
||||
// this._objects = new XoCollection()
|
||||
this._objects = new XoCollection()
|
||||
this._objects.createIndex('byRef', new XoUniqueIndex('ref'))
|
||||
|
||||
// These will be initialized in start()
|
||||
//
|
||||
@ -88,17 +85,14 @@ export default class Xo extends EventEmitter {
|
||||
this._nextConId = 0
|
||||
this._connections = Object.create(null)
|
||||
|
||||
// Collections of XAPI objects mapped to XO Api.
|
||||
this._xobjs = new MappedCollection()
|
||||
spec.call(this._xobjs)
|
||||
this._watchXobjs()
|
||||
|
||||
this._httpRequestWatchers = Object.create(null)
|
||||
|
||||
// TODO: remove when no longer necessary.
|
||||
this._proxyRequests = Object.create(null)
|
||||
|
||||
this._authenticationProviders = new Set()
|
||||
|
||||
this._watchObjects()
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
@ -146,9 +140,6 @@ export default class Xo extends EventEmitter {
|
||||
}
|
||||
}.bind(this))
|
||||
|
||||
// Exports the map from UUIDs to keys.
|
||||
this._UUIDsToKeys = this._xobjs.get('xo').$UUIDsToKeys
|
||||
|
||||
// Connects to existing servers.
|
||||
for (let server of await this._servers.get()) {
|
||||
this.connectXenServer(server.id).catch(error => {
|
||||
@ -299,6 +290,45 @@ export default class Xo extends EventEmitter {
|
||||
return server
|
||||
}
|
||||
|
||||
@autobind
|
||||
_onXenAdd (xapiObjects) {
|
||||
const {_objects: objects} = this
|
||||
forEach(xapiObjects, (xapiObject, id) => {
|
||||
const transform = xapiObjectsToXo[xapiObject.$type]
|
||||
if (!transform) {
|
||||
return
|
||||
}
|
||||
|
||||
const xoObject = transform(xapiObject)
|
||||
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
|
||||
}
|
||||
})
|
||||
|
||||
objects.set(id, xoObject)
|
||||
})
|
||||
}
|
||||
|
||||
@autobind
|
||||
_onXenRemove (xapiObjects) {
|
||||
const {_objects: objects} = this
|
||||
forEach(xapiObjects, (_, id) => {
|
||||
if (objects.has(id)) {
|
||||
objects.remove(id)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TODO the previous state should be marked as connected.
|
||||
async connectXenServer (id) {
|
||||
const server = (await this._getXenServer(id)).properties
|
||||
@ -312,29 +342,14 @@ export default class Xo extends EventEmitter {
|
||||
})
|
||||
|
||||
const {objects} = xapi
|
||||
objects.on('add', objects => {
|
||||
this._xapis[xapi.pool.$id] = xapi
|
||||
objects.on('add', this._onXenAdd)
|
||||
objects.on('update', this._onXenAdd)
|
||||
objects.on('remove', this._onXenRemove)
|
||||
|
||||
this._xobjs.set(objects, {
|
||||
add: true,
|
||||
update: false,
|
||||
remove: false
|
||||
})
|
||||
})
|
||||
objects.on('update', objects => {
|
||||
// Each time objects are refreshed, registers the connection with
|
||||
// the pool identifier.
|
||||
objects.on('finish', () => {
|
||||
this._xapis[xapi.pool.$id] = xapi
|
||||
|
||||
this._xobjs.set(objects, {
|
||||
add: true,
|
||||
update: true,
|
||||
remove: false
|
||||
})
|
||||
})
|
||||
objects.on('remove', objects => {
|
||||
this._xobjs.removeWithPredicate(object => (
|
||||
object.genval &&
|
||||
object.genval.$id in objects
|
||||
))
|
||||
})
|
||||
|
||||
try {
|
||||
@ -390,40 +405,50 @@ export default class Xo extends EventEmitter {
|
||||
//
|
||||
// TODO: should throw a NoSuchObject error on failure.
|
||||
getObject (key, type) {
|
||||
// Gracefully handles UUIDs.
|
||||
if (key in this._UUIDsToKeys) {
|
||||
key = this._UUIDsToKeys[key]
|
||||
}
|
||||
const {
|
||||
all,
|
||||
indexes: {
|
||||
byRef
|
||||
}
|
||||
} = this._objects
|
||||
|
||||
const obj = this._xobjs.get(key)
|
||||
const obj = all[key] || byRef[key]
|
||||
if (!obj) {
|
||||
throw new NoSuchObject(key)
|
||||
}
|
||||
|
||||
if (type != null && (
|
||||
isString(type) && type !== obj.type ||
|
||||
!includes(type, obj.type) // Array
|
||||
)) {
|
||||
throw new Error(`unexpected type ${obj.type} instead of ${type}`)
|
||||
throw new NoSuchObject(key, type)
|
||||
}
|
||||
|
||||
return obj
|
||||
}
|
||||
|
||||
getObjects (keys) {
|
||||
const {
|
||||
all,
|
||||
indexes: {
|
||||
byRef
|
||||
}
|
||||
} = this._objects
|
||||
|
||||
// Returns all objects if no keys have been passed.
|
||||
if (!keys) {
|
||||
return this._xobjs.get()
|
||||
}
|
||||
|
||||
// Resolves all UUIDs.
|
||||
const {_UUIDsToKeys: UUIDsToKeys} = this
|
||||
for (let i = 0, n = keys.length; i < n; ++i) {
|
||||
const key = UUIDsToKeys[keys[i]]
|
||||
if (key != null) {
|
||||
keys[i] = key
|
||||
}
|
||||
return all
|
||||
}
|
||||
|
||||
// Fetches all objects and ignores those missing.
|
||||
return this._xobjs.get(keys, true)
|
||||
const result = []
|
||||
forEach(keys, key => {
|
||||
const object = all[key] || byRef[key]
|
||||
if (object) {
|
||||
result.push(object)
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
@ -615,100 +640,56 @@ export default class Xo extends EventEmitter {
|
||||
_objects: objects
|
||||
} = this
|
||||
|
||||
const publicObjects = new XoView(objects, isObjectPublic)
|
||||
publicObjects.on('add', objects => {
|
||||
let entered, exited
|
||||
function reset () {
|
||||
entered = Object.create(null)
|
||||
exited = Object.create(null)
|
||||
}
|
||||
reset()
|
||||
|
||||
})
|
||||
publicObjects.on('update', objects => {
|
||||
|
||||
})
|
||||
publicObjects.on('remove', objects => {
|
||||
function onAdd (items) {
|
||||
forEach(items, (item, id) => {
|
||||
entered[id] = item
|
||||
})
|
||||
}
|
||||
objects.on('add', onAdd)
|
||||
objects.on('update', onAdd)
|
||||
|
||||
objects.on('remove', (items) => {
|
||||
forEach(items, (_, id) => {
|
||||
// We don't care about the value here, so we choose `0`
|
||||
// because it is small in JSON.
|
||||
exited[id] = 0
|
||||
})
|
||||
})
|
||||
|
||||
const persistentObjects = new XoView(objects, isObjectPersistent)
|
||||
persistentObjects.on('add', objects => {
|
||||
objects.on('finish', () => {
|
||||
const enteredMessage = !isEmpty(entered) && {
|
||||
type: 'enter',
|
||||
items: entered
|
||||
}
|
||||
const exitedMessage = !isEmpty(exited) && {
|
||||
type: 'exit',
|
||||
items: exited
|
||||
}
|
||||
|
||||
})
|
||||
persistentObjects.on('update', objects => {
|
||||
if (!enteredMessage && !exitedMessage) {
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
persistentObjects.on('remove', objects => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// When objects enter or exists, sends a notification to all
|
||||
// connected clients.
|
||||
//
|
||||
// TODO: remove when all objects are in `this._objects`.
|
||||
_watchXobjs () {
|
||||
const {
|
||||
_connections: connections,
|
||||
_xobjs: xobjs
|
||||
} = this
|
||||
|
||||
let entered = {}
|
||||
let exited = {}
|
||||
|
||||
let dispatcherRegistered = false
|
||||
const dispatcher = Bluebird.method(() => {
|
||||
dispatcherRegistered = false
|
||||
|
||||
if (!isEmpty(entered)) {
|
||||
const enterParams = {
|
||||
type: 'enter',
|
||||
items: pluck(entered, 'val')
|
||||
}
|
||||
entered = {}
|
||||
|
||||
for (let id in connections) {
|
||||
const connection = connections[id]
|
||||
|
||||
if (connection.has('user_id')) {
|
||||
connection.notify('all', enterParams)
|
||||
forEach(connections, connection => {
|
||||
// Notifies only authenticated clients.
|
||||
if (connection.has('user_id')) {
|
||||
if (enteredMessage) {
|
||||
connection.notify('all', enteredMessage)
|
||||
}
|
||||
if (exitedMessage) {
|
||||
connection.notify('all', exitedMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!isEmpty(exited)) {
|
||||
const exitParams = {
|
||||
type: 'exit',
|
||||
items: pluck(exited, 'val')
|
||||
}
|
||||
exited = {}
|
||||
|
||||
for (let id in connections) {
|
||||
const connection = connections[id]
|
||||
|
||||
if (connection.has('user_id')) {
|
||||
connection.notify('all', exitParams)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
xobjs.on('any', (event, items) => {
|
||||
if (!dispatcherRegistered) {
|
||||
dispatcherRegistered = true
|
||||
process.nextTick(dispatcher)
|
||||
}
|
||||
|
||||
if (event === 'exit') {
|
||||
forEach(items, item => {
|
||||
const {key} = item
|
||||
|
||||
delete entered[key]
|
||||
exited[key] = item
|
||||
})
|
||||
} else {
|
||||
forEach(items, item => {
|
||||
const {key} = item
|
||||
|
||||
delete exited[key]
|
||||
entered[key] = item
|
||||
})
|
||||
}
|
||||
reset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user