Merge 'origin/casablanca' into master
[so.git] / adapters / mso-adapter-utils / src / main / java / org / onap / so / openstack / utils / MsoMulticloudUtils.java
index 4ed35a4..5c0110b 100644 (file)
 package org.onap.so.openstack.utils;
 
 import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Scanner;
 
-import javax.ws.rs.core.UriBuilder;
-import javax.ws.rs.core.UriBuilderException;
 import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilderException;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 import org.onap.so.adapters.vdu.CloudInfo;
 import org.onap.so.adapters.vdu.PluginAction;
 import org.onap.so.adapters.vdu.VduArtifact;
@@ -40,40 +41,46 @@ import org.onap.so.adapters.vdu.VduModelInfo;
 import org.onap.so.adapters.vdu.VduPlugin;
 import org.onap.so.adapters.vdu.VduStateType;
 import org.onap.so.adapters.vdu.VduStatus;
+import org.onap.so.client.HttpClientFactory;
+import org.onap.so.client.RestClient;
+import org.onap.so.db.catalog.beans.CloudSite;
+import org.onap.so.logger.MessageEnum;
+import org.onap.so.logger.MsoLogger;
 import org.onap.so.openstack.beans.HeatStatus;
 import org.onap.so.openstack.beans.StackInfo;
+import org.onap.so.openstack.exceptions.MsoAdapterException;
 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
 import org.onap.so.openstack.exceptions.MsoException;
 import org.onap.so.openstack.exceptions.MsoOpenstackException;
-import org.onap.so.openstack.mappers.StackInfoMapper;
-import org.onap.so.client.HttpClient;
-import org.onap.so.client.RestClient;
-import org.onap.so.cloud.CloudConfig;
-import org.onap.so.db.catalog.beans.CloudSite;
 import org.onap.so.utils.TargetEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.core.env.Environment;
 import org.springframework.stereotype.Component;
 
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
 import com.woorea.openstack.heat.model.CreateStackParam;
-import com.woorea.openstack.heat.model.Stack;
 
 @Component
 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
 
-    @Autowired
-    protected CloudConfig cloudConfig;
-
-    @Autowired
-    private Environment env;
+    public static final String OOF_DIRECTIVES = "oof_directives";
+    public static final String SDNC_DIRECTIVES = "sdnc_directives";
+    public static final String VNF_ID = "vnf_id";
+    public static final String VF_MODULE_ID = "vf_module_id";
+    public static final String TEMPLATE_TYPE = "template_type";
+    public static final List<String> MULTICLOUD_INPUTS =
+            Arrays.asList(OOF_DIRECTIVES, SDNC_DIRECTIVES, TEMPLATE_TYPE);
 
-    private static final String ONAP_IP = "ONAP_IP";
-
-    private static final String DEFAULT_MSB_IP = "127.0.0.1";
+    private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
 
-    private static final Integer DEFAULT_MSB_PORT = 80;
+    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
+    private final HttpClientFactory httpClientFactory = new HttpClientFactory();
 
-    private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
+    @Autowired
+    private Environment environment;
 
     /******************************************************************************
      *
@@ -116,7 +123,7 @@ public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
      * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
      *        Template id)
      * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
-     * @param backout Donot delete stack on create Failure - defaulted to True
+     * @param backout Do not delete stack on create Failure - defaulted to True
      * @return A StackInfo object
      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
      */
@@ -135,59 +142,85 @@ public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
                                   Map <String, Object> heatFiles,
                                   boolean backout) throws MsoException {
 
-        // Get the directives, if present.
-        String oofDirectives = null;
-        String sdncDirectives = null;
-        String genericVnfId = null;
-        String vfModuleId = null;
+        logger.trace("Started MsoMulticloudUtils.createStack");
 
-        String key = "oof_directives";
-        if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
-            oofDirectives = (String) stackInputs.get(key);
-            stackInputs.remove(key);
-        }
-        key = "sdnc_directives";
-        if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
-            sdncDirectives = (String) stackInputs.get(key);
-            stackInputs.remove(key);
+        // Get the directives, if present.
+        String oofDirectives = "{}";
+        String sdncDirectives = "{}";
+        String genericVnfId = "";
+        String vfModuleId = "";
+        String templateType = "";
+
+        for (String key: MULTICLOUD_INPUTS) {
+            if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
+                if (key == OOF_DIRECTIVES) {
+                    oofDirectives = (String) stackInputs.get(key);
+                }
+                if (key == SDNC_DIRECTIVES) {
+                    sdncDirectives = (String) stackInputs.get(key);
+                }
+                if (key == TEMPLATE_TYPE) {
+                    templateType = (String) stackInputs.get(key);
+                }
+                if (logger.isDebugEnabled()) {
+                    logger.debug(String.format("Found %s: %s", key, stackInputs.get(key)));
+                }
+                stackInputs.remove(key);
+            }
         }
