diff options
author | ChrisC <cc697w@intl.att.com> | 2017-01-31 11:40:03 +0100 |
---|---|---|
committer | ChrisC <cc697w@intl.att.com> | 2017-01-31 12:59:33 +0100 |
commit | 025301d08b061482c1f046d562bf017c8cbcfe8d (patch) | |
tree | 68a2a549736c9bf0f7cd4e71c76e40ef7e2606f2 /adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoHeatUtilsWithUpdate.java | |
parent | 2754ad52f833278a5c925bd788a16d1dce16a598 (diff) |
Initial OpenECOMP MSO commit
Change-Id: Ia6a7574859480717402cc2f22534d9973a78fa6d
Signed-off-by: ChrisC <cc697w@intl.att.com>
Diffstat (limited to 'adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoHeatUtilsWithUpdate.java')
-rw-r--r-- | adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoHeatUtilsWithUpdate.java | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoHeatUtilsWithUpdate.java b/adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoHeatUtilsWithUpdate.java new file mode 100644 index 0000000000..04dfa12796 --- /dev/null +++ b/adapters/mso-adapter-utils/src/main/java/org/openecomp/mso/openstack/utils/MsoHeatUtilsWithUpdate.java @@ -0,0 +1,333 @@ +/*- + * ============LICENSE_START======================================================= + * OPENECOMP - MSO + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.mso.openstack.utils; + + +import java.util.HashMap; +import java.util.Map; + +import org.openecomp.mso.cloud.CloudConfigFactory; +import org.openecomp.mso.cloud.CloudSite; +import org.openecomp.mso.logger.MessageEnum; +import org.openecomp.mso.logger.MsoLogger; +import org.openecomp.mso.openstack.beans.StackInfo; +import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound; +import org.openecomp.mso.openstack.exceptions.MsoException; +import org.openecomp.mso.openstack.exceptions.MsoOpenstackException; +import org.openecomp.mso.openstack.exceptions.MsoStackNotFound; +import org.openecomp.mso.properties.MsoJavaProperties; +import org.openecomp.mso.properties.MsoPropertiesException; +import org.openecomp.mso.properties.MsoPropertiesFactory; +import com.woorea.openstack.base.client.OpenStackBaseException; +import com.woorea.openstack.base.client.OpenStackRequest; +import com.woorea.openstack.heat.Heat; +import com.woorea.openstack.heat.model.Stack; +import com.woorea.openstack.heat.model.UpdateStackParam; + +public class MsoHeatUtilsWithUpdate extends MsoHeatUtils { + + private static final String UPDATE_STACK = "UpdateStack"; + private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA); + + protected MsoJavaProperties msoProps = null; + + public MsoHeatUtilsWithUpdate (String msoPropID, MsoPropertiesFactory msoPropertiesFactory, CloudConfigFactory cloudConfFactory) { + super (msoPropID,msoPropertiesFactory,cloudConfFactory); + + try { + msoProps = msoPropertiesFactory.getMsoJavaProperties (msoPropID); + } catch (MsoPropertiesException e) { + LOGGER.error (MessageEnum.LOAD_PROPERTIES_FAIL, "Unknown. Mso Properties ID not found in cache: " + msoPropID, "", "", MsoLogger.ErrorCode.AvailabilityError, "Exception Mso Properties ID not found in cache: " + msoPropID, e); + } + } + + /* + * Keep these methods around for backward compatibility + */ + + public StackInfo updateStack (String cloudSiteId, + String tenantId, + String stackName, + String heatTemplate, + Map <String, Object> stackInputs, + boolean pollForCompletion, + int timeoutMinutes) throws MsoException { + // Keeping this method to allow compatibility with no environment or files variable sent. In this case, + // simply return the new method with the environment variable set to null. + return this.updateStack (cloudSiteId, + tenantId, + stackName, + heatTemplate, + stackInputs, + pollForCompletion, + timeoutMinutes, + null, + null, + null); + } + + public StackInfo updateStack (String cloudSiteId, + String tenantId, + String stackName, + String heatTemplate, + Map <String, Object> stackInputs, + boolean pollForCompletion, + int timeoutMinutes, + String environment) throws MsoException { + // Keeping this method to allow compatibility with no environment variable sent. In this case, + // simply return the new method with the files variable set to null. + return this.updateStack (cloudSiteId, + tenantId, + stackName, + heatTemplate, + stackInputs, + pollForCompletion, + timeoutMinutes, + environment, + null, + null); + } + + public StackInfo updateStack (String cloudSiteId, + String tenantId, + String stackName, + String heatTemplate, + Map <String, Object> stackInputs, + boolean pollForCompletion, + int timeoutMinutes, + String environment, + Map <String, Object> files) throws MsoException { + return this.updateStack (cloudSiteId, + tenantId, + stackName, + heatTemplate, + stackInputs, + pollForCompletion, + timeoutMinutes, + environment, + files, + null); + } + + /** + * Update a Stack in the specified cloud location and tenant. The Heat template + * and parameter map are passed in as arguments, along with the cloud access credentials. + * It is expected that parameters have been validated and contain at minimum the required + * parameters for the given template with no extra (undefined) parameters.. + * + * The Stack name supplied by the caller must be unique in the scope of this tenant. + * However, it should also be globally unique, as it will be the identifier for the + * resource going forward in Inventory. This latter is managed by the higher levels + * invoking this function. + * + * The caller may choose to let this function poll Openstack for completion of the + * stack creation, or may handle polling itself via separate calls to query the status. + * In either case, a StackInfo object will be returned containing the current status. + * When polling is enabled, a status of CREATED is expected. When not polling, a + * status of BUILDING is expected. + * + * An error will be thrown if the requested Stack already exists in the specified + * Tenant and Cloud. + * + * @param tenantId The Openstack ID of the tenant in which to create the Stack + * @param cloudSiteId The cloud identifier (may be a region) in which to create the tenant. + * @param stackName The name of the stack to update + * @param stackTemplate The Heat template + * @param stackInputs A map of key/value inputs + * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client + * @param environment An optional yaml-format string to specify environmental parameters + * @param files a Map<String, Object> for listing child template IDs + * @param heatFiles a Map<String, Object> for listing get_file entries (fileName, fileBody) + * @return A StackInfo object + * @throws MsoException Thrown if the Openstack API call returns an exception. + */ + + public StackInfo updateStack (String cloudSiteId, + String tenantId, + String stackName, + String heatTemplate, + Map <String, Object> stackInputs, + boolean pollForCompletion, + int timeoutMinutes, + String environment, + Map <String, Object> files, + Map <String, Object> heatFiles) throws MsoException { + boolean heatEnvtVariable = true; + if (environment == null || "".equalsIgnoreCase (environment.trim ())) { + heatEnvtVariable = false; + } + boolean haveFiles = true; + if (files == null || files.isEmpty ()) { + haveFiles = false; + } + boolean haveHeatFiles = true; + if (heatFiles == null || heatFiles.isEmpty ()) { + haveHeatFiles = false; + } + + // Obtain the cloud site information where we will create the stack + CloudSite cloudSite = cloudConfig.getCloudSite (cloudSiteId); + if (cloudSite == null) { + throw new MsoCloudSiteNotFound (cloudSiteId); + } + // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId) + // This could throw MsoTenantNotFound or MsoOpenstackException (both propagated) + Heat heatClient = getHeatClient (cloudSite, tenantId); + + // Perform a query first to get the current status + Stack heatStack = queryHeatStack (heatClient, stackName); + if (heatStack == null || "DELETE_COMPLETE".equals (heatStack.getStackStatus ())) { + // Not found. Return a StackInfo with status NOTFOUND + throw new MsoStackNotFound (stackName, tenantId, cloudSiteId); + } + + // Use canonical name "<stack name>/<stack-id>" to update the stack. + // Otherwise, update by name returns a 302 redirect. + // NOTE: This is specific to the v1 Orchestration API. + String canonicalName = heatStack.getStackName () + "/" + heatStack.getId (); + + LOGGER.debug ("Ready to Update Stack (" + canonicalName + ") with input params: " + stackInputs); + + // Build up the stack update parameters + // Disable auto-rollback, because error reason is lost. Always rollback in the code. + UpdateStackParam stack = new UpdateStackParam (); + stack.setTimeoutMinutes (timeoutMinutes); + stack.setParameters (stackInputs); + stack.setTemplate (heatTemplate); + stack.setDisableRollback (true); + // TJM add envt to stack + if (heatEnvtVariable) { + stack.setEnvironment (environment); + } + + // Handle nested templates & get_files here. if we have both - must combine + // and then add to stack (both are part of "files:" being added to stack) + if (haveFiles && haveHeatFiles) { + // Let's do this here - not in the bean + LOGGER.debug ("Found files AND heatFiles - combine and add!"); + Map <String, Object> combinedFiles = new HashMap <String, Object> (); + for (String keyString : files.keySet ()) { + combinedFiles.put (keyString, files.get (keyString)); + } + for (String keyString : heatFiles.keySet ()) { + combinedFiles.put (keyString, heatFiles.get (keyString)); + } + stack.setFiles (combinedFiles); + } else { + // Handle case where we have one or neither + if (haveFiles) { + stack.setFiles (files); + } + if (haveHeatFiles) { + // setFiles method modified to handle adding a map. + stack.setFiles (heatFiles); + } + } + + try { + // Execute the actual Openstack command to update the Heat stack + OpenStackRequest <Void> request = heatClient.getStacks ().update (canonicalName, stack); + executeAndRecordOpenstackRequest (request, msoProps); + } catch (OpenStackBaseException e) { + // Since this came on the 'Update Stack' command, nothing was changed + // in the cloud. Rethrow the error as an MSO exception. + throw heatExceptionToMsoException (e, UPDATE_STACK); + } catch (RuntimeException e) { + // Catch-all + throw runtimeExceptionToMsoException (e, UPDATE_STACK); + } + + // If client has requested a final response, poll for stack completion + Stack updateStack = null; + 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. + int createPollInterval = msoProps.getIntProperty (createPollIntervalProp, createPollIntervalDefault); + int pollTimeout = (timeoutMinutes * 60) + createPollInterval; + + boolean loopAgain = true; + while (loopAgain) { + try { + updateStack = queryHeatStack (heatClient, canonicalName); + LOGGER.debug (updateStack.getStackStatus ()); + + if ("UPDATE_IN_PROGRESS".equals (updateStack.getStackStatus ())) { + // Stack update 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 call. + LOGGER.error (MessageEnum.RA_UPDATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName, updateStack.getStackStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError, "Update stack timeout"); + loopAgain = false; + } else { + try { + Thread.sleep (createPollInterval * 1000L); + } catch (InterruptedException e) { + // If we are interrupted, we should stop ASAP. + loopAgain = false; + // Set again the interrupted flag + Thread.currentThread().interrupt(); + } + } + pollTimeout -= createPollInterval; + } else { + loopAgain = false; + } + } catch (MsoException e) { + // Cannot query the stack. Something is wrong. + + // TODO: No way to roll back the stack at this point. What to do? + e.addContext (UPDATE_STACK); + throw e; + } + } + + if (!"UPDATE_COMPLETE".equals (updateStack.getStackStatus ())) { + LOGGER.error (MessageEnum.RA_UPDATE_STACK_ERR, updateStack.getStackStatus(), updateStack.getStackStatusReason(), "", "", MsoLogger.ErrorCode.DataError, "Update Stack error"); + + // TODO: No way to roll back the stack at this point. What to do? + // Throw a 'special case' of MsoOpenstackException to report the Heat status + MsoOpenstackException me = null; + if ("UPDATE_IN_PROGRESS".equals (updateStack.getStackStatus ())) { + me = new MsoOpenstackException (0, "", "Stack Update Timeout"); + } else { + String error = "Stack error (" + updateStack.getStackStatus () + + "): " + + updateStack.getStackStatusReason (); + me = new MsoOpenstackException (0, "", error); + } + me.addContext (UPDATE_STACK); + throw me; + } + + } else { + // Return the current status. + updateStack = queryHeatStack (heatClient, canonicalName); + if (updateStack != null) { + LOGGER.debug ("UpdateStack, status = " + updateStack.getStackStatus ()); + } else { + LOGGER.debug ("UpdateStack, stack not found"); + } + } + return new StackInfo (updateStack); + } +} |