Compare commits

...

67 Commits

Author SHA1 Message Date
Julien Fontanet
c26efb111e 3.5.0-alpha2 2014-08-14 16:06:35 +02:00
Julien Fontanet
f07cbe0087 Config file can now be in /etc/xo-server & basic help message. 2014-08-14 16:05:23 +02:00
Julien Fontanet
7d72e196e0 Remove unused deps. 2014-08-12 16:13:28 +02:00
Julien Fontanet
9bc935da96 Update JSHint config. 2014-08-12 16:13:28 +02:00
Julien Fontanet
d2e3b317ef 3.5.0-alpha1 2014-08-12 14:18:31 +02:00
Julien Fontanet
0edd39805b Minor fixes. 2014-08-11 13:22:53 +02:00
Julien Fontanet
15189014d0 npm start 2014-08-11 13:16:47 +02:00
Julien Fontanet
46ab3ed1ea <Host>.power_state 2014-08-11 13:11:35 +02:00
Olivier Lambert
edfbbe8c5a add enable and disable method 2014-08-05 12:41:18 +02:00
Olivier Lambert
9d35295b47 add current_operation to be able to see if the host is halted or just disabled 2014-08-05 12:40:59 +02:00
Julien Fontanet
b59d07fb5f TODO in vm.stop(). 2014-07-28 16:45:16 +02:00
Julien Fontanet
9c0fda2b52 vm.stop() improvements
- The `force` parameter is optional and default to `false`.
- If `force` is `true`, always do a hard shutdown.
- Throw `INVALID_PARAMS` when a clean shutdown failed due to missing PV drivers.
2014-07-28 16:32:36 +02:00
Julien Fontanet
9ab1a980a7 Do not hide errors. 2014-07-28 11:24:11 +02:00
Julien Fontanet
07d91b8f50 3.5.0-alpha0 2014-07-26 18:02:38 +02:00
Julien Fontanet
5645921a8e Update deps. 2014-07-26 18:01:27 +02:00
Julien Fontanet
9c17ac1c33 Update deps. 2014-07-07 14:08:37 +02:00
Julien Fontanet
a80968aaa7 Minor updates. 2014-05-30 19:45:25 +01:00
Julien Fontanet
2589a15e0a Remove useless file. 2014-05-30 19:45:25 +01:00
Olivier Lambert
950efde3b8 initial import and export stuff in VM object 2014-05-30 18:50:48 +02:00
Julien Fontanet
de8d5e4489 CLI is now installable via npm & testable. 2014-05-30 10:38:37 +01:00
Julien Fontanet
d6eefa5185 Merge branch 'master' into next-release 2014-05-29 17:41:45 +02:00
Julien Fontanet
5ad0cffbb3 Minor updates. 2014-05-29 17:40:58 +02:00
Julien Fontanet
89bd00c65e All API methods must return a value. 2014-05-27 19:53:28 +01:00
Julien Fontanet
b7e7a0df94 More introspection. 2014-05-24 18:14:04 +02:00
Julien Fontanet
e3f02fc4d6 User cannot delete himself (fix vatesfr/xo-web#104). 2014-05-24 15:46:14 +02:00
Julien Fontanet
81bece9dc4 Remove unused API methods. 2014-05-24 15:02:20 +02:00
Julien Fontanet
db4b05e9d2 Various updates. 2014-05-24 10:42:20 +02:00
Julien Fontanet
0b9029c7b5 Use http-server-plus instead of custom implementation. 2014-05-22 18:02:45 +02:00
Julien Fontanet
7fd9a65ce5 Comment. 2014-05-22 12:12:23 +02:00
Julien Fontanet
8cd2d52b3d 3.4.0 2014-05-22 11:43:32 +02:00
Julien Fontanet
d8e8c5504a Merge branch 'next-release' 2014-05-22 11:41:49 +02:00
Julien Fontanet
7535f6af51 Merge branch 'events' into next-release 2014-05-21 20:02:32 +02:00
Julien Fontanet
347effcf70 Minor fixes. 2014-05-21 19:49:58 +02:00
Julien Fontanet
2b2176ba24 Update deps. 2014-05-21 16:49:15 +02:00
Julien Fontanet
a0b2d9384d Events are ok. 2014-05-21 15:25:34 +02:00
Julien Fontanet
58cc047ffa Comments. 2014-05-12 19:58:00 +02:00
Julien Fontanet
cfacd90815 Various updates. 2014-05-12 19:55:49 +02:00
Julien Fontanet
f46dca910b WIP. 2014-05-09 17:27:13 +02:00
Julien Fontanet
d362dfc359 Initial work on events. 2014-05-07 14:05:01 +02:00
Julien Fontanet
4dff1ead06 Various updates. 2014-04-24 15:33:55 +02:00
Julien Fontanet
ec8aa28aea Same thing for the WS server. 2014-04-22 16:42:47 +02:00
Julien Fontanet
e49abd909d WebSocket errors should not crash the server. 2014-04-22 16:39:48 +02:00
Julien Fontanet
ce2c700f1d new Promise() instead of Promise.defer() as suggested in Bluebird's doc. 2014-04-19 17:15:50 +02:00
Julien Fontanet
dcb9f90a8c Typo. 2014-04-19 17:05:36 +02:00
Olivier Lambert
c936d4f5a3 add support for convert vm into template 2014-04-18 13:15:19 +02:00
Olivier Lambert
44f9292bfd add CoW clone capabilities 2014-04-16 18:00:29 +02:00
Olivier Lambert
259fae0134 support now clone/copy 2014-04-16 17:30:50 +02:00
Julien Fontanet
04c28824b2 Remove old tests. 2014-04-16 16:26:26 +02:00
Julien Fontanet
c5bf4d1723 Get parameters description out of the API methods. 2014-04-16 16:17:24 +02:00
Julien Fontanet
b72a7f2c22 Minor fix. 2014-04-15 17:58:16 +02:00
Julien Fontanet
71cef03a4b Minor fix. 2014-04-15 17:55:59 +02:00
Julien Fontanet
13abde0b0f Various updates. 2014-04-15 17:40:35 +02:00
Julien Fontanet
e4850e0a48 TODOs. 2014-04-15 16:53:16 +02:00
Julien Fontanet
1c225ff1ec Remove existing install-repository entry in VM if any. 2014-04-15 16:29:56 +02:00
Julien Fontanet
1b4e80858d Major indentation fix. 2014-04-15 16:15:01 +02:00
Julien Fontanet
4f683edcb9 Dependencies update. 2014-04-15 16:14:48 +02:00
Julien Fontanet
834fe6da45 hashy uses promises out of the box. 2014-04-05 11:02:12 +02:00
Julien Fontanet
35678661d1 Deps updates & Initial API introspection. 2014-04-05 10:54:26 +02:00
Julien Fontanet
1ef6b0b5f9 Merge branch 'next-release' 2014-03-28 17:02:14 +01:00
Julien Fontanet
d1cf246600 3.3.1 2014-03-28 17:01:30 +01:00
Julien Fontanet
093e09c732 Minor fix. 2014-03-25 18:24:58 +01:00
Julien Fontanet
5c25a82a2b Bug fixes (Redis implementation). 2014-03-24 13:51:25 +01:00
Julien Fontanet
2f224fc26e Minor fix. 2014-03-15 12:02:16 +01:00
Julien Fontanet
2969f8da36 Skips a failed test. 2014-03-14 18:28:59 +01:00
Julien Fontanet
8959d12746 Using bluebird instead of q for promises. 2014-03-14 18:25:49 +01:00
Julien Fontanet
6e6574a235 Do not use npm dedupe as it cause problem with xmlbuilder (fix #24). 2014-03-14 11:49:58 +01:00
Julien Fontanet
bf3eea4a41 Updates dependencies. 2014-03-13 17:10:11 +01:00
40 changed files with 1060 additions and 1682 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
/node_modules/
npm-debug.log
.xo-server.*

203
.jshintrc
View File

@@ -1,126 +1,91 @@
{
// --------------------------------------------------------------------
// 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/
// Julien Fontanet JSHint configuration
// https://gist.github.com/julien-f/8095615
//
// Changes from defaults:
// - all enforcing options (except `++` & `--`) enabled
// - single quotes
// - indentation set to 2 instead of 4
// - almost all relaxing options disabled
// - allow expression statements (necessary for chai.expect())
// - allow global strict (most of my devs are in Node.js or Browserify)
// - environments are set to Browserify, mocha & Node.js
//
// See http://jshint.com/docs/ for more details
// == 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.
"maxerr" : 50, // {int} Maximum error before stopping
"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" : true, // 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.
// Enforcing
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
"camelcase" : true, // true: Identifiers must be in camelCase
"curly" : true, // true: Require {} for every new block or scope
"eqeqeq" : true, // true: Require triple equals (===) for comparison
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
"immed" : true, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
"indent" : 2, // {int} Number of spaces to use for indentation
"latedef" : true, // true: Require variables/functions to be defined before being used
"newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()`
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
"noempty" : true, // true: Prohibit use of empty blocks
"nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment)
"plusplus" : false, // true: Prohibit use of `++` & `--`
"quotmark" : "single", // Quotation mark consistency:
// false : do nothing (default)
// true : ensure whatever is used is consistent
// "single" : require single quotes
// "double" : require double quotes
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
"unused" : true, // true: Require all defined variables be used
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
"maxparams" : 4, // {int} Max number of formal params allowed per function
"maxdepth" : 3, // {int} Max depth of nested blocks (within functions)
"maxstatements" : 20, // {int} Max number statements per function
"maxcomplexity" : 7, // {int} Max cyclomatic complexity per function
"maxlen" : 80, // {int} Max number of characters per line
// == 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.
// Relaxing
"asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
"boss" : false, // true: Tolerate assignments where comparisons would be expected
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
"eqnull" : false, // true: Tolerate use of `== null`
"es5" : false, // true: Allow ES5 syntax (ex: getters and setters)
"esnext" : false, // true: Allow ES.next (ES6) syntax (ex: `const`)
"moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
// (ex: `for each`, multiple try/catch, function expression…)
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
"expr" : true, // true: Tolerate `ExpressionStatement` as Programs
"funcscope" : false, // true: Tolerate defining variables inside control statements
"globalstrict" : true, // true: Allow global "use strict" (also enables 'strict')
"iterator" : false, // true: Tolerate using the `__iterator__` property
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
"laxcomma" : false, // true: Tolerate comma-first style coding
"loopfunc" : false, // true: Tolerate functions being defined in loops
"multistr" : false, // true: Tolerate multi-line strings
"proto" : false, // true: Tolerate using the `__proto__` property
"scripturl" : false, // true: Tolerate script-targeted URLs
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
"validthis" : false, // true: Tolerate using this in a non-constructor function
"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
"browser" : false, // Web Browser (window, document, etc)
//"browserify" : true, // Browserify (node.js code in the browser)
"couch" : false, // CouchDB
"devel" : true, // Development/debugging (alert, confirm, etc)
"dojo" : false, // Dojo Toolkit
"jquery" : false, // jQuery
"mocha" : true, // mocha
"mootools" : false, // MooTools
"node" : true, // Node.js
"nonstandard" : false, // Widely adopted globals (escape, unescape, etc)
"prototypejs" : false, // Prototype and Scriptaculous
"rhino" : false, // Rhino
"worker" : false, // Web Workers
"wsh" : false, // Windows Scripting Host
"yui" : false, // Yahoo User Interface
// == 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
}
// Custom Globals
"globals" : {} // additional predefined global variables
}

7
bin/xo-server Executable file
View File

@@ -0,0 +1,7 @@
#!/usr/bin/env node
'use strict';
//====================================================================
require('exec-promise')(require('../'));

6
index.js Normal file
View File

@@ -0,0 +1,6 @@
'use strict';
//====================================================================
require('coffee-script/register');
module.exports = require('./src/main');

297
npm-shrinkwrap.json generated
View File

@@ -1,297 +0,0 @@
{
"name": "XO-Server",
"version": "3.1.0",
"dependencies": {
"async": {
"version": "0.2.9",
"from": "async@0.2.9"
},
"backoff": {
"version": "2.3.0",
"from": "backoff@~2.3.0"
},
"bindings": {
"version": "1.0.0",
"from": "bindings@1.0.0"
},
"coffee-script": {
"version": "1.7.1",
"from": "coffee-script@~1.7.1",
"dependencies": {
"mkdirp": {
"version": "0.3.5",
"from": "mkdirp@~0.3.5"
}
}
},
"connect": {
"version": "2.13.0",
"from": "connect@~2.13.0",
"dependencies": {
"compressible": {
"version": "1.0.0",
"from": "compressible@1.0.0",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-1.0.0.tgz"
},
"batch": {
"version": "0.5.0",
"from": "batch@0.5.0"
},
"qs": {
"version": "0.6.6",
"from": "qs@0.6.6"
},
"cookie-signature": {
"version": "1.0.1",
"from": "cookie-signature@1.0.1"
},
"buffer-crc32": {
"version": "0.2.1",
"from": "buffer-crc32@0.2.1"
},
"cookie": {
"version": "0.1.0",
"from": "cookie@0.1.0"
},
"send": {
"version": "0.1.4",
"from": "send@0.1.4",
"dependencies": {
"mime": {
"version": "1.2.11",
"from": "mime@~1.2.9"
},
"range-parser": {
"version": "0.0.4",
"from": "range-parser@0.0.4"
}
}
},
"bytes": {
"version": "0.2.1",
"from": "bytes@0.2.1"
},
"fresh": {
"version": "0.2.0",
"from": "fresh@0.2.0"
},
"pause": {
"version": "0.0.1",
"from": "pause@0.0.1"
},
"uid2": {
"version": "0.0.3",
"from": "uid2@0.0.3"
},
"debug": {
"version": "0.7.4",
"from": "debug@>= 0.7.3 < 1"
},
"methods": {
"version": "0.1.0",
"from": "methods@0.1.0"
},
"raw-body": {
"version": "1.1.2",
"from": "raw-body@1.1.2"
},
"negotiator": {
"version": "0.3.0",
"from": "negotiator@0.3.0"
},
"multiparty": {
"version": "2.2.0",
"from": "multiparty@2.2.0",
"dependencies": {
"readable-stream": {
"version": "1.1.11",
"from": "readable-stream@~1.1.9",
"dependencies": {
"core-util-is": {
"version": "1.0.1",
"from": "core-util-is@~1.0.0"
},
"string_decoder": {
"version": "0.10.25-1",
"from": "string_decoder@~0.10.x"
},
"debuglog": {
"version": "0.0.2",
"from": "debuglog@0.0.2"
}
}
},
"stream-counter": {
"version": "0.2.0",
"from": "stream-counter@~0.2.0"
}
}
}
}
},
"extendable": {
"version": "0.0.6",
"from": "extendable@~0.0.6",
"resolved": "https://registry.npmjs.org/extendable/-/extendable-0.0.6.tgz"
},
"fibers": {
"version": "1.0.1",
"from": "fibers@~1.0.1",
"resolved": "https://registry.npmjs.org/fibers/-/fibers-1.0.1.tgz"
},
"hashy": {
"version": "0.2.1",
"from": "hashy@~0.2.1",
"resolved": "https://registry.npmjs.org/hashy/-/hashy-0.2.1.tgz",
"dependencies": {
"bcrypt": {
"version": "0.7.7",
"from": "bcrypt@~0.7.7",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-0.7.7.tgz"
}
}
},
"hiredis": {
"version": "0.1.16",
"from": "hiredis@~0.1.16",
"resolved": "https://registry.npmjs.org/hiredis/-/hiredis-0.1.16.tgz"
},
"js-yaml": {
"version": "3.0.1",
"from": "js-yaml@~3.0.1",
"dependencies": {
"argparse": {
"version": "0.1.15",
"from": "argparse@~ 0.1.11",
"dependencies": {
"underscore": {
"version": "1.4.4",
"from": "underscore@~1.4.3"
},
"underscore.string": {
"version": "2.3.3",
"from": "underscore.string@~2.3.1"
}
}
},
"esprima": {
"version": "1.0.4",
"from": "esprima@~ 1.0.2"
}
}
},
"nconf": {
"version": "0.6.9",
"from": "nconf@~0.6.9",
"resolved": "https://registry.npmjs.org/nconf/-/nconf-0.6.9.tgz",
"dependencies": {
"ini": {
"version": "1.1.0",
"from": "ini@1.x.x"
},
"optimist": {
"version": "0.6.0",
"from": "optimist@0.6.0",
"dependencies": {
"wordwrap": {
"version": "0.0.2",
"from": "wordwrap@~0.0.2"
},
"minimist": {
"version": "0.0.7",
"from": "minimist@~0.0.1"
}
}
}
}
},
"q": {
"version": "1.0.0",
"from": "q@~1.0.0"
},
"redis": {
"version": "0.10.1",
"from": "redis@~0.10.1"
},
"require-tree": {
"version": "0.3.3",
"from": "require-tree@~0.3.2",
"resolved": "https://registry.npmjs.org/require-tree/-/require-tree-0.3.3.tgz"
},
"schema-inspector": {
"version": "1.3.9",
"from": "schema-inspector@~1.3.8"
},
"sync": {
"version": "0.2.2",
"from": "sync@~0.2.2",
"resolved": "https://registry.npmjs.org/sync/-/sync-0.2.2.tgz"
},
"then-redis": {
"version": "0.3.10",
"from": "then-redis@~0.3.9",
"resolved": "https://registry.npmjs.org/then-redis/-/then-redis-0.3.10.tgz",
"dependencies": {
"when": {
"version": "2.7.1",
"from": "when@2.7.1",
"resolved": "https://registry.npmjs.org/when/-/when-2.7.1.tgz"
}
}
},
"underscore": {
"version": "1.6.0",
"from": "underscore@~1.6.0"
},
"ws": {
"version": "0.4.31",
"from": "ws@~0.4.31",
"resolved": "https://registry.npmjs.org/ws/-/ws-0.4.31.tgz",
"dependencies": {
"commander": {
"version": "0.6.1",
"from": "commander@~0.6.1"
},
"nan": {
"version": "0.3.2",
"from": "nan@~0.3.0"
},
"tinycolor": {
"version": "0.0.1",
"from": "tinycolor@0.x",
"resolved": "https://registry.npmjs.org/tinycolor/-/tinycolor-0.0.1.tgz"
},
"options": {
"version": "0.0.5",
"from": "options@>=0.0.5",
"resolved": "https://registry.npmjs.org/options/-/options-0.0.5.tgz"
}
}
},
"xml2js": {
"version": "0.4.1",
"from": "xml2js@~0.4.1",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.1.tgz",
"dependencies": {
"sax": {
"version": "0.5.8",
"from": "sax@0.5.x"
}
}
},
"xmlbuilder": {
"version": "0.4.2",
"from": "xmlbuilder@0.4.2"
},
"xmlrpc": {
"version": "1.2.0",
"from": "xmlrpc@~1.2.0",
"dependencies": {
"sax": {
"version": "0.4.3",
"from": "sax@0.4.x"
}
}
}
}
}

View File

@@ -1,52 +1,63 @@
{
"author": {
"name": "Julien Fontanet",
"email": "julien.fontanet@vates.fr",
"url": "http://vates.fr/"
},
"name": "XO-Server",
"version": "3.3.0",
"name": "xo-server",
"version": "3.5.0-alpha2",
"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": {
"backoff": "~2.3.0",
"app-conf": "^0.1.3",
"backoff": "~2.4.0",
"bluebird": "^2.2.2",
"chalk": "^0.5.1",
"coffee-script": "~1.7.1",
"connect": "~2.13.0",
"compiled-accessors": "^0.2.0",
"connect": "^2.25.5",
"event-to-promise": "^0.3.1",
"exec-promise": "^0.4.0",
"extendable": "~0.0.6",
"fibers": "~1.0.1",
"hashy": "~0.2.1",
"hiredis": "~0.1.16",
"js-yaml": "~3.0.1",
"hashy": "~0.3.6",
"http-server-plus": "^0.2.3",
"js-yaml": "~3.1.0",
"lodash.isfunction": "^2.4.1",
"lodash.map": "^2.4.1",
"nconf": "~0.6.9",
"q": "~1.0.0",
"redis": "~0.10.1",
"require-tree": "~0.3.2",
"schema-inspector": "~1.3.8",
"sync": "~0.2.2",
"then-redis": "~0.3.9",
"require-tree": "~0.3.3",
"schema-inspector": "^1.4.8",
"serve-static": "^1.5.1",
"then-redis": "~0.3.12",
"underscore": "~1.6.0",
"ws": "~0.4.31",
"xml2js": "~0.4.1",
"ws": "~0.4.32",
"xml2js": "~0.4.4",
"xmlrpc": "~1.2.0"
},
"devDependencies": {
"chai": "~1.9.0",
"glob": "~3.2.8",
"mocha": "~1.17.1",
"mocha-as-promised": "~2.0.0",
"node-inspector": "~0.6.1",
"sinon": "~1.8.2"
},
"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"
"chai": "~1.9.1",
"glob": "~4.0.5",
"mocha": "^1.21.4",
"node-inspector": "^0.7.4",
"sinon": "^1.10.3"
},
"scripts": {
"test": "./run-tests"
},
"license": "AGPL3"
"start": "node bin/xo-server",
"test": "coffee run-tests"
}
}

