From ff6ba434b6d91b6a4a4e9b3a7fbb8cadced229ad Mon Sep 17 00:00:00 2001 From: Jack Lucas Date: Wed, 10 May 2017 01:48:41 +0000 Subject: Post-R1 API & other updates. Change-Id: Id0e2e15b95a5713a25a746534fc40b56599a5f06 Signed-off-by: Jack Lucas --- lib/cloudify.js | 231 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 143 insertions(+), 88 deletions(-) (limited to 'lib/cloudify.js') diff --git a/lib/cloudify.js b/lib/cloudify.js index b1565c4..145a10e 100644 --- a/lib/cloudify.js +++ b/lib/cloudify.js @@ -18,10 +18,8 @@ See the License for the specific language governing permissions and limitations "use strict"; -const stream = require('stream'); -const targz = require('node-tar.gz'); +const admzip = require('adm-zip'); -const utils = require('./utils'); const repeat = require('./repeat'); const req = require('./promise_request'); const doRequest = req.doRequest; @@ -39,83 +37,153 @@ var delay = function(dtime) { }); }; +// Get current status of a workflow execution +// Function for getting execution info +const getExecutionStatus = function(executionId) { + var reqOptions = { + method : "GET", + uri : cfyAPI + "/executions/" + executionId + }; + if (cfyAuth) { + reqOptions.auth = cfyAuth; + } + return doRequest(reqOptions); +}; + // Poll for the result of a workflow execution var getWorkflowResult = function(execution_id) { var finished = [ "terminated", "cancelled", "failed" ]; var retryInterval = 15000; // Every 15 seconds var maxTries = 240; // Up to an hour - logger.debug("Getting workflow status for execution id: " + execution_id); - - // Function for getting execution info - var getExecutionStatus = function() { - var reqOptions = { - method : "GET", - uri : cfyAPI + "/executions/" + execution_id - }; - if (cfyAuth) { - reqOptions.auth = cfyAuth; - } - return doRequest(reqOptions); - }; + logger.debug("Getting workflow result for execution id: " + execution_id); // Function for testing if workflow is finished - // Expects the result of getExecutionStatus + // Expects the result of getExecStatus var checkStatus = function(res) { logger.debug("Checking result: " + JSON.stringify(res) + " ==> " + (res.json && res.json.status && finished.indexOf(res.json.status) < 0)); return res.json && res.json.status && finished.indexOf(res.json.status) < 0; }; - - return repeat.repeatWhile(getExecutionStatus, checkStatus, maxTries, - retryInterval).then(function(res) { - if (res.json && res.json.status && res.json.status !== "terminated") { - throw ("workflow failed!"); - } else { + + // Create execution status checker function + var getExecStatus = function() { return getExecutionStatus(execution_id);}; + + return repeat.repeatWhile(getExecStatus, checkStatus, maxTries, retryInterval) + .then( + + /* Handle fulfilled promise from repeatWhile */ + function(res) { + + logger.debug('workflow result: ' + JSON.stringify(res)); + + /* Successful completion */ + if (res.json && res.json.status && res.json.status === 'terminated') { return res; } + + /* If we get here, we don't have a success and we're going to throw something */ + + var error = {}; + + /* We expect a JSON object with a status */ + if (res.json && res.json.status) { + + /* Failure -- we need to return something that looks like the CM API failures */ + if (res.json.status === 'failed') { + error.body = 'workflow failed: ' + execution_id + ' -- ' + (res.json.error ? JSON.stringify(res.json.error) : 'no error information'); + } + + /* Cancellation -- don't really expect this */ + else if (res.json.status === 'canceled' || res.json.status === 'cancelled') { + error.body = 'workflow canceled: ' + execution_id; + } + + /* Don't expect anything else -- but if we get it, it's not a success! */ + else { + error.body = 'workflow--unexpected status ' + res.json.status + ' for ' + execution_id; + } + } + + /* The body of the response from the API call to get execution status is not what we expect at all */ + else { + error.body = 'workflow--unexpected result body getting execution status from CM for ' + execution_id; + } + + throw error; + }, + + /* Handle rejection of promise from repeatWhile--don't use a catch because it would catch the error thrown above */ + function(err) { + /* repeatWhile could fail and we get here because: + * -- repeatWhile explicitly rejects the promise because it has exhausted the retries + * -- repeatWhile propagates a system error (e.g., network problem) trying to access the API + * -- repeatWhile propagates a rejected promise due to a bad HTTP response status + * These should all get normalized in deploy.js--so we just rethrow the error. + */ + + throw err; + }); }; +//Initiate a workflow execution against a deployment +const initiateWorkflowExecution = function(dpid, workflow) { + // Set up the HTTP POST request + var reqOptions = { + method : "POST", + uri : cfyAPI + "/executions", + headers : { + "Content-Type" : "application/json", + "Accept" : "*/*" + } + }; + if (cfyAuth) { + reqOptions.auth = cfyAuth; + } + var body = { + deployment_id : dpid, + workflow_id : workflow + }; + + // Make the POST request + return doRequest(reqOptions, JSON.stringify(body)) + .then(function(result) { + logger.debug("Result from POSTing workflow execution start: " + JSON.stringify(result)); + if (result.json && result.json.id) { + return {deploymentId: dpid, workflowType: workflow, executionId: result.json.id}; + } + else { + logger.debug("Did not get expected JSON body from POST to start workflow"); + var err = new Error("POST to start workflow got success response but no body"); + err.status = err.code = 502; + } + }); +}; + // Uploads a blueprint via the Cloudify API exports.uploadBlueprint = function(bpid, blueprint) { + // Cloudify API wants a gzipped tar of a directory, not the blueprint text - // So we make a directory and feed a gzipped tar as the body of the PUT - // request - var workingDir = "./work/" + bpid; - - return utils.makeDirAndFile(workingDir, 'blueprint.yaml', blueprint) - - .then(function() { - // Set up a read stream that presents tar'ed and gzipped data - var src = targz().createReadStream(workingDir); - - // Set up the HTTP PUT request - var reqOptions = { + var zip = new admzip(); + zip.addFile('work/', new Buffer(0)); + zip.addFile('work/blueprint.yaml', new Buffer(blueprint, 'utf8')); + var src = (zip.toBuffer()); + + // Set up the HTTP PUT request + var reqOptions = { method : "PUT", uri : cfyAPI + "/blueprints/" + bpid, headers : { "Content-Type" : "application/octet-stream", "Accept" : "*/*" } - }; - - if (cfyAuth) { - reqOptions.auth = cfyAuth; - } - // Initiate PUT request and return the promise for a result - return doRequest(reqOptions, src).then( - // Cleaning up the working directory without perturbing the result is - // messy! - function(result) { - utils.removeDir(workingDir); - return result; - }, function(err) { - logger.debug("Problem on upload: " + JSON.stringify(err)); - utils.removeDir(workingDir); - throw err; - }); + }; - }); + if (cfyAuth) { + reqOptions.auth = cfyAuth; + } + // Initiate PUT request and return the promise for a result + return doRequest(reqOptions, src); }; // Creates a deployment from a blueprint @@ -145,41 +213,31 @@ exports.createDeployment = function(dpid, bpid, inputs) { return doRequest(reqOptions, JSON.stringify(body)); }; -// Executes a workflow against a deployment (use for install and uninstall) -exports.executeWorkflow = function(dpid, workflow) { +// Initiate a workflow execution against a deployment +exports.initiateWorkflowExecution = initiateWorkflowExecution; - // Set up the HTTP POST request - var reqOptions = { - method : "POST", - uri : cfyAPI + "/executions", - headers : { - "Content-Type" : "application/json", - "Accept" : "*/*" - } - }; - if (cfyAuth) { - reqOptions.auth = cfyAuth; - } - var body = { - deployment_id : dpid, - workflow_id : workflow - }; +// Get the status of a workflow execution +exports.getWorkflowExecutionStatus = getExecutionStatus; - // Make the POST request - return doRequest(reqOptions, JSON.stringify(body)).then( - function(result) { - logger.debug("Result from POSTing workflow start: " + JSON.stringify(result)); - if (result.json && result.json.id) { - logger.debug("Waiting for workflow status: " + result.json.id); - return getWorkflowResult(result.json.id); - } - else { - logger.warn("Did not get expected JSON body from POST to start workflow"); - // TODO throw? we got an OK for workflow but no JSON? - } - }); +// Return a promise for the final result of a workflow execution +exports.getWorkflowResult = getWorkflowResult; + +// Executes a workflow against a deployment and returns a promise for final result +exports.executeWorkflow = function(dpid, workflow) { + + // Initiate the workflow + return initiateWorkflowExecution(dpid, workflow) + + // Wait for the result + .then (function(result) { + logger.debug("Result from initiating workflow: " + JSON.stringify(result)); + return getWorkflowResult(result.executionId); + }); }; +// Wait for workflow to complete and get result +exports.getWorkflowResult = getWorkflowResult; + // Retrieves outputs for a deployment exports.getOutputs = function(dpid) { var reqOptions = { @@ -245,10 +303,7 @@ exports.setAPIAddress = function(addr) { // Allow client to set Cloudify credentials exports.setCredentials = function(user, password) { - cfyAuth = { - user : user, - password : password - }; + cfyAuth = user + ':' + password; }; // Set a logger -- cgit 1.2.3-korg