From ca00a932eae5e706f01519612fce1015c9ff9d58 Mon Sep 17 00:00:00 2001 From: Alex Shatov Date: Thu, 6 Dec 2018 10:16:43 -0500 Subject: 3.1.0/5.1.0 - check for finished deployment - external version 3.1.0 - internal version 5.1.0 for code change - no API change - check for finished deployment creation - success or failure - stop querying cloudify manager on failed deployment creation - use optional $CONSUL_URL to get url of consul - improved info used for audit and logging and responses - added more unit tests unit test coverage summary Statements : 80.99% ( 946/1168 ) Branches : 58.22% ( 294/505 ) Functions : 81.28% ( 165/203 ) Lines : 81.53% ( 936/1148 ) Change-Id: I831cd0db0d2e148e6da4c9190495aacf72e2d39c Signed-off-by: Alex Shatov Issue-ID: DCAEGEN2-929 --- lib/cloudify.js | 268 +++++++++++++++-------------------------- lib/config.js | 7 +- lib/consul.js | 2 +- lib/deploy.js | 88 ++++++-------- lib/policy.js | 36 ++++-- package.json | 2 +- pom.xml | 2 +- tests/test_dcae-deployments.js | 71 ++++++++++- tests/test_policy.js | 34 +++++- version.properties | 4 +- 10 files changed, 269 insertions(+), 245 deletions(-) diff --git a/lib/cloudify.js b/lib/cloudify.js index 5c5c658..138b986 100644 --- a/lib/cloudify.js +++ b/lib/cloudify.js @@ -19,8 +19,7 @@ See the License for the specific language governing permissions and limitations "use strict"; const CLOUDIFY = "cloudify-manager"; -const FINISHED = [ "terminated", "cancelled", "failed" ]; -const DEPLOYMENT_CREATION_FINISHED = [ "terminated" ]; +const FINISHED = [ "terminated", "cancelled", "canceled", "failed" ]; const RETRY_INTERVAL = 5000; // Every 5 seconds const MAX_TRIES = 720; // Up to 1 hour const DEP_CREATION_STATUS_RETRY_INTERVAL = 30000; // Every 30 seconds @@ -71,8 +70,8 @@ const exeQueue = new ExeQueue(); exports.exeQueue = exeQueue; // Get current status of a workflow execution -const getExecutionStatus = function(req, execution_id) { - var reqOptions = { +exports.getExecutionStatus = function(req, execution_id) { + const reqOptions = { method : "GET", uri : cfyAPI + "/executions/" + execution_id }; @@ -84,7 +83,7 @@ const getExecutionStatus = function(req, execution_id) { // Get current status of a deployment creation const getDeploymentCreationStatus = function(req, deployment_id) { - var reqOptions = { + const reqOptions = { method : "GET", uri : cfyAPI + "/executions?deployment_id=" + deployment_id + "&workflow_id=create_deployment_environment&_include=id,status" }; @@ -94,77 +93,29 @@ const getDeploymentCreationStatus = function(req, deployment_id) { }; // Poll for the result of a workflow execution until it's done -const getWorkflowResult = function(mainReq, execution_id) { +// Return a promise for the final result of a workflow execution +exports.waitForWorkflowExecution = function(mainReq, execution_id) { /* Defense: Some callers do not supply mainReq */ mainReq = mainReq || {}; - 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.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(mainReq, execution_id);}; - - return repeat.repeatWhile(getExecStatus, checkStatus, MAX_TRIES, RETRY_INTERVAL) - .then( - - /* Handle fulfilled promise from repeatWhile */ - function(res) { - - logger.info(mainReq.dcaeReqId, '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; - - }); + const log_title = "execution_id(" + execution_id + "): workflow execution"; + logger.info(mainReq.dcaeReqId, log_title + ": waiting for completion"); + + const getStatus = function(res) {return res && res.json && res.json.status;}; + + return repeat.repeatWhile(function() {return exports.getExecutionStatus(mainReq, execution_id);}, + function(res) {return checkExecutionRunning(mainReq, res, log_title, getStatus);}, + MAX_TRIES, RETRY_INTERVAL) + .then(function (res) {return onFinishedExecution(mainReq, res, log_title, getStatus);}, + /* 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; + }); }; // bare start of a workflow execution against a deployment @@ -172,7 +123,7 @@ const startWorkflowExecution = function(mainReq, deployment_id, workflow_id, par /* Defense: Some callers do not supply mainReq */ mainReq = mainReq || {}; // Set up the HTTP POST request - var reqOptions = { + const reqOptions = { method : "POST", uri : cfyAPI + "/executions", headers : { @@ -183,7 +134,7 @@ const startWorkflowExecution = function(mainReq, deployment_id, workflow_id, par addAuthToOptions(reqOptions, mainReq); - var body = { + const body = { "deployment_id" : deployment_id, "workflow_id" : workflow_id }; @@ -194,7 +145,7 @@ const startWorkflowExecution = function(mainReq, deployment_id, workflow_id, par }; //Initiate a workflow execution against a deployment -const initiateWorkflowExecution = function(req, deployment_id, workflow_id, parameters) { +exports.initiateWorkflowExecution = function(req, deployment_id, workflow_id, parameters) { return startWorkflowExecution(req, deployment_id, workflow_id, parameters) .then(function(result) { logger.info(req.dcaeReqId, "Result from POSTing workflow execution start: " + JSON.stringify(result)); @@ -208,81 +159,70 @@ const initiateWorkflowExecution = function(req, deployment_id, workflow_id, para }); }; +// Function for testing if workflow execution or deployment creation has finished or still running +// Expects the result of getExecStatus +const checkExecutionRunning = function(mainReq, res, log_title, getStatus) { + const still_running = !FINISHED.includes(getStatus(res)); + logger.info(mainReq.dcaeReqId, log_title + ": checking status: " + JSON.stringify(res) + " ==> " + still_running); + return still_running; +}; + +const onFinishedExecution = function(mainReq, res, log_title, getStatus) { + logger.info(mainReq.dcaeReqId, log_title + " result: " + JSON.stringify(res)); + const status = getStatus(res); + /* Successful completion */ + if (status === 'terminated') { + logger.info(mainReq.dcaeReqId, log_title + ' completed'); + return res; + } + /* If we get here, we don't have a success and we're going to throw something */ + const error = { "body": log_title + " " + status }; + /* We expect a JSON object with a status */ + if (status) { + /* Failure -- we need to return something that looks like the CM API failures */ + if (status === 'failed') { + error.body += ' -- ' + (res.json.error ? JSON.stringify(res.json.error) : 'no error information'); + } + /* Cancellation -- don't really expect this */ + else if (status === 'canceled' || status === 'cancelled') { } + /* Don't expect anything else -- but if we get it, it's not a success! */ + else { + error.body += ' -- unexpected status'; + } + } + /* The body of the response from the API call to get execution status is not what we expect at all */ + else { + error.body += ' -- unexpected result body from Cloudify Manager'; + } + throw error; +}; + // Poll for the deployment creation status -const getDeploymentCreationResult = function(mainReq, deployment_id) { +const waitForDeploymentCreation = function(mainReq, deployment_id) { /* Defense: Some callers do not supply mainReq */ mainReq = mainReq || {}; - logger.info(mainReq.dcaeReqId, "Getting status for deployment id: " + deployment_id); - - // Function for testing if deployment creation is complete - // Expects the result of getDepCrStatus - const checkDepStatus = function(cloudify_response) { - cloudify_response = cloudify_response && cloudify_response.json; - logger.info(mainReq.dcaeReqId, "Checking Deployment creation result: " + JSON.stringify(cloudify_response) + " ==> " + - (cloudify_response.items.length == 1 && DEPLOYMENT_CREATION_FINISHED.indexOf(cloudify_response.items[0].status) < 0)); - return cloudify_response.items.length == 1 && DEPLOYMENT_CREATION_FINISHED.indexOf(cloudify_response.items[0].status) < 0; - }; - - // Create deployment creation status checker function - const getDepCrStatus = function() {return getDeploymentCreationStatus(mainReq, deployment_id);}; - - return repeat.repeatWhile(getDepCrStatus, checkDepStatus, DEP_CREATION_STATUS_MAX_TRIES, DEP_CREATION_STATUS_RETRY_INTERVAL) - .then( - - /* Handle fulfilled promise from repeatWhile */ - function(res) { - - logger.info(mainReq.dcaeReqId, 'Deployment creation result: ' + JSON.stringify(res)); - - /* Successful completion */ - if (res.json && res.json.items.length == 1 && res.json.items[0].status === 'terminated') { - logger.info(mainReq.dcaeReqId, 'Deployment creation completed for deployment_id: ' + deployment_id); - 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.items.length == 1 && res.json.items[0].status) { - - /* Failure -- we need to return something that looks like the CM API failures */ - if (res.json.items[0].status === 'failed') { - error.body = 'Deployment creation failed: ' + deployment_id + ' -- ' + (res.json.error ? JSON.stringify(res.json.error) : 'no error information'); - } + const log_title = "deployment_id(" + deployment_id + "): deployment creation"; + logger.info(mainReq.dcaeReqId, log_title + ": waiting for completion"); - /* Cancellation -- don't really expect this */ - else if (res.json.items[0].status === 'canceled' || res.json.status === 'cancelled') { - error.body = 'Deployment creation canceled: ' + deployment_id; - } - - /* Don't expect anything else -- but if we get it, it's not a success! */ - else { - error.body = 'Deployment creation--unexpected status ' + res.json.items[0].status + ' for ' + deployment_id; - } - } - - /* The body of the response from the API call to get execution status is not what we expect at all */ - else { - error.body = 'Deployment creation--unexpected result body getting execution status from CM for ' + deployment_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; + const getStatus = function(res) { + return res && res.json && Array.isArray(res.json.items) + && res.json.items.length == 1 && res.json.items[0].status; + }; - }); + return repeat.repeatWhile(function() {return getDeploymentCreationStatus(mainReq, deployment_id);}, + function(res) {return checkExecutionRunning(mainReq, res, log_title, getStatus);}, + DEP_CREATION_STATUS_MAX_TRIES, DEP_CREATION_STATUS_RETRY_INTERVAL) + .then(function (res) {return onFinishedExecution(mainReq, res, log_title, getStatus);}, + /* 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; + }); }; // Uploads a blueprint via the Cloudify API @@ -314,7 +254,7 @@ exports.uploadBlueprint = function(req, bpid, blueprint) { exports.createDeployment = function(req, dpid, bpid, inputs) { // Set up the HTTP PUT request - var reqOptions = { + const reqOptions = { method : "PUT", uri : cfyAPI + "/deployments/" + dpid, headers : { @@ -324,7 +264,7 @@ exports.createDeployment = function(req, dpid, bpid, inputs) { }; addAuthToOptions(reqOptions, req); - var body = { + const body = { blueprint_id : bpid }; if (inputs) { @@ -335,35 +275,23 @@ exports.createDeployment = function(req, dpid, bpid, inputs) { return doRequest(req, reqOptions, JSON.stringify(body), CLOUDIFY); }; -// Initiate a workflow execution against a deployment -exports.initiateWorkflowExecution = initiateWorkflowExecution; - -// Get the status of a workflow execution -exports.getWorkflowExecutionStatus = getExecutionStatus; - -// 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(req, deployment_id, workflow_id, parameters) { - return initiateWorkflowExecution(req, deployment_id, workflow_id, parameters) + return exports.initiateWorkflowExecution(req, deployment_id, workflow_id, parameters) // Wait for the result .then (function(result) { logger.info(req.dcaeReqId, "Result from initiating workflow: " + JSON.stringify(result)); - return getWorkflowResult(req, result.executionId); + return exports.waitForWorkflowExecution(req, result.executionId); }); }; // Return a promise for the final result of a deployment update -exports.getDeploymentCreationResult = getDeploymentCreationResult; - -// Get the status of a deployment update -exports.getDeploymentCreationStatus = getDeploymentCreationStatus; +exports.waitForDeploymentCreation = waitForDeploymentCreation; // Retrieves outputs for a deployment exports.getOutputs = function(req, dpid) { - var reqOptions = { + const reqOptions = { method : "GET", uri : cfyAPI + "/deployments/" + dpid + "/outputs", headers : { @@ -378,7 +306,7 @@ exports.getOutputs = function(req, dpid) { // Get the output descriptions for a deployment exports.getOutputDescriptions = function(req, dpid) { - var reqOptions = { + const reqOptions = { method : "GET", uri : cfyAPI + "/deployments/" + dpid + "?include=outputs", headers : { @@ -393,7 +321,7 @@ exports.getOutputDescriptions = function(req, dpid) { // Deletes a deployment exports.deleteDeployment = function(req, dpid) { - var reqOptions = { + const reqOptions = { method : "DELETE", uri : cfyAPI + "/deployments/" + dpid }; @@ -405,7 +333,7 @@ exports.deleteDeployment = function(req, dpid) { // Deletes a blueprint exports.deleteBlueprint = function(req, bpid) { - var reqOptions = { + const reqOptions = { method : "DELETE", uri : cfyAPI + "/blueprints/" + bpid }; @@ -444,7 +372,7 @@ exports.setLogger = function(log) { exports.getNodeInstances = function (mainReq, on_next_node_instances, offset) { offset = offset || 0; - var reqOptions = { + const reqOptions = { method : "GET", uri : cfyAPI + "/node-instances?_include=id,deployment_id,runtime_properties&_size=1000&_offset=" + offset }; @@ -506,7 +434,7 @@ const runQueuedExecution = function(mainReq, deployment_id, workflow_id, paramet 553, "api", 553, CLOUDIFY); } exeQueue.setExecutionId(deployment_id, execution_id); - return getWorkflowResult(mainReq, execution_id); + return exports.waitForWorkflowExecution(mainReq, execution_id); }) .then(function(result) { logger.info(mainReq.dcaeReqId, 'successfully finished execution: ' + execution_id + " for" + exe_deployment_str); diff --git a/lib/config.js b/lib/config.js index 8daa87f..d4fd3e3 100644 --- a/lib/config.js +++ b/lib/config.js @@ -19,8 +19,11 @@ See the License for the specific language governing permissions and limitations * Configuration may come from environment variables, a value in a Consul key-value store, or defaults, * in that order of precedence. * - * The address of the Consul host is passed in an environment variable called CONSUL_HOST. - * If present, the configuration value in the key-value store is a UTF-8 serialization of a JSON object. + * The url of the Consul is passed in an optional environment variable called CONSUL_URL. + * If $CONSUL_URL not provided, the deployment-handler looks at the Consul host that can be passed + * in as an environment variable called CONSUL_HOST, that corresponds to url "http://${CONSUL_HOST}:8500". + * If $CONSUL_HOST not provided as well, the consul url is "http://consul:8500". + * If present, the configuration value in the consul-key-value store is a UTF-8 serialization of a JSON object. * * * -------------------------------------------------------------------------------------- diff --git a/lib/consul.js b/lib/consul.js index 40de84b..a190093 100644 --- a/lib/consul.js +++ b/lib/consul.js @@ -19,7 +19,7 @@ See the License for the specific language governing permissions and limitations const KEY = '/v1/kv/'; const SERVICE = '/v1/catalog/service/'; const CONSUL = 'consul'; -const CONSUL_URL = 'http://' + (process.env.CONSUL_HOST || CONSUL) + ':8500'; +const CONSUL_URL = process.env.CONSUL_URL || ('http://' + (process.env.CONSUL_HOST || CONSUL) + ':8500'); const doRequest = require('./promise_request').doRequest; diff --git a/lib/deploy.js b/lib/deploy.js index 2d75b52..4829040 100644 --- a/lib/deploy.js +++ b/lib/deploy.js @@ -21,7 +21,6 @@ See the License for the specific language governing permissions and limitations const config = process.mainModule.exports.config; /* Set delays between steps */ -const DELAY_INSTALL_WORKFLOW = 30000; const DELAY_RETRIEVE_OUTPUTS = 5000; const DELAY_DELETE_DEPLOYMENT = 30000; const DELAY_DELETE_BLUEPRINT = 10000; @@ -43,9 +42,9 @@ cfy.setLogger(logger); // Try to parse a string as JSON var parseContent = function(input) { - var res = {json: false, content: input}; + const res = {json: false, content: input}; try { - var parsed = JSON.parse(input); + const parsed = JSON.parse(input); res.json = true; res.content = parsed; } @@ -96,8 +95,7 @@ var normalizeError = function (err) { // Augment the raw outputs from a deployment with the descriptions from the blueprint var annotateOutputs = function (req, id, rawOutputs) { return new Promise(function(resolve, reject) { - - var outItems = Object.keys(rawOutputs); + const outItems = Object.keys(rawOutputs); if (outItems.length < 1) { // No output items, so obviously no descriptions, just return empty object @@ -137,37 +135,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(req, id, blueprint, inputs) { - logger.info(req.dcaeReqId, "deploymentId: " + id + " starting blueprint upload"); - // Upload blueprint +exports.launchBlueprint = function(req, id, blueprint, inputs) { + const log_deployment_id = "deploymentId(" + id + "): "; + var step_log = log_deployment_id + "uploading blueprint"; + logger.info(req.dcaeReqId, step_log); return cfy.uploadBlueprint(req, id, blueprint) - // Create deployment .then (function(result) { - logger.info(req.dcaeReqId, "deploymentId: " + id + " blueprint uploaded"); - // Create deployment - return cfy.createDeployment(req, id, id, inputs); + step_log = log_deployment_id + "creating deployment"; + logger.info(req.dcaeReqId, step_log); + // Create deployment + return cfy.createDeployment(req, id, id, inputs); }) // create the deployment and keep checking, for up to 5 minutes, until creation is complete - .then(function(result){ - return cfy.getDeploymentCreationResult(req, id); - }) - .then(function(){ - logger.info(req.dcaeReqId, "deploymentId: " + id + " deployment created"); - return cfy.initiateWorkflowExecution(req, id, 'install'); - }) - .catch(function(error) { - logger.info(req.dcaeReqId, "Error: " + JSON.stringify(error) + " for launch blueprint/deployment creation for deploymentId " + id); - throw normalizeError(error); - }); + .then(function(result) { + step_log = log_deployment_id + "waiting for deployment creation"; + logger.info(req.dcaeReqId, step_log); + return cfy.waitForDeploymentCreation(req, id); + }) + .then(function() { + step_log = log_deployment_id + "install"; + logger.info(req.dcaeReqId, step_log); + return cfy.initiateWorkflowExecution(req, id, 'install'); + }) + .catch(function(error) { + step_log = " while " + step_log; + logger.info(req.dcaeReqId, "Error: " + JSON.stringify(error) + step_log); + error.message = (error.message && (step_log + ": " + error.message)) + || ("failed: " + step_log); + throw normalizeError(error); + }); }; -exports.launchBlueprint = launchBlueprint; // Finish installation launched with launchBlueprint -const finishInstallation = function(req, deploymentId, executionId) { +exports.finishInstallation = function(req, deploymentId, executionId) { logger.info(req.dcaeReqId, "finishInstallation: " + deploymentId + " -- executionId: " + executionId); - return cfy.getWorkflowResult(req, executionId) + return cfy.waitForWorkflowExecution(req, executionId) .then (function(result){ logger.info(req.dcaeReqId, "deploymentId: " + deploymentId + " install workflow successfully executed"); // Retrieve the outputs from the deployment, as specified in the blueprint @@ -194,10 +198,9 @@ const finishInstallation = function(req, deploymentId, executionId) { throw normalizeError(err); }); }; -exports.finishInstallation = finishInstallation; // Initiate uninstall workflow against a deployment, but don't wait for workflow to finish -const launchUninstall = function(req, deploymentId) { +exports.launchUninstall = function(req, deploymentId) { logger.info(req.dcaeReqId, "deploymentId: " + deploymentId + " starting uninstall workflow"); // Run uninstall workflow return cfy.initiateWorkflowExecution(req, deploymentId, 'uninstall') @@ -209,11 +212,10 @@ const launchUninstall = function(req, deploymentId) { throw normalizeError(err); }); }; -exports.launchUninstall = launchUninstall; -const finishUninstall = function(req, deploymentId, executionId) { +exports.finishUninstall = function(req, deploymentId, executionId) { logger.info(req.dcaeReqId, "finishUninstall: " + deploymentId + " -- executionId: " + executionId); - return cfy.getWorkflowResult(req, executionId) + return cfy.waitForWorkflowExecution(req, executionId) .then (function(result){ logger.info(req.dcaeReqId, "deploymentId: " + deploymentId + " uninstall workflow successfully executed"); // Delete the deployment @@ -236,11 +238,10 @@ const finishUninstall = function(req, deploymentId, executionId) { }); }; -exports.finishUninstall = finishUninstall; // Get the status of a workflow execution -exports.getExecutionStatus = function (req, exid) { - return cfy.getWorkflowExecutionStatus(req, exid) +exports.getExecutionStatus = function (req, execution_id) { + return cfy.getExecutionStatus(req, execution_id) .then(function(res){ var result = { @@ -276,17 +277,12 @@ exports.getExecutionStatus = function (req, exid) { exports.deployBlueprint = function(req, id, blueprint, inputs) { // Upload blueprint, create deployment, and initiate install workflow - return launchBlueprint(req, id, blueprint, inputs) + return exports.launchBlueprint(req, id, blueprint, inputs) // Wait for the workflow to complete - .then( - - // launchBlueprint promise fulfilled -- finish installation - function(result){ - return finishInstallation(req, result.deploymentId, result.executionId); // Will throw normalized error if it fails + .then(function(result){ + return exports.finishInstallation(req, result.deploymentId, result.executionId); // Will throw normalized error if it fails }, - - // launchBlueprint promise rejected -- report error function(err) { throw normalizeError(err); }); @@ -297,14 +293,10 @@ exports.undeployDeployment = function(req, id) { logger.info(req.dcaeReqId, "deploymentId: " + id + " starting uninstall workflow"); // Run launch uninstall workflow - return launchUninstall(req, id) - - // launchUninstall promise fulfilled -- finish uninstall + return exports.launchUninstall(req, id) .then (function(result){ - return finishUninstall(req, result.deploymentId, result.executionId); // Will throw normalized error if it fails + return exports.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/policy.js b/lib/policy.js index d6a701f..098a4a4 100644 --- a/lib/policy.js +++ b/lib/policy.js @@ -222,7 +222,7 @@ function update_policies(req, res) { * retrieve the unique set of policies and policy-filters from cloudify */ function get_policies_from_cloudify(req, res, next) { - logger.info(req.dcaeReqId, "getPoliciesFromCloudify " + req.originalUrl); + logger.info(req.dcaeReqId, "get_policies_from_cloudify " + req.originalUrl); const response = {"requestID": req.dcaeReqId}; response.started = new Date(); response.server_instance_uuid = process.mainModule.exports.config.server_instance_uuid; @@ -263,13 +263,16 @@ function get_policies_from_cloudify(req, res, next) { .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); + response.message = result.message + + " deployed policies[" + Object.keys(response.policies).length + + "] policy_filters[" + Object.keys(response.policy_filters).length + "]"; + logger.info(req.dcaeReqId, "response status " + response.status + + " body: " + JSON.stringify(response)); + if (response.status !== 200) { + logger.error(createError(response.message, response.status, "api", 502, 'cloudify-manager'), req); } - res.status(result.status).json(response); - logger.audit(req, result.status, result.message); + res.status(response.status).json(response); + logger.audit(req, response.status, response.message); }); } @@ -335,13 +338,20 @@ function getComponentPoliciesFromCloudify(req, res, next) { .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); + response.message = result.message + response.message = result.message + + " collected[" + response.node_instance_ids.length + + "] node_instance_ids[" + Object.keys(response.node_instance_ids).length + + "] component_policies[" + Object.keys(response.component_policies).length + + "] component_policy_filters[" + Object.keys(response.component_policy_filters).length + "]"; + + logger.info(req.dcaeReqId, "response status " + response.status + + " body: " + JSON.stringify(response)); + if (response.status !== 200) { + logger.error(createError(response.message, response.status, "api", 502, 'cloudify-manager'), req); } - res.status(result.status).json(response); - logger.audit(req, result.status, result.message); + res.status(response.status).json(response); + logger.audit(req, response.status, response.message); }); } diff --git a/package.json b/package.json index 1cfc046..2c5f677 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "onap-dcae-deployment-handler", - "version": "5.0.3", + "version": "5.1.0", "description": "ONAP DCAE Deployment Handler", "main": "deployment-handler.js", "dependencies": { diff --git a/pom.xml b/pom.xml index f8be698..6c2dcca 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property. org.onap.dcaegen2.platform deployment-handler dcaegen2-platform-deployment-handler - 3.0.3-SNAPSHOT + 3.1.0-SNAPSHOT http://maven.apache.org UTF-8 diff --git a/tests/test_dcae-deployments.js b/tests/test_dcae-deployments.js index 367a38d..7bf9831 100644 --- a/tests/test_dcae-deployments.js +++ b/tests/test_dcae-deployments.js @@ -174,19 +174,19 @@ const Cloudify = { "blueprint_id": blueprint_id || deployment_id }; }, - resp_dep_creation: function(deployment_id) { + resp_dep_creation: function(deployment_id, execution_id, status) { return { "items": [ { - "status": "terminated", - "id": "ee6b0d21-0257-46a3-bb83-6f61f9ab5f99" + "status": (status || "terminated"), + "id": (execution_id || "ee6b0d21-0257-46a3-bb83-6f61f9ab5f99") } ], "metadata": { "pagination": { "total": 1, "offset": 0, - "size": 10000 + "size": 1000 } } }; @@ -385,6 +385,66 @@ function test_put_dcae_deployments_missing_input_error(dh_server) { }); } +function test_put_dcae_deployments_creation_failed(dh_server) { + const req_path = "/dcae-deployments/" + DEPLOYMENT_ID_JFL_1; + const message = create_main_message(INV_EXISTING_SERVICE_TYPE, true); + const test_txt = "fail deployment-creation PUT " + req_path + ": " + JSON.stringify(message); + const execution_id = "execution_" + DEPLOYMENT_ID_JFL_1; + describe(test_txt, () => { + it('fail deployment-creation', function(done) { + const action_timer = new utils.ActionTimer(); + console.log(action_timer.step, test_txt); + + nock(dh.INVENTORY_URL).get(INV_PATH_DCAE_SERVICES + "/" + DEPLOYMENT_ID_JFL_1) + .reply(404, function(uri) { + console.log(action_timer.step, "get", dh.INVENTORY_URL, uri); + return JSON.stringify(Inventory.resp_not_found_service(DEPLOYMENT_ID_JFL_1)); + }); + nock(dh.INVENTORY_URL).get(INV_PATH_DCAE_SERVICE_TYPES + INV_EXISTING_SERVICE_TYPE) + .reply(200, function(uri) { + console.log(action_timer.step, "get", dh.INVENTORY_URL, uri); + return JSON.stringify(Inventory.resp_existing_blueprint(INV_EXISTING_SERVICE_TYPE)); + }); + nock(dh.INVENTORY_URL).put(INV_PATH_DCAE_SERVICES + "/" + DEPLOYMENT_ID_JFL_1) + .reply(200, function(uri, requestBody) { + console.log(action_timer.step, "put", dh.INVENTORY_URL, uri, JSON.stringify(requestBody)); + return JSON.stringify(Inventory.resp_put_service(DEPLOYMENT_ID_JFL_1, INV_EXISTING_SERVICE_TYPE)); + }); + + nock(dh.CLOUDIFY_URL).put("/api/v2.1/blueprints/" + DEPLOYMENT_ID_JFL_1) + .reply(200, function(uri, requestBody) { + console.log(action_timer.step, "put", dh.CLOUDIFY_URL, uri, JSON.stringify(requestBody)); + return JSON.stringify(Cloudify.resp_blueprint(DEPLOYMENT_ID_JFL_1)); + }); + + nock(dh.CLOUDIFY_URL).put("/api/v2.1/deployments/" + DEPLOYMENT_ID_JFL_1) + .reply(201, function(uri, requestBody) { + console.log(action_timer.step, "put", dh.CLOUDIFY_URL, uri, JSON.stringify(requestBody)); + return JSON.stringify(Cloudify.resp_deploy(DEPLOYMENT_ID_JFL_1, DEPLOYMENT_ID_JFL_1, message.inputs)); + }); + + nock(dh.CLOUDIFY_URL).get("/api/v2.1/executions?deployment_id=" + DEPLOYMENT_ID_JFL_1 + "&workflow_id=create_deployment_environment&_include=id,status") + .reply(200, function(uri) { + console.log(action_timer.step, "get", dh.CLOUDIFY_URL, uri); + return JSON.stringify(Cloudify.resp_dep_creation(DEPLOYMENT_ID_JFL_1, execution_id, "failed")); + }); + + chai.request(dh_server.app).put(req_path) + .set('content-type', 'application/json') + .send(message) + .end(function(err, res) { + console.log(action_timer.step, "res for", test_txt, res.text); + expect(res).to.have.status(500); + expect(res.body).to.have.property('message'); + expect(res.body.message).to.be.equal( + 'Status 502 from CM API -- error code: UNKNOWN -- message: deployment_id(' + + DEPLOYMENT_ID_JFL_1 + '): deployment creation failed -- no error information'); + done(); + }); + }).timeout(50000); + }); +} + function test_put_dcae_deployments_success(dh_server) { const req_path = "/dcae-deployments/" + DEPLOYMENT_ID_JFL_1; const message = create_main_message(INV_EXISTING_SERVICE_TYPE, true); @@ -426,7 +486,7 @@ function test_put_dcae_deployments_success(dh_server) { nock(dh.CLOUDIFY_URL).get("/api/v2.1/executions?deployment_id=" + DEPLOYMENT_ID_JFL_1 + "&workflow_id=create_deployment_environment&_include=id,status") .reply(200, function(uri) { console.log(action_timer.step, "get", dh.CLOUDIFY_URL, uri); - return JSON.stringify(Cloudify.resp_dep_creation(DEPLOYMENT_ID_JFL_1)); + return JSON.stringify(Cloudify.resp_dep_creation(DEPLOYMENT_ID_JFL_1, execution_id)); }); nock(dh.CLOUDIFY_URL).post("/api/v2.1/executions") @@ -651,6 +711,7 @@ dh.add_tests([ test_put_dcae_deployments_missing_input_error, test_get_dcae_deployments_operation, test_get_dcae_deployments_service_type_deployed, + test_put_dcae_deployments_creation_failed, test_put_dcae_deployments_success, test_delete_dcae_deployments_success ]); diff --git a/tests/test_policy.js b/tests/test_policy.js index bc424e0..32e5ed4 100644 --- a/tests/test_policy.js +++ b/tests/test_policy.js @@ -211,7 +211,13 @@ const cloudify_node_instances = [ ]; function nock_cfy_node_instances(action_timer) { - nock(dh.CLOUDIFY_URL).get(CFY_API_NODE_INSTANCES).query(true) + // "/node-instances?_include=id,deployment_id,runtime_properties&_size=1000&_offset=0" + nock(dh.CLOUDIFY_URL).get(CFY_API_NODE_INSTANCES) + .query(params => { + console.log(action_timer.step, "get", dh.CLOUDIFY_URL, CFY_API_NODE_INSTANCES, JSON.stringify(params)); + return !!(params._include === "id,deployment_id,runtime_properties" + && params._size === "1000" && params._offset === "0"); + }) .reply(200, function(uri) { console.log(action_timer.step, "get", dh.CLOUDIFY_URL, uri); return JSON.stringify({ @@ -221,6 +227,29 @@ function nock_cfy_node_instances(action_timer) { }); } +function test_get_policy(dh_server) { + const req_path = "/policy"; + const test_txt = "GET " + req_path; + describe(test_txt, () => { + it('GET all the policies and policy-filters from cloudify', function() { + const action_timer = new utils.ActionTimer(); + console.log(action_timer.step, test_txt); + nock_cfy_node_instances(action_timer); + + return chai.request(dh_server.app).get(req_path) + .then(function(res) { + console.log(action_timer.step, "res for", test_txt, res.text); + expect(res).to.have.status(200); + expect(res).to.be.json; + }) + .catch(function(err) { + console.error(action_timer.step, "err for", test_txt, err); + throw err; + }); + }); + }); +} + function test_get_policy_components(dh_server) { const req_path = "/policy/components"; const test_txt = "GET " + req_path; @@ -309,7 +338,7 @@ function test_put_policy_catch_up(dh_server) { return JSON.stringify(resp_to_exe); }); - for (var extra_i = 1; extra_i <= 100000; extra_i++) { + for (var extra_i = 1; extra_i <= 10000; extra_i++) { const policy_id = "extra_" + extra_i; message.latest_policies[policy_id] = create_policy(policy_id, extra_i); } @@ -474,6 +503,7 @@ function test_fail_404_cfy_policy_catch_up(dh_server) { } dh.add_tests([ + test_get_policy, test_get_policy_components, test_put_policy_catch_up, test_fail_cfy_policy_catch_up, diff --git a/version.properties b/version.properties index 0453b60..decdfc4 100644 --- a/version.properties +++ b/version.properties @@ -1,6 +1,6 @@ major=3 -minor=0 -patch=3 +minor=1 +patch=0 base_version=${major}.${minor}.${patch} release_version=${base_version} snapshot_version=${base_version}-SNAPSHOT -- cgit 1.2.3-korg