aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJack Lucas <jflucas@research.att.com>2017-05-10 01:48:41 +0000
committerJack Lucas <jflucas@research.att.com>2017-05-10 01:48:41 +0000
commitff6ba434b6d91b6a4a4e9b3a7fbb8cadced229ad (patch)
tree8ae919ab8d1b174944bd6bea6602ce29153be2f6
parent0806707cbc2dd6311228facabf0a6052862c51c7 (diff)
Post-R1 API & other updates.
Change-Id: Id0e2e15b95a5713a25a746534fc40b56599a5f06 Signed-off-by: Jack Lucas <jflucas@research.att.com>
-rw-r--r--Dockerfile3
-rw-r--r--dispatcher.js143
-rw-r--r--dispatcherAPI.md130
-rw-r--r--dispatcherAPI.yaml371
-rw-r--r--etc/config.json.development11
-rw-r--r--etc/config.json.docker12
-rw-r--r--etc/log4js.json78
-rw-r--r--lib/auth.js10
-rw-r--r--lib/cloudify.js231
-rw-r--r--lib/config.js23
-rw-r--r--lib/dcae-deployments.js242
-rw-r--r--lib/deploy.js184
-rw-r--r--lib/dispatcher-error.js53
-rw-r--r--lib/events.js10
-rw-r--r--lib/inventory.js213
-rw-r--r--lib/logging.js128
-rw-r--r--lib/middleware.js29
-rw-r--r--lib/promise_request.js54
-rw-r--r--lib/services.js20
-rw-r--r--lib/utils.js44
-rw-r--r--npm-shrinkwrap.json293
-rw-r--r--package.json8
-rwxr-xr-xset_version.sh2
-rw-r--r--version.js2
24 files changed, 1792 insertions, 502 deletions
diff --git a/Dockerfile b/Dockerfile
index 68811e3..744bf65 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -9,10 +9,11 @@ RUN mkdir -p ${INSROOT}/${APPUSER}/lib \
COPY *.js ${INSROOT}/${APPUSER}/
COPY package.json ${INSROOT}/${APPUSER}/
COPY lib ${INSROOT}/${APPUSER}/lib/
-COPY etc/config.json.development ${INSROOT}/${APPUSER}/etc/config.json
+COPY etc/config.json.docker ${INSROOT}/${APPUSER}/etc/config.json
COPY etc/log4js.json ${INSROOT}/${APPUSER}/etc/log4js.json
WORKDIR ${INSROOT}/${APPUSER}
RUN npm install --production && chown -R ${APPUSER}:${APPUSER} ${INSROOT}/${APPUSER} && npm remove -g npm
USER ${APPUSER}
VOLUME ${INSROOT}/${APPUSER}/log
+EXPOSE 8443
ENTRYPOINT ["/usr/local/bin/node", "dispatcher.js"]
diff --git a/dispatcher.js b/dispatcher.js
index f8f610f..6a982cf 100644
--- a/dispatcher.js
+++ b/dispatcher.js
@@ -12,61 +12,94 @@ 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 main */
"use strict";
-const API_VERSION = "2.0.0";
-
-const fs = require('fs');
-const util = require('util');
-const http = require('http');
-const https = require('https');
-const express = require('express');
-const daemon = require('daemon');
-const conf = require('./lib/config');
-const req = require('./lib/promise_request');
+const
+API_VERSION = "3.0.0";
+
+const
+fs = require('fs');
+const
+util = require('util');
+const
+http = require('http');
+const
+https = require('https');
+const
+express = require('express');
+const
+daemon = require('daemon');
+const
+conf = require('./lib/config');
+const
+req = require('./lib/promise_request');
+const
+createError = require('./lib/dispatcher-error').createDispatcherError;
/* Paths for API routes */
-const INFO_PATH = "/";
-const EVENTS_PATH = "/events";
+const
+INFO_PATH = "/";
+const
+EVENTS_PATH = "/events";
+const
+DEPLOYMENTS_PATH = "/dcae-deployments";
/* Set up log and work directories */
-try { fs.mkdirSync("./log"); } catch (e) { }
-try { fs.mkdirSync("./work"); } catch(e) { }
+try {
+ fs.mkdirSync("./log");
+} catch (e) {
+}
+try {
+ fs.mkdirSync("./work");
+} catch (e) {
+}
/* Set up logging */
-const log4js = require('log4js');
+const
+log4js = require('log4js');
log4js.configure('etc/log4js.json');
-const logger = log4js.getLogger('dispatcher');
+const
+logger = log4js.getLogger('dispatcher');
+exports.config = {
+ logSource : log4js
+};
+const
+logError = require('./lib/logging').logError;
logger.info("Starting dispatcher");
try {
/* Set configuration and make available to other modules */
- let config = conf.configure(process.argv[2] || "./etc/config.json");
+ var config = conf.configure(process.argv[2] || "./etc/config.json");
- config.locations = conf.getLocations(process.argv[3] || "./etc/locations.json");
+ config.locations = conf.getLocations(process.argv[3]
+ || "./etc/locations.json");
if (Object.keys(config.locations).length < 1) {
logger.warn('No locations specified')
}
- /* Set log level--config will supply a default of "INFO" if not explicitly set in config.json */
+ /*
+ * Set log level--config will supply a default of "INFO" if not explicitly
+ * set in config.json
+ */
log4js.setGlobalLogLevel(config.logLevel);
config.logSource = log4js;
config.version = require('./version').version;
config.apiVersion = API_VERSION;
config.apiLinks = {
- info: INFO_PATH,
- events: EVENTS_PATH
+ info : INFO_PATH,
+ events : EVENTS_PATH
};
exports.config = config;
req.setLogger(log4js);
/* Set up the application */
- const app = express();
+ const
+ app = express();
app.set('x-powered-by', false);
app.set('etag', false);
@@ -79,38 +112,41 @@ try {
/* Set up API routes */
app.use(EVENTS_PATH, require('./lib/events'));
app.use(INFO_PATH, require('./lib/info'));
+ app.use(DEPLOYMENTS_PATH, require('./lib/dcae-deployments'));
/* Set up error handling */
app.use(require('./lib/middleware').handleErrors);
/* Start the server */
- let server = null;
- let usingTLS = false;
+ var server = null;
+ var usingTLS = false;
try {
- if (config.ssl && config.ssl.pfx && config.ssl.passphrase && config.ssl.pfx.length > 0) {
- /* Check for non-zero pfx length--DCAE config will deliver an empty pfx if no cert
- * available for the host. */
- server = https.createServer({pfx: config.ssl.pfx, passphrase: config.ssl.passphrase}, app);
+ if (config.ssl && config.ssl.pfx && config.ssl.passphrase
+ && config.ssl.pfx.length > 0) {
+ /*
+ * Check for non-zero pfx length--DCAE config will deliver an empty
+ * pfx if no cert available for the host.
+ */
+ server = https.createServer({
+ pfx : config.ssl.pfx,
+ passphrase : config.ssl.passphrase
+ }, app);
usingTLS = true;
+ } else {
+ server = http.createServer(app);
}
- else {
- server = http.createServer(app);
- }
- }
- catch (e) {
- logger.fatal ('Could not create http(s) server--exiting: ' + e);
- console.log ('Could not create http(s) server--exiting: ' + e);
- process.exit(2);
+ } catch (e) {
+ throw (createError('Could not create http(s) server--exiting: '
+ + e.message, 500, 'system', 551));
}
server.setTimeout(0);
server.listen(config.listenPort, config.listenHost, function() {
- let addr = server.address();
- logger.info("Dispatcher version " + config.version +
- " listening on " + addr.address + ":" + addr.port +
- " pid: " + process.pid +
- (usingTLS ? " " : " not ") + "using TLS (HTTPS)");
+ var addr = server.address();
+ logger.info("Dispatcher version " + config.version + " listening on "
+ + addr.address + ":" + addr.port + " pid: " + process.pid
+ + (usingTLS ? " " : " not ") + "using TLS (HTTPS)");
});
/* Daemonize */
@@ -119,25 +155,32 @@ try {
}
/* Set up handling for terminate signal */
- process.on('SIGTERM', function(){
+ process.on('SIGTERM', function() {
logger.info("Dispatcher API server shutting down.");
server.close(function() {
logger.info("Dispatcher API server shut down.");
- });
+ });
});
/* Log actual exit */
- /* logger.info() is asynchronous, so we will see
- * another beforeExit event after it completes.
+ /*
+ * logger.info() is asynchronous, so we will see another beforeExit event
+ * after it completes.
*/
- let loggedExit = false;
+ var loggedExit = false;
process.on('beforeExit', function() {
if (!loggedExit) {
loggedExit = true;
logger.info("Dispatcher process exiting.");
}
});
-}
-catch (e) {
- logger.fatal("Dispatcher exiting due to start-up problems: " + e);
+} catch (e) {
+ /*
+ * If it's the error from HTTP server creation, log it as is, else create a
+ * new standard error to log
+ */
+ logError(e.logCode ? e : createError(
+ 'Dispatcher exiting due to start-up problem: ' + e.message, 500,
+ 'system', 552));
+ console.log("Dispatcher exiting due to startup problem: " + e.message);
}
diff --git a/dispatcherAPI.md b/dispatcherAPI.md
deleted file mode 100644
index 1380337..0000000
--- a/dispatcherAPI.md
+++ /dev/null
@@ -1,130 +0,0 @@
-# Dispatcher API
-
-
-<a name="overview"></a>
-## Overview
-High-level API for deploying/deploying composed services using Cloudify Manager.
-
-
-### Version information
-*Version* : 2.0.0
-
-
-
-
-<a name="paths"></a>
-## Paths
-
-<a name="get"></a>
-### GET /
-
-#### Description
-Get API version information, links to API operations, and location data
-
-
-#### Responses
-
-|HTTP Code|Description|Schema|
-|---|---|---|
-|**200**|Success|[DispatcherInfo](#dispatcherinfo)|
-
-<a name="dispatcherinfo"></a>
-**DispatcherInfo**
-
-|Name|Description|Schema|
-|---|---|---|
-|**apiVersion** <br>*optional*|version of API supported by this server|string|
-|**links** <br>*optional*|Links to API resources|[links](#get-links)|
-|**locations** <br>*optional*|Information about DCAE locations known to this dispatcher|object|
-|**serverVersion** <br>*optional*|version of software running on this server|string|
-
-<a name="get-links"></a>
-**links**
-
-|Name|Description|Schema|
-|---|---|---|
-|**dcaeServiceInstances** <br>*optional*|root of DCAE service instance resource tree|string|
-|**status** <br>*optional*|link to server status information|string|
-
-
-<a name="events-post"></a>
-### POST /events
-
-#### Description
-Signal an event that triggers deployment or undeployment of a DCAE service
-
-
-#### Parameters
-
-|Type|Name|Description|Schema|Default|
-|---|---|---|---|---|
-|**Body**|**dcae_event** <br>*required*||[DCAEEvent](#dcaeevent)||
-
-
-#### Responses
-
-|HTTP Code|Description|Schema|
-|---|---|---|
-|**202**|Success: The content that was posted is valid, the dispatcher has<br> found the needed blueprint (for a deploy operation) or the existing deployment<br> (for an undeploy operation), and is initiating the necessary orchestration steps.|[DCAEEventResponse](#dcaeeventresponse)|
-|**400**|Bad request: See the message in the response for details.|[DCAEErrorResponse](#dcaeerrorresponse)|
-|**415**|Bad request: The Content-Type header does not indicate that the content is<br>'application/json'|[DCAEErrorResponse](#dcaeerrorresponse)|
-|**500**|Problem on the server side, possible with downstream systems. See the message<br>in the response for more details.|[DCAEErrorResponse](#dcaeerrorresponse)|
-
-
-#### Consumes
-
-* `application/json`
-
-
-#### Produces
-
-* `application/json`
-
-
-
-
-<a name="definitions"></a>
-## Definitions
-
-<a name="dcaeerrorresponse"></a>
-### DCAEErrorResponse
-Object reporting an error.
-
-
-|Name|Description|Schema|
-|---|---|---|
-|**message** <br>*optional*|Human-readable description of the reason for the error|string|
-|**status** <br>*required*|HTTP status code for the response|integer|
-
-
-<a name="dcaeevent"></a>
-### DCAEEvent
-Data describing an event that should trigger a deploy or undeploy operation for one
-or more DCAE services.
-
-
-|Name|Description|Schema|
-|---|---|---|
-|**aai_additional_info** <br>*optional*|Additional information, not carried in the event, obtained from an A&AI query or set of queries. Data in this object is available for populating deployment-specific values in the blueprint.|object|
-|**dcae_service_action** <br>*required*|Indicates whether the event requires a DCAE service to be deployed or undeployed.<br>Valid values are 'deploy' and 'undeploy'.|string|
-|**dcae_service_location** <br>*required*|The location at which the DCAE service is to be deployed or from which it is to be<br>undeployed.|string|
-|**dcae_service_type** <br>*optional*|Identifier for the service of which the target entity is a part.|string|
-|**dcae_target_name** <br>*required*|The name of the entity that's the target for monitoring by a DCAE service. This uniquely identifies the monitoring target. For 'undeploy' operations, this value will be used to select the specific DCAE service instance to be undeployed.|string|
-|**dcae_target_type** <br>*required*|The type of the entity that's the target for monitoring by a DCAE service. In 1607, this field will have one of eight distinct values, based on which mobility VM is to<br> be monitored. For 'deploy' operations, this value will be used to select the<br> service blueprint to deploy.|string|
-|**event** <br>*required*|The original A&AI event object. <br>The data included here is available for populating deployment-specific values in the<br>service blueprint.|object|
-
-
-<a name="dcaeeventresponse"></a>
-### DCAEEventResponse
-Response body for a POST to /events.
-
-
-|Name|Description|Schema|
-|---|---|---|
-|**deploymentIds** <br>*required*|An array of deploymentIds, one for each service being deployed in response to this<br>event. A deploymentId uniquely identifies an attempt to deploy a service.|< string > array|
-|**requestId** <br>*required*|A unique identifier assigned to the request. Useful for tracing a request through<br>logs.|string|
-
-
-
-
-
diff --git a/dispatcherAPI.yaml b/dispatcherAPI.yaml
index 4350c4a..b335cf2 100644
--- a/dispatcherAPI.yaml
+++ b/dispatcherAPI.yaml
@@ -2,7 +2,7 @@
swagger: '2.0'
info:
- version: "2.0.0"
+ version: "3.0.0"
title: Dispatcher API
description: |
High-level API for deploying/deploying composed services using Cloudify Manager.
@@ -35,14 +35,14 @@ paths:
description: |
Links to API resources
properties:
- dcaeServiceInstances:
+ info:
type: string
description: |
- root of DCAE service instance resource tree
- status:
+ path for the server information endpoint
+ events:
type: string
description: |
- link to server status information
+ path for the events endpoint
locations:
type: object
description: |
@@ -89,10 +89,238 @@ paths:
500:
description: |
- Problem on the server side, possible with downstream systems. See the message
+ Problem on the server side. See the message
in the response for more details.
schema:
$ref: "#/definitions/DCAEErrorResponse"
+
+ 502:
+ description: |
+ Error reported to the dispatcher by a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 504:
+ description: |
+ Error communicating with a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+
+ /dcae-deployments:
+ get:
+ description: |
+ List service deployments known to the orchestrator, optionally restricted to a single service type
+
+ parameters:
+ - name: serviceTypeId
+ description: |
+ Service type identifier for the type whose deployments are to be listed
+ type: string
+ in: query
+ required: false
+
+ responses:
+
+ 200:
+ description: |
+ Success. (Note that if no matching deployments are found, the request is still a success; the
+ deployments array is empty in that case.)
+ schema:
+ $ref: "#/definitions/DCAEDeploymentsListResponse"
+
+ 500:
+ description: |
+ Problem on the server side. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+ 502:
+ description: |
+ Error reported to the dispatcher by a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 504:
+ description: |
+ Error communicating with a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ /dcae-deployments/{deploymentId}:
+ put:
+ description: |
+ Request deployment of a DCAE service
+
+ consumes:
+ - application/json
+ produces:
+ - application/json
+
+ parameters:
+ - name: deploymentId
+ description: |
+ Unique deployment identifier assigned by the API client.
+ in: path
+ type: string
+ required: true
+
+ - name: body
+ in: body
+ schema:
+ $ref: "#/definitions/DCAEDeploymentRequest"
+ required: true
+
+ responses:
+
+ 202:
+ description: |
+ Success: The content that was posted is valid, the dispatcher has
+ found the needed blueprint, created an instance of the topology in the orchestrator,
+ and started an installation workflow.
+ schema:
+ $ref: "#/definitions/DCAEDeploymentResponse"
+
+ 400:
+ description: |
+ Bad request: See the message in the response for details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 409:
+ description: |
+ A service with the specified deployment Id already exists. Using PUT to update the service is not a supported operation.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 415:
+ description: |
+ Bad request: The Content-Type header does not indicate that the content is
+ 'application/json'
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 500:
+ description: |
+ Problem on the server side. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 502:
+ description: |
+ Error reported to the dispatcher by a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 504:
+ description: |
+ Error communicating with a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ delete:
+ description: |
+ Uninstall the DCAE service and remove all associated data from the orchestrator.
+
+ parameters:
+ - name: deploymentId
+ description: |
+ Deployment identifier for the service to be uninstalled.
+ in: path
+ type: string
+ required: true
+
+ responses:
+
+ 202:
+ description: |
+ Success: The dispatcher has initiated the uninstall operation.
+ schema:
+ $ref: "#/definitions/DCAEDeploymentResponse"
+
+ 400:
+ description: |
+ Bad request: See the message in the response for details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 500:
+ description: |
+ Problem on the server side. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 502:
+ description: |
+ Error reported to the dispatcher by a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 504:
+ description: |
+ Error communicating with a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ /dcae-deployments/{deploymentId}/operation/{operationId}:
+ get:
+ description: |
+ Get status of a deployment operation
+ parameters:
+ - name: deploymentId
+ in: path
+ type: string
+ required: true
+ - name: operationId
+ in: path
+ type: string
+ required: true
+
+ responses:
+
+ 200:
+ description: Status information retrieved successfully
+ schema:
+ $ref: "#/definitions/DCAEOperationStatusResponse"
+
+ 404:
+ description: The operation information does not exist (possibly because the service has been uninstalled and deleted).
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 500:
+ description: |
+ Problem on the server side. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 502:
+ description: |
+ Error reported to the dispatcher by a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+ 504:
+ description: |
+ Error communicating with a downstream system. See the message
+ in the response for more details.
+ schema:
+ $ref: "#/definitions/DCAEErrorResponse"
+
+
+
definitions:
@@ -127,14 +355,34 @@ definitions:
dcae_service_location:
description: |
The location at which the DCAE service is to be deployed or from which it is to be
- undeployed.
+ undeployed.
type: string
dcae_service_type:
description: |
Identifier for the service of which the target entity is a part.
type: string
+
+ dcae_service-instance_persona-model-id:
+ description: |
+ ASDC identifier for the service.
+ type: string
+ dcae_service-instance_persona-model-version:
+ description: |
+ ASDC version for the service. (Currently ignored by the dispatcher.)
+ type: string
+
+ dcae_generic-vnf_persona-model-id:
+ description: |
+ ASDC identifier for the resource.
+ type: string
+
+ dcae_generic-vnf_persona-model-version:
+ description: |
+ ASDC version for the resource. (Currently ignored by the dispatcher.)
+ type: string
+
event:
description: |
The original A&AI event object.
@@ -169,7 +417,92 @@ definitions:
type: array
items:
type: string
-
+
+ DCAEDeploymentRequest:
+ description: |
+ Request for deploying a DCAE service.
+ type:
+ object
+ required: [serviceTypeId]
+
+ properties:
+
+ serviceTypeId:
+ description: |
+ The service type identifier (a unique ID assigned by DCAE inventory) for the service to be deployed.
+ type: string
+
+ inputs:
+ description: |
+ Object containing inputs needed by the service blueprint to create an instance of the service.
+ Content of the object depends on the service being deployed.
+ type: object
+
+ DCAEDeploymentResponse:
+ description: |
+ Response body for a PUT or DELETE to /dcae-deployments/{deploymentId}
+ type: object
+
+ required: [requestId, links]
+
+ properties:
+ requestId:
+ type: string
+ description: |
+ Unique identifier for the request
+ links:
+ description: |
+ Links that the API client can access.
+ type: object
+ properties:
+ self:
+ type: string
+ description: |
+ Link used to retrieve information about the service being deployed
+ status:
+ type: string
+ description:
+ Link used to retrieve information about the status of the installation workflow
+
+ DCAEOperationStatusResponse:
+ description: |
+ Response body for a request for status of an installation or uninstallation operation.
+ type: object
+
+ required: [requestId, operationType, status]
+
+ properties:
+ requestId:
+ type: string
+ description: |
+ A unique identifier assigned to the request. Useful for tracing a request through logs.
+ operationType:
+ description: |
+ Type of operation being reported on. ("install" or "uninstall")
+ type: string
+ status:
+ description: |
+ Status of the installation or uninstallation operation. Possible values are "processing",
+ "succeeded", and "failed"
+ type: string
+ error:
+ description: |
+ If status is "failed", this field will be present and contain additional information about the reason the operation failed.
+ type: string
+ links:
+ description: |
+ If the operation succeeded, links that the client can follow to take further action. Note that a successful "uninstall" operation removes the DCAE service instance completely, so there are no possible further actions, and no links.
+ type: object
+ properties:
+ self:
+ type: string
+ description: |
+ Link used to retrieve information about the service.
+ uninstall:
+ type: string
+ description:
+ Link used to trigger an "uninstall" operation for the service. (Use the DELETE method.)
+
DCAEErrorResponse:
description: |
Object reporting an error.
@@ -185,7 +518,27 @@ definitions:
message:
description: Human-readable description of the reason for the error
type: string
-
+
+ DCAEDeploymentsListResponse:
+ description: |
+ Object providing a list of deployments
+ type: object
+ required: [requestId, deployments]
+
+ properties:
+ requestId:
+ type: string
+ description: |
+ Unique identifier for the request
+ deployments:
+ type: array
+ items:
+ type: object
+ properties:
+ href:
+ type: string
+ description: |
+ URL for the service deployment
diff --git a/etc/config.json.development b/etc/config.json.development
deleted file mode 100644
index 95e8c93..0000000
--- a/etc/config.json.development
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "listenPort" : 8444,
- "cloudify": {
- "url": "https://cm.example.com/api/v2",
- "user": "admin",
- "password": "admin"
- },
- "inventory" : {
- "url" : "http://inventory.example.com:8080"
- }
-}
diff --git a/etc/config.json.docker b/etc/config.json.docker
new file mode 100644
index 0000000..5be3d05
--- /dev/null
+++ b/etc/config.json.docker
@@ -0,0 +1,12 @@
+{
+ "foreground": true,
+ "logLevel" : "INFO",
+ "cloudify": {
+ "url": "http://cm.example.com/api/v2.1",
+ "user": "admin",
+ "password": "admin"
+ },
+ "inventory" : {
+ "url" : "http://inventory:8080"
+ }
+}
diff --git a/etc/log4js.json b/etc/log4js.json
index 28d2970..89d137f 100644
--- a/etc/log4js.json
+++ b/etc/log4js.json
@@ -1,28 +1,56 @@
{
- "appenders":
- [
- {
- "type": "dateFile",
- "filename": "log/access-dispatcher.log",
- "pattern": "-yyyy-MM-dd",
- "alwaysIncludePattern": false,
- "category": ["access"],
- "layout" : {
- "type" : "pattern",
- "pattern" : "%d{ISO8601} %m"
- }
- },
- {
- "type": "categoryFilter",
- "exclude": ["access"],
- "appender": {
- "type": "dateFile",
- "filename": "log/dispatcher.log",
- "pattern":"-yyyy-MM-dd",
- "alwaysIncludePattern": false
+ "appenders":
+ [
+ {
+ "type": "categoryFilter",
+ "exclude": ["audit"],
- }
- }
- ]
-}
+ "appender":
+ {
+ "type": "dateFile",
+ "filename": "log/dispatcher.log",
+ "pattern": "-yyyy-MM-dd",
+ "alwaysIncludePattern": false
+ }
+ },
+ {
+ "type": "logLevelFilter",
+ "level": "WARN",
+ "appender":
+ {
+ "filename": "log/error.log",
+ "type": "file",
+ "maxLogSize": 10240000,
+ "backups" : 10,
+ "layout":
+ {
+ "type": "messagePassThrough"
+ }
+ }
+ },
+ {
+ "type": "logLevelFilter",
+ "level": "DEBUG",
+ "maxLevel": "DEBUG",
+ "appender":
+ {
+ "filename": "log/debug.log",
+ "type": "file",
+ "maxLogSize": 10240000,
+ "backups" : 10
+ }
+ },
+ {
+ "type": "file",
+ "filename": "log/audit.log",
+ "maxLogSize": 10240000,
+ "backups": 10,
+ "category": ["audit"],
+ "layout":
+ {
+ "type": "messagePassThrough"
+ }
+ }
+ ]
+}
diff --git a/lib/auth.js b/lib/auth.js
index 54b08e2..9ddd7b3 100644
--- a/lib/auth.js
+++ b/lib/auth.js
@@ -21,7 +21,7 @@ See the License for the specific language governing permissions and limitations
/* Extract user name and password from the 'Authorization' header */
const parseAuthHeader = function(authHeader){
- let parsedHeader = {};
+ var parsedHeader = {};
const authItems = authHeader.split(/\s+/); // Split on the white space between Basic and the base64 encoded user:password
@@ -48,15 +48,15 @@ exports.checkAuth = function(req, res, next) {
next();
}
else {
- let err = new Error('Authentication required');
+ var err = new Error('Authentication required');
err.status = 403;
next(err);
}
}
else {
- let err = new Error ('Authentication required');
- err.status = 403;
- next(err);
+ var errx = new Error ('Authentication required');
+ errx.status = 403;
+ next(errx);
}
}
else {
diff --git a/lib/cloudify.js b/lib/cloudify.js
index b1565c4..145a10e 100644
--- a/lib/cloudify.js
+++ b/lib/cloudify.js
@@ -18,10 +18,8 @@ See the License for the specific language governing permissions and limitations
"use strict";
-const stream = require('stream');
-const targz = require('node-tar.gz');
+const admzip = require('adm-zip');
-const utils = require('./utils');
const repeat = require('./repeat');
const req = require('./promise_request');
const doRequest = req.doRequest;
@@ -39,83 +37,153 @@ var delay = function(dtime) {
});
};
+// Get current status of a workflow execution
+// Function for getting execution info
+const getExecutionStatus = function(executionId) {
+ var reqOptions = {
+ method : "GET",
+ uri : cfyAPI + "/executions/" + executionId
+ };
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+ return doRequest(reqOptions);
+};
+
// 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);
- };
+ logger.debug("Getting workflow result for execution id: " + execution_id);
// Function for testing if workflow is finished
- // Expects the result of getExecutionStatus
+ // Expects the result of getExecStatus
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 {
+
+ // Create execution status checker function
+ var getExecStatus = function() { return getExecutionStatus(execution_id);};
+
+ return repeat.repeatWhile(getExecStatus, checkStatus, maxTries, retryInterval)
+ .then(
+
+ /* Handle fulfilled promise from repeatWhile */
+ function(res) {
+
+ logger.debug('workflow result: ' + JSON.stringify(res));
+
+ /* Successful completion */
+ if (res.json && res.json.status && res.json.status === 'terminated') {
return res;
}
+
+ /* If we get here, we don't have a success and we're going to throw something */
+
+ var error = {};
+
+ /* We expect a JSON object with a status */
+ if (res.json && res.json.status) {
+
+ /* Failure -- we need to return something that looks like the CM API failures */
+ if (res.json.status === 'failed') {
+ error.body = 'workflow failed: ' + execution_id + ' -- ' + (res.json.error ? JSON.stringify(res.json.error) : 'no error information');
+ }
+
+ /* Cancellation -- don't really expect this */
+ else if (res.json.status === 'canceled' || res.json.status === 'cancelled') {
+ error.body = 'workflow canceled: ' + execution_id;
+ }
+
+ /* Don't expect anything else -- but if we get it, it's not a success! */
+ else {
+ error.body = 'workflow--unexpected status ' + res.json.status + ' for ' + execution_id;
+ }
+ }
+
+ /* The body of the response from the API call to get execution status is not what we expect at all */
+ else {
+ error.body = 'workflow--unexpected result body getting execution status from CM for ' + execution_id;
+ }
+
+ throw error;
+ },
+
+ /* Handle rejection of promise from repeatWhile--don't use a catch because it would catch the error thrown above */
+ function(err) {
+ /* repeatWhile could fail and we get here because:
+ * -- repeatWhile explicitly rejects the promise because it has exhausted the retries
+ * -- repeatWhile propagates a system error (e.g., network problem) trying to access the API
+ * -- repeatWhile propagates a rejected promise due to a bad HTTP response status
+ * These should all get normalized in deploy.js--so we just rethrow the error.
+ */
+
+ throw err;
+
});
};
+//Initiate a workflow execution against a deployment
+const initiateWorkflowExecution = 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 execution start: " + JSON.stringify(result));
+ if (result.json && result.json.id) {
+ return {deploymentId: dpid, workflowType: workflow, executionId: result.json.id};
+ }
+ else {
+ logger.debug("Did not get expected JSON body from POST to start workflow");
+ var err = new Error("POST to start workflow got success response but no body");
+ err.status = err.code = 502;
+ }
+ });
+};
+
// 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 = {
+ var zip = new admzip();
+ zip.addFile('work/', new Buffer(0));
+ zip.addFile('work/blueprint.yaml', new Buffer(blueprint, 'utf8'));
+ var src = (zip.toBuffer());
+
+ // 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;
- });
+ };
- });
+ if (cfyAuth) {
+ reqOptions.auth = cfyAuth;
+ }
+ // Initiate PUT request and return the promise for a result
+ return doRequest(reqOptions, src);
};
// Creates a deployment from a blueprint
@@ -145,41 +213,31 @@ exports.createDeployment = function(dpid, bpid, inputs) {
return doRequest(reqOptions, JSON.stringify(body));
};
-// Executes a workflow against a deployment (use for install and uninstall)
-exports.executeWorkflow = function(dpid, workflow) {
+// Initiate a workflow execution against a deployment
+exports.initiateWorkflowExecution = initiateWorkflowExecution;
- // 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
- };
+// Get the status of a workflow execution
+exports.getWorkflowExecutionStatus = getExecutionStatus;
- // 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?
- }
- });
+// Return a promise for the final result of a workflow execution
+exports.getWorkflowResult = getWorkflowResult;
+
+// Executes a workflow against a deployment and returns a promise for final result
+exports.executeWorkflow = function(dpid, workflow) {
+
+ // Initiate the workflow
+ return initiateWorkflowExecution(dpid, workflow)
+
+ // Wait for the result
+ .then (function(result) {
+ logger.debug("Result from initiating workflow: " + JSON.stringify(result));
+ return getWorkflowResult(result.executionId);
+ });
};
+// Wait for workflow to complete and get result
+exports.getWorkflowResult = getWorkflowResult;
+
// Retrieves outputs for a deployment
exports.getOutputs = function(dpid) {
var reqOptions = {
@@ -245,10 +303,7 @@ exports.setAPIAddress = function(addr) {
// Allow client to set Cloudify credentials
exports.setCredentials = function(user, password) {
- cfyAuth = {
- user : user,
- password : password
- };
+ cfyAuth = user + ':' + password;
};
// Set a logger
diff --git a/lib/config.js b/lib/config.js
index f74faa9..2c9b9fc 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -24,7 +24,7 @@ See the License for the specific language governing permissions and limitations
* --------------------------------------------------------------------------------------
* | JSON property | Environment variable | Required? | Default |
* --------------------------------------------------------------------------------------
- * | foreground | FOREGROUND | Yes | true |
+ * | foreground | FOREGROUND | Yes | false |
* --------------------------------------------------------------------------------------
* | logLevel | LOG_LEVEL | Yes | "INFO" |
* --------------------------------------------------------------------------------------
@@ -49,13 +49,19 @@ See the License for the specific language governing permissions and limitations
* | inventory.password | INVENTORY_PASSWORD | No | none |
* --------------------------------------------------------------------------------------
*
+ * cloudify.cfyManagerAddress allowed as synonym for cloudify.url
+ * cloudify.cfyUser allowed as synonym for cloudify.user
+ * cloudify.cfyPassword allowed as synonym for cloudify.password
+ * inventory.inventoryAddress allowed as synonym for inventory.url
+ * ssl.pfx-file allowed as synonym for ssl.pfxFile
+ * Note that we're using ssl.passphrase directly in the config file--i.e., we get the passphrase, not a file.
*/
"use strict";
const fs = require("fs");
const utils = require("./utils");
-const DEFAULT_FOREGROUND = true;
+const DEFAULT_FOREGROUND = false;
const DEFAULT_LISTEN_PORT = 8443;
const DEFAULT_LISTEN_HOST = "0.0.0.0";
const DEFAULT_LOG_LEVEL = "INFO";
@@ -96,19 +102,22 @@ exports.configure = function(configFile) {
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;
+ cfg.cloudify.url = process.env['CLOUDIFY_URL'] || cfg.cloudify.url || cfg.cloudify.cfyManagerAddress;
+ cfg.cloudify.user = process.env['CLOUDIFY_USER'] || cfg.cloudify.user || cfg.cloudify.cfyUser;
+ cfg.cloudify.password = process.env['CLOUDIFY_PASSWORD'] || cfg.cloudify.password || cfg.cloudify.cfyPassword;
if (!cfg.inventory) {
cfg.inventory = {};
}
- cfg.inventory.url = process.env['INVENTORY_URL'] || cfg.inventory.url || DEFAULT_INVENTORY_URL;
+ cfg.inventory.url = process.env['INVENTORY_URL'] || cfg.inventory.url || cfg.inventory.inventoryAddress || 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.pfxFile || cfg.ssl['pfx-file']; // Allow synonym
+ }
if (cfg.ssl && cfg.ssl.pfxFile) {
cfg.ssl.pfx = fs.readFileSync(cfg.ssl.pfxFile);
if (cfg.ssl.pfxPassFile) {
@@ -128,7 +137,7 @@ exports.configure = function(configFile) {
/** Read locations file
*/
exports.getLocations = function(locationsFile) {
- let locations = {};
+ var locations = {};
try {
locations = JSON.parse(fs.readFileSync(locationsFile));
diff --git a/lib/dcae-deployments.js b/lib/dcae-deployments.js
new file mode 100644
index 0000000..b50dee4
--- /dev/null
+++ b/lib/dcae-deployments.js
@@ -0,0 +1,242 @@
+/*
+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 /dcae-deployments API */
+
+"use strict";
+
+/* Set this code up as a "sub-app"--lets us get the mountpoint for creating links */
+const app = require('express')();
+app.set('x-powered-by', false);
+app.set('etag', false);
+
+const bodyParser = require('body-parser');
+const deploy = require('./deploy');
+const middleware = require('./middleware');
+const inventory = require('./inventory');
+const logging = require('./logging');
+const logAccess = logging.logAccess;
+const logError = logging.logError;
+const logWarn = logging.logWarn;
+
+/* Pick up config exported by main */
+const config = process.mainModule.exports.config;
+const logger = config.logSource.getLogger('dcae-deployments');
+
+/* Set up middleware stack for initial processing of request */
+app.use(middleware.checkType('application/json')); // Validate type
+app.use(bodyParser.json({strict: true})); // Parse body as JSON
+
+
+/* Return a promise for a blueprint for the given service type ID */
+const getBlueprint = function(serviceTypeId) {
+ return inventory.getBlueprintByType(serviceTypeId)
+ .then(function (blueprintInfo) {
+ if (!blueprintInfo.blueprint) {
+ var e = new Error("No service type with ID " + serviceTypeId);
+ e.status = 404;
+ throw e;
+ }
+ return blueprintInfo;
+ })
+};
+
+/* Generate self and status links object for responses */
+const createLinks = function(req, deploymentId, executionId) {
+ var baseURL = req.protocol + '://' + req.get('Host') + req.app.mountpath + '/' + deploymentId;
+ return {
+ self: baseURL,
+ status: baseURL + '/operation/' + executionId
+ };
+};
+
+/* Generate a success response body for PUT and DELETE operations */
+const createResponse = function(req, result) {
+ return {
+ requestId: req.dcaeReqId,
+ links: createLinks(req, result.deploymentId, result.executionId)
+ };
+};
+
+/* Look up running (or in process of deploying) instances of the given service type */
+app.get('/', function (req, res, next) {
+ var services = []
+
+
+ var searchTerm = {};
+
+ req.query['serviceTypeId'] && (searchTerm = {typeId: req.query['serviceTypeId']});
+
+ inventory.getServicesByType(searchTerm)
+ .then(function (result) {
+ var deployments = result.map(function(service){
+ return {
+ href: req.protocol + '://' + req.get('Host') + req.app.mountpath + '/' + service.deploymentId
+ };
+ })
+ res.status(200).json({requestId: req.dcaeReqId, deployments: deployments});
+ logAccess(req, 200);
+ })
+ .catch(next); /* Let the error handler send response and log the error */
+});
+
+/* Accept an incoming deployment request */
+app.put('/:deploymentId', function(req, res, next) {
+
+ /* Make sure there's a serviceTypeId in the body */
+ if (!req.body['serviceTypeId']) {
+ var e = new Error ('Missing required parameter serviceTypeId');
+ e.status = 400;
+ throw e;
+ }
+
+ /* Make sure the deploymentId doesn't already exist */
+ inventory.verifyUniqueDeploymentId(req.params['deploymentId'])
+
+ /* Get the blueprint for this service type */
+ .then(function(res) {
+ return getBlueprint(req.body['serviceTypeId']);
+ })
+
+ /* Add this new service instance to inventory
+ * Easier to remove from inventory if deployment fails than vice versa
+ * Also lets client check for deployed/deploying instances if client wants to limit number of instances
+ */
+ .then(function (blueprintInfo) {
+ req.dcaeBlueprint = blueprintInfo.blueprint;
+ return inventory.addService(req.params['deploymentId'], blueprintInfo.typeId, "dummyVnfId", "dummyVnfType", "dummyLocation");
+ })
+
+ /* Upload blueprint, create deployment and start install workflow (but don't wait for completion */
+ .then (function() {
+ req.dcaeAddedToInventory = true;
+ return deploy.launchBlueprint(req.params['deploymentId'], req.dcaeBlueprint, req.body['inputs']);
+ })
+
+ /* Send the HTTP response indicating workflow has started */
+ .then(function(result) {
+ res.status(202).json(createResponse(req, result));
+ logAccess(req, 202, "Execution ID: " + result.executionId);
+ return result;
+ })
+
+ /* Finish deployment--wait for the install workflow to complete, retrieve and annotate outputs */
+ .then(function(result) {
+ return deploy.finishInstallation(result.deploymentId, result.executionId);
+ })
+
+ /* Log completion in audit log */
+ .then (function(result) {
+ logAccess(req, 200, "Deployed id: " + req.params['deploymentId']);
+ })
+
+ /* All errors show up here */
+ .catch(function(error) {
+
+
+
+ /* If we haven't already sent a response, let the error handler send response and log the error */
+ if (!res.headersSent) {
+
+ /* If we made an inventory entry, remove it */
+ if (req.dcaeAddedToInventory) {
+ inventory.deleteService(req.params['deploymentId'])
+ .then(function() {
+ logger.info("deleted failed deployment from inventory");
+ })
+ .catch(function(error) {
+ logger.debug("failed to delete service " + req.params['deploymentId'] + " from inventory");
+ logError(error, req);
+ });
+ }
+
+ next(error);
+ }
+ else {
+ /* Already sent the response, so just log error */
+ /* Don't remove from inventory, because there is a deployment on CM that might need to be removed */
+ error.message = "Error deploying deploymentId " + req.params['deploymentId'] + ": " + error.message
+ logError(error, req);
+ logAccess(req, 500, error.message);
+ }
+
+ });
+});
+
+/* Delete a running service instance */
+app.delete('/:deploymentId', function(req, res, next) {
+
+ /* Launch the uninstall workflow */
+ deploy.launchUninstall(req.params['deploymentId'])
+
+ /* Delete the service from inventory */
+ .then(function(result) {
+ return inventory.deleteService(req.params['deploymentId'])
+ .then(function() {
+ logger.info("Deleted deployment ID " + req.params['deploymentId'] + " from inventory");
+ return result;
+ })
+ })
+
+ /* Send the HTTP response indicating workflow has started */
+ .then(function(result) {
+ res.status(202).send(createResponse(req, result));
+ logAccess(req, 202, "ExecutionId: " + result.executionId);
+ return result;
+ })
+
+ /* Finish the delete processing--wait for the uninstall to complete, delete deployment, delete blueprint */
+ .then(function(result) {
+ return deploy.finishUninstall(result.deploymentId, result.executionId);
+ })
+
+ /* TODO Log completion in audit log */
+ .then(function(result) {
+ logAccess(req, 200, "Undeployed id: " + req.params['deploymentId']);
+ })
+
+ /* All errors show up here */
+ .catch(function(error) {
+ /* If we haven't already sent a response, give it to the error handler to send response */
+ if (!res.headersSent) {
+ next(error);
+ }
+ else {
+ /* Error happened after we sent the response--log it */
+ error.message = "Error undeploying deploymentId " + req.params['deploymentId'] + ": " + error.message
+ logError(error, req);
+ logAccess(req, 500, error.message);
+ }
+ });
+});
+
+/* Get the status of a workflow execution */
+app.get('/:deploymentId/operation/:executionId', function(req, res, next){
+ deploy.getExecutionStatus(req.params['executionId'])
+
+ /* Send success response */
+ .then(function(result) {
+ result.requestId = req.dcaeReqId;
+ result.links = createLinks(req, req.params['deploymentId'], req.params['executionId']);
+ res.status(200).json(result);
+ logAccess(req, 200, "Workflow type: " + result.operationType + " -- execution status: " + result.status);
+ })
+
+ .catch(next); /* Let the error handler send the response and log the error */
+
+});
+
+module.exports = app; \ No newline at end of file
diff --git a/lib/deploy.js b/lib/deploy.js
index e807060..fcee43d 100644
--- a/lib/deploy.js
+++ b/lib/deploy.js
@@ -26,6 +26,8 @@ const DELAY_RETRIEVE_OUTPUTS = 5000;
const DELAY_DELETE_DEPLOYMENT = 30000;
const DELAY_DELETE_BLUEPRINT = 10000;
+const createError = require('./dispatcher-error').createDispatcherError;
+
/* Set up the Cloudify low-level interface library */
var cfy = require("./cloudify.js");
/* Set config for deploy module */
@@ -52,36 +54,37 @@ var parseContent = function(input) {
// create a normalized representation of errors, whether they're a node.js Error or a Cloudify API error
var normalizeError = function (err) {
- var e = {};
+ var e;
+
if (err instanceof Error) {
- e.message = err.message;
- if (err.code) {
- e.code = err.code;
- }
- if (err.status) {
- e.status = err.status;
- }
+ /* node.js system error */
+ e = createError("Error communicating with CM: " + err.message, 504, "system", 202, 'cloudify-manager');
}
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;
- }
+ var message = err.message || "unknown Cloudify Manager API error";
+ var status = err.status || 502;
+ var cfyCode = "UNKNOWN";
+ var cfyMessage;
+
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";
+ cfyMessage = p.content.message ? p.content.message : "unknown Cloudify API error";
+ cfyCode = 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;
+ cfyMessage = err.body;
}
+ message = "Status " + status + " from CM API -- error code: " + cfyCode + " -- message: " + cfyMessage;
}
+
+ /* Pass through 400-level status, recast 500-level */
+ var returnStatus = (err.status > 499) ? 502 : err.status;
+ e = createError(message, returnStatus, "api", 502, 'cloudify-manager');
}
return e;
@@ -129,26 +132,43 @@ var delay = function(dtime) {
});
};
-// Go through the Cloudify API call sequence to do a deployment
-exports.deployBlueprint = function(id, blueprint, inputs) {
-
+// Go through the Cloudify API call sequence to upload blueprint, create deployment, and launch install workflow
+// (but don't wait for the workflow to finish)
+const launchBlueprint = function(id, blueprint, inputs) {
logger.debug("deploymentId: " + id + " starting blueprint upload");
// Upload blueprint
return cfy.uploadBlueprint(id, blueprint)
+
+ // Create deployment
.then (function(result) {
logger.debug("deploymentId: " + id + " blueprint uploaded");
// Create deployment
return cfy.createDeployment(id, id, inputs);
})
+
+ // Launch the workflow, but don't wait for it to complete
.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');});
+ return delay(DELAY_INSTALL_WORKFLOW)
+ .then(function(){
+ return cfy.initiateWorkflowExecution(id, 'install');
+ });
})
- .then(function(result) {
- logger.debug("deploymentId: " + id + " install workflow successfully executed");
+ .catch(function(error) {
+ logger.debug("Error: " + error + " for launch blueprint for deploymentId " + id);
+ throw normalizeError(error);
+ })
+};
+exports.launchBlueprint = launchBlueprint;
+
+// Finish installation launched with launchBlueprint
+const finishInstallation = function(deploymentId, executionId) {
+ logger.debug("finishInstallation: " + deploymentId + " -- executionId: " + executionId);
+ return cfy.getWorkflowResult(executionId)
+ .then (function(result){
+ logger.debug("deploymentId: " + deploymentId + " 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); });
+ return delay(DELAY_RETRIEVE_OUTPUTS).then(function() { return cfy.getOutputs(deploymentId); });
})
.then(function(result) {
// We have the raw outputs from the deployment but not annotated with the descriptions
@@ -161,35 +181,125 @@ exports.deployBlueprint = function(id, blueprint, inputs) {
}
}
}
- logger.debug("output retrieval result for " + id + ": " + JSON.stringify(result));
- logger.info("deploymentId " + id + " successfully deployed");
- return annotateOutputs(id, rawOutputs);
+ logger.debug("output retrieval result for " + deploymentId + ": " + JSON.stringify(result));
+ logger.info("deploymentId " + deploymentId + " successfully deployed");
+ return annotateOutputs(deploymentId, rawOutputs);
})
.catch(function(err) {
+ logger.debug("Error finishing install workflow: " + err + " -- " + JSON.stringify(err));
throw normalizeError(err);
});
-};
+}
+exports.finishInstallation = finishInstallation;
-// 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");
+// Initiate uninstall workflow against a deployment, but don't wait for workflow to finish
+const launchUninstall = function(deploymentId) {
+ logger.debug("deploymentId: " + deploymentId + " starting uninstall workflow");
// Run uninstall workflow
- return cfy.executeWorkflow(id, 'uninstall', 0)
+ return cfy.initiateWorkflowExecution(deploymentId, 'uninstall')
+ .then(function(result) {
+ return result;
+ })
+ .catch(function(err) {
+ logger.debug("Error initiating uninstall workflow: " + err + " -- " + JSON.stringify(err));
+ throw normalizeError(err);
+ });
+};
+exports.launchUninstall = launchUninstall;
+
+const finishUninstall = function(deploymentId, executionId) {
+ logger.debug("finishUninstall: " + deploymentId + " -- executionId: " + executionId);
+ return cfy.getWorkflowResult(executionId)
.then (function(result){
- logger.debug("deploymentId: " + id + " uninstall workflow completed");
+ logger.debug("deploymentId: " + deploymentId + " uninstall workflow successfully executed");
// Delete the deployment
- return delay(DELAY_DELETE_DEPLOYMENT).then(function() {return cfy.deleteDeployment(id);});
+ return delay(DELAY_DELETE_DEPLOYMENT).then(function() {return cfy.deleteDeployment(deploymentId);});
})
.then (function(result){
- logger.debug("deploymentId: " + id + " deployment deleted");
+ logger.debug("deploymentId: " + deploymentId + " deployment deleted");
// Delete the blueprint
- return delay(DELAY_DELETE_BLUEPRINT).then(function() {return cfy.deleteBlueprint(id);});
+ return delay(DELAY_DELETE_BLUEPRINT).then(function() {return cfy.deleteBlueprint(deploymentId);});
})
.then (function(result){
- logger.info("deploymentId: " + id + " successfully undeployed");
+ logger.info("deploymentId: " + deploymentId + " successfully undeployed");
return result;
})
.catch (function(err){
throw normalizeError(err);
});
+
};
+exports.finishUninstall = finishUninstall;
+
+// Get the status of a workflow execution
+exports.getExecutionStatus = function (exid) {
+ return cfy.getWorkflowExecutionStatus(exid)
+ .then(function(res){
+
+ var result = {
+ operationType: res.json.workflow_id
+ }
+
+ // Map execution status
+ if (res.json.status === "terminated") {
+ result.status = "succeeded";
+ }
+ else if (res.json.status === "failed") {
+ result.status = "failed";
+ }
+ else if (res.json.status === "cancelled" || res.stats === "canceled") {
+ result.status = "canceled";
+ }
+ else {
+ result.status = "processing";
+ }
+
+ if (res.json.error) {
+ result.error = res.json.error;
+ }
+ logger.debug("getExecutionStatus result: " + JSON.stringify(result));
+ return result;
+ })
+ .catch(function(error) {
+ throw normalizeError(error);
+ });
+};
+
+// Go through the Cloudify API call sequence to do a deployment
+exports.deployBlueprint = function(id, blueprint, inputs) {
+
+ // Upload blueprint, create deployment, and initiate install workflow
+ return launchBlueprint(id, blueprint, inputs)
+
+ // Wait for the workflow to complete
+ .then(
+
+ // launchBlueprint promise fulfilled -- finish installation
+ function(result){
+ return finishInstallation(result.deploymentId, result.executionId); // Will throw normalized error if it fails
+ },
+
+ // launchBlueprint promise rejected -- report error
+ 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 launch uninstall workflow
+ return launchUninstall(id)
+
+ // launchUninstall promise fulfilled -- finish uninstall
+ .then (function(result){
+ return finishUninstall(result.deploymentId, result.executionId); // Will throw normalized error if it fails
+ },
+
+ // launchUninstall promise rejected -- report error
+ function(err){
+ throw normalizeError(err);
+ });
+};
+
diff --git a/lib/dispatcher-error.js b/lib/dispatcher-error.js
new file mode 100644
index 0000000..ae51fcc
--- /dev/null
+++ b/lib/dispatcher-error.js
@@ -0,0 +1,53 @@
+/*
+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";
+
+/*
+ * Extend the standard Error type by appending fields to capture more information at the
+ * point of detection. The error information guides dispatcher's response to the incoming HTTP request
+ * that triggered the error and helps make the error log more specific and meaningful.
+ * This type of Error typically reports on problems encountered when attempting to use a downstream API.
+ *
+ * The standard Error has two fields:
+ * - name: the name of the Error, which is 'Error'
+ * - message: a text description of the error
+ *
+ * For dispatcher purposes, we add:
+ * - status: the HTTP status code that dispatcher should use in its response
+ * - type: "system" or "api" depending on whether the error was the result of a failed system call or
+ * an error reported by the downstream API.
+ * - logCode: the error code to use in the log entry.
+ * - target: the downstream system dispatcher was attempting to interact with
+ *
+ * Note that we're not defining a new class, just adding fields to the existing Error type. This pattern is
+ * used in Node for system errors.
+ */
+
+/* Create an error given the parameters */
+exports.createDispatcherError = function(message, status, type, logCode, target) {
+ var e = new Error();
+
+ e.message = message || 'no error information';
+ e.status = status || 500;
+ e.type = type;
+ e.logCode = logCode || 900;
+ e.target = target || '';
+
+ return e;
+};
+
+
diff --git a/lib/events.js b/lib/events.js
index 11a3ec0..743200d 100644
--- a/lib/events.js
+++ b/lib/events.js
@@ -44,7 +44,7 @@ router.use(middleware.expandTemplates); // Expand any blueprint templates
/* Accept an incoming event */
router.post('/', function(req, res, next) {
- let response = {requestId: req.dcaeReqId, deploymentIds:[]};
+ var response = {requestId: req.dcaeReqId, deploymentIds:[]};
if (req.body.dcae_service_action === 'deploy') {
@@ -56,8 +56,8 @@ router.post('/', function(req, res, next) {
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);
+ var deployer = services.createDeployer(req);
+ var outputs = req.dcae_blueprints.map(deployer);
response.deploymentIds = req.dcae_blueprints.map(function(s) {return s.deploymentId;});
}
else {
@@ -68,12 +68,12 @@ router.post('/', function(req, res, next) {
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);
+ var 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);
+ logAccess(req, 202, req.body.dcae_service_action);
});
module.exports = router; \ No newline at end of file
diff --git a/lib/inventory.js b/lib/inventory.js
index 5124a98..fbf83df 100644
--- a/lib/inventory.js
+++ b/lib/inventory.js
@@ -19,6 +19,7 @@ See the License for the specific language governing permissions and limitations
"use strict";
const req = require('./promise_request');
+ const createError = require('./dispatcher-error').createDispatcherError;
const INV_SERV_TYPES = '/dcae-service-types';
const INV_SERVICES = '/dcae-services';
@@ -34,12 +35,20 @@ See the License for the specific language governing permissions and limitations
return [];
}
else {
- var newErr = new Error("Error response " + err.status + " from DCAE inventory: " + err.body);
- newErr.status = 502;
- newErr.code = 502;
+ var newErr;
+ var message;
+ if (err.status) {
+ /* Got a response from inventory indicating an error */
+ message = "Error response " + err.status + " from DCAE inventory: " + err.body;
+ newErr = createError(message, 502, "api", 501, "dcae-inventory");
+ }
+ else {
+ /* Problem connecting to inventory */
+ message = "Error communicating with inventory: " + err.message;
+ newErr = createError(message, 504, "system", 201, "dcae-inventory");
+ }
throw newErr;
- }
-
+ }
};
/*
@@ -48,30 +57,45 @@ See the License for the specific language governing permissions and limitations
* objects, each object having: - type: the service type name associated
* with the blueprint template - template: the blueprint template
*/
- const findTemplates = function(targetType, location, serviceId) {
+ const findTemplates = function(targetType, location, serviceId, asdcServiceId, asdcResourceId) {
/* Set up query string based on available parameters */
- var qs = {vnfType: targetType, serviceLocation: location };
+ var qs = {serviceLocation: location, onlyActive: true, onlyLatest: true};
+
if (serviceId) {
qs.serviceId = serviceId;
}
+
+ if (asdcResourceId) {
+ qs.asdcResourceId = asdcResourceId;
+ }
+
+ if (asdcServiceId){
+ qs.asdcServiceId = asdcServiceId;
+ }
+
+ /* We'll set vnfType in the query except when both asdcServiceId and asdcResourceId are populated */
+ if (!(asdcResourceId && asdcServiceId)) {
+ qs.vnfType = targetType;
+ }
+ /* Make the request to inventory */
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);
+ method : "GET",
+ uri : config.inventory.url + INV_SERV_TYPES,
+ qs: qs
+ };
+ return req.doRequest(reqOptions)
+ .then(function(result) {
+ var templates = [];
+ var content = JSON.parse(result.body);
+ if (content.items) {
+ /* Pick out the fields we want */
+ templates = content.items.map(function(i) {return {type: i.typeId, template: i.blueprintTemplate};});
+ }
+ return templates;
+ })
+ .catch (invError);
};
/*
@@ -88,8 +112,8 @@ See the License for the specific language governing permissions and limitations
};
return req.doRequest(reqOptions)
.then(function(result) {
- let services = [];
- let content = JSON.parse(result.body);
+ var services = [];
+ var 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};});
@@ -113,8 +137,8 @@ See the License for the specific language governing permissions and limitations
};
return req.doRequest(reqOptions)
.then(function(result) {
- let shareables = {};
- let content = JSON.parse(result.body);
+ var shareables = {};
+ var content = JSON.parse(result.body);
if (content.items) {
content.items.forEach(function(s) {
s.components.filter(function(c) {return c.shareable === 1;})
@@ -130,25 +154,42 @@ See the License for the specific language governing permissions and limitations
/*
- * 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.
- */
+ * Middleware-style function to check inventory.
+ *
+ * For 'deploy' operations:
+ * - finds blueprint templates and shareable components
+ * - attaches list of templates to req.dcae_templates
+ * - attaches 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)
+ if (req.body.dcae_service_action.toLowerCase() === 'deploy') {
+ findTemplates(
+ req.body.dcae_target_type,
+ req.body.dcae_service_location,
+ req.body.dcae_service_type,
+ req.body['dcae_service-instance_persona-model-id'],
+ req.body['dcae_generic-vnf_persona-model-id']
+ )
.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);
+ var paramList = [
+ req.body.dcae_target_type,
+ req.body.dcae_service_location,
+ req.body.dcae_service_type || "unspecified",
+ req.body['dcae_service-instance_persona-model-id'] || "unspecified",
+ req.body['dcae_generic-vnf_persona-model-id'] || "unspecified"
+ ].join('/');
+ var err0 = new Error(paramList + ' has no associated DCAE service types');
+ err0.status = 400;
+ next(err0);
}
})
.then(function(result) {
@@ -170,9 +211,9 @@ See the License for the specific language governing permissions and limitations
next();
}
else {
- let err = new Error('"' + req.body.dcae_target_name + '" has no deployed DCAE services');
- err.status = 400;
- next(err);
+ var err1 = new Error('"' + req.body.dcae_target_name + '" has no deployed DCAE services');
+ err1.status = 400;
+ next(err1);
}
})
.catch(function(err) {
@@ -180,9 +221,9 @@ See the License for the specific language governing permissions and limitations
});
}
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);
+ var err2 = new Error ('"' + req.body.dcae_service_action + '" is not a valid service action. Valid actions: "deploy", "undeploy"');
+ err2.status = 400;
+ next(err2);
}
};
@@ -192,12 +233,12 @@ See the License for the specific language governing permissions and limitations
exports.addService = function(deploymentId, serviceType, vnfId, vnfType, vnfLocation, outputs) {
/* Create the service description */
- let serviceDescription =
+ var serviceDescription =
{
- "typeName" : serviceType,
"vnfId" : vnfId,
"vnfType" : vnfType,
"vnfLocation" : vnfLocation,
+ "typeId" : serviceType,
"deploymentRef" : deploymentId
};
@@ -226,4 +267,82 @@ See the License for the specific language governing permissions and limitations
exports.deleteService = function(serviceId) {
return req.doRequest({method: "DELETE", uri: config.inventory.url + INV_SERVICES + "/" + serviceId});
};
- \ No newline at end of file
+
+ /*
+ * Find running/deploying instances of services (with a given type name, if specified)
+ */
+
+ exports.getServicesByType = function(query) {
+ var options = {
+ method: 'GET',
+ uri: config.inventory.url + INV_SERVICES,
+ qs: query || {}
+ };
+
+ return req.doRequest(options)
+ .then (function (result) {
+ var services = [];
+ var content = JSON.parse(result.body);
+ if(content.items) {
+ /* Pick out the fields we want */
+ services = content.items.map(function(i) { return { deploymentId: i.deploymentRef, serviceTypeId: i.typeId};});
+ }
+ return services;
+ })
+ .catch(invError);
+ };
+
+ /*
+ * Find a blueprint given the service type ID -- return blueprint and type ID
+ */
+ exports.getBlueprintByType = function(serviceTypeId) {
+ return req.doRequest({
+ method: "GET",
+ uri: config.inventory.url + INV_SERV_TYPES + '/' + serviceTypeId
+ })
+ .then (function(result) {
+ var blueprintInfo = {};
+ var content = JSON.parse(result.body);
+ blueprintInfo.blueprint = content.blueprintTemplate;
+ blueprintInfo.typeId = content.typeId;
+
+ return blueprintInfo;
+ })
+ .catch(invError);
+ };
+
+ /*
+ * Verify that the specified deployment ID does not already have
+ * an entry in inventory. This is needed to enforce the rule that
+ * creating a second instance of a deployment under the
+ * same ID as an existing deployment is not permitted.
+ * The function checks for a service in inventory using the
+ * deployment ID as service name. If it doesn't exist, the function
+ * resolves its promise. If it *does* exist, then it throws an error.
+ */
+ exports.verifyUniqueDeploymentId = function(deploymentId) {
+
+ return req.doRequest({
+ method: "GET",
+ uri: config.inventory.url + INV_SERVICES + "/" + deploymentId
+ })
+
+ /* Successful lookup -- the deployment exists, so throw an error */
+ .then(function(res) {
+ throw createError("Deployment " + deploymentId + " already exists", 409, "api", 501);
+ },
+
+ /* Error from the lookup -- either deployment ID doesn't exist or some other problem */
+ function (err) {
+
+ /* Inventory returns a 404 if it does not find the deployment ID */
+ if (err.status && err.status === 404) {
+ return true;
+ }
+
+ /* Some other error -- it really is an error and we can't continue */
+ else {
+ return invError(err);
+ }
+ });
+ }
diff --git a/lib/logging.js b/lib/logging.js
index db168b3..21e6d68 100644
--- a/lib/logging.js
+++ b/lib/logging.js
@@ -16,16 +16,128 @@ See the License for the specific language governing permissions and limitations
"use strict";
const config = process.mainModule.exports.config;
-const accessLogger = config.logSource.getLogger('access');
+const auditLogger = config.logSource.getLogger('audit');
+const defaultLogger = config.logSource.getLogger();
+/* Audit log fields */
+const AUDIT_BEGIN = 0;
+const AUDIT_END = 1;
+const AUDIT_REQID = 2;
+const AUDIT_SVCINST = 3;
+const AUDIT_THREAD = 4;
+const AUDIT_SRVNAME = 5;
+const AUDIT_SVCNAME = 6;
+const AUDIT_PARTNER = 7;
+const AUDIT_STATUSCODE = 8;
+const AUDIT_RESPCODE = 9;
+const AUDIT_RESPDESC = 10;
+const AUDIT_INSTUUID = 11;
+const AUDIT_CATLOGLEVEL = 12;
+const AUDIT_SEVERITY = 13;
+const AUDIT_SRVIP = 14;
+const AUDIT_ELAPSED = 15;
+const AUDIT_SERVER = 16;
+const AUDIT_CLIENTIP = 17;
+const AUDIT_CLASSNAME = 18;
+const AUDIT_UNUSED = 19;
+const AUDIT_PROCESSKEY = 20;
+const AUDIT_CUSTOM1 = 21;
+const AUDIT_CUSTOM2 = 22;
+const AUDIT_CUSTOM3 = 23;
+const AUDIT_CUSTOM4 = 24;
+const AUDIT_DETAILMSG = 25;
+const AUDIT_NFIELDS = 26;
-/* Logging */
+/* Error log fields */
+const ERROR_TIMESTAMP = 0;
+const ERROR_REQID = 1;
+const ERROR_THREAD = 2;
+const ERROR_SVCNAME = 3;
+const ERROR_PARTNER = 4;
+const ERROR_TGTENTITY = 5;
+const ERROR_TGTSVC = 6;
+const ERROR_CATEGORY = 7;
+const ERROR_CODE = 8;
+const ERROR_DESCRIPTION = 9;
+const ERROR_MESSAGE = 10;
+const ERROR_NFIELDS = 11;
-exports.logAccess = function (req, status, extra) {
- let entry = req.dcaeReqId + " " + req.connection.remoteAddress + " " + req.method + " " + req.originalUrl + " " + status;
+/* Error code -> description mapping */
+const descriptions = {
+
+ 201: 'Inventory communication error',
+ 202: 'Cloudify Manager communication error',
+
+ 501: 'Inventory API error',
+ 502: 'Cloudify Manager API error',
+
+ 551: 'HTTP(S) Server initialization error',
+ 552: 'Dispatcher start-up error',
+
+ 999: 'Unknown error'
+};
+
+/* Format audit record for an incoming API request */
+const formatAuditRecord = function(req, status, extra) {
+ var rec = new Array(AUDIT_NFIELDS);
+ const end = new Date();
+ rec[AUDIT_END] = end.toISOString();
+ rec[AUDIT_BEGIN] = req.startTime.toISOString();
+ rec[AUDIT_REQID] = req.dcaeReqId;
+ rec[AUDIT_SRVNAME] = req.hostname; // Use the value from the Host header
+ rec[AUDIT_SVCNAME] = req.method + ' ' + req.originalUrl; // Method and URL identify the operation being performed
+ rec[AUDIT_STATUSCODE] = (status < 300 ) ? "COMPLETE" : "ERROR";
+ rec[AUDIT_RESPCODE] = status; // Use the HTTP status code--does not match the table in the logging spec, but makes more sense
+ rec[AUDIT_CATLOGLEVEL] = "INFO"; // The audit records are informational, regardless of the outcome of the operation
+ rec[AUDIT_SRVIP] = req.socket.address().address;
+ rec[AUDIT_ELAPSED] = end - req.startTime;
+ rec[AUDIT_SERVER] = req.hostname // From the Host header, again
+ rec[AUDIT_CLIENTIP] = req.connection.remoteAddress;
+
if (extra) {
- extra = extra.replace(/\n/g, " "); /* Collapse multi-line extra data to a single line */
- entry = entry + " <" + extra + ">";
+ rec[AUDIT_DETAILMSG]= extra.replace(/\n/g, " "); /* Collapse multi-line extra data to a single line */
+ }
+ return rec.join('|');
+};
+
+/* Format error log record */
+const formatErrorRecord = function(category, code, detail, req, target) {
+ var rec = new Array(ERROR_NFIELDS);
+
+ /* Common fields */
+ rec[ERROR_TIMESTAMP] = (new Date()).toISOString();
+ rec[ERROR_CATEGORY] = category;
+ rec[ERROR_CODE] = code;
+ rec[ERROR_DESCRIPTION] = descriptions[code] || 'no description available';
+
+ /* Log error detail in a single line if provided */
+ if (detail) {
+ rec[ERROR_MESSAGE] = detail.replace(/\n/g, " ");
+ }
+
+ /* Fields available if the error happened during processing of an incoming API request */
+ if (req) {
+ rec[ERROR_REQID] = req.dcaeReqId;
+ rec[ERROR_SVCNAME] = req.method + ' ' + req.originalUrl; // Method and URL identify the operation being performed
+ rec[ERROR_PARTNER] = req.connection.remoteAddress; // We don't have the partner's name, but we know the remote IP address
}
- accessLogger.info(entry);
-}; \ No newline at end of file
+
+ /* Include information about the target entity/service if available */
+ if (target) {
+ rec[ERROR_TGTENTITY] = target.entity || '';
+ rec[ERROR_TGTSVC] = target.service || '';
+ }
+ return rec.join('|');
+};
+
+exports.logAccess = function(req, status, extra) {
+ auditLogger.info(formatAuditRecord(req, status, extra));
+};
+
+exports.logError = function(error, req) {
+ defaultLogger.error(formatErrorRecord("ERROR", error.logCode, error.message, req, {entity: error.target}));
+};
+
+exports.logWarn = function(error, req) {
+ defaultLogger.error(formatErrorRecord("WARN", error.logCode, error.message, req, {entity: error.target}));
+};
diff --git a/lib/middleware.js b/lib/middleware.js
index 567620f..5f78b20 100644
--- a/lib/middleware.js
+++ b/lib/middleware.js
@@ -20,26 +20,31 @@ See the License for the specific language governing permissions and limitations
const ejs = require('ejs');
const utils = require('./utils');
-const logAccess = require('./logging').logAccess;
+const logging = require('./logging');
+const logAccess = logging.logAccess;
+const logError = logging.logError;
+const logWarn = logging.logWarn;
const config = process.mainModule.exports.config;
const locations = config.locations;
-const logger = config.logSource.getLogger("errhandler");
-/* Assign a request ID to each incoming request */
+/* Assign a request ID and start time to each incoming request */
exports.assignId = function(req, res, next) {
- req.dcaeReqId = utils.generateId();
+ /* Use request ID from header if available, otherwise generate one */
+ req.dcaeReqId = req.get('X-ECOMP-RequestID') || utils.generateId();
+ req.startTime = new Date();
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'
+ var status = err.status || 500;
+ var 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}));
+ logError(err, req);
}
};
@@ -53,7 +58,7 @@ exports.checkType = function(type){
next();
}
else {
- let err = new Error ('Content-Type must be \'' + type +'\'');
+ var err = new Error ('Content-Type must be \'' + type +'\'');
err.status = 415;
next (err);
}
@@ -65,7 +70,7 @@ 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(','));
+ var err = new Error ('Request missing required properties: ' + missing.join(','));
err.status = 400;
next(err);
}
@@ -83,7 +88,7 @@ exports.checkLocation = function(req, res, next) {
next();
}
else {
- let err = new Error ('"' + req.body.dcae_service_location + '" is not a supported location');
+ var err = new Error ('"' + req.body.dcae_service_location + '" is not a supported location');
err.status = 400;
next(err);
}
@@ -97,14 +102,14 @@ exports.checkLocation = function(req, res, next) {
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
+ var 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) {
+ var blueprints = req.dcae_templates.map(function (template) {
//TODO possibly compute intensive- is there a better way?
return {
blueprint: ejs.render(template.template, context),
diff --git a/lib/promise_request.js b/lib/promise_request.js
index 906c16c..5a97bba 100644
--- a/lib/promise_request.js
+++ b/lib/promise_request.js
@@ -19,26 +19,53 @@ See the License for the specific language governing permissions and limitations
"use strict";
/*
- * Make an HTTP request using a string or a readable stream for the body
+ * Make an HTTP request using a string 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');
+const http = require('http');
+const https = require('https');
+const url = require('url');
+const querystring = require('querystring');
-let logger = null;
+var logger = null;
exports.doRequest = function(options, body) {
+
return new Promise(function(resolve, reject) {
+
+ var reqBody = null;
+ if (options.json) {
+ reqBody = JSON.stringify(options.json);
+ options.headers = options.headers || {};
+ options.headers['Content-Type'] = 'application/json';
+ }
+ else if (body) {
+ reqBody = body;
+ }
+
+ if (options.uri) {
+ var parsed = url.parse(options.uri);
+ options.protocol = parsed.protocol;
+ options.hostname = parsed.hostname;
+ options.port = parsed.port;
+ options.path = parsed.path;
+ if (options.qs) {
+ options.path += ('?' + querystring.stringify(options.qs));
+ }
+ }
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;
+
+ try {
+ var req = (options.protocol === 'https:' ? https.request(options) : http.request(options));
+ }
+ catch (e) {
+ logger.debug('Error constructing request: ' + e);
+ reject(e);
}
- var req = request(options);
// Reject promise if there's an error
req.on('error', function(error) {
@@ -88,13 +115,12 @@ exports.doRequest = function(options, body) {
}
});
});
-
- // 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);
+
+ if (reqBody) {
+ req.write(reqBody, 'utf8');
}
- });
+ req.end();
+ });
};
exports.setLogger = function(logsource) {
diff --git a/lib/services.js b/lib/services.js
index 5a2a7d7..9508efd 100644
--- a/lib/services.js
+++ b/lib/services.js
@@ -22,6 +22,8 @@ const ejs = require('ejs');
const deploy = require('./deploy');
const inventory = require('./inventory');
const config = process.mainModule.exports.config;
+const logError = require('./logging').logError;
+const logAudit = require('./logging').logAccess;
/* Set up logging */
var logger = config.logSource.getLogger("services");
@@ -35,7 +37,7 @@ exports.createDeployer = function(req) {
return function(blueprint) {
/* Generate a deploymentId */
- let deploymentId = blueprint.deploymentId;
+ var deploymentId = blueprint.deploymentId;
/* Attempt the deployment */
logger.info(req.dcaeReqId + " " + "Attempting to deploy deploymentId " + deploymentId);
@@ -59,10 +61,14 @@ exports.createDeployer = function(req) {
);
})
.then(function(result) {
- logger.info(req.dcaeReqId + " Updated inventory for deploymentId: " + deploymentId);
+ logAudit(req, 200, "Deployed id: " + deploymentId);
})
.catch(function(err) {
- logger.error(req.dcaeReqId + " Failed to deploy deploymentId: " + deploymentId + " Error: " + JSON.stringify(err));
+ /* err should be a standard dispatcher error generated by the deploy or inventory modules */
+ /* Enrich the message with the deployment ID */
+ err.message = "Error deploying " + deploymentId + ": " + err.message;
+ logError(err, req);
+ logAudit(req, 500, err.message);
//TODO try uninstall?
});
};
@@ -87,10 +93,14 @@ exports.createUndeployer = function(req) {
return inventory.deleteService(deployment.deploymentId);
})
.then(function(result){
- logger.info(req.dcaeReqId + " Deleted service from inventory for deploymentId: " + deployment.deploymentId);
+ logAudit(req, 200, "Undeployed id: " + deployment.deploymentId);
})
.catch(function(err){
- logger.error(req.dcaeReqId + " Error undeploying " + deployment.deploymentId + ": " + JSON.stringify(err));
+ /* err should be a standard dispatcher error generated by the deploy or inventory modules */
+ /* Enrich the message with the deployment ID */
+ err.message = "Error undeploying " + deployment.deploymentId + ": " + err.message;
+ logError(err, req);
+ logAudit(req, 500, err.message);
});
};
diff --git a/lib/utils.js b/lib/utils.js
index bf582e8..856ace8 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -16,49 +16,9 @@ See the License for the specific language governing permissions and limitations
"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();
- }
-
- });
- }
- });
-
- });
-};
+const uuid = require('node-uuid');
-// 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();
- }
- });
- });
-};
+// Utility functions
/* Does object 'o' have property 'key' */
exports.hasProperty = function(o, key) {
diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json
new file mode 100644
index 0000000..4efd942
--- /dev/null
+++ b/npm-shrinkwrap.json
@@ -0,0 +1,293 @@
+{
+ "name": "DCAE-Orch-Dispatcher",
+ "version": "3.0.1",
+ "dependencies": {
+ "accepts": {
+ "version": "1.3.3",
+ "from": "accepts@>=1.3.3 <1.4.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz"
+ },
+ "adm-zip": {
+ "version": "0.4.7",
+ "from": "adm-zip@>=0.4.7 <0.5.0",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.7.tgz"
+ },
+ "array-flatten": {
+ "version": "1.1.1",
+ "from": "array-flatten@1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz"
+ },
+ "body-parser": {
+ "version": "1.17.1",
+ "from": "body-parser@>=1.15.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.17.1.tgz"
+ },
+ "bytes": {
+ "version": "2.4.0",
+ "from": "bytes@2.4.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz"
+ },
+ "content-disposition": {
+ "version": "0.5.2",
+ "from": "content-disposition@0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz"
+ },
+ "content-type": {
+ "version": "1.0.2",
+ "from": "content-type@>=1.0.2 <1.1.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.2.tgz"
+ },
+ "cookie": {
+ "version": "0.3.1",
+ "from": "cookie@0.3.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz"
+ },
+ "cookie-signature": {
+ "version": "1.0.6",
+ "from": "cookie-signature@1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz"
+ },
+ "core-util-is": {
+ "version": "1.0.2",
+ "from": "core-util-is@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
+ },
+ "daemon": {
+ "version": "1.1.0",
+ "from": "daemon@>=1.1.0 <2.0.0",
+ "resolved": "https://registry.npmjs.org/daemon/-/daemon-1.1.0.tgz"
+ },
+ "debug": {
+ "version": "2.6.1",
+ "from": "debug@2.6.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.1.tgz"
+ },
+ "depd": {
+ "version": "1.1.0",
+ "from": "depd@>=1.1.0 <1.2.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz"
+ },
+ "destroy": {
+ "version": "1.0.4",
+ "from": "destroy@>=1.0.4 <1.1.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz"
+ },
+ "ee-first": {
+ "version": "1.1.1",
+ "from": "ee-first@1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz"
+ },
+ "ejs": {
+ "version": "2.5.6",
+ "from": "ejs@>=2.4.1 <3.0.0",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.6.tgz"
+ },
+ "encodeurl": {
+ "version": "1.0.1",
+ "from": "encodeurl@>=1.0.1 <1.1.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.1.tgz"
+ },
+ "escape-html": {
+ "version": "1.0.3",
+ "from": "escape-html@>=1.0.3 <1.1.0",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz"
+ },
+ "etag": {
+ "version": "1.8.0",
+ "from": "etag@>=1.8.0 <1.9.0",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.0.tgz"
+ },
+ "express": {
+ "version": "4.15.2",
+ "from": "express@>=4.13.4 <5.0.0",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.15.2.tgz"
+ },
+ "finalhandler": {
+ "version": "1.0.1",
+ "from": "finalhandler@>=1.0.0 <1.1.0",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.1.tgz",
+ "dependencies": {
+ "debug": {
+ "version": "2.6.3",
+ "from": "debug@2.6.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.3.tgz"
+ }
+ }
+ },
+ "forwarded": {
+ "version": "0.1.0",
+ "from": "forwarded@>=0.1.0 <0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.0.tgz"
+ },
+ "fresh": {
+ "version": "0.5.0",
+ "from": "fresh@0.5.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz"
+ },
+ "http-errors": {
+ "version": "1.6.1",
+ "from": "http-errors@>=1.6.1 <1.7.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz"
+ },
+ "iconv-lite": {
+ "version": "0.4.15",
+ "from": "iconv-lite@0.4.15",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz"
+ },
+ "inherits": {
+ "version": "2.0.3",
+ "from": "inherits@2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
+ },
+ "ipaddr.js": {
+ "version": "1.3.0",
+ "from": "ipaddr.js@1.3.0",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.3.0.tgz"
+ },
+ "isarray": {
+ "version": "0.0.1",
+ "from": "isarray@0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
+ },
+ "log4js": {
+ "version": "0.6.38",
+ "from": "log4js@>=0.6.33 <0.7.0",
+ "resolved": "https://registry.npmjs.org/log4js/-/log4js-0.6.38.tgz"
+ },
+ "media-typer": {
+ "version": "0.3.0",
+ "from": "media-typer@0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz"
+ },
+ "merge-descriptors": {
+ "version": "1.0.1",
+ "from": "merge-descriptors@1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz"
+ },
+ "methods": {
+ "version": "1.1.2",
+ "from": "methods@>=1.1.2 <1.2.0",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz"
+ },
+ "mime": {
+ "version": "1.3.4",
+ "from": "mime@1.3.4",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz"
+ },
+ "mime-db": {
+ "version": "1.27.0",
+ "from": "mime-db@>=1.27.0 <1.28.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz"
+ },
+ "mime-types": {
+ "version": "2.1.15",
+ "from": "mime-types@>=2.1.13 <2.2.0",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz"
+ },
+ "ms": {
+ "version": "0.7.2",
+ "from": "ms@0.7.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz"
+ },
+ "negotiator": {
+ "version": "0.6.1",
+ "from": "negotiator@0.6.1",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz"
+ },
+ "node-uuid": {
+ "version": "1.4.8",
+ "from": "node-uuid@>=1.4.3 <2.0.0",
+ "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz"
+ },
+ "on-finished": {
+ "version": "2.3.0",
+ "from": "on-finished@>=2.3.0 <2.4.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz"
+ },
+ "parseurl": {
+ "version": "1.3.1",
+ "from": "parseurl@>=1.3.1 <1.4.0",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.1.tgz"
+ },
+ "path-to-regexp": {
+ "version": "0.1.7",
+ "from": "path-to-regexp@0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz"
+ },
+ "proxy-addr": {
+ "version": "1.1.4",
+ "from": "proxy-addr@>=1.1.3 <1.2.0",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.4.tgz"
+ },
+ "qs": {
+ "version": "6.4.0",
+ "from": "qs@6.4.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz"
+ },
+ "range-parser": {
+ "version": "1.2.0",
+ "from": "range-parser@>=1.2.0 <1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz"
+ },
+ "raw-body": {
+ "version": "2.2.0",
+ "from": "raw-body@>=2.2.0 <2.3.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.2.0.tgz"
+ },
+ "readable-stream": {
+ "version": "1.0.34",
+ "from": "readable-stream@>=1.0.2 <1.1.0",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz"
+ },
+ "semver": {
+ "version": "4.3.6",
+ "from": "semver@>=4.3.3 <4.4.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
+ },
+ "send": {
+ "version": "0.15.1",
+ "from": "send@0.15.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.15.1.tgz"
+ },
+ "serve-static": {
+ "version": "1.12.1",
+ "from": "serve-static@1.12.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.1.tgz"
+ },
+ "setprototypeof": {
+ "version": "1.0.3",
+ "from": "setprototypeof@1.0.3",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz"
+ },
+ "statuses": {
+ "version": "1.3.1",
+ "from": "statuses@>=1.3.1 <2.0.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz"
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "from": "string_decoder@>=0.10.0 <0.11.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
+ },
+ "type-is": {
+ "version": "1.6.14",
+ "from": "type-is@>=1.6.14 <1.7.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.14.tgz"
+ },
+ "unpipe": {
+ "version": "1.0.0",
+ "from": "unpipe@1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz"
+ },
+ "utils-merge": {
+ "version": "1.0.0",
+ "from": "utils-merge@1.0.0",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz"
+ },
+ "vary": {
+ "version": "1.1.1",
+ "from": "vary@>=1.1.0 <1.2.0",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.1.tgz"
+ }
+ }
+}
diff --git a/package.json b/package.json
index 4270f13..4636012 100644
--- a/package.json
+++ b/package.json
@@ -1,18 +1,16 @@
{
"name": "DCAE-Orch-Dispatcher",
- "version": "2.0.0",
+ "version": "3.0.1",
"description": "DCAE Orchestrator Dispatcher",
"main": "dispatcher.js",
"dependencies": {
+ "adm-zip": "^0.4.7",
"body-parser": "^1.15.0",
"daemon": "^1.1.0",
"ejs": "^2.4.1",
"express": "^4.13.4",
"log4js": "^0.6.33",
- "node-tar.gz": "^1.0.0",
- "node-uuid": "^1.4.3",
- "request": "^2.61.0",
- "rimraf": "^2.4.2"
+ "node-uuid": "^1.4.3"
},
"devDependencies": {},
"scripts": {
diff --git a/set_version.sh b/set_version.sh
new file mode 100755
index 0000000..dfb7e8e
--- /dev/null
+++ b/set_version.sh
@@ -0,0 +1,2 @@
+#!/bin/bash
+echo "exports.version=\"$(git describe --long --always)\";" > version.js
diff --git a/version.js b/version.js
index f2510a4..1fa7c69 100644
--- a/version.js
+++ b/version.js
@@ -1 +1 @@
-exports.version="2.0.0";
+exports.version="unspecified";