Compare commits
80 Commits
xo-server/
...
xo-server/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2e3b317ef | ||
|
|
0edd39805b | ||
|
|
15189014d0 | ||
|
|
46ab3ed1ea | ||
|
|
edfbbe8c5a | ||
|
|
9d35295b47 | ||
|
|
b59d07fb5f | ||
|
|
9c0fda2b52 | ||
|
|
9ab1a980a7 | ||
|
|
07d91b8f50 | ||
|
|
5645921a8e | ||
|
|
9c17ac1c33 | ||
|
|
a80968aaa7 | ||
|
|
2589a15e0a | ||
|
|
950efde3b8 | ||
|
|
de8d5e4489 | ||
|
|
d6eefa5185 | ||
|
|
5ad0cffbb3 | ||
|
|
89bd00c65e | ||
|
|
b7e7a0df94 | ||
|
|
e3f02fc4d6 | ||
|
|
81bece9dc4 | ||
|
|
db4b05e9d2 | ||
|
|
0b9029c7b5 | ||
|
|
7fd9a65ce5 | ||
|
|
8cd2d52b3d | ||
|
|
d8e8c5504a | ||
|
|
7535f6af51 | ||
|
|
347effcf70 | ||
|
|
2b2176ba24 | ||
|
|
a0b2d9384d | ||
|
|
58cc047ffa | ||
|
|
cfacd90815 | ||
|
|
f46dca910b | ||
|
|
d362dfc359 | ||
|
|
4dff1ead06 | ||
|
|
ec8aa28aea | ||
|
|
e49abd909d | ||
|
|
ce2c700f1d | ||
|
|
dcb9f90a8c | ||
|
|
c936d4f5a3 | ||
|
|
44f9292bfd | ||
|
|
259fae0134 | ||
|
|
04c28824b2 | ||
|
|
c5bf4d1723 | ||
|
|
b72a7f2c22 | ||
|
|
71cef03a4b | ||
|
|
13abde0b0f | ||
|
|
e4850e0a48 | ||
|
|
1c225ff1ec | ||
|
|
1b4e80858d | ||
|
|
4f683edcb9 | ||
|
|
834fe6da45 | ||
|
|
35678661d1 | ||
|
|
1ef6b0b5f9 | ||
|
|
d1cf246600 | ||
|
|
093e09c732 | ||
|
|
5c25a82a2b | ||
|
|
2f224fc26e | ||
|
|
2969f8da36 | ||
|
|
8959d12746 | ||
|
|
6e6574a235 | ||
|
|
bf3eea4a41 | ||
|
|
044594c614 | ||
|
|
aeba346a4e | ||
|
|
779a3d2d70 | ||
|
|
6a8667cc4b | ||
|
|
29972fe54e | ||
|
|
cf9e9e4fcf | ||
|
|
ddd14999d7 | ||
|
|
cfc9c60794 | ||
|
|
71f206446c | ||
|
|
e368465591 | ||
|
|
2f4314257c | ||
|
|
204d32b9fd | ||
|
|
a66f522e77 | ||
|
|
9016b7be90 | ||
|
|
545da61855 | ||
|
|
4c22f2525c | ||
|
|
2e5c4bb5ee |
@@ -9,7 +9,7 @@ root = true
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = 4
|
||||
indent_size = 2
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespaces = true
|
||||
|
||||
144
.jshintrc
144
.jshintrc
@@ -1,22 +1,126 @@
|
||||
{
|
||||
"bitwise": true,
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"latedef": true,
|
||||
"laxbreak": true,
|
||||
"maxcomplexity": 10,
|
||||
"maxdepth": 5,
|
||||
"maxlen": 80,
|
||||
"maxparams": 4,
|
||||
"maxstatements": 15,
|
||||
"newcap": true,
|
||||
"node": true,
|
||||
"noempty": true,
|
||||
"nonew": true,
|
||||
"quotmark": true,
|
||||
"smarttabs": true,
|
||||
"strict": false,
|
||||
"trailing": true,
|
||||
"undef": true,
|
||||
"unused": true
|
||||
// --------------------------------------------------------------------
|
||||
// JSHint Configuration, Node.js Edition
|
||||
// --------------------------------------------------------------------
|
||||
//
|
||||
// This is an options template for [JSHint][1], forked from
|
||||
// haschek's [JSHint template][2]:
|
||||
//
|
||||
// * the environment has been changed to `node`;
|
||||
// * recent options were added;
|
||||
// * coding style has been adapted to node (e.g. 2 spaces
|
||||
// indenting, global use strict).
|
||||
//
|
||||
// [1]: http://www.jshint.com/
|
||||
// [2]: https://gist.github.com/haschek/2595796
|
||||
//
|
||||
// @author Julien Fontanet <julien.fontanet@isonoe.net>
|
||||
// @license http://unlicense.org/
|
||||
|
||||
// == Enforcing Options ===============================================
|
||||
//
|
||||
// These options tell JSHint to be more strict towards your code. Use
|
||||
// them if you want to allow only a safe subset of JavaScript, very
|
||||
// useful when your codebase is shared with a big number of developers
|
||||
// with different skill levels.
|
||||
|
||||
"bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.).
|
||||
"camelcase" : true, // Require variable names to use either camelCase or UPPER_CASE styles.
|
||||
"curly" : true, // Require {} for every new block or scope.
|
||||
"eqeqeq" : true, // Require triple equals i.e. `===`.
|
||||
"forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`.
|
||||
"freeze" : true, // Prohibit modification of native objects' prototypes.
|
||||
"immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
|
||||
"indent" : 2, // Specify indentation spacing
|
||||
"latedef" : true, // Prohibit variable use before definition.
|
||||
"newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`.
|
||||
"noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
|
||||
"noempty" : true, // Prohibit use of empty blocks.
|
||||
"nonew" : true, // Prohibit use of constructors for side-effects.
|
||||
"plusplus" : false, // Prohibit use of `++` & `--`.
|
||||
"quotmark" : "'", // Require single quotes.
|
||||
"undef" : true, // Require all non-global variables be declared before they are used.
|
||||
"unused" : true, // Prohibit unused variables.
|
||||
"strict" : true, // Require `use strict` pragma in every function.
|
||||
"trailing" : true, // Prohibit trailing whitespaces.
|
||||
"maxparams" : 4, // Prohibit more than 4 parameters per function definition.
|
||||
"maxdepth" : 3, // Prohibit nesting more than 3 control blocks.
|
||||
"maxstatements" : 20, // Prohibit more than 20 statements per function.
|
||||
"maxcomplexity" : 7, // Prohibit having to much branches in your code.
|
||||
"maxlen" : 80, // Prohibit line with more than 80 characters.
|
||||
|
||||
// == Relaxing Options ================================================
|
||||
//
|
||||
// These options allow you to suppress certain types of warnings. Use
|
||||
// them only if you are absolutely positive that you know what you are
|
||||
// doing.
|
||||
|
||||
"asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons).
|
||||
"boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
|
||||
"debug" : false, // Allow debugger statements e.g. browser breakpoints.
|
||||
"eqnull" : false, // Tolerate use of `== null`.
|
||||
"es5" : false, // Allow EcmaScript 5 syntax.
|
||||
"esnext" : true, // Allow ES.next specific features such as `const` and `let`.
|
||||
"evil" : false, // Tolerate use of `eval`.
|
||||
"expr" : true, // Tolerate `ExpressionStatement` as Programs. (Allowed for Mocha.)
|
||||
"funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside.
|
||||
"gcl" : false, // Makes JSHint compatible with Google Closure Compiler.
|
||||
"globalstrict" : true, // Allow global "use strict" (also enables 'strict').
|
||||
"iterator" : false, // Allow usage of __iterator__ property.
|
||||
"lastsemic" : false, // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block.
|
||||
"laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
|
||||
"laxcomma" : false, // Suppress warnings about comma-first coding style.
|
||||
"loopfunc" : false, // Allow functions to be defined within loops.
|
||||
"maxerr" : 50, // Maximum errors before stopping.
|
||||
"moz" : false, // Tolerate Mozilla JavaScript extensions.
|
||||
"notypeof" : false, // Tolerate invalid typeof values.
|
||||
"multistr" : false, // Tolerate multi-line strings.
|
||||
"proto" : false, // Tolerate __proto__ property. This property is deprecated.
|
||||
"scripturl" : false, // Tolerate script-targeted URLs.
|
||||
"smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only.
|
||||
"shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
|
||||
"sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
|
||||
"supernew" : false, // Tolerate `new function () { ... };` and `new Object;`.
|
||||
"validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function.
|
||||
|
||||
// == Environments ====================================================
|
||||
//
|
||||
// These options pre-define global variables that are exposed by
|
||||
// popular JavaScript libraries and runtime environments—such as
|
||||
// browser or node.js.
|
||||
|
||||
"browser" : false, // Standard browser globals e.g. `window`, `document`.
|
||||
"couch" : false, // Enable globals exposed by CouchDB.
|
||||
"devel" : false, // Allow development statements e.g. `console.log();`.
|
||||
"dojo" : false, // Enable globals exposed by Dojo Toolkit.
|
||||
"jquery" : false, // Enable globals exposed by jQuery JavaScript library.
|
||||
"mootools" : false, // Enable globals exposed by MooTools JavaScript framework.
|
||||
"node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment.
|
||||
"nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape.
|
||||
"phantom" : false, // Enable globals exposed by PhantomJS.
|
||||
"prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework.
|
||||
"rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment.
|
||||
"worker" : false, // Enable globals exposed when running inside a Web Worker.
|
||||
"wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host.
|
||||
"yui" : false, // Enable globals exposed by YUI.
|
||||
|
||||
// == JSLint Legacy ===================================================
|
||||
//
|
||||
// These options are legacy from JSLint. Aside from bug fixes they will
|
||||
// not be improved in any way and might be removed at any point.
|
||||
|
||||
"nomen" : false, // Prohibit use of initial or trailing underbars in names.
|
||||
"onevar" : false, // Allow only one `var` statement per function.
|
||||
"passfail" : false, // Stop on first error.
|
||||
"white" : false, // Check against strict whitespace and indentation rules.
|
||||
|
||||
"globals": {
|
||||
// Mocha.
|
||||
"after" : false,
|
||||
"afterEach" : false,
|
||||
"before" : false,
|
||||
"beforeEach" : false,
|
||||
"describe" : false,
|
||||
"it" : false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ It contains all the logic of XO and handles:
|
||||
- users authentication and authorizations (work in progress);
|
||||
- a JSON-RPC based interface for XO clients (i.e. [XO-Web](https://github.com/vatesfr/xo-web)).
|
||||
|
||||
[](https://david-dm.org/vatesfr/xo-server)
|
||||
[](https://david-dm.org/vatesfr/xo-server#info=devDependencies)
|
||||
|
||||
___
|
||||
|
||||
## Installation
|
||||
|
||||
Manual install procedure is [available here](https://github.com/vatesfr/xo/blob/master/installation.md)
|
||||
|
||||
7
bin/xo-server
Executable file
7
bin/xo-server
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
//====================================================================
|
||||
|
||||
require('exec-promise')(require('../'));
|
||||
6
index.js
Normal file
6
index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
//====================================================================
|
||||
|
||||
require('coffee-script/register');
|
||||
module.exports = require('./src/main');
|
||||
88
package.json
88
package.json
@@ -1,58 +1,60 @@
|
||||
{
|
||||
"author": {
|
||||
"name": "Julien Fontanet",
|
||||
"email": "julien.fontanet@vates.fr",
|
||||
"url": "http://vates.fr/"
|
||||
},
|
||||
"name": "XO-Server",
|
||||
"version": "3.1.0",
|
||||
"name": "xo-server",
|
||||
"version": "3.5.0-alpha1",
|
||||
"license": "AGPL3",
|
||||
"description": "Server part of Xen-Orchestra",
|
||||
"keywords": [
|
||||
"xen",
|
||||
"orchestra",
|
||||
"xen-orchestra",
|
||||
"server"
|
||||
],
|
||||
"homepage": "http://github.com/vatesfr/xo-server/",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vatesfr/xo-server/issues"
|
||||
},
|
||||
"author": "Julien Fontanet <julien.fontanet@vates.fr>",
|
||||
"preferGlobal": true,
|
||||
"directories": {
|
||||
"bin": "bin"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/vatesfr/xo-server.git"
|
||||
},
|
||||
"main": "src/main.coffee",
|
||||
"dependencies": {
|
||||
"coffee-script": "~1.6.3",
|
||||
"connect": "~2.11.2",
|
||||
"extendable": "0.0.6",
|
||||
"backoff": "~2.4.0",
|
||||
"bluebird": "^2.2.2",
|
||||
"coffee-script": "~1.7.1",
|
||||
"connect": "^3.1.0",
|
||||
"event-to-promise": "^0.3.0",
|
||||
"exec-promise": "^0.3.0",
|
||||
"extendable": "~0.0.6",
|
||||
"fibers": "~1.0.1",
|
||||
"hashy": "~0.2.1",
|
||||
"hiredis": "~0.1.16",
|
||||
"js-yaml": "~2.1.3",
|
||||
"hashy": "~0.3.6",
|
||||
"hiredis": "~0.1.17",
|
||||
"http-server-plus": "^0.2.3",
|
||||
"js-yaml": "~3.1.0",
|
||||
"nconf": "~0.6.9",
|
||||
"q": "~0.9.7",
|
||||
"redis": "~0.9.1",
|
||||
"require-tree": "~0.3.2",
|
||||
"schema-inspector": "~1.3.8",
|
||||
"sync": "~0.2.2",
|
||||
"then-redis": "~0.3.9",
|
||||
"underscore": "~1.5.2",
|
||||
"redis": "~0.11.0",
|
||||
"require-tree": "~0.3.3",
|
||||
"schema-inspector": "^1.4.5",
|
||||
"serve-static": "^1.4.0",
|
||||
"then-redis": "~0.3.12",
|
||||
"underscore": "~1.6.0",
|
||||
"ws": "~0.4.31",
|
||||
"xml2js": "~0.4.1",
|
||||
"xmlrpc": "~1.1.1"
|
||||
"xml2js": "~0.4.4",
|
||||
"xmlrpc": "~1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "~1.8.1",
|
||||
"glob": "~3.2.8",
|
||||
"mocha": "~1.14.0",
|
||||
"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"
|
||||
"chai": "~1.9.1",
|
||||
"glob": "~4.0.4",
|
||||
"mocha": "^1.21.0",
|
||||
"node-inspector": "^0.7.4",
|
||||
"sinon": "^1.10.3"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "./run-tests"
|
||||
},
|
||||
"license": "AGPL3"
|
||||
"start": "node bin/xo-server",
|
||||
"test": "coffee run-tests"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,6 @@
|
||||
# Tests runner.
|
||||
$mocha = require 'mocha'
|
||||
|
||||
# Promises support for Mocha.
|
||||
(require 'mocha-as-promised')()
|
||||
|
||||
# Used to find the specification files.
|
||||
$glob = require 'glob'
|
||||
|
||||
|
||||
@@ -389,7 +389,7 @@ class $MappedCollection extends $EventEmitter
|
||||
return if $_.isEmpty items
|
||||
|
||||
# An update is similar to an exit followed by an enter.
|
||||
@_emitEvent 'exit', items unless areNew
|
||||
@_removeItems items unless areNew
|
||||
|
||||
$each items, (item) =>
|
||||
return unless @_runHook 'beforeUpdate', item
|
||||
@@ -426,15 +426,14 @@ class $MappedCollection extends $EventEmitter
|
||||
updateValue item, 'data', @_rules[ruleName].data
|
||||
updateValue item, 'val', @_rules[ruleName].val
|
||||
|
||||
return unless @_runHook 'beforeSave', item
|
||||
|
||||
# Registers the new item.
|
||||
@_byKey[item.key] = item
|
||||
unless @_runHook 'beforeSave', item
|
||||
# FIXME: should not be removed, only not saved.
|
||||
delete @_byKey[item.key]
|
||||
|
||||
# Really inserts the items and trigger events.
|
||||
$each items, (item) => @_byKey[item.key] = item
|
||||
@_emitEvent 'enter', items
|
||||
|
||||
# TODO: checks for loops.
|
||||
|
||||
#=====================================================================
|
||||
|
||||
module.exports = {$MappedCollection}
|
||||
|
||||
@@ -83,3 +83,39 @@ describe '$MappedCollection', ->
|
||||
]
|
||||
|
||||
$expect(beforeSave.called).to.be.true
|
||||
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
describe 'adding new items', ->
|
||||
|
||||
beforeEach ->
|
||||
collection.rule test: {}
|
||||
collection.dispatch = -> 'test'
|
||||
|
||||
#------------------------------
|
||||
|
||||
it 'should trigger three `enter` events', ->
|
||||
keySpy = $sinon.spy()
|
||||
ruleSpy = $sinon.spy()
|
||||
anySpy = $sinon.spy()
|
||||
|
||||
collection.on 'key=a key', keySpy
|
||||
collection.on 'rule=test', ruleSpy
|
||||
collection.on 'any', anySpy
|
||||
|
||||
collection.set {
|
||||
'a key': 'a value'
|
||||
}
|
||||
|
||||
item = collection.getRaw 'a key'
|
||||
|
||||
# TODO: items can be an array or a object (it is not defined).
|
||||
$expect(keySpy.args).to.deep.equal [
|
||||
['enter', item]
|
||||
]
|
||||
$expect(ruleSpy.args).to.deep.equal [
|
||||
['enter', [item]]
|
||||
]
|
||||
$expect(anySpy.args).to.deep.equal [
|
||||
['enter', {'a key': item}]
|
||||
]
|
||||
|
||||
81
src/api.js
81
src/api.js
@@ -1,3 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
var $_ = require('underscore');
|
||||
|
||||
var $requireTree = require('require-tree');
|
||||
@@ -19,6 +23,12 @@ function $deprecated(fn)
|
||||
};
|
||||
}
|
||||
|
||||
var wrap = function (val) {
|
||||
return function () {
|
||||
return val;
|
||||
};
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// TODO: Helper functions that could be written:
|
||||
@@ -30,9 +40,9 @@ helpers.checkPermission = function (permission)
|
||||
{
|
||||
// TODO: Handle token permission.
|
||||
|
||||
var user_id = this.session.get('user_id');
|
||||
var userId = this.session.get('user_id', undefined);
|
||||
|
||||
if (undefined === user_id)
|
||||
if (undefined === userId)
|
||||
{
|
||||
throw Api.err.UNAUTHORIZED;
|
||||
}
|
||||
@@ -42,7 +52,7 @@ helpers.checkPermission = function (permission)
|
||||
return;
|
||||
}
|
||||
|
||||
var user = $wait(this.users.first(user_id));
|
||||
var user = $wait(this.users.first(userId));
|
||||
// The user MUST exist at this time.
|
||||
|
||||
if (!user.hasPermission(permission))
|
||||
@@ -128,7 +138,17 @@ Api.prototype.exec = function (session, request) {
|
||||
throw Api.err.INVALID_METHOD;
|
||||
}
|
||||
|
||||
return method.call(ctx);
|
||||
if ('permission' in method)
|
||||
{
|
||||
helpers.checkPermission.call(ctx, method.permission)
|
||||
}
|
||||
|
||||
if (method.params)
|
||||
{
|
||||
helpers.getParams.call(ctx, method.params);
|
||||
}
|
||||
|
||||
return method.call(ctx, request.params);
|
||||
};
|
||||
|
||||
Api.prototype.getMethod = function (name) {
|
||||
@@ -158,13 +178,13 @@ Api.prototype.getMethod = function (name) {
|
||||
|
||||
// No entry found, looking for a catch-all method.
|
||||
current = Api.fn;
|
||||
var catch_all;
|
||||
var catchAll;
|
||||
for (i = 0; (i < n) && (current = current[parts[i]]); ++i)
|
||||
{
|
||||
catch_all = current.__catchAll || catch_all;
|
||||
catchAll = current.__catchAll || catchAll;
|
||||
}
|
||||
|
||||
return catch_all;
|
||||
return catchAll;
|
||||
};
|
||||
|
||||
module.exports = Api;
|
||||
@@ -214,9 +234,14 @@ Api.err = {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
var $register = function (path, fn) {
|
||||
var $register = function (path, fn, params) {
|
||||
var component, current;
|
||||
|
||||
if (params)
|
||||
{
|
||||
fn.params = params;
|
||||
}
|
||||
|
||||
if (!$_.isArray(path))
|
||||
{
|
||||
path = path.split('.');
|
||||
@@ -233,7 +258,7 @@ var $register = function (path, fn) {
|
||||
{
|
||||
current[path[n]] = fn;
|
||||
}
|
||||
else if ($_.isObject(fn))
|
||||
else if ($_.isObject(fn) && !$_.isArray(fn))
|
||||
{
|
||||
// If it is not an function but an object, copies its
|
||||
// properties.
|
||||
@@ -248,10 +273,7 @@ var $register = function (path, fn) {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Wrap this value in a function.
|
||||
current[path[n]] = function () {
|
||||
return fn;
|
||||
};
|
||||
current[path[n]] = wrap(fn);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -259,7 +281,7 @@ Api.fn = $requireTree('./api');
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
$register('api.getVersion', '0.1');
|
||||
$register('system.getVersion', wrap('0.1'));
|
||||
|
||||
$register('xo.getAllObjects', function () {
|
||||
return this.getObjects();
|
||||
@@ -268,7 +290,7 @@ $register('xo.getAllObjects', function () {
|
||||
// Returns the list of available methods similar to XML-RPC
|
||||
// introspection (http://xmlrpc-c.sourceforge.net/introspection.html).
|
||||
(function () {
|
||||
var methods = [];
|
||||
var methods = {};
|
||||
|
||||
(function browse(container, path) {
|
||||
var n = path.length;
|
||||
@@ -276,7 +298,11 @@ $register('xo.getAllObjects', function () {
|
||||
path[n] = key;
|
||||
if ($_.isFunction(content))
|
||||
{
|
||||
methods.push(path.join('.'));
|
||||
methods[path.join('.')] = {
|
||||
description: content.description,
|
||||
params: content.params || {},
|
||||
permission: content.permission,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -286,5 +312,26 @@ $register('xo.getAllObjects', function () {
|
||||
path.pop();
|
||||
})(Api.fn, []);
|
||||
|
||||
$register('system.listMethods', methods);
|
||||
$register('system.listMethods', wrap($_.keys(methods)));
|
||||
$register('system.methodSignature', function (params) {
|
||||
var method = methods[params.name];
|
||||
|
||||
if (!method)
|
||||
{
|
||||
this.throw('NO_SUCH_OBJECT');
|
||||
}
|
||||
|
||||
// XML-RPC can have multiple signatures per method.
|
||||
return [
|
||||
// XML-RPC requires the method name.
|
||||
$_.extend({name: name}, method)
|
||||
];
|
||||
}, {
|
||||
name: {
|
||||
description: 'method to describe',
|
||||
type: 'string',
|
||||
},
|
||||
});
|
||||
|
||||
$register('system.getMethodsInfo', wrap(methods));
|
||||
})();
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
exports.set = ->
|
||||
params = @getParams {
|
||||
id: { type: 'string' }
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
name_label: { type: 'string', optional: true }
|
||||
|
||||
name_description: { type: 'string', optional: true }
|
||||
|
||||
enabled: { type: 'boolean', optional: true }
|
||||
}
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
#=====================================================================
|
||||
|
||||
exports.set = (params) ->
|
||||
try
|
||||
host = @getObject params.id
|
||||
catch
|
||||
@@ -26,4 +17,118 @@ exports.set = ->
|
||||
}
|
||||
continue unless param of params
|
||||
|
||||
xapi.call "host.set_#{field}", host.ref, params[param]
|
||||
$wait xapi.call "host.set_#{field}", host.ref, params[param]
|
||||
|
||||
return true
|
||||
exports.set.permission = 'admin'
|
||||
exports.set.params =
|
||||
id: type: 'string'
|
||||
name_label:
|
||||
type: 'string'
|
||||
optional: true
|
||||
name_description:
|
||||
type: 'string'
|
||||
optional: true
|
||||
enabled:
|
||||
type: 'boolean'
|
||||
optional: true
|
||||
|
||||
exports.restart = ({id}) ->
|
||||
@checkPermission 'admin'
|
||||
|
||||
try
|
||||
host = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI host
|
||||
|
||||
$wait xapi.call 'host.disable', host.ref
|
||||
$wait xapi.call 'host.reboot', host.ref
|
||||
|
||||
return true
|
||||
exports.restart.permission = 'admin'
|
||||
exports.restart.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.restart_agent = ({id}) ->
|
||||
try
|
||||
host = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI host
|
||||
|
||||
$wait xapi.call 'host.restart_agent', host.ref
|
||||
|
||||
return true
|
||||
exports.restart_agent.permission = 'admin'
|
||||
exports.restart_agent.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.stop = ({id}) ->
|
||||
try
|
||||
host = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI host
|
||||
|
||||
$wait xapi.call 'host.disable', host.ref
|
||||
$wait xapi.call 'host.shutdown', host.ref
|
||||
|
||||
return true
|
||||
exports.stop.permission = 'admin'
|
||||
exports.stop.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.detach = ({id}) ->
|
||||
try
|
||||
host = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI host
|
||||
|
||||
$wait xapi.call 'pool.eject', host.ref
|
||||
|
||||
return true
|
||||
exports.detach.permission = 'admin'
|
||||
exports.detach.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.enable = ({id}) ->
|
||||
try
|
||||
host = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI host
|
||||
|
||||
$wait xapi.call 'host.enable', host.ref
|
||||
|
||||
return true
|
||||
exports.stop.permission = 'admin'
|
||||
exports.stop.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.disable = ({id}) ->
|
||||
try
|
||||
host = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI host
|
||||
|
||||
$wait xapi.call 'host.disable', host.ref
|
||||
|
||||
return true
|
||||
exports.stop.permission = 'admin'
|
||||
exports.stop.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
19
src/api/message.coffee
Normal file
19
src/api/message.coffee
Normal file
@@ -0,0 +1,19 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.delete = ({id}) ->
|
||||
try
|
||||
message = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI message
|
||||
|
||||
$wait xapi.call 'message.destroy', message.ref
|
||||
|
||||
return true
|
||||
exports.delete.permission = 'admin'
|
||||
exports.delete.params =
|
||||
id:
|
||||
type: 'string'
|
||||
@@ -1,15 +1,8 @@
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.set = ->
|
||||
params = @getParams {
|
||||
id: { type: 'string' }
|
||||
|
||||
name_label: { type: 'string', optional: true }
|
||||
|
||||
name_description: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
|
||||
try
|
||||
pool = @getObject params.id
|
||||
catch
|
||||
@@ -23,4 +16,16 @@ exports.set = ->
|
||||
}
|
||||
continue unless param of params
|
||||
|
||||
xapi.call "pool.set_#{field}", pool.ref, params[param]
|
||||
$wait xapi.call "pool.set_#{field}", pool.ref, params[param]
|
||||
|
||||
return true
|
||||
exports.set.permission = 'admin'
|
||||
exports.set.params =
|
||||
id:
|
||||
type: 'string'
|
||||
name_label:
|
||||
type: 'string'
|
||||
optional: true
|
||||
name_description:
|
||||
type: 'string'
|
||||
optional: true
|
||||
|
||||
@@ -6,46 +6,37 @@
|
||||
# Could we use tokens instead?
|
||||
|
||||
# Adds a new server.
|
||||
exports.add = ->
|
||||
{host, username, password} = @getParams {
|
||||
host: { type: 'string' }
|
||||
username: { type: 'string' }
|
||||
password: { type: 'string' }
|
||||
}
|
||||
|
||||
# Current user must be administrator.
|
||||
@checkPermission 'admin'
|
||||
|
||||
# Adds the server.
|
||||
exports.add = ({host, username, password}) ->
|
||||
server = $wait @servers.add {
|
||||
host
|
||||
username
|
||||
password
|
||||
}
|
||||
|
||||
# Returns the identifier of the newly registered server.
|
||||
server.id
|
||||
return server.id
|
||||
exports.add.description = 'Add a new Xen server to XO'
|
||||
exports.add.permission = 'admin'
|
||||
exports.add.params =
|
||||
host:
|
||||
type: 'string'
|
||||
username:
|
||||
type: 'string'
|
||||
password:
|
||||
type: 'string'
|
||||
|
||||
# Removes an existing server.
|
||||
exports.remove = ->
|
||||
{id} = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
# Current user must be administrator.
|
||||
@checkPermission 'admin'
|
||||
|
||||
exports.remove = ({id}) ->
|
||||
# Throws an error if the server did not exist.
|
||||
@throw 'NO_SUCH_OBJECT' unless $wait @servers.remove id
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
return true
|
||||
exports.remove.permission = 'admin'
|
||||
exports.remove.params =
|
||||
id:
|
||||
type: 'string'
|
||||
|
||||
# Returns all servers.
|
||||
exports.getAll = ->
|
||||
# Only an administrator can see all servers.
|
||||
@checkPermission 'admin'
|
||||
|
||||
# Retrieves the servers.
|
||||
servers = $wait @servers.get()
|
||||
|
||||
@@ -53,21 +44,11 @@ exports.getAll = ->
|
||||
for server, i in servers
|
||||
servers[i] = @getServerPublicProperties server
|
||||
|
||||
# Returns the servers.
|
||||
servers
|
||||
return servers
|
||||
exports.getAll.permission = 'admin'
|
||||
|
||||
# Changes the properties of an existing server.
|
||||
exports.set = ->
|
||||
{id, host, username, password} = @getParams {
|
||||
id: { type: 'string' }
|
||||
host: { type: 'string', optional: true }
|
||||
username: { type: 'string', optional: true }
|
||||
password: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
# Only an administrator can modify an server.
|
||||
@checkPermission 'admin'
|
||||
|
||||
exports.set = ({id, host, username, password}) ->
|
||||
# Retrieves the server.
|
||||
server = $wait @servers.first id
|
||||
|
||||
@@ -82,8 +63,21 @@ exports.set = ->
|
||||
# Updates the server.
|
||||
$wait @servers.update server
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
return true
|
||||
exports.set.permission = 'admin'
|
||||
exports.set.params =
|
||||
id:
|
||||
type: 'string'
|
||||
host:
|
||||
type: 'string'
|
||||
optional: true
|
||||
username:
|
||||
type: 'string'
|
||||
optional: true
|
||||
password:
|
||||
type: 'string'
|
||||
optional: true
|
||||
|
||||
|
||||
# Connects to an existing server.
|
||||
exports.connect = ->
|
||||
|
||||
@@ -3,12 +3,7 @@
|
||||
#=====================================================================
|
||||
|
||||
# Signs a user in with its email/password.
|
||||
exports.signInWithPassword = ->
|
||||
{email, password} = @getParams {
|
||||
email: { type: 'string' }
|
||||
password: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.signInWithPassword = ({email, password}) ->
|
||||
@throw 'ALREADY_AUTHENTICATED' if @session.has 'user_id'
|
||||
|
||||
# Gets the user.
|
||||
@@ -22,14 +17,14 @@ exports.signInWithPassword = ->
|
||||
@session.set 'user_id', user.get 'id'
|
||||
|
||||
# Returns the user.
|
||||
@getUserPublicProperties user
|
||||
return @getUserPublicProperties user
|
||||
exports.signInWithPassword.params = {
|
||||
email: { type: 'string' }
|
||||
password: { type: 'string' }
|
||||
}
|
||||
|
||||
# Signs a user in with a token.
|
||||
exports.signInWithToken = ->
|
||||
{token} = @getParams {
|
||||
token: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.signInWithToken = ({token}) ->
|
||||
@throw 'ALREADY_AUTHENTICATED' if @session.has 'user_id'
|
||||
|
||||
# Gets the token.
|
||||
@@ -43,21 +38,24 @@ exports.signInWithToken = ->
|
||||
|
||||
# Returns the user.
|
||||
user = $wait @users.first user_id
|
||||
@getUserPublicProperties user
|
||||
return @getUserPublicProperties user
|
||||
exports.signInWithToken.params = {
|
||||
token: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.signOut = ->
|
||||
@session.unset 'token_id'
|
||||
@session.unset 'user_id'
|
||||
|
||||
true
|
||||
return true
|
||||
|
||||
# Gets the the currently signed in user.
|
||||
exports.getUser = ->
|
||||
id = @session.get 'user_id'
|
||||
id = @session.get 'user_id', null
|
||||
|
||||
# If the user is not signed in, returns null.
|
||||
return null unless id?
|
||||
|
||||
# Returns the user.
|
||||
user = $wait @users.first id
|
||||
@getUserPublicProperties user
|
||||
return @getUserPublicProperties user
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
exports.set = ->
|
||||
params = @getParams {
|
||||
id: { type: 'string' }
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
name_label: { type: 'string', optional: true }
|
||||
|
||||
name_description: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
#=====================================================================
|
||||
|
||||
exports.set = (params) ->
|
||||
try
|
||||
SR = @getObject params.id
|
||||
catch
|
||||
@@ -23,21 +16,31 @@ exports.set = ->
|
||||
}
|
||||
continue unless param of params
|
||||
|
||||
xapi.call "SR.set_#{field}", SR.ref, params[param]
|
||||
$wait xapi.call "SR.set_#{field}", SR.ref, params[param]
|
||||
|
||||
exports.scan = ->
|
||||
params = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
return true
|
||||
exports.set.permission = 'admin'
|
||||
exports.set.params = {
|
||||
id: { type: 'string' }
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
name_label: { type: 'string', optional: true }
|
||||
|
||||
name_description: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
|
||||
exports.scan = ({id}) ->
|
||||
try
|
||||
SR = @getObject params.id
|
||||
SR = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI SR
|
||||
|
||||
xapi.call "SR.scan", SR.ref
|
||||
$wait xapi.call 'SR.scan', SR.ref
|
||||
|
||||
return true
|
||||
exports.scan.permission = 'admin'
|
||||
exports.scan.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
@@ -14,15 +14,10 @@ exports.create = ->
|
||||
# Creates the token.
|
||||
token = $wait @tokens.generate userId
|
||||
|
||||
# Returns its identifier.
|
||||
token.id
|
||||
return token.id
|
||||
|
||||
# Deletes a token.
|
||||
exports.delete = ->
|
||||
{token: tokenId} = @getParams {
|
||||
token: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.delete = ({token: tokenId}) ->
|
||||
# Gets the token.
|
||||
token = $wait @tokens.first tokenId
|
||||
@throw 'NO_SUCH_OBJECT' unless token?
|
||||
@@ -30,5 +25,7 @@ exports.delete = ->
|
||||
# Deletes the token.
|
||||
$wait @tokens.remove tokenId
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
return true
|
||||
exports.delete.params = {
|
||||
token: { type: 'string' }
|
||||
}
|
||||
|
||||
@@ -3,51 +3,36 @@
|
||||
#=====================================================================
|
||||
|
||||
# Creates a new user.
|
||||
exports.create = ->
|
||||
{email, password, permission} = @getParams {
|
||||
email: { type: 'string' }
|
||||
password: { type: 'string' }
|
||||
permission: { type: 'string', optional: true}
|
||||
}
|
||||
@throw 'INVALID_PARAMS' unless email? and password?
|
||||
|
||||
# Current user must be administrator.
|
||||
@checkPermission 'admin'
|
||||
|
||||
exports.create = ({email, password, permission}) ->
|
||||
# Creates the user.
|
||||
user = $wait @users.create email, password, permission
|
||||
|
||||
# Returns the identifier of the new user.
|
||||
user.id
|
||||
return user.id
|
||||
exports.create.permission = 'admin'
|
||||
exports.create.params = {
|
||||
email: { type: 'string' }
|
||||
password: { type: 'string' }
|
||||
permission: { type: 'string', optional: true}
|
||||
}
|
||||
|
||||
# Deletes an existing user.
|
||||
#
|
||||
# FIXME: a user should not be able to delete itself.
|
||||
exports.delete = ->
|
||||
{id} = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
# Current user must be administrator.
|
||||
@checkPermission 'admin'
|
||||
exports.delete = ({id}) ->
|
||||
# The user cannot delete himself.
|
||||
@throw 'INVALID_PARAMS' if id is @session.get 'user_id'
|
||||
|
||||
# Throws an error if the user did not exist.
|
||||
@throw 'NO_SUCH_OBJECT' unless $wait @users.remove id
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
return true
|
||||
exports.delete.permission = 'admin'
|
||||
exports.delete.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
# Changes the password of the current user.
|
||||
exports.changePassword = ->
|
||||
{old, new: newP} = @getParams {
|
||||
old: { type: 'string' }
|
||||
new: { type: 'string' }
|
||||
}
|
||||
@throw 'INVALID_PARAMS' unless old? and newP?
|
||||
|
||||
# Current user must be signed in.
|
||||
@checkPermission()
|
||||
|
||||
exports.changePassword = ({old, new: newP}) ->
|
||||
# Gets the current user (which MUST exist).
|
||||
user = $wait @users.first @session.get 'user_id'
|
||||
|
||||
@@ -60,15 +45,15 @@ exports.changePassword = ->
|
||||
# Updates the user.
|
||||
$wait @users.update user
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
return true
|
||||
exports.changePassword.permission = '' # Signed in.
|
||||
exports.changePassword.params = {
|
||||
old: { type: 'string' }
|
||||
new: { type: 'string' }
|
||||
}
|
||||
|
||||
# Returns the user with a given identifier.
|
||||
exports.get = ->
|
||||
{id} = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.get = ({id}) ->
|
||||
# Only an administrator can see another user.
|
||||
@checkPermission 'admin' unless @session.get 'user_id' is id
|
||||
|
||||
@@ -78,14 +63,13 @@ exports.get = ->
|
||||
# Throws an error if it did not exist.
|
||||
@throw 'NO_SUCH_OBJECT' unless user
|
||||
|
||||
# Returns public properties.
|
||||
@getUserPublicProperties user
|
||||
return @getUserPublicProperties user
|
||||
exports.get.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
# Returns all users.
|
||||
exports.getAll = ->
|
||||
# Only an administrator can see all users.
|
||||
@checkPermission 'admin'
|
||||
|
||||
# Retrieves the users.
|
||||
users = $wait @users.get()
|
||||
|
||||
@@ -93,21 +77,11 @@ exports.getAll = ->
|
||||
for user, i in users
|
||||
users[i] = @getUserPublicProperties user
|
||||
|
||||
# Returns the users.
|
||||
users
|
||||
return users
|
||||
exports.getAll.permission = 'admin'
|
||||
|
||||
# Changes the properties of an existing user.
|
||||
exports.set = ->
|
||||
{id, email, password, permission} = @getParams {
|
||||
id: { type: 'string' }
|
||||
email: { type: 'string', optional: true }
|
||||
password: { type: 'string', optional: true }
|
||||
permission: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
# Only an administrator can modify an user.
|
||||
@checkPermission 'admin'
|
||||
|
||||
exports.set = ({id, email, password, permission}) ->
|
||||
# Retrieves the user.
|
||||
user = $wait @users.first id
|
||||
|
||||
@@ -122,5 +96,11 @@ exports.set = ->
|
||||
# Updates the user.
|
||||
$wait @users.update user
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
return true
|
||||
exports.set.permission = 'admin'
|
||||
exports.set.params = {
|
||||
id: { type: 'string' }
|
||||
email: { type: 'string', optional: true }
|
||||
password: { type: 'string', optional: true }
|
||||
permission: { type: 'string', optional: true }
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
exports.delete = ->
|
||||
params = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
#=====================================================================
|
||||
|
||||
exports.delete = ({id}) ->
|
||||
try
|
||||
VBD = @getObject params.id
|
||||
catch
|
||||
@@ -14,16 +11,15 @@ exports.delete = ->
|
||||
xapi = @getXAPI VBD
|
||||
|
||||
# TODO: check if VBD is attached before
|
||||
xapi.call "VBD.destroy", VBD.ref
|
||||
$wait xapi.call 'VBD.destroy', VBD.ref
|
||||
|
||||
exports.disconnect = ->
|
||||
params = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
return true
|
||||
exports.delete.permission = 'admin'
|
||||
exports.delete.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.disconnect = ({id}) ->
|
||||
try
|
||||
VBD = @getObject params.id
|
||||
catch
|
||||
@@ -32,4 +28,10 @@ exports.disconnect = ->
|
||||
xapi = @getXAPI VBD
|
||||
|
||||
# TODO: check if VBD is attached before
|
||||
xapi.call "VBD.unplug_force", VBD.ref
|
||||
$wait xapi.call 'VBD.unplug_force', VBD.ref
|
||||
|
||||
return true
|
||||
exports.disconnect.permission = 'admin'
|
||||
exports.disconnect.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
@@ -1,38 +1,29 @@
|
||||
exports.delete = ->
|
||||
params = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
{isArray: $isArray} = require 'underscore'
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
{$wait} = require '../fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
exports.delete = ({id}) ->
|
||||
try
|
||||
VDI = @getObject params.id
|
||||
VDI = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VDI
|
||||
|
||||
# TODO: check if VDI is attached before
|
||||
xapi.call "VDI.destroy", VDI.ref
|
||||
$wait xapi.call 'VDI.destroy', VDI.ref
|
||||
|
||||
exports.set = ->
|
||||
params = @getParams {
|
||||
# Identifier of the VDI to update.
|
||||
id: { type: 'string' }
|
||||
|
||||
name_label: { type: 'string', optional: true }
|
||||
|
||||
name_description: { type: 'string', optional: true }
|
||||
|
||||
# size of VDI
|
||||
size: { type: 'integer' }
|
||||
|
||||
}
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
return true
|
||||
exports.delete.permission = 'admin'
|
||||
exports.delete.params =
|
||||
id:
|
||||
type: 'string'
|
||||
|
||||
exports.set = (params) ->
|
||||
try
|
||||
VDI = @getObject params.id
|
||||
catch
|
||||
@@ -52,7 +43,7 @@ exports.set = ->
|
||||
"cannot set new size below the current size (#{VDI.size})"
|
||||
)
|
||||
|
||||
xapi.call 'VDI.resize_online', ref, "#{size}"
|
||||
$wait xapi.call 'VDI.resize_online', ref, "#{size}"
|
||||
|
||||
# Other fields.
|
||||
for param, fields of {
|
||||
@@ -62,4 +53,18 @@ exports.set = ->
|
||||
continue unless param of params
|
||||
|
||||
for field in (if $isArray fields then fields else [fields])
|
||||
xapi.call "VDI.set_#{field}", ref, "#{params[param]}"
|
||||
$wait xapi.call "VDI.set_#{field}", ref, "#{params[param]}"
|
||||
|
||||
return true
|
||||
exports.set.permission = 'admin'
|
||||
exports.set.params = {
|
||||
# Identifier of the VDI to update.
|
||||
id: { type: 'string' }
|
||||
|
||||
name_label: { type: 'string', optional: true }
|
||||
|
||||
name_description: { type: 'string', optional: true }
|
||||
|
||||
# size of VDI
|
||||
size: { type: 'integer', optional: true }
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
$each
|
||||
} = require '../utils'
|
||||
|
||||
{
|
||||
$wait
|
||||
} = require '../fibers-utils'
|
||||
|
||||
$js2xml = do ->
|
||||
{Builder} = require 'xml2js'
|
||||
builder = new Builder {
|
||||
@@ -32,70 +36,13 @@ $isVMRunning = do ->
|
||||
#=====================================================================
|
||||
|
||||
# FIXME: Make the method as atomic as possible.
|
||||
exports.create = ->
|
||||
# Validates and retrieves the parameters.
|
||||
{
|
||||
installation
|
||||
name_label
|
||||
template
|
||||
VDIs
|
||||
VIFs
|
||||
} = @getParams {
|
||||
installation: {
|
||||
type: 'object'
|
||||
properties: {
|
||||
method: { type: 'string' }
|
||||
repository: { type: 'string' }
|
||||
}
|
||||
}
|
||||
|
||||
# Name of the new VM.
|
||||
name_label: { type: 'string' }
|
||||
|
||||
# TODO: add the install repository!
|
||||
# VBD.insert/eject
|
||||
# Also for the console!
|
||||
|
||||
# UUID of the template the VM will be created from.
|
||||
template: { type: 'string' }
|
||||
|
||||
# Virtual interfaces to create for the new VM.
|
||||
VIFs: {
|
||||
type: 'array'
|
||||
items: {
|
||||
type: 'object'
|
||||
properties: {
|
||||
# UUID of the network to create the interface in.
|
||||
network: { type: 'string' }
|
||||
|
||||
MAC: {
|
||||
optional: true # Auto-generated per default.
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Virtual disks to create for the new VM.
|
||||
VDIs: {
|
||||
optional: true # If not defined, use the template parameters.
|
||||
type: 'array'
|
||||
items: {
|
||||
type: 'object'
|
||||
properties: {
|
||||
bootable: { type: 'boolean' }
|
||||
device: { type: 'string' }
|
||||
size: { type: 'integer' }
|
||||
SR: { type: 'string' }
|
||||
type: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
|
||||
exports.create = ({
|
||||
installation
|
||||
name_label
|
||||
template
|
||||
VDIs
|
||||
VIFs
|
||||
}) ->
|
||||
# Gets the template.
|
||||
template = @getObject template
|
||||
@throw 'NO_SUCH_OBJECT' unless template
|
||||
@@ -105,13 +52,19 @@ exports.create = ->
|
||||
xapi = @getXAPI template
|
||||
|
||||
# Clones the VM from the template.
|
||||
ref = xapi.call 'VM.clone', template.ref, name_label
|
||||
ref = $wait xapi.call 'VM.clone', template.ref, name_label
|
||||
|
||||
# TODO: if there is an error from now, removes this VM.
|
||||
|
||||
# TODO: remove existing VIFs.
|
||||
# Creates associated virtual interfaces.
|
||||
$each VIFs, (VIF) =>
|
||||
network = @getObject VIF.network
|
||||
|
||||
xapi.call 'VIF.create', {
|
||||
$wait xapi.call 'VIF.create', {
|
||||
# FIXME: device n may already exists, we have to find the first
|
||||
# free device number.
|
||||
|
||||
device: '0'
|
||||
MAC: VIF.MAC ? ''
|
||||
MTU: '1500'
|
||||
@@ -122,12 +75,16 @@ exports.create = ->
|
||||
VM: ref
|
||||
}
|
||||
|
||||
# TODO: ? xapi.call 'VM.set_PV_args', ref, 'noninteractive'
|
||||
# TODO: ? $wait xapi.call 'VM.set_PV_args', ref, 'noninteractive'
|
||||
|
||||
# Updates the number of existing vCPUs.
|
||||
if CPUs?
|
||||
xapi.call 'VM.set_VCPUs_at_startup', ref, CPUs
|
||||
$wait xapi.call 'VM.set_VCPUs_at_startup', ref, CPUs
|
||||
|
||||
# TODO: remove existing VDIs (o make sure we have only those we
|
||||
# asked.
|
||||
#
|
||||
# Problem: how to know which VMs to clones for instance.
|
||||
if VDIs?
|
||||
# Transform the VDIs specs to conform to XAPI.
|
||||
$each VDIs, (VDI, key) ->
|
||||
@@ -147,17 +104,23 @@ exports.create = ->
|
||||
}
|
||||
|
||||
# Replace the existing entry in the VM object.
|
||||
try xapi.call 'VM.remove_from_other_config', ref, 'disks'
|
||||
xapi.call 'VM.add_to_other_config', ref, 'disks', VDIs
|
||||
try $wait xapi.call 'VM.remove_from_other_config', ref, 'disks'
|
||||
$wait xapi.call 'VM.add_to_other_config', ref, 'disks', VDIs
|
||||
|
||||
try $wait xapi.call(
|
||||
'VM.remove_from_other_config'
|
||||
ref
|
||||
'install-repository'
|
||||
)
|
||||
if installation
|
||||
switch installation.method
|
||||
when 'cdrom'
|
||||
xapi.call(
|
||||
$wait xapi.call(
|
||||
'VM.add_to_other_config', ref
|
||||
'install-repository', 'cdrom'
|
||||
)
|
||||
when 'ftp', 'http', 'nfs'
|
||||
xapi.call(
|
||||
$wait xapi.call(
|
||||
'VM.add_to_other_config', ref
|
||||
'install-repository', installation.repository
|
||||
)
|
||||
@@ -169,10 +132,10 @@ exports.create = ->
|
||||
|
||||
# Creates the VDIs and executes the initial steps of the
|
||||
# installation.
|
||||
xapi.call 'VM.provision', ref
|
||||
$wait xapi.call 'VM.provision', ref
|
||||
|
||||
# Gets the VM record.
|
||||
VM = xapi.call 'VM.get_record', ref
|
||||
VM = $wait xapi.call 'VM.get_record', ref
|
||||
|
||||
if installation.method is 'cdrom'
|
||||
# Gets the VDI containing the ISO to mount.
|
||||
@@ -185,7 +148,7 @@ exports.create = ->
|
||||
# CD.
|
||||
CD_drive = null
|
||||
$each VM.VBDs, (ref, _1, _2, done) ->
|
||||
VBD = xapi.call 'VBD.get_record', ref
|
||||
VBD = $wait xapi.call 'VBD.get_record', ref
|
||||
# TODO: Checks it has been correctly retrieved.
|
||||
if VBD.type is 'CD'
|
||||
CD_drive = VBD.ref
|
||||
@@ -194,7 +157,7 @@ exports.create = ->
|
||||
# No CD drives have been found, creates one.
|
||||
unless CD_drive
|
||||
# See: https://github.com/xenserver/xenadmin/blob/da00b13bb94603b369b873b0a555d44f15fa0ca5/XenModel/Actions/VM/CreateVMAction.cs#L370
|
||||
CD_drive = xapi.call 'VBD.create', {
|
||||
CD_drive = $wait xapi.call 'VBD.create', {
|
||||
bootable: true
|
||||
device: ''
|
||||
empty: true
|
||||
@@ -204,7 +167,7 @@ exports.create = ->
|
||||
qos_algorithm_type: ''
|
||||
type: 'CD'
|
||||
unpluggable: true
|
||||
userdevice: (xapi.call 'VM.get_allowed_VBD_devices', ref)[0]
|
||||
userdevice: ($wait xapi.call 'VM.get_allowed_VBD_devices', ref)[0]
|
||||
VDI: 'OpaqueRef:NULL'
|
||||
VM: ref
|
||||
}
|
||||
@@ -213,27 +176,69 @@ exports.create = ->
|
||||
@throw 'NO_SUCH_OBJECT' unless CD_drive
|
||||
|
||||
# Mounts the VDI into the VBD.
|
||||
xapi.call 'VBD.insert', CD_drive, VDIref
|
||||
$wait xapi.call 'VBD.insert', CD_drive, VDIref
|
||||
else
|
||||
$wait xapi.call 'VM.provision', ref
|
||||
VM = $wait xapi.call 'VM.get_record', ref
|
||||
|
||||
# The VM should be properly created.
|
||||
VM.uuid
|
||||
|
||||
exports.delete = ->
|
||||
{
|
||||
id
|
||||
delete_disks: deleteDisks
|
||||
} = @getParams {
|
||||
id: { type: 'string' }
|
||||
|
||||
delete_disks: {
|
||||
optional: true
|
||||
type: 'boolean'
|
||||
return VM.uuid
|
||||
exports.create.permission = 'admin'
|
||||
exports.create.params = {
|
||||
installation: {
|
||||
type: 'object'
|
||||
optional: true
|
||||
properties: {
|
||||
method: { type: 'string' }
|
||||
repository: { type: 'string' }
|
||||
}
|
||||
}
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
# Name of the new VM.
|
||||
name_label: { type: 'string' }
|
||||
|
||||
# TODO: add the install repository!
|
||||
# VBD.insert/eject
|
||||
# Also for the console!
|
||||
|
||||
# UUID of the template the VM will be created from.
|
||||
template: { type: 'string' }
|
||||
|
||||
# Virtual interfaces to create for the new VM.
|
||||
VIFs: {
|
||||
type: 'array'
|
||||
items: {
|
||||
type: 'object'
|
||||
properties: {
|
||||
# UUID of the network to create the interface in.
|
||||
network: { type: 'string' }
|
||||
|
||||
MAC: {
|
||||
optional: true # Auto-generated per default.
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Virtual disks to create for the new VM.
|
||||
VDIs: {
|
||||
optional: true # If not defined, use the template parameters.
|
||||
type: 'array'
|
||||
items: {
|
||||
type: 'object'
|
||||
properties: {
|
||||
bootable: { type: 'boolean' }
|
||||
device: { type: 'string' }
|
||||
size: { type: 'integer' }
|
||||
SR: { type: 'string' }
|
||||
type: { type: 'string' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.delete = ({id, delete_disks: deleteDisks}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@@ -253,15 +258,22 @@ exports.delete = ->
|
||||
|
||||
return if VBD.read_only or not VBD.VDI?
|
||||
|
||||
xapi.call 'VDI.destroy', VBD.VDI
|
||||
$wait xapi.call 'VDI.destroy', VBD.VDI
|
||||
|
||||
xapi.call 'VM.destroy', VM.ref
|
||||
$wait xapi.call 'VM.destroy', VM.ref
|
||||
|
||||
exports.ejectCd = ->
|
||||
{id} = @getParams {
|
||||
id: { type: 'string' }
|
||||
return true
|
||||
exports.delete.permission = 'admin'
|
||||
exports.delete.params = {
|
||||
id: { type: 'string' }
|
||||
|
||||
delete_disks: {
|
||||
optional: true
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
|
||||
exports.ejectCd = ({id}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@@ -277,16 +289,16 @@ exports.ejectCd = ->
|
||||
done
|
||||
|
||||
if cdDriveRef
|
||||
xapi.call 'VBD.eject', cdDriveRef
|
||||
xapi.call 'VBD.destroy', cdDriveRef
|
||||
$wait xapi.call 'VBD.eject', cdDriveRef
|
||||
$wait xapi.call 'VBD.destroy', cdDriveRef
|
||||
|
||||
exports.insertCd = ->
|
||||
{id, cd_id, force} = @getParams {
|
||||
id: { type: 'string' }
|
||||
cd_id: { type: 'string' }
|
||||
force: { type: 'boolean' }
|
||||
}
|
||||
return true
|
||||
exports.ejectCd.permission = 'admin'
|
||||
exports.ejectCd.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.insertCd = ({id, cd_id, force}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
VDI = @getObject cd_id
|
||||
@@ -307,9 +319,9 @@ exports.insertCd = ->
|
||||
|
||||
if cdDrive.VDI
|
||||
@throw 'INVALID_PARAMS' unless force
|
||||
xapi.call 'VBD.eject', cdDriveRef
|
||||
$wait xapi.call 'VBD.eject', cdDriveRef
|
||||
else
|
||||
cdDriveRef = xapi.call 'VBD.create', {
|
||||
cdDriveRef = $wait xapi.call 'VBD.create', {
|
||||
bootable: true
|
||||
device: ''
|
||||
empty: true
|
||||
@@ -319,25 +331,22 @@ exports.insertCd = ->
|
||||
qos_algorithm_type: ''
|
||||
type: 'CD'
|
||||
unpluggable: true
|
||||
userdevice: (xapi.call 'VM.get_allowed_VBD_devices', VM.ref)[0]
|
||||
userdevice: ($wait xapi.call 'VM.get_allowed_VBD_devices', VM.ref)[0]
|
||||
VDI: 'OpaqueRef:NULL'
|
||||
VM: VM.ref
|
||||
}
|
||||
|
||||
xapi.call 'VBD.insert', cdDriveRef, VDI.ref
|
||||
$wait xapi.call 'VBD.insert', cdDriveRef, VDI.ref
|
||||
|
||||
exports.migrate = ->
|
||||
{id, host_id} = @getParams {
|
||||
# Identifier of the VM to migrate.
|
||||
id: { type: 'string' }
|
||||
|
||||
# Identifier of the host to migrate to.
|
||||
host_id: { type: 'string' }
|
||||
}
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
return true
|
||||
exports.insertCd.permission = 'admin'
|
||||
exports.insertCd.params = {
|
||||
id: { type: 'string' }
|
||||
cd_id: { type: 'string' }
|
||||
force: { type: 'boolean' }
|
||||
}
|
||||
|
||||
exports.migrate = ({id, host_id}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
host = @getObject host_id
|
||||
@@ -349,29 +358,19 @@ exports.migrate = ->
|
||||
|
||||
xapi = @getXAPI VM
|
||||
|
||||
xapi.call 'VM.pool_migrate', VM.ref, host.ref, {}
|
||||
$wait xapi.call 'VM.pool_migrate', VM.ref, host.ref, {}
|
||||
|
||||
exports.set = ->
|
||||
params = @getParams {
|
||||
# Identifier of the VM to update.
|
||||
id: { type: 'string' }
|
||||
return true
|
||||
exports.migrate.permission = 'admin'
|
||||
exports.migrate.params = {
|
||||
# Identifier of the VM to migrate.
|
||||
id: { type: 'string' }
|
||||
|
||||
name_label: { type: 'string', optional: true }
|
||||
|
||||
name_description: { type: 'string', optional: true }
|
||||
|
||||
# Number of virtual CPUs to allocate.
|
||||
CPUs: { type: 'integer', optional: true }
|
||||
|
||||
# Memory to allocate (in bytes).
|
||||
#
|
||||
# Note: static_min ≤ dynamic_min ≤ dynamic_max ≤ static_max
|
||||
memory: { type: 'integer', optional: true }
|
||||
}
|
||||
|
||||
# Current user must be an administrator.
|
||||
@checkPermission 'admin'
|
||||
# Identifier of the host to migrate to.
|
||||
host_id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.set = (params) ->
|
||||
try
|
||||
VM = @getObject params.id
|
||||
catch
|
||||
@@ -399,10 +398,10 @@ exports.set = ->
|
||||
)
|
||||
|
||||
if memory < VM.memory.dynamic[0]
|
||||
xapi.call 'VM.set_memory_dynamic_min', ref, "#{memory}"
|
||||
$wait xapi.call 'VM.set_memory_dynamic_min', ref, "#{memory}"
|
||||
else if memory > VM.memory.static[1]
|
||||
xapi.call 'VM.set_memory_static_max', ref, "#{memory}"
|
||||
xapi.call 'VM.set_memory_dynamic_max', ref, "#{memory}"
|
||||
$wait xapi.call 'VM.set_memory_static_max', ref, "#{memory}"
|
||||
$wait xapi.call 'VM.set_memory_dynamic_max', ref, "#{memory}"
|
||||
|
||||
# Number of CPUs.
|
||||
if 'CPUs' of params
|
||||
@@ -415,11 +414,11 @@ exports.set = ->
|
||||
"cannot set CPUs above the static maximum (#{VM.CPUs.max}) "+
|
||||
"for a running VM"
|
||||
)
|
||||
xapi.call 'VM.set_VCPUs_number_live', ref, "#{CPUs}"
|
||||
$wait xapi.call 'VM.set_VCPUs_number_live', ref, "#{CPUs}"
|
||||
else
|
||||
if CPUs > VM.CPUs.max
|
||||
xapi.call 'VM.set_VCPUs_max', ref, "#{CPUs}"
|
||||
xapi.call 'VM.set_VCPUs_at_startup', ref, "#{CPUs}"
|
||||
$wait xapi.call 'VM.set_VCPUs_max', ref, "#{CPUs}"
|
||||
$wait xapi.call 'VM.set_VCPUs_at_startup', ref, "#{CPUs}"
|
||||
|
||||
# Other fields.
|
||||
for param, fields of {
|
||||
@@ -429,19 +428,28 @@ exports.set = ->
|
||||
continue unless param of params
|
||||
|
||||
for field in (if $isArray fields then fields else [fields])
|
||||
xapi.call "VM.set_#{field}", ref, "#{params[param]}"
|
||||
$wait xapi.call "VM.set_#{field}", ref, "#{params[param]}"
|
||||
|
||||
exports.restart = ->
|
||||
{
|
||||
id
|
||||
force
|
||||
} = @getParams {
|
||||
id: { type: 'string' }
|
||||
force: { type: 'boolean' }
|
||||
}
|
||||
return true
|
||||
exports.set.permission = 'admin'
|
||||
exports.set.params = {
|
||||
# Identifier of the VM to update.
|
||||
id: { type: 'string' }
|
||||
|
||||
@checkPermission 'admin'
|
||||
name_label: { type: 'string', optional: true }
|
||||
|
||||
name_description: { type: 'string', optional: true }
|
||||
|
||||
# Number of virtual CPUs to allocate.
|
||||
CPUs: { type: 'integer', optional: true }
|
||||
|
||||
# Memory to allocate (in bytes).
|
||||
#
|
||||
# Note: static_min ≤ dynamic_min ≤ dynamic_max ≤ static_max
|
||||
memory: { type: 'integer', optional: true }
|
||||
}
|
||||
|
||||
exports.restart = ({id, force}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@@ -451,48 +459,58 @@ exports.restart = ->
|
||||
|
||||
try
|
||||
# Attempts a clean reboot.
|
||||
xapi.call 'VM.clean_reboot', VM.ref
|
||||
$wait xapi.call 'VM.clean_reboot', VM.ref
|
||||
catch error
|
||||
return unless error[0] is 'VM_MISSING_PV_DRIVERS'
|
||||
|
||||
@throw 'INVALID_PARAMS' unless force
|
||||
|
||||
xapi.call 'VM.hard_reboot', VM.ref
|
||||
$wait xapi.call 'VM.hard_reboot', VM.ref
|
||||
|
||||
true
|
||||
|
||||
|
||||
exports.start = ->
|
||||
{id} = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
@checkPermission 'admin'
|
||||
return true
|
||||
exports.restart.permission = 'admin'
|
||||
exports.restart.params = {
|
||||
id: { type: 'string' }
|
||||
force: { type: 'boolean' }
|
||||
}
|
||||
|
||||
exports.clone = ({id, name, full_copy}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
(@getXAPI VM).call(
|
||||
'VM.start', VM.ref
|
||||
false # Start paused?
|
||||
false # Skips the pre-boot checks?
|
||||
)
|
||||
xapi = @getXAPI VM
|
||||
if full_copy
|
||||
$wait xapi.call 'VM.copy', VM.ref, name, ''
|
||||
else
|
||||
$wait xapi.call 'VM.clone', VM.ref, name
|
||||
|
||||
true
|
||||
return true
|
||||
exports.clone.permission = 'admin'
|
||||
exports.clone.params = {
|
||||
id: { type: 'string' }
|
||||
name: { type: 'string' }
|
||||
full_copy: { type: 'boolean' }
|
||||
}
|
||||
|
||||
exports.stop = ->
|
||||
{
|
||||
id
|
||||
force
|
||||
} = @getParams {
|
||||
id: { type: 'string' }
|
||||
force: { type: 'boolean' }
|
||||
}
|
||||
# TODO: rename convertToTemplate()
|
||||
exports.convert = ({id}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
@checkPermission 'admin'
|
||||
xapi = @getXAPI VM
|
||||
$wait xapi.call 'VM.set_is_a_template', VM.ref, true
|
||||
|
||||
return true
|
||||
exports.convert.permission = 'admin'
|
||||
exports.convert.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.snapshot = ({id, name}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@@ -500,14 +518,131 @@ exports.stop = ->
|
||||
|
||||
xapi = @getXAPI VM
|
||||
|
||||
$wait xapi.call 'VM.snapshot', VM.ref, name
|
||||
|
||||
return true
|
||||
exports.snapshot.permission = 'admin'
|
||||
exports.snapshot.params = {
|
||||
id: { type: 'string' }
|
||||
name: { type: 'string' }
|
||||
}
|
||||
|
||||
exports.start = ({id}) ->
|
||||
try
|
||||
# Attempts a clean shutdown.
|
||||
xapi.call 'VM.clean_shutdown', VM.ref
|
||||
VM = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
$wait (@getXAPI VM).call(
|
||||
'VM.start', VM.ref
|
||||
false # Start paused?
|
||||
false # Skips the pre-boot checks?
|
||||
)
|
||||
|
||||
return true
|
||||
exports.start.permission = 'admin'
|
||||
exports.start.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
# TODO: implements timeout.
|
||||
# - if !force → clean shutdown
|
||||
# - if force is true → hard shutdown
|
||||
# - if force is integer → clean shutdown and after force seconds, hard shutdown.
|
||||
exports.stop = ({id, force}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
|
||||
# Hard shutdown
|
||||
if force
|
||||
$wait xapi.call 'VM.hard_shutdown', VM.ref
|
||||
return true
|
||||
|
||||
# Clean shutdown
|
||||
try
|
||||
$wait xapi.call 'VM.clean_shutdown', VM.ref
|
||||
catch error
|
||||
return unless error[0] is 'VM_MISSING_PV_DRIVERS'
|
||||
if error[0] is 'VM_MISSING_PV_DRIVERS'
|
||||
# TODO: Improve reporting: this message is unclear.
|
||||
@throw 'INVALID_PARAMS'
|
||||
else
|
||||
throw error
|
||||
|
||||
@throw 'INVALID_PARAMS' unless force
|
||||
return true
|
||||
exports.stop.permission = 'admin'
|
||||
exports.stop.params = {
|
||||
id: { type: 'string' }
|
||||
force: { type: 'boolean', optional: true }
|
||||
}
|
||||
|
||||
xapi.call 'VM.hard_shutdown', VM.ref
|
||||
# revert a snapshot to its parent VM
|
||||
exports.revert = ({id}) ->
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
true
|
||||
xapi = @getXAPI VM
|
||||
|
||||
# Attempts a revert from this snapshot to its parent VM
|
||||
$wait xapi.call 'VM.revert', VM.ref
|
||||
|
||||
return true
|
||||
exports.revert.permission = 'admin'
|
||||
exports.revert.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
# export a VM
|
||||
exports.export = ({id, compress}) ->
|
||||
@throw 'NOT_IMPLEMENTED'
|
||||
compress ?= true
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
|
||||
# get the session ID
|
||||
sessionId = xapi.sessionId
|
||||
# HTTP object connected to the pool master
|
||||
http.put "/export/?session_id=#{sessionId}&ref=#{VM.ref}&use_compression=#{compress}"
|
||||
|
||||
# @TODO: we need to get the file somehow
|
||||
|
||||
return true
|
||||
exports.export.permission = 'admin'
|
||||
exports.export.params = {
|
||||
id: { type: 'string' }
|
||||
compress: { type: 'boolean', optional:true }
|
||||
}
|
||||
|
||||
# import a VM
|
||||
exports.import = ({id, file}) ->
|
||||
@throw 'NOT_IMPLEMENTED'
|
||||
try
|
||||
VM = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
|
||||
# get the session ID
|
||||
sessionId = xapi.sessionId
|
||||
|
||||
# HTTP object connected to the pool master
|
||||
http.put "/import/?session_id=#{sessionId}"
|
||||
|
||||
# @TODO: we need to put the file somehow
|
||||
|
||||
return true
|
||||
exports.import.permission = 'admin'
|
||||
exports.import.params = {
|
||||
id: { type: 'string' }
|
||||
file: { type: 'string' }
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
$_ = require 'underscore'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
# Generates method to delete objects.
|
||||
do ->
|
||||
types = [
|
||||
'message'
|
||||
'task'
|
||||
]
|
||||
$_.each types, (type) ->
|
||||
exports[type] ?= {}
|
||||
exports[type].destroy = ->
|
||||
{id} = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
# FIXME: For now, the current user must be an administrator, but
|
||||
# eventually, the user only need to have the “write” permission
|
||||
# over the object linked to the message or task.
|
||||
@checkPermission 'admin'
|
||||
|
||||
# Retrieves the object.
|
||||
object = @xobjs.get id
|
||||
@throw 'NO_SUCH_OBJECT' unless object?
|
||||
|
||||
# Gets the corresponding connection.
|
||||
xapi = @xapis[object.$pool]
|
||||
xapi.call "#{type}.destroy", object.$ref
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
@@ -1,48 +0,0 @@
|
||||
$_ = require 'underscore'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
# Definitions of the methods to generate.
|
||||
defs = {
|
||||
pause: {}
|
||||
|
||||
unpause: {}
|
||||
}
|
||||
|
||||
#=====================================================================
|
||||
|
||||
# Generates methods.
|
||||
$_.each defs, (def, name) ->
|
||||
method = name
|
||||
params = []
|
||||
|
||||
if $_.isString def
|
||||
method = def
|
||||
else if $_.isArray def
|
||||
params = def
|
||||
else if $_.isObject def
|
||||
method = def.method if def.method?
|
||||
params = def.params if def.params?
|
||||
|
||||
exports[name] = ->
|
||||
# This method expect to the VM's UUID.
|
||||
{id} = @getParams {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
|
||||
# The current session MUST have the `write`
|
||||
# permission.
|
||||
@checkPermission 'write'
|
||||
|
||||
# Retrieves the VM with this UUID.
|
||||
try
|
||||
vm = @getObject id
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
# Gets the corresponding connection.
|
||||
xapi = @getXAPI vm
|
||||
xapi.call.apply xapi, ["VM.#{method}", vm.ref].concat params
|
||||
|
||||
# Returns true.
|
||||
true
|
||||
@@ -1,7 +1,11 @@
|
||||
var _ = require('underscore');
|
||||
var Q = require('q');
|
||||
'use strict';
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//====================================================================
|
||||
|
||||
var _ = require('underscore');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
//====================================================================
|
||||
|
||||
function Collection()
|
||||
{
|
||||
@@ -43,7 +47,7 @@ Collection.prototype.add = function (models, options) {
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return Q.when(this._add(models, options), function (models) {
|
||||
return Promise.cast(this._add(models, options)).then(function (models) {
|
||||
self.emit('add', models);
|
||||
|
||||
if (!array)
|
||||
@@ -67,7 +71,7 @@ Collection.prototype.first = function (properties) {
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return Q.when(this._first(properties), function (model) {
|
||||
return Promise.cast(this._first(properties)).then(function (model) {
|
||||
if (!model)
|
||||
{
|
||||
return null;
|
||||
@@ -93,7 +97,7 @@ Collection.prototype.get = function (properties) {
|
||||
}
|
||||
|
||||
/* jshint newcap: false */
|
||||
return Q(this._get(properties));
|
||||
return Promise.cast(this._get(properties));
|
||||
};
|
||||
|
||||
|
||||
@@ -107,7 +111,7 @@ Collection.prototype.remove = function (ids) {
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return Q.when(this._remove(ids), function () {
|
||||
return Promise.cast(this._remove(ids)).then(function () {
|
||||
self.emit('remove', ids);
|
||||
return true;
|
||||
});
|
||||
@@ -151,7 +155,7 @@ Collection.prototype.update = function (models) {
|
||||
// Missing models should be added not updated.
|
||||
if (!id)
|
||||
{
|
||||
return Q.reject('a model without an id cannot be updated');
|
||||
return Promise.reject('a model without an id cannot be updated');
|
||||
}
|
||||
|
||||
var error = model.validate();
|
||||
@@ -165,7 +169,7 @@ Collection.prototype.update = function (models) {
|
||||
}
|
||||
|
||||
var self = this;
|
||||
return Q.when(this._update(models), function (models) {
|
||||
return Promise.cast(this._update(models)).then(function (models) {
|
||||
self.emit('update', models);
|
||||
|
||||
if (!array)
|
||||
@@ -236,7 +240,7 @@ Collection.prototype.exists = function (properties) {
|
||||
*
|
||||
*/
|
||||
Collection.prototype._first = function (properties) {
|
||||
return Q.when(this.get(properties), function (models) {
|
||||
return Promise.cast(this.get(properties)).then(function (models) {
|
||||
if (0 === models.length)
|
||||
{
|
||||
return null;
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
var _ = require('underscore');
|
||||
var Q = require('q');
|
||||
'use strict';
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
//====================================================================
|
||||
|
||||
var _ = require('underscore');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
//====================================================================
|
||||
|
||||
function Memory(models)
|
||||
{
|
||||
@@ -34,7 +38,7 @@ Memory.prototype._add = function (models, options) {
|
||||
else if (!replace && this.models[id])
|
||||
{
|
||||
// Existing models are ignored.
|
||||
return Q.reject('cannot add existing models!');
|
||||
return Promise.reject('cannot add existing models!');
|
||||
}
|
||||
|
||||
this.models[id] = model;
|
||||
@@ -84,7 +88,7 @@ Memory.prototype._update = function (models) {
|
||||
// Missing models should be added not updated.
|
||||
if (!this.models[id])
|
||||
{
|
||||
return Q.reject('missing model');
|
||||
return Promise.reject('missing model');
|
||||
}
|
||||
|
||||
_.extend(this.models[id], model);
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
//====================================================================
|
||||
|
||||
var _ = require('underscore');
|
||||
var Q = require('q');
|
||||
var Promise = require('bluebird');
|
||||
|
||||
var then_redis;
|
||||
function create_redis_client(uri)
|
||||
{
|
||||
if (!then_redis)
|
||||
{
|
||||
then_redis = require('then-redis');
|
||||
}
|
||||
var thenRedis = require('then-redis');
|
||||
|
||||
return then_redis.createClient(uri);
|
||||
}
|
||||
//====================================================================
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Data model:
|
||||
@@ -48,7 +45,7 @@ function Redis(options, models)
|
||||
|
||||
Redis.super_.call(this, models);
|
||||
|
||||
this.redis = options.connection || create_redis_client(options.uri);
|
||||
this.redis = options.connection || thenRedis.createClient(options.uri);
|
||||
this.prefix = options.prefix;
|
||||
this.indexes = options.indexes;
|
||||
}
|
||||
@@ -75,7 +72,7 @@ Redis.prototype._extract = function (ids) {
|
||||
}));
|
||||
});
|
||||
|
||||
return Q.all(promises).then(function (models) {
|
||||
return Promise.all(promises).then(function (models) {
|
||||
return _.filter(models, function (model) {
|
||||
return (null !== model);
|
||||
});
|
||||
@@ -102,8 +99,13 @@ Redis.prototype._add = function (models, options) {
|
||||
model.id = id;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensures the promise chain is correctly initialized.
|
||||
promise = Promise.cast();
|
||||
}
|
||||
|
||||
promise = Q.when(promise, function () {
|
||||
promise = promise.then(function () {
|
||||
// Adds the identifier to the models' ids set.
|
||||
return redis.sadd(prefix +'_ids', model.id);
|
||||
}).then(function (success) {
|
||||
@@ -142,14 +144,14 @@ Redis.prototype._add = function (models, options) {
|
||||
promises.push(redis.sadd(key, model.id));
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
return Promise.all(promises);
|
||||
|
||||
}).thenResolve(model);
|
||||
}).then(function () { return model; });
|
||||
|
||||
promises.push(promise);
|
||||
});
|
||||
|
||||
return Q.all(promises);
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
Redis.prototype._get = function (properties) {
|
||||
@@ -217,7 +219,7 @@ Redis.prototype._remove = function (ids) {
|
||||
redis.send('del', keys)
|
||||
);
|
||||
|
||||
return Q.all(promises);
|
||||
return Promise.all(promises);
|
||||
};
|
||||
|
||||
Redis.prototype._update = function (models) {
|
||||
|
||||
81
src/connection.js
Normal file
81
src/connection.js
Normal file
@@ -0,0 +1,81 @@
|
||||
'use strict';
|
||||
|
||||
//====================================================================
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var inherits = require('util').inherits;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
var extend = require('underscore').extend;
|
||||
|
||||
//====================================================================
|
||||
|
||||
var has = Object.prototype.hasOwnProperty;
|
||||
has = has.call.bind(has);
|
||||
|
||||
//====================================================================
|
||||
|
||||
var Connection = function Connection(adapter) {
|
||||
this.data = Object.create(null);
|
||||
|
||||
this._adapter = adapter;
|
||||
};
|
||||
inherits(Connection, EventEmitter);
|
||||
|
||||
extend(Connection.prototype, {
|
||||
// Close the connection.
|
||||
close: function () {
|
||||
this._adapter.close();
|
||||
this.emit('close');
|
||||
|
||||
// Releases values AMAP to ease the garbage collecting.
|
||||
for (var key in this)
|
||||
{
|
||||
if (has(this, key))
|
||||
{
|
||||
delete this[key];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Gets the value for this key.
|
||||
get: function (key, defaultValue) {
|
||||
var data = this.data;
|
||||
|
||||
if (key in data)
|
||||
{
|
||||
return data[key];
|
||||
}
|
||||
|
||||
if (arguments.length >= 2)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
throw new Error('no value for `'+ key +'`');
|
||||
},
|
||||
|
||||
// Checks whether there is a value for this key.
|
||||
has: function (key) {
|
||||
return key in this.data;
|
||||
},
|
||||
|
||||
// Sets the value for this key.
|
||||
set: function (key, value) {
|
||||
this.data[key] = value;
|
||||
},
|
||||
|
||||
// Sends a message.
|
||||
send: function (name, data) {
|
||||
this._adapter.send(name, data);
|
||||
},
|
||||
|
||||
unset: function (key) {
|
||||
delete this.data[key];
|
||||
},
|
||||
});
|
||||
|
||||
//====================================================================
|
||||
|
||||
module.exports = Connection;
|
||||
@@ -4,57 +4,84 @@ $_ = require 'underscore'
|
||||
# Async code is easier with fibers (light threads)!
|
||||
$fiber = require 'fibers'
|
||||
|
||||
$Q = require 'q'
|
||||
$Promise = require 'bluebird'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$isPromise = (obj) -> obj? and $_.isFunction obj.then
|
||||
|
||||
# The value is guarantee to resolve asynchronously.
|
||||
$runAsync = (value, resolve, reject) ->
|
||||
if $isPromise value
|
||||
return value.then resolve, reject
|
||||
|
||||
if $_.isFunction value # Continuable
|
||||
async = false
|
||||
handler = (error, result) ->
|
||||
unless async
|
||||
return process.nextTick handler.bind null, error, result
|
||||
if error?
|
||||
return reject error
|
||||
resolve result
|
||||
value handler
|
||||
async = true
|
||||
return
|
||||
|
||||
unless $_.isObject value
|
||||
return process.nextTick -> resolve value
|
||||
|
||||
left = 0
|
||||
results = if $_.isArray value
|
||||
new Array value.length
|
||||
else
|
||||
Object.create null
|
||||
|
||||
$_.each value, (value, index) ->
|
||||
++left
|
||||
$runAsync(
|
||||
value
|
||||
(result) ->
|
||||
# Returns if already rejected.
|
||||
return unless results
|
||||
|
||||
results[index] = result
|
||||
resolve results unless --left
|
||||
(error) ->
|
||||
# Returns if already rejected.
|
||||
return unless results
|
||||
|
||||
# Frees the reference ASAP.
|
||||
results = null
|
||||
|
||||
reject error
|
||||
)
|
||||
|
||||
if left is 0
|
||||
process.nextTick -> resolve value
|
||||
|
||||
#=====================================================================
|
||||
|
||||
# Makes a function running in its own fiber.
|
||||
$fiberize = (fn) ->
|
||||
(args...) ->
|
||||
$fiber(=> fn.apply this, args).run()
|
||||
|
||||
# Makes a function run in its own fiber and returns a promise.
|
||||
#
|
||||
# TODO: should we keep it?
|
||||
$promisify = (fn) ->
|
||||
(args...) ->
|
||||
deferred = $Q.defer()
|
||||
|
||||
$fiber(=>
|
||||
try
|
||||
deferred.resolve fn.apply this, args
|
||||
fn.apply this, args
|
||||
catch error
|
||||
deferred.reject error
|
||||
process.nextTick ->
|
||||
throw error
|
||||
).run()
|
||||
|
||||
deferred.promise
|
||||
|
||||
# Makes the fiber waits for a number of milliseconds.
|
||||
$sleep = (ms) ->
|
||||
fiber = $fiber.current
|
||||
setTimeout (-> fiber.run()), ms
|
||||
$fiber.yield()
|
||||
|
||||
# Makes an Node like asynchronous function synchronous (in a fiber).
|
||||
$synchronize = (fn, ctx) ->
|
||||
fn = ctx[fn] if $_.isString fn
|
||||
|
||||
# Makes a function run in its own fiber and returns a promise.
|
||||
$promisify = (fn) ->
|
||||
(args...) ->
|
||||
fiber = $fiber.current
|
||||
throw new Error 'not running in a fiber' unless fiber?
|
||||
|
||||
args.push (error, result) ->
|
||||
if error?
|
||||
fiber.throwInto error
|
||||
else
|
||||
fiber.run result
|
||||
fn.apply ctx, args
|
||||
|
||||
$fiber.yield()
|
||||
new $Promise (resolve, reject) ->
|
||||
$fiber(=>
|
||||
try
|
||||
resolve fn.apply this, args
|
||||
catch error
|
||||
reject error
|
||||
).run()
|
||||
|
||||
# Waits for an event.
|
||||
#
|
||||
@@ -76,38 +103,39 @@ $waitEvent = (emitter, event) ->
|
||||
|
||||
$fiber.yield()
|
||||
|
||||
# Waits for a promise or a thunk to end.
|
||||
# Waits for a promise or a continuable to end.
|
||||
#
|
||||
# If value is composed (array or map), every asynchronous value is
|
||||
# resolved before returning (parallelization).
|
||||
$wait = (value) ->
|
||||
fiber = $fiber.current
|
||||
throw new Error 'not running in a fiber' unless fiber?
|
||||
|
||||
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.
|
||||
if $wait._stash
|
||||
value = $wait._stash
|
||||
delete $wait._stash
|
||||
|
||||
# No idea what is it, just forwards.
|
||||
return value
|
||||
$runAsync(
|
||||
value
|
||||
fiber.run.bind fiber
|
||||
fiber.throwInto.bind fiber
|
||||
)
|
||||
|
||||
$fiber.yield()
|
||||
|
||||
$wait.register = ->
|
||||
throw new Error 'something has already been registered' if $wait._stash
|
||||
|
||||
deferred = $Promise.defer()
|
||||
$wait._stash = deferred.promise
|
||||
|
||||
deferred.callback
|
||||
|
||||
#=====================================================================
|
||||
|
||||
module.exports = {
|
||||
$fiberize
|
||||
$promisify
|
||||
$sleep
|
||||
$synchronize
|
||||
$waitEvent
|
||||
$wait
|
||||
}
|
||||
|
||||
247
src/fibers-utils.spec.js
Normal file
247
src/fibers-utils.spec.js
Normal file
@@ -0,0 +1,247 @@
|
||||
'use strict';
|
||||
|
||||
//====================================================================
|
||||
|
||||
var expect = require('chai').expect;
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
var Promise = require('bluebird');
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
var utils = require('./fibers-utils');
|
||||
var $fiberize = utils.$fiberize;
|
||||
|
||||
//====================================================================
|
||||
|
||||
describe('$fiberize', function () {
|
||||
it('creates a function which runs in a new fiber', function () {
|
||||
var previous = require('fibers').current;
|
||||
|
||||
var fn = $fiberize(function () {
|
||||
var current = require('fibers').current;
|
||||
|
||||
expect(current).to.exists;
|
||||
expect(current).to.not.equal(previous);
|
||||
});
|
||||
|
||||
fn();
|
||||
});
|
||||
|
||||
it('forwards all arguments (even this)', function () {
|
||||
var self = {};
|
||||
var arg1 = {};
|
||||
var arg2 = {};
|
||||
|
||||
$fiberize(function (arg1, arg2) {
|
||||
expect(this).to.equal(self);
|
||||
expect(arg1).to.equal(arg1);
|
||||
expect(arg2).to.equal(arg2);
|
||||
}).call(self, arg1, arg2);
|
||||
});
|
||||
});
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
describe('$wait', function () {
|
||||
var $wait = utils.$wait;
|
||||
|
||||
it('waits for a promise', function (done) {
|
||||
$fiberize(function () {
|
||||
var value = {};
|
||||
var promise = Promise.cast(value);
|
||||
|
||||
expect($wait(promise)).to.equal(value);
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
|
||||
it('handles promise rejection', function (done) {
|
||||
$fiberize(function () {
|
||||
var promise = Promise.reject('an exception');
|
||||
|
||||
expect(function () {
|
||||
$wait(promise);
|
||||
}).to.throw('an exception');
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
|
||||
it('waits for a continuable', function (done) {
|
||||
$fiberize(function () {
|
||||
var value = {};
|
||||
var continuable = function (callback) {
|
||||
callback(null, value);
|
||||
};
|
||||
|
||||
expect($wait(continuable)).to.equal(value);
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
|
||||
it('handles continuable error', function (done) {
|
||||
$fiberize(function () {
|
||||
var continuable = function (callback) {
|
||||
callback('an exception');
|
||||
};
|
||||
|
||||
expect(function () {
|
||||
$wait(continuable);
|
||||
}).to.throw('an exception');
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
|
||||
it('forwards scalar values', function (done) {
|
||||
$fiberize(function () {
|
||||
var value = 'a scalar value';
|
||||
expect($wait(value)).to.equal(value);
|
||||
|
||||
value = [
|
||||
'foo',
|
||||
'bar',
|
||||
'baz',
|
||||
];
|
||||
expect($wait(value)).to.deep.equal(value);
|
||||
|
||||
value = [];
|
||||
expect($wait(value)).to.deep.equal(value);
|
||||
|
||||
value = {
|
||||
foo: 'foo',
|
||||
bar: 'bar',
|
||||
baz: 'baz',
|
||||
};
|
||||
expect($wait(value)).to.deep.equal(value);
|
||||
|
||||
value = {};
|
||||
expect($wait(value)).to.deep.equal(value);
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
|
||||
it('handles arrays of promises/continuables', function (done) {
|
||||
$fiberize(function () {
|
||||
var value1 = {};
|
||||
var value2 = {};
|
||||
|
||||
var promise = Promise.cast(value1);
|
||||
var continuable = function (callback) {
|
||||
callback(null, value2);
|
||||
};
|
||||
|
||||
var results = $wait([promise, continuable]);
|
||||
expect(results[0]).to.equal(value1);
|
||||
expect(results[1]).to.equal(value2);
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
|
||||
it('handles maps of promises/continuable', function (done) {
|
||||
$fiberize(function () {
|
||||
var value1 = {};
|
||||
var value2 = {};
|
||||
|
||||
var promise = Promise.cast(value1);
|
||||
var continuable = function (callback) {
|
||||
callback(null, value2);
|
||||
};
|
||||
|
||||
var results = $wait({
|
||||
foo: promise,
|
||||
bar: continuable
|
||||
});
|
||||
expect(results.foo).to.equal(value1);
|
||||
expect(results.bar).to.equal(value2);
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
|
||||
it('handles nested arrays/maps', function (done) {
|
||||
var promise = Promise.cast('a promise');
|
||||
var continuable = function (callback) {
|
||||
callback(null, 'a continuable');
|
||||
};
|
||||
|
||||
$fiberize(function () {
|
||||
expect($wait({
|
||||
foo: promise,
|
||||
bar: [
|
||||
continuable,
|
||||
'a scalar'
|
||||
]
|
||||
})).to.deep.equal({
|
||||
foo: 'a promise',
|
||||
bar: [
|
||||
'a continuable',
|
||||
'a scalar'
|
||||
]
|
||||
});
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
|
||||
describe('#register()', function () {
|
||||
it('registers a callback-based function to be waited', function (done) {
|
||||
$fiberize(function () {
|
||||
var fn = function (value, callback) {
|
||||
callback(null, value);
|
||||
};
|
||||
|
||||
var value = {};
|
||||
expect($wait(fn(value, $wait.register()))).to.equal(value);
|
||||
|
||||
value = {};
|
||||
expect($wait(fn(value, $wait.register()))).to.equal(value);
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
describe('$waitEvent', function () {
|
||||
var $waitEvent = utils.$waitEvent;
|
||||
|
||||
it('waits for an event', function (done) {
|
||||
$fiberize(function () {
|
||||
var emitter = new (require('events').EventEmitter)();
|
||||
|
||||
var value = {};
|
||||
process.nextTick(function () {
|
||||
emitter.emit('foo', value);
|
||||
});
|
||||
|
||||
expect($waitEvent(emitter, 'foo')[0]).to.equal(value);
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
|
||||
it('handles the error event', function (done) {
|
||||
$fiberize(function () {
|
||||
var emitter = new (require('events').EventEmitter)();
|
||||
|
||||
process.nextTick(function () {
|
||||
emitter.emit('error', 'an error');
|
||||
});
|
||||
|
||||
expect(function () {
|
||||
$waitEvent(emitter, 'foo');
|
||||
}).to.throw('an error');
|
||||
|
||||
done();
|
||||
})();
|
||||
});
|
||||
});
|
||||
@@ -83,7 +83,8 @@ describe 'Helper', ->
|
||||
|
||||
$expect(collection.get 'sum').to.equal 3
|
||||
|
||||
it 'with dynamic keys', ->
|
||||
# FIXME: This test fails but this feature is not used.
|
||||
it.skip 'with dynamic keys', ->
|
||||
collection.set {
|
||||
foo: 1
|
||||
bar: 2
|
||||
|
||||
@@ -8,12 +8,18 @@ $_ = require 'underscore'
|
||||
|
||||
# HTTP(s) middleware framework.
|
||||
$connect = require 'connect'
|
||||
$serveStatic = require 'serve-static'
|
||||
|
||||
$eventToPromise = require 'event-to-promise'
|
||||
|
||||
# Configuration handling.
|
||||
$nconf = require 'nconf'
|
||||
|
||||
$Promise = require 'bluebird'
|
||||
$Promise.longStackTraces()
|
||||
|
||||
# WebSocket server.
|
||||
$WSServer = (require 'ws').Server
|
||||
{Server: $WSServer} = require 'ws'
|
||||
|
||||
# YAML formatting and parsing.
|
||||
$YAML = require 'js-yaml'
|
||||
@@ -21,17 +27,19 @@ $YAML = require 'js-yaml'
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
$API = require './api'
|
||||
$Session = require './session'
|
||||
$Connection = require './connection'
|
||||
$XO = require './xo'
|
||||
|
||||
# Helpers for dealing with fibers.
|
||||
{$fiberize, $waitEvent, $wait} = require './fibers-utils'
|
||||
{$fiberize, $promisify, $waitEvent, $wait} = require './fibers-utils'
|
||||
|
||||
# HTTP/HTTPS server which can listen on multiple ports.
|
||||
$WebServer = require './web-server'
|
||||
$WebServer = require 'http-server-plus'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$readFile = $Promise.promisify $fs.readFile
|
||||
|
||||
$handleJsonRpcCall = (api, session, encodedRequest) ->
|
||||
request = {
|
||||
id: null
|
||||
@@ -67,7 +75,7 @@ $handleJsonRpcCall = (api, session, encodedRequest) ->
|
||||
try
|
||||
JSON.stringify {
|
||||
jsonrpc: '2.0'
|
||||
result: api.exec session, request
|
||||
result: $wait api.exec session, request
|
||||
id: request.id
|
||||
}
|
||||
catch error
|
||||
@@ -81,7 +89,11 @@ $handleJsonRpcCall = (api, session, encodedRequest) ->
|
||||
#=====================================================================
|
||||
|
||||
# Main.
|
||||
do $fiberize ->
|
||||
module.exports = $promisify (args) ->
|
||||
|
||||
# Relative paths in the configuration are relative to this
|
||||
# directory's parent.
|
||||
process.chdir "#{__dirname}/.."
|
||||
|
||||
# Loads the environment.
|
||||
$nconf.env()
|
||||
@@ -89,13 +101,13 @@ do $fiberize ->
|
||||
# Parses process' arguments.
|
||||
$nconf.argv()
|
||||
|
||||
# Loads the configuration file.
|
||||
# Loads the configuration files.
|
||||
format =
|
||||
stringify: $YAML.safeDump
|
||||
parse: $YAML.safeLoad
|
||||
$nconf.use 'file', {
|
||||
file: "#{__dirname}/../config/local.yaml"
|
||||
format: {
|
||||
stringify: (obj) -> $YAML.safeDump obj
|
||||
parse: (string) -> $YAML.safeLoad string
|
||||
}
|
||||
format
|
||||
}
|
||||
|
||||
# Defines defaults configuration.
|
||||
@@ -118,10 +130,26 @@ do $fiberize ->
|
||||
|
||||
# Creates the web server according to the configuration.
|
||||
webServer = new $WebServer()
|
||||
webServer.listen options for options in $nconf.get 'http:listen'
|
||||
$wait $Promise.map ($nconf.get 'http:listen'), (options) ->
|
||||
# Reads certificate and key if necessary.
|
||||
if options.certificate? and options.key?
|
||||
options.certificate = $wait $readFile options.certificate
|
||||
options.key = $wait $readFile options.key
|
||||
|
||||
# Waits for the web server to start listening to drop privileges.
|
||||
$waitEvent webServer, 'listening'
|
||||
# Starts listening
|
||||
webServer.listen options
|
||||
.then ->
|
||||
console.log "WebServer listening on #{@niceAddress()}"
|
||||
.catch (error) ->
|
||||
console.warn "[WARN] WebServer could not listen on #{@niceAddress()}"
|
||||
switch error.code
|
||||
when 'EACCES'
|
||||
console.warn ' Access denied.'
|
||||
console.warn ' Ports < 1024 are often reserved to privileges users.'
|
||||
when 'EADDRINUSE'
|
||||
console.warn ' Address already in use.'
|
||||
|
||||
# Now the web server is listening, drop privileges.
|
||||
try
|
||||
if (group = $nconf.get 'group')?
|
||||
process.setgid group
|
||||
@@ -130,6 +158,11 @@ do $fiberize ->
|
||||
catch error
|
||||
console.warn "[WARN] Failed to change the user or group: #{error.message}"
|
||||
|
||||
# Handles error as gracefully as possible.
|
||||
webServer.on 'error', (error) ->
|
||||
console.error '[ERR] Web server', error
|
||||
webServer.close()
|
||||
|
||||
# Creates the main object which will connects to Xen servers and
|
||||
# manages all the models.
|
||||
xo = new $XO()
|
||||
@@ -146,33 +179,52 @@ do $fiberize ->
|
||||
for urlPath, filePaths of $nconf.get 'http:mounts'
|
||||
filePaths = [filePaths] unless $_.isArray filePaths
|
||||
for filePath in filePaths
|
||||
connect.use urlPath, $connect.static filePath
|
||||
connect.use urlPath, $serveStatic filePath
|
||||
webServer.on 'request', connect
|
||||
|
||||
# Creates the API.
|
||||
api = new $API xo
|
||||
|
||||
# # JSON-RPC over WebSocket.
|
||||
new $WSServer({
|
||||
conId = 0
|
||||
unregisterConnection = ->
|
||||
delete xo.connections[@id]
|
||||
|
||||
# JSON-RPC over WebSocket.
|
||||
wsServer = new $WSServer {
|
||||
server: webServer
|
||||
path: '/api/'
|
||||
}).on 'connection', (socket) ->
|
||||
# Binds a session to this connection.
|
||||
session = new $Session xo
|
||||
session.once 'close', -> socket.close()
|
||||
socket.once 'close', -> session.close()
|
||||
}
|
||||
wsServer.on 'connection', (socket) ->
|
||||
connection = new $Connection {
|
||||
close: socket.close.bind socket
|
||||
send: socket.send.bind socket
|
||||
}
|
||||
connection.id = conId++
|
||||
xo.connections[connection.id] = connection
|
||||
connection.on 'close', unregisterConnection
|
||||
|
||||
socket.on 'close', connection.close.bind connection
|
||||
|
||||
# Handles each request in a separate fiber.
|
||||
socket.on 'message', $fiberize (request) ->
|
||||
response = $handleJsonRpcCall api, session, request
|
||||
response = $handleJsonRpcCall api, connection, request
|
||||
|
||||
# The socket may have closed between the request and the
|
||||
# response.
|
||||
socket.send response if socket.readyState is socket.OPEN
|
||||
|
||||
socket.on 'error', $fiberize (error) ->
|
||||
console.error '[WARN] WebSocket connection', error
|
||||
socket.close()
|
||||
wsServer.on 'error', $fiberize (error) ->
|
||||
console.error '[WARN] WebSocket server', error
|
||||
wsServer.close()
|
||||
|
||||
# Creates a default user if there is none.
|
||||
unless $wait xo.users.exists()
|
||||
email = 'admin@admin.net'
|
||||
password = 'admin' # TODO: Should be generated.
|
||||
xo.users.create email, password, 'admin'
|
||||
console.log "[INFO] Default user: “#{email}” with password “#{password}”"
|
||||
|
||||
return $eventToPromise webServer, 'close'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var _ = require('underscore');
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
var Model = require('./model');
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
var Session = Model.extend({
|
||||
'constructor': function (xo) {
|
||||
Model.call(this);
|
||||
|
||||
|
||||
var self = this;
|
||||
var close = function () {
|
||||
self.close();
|
||||
};
|
||||
|
||||
// If the user associated to this session is deleted or
|
||||
// disabled, the session must close.
|
||||
this.on('change:user_id', function () {
|
||||
var event = 'user.revoked:'+ this.get('user_id');
|
||||
|
||||
xo.on(event, close);
|
||||
|
||||
// Prevents a memory leak.
|
||||
self.on('close', function () {
|
||||
xo.removeListener(event, close);
|
||||
});
|
||||
});
|
||||
|
||||
// If the token associated to this session is deleted, the
|
||||
// session must close.
|
||||
this.on('change:token_id', function () {
|
||||
var event = 'token.revoked:'+ this.get('token_id');
|
||||
|
||||
xo.on(event, close);
|
||||
|
||||
// Prevents a memory leak.
|
||||
self.on('close', function () {
|
||||
xo.removeListener(event, close);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
'close': function () {
|
||||
// This function can be called multiple times but will only
|
||||
// emit an event once.
|
||||
this.close = function () {};
|
||||
|
||||
this.emit('close');
|
||||
},
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
module.exports = Session;
|
||||
@@ -6,15 +6,12 @@ $xml2js = require 'xml2js'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Helpers for dealing with fibers.
|
||||
{$synchronize} = require './fibers-utils'
|
||||
|
||||
$helpers = require './helpers'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$isVMRunning = ->
|
||||
switch @genval.power_state
|
||||
switch @val.power_state
|
||||
when 'Paused', 'Running'
|
||||
true
|
||||
else
|
||||
@@ -24,7 +21,7 @@ $isHostRunning = ->
|
||||
@val.power_state is 'Running'
|
||||
|
||||
$isTaskLive = ->
|
||||
@genval.status is 'pending' or @genval.status is 'cancelling'
|
||||
@val.status is 'pending' or @val.status is 'cancelling'
|
||||
|
||||
# $xml2js.parseString() uses callback for synchronous code.
|
||||
$parseXML = (XML) ->
|
||||
@@ -206,7 +203,9 @@ module.exports = ->
|
||||
|
||||
# Injects various common definitions.
|
||||
@val.type = @name
|
||||
unless @singleton
|
||||
if @singleton
|
||||
@val.ref = -> @key
|
||||
else
|
||||
# This definition are for non singleton items only.
|
||||
@key = -> @genval.$ref
|
||||
@val.UUID = -> @genval.uuid
|
||||
@@ -255,12 +254,12 @@ module.exports = ->
|
||||
$memory: {
|
||||
usage: $sum {
|
||||
rule: 'host'
|
||||
if: -> @val?.memory?.usage?
|
||||
if: $isHostRunning
|
||||
val: -> @val.memory.usage
|
||||
}
|
||||
size: $sum {
|
||||
rule: 'host'
|
||||
if: -> @val?.memory?.size?
|
||||
if: $isHostRunning
|
||||
val: -> @val.memory.size
|
||||
}
|
||||
}
|
||||
@@ -355,6 +354,8 @@ module.exports = ->
|
||||
|
||||
enabled: -> @genval.enabled
|
||||
|
||||
current_operations: -> @genval.current_operations
|
||||
|
||||
hostname: -> @genval.hostname
|
||||
|
||||
iSCSI_name: -> @genval.other_config?.iscsi_iqn ? null
|
||||
@@ -372,8 +373,14 @@ module.exports = ->
|
||||
size: 0
|
||||
}
|
||||
|
||||
# TODO
|
||||
power_state: 'Running'
|
||||
power_state: ->
|
||||
if (
|
||||
@genval.enabled or
|
||||
not $_.contains @genval.current_operations, 'shutdown'
|
||||
)
|
||||
'Running'
|
||||
else
|
||||
'Halted'
|
||||
|
||||
# Local SRs are handled directly in `SR.$container`.
|
||||
SRs: $set {
|
||||
@@ -452,6 +459,8 @@ module.exports = ->
|
||||
else
|
||||
null
|
||||
|
||||
power_state: -> @genval.power_state
|
||||
|
||||
memory: ->
|
||||
{metrics, guest_metrics} = @data
|
||||
|
||||
@@ -478,8 +487,6 @@ module.exports = ->
|
||||
|
||||
memory
|
||||
|
||||
power_state: -> @genval.power_state
|
||||
|
||||
PV_drivers: ->
|
||||
{guest_metrics} = @data
|
||||
if guest_metrics
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
# Events handling.
|
||||
{EventEmitter: $EventEmitter} = require 'events'
|
||||
|
||||
# File handling.
|
||||
$fs = require 'fs'
|
||||
|
||||
# HTTP(S) handling.
|
||||
$http = require 'http'
|
||||
$https = require 'https'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Low level tools.
|
||||
$_ = require 'underscore'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Helpers for dealing with fibers.
|
||||
{$synchronize} = require './fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
# Events which may be emitted by a `http(s).Server`
|
||||
$events = [
|
||||
'checkContinue'
|
||||
'clientError'
|
||||
'close'
|
||||
'connect'
|
||||
'connection'
|
||||
'error'
|
||||
'request'
|
||||
'upgrade'
|
||||
]
|
||||
|
||||
$readFile = $synchronize 'readFile', $fs
|
||||
|
||||
#=====================================================================
|
||||
|
||||
# HTTP/HTTPS server which can listen on multiple address/ports or
|
||||
# sockets.
|
||||
class $WebServer extends $EventEmitter
|
||||
|
||||
constructor: ->
|
||||
@_servers = []
|
||||
@_notYetListening = 0
|
||||
|
||||
close: ->
|
||||
server.close() for server in @_servers
|
||||
|
||||
# Does not return anything.
|
||||
undefined
|
||||
|
||||
listen: ({host, port, socket, certificate, key}) ->
|
||||
server = if certificate? and key?
|
||||
$https.createServer {
|
||||
cert: $readFile certificate
|
||||
key: $readFile key
|
||||
}
|
||||
else
|
||||
$http.createServer()
|
||||
@_servers.push server
|
||||
|
||||
# Makes it start listening.
|
||||
if socket?
|
||||
server.listen socket
|
||||
else
|
||||
host ?= '0.0.0.0'
|
||||
server.listen port, host
|
||||
|
||||
++@_notYetListening
|
||||
|
||||
errorHandler = (error) =>
|
||||
# `address()` can only be used once listening.
|
||||
address = if socket?
|
||||
socket
|
||||
else
|
||||
"#{host}:#{port}"
|
||||
|
||||
# Prints a (hopefully) helpful message if the server could not
|
||||
# listen.
|
||||
console.log "[WARN] WebServer could not listen on #{address}"
|
||||
switch error.code
|
||||
when 'EACCES'
|
||||
console.log ' Access denied.'
|
||||
if port < 1024
|
||||
console.log ' Ports < 1024 are often reserved to privileges users.'
|
||||
when 'EADDRINUSE'
|
||||
console.log ' Address already in use.'
|
||||
|
||||
# This server will never start listening.
|
||||
--@_notYetListening
|
||||
|
||||
# Registers the error handler.
|
||||
server.on 'error', errorHandler
|
||||
|
||||
server.once 'listening', =>
|
||||
# Removes the error handler.
|
||||
server.removeListener 'error', errorHandler
|
||||
|
||||
# Prints a helpful message.
|
||||
address = server.address()
|
||||
if $_.isObject address
|
||||
{address, port} = address
|
||||
address = "#{address}:#{port}"
|
||||
console.log "WebServer listening on #{address}"
|
||||
|
||||
# If the web server is listening on all addresses, fire the
|
||||
# `listening` event.
|
||||
@emit 'listening' unless --@_notYetListening
|
||||
|
||||
# Forwards events to this object.
|
||||
$_.each $events, (event) =>
|
||||
server.on event, (args...) => @emit event, args...
|
||||
|
||||
# Does not return anything.
|
||||
undefined
|
||||
|
||||
#=====================================================================
|
||||
|
||||
module.exports = $WebServer
|
||||
@@ -8,7 +8,12 @@ $xmlrpc = require 'xmlrpc'
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# Helpers for dealing with fibers.
|
||||
{$sleep, $synchronize} = require './fibers-utils'
|
||||
{$wait} = require './fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$sleep = (delay) ->
|
||||
(cb) -> setTimeout cb, delay
|
||||
|
||||
#=====================================================================
|
||||
|
||||
@@ -36,9 +41,6 @@ class $XAPI
|
||||
rejectUnauthorized: false
|
||||
}
|
||||
|
||||
# Make `methodCall()` synchronous.
|
||||
@xmlrpc.methodCall = $synchronize 'methodCall', @xmlrpc
|
||||
|
||||
# Logs in.
|
||||
@logIn()
|
||||
|
||||
@@ -50,7 +52,8 @@ class $XAPI
|
||||
tries = @tries
|
||||
do helper = =>
|
||||
try
|
||||
result = @xmlrpc.methodCall method, args
|
||||
result = $wait (callback) =>
|
||||
@xmlrpc.methodCall method, args, callback
|
||||
|
||||
# Returns the plain result if it does not have a valid XAPI format.
|
||||
return result unless result.Status?
|
||||
@@ -63,7 +66,7 @@ class $XAPI
|
||||
catch error # Captures the error if it was thrown.
|
||||
|
||||
# If it failed too much times, just stops.
|
||||
throw error unless --@tries
|
||||
throw error unless --tries
|
||||
|
||||
# Gets the error code for transport errors and XAPI errors.
|
||||
code = error.code or error[0]
|
||||
@@ -81,13 +84,11 @@ class $XAPI
|
||||
# Node.js seems to reuse the broken socket, so we add a small
|
||||
# delay.
|
||||
#
|
||||
# TODO Add a limit to avoid trying indefinitely.
|
||||
#
|
||||
# TODO Magic number!!!
|
||||
#
|
||||
# I would like to be able to use a shorter delay but for some
|
||||
# reason, when we connect to XAPI at a give moment, the
|
||||
# connection hangs.
|
||||
# I would like to be able to use a shorter delay but for
|
||||
# some reason, when we connect to XAPI at a given moment,
|
||||
# the connection hangs.
|
||||
$sleep 500
|
||||
helper()
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ $hashy = require 'hashy'
|
||||
# Redis.
|
||||
$createRedisClient = (require 'then-redis').createClient
|
||||
|
||||
$Promise = require 'bluebird'
|
||||
|
||||
#---------------------------------------------------------------------
|
||||
|
||||
# A mapped collection is generated from another collection through a
|
||||
@@ -31,17 +33,16 @@ $Model = require './model'
|
||||
$XAPI = require './xapi'
|
||||
|
||||
# Helpers for dealing with fibers.
|
||||
{$fiberize, $synchronize, $wait} = require './fibers-utils'
|
||||
{$fiberize, $wait} = require './fibers-utils'
|
||||
|
||||
#=====================================================================
|
||||
|
||||
$hash = $synchronize 'hash', $hashy
|
||||
# Promise versions of asynchronous functions.
|
||||
$randomBytes = $Promise.promisify $crypto.randomBytes
|
||||
|
||||
$needsRehash = $hashy.needsRehash.bind $hashy
|
||||
|
||||
$randomBytes = $synchronize 'randomBytes', $crypto
|
||||
|
||||
$verifyHash = $synchronize 'verify', $hashy
|
||||
$hash = $hashy.hash
|
||||
$needsRehash = $hashy.needsRehash
|
||||
$verifyHash = $hashy.verify
|
||||
|
||||
#=====================================================================
|
||||
# Models and collections.
|
||||
@@ -57,7 +58,7 @@ class $Servers extends $RedisCollection
|
||||
class $Token extends $Model
|
||||
@generate: (userId) ->
|
||||
new $Token {
|
||||
id: ($randomBytes 32).toString 'base64'
|
||||
id: ($wait $randomBytes 32).toString 'base64'
|
||||
user_id: userId
|
||||
}
|
||||
|
||||
@@ -79,13 +80,13 @@ class $User extends $Model
|
||||
validate: -> # TODO
|
||||
|
||||
setPassword: (password) ->
|
||||
@set 'pw_hash', $hash password
|
||||
@set 'pw_hash', $wait $hash password
|
||||
|
||||
# Checks the password and updates the hash if necessary.
|
||||
checkPassword: (password) ->
|
||||
hash = @get 'pw_hash'
|
||||
|
||||
unless $verifyHash password, hash
|
||||
unless $wait $verifyHash password, hash
|
||||
return false
|
||||
|
||||
if $needsRehash hash
|
||||
@@ -153,6 +154,59 @@ class $XO extends $EventEmitter
|
||||
@_xobjs = new $MappedCollection()
|
||||
(require './spec').call @_xobjs
|
||||
|
||||
# When objects enter or exists, sends a notification to all
|
||||
# connected clients.
|
||||
do =>
|
||||
entered = {}
|
||||
exited = {}
|
||||
|
||||
dispatcherRegistered = false
|
||||
dispatcher = =>
|
||||
entered = $_.pluck entered, 'val'
|
||||
enterEvent = if entered.length
|
||||
JSON.stringify {
|
||||
jsonrpc: '2.0'
|
||||
method: 'all'
|
||||
params: {
|
||||
type: 'enter'
|
||||
items: entered
|
||||
}
|
||||
}
|
||||
exited = $_.pluck exited, 'val'
|
||||
exitEvent = if exited.length
|
||||
JSON.stringify {
|
||||
jsonrpc: '2.0'
|
||||
method: 'all'
|
||||
params: {
|
||||
type: 'exit'
|
||||
items: exited
|
||||
}
|
||||
}
|
||||
|
||||
if entered.length
|
||||
connection.send enterEvent for id, connection of @connections
|
||||
if exited.length
|
||||
connection.send exitEvent for id, connection of @connections
|
||||
dispatcherRegistered = false
|
||||
entered = {}
|
||||
exited = {}
|
||||
|
||||
@_xobjs.on 'any', (event, items) ->
|
||||
unless dispatcherRegistered
|
||||
dispatcherRegistered = true
|
||||
process.nextTick dispatcher
|
||||
|
||||
if event is 'exit'
|
||||
$_.each items, (item) ->
|
||||
{key} = item
|
||||
delete entered[key]
|
||||
exited[key] = item
|
||||
else
|
||||
$_.each items, (item) ->
|
||||
{key} = item
|
||||
delete exited[key]
|
||||
entered[key] = item
|
||||
|
||||
# Exports the map from UUIDs to keys.
|
||||
{$UUIDsToKeys: @_UUIDsToKeys} = (@_xobjs.get 'xo')
|
||||
|
||||
@@ -177,7 +231,7 @@ class $XO extends $EventEmitter
|
||||
# First construct the list of retrievable types. except pool
|
||||
# which will handled specifically.
|
||||
retrievableTypes = do ->
|
||||
methods = xapi.call 'system.listMethods'
|
||||
methods = $wait xapi.call 'system.listMethods'
|
||||
|
||||
types = []
|
||||
for method in methods
|
||||
@@ -195,7 +249,7 @@ class $XO extends $EventEmitter
|
||||
objects = {}
|
||||
|
||||
# Then retrieve the pool.
|
||||
pools = xapi.call 'pool.get_all_records'
|
||||
pools = $wait xapi.call 'pool.get_all_records'
|
||||
|
||||
# Gets the first pool and ensures it is the only one.
|
||||
ref = pool = null
|
||||
@@ -222,7 +276,7 @@ class $XO extends $EventEmitter
|
||||
# Then retrieve all other objects.
|
||||
for type in retrievableTypes
|
||||
try
|
||||
for ref, object of xapi.call "#{type}.get_all_records"
|
||||
for ref, object of $wait xapi.call "#{type}.get_all_records"
|
||||
normalizeObject object, ref, type
|
||||
|
||||
objects[ref] = object
|
||||
@@ -240,12 +294,12 @@ class $XO extends $EventEmitter
|
||||
|
||||
# Finally, monitors events.
|
||||
loop
|
||||
xapi.call 'event.register', ['*']
|
||||
$wait xapi.call 'event.register', ['*']
|
||||
|
||||
try
|
||||
# Once the session is registered, just handle events.
|
||||
loop
|
||||
event = xapi.call 'event.next'
|
||||
event = $wait xapi.call 'event.next'
|
||||
|
||||
updatedObjects = {}
|
||||
removedObjects = {}
|
||||
@@ -279,7 +333,7 @@ class $XO extends $EventEmitter
|
||||
# XAPI error, the program must unregister from events and then
|
||||
# register again.
|
||||
try
|
||||
xapi.call 'event.unregister', ['*']
|
||||
$wait xapi.call 'event.unregister', ['*']
|
||||
else
|
||||
throw error unless error[0] is 'SESSION_NOT_REGISTERED'
|
||||
|
||||
@@ -302,6 +356,9 @@ class $XO extends $EventEmitter
|
||||
|
||||
# TODO: Automatically disconnects from removed servers.
|
||||
|
||||
# Connections to users.
|
||||
@connections = {}
|
||||
|
||||
# Returns an object from its key or UUID.
|
||||
getObject: (key) ->
|
||||
# Gracefully handles UUIDs.
|
||||
|
||||
@@ -1,380 +0,0 @@
|
||||
/* jshint loopfunc:false */
|
||||
|
||||
var assert = require('assert');
|
||||
var sync = require('sync');
|
||||
var WS = require('ws');
|
||||
var _ = require('underscore');
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
var tests = {
|
||||
|
||||
'Session management': {
|
||||
|
||||
'Password sign in': function () {
|
||||
// Connects, signs in (with a password).
|
||||
var conn = this.connect();
|
||||
assert(conn('session.signInWithPassword', {
|
||||
'email': 'bob@gmail.com',
|
||||
'password': '123',
|
||||
}));
|
||||
},
|
||||
|
||||
'Password sign in with a inexistent user': function() {
|
||||
// Connects
|
||||
var conn = this.connect();
|
||||
try
|
||||
{
|
||||
conn('session.signInWithPassword', {
|
||||
'email': ' @gmail.com',
|
||||
'password': '123',
|
||||
});
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// Check the error.
|
||||
assert.strictEqual(e.code, 3);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
},
|
||||
|
||||
/* jshint maxlen:90 */
|
||||
'Password sign in withan existing user and incorrect password': function () {
|
||||
// Connects
|
||||
var conn = this.connect();
|
||||
|
||||
try
|
||||
{
|
||||
// Try to sign in (with password).
|
||||
conn('session.signInWithPassword', {
|
||||
'email': 'bob@gmail.com',
|
||||
'password': 'abc',
|
||||
});
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// Check if received invalid credential error.
|
||||
assert.strictEqual(e.code, 3);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
},
|
||||
|
||||
'Password sign in with user already authenticated': function() {
|
||||
// Connects, signs in (with a password).
|
||||
var conn = this.connect();
|
||||
conn('session.signInWithPassword', {
|
||||
'email': 'bob@gmail.com',
|
||||
'password': '123',
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
// Try to connect with other user account
|
||||
conn('session.signInWithPassword', {
|
||||
'email': 'toto@gmail.com',
|
||||
'password': '123',
|
||||
});
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// Check if received already authenticated error.
|
||||
assert.strictEqual(e.code, 4);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
},
|
||||
},
|
||||
|
||||
///////////////////////////////////////
|
||||
|
||||
'Token management': {
|
||||
|
||||
'Token sign in': function () {
|
||||
// Creates a token.
|
||||
var token = this.master('token.create');
|
||||
|
||||
// Connects, signs in (with a token).
|
||||
var conn = this.connect();
|
||||
assert(conn('session.signInWithToken', {
|
||||
'token': token
|
||||
}));
|
||||
|
||||
// Check if connected with the same user.
|
||||
assert.strictEqual(
|
||||
conn('session.getUserId'),
|
||||
this.master('session.getUserId')
|
||||
);
|
||||
},
|
||||
|
||||
'Token sign in with invalid parameter': function() {
|
||||
// Connects.
|
||||
var conn = this.connect();
|
||||
try
|
||||
{
|
||||
// Try to sign in (with a token).
|
||||
conn('session.signInWithToken', {
|
||||
'token': ' ',
|
||||
});
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
// Check if received invalid credential error.
|
||||
assert.strictEqual(e.code, 3);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(false);
|
||||
},
|
||||
|
||||
'Connection close out when token removed': function () {
|
||||
// Creates a token.
|
||||
var token = this.master('token.create');
|
||||
|
||||
// Connects again and uses the token to sign in.
|
||||
var conn = this.connect();
|
||||
conn('session.signInWithToken', {'token': token});
|
||||
|
||||
// Delete the tokens.
|
||||
this.master('token.delete', {'token': token});
|
||||
|
||||
// Checks the connection is closed.
|
||||
assert.throws(function () {
|
||||
conn('session.getUserId');
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
///////////////////////////////////////
|
||||
|
||||
'User management': {
|
||||
|
||||
'Create user': function() {
|
||||
// Connects, sign in (with a password).
|
||||
var conn = this.connect();
|
||||
conn('session.signInWithPassword', {
|
||||
'email': 'bob@gmail.com',
|
||||
'password': '123',
|
||||
});
|
||||
|
||||
// Create a user account.
|
||||
assert(conn('user.create', {
|
||||
'email': 'tintin@gmail.com',
|
||||
'password': 'abc',
|
||||
'permission': 'admin',
|
||||
}));
|
||||
},
|
||||
|
||||
'Delete user': function() {
|
||||
// Connects, sign in (with a password).
|
||||
var user_id = this.master('user.create', {
|
||||
'email': 'fox@gmail.com',
|
||||
'password': '123',
|
||||
'permission': 'none',
|
||||
});
|
||||
|
||||
// Delete user
|
||||
assert(this.master('user.delete', {
|
||||
'id': user_id,
|
||||
}));
|
||||
},
|
||||
|
||||
'Connection close out when user removed': function() {
|
||||
// Connects, sign in (with a password).
|
||||
var user_id = this.master('user.create', {
|
||||
'email': 'fox@gmail.com',
|
||||
'password': '123',
|
||||
'permission': 'none',
|
||||
});
|
||||
|
||||
// Connects, sign in (with a password)
|
||||
var conn = this.connect();
|
||||
conn('session.signInWithPassword', {
|
||||
'email': 'fox@gmail.com',
|
||||
'password': '123',
|
||||
});
|
||||
|
||||
// Delete the user
|
||||
this.master('user.delete', {
|
||||
'id': user_id,
|
||||
});
|
||||
|
||||
// Checks the connection is closed.
|
||||
assert.throws(function () {
|
||||
conn('session.getUserId');
|
||||
});
|
||||
},
|
||||
|
||||
'Change password': function() {
|
||||
// Create new account.
|
||||
this.master('user.create', {
|
||||
'email': 'fox@gmail.com',
|
||||
'password': '123',
|
||||
'permission': 'none',
|
||||
});
|
||||
|
||||
// Connects, sign in (with a password).
|
||||
var conn = this.connect();
|
||||
conn('session.signInWithPassword', {
|
||||
'email': 'fox@gmail.com',
|
||||
'password': '123',
|
||||
});
|
||||
|
||||
// Change password.
|
||||
conn('user.changePassword', {
|
||||
'old': '123',
|
||||
'new': 'abc',
|
||||
});
|
||||
|
||||
// Check if password has changed
|
||||
var conn2 = this.connect();
|
||||
assert(conn2('session.signInWithPassword', {
|
||||
'email': 'fox@gmail.com',
|
||||
'password': 'abc',
|
||||
}));
|
||||
},
|
||||
|
||||
'Get all users': function() {
|
||||
var users = this.master('user.getAll');
|
||||
assert(_.isArray(users));
|
||||
},
|
||||
|
||||
'Set user': function() {
|
||||
var user_id = this.master('user.create', {
|
||||
'email': 'link@gmail.com',
|
||||
'password': 'abc',
|
||||
'permission': 'none',
|
||||
});
|
||||
|
||||
this.master('user.set', {
|
||||
'id': user_id,
|
||||
'email': 'mario@gmail.com',
|
||||
'password': '123',
|
||||
});
|
||||
|
||||
var conn = this.connect();
|
||||
assert(conn('session.signInWithPassword', {
|
||||
'email': 'mario@gmail.com',
|
||||
'password': '123',
|
||||
}));
|
||||
},
|
||||
|
||||
},
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
var next_id = 0;
|
||||
function call(method, params)
|
||||
{
|
||||
return function (callback)
|
||||
{
|
||||
var request = {
|
||||
'jsonrpc': '2.0',
|
||||
'id': next_id++,
|
||||
'method': method,
|
||||
'params': params || {},
|
||||
};
|
||||
this.send(JSON.stringify(request), function (error) {
|
||||
if (error)
|
||||
{
|
||||
callback(error);
|
||||
}
|
||||
});
|
||||
|
||||
this.once('message', function (response) {
|
||||
try
|
||||
{
|
||||
response = JSON.parse(response.toString());
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
callback(e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.error)
|
||||
{
|
||||
// To help find the problem, the request is included
|
||||
// in the error.
|
||||
var error = response.error;
|
||||
error.request = request;
|
||||
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null, response.result);
|
||||
});
|
||||
}.sync(this);
|
||||
}
|
||||
|
||||
function connect(url)
|
||||
{
|
||||
var socket;
|
||||
|
||||
(function (callback)
|
||||
{
|
||||
socket = new WS(url);
|
||||
socket.on('open', function () {
|
||||
callback(null, socket);
|
||||
});
|
||||
socket.on('error', function (error) {
|
||||
callback(error);
|
||||
});
|
||||
}).sync();
|
||||
|
||||
var conn = function (method, params) {
|
||||
return call.call(socket, method, params);
|
||||
};
|
||||
return conn;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
sync(function () {
|
||||
|
||||
// All tests have access to this master connection to create
|
||||
// initial data.
|
||||
var master = connect('ws://localhost:8080/');
|
||||
master('session.signInWithPassword', {
|
||||
'email': 'bob@gmail.com',
|
||||
'password': '123',
|
||||
});
|
||||
|
||||
var self = {
|
||||
'connect': function () {
|
||||
return connect('ws://localhost:8080/');
|
||||
},
|
||||
'master': master,
|
||||
};
|
||||
|
||||
for (var category in tests)
|
||||
{
|
||||
console.log();
|
||||
console.log(category);
|
||||
console.log('====================');
|
||||
|
||||
for (var test in tests[category])
|
||||
{
|
||||
console.log('- '+ test);
|
||||
|
||||
var f = tests[category][test];
|
||||
try
|
||||
{
|
||||
f.call(self);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, function () {
|
||||
process.exit();
|
||||
});
|
||||
|
||||
46
xo-server
46
xo-server
@@ -1,46 +0,0 @@
|
||||
#/bin/sh -eu
|
||||
|
||||
# This file is a part of Xen Orchestra Server.
|
||||
#
|
||||
# Xen Orchestra Server is free software: you can redistribute it
|
||||
# and/or modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
#
|
||||
# Xen Orchestra Server is distributed in the hope that it will be
|
||||
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
|
||||
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Xen Orchestra Server. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# @author Julien Fontanet <julien.fontanet@vates.fr>
|
||||
# @license http://www.gnu.org/licenses/gpl-3.0-standalone.html GPLv3
|
||||
#
|
||||
# @package Xen Orchestra Server
|
||||
|
||||
MAIN='src/main.coffee'
|
||||
|
||||
COFFEE='./node_modules/.bin/coffee'
|
||||
|
||||
#######################################
|
||||
|
||||
cd -P "$(dirname "$(which "$0")")"
|
||||
|
||||
########################################
|
||||
|
||||
if [ "${1:-}" = '--debug' ]
|
||||
then
|
||||
shift
|
||||
|
||||
# Launch XO-Server in debug mode.
|
||||
"$COFFEE" --nodejs --debug-brk "$MAIN" "$@" > /dev/null &
|
||||
|
||||
# Runs Node Inspector (avoids the recommended alternate HTTP port
|
||||
# for XO-Server).
|
||||
exec ./node_modules/.bin/node-inspector --web-port 64985
|
||||
else
|
||||
exec "$COFFEE" "$MAIN" "$@"
|
||||
fi
|
||||
Reference in New Issue
Block a user