Add support to run an ansible playbook

- Add an API to upload a playbook
- Add an API to run an ansible playbook on a rackHD node
- fix new eslint errors
- Add unit tests

Change-Id: Ibe623c228c6ac13cab0f2726e267878bec1bdb6e
Implements: blueprint shovel-deployment-capability
This commit is contained in:
Andre Keedy 2016-03-03 11:59:05 -05:00 committed by Andre keedy
parent 9449f7a443
commit 50ec0f2000
12 changed files with 381 additions and 73 deletions

View File

@ -589,6 +589,40 @@
} }
} }
} }
},
"/run/ansible-playbook/{identifier}": {
"post": {
"x-swagger-router-controller": "Shovel",
"tags": [ "Nodes Provisionning" ],
"operationId": "runAnsible",
"summary": "run the uploaded ansible playbook",
"parameters": [
{
"name": "identifier",
"in": "path",
"description": "rackHD node ID",
"required": true,
"type": "string"
},
{
"name": "Config",
"in": "body",
"description": "OS Configuration",
"required": true,
"schema": {
"$ref": "#/definitions/run-ansible"
}
}
],
"responses": {
"200": {
"description": "Not Implemented"
},
"default": {
"description": "unexpected error"
}
}
}
} }
}, },
"definitions": { "definitions": {
@ -753,7 +787,7 @@
"properties": { "properties": {
"name": { "name": {
"type": "string", "type": "string",
"example":"Graph.InstallCentOS" "example": "Graph.InstallCentOS"
}, },
"options": { "options": {
"type": "object", "type": "object",
@ -823,6 +857,25 @@
} }
} }
} }
},
"run-ansible": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string",
"example": "runExample"
},
"vars": {
"type": "object"
},
"playbookPath": {
"type": "string",
"example": "files/extract/main.yml"
}
}
} }
} }
} }

View File

