Use standard style.
This commit is contained in:
parent
b4a3b832dc
commit
0eec1c1f61
@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
'use strict';
|
'use strict'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
require('exec-promise')(require('../'));
|
require('exec-promise')(require('../'))
|
||||||
|
14
gulpfile.js
14
gulpfile.js
@ -1,4 +1,4 @@
|
|||||||
'use strict';
|
'use strict'
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
@ -12,18 +12,18 @@ var watch = require('gulp-watch')
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
var SRC_DIR = __dirname + '/src';
|
var SRC_DIR = __dirname + '/src'
|
||||||
var DIST_DIR = __dirname + '/dist';
|
var DIST_DIR = __dirname + '/dist'
|
||||||
|
|
||||||
var PRODUCTION = process.argv.indexOf('--production') !== -1
|
var PRODUCTION = process.argv.indexOf('--production') !== -1
|
||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
function src(patterns) {
|
function src (patterns) {
|
||||||
return PRODUCTION ?
|
return PRODUCTION ?
|
||||||
gulp.src(patterns, {
|
gulp.src(patterns, {
|
||||||
base: SRC_DIR,
|
base: SRC_DIR,
|
||||||
cwd: SRC_DIR,
|
cwd: SRC_DIR
|
||||||
}) :
|
}) :
|
||||||
watch(patterns, {
|
watch(patterns, {
|
||||||
base: SRC_DIR,
|
base: SRC_DIR,
|
||||||
@ -36,7 +36,7 @@ function src(patterns) {
|
|||||||
|
|
||||||
// ===================================================================
|
// ===================================================================
|
||||||
|
|
||||||
gulp.task(function buildCoffee() {
|
gulp.task(function buildCoffee () {
|
||||||
return src('**/*.coffee')
|
return src('**/*.coffee')
|
||||||
.pipe(sourceMaps.init())
|
.pipe(sourceMaps.init())
|
||||||
.pipe(coffee({
|
.pipe(coffee({
|
||||||
@ -46,7 +46,7 @@ gulp.task(function buildCoffee() {
|
|||||||
.pipe(gulp.dest(DIST_DIR))
|
.pipe(gulp.dest(DIST_DIR))
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task(function buildEs6() {
|
gulp.task(function buildEs6 () {
|
||||||
return src('**/*.js')
|
return src('**/*.js')
|
||||||
.pipe(sourceMaps.init())
|
.pipe(sourceMaps.init())
|
||||||
.pipe(babel({
|
.pipe(babel({
|
||||||
|
10
package.json
10
package.json
@ -91,13 +91,19 @@
|
|||||||
"in-publish": "^1.1.1",
|
"in-publish": "^1.1.1",
|
||||||
"mocha": "^2.2.1",
|
"mocha": "^2.2.1",
|
||||||
"node-inspector": "^0.9.2",
|
"node-inspector": "^0.9.2",
|
||||||
"sinon": "^1.14.1"
|
"sinon": "^1.14.1",
|
||||||
|
"standard": "*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "gulp build --production",
|
"build": "gulp build --production",
|
||||||
"dev": "gulp build",
|
"dev": "gulp build",
|
||||||
"prepublish": "in-publish && npm run build || in-install",
|
"prepublish": "in-publish && npm run build || in-install",
|
||||||
"start": "node bin/xo-server",
|
"start": "node bin/xo-server",
|
||||||
"test": "mocha 'dist/**/*.spec.js'"
|
"test": "standard && mocha 'dist/**/*.spec.js'"
|
||||||
|
},
|
||||||
|
"standard": {
|
||||||
|
"ignore": [
|
||||||
|
"dist/**"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,6 @@ class $MappedCollection extends $EventEmitter
|
|||||||
|
|
||||||
removeWithPredicate: (predicate, thisArg) ->
|
removeWithPredicate: (predicate, thisArg) ->
|
||||||
items = ($filter @_byKey, predicate, thisArg)
|
items = ($filter @_byKey, predicate, thisArg)
|
||||||
console.log('%s items to remove', items.length)
|
|
||||||
@_removeItems items
|
@_removeItems items
|
||||||
|
|
||||||
set: (items, {add, update, remove} = {}) ->
|
set: (items, {add, update, remove} = {}) ->
|
||||||
|
@ -1,54 +1,51 @@
|
|||||||
'use strict';
|
import assign from 'lodash.assign'
|
||||||
|
import {JsonRpcError} from 'json-rpc/errors'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
var assign = require('lodash.assign');
|
|
||||||
var JsonRpcError = require('json-rpc/errors').JsonRpcError;
|
|
||||||
var jsonRpcErrors = require('json-rpc/errors');
|
|
||||||
var makeError = require('make-error');
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
function exportError(constructor) {
|
|
||||||
makeError(constructor, JsonRpcError);
|
|
||||||
exports[constructor.name] = constructor;
|
|
||||||
}
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
// Export standard JSON-RPC errors.
|
// Export standard JSON-RPC errors.
|
||||||
assign(exports, jsonRpcErrors);
|
export * from 'json-rpc/errors'
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
exportError(function NotImplemented() {
|
export class NotImplemented extends JsonRpcError {
|
||||||
NotImplemented.super.call(this, 'not implemented', 0);
|
constructor () {
|
||||||
});
|
super('not implemented', 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
exportError(function NoSuchObject() {
|
export class NoSuchObject extends JsonRpcError {
|
||||||
NoSuchObject.super.call(this, 'no such object', 1);
|
constructor () {
|
||||||
});
|
super(this, 'no such object', 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
exportError(function Unauthorized() {
|
export class Unauthorized extends JsonRpcError {
|
||||||
Unauthorized.super.call(
|
constructor () {
|
||||||
this,
|
super(
|
||||||
'not authenticated or not enough permissions',
|
this,
|
||||||
2
|
'not authenticated or not enough permissions',
|
||||||
);
|
2
|
||||||
});
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
exportError(function InvalidCredential() {
|
export class InvalidCredential extends JsonRpcError {
|
||||||
InvalidCredential.super.call(this, 'invalid credential', 3);
|
constructor () {
|
||||||
});
|
super(this, 'invalid credential', 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
exportError(function AlreadyAuthenticated() {
|
export class AlreadyAuthenticated extends JsonRpcError {
|
||||||
AlreadyAuthenticated.super.call(this, 'already authenticated', 4);
|
constructor () {
|
||||||
});
|
super(this, 'already authenticated', 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
290
src/api.js
290
src/api.js
@ -1,82 +1,83 @@
|
|||||||
import debug from 'debug';
|
import debug from 'debug'
|
||||||
debug = debug('xo:api');
|
debug = debug('xo:api')
|
||||||
|
|
||||||
import assign from 'lodash.assign';
|
import assign from 'lodash.assign'
|
||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird'
|
||||||
import forEach from 'lodash.foreach';
|
import forEach from 'lodash.foreach'
|
||||||
import getKeys from 'lodash.keys';
|
import getKeys from 'lodash.keys'
|
||||||
import isFunction from 'lodash.isfunction';
|
import isFunction from 'lodash.isfunction'
|
||||||
import map from 'lodash.map';
|
import map from 'lodash.map'
|
||||||
import requireTree from 'require-tree';
|
import requireTree from 'require-tree'
|
||||||
import schemaInspector from 'schema-inspector';
|
import schemaInspector from 'schema-inspector'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
InvalidParameters,
|
InvalidParameters,
|
||||||
MethodNotFound,
|
MethodNotFound,
|
||||||
NoSuchObject,
|
NoSuchObject,
|
||||||
Unauthorized,
|
Unauthorized
|
||||||
} from './api-errors';
|
} from './api-errors'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
// FIXME: this function is specific to XO and should not be defined in
|
// FIXME: this function is specific to XO and should not be defined in
|
||||||
// this file.
|
// this file.
|
||||||
function checkPermission(method) {
|
function checkPermission (method) {
|
||||||
/* jshint validthis: true */
|
/* jshint validthis: true */
|
||||||
|
|
||||||
let {permission} = method;
|
const {permission} = method
|
||||||
|
|
||||||
// No requirement.
|
// No requirement.
|
||||||
if (permission === undefined) {
|
if (permission === undefined) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let {user} = this;
|
const {user} = this
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Unauthorized();
|
throw new Unauthorized()
|
||||||
}
|
}
|
||||||
|
|
||||||
// The only requirement is login.
|
// The only requirement is login.
|
||||||
if (!permission) {
|
if (!permission) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.hasPermission(permission)) {
|
if (!user.hasPermission(permission)) {
|
||||||
throw new Unauthorized();
|
throw new Unauthorized()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
function checkParams(method, params) {
|
function checkParams (method, params) {
|
||||||
var schema = method.params;
|
var schema = method.params
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = schemaInspector.validate({
|
const result = schemaInspector.validate({
|
||||||
type: 'object',
|
type: 'object',
|
||||||
properties: schema,
|
properties: schema
|
||||||
}, params);
|
}, params)
|
||||||
|
|
||||||
if (!result.valid) {
|
if (!result.valid) {
|
||||||
throw new InvalidParameters(result.error);
|
throw new InvalidParameters(result.error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
let checkAuthorization;
|
// Forward declaration.
|
||||||
|
let checkAuthorization
|
||||||
|
|
||||||
function authorized() {}
|
function authorized () {}
|
||||||
function forbiddden() {
|
// function forbiddden () {
|
||||||
throw new Unauthorized();
|
// throw new Unauthorized()
|
||||||
}
|
// }
|
||||||
function checkMemberAuthorization(member) {
|
function checkMemberAuthorization (member) {
|
||||||
return function (userId, object) {
|
return function (userId, object) {
|
||||||
let memberObject = this.getObject(object[member]);
|
const memberObject = this.getObject(object[member])
|
||||||
return checkAuthorization.call(this, userId, memberObject);
|
return checkAuthorization.call(this, userId, memberObject)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkAuthorizationByTypes = {
|
const checkAuthorizationByTypes = {
|
||||||
@ -92,129 +93,129 @@ const checkAuthorizationByTypes = {
|
|||||||
|
|
||||||
// Access to a VDI is granted if the user has access to the
|
// Access to a VDI is granted if the user has access to the
|
||||||
// containing SR or to a linked VM.
|
// containing SR or to a linked VM.
|
||||||
VDI(userId, vdi) {
|
VDI (userId, vdi) {
|
||||||
// Check authorization for each of the connected VMs.
|
// Check authorization for each of the connected VMs.
|
||||||
let promises = map(this.getObjects(vdi.$VBDs, 'VBD'), vbd => {
|
const promises = map(this.getObjects(vdi.$VBDs, 'VBD'), vbd => {
|
||||||
let vm = this.getObject(vbd.VM, 'VM');
|
const vm = this.getObject(vbd.VM, 'VM')
|
||||||
return checkAuthorization.call(this, userId, vm);
|
return checkAuthorization.call(this, userId, vm)
|
||||||
});
|
})
|
||||||
|
|
||||||
// Check authorization for the containing SR.
|
// Check authorization for the containing SR.
|
||||||
let sr = this.getObject(vdi.$SR, 'SR');
|
const sr = this.getObject(vdi.$SR, 'SR')
|
||||||
promises.push(checkAuthorization.call(this, userId, sr));
|
promises.push(checkAuthorization.call(this, userId, sr))
|
||||||
|
|
||||||
// We need at least one success
|
// We need at least one success
|
||||||
return Bluebird.any(promises).catch(function (aggregateError) {
|
return Bluebird.any(promises).catch(function (aggregateError) {
|
||||||
throw aggregateError[0];
|
throw aggregateError[0]
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
VIF(userId, vif) {
|
VIF (userId, vif) {
|
||||||
let network = this.getObject(vif.$network);
|
const network = this.getObject(vif.$network)
|
||||||
let vm = this.getObject(vif.$VM);
|
const vm = this.getObject(vif.$VM)
|
||||||
|
|
||||||
return Bluebird.any([
|
return Bluebird.any([
|
||||||
checkAuthorization.call(this, userId, network),
|
checkAuthorization.call(this, userId, network),
|
||||||
checkAuthorization.call(this, userId, vm),
|
checkAuthorization.call(this, userId, vm)
|
||||||
]);
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
'VM-snapshot': checkMemberAuthorization('$snapshot_of'),
|
'VM-snapshot': checkMemberAuthorization('$snapshot_of')
|
||||||
};
|
}
|
||||||
|
|
||||||
function defaultCheckAuthorization(userId, object) {
|
function defaultCheckAuthorization (userId, object) {
|
||||||
return this.acls.exists({
|
return this.acls.exists({
|
||||||
subject: userId,
|
subject: userId,
|
||||||
object: object.id,
|
object: object.id
|
||||||
}).then(success => {
|
}).then(success => {
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new Unauthorized();
|
throw new Unauthorized()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAuthorization = Bluebird.method(function (userId, object) {
|
checkAuthorization = Bluebird.method(function (userId, object) {
|
||||||
let fn = checkAuthorizationByTypes[object.type] || defaultCheckAuthorization;
|
const fn = checkAuthorizationByTypes[object.type] || defaultCheckAuthorization
|
||||||
return fn.call(this, userId, object);
|
return fn.call(this, userId, object)
|
||||||
});
|
})
|
||||||
|
|
||||||
function resolveParams(method, params) {
|
function resolveParams (method, params) {
|
||||||
var resolve = method.resolve;
|
var resolve = method.resolve
|
||||||
if (!resolve) {
|
if (!resolve) {
|
||||||
return params;
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
let {user} = this;
|
const {user} = this
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Unauthorized();
|
throw new Unauthorized()
|
||||||
}
|
}
|
||||||
|
|
||||||
let userId = user.get('id');
|
const userId = user.get('id')
|
||||||
let isAdmin = this.user.hasPermission('admin');
|
const isAdmin = this.user.hasPermission('admin')
|
||||||
|
|
||||||
let promises = [];
|
const promises = []
|
||||||
try {
|
try {
|
||||||
forEach(resolve, ([param, types], key) => {
|
forEach(resolve, ([param, types], key) => {
|
||||||
let id = params[param];
|
const id = params[param]
|
||||||
if (id === undefined) {
|
if (id === undefined) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let object = this.getObject(params[param], types);
|
const object = this.getObject(params[param], types)
|
||||||
|
|
||||||
// This parameter has been handled, remove it.
|
// This parameter has been handled, remove it.
|
||||||
delete params[param];
|
delete params[param]
|
||||||
|
|
||||||
// Register this new value.
|
// Register this new value.
|
||||||
params[key] = object;
|
params[key] = object
|
||||||
|
|
||||||
if (!isAdmin) {
|
if (!isAdmin) {
|
||||||
promises.push(checkAuthorization.call(this, userId, object));
|
promises.push(checkAuthorization.call(this, userId, object))
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new NoSuchObject();
|
throw new NoSuchObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
return Bluebird.all(promises).return(params);
|
return Bluebird.all(promises).return(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
function getMethodsInfo() {
|
function getMethodsInfo () {
|
||||||
let methods = {};
|
const methods = {}
|
||||||
|
|
||||||
forEach(this.api._methods, function (method, name) {
|
forEach(this.api._methods, function (method, name) {
|
||||||
this[name] = assign({}, {
|
this[name] = assign({}, {
|
||||||
description: method.description,
|
description: method.description,
|
||||||
params: method.params || {},
|
params: method.params || {},
|
||||||
permission: method.permission,
|
permission: method.permission
|
||||||
});
|
})
|
||||||
}, methods);
|
}, methods)
|
||||||
|
|
||||||
return methods;
|
return methods
|
||||||
}
|
}
|
||||||
getMethodsInfo.description = 'returns the signatures of all available API methods';
|
getMethodsInfo.description = 'returns the signatures of all available API methods'
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
let getVersion = () => '0.1';
|
const getVersion = () => '0.1'
|
||||||
getVersion.description = 'API version (unstable)';
|
getVersion.description = 'API version (unstable)'
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
function listMethods() {
|
function listMethods () {
|
||||||
return getKeys(this.api._methods);
|
return getKeys(this.api._methods)
|
||||||
}
|
}
|
||||||
listMethods.description = 'returns the name of all available API methods';
|
listMethods.description = 'returns the name of all available API methods'
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
function methodSignature({method: name}) {
|
function methodSignature ({method: name}) {
|
||||||
let method = this.api.getMethod(name);
|
const method = this.api.getMethod(name)
|
||||||
|
|
||||||
if (!method) {
|
if (!method) {
|
||||||
throw new NoSuchObject();
|
throw new NoSuchObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return an array for compatibility with XML-RPC.
|
// Return an array for compatibility with XML-RPC.
|
||||||
@ -223,105 +224,104 @@ function methodSignature({method: name}) {
|
|||||||
assign({ name }, {
|
assign({ name }, {
|
||||||
description: method.description,
|
description: method.description,
|
||||||
params: method.params || {},
|
params: method.params || {},
|
||||||
permission: method.permission,
|
permission: method.permission
|
||||||
})
|
})
|
||||||
];
|
]
|
||||||
}
|
}
|
||||||
methodSignature.description = 'returns the signature of an API method';
|
methodSignature.description = 'returns the signature of an API method'
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
export default class Api {
|
export default class Api {
|
||||||
constructor({context} = {}) {
|
constructor ({context} = {}) {
|
||||||
this._methods = Object.create(null);
|
this._methods = Object.create(null)
|
||||||
this.context = context;
|
this.context = context
|
||||||
|
|
||||||
this.addMethods({
|
this.addMethods({
|
||||||
system: {
|
system: {
|
||||||
getMethodsInfo,
|
getMethodsInfo,
|
||||||
getVersion,
|
getVersion,
|
||||||
listMethods,
|
listMethods,
|
||||||
methodSignature,
|
methodSignature
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
// FIXME: this too is specific to XO and should be moved out of this file.
|
// FIXME: this too is specific to XO and should be moved out of this file.
|
||||||
this.addMethods(requireTree('./api'));
|
this.addMethods(requireTree('./api'))
|
||||||
}
|
}
|
||||||
|
|
||||||
addMethod(name, method) {
|
addMethod (name, method) {
|
||||||
this._methods[name] = method;
|
this._methods[name] = method
|
||||||
}
|
}
|
||||||
|
|
||||||
addMethods(methods) {
|
addMethods (methods) {
|
||||||
let base = '';
|
let base = ''
|
||||||
forEach(methods, function addMethod(method, name) {
|
forEach(methods, function addMethod (method, name) {
|
||||||
name = base + name;
|
name = base + name
|
||||||
|
|
||||||
if (isFunction(method)) {
|
if (isFunction(method)) {
|
||||||
this.addMethod(name, method);
|
this.addMethod(name, method)
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let oldBase = base;
|
const oldBase = base
|
||||||
base = name + '.';
|
base = name + '.'
|
||||||
forEach(method, addMethod, this);
|
forEach(method, addMethod, this)
|
||||||
base = oldBase;
|
base = oldBase
|
||||||
}, this);
|
}, this)
|
||||||
}
|
}
|
||||||
|
|
||||||
call(session, name, params) {
|
call (session, name, params) {
|
||||||
debug('%s(...)', name);
|
debug('%s(...)', name)
|
||||||
|
|
||||||
let method;
|
let method
|
||||||
let context;
|
let context
|
||||||
|
|
||||||
return Bluebird.try(() => {
|
return Bluebird.try(() => {
|
||||||
method = this.getMethod(name);
|
method = this.getMethod(name)
|
||||||
if (!method) {
|
if (!method) {
|
||||||
throw new MethodNotFound(name);
|
throw new MethodNotFound(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
context = Object.create(this.context);
|
context = Object.create(this.context)
|
||||||
context.api = this; // Used by system.*().
|
context.api = this // Used by system.*().
|
||||||
context.session = session;
|
context.session = session
|
||||||
|
|
||||||
// FIXME: too coupled with XO.
|
// FIXME: too coupled with XO.
|
||||||
// Fetch and inject the current user.
|
// Fetch and inject the current user.
|
||||||
let userId = session.get('user_id', undefined);
|
const userId = session.get('user_id', undefined)
|
||||||
return userId === undefined ? null : context.users.first(userId);
|
return userId === undefined ? null : context.users.first(userId)
|
||||||
}).then(function (user) {
|
}).then(function (user) {
|
||||||
context.user = user;
|
context.user = user
|
||||||
|
|
||||||
return checkPermission.call(context, method);
|
return checkPermission.call(context, method)
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
checkParams(method, params);
|
checkParams(method, params)
|
||||||
|
|
||||||
return resolveParams.call(context, method, params);
|
return resolveParams.call(context, method, params)
|
||||||
}).then(params => {
|
}).then(params => {
|
||||||
return method.call(context, params);
|
return method.call(context, params)
|
||||||
}).then(
|
}).then(
|
||||||
result => {
|
result => {
|
||||||
// If nothing was returned, consider this operation a success
|
// If nothing was returned, consider this operation a success
|
||||||
// and return true.
|
// and return true.
|
||||||
if (result === undefined) {
|
if (result === undefined) {
|
||||||
result = true;
|
result = true
|
||||||
}
|
}
|
||||||
|
|
||||||
debug('%s(...) → %s', name, typeof result);
|
debug('%s(...) → %s', name, typeof result)
|
||||||
|
|
||||||
return result;
|
return result
|
||||||
},
|
},
|
||||||
error => {
|
error => {
|
||||||
debug('Error: %s(...) → %s', name, error);
|
debug('Error: %s(...) → %s', name, error)
|
||||||
|
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
getMethod(name) {
|
getMethod (name) {
|
||||||
return this._methods[name];
|
return this._methods[name]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,82 +1,82 @@
|
|||||||
import {coroutine} from 'bluebird';
|
import {coroutine} from 'bluebird'
|
||||||
import {ModelAlreadyExists} from '../collection';
|
import {ModelAlreadyExists} from '../collection'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export const get = coroutine(function *({subject, object}) {
|
export const get = coroutine(function * ({subject, object}) {
|
||||||
const sieve = {};
|
const sieve = {}
|
||||||
try {
|
try {
|
||||||
if (subject !== undefined) {
|
if (subject !== undefined) {
|
||||||
sieve.subject = (yield this.users.first(subject)).get('id');
|
sieve.subject = (yield this.users.first(subject)).get('id')
|
||||||
}
|
}
|
||||||
if (object !== undefined) {
|
if (object !== undefined) {
|
||||||
sieve.object = this.getObject(object).id;
|
sieve.object = this.getObject(object).id
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.throw('NO_SUCH_OBJECT');
|
this.throw('NO_SUCH_OBJECT')
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.acls.get(sieve);
|
return this.acls.get(sieve)
|
||||||
});
|
})
|
||||||
|
|
||||||
get.permission = 'admin';
|
get.permission = 'admin'
|
||||||
|
|
||||||
get.params = {
|
get.params = {
|
||||||
subject: { type: 'string', optional: true },
|
subject: { type: 'string', optional: true },
|
||||||
object: { type: 'string', optional: true },
|
object: { type: 'string', optional: true }
|
||||||
};
|
}
|
||||||
|
|
||||||
get.description = 'get existing ACLs';
|
get.description = 'get existing ACLs'
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const getCurrent = coroutine(function *() {
|
export const getCurrent = coroutine(function * () {
|
||||||
return this.acls.get({ subject: this.session.get('user_id') });
|
return this.acls.get({ subject: this.session.get('user_id') })
|
||||||
});
|
})
|
||||||
|
|
||||||
getCurrent.permission = '';
|
getCurrent.permission = ''
|
||||||
|
|
||||||
getCurrent.description = 'get existing ACLs concerning current user';
|
getCurrent.description = 'get existing ACLs concerning current user'
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const add = coroutine(function *({subject, object}) {
|
export const add = coroutine(function * ({subject, object}) {
|
||||||
try {
|
try {
|
||||||
subject = (yield this.users.first(subject)).get('id');
|
subject = (yield this.users.first(subject)).get('id')
|
||||||
object = this.getObject(object).id;
|
object = this.getObject(object).id
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.throw('NO_SUCH_OBJECT');
|
this.throw('NO_SUCH_OBJECT')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
yield this.acls.create(subject, object);
|
yield this.acls.create(subject, object)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (!(error instanceof ModelAlreadyExists)) {
|
if (!(error instanceof ModelAlreadyExists)) {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
add.permission = 'admin';
|
add.permission = 'admin'
|
||||||
|
|
||||||
add.params = {
|
add.params = {
|
||||||
subject: { type: 'string' },
|
subject: { type: 'string' },
|
||||||
object: { type: 'string' },
|
object: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
add.description = 'add a new ACL entry';
|
add.description = 'add a new ACL entry'
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const remove = coroutine(function *({subject, object}) {
|
export const remove = coroutine(function * ({subject, object}) {
|
||||||
yield this.acls.deconste(subject, object);
|
yield this.acls.deconste(subject, object)
|
||||||
});
|
})
|
||||||
|
|
||||||
remove.permission = 'admin';
|
remove.permission = 'admin'
|
||||||
|
|
||||||
remove.params = {
|
remove.params = {
|
||||||
subject: { type: 'string' },
|
subject: { type: 'string' },
|
||||||
object: { type: 'string' },
|
object: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
remove.description = 'remove an existing ACL entry';
|
remove.description = 'remove an existing ACL entry'
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import {coroutine, wait} from '../fibers-utils';
|
import {coroutine, wait} from '../fibers-utils'
|
||||||
import {parseSize} from '../utils';
|
import {parseSize} from '../utils'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export const create = coroutine(function ({name, size, sr}) {
|
export const create = coroutine(function ({name, size, sr}) {
|
||||||
const xapi = this.getXAPI(sr);
|
const xapi = this.getXAPI(sr)
|
||||||
|
|
||||||
const ref = wait(xapi.call('VDI.create', {
|
const ref = wait(xapi.call('VDI.create', {
|
||||||
name_label: name,
|
name_label: name,
|
||||||
@ -13,20 +13,20 @@ export const create = coroutine(function ({name, size, sr}) {
|
|||||||
sharable: false,
|
sharable: false,
|
||||||
SR: sr.ref,
|
SR: sr.ref,
|
||||||
type: 'user',
|
type: 'user',
|
||||||
virtual_size: String(parseSize(size)),
|
virtual_size: String(parseSize(size))
|
||||||
}));
|
}))
|
||||||
|
|
||||||
return wait(xapi.call('VDI.get_record', ref)).uuid;
|
return wait(xapi.call('VDI.get_record', ref)).uuid
|
||||||
});
|
})
|
||||||
|
|
||||||
create.description = 'create a new disk on a SR';
|
create.description = 'create a new disk on a SR'
|
||||||
|
|
||||||
create.params = {
|
create.params = {
|
||||||
name: { type: 'string' },
|
name: { type: 'string' },
|
||||||
size: { type: 'string' },
|
size: { type: 'string' },
|
||||||
sr: { type: 'string' },
|
sr: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
create.resolve = {
|
create.resolve = {
|
||||||
sr: ['sr', 'SR'],
|
sr: ['sr', 'SR']
|
||||||
};
|
}
|
||||||
|
@ -1,64 +1,63 @@
|
|||||||
import {deprecate} from 'util';
|
import {deprecate} from 'util'
|
||||||
|
|
||||||
import {InvalidCredential, AlreadyAuthenticated} from '../api-errors';
|
import {InvalidCredential, AlreadyAuthenticated} from '../api-errors'
|
||||||
|
|
||||||
import {coroutine, wait} from '../fibers-utils';
|
import {coroutine, wait} from '../fibers-utils'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export const signIn = coroutine(function (credentials) {
|
export const signIn = coroutine(function (credentials) {
|
||||||
if (this.session.has('user_id')) {
|
if (this.session.has('user_id')) {
|
||||||
throw new AlreadyAuthenticated();
|
throw new AlreadyAuthenticated()
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = wait(this.authenticateUser(credentials));
|
const user = wait(this.authenticateUser(credentials))
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new InvalidCredential();
|
throw new InvalidCredential()
|
||||||
}
|
}
|
||||||
this.session.set('user_id', user.get('id'));
|
this.session.set('user_id', user.get('id'))
|
||||||
|
|
||||||
return this.getUserPublicProperties(user);
|
return this.getUserPublicProperties(user)
|
||||||
});
|
})
|
||||||
|
|
||||||
signIn.description = 'sign in';
|
signIn.description = 'sign in'
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const signInWithPassword = deprecate(signIn, 'use session.signIn() instead');
|
export const signInWithPassword = deprecate(signIn, 'use session.signIn() instead')
|
||||||
|
|
||||||
signInWithPassword.params = {
|
signInWithPassword.params = {
|
||||||
email: { type: 'string' },
|
email: { type: 'string' },
|
||||||
password: { type: 'string' },
|
password: { type: 'string' }
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
|
|
||||||
export const signInWithToken = deprecate(signIn, 'use session.signIn() instead');
|
|
||||||
|
|
||||||
signInWithToken.params = {
|
|
||||||
token: { type: 'string' },
|
|
||||||
};
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
|
|
||||||
export function signOut() {
|
|
||||||
this.session.unset('user_id');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signOut.description = 'sign out the user from the current session';
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
export const signInWithToken = deprecate(signIn, 'use session.signIn() instead')
|
||||||
|
|
||||||
|
signInWithToken.params = {
|
||||||
|
token: { type: 'string' }
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
export function signOut () {
|
||||||
|
this.session.unset('user_id')
|
||||||
|
}
|
||||||
|
|
||||||
|
signOut.description = 'sign out the user from the current session'
|
||||||
|
|
||||||
// This method requires the user to be signed in.
|
// This method requires the user to be signed in.
|
||||||
signOut.permission = '';
|
signOut.permission = ''
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const getUser = coroutine(function () {
|
export const getUser = coroutine(function () {
|
||||||
const userId = this.session.get('user_id');
|
const userId = this.session.get('user_id')
|
||||||
|
|
||||||
return userId === undefined ?
|
return userId === undefined ?
|
||||||
null :
|
null :
|
||||||
this.getUserPublicProperties(wait(this.users.first(userId)))
|
this.getUserPublicProperties(wait(this.users.first(userId)))
|
||||||
;
|
})
|
||||||
});
|
|
||||||
|
|
||||||
getUser.description = 'return the currently connected user';
|
getUser.description = 'return the currently connected user'
|
||||||
|
426
src/api/sr.js
426
src/api/sr.js
@ -1,93 +1,93 @@
|
|||||||
import forEach from 'lodash.foreach';
|
import forEach from 'lodash.foreach'
|
||||||
import {coroutine, wait} from '../fibers-utils';
|
import {coroutine, wait} from '../fibers-utils'
|
||||||
import {ensureArray, parseXml} from '../utils';
|
import {ensureArray, parseXml} from '../utils'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export const set = coroutine(function (params) {
|
export const set = coroutine(function (params) {
|
||||||
const {SR} = params;
|
const {SR} = params
|
||||||
const xapi = this.getXAPI();
|
const xapi = this.getXAPI()
|
||||||
|
|
||||||
forEach(['name_label', 'name_description'], param => {
|
forEach(['name_label', 'name_description'], param => {
|
||||||
const value = params[param];
|
const value = params[param]
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
wait(xapi.call(`SR.set_${value}`, SR.ref, params[param]));
|
wait(xapi.call(`SR.set_${value}`, SR.ref, params[param]))
|
||||||
});
|
})
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
|
|
||||||
set.params = {
|
set.params = {
|
||||||
id: { type: 'string' },
|
id: { type: 'string' },
|
||||||
|
|
||||||
name_label: { type: 'string', optional: true },
|
name_label: { type: 'string', optional: true },
|
||||||
|
|
||||||
name_description: { type: 'string', optional: true },
|
name_description: { type: 'string', optional: true }
|
||||||
};
|
}
|
||||||
|
|
||||||
set.resolve = {
|
set.resolve = {
|
||||||
SR: ['id', 'SR'],
|
SR: ['id', 'SR']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const scan = coroutine(function ({SR}) {
|
export const scan = coroutine(function ({SR}) {
|
||||||
const xapi = this.getXAPI(SR);
|
const xapi = this.getXAPI(SR)
|
||||||
|
|
||||||
wait(xapi.call('SR.scan', SR.ref));
|
wait(xapi.call('SR.scan', SR.ref))
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
|
|
||||||
scan.params = {
|
scan.params = {
|
||||||
id: { type: 'string' },
|
id: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
scan.resolve = {
|
scan.resolve = {
|
||||||
SR: ['id', 'SR'],
|
SR: ['id', 'SR']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// TODO: find a way to call this "deconste" and not destroy
|
// TODO: find a way to call this "deconste" and not destroy
|
||||||
export const destroy = coroutine(function ({SR}) {
|
export const destroy = coroutine(function ({SR}) {
|
||||||
const xapi = this.getXAPI(SR);
|
const xapi = this.getXAPI(SR)
|
||||||
|
|
||||||
wait(xapi.call('SR.destroy', SR.ref));
|
wait(xapi.call('SR.destroy', SR.ref))
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
|
|
||||||
destroy.params = {
|
destroy.params = {
|
||||||
id: { type: 'string' },
|
id: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
destroy.resolve = {
|
destroy.resolve = {
|
||||||
SR: ['id', 'SR'],
|
SR: ['id', 'SR']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const forget = coroutine(function ({SR}) {
|
export const forget = coroutine(function ({SR}) {
|
||||||
const xapi = this.getXAPI(SR);
|
const xapi = this.getXAPI(SR)
|
||||||
|
|
||||||
wait(xapi.call('SR.forget', SR.ref));
|
wait(xapi.call('SR.forget', SR.ref))
|
||||||
|
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
|
|
||||||
forget.params = {
|
forget.params = {
|
||||||
id: { type: 'string' },
|
id: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
forget.resolve = {
|
forget.resolve = {
|
||||||
SR: ['id', 'SR'],
|
SR: ['id', 'SR']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
export const createIso = coroutine(function ({
|
export const createIso = coroutine(function ({
|
||||||
host,
|
host,
|
||||||
@ -95,14 +95,14 @@ export const createIso = coroutine(function ({
|
|||||||
nameDescription,
|
nameDescription,
|
||||||
path
|
path
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
// FIXME: won't work for IPv6
|
// FIXME: won't work for IPv6
|
||||||
// Detect if NFS or local path for ISO files
|
// Detect if NFS or local path for ISO files
|
||||||
const deviceConfig = {location: path};
|
const deviceConfig = {location: path}
|
||||||
if (path.indexOf(':') === -1) { // not NFS share
|
if (path.indexOf(':') === -1) { // not NFS share
|
||||||
// TODO: legacy will be removed in XAPI soon by FileSR
|
// TODO: legacy will be removed in XAPI soon by FileSR
|
||||||
deviceConfig.legacy_mode = 'true';
|
deviceConfig.legacy_mode = 'true'
|
||||||
}
|
}
|
||||||
const srRef = wait(xapi.call(
|
const srRef = wait(xapi.call(
|
||||||
'SR.create',
|
'SR.create',
|
||||||
@ -115,24 +115,24 @@ export const createIso = coroutine(function ({
|
|||||||
'iso', // SR content type ISO
|
'iso', // SR content type ISO
|
||||||
true,
|
true,
|
||||||
{}
|
{}
|
||||||
));
|
))
|
||||||
|
|
||||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||||
return sr.uuid;
|
return sr.uuid
|
||||||
});
|
})
|
||||||
|
|
||||||
createIso.params = {
|
createIso.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
nameLabel: { type: 'string' },
|
nameLabel: { type: 'string' },
|
||||||
nameDescription: { type: 'string' },
|
nameDescription: { type: 'string' },
|
||||||
path: { type: 'string' }
|
path: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
createIso.resolve = {
|
createIso.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// NFS SR
|
// NFS SR
|
||||||
|
|
||||||
// This functions creates a NFS SR
|
// This functions creates a NFS SR
|
||||||
@ -145,16 +145,16 @@ export const createNfs = coroutine(function ({
|
|||||||
serverPath,
|
serverPath,
|
||||||
nfsVersion
|
nfsVersion
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
server,
|
server,
|
||||||
serverpath: serverPath,
|
serverpath: serverPath
|
||||||
};
|
}
|
||||||
|
|
||||||
// if NFS version given
|
// if NFS version given
|
||||||
if (nfsVersion) {
|
if (nfsVersion) {
|
||||||
deviceConfig.nfsversion = nfsVersion;
|
deviceConfig.nfsversion = nfsVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
const srRef = wait(xapi.call(
|
const srRef = wait(xapi.call(
|
||||||
@ -168,11 +168,11 @@ export const createNfs = coroutine(function ({
|
|||||||
'user', // recommended by Citrix
|
'user', // recommended by Citrix
|
||||||
true,
|
true,
|
||||||
{}
|
{}
|
||||||
));
|
))
|
||||||
|
|
||||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||||
return sr.uuid;
|
return sr.uuid
|
||||||
});
|
})
|
||||||
|
|
||||||
createNfs.params = {
|
createNfs.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
@ -180,14 +180,14 @@ createNfs.params = {
|
|||||||
nameDescription: { type: 'string' },
|
nameDescription: { type: 'string' },
|
||||||
server: { type: 'string' },
|
server: { type: 'string' },
|
||||||
serverPath: { type: 'string' },
|
serverPath: { type: 'string' },
|
||||||
nfsVersion: { type: 'string' , optional: true},
|
nfsVersion: { type: 'string', optional: true }
|
||||||
};
|
}
|
||||||
|
|
||||||
createNfs.resolve = {
|
createNfs.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// Local LVM SR
|
// Local LVM SR
|
||||||
|
|
||||||
// This functions creates a local LVM SR
|
// This functions creates a local LVM SR
|
||||||
@ -198,11 +198,11 @@ export const createLvm = coroutine(function ({
|
|||||||
nameDescription,
|
nameDescription,
|
||||||
device
|
device
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
device
|
device
|
||||||
};
|
}
|
||||||
|
|
||||||
const srRef = wait(xapi.call(
|
const srRef = wait(xapi.call(
|
||||||
'SR.create',
|
'SR.create',
|
||||||
@ -215,24 +215,24 @@ export const createLvm = coroutine(function ({
|
|||||||
'user', // recommended by Citrix
|
'user', // recommended by Citrix
|
||||||
false,
|
false,
|
||||||
{}
|
{}
|
||||||
));
|
))
|
||||||
|
|
||||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||||
return sr.uuid;
|
return sr.uuid
|
||||||
});
|
})
|
||||||
|
|
||||||
createLvm.params = {
|
createLvm.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
nameLabel: { type: 'string' },
|
nameLabel: { type: 'string' },
|
||||||
nameDescription: { type: 'string' },
|
nameDescription: { type: 'string' },
|
||||||
device: { type: 'string' },
|
device: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
createLvm.resolve = {
|
createLvm.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// This function helps to detect all NFS shares (exports) on a NFS server
|
// This function helps to detect all NFS shares (exports) on a NFS server
|
||||||
// Return a table of exports with their paths and ACLs
|
// Return a table of exports with their paths and ACLs
|
||||||
|
|
||||||
@ -240,13 +240,13 @@ export const probeNfs = coroutine(function ({
|
|||||||
host,
|
host,
|
||||||
server
|
server
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
server,
|
server
|
||||||
};
|
}
|
||||||
|
|
||||||
let xml;
|
let xml
|
||||||
|
|
||||||
try {
|
try {
|
||||||
wait(xapi.call(
|
wait(xapi.call(
|
||||||
@ -255,38 +255,38 @@ export const probeNfs = coroutine(function ({
|
|||||||
deviceConfig,
|
deviceConfig,
|
||||||
'nfs',
|
'nfs',
|
||||||
{}
|
{}
|
||||||
));
|
))
|
||||||
|
|
||||||
throw new Error('the call above should have thrown an error')
|
throw new Error('the call above should have thrown an error')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error[0] !== 'SR_BACKEND_FAILURE_101') {
|
if (error[0] !== 'SR_BACKEND_FAILURE_101') {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
xml = parseXml(error[3]);
|
xml = parseXml(error[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
const nfsExports = [];
|
const nfsExports = []
|
||||||
forEach(ensureArray(xml['nfs-exports'].Export), nfsExport => {
|
forEach(ensureArray(xml['nfs-exports'].Export), nfsExport => {
|
||||||
nfsExports.push({
|
nfsExports.push({
|
||||||
path: nfsExport.Path.trim(),
|
path: nfsExport.Path.trim(),
|
||||||
acl: nfsExport.Accesslist.trim()
|
acl: nfsExport.Accesslist.trim()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
return nfsExports;
|
return nfsExports
|
||||||
});
|
})
|
||||||
|
|
||||||
probeNfs.params = {
|
probeNfs.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
server: { type: 'string' },
|
server: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
probeNfs.resolve = {
|
probeNfs.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// ISCSI SR
|
// ISCSI SR
|
||||||
|
|
||||||
// This functions creates a iSCSI SR
|
// This functions creates a iSCSI SR
|
||||||
@ -303,23 +303,23 @@ export const createIscsi = coroutine(function ({
|
|||||||
chapUser,
|
chapUser,
|
||||||
chapPassword
|
chapPassword
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
target,
|
target,
|
||||||
targetIQN: targetIqn,
|
targetIQN: targetIqn,
|
||||||
SCSIid: scsiId,
|
SCSIid: scsiId
|
||||||
};
|
}
|
||||||
|
|
||||||
// if we give user and password
|
// if we give user and password
|
||||||
if (chapUser && chapPassword) {
|
if (chapUser && chapPassword) {
|
||||||
deviceConfig.chapUser = chapUser;
|
deviceConfig.chapUser = chapUser
|
||||||
deviceConfig.chapPassword = chapPassword;
|
deviceConfig.chapPassword = chapPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we give another port than default iSCSI
|
// if we give another port than default iSCSI
|
||||||
if (port) {
|
if (port) {
|
||||||
deviceConfig.port = port;
|
deviceConfig.port = port
|
||||||
}
|
}
|
||||||
|
|
||||||
const srRef = wait(xapi.call(
|
const srRef = wait(xapi.call(
|
||||||
@ -333,57 +333,57 @@ export const createIscsi = coroutine(function ({
|
|||||||
'user', // recommended by Citrix
|
'user', // recommended by Citrix
|
||||||
true,
|
true,
|
||||||
{}
|
{}
|
||||||
));
|
))
|
||||||
|
|
||||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||||
return sr.uuid;
|
return sr.uuid
|
||||||
});
|
})
|
||||||
|
|
||||||
createIscsi.params = {
|
createIscsi.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
nameLabel: { type: 'string' },
|
nameLabel: { type: 'string' },
|
||||||
nameDescription: { type: 'string' },
|
nameDescription: { type: 'string' },
|
||||||
target: { type: 'string' },
|
target: { type: 'string' },
|
||||||
port: { type: 'integer' , optional: true},
|
port: { type: 'integer', optional: true},
|
||||||
targetIqn: { type: 'string' },
|
targetIqn: { type: 'string' },
|
||||||
scsiId: { type: 'string' },
|
scsiId: { type: 'string' },
|
||||||
chapUser: { type: 'string' , optional: true },
|
chapUser: { type: 'string', optional: true },
|
||||||
chapPassword: { type: 'string' , optional: true },
|
chapPassword: { type: 'string', optional: true }
|
||||||
};
|
}
|
||||||
|
|
||||||
createIscsi.resolve = {
|
createIscsi.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// This function helps to detect all iSCSI IQN on a Target (iSCSI "server")
|
// This function helps to detect all iSCSI IQN on a Target (iSCSI "server")
|
||||||
// Return a table of IQN or empty table if no iSCSI connection to the target
|
// Return a table of IQN or empty table if no iSCSI connection to the target
|
||||||
|
|
||||||
export const probeIscsiIqns = coroutine(function ({
|
export const probeIscsiIqns = coroutine(function ({
|
||||||
host,
|
host,
|
||||||
target:targetIp,
|
target: targetIp,
|
||||||
port,
|
port,
|
||||||
chapUser,
|
chapUser,
|
||||||
chapPassword
|
chapPassword
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
target: targetIp,
|
target: targetIp
|
||||||
};
|
}
|
||||||
|
|
||||||
// if we give user and password
|
// if we give user and password
|
||||||
if (chapUser && chapPassword) {
|
if (chapUser && chapPassword) {
|
||||||
deviceConfig.chapUser = chapUser;
|
deviceConfig.chapUser = chapUser
|
||||||
deviceConfig.chapPassword = chapPassword;
|
deviceConfig.chapPassword = chapPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we give another port than default iSCSI
|
// if we give another port than default iSCSI
|
||||||
if (port) {
|
if (port) {
|
||||||
deviceConfig.port = port;
|
deviceConfig.port = port
|
||||||
}
|
}
|
||||||
|
|
||||||
let xml;
|
let xml
|
||||||
|
|
||||||
try {
|
try {
|
||||||
wait(xapi.call(
|
wait(xapi.call(
|
||||||
@ -392,76 +392,76 @@ export const probeIscsiIqns = coroutine(function ({
|
|||||||
deviceConfig,
|
deviceConfig,
|
||||||
'lvmoiscsi',
|
'lvmoiscsi',
|
||||||
{}
|
{}
|
||||||
));
|
))
|
||||||
|
|
||||||
throw new Error('the call above should have thrown an error')
|
throw new Error('the call above should have thrown an error')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error[0] === 'SR_BACKEND_FAILURE_141') {
|
if (error[0] === 'SR_BACKEND_FAILURE_141') {
|
||||||
return [];
|
return []
|
||||||
}
|
}
|
||||||
if (error[0] !== 'SR_BACKEND_FAILURE_96') {
|
if (error[0] !== 'SR_BACKEND_FAILURE_96') {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
xml = parseXml(error[3]);
|
xml = parseXml(error[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
const targets = [];
|
const targets = []
|
||||||
forEach(ensureArray(xml['iscsi-target-iqns'].TGT), target => {
|
forEach(ensureArray(xml['iscsi-target-iqns'].TGT), target => {
|
||||||
// if the target is on another IP adress, do not display it
|
// if the target is on another IP adress, do not display it
|
||||||
if (target.IPAddress.trim() === targetIp) {
|
if (target.IPAddress.trim() === targetIp) {
|
||||||
targets.push({
|
targets.push({
|
||||||
iqn: target.TargetIQN.trim(),
|
iqn: target.TargetIQN.trim(),
|
||||||
ip: target.IPAddress.trim()
|
ip: target.IPAddress.trim()
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
return targets;
|
return targets
|
||||||
});
|
})
|
||||||
|
|
||||||
probeIscsiIqns.params = {
|
probeIscsiIqns.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
target: { type: 'string' },
|
target: { type: 'string' },
|
||||||
port: { type: 'integer', optional: true },
|
port: { type: 'integer', optional: true },
|
||||||
chapUser: { type: 'string' , optional: true },
|
chapUser: { type: 'string', optional: true },
|
||||||
chapPassword: { type: 'string' , optional: true },
|
chapPassword: { type: 'string', optional: true }
|
||||||
};
|
}
|
||||||
probeIscsiIqns.resolve = {
|
probeIscsiIqns.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// This function helps to detect all iSCSI ID and LUNs on a Target
|
// This function helps to detect all iSCSI ID and LUNs on a Target
|
||||||
// It will return a LUN table
|
// It will return a LUN table
|
||||||
|
|
||||||
export const probeIscsiLuns = coroutine(function ({
|
export const probeIscsiLuns = coroutine(function ({
|
||||||
host,
|
host,
|
||||||
target:targetIp,
|
target: targetIp,
|
||||||
port,
|
port,
|
||||||
targetIqn,
|
targetIqn,
|
||||||
chapUser,
|
chapUser,
|
||||||
chapPassword
|
chapPassword
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
target: targetIp,
|
target: targetIp,
|
||||||
targetIQN: targetIqn,
|
targetIQN: targetIqn
|
||||||
};
|
}
|
||||||
|
|
||||||
// if we give user and password
|
// if we give user and password
|
||||||
if (chapUser && chapPassword) {
|
if (chapUser && chapPassword) {
|
||||||
deviceConfig.chapUser = chapUser;
|
deviceConfig.chapUser = chapUser
|
||||||
deviceConfig.chapPassword = chapPassword;
|
deviceConfig.chapPassword = chapPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we give another port than default iSCSI
|
// if we give another port than default iSCSI
|
||||||
if (port) {
|
if (port) {
|
||||||
deviceConfig.port = port;
|
deviceConfig.port = port
|
||||||
}
|
}
|
||||||
|
|
||||||
let xml;
|
let xml
|
||||||
|
|
||||||
try {
|
try {
|
||||||
wait(xapi.call(
|
wait(xapi.call(
|
||||||
@ -470,18 +470,18 @@ export const probeIscsiLuns = coroutine(function ({
|
|||||||
deviceConfig,
|
deviceConfig,
|
||||||
'lvmoiscsi',
|
'lvmoiscsi',
|
||||||
{}
|
{}
|
||||||
));
|
))
|
||||||
|
|
||||||
throw new Error('the call above should have thrown an error')
|
throw new Error('the call above should have thrown an error')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error[0] !== 'SR_BACKEND_FAILURE_107') {
|
if (error[0] !== 'SR_BACKEND_FAILURE_107') {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
xml = parseXml(error[3]);
|
xml = parseXml(error[3])
|
||||||
}
|
}
|
||||||
|
|
||||||
const luns = [];
|
const luns = []
|
||||||
forEach(ensureArray(xml['iscsi-target'].LUN), lun => {
|
forEach(ensureArray(xml['iscsi-target'].LUN), lun => {
|
||||||
luns.push({
|
luns.push({
|
||||||
id: lun.LUNid.trim(),
|
id: lun.LUNid.trim(),
|
||||||
@ -489,67 +489,67 @@ export const probeIscsiLuns = coroutine(function ({
|
|||||||
serial: lun.serial.trim(),
|
serial: lun.serial.trim(),
|
||||||
size: lun.size.trim(),
|
size: lun.size.trim(),
|
||||||
scsiId: lun.SCSIid.trim()
|
scsiId: lun.SCSIid.trim()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
return luns;
|
return luns
|
||||||
});
|
})
|
||||||
|
|
||||||
probeIscsiLuns.params = {
|
probeIscsiLuns.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
target: { type: 'string' },
|
target: { type: 'string' },
|
||||||
port: { type: 'integer' , optional: true},
|
port: { type: 'integer', optional: true},
|
||||||
targetIqn: { type: 'string' },
|
targetIqn: { type: 'string' },
|
||||||
chapUser: { type: 'string' , optional: true },
|
chapUser: { type: 'string', optional: true },
|
||||||
chapPassword: { type: 'string' , optional: true },
|
chapPassword: { type: 'string', optional: true }
|
||||||
};
|
}
|
||||||
|
|
||||||
probeIscsiLuns.resolve = {
|
probeIscsiLuns.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// This function helps to detect if this target already exists in XAPI
|
// This function helps to detect if this target already exists in XAPI
|
||||||
// It returns a table of SR UUID, empty if no existing connections
|
// It returns a table of SR UUID, empty if no existing connections
|
||||||
|
|
||||||
export const probeIscsiExists = coroutine(function ({
|
export const probeIscsiExists = coroutine(function ({
|
||||||
host,
|
host,
|
||||||
target:targetIp,
|
target: targetIp,
|
||||||
port,
|
port,
|
||||||
targetIqn,
|
targetIqn,
|
||||||
scsiId,
|
scsiId,
|
||||||
chapUser,
|
chapUser,
|
||||||
chapPassword
|
chapPassword
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
target: targetIp,
|
target: targetIp,
|
||||||
targetIQN: targetIqn,
|
targetIQN: targetIqn,
|
||||||
SCSIid: scsiId,
|
SCSIid: scsiId
|
||||||
};
|
}
|
||||||
|
|
||||||
// if we give user and password
|
// if we give user and password
|
||||||
if (chapUser && chapPassword) {
|
if (chapUser && chapPassword) {
|
||||||
deviceConfig.chapUser = chapUser;
|
deviceConfig.chapUser = chapUser
|
||||||
deviceConfig.chapPassword = chapPassword;
|
deviceConfig.chapPassword = chapPassword
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we give another port than default iSCSI
|
// if we give another port than default iSCSI
|
||||||
if (port) {
|
if (port) {
|
||||||
deviceConfig.port = port;
|
deviceConfig.port = port
|
||||||
}
|
}
|
||||||
|
|
||||||
const xml = parseXml(wait(xapi.call('SR.probe', host.ref, deviceConfig, 'lvmoiscsi', {})));
|
const xml = parseXml(wait(xapi.call('SR.probe', host.ref, deviceConfig, 'lvmoiscsi', {})))
|
||||||
|
|
||||||
const srs = [];
|
const srs = []
|
||||||
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
||||||
// get the UUID of SR connected to this LUN
|
// get the UUID of SR connected to this LUN
|
||||||
srs.push({uuid: sr.UUID.trim()});
|
srs.push({uuid: sr.UUID.trim()})
|
||||||
});
|
})
|
||||||
|
|
||||||
return srs;
|
return srs
|
||||||
});
|
})
|
||||||
|
|
||||||
probeIscsiExists.params = {
|
probeIscsiExists.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
@ -557,15 +557,15 @@ probeIscsiExists.params = {
|
|||||||
port: { type: 'integer', optional: true },
|
port: { type: 'integer', optional: true },
|
||||||
targetIqn: { type: 'string' },
|
targetIqn: { type: 'string' },
|
||||||
scsiId: { type: 'string' },
|
scsiId: { type: 'string' },
|
||||||
chapUser: { type: 'string' , optional: true },
|
chapUser: { type: 'string', optional: true },
|
||||||
chapPassword: { type: 'string' , optional: true },
|
chapPassword: { type: 'string', optional: true }
|
||||||
};
|
}
|
||||||
|
|
||||||
probeIscsiExists.resolve = {
|
probeIscsiExists.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// This function helps to detect if this NFS SR already exists in XAPI
|
// This function helps to detect if this NFS SR already exists in XAPI
|
||||||
// It returns a table of SR UUID, empty if no existing connections
|
// It returns a table of SR UUID, empty if no existing connections
|
||||||
|
|
||||||
@ -574,36 +574,36 @@ export const probeNfsExists = coroutine(function ({
|
|||||||
server,
|
server,
|
||||||
serverPath,
|
serverPath,
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
const deviceConfig = {
|
const deviceConfig = {
|
||||||
server,
|
server,
|
||||||
serverpath: serverPath,
|
serverpath: serverPath
|
||||||
};
|
}
|
||||||
|
|
||||||
const xml = parseXml(wait(xapi.call('SR.probe', host.ref, deviceConfig, 'nfs', {})));
|
const xml = parseXml(wait(xapi.call('SR.probe', host.ref, deviceConfig, 'nfs', {})))
|
||||||
|
|
||||||
const srs = [];
|
const srs = []
|
||||||
|
|
||||||
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
forEach(ensureArray(xml['SRlist'].SR), sr => {
|
||||||
// get the UUID of SR connected to this LUN
|
// get the UUID of SR connected to this LUN
|
||||||
srs.push({uuid: sr.UUID.trim()});
|
srs.push({uuid: sr.UUID.trim()})
|
||||||
});
|
})
|
||||||
|
|
||||||
return srs;
|
return srs
|
||||||
});
|
})
|
||||||
|
|
||||||
probeNfsExists.params = {
|
probeNfsExists.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
server: { type: 'string' },
|
server: { type: 'string' },
|
||||||
serverPath: { type: 'string' },
|
serverPath: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
probeNfsExists.resolve = {
|
probeNfsExists.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// This function helps to reattach a forgotten NFS/iSCSI SR
|
// This function helps to reattach a forgotten NFS/iSCSI SR
|
||||||
|
|
||||||
export const reattach = coroutine(function ({
|
export const reattach = coroutine(function ({
|
||||||
@ -613,10 +613,10 @@ export const reattach = coroutine(function ({
|
|||||||
nameDescription,
|
nameDescription,
|
||||||
type,
|
type,
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
if (type === 'iscsi') {
|
if (type === 'iscsi') {
|
||||||
type = 'lvmoiscsi'; // the internal XAPI name
|
type = 'lvmoiscsi' // the internal XAPI name
|
||||||
}
|
}
|
||||||
|
|
||||||
const srRef = wait(xapi.call(
|
const srRef = wait(xapi.call(
|
||||||
@ -628,25 +628,25 @@ export const reattach = coroutine(function ({
|
|||||||
'user',
|
'user',
|
||||||
true,
|
true,
|
||||||
{}
|
{}
|
||||||
));
|
))
|
||||||
|
|
||||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||||
return sr.uuid;
|
return sr.uuid
|
||||||
});
|
})
|
||||||
|
|
||||||
reattach.params = {
|
reattach.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
uuid: { type: 'string' },
|
uuid: { type: 'string' },
|
||||||
nameLabel: { type: 'string' },
|
nameLabel: { type: 'string' },
|
||||||
nameDescription: { type: 'string' },
|
nameDescription: { type: 'string' },
|
||||||
type: { type: 'string' },
|
type: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
reattach.resolve = {
|
reattach.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
// This function helps to reattach a forgotten ISO SR
|
// This function helps to reattach a forgotten ISO SR
|
||||||
|
|
||||||
export const reattachIso = coroutine(function ({
|
export const reattachIso = coroutine(function ({
|
||||||
@ -656,10 +656,10 @@ export const reattachIso = coroutine(function ({
|
|||||||
nameDescription,
|
nameDescription,
|
||||||
type,
|
type,
|
||||||
}) {
|
}) {
|
||||||
const xapi = this.getXAPI(host);
|
const xapi = this.getXAPI(host)
|
||||||
|
|
||||||
if (type === 'iscsi') {
|
if (type === 'iscsi') {
|
||||||
type = 'lvmoiscsi'; // the internal XAPI name
|
type = 'lvmoiscsi' // the internal XAPI name
|
||||||
}
|
}
|
||||||
|
|
||||||
const srRef = wait(xapi.call(
|
const srRef = wait(xapi.call(
|
||||||
@ -671,20 +671,20 @@ export const reattachIso = coroutine(function ({
|
|||||||
'iso',
|
'iso',
|
||||||
true,
|
true,
|
||||||
{}
|
{}
|
||||||
));
|
))
|
||||||
|
|
||||||
const sr = wait(xapi.call('SR.get_record', srRef));
|
const sr = wait(xapi.call('SR.get_record', srRef))
|
||||||
return sr.uuid;
|
return sr.uuid
|
||||||
});
|
})
|
||||||
|
|
||||||
reattachIso.params = {
|
reattachIso.params = {
|
||||||
host: { type: 'string' },
|
host: { type: 'string' },
|
||||||
uuid: { type: 'string' },
|
uuid: { type: 'string' },
|
||||||
nameLabel: { type: 'string' },
|
nameLabel: { type: 'string' },
|
||||||
nameDescription: { type: 'string' },
|
nameDescription: { type: 'string' },
|
||||||
type: { type: 'string' },
|
type: { type: 'string' }
|
||||||
};
|
}
|
||||||
|
|
||||||
reattachIso.resolve = {
|
reattachIso.resolve = {
|
||||||
host: ['host', 'host'],
|
host: ['host', 'host']
|
||||||
};
|
}
|
||||||
|
@ -1,175 +1,173 @@
|
|||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird'
|
||||||
import isArray from 'lodash.isarray';
|
import isArray from 'lodash.isarray'
|
||||||
import isObject from 'lodash.isobject';
|
import isObject from 'lodash.isobject'
|
||||||
import makeError from 'make-error';
|
import Model from './model'
|
||||||
import Model from './model';
|
import {BaseError} from 'make-error'
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events'
|
||||||
import {mapInPlace} from './utils';
|
import {mapInPlace} from './utils'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
function ModelAlreadyExists(id) {
|
export class ModelAlreadyExists {
|
||||||
ModelAlreadyExists.super.call(this, 'this model already exists: ' + id);
|
constructor (id) {
|
||||||
|
super('this model already exists: ' + id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
makeError(ModelAlreadyExists);
|
|
||||||
export {ModelAlreadyExists};
|
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export default class Collection extends EventEmitter {
|
export default class Collection extends EventEmitter {
|
||||||
// Default value for Model.
|
// Default value for Model.
|
||||||
get Model() {
|
get Model () {
|
||||||
return Model;
|
return Model
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make this property writable.
|
// Make this property writable.
|
||||||
set Model(Model) {
|
set Model (Model) {
|
||||||
Object.defineProperty(this, 'Model', {
|
Object.defineProperty(this, 'Model', {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerale: true,
|
enumerale: true,
|
||||||
value: Model,
|
value: Model,
|
||||||
writable: true,
|
writable: true
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor () {
|
||||||
super();
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
add(models, opts) {
|
add (models, opts) {
|
||||||
let array = isArray(models);
|
const array = isArray(models)
|
||||||
if (!array) {
|
if (!array) {
|
||||||
models = [models];
|
models = [models]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let {Model} = this;
|
const {Model} = this
|
||||||
mapInPlace(models, model => {
|
mapInPlace(models, model => {
|
||||||
if (!(model instanceof Model)) {
|
if (!(model instanceof Model)) {
|
||||||
model = new Model(model);
|
model = new Model(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
let error = model.validate();
|
const error = model.validate()
|
||||||
if (error) {
|
if (error) {
|
||||||
// TODO: Better system inspired by Backbone.js
|
// TODO: Better system inspired by Backbone.js
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
return model.properties;
|
return model.properties
|
||||||
});
|
})
|
||||||
|
|
||||||
return Bluebird.try(this._add, [models, opts], this).then(models => {
|
return Bluebird.try(this._add, [models, opts], this).then(models => {
|
||||||
this.emit('add', models);
|
this.emit('add', models)
|
||||||
|
|
||||||
return array ? models : new this.Model(models[0]);
|
return array ? models : new this.Model(models[0])
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
first(properties) {
|
first (properties) {
|
||||||
if (!isObject(properties)) {
|
if (!isObject(properties)) {
|
||||||
properties = (properties !== undefined) ?
|
properties = (properties !== undefined) ?
|
||||||
{ id: properties } :
|
{ id: properties } :
|
||||||
{}
|
{}
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Bluebird.try(this._first, [properties], this).then(
|
return Bluebird.try(this._first, [properties], this).then(
|
||||||
model => model && new this.Model(model)
|
model => model && new this.Model(model)
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get(properties) {
|
get (properties) {
|
||||||
if (!isObject(properties)) {
|
if (!isObject(properties)) {
|
||||||
properties = (properties !== undefined) ?
|
properties = (properties !== undefined) ?
|
||||||
{ id: properties } :
|
{ id: properties } :
|
||||||
{}
|
{}
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Bluebird.try(this._get, [properties], this);
|
return Bluebird.try(this._get, [properties], this)
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(ids) {
|
remove (ids) {
|
||||||
if (!isArray(ids)) {
|
if (!isArray(ids)) {
|
||||||
ids = [ids];
|
ids = [ids]
|
||||||
}
|
}
|
||||||
|
|
||||||
return Bluebird.try(this._remove, [ids], this).then(() => {
|
return Bluebird.try(this._remove, [ids], this).then(() => {
|
||||||
this.emit('remove', ids);
|
this.emit('remove', ids)
|
||||||
return true;
|
return true
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
update(models) {
|
update (models) {
|
||||||
var array = isArray(models);
|
var array = isArray(models)
|
||||||
if (!isArray(models)) {
|
if (!isArray(models)) {
|
||||||
models = [models];
|
models = [models]
|
||||||
}
|
}
|
||||||
|
|
||||||
let {Model} = this;
|
const {Model} = this
|
||||||
mapInPlace(models, model => {
|
mapInPlace(models, model => {
|
||||||
if (!(model instanceof Model)) {
|
if (!(model instanceof Model)) {
|
||||||
// TODO: Problems, we may be mixing in some default
|
// TODO: Problems, we may be mixing in some default
|
||||||
// properties which will overwrite existing ones.
|
// properties which will overwrite existing ones.
|
||||||
model = new Model(model);
|
model = new Model(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = model.get('id');
|
const id = model.get('id')
|
||||||
|
|
||||||
// Missing models should be added not updated.
|
// Missing models should be added not updated.
|
||||||
if (id === undefined){
|
if (id === undefined) {
|
||||||
// FIXME: should not throw an exception but return a rejected promise.
|
// FIXME: should not throw an exception but return a rejected promise.
|
||||||
throw new Error('a model without an id cannot be updated');
|
throw new Error('a model without an id cannot be updated')
|
||||||
}
|
}
|
||||||
|
|
||||||
var error = model.validate();
|
var error = model.validate()
|
||||||
if (error !== undefined) {
|
if (error !== undefined) {
|
||||||
// TODO: Better system inspired by Backbone.js.
|
// TODO: Better system inspired by Backbone.js.
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
return model.properties;
|
return model.properties
|
||||||
});
|
})
|
||||||
|
|
||||||
return Bluebird.try(this._update, [models], this).then(models => {
|
return Bluebird.try(this._update, [models], this).then(models => {
|
||||||
this.emit('update', models);
|
this.emit('update', models)
|
||||||
|
|
||||||
return array ? models : new this.Model(models[0]);
|
return array ? models : new this.Model(models[0])
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods to override in implementations.
|
// Methods to override in implementations.
|
||||||
|
|
||||||
_add() {
|
_add () {
|
||||||
throw new Error('not implemented');
|
throw new Error('not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
_get() {
|
_get () {
|
||||||
throw new Error('not implemented');
|
throw new Error('not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
_remove() {
|
_remove () {
|
||||||
throw new Error('not implemented');
|
throw new Error('not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
_update() {
|
_update () {
|
||||||
throw new Error('not implemented');
|
throw new Error('not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Methods which may be overridden in implementations.
|
// Methods which may be overridden in implementations.
|
||||||
|
|
||||||
count(properties) {
|
count (properties) {
|
||||||
return this.get(properties).get('count');
|
return this.get(properties).get('count')
|
||||||
}
|
}
|
||||||
|
|
||||||
exists(properties) {
|
exists (properties) {
|
||||||
/* jshint eqnull: true */
|
/* jshint eqnull: true */
|
||||||
return this.first(properties).then(model => model != null);
|
return this.first(properties).then(model => model != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
_first(properties) {
|
_first (properties) {
|
||||||
return Bluebird.try(this.get, [properties], this).then(
|
return Bluebird.try(this.get, [properties], this).then(
|
||||||
models => models.length ? models[0] : null
|
models => models.length ? models[0] : null
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import Bluebird, {coroutine} from 'bluebird';
|
import Bluebird, {coroutine} from 'bluebird'
|
||||||
import Collection, {ModelAlreadyExists} from '../collection';
|
import Collection, {ModelAlreadyExists} from '../collection'
|
||||||
import difference from 'lodash.difference';
|
import difference from 'lodash.difference'
|
||||||
import filter from 'lodash.filter';
|
import filter from 'lodash.filter'
|
||||||
import forEach from 'lodash.foreach';
|
import forEach from 'lodash.foreach'
|
||||||
import getKey from 'lodash.keys';
|
import getKey from 'lodash.keys'
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty'
|
||||||
import map from 'lodash.map';
|
import map from 'lodash.map'
|
||||||
import thenRedis from 'then-redis';
|
import thenRedis from 'then-redis'
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////
|
||||||
// Data model:
|
// Data model:
|
||||||
// - prefix +'_id': value of the last generated identifier;
|
// - prefix +'_id': value of the last generated identifier;
|
||||||
// - prefix +'_ids': set containing identifier of all models;
|
// - prefix +'_ids': set containing identifier of all models;
|
||||||
// - prefix +'_'+ index +':' + value: set of identifiers which have
|
// - prefix +'_'+ index +':' + value: set of identifiers which have
|
||||||
// value for the given index.
|
// value for the given index.
|
||||||
// - prefix +':'+ id: hash containing the properties of a model;
|
// - prefix +':'+ id: hash containing the properties of a model;
|
||||||
//////////////////////////////////////////////////////////////////////
|
// ///////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// TODO: then-redis sends commands in order, we should use this
|
// TODO: then-redis sends commands in order, we should use this
|
||||||
// semantic to simplify the code.
|
// semantic to simplify the code.
|
||||||
@ -26,124 +26,124 @@ import thenRedis from 'then-redis';
|
|||||||
// TODO: Remote events.
|
// TODO: Remote events.
|
||||||
|
|
||||||
export default class Redis extends Collection {
|
export default class Redis extends Collection {
|
||||||
constructor({
|
constructor ({
|
||||||
connection,
|
connection,
|
||||||
indexes = [],
|
indexes = [],
|
||||||
prefix,
|
prefix,
|
||||||
uri = 'tcp://localhost:6379',
|
uri = 'tcp://localhost:6379',
|
||||||
}) {
|
}) {
|
||||||
super();
|
super()
|
||||||
|
|
||||||
this.indexes = indexes;
|
this.indexes = indexes
|
||||||
this.prefix = prefix;
|
this.prefix = prefix
|
||||||
this.redis = connection || thenRedis.createClient(uri);
|
this.redis = connection || thenRedis.createClient(uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
_extract(ids) {
|
_extract (ids) {
|
||||||
let prefix = this.prefix + ':';
|
const prefix = this.prefix + ':'
|
||||||
let {redis} = this;
|
const {redis} = this
|
||||||
|
|
||||||
let models = [];
|
const models = []
|
||||||
return Bluebird.map(ids, id => {
|
return Bluebird.map(ids, id => {
|
||||||
return redis.hgetall(prefix + id).then(model => {
|
return redis.hgetall(prefix + id).then(model => {
|
||||||
// If empty, consider it a no match.
|
// If empty, consider it a no match.
|
||||||
if (isEmpty(model)) {
|
if (isEmpty(model)) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mix the identifier in.
|
// Mix the identifier in.
|
||||||
model.id = id;
|
model.id = id
|
||||||
|
|
||||||
models.push(model);
|
models.push(model)
|
||||||
});
|
})
|
||||||
}).return(models);
|
}).return(models)
|
||||||
}
|
}
|
||||||
|
|
||||||
_add(models, {replace = false} = {}) {
|
_add (models, {replace = false} = {}) {
|
||||||
// TODO: remove “replace” which is a temporary measure, implement
|
// TODO: remove “replace” which is a temporary measure, implement
|
||||||
// “set()” instead.
|
// “set()” instead.
|
||||||
|
|
||||||
let {indexes, prefix, redis} = this;
|
const {indexes, prefix, redis} = this
|
||||||
|
|
||||||
return Bluebird.map(models, coroutine(function *(model) {
|
return Bluebird.map(models, coroutine(function * (model) {
|
||||||
// Generate a new identifier if necessary.
|
// Generate a new identifier if necessary.
|
||||||
if (model.id === undefined) {
|
if (model.id === undefined) {
|
||||||
model.id = String(yield redis.incr(prefix + '_id'));
|
model.id = String(yield redis.incr(prefix + '_id'))
|
||||||
}
|
}
|
||||||
|
|
||||||
let success = yield redis.sadd(prefix + '_ids', model.id);
|
const success = yield redis.sadd(prefix + '_ids', model.id)
|
||||||
|
|
||||||
// The entry already exists an we are not in replace mode.
|
// The entry already exists an we are not in replace mode.
|
||||||
if (!success && !replace) {
|
if (!success && !replace) {
|
||||||
throw new ModelAlreadyExists(model.id);
|
throw new ModelAlreadyExists(model.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Remove existing fields.
|
// TODO: Remove existing fields.
|
||||||
|
|
||||||
let params = [];
|
const params = []
|
||||||
forEach(model, (value, name) => {
|
forEach(model, (value, name) => {
|
||||||
// No need to store the identifier (already in the key).
|
// No need to store the identifier (already in the key).
|
||||||
if (name === 'id') {
|
if (name === 'id') {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
params.push(name, value);
|
params.push(name, value)
|
||||||
});
|
})
|
||||||
|
|
||||||
let promises = [
|
const promises = [
|
||||||
redis.hmset(prefix + ':' + model.id, ...params),
|
redis.hmset(prefix + ':' + model.id, ...params)
|
||||||
];
|
]
|
||||||
|
|
||||||
// Update indexes.
|
// Update indexes.
|
||||||
forEach(indexes, (index) => {
|
forEach(indexes, (index) => {
|
||||||
let value = model[index];
|
const value = model[index]
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = prefix + '_' + index + ':' + value;
|
const key = prefix + '_' + index + ':' + value
|
||||||
promises.push(redis.sadd(key, model.id));
|
promises.push(redis.sadd(key, model.id))
|
||||||
});
|
})
|
||||||
|
|
||||||
yield Bluebird.all(promises);
|
yield Bluebird.all(promises)
|
||||||
|
|
||||||
return model;
|
return model
|
||||||
}));
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
_get(properties) {
|
_get (properties) {
|
||||||
let {prefix, redis} = this;
|
const {prefix, redis} = this
|
||||||
|
|
||||||
if (isEmpty(properties)) {
|
if (isEmpty(properties)) {
|
||||||
return redis.smembers(prefix + '_ids').then(ids => this._extract(ids));
|
return redis.smembers(prefix + '_ids').then(ids => this._extract(ids))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special treatment for the identifier.
|
// Special treatment for the identifier.
|
||||||
let id = properties.id;
|
const id = properties.id
|
||||||
if (id !== undefined) {
|
if (id !== undefined) {
|
||||||
delete properties.id
|
delete properties.id
|
||||||
return this._extract([id]).then(models => {
|
return this._extract([id]).then(models => {
|
||||||
return (models.length && !isEmpty(properties)) ?
|
return (models.length && !isEmpty(properties)) ?
|
||||||
filter(models) :
|
filter(models) :
|
||||||
models
|
models
|
||||||
;
|
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let {indexes} = this;
|
const {indexes} = this
|
||||||
|
|
||||||
// Check for non indexed fields.
|
// Check for non indexed fields.
|
||||||
let unfit = difference(getKey(properties), indexes);
|
const unfit = difference(getKey(properties), indexes)
|
||||||
if (unfit.length) {
|
if (unfit.length) {
|
||||||
throw new Error('fields not indexed: ' + unfit.join());
|
throw new Error('fields not indexed: ' + unfit.join())
|
||||||
}
|
}
|
||||||
|
|
||||||
let keys = map(properties, (value, index) => prefix + '_' + index + ':' + value);
|
const keys = map(properties, (value, index) => prefix + '_' + index + ':' + value)
|
||||||
return redis.sinter(...keys).then(ids => this._extract(ids));
|
return redis.sinter(...keys).then(ids => this._extract(ids))
|
||||||
}
|
}
|
||||||
|
|
||||||
_remove(ids) {
|
_remove (ids) {
|
||||||
let {prefix, redis} = this;
|
const {prefix, redis} = this
|
||||||
|
|
||||||
// TODO: handle indexes.
|
// TODO: handle indexes.
|
||||||
|
|
||||||
@ -152,11 +152,11 @@ export default class Redis extends Collection {
|
|||||||
redis.srem(prefix + '_ids', ...ids),
|
redis.srem(prefix + '_ids', ...ids),
|
||||||
|
|
||||||
// Remove the models.
|
// Remove the models.
|
||||||
redis.del(map(ids, id => prefix + ':' + id)),
|
redis.del(map(ids, id => prefix + ':' + id))
|
||||||
]);
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
_update(models) {
|
_update (models) {
|
||||||
return this._add(models, { replace: true });
|
return this._add(models, { replace: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,83 +1,79 @@
|
|||||||
'use strict';
|
'use strict'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter;
|
var EventEmitter = require('events').EventEmitter
|
||||||
var inherits = require('util').inherits;
|
var inherits = require('util').inherits
|
||||||
|
|
||||||
var assign = require('lodash.assign');
|
var assign = require('lodash.assign')
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
var has = Object.prototype.hasOwnProperty;
|
var has = Object.prototype.hasOwnProperty
|
||||||
has = has.call.bind(has);
|
has = has.call.bind(has)
|
||||||
|
|
||||||
function noop() {}
|
function noop () {}
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
function Connection(opts) {
|
function Connection (opts) {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this)
|
||||||
|
|
||||||
this.data = Object.create(null);
|
this.data = Object.create(null)
|
||||||
|
|
||||||
this._close = opts.close;
|
this._close = opts.close
|
||||||
this.notify = opts.notify;
|
this.notify = opts.notify
|
||||||
}
|
}
|
||||||
inherits(Connection, EventEmitter);
|
inherits(Connection, EventEmitter)
|
||||||
|
|
||||||
assign(Connection.prototype, {
|
assign(Connection.prototype, {
|
||||||
// Close the connection.
|
// Close the connection.
|
||||||
close: function () {
|
close: function () {
|
||||||
// Prevent errors when the connection is closed more than once.
|
// Prevent errors when the connection is closed more than once.
|
||||||
this.close = noop;
|
this.close = noop
|
||||||
|
|
||||||
this._close();
|
this._close()
|
||||||
|
|
||||||
this.emit('close');
|
this.emit('close')
|
||||||
|
|
||||||
// Releases values AMAP to ease the garbage collecting.
|
// Releases values AMAP to ease the garbage collecting.
|
||||||
for (var key in this)
|
for (var key in this) {
|
||||||
{
|
if (key !== 'close' && has(this, key)) {
|
||||||
if (key !== 'close' && has(this, key))
|
delete this[key]
|
||||||
{
|
}
|
||||||
delete this[key];
|
}
|
||||||
}
|
},
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Gets the value for this key.
|
// Gets the value for this key.
|
||||||
get: function (key, defaultValue) {
|
get: function (key, defaultValue) {
|
||||||
var data = this.data;
|
var data = this.data
|
||||||
|
|
||||||
if (key in data)
|
if (key in data) {
|
||||||
{
|
return data[key]
|
||||||
return data[key];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments.length >= 2)
|
if (arguments.length >= 2) {
|
||||||
{
|
return defaultValue
|
||||||
return defaultValue;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error('no value for `'+ key +'`');
|
throw new Error('no value for `' + key + '`')
|
||||||
},
|
},
|
||||||
|
|
||||||
// Checks whether there is a value for this key.
|
// Checks whether there is a value for this key.
|
||||||
has: function (key) {
|
has: function (key) {
|
||||||
return key in this.data;
|
return key in this.data
|
||||||
},
|
},
|
||||||
|
|
||||||
// Sets the value for this key.
|
// Sets the value for this key.
|
||||||
set: function (key, value) {
|
set: function (key, value) {
|
||||||
this.data[key] = value;
|
this.data[key] = value
|
||||||
},
|
},
|
||||||
|
|
||||||
unset: function (key) {
|
unset: function (key) {
|
||||||
delete this.data[key];
|
delete this.data[key]
|
||||||
},
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
module.exports = Connection;
|
module.exports = Connection
|
||||||
|
@ -1,199 +1,199 @@
|
|||||||
'use strict';
|
'use strict'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
/* jshint mocha: true */
|
/* eslint-env mocha */
|
||||||
|
|
||||||
var expect = require('chai').expect;
|
var expect = require('chai').expect
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
var Promise = require('bluebird');
|
var Promise = require('bluebird')
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
var utils = require('./fibers-utils');
|
var utils = require('./fibers-utils')
|
||||||
var $coroutine = utils.$coroutine;
|
var $coroutine = utils.$coroutine
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
describe('$coroutine', function () {
|
describe('$coroutine', function () {
|
||||||
it('creates a on which returns promises', function () {
|
it('creates a on which returns promises', function () {
|
||||||
var fn = $coroutine(function () {});
|
var fn = $coroutine(function () {})
|
||||||
expect(fn().then).to.be.a('function');
|
expect(fn().then).to.be.a('function')
|
||||||
});
|
})
|
||||||
|
|
||||||
it('creates a function which runs in a new fiber', function () {
|
it('creates a function which runs in a new fiber', function () {
|
||||||
var previous = require('fibers').current;
|
var previous = require('fibers').current
|
||||||
|
|
||||||
var fn = $coroutine(function () {
|
var fn = $coroutine(function () {
|
||||||
var current = require('fibers').current;
|
var current = require('fibers').current
|
||||||
|
|
||||||
expect(current).to.exists;
|
expect(current).to.exists
|
||||||
expect(current).to.not.equal(previous);
|
expect(current).to.not.equal(previous)
|
||||||
});
|
})
|
||||||
|
|
||||||
fn();
|
fn()
|
||||||
});
|
})
|
||||||
|
|
||||||
it('forwards all arguments (even this)', function () {
|
it('forwards all arguments (even this)', function () {
|
||||||
var self = {};
|
var self = {}
|
||||||
var arg1 = {};
|
var arg1 = {}
|
||||||
var arg2 = {};
|
var arg2 = {}
|
||||||
|
|
||||||
$coroutine(function (arg1_, arg2_) {
|
$coroutine(function (arg1_, arg2_) {
|
||||||
expect(this).to.equal(self);
|
expect(this).to.equal(self)
|
||||||
expect(arg1_).to.equal(arg1);
|
expect(arg1_).to.equal(arg1)
|
||||||
expect(arg2_).to.equal(arg2);
|
expect(arg2_).to.equal(arg2)
|
||||||
}).call(self, arg1, arg2);
|
}).call(self, arg1, arg2)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
describe('$wait', function () {
|
describe('$wait', function () {
|
||||||
var $wait = utils.$wait;
|
var $wait = utils.$wait
|
||||||
|
|
||||||
it('waits for a promise', function (done) {
|
it('waits for a promise', function (done) {
|
||||||
$coroutine(function () {
|
$coroutine(function () {
|
||||||
var value = {};
|
var value = {}
|
||||||
var promise = Promise.cast(value);
|
var promise = Promise.cast(value)
|
||||||
|
|
||||||
expect($wait(promise)).to.equal(value);
|
expect($wait(promise)).to.equal(value)
|
||||||
|
|
||||||
done();
|
done()
|
||||||
})();
|
})()
|
||||||
});
|
})
|
||||||
|
|
||||||
it('handles promise rejection', function (done) {
|
it('handles promise rejection', function (done) {
|
||||||
$coroutine(function () {
|
$coroutine(function () {
|
||||||
var promise = Promise.reject('an exception');
|
var promise = Promise.reject('an exception')
|
||||||
|
|
||||||
expect(function () {
|
expect(function () {
|
||||||
$wait(promise);
|
$wait(promise)
|
||||||
}).to.throw('an exception');
|
}).to.throw('an exception')
|
||||||
|
|
||||||
done();
|
done()
|
||||||
})();
|
})()
|
||||||
});
|
})
|
||||||
|
|
||||||
it('waits for a continuable', function (done) {
|
it('waits for a continuable', function (done) {
|
||||||
$coroutine(function () {
|
$coroutine(function () {
|
||||||
var value = {};
|
var value = {}
|
||||||
var continuable = function (callback) {
|
var continuable = function (callback) {
|
||||||
callback(null, value);
|
callback(null, value)
|
||||||
};
|
}
|
||||||
|
|
||||||
expect($wait(continuable)).to.equal(value);
|
expect($wait(continuable)).to.equal(value)
|
||||||
|
|
||||||
done();
|
done()
|
||||||
})();
|
})()
|
||||||
});
|
})
|
||||||
|
|
||||||
it('handles continuable error', function (done) {
|
it('handles continuable error', function (done) {
|
||||||
$coroutine(function () {
|
$coroutine(function () {
|
||||||
var continuable = function (callback) {
|
var continuable = function (callback) {
|
||||||
callback('an exception');
|
callback('an exception')
|
||||||
};
|
}
|
||||||
|
|
||||||
expect(function () {
|
expect(function () {
|
||||||
$wait(continuable);
|
$wait(continuable)
|
||||||
}).to.throw('an exception');
|
}).to.throw('an exception')
|
||||||
|
|
||||||
done();
|
done()
|
||||||
})();
|
})()
|
||||||
});
|
})
|
||||||
|
|
||||||
it('forwards scalar values', function (done) {
|
it('forwards scalar values', function (done) {
|
||||||
$coroutine(function () {
|
$coroutine(function () {
|
||||||
var value = 'a scalar value';
|
var value = 'a scalar value'
|
||||||
expect($wait(value)).to.equal(value);
|
expect($wait(value)).to.equal(value)
|
||||||
|
|
||||||
value = [
|
value = [
|
||||||
'foo',
|
'foo',
|
||||||
'bar',
|
'bar',
|
||||||
'baz',
|
'baz'
|
||||||
];
|
]
|
||||||
expect($wait(value)).to.deep.equal(value);
|
expect($wait(value)).to.deep.equal(value)
|
||||||
|
|
||||||
value = [];
|
value = []
|
||||||
expect($wait(value)).to.deep.equal(value);
|
expect($wait(value)).to.deep.equal(value)
|
||||||
|
|
||||||
value = {
|
value = {
|
||||||
foo: 'foo',
|
foo: 'foo',
|
||||||
bar: 'bar',
|
bar: 'bar',
|
||||||
baz: 'baz',
|
baz: 'baz'
|
||||||
};
|
}
|
||||||
expect($wait(value)).to.deep.equal(value);
|
expect($wait(value)).to.deep.equal(value)
|
||||||
|
|
||||||
value = {};
|
value = {}
|
||||||
expect($wait(value)).to.deep.equal(value);
|
expect($wait(value)).to.deep.equal(value)
|
||||||
|
|
||||||
done();
|
done()
|
||||||
})();
|
})()
|
||||||
});
|
})
|
||||||
|
|
||||||
it('handles arrays of promises/continuables', function (done) {
|
it('handles arrays of promises/continuables', function (done) {
|
||||||
$coroutine(function () {
|
$coroutine(function () {
|
||||||
var value1 = {};
|
var value1 = {}
|
||||||
var value2 = {};
|
var value2 = {}
|
||||||
|
|
||||||
var promise = Promise.cast(value1);
|
var promise = Promise.cast(value1)
|
||||||
var continuable = function (callback) {
|
var continuable = function (callback) {
|
||||||
callback(null, value2);
|
callback(null, value2)
|
||||||
};
|
}
|
||||||
|
|
||||||
var results = $wait([promise, continuable]);
|
var results = $wait([promise, continuable])
|
||||||
expect(results[0]).to.equal(value1);
|
expect(results[0]).to.equal(value1)
|
||||||
expect(results[1]).to.equal(value2);
|
expect(results[1]).to.equal(value2)
|
||||||
|
|
||||||
done();
|
done()
|
||||||
})();
|
})()
|
||||||
});
|
})
|
||||||
|
|
||||||
it('handles maps of promises/continuable', function (done) {
|
it('handles maps of promises/continuable', function (done) {
|
||||||
$coroutine(function () {
|
$coroutine(function () {
|
||||||
var value1 = {};
|
var value1 = {}
|
||||||
var value2 = {};
|
var value2 = {}
|
||||||
|
|
||||||
var promise = Promise.cast(value1);
|
var promise = Promise.cast(value1)
|
||||||
var continuable = function (callback) {
|
var continuable = function (callback) {
|
||||||
callback(null, value2);
|
callback(null, value2)
|
||||||
};
|
}
|
||||||
|
|
||||||
var results = $wait({
|
var results = $wait({
|
||||||
foo: promise,
|
foo: promise,
|
||||||
bar: continuable
|
bar: continuable
|
||||||
});
|
})
|
||||||
expect(results.foo).to.equal(value1);
|
expect(results.foo).to.equal(value1)
|
||||||
expect(results.bar).to.equal(value2);
|
expect(results.bar).to.equal(value2)
|
||||||
|
|
||||||
done();
|
done()
|
||||||
})();
|
})()
|
||||||
});
|
})
|
||||||
|
|
||||||
it('handles nested arrays/maps', function (done) {
|
it('handles nested arrays/maps', function (done) {
|
||||||
var promise = Promise.cast('a promise');
|
var promise = Promise.cast('a promise')
|
||||||
var continuable = function (callback) {
|
var continuable = function (callback) {
|
||||||
callback(null, 'a continuable');
|
callback(null, 'a continuable')
|
||||||
};
|
}
|
||||||
|
|
||||||
$coroutine(function () {
|
$coroutine(function () {
|
||||||
expect($wait({
|
expect($wait({
|
||||||
foo: promise,
|
foo: promise,
|
||||||
bar: [
|
bar: [
|
||||||
continuable,
|
continuable,
|
||||||
'a scalar'
|
'a scalar'
|
||||||
]
|
]
|
||||||
})).to.deep.equal({
|
})).to.deep.equal({
|
||||||
foo: 'a promise',
|
foo: 'a promise',
|
||||||
bar: [
|
bar: [
|
||||||
'a continuable',
|
'a continuable',
|
||||||
'a scalar'
|
'a scalar'
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|
||||||
done();
|
done()
|
||||||
})();
|
})()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
800
src/index.js
800
src/index.js
@ -1,419 +1,425 @@
|
|||||||
import createLogger from 'debug';
|
import createLogger from 'debug'
|
||||||
let debug = createLogger('xo:main');
|
const debug = createLogger('xo:main')
|
||||||
let debugPlugin = createLogger('xo:plugin');
|
const debugPlugin = createLogger('xo:plugin')
|
||||||
|
|
||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird'
|
||||||
Bluebird.longStackTraces();
|
Bluebird.longStackTraces()
|
||||||
|
|
||||||
import appConf from 'app-conf';
|
import appConf from 'app-conf'
|
||||||
import assign from 'lodash.assign';
|
import assign from 'lodash.assign'
|
||||||
import bind from 'lodash.bind';
|
import bind from 'lodash.bind'
|
||||||
import createConnectApp from 'connect';
|
import createConnectApp from 'connect'
|
||||||
import eventToPromise from 'event-to-promise';
|
import eventToPromise from 'event-to-promise'
|
||||||
import forEach from 'lodash.foreach';
|
import forEach from 'lodash.foreach'
|
||||||
import has from 'lodash.has';
|
import has from 'lodash.has'
|
||||||
import isArray from 'lodash.isarray';
|
import isArray from 'lodash.isarray'
|
||||||
import isFunction from 'lodash.isfunction';
|
import isFunction from 'lodash.isfunction'
|
||||||
import map from 'lodash.map';
|
import map from 'lodash.map'
|
||||||
import pick from 'lodash.pick';
|
import pick from 'lodash.pick'
|
||||||
import serveStatic from 'serve-static';
|
import serveStatic from 'serve-static'
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws'
|
||||||
import {
|
import {
|
||||||
AlreadyAuthenticated,
|
AlreadyAuthenticated,
|
||||||
InvalidCredential,
|
InvalidCredential,
|
||||||
InvalidParameters,
|
InvalidParameters,
|
||||||
NoSuchObject,
|
NoSuchObject,
|
||||||
NotImplementd,
|
NotImplemented
|
||||||
} from './api-errors';
|
} from './api-errors'
|
||||||
import {coroutine} from 'bluebird';
|
import {coroutine} from 'bluebird'
|
||||||
import {createServer as createJsonRpcServer} from 'json-rpc';
|
import {createServer as createJsonRpcServer} from 'json-rpc'
|
||||||
import {readFile} from 'fs-promise';
|
import {readFile} from 'fs-promise'
|
||||||
|
|
||||||
import Api from './api';
|
import Api from './api'
|
||||||
import WebServer from 'http-server-plus';
|
import WebServer from 'http-server-plus'
|
||||||
import wsProxy from './ws-proxy';
|
import wsProxy from './ws-proxy'
|
||||||
import XO from './xo';
|
import XO from './xo'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
let info = (...args) => {
|
const info = (...args) => {
|
||||||
console.info('[Info]', ...args);
|
console.info('[Info]', ...args)
|
||||||
};
|
|
||||||
|
|
||||||
let warn = (...args) => {
|
|
||||||
console.warn('[Warn]', ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
const DEFAULTS = {
|
|
||||||
http: {
|
|
||||||
listen: [
|
|
||||||
{ port: 80 },
|
|
||||||
],
|
|
||||||
mounts: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEPRECATED_ENTRIES = [
|
|
||||||
'users',
|
|
||||||
'servers',
|
|
||||||
];
|
|
||||||
|
|
||||||
let loadConfiguration = coroutine(function *() {
|
|
||||||
let config = yield appConf.load('xo-server', {
|
|
||||||
defaults: DEFAULTS,
|
|
||||||
ignoreUnknownFormats: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
debug('Configuration loaded.');
|
|
||||||
|
|
||||||
// Print a message if deprecated entries are specified.
|
|
||||||
forEach(DEPRECATED_ENTRIES, entry => {
|
|
||||||
if (has(config, entry)) {
|
|
||||||
warn(`${entry} configuration is deprecated.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return config;
|
|
||||||
});
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
let loadPlugin = Bluebird.method(function (pluginConf, pluginName) {
|
|
||||||
debugPlugin('loading %s', pluginName);
|
|
||||||
|
|
||||||
var pluginPath;
|
|
||||||
try {
|
|
||||||
pluginPath = require.resolve('xo-server-' + pluginName);
|
|
||||||
} catch (e) {
|
|
||||||
pluginPath = require.resolve(pluginName);
|
|
||||||
}
|
|
||||||
|
|
||||||
var plugin = require(pluginPath);
|
|
||||||
|
|
||||||
if (isFunction(plugin)) {
|
|
||||||
plugin = plugin(pluginConf);
|
|
||||||
}
|
|
||||||
|
|
||||||
return plugin.load(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
let loadPlugins = function (plugins, xo) {
|
|
||||||
return Bluebird.all(map(plugins, loadPlugin, xo)).then(() => {
|
|
||||||
debugPlugin('all plugins loaded');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
let makeWebServerListen = coroutine(function *(opts) {
|
|
||||||
// Read certificate and key if necessary.
|
|
||||||
let {certificate, key} = opts;
|
|
||||||
if (certificate && key) {
|
|
||||||
[opts.certificate, opts.key] = yield Bluebird.all([
|
|
||||||
readFile(certificate),
|
|
||||||
readFile(key),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let niceAddress = yield this.listen(opts);
|
|
||||||
debug(`Web server listening on ${niceAddress}`);
|
|
||||||
} catch (error) {
|
|
||||||
warn(`Web server could not listen on ${error.niceAddress}`);
|
|
||||||
|
|
||||||
let {code} = error;
|
|
||||||
if (code === 'EACCES') {
|
|
||||||
warn(' Access denied.');
|
|
||||||
warn(' Ports < 1024 are often reserved to privileges users.');
|
|
||||||
} else if (code === 'EADDRINUSE') {
|
|
||||||
warn(' Address already in use.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let createWebServer = opts => {
|
|
||||||
let webServer = new WebServer();
|
|
||||||
|
|
||||||
return Bluebird
|
|
||||||
.bind(webServer).return(opts).map(makeWebServerListen)
|
|
||||||
.return(webServer)
|
|
||||||
;
|
|
||||||
};
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
let setUpStaticFiles = (connect, opts) => {
|
|
||||||
forEach(opts, (paths, url) => {
|
|
||||||
if (!isArray(paths)) {
|
|
||||||
paths = [paths];
|
|
||||||
}
|
|
||||||
|
|
||||||
forEach(paths, path => {
|
|
||||||
debug('Setting up %s → %s', url, path);
|
|
||||||
|
|
||||||
connect.use(url, serveStatic(path));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
let errorClasses = {
|
|
||||||
ALREADY_AUTHENTICATED: AlreadyAuthenticated,
|
|
||||||
INVALID_CREDENTIAL: InvalidCredential,
|
|
||||||
INVALID_PARAMS: InvalidParameters,
|
|
||||||
NO_SUCH_OBJECT: NoSuchObject,
|
|
||||||
NOT_IMPLEMENTED: NotImplementd,
|
|
||||||
};
|
|
||||||
|
|
||||||
let apiHelpers = {
|
|
||||||
getUserPublicProperties(user) {
|
|
||||||
// Handles both properties and wrapped models.
|
|
||||||
let properties = user.properties || user;
|
|
||||||
|
|
||||||
return pick(properties, 'id', 'email', 'permission');
|
|
||||||
},
|
|
||||||
|
|
||||||
getServerPublicProperties(server) {
|
|
||||||
// Handles both properties and wrapped models.
|
|
||||||
let properties = server.properties || server;
|
|
||||||
|
|
||||||
server = pick(properties, 'id', 'host', 'username');
|
|
||||||
|
|
||||||
// Injects connection status.
|
|
||||||
const xapi = this._xapis[server.id]
|
|
||||||
server.status = xapi ? xapi.status : 'disconnected'
|
|
||||||
|
|
||||||
return server
|
|
||||||
},
|
|
||||||
|
|
||||||
throw(errorId, data) {
|
|
||||||
throw new (errorClasses[errorId])(data);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let setUpApi = (webServer, xo) => {
|
|
||||||
let context = Object.create(xo);
|
|
||||||
assign(xo, apiHelpers);
|
|
||||||
|
|
||||||
let api = new Api({
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
|
|
||||||
let webSocketServer = new WebSocket.Server({
|
|
||||||
server: webServer,
|
|
||||||
path: '/api/',
|
|
||||||
});
|
|
||||||
|
|
||||||
webSocketServer.on('connection', connection => {
|
|
||||||
debug('+ WebSocket connection');
|
|
||||||
|
|
||||||
let xoConnection;
|
|
||||||
|
|
||||||
// Create the JSON-RPC server for this connection.
|
|
||||||
let jsonRpc = createJsonRpcServer(message => {
|
|
||||||
if (message.type === 'request') {
|
|
||||||
return api.call(xoConnection, message.method, message.params);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create the abstract XO object for this connection.
|
|
||||||
xoConnection = xo.createUserConnection({
|
|
||||||
close: bind(connection.close, connection),
|
|
||||||
notify: bind(jsonRpc.notify, jsonRpc),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Close the XO connection with this WebSocket.
|
|
||||||
connection.once('close', () => {
|
|
||||||
debug('- WebSocket connection');
|
|
||||||
|
|
||||||
xoConnection.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Connect the WebSocket to the JSON-RPC server.
|
|
||||||
connection.on('message', message => {
|
|
||||||
jsonRpc.write(message);
|
|
||||||
});
|
|
||||||
|
|
||||||
let onSend = error => {
|
|
||||||
if (error) {
|
|
||||||
warn('WebSocket send:', error.stack);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
jsonRpc.on('data', data => {
|
|
||||||
connection.send(JSON.stringify(data), onSend);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
let getVmConsoleUrl = (xo, id) => {
|
|
||||||
let vm = xo.getObject(id, ['VM', 'VM-controller']);
|
|
||||||
if (!vm || vm.power_state !== 'Running') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let {sessionId} = xo.getXAPI(vm);
|
|
||||||
|
|
||||||
let url;
|
|
||||||
forEach(vm.consoles, console => {
|
|
||||||
if (console.protocol === 'rfb') {
|
|
||||||
url = `${console.location}&session_id=${sessionId}`;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return url;
|
|
||||||
};
|
|
||||||
|
|
||||||
const CONSOLE_PROXY_PATH_RE = /^\/consoles\/(.*)$/;
|
|
||||||
|
|
||||||
let setUpConsoleProxy = (webServer, xo) => {
|
|
||||||
let webSocketServer = new WebSocket.Server({
|
|
||||||
noServer: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
webServer.on('upgrade', (req, res, head) => {
|
|
||||||
let matches = CONSOLE_PROXY_PATH_RE.exec(req.url);
|
|
||||||
if (!matches) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let url = getVmConsoleUrl(xo, matches[1]);
|
|
||||||
if (!url) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: lost connection due to VM restart is not detected.
|
|
||||||
webSocketServer.handleUpgrade(req, res, head, connection => {
|
|
||||||
wsProxy(connection, url);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
let registerPasswordAuthenticationProvider = (xo) => {
|
|
||||||
let passwordAuthenticationProvider = coroutine(function *({
|
|
||||||
email,
|
|
||||||
password,
|
|
||||||
}) {
|
|
||||||
if (email === undefined || password === undefined) {
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let user = yield xo.users.first({email});
|
|
||||||
if (!user || !yield user.checkPassword(password)) {
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
return user;
|
|
||||||
});
|
|
||||||
|
|
||||||
xo.registerAuthenticationProvider(passwordAuthenticationProvider);
|
|
||||||
};
|
|
||||||
|
|
||||||
let registerTokenAuthenticationProvider = (xo) => {
|
|
||||||
let tokenAuthenticationProvider = coroutine(function *({
|
|
||||||
token: tokenId,
|
|
||||||
}) {
|
|
||||||
if (!tokenId) {
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let token = yield xo.tokens.first(tokenId);
|
|
||||||
if (!token) {
|
|
||||||
throw null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return token.get('user_id');
|
|
||||||
});
|
|
||||||
|
|
||||||
xo.registerAuthenticationProvider(tokenAuthenticationProvider);
|
|
||||||
};
|
|
||||||
|
|
||||||
//====================================================================
|
|
||||||
|
|
||||||
let help;
|
|
||||||
{
|
|
||||||
let {name, version} = require('../package');
|
|
||||||
help = () => `${name} v${version}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//====================================================================
|
const warn = (...args) => {
|
||||||
|
console.warn('[Warn]', ...args)
|
||||||
|
}
|
||||||
|
|
||||||
let main = coroutine(function *(args) {
|
// ===================================================================
|
||||||
if (args.indexOf('--help') !== -1 || args.indexOf('-h') !== -1) {
|
|
||||||
return help();
|
|
||||||
}
|
|
||||||
|
|
||||||
let config = yield loadConfiguration();
|
const DEFAULTS = {
|
||||||
|
http: {
|
||||||
|
listen: [
|
||||||
|
{ port: 80 }
|
||||||
|
],
|
||||||
|
mounts: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let webServer = yield createWebServer(config.http.listen);
|
const DEPRECATED_ENTRIES = [
|
||||||
|
'users',
|
||||||
|
'servers'
|
||||||
|
]
|
||||||
|
|
||||||
// Now the web server is listening, drop privileges.
|
const loadConfiguration = coroutine(function * () {
|
||||||
try {
|
const config = yield appConf.load('xo-server', {
|
||||||
let {user, group} = config;
|
defaults: DEFAULTS,
|
||||||
if (group) {
|
ignoreUnknownFormats: true
|
||||||
process.setgid(group);
|
})
|
||||||
debug('Group changed to', group);
|
|
||||||
}
|
|
||||||
if (user) {
|
|
||||||
process.setuid(user);
|
|
||||||
debug('User changed to', user);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
warn('Failed to change user/group:', error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the main object which will connects to Xen servers and
|
debug('Configuration loaded.')
|
||||||
// manages all the models.
|
|
||||||
let xo = new XO();
|
|
||||||
xo.start({
|
|
||||||
redis: {
|
|
||||||
uri: config.redis && config.redis.uri,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Loads default authentication providers.
|
// Print a message if deprecated entries are specified.
|
||||||
registerPasswordAuthenticationProvider(xo);
|
forEach(DEPRECATED_ENTRIES, entry => {
|
||||||
registerTokenAuthenticationProvider(xo);
|
if (has(config, entry)) {
|
||||||
|
warn(`${entry} configuration is deprecated.`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (config.plugins) {
|
return config
|
||||||
yield loadPlugins(config.plugins, xo);
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// Connect is used to manage non WebSocket connections.
|
// ===================================================================
|
||||||
let connect = createConnectApp();
|
|
||||||
webServer.on('request', connect);
|
|
||||||
|
|
||||||
// Must be set up before the API.
|
const loadPlugin = Bluebird.method(function (pluginConf, pluginName) {
|
||||||
setUpConsoleProxy(webServer, xo);
|
debugPlugin('loading %s', pluginName)
|
||||||
|
|
||||||
// Must be set up before the API.
|
var pluginPath
|
||||||
connect.use(bind(xo.handleProxyRequest, xo));
|
try {
|
||||||
|
pluginPath = require.resolve('xo-server-' + pluginName)
|
||||||
|
} catch (e) {
|
||||||
|
pluginPath = require.resolve(pluginName)
|
||||||
|
}
|
||||||
|
|
||||||
// Must be set up before the static files.
|
var plugin = require(pluginPath)
|
||||||
setUpApi(webServer, xo);
|
|
||||||
|
|
||||||
setUpStaticFiles(connect, config.http.mounts);
|
if (isFunction(plugin)) {
|
||||||
|
plugin = plugin(pluginConf)
|
||||||
|
}
|
||||||
|
|
||||||
if (!yield xo.users.exists()) {
|
return plugin.load(this)
|
||||||
let email = 'admin@admin.net';
|
})
|
||||||
let password = 'admin';
|
|
||||||
|
|
||||||
xo.users.create(email, password, 'admin');
|
const loadPlugins = function (plugins, xo) {
|
||||||
info('Default user created:', email, ' with password', password);
|
return Bluebird.all(map(plugins, loadPlugin, xo)).then(() => {
|
||||||
}
|
debugPlugin('all plugins loaded')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Handle gracefully shutdown.
|
// ===================================================================
|
||||||
let closeWebServer = () => { webServer.close(); };
|
|
||||||
process.on('SIGINT', closeWebServer);
|
|
||||||
process.on('SIGTERM', closeWebServer);
|
|
||||||
|
|
||||||
return eventToPromise(webServer, 'close');
|
const makeWebServerListen = coroutine(function * (opts) {
|
||||||
});
|
// Read certificate and key if necessary.
|
||||||
|
const {certificate, key} = opts
|
||||||
|
if (certificate && key) {
|
||||||
|
[opts.certificate, opts.key] = yield Bluebird.all([
|
||||||
|
readFile(certificate),
|
||||||
|
readFile(key)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
exports = module.exports = main;
|
try {
|
||||||
|
const niceAddress = yield this.listen(opts)
|
||||||
|
debug(`Web server listening on ${niceAddress}`)
|
||||||
|
} catch (error) {
|
||||||
|
warn(`Web server could not listen on ${error.niceAddress}`)
|
||||||
|
|
||||||
|
const {code} = error
|
||||||
|
if (code === 'EACCES') {
|
||||||
|
warn(' Access denied.')
|
||||||
|
warn(' Ports < 1024 are often reserved to privileges users.')
|
||||||
|
} else if (code === 'EADDRINUSE') {
|
||||||
|
warn(' Address already in use.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const createWebServer = opts => {
|
||||||
|
const webServer = new WebServer()
|
||||||
|
|
||||||
|
return Bluebird
|
||||||
|
.bind(webServer).return(opts).map(makeWebServerListen)
|
||||||
|
.return(webServer)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
const setUpStaticFiles = (connect, opts) => {
|
||||||
|
forEach(opts, (paths, url) => {
|
||||||
|
if (!isArray(paths)) {
|
||||||
|
paths = [paths]
|
||||||
|
}
|
||||||
|
|
||||||
|
forEach(paths, path => {
|
||||||
|
debug('Setting up %s → %s', url, path)
|
||||||
|
|
||||||
|
connect.use(url, serveStatic(path))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
const errorClasses = {
|
||||||
|
ALREADY_AUTHENTICATED: AlreadyAuthenticated,
|
||||||
|
INVALID_CREDENTIAL: InvalidCredential,
|
||||||
|
INVALID_PARAMS: InvalidParameters,
|
||||||
|
NO_SUCH_OBJECT: NoSuchObject,
|
||||||
|
NOT_IMPLEMENTED: NotImplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
const apiHelpers = {
|
||||||
|
getUserPublicProperties (user) {
|
||||||
|
// Handles both properties and wrapped models.
|
||||||
|
const properties = user.properties || user
|
||||||
|
|
||||||
|
return pick(properties, 'id', 'email', 'permission')
|
||||||
|
},
|
||||||
|
|
||||||
|
getServerPublicProperties (server) {
|
||||||
|
// Handles both properties and wrapped models.
|
||||||
|
const properties = server.properties || server
|
||||||
|
|
||||||
|
server = pick(properties, 'id', 'host', 'username')
|
||||||
|
|
||||||
|
// Injects connection status.
|
||||||
|
const xapi = this._xapis[server.id]
|
||||||
|
server.status = xapi ? xapi.status : 'disconnected'
|
||||||
|
|
||||||
|
return server
|
||||||
|
},
|
||||||
|
|
||||||
|
throw (errorId, data) {
|
||||||
|
throw new (errorClasses[errorId])(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setUpApi = (webServer, xo) => {
|
||||||
|
const context = Object.create(xo)
|
||||||
|
assign(xo, apiHelpers)
|
||||||
|
|
||||||
|
const api = new Api({
|
||||||
|
context
|
||||||
|
})
|
||||||
|
|
||||||
|
const webSocketServer = new WebSocket.Server({
|
||||||
|
server: webServer,
|
||||||
|
path: '/api/'
|
||||||
|
})
|
||||||
|
|
||||||
|
webSocketServer.on('connection', connection => {
|
||||||
|
debug('+ WebSocket connection')
|
||||||
|
|
||||||
|
let xoConnection
|
||||||
|
|
||||||
|
// Create the JSON-RPC server for this connection.
|
||||||
|
const jsonRpc = createJsonRpcServer(message => {
|
||||||
|
if (message.type === 'request') {
|
||||||
|
return api.call(xoConnection, message.method, message.params)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create the abstract XO object for this connection.
|
||||||
|
xoConnection = xo.createUserConnection({
|
||||||
|
close: bind(connection.close, connection),
|
||||||
|
notify: bind(jsonRpc.notify, jsonRpc)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Close the XO connection with this WebSocket.
|
||||||
|
connection.once('close', () => {
|
||||||
|
debug('- WebSocket connection')
|
||||||
|
|
||||||
|
xoConnection.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
// Connect the WebSocket to the JSON-RPC server.
|
||||||
|
connection.on('message', message => {
|
||||||
|
jsonRpc.write(message)
|
||||||
|
})
|
||||||
|
|
||||||
|
const onSend = error => {
|
||||||
|
if (error) {
|
||||||
|
warn('WebSocket send:', error.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonRpc.on('data', data => {
|
||||||
|
connection.send(JSON.stringify(data), onSend)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
const getVmConsoleUrl = (xo, id) => {
|
||||||
|
const vm = xo.getObject(id, ['VM', 'VM-controller'])
|
||||||
|
if (!vm || vm.power_state !== 'Running') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const {sessionId} = xo.getXAPI(vm)
|
||||||
|
|
||||||
|
let url
|
||||||
|
forEach(vm.consoles, console => {
|
||||||
|
if (console.protocol === 'rfb') {
|
||||||
|
url = `${console.location}&session_id=${sessionId}`
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONSOLE_PROXY_PATH_RE = /^\/consoles\/(.*)$/
|
||||||
|
|
||||||
|
const setUpConsoleProxy = (webServer, xo) => {
|
||||||
|
const webSocketServer = new WebSocket.Server({
|
||||||
|
noServer: true
|
||||||
|
})
|
||||||
|
|
||||||
|
webServer.on('upgrade', (req, res, head) => {
|
||||||
|
const matches = CONSOLE_PROXY_PATH_RE.exec(req.url)
|
||||||
|
if (!matches) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = getVmConsoleUrl(xo, matches[1])
|
||||||
|
if (!url) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: lost connection due to VM restart is not detected.
|
||||||
|
webSocketServer.handleUpgrade(req, res, head, connection => {
|
||||||
|
wsProxy(connection, url)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
const registerPasswordAuthenticationProvider = (xo) => {
|
||||||
|
const passwordAuthenticationProvider = coroutine(function * ({
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
}) {
|
||||||
|
/* eslint no-throw-literal: 0 */
|
||||||
|
|
||||||
|
if (email === undefined || password === undefined) {
|
||||||
|
throw null
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = yield xo.users.first({email})
|
||||||
|
if (!user || !(yield user.checkPassword(password))) {
|
||||||
|
throw null
|
||||||
|
}
|
||||||
|
return user
|
||||||
|
})
|
||||||
|
|
||||||
|
xo.registerAuthenticationProvider(passwordAuthenticationProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
const registerTokenAuthenticationProvider = (xo) => {
|
||||||
|
const tokenAuthenticationProvider = coroutine(function * ({
|
||||||
|
token: tokenId,
|
||||||
|
}) {
|
||||||
|
/* eslint no-throw-literal: 0 */
|
||||||
|
|
||||||
|
if (!tokenId) {
|
||||||
|
throw null
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = yield xo.tokens.first(tokenId)
|
||||||
|
if (!token) {
|
||||||
|
throw null
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.get('user_id')
|
||||||
|
})
|
||||||
|
|
||||||
|
xo.registerAuthenticationProvider(tokenAuthenticationProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
let help
|
||||||
|
{
|
||||||
|
/* eslint no-lone-blocks: 0 */
|
||||||
|
|
||||||
|
const {name, version} = require('../package')
|
||||||
|
help = () => `${name} v${version}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
|
const main = coroutine(function * (args) {
|
||||||
|
if (args.indexOf('--help') !== -1 || args.indexOf('-h') !== -1) {
|
||||||
|
return help()
|
||||||
|
}
|
||||||
|
|
||||||
|
const config = yield loadConfiguration()
|
||||||
|
|
||||||
|
const webServer = yield createWebServer(config.http.listen)
|
||||||
|
|
||||||
|
// Now the web server is listening, drop privileges.
|
||||||
|
try {
|
||||||
|
const {user, group} = config
|
||||||
|
if (group) {
|
||||||
|
process.setgid(group)
|
||||||
|
debug('Group changed to', group)
|
||||||
|
}
|
||||||
|
if (user) {
|
||||||
|
process.setuid(user)
|
||||||
|
debug('User changed to', user)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
warn('Failed to change user/group:', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the main object which will connects to Xen servers and
|
||||||
|
// manages all the models.
|
||||||
|
const xo = new XO()
|
||||||
|
xo.start({
|
||||||
|
redis: {
|
||||||
|
uri: config.redis && config.redis.uri
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Loads default authentication providers.
|
||||||
|
registerPasswordAuthenticationProvider(xo)
|
||||||
|
registerTokenAuthenticationProvider(xo)
|
||||||
|
|
||||||
|
if (config.plugins) {
|
||||||
|
yield loadPlugins(config.plugins, xo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect is used to manage non WebSocket connections.
|
||||||
|
const connect = createConnectApp()
|
||||||
|
webServer.on('request', connect)
|
||||||
|
|
||||||
|
// Must be set up before the API.
|
||||||
|
setUpConsoleProxy(webServer, xo)
|
||||||
|
|
||||||
|
// Must be set up before the API.
|
||||||
|
connect.use(bind(xo.handleProxyRequest, xo))
|
||||||
|
|
||||||
|
// Must be set up before the static files.
|
||||||
|
setUpApi(webServer, xo)
|
||||||
|
|
||||||
|
setUpStaticFiles(connect, config.http.mounts)
|
||||||
|
|
||||||
|
if (!(yield xo.users.exists())) {
|
||||||
|
const email = 'admin@admin.net'
|
||||||
|
const password = 'admin'
|
||||||
|
|
||||||
|
xo.users.create(email, password, 'admin')
|
||||||
|
info('Default user created:', email, ' with password', password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle gracefully shutdown.
|
||||||
|
const closeWebServer = () => { webServer.close() }
|
||||||
|
process.on('SIGINT', closeWebServer)
|
||||||
|
process.on('SIGTERM', closeWebServer)
|
||||||
|
|
||||||
|
return eventToPromise(webServer, 'close')
|
||||||
|
})
|
||||||
|
|
||||||
|
exports = module.exports = main
|
||||||
|
54
src/model.js
54
src/model.js
@ -1,70 +1,70 @@
|
|||||||
import assign from 'lodash.assign';
|
import assign from 'lodash.assign'
|
||||||
import forEach from 'lodash.foreach';
|
import forEach from 'lodash.foreach'
|
||||||
import isEmpty from 'lodash.isempty';
|
import isEmpty from 'lodash.isempty'
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events'
|
||||||
|
|
||||||
//====================================================================
|
// ===================================================================
|
||||||
|
|
||||||
export default class Model extends EventEmitter {
|
export default class Model extends EventEmitter {
|
||||||
constructor(properties) {
|
constructor (properties) {
|
||||||
super();
|
super()
|
||||||
|
|
||||||
this.properties = assign({}, this.default);
|
this.properties = assign({}, this.default)
|
||||||
|
|
||||||
if (properties) {
|
if (properties) {
|
||||||
this.set(properties);
|
this.set(properties)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the model after construction.
|
// Initialize the model after construction.
|
||||||
initialize() {}
|
initialize () {}
|
||||||
|
|
||||||
// Validate the defined properties.
|
// Validate the defined properties.
|
||||||
//
|
//
|
||||||
// Returns the error if any.
|
// Returns the error if any.
|
||||||
validate(properties) {}
|
validate (properties) {}
|
||||||
|
|
||||||
// Get a property.
|
// Get a property.
|
||||||
get(name, def) {
|
get (name, def) {
|
||||||
let value = this.properties[name];
|
const value = this.properties[name]
|
||||||
return value !== undefined ? value : def;
|
return value !== undefined ? value : def
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether a property exists.
|
// Check whether a property exists.
|
||||||
has(name) {
|
has (name) {
|
||||||
return (this.properties[name] !== undefined);
|
return (this.properties[name] !== undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set properties.
|
// Set properties.
|
||||||
set(properties, value) {
|
set (properties, value) {
|
||||||
// This method can also be used with two arguments to set a single
|
// This method can also be used with two arguments to set a single
|
||||||
// property.
|
// property.
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
properties = { [properties]: value };
|
properties = { [properties]: value }
|
||||||
}
|
}
|
||||||
|
|
||||||
let previous = {};
|
const previous = {}
|
||||||
|
|
||||||
forEach(properties, (value, name) => {
|
forEach(properties, (value, name) => {
|
||||||
let prev = this.properties[name];
|
const prev = this.properties[name]
|
||||||
|
|
||||||
if (value !== prev) {
|
if (value !== prev) {
|
||||||
previous[name] = prev;
|
previous[name] = prev
|
||||||
|
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
delete this.properties[name];
|
delete this.properties[name]
|
||||||
} else {
|
} else {
|
||||||
this.properties[name] = value;
|
this.properties[name] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
if (!isEmpty(previous)) {
|
if (!isEmpty(previous)) {
|
||||||
this.emit('change', previous);
|
this.emit('change', previous)
|
||||||
|
|
||||||
forEach(previous, (value, name) => {
|
forEach(previous, (value, name) => {
|
||||||
this.emit('change:' + name, value);
|
this.emit('change:' + name, value)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
163
src/utils.js
163
src/utils.js
@ -1,39 +1,39 @@
|
|||||||
import base64url from 'base64url';
|
import base64url from 'base64url'
|
||||||
import forEach from 'lodash.foreach';
|
import forEach from 'lodash.foreach'
|
||||||
import has from 'lodash.has';
|
import has from 'lodash.has'
|
||||||
import humanFormat from 'human-format';
|
import humanFormat from 'human-format'
|
||||||
import isArray from 'lodash.isarray';
|
import isArray from 'lodash.isarray'
|
||||||
import multiKeyHash from 'multikey-hash';
|
import multiKeyHashInt from 'multikey-hash'
|
||||||
import xml2js from 'xml2js';
|
import xml2js from 'xml2js'
|
||||||
import {promisify, method} from 'bluebird';
|
import {promisify, method} from 'bluebird'
|
||||||
import {randomBytes} from 'crypto';
|
import {randomBytes} from 'crypto'
|
||||||
|
|
||||||
randomBytes = promisify(randomBytes);
|
randomBytes = promisify(randomBytes)
|
||||||
|
|
||||||
//====================================================================
|
/* eslint no-lone-blocks: 0 */
|
||||||
|
|
||||||
|
// ===================================================================
|
||||||
|
|
||||||
// Ensure the value is an array, wrap it if necessary.
|
// Ensure the value is an array, wrap it if necessary.
|
||||||
let ensureArray = (value) => {
|
export const ensureArray = (value) => {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return [];
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return isArray(value) ? value : [value];
|
return isArray(value) ? value : [value]
|
||||||
};
|
}
|
||||||
export {ensureArray};
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
// Generate a secure random Base64 string.
|
// Generate a secure random Base64 string.
|
||||||
let generateToken = (n = 32) => randomBytes(n).then(base64url);
|
export const generateToken = (n = 32) => randomBytes(n).then(base64url)
|
||||||
export {generateToken};
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
let formatXml;
|
export let formatXml
|
||||||
{
|
{
|
||||||
let builder = new xml2js.Builder({
|
const builder = new xml2js.Builder({
|
||||||
xmldec: {
|
xmldec: {
|
||||||
// Do not include an XML header.
|
// Do not include an XML header.
|
||||||
//
|
//
|
||||||
// This is not how this setting should be set but due to the
|
// This is not how this setting should be set but due to the
|
||||||
@ -41,110 +41,99 @@ let formatXml;
|
|||||||
//
|
//
|
||||||
// TODO: Find a better alternative.
|
// TODO: Find a better alternative.
|
||||||
headless: true
|
headless: true
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
formatXml = (...args) => builder.buildObject(...args);
|
formatXml = (...args) => builder.buildObject(...args)
|
||||||
}
|
}
|
||||||
export {formatXml};
|
|
||||||
|
|
||||||
let parseXml;
|
export let parseXml
|
||||||
{
|
{
|
||||||
let opts = {
|
const opts = {
|
||||||
mergeAttrs: true,
|
mergeAttrs: true,
|
||||||
explicitArray: false,
|
explicitArray: false
|
||||||
};
|
}
|
||||||
|
|
||||||
parseXml = (xml) => {
|
parseXml = (xml) => {
|
||||||
let result;
|
let result
|
||||||
|
|
||||||
// xml2js.parseString() use a callback for synchronous code.
|
// xml2js.parseString() use a callback for synchronous code.
|
||||||
xml2js.parseString(xml, opts, (error, result_) => {
|
xml2js.parseString(xml, opts, (error, result_) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
throw error;
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
result = result_;
|
result = result_
|
||||||
});
|
})
|
||||||
|
|
||||||
return result;
|
return result
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
export {parseXml};
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
function parseSize(size) {
|
export function parseSize (size) {
|
||||||
let bytes = humanFormat.parse.raw(size, { scale: 'binary' });
|
let bytes = humanFormat.parse.raw(size, { scale: 'binary' })
|
||||||
if (bytes.unit && bytes.unit !== 'B') {
|
if (bytes.unit && bytes.unit !== 'B') {
|
||||||
bytes = humanFormat.parse.raw(size);
|
bytes = humanFormat.parse.raw(size)
|
||||||
|
|
||||||
if (bytes.unit && bytes.unit !== 'B') {
|
if (bytes.unit && bytes.unit !== 'B') {
|
||||||
throw new Error('invalid size: ' + size);
|
throw new Error('invalid size: ' + size)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Math.floor(bytes.value * bytes.factor);
|
return Math.floor(bytes.value * bytes.factor)
|
||||||
}
|
}
|
||||||
|
|
||||||
export {parseSize};
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
//--------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Special value which can be returned to stop an iteration in map()
|
// Special value which can be returned to stop an iteration in map()
|
||||||
// and mapInPlace().
|
// and mapInPlace().
|
||||||
let done = {};
|
export const done = {}
|
||||||
export {done};
|
|
||||||
|
|
||||||
// Similar to `lodash.map()` for array and `lodash.mapValues()` for
|
// Similar to `lodash.map()` for array and `lodash.mapValues()` for
|
||||||
// objects.
|
// objects.
|
||||||
//
|
//
|
||||||
// Note: can be interrupted by returning the special value `done`
|
// Note: can be interrupted by returning the special value `done`
|
||||||
// provided as the forth argument.
|
// provided as the forth argument.
|
||||||
function map(col, iterator, thisArg = this) {
|
export function map (col, iterator, thisArg = this) {
|
||||||
let result = has(col, 'length') ? [] : {};
|
const result = has(col, 'length') ? [] : {}
|
||||||
forEach(col, (item, i) => {
|
forEach(col, (item, i) => {
|
||||||
let value = iterator.call(thisArg, item, i, done);
|
const value = iterator.call(thisArg, item, i, done)
|
||||||
if (value === done) {
|
if (value === done) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
result[i] = value;
|
result[i] = value
|
||||||
});
|
})
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
export {map};
|
|
||||||
|
|
||||||
// Create a hash from multiple values.
|
// Create a hash from multiple values.
|
||||||
multiKeyHash = (function (multiKeyHash) {
|
export const multiKeyHash = method((...args) => {
|
||||||
return method((...args) => {
|
const hash = multiKeyHashInt(...args)
|
||||||
let hash = multiKeyHash(...args);
|
|
||||||
|
|
||||||
let buf = new Buffer(4);
|
const buf = new Buffer(4)
|
||||||
buf.writeUInt32LE(hash, 0);
|
buf.writeUInt32LE(hash, 0)
|
||||||
|
|
||||||
return base64url(buf);
|
return base64url(buf)
|
||||||
});
|
})
|
||||||
})(multiKeyHash);
|
|
||||||
export {multiKeyHash};
|
|
||||||
|
|
||||||
// Similar to `map()` but change the current collection.
|
// Similar to `map()` but change the current collection.
|
||||||
//
|
//
|
||||||
// Note: can be interrupted by returning the special value `done`
|
// Note: can be interrupted by returning the special value `done`
|
||||||
// provided as the forth argument.
|
// provided as the forth argument.
|
||||||
function mapInPlace(col, iterator, thisArg = this) {
|
export function mapInPlace (col, iterator, thisArg = this) {
|
||||||
forEach(col, (item, i) => {
|
forEach(col, (item, i) => {
|
||||||
let value = iterator.call(thisArg, item, i, done);
|
const value = iterator.call(thisArg, item, i, done)
|
||||||
if (value === done) {
|
if (value === done) {
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
col[i] = value;
|
col[i] = value
|
||||||
});
|
})
|
||||||
|
|
||||||
return col;
|
return col
|
||||||
}
|
}
|
||||||
export {mapInPlace};
|
|
||||||
|
|
||||||
// Wrap a value in a function.
|
// Wrap a value in a function.
|
||||||
let wrap = (value) => () => value;
|
export const wrap = (value) => () => value
|
||||||
export {wrap};
|
|
||||||
|
@ -1,51 +1,51 @@
|
|||||||
import assign from 'lodash.assign';
|
import assign from 'lodash.assign'
|
||||||
import debug from 'debug';
|
import debug from 'debug'
|
||||||
import WebSocket from 'ws';
|
import WebSocket from 'ws'
|
||||||
|
|
||||||
debug = debug('xo:wsProxy');
|
debug = debug('xo:wsProxy')
|
||||||
|
|
||||||
let defaults = {
|
const defaults = {
|
||||||
// Automatically close the client connection when the remote close.
|
// Automatically close the client connection when the remote close.
|
||||||
autoClose: true,
|
autoClose: true,
|
||||||
|
|
||||||
// Reject secure connections to unauthorized remotes (bad CA).
|
// Reject secure connections to unauthorized remotes (bad CA).
|
||||||
rejectUnauthorized: false,
|
rejectUnauthorized: false
|
||||||
};
|
}
|
||||||
|
|
||||||
// Proxy a WebSocket `client` to a remote server which has `url` as
|
// Proxy a WebSocket `client` to a remote server which has `url` as
|
||||||
// address.
|
// address.
|
||||||
export default function wsProxy(client, url, opts) {
|
export default function wsProxy (client, url, opts) {
|
||||||
opts = assign({}, defaults, opts);
|
opts = assign({}, defaults, opts)
|
||||||
|
|
||||||
let remote = new WebSocket(url, {
|
const remote = new WebSocket(url, {
|
||||||
protocol: opts.protocol || client.protocol,
|
protocol: opts.protocol || client.protocol,
|
||||||
rejectUnauthorized: opts.rejectUnauthorized,
|
rejectUnauthorized: opts.rejectUnauthorized
|
||||||
}).once('open', function () {
|
}).once('open', function () {
|
||||||
debug('connected to', url);
|
debug('connected to', url)
|
||||||
}).once('close', function () {
|
}).once('close', function () {
|
||||||
debug('remote closed');
|
debug('remote closed')
|
||||||
|
|
||||||
if (opts.autoClose) {
|
if (opts.autoClose) {
|
||||||
client.close();
|
client.close()
|
||||||
}
|
}
|
||||||
}).once('error', function (error) {
|
}).once('error', function (error) {
|
||||||
debug('remote error', error);
|
debug('remote error', error)
|
||||||
}).on('message', function (message) {
|
}).on('message', function (message) {
|
||||||
client.send(message, function (error) {
|
client.send(message, function (error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
debug('client send error', error);
|
debug('client send error', error)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
client.once('close', function () {
|
client.once('close', function () {
|
||||||
debug('client closed');
|
debug('client closed')
|
||||||
remote.close();
|
remote.close()
|
||||||
}).on('message', function (message) {
|
}).on('message', function (message) {
|
||||||
remote.send(message, function (error) {
|
remote.send(message, function (error) {
|
||||||
if (error) {
|
if (error) {
|
||||||
debug('remote send error', error);
|
debug('remote send error', error)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user