-        key = "generic_vnf_id";
-        if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
-            genericVnfId = (String) stackInputs.get(key);
-            stackInputs.remove(key);
+
+        if (!stackInputs.isEmpty() && stackInputs.containsKey(VF_MODULE_ID)){
+            vfModuleId = (String) stackInputs.get(VF_MODULE_ID);
         }
-        key = "vf_module_id";
-        if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
-            vfModuleId = (String) stackInputs.get(key);
-            stackInputs.remove(key);
+        if (!stackInputs.isEmpty() && stackInputs.containsKey(VNF_ID)){
+            genericVnfId = (String) stackInputs.get(VNF_ID);
         }
 
         // create the multicloud payload
         CreateStackParam stack = createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
 
-        MsoMulticloudParam multicloudParam = new MsoMulticloudParam();
-        multicloudParam.setGenericVnfId(genericVnfId);
-        multicloudParam.setVfModuleId(vfModuleId);
-        multicloudParam.setOofDirectives(oofDirectives);
-        multicloudParam.setSdncDirectives(sdncDirectives);
-        multicloudParam.setTemplateType("heat");
-        multicloudParam.setTemplateData(stack.toString());
+        MulticloudRequest multicloudRequest= new MulticloudRequest();
 
+        multicloudRequest.setGenericVnfId(genericVnfId);
+        multicloudRequest.setVfModuleId(vfModuleId);
+        multicloudRequest.setTemplateType(templateType);
+        multicloudRequest.setTemplateData(stack);
+        multicloudRequest.setOofDirectives(getDirectiveNode(oofDirectives));
+        multicloudRequest.setSdncDirectives(getDirectiveNode(sdncDirectives));
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Multicloud Request is: %s", multicloudRequest.toString()));
+        }
 
         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, null);
         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
 
-        if (multicloudClient != null) {
-            Response res = multicloudClient.post(multicloudParam);
-            logger.debug("Multicloud Post response is: " + res);
-        }
+        Response response = multicloudClient.post(multicloudRequest);
 
-        Stack responseStack = new Stack();
-        responseStack.setStackStatus(HeatStatus.CREATED.toString());
+        StackInfo createInfo = new StackInfo();
+        createInfo.setName(stackName);
 
-        return new StackInfoMapper(responseStack).map();
+        MulticloudCreateResponse multicloudResponseBody = null;
+        if (response.hasEntity()) {
+            multicloudResponseBody = getCreateBody((java.io.InputStream)response.getEntity());
+        }
+        if (response.getStatus() == Response.Status.CREATED.getStatusCode() && response.hasEntity()) {
+            createInfo.setCanonicalName(stackName + "/" + multicloudResponseBody.getWorkloadId());
+            if (logger.isDebugEnabled()) {
+                logger.debug("Multicloud Create Response Body: " + multicloudResponseBody);
+            }
+            return getStackStatus(cloudSiteId, tenantId, createInfo.getCanonicalName(), pollForCompletion, timeoutMinutes, backout);
+        } else {
+            StringBuilder stackErrorStatusReason = new StringBuilder(response.getStatusInfo().getReasonPhrase());
+            if (null != multicloudResponseBody) {
+                stackErrorStatusReason.append(multicloudResponseBody.toString());
+            }
+            MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
+            me.addContext(CREATE_STACK);
+            throw me;
+        }
     }
 
+    @Override
     public Map<String, Object> queryStackForOutputs(String cloudSiteId,
                                                            String tenantId, String stackName) throws MsoException {
         logger.debug("MsoHeatUtils.queryStackForOutputs)");
@@ -199,76 +232,364 @@ public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
     }
 
     /**
-     * Query for a single stack (by Name) in a tenant. This call will always return a
+     * Query for a single stack (by ID) in a tenant. This call will always return a
      * StackInfo object. If the stack does not exist, an "empty" StackInfo will be
      * returned - containing only the stack name and a status of NOTFOUND.
      *
      * @param tenantId The Openstack ID of the tenant in which to query
      * @param cloudSiteId The cloud identifier (may be a region) in which to query
-     * @param stackName The name of the stack to query (may be simple or canonical)
+     * @param stackId The ID of the stack to query
      * @return A StackInfo object
      * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
      */
     @Override
