lib/jobs/redfish-discovery-list.js
// Copyright 2017, Dell EMC, Inc.
'use strict';
var di = require('di'),
urlParse = require('url-parse');
var request = require('requestretry');
module.exports = RedfishDiscoveryListJobFactory;
di.annotate(RedfishDiscoveryListJobFactory, new di.Provide('Job.Redfish.Discovery.List'));
di.annotate(RedfishDiscoveryListJobFactory, new di.Inject(
'Protocol.Events',
'Job.Base',
'Logger',
'Promise',
'Assert',
'Util',
'Services.Waterline',
'Services.Encryption',
'_',
'JobUtils.RedfishTool'
));
function RedfishDiscoveryListJobFactory(
eventsProtocol,
BaseJob,
Logger,
Promise,
assert,
util,
waterline,
encryption,
_,
RedfishTool
) {
var logger = Logger.initialize(RedfishDiscoveryListJobFactory);
/**
* @param {Object} options task options object
* @param {Object} context graph context object
* @param {String} taskId running task identifier
* @constructor
*/
function RedfishDiscoveryListJob(options, context, taskId) {
RedfishDiscoveryListJob.super_.call(this,
logger,
options,
context,
taskId);
this.endpointList = this.context.discoverList;
}
util.inherits(RedfishDiscoveryListJob, BaseJob);
/**
* @memberOf RedfishDiscoveryListJob
*/
RedfishDiscoveryListJob.prototype._run = function() {
var self = this;
return Promise.map(self.endpointList, function(endpoint) {
logger.debug("attempting to discover: " + endpoint.uri);
return self.discoverList(endpoint);
}, {concurrency: 128})
.then(function() {
self._done();
})
.catch(function(err){
self._done(err);
});
};
RedfishDiscoveryListJob.prototype.restRequest = function(settings, path) {
var url = settings.protocol + '://' + settings.host + ':' + settings.port + path;
return request.get(url, {
'auth': {
'user': settings.username || '',
'pass': settings.password || '',
},
strictSSL: false,
json: true
});
};
RedfishDiscoveryListJob.prototype.discoverList = function (endpoint) {
var self = this;
assert.object(endpoint);
assert.string(endpoint.uri);
var parse = urlParse(endpoint.uri);
var protocol = parse.protocol.replace(':', '').trim();
self.settings = {
uri: parse.href,
host: parse.host.split(':')[0],
root: parse.pathname + '/',
port: parse.port,
protocol: protocol,
username: endpoint.username,
password: endpoint.password,
verifySSL: false
};
var redfish = new RedfishTool();
redfish.settings = self.settings;
return self.getRoot(redfish)
.then(function (root) {
return [root, self.createSystems(root, redfish)];
})
.spread(function (root, systems) {
return [root, systems, self.createChassis(root, redfish)];
})
.spread(function (root, systems, chassis) {
return [root, systems, chassis, self.createManagers(root, redfish)];
})
.spread(function (root, systems, chassis, managers) {
chassis = chassis.filter(function(data) {return !(_.isNull(data) || _.isUndefined(data));});
var cooling = self.createRedfishNode(root, 'DCIMCooling',
['CRAH', 'CRAC', 'AirHandlingUnit', 'Chiller', 'CoolingTower'], 'cooling', redfish);
var power = self.createRedfishNode(root, 'DCIMPower',
['Generator', 'TransferSwitch', 'PDU', 'Rectifier',
'UPS', 'RackPDU', 'Transformer', 'Switchgear', 'VFD'],
'power', redfish);
var network;
if (_.has(root, 'NetworkDevices')) {
network = self.createNetwork(root, redfish);
} else {
network = [];
}
return [root, chassis, systems, managers, cooling, power, network];
})
.spread(function (root, chassis, systems, managers, cooling, power, networks) {
self.context.chassis = (self.context.chassis || []).concat(_.map(chassis, function (it) {
return _.get(it, 'id');
}));
self.context.systems = (self.context.systems || []).concat(_.map(systems, function (it) {
return _.get(it, 'id');
}));
self.context.managers = (self.context.managers || []).concat(_.map(managers, function (it) {
return _.get(it, 'id');
}));
if (cooling) {
_.forEach(cooling[0], function (coolingType) {
self.context.cooling = (self.context.cooling || []).concat(_.map(coolingType,
function (it) {
return _.get(it, 'id');
}));
});
}
if (power) {
_.forEach(power[0], function (powerType) {
self.context.power = (self.context.power || []).concat(_.map(powerType, function (it) {
return _.get(it, 'id');
}));
});
}
self.context.networks = (self.context.networks || []).concat(_.map(networks, function (it) {
return _.get(it, 'id');
}));
systems = Array.isArray(systems) ? systems : [systems];
return [root, Promise.all([
self.mapPathToIdRelation(chassis, systems.concat(networks), 'encloses'),
self.mapPathToIdRelation(systems, chassis, 'enclosedBy'),
self.mapPathToIdRelation(networks, chassis, 'enclosedBy'),
self.mapPathToIdRelation(managers, chassis.concat(systems), 'manages'),
self.mapPathToIdRelation(systems, managers, 'managedBy'),
self.mapPathToIdRelation(chassis, managers, 'managedBy')
])];
})
.catch(function(error) {
logger.debug("Error occured during discovery of " + endpoint.uri + " with error "+ JSON.stringify(error, null, 4));
});
};
RedfishDiscoveryListJob.prototype.upsertRelations = function(node, relations) {
//todo fix this hack to allow mulitple nodes to be created
var lookup;
if ( 'identifiers' in node )
{
lookup = { identifiers: node.identifiers[1] };
}
else
{
lookup = node;
}
// Update existing node with new relations or create one
return waterline.nodes.needOne(lookup)
.then(function(curNode) {
relations = _.uniq(relations.concat(curNode.relations), 'relationType');
return waterline.nodes.updateOne(
{ id: curNode.id },
{ relations: relations }
)
.then(function(node){
eventsProtocol.publishNodeEvent(node, 'updated');
return node;
});
})
.catch(function(error) {
if (error.name === 'NotFoundError') {
node.relations = relations;
return waterline.nodes.create(node)
.then(function(node){
eventsProtocol.publishNodeEvent(node, 'discovered');
return node;
});
}
throw error;
});
};
/**
* @function getRoot
*/
RedfishDiscoveryListJob.prototype.getRoot = function (redfish) {
return Promise.resolve(this.restRequest(redfish.settings, "/redfish/v1"))
.then(function(response) {
return response.body;
});
};
/**
* @function createRedfishNode
* @description initiate redfish discovery
*/
RedfishDiscoveryListJob.prototype.createRedfishNode = function (
root,
resourceName,
resourceTypes,
nodeType,
redfish) {
var self = this;
if (!_.has(root, resourceName)) {
return Promise.resolve([]);
}
return Promise.resolve(self.restRequest(redfish.settings, root[resourceName]['@odata.id']))
.then(function(res) {
assert.object(res);
return res.body.Members;
})
.map(function(member) {
return Promise.resolve(self.restRequest(redfish.settings, member['@odata.id']));
})
.map(function(resource) {
resource = resource.body;
return Promise.map(resourceTypes, function (resourceType) {
if (resource[resourceType])
{
return Promise.resolve(self.restRequest(redfish.settings, resource[resourceType]['@odata.id']));
}
})
.map(function (data, idx) {
if (data) {
return Promise.map(data.body.Members, function (elem) {
return Promise.resolve(self.restRequest(redfish.settings, elem['@odata.id']));
})
.map(function (data) {
var node = {
type: nodeType,
name: data.body.Name,
identifiers: [data.body.ID, redfish.settings.uri, resource.Id, resourceTypes[idx]]
};
var relations = [];
return self.upsertRelations(node, relations)
.then(function (n) {
logger.debug("New node created");
var config = Object.assign({}, redfish.settings);
config.root = data.body['@odata.id'];
var obm = {
config: config,
service: 'redfish-obm-service'
};
return waterline.obms.upsertByNode(n.id, obm)
.then(function () {
return n;
});
});
});
}
})
.catch(function (err){
logger.debug('ERROR: ' + err.message);
return [];
});
})
.catch(function (err) {
logger.debug('ERROR: ' + err.message);
return [];
});
};
/**
* @function createChassis
* @description initiate redfish chassis discovery
*/
RedfishDiscoveryListJob.prototype.createChassis = function (root, redfish) {
var self = this;
var createEnclosure = true;
if (!_.has(root, 'Chassis')) {
throw new Error('No Chassis Members Found');
}
return Promise.resolve(self.restRequest(redfish.settings, root.Chassis['@odata.id']))
.then(function(res) {
assert.object(res);
return res.body.Members;
})
.map(function(member) {
return Promise.resolve(self.restRequest(redfish.settings, member['@odata.id']));
})
.map(function(chassis) {
chassis = chassis.body;
var systems = _.get(chassis, 'Links.ComputerSystems') ||
_.get(chassis, 'links.ComputerSystems', []);
var chassisCollection = _.get(chassis, 'Links.Contains') ||
_.get(chassis, 'links.Contains', []);
if (_.isUndefined(systems) && _.isUndefined(chassisCollection)) {
// Log a warning and skip System to Chassis relation if no links are provided.
logger.warning('No System or NetworkDevice members for Chassis were available');
}
return {
chassis: chassis || {},
systems: systems || [],
chassisCollection: chassisCollection || []
};
})
.map(function(data) {
return Promise.each(data.systems, function(system) {
var systemUrl = _.get(system, '@odata.id') ||
_.get(system, 'href');
return Promise.resolve(self.restRequest(redfish.settings, systemUrl))
.then(function(res) {
if (res.body.Id === data.chassis.Id && res.body.SerialNumber === data.chassis.SerialNumber) {
createEnclosure = false;
}
})
.then(function() {
return data;
});
})
.then(function() {
return data;
});
})
.map(function(data) {
if (!createEnclosure) {
return;
}
assert.object(data);
var targetList = [];
_.forEach(data.systems, function(sys) {
var target = _.get(sys, '@odata.id') ||
_.get(sys, 'href');
if (!_.isUndefined(target)) {
targetList.push(target);
}
});
_.forEach(data.chassisCollection, function(chassisUrl) {
var target = _.get(chassisUrl, '@odata.id') ||
_.get(chassisUrl, 'href');
if (!_.isUndefined(target)) {
targetList.push(target);
}
});
var nodeRoot = redfish.settings.protocol + "://" +
redfish.settings.host + ":" + redfish.settings.port + data.chassis['@odata.id'];
var node = {
type: 'enclosure',
name: data.chassis.Name,
// todo find a better unique identifier
identifiers: [ data.chassis.Id, nodeRoot, data.chassis.SKU + '-' + data.chassis.SerialNumber ]
};
var relations = [{
relationType: 'encloses',
targets: targetList
}];
return self.upsertRelations(node, relations)
.then(function(n) {
var config = Object.assign({}, redfish.settings);
config.root = data.chassis['@odata.id'];
var obm = {
config: config,
service: 'redfish-obm-service'
};
return waterline.obms.upsertByNode(n.id, obm)
.then(function() {
return n;
});
});
})
.catch(function (err) {
logger.debug("Error creating Chassis!");
logger.debug("Error: " + JSON.stringify(err, null, 4));
return ;
});
};
/**
* @function createSystems
* @description initiate redfish system discovery
*/
RedfishDiscoveryListJob.prototype.createSystems = function (root, redfish) {
var self = this;
if (!_.has(root, 'Systems')) {
logger.warning('No System Members Found');
return Promise.resolve();
}
return Promise.resolve(self.restRequest(redfish.settings, root.Systems['@odata.id']))
.then(function(res) {
assert.object(res);
return res.body.Members;
})
.map(function(member) {
return Promise.resolve(self.restRequest(redfish.settings, member['@odata.id']));
})
.map(function(system) {
system = system.body;
var chassis = _.get(system, 'Links.Chassis') ||
_.get(system, 'links.Chassis');
var managers = _.get(system, 'Links.ManagedBy') ||
_.get(system, 'links.ManagedBy');
return {
system: system || {},
chassis: chassis || [],
managers: managers || []
};
})
.map(function(data) {
var createEnclosure = false;
assert.object(data);
var targetList = [];
var managedByList = [];
_.forEach(data.chassis, function(chassis) {
var target = _.get(chassis, '@odata.id') ||
_.get(chassis, 'href');
targetList.push(target);
});
_.forEach(data.managers, function(manager) {
var target = _.get(manager, '@odata.id') ||
_.get(manager, 'href');
managedByList.push(target);
});
return Promise.each(data.chassis, function(chassis) {
return Promise.resolve(self.restRequest(redfish.settings, chassis['@odata.id']))
.then(function(res) {
if (res.body.id !== data.system.id || res.body.SerialNumber !== data.system.SerialNumber) {
createEnclosure = true;
}
})
.catch(function (err) {
logger.debug("Error enclosure determination!: " + JSON.stringify(err, null, 4));
});
})
.then(function() {
var nodeRoot = redfish.settings.protocol + "://" +
redfish.settings.host + ":" + redfish.settings.port + data.system['@odata.id'];
var identifiers = [data.system.Id, nodeRoot, data.system.SKU + '-' + data.system.SerialNumber];
var config = Object.assign({}, redfish.settings);
var relations;
var nodeType;
config.root = data.system['@odata.id'];
if (createEnclosure) {
relations = [{
relationType: 'enclosedBy',
targets: targetList
},
{
relationType: 'managedBy',
targets: managedByList
}];
nodeType = 'compute';
} else {
relations = [{
relationType: 'managedBy',
targets: managedByList
}];
nodeType = 'redfish';
}
var node = {
type: nodeType,
name: data.system.Name,
identifiers: identifiers
};
var obm = {
config: config,
service: 'redfish-obm-service'
};
return self.upsertRelations(node, relations)
.then(function(nodeRecord) {
return Promise.all([
waterline.obms.upsertByNode(nodeRecord.id, obm),
nodeRecord
]);
})
.spread(function(obm, node) {
return node;
});
});
});
};
/**
* @function createManager
* @description initiate redfish manager discovery
*/
RedfishDiscoveryListJob.prototype.createManagers = function (root, redfish) {
var self = this;
if (!_.has(root, 'Managers')) {
throw new Error('No Manager Members Found');
}
return Promise.resolve(self.restRequest(redfish.settings, root.Managers['@odata.id']))
.then(function(res) {
assert.object(res);
return res.body.Members;
})
.map(function(members) {
return Promise.resolve(self.restRequest(redfish.settings, members['@odata.id']));
})
.map(function(manager) {
manager = manager.body;
var systems = _.get(manager, 'Links.ManagerForServers') ||
_.get(manager, 'links.ManagerForServers', []);
var chassis = _.get(manager, 'Links.ManagerForChassis') ||
_.get(manager, 'links.ManagerForChassis', []);
return {
manager: manager || {},
chassis: chassis || [],
systems: systems || []
};
})
.map(function(data) {
assert.object(data);
var targetList = [];
_.forEach(data.systems, function(systemUrl) {
var target = _.get(systemUrl, '@odata.id') ||
_.get(systemUrl, 'href');
if (!_.isUndefined(target)) {
targetList.push(target);
}
});
_.forEach(data.chassis, function(chassisUrl) {
var target = _.get(chassisUrl, '@odata.id') ||
_.get(chassisUrl, 'href');
if (!_.isUndefined(target)) {
targetList.push(target);
}
});
var nodeRoot = redfish.settings.protocol + "://" +
redfish.settings.host + ":" + redfish.settings.port + data.manager['@odata.id'];
var node = {
type: 'redfishManager',
name: data.manager.Name,
identifiers: [ data.manager.Id, nodeRoot]
};
var relations = [{
relationType: 'manages',
targets: targetList
}];
return self.upsertRelations(node, relations)
.then(function(n) {
var config = Object.assign({}, redfish.settings);
config.root = data.manager['@odata.id'];
var obm = {
config: config,
service: 'redfish-obm-service'
};
return waterline.obms.upsertByNode(n.id, obm)
.then(function() {
return n;
});
});
})
.catch(function (err) {
logger.debug("Error creating Manager!");
logger.debug("Error: " + JSON.stringify(err, null, 4));
throw err;
});
};
/**
* @function createNetwork
* @description initiate redfish network discovery
*/
RedfishDiscoveryListJob.prototype.createNetwork = function (root, redfish) {
var self = this;
if (!_.has(root, 'NetworkDevices')) {
logger.warning('No NetworkDevices Members Found');
return Promise.resolve();
}
return Promise.resolve(self.restRequest(redfish.settings, root.NetworkDevices['@odata.id']))
.then(function(res) {
assert.object(res);
return res.body.Members;
})
.map(function(member) {
return Promise.resolve(self.restRequest(redfish.settings, member['@odata.id']));
})
.map(function(network) {
network = network.body;
var chassis = _.get(network, 'Links.Chassis') ||
_.get(network, 'links.Chassis');
if (_.isUndefined(chassis)) {
// Log a warning and skip Chassis to Network relation if no links are provided.
logger.warning('No Chassis members for NetworkDevices were available');
}
return {
network: network || [],
chassis: chassis || []
};
})
.map(function(data) {
assert.object(data);
var targetList = [];
_.forEach(data.chassis, function(chassis) {
var target = _.get(chassis, '@odata.id') ||
_.get(chassis, 'href');
targetList.push(target);
});
var config = Object.assign({}, redfish.settings);
config.root = data.network['@odata.id'];
var nodeRoot = redfish.settings.protocol + "://" +
redfish.settings.host + ":" + redfish.settings.port + data.network['@odata.id'];
var identifiers = [ data.network.Id, nodeRoot ];
var node = {
type: 'switch',
name: data.network.Name,
identifiers: identifiers
};
var relations = [{
relationType: 'enclosedBy',
targets: targetList
}];
var obm = {
config: config,
service: 'redfish-obm-service'
};
return self.upsertRelations(node, relations)
.then(function(nodeRecord) {
return Promise.all([
waterline.obms.upsertByNode(nodeRecord.id, obm),
nodeRecord
]);
})
.spread(function(obm, node) {
return node;
});
});
};
/**
* @function mapPathToIdRelation
* @description map source node relation types to a target
*/
RedfishDiscoveryListJob.prototype.mapPathToIdRelation = function (src, target, type) {
var self = this;
if(src === undefined || src === [] || target === undefined || target === [])
{
return;
}
src = Array.isArray(src) ? src : [ src ];
target = Array.isArray(target) ? target : [ target ];
return Promise.resolve(src)
.map(function(node) {
if (_.isUndefined(node) || _.isNull(node)) {
return ;
}
var ids = [];
var deferredObms = [];
var relations = _(node.relations).find({
relationType: type
});
if (_.isUndefined(relations) || _.isNull(relations)) {
return ;
}
_.forEach(target, function(t) {
deferredObms.push(waterline.obms.findByNode(t.id, 'redfish-obm-service'));
});
Promise.all(deferredObms)
.then(function(obms) {
_.forEach(target, function(t, i) {
_.forEach(_.get(relations, 'targets', []), function(relation) {
if (relation === obms[i].config.root) {
ids.push(t.id);
}
});
});
if (_.has(relations, 'targets')) {
relations.targets = ids;
}
relations = [ relations ];
return self.upsertRelations({id: node.id}, relations);
});
});
};
return RedfishDiscoveryListJob;
}