View File

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

View File

@@ -1,4 +1,13 @@
# Note: Relative paths will be resolved from XO-Server's directory.
# Example XO-Server configuration.
# This file is automatically looking for at the following places:
# - `./.xo-server.yaml` up to `/.xo-server.yaml`
# - `$HOME/.config/xo-server/config.yaml`
# - `/etc/xo-server/config.yaml`
#
# The first entries have priority.
# Note: paths are relative to the configuration file.
#=====================================================================

View File

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

View File

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

View File

@@ -23,6 +23,12 @@ function $deprecated(fn)
};
}
var wrap = function (val) {
return function () {
return val;
};
};
//////////////////////////////////////////////////////////////////////
// TODO: Helper functions that could be written:
@@ -34,7 +40,7 @@ helpers.checkPermission = function (permission)
{
// TODO: Handle token permission.
var userId = this.session.get('user_id');
var userId = this.session.get('user_id', undefined);
if (undefined === userId)
{
@@ -132,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) {
@@ -218,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('.');
@@ -237,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.
@@ -252,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);
}
};
@@ -263,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();
@@ -272,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;
@@ -280,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
{
@@ -290,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));
})();

View File

@@ -2,20 +2,7 @@
#=====================================================================
exports.set = ->
params = @getParams {
id: { type: 'string' }
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
@@ -32,15 +19,21 @@ exports.set = ->
$wait xapi.call "host.set_#{field}", host.ref, params[param]
return
exports.restart = ->
{
id
} = @getParams {
id: { type: 'string' }
}
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
@@ -54,16 +47,12 @@ exports.restart = ->
$wait xapi.call 'host.reboot', host.ref
return true
exports.restart.permission = 'admin'
exports.restart.params = {
id: { type: 'string' }
}
exports.restart_agent = ->
{
id
} = @getParams {
id: { type: 'string' }
}
@checkPermission 'admin'
exports.restart_agent = ({id}) ->
try
host = @getObject id
catch
@@ -74,16 +63,12 @@ exports.restart_agent = ->
$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
} = @getParams {
id: { type: 'string' }
}
@checkPermission 'admin'
exports.stop = ({id}) ->
try
host = @getObject id
catch
@@ -95,16 +80,12 @@ exports.stop = ->
$wait xapi.call 'host.shutdown', host.ref
return true
exports.stop.permission = 'admin'
exports.stop.params = {
id: { type: 'string' }
}
exports.detach = ->
{
id
} = @getParams {
id: { type: 'string' }
}
@checkPermission 'admin'
exports.detach = ({id}) ->
try
host = @getObject id
catch
@@ -115,3 +96,39 @@ exports.detach = ->
$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' }
}

