lib/jobs/redfish-job.js
// Copyright 2016, EMC, Inc.
'use strict';
var di = require('di'),
urlParse = require('url-parse');
module.exports = redfishJobFactory;
di.annotate(redfishJobFactory, new di.Provide('Job.Redfish'));
di.annotate(redfishJobFactory, new di.Inject(
'Job.Base',
'Logger',
'Util',
'Assert',
'Promise',
'_',
'Services.Waterline',
'JobUtils.RedfishTool',
'Constants'
));
function redfishJobFactory(
BaseJob,
Logger,
util,
assert,
Promise,
_,
waterline,
RedfishTool,
Constants
) {
var logger = Logger.initialize(redfishJobFactory);
/**
*
* @param {Object} options
* @param {Object} context
* @param {String} taskId
* @constructor
*/
function RedfishJob(options, context, taskId) {
RedfishJob.super_.call(this, logger, options, context, taskId);
this.routingKey = this.context.graphId;
assert.uuid(this.routingKey) ;
this.concurrent = {};
this.maxConcurrent = 1;
this.commandOptions = {};
}
util.inherits(RedfishJob, BaseJob);
RedfishJob.prototype.initClient = function(settings) {
var redfish = new RedfishTool();
redfish.settings = settings;
return redfish;
};
/**
* @function _run
* @description the jobs internal run method
*/
RedfishJob.prototype._run = function run() {
var self = this;
return waterline.workitems.update({name: "Pollers.Redfish"}, {failureCount: 0})
.then(function() {
self._subscribeRedfishCommand(self.routingKey, function(data) {
self.commandOptions = data;
if (self.concurrentRequests(data.node, data.workItemId)) {
return;
}
self.addConcurrentRequest(data.node, data.workItemId);
assert.object(data.config,
'Redfish Poller Data Config');
return waterline.obms.findByNode(data.node, 'redfish-obm-service', true)
.then(function (obmSetting) {
if (obmSetting) {
return [
obmSetting.config,
data.config.command
];
}
})
.spread(function (config, command) {
return self.collectData(
config,
command
);
})
.then(function (result) {
assert.object(result,
'Expected Result Object For: ' + data.config.command
);
data[data.config.command] = result;
return self._publishRedfishCommandResult(
self.routingKey,
data.config.command,
data
);
})
.then(function () {
return waterline.workitems.findOne({id: data.workItemId});
})
.then(function (workitem) {
return waterline.workitems.setSucceeded(null, null, workitem);
})
.catch(function (err) {
logger.error("Error Retrieving Redfish Data.", {
data: data,
error: err
});
})
.finally(function () {
self.removeConcurrentRequest(data.node, data.workItemId);
});
});
})
.catch(function(err) {
logger.error("Failed to initialize job", {
error:err
});
self._done(err);
});
};
/**
* @function concurrentRequests
* @description manage concurrent work item command requests
*/
RedfishJob.prototype.concurrentRequests = function(node, workItemId) {
assert.string(node);
assert.string(workItemId);
if(!_.has(this.concurrent, node)){
this.concurrent[node] = {};
}
if(!_.has(this.concurrent[node], workItemId)){
this.concurrent[node][workItemId] = 0;
}
if(this.concurrent[node][workItemId] >= this.maxConcurrent){
return true;
} else {
return false;
}
};
/**
* @function addConcurrentRequest
* @description add a new command request for this work item
*/
RedfishJob.prototype.addConcurrentRequest = function(node, type) {
assert.object(this.concurrent[node]);
assert.number(this.concurrent[node][type]);
this.concurrent[node][type] += 1;
};
/**
* @function removeConcurrentRequest
* @description remove a completed command request for this work item
*/
RedfishJob.prototype.removeConcurrentRequest = function(node, type) {
assert.object(this.concurrent[node]);
assert.number(this.concurrent[node][type]);
this.concurrent[node][type] -= 1;
};
/**
* Collect Redfish Log Service Entries for Systems resource member
* @param redfish client utility
* @param system resource path to collect entries from
*/
RedfishJob.prototype.collectSystemLogEntries = function(redfish, resource) {
return redfish.clientRequest(resource)
.then(function(res) {
assert.ok(_.has(res.body, 'Members'),
'Has LogServices Members');
return res.body.Members;
})
.map(function(member) {
assert.object(member, 'LogService Member');
return redfish.clientRequest(member['@odata.id']);
})
.map(function(res) {
assert.ok(_.has(res.body, 'Entries'),
'Has LogService Entries');
return redfish.clientRequest(res.body.Entries['@odata.id']);
})
.map(function(res) {
assert.ok(_.has(res.body, 'Members'),
'Has Entry Members');
return res.body.Members;
})
.map(function(memberArr) {
var promises = [];
_.forEach(memberArr, function(member) {
promises.push(redfish.clientRequest(member['@odata.id']));
});
return Promise.all(promises);
})
.map(function(resArr) {
var data = [];
_.forEach(resArr, function(res) {
data.push(res.body);
});
return data;
});
};
/**
* Collect Redfish Log Service Entries for Managers resource member
* @param redfish client utility
*/
RedfishJob.prototype.collectManagersLogEntries = function(redfish) {
var parse = urlParse(redfish.settings.uri);
var rootPath = parse.pathname + '/';
// Managers is a root resource
return redfish.clientRequest(rootPath)
.then(function(res) {
assert.ok(_.has(res.body, 'Managers'),
'Managers Resource');
return redfish.clientRequest(res.body.Managers['@odata.id']);
})
.then(function(res) {
assert.ok(_.has(res.body, 'Members'),
'Has Manager Members');
return res.body.Members;
})
.map(function(member) {
assert.object(member, 'Manager Member');
return redfish.clientRequest(member['@odata.id']);
})
.map(function(res) {
assert.ok(_.has(res.body, 'LogServices'),
'Has LogServices Resource');
return redfish.clientRequest(res.body.LogServices['@odata.id']);
})
.map(function(res) {
assert.ok(_.has(res.body, 'Members'),
'Has LogService Members');
return res.body.Members;
})
.map(function(member) {
assert.object(member, 'Log Service Member');
return redfish.clientRequest(member[0]['@odata.id']);
})
.map(function(res) {
assert.ok(_.has(res.body, 'Entries'),
'Has LogServices Entries');
return redfish.clientRequest(res.body.Entries['@odata.id']);
})
.map(function(res) {
assert.ok(_.has(res.body, 'Members'),
'Has Entry Members');
return res.body.Members;
})
.map(function(memberArr) {
var promises = [];
_.forEach(memberArr, function(member) {
promises.push(redfish.clientRequest(member['@odata.id']));
});
return Promise.all(promises);
})
.map(function(resArr) {
var data = [];
_.forEach(resArr, function(res) {
data.push(res.body);
});
return data;
});
};
RedfishJob.prototype._formatFabricServiceAlert = function _formatFabricServiceAlert(alert) {
/*
* alert format:
{ pollerName: 'FabricService',
data: [ { EndPointName: 'epName', Available: true } ],
EventType: 'ResourceUpdated'
}
* expected event format:
{ type: 'polleralert',
action: 'fabricservice.updated',
typeId: '5858c89bca4349fa0409ba8d',
nodeId: '5858c7f7f6f0ce7d08a7298b',
severity: 'information',
data: {
eventType: 'ResourceUpdated',
resource: [ { EndPointName: 'epName', Available: true } ]
}
}
*/
var self = this;
return {
type: 'polleralert',
action: 'fabricservice.updated',
typeId: self.commandOptions.workItemId,
nodeId: self.commandOptions.node,
severity: "information",
data: {
eventType: alert.EventType,
resource: alert.data
}
};
};
/**
* Check fabric service status and generate alert on status change
* @param redfish client utility
*/
RedfishJob.prototype.fabricServiceDataAlert = function(redfish, currentData) {
var self = this;
var alert = {
pollerName: 'FabricService',
data: []
};
var eventTypes = Constants.Redfish.EventTypes;
return Promise.resolve()
.then(function() {
if(self.fabricDataCache) {
assert.ok(_.has(self.fabricDataCache, 'EndPoints'),
'Last Endpoint Data'
);
assert.ok(_.has(currentData, 'EndPoints'),
'Current Endpoint Data'
);
var currentEndpoints = currentData.EndPoints;
var lastEndpoints = self.fabricDataCache.EndPoints;
if(JSON.stringify(currentEndpoints) !== JSON.stringify(lastEndpoints)) {
_.forEach(currentEndpoints, function(endpoint) {
var match = _.find(lastEndpoints,
_.matches({EndPointName:endpoint.EndPointName}));
if(_.isUndefined(match)) {
alert.data.push(endpoint);
alert.EventType = eventTypes.ResourceAdded;
} else {
if(match.Available !== endpoint.Available) {
alert.data.push(endpoint);
alert.EventType = eventTypes.ResourceUpdated;
}
}
});
_.forEach(lastEndpoints, function(endpoint) {
var match = _.find(currentEndpoints,
_.matches({EndPointName:endpoint.EndPointName}));
if(_.isUndefined(match)) {
alert.data.push(endpoint);
alert.EventType = eventTypes.ResourceRemoved;
}
});
}
if(alert.data.length) {
logger.debug('FabricService Status Alert', {alert:alert});
return self._publishPollerAlert(self._formatFabricServiceAlert(alert));
}
return;
}
})
.then(function() {
self.fabricDataCache = currentData;
return currentData;
});
};
/**
* Collect Emc OEM Redfish FabricService resource data
* @param redfish client utility
*/
RedfishJob.prototype.collectFabricServiceData = function(redfish) {
var self = this;
var parse = urlParse(redfish.settings.uri);
var rootPath = parse.pathname + '/';
// FabricService is a root Oem resource
return redfish.clientRequest(rootPath)
.then(function(res) {
assert.ok(_.has(res.body, 'Oem.Emc.FabricService'),
'Has FabricService Resource');
return redfish.clientRequest(
res.body.Oem.Emc.FabricService['@odata.id']
);
})
.then(function(res) {
return self.fabricServiceDataAlert(redfish, res.body);
});
};
/**
* Collect Emc OEM Redfish Elements Power/Thermal resource data
* @param redfish client utility
* @param type the resource type Power or Thermal
*/
RedfishJob.prototype.collectOemElementsData = function(redfish, type) {
var self = this;
var parse = urlParse(redfish.settings.root);
return redfish.clientRequest(parse)
.then(function (res) {
assert.ok(_.has(res.body, 'Oem.Emc.Elements'),
'Has Oem Elements Resource');
var elements = _.get(res.body, 'Oem.Emc.Elements');
return elements['@odata.id'];
})
.then(function (id) {
assert.string(id, 'Element Identifier');
return redfish.clientRequest(id);
})
.then(function (res) {
assert.object(res, 'Element Resource Object');
var elements = _.get(res, 'body.Members');
return elements;
})
.map(function (element) {
return redfish.clientRequest(element['@odata.id']);
})
.map(function (element) {
assert.object(element.body);
return element.body;
})
.map(function (element) {
return redfish.clientRequest(element['@odata.id'])
.then(function (element) {
return element;
});
})
.map(function (element) {
assert.ok(_.has(element.body, type), 'Has Thermal Resource');
var id = _.get(element.body, type, {})['@odata.id'];
return redfish.clientRequest(id)
.then(function(res) {
assert.object(res.body, type + ' data');
return(res.body);
});
});
};
/**
* Collect Emc OEM Redfish Spine Modules Power/Thermal resource data
* @param redfish client utility
* @param type the resource type Power or Thermal
*/
RedfishJob.prototype.collectOemSpineData = function(redfish, type) {
return redfish.clientRequest().then(function(res) {
var spineModId = _.get(res.body, 'Oem.Emc.SpineModules', {})['@odata.id'];
if (_.isUndefined(spineModId)) {
throw new Error('Missing SpineModules Resource');
}
return redfish.clientRequest(spineModId).then(function(res) {
var members = _.get(res, 'body.Members', {});
assert.ok(Array.isArray(members), 'SpineModules Member List');
return members;
});
}).map(function(member) {
return redfish.clientRequest(member['@odata.id']);
}).map(function(res) {
var id = _.get(res.body, type, {})['@odata.id'];
return redfish.clientRequest(id);
}).map(function(res) {
return res.body;
});
};
/**
* Collect Emc OEM Redfish Aggregator Power/Thermal resource data
* @param redfish client utility
* @param type the resource type Power or Thermal
*/
RedfishJob.prototype.collectOemAggregatorData = function(redfish, type) {
return redfish.clientRequest().then(function(res) {
var spineModId = _.get(res.body, 'Oem.Emc.Aggregators', {})['@odata.id'];
if (_.isUndefined(spineModId)) {
throw new Error('Missing Aggregators Resource');
}
return redfish.clientRequest(spineModId).then(function(res) {
var members = _.get(res, 'body.Members', {});
assert.ok(Array.isArray(members), 'Aggregators Member List');
return members;
});
}).map(function(member) {
return redfish.clientRequest(member['@odata.id']);
}).map(function(res) {
var id = _.get(res.body, type, {})['@odata.id'];
return redfish.clientRequest(id);
}).map(function(res) {
return res.body;
});
};
/**
* Collect Redfish telemetry data for specified Redfish command
* @param config the redfish configuration settings object
* @param command the redfish command
*/
RedfishJob.prototype.collectData = function(config, command) {
var self = this;
assert.string(command, 'Redfish Command');
var redfish = self.initClient(config);
return redfish.clientRequest(config.root)
.then(function(response) {
return response.body;
})
.then(function(member) {
switch(command) {
case 'power':
assert.ok(_.has(member, 'Power'),
'Power Resource for ' + member.Name);
return redfish.clientRequest(
member.Power['@odata.id']
);
case 'thermal':
assert.ok(_.has(member, 'Thermal'),
'Thermal Resource for ' + member.Name);
return redfish.clientRequest(
member.Thermal['@odata.id']
);
case 'systems.logservices':
assert.ok(_.has(member, 'LogServices'),
'LogService for ' + member.Name);
return self.collectSystemLogEntries(
redfish, member.LogServices['@odata.id']
);
case 'managers.logservices':
return self.collectManagersLogEntries(
redfish // starts at root
);
case 'fabricservice':
return self.collectFabricServiceData(
redfish // starts at root
);
case 'elements.thermal':
case 'elements.power':
return self.collectOemElementsData(
redfish,
(command === 'elements.thermal') ? 'Thermal' : 'Power'
);
case 'spine.thermal':
case 'spine.power':
return self.collectOemSpineData(
redfish,
(command === 'spine.thermal') ? 'Thermal' : 'Power'
);
case 'aggregator.thermal':
case 'aggregator.power':
return self.collectOemAggregatorData(
redfish,
(command === 'aggregator.thermal') ? 'Thermal' : 'Power'
);
default:
throw new Error(
'Unsupported Redfish Command: ' + command
);
}
})
.then(function(data) {
if (!data) {
throw new Error(
'No Data Found For Command: ' + command
);
}
if (data.body) {
data = data.body;
}
return data;
})
.catch(function(err) {
if(err.message !== 'Unsupported Redfish Command: ' + command &&
err.message !== 'No Data Found For Command: ' + command ) {
logger.warning('Collect Data', {error:err});
return {error:err}; // publish exceptions as data
}
throw err;
});
};
return RedfishJob;
}