lib/api/redfish-1.0/chassis.js
// Copyright 2016, EMC, Inc.
'use strict';
var injector = require('../../../index.js').injector;
var redfish = injector.get('Http.Api.Services.Redfish');
var _ = injector.get('_'); // jshint ignore:line
var waterline = injector.get('Services.Waterline');
var Promise = injector.get('Promise'); // jshint ignore:line
var Errors = injector.get('Errors');
var taskProtocol = injector.get('Protocol.Task');
var controller = injector.get('Http.Services.Swagger').controller;
var env = injector.get('Services.Environment');
var nodeApi = injector.get('Http.Services.Api.Nodes');
/*
* This mapping is used by the Redfish Chassis logic to determine what
* sensors should be displayed in the chassis object representing the
* enclosure vs. what sensors should be displayed in the chassis object
* representing the blade element. Entities that are specific to the
* blade are filtered out of the enclosure by default.
*
* This value can be overridden on a sku basis via the redfish.sensorFilter
* value.
*/
var EntityIdFilterDefaults = [
'3', // processor
'7', // system board
'8', // memory module
'66' // baseboard
];
function _getEnclosedSystems(node) {
return _(node.relations)
.filter({relationType: 'encloses'})
.pluck('targets')
.flattenDeep()
.value();
}
var dataFactory = function(identifier, dataName) {
switch(dataName) {
case 'catData':
return nodeApi.getNodeCatalogSourceById(identifier, 'ohai')
.then(function(catalog) {
var catData = {
chassis: {
asset_tag: catalog.data.dmi.chassis.asset_tag,
manufacturer: catalog.data.dmi.chassis.manufacturer,
sku_number: catalog.data.dmi.chassis.sku_number,
serial_number: catalog.data.dmi.chassis.serial_number,
type: catalog.data.dmi.chassis.type
},
system: {
product_name: catalog.data.dmi.system.product_name
}
};
return catData;
})
.catch(function(err) {
if (err instanceof Errors.NotFoundError) {
//If ohai catalog not found look for UCS chassis data catalog
return nodeApi.getNodeCatalogSourceById(identifier, 'UCS')
.then(function(catalog) {
var catData = {
chassis: {
asset_tag: '',
manufacturer: catalog.data.vendor,
sku_number: '',
serial_number: catalog.data.serial,
type: (_.includes(catalog.data.rn, 'chassis')) ? 'Enclosure' : 'Other'
},
system: {
product_name: catalog.data.model
}
};
return catData;
});
}
throw err;
});
case 'system':
return nodeApi.getNodeById(identifier)
.then(function(node) {
return _.filter(node.relations, function(relation) {
return relation.relationType === 'encloses';
}).map(function(relation) {
return relation.targets[0];
});
});
case 'chassis':
return nodeApi.getNodeById(identifier)
.then(function(node) {
return _.filter(node.relations, function(relation) {
return relation.relationType === 'encloses';
}).map(function(relation) {
return relation.targets;
});
})
.then(function(targets) {
if(targets) {
return targets[0];
}
});
case 'chassisData':
return nodeApi.getPollersByNodeId(identifier)
.filter(function(poller) {
return poller.config.command === 'chassis';
}).spread(function(poller) {
if (poller) {
return taskProtocol.requestPollerCache(poller.id, { latestOnly: true })
.then(function(data) {
var chassisData = data[0].chassis;
chassisData.power = (chassisData.power) ? 'On' : 'Off';
var uidStatus = ['Off', 'Temporary On', 'On', 'Reserved'];
var validEnum = ['Off', 'Blinking', 'Lit', 'Unknown']; // map to valid redfish value
chassisData.uid = validEnum[_.indexOf(uidStatus, chassisData.uid)];
return chassisData;
});
}
else {
//If no pollers found check for UCS chassis data catalog to get the power and uid LEDs
var chassisData;
return nodeApi.getNodeCatalogSourceById(identifier, 'UCS')
.then(function(catalog) {
chassisData = catalog.data;
chassisData.power = (chassisData.power === 'ok') ? 'On' : 'Off';
return nodeApi.getNodeCatalogSourceById(identifier, 'UCS:locator-led');
})
.then(function(catalog) {
chassisData.uid = (catalog.data.oper_state === 'on') ? 'Lit' : 'Off';
return chassisData;
})
.catch(function(err) {
if (err instanceof Errors.NotFoundError) {
chassisData.uid = 'Unknown';
return chassisData;
}
throw err;
});
}
});
case 'sdrData':
return nodeApi.getPollersByNodeId(identifier)
.filter(function(poller) {
return poller.config.command === 'sdr';
}).spread(function(poller) {
return taskProtocol.requestPollerCache(poller.id, { latestOnly: true });
}).then(function(data) {
return data[0].sdr;
});
case 'ucsThermal':
var ucsThermalPollerData = {};
var ucsThermalKeyMap = {
'ucs.powerthermal': 'computeMbTempStats',
'ucs.fan': 'equipmentFanStats'
};
return nodeApi.getPollersByNodeId(identifier)
.filter(function(poller) {
return poller.config.command === 'ucs.powerthermal' ||
poller.config.command === 'ucs.fan';
})
.map(function(poller) {
return taskProtocol.requestPollerCache(poller.id, {latestOnly: true})
.then(function(data) {
ucsThermalPollerData[poller.config.command]
= _.isEmpty(data[0].result['equipmentChassisStats'])
? data[0].result[ucsThermalKeyMap[poller.config.command]]
: data[0].result['equipmentChassisStats'];
});
}).then(function() {
return ucsThermalPollerData;
});
case 'ucsPower':
var ucsPowerPollerData = {};
var ucsPowerKeyMap = {
'ucs.powerthermal': 'computeMbPowerStats',
'ucs.psu': 'equipmentPsuStats'
};
return nodeApi.getPollersByNodeId(identifier)
.filter(function(poller) {
return poller.config.command === 'ucs.powerthermal' ||
poller.config.command === 'ucs.psu';
}).map(function(poller) {
return taskProtocol.requestPollerCache(poller.id, {latestOnly: true})
.then(function(data) {
ucsPowerPollerData[poller.config.command]
= _.isEmpty(data[0].result['equipmentChassisStats'])
? data[0].result[ucsPowerKeyMap[poller.config.command]]
: data[0].result['equipmentChassisStats'];
});
}).then(function() {
return ucsPowerPollerData;
});
default:
return nodeApi.getNodeCatalogSourceById(identifier, dataName);
}
};
var listChassis = controller(function(req, res) {
var options = redfish.makeOptions(req, res);
options.nodes = [];
return Promise.each(nodeApi.getAllNodes({type: ['enclosure', 'redfish']}), function(node) {
options.nodes.push(node);
return Promise.each(_getEnclosedSystems(node), function(objId) {
return waterline.nodes.findByIdentifier(objId)
.then(function(obj) {
return env.get('config.redfish.computeType', 'Other', obj && obj.sku ? [ obj.sku ] : null );
})
.then(function(val) {
if(val === 'Blade') {
node.id = node.id + '.' + objId;
options.nodes.push(node);
}
});
});
}).then(function() {
return redfish.render('redfish.1.0.0.chassiscollection.json',
'ChassisCollection.json#/definitions/ChassisCollection',
options);
}).catch(function(error) {
return redfish.handleError(error, res);
});
});
var getChassis = controller(function(req, res) {
var chassisId = req.swagger.params.identifier.value.split('.')[0];
var systemId = req.swagger.params.identifier.value.split('.')[1];
var typeEnum = [
"Rack",
"Blade",
"Enclosure",
"StandAlone",
"Stand Alone",
"RackMount",
"Rack Mount",
"Card",
"Cartridge",
"Row",
"Pod",
"Expansion",
"Sidecar",
"Zone",
"Sled",
"Shelf",
"Drawer",
"Module",
"Component",
"Other" ];
return redfish.getVendorNameById(chassisId)
.then(function(result) {
chassisId = result.node.id;
var options = redfish.makeOptions(req, res, req.swagger.params.identifier.value);
if (result.vendor === 'Redfish') {
var catalogSource = req.url.replace(req.swagger.params.identifier.value, chassisId).replace(/\/$/, "");
return dataFactory(chassisId, catalogSource)
.then(function(response) {
return response.data;
})
.catch(function() {
throw new Errors.NotImplementedError(req.url + ' Is Not Implemented on this Device');
});
} else {
return Promise.all([
nodeApi.getNodeById(chassisId),
systemId ? nodeApi.getNodeById(systemId) : null
])
.tap(function (nodes) {
if (!nodes[1]) {
return Promise.each(_getEnclosedSystems(nodes[0]), function (objId) {
return waterline.nodes.findByIdentifier(objId)
.then(function (obj) {
return env.get('config.redfish.computeType', 'Other', obj && obj.sku ? [obj.sku] : null);
})
.then(function (val) {
if (val === 'Blade') {
options.contains = options.contains || [];
options.contains.push(nodes[0].id + '.' + objId);
options.chassisType = 'Enclosure';
}
});
});
} else {
if (_.includes(_getEnclosedSystems(nodes[0]), systemId)) {
options.containedBy = chassisId;
options.chassisType = 'Blade';
}
}
})
.spread(function (chassisNode) {
var systems = _getEnclosedSystems(chassisNode);
var chassisName = chassisNode.name;
if (!_.isEmpty(systems)) {
if (chassisName.indexOf("sys/chassis") !== -1) {
return Promise.props({
systems: Promise.map(systems, nodeApi.getNodeById.bind(nodeApi)),
catData: dataFactory(chassisNode.id, 'catData'),
chassisData: dataFactory(chassisNode.id, 'chassisData')
});
}
else {
return Promise.props({
systems: Promise.map(systems, nodeApi.getNodeById.bind(nodeApi)),
catData: dataFactory(systemId || systems[0], 'catData'),
chassisData: dataFactory(systemId || systems[0], 'chassisData')
});
}
}
return {};
})
.then(function (obj) {
if (!_.get(options, 'chassisType')) {
options.chassisType = 'Other';
_.forEach(typeEnum, function (type) {
var re = new RegExp(type, 'g');
var match = obj.catData.chassis.type.match(re);
if (match) {
options.chassisType = match.toString().replace(/ /g, '');
}
});
}
options.obm = ['RackHD'];
options.targetList = [];
_.forEach(obj.systems, function (system) {
options.targetList.push(system.id);
});
return redfish.render('redfish.1.0.0.chassis.1.0.0.json',
'Chassis.v1_4_0.json#/definitions/Chassis',
_.merge({}, obj, options));
});
}
})
.catch(function (error) {
return redfish.handleError(error, res);
});
});
var getThermal = controller(function(req, res) {
var chassisId = req.swagger.params.identifier.value.split('.')[0];
var systemId = req.swagger.params.identifier.value.split('.')[1];
return redfish.getVendorNameById(chassisId)
.then(function(result) {
chassisId = result.node.id;
var options = redfish.makeOptions(req, res, req.swagger.params.identifier.value);
if (result.vendor === 'Redfish') {
var catalogSource = req.url.replace(req.swagger.params.identifier.value, chassisId).replace(/\/$/, "");
return dataFactory(chassisId, catalogSource)
.then(function(response) {
return response.data;
})
.catch(function() {
throw new Errors.NotImplementedError(req.url + ' Is Not Implemented on this Device');
});
} else if (result.vendor === 'Cisco') {
return dataFactory(chassisId, 'ucsThermal')
.then(function(thermalData) {
var ucsPowerThermalData = thermalData['ucs.powerthermal'];
var ucsFanData = thermalData['ucs.fan'];
var regxObj = /^.*_temp_sen(.(?!avg|min|max$))*$/;
options.fanList = [];
options.tempList = [];
_.keys(ucsPowerThermalData[0])
.filter(function(key) {
return regxObj.test(key);
}).map(function(key) {
options.tempList.push(
{
'sensorId': key,
'sensorReading': ucsPowerThermalData[0][key]
}
);
});
ucsFanData.map(function (item) {
options.fanList.push(
{
'sensorId': item.dn.split('/').slice(0, -1).join('/'),
'sensorReading': item.speed
}
);
});
return redfish.render('redfish.1.0.0.thermal.1.0.0.json',
'Thermal.v1_2_0.json#/definitions/Thermal',
options);
});
} else {
return dataFactory(chassisId, 'system')
.then(function(system) {
return Promise.all([
dataFactory(systemId || system, 'sdrData'),
waterline.nodes.findByIdentifier(systemId || system)
.then(function(obj) {
return env.get('config.redfish', '{}', obj && obj.sku ? [ obj.sku ] : null );
})
]);
})
.spread(function(sdrData, redfishCfg) {
var defaultFilter = (_.get(redfishCfg, 'computeType') === 'Blade') ? EntityIdFilterDefaults : [];
var sensorFilter = _.get(redfishCfg, 'sensorFilter', defaultFilter);
options.tempList = [];
options.fanList = [];
_.forEach(sdrData,function(sdr) {
var included = _.includes(sensorFilter, sdr.entityId.split('.')[0]);
if(systemId ? included : !included ) {
if (sdr.sensorType === 'Temperature') {
options.tempList.push(sdr);
}
if (sdr.sensorType === 'Fan' ) {
options.fanList.push(sdr);
}
}
});
return redfish.render('redfish.1.0.0.thermal.1.0.0.json',
'Thermal.v1_2_0.json#/definitions/Thermal',
options);
});
}
})
.catch(function(error) {
return redfish.handleError(error, res);
});
});
var getPower = controller(function(req, res) {
var chassisId = req.swagger.params.identifier.value.split('.')[0];
var systemId = req.swagger.params.identifier.value.split('.')[1];
return redfish.getVendorNameById(chassisId)
.then(function(result) {
chassisId = result.node.id;
var options = redfish.makeOptions(req, res, req.swagger.params.identifier.value);
if (result.vendor === 'Redfish') {
var catalogSource = req.url.replace(req.swagger.params.identifier.value, chassisId).replace(/\/$/, "");
return dataFactory(chassisId, catalogSource)
.then(function(response) {
return response.data;
})
.catch(function() {
throw new Errors.NotImplementedError(req.url + ' Is Not Implemented on this Device');
});
} else if (result.vendor === 'Cisco') {
return dataFactory(chassisId, 'ucsPower')
.then(function(powerData) {
var ucsPowerThermalData = powerData['ucs.powerthermal'];
var ucsPsuData = powerData['ucs.psu'];
options.voltageList = [];
options.wattsList = [];
ucsPowerThermalData.map(function(item) {
options.voltageList.push(
{
'sensorId': item.dn.split('/').slice(0, -1).join('/'),
'sensorReading': item.input_voltage
}
);
});
ucsPsuData.map(function(item) {
options.wattsList.push(
{
'sensorId': item.dn.split('/').slice(0, -1).join('/'),
'sensorReading': item.psu_wattage
}
);
});
return redfish.render('redfish.1.0.0.power.1.0.0.json',
'Power.v1_2_1.json#/definitions/Power',
options);
});
} else {
return dataFactory(chassisId, 'system')
.then(function(system) {
return Promise.all([
dataFactory(systemId || system, 'sdrData'),
waterline.nodes.findByIdentifier(systemId || system)
.then(function(obj) {
return env.get('config.redfish', '{}', obj && obj.sku ? [ obj.sku ] : null );
})
]);
})
.spread(function(sdrData, redfishCfg) {
var defaultFilter = (_.get(redfishCfg, 'computeType') === 'Blade') ? EntityIdFilterDefaults : [];
var sensorFilter = _.get(redfishCfg, 'sensorFilter', defaultFilter);
options.voltageList = [];
options.wattsList = [];
_.forEach(sdrData,function(sdr) {
var included = _.includes(sensorFilter, sdr.entityId.split('.')[0]);
if(systemId ? included : !included ) {
if (sdr.sensorType === 'Voltage') {
options.voltageList.push(sdr);
}
if (sdr.sensorReadingUnits === 'Watts') {
options.wattsList.push(sdr);
}
}
});
return redfish.render('redfish.1.0.0.power.1.0.0.json',
'Power.v1_2_1.json#/definitions/Power',
options);
});
}
})
.catch(function(error) {
return redfish.handleError(error, res);
});
});
module.exports = {
listChassis: listChassis,
getChassis: getChassis,
getThermal: getThermal,
getPower: getPower
};