shovel/controllers/Shovel.js
Andre Keedy 9449f7a443 Enable OS Deployement using rackHD
- Allow user to deploy OS using rackHD graphs
- OS supproted are ESXI, CentOS, Ubuntu, RedHat
- Allow user to monitor the active workflow on certain node
- Add hooks in shovel to post/get/and delete node workflow in rackhd

Implements: blueprint shovel-deployment-capability
Change-Id: I9411be06de64f76496412e77ecfa6ebfdd9ce3cd
2016-02-19 13:45:45 -05:00

666 lines
21 KiB
JavaScript

// Copyright 2015, EMC, Inc.
/*eslint-env node*/
var monorail = require('./../lib/api/monorail/monorail');
var ironic = require('./../lib/api/openstack/ironic');
var config = require('./../config.json');
var glance = require('./../lib/api/openstack/glance');
var keystone = require('./../lib/api/openstack/keystone');
var logger = require('./../lib/services/logger').Logger('info');
var encryption = require('./../lib/services/encryption');
var jsonfile = require('jsonfile');
var _ = require('underscore');
var Promise = require('bluebird');
var ironicConfig = config.ironic;
var glanceConfig = config.glance;
/*
* @api {get} /api/1.1/info / GET /
* @apiDescription get shovel information
* @apiVersion 1.1.0
*/
module.exports.infoGet = function infoGet(req, res) {
'use strict';
var info = {
name: 'shovel',
description: 'rackHD-ironic agent',
appversion: config.shovel.appver,
apiversion: config.shovel.apiver
};
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(info));
};
/*
* @api {get} /api/1.1/ironic/drivers / GET /
* @apiDescription get ironic drivers
* @apiVersion 1.1.0
*/
module.exports.driversGet = function driversGet(req, res) {
'use strict';
return keystone.authenticatePassword(ironicConfig.os_tenant_name, ironicConfig.os_username,
ironicConfig.os_password).
then(function (token) {
token = JSON.parse(token).access.token.id;
return ironic.get_driver_list(token);
}).
then(function (result) {
res.setHeader('Content-Type', 'application/json');
res.end(result);
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {get} /api/1.1/ironic/nodes / GET /
* @apiDescription get ironic nodes
* @apiVersion 1.1.0
*/
module.exports.ironicnodesGet = function ironicnodesGet(req, res) {
'use strict';
return keystone.authenticatePassword(ironicConfig.os_tenant_name, ironicConfig.os_username,
ironicConfig.os_password).
then(function (token) {
token = JSON.parse(token).access.token.id;
return ironic.get_node_list(token);
}).
then(function (result) {
res.setHeader('Content-Type', 'application/json');
res.end(result);
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {get} /api/1.1/ironic/chassis / GET /
* @apiDescription get ironic chassis
* @apiVersion 1.1.0
*/
module.exports.ironicchassisGet = function ironicchassisGet(req, res) {
'use strict';
return keystone.authenticatePassword(ironicConfig.os_tenant_name, ironicConfig.os_username,
ironicConfig.os_password).
then(function (token) {
token = JSON.parse(token).access.token.id;
return ironic.get_chassis_by_id(token, req.swagger.params.identifier.value);
}).
then(function (result) {
res.setHeader('Content-Type', 'application/json');
res.end(result);
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {get} /api/1.1/ironic/nodes / GET /
* @apiDescription get ironic node
* @apiVersion 1.1.0
*/
module.exports.ironicnodeGet = function ironicnodeGet(req, res) {
'use strict';
return keystone.authenticatePassword(ironicConfig.os_tenant_name, ironicConfig.os_username,
ironicConfig.os_password).
then(function (token) {
token = JSON.parse(token).access.token.id;
return ironic.get_node(token, req.swagger.params.identifier.value);
}).
then(function (result) {
res.setHeader('Content-Type', 'application/json');
res.end(result);
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {patch} /api/1.1/ironic/node/identifier / PATCH /
* @apiDescription patch ironic node info
* @apiVersion 1.1.0
*/
module.exports.ironicnodePatch = function ironicnodePatch(req, res) {
'use strict';
return keystone.authenticatePassword(ironicConfig.os_tenant_name, ironicConfig.os_username,
ironicConfig.os_password).
then(function (token) {
token = JSON.parse(token).access.token.id;
var data = JSON.stringify(req.body);
return ironic.patch_node(token, req.swagger.params.identifier.value, data);
}).
then(function (result) {
res.setHeader('Content-Type', 'application/json');
res.end(result);
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {get} /api/1.1/catalogs/identifier / GET /
* @apiDescription get catalogs
* @apiVersion 1.1.0
*/
module.exports.catalogsGet = function catalogsGet(req, res) {
'use strict';
return monorail.request_catalogs_get(req.swagger.params.identifier.value).
then(function (catalogs) {
res.setHeader('Content-Type', 'application/json');
res.end(catalogs);
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {get} /api/1.1/catalogs/identifier / GET /
* @apiDescription get catalogs by source
* @apiVersion 1.1.0
*/
module.exports.catalogsbysourceGet = function catalogsbysourceGet(req, res) {
'use strict';
return monorail.get_catalog_data_by_source(req.swagger.params.identifier.value,
req.swagger.params.source.value).
then(function (catalogs) {
res.setHeader('Content-Type', 'application/json');
res.end(catalogs);
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {get} /api/1.1/nodes/identifier / GET /
* @apiDescription get specific node by id
* @apiVersion 1.1.0
*/
module.exports.nodeGet = function nodeGet(req, res) {
'use strict';
return monorail.request_node_get(req.swagger.params.identifier.value).
then(function (node) {
res.setHeader('Content-Type', 'application/json');
res.end(node);
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {get} /api/1.1/nodes / GET /
* @apiDescription get list of monorail nodes
* @apiVersion 1.1.0
*/
module.exports.nodesGet = function nodesGet(req, res) {
'use strict';
return monorail.request_nodes_get().
then(function (nodes) {
Promise.filter(JSON.parse(nodes), function (node) {
return monorail.lookupCatalog(node);
})
.then(function (discoveredNodes) {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(discoveredNodes));
});
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {get} /api/1.1/nodes/identifier/sel / GET /
* @apiDescription get specific node by id
* @apiVersion 1.1.0
*/
module.exports.getSeldata = function getSeldata(req, res,next) {
'use strict';
return monorail.request_poller_get(req.swagger.params.identifier.value).
then(function (pollers) {
pollers = JSON.parse(pollers);
return Promise.filter(pollers, function (poller) {
return poller.config.command === 'sel';
})
.then(function (sel) {
if (sel.length > 0) {
return monorail.request_poller_data_get(sel[0].id)
.then(function (data) {
res.setHeader('Content-Type', 'application/json');
res.end(data);
});
}
next();
});
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api register: node
* @apiDescription register a node in Ironic
* @apiVersion 1.1.0
*/
module.exports.registerpost = function registerpost(req, res) {
'use strict';
var localGb, ironicToken, ironicNode, extra, port, propreties, node, info, userEntry;
//init
extra = port = propreties = node = info = {};
userEntry = req.body;
if (userEntry.driver === 'pxe_ipmitool') {
info = {
'ipmi_address': userEntry.ipmihost,
'ipmi_username': userEntry.ipmiuser,
'ipmi_password': userEntry.ipmipass,
'deploy_kernel': userEntry.kernel,
'deploy_ramdisk': userEntry.ramdisk
};
} else if (userEntry.driver === 'pxe_ssh') {
info = {
'ssh_address': userEntry.sshhost,
'ssh_username': userEntry.sshuser,
'ssh_password': userEntry.sshpass,
'ssh_port': userEntry.sshport,
'deploy_kernel': userEntry.kernel,
'deploy_ramdisk': userEntry.ramdisk
};
} else {
info = {};
}
/* Fill in the extra meta data with some failover and event data */
extra = {
'nodeid': userEntry.uuid,
'name': userEntry.name,
'lsevents': { 'time': 0 },
'eventcnt': 0,
'timer': {}
};
if (typeof userEntry.failovernode !== 'undefined') {
extra.failover = userEntry.failovernode;
}
if (typeof userEntry.eventre !== 'undefined') {
extra.eventre = userEntry.eventre;
}
localGb = 0.0;
return monorail.request_node_get(userEntry.uuid).
then(function (result) {
if (!JSON.parse(result).name) {
var error = { error_message: { message: 'failed to find required node in RackHD' } };
logger.error(error);
throw error;
}
ironicNode = JSON.parse(result);
return monorail.nodeDiskSize(ironicNode)
.catch(function (err) {
var error = { error_message: { message: 'failed to get compute node Disk Size' } };
logger.error(err);
throw error;
});
}).then(function (localDisk) {
localGb = localDisk;
return monorail.getNodeMemoryCpu(ironicNode)
.catch(function (err) {
var error = { error_message: { message: 'failed to get compute node memory size' } };
logger.error(err);
throw error;
});
}).then(function (dmiData) {
if (localGb === 0 || dmiData.cpus === 0 || dmiData.memory === 0) {
var error = {
error_message: {
message: 'failed to get compute node data',
nodeDisk: localGb,
memorySize: dmiData.memory,
cpuCount: dmiData.cpus
}
};
throw error;
}
propreties = {
'cpus': dmiData.cpus,
'memory_mb': dmiData.memory,
'local_gb': localGb
};
node = {
'name': userEntry.uuid,
'driver': userEntry.driver,
'driver_info': info,
'properties': propreties,
'extra': extra
};
return keystone.authenticatePassword(ironicConfig.os_tenant_name, ironicConfig.os_username,
ironicConfig.os_password);
}).
then(function (token) {
ironicToken = JSON.parse(token).access.token.id;
return ironic.create_node(ironicToken, JSON.stringify(node));
}).
then(function (ret) {
logger.debug('\r\ncreate node:\r\n' + ret);
if (ret && JSON.parse(ret).error_message) {
throw JSON.parse(ret);
}
ironicNode = JSON.parse(ret);
port = { 'address': userEntry.port, 'node_uuid': ironicNode.uuid };
return ironic.createPort(ironicToken, JSON.stringify(port));
}).
then(function (createPort) {
logger.info('\r\nCreate port:\r\n' + JSON.stringify(createPort));
return ironic.set_power_state(ironicToken, ironicNode.uuid, 'on');
}).
then(function (pwrState) {
logger.info('\r\npwrState: on');
if (pwrState && JSON.parse(pwrState).error_message) {
throw JSON.parse(pwrState);
}
}).then(function () {
var timer = {};
timer.start = new Date().toJSON();
timer.finish = new Date().toJSON();
timer.stop = false;
timer.timeInterval = 15000;
timer.isDone = true;
var data = [{ 'path': '/extra/timer', 'value': timer, 'op': 'replace' }];
return ironic.patch_node(ironicToken, ironicNode.uuid, JSON.stringify(data));
}).
then(function (result) {
logger.info('\r\patched node:\r\n' + result);
}).
then(function () {
_.each(ironicNode.identifiers, function (mac) {
return monorail.request_whitelist_set(mac)
.then(function (whitelist) {
logger.info('\r\nmonorail whitelist:\r\n' + JSON.stringify(whitelist));
});
});
})
.then(function () {
res.setHeader('Content-Type', 'application/json');
var success = {
result: 'success'
};
res.end(JSON.stringify(success));
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api unregister: node
* @apiDescription unregister a node from Ironic
* @apiVersion 1.1.0
*/
module.exports.unregisterdel = function unregisterdel(req, res) {
'use strict';
var ironicToken;
return keystone.authenticatePassword(ironicConfig.os_tenant_name, ironicConfig.os_username,
ironicConfig.os_password).
then(function (token) {
ironicToken = JSON.parse(token).access.token.id;
return ironic.delete_node(ironicToken, req.swagger.params.identifier.value);
})
.then(function (delNode) {
if (delNode && JSON.parse(delNode).error_message) {
throw delNode;
} else {
logger.info('ironicNode: ' +
req.swagger.params.identifier.value +
' is been deleted susccessfully');
res.setHeader('Content-Type', 'application/json');
var success = {
result: 'success'
};
res.end(JSON.stringify(success));
//remove macs from whitelist in rackHD
return monorail.request_node_get(req.swagger.params.identifier.value)
.then(function (node) {
_.each(JSON.parse(node).identifiers, function (mac) {
return monorail.request_whitelist_del(mac);
});
});
}
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api config.json: modify shovel-monorail
* @apiDescription modify shovel config.json file and restart the server
* @apiVersion 1.1.0
*/
module.exports.configsetmono = function configsetmono(req, res) {
'use strict';
res.setHeader('content-type', 'text/plain');
if (setConfig('monorail', req.body)) {
res.end('success');
} else {
res.end('failed to update monorail config');
}
};
/*
* @api config.json: modify shovel-keystone
* @apiDescription modify shovel config.json file and restart the server
* @apiVersion 1.1.0
*/
module.exports.configsetkeystone = function configsetkeystone(req, res) {
'use strict';
res.setHeader('content-type', 'text/plain');
if (setConfig('keystone', req.body)) {
res.end('success');
} else {
res.end('failed to update keystone config');
}
};
/*
* @api config.json: modify shovel-ironic
* @apiDescription modify shovel config.json file and restart the server
* @apiVersion 1.1.0
*/
module.exports.configsetironic = function configsetironic(req, res) {
'use strict';
res.setHeader('content-type', 'text/plain');
if (req.body.hasOwnProperty('os_password')) {
var password = req.body.os_password;
//replace password with encrypted value
try {
req.body.os_password = encryption.encrypt(password);
} catch (err) {
logger.error(err);
res.end('failed to update ironic config');
}
}
if (setConfig('ironic', req.body)) {
res.end('success');
} else {
res.end('failed to update ironic config');
}
};
/*
* @api config.json: modify shovel-glance
* @apiDescription modify shovel config.json file and restart the server
* @apiVersion 1.1.0
*/
module.exports.configsetglance = function configsetglance(req, res) {
'use strict';
res.setHeader('content-type', 'text/plain');
if (req.body.hasOwnProperty('os_password')) {
var password = req.body.os_password;
//replace password with encrypted value
try {
req.body.os_password = encryption.encrypt(password);
} catch (err) {
logger.error(err);
res.end('failed to update ironic config');
}
}
if (setConfig('glance', req.body)) {
res.end('success');
} else {
res.end('failed to update glance config');
}
};
/*
* @api config.json: modify
* @apiDescription modify shovel config.json file and restart the server
* @apiVersion 1.1.0
*/
module.exports.configset = function configset(req, res) {
'use strict';
res.setHeader('content-type', 'text/plain');
if (setConfig('shovel', req.body) === true) {
res.end('success');
} else {
res.end('failed to update shovel config');
}
};
function setConfig(keyValue, entry) {
'use strict';
var filename = 'config.json';
jsonfile.readFile(filename, function (error, output) {
try {
var content = keyValue === null ? output : output[keyValue];
var filteredList = _.pick(content, Object.keys(entry));
_.each(Object.keys(filteredList), function (key) {
content[key] = entry[key];
});
output[keyValue] = content;
jsonfile.writeFile(filename, output, { spaces: 2 }, function () {
logger.debug(content);
});
} catch (err) {
logger.error(err);
return false;
}
});
return true;
}
/*
* @api config.json: get
* @apiDescription get shovel config.json file and restart the server
* @apiVersion 1.1.0
*/
module.exports.configget = function configget(req, res) {
'use strict';
var filename = 'config.json';
jsonfile.readFile(filename, function (error,content) {
try {
delete content.key;
if (content.ironic.hasOwnProperty('os_password')) {
content.ironic.os_password = '[REDACTED]';
}
if (content.glance.hasOwnProperty('os_password')) {
content.glance.os_password = '[REDACTED]';
}
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(content));
} catch (err) {
logger.error(err);
res.setHeader('content-type', 'text/plain');
res.end('failed to get config');
}
});
};
/*
* @api {get} /api/1.1/glance/images / GET /
* @apiDescription get glance images
*/
module.exports.imagesGet = function imagesGet(req, res) {
'use strict';
return keystone.authenticatePassword(glanceConfig.os_tenant_name, glanceConfig.os_username,
glanceConfig.os_password).
then(function (token) {
token = JSON.parse(token).access.token.id;
return glance.get_images(token);
}).
then(function (result) {
res.setHeader('Content-Type', 'application/json');
res.end(result);
})
.catch(function (err) {
logger.error({ message: err, path: req.url });
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
});
};
/*
* @api {post} /api/1.1/deployOS/identifier / POST /
* @apiDescription deploy OS to specific node
*/
module.exports.deployOS = function deployOS(req, res) {
'use strict';
res.setHeader('Content-Type', 'application/json');
return monorail.runWorkFlow(req.swagger.params.identifier.value,
req.body.name,req.body)
.then(function(data) {
res.end(data);
})
.catch(function(err) {
res.end(JSON.stringify(err));
});
};
/*
* @api {get} /api/1.1/workflow-status/identifier / GET /
* @apiDescription Get status of an active worflow running on a node in rackHD
*/
module.exports.workflowStatus = function workflowStatus(req,res) {
'use strict';
res.setHeader('Content-Type', 'application/json');
return monorail.getWorkFlowActive(req.swagger.params.identifier.value)
.then(function(data) {
if (data) {
res.end(JSON.stringify({'jobStatus':'Running'}));
} else {
res.end(JSON.stringify({'jobStatus':'Currently there is no job running on this node'}));
}
})
.catch(function(err) {
res.end(JSON.stringify(err));
});
};