Merge branch 'spec-refactoring' into next-release
This commit is contained in:
commit
05c45e1b6b
21
package.json
21
package.json
@ -11,7 +11,7 @@
|
||||
"type": "git",
|
||||
"url": "git://github.com/vatesfr/xo-server.git"
|
||||
},
|
||||
"main": "src/main.js",
|
||||
"main": "src/main.coffee",
|
||||
"dependencies": {
|
||||
"coffee-script": "~1.6.3",
|
||||
"connect": "~2.11.2",
|
||||
@ -29,15 +29,30 @@
|
||||
"underscore": "~1.5.2",
|
||||
"validator": "~2.0.0",
|
||||
"ws": "~0.4.31",
|
||||
"xml2js": "~0.4.1",
|
||||
"xmlrpc": "~1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "~1.8.1",
|
||||
"glob": "~3.2.8",
|
||||
"mocha": "~1.14.0",
|
||||
"node-inspector": "~0.6.1"
|
||||
"mocha-as-promised": "~2.0.0",
|
||||
"node-inspector": "~0.6.1",
|
||||
"sinon": "~1.7.3"
|
||||
},
|
||||
"optionalDependencies": {},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"description": "XO-Server is part of Xen-Orchestra, a web interface for XenServer or XAPI enabled hosts.",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vatesfr/xo-server/issues"
|
||||
},
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./run-tests"
|
||||
},
|
||||
"license": "AGPL3"
|
||||
}
|
||||
|
32
run-tests
Executable file
32
run-tests
Executable file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env coffee
|
||||
|
||||
# Tests runner.
|
||||
$mocha = require 'mocha'
|
||||
|
||||
# Promises support for Mocha.
|
||||
(require 'mocha-as-promised')()
|
||||
|
||||
# Used to find the specification files.
|
||||
$glob = require 'glob'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
do ->
|
||||
# Instantiates the tests runner.
|
||||
mocha = new $mocha {
|
||||
reporter: 'spec'
|
||||
}
|
||||
|
||||
# Processes arguments.
|
||||
do ->
|
||||
{argv} = process
|
||||
i = 2
|
||||
n = argv.length
|
||||
mocha.grep argv[i++] while i < n
|
||||
|
||||
$glob 'src/**/*.spec.{coffee,js}', (error, files) ->
|
||||
console.error(error) if error
|
||||
|
||||
mocha.addFile file for file in files
|
||||
|
||||
mocha.run()
|
@ -1,387 +1,492 @@
|
||||
# Low level tools.
|
||||
{EventEmitter: $EventEmitter} = require 'events'
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
$_ = require 'underscore'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
class $DynamicProperty
|
||||
# TODO: move these helpers in a dedicated module.
|
||||
|
||||
constructor: (@value, @hooks) ->
|
||||
$done = {}
|
||||
|
||||
# Similar to `$_.each()` but can be interrupted by returning the
|
||||
# special value `done` provided as the forth argument.
|
||||
$each = (col, iterator, ctx) ->
|
||||
# The default context is inherited.
|
||||
ctx ?= this
|
||||
|
||||
if (n = col.length)?
|
||||
# Array-like object.
|
||||
i = 0
|
||||
while i < n and (iterator.call ctx, col[i], "#{i}", col, $done) isnt $done
|
||||
++i
|
||||
else
|
||||
for key of col
|
||||
break if (iterator.call ctx, col[key], key, $done) is $done
|
||||
|
||||
# For performance.
|
||||
undefined
|
||||
|
||||
$makeFunction = (val) -> -> val
|
||||
|
||||
# Similar to `$_.map()` for array and `$_.mapValues()` for objects.
|
||||
#
|
||||
# Note: can be interrupted by returning the special value `done`
|
||||
# provided as the forth argument.
|
||||
$map = (col, iterator, ctx) ->
|
||||
# The default context is inherited.
|
||||
ctx ?= this
|
||||
|
||||
if (n = col.length)?
|
||||
result = []
|
||||
# Array-like object.
|
||||
i = 0
|
||||
while i < n
|
||||
value = iterator.call ctx, col[i], "#{i}", col, $done
|
||||
break if value is $done
|
||||
result.push value
|
||||
++i
|
||||
else
|
||||
result = {}
|
||||
for key of col
|
||||
value = iterator.call ctx, col[key], key, $done
|
||||
break if value is $done
|
||||
result.push value
|
||||
|
||||
# The new collection is returned.
|
||||
result
|
||||
|
||||
# Similar to `$map()` but change the current collection.
|
||||
#
|
||||
# Note: can be interrupted by returning the special value `done`
|
||||
# provided as the forth argument.
|
||||
$mapInPlace = (col, iterator, ctx) ->
|
||||
# The default context is inherited.
|
||||
ctx ?= this
|
||||
|
||||
if (n = col.length)?
|
||||
# Array-like object.
|
||||
i = 0
|
||||
while i < n
|
||||
value = iterator.call ctx, col[i], "#{i}", col, $done
|
||||
break if value is $done
|
||||
col[i] = value
|
||||
++i
|
||||
else
|
||||
for key of col
|
||||
value = iterator.call ctx, col[key], key, $done
|
||||
break if value is $done
|
||||
col[key] = value
|
||||
|
||||
# The collection is returned.
|
||||
col
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$noop = ->
|
||||
class $MappedCollection extends $EventEmitter
|
||||
|
||||
$copyDeep = (value) ->
|
||||
if value instanceof Array
|
||||
return ($copyDeep item for item in value)
|
||||
|
||||
if value instanceof Object
|
||||
result = {}
|
||||
result[key] = $copyDeep item for key, item of value
|
||||
return result
|
||||
|
||||
return value
|
||||
|
||||
$getDeep = (obj, path) ->
|
||||
return obj if path.length is 0
|
||||
|
||||
current = obj
|
||||
|
||||
i = 0
|
||||
n = path.length - 1
|
||||
while i < n
|
||||
current = current[path[i++]]
|
||||
throw new Error 'invalid path component' if current is undefined
|
||||
|
||||
current[path[i]]
|
||||
|
||||
$setDeep = (obj, path, value) ->
|
||||
throw new Error 'invalid path' if path.length is 0
|
||||
|
||||
current = obj
|
||||
|
||||
i = 0
|
||||
n = path.length - 1
|
||||
while i < n
|
||||
current = current[path[i++]]
|
||||
throw new Error 'invalid path component' if current is undefined
|
||||
|
||||
current[path[i]] = value
|
||||
|
||||
# @param rule Rule of the current item.
|
||||
# @param item Current item.
|
||||
# @param value Value of the generator item.
|
||||
$computeValue = (collection, rule, item) ->
|
||||
value = item.generator
|
||||
|
||||
# @param parent The parent object of this entry (necessary for
|
||||
# assignment).
|
||||
# @param name The name of this entry.
|
||||
# @param spec Specification for the current entry.
|
||||
# The dispatch function is called whenever a new item has to be
|
||||
# processed and returns the name of the rule to use.
|
||||
#
|
||||
# @returns The generated value for this entry.
|
||||
helper = (parent, name, spec) ->
|
||||
if not $_.isObject spec
|
||||
parent[name] = spec
|
||||
else if spec instanceof $DynamicProperty
|
||||
# If there was no previous value use $DynamicProperty.value,
|
||||
# otherwise, just keep the previous value.
|
||||
if parent[name] is undefined
|
||||
# Helper is re-called for the initial value.
|
||||
helper parent, name, spec.value
|
||||
else if $_.isFunction spec
|
||||
ctx = {collection, rule}
|
||||
ctx.__proto__ = item # Links to the current item.
|
||||
parent[name] = spec.call ctx, value, item.key
|
||||
else if $_.isArray spec
|
||||
current = parent[name] or= new Array spec.length
|
||||
for entry, index in spec
|
||||
helper current, index, entry
|
||||
else
|
||||
# It's a plain object.
|
||||
current = parent[name] or= {}
|
||||
for key, property of spec
|
||||
helper current, key, property
|
||||
# To change the way it is dispatched, just override this it.
|
||||
dispatch: ->
|
||||
(@genval and (@genval.rule ? @genval.type)) ? 'unknown'
|
||||
|
||||
helper item, 'value', rule.value
|
||||
# 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}”"
|
||||
|
||||
######################################################################
|
||||
|
||||
class $MappedCollection
|
||||
|
||||
constructor: (spec) ->
|
||||
|
||||
# If spec is a function, it is called with various helpers in
|
||||
# `this`.
|
||||
if $_.isFunction spec
|
||||
ctx =
|
||||
dynamic: (initialValue, hooks) ->
|
||||
new $DynamicProperty initialValue, hooks
|
||||
noop: $noop
|
||||
|
||||
spec = spec.call ctx
|
||||
|
||||
# This key function returns the identifier used to map the
|
||||
# generator to the generated item.
|
||||
constructor: ->
|
||||
# Items are stored here indexed by key.
|
||||
#
|
||||
# The default function uses the index if the generator collection
|
||||
# is an array or the property name if it is an object.
|
||||
# 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.
|
||||
#
|
||||
# /!\: This entry MUST be overridden in rules for new items.
|
||||
if spec.key?
|
||||
@_key = spec.key
|
||||
throw new Error 'key must be a function' unless $_.isFunction @_key
|
||||
# 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
|
||||
val: undefined
|
||||
singleton
|
||||
}
|
||||
definition.call ctx
|
||||
else
|
||||
@_key = (_, key) -> key
|
||||
ctx = {
|
||||
name
|
||||
key: definition?.key
|
||||
val: definition?.val
|
||||
singleton
|
||||
}
|
||||
|
||||
spec.rules or= {}
|
||||
# Runs the `afterRule` hook and returns if the registration has
|
||||
# been prevented.
|
||||
return unless @_runHook 'afterRule', ctx
|
||||
|
||||
# Rules are the core of $MappedCollection, they allow to categorize
|
||||
# objects and to treat them differently.
|
||||
@_rules = {}
|
||||
{key, val} = ctx
|
||||
|
||||
# Hooks are functions which are run when a item of a given rule
|
||||
# enters, exists or is updated.
|
||||
@_hooks = {}
|
||||
# The default key.
|
||||
key ?= if singleton then -> name else -> @genkey
|
||||
|
||||
# Initially the collection is empty.
|
||||
@_byKey = {}
|
||||
# The default value.
|
||||
val ?= -> @genval
|
||||
|
||||
# For performance concerns, items are also categorized by rules.
|
||||
@_byRule = {}
|
||||
# Makes sure `key` is a function for uniformity.
|
||||
key = $makeFunction key unless $_.isFunction key
|
||||
|
||||
# Rules are checked for conformity and created in the system.
|
||||
for name, def of spec.rules
|
||||
# If it's a function, runs it.
|
||||
def = def() if $_.isFunction def
|
||||
# Register the new rule.
|
||||
@_rules[name] = {
|
||||
name
|
||||
key
|
||||
val
|
||||
singleton
|
||||
}
|
||||
|
||||
unless $_.isObject def
|
||||
throw new Error "#{name} definition must be an object"
|
||||
#--------------------------------
|
||||
|
||||
# A rule can extends another (not recursive for now!).
|
||||
if def.extends?
|
||||
unless $_.isString def.extends
|
||||
throw new Error "#{name}.extends must be a string"
|
||||
get: (keys) ->
|
||||
if keys is undefined
|
||||
items = $_.map @_byKey, (item) -> item.val
|
||||
else
|
||||
items = $mapInPlace (@_fetchItems keys), (item) -> item.val
|
||||
|
||||
if spec.rules[def.extends] is undefined
|
||||
throw new Error "#{name}.extends must reference a valid rule (#{def.extends})"
|
||||
if $_.isString keys then items[0] else items
|
||||
|
||||
$_.defaults def, spec.rules[def.extends]
|
||||
getRaw: (keys) ->
|
||||
if keys is undefined
|
||||
item for _, item of @_byKey
|
||||
else
|
||||
items = @_fetchItems keys
|
||||
|
||||
rule = {name}
|
||||
if $_.isString keys then items[0] else items
|
||||
|
||||
if def.key?
|
||||
# Static rule, used to create a new item (without generator).
|
||||
remove: (keys) ->
|
||||
@_removeItems (@_fetchItems keys)
|
||||
|
||||
if def.test?
|
||||
throw new Error "both #{name}.key and #{name}.test cannot be defined"
|
||||
set: (items, {add, update, remove} = {}) ->
|
||||
add = true unless add?
|
||||
update = true unless update?
|
||||
remove = false unless remove?
|
||||
|
||||
unless $_.isString def.key
|
||||
throw new Error "#{name}.key must be a string"
|
||||
itemsToAdd = {}
|
||||
itemsToUpdate = {}
|
||||
|
||||
rule.key = if $_.isFunction def.key then def.key() else def.key
|
||||
else if def.test?
|
||||
# Dynamic rule, used to create new items from generator items.
|
||||
itemsToRemove = {}
|
||||
$_.extend itemsToRemove, @_byKey if remove
|
||||
|
||||
unless $_.isFunction def.test
|
||||
throw new Error "#{name}.test must be a function"
|
||||
$each items, (genval, genkey) =>
|
||||
item = {
|
||||
rule: undefined
|
||||
key: undefined
|
||||
val: undefined
|
||||
genkey
|
||||
genval
|
||||
}
|
||||
|
||||
rule.test = def.test
|
||||
else
|
||||
# Invalid rule!
|
||||
throw new Error "#{name} must have either a key or a test entry"
|
||||
return unless @_runHook 'beforeDispatch', item
|
||||
|
||||
# A rule must have a value.
|
||||
throw new Error "#{name}.value must be defined" unless def.value?
|
||||
|
||||
rule.value = def.value
|
||||
|
||||
rule.private = !!def.private
|
||||
|
||||
@_rules[name] = rule
|
||||
@_hooks[name] =
|
||||
enter: []
|
||||
update: []
|
||||
exit: []
|
||||
@_byRule[name] = {}
|
||||
|
||||
# For each rules, values are browsed and hooks are created when
|
||||
# necessary (dynamic properties).
|
||||
for name, rule of @_rules or {}
|
||||
|
||||
# Browse the value searching for dynamic properties/entries.
|
||||
#
|
||||
# An immediately invoked function is used to easily handle
|
||||
# recursion.
|
||||
browse = (value, path) =>
|
||||
|
||||
# Unless the value is an object, there is nothing to browse.
|
||||
return unless $_.isObject value
|
||||
|
||||
# If the value is a function, it is a factory which will be
|
||||
# called later, when an item will be created.
|
||||
return if $_.isFunction value
|
||||
|
||||
# If the value is a dynamic property, grabs the initial value
|
||||
# and registers its hooks.
|
||||
if value instanceof $DynamicProperty
|
||||
hooks = value.hooks
|
||||
|
||||
# Browse hooks for each rules.
|
||||
for name_, hooks_ of hooks
|
||||
|
||||
# Wraps a hook.
|
||||
#
|
||||
# A hook is run with a defined environment
|
||||
wrap = (hook, rule, items) =>
|
||||
# Last two parameters are here to be protected from the
|
||||
# environment.
|
||||
|
||||
# FIXME: @_rules[name] and @_byRule are not defined at
|
||||
# this point.
|
||||
|
||||
rule = @_rules[name]
|
||||
|
||||
items = @_byRule[name]
|
||||
|
||||
(value, key) ->
|
||||
# The current hook is runs for all items of the
|
||||
# current rule.
|
||||
for _, item of items
|
||||
# Value of the current field.
|
||||
field = $getDeep item.value, path
|
||||
|
||||
ctx = {rule, field}
|
||||
ctx.__proto__ = item # Links to the current item.
|
||||
|
||||
hook.call ctx, value, key
|
||||
|
||||
# Updates the value if it changed.
|
||||
$setDeep item.value, path, ctx.field if ctx.field isnt field
|
||||
|
||||
# Checks each hook is correctly defined.
|
||||
{enter, update, exit} = hooks_
|
||||
|
||||
enter ?= update
|
||||
@_hooks[name_].enter.push wrap(enter) if enter?
|
||||
|
||||
if not update? and exit? and enter?
|
||||
update = (args...) ->
|
||||
exit.apply this, args
|
||||
enter.apply this, args
|
||||
@_hooks[name_].update.push wrap(update) if update?
|
||||
|
||||
@_hooks[name_].exit.push wrap(exit) if exit?
|
||||
|
||||
# OPTIMIZE: do not register hooks if they are `noop`.
|
||||
|
||||
# FIXME: Hooks must be associated to the rule (because they
|
||||
# must be run for each object of this type), and to the
|
||||
# field (because it must be available through @field).
|
||||
|
||||
return
|
||||
|
||||
# If the value is an array, browse each entry.
|
||||
if $_.isArray value
|
||||
for entry, index in value
|
||||
browse entry, path.concat(index)
|
||||
return
|
||||
|
||||
# The value is an object, browse each property.
|
||||
for key, property of value
|
||||
browse property, path.concat(key)
|
||||
|
||||
browse rule.value, []
|
||||
|
||||
# If it is a static rule, creates its item right now.
|
||||
if rule.key?
|
||||
# Adds the item.
|
||||
item = @_byKey[rule.key] = @_byRule[rule.name][rule.key] =
|
||||
_ruleName: rule.name
|
||||
key: rule.key
|
||||
value: undefined
|
||||
|
||||
# Computes the value.
|
||||
$computeValue this, rule, item
|
||||
|
||||
# No events for static items.
|
||||
|
||||
get: (key) -> @_byKey[key]?.value
|
||||
|
||||
getAll: ->
|
||||
items = {}
|
||||
|
||||
for ruleName, ruleItems of @_byRule
|
||||
# Searches for a rule to handle it.
|
||||
ruleName = @dispatch.call item
|
||||
rule = @_rules[ruleName]
|
||||
|
||||
# Items of private rules are not exported.
|
||||
continue if rule.private
|
||||
unless rule?
|
||||
@missingRule ruleName
|
||||
|
||||
for key, {value} of ruleItems
|
||||
items[key] = value
|
||||
# If `missingRule()` has not created the rule, just keep this
|
||||
# item.
|
||||
rule = @_rules[ruleName]
|
||||
return unless rule?
|
||||
|
||||
items
|
||||
# Checks if this is a singleton.
|
||||
@_assert(
|
||||
not rule.singleton
|
||||
"cannot add items to singleton rule “#{rule.name}”"
|
||||
)
|
||||
|
||||
remove: (items) ->
|
||||
itemsToRemove = {}
|
||||
$_.each items, (value, key) =>
|
||||
key = @_key key
|
||||
item = @_byKey[key]
|
||||
if item?
|
||||
itemsToRemove[key] = item
|
||||
# Computes its key.
|
||||
key = rule.key.call item
|
||||
|
||||
@_remove items
|
||||
@_assert(
|
||||
$_.isString key
|
||||
"the key “#{key}” is not a string"
|
||||
)
|
||||
|
||||
# Adds, updates or removes items from the collections. Items not
|
||||
# present are added, present are updated, and present in the
|
||||
# generated collection but not in the generator are removed.
|
||||
set: (items, {add, update, remove} = {}) ->
|
||||
add = true if add is undefined
|
||||
update = true if update is undefined
|
||||
remove = false if remove is undefined
|
||||
# Updates known values.
|
||||
item.rule = rule.name
|
||||
item.key = key
|
||||
|
||||
itemsToRemove = {}
|
||||
if remove
|
||||
$_.extend(itemsToRemove, @_byKey)
|
||||
|
||||
$_.each items, (value, generatorKey) =>
|
||||
key = @_key value, generatorKey
|
||||
|
||||
# If the item already existed.
|
||||
if @_byKey[key]?
|
||||
if key of @_byKey
|
||||
# Marks this item as not to be removed.
|
||||
delete itemsToRemove[key] if remove
|
||||
delete itemsToRemove[key]
|
||||
|
||||
if update
|
||||
item = @_byKey[key]
|
||||
rule = @_rules[item._ruleName]
|
||||
# Fetches the existing entry.
|
||||
prev = @_byKey[key]
|
||||
|
||||
# Compute the new value.
|
||||
item.generator = value
|
||||
item.generatorKey = generatorKey
|
||||
$computeValue this, rule, item
|
||||
# Checks if there is a conflict in rules.
|
||||
@_assert(
|
||||
item.rule is prev.rule
|
||||
"the key “#{key}” cannot be of rule “#{item.rule}”, "
|
||||
"already used by “#{prev.rule}”"
|
||||
)
|
||||
|
||||
# Runs related hooks.
|
||||
for hook in @_hooks[rule.name]?.update or []
|
||||
hook item.value, item.key
|
||||
else if add
|
||||
# First we have to find to which rule this item belongs to.
|
||||
rule = do =>
|
||||
for _, rule of @_rules
|
||||
ctx = {rule}
|
||||
return rule if rule.test? and rule.test.call ctx, value, key
|
||||
# Gets its previous value.
|
||||
item.val = prev.val
|
||||
|
||||
# If no rule has been found, just stops.
|
||||
return unless rule
|
||||
# Registers the item to be updated.
|
||||
itemsToUpdate[key] = item
|
||||
|
||||
# Adds the item.
|
||||
item = @_byKey[key] = @_byRule[rule.name][key] = {
|
||||
_ruleName: rule.name
|
||||
key
|
||||
value: undefined
|
||||
generator: value
|
||||
generatorKey
|
||||
}
|
||||
# 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
|
||||
|
||||
# Computes the value.
|
||||
$computeValue this, rule, item
|
||||
# Adds items.
|
||||
@_updateItems itemsToAdd, true
|
||||
|
||||
# Runs related hooks.
|
||||
for hook in @_hooks[rule.name]?.enter or []
|
||||
hook item.value, item.key
|
||||
# Updates items.
|
||||
@_updateItems itemsToUpdate
|
||||
|
||||
# There are keys inside only if remove is `true`.
|
||||
@_remove itemsToRemove if remove
|
||||
# Removes any items not seen (iff `remove` is true).
|
||||
@_removeItems itemsToRemove
|
||||
|
||||
_remove: (items) ->
|
||||
for {_ruleName: ruleName, value}, key in items
|
||||
# If there are some hooks registered, runs them.
|
||||
for hook in @_hooks[ruleName]?.remove or []
|
||||
hook value, key
|
||||
# Forces items to update their value.
|
||||
touch: (keys) ->
|
||||
@_updateItems (@_fetchItems keys, true)
|
||||
|
||||
# Removes effectively the item.
|
||||
delete @_byKey[key] @_byRule[ruleName][key]
|
||||
#--------------------------------
|
||||
|
||||
_assert: (cond, message) ->
|
||||
throw new Error message unless cond
|
||||
|
||||
# Emits item related event.
|
||||
_emitEvent: (event, items) ->
|
||||
byRule = {}
|
||||
|
||||
# One per item.
|
||||
$each items, (item) =>
|
||||
@emit "key=#{item.key}", event, item
|
||||
|
||||
(byRule[item.rule] ?= []).push item
|
||||
|
||||
# One per rule.
|
||||
@emit "rule=#{rule}", event, items for rule, items of byRule
|
||||
|
||||
# One for everything.
|
||||
@emit "any", event, items
|
||||
|
||||
_fetchItems: (keys, ignoreMissingItems = false) ->
|
||||
unless $_.isArray keys
|
||||
keys = if $_.isObject keys then $_.keys 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
|
||||
|
||||
$each items, (item) => delete @_byKey[item.key]
|
||||
|
||||
@_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.
|
||||
@_emitEvent 'exit', items unless areNew
|
||||
|
||||
$each 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, 'val', @_rules[ruleName].val
|
||||
|
||||
return unless @_runHook 'beforeSave', item
|
||||
|
||||
# Registers the new item.
|
||||
@_byKey[item.key] = item
|
||||
|
||||
@_emitEvent 'enter', items
|
||||
|
||||
# TODO: checks for loops.
|
||||
|
||||
#=====================================================================
|
||||
|
||||
module.exports = $MappedCollection
|
||||
module.exports = {$MappedCollection}
|
||||
|
85
src/MappedCollection.spec.coffee
Normal file
85
src/MappedCollection.spec.coffee
Normal file
@ -0,0 +1,85 @@
|
||||
{expect: $expect} = require 'chai'
|
||||
|
||||
$sinon = require 'sinon'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
{$MappedCollection} = require './MappedCollection.coffee'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
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
|
@ -4,7 +4,7 @@ var $requireTree = require('require-tree');
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
var $waitPromise = require('./fibers-utils').$waitPromise;
|
||||
var $wait = require('./fibers-utils').$wait;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -144,7 +144,7 @@ Api.prototype.checkPermission = function (session, permission)
|
||||
return;
|
||||
}
|
||||
|
||||
var user = $waitPromise(this.xo.users.first(user_id));
|
||||
var user = $wait(this.xo.users.first(user_id));
|
||||
// The user MUST exist at this time.
|
||||
|
||||
if (!user.hasPermission(permission))
|
||||
@ -221,7 +221,7 @@ Api.fn = $requireTree('./api');
|
||||
$register('api.getVersion', '0.1');
|
||||
|
||||
$register('xo.getAllObjects', function () {
|
||||
return this.xo.xobjs.getAll();
|
||||
return this.xo.xobjs.get();
|
||||
});
|
||||
|
||||
// Returns the list of available methods similar to XML-RPC
|
||||
|
@ -1,4 +1,4 @@
|
||||
{$waitPromise} = require '../fibers-utils'
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
@ -14,7 +14,7 @@ exports.add = (session, request) ->
|
||||
@checkPermission session, 'admin'
|
||||
|
||||
# Adds the server.
|
||||
server = $waitPromise @xo.servers.add {
|
||||
server = $wait @xo.servers.add {
|
||||
host
|
||||
username
|
||||
password
|
||||
@ -32,7 +32,7 @@ exports.remove = (session, request) ->
|
||||
@checkPermission session, 'admin'
|
||||
|
||||
# Throws an error if the server did not exist.
|
||||
@throw 'NO_SUCH_OBJECT' unless $waitPromise @xo.servers.remove id
|
||||
@throw 'NO_SUCH_OBJECT' unless $wait @xo.servers.remove id
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
@ -43,7 +43,7 @@ exports.getAll = (session) ->
|
||||
@checkPermission session, 'admin'
|
||||
|
||||
# Retrieves the servers.
|
||||
servers = $waitPromise @xo.servers.get()
|
||||
servers = $wait @xo.servers.get()
|
||||
|
||||
# Filters out private properties.
|
||||
for server, i in servers
|
||||
@ -61,7 +61,7 @@ exports.set = (session, request) ->
|
||||
@checkPermission session, 'admin'
|
||||
|
||||
# Retrieves the server.
|
||||
server = $waitPromise @xo.servers.first id
|
||||
server = $wait @xo.servers.first id
|
||||
|
||||
# Throws an error if it did not exist.
|
||||
@throw 'NO_SUCH_OBJECT' unless server
|
||||
@ -72,7 +72,7 @@ exports.set = (session, request) ->
|
||||
server.set {password} if password?
|
||||
|
||||
# Updates the server.
|
||||
$waitPromise @xo.servers.update server
|
||||
$wait @xo.servers.update server
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
|
@ -1,4 +1,4 @@
|
||||
{$waitPromise} = require '../fibers-utils'
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
@ -10,7 +10,7 @@ exports.signInWithPassword = (session, req) ->
|
||||
@throw 'ALREADY_AUTHENTICATED' if session.has 'user_id'
|
||||
|
||||
# Gets the user.
|
||||
user = $waitPromise @xo.users.first {email: email}
|
||||
user = $wait @xo.users.first {email: email}
|
||||
|
||||
# Invalid credentials if the user does not exists or if the password
|
||||
# does not check.
|
||||
@ -30,7 +30,7 @@ exports.signInWithToken = (session, req) ->
|
||||
@throw 'ALREADY_AUTHENTICATED' if session.has('user_id')
|
||||
|
||||
# Gets the token.
|
||||
token = $waitPromise @xo.tokens.first token
|
||||
token = $wait @xo.tokens.first token
|
||||
@throw 'INVALID_CREDENTIAL' unless token?
|
||||
|
||||
# Stores the user and the token identifiers in the session.
|
||||
@ -39,7 +39,7 @@ exports.signInWithToken = (session, req) ->
|
||||
session.set 'user_id', user_id
|
||||
|
||||
# Returns the user.
|
||||
user = $waitPromise @xo.users.first user_id
|
||||
user = $wait @xo.users.first user_id
|
||||
@getUserPublicProperties user
|
||||
|
||||
# Gets the the currently signed in user.
|
||||
@ -50,5 +50,5 @@ exports.getUser = (session) ->
|
||||
return null unless id?
|
||||
|
||||
# Returns the user.
|
||||
user = $waitPromise @xo.users.first id
|
||||
user = $wait @xo.users.first id
|
||||
@getUserPublicProperties user
|
||||
|
@ -1,4 +1,4 @@
|
||||
{$waitPromise} = require '../fibers-utils'
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
@ -12,7 +12,7 @@ exports.create = (session) ->
|
||||
@throw 'UNAUTHORIZED' if not userId? or session.has 'token_id'
|
||||
|
||||
# Creates the token.
|
||||
token = $waitPromise @xo.tokens.generate userId
|
||||
token = $wait @xo.tokens.generate userId
|
||||
|
||||
# Returns its identifier.
|
||||
token.id
|
||||
@ -23,11 +23,11 @@ exports.delete = (session, req) ->
|
||||
@throw 'INVALID_PARAMS' unless token?
|
||||
|
||||
# Gets the token.
|
||||
token = $waitPromise @xo.tokens.first tokenId
|
||||
token = $wait @xo.tokens.first tokenId
|
||||
@throw 'NO_SUCH_OBJECT' unless token?
|
||||
|
||||
# Deletes the token.
|
||||
$waitPromise @xo.tokens.remove tokenId
|
||||
$wait @xo.tokens.remove tokenId
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
|
@ -1,4 +1,4 @@
|
||||
{$waitPromise} = require '../fibers-utils'
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
@ -11,7 +11,7 @@ exports.create = (session, request) ->
|
||||
@checkPermission session, 'admin'
|
||||
|
||||
# Creates the user.
|
||||
user = $waitPromise @xo.users.create email, password, permission
|
||||
user = $wait @xo.users.create email, password, permission
|
||||
|
||||
# Returns the identifier of the new user.
|
||||
user.id
|
||||
@ -27,7 +27,7 @@ exports.delete = (session, request) ->
|
||||
@checkPermission session, 'admin'
|
||||
|
||||
# Throws an error if the user did not exist.
|
||||
@throw 'NO_SUCH_OBJECT' unless $waitPromise @xo.users.remove id
|
||||
@throw 'NO_SUCH_OBJECT' unless $wait @xo.users.remove id
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
@ -41,7 +41,7 @@ exports.changePassword = (session, request) ->
|
||||
@checkPermission session
|
||||
|
||||
# Gets the current user (which MUST exist).
|
||||
user = $waitPromise @xo.users.first session.get 'user_id'
|
||||
user = $wait @xo.users.first session.get 'user_id'
|
||||
|
||||
# Checks its old password.
|
||||
@throw 'INVALID_CREDENTIAL' unless user.checkPassword old
|
||||
@ -50,7 +50,7 @@ exports.changePassword = (session, request) ->
|
||||
user.setPassword newP
|
||||
|
||||
# Updates the user.
|
||||
$waitPromise @xo.users.update user
|
||||
$wait @xo.users.update user
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
@ -64,7 +64,7 @@ exports.get = (session, request) ->
|
||||
@checkPermission session, 'admin' unless session.get 'user_id' is id
|
||||
|
||||
# Retrieves the user.
|
||||
user = $waitPromise @xo.users.first id
|
||||
user = $wait @xo.users.first id
|
||||
|
||||
# Throws an error if it did not exist.
|
||||
@throw 'NO_SUCH_OBJECT' unless user
|
||||
@ -78,7 +78,7 @@ exports.getAll = (session) ->
|
||||
@checkPermission session, 'admin'
|
||||
|
||||
# Retrieves the users.
|
||||
users = $waitPromise @xo.users.get()
|
||||
users = $wait @xo.users.get()
|
||||
|
||||
# Filters out private properties.
|
||||
for user, i in users
|
||||
@ -96,7 +96,7 @@ exports.set = (session, request) ->
|
||||
@checkPermission session, 'admin'
|
||||
|
||||
# Retrieves the user.
|
||||
user = $waitPromise @xo.users.first id
|
||||
user = $wait @xo.users.first id
|
||||
|
||||
# Throws an error if it did not exist.
|
||||
@throw 'NO_SUCH_OBJECT' unless user
|
||||
@ -107,7 +107,7 @@ exports.set = (session, request) ->
|
||||
user.setPassword password if password?
|
||||
|
||||
# Updates the user.
|
||||
$waitPromise @xo.users.update user
|
||||
$wait @xo.users.update user
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
|
@ -4,6 +4,8 @@ $_ = require 'underscore'
|
||||
# Async code is easier with fibers (light threads)!
|
||||
$fiber = require 'fibers'
|
||||
|
||||
$Q = require 'q'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$isPromise = (obj) -> obj? and $_.isFunction obj.then
|
||||
@ -20,7 +22,7 @@ $fiberize = (fn) ->
|
||||
# TODO: should we keep it?
|
||||
$promisify = (fn) ->
|
||||
(args...) ->
|
||||
deferred = Q.defer()
|
||||
deferred = $Q.defer()
|
||||
|
||||
$fiber(=>
|
||||
try
|
||||
@ -43,6 +45,7 @@ $synchronize = (fn, ctx) ->
|
||||
|
||||
(args...) ->
|
||||
fiber = $fiber.current
|
||||
throw new Error 'not running in a fiber' unless fiber?
|
||||
|
||||
args.push (error, result) ->
|
||||
if error?
|
||||
@ -58,6 +61,7 @@ $synchronize = (fn, ctx) ->
|
||||
# Note: if the *error* event is emitted, this function will throw.
|
||||
$waitEvent = (emitter, event) ->
|
||||
fiber = $fiber.current
|
||||
throw new Error 'not running in a fiber' unless fiber?
|
||||
|
||||
errorHandler = null
|
||||
handler = (args...) ->
|
||||
@ -72,17 +76,28 @@ $waitEvent = (emitter, event) ->
|
||||
|
||||
$fiber.yield()
|
||||
|
||||
# Waits for a promise to be fulfilled or broken.
|
||||
$waitPromise = (promise) ->
|
||||
# If it is not a promise, just forwards it.
|
||||
return promise unless $isPromise promise
|
||||
|
||||
# Waits for a promise or a thunk to end.
|
||||
$wait = (value) ->
|
||||
fiber = $fiber.current
|
||||
throw new Error 'not running in a fiber' unless fiber?
|
||||
|
||||
promise.then(
|
||||
(result) -> fiber.run result
|
||||
(error) -> fiber.throwInto error
|
||||
)
|
||||
if $isPromise value
|
||||
value.then(
|
||||
(result) -> fiber.run result
|
||||
(error) -> fiber.throwInto error
|
||||
)
|
||||
else if $_.isFunction value
|
||||
# It should be a thunk.
|
||||
value (error, result) ->
|
||||
if error?
|
||||
fiber.throwInto error
|
||||
else
|
||||
fibre.run result
|
||||
else
|
||||
# TODO: handle array and object of promises/thunks.
|
||||
|
||||
# No idea what is it, just forwards.
|
||||
return value
|
||||
|
||||
$fiber.yield()
|
||||
|
||||
@ -90,8 +105,9 @@ $waitPromise = (promise) ->
|
||||
|
||||
module.exports = {
|
||||
$fiberize
|
||||
$promisify
|
||||
$sleep
|
||||
$synchronize
|
||||
$waitEvent
|
||||
$waitPromise
|
||||
$wait
|
||||
}
|
||||
|
353
src/helpers.coffee
Normal file
353
src/helpers.coffee
Normal file
@ -0,0 +1,353 @@
|
||||
$_ = require 'underscore'
|
||||
|
||||
# FIXME: This file name should reflect what's inside!
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$asArray = (val) -> if $_.isArray val then val else [val]
|
||||
$asFunction = (val) -> if $_.isFunction val then val else -> val
|
||||
|
||||
$each = $_.each
|
||||
|
||||
$first = (collection, def) ->
|
||||
if (n = collection.length)?
|
||||
return collection[0] unless n is 0
|
||||
else
|
||||
return value for own _, value of collection
|
||||
|
||||
# Nothing was found, returns the `def` value.
|
||||
def
|
||||
|
||||
$removeValue = (array, value) ->
|
||||
index = array.indexOf value
|
||||
return false if index is -1
|
||||
array.splice index, 1
|
||||
true
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# TODO: currently the watch can be updated multiple times per
|
||||
# “$MappedCollection.set()” which is inefficient: it should be
|
||||
# possible to address that.
|
||||
|
||||
$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.
|
||||
#
|
||||
# Default: -> throw new Error 'loop detected'
|
||||
loopDetected
|
||||
}, fn) ->
|
||||
val = if val is undefined
|
||||
# The default value is simply the value of the item.
|
||||
-> @val
|
||||
else
|
||||
$asFunction val
|
||||
|
||||
loopDetected ?= -> throw new Error 'loop detected'
|
||||
|
||||
# Method allowing the cleanup when the helper is no longer used.
|
||||
#cleanUp = -> # TODO: noop for now.
|
||||
|
||||
# Keys of items using the current helper.
|
||||
consumers = Object.create null
|
||||
|
||||
# Current values.
|
||||
values = Object.create null
|
||||
values.common = init
|
||||
|
||||
isProcessing = false
|
||||
process = (event, items) ->
|
||||
|
||||
# Values are grouped by namespace.
|
||||
valuesByNamespace = Object.create null
|
||||
|
||||
$each items, (item, key) -> # `key` is a local variable.
|
||||
return unless not cond? or cond.call item
|
||||
|
||||
if bind?
|
||||
key = bind.call item
|
||||
|
||||
# If bind did not return a key, ignores this value.
|
||||
return unless key?
|
||||
|
||||
namespace = "$#{key}"
|
||||
else
|
||||
namespace = 'common'
|
||||
|
||||
# Computes the current value.
|
||||
value = val.call item
|
||||
|
||||
(valuesByNamespace[namespace] ?= []).push value
|
||||
|
||||
# Stops here if no values were computed.
|
||||
return if do ->
|
||||
return false for _ of valuesByNamespace
|
||||
true
|
||||
|
||||
if isProcessing
|
||||
return unless loopDetected() is true
|
||||
isProcessing = true
|
||||
|
||||
# For each namespace.
|
||||
for namespace, values_ of valuesByNamespace
|
||||
|
||||
# Updates the value.
|
||||
value = values[namespace]
|
||||
ctx = {
|
||||
# TODO: test the $_.clone
|
||||
value: if value is undefined then $_.clone init else value
|
||||
}
|
||||
changed = if event is 'enter'
|
||||
fn.call ctx, values_, {}
|
||||
else
|
||||
fn.call ctx, {}, values_
|
||||
|
||||
# Notifies watchers unless it is known the value has not
|
||||
# changed.
|
||||
unless changed is false
|
||||
values[namespace] = ctx.value
|
||||
if namespace is 'common'
|
||||
collection.touch consumers
|
||||
else
|
||||
collection.touch (namespace.substr 1)
|
||||
|
||||
isProcessing = false
|
||||
|
||||
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 not $_.isEmpty keys
|
||||
# Matching is done on the keys.
|
||||
|
||||
throw new Error 'cannot use keys and rules' unless $_.isEmpty rules
|
||||
|
||||
$each keys, (key) -> collection.on "key=#{key}", processOne
|
||||
|
||||
# Handles existing items.
|
||||
process 'enter', collection.getRaw keys
|
||||
else if not $_.isEmpty rules
|
||||
# Matching is done the rules.
|
||||
|
||||
$each rules, (rule) -> collection.on "rule=#{rule}", process
|
||||
|
||||
# TODO: Inefficient, is there another way?
|
||||
rules = do -> # Minor optimization.
|
||||
tmp = Object.create null
|
||||
tmp[rule] = true for rule in rules
|
||||
tmp
|
||||
$each collection.getRaw(), (item) ->
|
||||
processOne 'enter', item if item.rule of rules
|
||||
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.
|
||||
generator = do (key) -> # Declare a local variable.
|
||||
->
|
||||
{key} = this
|
||||
|
||||
# Register this item has a consumer.
|
||||
consumers[key] = true
|
||||
|
||||
# Returns the value for this item if any or the common value.
|
||||
namespace = "$#{key}"
|
||||
if namespace of values
|
||||
values[namespace]
|
||||
else
|
||||
values.common
|
||||
|
||||
# Creates a helper to unregister an item from this watcher.
|
||||
generator.unregister = do (key) -> # Declare a local variable.
|
||||
->
|
||||
{key} = this
|
||||
delete consumers[key]
|
||||
delete values["$#{key}"]
|
||||
|
||||
# Creates a helper to get the value without using an item.
|
||||
generator.raw = (key) ->
|
||||
values[if key? then "$#{key}" else 'common']
|
||||
|
||||
# Returns the generator.
|
||||
generator
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$map = (options) ->
|
||||
options.init = Object.create null
|
||||
|
||||
$watch this, options, (entered, exited) ->
|
||||
changed = false
|
||||
|
||||
$each entered, ([key, value]) =>
|
||||
unless @value[key] is value
|
||||
@value[key] = value
|
||||
changed = true
|
||||
$each exited, ([key, value]) =>
|
||||
if key of @value
|
||||
delete @value[key]
|
||||
changed = true
|
||||
|
||||
changed
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Creates a set of value from various items.
|
||||
$set = (options) ->
|
||||
# Contrary to other helpers, the default value is the key.
|
||||
options.val ?= -> @key
|
||||
|
||||
options.init = []
|
||||
|
||||
$watch this, options, (entered, exited) ->
|
||||
changed = false
|
||||
|
||||
$each entered, (value) =>
|
||||
if (@value.indexOf value) is -1
|
||||
@value.push value
|
||||
changed = true
|
||||
|
||||
$each exited, (value) =>
|
||||
changed = true if $removeValue @value, value
|
||||
|
||||
changed
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
$sum = (options) ->
|
||||
init = options.init ?= 0
|
||||
|
||||
add = (a, b) ->
|
||||
if $_.isArray a
|
||||
n = a.length
|
||||
throw new Error 'invalid sum' unless $_.isArray b and b.length is n
|
||||
i = 0
|
||||
while i < n
|
||||
a[i] = add a[i], b[i]
|
||||
++i
|
||||
else if $_.isObject a
|
||||
throw new Error 'invalid sum' unless $_.isObject b
|
||||
for key of a
|
||||
a[key] = add a[key], b[key]
|
||||
else
|
||||
a += b
|
||||
a
|
||||
sub = (a, b) ->
|
||||
if $_.isArray a
|
||||
n = a.length
|
||||
throw new Error 'invalid sum' unless $_.isArray b and b.length is n
|
||||
i = 0
|
||||
while i < n
|
||||
a[i] = sub a[i], b[i]
|
||||
++i
|
||||
else if $_.isObject a
|
||||
throw new Error 'invalid sum' unless $_.isObject b
|
||||
for key of a
|
||||
a[key] = sub a[key], b[key]
|
||||
else
|
||||
a -= b
|
||||
a
|
||||
|
||||
$watch this, options, (entered, exited) ->
|
||||
prev = @value
|
||||
|
||||
$each entered, (value) => @value = add @value, value
|
||||
$each exited, (value) => @value = sub @value, value
|
||||
|
||||
@value isnt prev
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Uses a value from another item.
|
||||
#
|
||||
# Important note: Behavior is not specified when binding to multiple
|
||||
# items.
|
||||
$val = (options) ->
|
||||
# The default value.
|
||||
def = options.default
|
||||
delete options.default
|
||||
|
||||
options.init ?= def
|
||||
|
||||
# Should the last value be kept instead of returning to the default
|
||||
# value when no items are available!
|
||||
keepLast = !!options.keepLast
|
||||
delete options.keepLast
|
||||
|
||||
$watch this, options, (entered, exited) ->
|
||||
prev = @value
|
||||
|
||||
@value = $first entered, (if keepLast then @value else def)
|
||||
|
||||
@value isnt prev
|
||||
|
||||
#=====================================================================
|
||||
|
||||
module.exports = {
|
||||
$map
|
||||
$set
|
||||
$sum
|
||||
$val
|
||||
}
|
243
src/helpers.spec.coffee
Normal file
243
src/helpers.spec.coffee
Normal file
@ -0,0 +1,243 @@
|
||||
{expect: $expect} = require 'chai'
|
||||
|
||||
$sinon = require 'sinon'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
{$MappedCollection} = require './MappedCollection.coffee'
|
||||
|
||||
$nonBindedHelpers = require './helpers'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
describe 'Helper', ->
|
||||
|
||||
# Shared variables.
|
||||
collection = $set = $sum = $val = null
|
||||
beforeEach ->
|
||||
# Creates the collection.
|
||||
collection = new $MappedCollection()
|
||||
|
||||
# Dispatcher used for tests.
|
||||
collection.dispatch = -> (@genkey.split '.')[0]
|
||||
|
||||
# Missing rules should be automatically created.
|
||||
collection.missingRule = collection.rule
|
||||
|
||||
# # Monkey patch the collection to see all emitted events.
|
||||
# emit = collection.emit
|
||||
# collection.emit = (args...) ->
|
||||
# console.log args...
|
||||
# emit.call collection, args...
|
||||
|
||||
# Binds helpers to this collection.
|
||||
{$set, $sum, $val} = do ->
|
||||
helpers = {}
|
||||
helpers[name] = fn.bind collection for name, fn of $nonBindedHelpers
|
||||
helpers
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
# 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: ->
|
||||
@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: ->
|
||||
@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
|
||||
|
||||
it 'with dynamic keys', ->
|
||||
collection.set {
|
||||
foo: 1
|
||||
bar: 2
|
||||
}
|
||||
|
||||
collection.rule sum: ->
|
||||
@val = $sum {
|
||||
key: -> (@key.split '.')[1]
|
||||
}
|
||||
collection.set {
|
||||
'sum.foo': null
|
||||
'sum.bar': null
|
||||
}
|
||||
|
||||
$expect(collecter.get 'sum.foo').to.equal 1
|
||||
$expect(collecter.get 'sum.bar').to.equal 2
|
||||
|
||||
collection.remove 'bar'
|
||||
|
||||
$expect(collecter.get 'sum.foo').to.equal 1
|
||||
$expect(collecter.get 'sum.bar').to.equal 0
|
||||
|
||||
it 'with single rule', ->
|
||||
collection.set {
|
||||
'foo.1': 1
|
||||
'foo.2': 2
|
||||
}
|
||||
|
||||
collection.item sum: ->
|
||||
@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: ->
|
||||
@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: ->
|
||||
@val = $sum {
|
||||
bind: ->
|
||||
id = @val.sum
|
||||
return unless id?
|
||||
"sum.#{id}"
|
||||
val: -> @val.val
|
||||
}
|
||||
collection.set {
|
||||
'sum.1': null
|
||||
'sum.2': null
|
||||
}
|
||||
|
||||
$expect(collection.get 'sum.1').equal 2
|
||||
$expect(collection.get 'sum.2').equal 1
|
||||
|
||||
collection.set {
|
||||
'foo': {
|
||||
sum: 1
|
||||
val: 3
|
||||
}
|
||||
}
|
||||
|
||||
$expect(collection.get 'sum.1').equal 5
|
||||
$expect(collection.get 'sum.2').equal 0
|
||||
|
||||
collection.remove 'bar'
|
||||
|
||||
$expect(collection.get 'sum.1').equal 3
|
||||
$expect(collection.get 'sum.2').equal 0
|
||||
|
||||
|
||||
it 'with predicate', ->
|
||||
collection.set {
|
||||
foo: 1
|
||||
bar: 2
|
||||
baz: 3
|
||||
}
|
||||
|
||||
collection.item sum: ->
|
||||
@val = $sum {
|
||||
if: -> /^b/.test @rule
|
||||
}
|
||||
|
||||
$expect(collection.get 'sum').equal 5
|
||||
|
||||
collection.set foo:4
|
||||
|
||||
$expect(collection.get 'sum').equal 5
|
||||
|
||||
collection.set bar:5
|
||||
|
||||
$expect(collection.get 'sum').equal 8
|
||||
|
||||
collection.remove 'baz'
|
||||
|
||||
$expect(collection.get 'sum').equal 5
|
||||
|
||||
it 'with initial value', ->
|
||||
collection.set foo: 1
|
||||
|
||||
collection.item sum: ->
|
||||
@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
|
@ -25,7 +25,7 @@ $Session = require './session'
|
||||
$XO = require './xo'
|
||||
|
||||
# Helpers for dealing with fibers.
|
||||
{$fiberize, $waitEvent, $waitPromise} = require './fibers-utils'
|
||||
{$fiberize, $waitEvent, $wait} = require './fibers-utils'
|
||||
|
||||
# HTTP/HTTPS server which can listen on multiple ports.
|
||||
$WebServer = require './web-server'
|
||||
@ -171,7 +171,7 @@ do $fiberize ->
|
||||
socket.send response if socket.readyState is socket.OPEN
|
||||
|
||||
# Creates a default user if there is none.
|
||||
unless $waitPromise xo.users.exists()
|
||||
unless $wait xo.users.exists()
|
||||
email = 'admin@admin.net'
|
||||
password = 'admin' # TODO: Should be generated.
|
||||
xo.users.create email, password, 'admin'
|
||||
|
1209
src/spec.coffee
1209
src/spec.coffee
File diff suppressed because it is too large
Load Diff
1
src/spec.spec-data.json
Normal file
1
src/spec.spec-data.json
Normal file
File diff suppressed because one or more lines are too long
533
src/spec.spec.coffee
Normal file
533
src/spec.spec.coffee
Normal file
@ -0,0 +1,533 @@
|
||||
{expect: $expect} = require 'chai'
|
||||
|
||||
$sinon = require 'sinon'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
{$MappedCollection} = require './MappedCollection.coffee'
|
||||
|
||||
# Helpers for dealing with fibers.
|
||||
{$promisify} = require './fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
describe 'spec', ->
|
||||
|
||||
collection = null
|
||||
before $promisify ->
|
||||
# Creates the collection.
|
||||
collection = new $MappedCollection()
|
||||
|
||||
# Loads the spec.
|
||||
(require './spec').call collection
|
||||
|
||||
# Skips missing rules.
|
||||
collection.missingRule = ( -> )
|
||||
|
||||
# Loads the mockup data.
|
||||
collection.set (require './spec.spec-data')
|
||||
|
||||
#console.log collection.get()
|
||||
|
||||
it 'xo', ->
|
||||
xo = collection.get '00000000-0000-0000-0000-000000000000'
|
||||
|
||||
#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 0 # TODO: 10
|
||||
|
||||
$expect(xo.$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'
|
||||
]
|
||||
|
||||
# TODO
|
||||
# $expect(xo.memory).to.be.an 'object'
|
||||
# $expect(xo.memory.usage).to.equal 0
|
||||
# $expect(xo.memory.size).to.equal 0
|
||||
|
||||
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.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.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'
|
||||
]
|
||||
|
||||
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 0 # TODO
|
||||
# $expect(host.memory.size).to.equal 0 # TODO
|
||||
|
||||
$expect(host.power_state).to.equal 'Running'
|
||||
|
||||
$expect(host.SRs).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 0
|
||||
|
||||
it 'VM', ->
|
||||
vm = collection.get 'OpaqueRef:fdaba312-c3a5-0190-b1a1-bf389567e620'
|
||||
|
||||
$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.memory).to.be.an 'object'
|
||||
$expect(vm.memory.usage).to.be.null
|
||||
#$expect(vm.memory.size).to.equal '' # FIXME
|
||||
|
||||
$expect(vm.messages).to.have.members []
|
||||
|
||||
$expect(vm.power_state).to.equal 'Running'
|
||||
|
||||
#$expect(vm.CPUs).to.be.an # FIXME
|
||||
|
||||
$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 '1969-12-31T23:00:00.000Z'
|
||||
|
||||
$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 [
|
||||
{
|
||||
device: '0'
|
||||
size: 8589934592
|
||||
SR: ''
|
||||
bootable: true
|
||||
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'
|
||||
]
|
||||
|
||||
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.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', ->
|
||||
# FIXME
|
||||
#console.log collection.get()
|
||||
|
||||
it 'task', ->
|
||||
# FIXME: we need to update the tests data to complete this test.
|
@ -19,7 +19,7 @@ $createRedisClient = (require 'then-redis').createClient
|
||||
|
||||
# A mapped collection is generated from another collection through a
|
||||
# specification.
|
||||
$MappedCollection = require './MappedCollection'
|
||||
{$MappedCollection} = require './MappedCollection'
|
||||
|
||||
# Collection where models are stored in a Redis DB.
|
||||
$RedisCollection = require './collection/redis'
|
||||
@ -31,7 +31,7 @@ $Model = require './model'
|
||||
$XAPI = require './xapi'
|
||||
|
||||
# Helpers for dealing with fibers.
|
||||
{$fiberize, $synchronize, $waitPromise} = require './fibers-utils'
|
||||
{$fiberize, $synchronize, $wait} = require './fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
@ -150,13 +150,8 @@ class $XO extends $EventEmitter
|
||||
@tokens.remove (token.id for token in tokens)
|
||||
|
||||
# Collections of XAPI objects mapped to XO API.
|
||||
refsToUUIDs = { # Needed for the mapping.
|
||||
'OpaqueRef:NULL': null
|
||||
}
|
||||
@xobjs = do ->
|
||||
spec = (require './spec') refsToUUIDs
|
||||
|
||||
new $MappedCollection spec
|
||||
@xobjs = new $MappedCollection()
|
||||
(require './spec').call @xobjs
|
||||
|
||||
# XAPI connections.
|
||||
@xapis = {}
|
||||
@ -168,7 +163,8 @@ class $XO extends $EventEmitter
|
||||
id = server.id
|
||||
|
||||
# UUID of the pool of this connection.
|
||||
poolUUID = undefined
|
||||
poolUUID = undefined #TODO: Remove.
|
||||
poolRef = undefined
|
||||
|
||||
xapi = @xapis[id] = new $XAPI {
|
||||
host: server.host
|
||||
@ -188,11 +184,10 @@ class $XO extends $EventEmitter
|
||||
types.push type
|
||||
types
|
||||
|
||||
# This helper normalizes a record by inserting its type and by
|
||||
# storing its UUID in the `refsToUUIDs` map if any.
|
||||
# This helper normalizes a record by inserting its type.
|
||||
normalizeObject = (object, ref, type) ->
|
||||
refsToUUIDs[ref] = object.uuid if object.uuid?
|
||||
object.$pool = poolUUID unless type is 'pool'
|
||||
object.$poolRef = poolRef unless type is 'pool'
|
||||
object.$ref = ref
|
||||
object.$type = type
|
||||
|
||||
objects = {}
|
||||
@ -207,9 +202,12 @@ class $XO extends $EventEmitter
|
||||
pool = pools[ref]
|
||||
throw new Error 'no pool found' unless pool?
|
||||
|
||||
# Remembers its UUID.
|
||||
# Remembers its UUID. TODO: remove
|
||||
poolUUID = pool.uuid
|
||||
|
||||
# Remembers its reference.
|
||||
poolRef = ref
|
||||
|
||||
# Makes the connection accessible through the pool UUID.
|
||||
# TODO: Properly handle disconnections.
|
||||
@xapis[poolUUID] = xapi
|
||||
@ -284,14 +282,17 @@ class $XO extends $EventEmitter
|
||||
throw error unless error[0] is 'SESSION_NOT_REGISTERED'
|
||||
|
||||
# Prevents errors from stopping the server.
|
||||
connectSafe = $fiberize (server) =>
|
||||
connectSafe = $fiberize (server) ->
|
||||
try
|
||||
connect server
|
||||
catch error
|
||||
console.log "[WARN] #{server.host}:", error[0] ? error.code ? error
|
||||
console.error(
|
||||
"[WARN] #{server.host}:"
|
||||
error[0] ? error.stack ? error.code ? error
|
||||
)
|
||||
|
||||
# Connects to existing servers.
|
||||
connectSafe server for server in $waitPromise @servers.get()
|
||||
connectSafe server for server in $wait @servers.get()
|
||||
|
||||
# Automatically connects to new servers.
|
||||
@servers.on 'add', (servers) ->
|
||||
|
Loading…
Reference in New Issue
Block a user