-    public StackInfo queryStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
-        logger.debug ("Query multicloud HEAT stack: " + stackName + " in tenant " + tenantId);
+    public StackInfo queryStack (String cloudSiteId, String tenantId, String instanceId) throws MsoException {
+        if (logger.isDebugEnabled()) {
+            logger.debug (String.format("Query multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
+        }
+        String stackName = null;
+        String stackId = null;
+        int offset = instanceId.indexOf('/');
+        if (offset > 0 && offset < (instanceId.length() - 1)) {
+            stackName = instanceId.substring(0, offset);
+            stackId = instanceId.substring(offset + 1);
+        } else {
+            stackName = instanceId;
+            stackId = instanceId;
+        }
 
-        String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackName);
+        StackInfo returnInfo = new StackInfo();
+        returnInfo.setName(stackName);
 
+        String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackId);
         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
 
         if (multicloudClient != null) {
             Response response = multicloudClient.get();
-            logger.debug("Multicloud Get response is: " + response);
+            if (logger.isDebugEnabled()) {
+                logger.debug (String.format("Mulicloud GET Response: %s", response.toString()));
+            }
 
-            return new StackInfo (stackName, HeatStatus.CREATED);
+            MulticloudQueryResponse multicloudQueryBody = null;
+            if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
+                returnInfo.setStatus(HeatStatus.NOTFOUND);
+                returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
+            } else if (response.getStatus() == Response.Status.OK.getStatusCode() && response.hasEntity()) {
+                multicloudQueryBody = getQueryBody((java.io.InputStream)response.getEntity());
+                returnInfo.setCanonicalName(stackName + "/" + multicloudQueryBody.getWorkloadId());
+                returnInfo.setStatus(getHeatStatus(multicloudQueryBody.getWorkloadStatus()));
+                returnInfo.setStatusMessage(multicloudQueryBody.getWorkloadStatus());
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Multicloud Create Response Body: " + multicloudQueryBody.toString());
+                }
+            } else {
+                returnInfo.setStatus(HeatStatus.FAILED);
+                returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
+            }
         }
 
-        return new StackInfo (stackName, HeatStatus.NOTFOUND);
+        return returnInfo;
     }
 
-    public StackInfo deleteStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
-        logger.debug ("Delete multicloud HEAT stack: " + stackName + " in tenant " + tenantId);
+    public StackInfo deleteStack (String cloudSiteId, String tenantId, String instanceId) throws MsoException {
+        if (logger.isDebugEnabled()) {
+            logger.debug (String.format("Delete multicloud HEAT stack: %s in tenant %s", instanceId, tenantId));
+        }
+        String stackName = null;
+        String stackId = null;
+        int offset = instanceId.indexOf('/');
+        if (offset > 0 && offset < (instanceId.length() - 1)) {
+            stackName = instanceId.substring(0, offset);
+            stackId = instanceId.substring(offset + 1);
+        } else {
+            stackName = instanceId;
+            stackId = instanceId;
+        }
 
-        String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackName);
+        StackInfo returnInfo = new StackInfo();
+        returnInfo.setName(stackName);
+        Response response = null;
 
+        String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackId);
         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
 
         if (multicloudClient != null) {
-            Response response = multicloudClient.delete();
-            logger.debug("Multicloud Get response is: " + response);
+            response = multicloudClient.delete();
+            if (logger.isDebugEnabled()) {
+                logger.debug(String.format("Multicloud Delete response is: %s", response.getEntity().toString()));
+            }
 
-            return new StackInfo (stackName, HeatStatus.DELETING);
-        }
+            if (response.getStatus() == Response.Status.NOT_FOUND.getStatusCode()) {
+                returnInfo.setStatus(HeatStatus.NOTFOUND);
+                returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
+            } else if (response.getStatus() == Response.Status.NO_CONTENT.getStatusCode()) {
+                return getStackStatus(cloudSiteId, tenantId, instanceId);
+            } else {
+                returnInfo.setStatus(HeatStatus.FAILED);
+                returnInfo.setStatusMessage(response.getStatusInfo().getReasonPhrase());
+            }
 