View File

@@ -2,16 +2,7 @@
#=====================================================================
exports.delete = ->
{
id
} = @getParams {
id: { type: 'string' }
}
# Current user must be an administrator.
@checkPermission 'admin'
exports.delete = ({id}) ->
try
message = @getObject id
catch
@@ -21,4 +12,8 @@ exports.delete = ->
$wait xapi.call 'message.destroy', message.ref
return true
return true
exports.delete.permission = 'admin'
exports.delete.params =
id:
type: 'string'

View File

@@ -3,17 +3,6 @@
#=====================================================================
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
@@ -29,4 +18,14 @@ exports.set = ->
$wait xapi.call "pool.set_#{field}", pool.ref, params[param]
return
return true
exports.set.permission = 'admin'
exports.set.params =
id:
type: 'string'
name_label:
type: 'string'
optional: true
name_description:
type: 'string'
optional: true

View File

@@ -6,17 +6,7 @@
# 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
@@ -24,26 +14,29 @@ exports.add = ->
}
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
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()
@@ -52,19 +45,10 @@ exports.getAll = ->
servers[i] = @getServerPublicProperties server
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
@@ -80,6 +64,20 @@ exports.set = ->
$wait @servers.update server
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 = ->

View File

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

View File

@@ -2,18 +2,7 @@
#=====================================================================
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'
exports.set = (params) ->
try
SR = @getObject params.id
catch
@@ -29,18 +18,20 @@ exports.set = ->
$wait xapi.call "SR.set_#{field}", SR.ref, params[param]
return
return true
exports.set.permission = 'admin'
exports.set.params = {
id: { type: 'string' }
exports.scan = ->
params = @getParams {
id: { type: 'string' }
}
name_label: { type: 'string', optional: true }
# Current user must be an administrator.
@checkPermission 'admin'
name_description: { type: 'string', optional: true }
}
exports.scan = ({id}) ->
try
SR = @getObject params.id
SR = @getObject id
catch
@throw 'NO_SUCH_OBJECT'
@@ -48,4 +39,8 @@ exports.scan = ->
$wait xapi.call 'SR.scan', SR.ref
return
return true
exports.scan.permission = 'admin'
exports.scan.params = {
id: { type: 'string' }
}

