From ceda84d021dde70299f96984ca7aec16740854be Mon Sep 17 00:00:00 2001 From: Shadi Haidar Date: Fri, 7 Sep 2018 22:05:43 -0400 Subject: Check deployment creation before install Unit Test Code Coverage: Statements : 76.85% ( 893/1162 ) Branches : 52.99% ( 275/519 ) Functions : 78.68% ( 155/197 ) Lines : 77.21% ( 884/1145 ) Issue-ID: DCAEGEN2-754 Change-Id: Id8f3fa26e8ece7e9099145ea20034829a1ad7d13 Signed-off-by: Shadi Haidar --- lib/cloudify.js | 97 ++++++++++++++++++++++++++++++++++++++++++ lib/deploy.js | 28 ++++++------ package.json | 2 +- pom.xml | 2 +- tests/test_dcae-deployments.js | 23 ++++++++++ version.properties | 2 +- 6 files changed, 137 insertions(+), 17 deletions(-) diff --git a/lib/cloudify.js b/lib/cloudify.js index 053bdb8..5ab0663 100644 --- a/lib/cloudify.js +++ b/lib/cloudify.js @@ -20,8 +20,11 @@ See the License for the specific language governing permissions and limitations const CLOUDIFY = "cloudify-manager"; const FINISHED = [ "terminated", "cancelled", "failed" ]; +const DEPLOYMENT_CREATION_FINISHED = [ "terminated" ]; 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 +const DEP_CREATION_STATUS_MAX_TRIES = 10; // Up to 5 minutes const DEFAULT_TENANT = "default_tenant"; const doRequest = require('./promise_request').doRequest; const repeat = require('./repeat'); @@ -79,6 +82,17 @@ const getExecutionStatus = function(req, execution_id) { return doRequest(req, reqOptions, null, CLOUDIFY); }; +// Get current status of a deployment creation +const getDeploymentCreationStatus = function(req, deployment_id) { + var reqOptions = { + method : "GET", + uri : cfyAPI + "/executions?deployment_id=" + deployment_id + "&workflow_id=create_deployment_environment&_include=id,status" + }; + addAuthToOptions(reqOptions, req); + + return doRequest(req, reqOptions, null, CLOUDIFY); +}; + // Poll for the result of a workflow execution until it's done const getWorkflowResult = function(mainReq, execution_id) { /* Defense: Some callers do not supply mainReq */ @@ -194,6 +208,83 @@ const initiateWorkflowExecution = function(req, deployment_id, workflow_id, para }); }; +// Poll for the deployment creation status +const getDeploymentCreationResult = 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'); + } + + /* 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; + + }); +}; + // Uploads a blueprint via the Cloudify API exports.uploadBlueprint = function(req, bpid, blueprint) { logger.info(req.dcaeReqId, "uploadBlueprint " + bpid); @@ -264,6 +355,11 @@ exports.executeWorkflow = function(req, deployment_id, workflow_id, parameters) }); }; +// Return a promise for the final result of a deployment update +exports.getDeploymentCreationResult = getDeploymentCreationResult; + +// Get the status of a deployment update +exports.getDeploymentCreationStatus = getDeploymentCreationStatus; // Retrieves outputs for a deployment exports.getOutputs = function(req, dpid) { @@ -469,3 +565,4 @@ exports.executeOperation = function (mainReq, deployment_id, operation, operatio exeQueue.queueUpExecution(mainReq, deployment_id, workflow_id, parameters); runQueuedExecution(mainReq, deployment_id, workflow_id, parameters); }; + diff --git a/lib/deploy.js b/lib/deploy.js index ee31fd3..2d75b52 100644 --- a/lib/deploy.js +++ b/lib/deploy.js @@ -144,23 +144,23 @@ const launchBlueprint = function(req, id, blueprint, inputs) { // Create deployment .then (function(result) { - logger.info(req.dcaeReqId, "deploymentId: " + id + " blueprint uploaded"); - // Create deployment - return cfy.createDeployment(req, id, id, inputs); + logger.info(req.dcaeReqId, "deploymentId: " + id + " blueprint uploaded"); + // Create deployment + return cfy.createDeployment(req, id, id, inputs); }) - // Launch the workflow, but don't wait for it to complete + // create the deployment and keep checking, for up to 5 minutes, until creation is complete .then(function(result){ - logger.info(req.dcaeReqId, "deploymentId: " + id + " deployment created"); - return delay(DELAY_INSTALL_WORKFLOW) - .then(function(){ - return cfy.initiateWorkflowExecution(req, id, 'install'); - }); - }) - .catch(function(error) { - logger.info(req.dcaeReqId, "Error: " + JSON.stringify(error) + " for launch blueprint for deploymentId " + id); - throw normalizeError(error); - }); + 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); + }); }; exports.launchBlueprint = launchBlueprint; diff --git a/package.json b/package.json index 196c8e2..4611db6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "onap-dcae-deployment-handler", - "version": "5.0.0", + "version": "5.0.1", "description": "ONAP DCAE Deployment Handler", "main": "deployment-handler.js", "dependencies": { diff --git a/pom.xml b/pom.xml index 283843c..03ed413 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.0-SNAPSHOT + 3.0.1-SNAPSHOT http://maven.apache.org UTF-8 diff --git a/tests/test_dcae-deployments.js b/tests/test_dcae-deployments.js index a010017..ca64d94 100644 --- a/tests/test_dcae-deployments.js +++ b/tests/test_dcae-deployments.js @@ -174,6 +174,23 @@ const Cloudify = { "blueprint_id": blueprint_id || deployment_id }; }, + resp_dep_creation: function(deployment_id) { + return { + "items": [ + { + "status": "terminated", + "id": "ee6b0d21-0257-46a3-bb83-6f61f9ab5f99" + } + ], + "metadata": { + "pagination": { + "total": 1, + "offset": 0, + "size": 10000 + } + } + }; + }, resp_execution: function(deployment_id, blueprint_id, execution_id, terminated, workflow_id) { return { "status": (terminated && "terminated") || "pending", @@ -406,6 +423,12 @@ function test_put_dcae_deployments_success(dh_server) { 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)); + }); + nock(dh.CLOUDIFY_URL).post("/api/v2.1/executions") .reply(201, function(uri, requestBody) { console.log(action_timer.step, "post", dh.CLOUDIFY_URL, uri, JSON.stringify(requestBody)); diff --git a/version.properties b/version.properties index b67dd8a..77d3d8c 100644 --- a/version.properties +++ b/version.properties @@ -1,6 +1,6 @@ major=3 minor=0 -patch=0 +patch=1 base_version=${major}.${minor}.${patch} release_version=${base_version} snapshot_version=${base_version}-SNAPSHOT -- cgit 1.2.3-korg