RackHD/on-tasks

View on GitHub
lib/utils/job-utils/ipmi-parser.js

Summary

Maintainability
F
3 days
Test Coverage
// Copyright 2015, EMC, Inc.
// jshint bitwise: false

// Javascript port of the ipmitool sensors parser from
// http://docs.openstack.org/developer/ironic/
//   _modules/ironic/drivers/modules/ipmitool.html

"use strict";

var di = require('di');

module.exports = parseSensorsDataFactory;
di.annotate(parseSensorsDataFactory, new di.Provide('JobUtils.IpmiCommandParser'));
di.annotate(parseSensorsDataFactory, new di.Inject('Assert', '_'));
function parseSensorsDataFactory(assert, _) {
    //UID status code, 0 = Off, 1 = Temporary On , 2 = On, 3 = reserved
    var uidStatus = ['Off', 'Temporary On', 'On', 'Reserved'];

    function parseSelInformationData(selData) {
        var lines = selData.trim().split('\n');
        lines.shift();
        return _.transform(lines, function(result, line) {
            var split = line.split(' : ');
            result[split.shift().trim()] = split.shift().trim();
        }, {});
    }
    
    function parseSelData(selData) {
        if (_.isEmpty(selData)) {
            return;
        }
        assert.string(selData);
        var lines = selData.trim().split('\n');
        var sel = [];
        for (var i = 0; i < lines.length; i+=1) {
            if (lines[i].indexOf('SEL Entry') >= 0) {
                continue;
            }
            var rows = lines[i].split(',');
            if (rows.length !== 6) {
                continue;
            }
            var sensorInfo = rows[3].split(' ');
            if(sensorInfo.length < 3) {
                sensorInfo.push('00');
            }
            if(rows[1].indexOf('Pre-Init') > -1) {
                rows[1] = "";
                rows[2] = "";
            }
            var kv = {
                logId: rows[0],
                date: rows[1],
                time: rows[2],
                sensorType: sensorInfo.slice(0, -1).join(' '),
                sensorNumber: sensorInfo[sensorInfo.length-1],
                'event': rows[4],
                value: rows[5]
            };
            sel.push(kv);
        }
        return sel;
    }

    /**
     * Parse full SEL entry output into key/value pairs into an object list
     * information:
     * "SEL Record ID          : 0063
     *   Record Type           : 02
     *   Timestamp             : 01/01/1970 00:29:36
     *   Generator ID          : 0000
     *   EvM Revision          : 04
     *   Sensor Type           : Memory
     *   Sensor Number         : 87
     *   Event Type            : Sensor-specific Discrete
     *   Event Direction       : Assertion Event
     *   Event Data            : a00040
     *   Description           : Correctable ECC"
     * @param selData
     * @returns {*}
     */
    function parseSelDataEntries(selData) {
        var parsedSel = [];
        var obj;

        if(_.isEmpty(selData)) {
            return [];
        }

        var lines = selData.trim().split('\n');
        _.forEach(lines, function(line) {
            if(line === "" || _.startsWith(line,'------') || _.startsWith(line,'<<'))
            {//removes the lines that we don't care about
                return;
            }
            var kvPair = _.trim(line).split(' : ');
            var key = _.trim(kvPair[0]);
            var value = _.trim(kvPair[1]);

            if (key.match(/SEL Record/)) {
                if (!_.isEmpty(obj) ) {
                    parsedSel.push(obj);
                }
                obj = {};
            }

            obj[key] = value;
        });
        
        if (!_.isEmpty(obj)) {
            parsedSel.push(obj);
        }

        return parsedSel;
    }

    /**
     * parse/extract text output from IPMI call into key/value data for the SDR
     * information
     *
     * @param sdrData
     * @returns {*}
     */
    function parseSdrData(sdrData) {
        var sdrArray = sdrData.split('\n\n');
        var sdrObjArray = _.transform(sdrArray, function(result, sdr) {
            var lines = sdr.trim().split('\n');
            var key = '';
            var value = '';
            var sdrObj = {
                sensorId: "",
                entityId: "",
                entryIdName: "",
                sdrType: "",
                sensorType: "",
                sensorReading: "",
                sensorReadingUnits: "",
                nominalReading: "",
                normalMinimum: "",
                normalMaximum: "",
                statesAsserted: []
            };

            // The value associated with these keys is a list
            var listKeys = [
                'statesAsserted',
                'assertionsEnabled',
                'deassertionsEnabled'
            ];

            // Expect all of these keys to have non-empty values.
            var requiredKeys = [
                'sensorId',
                'entityId',
                'sdrType',
                'sensorType',
                'sensorReading'
            ];

            _.forEach(lines, function(line) {
                if (line.search(':') >= 0) {
                    key = _.camelCase(line.split(':')[0]);
                    value = line.split(':')[1].trim();

                    // The sensorType key requires a bit of additional processing.  Converting
                    // to camelCase will yield 'sensorTypeDiscrete' or 'sensorTypeThreshold'
                    // We want to add an additional sdrType key with a value of Discrete or
                    // Threshold and change the key from sensorTypeXxx to sensorType.
                    if (key.search(/sensorType/) >= 0) {
                        var sensorTypeKey = key.slice(0, 'sensorType'.length);
                        var sdrTypeValue = key.slice('sensorType'.length, key.length);
                        var match = value.match(/(.+) (\(0x.+\))?/);
                        key = sensorTypeKey;
                        if (match) {
                            value = match[1].trim();
                        }
                        sdrObj.sdrType = sdrTypeValue;
                    }
                    // The sensorReading key requires a bit of additional processing.
                    // sensorReading also includes sensorReadingUnits value, and we need
                    // to extract from sensorReading
                    if (key.search(/sensorReading/) >= 0) {
                        key = "sensorReadingUnits";
                        var sensorReading = "";
                        if (value.split(/\)/)[1]) {
                            sensorReading = value.split(/\(/)[0];
                            value = value.split(/\)/)[1].trim();
                        }
                        else {
                            // Sensor that does not have any SensorReadingUnits, assign NA
                            sensorReading = value;
                            value = "";
                        }
                        sdrObj.sensorReading = sensorReading.trim();
                    }
                    // The entityId key requires a bit of additional processing.
                    // entityId also includes entryIdName value, and we need
                    // to extract from entityId.
                    if (key.search(/entityId/) >= 0) {
                        key = "entryIdName";
                        var entityId = value.split(/\(([^)]+)\)/)[0];
                        value = value.split(/\(([^)]+)\)/)[1];
                        sdrObj.entityId = entityId.trim();
                    }
                }
                else {
                        value = line.trim();
                }

                //ipmitool displays states as [state name] so remove the []
                value = value.replace('[', '').replace(']', '');
                if (key.length > 0) {
                    if (_.contains(listKeys, key)) {
                        //The key needs to be mapped to a list.
                        //There is a bit of ugliness here.
                        // 1. For some reason, ipmitool includes the sensor type name in
                        //    the state lists.  We'll remove it by omitting values that
                        //    match the sensor type name.
                        // 2. The first time the key is processed, its value is undefined.
                        //    Thus, we can't use .push since it isn't a list yet.  So check
                        //    if the key exists in the object first.
                        var sensorTypeName = _.has(sdrObj, 'sensorType') ? sdrObj.sensorType : null;
                        if (sensorTypeName === null  || !sensorTypeName.match(value)) {
                            if (_.has(sdrObj, key)) {
                                sdrObj[key].push(value);
                            } else {
                                sdrObj[key] = [value];
                            }
                        }
                    } else {
                        sdrObj[key] = value;
                    }
                }
            });
            
            // For ipmitool v1.8.11, sensor reading '0h' doesn't exist
            // It is presumed that ipmitool v1.8.13 generates '0h' if sensor reading doesn't exit
            // To accommodate both v1.8.11 and v1.8.13, sensor reading will be automatically
            // generated if it doesn't exist
            if (sdrObj.sensorReading === "") {
               sdrObj.sensorReading = "0h";
               sdrObj.sensorReadingUnits = "";
            }

            // For ipmitool v1.8.11 sdr type is "Discrete" and "Analog"
            // For ipmitool v1.8.13 sdr type is "Discrete" and "Threshold"
            // Align sdr type to "Discrete" and "Threshold"
            if(sdrObj.sdrType === "Analog") {
                sdrObj.sdrType = "Threshold";
            }

            // if sdrObj has a non-empty value in every required key.
            if (_.every(requiredKeys, function(key) {return _.has(sdrObj, key) && sdrObj[key];})) {
                result.push(sdrObj);
            }
        });
        return sdrObjArray;
    }

    /**
     * parse/extract text output from IPMI call into raw data for the chassis status
     *
     * @param statusData
     * @returns {uid: 'Off|Temporary On|On|Reserved', power: true|false}
     */
    function parseChassisData(statusData) {
        // sample data : 20 00 60 71 or 20 00 60
        var numStr = statusData.replace(/\s/g, '');
        assert.ok(numStr.match(/^[0-9A-Fa-f]{8}$|^[0-9A-Fa-f]{6}$/),
                  "Invalid chassis status output : " + statusData);
        var uidCode = parseInt(numStr[4], 16);
        var powerCode = parseInt(numStr[1], 16);

        // UID status need last 2 bit , so use mask 0x3(0011)
        // Power status need last bit , so use mask 0x1(0001)
        return {
            uid: uidStatus[uidCode & 0x3],
            power: (powerCode & 0x1) === 1
        };
    }

    /**
     * parse/extract text output from IPMI call into a list of drive health status
     *
     * @param {string} drive health data
     * @returns {Array} Returns a list of drive name and health status
     */
    function parseDriveHealthData(driveHealthData) {
        var lines = driveHealthData.trim().split('\n');
        var statusArr = [];
        for (var i = 0; i < lines.length; i+=1) {
            var items = lines[i].trim().split(',');
            if (items[2] !== 'ok') {
                continue;
            }
            statusArr.push({
                name: items.shift(),
                status: items.pop() || 'Not Present' //if empty means drive not present
            });
        }
        return statusArr;
    }

    return {
        parseSelInformationData: parseSelInformationData,
        parseSelData: parseSelData,
        parseSelDataEntries: parseSelDataEntries,
        parseSdrData: parseSdrData,
        parseChassisData: parseChassisData,
        parseDriveHealthData: parseDriveHealthData
    };
}