View File

@@ -17,11 +17,7 @@ exports.create = ->
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,3 +26,6 @@ exports.delete = ->
$wait @tokens.remove tokenId
return true
exports.delete.params = {
token: { type: 'string' }
}

View File

@@ -3,49 +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
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
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'
@@ -59,13 +46,14 @@ exports.changePassword = ->
$wait @users.update user
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
@@ -76,12 +64,12 @@ exports.get = ->
@throw 'NO_SUCH_OBJECT' unless 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()
@@ -90,19 +78,10 @@ exports.getAll = ->
users[i] = @getUserPublicProperties user
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
@@ -118,3 +97,10 @@ exports.set = ->
$wait @users.update user
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 }
}

View File

@@ -2,14 +2,7 @@
#=====================================================================
exports.delete = ->
params = @getParams {
id: { type: 'string' }
}
# Current user must be an administrator.
@checkPermission 'admin'
exports.delete = ({id}) ->
try
VBD = @getObject params.id
catch
@@ -18,18 +11,15 @@ exports.delete = ->
xapi = @getXAPI VBD
# TODO: check if VBD is attached before
$wait xapi.call "VBD.destroy", VBD.ref
$wait xapi.call 'VBD.destroy', VBD.ref
return
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
@@ -38,6 +28,10 @@ exports.disconnect = ->
xapi = @getXAPI VBD
# TODO: check if VBD is attached before
$wait xapi.call "VBD.unplug_force", VBD.ref
$wait xapi.call 'VBD.unplug_force', VBD.ref
return
return true
exports.disconnect.permission = 'admin'
exports.disconnect.params = {
id: { type: 'string' }
}