-        return new StackInfo (stackName, HeatStatus.FAILED);
+        }
+        returnInfo.setStatus(mapResponseToHeatStatus(response));
+        return returnInfo;
     }
 
     // ---------------------------------------------------------------
     // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
 
+    private HeatStatus getHeatStatus(String workloadStatus) {
+        if (workloadStatus.length() == 0) return HeatStatus.INIT;
+        if ("CREATE_IN_PROGRESS".equals(workloadStatus)) return HeatStatus.BUILDING;
+        if ("CREATE_COMPLETE".equals(workloadStatus)) return HeatStatus.CREATED;
+        if ("CREATE_FAILED".equals(workloadStatus)) return HeatStatus.FAILED;
+        if ("DELETE_IN_PROGRESS".equals(workloadStatus)) return HeatStatus.DELETING;
+        if ("DELETE_COMPLETE".equals(workloadStatus)) return HeatStatus.NOTFOUND;
+        if ("DELETE_FAILED".equals(workloadStatus)) return HeatStatus.FAILED;
+        if ("UPDATE_IN_PROGRESS".equals(workloadStatus)) return HeatStatus.UPDATING;
+        if ("UPDATE_FAILED".equals(workloadStatus)) return HeatStatus.FAILED;
+        if ("UPDATE_COMPLETE".equals(workloadStatus)) return HeatStatus.UPDATED;
+        return HeatStatus.UNKNOWN;
+    }
+
+    private StackInfo getStackStatus(String cloudSiteId, String tenantId, String instanceId) throws MsoException {
+        return getStackStatus(cloudSiteId, tenantId, instanceId, false, 0, false);
+    }
+
+    private StackInfo getStackStatus(String cloudSiteId, String tenantId, String instanceId, boolean pollForCompletion, int timeoutMinutes, boolean backout) throws MsoException {
+        StackInfo stackInfo = new StackInfo();
+
+        // If client has requested a final response, poll for stack completion
+        if (pollForCompletion) {
+            // Set a time limit on overall polling.
+            // Use the resource (template) timeout for Openstack (expressed in minutes)
+            // and add one poll interval to give Openstack a chance to fail on its own.s
+
+            int createPollInterval = Integer.parseInt(this.environment.getProperty(createPollIntervalProp, createPollIntervalDefault));
+            int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
+            // New 1610 - poll on delete if we rollback - use same values for now
+            int deletePollInterval = createPollInterval;
+            int deletePollTimeout = pollTimeout;
+            boolean createTimedOut = false;
+            StringBuilder stackErrorStatusReason = new StringBuilder("");
+            logger.debug("createPollInterval=" + createPollInterval + ", pollTimeout=" + pollTimeout);
+
+            while (true) {
+                try {
+                    stackInfo = queryStack(cloudSiteId, tenantId, instanceId);
+                    logger.debug (stackInfo.getStatus() + " (" + instanceId + ")");
+
+                    if (HeatStatus.BUILDING.equals(stackInfo.getStatus())) {
+                        // Stack creation is still running.
+                        // Sleep and try again unless timeout has been reached
+                        if (pollTimeout <= 0) {
+                            // Note that this should not occur, since there is a timeout specified
+                            // in the Openstack (multicloud?) call.
+                            logger.error(String.format("%s %s %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudSiteId, tenantId, instanceId, stackInfo.getStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError.getValue(), "Create stack timeout"));
+                            createTimedOut = true;
+                            break;
+                        }
+
+                        sleep(createPollInterval * 1000L);
+
+                        pollTimeout -= createPollInterval;
+                        logger.debug("pollTimeout remaining: " + pollTimeout);
+                    } else {
+                        //save off the status & reason msg before we attempt delete
+                        stackErrorStatusReason.append("Stack error (" + stackInfo.getStatus() + "): " + stackInfo.getStatusMessage());
+                        break;
+                    }
+                } catch (MsoException me) {
+                    // Cannot query the stack status. Something is wrong.
+                    // Try to roll back the stack
+                    if (!backout) {
+                        logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack error, stack deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError.getValue(), "Exception in Create Stack, stack deletion suppressed"));
+                    } else {
+                        try {
+                            logger.debug("Create Stack error - unable to query for stack status - attempting to delete stack: " + instanceId + " - This will likely fail and/or we won't be able to query to see if delete worked");
+                            StackInfo deleteInfo = deleteStack(cloudSiteId, tenantId, instanceId);
+                            // this may be a waste of time - if we just got an exception trying to query the stack - we'll just
+                            // get another one, n'est-ce pas?
+                            boolean deleted = false;
+                            while (!deleted) {
+                                try {
+                                    StackInfo queryInfo = queryStack(cloudSiteId, tenantId, instanceId);
+                                    logger.debug("Deleting " + instanceId + ", status: " + queryInfo.getStatus());
+                                    if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
+                                        if (deletePollTimeout <= 0) {
+                                            logger.error(String.format("%s %s %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudSiteId, tenantId, instanceId,
+                                                    queryInfo.getStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError.getValue(),
+                                                    "Rollback: DELETE stack timeout"));
+                                            break;
+                                        } else {
+                                            sleep(deletePollInterval * 1000L);
+                                            deletePollTimeout -= deletePollInterval;
+                                        }
+                                    } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())){
+                                        logger.debug("DELETE_COMPLETE for " + instanceId);
+                                        deleted = true;
+                                        continue;
+                                    } else {
+                                        //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
+                                        break;
+                                    }
+                                } catch (Exception e3) {
+                                    // Just log this one. We will report the original exception.
+                                    logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack: Nested exception rolling back stack: " + e3, "", "", MsoLogger.ErrorCode.BusinessProcesssError.getValue(), "Create Stack: Nested exception rolling back stack on error on query"));
+                                }
+                            }
+                        } catch (Exception e2) {
+                            // Just log this one. We will report the original exception.
+                            logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack: Nested exception rolling back stack: " + e2, "", "", MsoLogger.ErrorCode.BusinessProcesssError.getValue(), "Create Stack: Nested exception rolling back stack"));
+                        }
+                    }
+
+                    // Propagate the original exception from Stack Query.
+                    me.addContext (CREATE_STACK);
+                    throw me;
+                }
+            }
+
+            if (!HeatStatus.CREATED.equals(stackInfo.getStatus())) {
+                logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack error:  Polling complete with non-success status: "
+                              + stackInfo.getStatus () + ", " + stackInfo.getStatusMessage(), "", "", MsoLogger.ErrorCode.BusinessProcesssError.getValue(), "Create Stack error"));
+
+                // Rollback the stack creation, since it is in an indeterminate state.
+                if (!backout) {
+                    logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack errored, stack deletion suppressed", "", "", MsoLogger.ErrorCode.BusinessProcesssError.getValue(), "Create Stack error, stack deletion suppressed"));
+                }
+                else
+                {
+                    try {
+                        logger.debug("Create Stack errored - attempting to DELETE stack: " + instanceId);
+                        logger.debug("deletePollInterval=" + deletePollInterval + ", deletePollTimeout=" + deletePollTimeout);
+                        StackInfo deleteInfo = deleteStack(cloudSiteId, tenantId, instanceId);
+                        boolean deleted = false;
+                        while (!deleted) {
+                            try {
+                                StackInfo queryInfo = queryStack(cloudSiteId, tenantId, instanceId);
+                                logger.debug("Deleting " + instanceId + ", status: " + queryInfo.getStatus());
+                                if (HeatStatus.DELETING.equals(queryInfo.getStatus())) {
+                                    if (deletePollTimeout <= 0) {
+                                        logger.error(String.format("%s %s %s %s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_TIMEOUT.toString(), cloudSiteId, tenantId, instanceId,
+                                                queryInfo.getStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError.getValue(),
+                                                "Rollback: DELETE stack timeout"));
+                                        break;
+                                    } else {
+                                        sleep(deletePollInterval * 1000L);
+                                        deletePollTimeout -= deletePollInterval;
+                                    }
+                                } else if (HeatStatus.NOTFOUND.equals(queryInfo.getStatus())){
+                                    logger.debug("DELETE_COMPLETE for " + instanceId);
+                                    deleted = true;
+                                    continue;
+                                } else {
+                                    //got a status other than DELETE_IN_PROGRESS or DELETE_COMPLETE - so break and evaluate
+                                    logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack errored, stack deletion FAILED", "", "", MsoLogger.ErrorCode.BusinessProcesssError.getValue(), "Create Stack error, stack deletion FAILED"));
+                                    logger.debug("Stack deletion FAILED on a rollback of a create - " + instanceId + ", status=" + queryInfo.getStatus() + ", reason=" + queryInfo.getStatusMessage());
+                                    break;
+                                }
+                            } catch (MsoException me2) {
+                                // Just log this one. We will report the original exception.
+                                logger.debug("Exception thrown trying to delete " + instanceId + " on a create->rollback: " + me2.getContextMessage(), me2);
+                                logger.warn(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack errored, then stack deletion FAILED - exception thrown", "", "", MsoLogger.ErrorCode.BusinessProcesssError.getValue(), me2.getContextMessage()));
+                            }
+                        }
+                        StringBuilder errorContextMessage;
+                        if (createTimedOut) {
+                            errorContextMessage = new StringBuilder("Stack Creation Timeout");
+                        } else {
+                            errorContextMessage  = stackErrorStatusReason;
+                        }
+                        if (deleted) {
+                            errorContextMessage.append(" - stack successfully deleted");
+                        } else {
+                            errorContextMessage.append(" - encountered an error trying to delete the stack");
+                        }
+                    } catch (MsoException e2) {
+                        // shouldn't happen - but handle
+                        logger.error(String.format("%s %s %s %s %d %s", MessageEnum.RA_CREATE_STACK_ERR.toString(), "Create Stack: Nested exception rolling back stack: " + e2, "", "", MsoLogger.ErrorCode.BusinessProcesssError.getValue(), "Exception in Create Stack: rolling back stack"));
+                    }
+                }
+                MsoOpenstackException me = new MsoOpenstackException(0, "", stackErrorStatusReason.toString());
+                me.addContext(CREATE_STACK);
+                throw me;
+            }
+        } else {
+            // Get initial status, since it will have been null after the create.
+            stackInfo = queryStack(cloudSiteId, tenantId, instanceId);
+            logger.debug("Multicloud stack query status is: " + stackInfo.getStatus());
+        }
+        return stackInfo;
+    }
+
+    private HeatStatus mapResponseToHeatStatus(Response response) {
+        if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode()) {
+            return HeatStatus.CREATED;
+        } else if (response.getStatusInfo().getStatusCode() == Response.Status.CREATED.getStatusCode()) {
+            return HeatStatus.CREATED;
+        } else if (response.getStatusInfo().getStatusCode() == Response.Status.NO_CONTENT.getStatusCode()) {
+            return HeatStatus.CREATED;
+        } else if (response.getStatusInfo().getStatusCode() == Response.Status.BAD_REQUEST.getStatusCode()) {
+            return HeatStatus.FAILED;
+        } else if (response.getStatusInfo().getStatusCode() == Response.Status.UNAUTHORIZED.getStatusCode()) {
+            return HeatStatus.FAILED;
+        } else if (response.getStatusInfo().getStatusCode() == Response.Status.NOT_FOUND.getStatusCode()) {
+            return HeatStatus.NOTFOUND;
+        } else if (response.getStatusInfo().getStatusCode() == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
+            return HeatStatus.FAILED;
+        } else {
+            return HeatStatus.UNKNOWN;
+        }
+    }
+
+    private MulticloudCreateResponse getCreateBody(java.io.InputStream in) {
+        Scanner scanner = new Scanner(in);
+        scanner.useDelimiter("\\Z");
+        String body = "";
+        if (scanner.hasNext()) {
+            body = scanner.next();
+        }
+        scanner.close();
 
-    private String getMsbHost() {
-        // MSB_IP will be set as ONAP_IP environment parameter in install flow.
-        String msbIp = System.getenv().get(ONAP_IP);
+        try {
+            return new ObjectMapper().readerFor(MulticloudCreateResponse.class).readValue(body);
+        } catch (Exception e) {
+            logger.debug("Exception retrieving multicloud vfModule POST response body " + e);
+        }
+        return null;
+    }
 
-        // if ONAP IP is not set. get it from config file.
-        if (null == msbIp || msbIp.isEmpty()) {
-            msbIp = env.getProperty("mso.msb-ip", DEFAULT_MSB_IP);
+    private MulticloudQueryResponse getQueryBody(java.io.InputStream in) {
+        Scanner scanner = new Scanner(in);
+        scanner.useDelimiter("\\Z");
+        String body = "";
+        if (scanner.hasNext()) {
+            body = scanner.next();
         }
-        Integer msbPort = env.getProperty("mso.msb-port", Integer.class, DEFAULT_MSB_PORT);
+        scanner.close();
 
-        return UriBuilder.fromPath("").host(msbIp).port(msbPort).scheme("http").build().toString();
+        try {
+            return new ObjectMapper().readerFor(MulticloudQueryResponse.class).readValue(body);
+        } catch (Exception e) {
+            logger.debug("Exception retrieving multicloud workload query response body " + e);
+        }
+        return null;
     }
 
     private String getMulticloudEndpoint(String cloudSiteId, String workloadId) throws MsoCloudSiteNotFound {
 
         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
-        String endpoint = getMsbHost() + cloudSite.getIdentityService().getIdentityUrl();
+        String endpoint = cloudSite.getIdentityService().getIdentityUrl();
 
         if (workloadId != null) {
-            return endpoint + workloadId;
+            if (logger.isDebugEnabled()) {
+                logger.debug(String.format("Multicloud Endpoint is: %s/%s", endpoint, workloadId));
+            }
+            return String.format("%s/%s", endpoint, workloadId);
         } else {
+            if (logger.isDebugEnabled()) {
+                logger.debug(String.format("Multicloud Endpoint is: %s", endpoint));
+            }
             return endpoint;
         }
     }
@@ -276,18 +597,34 @@ public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
     private RestClient getMulticloudClient(String endpoint) {
         RestClient client = null;
         try {
-            client= new HttpClient(UriBuilder.fromUri(endpoint).build().toURL(),
-            "application/json", TargetEntity.OPENSTACK_ADAPTER);
+            client = httpClientFactory.newJsonClient(
+                new URL(endpoint),
+                TargetEntity.MULTICLOUD);
         } catch (MalformedURLException e) {
-            logger.debug("Encountered malformed URL error getting multicloud rest client " + e.getMessage());
+            logger.debug(String.format("Encountered malformed URL error getting multicloud rest client %s", e.getMessage()));
         } catch (IllegalArgumentException e) {
-            logger.debug("Encountered illegal argument getting multicloud rest client " + e.getMessage());
+            logger.debug(String.format("Encountered illegal argument getting multicloud rest client %s",e.getMessage()));
         } catch (UriBuilderException e) {
-            logger.debug("Encountered URI builder error getting multicloud rest client " + e.getMessage());
+            logger.debug(String.format("Encountered URI builder error getting multicloud rest client %s", e.getMessage()));
         }
         return client;
     }
 
+    private JsonNode getDirectiveNode(String directives) throws MsoException {
+        try {
+            return JSON_MAPPER.readTree(directives);
+        } catch (Exception e) {
+            logger.error(String.format("%s %s %s %s %d %s",
+                    MessageEnum.RA_CREATE_STACK_ERR.toString(),
+                    "Create Stack: " + e, "", "",
+                    MsoLogger.ErrorCode.BusinessProcesssError.getValue(),
+                    "Exception in Create Stack: Invalid JSON format of directives" + directives));
+            MsoException me = new MsoAdapterException("Invalid JSON format of directives parameter: " + directives);
+            me.addContext(CREATE_STACK);
+            throw me;
+        }
+    }
+
     /**
      * VduPlugin interface for instantiate function.
      *
@@ -382,7 +719,7 @@ public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
 
         try {
             // Delete the Multicloud stack
-            StackInfo stackInfo = deleteStack (tenantId, cloudSiteId, instanceId, true);
+            StackInfo stackInfo = deleteStack (cloudSiteId, tenantId, instanceId);
 
             // Populate a VduInstance based on the deleted Cloudify Deployment object
             VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
@@ -425,6 +762,9 @@ public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
     {
         VduInstance vduInstance = new VduInstance();
 
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("StackInfo to convert: %s", stackInfo.getParameters().toString()));
+        }
         // The full canonical name as the instance UUID
         vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
         vduInstance.setVduInstanceName(stackInfo.getName());
@@ -447,6 +787,12 @@ public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
         // There are lots of HeatStatus values, so this is a bit long...
         HeatStatus heatStatus = stackInfo.getStatus();
         String statusMessage = stackInfo.getStatusMessage();
+        logger.debug("HeatStatus = " + heatStatus + " msg = " + statusMessage);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Stack Status: %s", heatStatus.toString()));
+            logger.debug(String.format("Stack Status Message: %s", statusMessage));
+        }
 
         if (heatStatus == HeatStatus.INIT  ||  heatStatus == HeatStatus.BUILDING) {
             vduStatus.setState(VduStateType.INSTANTIATING);