2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * ================================================================================
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 * ============LICENSE_END=========================================================
21 package org.openecomp.mso.openstack.utils;
24 import java.util.HashMap;
27 import org.openecomp.mso.cloud.CloudConfigFactory;
28 import org.openecomp.mso.cloud.CloudSite;
29 import org.openecomp.mso.logger.MessageEnum;
30 import org.openecomp.mso.logger.MsoLogger;
31 import org.openecomp.mso.openstack.beans.StackInfo;
32 import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound;
33 import org.openecomp.mso.openstack.exceptions.MsoException;
34 import org.openecomp.mso.openstack.exceptions.MsoOpenstackException;
35 import org.openecomp.mso.openstack.exceptions.MsoStackNotFound;
36 import org.openecomp.mso.properties.MsoJavaProperties;
37 import org.openecomp.mso.properties.MsoPropertiesException;
38 import org.openecomp.mso.properties.MsoPropertiesFactory;
39 import com.woorea.openstack.base.client.OpenStackBaseException;
40 import com.woorea.openstack.base.client.OpenStackRequest;
41 import com.woorea.openstack.heat.Heat;
42 import com.woorea.openstack.heat.model.Stack;
43 import com.woorea.openstack.heat.model.UpdateStackParam;
45 public class MsoHeatUtilsWithUpdate extends MsoHeatUtils {
47 private static final String UPDATE_STACK = "UpdateStack";
48 private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
50 protected MsoJavaProperties msoProps = null;
52 public MsoHeatUtilsWithUpdate (String msoPropID, MsoPropertiesFactory msoPropertiesFactory, CloudConfigFactory cloudConfFactory) {
53 super (msoPropID,msoPropertiesFactory,cloudConfFactory);
56 msoProps = msoPropertiesFactory.getMsoJavaProperties (msoPropID);
57 } catch (MsoPropertiesException e) {
58 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);
63 * Keep these methods around for backward compatibility
66 public StackInfo updateStack (String cloudSiteId,
70 Map <String, Object> stackInputs,
71 boolean pollForCompletion,
72 int timeoutMinutes) throws MsoException {
73 // Keeping this method to allow compatibility with no environment or files variable sent. In this case,
74 // simply return the new method with the environment variable set to null.
75 return this.updateStack (cloudSiteId,
87 public StackInfo updateStack (String cloudSiteId,
91 Map <String, Object> stackInputs,
92 boolean pollForCompletion,
94 String environment) throws MsoException {
95 // Keeping this method to allow compatibility with no environment variable sent. In this case,
96 // simply return the new method with the files variable set to null.
97 return this.updateStack (cloudSiteId,
109 public StackInfo updateStack (String cloudSiteId,
113 Map <String, Object> stackInputs,
114 boolean pollForCompletion,
117 Map <String, Object> files) throws MsoException {
118 return this.updateStack (cloudSiteId,
131 * Update a Stack in the specified cloud location and tenant. The Heat template
132 * and parameter map are passed in as arguments, along with the cloud access credentials.
133 * It is expected that parameters have been validated and contain at minimum the required
134 * parameters for the given template with no extra (undefined) parameters..
136 * The Stack name supplied by the caller must be unique in the scope of this tenant.
137 * However, it should also be globally unique, as it will be the identifier for the
138 * resource going forward in Inventory. This latter is managed by the higher levels
139 * invoking this function.
141 * The caller may choose to let this function poll Openstack for completion of the
142 * stack creation, or may handle polling itself via separate calls to query the status.
143 * In either case, a StackInfo object will be returned containing the current status.
144 * When polling is enabled, a status of CREATED is expected. When not polling, a
145 * status of BUILDING is expected.
147 * An error will be thrown if the requested Stack already exists in the specified
150 * @param tenantId The Openstack ID of the tenant in which to create the Stack
151 * @param cloudSiteId The cloud identifier (may be a region) in which to create the tenant.
152 * @param stackName The name of the stack to update
153 * @param stackTemplate The Heat template
154 * @param stackInputs A map of key/value inputs
155 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
156 * @param environment An optional yaml-format string to specify environmental parameters
157 * @param files a Map<String, Object> for listing child template IDs
158 * @param heatFiles a Map<String, Object> for listing get_file entries (fileName, fileBody)
159 * @return A StackInfo object
160 * @throws MsoException Thrown if the Openstack API call returns an exception.
163 public StackInfo updateStack (String cloudSiteId,
167 Map <String, Object> stackInputs,
168 boolean pollForCompletion,
171 Map <String, Object> files,
172 Map <String, Object> heatFiles) throws MsoException {
173 boolean heatEnvtVariable = true;
174 if (environment == null || "".equalsIgnoreCase (environment.trim ())) {
175 heatEnvtVariable = false;
177 boolean haveFiles = true;
178 if (files == null || files.isEmpty ()) {
181 boolean haveHeatFiles = true;
182 if (heatFiles == null || heatFiles.isEmpty ()) {
183 haveHeatFiles = false;
186 // Obtain the cloud site information where we will create the stack
187 CloudSite cloudSite = cloudConfig.getCloudSite (cloudSiteId);
188 if (cloudSite == null) {
189 throw new MsoCloudSiteNotFound (cloudSiteId);
191 // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
192 // This could throw MsoTenantNotFound or MsoOpenstackException (both propagated)
193 Heat heatClient = getHeatClient (cloudSite, tenantId);
195 // Perform a query first to get the current status
196 Stack heatStack = queryHeatStack (heatClient, stackName);
197 if (heatStack == null || "DELETE_COMPLETE".equals (heatStack.getStackStatus ())) {
198 // Not found. Return a StackInfo with status NOTFOUND
199 throw new MsoStackNotFound (stackName, tenantId, cloudSiteId);
202 // Use canonical name "<stack name>/<stack-id>" to update the stack.
203 // Otherwise, update by name returns a 302 redirect.
204 // NOTE: This is specific to the v1 Orchestration API.
205 String canonicalName = heatStack.getStackName () + "/" + heatStack.getId ();
207 LOGGER.debug ("Ready to Update Stack (" + canonicalName + ") with input params: " + stackInputs);
209 // Build up the stack update parameters
210 // Disable auto-rollback, because error reason is lost. Always rollback in the code.
211 UpdateStackParam stack = new UpdateStackParam ();
212 stack.setTimeoutMinutes (timeoutMinutes);
213 stack.setParameters (stackInputs);
214 stack.setTemplate (heatTemplate);
215 stack.setDisableRollback (true);
216 // TJM add envt to stack
217 if (heatEnvtVariable) {
218 stack.setEnvironment (environment);
221 // Handle nested templates & get_files here. if we have both - must combine
222 // and then add to stack (both are part of "files:" being added to stack)
223 if (haveFiles && haveHeatFiles) {
224 // Let's do this here - not in the bean
225 LOGGER.debug ("Found files AND heatFiles - combine and add!");
226 Map <String, Object> combinedFiles = new HashMap <String, Object> ();
227 for (String keyString : files.keySet ()) {
228 combinedFiles.put (keyString, files.get (keyString));
230 for (String keyString : heatFiles.keySet ()) {
231 combinedFiles.put (keyString, heatFiles.get (keyString));
233 stack.setFiles (combinedFiles);
235 // Handle case where we have one or neither
237 stack.setFiles (files);
240 // setFiles method modified to handle adding a map.
241 stack.setFiles (heatFiles);
246 // Execute the actual Openstack command to update the Heat stack
247 OpenStackRequest <Void> request = heatClient.getStacks ().update (canonicalName, stack);
248 executeAndRecordOpenstackRequest (request, msoProps);
249 } catch (OpenStackBaseException e) {
250 // Since this came on the 'Update Stack' command, nothing was changed
251 // in the cloud. Rethrow the error as an MSO exception.
252 throw heatExceptionToMsoException (e, UPDATE_STACK);
253 } catch (RuntimeException e) {
255 throw runtimeExceptionToMsoException (e, UPDATE_STACK);
258 // If client has requested a final response, poll for stack completion
259 Stack updateStack = null;
260 if (pollForCompletion) {
261 // Set a time limit on overall polling.
262 // Use the resource (template) timeout for Openstack (expressed in minutes)
263 // and add one poll interval to give Openstack a chance to fail on its own.
264 int createPollInterval = msoProps.getIntProperty (createPollIntervalProp, createPollIntervalDefault);
265 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
267 boolean loopAgain = true;
270 updateStack = queryHeatStack (heatClient, canonicalName);
271 LOGGER.debug (updateStack.getStackStatus ());
273 if ("UPDATE_IN_PROGRESS".equals (updateStack.getStackStatus ())) {
274 // Stack update is still running.
275 // Sleep and try again unless timeout has been reached
276 if (pollTimeout <= 0) {
277 // Note that this should not occur, since there is a timeout specified
278 // in the Openstack call.
279 LOGGER.error (MessageEnum.RA_UPDATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName, updateStack.getStackStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError, "Update stack timeout");
283 Thread.sleep (createPollInterval * 1000L);
284 } catch (InterruptedException e) {
285 // If we are interrupted, we should stop ASAP.
287 // Set again the interrupted flag
288 Thread.currentThread().interrupt();
291 pollTimeout -= createPollInterval;
295 } catch (MsoException e) {
296 // Cannot query the stack. Something is wrong.
298 // TODO: No way to roll back the stack at this point. What to do?
299 e.addContext (UPDATE_STACK);
304 if (!"UPDATE_COMPLETE".equals (updateStack.getStackStatus ())) {
305 LOGGER.error (MessageEnum.RA_UPDATE_STACK_ERR, updateStack.getStackStatus(), updateStack.getStackStatusReason(), "", "", MsoLogger.ErrorCode.DataError, "Update Stack error");
307 // TODO: No way to roll back the stack at this point. What to do?
308 // Throw a 'special case' of MsoOpenstackException to report the Heat status
309 MsoOpenstackException me = null;
310 if ("UPDATE_IN_PROGRESS".equals (updateStack.getStackStatus ())) {
311 me = new MsoOpenstackException (0, "", "Stack Update Timeout");
313 String error = "Stack error (" + updateStack.getStackStatus ()
315 + updateStack.getStackStatusReason ();
316 me = new MsoOpenstackException (0, "", error);
318 me.addContext (UPDATE_STACK);
323 // Return the current status.
324 updateStack = queryHeatStack (heatClient, canonicalName);
325 if (updateStack != null) {
326 LOGGER.debug ("UpdateStack, status = " + updateStack.getStackStatus ());
328 LOGGER.debug ("UpdateStack, stack not found");
331 return new StackInfo (updateStack);