diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/auth.js | 2 | ||||
-rw-r--r-- | lib/cloudify.js | 137 | ||||
-rw-r--r-- | lib/config.js | 6 | ||||
-rw-r--r-- | lib/consul.js | 8 | ||||
-rw-r--r-- | lib/dcae-deployments.js | 109 | ||||
-rw-r--r-- | lib/deploy.js | 144 | ||||
-rw-r--r-- | lib/info.js | 40 | ||||
-rw-r--r-- | lib/inventory.js | 24 | ||||
-rw-r--r-- | lib/logging.js | 8 | ||||
-rw-r--r-- | lib/middleware.js | 29 | ||||
-rw-r--r-- | lib/policy.js | 545 | ||||
-rw-r--r-- | lib/promise_request.js | 22 |
12 files changed, 651 insertions, 423 deletions
diff --git a/lib/auth.js b/lib/auth.js index 9ddd7b3..901f318 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -62,4 +62,4 @@ exports.checkAuth = function(req, res, next) { else { next(); // Nothing to do, no authentication required } -};
\ No newline at end of file +}; diff --git a/lib/cloudify.js b/lib/cloudify.js index 23e779a..2db460a 100644 --- a/lib/cloudify.js +++ b/lib/cloudify.js @@ -1,5 +1,5 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 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. @@ -22,7 +22,7 @@ const CLOUDIFY = "cloudify-manager"; const FINISHED = [ "terminated", "cancelled", "failed" ]; const RETRY_INTERVAL = 5000; // Every 5 seconds const MAX_TRIES = 720; // Up to 1 hour - +const DEFAULT_TENANT = "default_tenant"; const doRequest = require('./promise_request').doRequest; const repeat = require('./repeat'); const admzip = require('adm-zip'); @@ -64,7 +64,8 @@ ExeQueue.prototype.nextExecution = function(deployment_id) { } return depl.exe_queue[0]; }; -var exeQueue = new ExeQueue(); +const exeQueue = new ExeQueue(); +exports.exeQueue = exeQueue; // Delay function--returns a promise that's resolved after 'dtime' // milliseconds.` @@ -75,32 +76,32 @@ var delay = function(dtime) { }; // Get current status of a workflow execution -const getExecutionStatus = function(execution_id, mainReq) { - /* Defense: Some callers do not supply mainReq */ - mainReq = mainReq || {}; +const getExecutionStatus = function(req, execution_id) { var reqOptions = { method : "GET", uri : cfyAPI + "/executions/" + execution_id }; - addAuthToOptions(reqOptions); - return doRequest(reqOptions, null, CLOUDIFY, mainReq); + + addAuthToOptions(reqOptions, req); + + return doRequest(req, reqOptions, null, CLOUDIFY); }; // Poll for the result of a workflow execution until it's done -var getWorkflowResult = function(execution_id, mainReq) { +const getWorkflowResult = function(mainReq, execution_id) { /* Defense: Some callers do not supply mainReq */ mainReq = mainReq || {}; - logger.debug(mainReq.dcaeReqId, "Getting workflow result for execution id: " + execution_id); + logger.info(mainReq.dcaeReqId, "Getting workflow result for execution id: " + execution_id); // Function for testing if workflow is finished // Expects the result of getExecStatus var checkStatus = function(res) { - logger.debug(mainReq.dcaeReqId, "Checking result: " + JSON.stringify(res) + " ==> " + (res.json && res.json.status && FINISHED.indexOf(res.json.status) < 0)); + logger.info(mainReq.dcaeReqId, "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; }; // Create execution status checker function - var getExecStatus = function() {return getExecutionStatus(execution_id, mainReq);}; + var getExecStatus = function() {return getExecutionStatus(mainReq, execution_id);}; return repeat.repeatWhile(getExecStatus, checkStatus, MAX_TRIES, RETRY_INTERVAL) .then( @@ -108,7 +109,7 @@ var getWorkflowResult = function(execution_id, mainReq) { /* Handle fulfilled promise from repeatWhile */ function(res) { - logger.debug(mainReq.dcaeReqId, 'workflow result: ' + JSON.stringify(res)); + logger.info(mainReq.dcaeReqId, 'workflow result: ' + JSON.stringify(res)); /* Successful completion */ if (res.json && res.json.status && res.json.status === 'terminated') { @@ -173,7 +174,9 @@ const startWorkflowExecution = function(mainReq, deployment_id, workflow_id, par "Accept" : "*/*" } }; - addAuthToOptions(reqOptions); + + addAuthToOptions(reqOptions, mainReq); + var body = { "deployment_id" : deployment_id, "workflow_id" : workflow_id @@ -181,18 +184,18 @@ const startWorkflowExecution = function(mainReq, deployment_id, workflow_id, par if (parameters) {body.parameters = parameters;} // Make the POST request - return doRequest(reqOptions, JSON.stringify(body), CLOUDIFY, mainReq); + return doRequest(mainReq, reqOptions, JSON.stringify(body), CLOUDIFY); }; //Initiate a workflow execution against a deployment -const initiateWorkflowExecution = function(deployment_id, workflow_id, parameters) { - return startWorkflowExecution(null, deployment_id, workflow_id, parameters) +const initiateWorkflowExecution = function(req, deployment_id, workflow_id, parameters) { + return startWorkflowExecution(req, deployment_id, workflow_id, parameters) .then(function(result) { - logger.debug(null, "Result from POSTing workflow execution start: " + JSON.stringify(result)); + logger.info(req.dcaeReqId, "Result from POSTing workflow execution start: " + JSON.stringify(result)); if (result.json && result.json.id) { return {deploymentId: deployment_id, workflowType: workflow_id, executionId: result.json.id}; } - logger.debug(null,"Did not get expected JSON body from POST to start workflow"); + logger.info(req.dcaeReqId,"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; throw err; @@ -200,31 +203,32 @@ const initiateWorkflowExecution = function(deployment_id, workflow_id, parameter }; // Uploads a blueprint via the Cloudify API -exports.uploadBlueprint = function(bpid, blueprint) { +exports.uploadBlueprint = function(req, bpid, blueprint) { + logger.info(req.dcaeReqId, "uploadBlueprint " + bpid); // Cloudify API wants a gzipped tar of a directory, not the blueprint text - var zip = new admzip(); + const zip = new admzip(); zip.addFile('work/', new Buffer(0)); zip.addFile('work/blueprint.yaml', new Buffer(blueprint, 'utf8')); - var src = (zip.toBuffer()); + const zip_buffer = zip.toBuffer(); // Set up the HTTP PUT request - var reqOptions = { - method : "PUT", - uri : cfyAPI + "/blueprints/" + bpid, - headers : { - "Content-Type" : "application/octet-stream", - "Accept" : "*/*" - } + const reqOptions = { + method : "PUT", + uri : cfyAPI + "/blueprints/" + bpid, + headers : { + "Content-Type" : "application/octet-stream", + "Accept" : "*/*" + } }; - addAuthToOptions(reqOptions); + addAuthToOptions(reqOptions, req); // Initiate PUT request and return the promise for a result - return doRequest(reqOptions, src, CLOUDIFY); + return doRequest(req, reqOptions, zip_buffer, CLOUDIFY); }; // Creates a deployment from a blueprint -exports.createDeployment = function(dpid, bpid, inputs) { +exports.createDeployment = function(req, dpid, bpid, inputs) { // Set up the HTTP PUT request var reqOptions = { @@ -235,7 +239,7 @@ exports.createDeployment = function(dpid, bpid, inputs) { "Accept" : "*/*" } }; - addAuthToOptions(reqOptions); + addAuthToOptions(reqOptions, req); var body = { blueprint_id : bpid @@ -245,7 +249,7 @@ exports.createDeployment = function(dpid, bpid, inputs) { } // Make the PUT request to create the deployment - return doRequest(reqOptions, JSON.stringify(body), CLOUDIFY); + return doRequest(req, reqOptions, JSON.stringify(body), CLOUDIFY); }; // Initiate a workflow execution against a deployment @@ -258,19 +262,19 @@ exports.getWorkflowExecutionStatus = getExecutionStatus; exports.getWorkflowResult = getWorkflowResult; // Executes a workflow against a deployment and returns a promise for final result -exports.executeWorkflow = function(deployment_id, workflow_id, parameters) { - return initiateWorkflowExecution(deployment_id, workflow_id, parameters) +exports.executeWorkflow = function(req, deployment_id, workflow_id, parameters) { + return initiateWorkflowExecution(req, deployment_id, workflow_id, parameters) // Wait for the result .then (function(result) { - logger.debug(null, "Result from initiating workflow: " + JSON.stringify(result)); - return getWorkflowResult(result.executionId); + logger.info(req.dcaeReqId, "Result from initiating workflow: " + JSON.stringify(result)); + return getWorkflowResult(req, result.executionId); }); }; // Retrieves outputs for a deployment -exports.getOutputs = function(dpid) { +exports.getOutputs = function(req, dpid) { var reqOptions = { method : "GET", uri : cfyAPI + "/deployments/" + dpid + "/outputs", @@ -278,13 +282,14 @@ exports.getOutputs = function(dpid) { "Accept" : "*/*" } }; - addAuthToOptions(reqOptions); - return doRequest(reqOptions, null, CLOUDIFY); + addAuthToOptions(reqOptions, req); + + return doRequest(req, reqOptions, null, CLOUDIFY); }; // Get the output descriptions for a deployment -exports.getOutputDescriptions = function(dpid) { +exports.getOutputDescriptions = function(req, dpid) { var reqOptions = { method : "GET", uri : cfyAPI + "/deployments/" + dpid + "?include=outputs", @@ -292,31 +297,34 @@ exports.getOutputDescriptions = function(dpid) { "Accept" : "*/*" } }; - addAuthToOptions(reqOptions); - return doRequest(reqOptions, null, CLOUDIFY); + addAuthToOptions(reqOptions, req); + + return doRequest(req, reqOptions, null, CLOUDIFY); }; // Deletes a deployment -exports.deleteDeployment = function(dpid) { +exports.deleteDeployment = function(req, dpid) { var reqOptions = { method : "DELETE", uri : cfyAPI + "/deployments/" + dpid }; - addAuthToOptions(reqOptions); - return doRequest(reqOptions, null, CLOUDIFY); + addAuthToOptions(reqOptions, req); + + return doRequest(req, reqOptions, null, CLOUDIFY); }; // Deletes a blueprint -exports.deleteBlueprint = function(bpid) { +exports.deleteBlueprint = function(req, bpid) { var reqOptions = { method : "DELETE", uri : cfyAPI + "/blueprints/" + bpid }; - addAuthToOptions(reqOptions); - return doRequest(reqOptions, null, CLOUDIFY); + addAuthToOptions(reqOptions, req); + + return doRequest(req, reqOptions, null, CLOUDIFY); }; // Allow client to set the Cloudify API root address @@ -329,10 +337,16 @@ exports.setCredentials = function(user, password) { cfyAuth = cfyAuth || (user + ':' + password); }; -function addAuthToOptions(reqOptions) { +function addAuthToOptions(reqOptions, req) { + if (!!cfyAuth && cfyAuth !== "undefined:undefined") { reqOptions.auth = cfyAuth; } + reqOptions.headers = reqOptions.headers || {}; + reqOptions.headers.Tenant = req.query.cfy_tenant_name || DEFAULT_TENANT; + + logger.debug(req.dcaeReqId, "Calling " + reqOptions.uri + " with Tenant: " + reqOptions.headers.Tenant ); + } // Set a logger @@ -346,12 +360,13 @@ exports.getNodeInstances = function (mainReq, on_next_node_instances, offset) { method : "GET", uri : cfyAPI + "/node-instances?_include=id,deployment_id,runtime_properties&_offset=" + offset }; - addAuthToOptions(reqOptions); - logger.debug(mainReq.dcaeReqId, "getNodeInstances: " + JSON.stringify(reqOptions)); - return doRequest(reqOptions, null, CLOUDIFY, mainReq) + addAuthToOptions(reqOptions, mainReq); + + logger.info(mainReq.dcaeReqId, "getNodeInstances: " + JSON.stringify(reqOptions)); + return doRequest(mainReq, reqOptions, null, CLOUDIFY) .then(function(cloudify_response) { - logger.debug(mainReq.dcaeReqId, "getNodeInstances response: " + JSON.stringify(cloudify_response)); + logger.info(mainReq.dcaeReqId, "getNodeInstances response: " + JSON.stringify(cloudify_response)); var response = {}; cloudify_response = cloudify_response && cloudify_response.json; if (!cloudify_response || !Array.isArray(cloudify_response.items)) { @@ -364,7 +379,7 @@ exports.getNodeInstances = function (mainReq, on_next_node_instances, offset) { response.message = 'got no more node_instances'; return response; } - logger.debug(mainReq.dcaeReqId, 'getNodeInstances got node_instances ' + cloudify_response.items.length); + logger.info(mainReq.dcaeReqId, 'getNodeInstances got node_instances ' + cloudify_response.items.length); if (typeof on_next_node_instances === 'function') { on_next_node_instances(cloudify_response.items); } @@ -396,20 +411,20 @@ const runQueuedExecution = function(mainReq, deployment_id, workflow_id, paramet + " with params(" + JSON.stringify(parameters || {}) + ")"; startWorkflowExecution(mainReq, deployment_id, workflow_id, parameters) .then(function(result) { - logger.debug(mainReq.dcaeReqId, "result of start the execution for" + exe_deployment_str + ": " + JSON.stringify(result)); + logger.info(mainReq.dcaeReqId, "result of start the execution for" + exe_deployment_str + ": " + JSON.stringify(result)); execution_id = result.json && result.json.id; if (!execution_id) { throw createError("failed to start execution - no execution_id for" + exe_deployment_str, 553, "api", 553, CLOUDIFY); } exeQueue.setExecutionId(deployment_id, execution_id); - return getWorkflowResult(execution_id, mainReq); + return getWorkflowResult(mainReq, execution_id); }) .then(function(result) { - logger.debug(mainReq.dcaeReqId, 'successfully finished execution: ' + execution_id + " for" + exe_deployment_str); + logger.info(mainReq.dcaeReqId, 'successfully finished execution: ' + execution_id + " for" + exe_deployment_str); var nextExecution = exeQueue.nextExecution(deployment_id); if (nextExecution) { - logger.debug(nextExecution.mainReq.dcaeReqId, "next execution for deployment_id " + deployment_id + logger.info(nextExecution.mainReq.dcaeReqId, "next execution for deployment_id " + deployment_id + " to " + nextExecution.workflow_id + " with params(" + JSON.stringify(nextExecution.parameters || {}) + ")"); runQueuedExecution(nextExecution.mainReq, deployment_id, nextExecution.workflow_id, nextExecution.parameters); @@ -455,7 +470,7 @@ exports.executeOperation = function (mainReq, deployment_id, operation, operatio if (exeQueue.isDeploymentBusy(deployment_id)) { exeQueue.queueUpExecution(mainReq, deployment_id, workflow_id, parameters); - logger.debug(mainReq.dcaeReqId, "deployment busy - queue up execution for deployment_id " + deployment_id + logger.info(mainReq.dcaeReqId, "deployment busy - queue up execution for deployment_id " + deployment_id + " to " + workflow_id + " with params(" + JSON.stringify(parameters || {}) + ")"); return; } diff --git a/lib/config.js b/lib/config.js index e44e9b5..fd7d38c 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,5 +1,5 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 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. @@ -148,7 +148,8 @@ const getTLSCredentials = function() { } exports.configure = function() { - var config = {}; + const config = {}; + config.server_instance_uuid = utils.generateId(); /* Get configuration from configuration store */ return getFileContents(PACKAGE_JSON_FILE) @@ -213,6 +214,7 @@ exports.configure = function() { throw new Error ("Required configuration elements missing: " + missing.join(',')); config = null; } + console.log( (new Date()) + ": config -> " + JSON.stringify(config, undefined, 2)); return config; }); }; diff --git a/lib/consul.js b/lib/consul.js index 3a3257b..40de84b 100644 --- a/lib/consul.js +++ b/lib/consul.js @@ -1,5 +1,5 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 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. @@ -29,7 +29,7 @@ module.exports = { * If there is no such key, resolve to null. */ getKey: function(key) { - return doRequest({method: 'GET', uri: CONSUL_URL + KEY + key + '?raw'}, null, CONSUL) + return doRequest(null, {method: 'GET', uri: CONSUL_URL + KEY + key + '?raw'}, null, CONSUL) .then(function(res) { return res.json || res.body; }) @@ -51,7 +51,7 @@ module.exports = { * If the service is not found, returns a zero-length array. */ getService: function(serviceId) { - return doRequest({method: 'GET', uri: CONSUL_URL + SERVICE + serviceId}, null, CONSUL) + return doRequest(null, {method: 'GET', uri: CONSUL_URL + SERVICE + serviceId}, null, CONSUL) .then(function(res){ return res.json.map(function(r) { /* Address for external service is in r.Address with r.ServiceAddress empty */ @@ -59,4 +59,4 @@ module.exports = { }); }); } -};
\ No newline at end of file +}; diff --git a/lib/dcae-deployments.js b/lib/dcae-deployments.js index 38dc3c4..193f6b9 100644 --- a/lib/dcae-deployments.js +++ b/lib/dcae-deployments.js @@ -1,16 +1,16 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 AT&T Intellectual Property. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); +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, +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. +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ @@ -36,11 +36,18 @@ const inventory = inv({url: config.inventory.url}); /* 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 +app.use(function(req, res, next) { + log.info(req.dcaeReqId, + "new req: " + req.method + " " + req.originalUrl + + " from: " + req.ip + " body: " + JSON.stringify(req.body) + ); + next(); +}); /* Return a promise for a blueprint for the given service type ID */ -const getBlueprint = function(serviceTypeId) { - return inventory.getBlueprintByType(serviceTypeId) +const getBlueprint = function(req, serviceTypeId) { + return inventory.getBlueprintByType(req, serviceTypeId) .then(function (blueprintInfo) { if (!blueprintInfo.blueprint) { var e = new Error("No service type with ID " + serviceTypeId); @@ -48,7 +55,7 @@ const getBlueprint = function(serviceTypeId) { throw e; } return blueprintInfo; - }) + }) }; /* Generate self and status links object for responses */ @@ -57,7 +64,7 @@ const createLinks = function(req, deploymentId, executionId) { return { self: baseURL, status: baseURL + '/operation/' + executionId - }; + }; }; /* Generate a success response body for PUT and DELETE operations */ @@ -71,13 +78,11 @@ const createResponse = function(req, result) { /* Look up running (or in process of deploying) instances of the given service type */ app.get('/', function (req, res, next) { var services = [] - - - var searchTerm = {}; + var searchTerm; req.query['serviceTypeId'] && (searchTerm = {typeId: req.query['serviceTypeId']}); - - inventory.getServicesByType(searchTerm) + + inventory.getServicesByType(req, searchTerm) .then(function (result) { var deployments = result.map(function(service){ return { @@ -92,123 +97,123 @@ app.get('/', function (req, res, next) { /* Accept an incoming deployment request */ app.put('/:deploymentId', function(req, res, next) { - - log.debug(req.dcaeReqId, "body: " + JSON.stringify(req.body)); - + /* 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']) + inventory.verifyUniqueDeploymentId(req, req.params['deploymentId']) /* Get the blueprint for this service type */ .then(function(res) { - return getBlueprint(req.body['serviceTypeId']); + return getBlueprint(req, req.body['serviceTypeId']); }) - - /* Add this new service instance to inventory - * Easier to remove from inventory if deployment fails than vice versa + + /* 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"); + return inventory.addService(req, 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']); + return deploy.launchBlueprint(req, 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)); log.audit(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); + return deploy.finishInstallation(req, result.deploymentId, result.executionId); }) - + /* Log completion in audit log */ .then (function(result) { log.audit(req, 200, "Deployed id: " + req.params['deploymentId']); }) - + /* All errors show up here */ - .catch(function(error) { - + .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']) + inventory.deleteService(req, req.params['deploymentId']) .catch(function(error) { log.error(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 + + " " + (error.stack || "").replace(/\n/g, " "); log.error(error, req); log.audit(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']) - + deploy.launchUninstall(req, req.params['deploymentId']) + /* Delete the service from inventory */ .then(function(result) { - return inventory.deleteService(req.params['deploymentId']) + return inventory.deleteService(req, req.params['deploymentId']) .then (function() { return result; }); }) - + /* Send the HTTP response indicating workflow has started */ .then(function(result) { res.status(202).send(createResponse(req, result)); log.audit(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); + return deploy.finishUninstall(req, result.deploymentId, result.executionId); }) - + /* Log completion in audit log */ .then(function(result) { - log.audit(req, 200, "Undeployed id: " + req.params['deploymentId']); + log.audit(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) { + 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 + + " " + (error.stack || "").replace(/\n/g, " "); log.error(error, req); log.audit(req, 500, error.message); } @@ -217,8 +222,8 @@ app.delete('/:deploymentId', function(req, res, next) { /* Get the status of a workflow execution */ app.get('/:deploymentId/operation/:executionId', function(req, res, next){ - deploy.getExecutionStatus(req.params['executionId']) - + deploy.getExecutionStatus(req, req.params['executionId']) + /* Send success response */ .then(function(result) { result.requestId = req.dcaeReqId; @@ -226,9 +231,9 @@ app.get('/:deploymentId/operation/:executionId', function(req, res, next){ res.status(200).json(result); log.audit(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 +module.exports = app; diff --git a/lib/deploy.js b/lib/deploy.js index 7f83620..ee31fd3 100644 --- a/lib/deploy.js +++ b/lib/deploy.js @@ -1,16 +1,16 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 AT&T Intellectual Property. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); +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, +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. +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ @@ -58,7 +58,7 @@ 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; - + if (err instanceof Error) { /* node.js system error */ e = createError("Error communicating with CM: " + err.message, 504, "system", 202, 'cloudify-manager'); @@ -71,7 +71,7 @@ var normalizeError = function (err) { var status = err.status || 502; var cfyCode = "UNKNOWN"; var cfyMessage; - + if (err.body) { var p = parseContent(err.body); if (p.json) { @@ -84,28 +84,28 @@ var normalizeError = function (err) { } 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; }; // Augment the raw outputs from a deployment with the descriptions from the blueprint -var annotateOutputs = function (id, rawOutputs) { +var annotateOutputs = function (req, id, rawOutputs) { return new Promise(function(resolve, reject) { - + var outItems = Object.keys(rawOutputs); - + if (outItems.length < 1) { // No output items, so obviously no descriptions, just return empty object resolve({}); } else { // Call Cloudify to get the descriptions - cfy.getOutputDescriptions(id) + cfy.getOutputDescriptions(req, id) .then(function(res) { // Assemble an outputs object with values from raw output and descriptions just obtained var p = parseContent(res.body); @@ -115,16 +115,16 @@ var annotateOutputs = function (id, rawOutputs) { outs[i] = {value: rawOutputs[i]}; if (p.content.outputs[i] && p.content.outputs[i].description) { outs[i].description = p.content.outputs[i].description; - } + } }); resolve(outs); } else { reject({code: "API_INVALID_RESPONSE", message: "Invalid response for output descriptions query"}); - } + } }); } - + }); }; @@ -137,41 +137,43 @@ var delay = function(dtime) { // 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(null, "deploymentId: " + id + " starting blueprint upload"); +const launchBlueprint = function(req, id, blueprint, inputs) { + logger.info(req.dcaeReqId, "deploymentId: " + id + " starting blueprint upload"); // Upload blueprint - return cfy.uploadBlueprint(id, blueprint) - + return cfy.uploadBlueprint(req, id, blueprint) + // Create deployment .then (function(result) { - logger.debug(null, "deploymentId: " + id + " blueprint uploaded"); + logger.info(req.dcaeReqId, "deploymentId: " + id + " blueprint uploaded"); // Create deployment - return cfy.createDeployment(id, id, inputs); + return cfy.createDeployment(req, id, id, inputs); }) - + // Launch the workflow, but don't wait for it to complete .then(function(result){ - logger.debug(null, "deploymentId: " + id + " deployment created"); + logger.info(req.dcaeReqId, "deploymentId: " + id + " deployment created"); return delay(DELAY_INSTALL_WORKFLOW) - .then(function(){ - return cfy.initiateWorkflowExecution(id, 'install'); + .then(function(){ + return cfy.initiateWorkflowExecution(req, id, 'install'); }); }) .catch(function(error) { - logger.debug(null, "Error: " + error + " for launch blueprint for deploymentId " + id); + logger.info(req.dcaeReqId, "Error: " + JSON.stringify(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(null, "finishInstallation: " + deploymentId + " -- executionId: " + executionId); - return cfy.getWorkflowResult(executionId) +const finishInstallation = function(req, deploymentId, executionId) { + logger.info(req.dcaeReqId, "finishInstallation: " + deploymentId + " -- executionId: " + executionId); + return cfy.getWorkflowResult(req, executionId) .then (function(result){ - logger.debug(null, "deploymentId: " + deploymentId + " install workflow successfully executed"); + logger.info(req.dcaeReqId, "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(deploymentId); }); + return delay(DELAY_RETRIEVE_OUTPUTS).then(function() { + return cfy.getOutputs(req, deploymentId); + }); }) .then(function(result) { // We have the raw outputs from the deployment but not annotated with the descriptions @@ -182,45 +184,49 @@ const finishInstallation = function(deploymentId, executionId) { if (p.content.outputs) { rawOutputs = p.content.outputs; } - } + } } - logger.debug(null, "output retrieval result for " + deploymentId + ": " + JSON.stringify(result)); - return annotateOutputs(deploymentId, rawOutputs); + logger.info(req.dcaeReqId, "output retrieval result for " + deploymentId + ": " + JSON.stringify(result)); + return annotateOutputs(req, deploymentId, rawOutputs); }) .catch(function(err) { - logger.debug(null, "Error finishing install workflow: " + err + " -- " + JSON.stringify(err)); + logger.info(req.dcaeReqId, "Error finishing install workflow: " + err + " -- " + JSON.stringify(err)); throw normalizeError(err); }); }; exports.finishInstallation = finishInstallation; // Initiate uninstall workflow against a deployment, but don't wait for workflow to finish -const launchUninstall = function(deploymentId) { - logger.debug(null, "deploymentId: " + deploymentId + " starting uninstall workflow"); +const launchUninstall = function(req, deploymentId) { + logger.info(req.dcaeReqId, "deploymentId: " + deploymentId + " starting uninstall workflow"); // Run uninstall workflow - return cfy.initiateWorkflowExecution(deploymentId, 'uninstall') + return cfy.initiateWorkflowExecution(req, deploymentId, 'uninstall') .then(function(result) { return result; }) .catch(function(err) { - logger.debug(null, "Error initiating uninstall workflow: " + err + " -- " + JSON.stringify(err)); + logger.info(req.dcaeReqId, "Error initiating uninstall workflow: " + err + " -- " + JSON.stringify(err)); throw normalizeError(err); - }); + }); }; exports.launchUninstall = launchUninstall; -const finishUninstall = function(deploymentId, executionId) { - logger.debug(null, "finishUninstall: " + deploymentId + " -- executionId: " + executionId); - return cfy.getWorkflowResult(executionId) +const finishUninstall = function(req, deploymentId, executionId) { + logger.info(req.dcaeReqId, "finishUninstall: " + deploymentId + " -- executionId: " + executionId); + return cfy.getWorkflowResult(req, executionId) .then (function(result){ - logger.debug(null, "deploymentId: " + deploymentId + " uninstall workflow successfully executed"); + logger.info(req.dcaeReqId, "deploymentId: " + deploymentId + " uninstall workflow successfully executed"); // Delete the deployment - return delay(DELAY_DELETE_DEPLOYMENT).then(function() {return cfy.deleteDeployment(deploymentId);}); + return delay(DELAY_DELETE_DEPLOYMENT).then(function() { + return cfy.deleteDeployment(req, deploymentId); + }); }) .then (function(result){ - logger.debug(null, "deploymentId: " + deploymentId + " deployment deleted"); + logger.info(req.dcaeReqId, "deploymentId: " + deploymentId + " deployment deleted"); // Delete the blueprint - return delay(DELAY_DELETE_BLUEPRINT).then(function() {return cfy.deleteBlueprint(deploymentId);}); + return delay(DELAY_DELETE_BLUEPRINT).then(function() { + return cfy.deleteBlueprint(req, deploymentId); + }); }) .then (function(result){ return result; @@ -228,19 +234,19 @@ const finishUninstall = function(deploymentId, executionId) { .catch (function(err){ throw normalizeError(err); }); - + }; exports.finishUninstall = finishUninstall; // Get the status of a workflow execution -exports.getExecutionStatus = function (exid) { - return cfy.getWorkflowExecutionStatus(exid) +exports.getExecutionStatus = function (req, exid) { + return cfy.getWorkflowExecutionStatus(req, exid) .then(function(res){ - + var result = { operationType: res.json.workflow_id }; - + // Map execution status if (res.json.status === "terminated") { result.status = "succeeded"; @@ -254,11 +260,11 @@ exports.getExecutionStatus = function (exid) { else { result.status = "processing"; } - + if (res.json.error) { result.error = res.json.error; } - logger.debug(null, "getExecutionStatus result: " + JSON.stringify(result)); + logger.info(req.dcaeReqId, "getExecutionStatus result: " + JSON.stringify(result)); return result; }) .catch(function(error) { @@ -267,37 +273,37 @@ exports.getExecutionStatus = function (exid) { }; // Go through the Cloudify API call sequence to do a deployment -exports.deployBlueprint = function(id, blueprint, inputs) { +exports.deployBlueprint = function(req, id, blueprint, inputs) { + + // Upload blueprint, create deployment, and initiate install workflow + return launchBlueprint(req, 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 + return finishInstallation(req, result.deploymentId, result.executionId); // Will throw normalized error if it fails }, - + // launchBlueprint promise rejected -- report error function(err) { - throw normalizeError(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(null, "deploymentId: " + id + " starting uninstall workflow"); - +exports.undeployDeployment = function(req, id) { + logger.info(req.dcaeReqId, "deploymentId: " + id + " starting uninstall workflow"); + // Run launch uninstall workflow - return launchUninstall(id) - + return launchUninstall(req, id) + // launchUninstall promise fulfilled -- finish uninstall .then (function(result){ - return finishUninstall(result.deploymentId, result.executionId); // Will throw normalized error if it fails + return finishUninstall(req, 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/info.js b/lib/info.js index f6b37a8..424f424 100644 --- a/lib/info.js +++ b/lib/info.js @@ -1,5 +1,5 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 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. @@ -19,27 +19,27 @@ See the License for the specific language governing permissions and limitations "use strict"; const router = require('express').Router(); - -/* Pick up config exported by main */ -const config = process.mainModule.exports.config; +const logger = require('./logging').getLogger(); /* Accept an incoming event */ router.get('/', function(req, res) { - res.json( - { - "server" : { - "name": config.name, - "description": config.description, - "version": config.version, - "branch": config.branch, - "commit": config.commit, - "commit_datetime": config.commit_datetime - }, - "apiVersion": config.apiVersion, - "links": config.apiLinks - } - ); - require('./logging').getLogger().audit(req, 200); + /* Pick up config exported by main */ + const config = process.mainModule.exports.config; + const info = { + "server" : { + "name": config.name, + "description": config.description, + "version": config.version, + "branch": config.branch, + "commit": config.commit, + "commit_datetime": config.commit_datetime, + "server_instance_uuid": config.server_instance_uuid + }, + "apiVersion": config.apiVersion, + "links": config.apiLinks + }; + res.json(info); + logger.audit(req, 200, JSON.stringify(info)); }); -module.exports = router;
\ No newline at end of file +module.exports = router; diff --git a/lib/inventory.js b/lib/inventory.js index c2e13c9..5935067 100644 --- a/lib/inventory.js +++ b/lib/inventory.js @@ -1,5 +1,5 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 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. @@ -55,7 +55,7 @@ module.exports = function(options) { return { /* Add a DCAE service to the inventory. Done after a deployment.*/ - addService: function(deploymentId, serviceType, vnfId, vnfType, vnfLocation, outputs) { + addService: function(req, deploymentId, serviceType, vnfId, vnfType, vnfLocation, outputs) { /* Create the service description */ var serviceDescription = @@ -83,23 +83,23 @@ module.exports = function(options) { json: serviceDescription }; - return doRequest(reqOptions, null, INVENTORY); + return doRequest(req, reqOptions, null, INVENTORY); }, /* Remove a DCAE service from the inventory. Done after an undeployment. */ - deleteService: function(serviceId) { - return doRequest({method: "DELETE", uri: url + INV_SERVICES + "/" + serviceId}, null, INVENTORY); + deleteService: function(req, serviceId) { + return doRequest(req, {method: "DELETE", uri: url + INV_SERVICES + "/" + serviceId}, null, INVENTORY); }, /* Find running/deploying instances of services (with a given type name, if specified) */ - getServicesByType: function(query) { + getServicesByType: function(req, query) { var options = { method: 'GET', uri: url + INV_SERVICES, - qs: query || {} + qs: query }; - return doRequest(options, null, INVENTORY) + return doRequest(req, options, null, INVENTORY) .then (function (result) { var services = []; var content = JSON.parse(result.body); @@ -113,8 +113,8 @@ module.exports = function(options) { }, /* Find a blueprint given the service type ID -- return blueprint and type ID */ - getBlueprintByType: function(serviceTypeId) { - return doRequest({ + getBlueprintByType: function(req, serviceTypeId) { + return doRequest(req, { method: "GET", uri: url + INV_SERV_TYPES + '/' + serviceTypeId }, null, INVENTORY) @@ -138,8 +138,8 @@ module.exports = function(options) { * deployment ID as service name. If it doesn't exist, the function * resolves its promise. If it *does* exist, then it throws an error. */ - verifyUniqueDeploymentId: function(deploymentId) { - return doRequest({ + verifyUniqueDeploymentId: function(req, deploymentId) { + return doRequest(req, { method: "GET", uri: url + INV_SERVICES + "/" + deploymentId }, null, INVENTORY) diff --git a/lib/logging.js b/lib/logging.js index a21f37e..8d94f87 100644 --- a/lib/logging.js +++ b/lib/logging.js @@ -1,5 +1,5 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 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. @@ -130,6 +130,7 @@ const DEBUG_MARKER = '^'; const formatAuditRecord = function(req, status, extra) { var rec = new Array(AUDIT_NFIELDS); const end = new Date(); + rec[AUDIT_INSTUUID] = (process.mainModule.exports.config || {}).server_instance_uuid || ""; rec[AUDIT_END] = end.toISOString(); rec[AUDIT_BEGIN] = req.startTime.toISOString(); rec[AUDIT_REQID] = req.dcaeReqId; @@ -161,6 +162,7 @@ const formatAuditRecord = function(req, status, extra) { const formatMetricsRecord = function(req, opInfo, extra) { var rec = new Array(METRICS_NFIELDS); const end = new Date(); + rec[METRICS_INSTUUID] = (process.mainModule.exports.config || {}).server_instance_uuid || ""; rec[METRICS_END] = end.toISOString(); rec[METRICS_BEGIN] = opInfo.startTime.toISOString(); @@ -257,6 +259,10 @@ exports.getLogger = function() { metricsLogger.info(formatMetricsRecord(req, opInfo, extra)); }, + info: function(reqId, msg) { + debugLogger.info(formatDebugRecord(reqId, msg)); + }, + debug: function(reqId, msg) { debugLogger.debug(formatDebugRecord(reqId, msg)); } diff --git a/lib/middleware.js b/lib/middleware.js index 183cf77..ee39863 100644 --- a/lib/middleware.js +++ b/lib/middleware.js @@ -1,16 +1,16 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 AT&T Intellectual Property. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); +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, +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. +CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ @@ -32,12 +32,19 @@ exports.assignId = function(req, res, next) { /* Error handler -- send error with JSON body */ exports.handleErrors = function(err, req, res, next) { - var status = err.status || 500; - var msg = err.message || err.body || 'unknown error' - res.status(status).type('application/json').send({status: status, message: msg }); - log.audit(req, status, msg); + const response = { + status : err.status || 500, + message : err.message || err.body || 'unknown error' + }; + if (err.stack) { + response.stack = err.stack.split("\n"); + } + + res.status(response.status).type('application/json').send(response); + log.audit(req, response.status, JSON.stringify(response)); - if (status >= 500) { + if (response.status >= 500) { + err.message = response.message + (err.stack && " " + response.stack.join(', ')); log.error(err, req); } }; @@ -55,7 +62,7 @@ exports.checkType = function(type){ var err = new Error ('Content-Type must be \'' + type +'\''); err.status = 415; next (err); - } + } }; }; @@ -70,7 +77,7 @@ exports.checkProps = function(props) { } else { next(); - } + } }; }; diff --git a/lib/policy.js b/lib/policy.js index 620870c..1aefc8a 100644 --- a/lib/policy.js +++ b/lib/policy.js @@ -1,181 +1,364 @@ -/*
-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.
-*/
-
-/**
- * handling policy updates
- */
-
-"use strict";
-
-const POLICY_UPDATE_OPERATION = "dcae.interfaces.policy.policy_update";
-
-const config = process.mainModule.exports.config;
-const createError = require('./dispatcher-error').createDispatcherError;
-const logger = require('./logging').getLogger();
-
-var cloudify = require("./cloudify.js");
-
-// Set config for cloudify interface library
-cloudify.setAPIAddress(config.cloudify.url);
-cloudify.setCredentials(config.cloudify.user, config.cloudify.password);
-cloudify.setLogger(logger);
-
-/**
- * receive the policy-updated message from the policy-handler
- */
-function policyUpdate(req, res, next) {
- var latest_policies = JSON.stringify((req.body && req.body.latest_policies) || {});
- logger.debug(req.dcaeReqId, "policyUpdate " + req.originalUrl + " " + latest_policies);
- /**
- * reply to and free up the policy_handler
- */
- res.json({});
-
- latest_policies = JSON.parse(latest_policies);
- /**
- * filter out the policies to what is deployed in components and needs updating (new policyVersion)
- */
- var policy_deployments = {};
- var policy_ids = {};
-
- cloudify.getNodeInstances(req, function(node_instances) {
- node_instances.forEach(node_instance => {
- if (!node_instance.runtime_properties || !node_instance.runtime_properties.policies) {
- return;
- }
- var deployment = policy_deployments[node_instance.deployment_id] || {
- "deployment_id": node_instance.deployment_id, "policies": {}, "component_ids": []
- };
-
- logger.debug(req.dcaeReqId, "have policy on node_instance: " + JSON.stringify(node_instance));
- var have_policies = false;
- Object.keys(node_instance.runtime_properties.policies).forEach(policy_id => {
- var deployed_policy = node_instance.runtime_properties.policies[policy_id];
- var latest_policy = latest_policies[policy_id];
- if (!latest_policy || !latest_policy.policy_body
- || isNaN(latest_policy.policy_body.policyVersion)
- || latest_policy.policy_body.policyVersion
- === (deployed_policy.policy_body && deployed_policy.policy_body.policyVersion)) {
- return;
- }
- have_policies = true;
- deployment.policies[policy_id] = latest_policy;
- policy_ids[policy_id] = true;
- });
- if (have_policies) {
- deployment.component_ids.push(node_instance.id);
- policy_deployments[deployment.deployment_id] = deployment;
- }
- });
-
- logger.debug(req.dcaeReqId, "collected policy_deployments to update " + JSON.stringify(policy_deployments));
- })
- .then(function(result) {
- logger.debug(req.dcaeReqId, "finished loading policy_deployments" + JSON.stringify(result));
- if (result.status !== 200) {
- const error_msg = "failed to retrieve component policies from cloudify " + result.message;
- logger.error(createError(error_msg, result.status, "api", 502, 'cloudify-manager'), req);
- logger.audit(req, result.status, error_msg);
- return;
- }
-
- var deployment_ids = Object.keys(policy_deployments);
- var policy_id_count = Object.keys(policy_ids).length;
- if (!deployment_ids.length) {
- const msg = "no updated policies to apply to deployments";
- logger.debug(req.dcaeReqId, msg);
- logger.audit(req, result.status, msg);
- return;
- }
- const msg = "going to apply updated policies[" + policy_id_count + "] to deployments " + deployment_ids.length;
- logger.debug(req.dcaeReqId, msg + ": " + JSON.stringify(deployment_ids));
- logger.audit(req, result.status, msg);
- deployment_ids.forEach(deployment_id => {
- var deployment = policy_deployments[deployment_id];
- deployment.policies = Object.keys(deployment.policies).map(policy_id => {
- return deployment.policies[policy_id];
- });
-
- logger.debug(req.dcaeReqId, "ready to execute-operation policy-update on deployment " + JSON.stringify(deployment));
- cloudify.executeOperation(req, deployment.deployment_id, POLICY_UPDATE_OPERATION,
- {'updated_policies': deployment.policies}, deployment.component_ids);
- });
- });
-}
-
-/**
- * retrieve all component-policies from cloudify
- */
-function getComponentPoliciesFromCloudify(req, res, next) {
- logger.debug(req.dcaeReqId, "getComponentPoliciesFromCloudify " + req.originalUrl);
- var response = {"requestId": req.dcaeReqId};
- response.started = new Date();
- response.component_policies = [];
- response.component_ids = [];
- response.node_instances = [];
-
- cloudify.getNodeInstances(req, function(node_instances) {
- Array.prototype.push.apply(response.node_instances, node_instances);
- node_instances.forEach(node_instance => {
- if (!node_instance.runtime_properties || !node_instance.runtime_properties.policies) {
- return;
- }
-
- var policies_count = 0;
- Object.keys(node_instance.runtime_properties.policies).forEach(policy_id => {
- ++policies_count;
- var policy = node_instance.runtime_properties.policies[policy_id];
- policy.component_id = node_instance.id;
- policy.deployment_id = node_instance.deployment_id;
- response.component_policies.push(policy);
- });
- if (policies_count) {
- response.component_ids.push({
- "component_id" : node_instance.id,
- "policies_count" : policies_count
- });
- }
- });
-
- logger.debug(req.dcaeReqId, "collected " + response.component_ids.length
- + " component_ids: " + JSON.stringify(response.component_ids)
- + " component_policies: " + JSON.stringify(response.component_policies));
- })
- .then(function(result) {
- response.ended = new Date();
- response.status = result.status;
- response.message = result.message;
- logger.debug(req.dcaeReqId, result.message);
- if (result.status !== 200) {
- logger.error(createError(result.message, result.status, "api", 502, 'cloudify-manager'), req);
- }
- res.status(result.status).json(response);
- logger.audit(req, result.status, result.message);
- });
-}
-
-// ========================================================
-
-const app = require('express')();
-app.set('x-powered-by', false);
-app.set('etag', false);
-app.use(require('./middleware').checkType('application/json'));
-app.use(require('body-parser').json({strict: true}));
-
-app.post('/', policyUpdate);
-app.get('/components', getComponentPoliciesFromCloudify);
-
-module.exports = app;
+/* +Copyright(c) 2017-2018 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. +*/ + +/** + * handling policy updates + */ + +"use strict"; + +const POLICY_UPDATE_OPERATION = "dcae.interfaces.policy.policy_update"; + +const config = process.mainModule.exports.config; +const createError = require('./dispatcher-error').createDispatcherError; +const logger = require('./logging').getLogger(); + +const cloudify = require("./cloudify.js"); + +// Set config for cloudify interface library +cloudify.setAPIAddress(config.cloudify.url); +cloudify.setCredentials(config.cloudify.user, config.cloudify.password); +cloudify.setLogger(logger); + +/** + * receive the policy-updated message from the policy-handler + */ +function policyUpdate(req, res, next) { + + const policy_update = { + catch_up : req.body && req.body.catch_up, + latest_policies : JSON.stringify((req.body && req.body.latest_policies) || {}), + removed_policies : JSON.stringify((req.body && req.body.removed_policies) || {}), + errored_policies : JSON.stringify((req.body && req.body.errored_policies) || {}), + errored_scopes : JSON.stringify((req.body && req.body.errored_scopes) || []), + scope_prefixes : JSON.stringify((req.body && req.body.scope_prefixes) || []), + policy_deployments : {}, + updated_policy_ids : {}, + added_policy_ids : {}, + removed_policy_ids : {} + }; + + logger.info(req.dcaeReqId, "policyUpdate " + + req.method + ' ' + req.protocol + '://' + req.get('host') + req.originalUrl + + " catch_up: " + policy_update.catch_up + + " latest_policies: " + policy_update.latest_policies + + " removed_policies: " + policy_update.removed_policies + + " errored_policies: " + policy_update.errored_policies + + " errored_scopes: " + policy_update.errored_scopes + + " scope_prefixes: " + policy_update.scope_prefixes + ); + /** + * reply to and free up the policy_handler + */ + const response = {"requestID": req.dcaeReqId}; + response.started = new Date(); + response.server_instance_uuid = process.mainModule.exports.config.server_instance_uuid; + res.json(response); + + policy_update.latest_policies = JSON.parse(policy_update.latest_policies); + policy_update.removed_policies = JSON.parse(policy_update.removed_policies); + policy_update.errored_policies = JSON.parse(policy_update.errored_policies); + policy_update.errored_scopes = JSON.parse(policy_update.errored_scopes); + policy_update.scope_prefixes = JSON.parse(policy_update.scope_prefixes); + + const is_policy_in_scopes = function(policy_id) { + return policy_update.scope_prefixes.some(scope_prefix => { + return policy_id.startsWith(scope_prefix); + }); + }; + + const is_policy_in_errored_scopes = function(policy_id) { + return policy_update.errored_scopes.some(errored_scope => { + return policy_id.startsWith(errored_scope); + }); + }; + /** + * filter out the policies to what is deployed in components and needs updating (new policyVersion) + */ + const collect_policy_deployments = function(node_instances) { + node_instances.forEach(node_instance => { + if (!node_instance.runtime_properties + || (!node_instance.runtime_properties.policies + && !node_instance.runtime_properties.policy_filters)) { + return; + } + logger.info(req.dcaeReqId, "checking policies on node_instance: " + JSON.stringify(node_instance)); + + const deployment = policy_update.policy_deployments[node_instance.deployment_id] || { + "deployment_id": node_instance.deployment_id, + "updated_policies": {}, + "added_policies": {}, + "removed_policy_ids": {}, + "node_instance_ids": [], + "is_deployment_busy": cloudify.exeQueue.isDeploymentBusy(node_instance.deployment_id) + }; + + var have_policies = false; + const deployed_policies = node_instance.runtime_properties.policies || {}; + + Object.keys(deployed_policies).forEach(policy_id => { + const deployed_policy = deployed_policies[policy_id]; + const latest_policy = policy_update.latest_policies[policy_id]; + if (policy_update.removed_policies[policy_id] + || (policy_update.catch_up + && (deployed_policy.policy_body || deployment.is_deployment_busy) + && !latest_policy + && !policy_update.errored_policies[policy_id] + && !is_policy_in_errored_scopes(policy_id) + && is_policy_in_scopes(policy_id))) { + have_policies = true; + deployment.removed_policy_ids[policy_id] = true; + policy_update.removed_policy_ids[policy_id] = true; + logger.info(req.dcaeReqId, "going to remove policy " + policy_id + " from node_instance: " + JSON.stringify(node_instance)); + return; + } + + if (!latest_policy || !latest_policy.policy_body + || isNaN(latest_policy.policy_body.policyVersion)) {return;} + + if (!deployment.is_deployment_busy && latest_policy.policy_body.policyVersion + === (deployed_policy.policy_body && deployed_policy.policy_body.policyVersion)) {return;} + + have_policies = true; + deployment.updated_policies[policy_id] = latest_policy; + policy_update.updated_policy_ids[policy_id] = true; + logger.info(req.dcaeReqId, "going to update policy " + policy_id + " on node_instance: " + JSON.stringify(node_instance)); + }); + + const policy_filters = node_instance.runtime_properties.policy_filters || {}; + const policy_filter_ids = Object.keys(policy_filters); + if (policy_filter_ids.length) { + logger.info(req.dcaeReqId, "matching latest policies to policy_filters[" + policy_filter_ids.length + "] on node_instance: " + JSON.stringify(node_instance)); + try { + Object.keys(policy_update.latest_policies).forEach(policy_id => { + if (!deployment.is_deployment_busy && deployed_policies[policy_id]) {return;} + + const latest_policy = policy_update.latest_policies[policy_id]; + const policy_body = latest_policy && latest_policy.policy_body; + if (!policy_body || isNaN(policy_body.policyVersion)) {return;} + const policy_name = policy_body.policyName; + if (!policy_name) {return;} + const matching_conditions = policy_body.matchingConditions || {}; + + logger.debug(req.dcaeReqId, "matching policy " + JSON.stringify(latest_policy)); + policy_filter_ids.some(policy_filter_id => { + const policy_filter = policy_filters[policy_filter_id].policy_filter; + if (!policy_filter || !policy_filter.policyName) {return false;} + + logger.debug(req.dcaeReqId, "matching to policy_filter " + JSON.stringify(policy_filter)); + + if (!!policy_filter.onapName + && policy_filter.onapName !== matching_conditions.ONAPName) { + logger.debug(req.dcaeReqId, "not match policy_filter_id " + policy_filter_id + + " by ONAPName: " + + policy_filter.onapName + " !== " + matching_conditions.ONAPName); + return false; + } + if (!!policy_filter.configName + && policy_filter.configName !== matching_conditions.ConfigName) { + logger.debug(req.dcaeReqId, "not match policy_filter_id " + policy_filter_id + + " by configName: " + + policy_filter.configName + " !== " + matching_conditions.ConfigName); + return false; + } + + if (policy_filter.configAttributes + && !Object.keys(policy_filter.configAttributes).every(filter_key => { + return (matching_conditions.hasOwnProperty(filter_key) + && policy_filter.configAttributes[filter_key] + === matching_conditions[filter_key]); + })) { + logger.debug(req.dcaeReqId, "not match policy_filter_id " + policy_filter_id + + " by configAttributes: " + + JSON.stringify(policy_filter.configAttributes) + " !== " + JSON.stringify(matching_conditions)); + return false; + } + + if (policy_filter.policyName !== policy_id && policy_filter.policyName !== policy_name) { + const match_policy_name = new RegExp(policy_filter.policyName); + if (!match_policy_name.test(policy_name)) { + logger.debug(req.dcaeReqId, "not match policy_filter_id " + policy_filter_id + + " by policyName: " + + policy_filter.policyName + " versus " + policy_name); + return false; + } + } + + have_policies = true; + if (!deployment.added_policies[policy_filter_id]) { + deployment.added_policies[policy_filter_id] = { + "policy_filter_id" : policy_filter_id, + "policies" : {} + }; + } + deployment.added_policies[policy_filter_id].policies[policy_id] = latest_policy; + policy_update.added_policy_ids[policy_id] = true; + logger.info(req.dcaeReqId, "going to add policy " + JSON.stringify(latest_policy) + + " per policy_filter_id " + policy_filter_id + + " on node_instance: " + JSON.stringify(node_instance)); + return true; + }); + }); + } catch (e) { + const error_msg = "error on matching policy to filter " + (e.message || "") + + " " + (e.stack || "").replace(/\n/g, " ") + logger.error(createError(error_msg, 500, "api", 553, 'deployment-handler'), req); + } + } + + if (have_policies) { + deployment.node_instance_ids.push(node_instance.id); + policy_update.policy_deployments[deployment.deployment_id] = deployment; + } + }); + + logger.info(req.dcaeReqId, "collected policy_deployments to update " + JSON.stringify(policy_update.policy_deployments)); + }; + + const update_policies_on_deployments = function(result) { + logger.info(req.dcaeReqId, "finished loading policy_deployments" + JSON.stringify(result)); + if (result.status !== 200) { + const error_msg = "failed to retrieve component policies from cloudify " + result.message; + logger.error(createError(error_msg, result.status, "api", 502, 'cloudify-manager'), req); + logger.audit(req, result.status, error_msg); + return; + } + + const deployment_ids = Object.keys(policy_update.policy_deployments); + if (!deployment_ids.length) { + const audit_msg = "no updated policies to apply to deployments"; + logger.debug(req.dcaeReqId, audit_msg); + logger.audit(req, result.status, audit_msg); + return; + } + const audit_msg = "going to apply updated policies[" + Object.keys(policy_update.updated_policy_ids).length + + "] and added policies[" + Object.keys(policy_update.added_policy_ids).length + + "] and removed policies[" + Object.keys(policy_update.removed_policy_ids).length + + "] to deployments[" + deployment_ids.length + "]"; + logger.info(req.dcaeReqId, audit_msg + ": " + JSON.stringify(deployment_ids)); + logger.audit(req, result.status, audit_msg); + deployment_ids.forEach(deployment_id => { + const deployment = policy_update.policy_deployments[deployment_id]; + deployment.updated_policies = Object.keys(deployment.updated_policies).map(policy_id => { + return deployment.updated_policies[policy_id]; + }); + deployment.removed_policy_ids = Object.keys(deployment.removed_policy_ids); + + logger.info(req.dcaeReqId, "ready to execute-operation policy-update on deployment " + JSON.stringify(deployment)); + cloudify.executeOperation(req, deployment.deployment_id, POLICY_UPDATE_OPERATION, + { + 'updated_policies': deployment.updated_policies, + 'added_policies': deployment.added_policies, + 'removed_policies': deployment.removed_policy_ids + }, + deployment.node_instance_ids + ); + }); + }; + + cloudify.getNodeInstances(req, collect_policy_deployments).then(update_policies_on_deployments); +} + +/** + * retrieve all component-policies from cloudify + */ +function getComponentPoliciesFromCloudify(req, res, next) { + logger.info(req.dcaeReqId, "getComponentPoliciesFromCloudify " + req.originalUrl); + const response = {"requestID": req.dcaeReqId}; + response.started = new Date(); + response.server_instance_uuid = process.mainModule.exports.config.server_instance_uuid; + response.node_instance_ids = []; + response.component_policies = []; + response.component_policy_filters = []; + response.node_instances = []; + + cloudify.getNodeInstances(req, function(node_instances) { + Array.prototype.push.apply(response.node_instances, node_instances); + node_instances.forEach(node_instance => { + if (!node_instance.runtime_properties + || (!node_instance.runtime_properties.policies + && !node_instance.runtime_properties.policy_filters)) { + return; + } + + var policies_count = 0; + var policy_filters_count = 0; + if (node_instance.runtime_properties.policies) { + Object.keys(node_instance.runtime_properties.policies).forEach(policy_id => { + ++policies_count; + const policy = node_instance.runtime_properties.policies[policy_id]; + policy.component_id = node_instance.id; + policy.deployment_id = node_instance.deployment_id; + response.component_policies.push(policy); + }); + } + if (node_instance.runtime_properties.policy_filters) { + Object.keys(node_instance.runtime_properties.policy_filters).forEach(policy_filter => { + ++policy_filters_count; + policy_filter = node_instance.runtime_properties.policy_filters[policy_filter]; + policy_filter.component_id = node_instance.id; + policy_filter.deployment_id = node_instance.deployment_id; + response.component_policy_filters.push(policy_filter); + }); + } + if (policies_count + policy_filters_count) { + response.node_instance_ids.push({ + "node_instance_id" : node_instance.id, + "deployment_id" : node_instance.deployment_id, + "policies_count" : policies_count, + "policy_filters_count" : policy_filters_count + }); + } + }); + + logger.info(req.dcaeReqId, "collected " + response.node_instance_ids.length + + " node_instance_ids: " + JSON.stringify(response.node_instance_ids) + + " component_policies: " + JSON.stringify(response.component_policies) + + " component_policy_filters: " + JSON.stringify(response.component_policy_filters) + ); + }) + .then(function(result) { + response.ended = new Date(); + response.status = result.status; + response.message = result.message; + logger.info(req.dcaeReqId, result.message); + if (result.status !== 200) { + logger.error(createError(result.message, result.status, "api", 502, 'cloudify-manager'), req); + } + res.status(result.status).json(response); + logger.audit(req, result.status, result.message); + }); +} + +// ======================================================== + +const app = require('express')(); +app.set('x-powered-by', false); +app.set('etag', false); +app.use(require('./middleware').checkType('application/json')); +app.use(require('body-parser').json({strict: true, limit: '150mb'})); +app.use(function(req, res, next) { + logger.info(req.dcaeReqId, + "new req: " + req.method + " " + req.originalUrl + + " from: " + req.ip + " body: " + JSON.stringify(req.body) + ); + next(); +}); + +app.post('/', policyUpdate); +app.get('/components', getComponentPoliciesFromCloudify); + +module.exports = app; diff --git a/lib/promise_request.js b/lib/promise_request.js index 0572ac4..200e516 100644 --- a/lib/promise_request.js +++ b/lib/promise_request.js @@ -1,5 +1,5 @@ /* -Copyright(c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright(c) 2017-2018 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. @@ -31,11 +31,11 @@ const url = require('url'); const querystring = require('querystring'); const logger = require('./logging').getLogger(); -exports.doRequest = function(options, body, targetEntity, mainReq) { - +exports.doRequest = function(mainReq, options, body, targetEntity) { + /* Defense: for now, some callers don't provide mainReq */ mainReq = mainReq || {}; - + var opInfo = {"startTime":new Date(), "targetEntity": targetEntity}; return new Promise(function(resolve, reject) { @@ -56,10 +56,12 @@ exports.doRequest = function(options, body, targetEntity, mainReq) { options.hostname = parsed.hostname; options.port = parsed.port; options.path = parsed.path; + opInfo.targetService = options.method + " " + options.uri; if (options.qs) { - options.path += ('?' + querystring.stringify(options.qs)); + const qry = ('?' + querystring.stringify(options.qs)); + options.path += qry; + opInfo.targetService += qry; } - opInfo.targetService = options.method + " " + options.uri; } try { @@ -68,7 +70,7 @@ exports.doRequest = function(options, body, targetEntity, mainReq) { catch (e) { opInfo.respCode = 500; opInfo.complete = false; - logger.metrics(mainReq, opInfo, e.message); + logger.metrics(mainReq, opInfo, (e.message || "") + " " + (e.stack || "").replace(/\n/g, " ")); reject(e); } @@ -111,16 +113,18 @@ exports.doRequest = function(options, body, targetEntity, mainReq) { } opInfo.respCode = resp.statusCode || 500; + const metrics_text = "res: " + result.body + + ((reqBody && " req: " + ((typeof(reqBody) !== 'string' && typeof(reqBody)) || reqBody)) || ""); if (resp.statusCode > 199 && resp.statusCode < 300) { // HTTP status code indicates success - resolve the promise opInfo.complete = true; - logger.metrics(mainReq, opInfo, result.body); + logger.metrics(mainReq, opInfo, metrics_text); resolve(result); } else { // Reject the promise opInfo.complete = false; - logger.metrics(mainReq, opInfo, result.body); + logger.metrics(mainReq, opInfo, metrics_text); reject(result); } |