@ -278,20 +278,20 @@ module.exports.registerpost = function registerpost(req, res) {
userEntry = req.body; userEntry = req.body;
if (userEntry.driver === 'pxe_ipmitool') { if (userEntry.driver === 'pxe_ipmitool') {
info = { info = {
'ipmi_address': userEntry.ipmihost, ipmi_address: userEntry.ipmihost,
'ipmi_username': userEntry.ipmiuser, ipmi_username: userEntry.ipmiuser,
'ipmi_password': userEntry.ipmipass, ipmi_password: userEntry.ipmipass,
'deploy_kernel': userEntry.kernel, deploy_kernel: userEntry.kernel,
'deploy_ramdisk': userEntry.ramdisk deploy_ramdisk: userEntry.ramdisk
}; };
} else if (userEntry.driver === 'pxe_ssh') { } else if (userEntry.driver === 'pxe_ssh') {
info = { info = {
'ssh_address': userEntry.sshhost, ssh_address: userEntry.sshhost,
'ssh_username': userEntry.sshuser, ssh_username: userEntry.sshuser,
'ssh_password': userEntry.sshpass, ssh_password: userEntry.sshpass,
'ssh_port': userEntry.sshport, ssh_port: userEntry.sshport,
'deploy_kernel': userEntry.kernel, deploy_kernel: userEntry.kernel,
'deploy_ramdisk': userEntry.ramdisk deploy_ramdisk: userEntry.ramdisk
}; };
} else { } else {
info = {}; info = {};
@ -299,11 +299,11 @@ module.exports.registerpost = function registerpost(req, res) {
/* Fill in the extra meta data with some failover and event data */ /* Fill in the extra meta data with some failover and event data */
extra = { extra = {
'nodeid': userEntry.uuid, nodeid: userEntry.uuid,
'name': userEntry.name, name: userEntry.name,
'lsevents': { 'time': 0 }, lsevents: { time: 0 },
'eventcnt': 0, eventcnt: 0,
'timer': {} timer: {}
}; };
if (typeof userEntry.failovernode !== 'undefined') { if (typeof userEntry.failovernode !== 'undefined') {
extra.failover = userEntry.failovernode; extra.failover = userEntry.failovernode;
@ -315,7 +315,7 @@ module.exports.registerpost = function registerpost(req, res) {
localGb = 0.0; localGb = 0.0;
return monorail.request_node_get(userEntry.uuid). return monorail.request_node_get(userEntry.uuid).
then(function (result) { then(function (result) {
if (!JSON.parse(result).name) { if (!JSON.parse(result).hasOwnProperty('name')) {
var error = { error_message: { message: 'failed to find required node in RackHD' } }; var error = { error_message: { message: 'failed to find required node in RackHD' } };
logger.error(error); logger.error(error);
throw error; throw error;
@ -349,16 +349,16 @@ module.exports.registerpost = function registerpost(req, res) {
throw error; throw error;
} }
propreties = { propreties = {
'cpus': dmiData.cpus, cpus: dmiData.cpus,
'memory_mb': dmiData.memory, memory_mb: dmiData.memory,
'local_gb': localGb local_gb: localGb
}; };
node = { node = {
'name': userEntry.uuid, name: userEntry.uuid,
'driver': userEntry.driver, driver: userEntry.driver,
'driver_info': info, driver_info: info,
'properties': propreties, properties: propreties,
'extra': extra extra: extra
}; };
return keystone.authenticatePassword(ironicConfig.os_tenant_name, ironicConfig.os_username, return keystone.authenticatePassword(ironicConfig.os_tenant_name, ironicConfig.os_username,
ironicConfig.os_password); ironicConfig.os_password);
@ -373,10 +373,13 @@ module.exports.registerpost = function registerpost(req, res) {
throw JSON.parse(ret); throw JSON.parse(ret);
} }
ironicNode = JSON.parse(ret); ironicNode = JSON.parse(ret);
port = { 'address': userEntry.port, 'node_uuid': ironicNode.uuid }; port = { address: userEntry.port, node_uuid: ironicNode.uuid };
return ironic.createPort(ironicToken, JSON.stringify(port)); return ironic.create_port(ironicToken, JSON.stringify(port));
}). }).
then(function (createPort) { then(function (createPort) {
if (createPort && JSON.parse(createPort).error_message) {
throw JSON.parse(createPort);
}
logger.info('\r\nCreate port:\r\n' + JSON.stringify(createPort)); logger.info('\r\nCreate port:\r\n' + JSON.stringify(createPort));
return ironic.set_power_state(ironicToken, ironicNode.uuid, 'on'); return ironic.set_power_state(ironicToken, ironicNode.uuid, 'on');
}). }).
@ -392,7 +395,7 @@ module.exports.registerpost = function registerpost(req, res) {
timer.stop = false; timer.stop = false;
timer.timeInterval = 15000; timer.timeInterval = 15000;
timer.isDone = true; timer.isDone = true;
var data = [{ 'path': '/extra/timer', 'value': timer, 'op': 'replace' }]; var data = [{ path: '/extra/timer', value: timer, op: 'replace' }];
return ironic.patch_node(ironicToken, ironicNode.uuid, JSON.stringify(data)); return ironic.patch_node(ironicToken, ironicNode.uuid, JSON.stringify(data));
}). }).
then(function (result) { then(function (result) {
@ -637,8 +640,8 @@ module.exports.deployOS = function deployOS(req, res) {
res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Type', 'application/json');
return monorail.runWorkFlow(req.swagger.params.identifier.value, return monorail.runWorkFlow(req.swagger.params.identifier.value,
req.body.name,req.body) req.body.name,req.body)
.then(function(data) { .then(function(result) {
res.end(data); res.end(result);
}) })
.catch(function(err) { .catch(function(err) {
res.end(JSON.stringify(err)); res.end(JSON.stringify(err));
@ -654,12 +657,79 @@ module.exports.workflowStatus = function workflowStatus(req,res) {
return monorail.getWorkFlowActive(req.swagger.params.identifier.value) return monorail.getWorkFlowActive(req.swagger.params.identifier.value)
.then(function(data) { .then(function(data) {
if (data) { if (data) {
res.end(JSON.stringify({'jobStatus':'Running'})); res.end(JSON.stringify({jobStatus:'Running'}));
} else { } else {
res.end(JSON.stringify({'jobStatus':'Currently there is no job running on this node'})); res.end(JSON.stringify({jobStatus:'Currently there is no job running on this node'}));
} }
}) })
.catch(function(err) { .catch(function(err) {
res.end(JSON.stringify(err)); res.end(JSON.stringify(err));
}); });
}; };
/*
* @api {put} /api/1.1/uploadFiles/filename / PUT /
* @apiDescription uploaded ansible playbook in tar form and extract it
*/
//Code for tar file uploads
// module.exports.uploadFiles = function uploadFiles(req, res) {
// 'use strict';
// res.setHeader('content-type', 'text/plain');
// var tar = require('tar');
// var extractor = tar.Extract({path: 'files/extract'})
// .on('error', function(err) {
// logger.error(err);
// res.status(500);
// res.end('error');
// })
// .on('end', function() {
// res.status(202);
// res.end('success');
// });
// var stream = require('stream');
// var bufferStream = new stream.PassThrough();
// bufferStream.end(new Buffer(req.swagger.params.playbook.value.buffer));
// bufferStream.pipe(extractor);
// };
/*
* @api {post} /api/1.1/runAnsible/{identifier} / POST /
* @apiDescription run uploaded ansible playbook in tar form and extract it
*/
module.exports.runAnsible = function runAnsible(req, res) {
'use strict';
res.setHeader('Content-Type', 'application/json');
var ansibleTask = {
friendlyName: req.body.name,
injectableName: 'Task.Ansible.' + req.body.name,
implementsTask: 'Task.Base.Ansible',
options: {
playbook: req.body.playbookPath,
vars : req.body.vars
},
properties: { }
};
var ansibleWorkflow = {
friendlyName: 'Graph ' + req.body.name,
injectableName: 'Graph.Ansible.' + req.body.name,
tasks : [
{
label: 'ansible-job',
taskName: 'Task.Ansible.' + req.body.name
}
]
};
return monorail.createTask(ansibleTask)
.then(function() {
return monorail.createWorkflow(ansibleWorkflow);
})
.then(function() {
return monorail.runWorkFlow(req.swagger.params.identifier.value,
'Graph.Ansible.' + req.body.name,null);
})
.then(function(result) {
res.end(result);
})
.catch(function(err) {
res.end(JSON.stringify(err));
});
};

View File

@ -1,7 +1,7 @@
/* global process */ /* global process */
'use strict'; 'use strict';
var app = require('connect')(); var app = require('express')();
var http = require('http'); var http = require('http');
var swaggerTools = require('swagger-tools'); var swaggerTools = require('swagger-tools');
var config = require('./config.json'); var config = require('./config.json');

View File

@ -50,7 +50,7 @@ var HttpClient = {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-type': 'application/json', 'Content-type': 'application/json',
'Accept': 'application/json', Accept: 'application/json',
'Content-Length': Buffer.byteLength(msg.data), 'Content-Length': Buffer.byteLength(msg.data),
'User-Agent': 'shovel-client' 'User-Agent': 'shovel-client'
} }
@ -140,16 +140,16 @@ var HttpClient = {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-type': 'application/json', 'Content-type': 'application/json',
'Accept': 'application/json', Accept: 'application/json',
'Content-Length': Buffer.byteLength(msg.data), 'Content-Length': Buffer.byteLength(msg.data),
'User-Agent': 'shovel-client' 'User-Agent': 'shovel-client'
} }
}; };
/*Update the request header with special fields*/
if (Buffer.byteLength(msg.token)) { if (Buffer.byteLength(msg.token)) {
options.headers['X-Auth-Token'] = msg.token; options.headers['X-Auth-Token'] = msg.token;
} }
if (Buffer.byteLength(JSON.stringify(msg.api))) { if (Buffer.byteLength(JSON.stringify(msg.api))) {
options.headers[msg.api.name] = msg.api.version; options.headers[msg.api.name] = msg.api.version;
} }
@ -159,8 +159,8 @@ var HttpClient = {
response.on('data', function (chunk) { response.on('data', function (chunk) {
body += chunk; body += chunk;
}); });
response.on('error', function (err) { response.on('error', function (e) {
var errorMessage = { errorMessage: { hostname: msg.host, message: err } }; var errorMessage = { errorMessage: { hostname: msg.host, message: e } };
output(errorMessage); output(errorMessage);
}); });
response.on('end', function () { response.on('end', function () {
@ -174,7 +174,9 @@ var HttpClient = {
output(errorMessage); output(errorMessage);
}); });
request.write(msg.data); if (Buffer.byteLength(msg.data)) {
request.write(msg.data);
}
request.end(); request.end();
}, },
Patch: function (msg, output) { Patch: function (msg, output) {
@ -187,7 +189,7 @@ var HttpClient = {
method: 'PATCH', method: 'PATCH',
headers: { headers: {
'Content-type': 'application/json', 'Content-type': 'application/json',
'Accept': 'application/json', Accept: 'application/json',
'Content-Length': Buffer.byteLength(msg.data), 'Content-Length': Buffer.byteLength(msg.data),
'User-Agent': 'shovel-client' 'User-Agent': 'shovel-client'
} }

View File

@ -149,7 +149,9 @@ var MonorailWrapper = {
runWorkFlow: function runWorkFlow(hwaddr,graphName,content) { runWorkFlow: function runWorkFlow(hwaddr,graphName,content) {
'use strict'; 'use strict';
request.path = pfx + '/nodes/' + hwaddr + '/workflows/?name=' + graphName; request.path = pfx + '/nodes/' + hwaddr + '/workflows/?name=' + graphName;
request.data = JSON.stringify(content); if (content !== null) {
request.data = JSON.stringify(content);
}
return client.PostAsync(request); return client.PostAsync(request);
}, },
getWorkFlowActive: function getWorkFlowActive(hwaddr) { getWorkFlowActive: function getWorkFlowActive(hwaddr) {
@ -161,6 +163,18 @@ var MonorailWrapper = {
'use strict'; 'use strict';
request.path = pfx + '/nodes/' + hwaddr + '/workflows/active'; request.path = pfx + '/nodes/' + hwaddr + '/workflows/active';
return client.DeleteAsync(request); return client.DeleteAsync(request);
},
createTask: function createTask(content) {
'use strict';
request.path = pfx + '/workflows/tasks/';
request.data = JSON.stringify(content);
return client.PutAsync(request);
},
createWorkflow: function createWorkflow(content) {
'use strict';
request.path = pfx + '/workflows';
request.data = JSON.stringify(content);
return client.PutAsync(request);
} }
}; };
module.exports = Object.create(MonorailWrapper); module.exports = Object.create(MonorailWrapper);

View File

@ -13,8 +13,8 @@ var request = {
token: '', token: '',
data: '', data: '',
api: { api: {
'name': 'X-OpenStack-Ironic-API-Version', name: 'X-OpenStack-Ironic-API-Version',
'version': '1.6' version: '1.6'
} }
}; };

View File

@ -33,11 +33,11 @@ var KeystoneAuthentication = {
} }
request.data = JSON.stringify( request.data = JSON.stringify(
{ {
'auth': { auth: {
'tenantName': tenantName, tenantName: tenantName,
'passwordCredentials': { passwordCredentials: {
'username': username, username: username,
'password': decrypted password: decrypted
} }
} }
@ -49,10 +49,10 @@ var KeystoneAuthentication = {
'use strict'; 'use strict';
request.data = JSON.stringify( request.data = JSON.stringify(
{ {
'auth': { auth: {
'tenantName': tenantName, tenantName: tenantName,
'token': { token: {
'id': token id: token
} }
} }
}); });

View File

@ -135,9 +135,9 @@ function Poller(timeInterval) {
nodeData.extra.events = lastEvent; nodeData.extra.events = lastEvent;
var data = [ var data = [
{ {
'path': '/extra', path: '/extra',
'value': nodeData.extra, value: nodeData.extra,
'op': 'replace' op: 'replace'
}]; }];
return data; return data;
}) })

View File

@ -9,7 +9,6 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"bluebird": "3.1.1", "bluebird": "3.1.1",
"connect": "^3.2.0",
"swagger-tools": "0.8.*", "swagger-tools": "0.8.*",
"should": "~7.0.1", "should": "~7.0.1",
"mocha": "^2.1.0", "mocha": "^2.1.0",
@ -23,7 +22,9 @@
"istanbul": "0.4.1", "istanbul": "0.4.1",
"nock": "3.6.0", "nock": "3.6.0",
"eslint-config-openstack": "~1.2.3", "eslint-config-openstack": "~1.2.3",
"eslint": "~1.10.3" "eslint": "~1.10.3",
"express": "~4.13.4",
"tar": "~2.2.1"
}, },
"scripts": { "scripts": {
"start": "start shovel", "start": "start shovel",

View File

@ -185,5 +185,19 @@ describe('****Monorail Lib****',function(){
done(); done();
}); });
}); });
it('monorail.createWorkflow return data from monorail', function (done) {
return monorail.createWorkflow({})
.then(function (result) {
result.should.have.property('data');
done();
});
});
it('monorail.createTask return data from monorail', function (done) {
return monorail.createTask({})
.then(function (result) {
result.should.have.property('data');
done();
});
});
}); });
}); });

