Initial work on the new data model.
This commit is contained in:
@@ -18,3 +18,8 @@ trim_trailing_whitespaces = true
|
|||||||
[/package.json]
|
[/package.json]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
|
||||||
|
# For CoffeeScript files, we follow this Polarmobile style guide (https://github.com/polarmobile/coffeescript-style-guide/blob/master/README.md).
|
||||||
|
[*{,.spec}.{,lit}coffee]
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
|||||||
25
package.json
25
package.json
@@ -14,19 +14,22 @@
|
|||||||
"main": "src/main.js",
|
"main": "src/main.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"coffee-script": "~1.6.3",
|
"coffee-script": "~1.6.3",
|
||||||
"connect": ">=2.8.4",
|
"connect": "~2.11.2",
|
||||||
"extendable": ">=0.0.3",
|
"extendable": "0.0.6",
|
||||||
"hashy": ">=0.1.0",
|
"hashy": "~0.1.0",
|
||||||
"js-yaml": ">=2.1.0",
|
"js-yaml": "~2.1.3",
|
||||||
"q": ">=0.9.6",
|
"optimist": "~0.6.0",
|
||||||
"sync": ">=0.2.2",
|
"q": "~0.9.7",
|
||||||
"then-redis": ">=0.3.8",
|
"sync": "~0.2.2",
|
||||||
"underscore": ">=1.5.2",
|
"then-redis": "~0.3.9",
|
||||||
"validator": ">=1.2.1",
|
"underscore": "~1.5.2",
|
||||||
"ws": ">=0.4.27",
|
"validator": "~2.0.0",
|
||||||
"xmlrpc": ">=1.1.0"
|
"ws": "~0.4.31",
|
||||||
|
"xmlrpc": "~1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"chai": "~1.8.1",
|
||||||
|
"mocha": "~1.14.0",
|
||||||
"node-inspector": "~0.6.1"
|
"node-inspector": "~0.6.1"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {},
|
"optionalDependencies": {},
|
||||||
|
|||||||
374
src/MappedCollection.coffee
Normal file
374
src/MappedCollection.coffee
Normal file
@@ -0,0 +1,374 @@
|
|||||||
|
$_ = require 'underscore'
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
class DynamicProperty
|
||||||
|
|
||||||
|
constructor: (@value, @hooks) ->
|
||||||
|
|
||||||
|
#---------------------------------------------------------------------
|
||||||
|
|
||||||
|
noop = ->
|
||||||
|
|
||||||
|
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 = (rule, item, value) ->
|
||||||
|
|
||||||
|
# @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.
|
||||||
|
#
|
||||||
|
# @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 = {rule}
|
||||||
|
ctx.__proto__ = item # Links to the current item.
|
||||||
|
parent[name] = spec.call ctx, value, item.key
|
||||||
|
else if $_.isArray spec
|
||||||
|
current = parent[name] = new Array spec.length
|
||||||
|
for entry, index in spec
|
||||||
|
helper current, index, entry
|
||||||
|
else
|
||||||
|
# It's a plain object.
|
||||||
|
current = parent[name] = {}
|
||||||
|
for key, property of spec
|
||||||
|
helper current, key, property
|
||||||
|
|
||||||
|
helper item, 'value', rule.value
|
||||||
|
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
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.
|
||||||
|
#
|
||||||
|
# The default function uses the index if the generator collection
|
||||||
|
# is an array or the property name if it is an object.
|
||||||
|
#
|
||||||
|
# /!\: This entry MUST be overriden in rules for new items.
|
||||||
|
if spec.key?
|
||||||
|
@_key = spec.key
|
||||||
|
throw new Error 'key must be a function' unless $_.isFunction @_key
|
||||||
|
else
|
||||||
|
@_key = (_, key) -> key
|
||||||
|
|
||||||
|
spec.rules or= {}
|
||||||
|
|
||||||
|
# Rules are the core of MappedCollection, they allow to categorize
|
||||||
|
# objects and to treat them differently.
|
||||||
|
@_rules = {}
|
||||||
|
|
||||||
|
# Hooks are functions which are run when a item of a given rule
|
||||||
|
# enters, exists or is updated.
|
||||||
|
@_hooks = {}
|
||||||
|
|
||||||
|
# Initialy the collection is empty.
|
||||||
|
@_byKey = {}
|
||||||
|
|
||||||
|
# For performance concerns, items are also categorized by rules.
|
||||||
|
@_byRule = {}
|
||||||
|
|
||||||
|
# Rules are checked for conformity and created in the sytem.
|
||||||
|
for name, def of spec.rules
|
||||||
|
# If it's a function, runs it.
|
||||||
|
def = def() if $_.isFunction def
|
||||||
|
|
||||||
|
throw new Error "#{name} definition must be an object" unless $_.isObject def
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
|
||||||
|
if spec.rules[def.extends] is undefined
|
||||||
|
throw new Error "#{name}.extends must reference a valid rule (#{def.extends})"
|
||||||
|
|
||||||
|
$_.defaults def, spec.rules[def.extends]
|
||||||
|
|
||||||
|
rule = {name}
|
||||||
|
|
||||||
|
if def.key?
|
||||||
|
# Static rule, used to create a new item (without generator).
|
||||||
|
|
||||||
|
throw new Error "both #{name}.key and #{name}.test cannot be defined" if def.test?
|
||||||
|
|
||||||
|
# The key MUST be a string.
|
||||||
|
throw new Error "#{name}.key must be a string" unless $_.isString def.key
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
# The test MUST be a function.
|
||||||
|
throw new Error "#{name}.test must be a function" unless $_.isFunction def.test
|
||||||
|
|
||||||
|
rule.test = def.test
|
||||||
|
else
|
||||||
|
# Invalid rule!
|
||||||
|
throw new Error "#{name} must have either a key or a test entry"
|
||||||
|
|
||||||
|
# A rule must have a value.
|
||||||
|
throw new Error "#{name}.value must be defined" unless def.value?
|
||||||
|
|
||||||
|
rule.value = def.value
|
||||||
|
|
||||||
|
@_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 immediatly invoked function is used to easily handle
|
||||||
|
# recursivity.
|
||||||
|
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 vars 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 key, 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 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 rule, item
|
||||||
|
|
||||||
|
# No events for static items.
|
||||||
|
|
||||||
|
get: (key) -> @_byKey[key]
|
||||||
|
|
||||||
|
getAll: ->
|
||||||
|
items = {}
|
||||||
|
|
||||||
|
for ruleName, ruleItems in @_byRule
|
||||||
|
rule = @_rules[ruleName]
|
||||||
|
|
||||||
|
# Items of private rules are not exported.
|
||||||
|
continue if rule.private
|
||||||
|
|
||||||
|
for key, {value} of ruleItems
|
||||||
|
items[key] = value
|
||||||
|
|
||||||
|
items
|
||||||
|
|
||||||
|
remove: (items) ->
|
||||||
|
itemsToRemove = {}
|
||||||
|
$_.each items, (value, key) ->
|
||||||
|
key = @_key key
|
||||||
|
|
||||||
|
itemsToRemove[key] = @_byKey[key]
|
||||||
|
|
||||||
|
@_remove items
|
||||||
|
|
||||||
|
# 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, options = {}) ->
|
||||||
|
{add, update, remove} = options
|
||||||
|
add or= true
|
||||||
|
update or= true
|
||||||
|
remove or= true
|
||||||
|
|
||||||
|
itemsToRemove = {}
|
||||||
|
if remove
|
||||||
|
$_.extend(itemsToRemove, @_byKey)
|
||||||
|
|
||||||
|
$_.each items, (value, key) =>
|
||||||
|
key = @_key key
|
||||||
|
|
||||||
|
# If the item already existed.
|
||||||
|
if @_byKey[key]
|
||||||
|
# Marks this item as not to be removed.
|
||||||
|
delete itemsToRemove[key] if remove
|
||||||
|
|
||||||
|
if update
|
||||||
|
item = @_byKey[key]
|
||||||
|
rule = @_rules[item._ruleName]
|
||||||
|
|
||||||
|
# Compute the new value.
|
||||||
|
computeValue rule, item, value
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# If no rule has been found, just stops.
|
||||||
|
return unless rule
|
||||||
|
|
||||||
|
# Adds the item.
|
||||||
|
item = @_byKey[key] = @_byRule[rule.name][key] =
|
||||||
|
_ruleName: rule.name
|
||||||
|
key: key
|
||||||
|
value: undefined
|
||||||
|
|
||||||
|
# Computes the value.
|
||||||
|
computeValue rule, item, value
|
||||||
|
|
||||||
|
# Runs related hooks.
|
||||||
|
for hook in @_hooks[rule.name]?.enter or []
|
||||||
|
hook item.value, item.key
|
||||||
|
|
||||||
|
# There are keys inside only if remove is `true`.
|
||||||
|
@_remove itemsToRemove if remove
|
||||||
|
|
||||||
|
_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
|
||||||
|
|
||||||
|
# Removes effectively the item.
|
||||||
|
delete @_byKey[key] @_byRule[ruleName][key]
|
||||||
|
|
||||||
|
######################################################################
|
||||||
|
|
||||||
|
module.exports = (spec) -> new MappedCollection spec
|
||||||
70
src/api.js
70
src/api.js
@@ -530,39 +530,12 @@ Api.fn.server = {
|
|||||||
|
|
||||||
// Extra methods not really bound to an object.
|
// Extra methods not really bound to an object.
|
||||||
Api.fn.xo = {
|
Api.fn.xo = {
|
||||||
'getStats': function () {
|
'getAllObjects': function () {
|
||||||
return this.xo.stats;
|
return this.xo.xobjs.getAll()
|
||||||
},
|
}
|
||||||
|
|
||||||
'getSessionId': function (req) {
|
|
||||||
var p_pool_id = req.params.id;
|
|
||||||
if (undefined === p_pool_id)
|
|
||||||
{
|
|
||||||
throw Api.err.INVALID_PARAMS;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.xobjs.pool.first(p_pool_id).then(function (pool) {
|
|
||||||
return pool.get('sessionId');
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Api.fn.xapi = {
|
Api.fn.xapi = {
|
||||||
'__catchAll': function (session, req) {
|
|
||||||
var RE = /^xapi\.(.*)\.getAll$/;
|
|
||||||
var match = req.method.match(RE);
|
|
||||||
var collection;
|
|
||||||
if (!match || !(collection = this.xo.xobjs[match[1]]))
|
|
||||||
{
|
|
||||||
throw Api.err.INVALID_METHOD;
|
|
||||||
}
|
|
||||||
|
|
||||||
return collection.get();
|
|
||||||
},
|
|
||||||
|
|
||||||
'getClasses': function () {
|
|
||||||
return this.xo.xclasses;
|
|
||||||
},
|
|
||||||
|
|
||||||
'vm': {
|
'vm': {
|
||||||
'pause': function (session, req) {
|
'pause': function (session, req) {
|
||||||
@@ -576,7 +549,7 @@ Api.fn.xapi = {
|
|||||||
var xobjs = xo.xobjs;
|
var xobjs = xo.xobjs;
|
||||||
var vm;
|
var vm;
|
||||||
return this.checkPermission(session, 'write').then(function () {
|
return this.checkPermission(session, 'write').then(function () {
|
||||||
return xobjs.VM.first(p_id);
|
return xobjs.get(p_id);
|
||||||
}).then(function (tmp) {
|
}).then(function (tmp) {
|
||||||
vm = tmp;
|
vm = tmp;
|
||||||
|
|
||||||
@@ -601,7 +574,7 @@ Api.fn.xapi = {
|
|||||||
var xobjs = xo.xobjs;
|
var xobjs = xo.xobjs;
|
||||||
var vm;
|
var vm;
|
||||||
return this.checkPermission(session, 'write').then(function () {
|
return this.checkPermission(session, 'write').then(function () {
|
||||||
return xobjs.VM.first(p_id);
|
return xobjs.get(p_id);
|
||||||
}).then(function (tmp) {
|
}).then(function (tmp) {
|
||||||
vm = tmp;
|
vm = tmp;
|
||||||
|
|
||||||
@@ -626,7 +599,7 @@ Api.fn.xapi = {
|
|||||||
var xobjs = xo.xobjs;
|
var xobjs = xo.xobjs;
|
||||||
var vm;
|
var vm;
|
||||||
return this.checkPermission(session, 'write').then(function () {
|
return this.checkPermission(session, 'write').then(function () {
|
||||||
return xobjs.VM.first(p_id);
|
return xobjs.get(p_id);
|
||||||
}).then(function (tmp) {
|
}).then(function (tmp) {
|
||||||
vm = tmp;
|
vm = tmp;
|
||||||
|
|
||||||
@@ -653,7 +626,7 @@ Api.fn.xapi = {
|
|||||||
var xobjs = xo.xobjs;
|
var xobjs = xo.xobjs;
|
||||||
var vm;
|
var vm;
|
||||||
return this.checkPermission(session, 'write').then(function () {
|
return this.checkPermission(session, 'write').then(function () {
|
||||||
return xobjs.VM.first(p_id);
|
return xobjs.get(p_id);
|
||||||
}).then(function (tmp) {
|
}).then(function (tmp) {
|
||||||
vm = tmp;
|
vm = tmp;
|
||||||
|
|
||||||
@@ -682,7 +655,7 @@ Api.fn.xapi = {
|
|||||||
var xobjs = xo.xobjs;
|
var xobjs = xo.xobjs;
|
||||||
var vm;
|
var vm;
|
||||||
return this.checkPermission(session, 'write').then(function () {
|
return this.checkPermission(session, 'write').then(function () {
|
||||||
return xobjs.VM.first(p_id);
|
return xobjs.get(p_id);
|
||||||
}).then(function (tmp) {
|
}).then(function (tmp) {
|
||||||
vm = tmp;
|
vm = tmp;
|
||||||
|
|
||||||
@@ -697,3 +670,30 @@ Api.fn.xapi = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Api.fn.system = {
|
||||||
|
|
||||||
|
// Returns the list of available methods similar to XML-RPC
|
||||||
|
// introspection (http://xmlrpc-c.sourceforge.net/introspection.html).
|
||||||
|
'listMethods': function () {
|
||||||
|
var methods = [];
|
||||||
|
|
||||||
|
(function browse(container, path) {
|
||||||
|
var n = path.length;
|
||||||
|
_.each(container, function (content, key) {
|
||||||
|
path[n] = key;
|
||||||
|
if (_.isFunction(content))
|
||||||
|
{
|
||||||
|
methods.push(path.join('.'));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
browse(content, path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
path.pop();
|
||||||
|
})(Api.fn, []);
|
||||||
|
|
||||||
|
return methods;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|||||||
338
src/spec.coffee
Normal file
338
src/spec.coffee
Normal file
@@ -0,0 +1,338 @@
|
|||||||
|
retrieveTags = (UUID) -> [] # TODO
|
||||||
|
|
||||||
|
test = (value) -> value.$type is @rule.name
|
||||||
|
|
||||||
|
remove = (array, value) ->
|
||||||
|
index = array.indexOf array, value
|
||||||
|
|
||||||
|
array.splice(index, 1) unless index is -1
|
||||||
|
|
||||||
|
####
|
||||||
|
|
||||||
|
module.exports = (refsToUUIDs) ->
|
||||||
|
|
||||||
|
get = (name, defaultValue) ->
|
||||||
|
(value) ->
|
||||||
|
if value[name] is undefined
|
||||||
|
return defaultValue
|
||||||
|
|
||||||
|
# If the value looks like an OpaqueRef, resolve it to a UUID.
|
||||||
|
value = value[name]
|
||||||
|
if refsToUUIDs[value]
|
||||||
|
refsToUUIDs[value]
|
||||||
|
else
|
||||||
|
value
|
||||||
|
|
||||||
|
->
|
||||||
|
key: (value, key) -> value.uuid or key
|
||||||
|
|
||||||
|
rules:
|
||||||
|
|
||||||
|
xo:
|
||||||
|
|
||||||
|
# The key is directly defined here because this is a new object,
|
||||||
|
# not bound to an existing item.
|
||||||
|
#
|
||||||
|
# TODO: provides a way to create multiple new items per rule.
|
||||||
|
key: '00000000-0000-0000-0000-000000000000'
|
||||||
|
|
||||||
|
# The value is an object.
|
||||||
|
value:
|
||||||
|
|
||||||
|
type: -> @rule.name
|
||||||
|
|
||||||
|
UUID: -> @key
|
||||||
|
|
||||||
|
pools: @dynamic [],
|
||||||
|
pool:
|
||||||
|
enter: (pool) -> @field.push pool.UUID
|
||||||
|
exit: (pool) -> remove @field, pool.UUID
|
||||||
|
update: @noop
|
||||||
|
|
||||||
|
$CPUs: @dynamic 0,
|
||||||
|
host:
|
||||||
|
# No `update`: `exit` then `enter` will be called instead.
|
||||||
|
enter: (host) -> @field += host.CPUs.length
|
||||||
|
exit: (host) -> @field -= host.CPUs.length
|
||||||
|
|
||||||
|
$running_VMs: @dynamic [],
|
||||||
|
VM:
|
||||||
|
# No `enter`: `update` will be called instead.
|
||||||
|
update: (VM) ->
|
||||||
|
remove @field, VM.UUID
|
||||||
|
if VM.power_state is 'Running'
|
||||||
|
@field.push VM.UUID
|
||||||
|
exit: (VM) -> remove @field, VM.UUID
|
||||||
|
|
||||||
|
$vCPUs: @dynamic 0,
|
||||||
|
VM:
|
||||||
|
# No `update`: `exit` then `enter` will be called instead.
|
||||||
|
enter: (VM) -> @field += VM.CPUs.length
|
||||||
|
exit: (VM) -> @field -= VM.CPUs.length
|
||||||
|
|
||||||
|
$memory: @dynamic { usage: 0, size: 0 },
|
||||||
|
host:
|
||||||
|
# No `update`: `exit` then `enter` will be called instead.
|
||||||
|
enter: (host) ->
|
||||||
|
@field.usage += host.memory.usage
|
||||||
|
@field.size += host.memory.size
|
||||||
|
exit: (host) ->
|
||||||
|
@field.usage -= host.memory.usage
|
||||||
|
@field.size -= host.memory.size
|
||||||
|
|
||||||
|
pool:
|
||||||
|
|
||||||
|
test: test
|
||||||
|
|
||||||
|
value:
|
||||||
|
|
||||||
|
type: -> @rule.name
|
||||||
|
|
||||||
|
UUID: -> @key
|
||||||
|
|
||||||
|
tags: -> retrieveTags @value.UUID
|
||||||
|
|
||||||
|
SRs: @dynamic [],
|
||||||
|
SR:
|
||||||
|
enter: (value) ->
|
||||||
|
if value.$pool is @value.UUID
|
||||||
|
@field.push value.UUID
|
||||||
|
exit: (value) -> remove @field, value.UUID
|
||||||
|
|
||||||
|
HA_enabled: get('ha_enabled')
|
||||||
|
|
||||||
|
hosts: @dynamic [],
|
||||||
|
host:
|
||||||
|
enter: (value) ->
|
||||||
|
if value.$pool is @value.UUID
|
||||||
|
@field.push value.UUID
|
||||||
|
exit: (value) -> remove @field, value.UUID
|
||||||
|
|
||||||
|
VMs: @dynamic [],
|
||||||
|
VM:
|
||||||
|
# FIXME: when a VM is updated, this hook will run for each
|
||||||
|
# pool even though we know which pool to update
|
||||||
|
# (`value.$pool`).
|
||||||
|
# There must be a way to fix this problem while still
|
||||||
|
# keeping a generic implementation.
|
||||||
|
#
|
||||||
|
# Note: I do not want to handle this field from the VM
|
||||||
|
# rule, it would make the maintenance harder.
|
||||||
|
update: (VM) ->
|
||||||
|
# Unless this VM belongs to this pool, there is no need
|
||||||
|
# to continue.
|
||||||
|
return unless VM.$pool is @value.UUID
|
||||||
|
|
||||||
|
# If this VM is running or paused, it is necessarily on
|
||||||
|
# a host.
|
||||||
|
if state is 'Paused' or state is 'Runnning'
|
||||||
|
remove @field, VM.UUID
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: Check whether this VM belong to a local SR.
|
||||||
|
local = false
|
||||||
|
unless local
|
||||||
|
@field.push VM.UUID
|
||||||
|
|
||||||
|
exit: (value) -> remove @field, value.UUID
|
||||||
|
|
||||||
|
$running_hosts: @dynamic [],
|
||||||
|
host:
|
||||||
|
update: (host) ->
|
||||||
|
remove @field, host.UUID
|
||||||
|
if host.$pool is @value.UUID and host.power_state is 'Running'
|
||||||
|
@field.push host.UUID
|
||||||
|
exit: (host) -> remove @field, host.UUID
|
||||||
|
|
||||||
|
$running_VMs: @dynamic [],
|
||||||
|
VM:
|
||||||
|
update: (VM) ->
|
||||||
|
remove @field, VM.UUID
|
||||||
|
if VM.$pool is @value.UUID and VM.power_state is 'Running'
|
||||||
|
@field.push VM.UUID
|
||||||
|
exit: (VM) ->
|
||||||
|
remove @field, VM.UUID
|
||||||
|
|
||||||
|
$VMs: @dynamic (-> @value.VMs.slice 0),
|
||||||
|
VM:
|
||||||
|
update: (VM) ->
|
||||||
|
remove @field, VM.UUID
|
||||||
|
if VM.$pool is @value.UUID
|
||||||
|
@field.push VM.UUID
|
||||||
|
exit: (VM) -> remove @field, VM.UUID
|
||||||
|
|
||||||
|
host:
|
||||||
|
|
||||||
|
test: test
|
||||||
|
|
||||||
|
value:
|
||||||
|
|
||||||
|
type: -> @rule.name
|
||||||
|
|
||||||
|
UUID: -> @key
|
||||||
|
|
||||||
|
name_label: get('name_label')
|
||||||
|
|
||||||
|
name_description: get('name_description')
|
||||||
|
|
||||||
|
tags: -> retrieveTags @value.UUID
|
||||||
|
|
||||||
|
address: get('address')
|
||||||
|
|
||||||
|
controller: get('controller')
|
||||||
|
|
||||||
|
CPUs: [] # TODO
|
||||||
|
|
||||||
|
enabled: get('enabled')
|
||||||
|
|
||||||
|
hostname: get('hostname')
|
||||||
|
|
||||||
|
iSCSI_name: (value) -> value.other_config?.iscsi_iqn
|
||||||
|
|
||||||
|
memory: {} # TODO
|
||||||
|
|
||||||
|
power_state: 'Running' # TODO
|
||||||
|
|
||||||
|
SRs: [] # TODO
|
||||||
|
|
||||||
|
VMs: [] # TODO
|
||||||
|
|
||||||
|
$PBDs: [] # TODO
|
||||||
|
|
||||||
|
$pool: null # TODO
|
||||||
|
|
||||||
|
$running_VMs: [] # TODO
|
||||||
|
|
||||||
|
$vCPUs: []
|
||||||
|
|
||||||
|
VM:
|
||||||
|
|
||||||
|
test: (value) ->
|
||||||
|
value.$type is @rule.name and
|
||||||
|
not value.is_control_domain and
|
||||||
|
not value.is_a_template
|
||||||
|
|
||||||
|
value:
|
||||||
|
|
||||||
|
type: -> @rule.name
|
||||||
|
|
||||||
|
UUID: -> @key
|
||||||
|
|
||||||
|
name_label: get('name_label')
|
||||||
|
|
||||||
|
name_description: get('name_description')
|
||||||
|
|
||||||
|
tags: -> retrieveTags @value.UUID
|
||||||
|
|
||||||
|
address: -> null # TODO
|
||||||
|
|
||||||
|
memory: # TODO
|
||||||
|
usage: 0
|
||||||
|
size: 0
|
||||||
|
|
||||||
|
power_state: get('power_state')
|
||||||
|
|
||||||
|
CPUs: [] # TODO
|
||||||
|
|
||||||
|
$CPU_usage: ->
|
||||||
|
n = @value.CPUs.length
|
||||||
|
return undefined unless n
|
||||||
|
sum = 0
|
||||||
|
sum += CPU.usage for CPU in @value.CPUs
|
||||||
|
sum / n
|
||||||
|
|
||||||
|
$container: null # TODO
|
||||||
|
|
||||||
|
$VBDs: [] # TODO
|
||||||
|
|
||||||
|
'VM-controller':
|
||||||
|
extends: 'VM'
|
||||||
|
|
||||||
|
test: (value) ->
|
||||||
|
value.$type is 'VM' and value.is_control_domain
|
||||||
|
|
||||||
|
SR:
|
||||||
|
|
||||||
|
test: test
|
||||||
|
|
||||||
|
value:
|
||||||
|
|
||||||
|
type: -> @rule.name
|
||||||
|
|
||||||
|
UUID: -> @key
|
||||||
|
|
||||||
|
name_label: get('name_label')
|
||||||
|
|
||||||
|
name_description: get('name_description')
|
||||||
|
|
||||||
|
tags: -> retrieveTags @value.UUID
|
||||||
|
|
||||||
|
SR_type: get('type')
|
||||||
|
|
||||||
|
physical_usage: get('physical_utilization')
|
||||||
|
|
||||||
|
usage: get('virtual_allocation')
|
||||||
|
|
||||||
|
size: get('physical_size')
|
||||||
|
|
||||||
|
$container: null # TODO
|
||||||
|
|
||||||
|
$PBDs: [] # TODO
|
||||||
|
|
||||||
|
$VDIs: [] # TODO
|
||||||
|
|
||||||
|
PBD:
|
||||||
|
|
||||||
|
test: test
|
||||||
|
|
||||||
|
value:
|
||||||
|
|
||||||
|
type: -> @rule.name
|
||||||
|
|
||||||
|
UUID: -> @key
|
||||||
|
|
||||||
|
attached: get('currently_attached')
|
||||||
|
|
||||||
|
host: get('host')
|
||||||
|
|
||||||
|
SR: get('SR')
|
||||||
|
|
||||||
|
VDI:
|
||||||
|
|
||||||
|
test: test
|
||||||
|
|
||||||
|
value:
|
||||||
|
|
||||||
|
type: -> @rule.name
|
||||||
|
|
||||||
|
UUID: -> @key
|
||||||
|
|
||||||
|
name_label: get('name_label')
|
||||||
|
|
||||||
|
name_description: get('name_description')
|
||||||
|
|
||||||
|
# TODO: determine whether or not tags are required for a VDI.
|
||||||
|
#tags: -> retrieveTags @value.UUID
|
||||||
|
|
||||||
|
usage: get('physical_utilization')
|
||||||
|
|
||||||
|
size: get('virtual_size')
|
||||||
|
|
||||||
|
# FIXME: SR.VDIs -> VDI instead of VDI.SR -> SR.
|
||||||
|
SR: get('SR')
|
||||||
|
|
||||||
|
$VBD: null # TODO
|
||||||
|
|
||||||
|
VBD:
|
||||||
|
|
||||||
|
test: test
|
||||||
|
|
||||||
|
value:
|
||||||
|
|
||||||
|
type: -> @rule.name
|
||||||
|
|
||||||
|
UUID: -> @key
|
||||||
|
|
||||||
|
VDI: get('VDI')
|
||||||
|
|
||||||
|
VM: get('VM')
|
||||||
131
src/xo.js
131
src/xo.js
@@ -3,6 +3,7 @@ var crypto = require('crypto');
|
|||||||
var hashy = require('hashy');
|
var hashy = require('hashy');
|
||||||
var Q = require('q');
|
var Q = require('q');
|
||||||
|
|
||||||
|
var createMappedCollection = require('./MappedCollection');
|
||||||
var MemoryCollection = require('./collection/memory');
|
var MemoryCollection = require('./collection/memory');
|
||||||
var RedisCollection = require('./collection/redis');
|
var RedisCollection = require('./collection/redis');
|
||||||
var Model = require('./model');
|
var Model = require('./model');
|
||||||
@@ -165,67 +166,10 @@ function Xo()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Connections to Xen pools/servers.
|
// Connections to Xen pools/servers.
|
||||||
//this.connections = {};
|
this.connections = {};
|
||||||
|
|
||||||
// We will keep up-to-date stats in this object
|
|
||||||
this.stats = {};
|
|
||||||
}
|
}
|
||||||
require('util').inherits(Xo, require('events').EventEmitter);
|
require('util').inherits(Xo, require('events').EventEmitter);
|
||||||
|
|
||||||
Xo.prototype.computeStats = _.throttle(function () {
|
|
||||||
var xo = this;
|
|
||||||
var xobjs = xo.xobjs;
|
|
||||||
|
|
||||||
return Q.all([
|
|
||||||
xobjs.host.get(),
|
|
||||||
xobjs.host_metrics.get().then(function (metrics) {
|
|
||||||
return _.indexBy(metrics, 'id');
|
|
||||||
}),
|
|
||||||
xobjs.VM.get({
|
|
||||||
'is_a_template': false,
|
|
||||||
'is_control_domain': false,
|
|
||||||
}),
|
|
||||||
xobjs.VM_metrics.get().then(function (metrics) {
|
|
||||||
return _.indexBy(metrics, 'id');
|
|
||||||
}),
|
|
||||||
xobjs.SR.count(),
|
|
||||||
]).spread(function (hosts, host_metrics, vms, vms_metrics, n_srs) {
|
|
||||||
var running_vms = _.where(vms, {
|
|
||||||
'power_state': 'Running',
|
|
||||||
});
|
|
||||||
|
|
||||||
var n_cpus = 0;
|
|
||||||
var total_memory = 0;
|
|
||||||
_.each(hosts, function (host) {
|
|
||||||
n_cpus += host.host_CPUs.length;
|
|
||||||
total_memory += +host_metrics[host.metrics].memory_total;
|
|
||||||
});
|
|
||||||
|
|
||||||
var n_vifs = 0;
|
|
||||||
var n_vcpus = 0;
|
|
||||||
var used_memory = 0;
|
|
||||||
_.each(vms, function (vm) {
|
|
||||||
var metrics = vms_metrics[vm.metrics];
|
|
||||||
|
|
||||||
n_vifs += vm.VIFs.length;
|
|
||||||
n_vcpus += +metrics.VCPUs_number;
|
|
||||||
used_memory += +metrics.memory_actual;
|
|
||||||
});
|
|
||||||
|
|
||||||
xo.stats = {
|
|
||||||
'hosts': hosts.length,
|
|
||||||
'vms': vms.length,
|
|
||||||
'running_vms': running_vms.length,
|
|
||||||
'used_memory': used_memory,
|
|
||||||
'total_memory': total_memory,
|
|
||||||
'vcpus': n_vcpus,
|
|
||||||
'cpus': n_cpus,
|
|
||||||
'vifs': n_vifs,
|
|
||||||
'srs': n_srs,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}, 5000);
|
|
||||||
|
|
||||||
Xo.prototype.start = function (data) {
|
Xo.prototype.start = function (data) {
|
||||||
var cfg = data.config;
|
var cfg = data.config;
|
||||||
|
|
||||||
@@ -283,16 +227,8 @@ Xo.prototype.start = function (data) {
|
|||||||
// 'VMPP',
|
// 'VMPP',
|
||||||
// 'VTPM',
|
// 'VTPM',
|
||||||
];
|
];
|
||||||
xo.xobjs = {};
|
xo.xobjs = createMappedCollection(require('./spec'));
|
||||||
_.each(xo.xclasses, function (xclass) {
|
|
||||||
xo.xobjs[xclass] = new MemoryCollection();
|
|
||||||
});
|
|
||||||
|
|
||||||
// When a server is added we should connect to it and fetch data.
|
|
||||||
var xclasses_map = {}; // @todo Remove this ugly map.
|
|
||||||
_.each(xo.xclasses, function (xclass) {
|
|
||||||
xclasses_map[xclass.toLowerCase()] = xclass;
|
|
||||||
});
|
|
||||||
var connect = function (server) {
|
var connect = function (server) {
|
||||||
var pool_id = server.id;
|
var pool_id = server.id;
|
||||||
var xapi = new Xapi(server.host, server.username, server.password);
|
var xapi = new Xapi(server.host, server.username, server.password);
|
||||||
@@ -301,53 +237,44 @@ Xo.prototype.start = function (data) {
|
|||||||
var xclasses = xo.xclasses;
|
var xclasses = xo.xclasses;
|
||||||
var xobjs = xo.xobjs;
|
var xobjs = xo.xobjs;
|
||||||
|
|
||||||
|
// First retrieves all objects.
|
||||||
return Q.all(_.map(xclasses, function (xclass) {
|
return Q.all(_.map(xclasses, function (xclass) {
|
||||||
var collection = xobjs[xclass];
|
return xapi.call(xclass +'.get_all_records')
|
||||||
|
.then(function (records) {
|
||||||
|
_.each(records, function (record) {
|
||||||
|
record.$type = xclass;
|
||||||
|
record.$pool = pool_id;
|
||||||
|
});
|
||||||
|
|
||||||
return xapi.call(xclass +'.get_all_records').then(function (records) {
|
return xobjs.set(records);
|
||||||
records = _.map(records, function (record, ref) {
|
|
||||||
record.id = ref;
|
|
||||||
record.pool = pool_id;
|
|
||||||
record.session = xapi.sessionId;
|
|
||||||
|
|
||||||
return record;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return collection.add(records, {
|
|
||||||
'replace': true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})).then(function () {
|
})).then(function () {
|
||||||
xo.computeStats();
|
|
||||||
|
|
||||||
|
// Then listens for events.
|
||||||
return function loop() {
|
return function loop() {
|
||||||
return xapi.call('event.next').then(function (events) {
|
return xapi.call('event.next').then(function (events) {
|
||||||
_.each(events, function (event) {
|
_.each(events, function (event) {
|
||||||
var klass = event.class;
|
var operation = event.operation;
|
||||||
var collection = xobjs[xclasses_map[klass]];
|
var record = event.snapshot;
|
||||||
if (collection)
|
var ref = event.ref;
|
||||||
|
var type = event.class;
|
||||||
|
|
||||||
|
console.log(xapi.host, operation, type, ref);
|
||||||
|
|
||||||
|
// Normalizes the model.
|
||||||
|
record.$type = type;
|
||||||
|
record.$pool = pool_id;
|
||||||
|
|
||||||
|
if ('del' === event.operation)
|
||||||
{
|
{
|
||||||
var operation = event.operation;
|
xobjs.remove({ref: record});
|
||||||
var ref = event.ref;
|
}
|
||||||
|
else
|
||||||
console.log(xapi.host, operation, klass, ref);
|
{
|
||||||
|
xobjs.set({ref: record}, {remove: false});
|
||||||
if ('del' === event.operation)
|
|
||||||
{
|
|
||||||
collection.remove(event.ref);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var record = event.snapshot;
|
|
||||||
record.id = ref;
|
|
||||||
record.pool = pool_id;
|
|
||||||
|
|
||||||
collection.add(record, {'replace': true});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
xo.computeStats();
|
|
||||||
return loop();
|
return loop();
|
||||||
}, function (error) {
|
}, function (error) {
|
||||||
if ('SESSION_NOT_REGISTERED' === error[0])
|
if ('SESSION_NOT_REGISTERED' === error[0])
|
||||||
|
|||||||
Reference in New Issue
Block a user