aboutsummaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorlj1412 <lji@research.att.com>2017-02-14 15:10:25 +0000
committerlj1412 <lji@research.att.com>2017-02-14 15:10:27 +0000
commitf2ec39706a7a31017f5d219c44d54d40714d9a27 (patch)
tree0442ccf8420a388d264cca3bc4965a0c1035af0d /lib
parentd53e0cf57fc289259b6c9de5bfad224f23cd2988 (diff)
Init dcae.orch-dispatcher
Change-Id: I52aa696bd5d1d5ed3bc6e03a3c994dc0b3a71062 Signed-off-by: lj1412 <lji@research.att.com>
Diffstat (limited to 'lib')
-rw-r--r--lib/auth.js65
-rw-r--r--lib/cloudify.js257
-rw-r--r--lib/config.js140
-rw-r--r--lib/deploy.js195
-rw-r--r--lib/events.js79
-rw-r--r--lib/info.js39
-rw-r--r--lib/inventory.js229
-rw-r--r--lib/logging.js31
-rw-r--r--lib/middleware.js126
-rw-r--r--lib/promise_request.js102
-rw-r--r--lib/repeat.js54
-rw-r--r--lib/services.js97
-rw-r--r--lib/setup.js37
-rw-r--r--lib/utils.js79
14 files changed, 1530 insertions, 0 deletions
diff --git a/lib/auth.js b/lib/auth.js
new file mode 100644
index 0000000..54b08e2
--- /dev/null
+++ b/lib/auth.js
@@ -0,0 +1,65 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+/* HTTP Basic Authentication */
+
+"use strict";
+
+/* Extract user name and password from the 'Authorization' header */
+const parseAuthHeader = function(authHeader){
+
+ let parsedHeader = {};
+
+ const authItems = authHeader.split(/\s+/); // Split on the white space between Basic and the base64 encoded user:password
+
+ if (authItems[0].toLowerCase() === 'basic') {
+ if (authItems[1]) {
+ const authString = (new Buffer(authItems[1], 'base64')).toString();
+ const userpass = authString.split(':');
+ if (userpass.length > 1) {
+ parsedHeader = {user: userpass[0], password: userpass[1]};
+ }
+ }
+ }
+ return parsedHeader;
+};
+
+/* Middleware function to check authentication */
+exports.checkAuth = function(req, res, next) {
+ const auth = process.mainModule.exports.config.auth;
+ if (auth) {
+ /* Authentication is configured */
+ if (req.headers.authorization) {
+ const creds = parseAuthHeader(req.headers.authorization);
+ if (creds.user && creds.password && (creds.user in auth) && (auth[creds.user] === creds.password)) {
+ next();
+ }
+ else {
+ let err = new Error('Authentication required');
+ err.status = 403;
+ next(err);
+ }
+ }
+ else {
+ let err = new Error ('Authentication required');
+ err.status = 403;
+ next(err);
+ }
+ }
+ else {
+ next(); // Nothing to do, no authentication required
+ }
+}; \ No newline at end of file
diff --git a/lib/cloudify.js b/lib/cloudify.js
new file mode 100644
index 0000000..b1565c4
--- /dev/null
+++ b/lib/cloudify.js
@@ -0,0 +1,257 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+/* Low-level routines for using the Cloudify Manager REST API */
+
+"use strict";
+
+const stream = require('stream');
+const targz = require('node-tar.gz');
+
+const utils = require('./utils');
+const repeat = require('./repeat');
+const req = require('./promise_request');
+const doRequest = req.doRequest;
+
+var cfyAPI = null;
+var cfyAuth = null;
+var logger = null;
+
+
+// Delay function--returns a promise that's resolved after 'dtime'
+// milliseconds.`
+var delay = function(dtime) {
+ return new Promise(function(resolve, reject) {
+ setTimeout(resolve, dtime);
+ });
+};
+
+// Poll for the result of a workflow execution
+var getWorkflowResult = function(execution_id) {
+ var finished = [ "terminated", "cancelled", "failed" ];
+ var retryInterval = 15000; // Every 15 seconds
+ var maxTries = 240; // Up to an hour
+
+ logger.debug("Getting workflow status for execution id: " + execution_id);
+
+ // Function for getting execution info
+ var getExecutionStatus = function() {
+ var reqOptions = {
+ method : "GET",
+ uri : cfyAPI + "/executions/" + execution_id
+ };
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+ return doRequest(reqOptions);
+ };
+
+ // Function for testing if workflow is finished
+ // Expects the result of getExecutionStatus
+ var checkStatus = function(res) {
+ logger.debug("Checking result: " + JSON.stringify(res) + " ==> " + (res.json && res.json.status && finished.indexOf(res.json.status) < 0));
+ return res.json && res.json.status && finished.indexOf(res.json.status) < 0;
+ };
+
+ return repeat.repeatWhile(getExecutionStatus, checkStatus, maxTries,
+ retryInterval).then(function(res) {
+ if (res.json && res.json.status && res.json.status !== "terminated") {
+ throw ("workflow failed!");
+ } else {
+ return res;
+ }
+ });
+};
+
+// Uploads a blueprint via the Cloudify API
+exports.uploadBlueprint = function(bpid, blueprint) {
+ // Cloudify API wants a gzipped tar of a directory, not the blueprint text
+ // So we make a directory and feed a gzipped tar as the body of the PUT
+ // request
+ var workingDir = "./work/" + bpid;
+
+ return utils.makeDirAndFile(workingDir, 'blueprint.yaml', blueprint)
+
+ .then(function() {
+ // Set up a read stream that presents tar'ed and gzipped data
+ var src = targz().createReadStream(workingDir);
+
+ // Set up the HTTP PUT request
+ var reqOptions = {
+ method : "PUT",
+ uri : cfyAPI + "/blueprints/" + bpid,
+ headers : {
+ "Content-Type" : "application/octet-stream",
+ "Accept" : "*/*"
+ }
+ };
+
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+ // Initiate PUT request and return the promise for a result
+ return doRequest(reqOptions, src).then(
+ // Cleaning up the working directory without perturbing the result is
+ // messy!
+ function(result) {
+ utils.removeDir(workingDir);
+ return result;
+ }, function(err) {
+ logger.debug("Problem on upload: " + JSON.stringify(err));
+ utils.removeDir(workingDir);
+ throw err;
+ });
+
+ });
+};
+
+// Creates a deployment from a blueprint
+exports.createDeployment = function(dpid, bpid, inputs) {
+
+ // Set up the HTTP PUT request
+ var reqOptions = {
+ method : "PUT",
+ uri : cfyAPI + "/deployments/" + dpid,
+ headers : {
+ "Content-Type" : "application/json",
+ "Accept" : "*/*"
+ }
+ };
+
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+ var body = {
+ blueprint_id : bpid
+ };
+ if (inputs) {
+ body.inputs = inputs;
+ }
+
+ // Make the PUT request to create the deployment
+ return doRequest(reqOptions, JSON.stringify(body));
+};
+
+// Executes a workflow against a deployment (use for install and uninstall)
+exports.executeWorkflow = function(dpid, workflow) {
+
+ // Set up the HTTP POST request
+ var reqOptions = {
+ method : "POST",
+ uri : cfyAPI + "/executions",
+ headers : {
+ "Content-Type" : "application/json",
+ "Accept" : "*/*"
+ }
+ };
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+ var body = {
+ deployment_id : dpid,
+ workflow_id : workflow
+ };
+
+ // Make the POST request
+ return doRequest(reqOptions, JSON.stringify(body)).then(
+ function(result) {
+ logger.debug("Result from POSTing workflow start: " + JSON.stringify(result));
+ if (result.json && result.json.id) {
+ logger.debug("Waiting for workflow status: " + result.json.id);
+ return getWorkflowResult(result.json.id);
+ }
+ else {
+ logger.warn("Did not get expected JSON body from POST to start workflow");
+ // TODO throw? we got an OK for workflow but no JSON?
+ }
+ });
+};
+
+// Retrieves outputs for a deployment
+exports.getOutputs = function(dpid) {
+ var reqOptions = {
+ method : "GET",
+ uri : cfyAPI + "/deployments/" + dpid + "/outputs",
+ headers : {
+ "Accept" : "*/*"
+ }
+ };
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+
+ return doRequest(reqOptions);
+};
+
+// Get the output descriptions for a deployment
+exports.getOutputDescriptions = function(dpid) {
+ var reqOptions = {
+ method : "GET",
+ uri : cfyAPI + "/deployments/" + dpid + "?include=outputs",
+ headers : {
+ "Accept" : "*/*"
+ }
+ };
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+
+ return doRequest(reqOptions);
+};
+
+// Deletes a deployment
+exports.deleteDeployment = function(dpid) {
+ var reqOptions = {
+ method : "DELETE",
+ uri : cfyAPI + "/deployments/" + dpid
+ };
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+
+ return doRequest(reqOptions);
+};
+
+// Deletes a blueprint
+exports.deleteBlueprint = function(bpid) {
+ var reqOptions = {
+ method : "DELETE",
+ uri : cfyAPI + "/blueprints/" + bpid
+ };
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+
+ return doRequest(reqOptions);
+};
+
+// Allow client to set the Cloudify API root address
+exports.setAPIAddress = function(addr) {
+ cfyAPI = addr;
+};
+
+// Allow client to set Cloudify credentials
+exports.setCredentials = function(user, password) {
+ cfyAuth = {
+ user : user,
+ password : password
+ };
+};
+
+// Set a logger
+exports.setLogger = function(logsource) {
+ logger = logsource.getLogger('cfyinterface');
+};
diff --git a/lib/config.js b/lib/config.js
new file mode 100644
index 0000000..f74faa9
--- /dev/null
+++ b/lib/config.js
@@ -0,0 +1,140 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+/*
+ * Dispatcher configuration
+ * Configuration may come from environment variables, configuration file, or defaults,
+ * in that order of precedence.
+ * The configuration file is optional.
+ * If present, the configuration file is a UTF-8 serialization of a JSON object.
+ *
+ * --------------------------------------------------------------------------------------
+ * | JSON property | Environment variable | Required? | Default |
+ * --------------------------------------------------------------------------------------
+ * | foreground | FOREGROUND | Yes | true |
+ * --------------------------------------------------------------------------------------
+ * | logLevel | LOG_LEVEL | Yes | "INFO" |
+ * --------------------------------------------------------------------------------------
+ * | listenHost | LISTEN_HOST | Yes | "0.0.0.0" |
+ * --------------------------------------------------------------------------------------
+ * | listenPort | LISTEN_PORT | Yes | 8443 |
+ * --------------------------------------------------------------------------------------
+ * | ssl.pfxFile | SSL_PFX_FILE | No | none |
+ * --------------------------------------------------------------------------------------
+ * | ssl.pfxPassFile | SSL_PFX_PASSFILE | No | none |
+ * --------------------------------------------------------------------------------------
+ * | cloudify.url | CLOUDIFY_URL | Yes | none |
+ * --------------------------------------------------------------------------------------
+ * | cloudify.user | CLOUDIFY_USER | No | none |
+ * --------------------------------------------------------------------------------------
+ * | cloudify.password | CLOUDIFY_PASSWORD | No | none |
+ * --------------------------------------------------------------------------------------
+ * | inventory.url | INVENTORY_URL | Yes | http://inventory:8080 |
+ * --------------------------------------------------------------------------------------
+ * | inventory.user | INVENTORY_USER | No | none |
+ * --------------------------------------------------------------------------------------
+ * | inventory.password | INVENTORY_PASSWORD | No | none |
+ * --------------------------------------------------------------------------------------
+ *
+ */
+"use strict";
+
+const fs = require("fs");
+const utils = require("./utils");
+
+const DEFAULT_FOREGROUND = true;
+const DEFAULT_LISTEN_PORT = 8443;
+const DEFAULT_LISTEN_HOST = "0.0.0.0";
+const DEFAULT_LOG_LEVEL = "INFO";
+const DEFAULT_INVENTORY_URL = "http://inventory:8080";
+
+/* Check configuration for completeness */
+const findMissingConfig = function(cfg) {
+ const requiredProps = ['logLevel', 'listenHost', 'listenPort', 'cloudify.url', 'inventory.url'];
+ return requiredProps.filter(function(p){return !utils.hasProperty(cfg,p);});
+};
+
+/** Reads configuration-related files and returns a configuration object
+ * Sets some defaults
+ * Throws I/O errors, JSON parsing errors, and an error if required config elements are missing
+ */
+exports.configure = function(configFile) {
+ var cfg = {};
+
+ /* If there's a config file, read it */
+ if (configFile) {
+ cfg = JSON.parse(fs.readFileSync(configFile, 'utf8').trim());
+ }
+
+ /* Set config values */
+ cfg.foreground = process.env['FOREGROUND'] || cfg.foreground || DEFAULT_FOREGROUND;
+ cfg.logLevel = process.env['LOG_LEVEL'] || cfg.logLevel || DEFAULT_LOG_LEVEL;
+ cfg.listenHost = process.env['LISTEN_HOST'] || cfg.listenHost || DEFAULT_LISTEN_HOST;
+ cfg.listenPort = (process.env['LISTEN_PORT'] && parseInt(process.env['LISTEN_PORT'])) || cfg.listenPort || DEFAULT_LISTEN_PORT;
+
+ if (process.env['SSL_PFX_FILE']) {
+ if (!cfg.ssl) {
+ cfg.ssl = {};
+ }
+ cfg.ssl.pfxFile = process.env['SSL_PFX_FILE'];
+ cfg.ssl.pfxPassFile = process.env['SSL_PFX_PASS_FILE'] || cfg.ssl.pfxPassFile;
+ }
+
+ if (!cfg.cloudify) {
+ cfg.cloudify = {};
+ }
+ cfg.cloudify.url = process.env['CLOUDIFY_URL'] || cfg.cloudify.url;
+ cfg.cloudify.user = process.env['CLOUDIFY_USER'] || cfg.cloudify.user;
+ cfg.cloudify.password = process.env['CLOUDIFY_PASSWORD'] || cfg.cloudify.password;
+
+ if (!cfg.inventory) {
+ cfg.inventory = {};
+ }
+ cfg.inventory.url = process.env['INVENTORY_URL'] || cfg.inventory.url || DEFAULT_INVENTORY_URL;
+ cfg.inventory.user = process.env['INVENTORY_USER'] || cfg.inventory.user;
+ cfg.inventory.password = process.env['INVENTORY_PASSWORD'] || cfg.inventory.password;
+ cfg.locations = {};
+
+ /* If https params are present, read in the cert/passphrase */
+ if (cfg.ssl && cfg.ssl.pfxFile) {
+ cfg.ssl.pfx = fs.readFileSync(cfg.ssl.pfxFile);
+ if (cfg.ssl.pfxPassFile) {
+ cfg.ssl.passphrase = fs.readFileSync(cfg.ssl.pfxPassFile,'utf8').trim();
+ }
+ }
+
+ const missing = findMissingConfig(cfg);
+ if (missing.length > 0) {
+ throw new Error ("Required configuration elements missing: " + missing.join(','));
+ cfg = null;
+ }
+
+ return cfg;
+};
+
+/** Read locations file
+*/
+exports.getLocations = function(locationsFile) {
+ let locations = {};
+
+ try {
+ locations = JSON.parse(fs.readFileSync(locationsFile));
+ }
+ catch (e) {
+ locations = {};
+ }
+ return locations;
+} \ No newline at end of file
diff --git a/lib/deploy.js b/lib/deploy.js
new file mode 100644
index 0000000..e807060
--- /dev/null
+++ b/lib/deploy.js
@@ -0,0 +1,195 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+"use strict";
+
+/* Deploy and undeploy using Cloudify blueprints */
+
+const config = process.mainModule.exports.config;
+
+/* Set delays between steps */
+const DELAY_INSTALL_WORKFLOW = 30000;
+const DELAY_RETRIEVE_OUTPUTS = 5000;
+const DELAY_DELETE_DEPLOYMENT = 30000;
+const DELAY_DELETE_BLUEPRINT = 10000;
+
+/* Set up the Cloudify low-level interface library */
+var cfy = require("./cloudify.js");
+/* Set config for deploy module */
+cfy.setAPIAddress(config.cloudify.url);
+cfy.setCredentials(config.cloudify.user, config.cloudify.password);
+cfy.setLogger(config.logSource);
+
+/* Set up logging */
+var logger = config.logSource.getLogger("deploy");
+
+// Try to parse a string as JSON
+var parseContent = function(input) {
+ var res = {json: false, content: input};
+ try {
+ var parsed = JSON.parse(input);
+ res.json = true;
+ res.content = parsed;
+ }
+ catch (pe) {
+ // Do nothing, just indicate it's not JSON and return content as is
+ }
+ return res;
+};
+
+// create a normalized representation of errors, whether they're a node.js Error or a Cloudify API error
+var normalizeError = function (err) {
+ var e = {};
+ if (err instanceof Error) {
+ e.message = err.message;
+ if (err.code) {
+ e.code = err.code;
+ }
+ if (err.status) {
+ e.status = err.status;
+ }
+ }
+ else {
+ // Try to populate error with information from a Cloudify API error
+ // We expect to see err.body, which is a stringified JSON object
+ // We can parse it and extract message and error_code
+ e.message = "unknown API error";
+ e.code = "UNKNOWN";
+ if (err.status) {
+ e.status = err.status;
+ }
+ if (err.body) {
+ var p = parseContent(err.body);
+ if (p.json) {
+ e.message = p.content.message ? p.content.message : "unknown API error";
+ e.code = p.content.error_code ? p.content.error_code : "UNKNOWN";
+ }
+ else {
+ // if there's a body and we can't parse it just attach it as the message
+ e.message = err.body;
+ }
+ }
+ }
+
+ return e;
+};
+
+// Augment the raw outputs from a deployment with the descriptions from the blueprint
+var annotateOutputs = function (id, rawOutputs) {
+ return new Promise(function(resolve, reject) {
+
+ var outItems = Object.keys(rawOutputs);
+
+ if (outItems.length < 1) {
+ // No output items, so obviously no descriptions, just return empty object
+ resolve({});
+ }
+ else {
+ // Call Cloudify to get the descriptions
+ cfy.getOutputDescriptions(id)
+ .then(function(res) {
+ // Assemble an outputs object with values from raw output and descriptions just obtained
+ var p = parseContent(res.body);
+ if (p.json && p.content.outputs) {
+ var outs = {};
+ outItems.forEach(function(i) {
+ outs[i] = {value: rawOutputs[i]};
+ if (p.content.outputs[i] && p.content.outputs[i].description) {
+ outs[i].description = p.content.outputs[i].description;
+ }
+ });
+ resolve(outs);
+ }
+ else {
+ reject({code: "API_INVALID_RESPONSE", message: "Invalid response for output descriptions query"});
+ }
+ });
+ }
+
+ });
+};
+
+// Delay function--returns a promise that's resolved after 'dtime' milliseconds.`
+var delay = function(dtime) {
+ return new Promise(function(resolve, reject){
+ setTimeout(resolve, dtime);
+ });
+};
+
+// Go through the Cloudify API call sequence to do a deployment
+exports.deployBlueprint = function(id, blueprint, inputs) {
+
+ logger.debug("deploymentId: " + id + " starting blueprint upload");
+ // Upload blueprint
+ return cfy.uploadBlueprint(id, blueprint)
+ .then (function(result) {
+ logger.debug("deploymentId: " + id + " blueprint uploaded");
+ // Create deployment
+ return cfy.createDeployment(id, id, inputs);
+ })
+ .then(function(result){
+ logger.debug("deploymentId: " + id + " deployment created");
+ // Execute the install workflow
+ return delay(DELAY_INSTALL_WORKFLOW).then(function(){ return cfy.executeWorkflow(id, 'install');});
+ })
+ .then(function(result) {
+ logger.debug("deploymentId: " + id + " install workflow successfully executed");
+ // Retrieve the outputs from the deployment, as specified in the blueprint
+ return delay(DELAY_RETRIEVE_OUTPUTS).then(function() { return cfy.getOutputs(id); });
+ })
+ .then(function(result) {
+ // We have the raw outputs from the deployment but not annotated with the descriptions
+ var rawOutputs = {};
+ if (result.body) {
+ var p = parseContent(result.body);
+ if (p.json) {
+ if (p.content.outputs) {
+ rawOutputs = p.content.outputs;
+ }
+ }
+ }
+ logger.debug("output retrieval result for " + id + ": " + JSON.stringify(result));
+ logger.info("deploymentId " + id + " successfully deployed");
+ return annotateOutputs(id, rawOutputs);
+ })
+ .catch(function(err) {
+ throw normalizeError(err);
+ });
+};
+
+// Go through the Cloudify API call sequence to do an undeployment of a previously deployed blueprint
+exports.undeployDeployment = function(id) {
+ logger.debug("deploymentId: " + id + " starting uninstall workflow");
+ // Run uninstall workflow
+ return cfy.executeWorkflow(id, 'uninstall', 0)
+ .then (function(result){
+ logger.debug("deploymentId: " + id + " uninstall workflow completed");
+ // Delete the deployment
+ return delay(DELAY_DELETE_DEPLOYMENT).then(function() {return cfy.deleteDeployment(id);});
+ })
+ .then (function(result){
+ logger.debug("deploymentId: " + id + " deployment deleted");
+ // Delete the blueprint
+ return delay(DELAY_DELETE_BLUEPRINT).then(function() {return cfy.deleteBlueprint(id);});
+ })
+ .then (function(result){
+ logger.info("deploymentId: " + id + " successfully undeployed");
+ return result;
+ })
+ .catch (function(err){
+ throw normalizeError(err);
+ });
+};
diff --git a/lib/events.js b/lib/events.js
new file mode 100644
index 0000000..11a3ec0
--- /dev/null
+++ b/lib/events.js
@@ -0,0 +1,79 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+/* Handle the /events API */
+
+"use strict";
+
+const router = require('express').Router();
+const bodyParser = require('body-parser');
+const deploy = require('./deploy');
+const middleware = require('./middleware');
+const inventory = require('./inventory');
+const logAccess = require('./logging').logAccess;
+const services = require('./services');
+
+/* Required properties for event POST */
+const requiredProps = ['dcae_target_name','dcae_target_type','dcae_service_action','dcae_service_location'];
+
+/* Pick up config exported by main */
+const config = process.mainModule.exports.config;
+const logger = config.logSource.getLogger('events');
+
+/* Set up middleware stack for initial processing of request */
+router.use(middleware.checkType('application/json')); // Validate type
+router.use(bodyParser.json({strict: true})); // Parse body as JSON
+router.use(middleware.checkProps(requiredProps)); // Make sure we have required properties
+router.use(inventory.checkInventory); // Get template(s) (deploy) or services (undeploy)
+router.use(middleware.checkLocation); // Check location and get location information
+router.use(middleware.expandTemplates); // Expand any blueprint templates
+
+
+/* Accept an incoming event */
+router.post('/', function(req, res, next) {
+ let response = {requestId: req.dcaeReqId, deploymentIds:[]};
+
+ if (req.body.dcae_service_action === 'deploy') {
+
+ /* Deploy services for the VNF */
+
+ /* req.dcae_blueprints has been populated by the expandTemplates middleware */
+ logger.info(req.dcaeReqId + " services to deploy: " + JSON.stringify(req.dcae_blueprints));
+ logger.debug(JSON.stringify(req.dcae_shareables, null, '\t'));
+ logger.debug(JSON.stringify(req.dcae_locations, null, '\t'));
+
+ /* Create a deployer function and use it for each of the services */
+ let deployer = services.createDeployer(req);
+ let outputs = req.dcae_blueprints.map(deployer);
+ response.deploymentIds = req.dcae_blueprints.map(function(s) {return s.deploymentId;});
+ }
+ else {
+
+ /* Undeploy services for the VNF */
+
+ /* req.dcae_services has been populated by the checkInventory middleware */
+ logger.info(req.dcaeReqId + " deployments to undeploy: " + JSON.stringify(req.dcae_services));
+
+ /* Create an undeployer function and use it for each of the services */
+ let undeployer = services.createUndeployer(req);
+ req.dcae_services.forEach(undeployer);
+ response.deploymentIds = req.dcae_services.map(function(s) {return s.deploymentId;});
+ }
+ res.status(202).json(response);
+ logAccess(req, 202);
+});
+
+module.exports = router; \ No newline at end of file
diff --git a/lib/info.js b/lib/info.js
new file mode 100644
index 0000000..0549de1
--- /dev/null
+++ b/lib/info.js
@@ -0,0 +1,39 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+/* Handle the / API that provides API information */
+
+"use strict";
+
+const router = require('express').Router();
+
+/* Pick up config exported by main */
+const config = process.mainModule.exports.config;
+
+/* Accept an incoming event */
+router.get('/', function(req, res) {
+ res.json(
+ {
+ apiVersion: config.apiVersion,
+ serverVersion: config.version,
+ links: config.apiLinks,
+ locations: config.locations
+ }
+ );
+ require('./logging').logAccess(req, 200);
+});
+
+module.exports = router; \ No newline at end of file
diff --git a/lib/inventory.js b/lib/inventory.js
new file mode 100644
index 0000000..5124a98
--- /dev/null
+++ b/lib/inventory.js
@@ -0,0 +1,229 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+ /* Routines related to accessing DCAE inventory */
+
+ "use strict";
+
+ const req = require('./promise_request');
+
+ const INV_SERV_TYPES = '/dcae-service-types';
+ const INV_SERVICES = '/dcae-services';
+
+ const config = process.mainModule.exports.config;
+
+ /*
+ * Common error handling for inventory API calls
+ */
+ const invError = function(err) {
+ if (err.status && err.status === 404) {
+ /* Map 404 to an empty list */
+ return [];
+ }
+ else {
+ var newErr = new Error("Error response " + err.status + " from DCAE inventory: " + err.body);
+ newErr.status = 502;
+ newErr.code = 502;
+ throw newErr;
+ }
+
+ };
+
+ /*
+ * Query the inventory for all of the blueprint templates that need to be
+ * deployed for a given target type. Returns a promise for an array of
+ * objects, each object having: - type: the service type name associated
+ * with the blueprint template - template: the blueprint template
+ */
+ const findTemplates = function(targetType, location, serviceId) {
+
+ /* Set up query string based on available parameters */
+ var qs = {vnfType: targetType, serviceLocation: location };
+ if (serviceId) {
+ qs.serviceId = serviceId;
+ }
+
+ const reqOptions = {
+ method : "GET",
+ uri : config.inventory.url + INV_SERV_TYPES,
+ qs: qs
+ };
+ return req.doRequest(reqOptions)
+ .then(function(result) {
+ let templates = [];
+ let content = JSON.parse(result.body);
+ if (content.items) {
+ /* Pick out the fields we want */
+ templates = content.items.map(function(i) {return {type: i.typeName, template: i.blueprintTemplate};});
+ }
+ return templates;
+ })
+ .catch (invError);
+ };
+
+ /*
+ * Query the inventory for all of the services running for a given target
+ * name. Returns a promise for an array of objects, each object having: -
+ * type: the service type name associated with the service - deploymentID:
+ * the deploymentID for the service
+ */
+ const findServices = function(target_name) {
+ const reqOptions = {
+ method : "GET",
+ uri : config.inventory.url + INV_SERVICES,
+ qs: {vnfId: target_name}
+ };
+ return req.doRequest(reqOptions)
+ .then(function(result) {
+ let services = [];
+ let content = JSON.parse(result.body);
+ if(content.items) {
+ /* Pick out the fields we want */
+ services = content.items.map(function(i) { return {type: i.typeLink.title, deploymentId: i.deploymentRef};});
+ }
+ return services;
+ })
+ .catch(invError);
+ };
+
+ /*
+ * Find shareable components at 'location'. Return an object whose keys are
+ * component type names and whose values are component identifiers for
+ * components of the type. NOTE: if there are multiple shareable components
+ * with the same component type, the last one wins.
+ */
+ const findShareables = function(location) {
+ const reqOptions = {
+ method : "GET",
+ uri : config.inventory.url + INV_SERVICES,
+ qs: {vnfLocation: location}
+ };
+ return req.doRequest(reqOptions)
+ .then(function(result) {
+ let shareables = {};
+ let content = JSON.parse(result.body);
+ if (content.items) {
+ content.items.forEach(function(s) {
+ s.components.filter(function(c) {return c.shareable === 1;})
+ .forEach(function(c){
+ shareables[c.componentType] = c.componentId;
+ });
+ });
+ }
+ return shareables;
+ })
+ .catch(invError);
+ };
+
+
+ /*
+ * Middleware-style function to check inventory. For 'deploy' operations,
+ * finds blueprint templates and shareable components Attaches list of
+ * templates to req.dcae_templates, object with shareable components to
+ * req.dcae_shareables. For 'undeploy' operations, finds deployed services.
+ * Attaches list of deployed services to req.dcae_services.
+ */
+ exports.checkInventory = function(req, res, next) {
+ if (req.body.dcae_service_action.toLowerCase() === 'deploy'){
+ findTemplates(req.body.dcae_target_type, req.body.dcae_service_location, req.body.dcae_service_type)
+ .then(function (templates) {
+ if (templates.length > 0) {
+ req.dcae_templates = templates;
+ return templates;
+ }
+ else {
+ var paramList = [req.body.dcae_target_type, req.body.dcae_service_location, req.body.dcae_service_type ? req.body.dcae_service_type : "unspecified"].join('/');
+ let err = new Error(paramList + ' has no associated DCAE service types');
+ err.status = 400;
+ next(err);
+ }
+ })
+ .then(function(result) {
+ return findShareables(req.body.dcae_service_location);
+ })
+ .then(function(shareables) {
+ req.dcae_shareables = shareables;
+ next();
+ })
+ .catch(function(err) {
+ next(err);
+ });
+ }
+ else if (req.body.dcae_service_action.toLowerCase() === 'undeploy') {
+ findServices(req.body.dcae_target_name)
+ .then(function (services){
+ if (services.length > 0) {
+ req.dcae_services = services;
+ next();
+ }
+ else {
+ let err = new Error('"' + req.body.dcae_target_name + '" has no deployed DCAE services');
+ err.status = 400;
+ next(err);
+ }
+ })
+ .catch(function(err) {
+ next(err);
+ });
+ }
+ else {
+ let err = new Error ('"' + req.body.dcae_service_action + '" is not a valid service action. Valid actions: "deploy", "undeploy"');
+ err.status = 400;
+ next(err);
+ }
+ };
+
+/*
+ * Add a DCAE service to the inventory. Done after a deployment.
+ */
+ exports.addService = function(deploymentId, serviceType, vnfId, vnfType, vnfLocation, outputs) {
+
+ /* Create the service description */
+ let serviceDescription =
+ {
+ "typeName" : serviceType,
+ "vnfId" : vnfId,
+ "vnfType" : vnfType,
+ "vnfLocation" : vnfLocation,
+ "deploymentRef" : deploymentId
+ };
+
+ // TODO create 'components' array using 'outputs'--for now, a dummy
+ serviceDescription.components = [
+ {
+ componentType: "dummy_component",
+ componentId: "/components/dummy",
+ componentSource: "DCAEController",
+ shareable: 0
+ }
+ ];
+
+ const reqOptions = {
+ method : "PUT",
+ uri : config.inventory.url + INV_SERVICES + "/" + deploymentId,
+ json: serviceDescription
+ };
+
+ return req.doRequest(reqOptions);
+ };
+
+/*
+ * Remove a DCAE service from the inventory. Done after an undeployment.
+ */
+ exports.deleteService = function(serviceId) {
+ return req.doRequest({method: "DELETE", uri: config.inventory.url + INV_SERVICES + "/" + serviceId});
+ };
+ \ No newline at end of file
diff --git a/lib/logging.js b/lib/logging.js
new file mode 100644
index 0000000..db168b3
--- /dev/null
+++ b/lib/logging.js
@@ -0,0 +1,31 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+"use strict";
+const config = process.mainModule.exports.config;
+const accessLogger = config.logSource.getLogger('access');
+
+
+/* Logging */
+
+exports.logAccess = function (req, status, extra) {
+ let entry = req.dcaeReqId + " " + req.connection.remoteAddress + " " + req.method + " " + req.originalUrl + " " + status;
+ if (extra) {
+ extra = extra.replace(/\n/g, " "); /* Collapse multi-line extra data to a single line */
+ entry = entry + " <" + extra + ">";
+ }
+ accessLogger.info(entry);
+}; \ No newline at end of file
diff --git a/lib/middleware.js b/lib/middleware.js
new file mode 100644
index 0000000..567620f
--- /dev/null
+++ b/lib/middleware.js
@@ -0,0 +1,126 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+/* Middleware modules */
+
+"use strict";
+
+const ejs = require('ejs');
+const utils = require('./utils');
+const logAccess = require('./logging').logAccess;
+const config = process.mainModule.exports.config;
+const locations = config.locations;
+const logger = config.logSource.getLogger("errhandler");
+
+/* Assign a request ID to each incoming request */
+exports.assignId = function(req, res, next) {
+ req.dcaeReqId = utils.generateId();
+ next();
+};
+
+/* Error handler -- send error with JSON body */
+exports.handleErrors = function(err, req, res, next) {
+ let status = err.status || 500;
+ let msg = err.message || err.body || 'unknown error'
+ res.status(status).type('application/json').send({status: status, message: msg });
+ logAccess(req, status, msg);
+
+ if (status >= 500) {
+ logger.error(req.dcaeReqId + " Error: " + JSON.stringify({message: msg, code: err.code, status: status}));
+ }
+};
+
+/* Make sure Content-Type is correct for POST and PUT */
+exports.checkType = function(type){
+ return function(req, res, next) {
+ const ctype = req.header('content-type');
+ const method = req.method.toLowerCase();
+ /* Content-Type matters only for POST and PUT */
+ if (ctype === type || ['post','put'].indexOf(method) < 0) {
+ next();
+ }
+ else {
+ let err = new Error ('Content-Type must be \'' + type +'\'');
+ err.status = 415;
+ next (err);
+ }
+ };
+};
+
+/* Check that a JSON body has a set of properties */
+exports.checkProps = function(props) {
+ return function (req, res, next) {
+ const missing = props.filter(function(p){return !utils.hasProperty(req.body,p);});
+ if (missing.length > 0) {
+ let err = new Error ('Request missing required properties: ' + missing.join(','));
+ err.status = 400;
+ next(err);
+ }
+ else {
+ next();
+ }
+ };
+};
+
+/* Check that there is location information for this event */
+/* Appends locations to req.dcae_locations for later use */
+exports.checkLocation = function(req, res, next) {
+ if (req.body.dcae_service_location in locations) {
+ req.dcae_locations = {central: locations.central, local: locations[req.body.dcae_service_location]};
+ next();
+ }
+ else {
+ let err = new Error ('"' + req.body.dcae_service_location + '" is not a supported location');
+ err.status = 400;
+ next(err);
+ }
+
+};
+
+/* Expand blueprint templates into full blueprints.
+ * Expects req.dcae_templates to contain templates.
+ * Puts expanded blueprints into req.dcae_blueprints
+ */
+exports.expandTemplates = function(req, res, next) {
+
+ /* Build the context for rendering the template */
+ let context = req.body; // start with the body of POST /events request
+ context.locations = req.dcae_locations; // location information from the location "map" in config file
+ context.dcae_shareables = req.dcae_shareables; // local shareable components
+
+ /* Expand the templates */
+ try {
+ if (req.dcae_templates) { // There won't be any templates for an undeploy
+ let blueprints = req.dcae_templates.map(function (template) {
+ //TODO possibly compute intensive- is there a better way?
+ return {
+ blueprint: ejs.render(template.template, context),
+ type: template.type,
+ deploymentId: utils.generateId() // Assign ID now, so we can return it in response
+ };
+ });
+ req.dcae_blueprints = blueprints;
+ req.dcae_templates = null; // Make they'll get garbage-collected
+ }
+ next();
+ }
+ catch (err) {
+ err.status = 400;
+ next(err);
+ }
+};
+
+
diff --git a/lib/promise_request.js b/lib/promise_request.js
new file mode 100644
index 0000000..906c16c
--- /dev/null
+++ b/lib/promise_request.js
@@ -0,0 +1,102 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+ /* Promise-based HTTP request client */
+
+ "use strict";
+
+/*
+ * Make an HTTP request using a string or a readable stream for the body
+ * of the request.
+ * Return a promise for result in the form
+ * {status: <http status code>, body: <response body>}
+ */
+
+const stream = require('stream');
+const request = require('request');
+
+let logger = null;
+
+exports.doRequest = function(options, body) {
+ return new Promise(function(resolve, reject) {
+
+ logger.debug("doRequest: " + JSON.stringify(options));
+ if (body && !(body instanceof stream.Readable)) {
+ // Body is a string, just include it in the request
+ options.body = body;
+ }
+ var req = request(options);
+
+ // Reject promise if there's an error
+ req.on('error', function(error) {
+ logger.debug('Error in on error handler for request: ' + error);
+ reject(error);
+ });
+
+ // Capture the response
+ req.on('response', function(resp) {
+
+ // Collect the body of the response
+ var rbody = '';
+ resp.on('data', function(d) {
+ rbody += d;
+ });
+
+ // resolve or reject when finished
+ resp.on('end', function() {
+
+ var result = {
+ status : resp.statusCode,
+ body : rbody
+ };
+
+ // Add a JSON version of the body if appropriate
+ if (rbody.length > 0) {
+ try {
+ var jbody = JSON.parse(rbody);
+ result.json = jbody;
+ }
+ catch (pe) {
+ // Do nothing, no json property added to the result object
+ }
+ }
+
+ logger.debug(JSON.stringify(result));
+
+ if (resp.statusCode > 199 && resp.statusCode < 300) {
+ // HTTP status code indicates success - resolve the promise
+ logger.debug("do request resolve: " + JSON.stringify(result));
+ resolve(result);
+ }
+ else {
+ // Reject the promise
+ logger.debug("do request reject: " + JSON.stringify(result));
+ reject(result);
+ }
+ });
+ });
+
+ // If there's a readable stream for a body, pipe it to the request
+ if (body && (body instanceof stream.Readable)) {
+ // Pipe the body readable stream into the request
+ body.pipe(req);
+ }
+ });
+};
+
+exports.setLogger = function(logsource) {
+ logger = logsource.getLogger('httprequest');
+};
diff --git a/lib/repeat.js b/lib/repeat.js
new file mode 100644
index 0000000..2ea0e14
--- /dev/null
+++ b/lib/repeat.js
@@ -0,0 +1,54 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+"use strict";
+
+/**
+ * Returns a promise for running and re-running the specified action until the result meets a specific condition
+ * - action is a function that returns a promise
+ * - predicate is a function that takes a success result from action and returns true if the action should be rerun
+ * - maxTries is the total number of times to try the action
+ * - interval is the interval, in milliseconds, between tries, as approximated by setTimeout()
+ */
+
+exports.repeatWhile = function(action, predicate, maxTries, interval) {
+ return new Promise(function(resolve, reject) {
+
+ var count = 0;
+
+ function makeAttempt() {
+ action()
+ .then (function(res) {
+ if (!predicate(res)) {
+ // We're done
+ resolve(res);
+ }
+ else {
+ if (++count < maxTries) {
+ // set up next attempt
+ setTimeout(makeAttempt, interval);
+ }
+ else {
+ // we've run out of retries or it's not retryable, so reject the promise
+ reject({message: "maximum repetions reached: " + count });
+ }
+ }
+ });
+ }
+
+ makeAttempt();
+ });
+};
diff --git a/lib/services.js b/lib/services.js
new file mode 100644
index 0000000..5a2a7d7
--- /dev/null
+++ b/lib/services.js
@@ -0,0 +1,97 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+/* Deploying and undeploying services based on incoming events */
+
+"use strict";
+
+const ejs = require('ejs');
+const deploy = require('./deploy');
+const inventory = require('./inventory');
+const config = process.mainModule.exports.config;
+
+/* Set up logging */
+var logger = config.logSource.getLogger("services");
+
+/* Create a deployer function that can deploy a service from a
+ * blueprint template in the context of the event request 'req'.
+ * 'template' is a template object (with 'type' and 'template')
+ * created by the checkInventory middleware
+ */
+exports.createDeployer = function(req) {
+
+ return function(blueprint) {
+ /* Generate a deploymentId */
+ let deploymentId = blueprint.deploymentId;
+
+ /* Attempt the deployment */
+ logger.info(req.dcaeReqId + " " + "Attempting to deploy deploymentId " + deploymentId);
+ logger.debug(req.dcaeReqId + " deploymentId: " + deploymentId + " blueprint: " + blueprint.blueprint);
+
+ deploy.deployBlueprint(deploymentId, blueprint.blueprint)
+ .then(function(outputs) {
+ logger.info(req.dcaeReqId + " Deployed deploymentId: " + deploymentId);
+ logger.debug (req.dcaeReqId + " deploymentId: " + deploymentId + " outputs: " + JSON.stringify(outputs));
+ return outputs;
+ })
+ .then(function(outputs) {
+ /* Update the inventory */
+ return inventory.addService(
+ deploymentId,
+ blueprint.type,
+ req.body.dcae_target_name,
+ req.body.dcae_target_type,
+ req.body.dcae_service_location,
+ outputs
+ );
+ })
+ .then(function(result) {
+ logger.info(req.dcaeReqId + " Updated inventory for deploymentId: " + deploymentId);
+ })
+ .catch(function(err) {
+ logger.error(req.dcaeReqId + " Failed to deploy deploymentId: " + deploymentId + " Error: " + JSON.stringify(err));
+ //TODO try uninstall?
+ });
+ };
+};
+
+/* Create an undeployer function that can undeploy
+ * a previously deployed service.
+ */
+exports.createUndeployer = function(req) {
+
+ return function(deployment) {
+
+ /* Undeploy */
+ deploy.undeployDeployment(deployment.deploymentId)
+ .then(function(result){
+ logger.info(req.dcaeReqId + " Undeployed deploymentId: " + deployment.deploymentId);
+ return result;
+ })
+ .then(function(result) {
+ /* Delete the service from the inventory */
+ /* When we create service we set service id = deployment id */
+ return inventory.deleteService(deployment.deploymentId);
+ })
+ .then(function(result){
+ logger.info(req.dcaeReqId + " Deleted service from inventory for deploymentId: " + deployment.deploymentId);
+ })
+ .catch(function(err){
+ logger.error(req.dcaeReqId + " Error undeploying " + deployment.deploymentId + ": " + JSON.stringify(err));
+ });
+ };
+
+};
diff --git a/lib/setup.js b/lib/setup.js
new file mode 100644
index 0000000..72674b0
--- /dev/null
+++ b/lib/setup.js
@@ -0,0 +1,37 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+"use strict";
+
+var fs = require("fs");
+
+/** Reads configuration file and parses into an object
+ *
+ */
+var cfg = null;
+
+exports.setConfig = function(configFile) {
+
+ var cfg = null;
+ try {
+ cfg = JSON.parse(fs.readFileSync(configFile));
+ }
+ catch (e) {
+ console.log ("Configuration error: " + e);
+ }
+
+ return cfg;
+};
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..bf582e8
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,79 @@
+/*
+Copyright(c) 2017 AT&T Intellectual Property. All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and limitations under the License.
+*/
+
+"use strict";
+
+// Utility functions
+
+var fs = require('fs');
+var rimraf = require('rimraf');
+var uuid = require('node-uuid');
+
+// Create a directory (named 'dirName') and write 'content' into a file (named 'fileName') in that directory.
+exports.makeDirAndFile = function(dirName, fileName, content){
+
+ return new Promise(function(resolve, reject){
+ fs.mkdir(dirName, function(err) {
+ if (err) {
+ reject(err);
+ }
+ else {
+ fs.writeFile(dirName + "/" + fileName, content, function(err, fd) {
+ if (err) {
+ reject(err);
+ }
+ else {
+ resolve();
+ }
+
+ });
+ }
+ });
+
+ });
+};
+
+// Remove directory and its contents
+exports.removeDir = function(dirName) {
+ return new Promise(function(resolve, reject){
+ rimraf(dirName, function(err) {
+ if (err) {
+ reject(err);
+ }
+ else {
+ resolve();
+ }
+ });
+ });
+};
+
+/* Does object 'o' have property 'key' */
+exports.hasProperty = function(o, key) {
+ return key.split('.').every(function(e){
+ if (typeof(o) === 'object' && o !== null && (e in o) && (typeof o[e] !== 'undefined')) {
+ o = o[e];
+ return true;
+ }
+ else {
+ return false;
+ }
+ });
+};
+
+/* Generate a random ID string */
+exports.generateId = function() {
+ return uuid.v4();
+};