RackHD/on-tasks

View on GitHub
lib/jobs/generate-enclosure.js

Summary

Maintainability
C
1 day
Test Coverage
// Copyright 2015, EMC, Inc.

'use strict';

var di = require('di');

module.exports = generateEnclosureJobFactory;
di.annotate(generateEnclosureJobFactory, new di.Provide('Job.Catalog.GenerateEnclosure'));
di.annotate(generateEnclosureJobFactory, new di.Inject(
    'Job.Base',
    'Services.Waterline',
    'JobUtils.CatalogSearchHelpers',
    'Logger',
    'Util',
    'Promise',
    'Assert',
    '_'
));

function generateEnclosureJobFactory(
    BaseJob,
    waterline,
    catalogSearch,
    Logger,
    util,
    Promise,
    assert,
    _
) {

    var logger = Logger.initialize(generateEnclosureJobFactory);

    /**
     *
     * @param {Object} [options]
     * @constructor
     */
    function GenerateEnclosureJob(options, context, taskId) {
        GenerateEnclosureJob.super_.call(this, logger, options, context, taskId);

        this.nodeId = context.target || options.nodeId;
        this.enclConst = Object.freeze({
            type: 'enclosure',
            namePrefix: 'Enclosure Node ',
            relationEncl: 'encloses',
            relationEnclBy: 'enclosedBy',
            snPath: [
                {
                    src: 'ipmi-fru',
                    entry: 'Builtin FRU Device (ID 0).Chassis Serial'
                },
                {
                    src: 'dmi',
                    entry: 'Chassis Information.Serial Number'
                },
                {
                    src: 'ipmi-fru',
                    entry: 'Builtin FRU Device (ID 0).Product Serial'
                },
                {
                    src: 'dmi',
                    entry: 'System Information.Serial Number'
                }
            ]
        });
        assert.isMongoId(this.nodeId);
    }

    util.inherits(GenerateEnclosureJob, BaseJob);


    /**
     * @memberOf GenerateEnclosureJob
     * @returns {Promise}
     */
    GenerateEnclosureJob.prototype._run = function run() {
        var self = this;
        var enclNode;

        Promise.any([
            self._findSerialNumber(self.enclConst.snPath[0]),
            self._findSerialNumber(self.enclConst.snPath[1]),
            self._findSerialNumber(self.enclConst.snPath[2]),
            self._findSerialNumber(self.enclConst.snPath[3])
        ])
        .then(function(nodeSn){
            var enclName = self.enclConst.namePrefix + nodeSn;
            var enclData = {
                name: enclName,
                type: self.enclConst.type,
                relations: []
            };
            return waterline.nodes.findOrCreate({ type: self.enclConst.type, name: enclName}, enclData);
        })
        .then(function(node) {
            if (!node) {
                return Promise.reject('Could not create enclosure node');
            }
            enclNode = node;
            logger.debug('findOrCreate enclosure', {
                id: self.nodeId,
                enclosure: enclNode.id
            });
        })
        .then(function () {
            // Add compute relations into enclosure node
            return self.addRelation(enclNode, self.enclConst.relationEncl, self.nodeId);
        })
        .then(function () {
            // Add enclosure node info into compute node
            return waterline.nodes.findByIdentifier(self.nodeId)
            .then(function (node) {
                if (!node) {
                    return Promise.reject('Could not find node with identifier ' + self.nodeId);
                }
                return self.addRelation(node, self.enclConst.relationEnclBy, enclNode);
            });
        })
        .then(function () {
            self._done();
        })
        .catch(function (err) {
            self._done(err);
        });

    };

    /**
     * Find serial number from node's catalog
     * @param {object} enclPath path in catalogs to get serail number
     * @return {Promise}
     */
    GenerateEnclosureJob.prototype._findSerialNumber = function (enclPath) {
        var self = this;

        return waterline.catalogs.findMostRecent({
            node: self.nodeId,
            source: enclPath.src
        }).then(function (catalog) {
            var nodeSn = catalogSearch.getPath(catalog.data, enclPath.entry);
            var regex = /^\S+$/;

            if (!nodeSn) {
                throw new Error("Could not find serial number in source: " + enclPath.src);
            }
            else if (nodeSn.match(regex) === null) {
                throw new Error("No valid serial number in SN: " + nodeSn);
            }
            else {
                return nodeSn;
            }
        });
    };

    /**
     * This function is duplicated with on-http/lib/service/node-api-service.js/addRelation
     * Add the given target nodes to the given relationType on the given node. Fail
     * silently with missing arguments. If a relation does not already exist on the node
     * create it, otherwise append to the existing one.
     * @param  {Object} node - node whose relation needs to be updated
     * @param  {String} type - relation type that needs to be updated
     * @param  {String[] | Object[]}  targets - nodes or ids in relation type that to be added
     * @return {Object}  the updated node
     */
    GenerateEnclosureJob.prototype.addRelation = function addRelation(node, type, targets) {
        if (!(node && type && targets)) {
            return;
        }
        return waterline.nodes.addFieldIfNotExistByIdentifier(node.id, "relations", [])
        .then(function(){
            var relationsItemToBeAdded = {
                relations: [{relationType: type, targets: []}]
            };
            var existSign = [{relationType: type}];
            return waterline.nodes.addListItemsIfNotExistByIdentifier(
                node.id, 
                relationsItemToBeAdded, 
                existSign
            );
        })
        .then(function(modifiedNode){
            if (!modifiedNode){
                return node;
            }
            return modifiedNode;
        })
        .then(function(modifiedNode){
            var targetsItems = _.map([].concat(targets), function(targetNode) {
                targetNode = targetNode.id || targetNode;
                if(targetNode === node.id ) {
                    return Promise.reject(new Error('Node cannot have relationship '+type+' with itself'));
                }
                return targetNode;
            });
            var index = _.findIndex(modifiedNode.relations, { relationType: type });
            var field = ["relations.", String(index),".targets"].join("");
            var targetsToBeAdded = {};
            targetsToBeAdded[field] = _.uniq(targetsItems);

            // Can not make sure prevent every exception in high concurrency.
            if (type === 'containedBy' && 
                modifiedNode.relations[index].targets.length + targets.length > 1) {
                return Promise.reject(new Error("Node "+node.id+" can only be contained by one node"));
            }

            // Compute node can only have one enclosure target.
            if (type === "enclosedBy") {
                var targetsToBeRemoveded = {};
                targetsToBeRemoveded[field] = [modifiedNode.relations[index].targets[0]];
                return waterline.nodes.removeListItemsByIdentifier(
                    node.id, targetsToBeRemoveded
                )
                .then(function(){
                    return targetsToBeAdded;
                });
            }
            return targetsToBeAdded;
        })
        .then(function(targetsToBeAdded){
            return waterline.nodes.addListItemsIfNotExistByIdentifier(
                 node.id, targetsToBeAdded
            );
        });
    };
    return GenerateEnclosureJob;
}