View File

@ -56,6 +56,8 @@ describe('****SHOVEL API Interface****', function () {
sinon.stub(monorail, 'get_catalog_data_by_source').returns(Promise.resolve(JSON.stringify(catalogSource[0]))); sinon.stub(monorail, 'get_catalog_data_by_source').returns(Promise.resolve(JSON.stringify(catalogSource[0])));
sinon.stub(monorail, 'runWorkFlow').returns(Promise.resolve('{"definition":{}}')); sinon.stub(monorail, 'runWorkFlow').returns(Promise.resolve('{"definition":{}}'));
getWorkflow = sinon.stub(monorail,'getWorkFlowActive'); getWorkflow = sinon.stub(monorail,'getWorkFlowActive');
sinon.stub(monorail, 'createTask').returns(Promise.resolve());
sinon.stub(monorail, 'createWorkflow').returns(Promise.resolve());
//glance //glance
sinon.stub(glance, 'get_images').returns(Promise.resolve(JSON.stringify(glanceImages))); sinon.stub(glance, 'get_images').returns(Promise.resolve(JSON.stringify(glanceImages)));
//keystone //keystone
@ -83,6 +85,8 @@ describe('****SHOVEL API Interface****', function () {
monorail['get_catalog_data_by_source'].restore(); monorail['get_catalog_data_by_source'].restore();
monorail['runWorkFlow'].restore(); monorail['runWorkFlow'].restore();
monorail['getWorkFlowActive'].restore(); monorail['getWorkFlowActive'].restore();
monorail['createTask'].restore();
monorail['createWorkflow'].restore();
//ironic //ironic
ironic['patch_node'].restore(); ironic['patch_node'].restore();
ironic['get_node_list'].restore(); ironic['get_node_list'].restore();
@ -334,10 +338,8 @@ describe('****SHOVEL API Interface****', function () {
.send({"name": "Graph.InstallCentOS","options": { "defaults": {"obmServiceName": "ipmi-obm-service"}}}) .send({"name": "Graph.InstallCentOS","options": { "defaults": {"obmServiceName": "ipmi-obm-service"}}})
.end(function (err, res) { .end(function (err, res) {
if (err) { if (err) {
console.log('hey yo');
throw err; throw err;
} }
console.log('hello' + res.text)
JSON.parse(res.text).should.have.property('definition'); JSON.parse(res.text).should.have.property('definition');
done(); done();
}); });
@ -350,11 +352,22 @@ describe('****SHOVEL API Interface****', function () {
if (err) { if (err) {
throw err; throw err;
} }
console.log(res.text);
JSON.parse(res.text).should.have.property('jobStatus'); JSON.parse(res.text).should.have.property('jobStatus');
done(); done();
}); });
}); });
it('/api/1.1/run/ansible-playbook/{id} should return property definition', function (done) {
request(url)
.post('/api/1.1/run/ansible-playbook/123')
.send({"name": "Graph.Example","options": {}})
.end(function (err, res) {
if (err) {
throw err;
}
JSON.parse(res.text).should.have.property('definition');
done();
});
});
it('/api/1.1/worflow-status/{identifier} should return property jobStatus even if no job is running', function (done) { it('/api/1.1/worflow-status/{identifier} should return property jobStatus even if no job is running', function (done) {
getWorkflow.returns(Promise.resolve()); getWorkflow.returns(Promise.resolve());
request(url) request(url)
@ -379,10 +392,12 @@ describe('****SHOVEL API Interface****', function () {
var output = ({ error: 'error_message' }); var output = ({ error: 'error_message' });
sinon.stub(client, 'GetAsync').returns(Promise.reject(output)); sinon.stub(client, 'GetAsync').returns(Promise.reject(output));
sinon.stub(client, 'PostAsync').returns(Promise.reject(output)); sinon.stub(client, 'PostAsync').returns(Promise.reject(output));
sinon.stub(client, 'PutAsync').returns(Promise.reject(output));
}); });
after('teardown mocks', function () { after('teardown mocks', function () {
client['GetAsync'].restore(); client['GetAsync'].restore();
client['PostAsync'].restore(); client['PostAsync'].restore();
client['PutAsync'].restore();
}); });
it('/api/1.1/nodes/identifier should return error message', function (done) { it('/api/1.1/nodes/identifier should return error message', function (done) {
@ -558,34 +573,92 @@ describe('****SHOVEL API Interface****', function () {
done(); done();
}); });
}); });
it('api/1.1/run/ansible-playbook/{id} should return error message', function (done) {
request(url)
.post('/api/1.1/run/ansible-playbook/123')
.send({name: 'runExample',vars: {},
playbookPath: 'main.yml'
})
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
JSON.parse(res.text).should.have.property('error');
done();
});
});
}); });
describe('Shovel api unit test for register', function () { describe('Shovel api unit test for register', function () {
var error_message = '{"error_message": "{\\"debuginfo\\": null, \\"faultcode\\": \\"Client\\", \\"faultstring\\": \\"A node with name 5668b42d8bee16a10989e4e4 already exists.\\"}"}'; var error_message = '{"error_message": "{\\"debuginfo\\": null, \\"faultcode\\": \\"Client\\", \\"faultstring\\": \\"some error\\"}"}';
var body = { "id": identifier, "driver": "string", "ipmihost": "string", "ipmiusername": "string", "ipmipasswd": "string" }; var body = { "id": identifier, "driver": "string", "ipmihost": "string", "ipmiusername": "string", "ipmipasswd": "string" };
var getNode, diskSize, memoryCpu, ironicNodeCreate,
ironicCreatePort, ironicPowerState, ironicPatch;
beforeEach('set up mocks', function () { beforeEach('set up mocks', function () {
//monorail //monorail
getNode = sinon.stub(monorail, 'request_node_get');
diskSize = sinon.stub(monorail, 'nodeDiskSize');
memoryCpu = sinon.stub(monorail, 'getNodeMemoryCpu');
monorailWhiteList = sinon.stub(monorail,'request_whitelist_set');
//keystone //keystone
sinon.stub(keystone, 'authenticatePassword').returns(Promise.resolve(JSON.stringify(keyToken))); sinon.stub(keystone, 'authenticatePassword').returns(Promise.resolve(JSON.stringify(keyToken)));
//ironic //ironic
sinon.stub(ironic, 'create_node').returns(Promise.resolve(error_message)); ironicNodeCreate = sinon.stub(ironic, 'create_node');
ironicCreatePort = sinon.stub(ironic,'create_port');
ironicPowerState = sinon.stub(ironic,'set_power_state');
ironicPatch = sinon.stub(ironic, 'patch_node');
}); });
afterEach('teardown mocks', function () { afterEach('teardown mocks', function () {
//monorail //monorail
monorail['nodeDiskSize'].restore(); monorail['nodeDiskSize'].restore();
monorail['getNodeMemoryCpu'].restore(); monorail['getNodeMemoryCpu'].restore();
monorail['request_node_get'].restore(); monorail['request_node_get'].restore();
monorail['request_whitelist_set'].restore();
//keystone //keystone
keystone['authenticatePassword'].restore(); keystone['authenticatePassword'].restore();
//ironic //ironic
ironic['create_node'].restore(); ironic['create_node'].restore();
ironic['create_port'].restore();
ironic['set_power_state'].restore();
ironic['patch_node'].restore();
});
it('response in register should have property error_message when node returns empty ', function (done) {
getNode.returns(Promise.resolve('{}'));
request(url)
.post('/api/1.1/register')
.send(body)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
JSON.parse(res.text).should.have.property('error_message');
done();
});
});
it('response in register should have property error_message when diskSize has an exception ', function (done) {
var output = {error_message: { message: 'failed to get compute node Disk Size' }};
getNode.returns(Promise.resolve(JSON.stringify(rackhdNode[0])));
diskSize.returns(Promise.reject(output));
request(url)
.post('/api/1.1/register')
.send(body)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
JSON.parse(res.text).should.have.property('error_message');
done();
});
}); });
it('response in register should have property error_message when any of node info equal to 0 ', function (done) { it('response in register should have property error_message when any of node info equal to 0 ', function (done) {
sinon.stub(monorail, 'request_node_get').returns(Promise.resolve(JSON.stringify(rackhdNode[0]))); getNode.returns(Promise.resolve(JSON.stringify(rackhdNode[0])));
sinon.stub(monorail, 'nodeDiskSize').returns(Promise.resolve(0)); diskSize.returns(Promise.resolve(0));
sinon.stub(monorail, 'getNodeMemoryCpu').returns(Promise.resolve({ cpus: 0, memory: 0 })); memoryCpu.returns(Promise.resolve({ cpus: 0, memory: 0 }));
request(url) request(url)
.post('/api/1.1/register') .post('/api/1.1/register')
@ -601,10 +674,10 @@ describe('****SHOVEL API Interface****', function () {
}); });
}); });
it('response in register should have property error_message create node return error in ironic', function (done) { it('response in register should have property error_message create node return error in ironic', function (done) {
sinon.stub(monorail, 'request_node_get').returns(Promise.resolve(JSON.stringify(rackhdNode[0]))); getNode.returns(Promise.resolve(JSON.stringify(rackhdNode[0])));
sinon.stub(monorail, 'nodeDiskSize').returns(Promise.resolve(1)); diskSize.returns(Promise.resolve(1));
sinon.stub(monorail, 'getNodeMemoryCpu').returns(Promise.resolve({ cpus: 1, memory: 1 })); memoryCpu.returns(Promise.resolve({ cpus: 1, memory: 1 }));
ironicNodeCreate.returns(Promise.resolve(error_message));
request(url) request(url)
.post('/api/1.1/register') .post('/api/1.1/register')
.send(body) .send(body)
@ -618,5 +691,86 @@ describe('****SHOVEL API Interface****', function () {
done(); done();
}); });
}); });
it('response in register should have property error_message create port return error in ironic', function (done) {
getNode.returns(Promise.resolve(JSON.stringify(rackhdNode[0])));
diskSize.returns(Promise.resolve(1));
memoryCpu.returns(Promise.resolve({ cpus: 1, memory: 1 }));
ironicNodeCreate.returns(Promise.resolve(JSON.stringify(ironic_node_list[0])));
ironicCreatePort.returns(Promise.resolve(error_message));
request(url)
.post('/api/1.1/register')
.send(body)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
JSON.parse(res.text).should.have.property('error_message');
done();
});
});
it('response in register should have property error_message set power state return error in ironic', function (done) {
getNode.returns(Promise.resolve(JSON.stringify(rackhdNode[0])));
diskSize.returns(Promise.resolve(1));
memoryCpu.returns(Promise.resolve({ cpus: 1, memory: 1 }));
ironicNodeCreate.returns(Promise.resolve(JSON.stringify(ironic_node_list[0])));
ironicCreatePort.returns(Promise.resolve());
ironicPowerState.returns(Promise.resolve(error_message));
request(url)
.post('/api/1.1/register')
.send(body)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
JSON.parse(res.text).should.have.property('error_message');
done();
});
});
it('response in register should have property error_message ironic patch node return error in ironic', function (done) {
getNode.returns(Promise.resolve(JSON.stringify(rackhdNode[0])));
diskSize.returns(Promise.resolve(1));
memoryCpu.returns(Promise.resolve({ cpus: 1, memory: 1 }));
ironicNodeCreate.returns(Promise.resolve(JSON.stringify(ironic_node_list[0])));
ironicCreatePort.returns(Promise.resolve());
ironicPowerState.returns(Promise.resolve());
ironicPatch.returns(Promise.reject({error_message:'some error'}));
request(url)
.post('/api/1.1/register')
.send(body)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
JSON.parse(res.text).should.have.property('error_message');
done();
});
});
it('response in register should have property result on success', function (done) {
getNode.returns(Promise.resolve(JSON.stringify(rackhdNode[0])));
diskSize.returns(Promise.resolve(1));
memoryCpu.returns(Promise.resolve({ cpus: 1, memory: 1 }));
ironicNodeCreate.returns(Promise.resolve(JSON.stringify(ironic_node_list[0])));
ironicCreatePort.returns(Promise.resolve());
ironicPowerState.returns(Promise.resolve());
ironicPatch.returns(Promise.resolve());
request(url)
.post('/api/1.1/register')
.send(body)
.expect('Content-Type', /json/)
.expect(200)
.end(function (err, res) {
if (err) {
throw err;
}
JSON.parse(res.text).should.have.property('result');
done();
});
});
}); });
}); });

View File

@ -1,6 +1,6 @@
// Copyright 2015, EMC, Inc. // Copyright 2015, EMC, Inc.
var app = require('connect')(); var app = require('express')();
var http = require('http'); var http = require('http');
var swaggerTools = require('swagger-tools'); var swaggerTools = require('swagger-tools');
var sinon = require('sinon'); var sinon = require('sinon');