RIFT OSM R1 Initial Submission
[osm/SO.git] / rwlaunchpad / mock / data_model.js
diff --git a/rwlaunchpad/mock/data_model.js b/rwlaunchpad/mock/data_model.js
new file mode 100644 (file)
index 0000000..ef56c68
--- /dev/null
@@ -0,0 +1,569 @@
+/*
+ *  This module provides the data model layer for the Launchpad Mocklet
+ */
+
+var util = require('util');
+var uuid = require('node-uuid');
+var _ = require('lodash');
+
+// Our modules
+var simmp_module = require('./simmp.js');
+
+// Data packages
+// TODO: Make these parameters to pass to the data model
+// instead of hardcoding them as requires here
+var simmp_rules = require('./data/simmp-rules.json');
+var nsr_templates = require('./data/nsr-templates.json');
+var vnfr_templates = require('./data/vnfr-templates.json');
+
+/*
+ * Generic  to throw on data model exceptions
+ */
+function DataModelException(message) {
+    this.message = message;
+    this.name = "DataModelException";
+}
+
+/*
+ * This 
+ * This function is temporary until all needed features are implemented in this mocklet
+ */
+function NotImplementedException(message) {
+    this.message = "You have fallen off the edge of the world: "+message;
+    this.name = 'NotImplementedException';
+}
+
+
+/*
+ * Class to handle simulating events over time for monitoring params
+ */
+MonitoringParam = function(values, time_function) {
+    this.values = values;
+    this.timeFunc = time_function;
+}
+
+MonitoringParam.prototype.timeStep = function(elapsed_seconds) {
+    this.values.current_value = this.timeFunc(this.values.current_value,
+            elapsed_seconds);
+    return this.values.current_value;
+};
+
+/*
+ * DataModel constructor
+ *
+ * Arguments
+ *   restconf_host - Host name and port. eg: 'localhost:8008'
+ */
+DataModel = function (restconf_host) {
+    this.restconf_host = restconf_host ? restconf_host : "localhost:8008";
+
+    this.simmp = new simmp_module.SimMp(simmp_rules);
+    if (!this.simmp) {
+        throw "simmp failed to initialize";
+    }
+    // Time data for event simulation (monitoring params)
+    this.start_time = Date.now();
+    this.previous_time =this.start_time;
+
+    // Store descriptors
+    this.descriptors = { nsd: {}, vnfd: {}, vld: {} };
+
+    // Store instance config data. Currently only NS Yang implements config data
+    this.config_records = { nsr: {}, vnfr: {}, vlr: {} };
+
+    // Stores Virtual Network Function instance records
+    this.vnfr_records = { };
+
+    // Stores Network Service instance operational records
+    this.ns_opdata_records = { };
+
+    // Manage which mock data to use next
+    this.vnfr_template_index = 0;
+    this.nsr_template_index = 0;
+
+    // Operational (running) state for opdata records
+    // 'on', 'off'
+    // TBD: do restarting
+    this.opstate = { nsr: {}, vnfr: {} };
+
+    // Store MonitoringParam objects
+    this.monitoring_params = {nsr: {}, vnfr: {} };
+}
+
+
+/*
+ * creates a descriptor name from the record name
+ */
+DataModel.prototype.rec2desc = function (record_type) {
+    if (record_type.charAt(record_type.lenth-1) == 'r') {
+        return record_type.slice(0, -1)+'d';
+    } else if (["ns","vnf","vl"].indexOf(record_type_) != -1) {
+        return record_type + 'd';
+    } else {
+        throw new DataModelException('"%s" is not a supported record type', record_type);
+    }
+};
+
+DataModel.prototype.setDescriptor = function(descriptor_type, descriptor) {
+        if (!this.descriptors.hasOwnProperty(descriptor_type)) {
+            throw new DataModelException('"%s" is not a supported descriptor type', descriptor_type);
+        }
+
+        this.descriptors[descriptor_type][descriptor.id] = descriptor;
+};
+
+DataModel.prototype.setConfigRecord = function(record_type, record) {
+         if (!this.config_records.hasOwnProperty(record_type)) {
+            throw new DataModelException('"%s" is not a supported record type', record_type);
+        }
+
+        this.config_records[record_type][record.id] = record;
+};
+
+DataModel.prototype.findConfigRecord = function(record_type, record_id) {
+        if (this.config_records.hasOwnProperty(record_type)) {
+            return this.config_records[record_type][record_id];
+        } else {
+            return null;
+        }
+};
+
+/*
+ *
+ */
+DataModel.prototype.updateControlParam = function(record_type, record_id,
+        control_id, value) {
+    if (record_type == 'vnfr') {
+        var record = this.vnfr_records[record_id];
+    } else {
+        var record = this.ns_opdata_records[record_id];
+    }
+    // find the control param
+    if ('control_param' in record) {
+        for (var i=0; i < record.control_param.length; i++) {
+            if (control_id == record.control_param[i].id) {
+                // Make sure value is within min and max values
+                if (value >= record.control_param[i].min_value &&
+                    value <= record.control_param[i].max_value) {
+
+                    record.control_param[i].current_value = value;
+                    return 'SUCCESS';
+                } else {
+                    var errmsg = 'value "'+value+'" out of range. '+
+                        'Needs to be within '+ record_control_param[i].min_value +
+                        ' and ' + record_control_param[i].max_value;
+                    throw new DataModelException(errmsg);
+                }
+            }
+        }
+    } else {
+        var errmsg = 'Record type "' + record_type + '" with id "'+
+            record_id + '" does not have any control params';
+        throw new DataModelException(errmsg);
+    }
+};
+
+/*
+ * NS functions
+ *
+ * General comments on NS instance config/opdata:
+ *  For each ns-instance-config, the descriptor needs to be added first
+ */
+
+// TODO: Consolidate the template handling functions
+DataModel.prototype.nextNsrTemplate = function() {
+    var nsr_template = _.clone(nsr_templates[this.nsr_template_index], true);
+    this.nsr_template_index += 1;
+    if (this.nsr_template_index >= nsr_templates.length) {
+        this.nsr_template_index = 0;
+    }
+    return nsr_template;
+};
+
+DataModel.prototype.getNsdConnectionPoints = function(nsd_id) {
+    var nsd =  this.descriptors['nsd'][nsd_id];
+    if (!nsd) {
+        throw new DataModelException("NSD ID '%s' does not exist", nsd_id);
+    }
+    // console.log("\n\nnsd = %s", JSON.stringify(nsd));
+    return nsd['connection_point'];
+};
+
+
+DataModel.prototype.createNsrControlParams = function(ns_instance_config_id) {
+    // TODO: find all VNFDs associated with this NS instance
+    // then either call this.createVnfrControlParams if you want to talk
+    // VNFR specific control params or we can generalize 'createVnfrControlParams'
+    // to pass in 'record_id' instead of vnfr_id.
+    //
+    var control_params = [];
+
+    return control_params;
+};
+
+/*
+ * Sets an ns-instance-config object record and creates an
+ * ns-instance-opdata record.
+ *
+ * If the NS instance opdata record matching the id of the ns-instance-config
+ * already exists, then remove the ns-instance-opdate record and reconstruct.
+ */
+DataModel.prototype.setNsInstanceConfig = function(ns_instance_config) {
+    // we are updating an existing ns-instance record set
+    // There is an issue that subsequent 'PUT' actions do not transfer
+    // the whole data to the mocklet. So we need to retrieve the existingt
+    // ns-instance-config to get the nsd-ref
+
+    // TODO: Consider creating a 'set_or_update' method for ns-instance-config
+    var ns_config = this.findConfigRecord('nsr', ns_instance_config.id);
+    if (ns_config) {
+        ns_config.admin_status = ns_instance_config.admin_status;
+    } else {
+        this.setConfigRecord('nsr', ns_instance_config);
+        ns_config = ns_instance_config;
+    }
+    if (ns_config.id in this.ns_opdata_records) {
+        delete this.ns_opdata_records[ns_config.id];
+    }
+    // if ns-instance-config is 'ENABLED', then create an ns-instance-opdata
+    if (ns_config.admin_status == 'ENABLED') {
+        ns_opdata = this.generateNsInstanceOpdata(ns_config);
+        // set the ns instance opdata. Doesn't matter if it already exists
+        this.ns_opdata_records[ns_opdata.ns_instance_config_ref] = ns_opdata;
+    }
+};
+
+DataModel.prototype.generateMonitoringParams = function(descriptor_type, descriptor_id) {
+    console.log('Called generateMonitoringParams');
+    if (!(descriptor_type in this.descriptors)) {
+        throw DataModelException('descriptor type "%s" not found');
+    }
+    var descriptor = this.descriptors[descriptor_type][descriptor_id];
+    var a_simmp = this.simmp;
+    if (descriptor) {
+        if ('monitoring_param' in descriptor) {
+            return descriptor['monitoring_param'].map(function(obj) {
+                var simFunc = a_simmp.createSimMonitorFunc(obj);
+                return new MonitoringParam(_.clone(obj, true), simFunc);
+            });
+        } else {
+            console.log('Descriptor(type=%s) with (id=%s) does not have ' +
+               'monitoring params', descriptor_type, descriptor_id);
+            return [];
+        }
+    } else {
+        throw new DataModelException("Cannot find descriptor %s with id '%s'",
+                descriptor_type, descriptor_id);
+    }
+};
+
+DataModel.prototype.updateMonitoringParams = function(instance_type, instance_id) {
+    var sim_mp = this.monitoring_params[instance_type][instance_id];
+    if (sim_mp) {
+        var time_now = Date.now();
+        var elapsed_seconds = (time_now - this.previous_time) / 1000;
+        var monitoring_params = sim_mp.map(function(obj) {
+            obj.timeStep(elapsed_seconds);
+            return obj.values;
+        });
+        this.previous_time = time_now;
+        return monitoring_params;
+    } else {
+        // TODO: Figure out hosw we want to handle this case
+        return [];
+    }
+};
+
+/*
+ * Creates an ns-instance-opdata object, but does not add it to the data
+ * store.
+ */
+DataModel.prototype.generateNsInstanceOpdata = function (ns_config) {
+    var nsr_template = this.nextNsrTemplate();
+
+    // HACK: We need to get control and action param from the nsr
+    // or have a function that synchronizes the next array element in
+    // the templates
+    var vnfr_template = this.nextVnfrTemplate();
+
+    var nsd_id = ns_config.nsd_ref;
+    var connection_points = this.getNsdConnectionPoints(ns_config.nsd_ref);
+    var sim_mp = this.generateMonitoringParams('nsd', nsd_id);
+    // save for using in update
+    this.monitoring_params['nsr'][ns_config.id] = sim_mp;
+    var monitoring_params = sim_mp.map(function(obj) {
+        // not time stepping when we create them
+        return obj.values;
+    });
+
+    return {
+        ns_instance_config_ref: ns_config.id,
+        'connection_point' : _.clone(connection_points, true),
+        epa_param: _.clone(nsr_template['epa_param'], true),
+        // NOTE: Remarked out until nfvi metrics figured out
+        //nfvi_metric: _.clone(nsr_template['nfvi_metric'], true),
+        monitoring_param: monitoring_params,
+        //monitoring_param: _.clone(nsr_template['monitoring_param'], true),
+        create_time: nsr_template['create_time'],
+        action_param: vnfr_template['action_param'],
+        // TODO: control_param: this.createNsrControlParams(ns_config.id);
+        control_param: vnfr_template['control_param']
+    };
+};
+
+DataModel.prototype.getNsInstanceOpdata = function() {
+    var opdata_records = [];
+    var config_records = this.config_records['nsr'];
+    for (config_record_id in config_records) {
+        if (config_records[config_record_id]['admin_status'] == 'ENABLED') {
+            console.log('Is ENABLED: ns-instance-config record with id %s', config_record_id);
+
+            ns_op_rec = this.ns_opdata_records[config_record_id];
+            if (ns_op_rec) {
+                // TODO: update monitoring params
+                ns_op_rec.monitoring_param = this.updateMonitoringParams(
+                        'nsr', config_record_id);
+                opdata_records.push(ns_op_rec);
+            } else {
+                console.log('NO RECORD FOUND for ns config id: %s', config_record_id);
+            }
+        } else {
+            console.log('Either no admin status record or not enabled');
+        }
+    }
+    return opdata_records;
+};
+
+
+/* =============
+ * VNF functions
+ * =============
+ */
+
+/*
+ * Gets the next VNFR template from the array of VNFR templates and 
+ * increments the VNFR template counter. Wraps back to the first VNFR
+ * template when the last one is used.
+ */
+DataModel.prototype.nextVnfrTemplate = function() {
+    var vnfr_template = _.clone(vnfr_templates[this.vnfr_template_index], true);
+    this.vnfr_template_index += 1;
+    if (this.vnfr_template_index >= vnfr_templates.length) {
+        this.vnfr_template_index = 0;
+    }
+    return vnfr_template;
+}
+
+/*
+ * Arguments
+ *  vnfd - VNF Descriptor object
+ *  vnfr_id - VNFR unique identifier
+ *  host  - host name and port
+ */
+DataModel.prototype.createVnfrActionParams = function(vnfd, vnfr_id) {
+    // Canned start, stop for now
+    // TBD: read action params from VNFD and create here
+    // Use
+    var action_param = [
+        {
+            id: uuid.v1(),
+            name: "Start Me",
+            description: "Start this VNFR",
+            group_tag: "start-vnfr",
+            url: "https://"+this.restconf_host+"/api/operations/start-vnfr",
+            operation: "POST",
+            payload: '{"start-vnfr": { "id": "'+vnfr_id+'"}}'
+        },
+        {
+            id: uuid.v1(),
+            name: "Stop Me",
+            description: "Stop this VNFR",
+            group_tag: "stop-vnfr",
+            url: "https://"+this.restconf_host+"/api/operations/stop-vnfr",
+            operation: "POST",
+            payload: '{"stop-vnfr": { "id": "'+vnfr_id+'"}}'
+        }
+    ];
+    return action_param;
+};
+
+DataModel.prototype.createVnfrControlParams = function(vnfd, vnfr_id,
+        vnfr_template) {
+    console.log("Called Datamodel.prototype.createVnfrControlParams");
+    if (vnfr_template) {
+        console.log("returning clone of vnfr_template['control_param']");
+        return _.clone(vnfr_template['control_param'], true);
+    } else {
+        if (vnfd.control_param) {
+            console.log("VNFD's control-param="+JSON.stringify(vnfd.control_param));
+            var a_restconf_host = this.restconf_host;
+            var cp_arry = _.clone(vnfd.control_param, true);
+            var control_params = vnfd.control_param.map(function(obj) {
+                var cp = _.clone(obj, true);
+                cp.url = util.format(cp.url, a_restconf_host);
+                console.log("\ncontrol-param payload before:"+ cp.payload);
+                cp.payload = util.format(cp.payload, vnfr_id);
+                console.log("\ncontrol-param payload after:"+ cp.payload+"\n");
+                return cp;
+            });
+            return control_params;
+        } else {
+            return [];
+        }
+        throw new NotImplementedException("createVnfrControlParam: non-template");
+    }
+}
+
+/*
+ * Creates a new VNFR base on the VNFD in the argument.
+ * This method is intended to not have side effects, otherwise
+ * just put this code in this.addVnfData
+ */
+DataModel.prototype.createVnfr = function(vnfd) {
+    //var vnfr_template = this.nextVnfrTemplate();
+    var vnfr_id = uuid.v1();
+
+    return {
+        id: vnfr_id,
+        // Hack: Copy the VNFD values but append '-Record' to end
+        name: vnfd.name + ' Record',
+        short_name: vnfd.short_name + '_REC',
+        vendor: vnfd.vendor,
+        description: vnfd.description,
+        version: vnfd.version,
+        vnfd_ref: vnfd.id,
+        internal_vlr: [],
+        // Even though this is in the Yang, it doesn't exist in the
+        // instantiated model:
+        // 'internal_connection_point_ref': [],
+        action_param: this.createVnfrActionParams(vnfd, vnfr_id),
+        //control_param: _.clone(vnfr_template['control_param'], true)
+        control_param: this.createVnfrControlParams(vnfd, vnfr_id)
+    };
+};
+
+
+/*
+ * Creates and adds a new VNFD and matching VNFR record to our data store
+ *
+ * TODO: Might need to be updated so we create a VNFR when a start VNFR is called
+ *
+ */
+DataModel.prototype.addVnfData = function(vnfd) {
+    // if the vnfd does not already exist:
+    if (this.descriptors['vnfd'][vnfd.id] == null) {
+        console.log("adding new vnfd with id %s", vnfd.id);
+        this.setDescriptor('vnfd', vnfd);
+        // create a vnfr record, but without monitoring-param
+        var vnfr = this.createVnfr(vnfd);
+
+        var sim_mp = this.generateMonitoringParams('vnfd', vnfd.id);
+        // save for using in update
+        this.monitoring_params['vnfr'][vnfr.id] = sim_mp;
+        vnfr.monitoring_param = sim_mp.map(function(obj) {
+            // not time stepping when we create them
+            return obj.values;
+        });
+        this.vnfr_records[vnfr.id] = vnfr;
+    } else {
+        // do nothing
+    }
+};
+
+
+DataModel.prototype.getVnfrs = function () {
+    records = [];
+    for (vnfr_id in this.vnfr_records) {
+        // When admin-status is implemented, then return only those 'ENABLED'
+        var vnfr_record = this.vnfr_records[vnfr_id];
+        vnfr_record.monitoring_param = this.updateMonitoringParams(
+                'vnfr', vnfr_id);
+        records.push(vnfr_record);
+    }
+    return records;
+}
+
+
+// Move the following to a new VnfrManager class
+
+DataModel.prototype.startVnfr = function(vnfr_id) {
+    console.log('Calling DataModel.startVnfr with id "%s"', vnfr_id);
+
+    console.log('Here are the VNFR ids we have:');
+    for (key in this.vnfr_records) {
+        console.log('id:%s"', key);
+    }
+    //console.log('vnfr_records = %s', JSON.stringify(this.vnfr_records));
+
+    if (!(vnfr_id in this.vnfr_records)) {
+        var errmsg = 'Cannot find vnfr record with id "'+vnfr_id+'"';
+        console.error('\n\n'+errmsg+'\n\n');
+        throw new DataModelException(errmsg);
+    }
+    // Just add/set it
+    this.opstate.vnfr[vnfr_id] = 'ON';
+    return this.vnfr_records[vnfr_id];
+}
+
+DataModel.prototype.stopVnfr = function(vnfr_id) {
+    console.log('Calling DataModel.stopVnfr with id "%s"', vnfr_id);
+    if (!(vnfr_id in this.vnfr_records)) {
+        var errmsg = 'Cannot find vnfr record with id "'+vnfr_id+'"';
+        console.error(errmsg);
+        throw new DataModelException(errmsg);
+    }
+    // Just add/set it
+    this.opstate.vnfr[vnfr_id] = 'OFF';
+    return this.vnfr_records[vnfr_id];
+}
+
+DataModel.prototype.vnfrRunningState = function(vnfr_id) {
+    if (!(vnfr_id in this.vnfr_records)) {
+        throw new DataModelException(
+                'DataModel.stopVnfr: Cannot find VNFR with id "%s"', vnfr_id);
+    }
+    if (vnfr_id in this.opstate.vnfr) {
+        return this.opstate.vnfr[vnfr_data];
+    } else {
+        // Assume we are 'ON'
+        return 'ON';
+    }
+}
+
+
+/* ==========================
+ * Debug and helper functions
+ * ==========================
+ */
+
+DataModel.prototype.prettyPrint = function (out) {
+    if (out == undefined) {
+        out = console.log;
+    }
+    out('Descriptors:');
+    for (descriptor_type in this.descriptors) {
+        out("Descriptor type: %s", descriptor_type);
+        for (descriptor_id in this.descriptors[descriptor_type]) {
+            out("data=%s",descriptor_id,
+                    JSON.stringify(this.descriptors[descriptor_type][descriptor_id]));
+        };
+    };
+
+    out('\nConfigRecords:');
+    for (record_type in this.config_records) {
+        out("Record type: %s", record_type);
+        for (record_id in this.config_records[record_type]) {
+            out("data=%s", record_id,
+                    JSON.stringify(this.config_records[record_type][record_id]));
+        };
+    };
+};
+
+
+module.exports = {
+    DataModelException: DataModelException,
+    NotImplementedException: NotImplementedException,
+    MonitoringParam: MonitoringParam,
+    DataModel: DataModel
+};
+