View File

@@ -1,43 +1,29 @@
{isArray: $isArray} = require 'underscore'
#---------------------------------------------------------------------
{$wait} = require '../fibers-utils'
#=====================================================================
exports.delete = ->
params = @getParams {
id: { type: 'string' }
}
# Current user must be an administrator.
@checkPermission 'admin'
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
$wait xapi.call "VDI.destroy", VDI.ref
$wait xapi.call 'VDI.destroy', VDI.ref
return
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
@@ -69,4 +55,16 @@ exports.set = ->
for field in (if $isArray fields then fields else [fields])
$wait xapi.call "VDI.set_#{field}", ref, "#{params[param]}"
return
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 }
}

View File

@@ -36,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
@@ -111,11 +54,17 @@ exports.create = ->
# Clones the VM from the template.
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
$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'
@@ -132,6 +81,10 @@ exports.create = ->
if 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) ->
@@ -154,6 +107,12 @@ exports.create = ->
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'
$wait xapi.call(
@@ -218,26 +177,68 @@ exports.create = ->
# Mounts the VDI into the VBD.
$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.
return VM.uuid
exports.delete = ->
{
id
delete_disks: deleteDisks
} = @getParams {
id: { type: 'string' }
delete_disks: {
optional: true
type: 'boolean'
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
@@ -262,12 +263,17 @@ exports.delete = ->
$wait xapi.call 'VM.destroy', VM.ref
return true
exports.delete.permission = 'admin'
exports.delete.params = {
id: { type: 'string' }
exports.ejectCd = ->
{id} = @getParams {
id: { type: 'string' }
delete_disks: {
optional: true
type: 'boolean'
}
}
exports.ejectCd = ({id}) ->
try
VM = @getObject id
catch
@@ -287,14 +293,12 @@ exports.ejectCd = ->
$wait xapi.call 'VBD.destroy', cdDriveRef
return true
exports.ejectCd.permission = 'admin'
exports.ejectCd.params = {
id: { type: 'string' }
}
exports.insertCd = ->
{id, cd_id, force} = @getParams {
id: { type: 'string' }
cd_id: { type: 'string' }
force: { type: 'boolean' }
}
exports.insertCd = ({id, cd_id, force}) ->
try
VM = @getObject id
VDI = @getObject cd_id
@@ -335,19 +339,14 @@ exports.insertCd = ->
$wait xapi.call 'VBD.insert', cdDriveRef, VDI.ref
return true
exports.insertCd.permission = 'admin'
exports.insertCd.params = {
id: { type: 'string' }
cd_id: { type: 'string' }
force: { type: 'boolean' }
}
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'
exports.migrate = ({id, host_id}) ->
try
VM = @getObject id
host = @getObject host_id
@@ -362,28 +361,16 @@ exports.migrate = ->
$wait xapi.call 'VM.pool_migrate', VM.ref, host.ref, {}
return true
exports.migrate.permission = 'admin'
exports.migrate.params = {
# Identifier of the VM to migrate.
id: { type: 'string' }
exports.set = ->
params = @getParams {
# Identifier of the VM to update.
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
@@ -444,18 +431,25 @@ exports.set = ->
$wait xapi.call "VM.set_#{field}", ref, "#{params[param]}"
return true
exports.set.permission = 'admin'
exports.set.params = {
# Identifier of the VM to update.
id: { type: 'string' }
exports.restart = ->
{
id
force
} = @getParams {
id: { type: 'string' }
force: { type: 'boolean' }
}
name_label: { type: 'string', optional: true }
@checkPermission 'admin'
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
@@ -474,18 +468,49 @@ exports.restart = ->
$wait xapi.call 'VM.hard_reboot', VM.ref
return true
exports.restart.permission = 'admin'
exports.restart.params = {
id: { type: 'string' }
force: { type: 'boolean' }
}
exports.snapshot = ->
{
id
name
} = @getParams {
id: { type: 'string' }
name: { type: 'string' }
}
exports.clone = ({id, name, full_copy}) ->
try
VM = @getObject id
catch
@throw 'NO_SUCH_OBJECT'
@checkPermission 'admin'
xapi = @getXAPI VM
if full_copy
$wait xapi.call 'VM.copy', VM.ref, name, ''
else
$wait xapi.call 'VM.clone', VM.ref, name
return true
exports.clone.permission = 'admin'
exports.clone.params = {
id: { type: 'string' }
name: { type: 'string' }
full_copy: { type: 'boolean' }
}
# TODO: rename convertToTemplate()
exports.convert = ({id}) ->
try
VM = @getObject id
catch
@throw 'NO_SUCH_OBJECT'
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
@@ -496,38 +521,35 @@ exports.snapshot = ->
$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} = @getParams {
id: { type: 'string' }
}
@checkPermission 'admin'
exports.start = ({id}) ->
try
VM = @getObject id
catch
@throw 'NO_SUCH_OBJECT'
(@getXAPI VM).call(
$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' }
}
exports.stop = ->
{
id
force
} = @getParams {
id: { type: 'string' }
force: { type: 'boolean' }
}
@checkPermission 'admin'
# 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
@@ -535,26 +557,30 @@ exports.stop = ->
xapi = @getXAPI VM
# Hard shutdown
if force
$wait xapi.call 'VM.hard_shutdown', VM.ref
return true
# Clean shutdown
try
# Attempts a clean shutdown.
$wait xapi.call 'VM.clean_shutdown', VM.ref
catch error
return unless error[0] is 'VM_MISSING_PV_DRIVERS'
@throw 'INVALID_PARAMS' unless force
$wait xapi.call 'VM.hard_shutdown', VM.ref
if error[0] is 'VM_MISSING_PV_DRIVERS'
# TODO: Improve reporting: this message is unclear.
@throw 'INVALID_PARAMS'
else
throw error
return true
exports.stop.permission = 'admin'
exports.stop.params = {
id: { type: 'string' }
force: { type: 'boolean', optional: true }
}
# revert a snapshot to its parent VM
exports.revert = ->
{id} = @getParams {
id: { type: 'string' }
}
@checkPermission 'admin'
exports.revert = ({id}) ->
try
VM = @getObject id
catch
@@ -566,3 +592,57 @@ exports.revert = ->
$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' }
}

View File

@@ -1,33 +0,0 @@
$_ = require 'underscore'
{$wait} = require '../../fibers-utils'
#=====================================================================
# 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]
$wait xapi.call "#{type}.destroy", object.$ref
return true

View File

@@ -1,49 +0,0 @@
$_ = require 'underscore'
{$wait} = require '../../fibers-utils'
#=====================================================================
# 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
$wait xapi.call.apply xapi, ["VM.#{method}", vm.ref].concat params
return true

View File

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

View File

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

View File

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

View File

@@ -4,7 +4,7 @@ $_ = require 'underscore'
# Async code is easier with fibers (light threads)!
$fiber = require 'fibers'
$Q = require 'q'
$Promise = require 'bluebird'
#=====================================================================
@@ -73,20 +73,15 @@ $fiberize = (fn) ->
).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
catch error
deferred.reject error
).run()
deferred.promise
new $Promise (resolve, reject) ->
$fiber(=>
try
resolve fn.apply this, args
catch error
reject error
).run()
# Waits for an event.
#
@@ -131,13 +126,10 @@ $wait = (value) ->
$wait.register = ->
throw new Error 'something has already been registered' if $wait._stash
deferred = $Q.defer()
deferred = $Promise.defer()
$wait._stash = deferred.promise
(error, result) ->
if error?
return deferred.reject error
deferred.resolve result
deferred.callback
#=====================================================================

View File

@@ -6,6 +6,10 @@ var expect = require('chai').expect;
//--------------------------------------------------------------------
var Promise = require('bluebird');
//--------------------------------------------------------------------
var utils = require('./fibers-utils');
var $fiberize = utils.$fiberize;
@@ -46,7 +50,7 @@ describe('$wait', function () {
it('waits for a promise', function (done) {
$fiberize(function () {
var value = {};
var promise = require('q')(value);
var promise = Promise.cast(value);
expect($wait(promise)).to.equal(value);
@@ -56,7 +60,7 @@ describe('$wait', function () {
it('handles promise rejection', function (done) {
$fiberize(function () {
var promise = require('q').reject('an exception');
var promise = Promise.reject('an exception');
expect(function () {
$wait(promise);
@@ -127,7 +131,7 @@ describe('$wait', function () {
var value1 = {};
var value2 = {};
var promise = require('q')(value1);
var promise = Promise.cast(value1);
var continuable = function (callback) {
callback(null, value2);
};
@@ -145,7 +149,7 @@ describe('$wait', function () {
var value1 = {};
var value2 = {};
var promise = require('q')(value1);
var promise = Promise.cast(value1);
var continuable = function (callback) {
callback(null, value2);
};
@@ -162,7 +166,7 @@ describe('$wait', function () {
});
it('handles nested arrays/maps', function (done) {
var promise = require('q')('a promise');
var promise = Promise.cast('a promise');
var continuable = function (callback) {
callback(null, 'a continuable');
};

View File

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

View File

@@ -6,14 +6,21 @@ $fs = require 'fs'
# Low level tools.
$_ = require 'underscore'
$appConf = require 'app-conf'
$chalk = require 'chalk'
# HTTP(s) middleware framework.
$connect = require 'connect'
$serveStatic = require 'serve-static'
# Configuration handling.
$nconf = require 'nconf'
$eventToPromise = require 'event-to-promise'
$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 +28,21 @@ $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'
{$fileExists, $wrap} = require './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
@@ -81,55 +92,66 @@ $handleJsonRpcCall = (api, session, encodedRequest) ->
#=====================================================================
# Main.
do $fiberize ->
exports = module.exports = $promisify (args) ->
return exports.help() unless (
(args.indexOf '--help') is -1 and
(args.indexOf '-h') is -1
)
# Loads the environment.
$nconf.env()
# Parses process' arguments.
$nconf.argv()
# Loads the configuration file.
$nconf.use 'file', {
file: "#{__dirname}/../config/local.yaml"
format: {
stringify: (obj) -> $YAML.safeDump obj
parse: (string) -> $YAML.safeLoad string
}
}
# Defines defaults configuration.
$nconf.defaults {
# Default config.
opts = {
http: {
listen: [
port: 80
]
mounts: []
mounts: {}
}
redis: {
# Default values are handled by `redis`.
}
}
# Loads config files.
opts = $wait $appConf.load 'xo-server', opts
# Prints a message if deprecated entries are specified.
for entry in ['users', 'servers']
if $nconf.get entry
if entry of opts
console.warn "[Warn] `#{entry}` configuration is deprecated."
# Creates the web server according to the configuration.
webServer = new $WebServer()
webServer.listen options for options in $nconf.get 'http:listen'
$wait $Promise.map opts.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
if (user = $nconf.get 'user')?
process.setuid user
process.setgid opts.group if opts.group?
process.setuid opts.user if opts.user?
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()
@@ -137,42 +159,75 @@ do $fiberize ->
# Starts it.
xo.start {
redis: {
uri: $nconf.get 'redis:uri'
uri: opts.redis?.uri
}
}
# Static file serving (e.g. for XO-Web).
connect = $connect()
for urlPath, filePaths of $nconf.get 'http:mounts'
for urlPath, filePaths of opts.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'
exports.help = do (pkg = require '../package') ->
name = $chalk.bold pkg.name
return $wrap '''
Usage: $name
$name v$version
'''.replace /<([^>]+)>|\$(\w+)/g, (_, arg, key) ->
return '<'+ ($chalk.yellow arg) +'>' if arg
return name if key is 'name'
return pkg[key]

View File

@@ -1,3 +1,5 @@
'use strict';
var _ = require('underscore');
//////////////////////////////////////////////////////////////////////

View File

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

View File

@@ -11,7 +11,7 @@ $helpers = require './helpers'
#=====================================================================
$isVMRunning = ->
switch @genval.power_state
switch @val.power_state
when 'Paused', 'Running'
true
else
@@ -21,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) ->
@@ -203,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
@@ -252,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
}
}
@@ -352,6 +354,8 @@ module.exports = ->
enabled: -> @genval.enabled
current_operations: -> @genval.current_operations
hostname: -> @genval.hostname
iSCSI_name: -> @genval.other_config?.iscsi_iqn ? null
@@ -369,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 {
@@ -449,6 +459,8 @@ module.exports = ->
else
null
power_state: -> @genval.power_state
memory: ->
{metrics, guest_metrics} = @data
@@ -475,8 +487,6 @@ module.exports = ->
memory
power_state: -> @genval.power_state
PV_drivers: ->
{guest_metrics} = @data
if guest_metrics

View File

@@ -1,4 +1,4 @@
$done = {}
$done = exports.$done = {}
# Similar to `$_.each()` but can be interrupted by returning the
# special value `done` provided as the forth argument.
@@ -71,3 +71,18 @@ exports.$mapInPlace = (col, iterator, ctx) ->
# The collection is returned.
col
# Wraps a value in a function.
exports.$wrap = (val) -> -> val
#=====================================================================
$fs = require 'fs'
$Promise = require 'bluebird'
exports.$fileExists = (path) ->
return new Promise (resolve) ->
$fs.exists path, resolve
exports.$readFile = $Promise.promisify $fs.readFile

View File

@@ -1,123 +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.
{$wait} = require './fibers-utils'
#=====================================================================
# Events which may be emitted by a `http(s).Server`
$events = [
'checkContinue'
'clientError'
'close'
'connect'
'connection'
'error'
'request'
'upgrade'
]
# Thunk version of `$fs.readFile()`.
$readFile = (args...) ->
(cb) ->
$fs.readFile args..., cb
#=====================================================================
# 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: $wait $readFile certificate
key: $wait $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

View File

@@ -15,7 +15,7 @@ $hashy = require 'hashy'
# Redis.
$createRedisClient = (require 'then-redis').createClient
$q = require 'q'
$Promise = require 'bluebird'
#---------------------------------------------------------------------
@@ -38,11 +38,11 @@ $XAPI = require './xapi'
#=====================================================================
# Promise versions of asynchronous functions.
$hash = $q.denodeify $hashy.hash
$randomBytes = $q.denodeify $crypto.randomBytes
$verifyHash = $q.denodeify $hashy.verify
$randomBytes = $Promise.promisify $crypto.randomBytes
$hash = $hashy.hash
$needsRehash = $hashy.needsRehash
$verifyHash = $hashy.verify
#=====================================================================
# Models and collections.
@@ -154,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')
@@ -303,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.

View File

@@ -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();
});

View File

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