2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6 * Copyright (C) 2017 Huawei Technologies Co., Ltd. All rights reserved.
7 * ================================================================================
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
12 * http://www.apache.org/licenses/LICENSE-2.0
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 * ============LICENSE_END=========================================================
22 package org.openecomp.mso.openstack.utils;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
30 import org.codehaus.jackson.JsonNode;
31 import org.codehaus.jackson.JsonParseException;
32 import org.codehaus.jackson.map.ObjectMapper;
34 import org.openecomp.mso.cloud.CloudConfigFactory;
35 import org.openecomp.mso.cloud.CloudSite;
36 import org.openecomp.mso.logger.MessageEnum;
37 import org.openecomp.mso.logger.MsoLogger;
38 import org.openecomp.mso.openstack.beans.StackInfo;
39 import org.openecomp.mso.openstack.exceptions.MsoCloudSiteNotFound;
40 import org.openecomp.mso.openstack.exceptions.MsoException;
41 import org.openecomp.mso.openstack.exceptions.MsoOpenstackException;
42 import org.openecomp.mso.openstack.exceptions.MsoStackNotFound;
43 import org.openecomp.mso.properties.MsoJavaProperties;
44 import org.openecomp.mso.properties.MsoPropertiesException;
45 import org.openecomp.mso.properties.MsoPropertiesFactory;
46 import com.woorea.openstack.base.client.OpenStackBaseException;
47 import com.woorea.openstack.base.client.OpenStackRequest;
48 import com.woorea.openstack.heat.Heat;
49 import com.woorea.openstack.heat.model.Stack;
50 import com.woorea.openstack.heat.model.UpdateStackParam;
51 import com.woorea.openstack.heat.model.Stack.Output;
53 public class MsoHeatUtilsWithUpdate extends MsoHeatUtils {
55 private static final String UPDATE_STACK = "UpdateStack";
56 private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
58 protected MsoJavaProperties msoProps = null;
60 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
62 public MsoHeatUtilsWithUpdate (String msoPropID, MsoPropertiesFactory msoPropertiesFactory, CloudConfigFactory cloudConfFactory) {
63 super (msoPropID,msoPropertiesFactory,cloudConfFactory);
66 msoProps = msoPropertiesFactory.getMsoJavaProperties (msoPropID);
67 } catch (MsoPropertiesException e) {
68 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);
73 * Keep these methods around for backward compatibility
76 public StackInfo updateStack (String cloudSiteId,
80 Map <String, Object> stackInputs,
81 boolean pollForCompletion,
82 int timeoutMinutes) throws MsoException {
83 // Keeping this method to allow compatibility with no environment or files variable sent. In this case,
84 // simply return the new method with the environment variable set to null.
85 return this.updateStack (cloudSiteId,
97 public StackInfo updateStack (String cloudSiteId,
101 Map <String, Object> stackInputs,
102 boolean pollForCompletion,
104 String environment) throws MsoException {
105 // Keeping this method to allow compatibility with no environment variable sent. In this case,
106 // simply return the new method with the files variable set to null.
107 return this.updateStack (cloudSiteId,
119 public StackInfo updateStack (String cloudSiteId,
123 Map <String, Object> stackInputs,
124 boolean pollForCompletion,
127 Map <String, Object> files) throws MsoException {
128 return this.updateStack (cloudSiteId,
141 * Update a Stack in the specified cloud location and tenant. The Heat template
142 * and parameter map are passed in as arguments, along with the cloud access credentials.
143 * It is expected that parameters have been validated and contain at minimum the required
144 * parameters for the given template with no extra (undefined) parameters..
146 * The Stack name supplied by the caller must be unique in the scope of this tenant.
147 * However, it should also be globally unique, as it will be the identifier for the
148 * resource going forward in Inventory. This latter is managed by the higher levels
149 * invoking this function.
151 * The caller may choose to let this function poll Openstack for completion of the
152 * stack creation, or may handle polling itself via separate calls to query the status.
153 * In either case, a StackInfo object will be returned containing the current status.
154 * When polling is enabled, a status of CREATED is expected. When not polling, a
155 * status of BUILDING is expected.
157 * An error will be thrown if the requested Stack already exists in the specified
160 * @param tenantId The Openstack ID of the tenant in which to create the Stack
161 * @param cloudSiteId The cloud identifier (may be a region) in which to create the tenant.
162 * @param stackName The name of the stack to update
163 * @param stackTemplate The Heat template
164 * @param stackInputs A map of key/value inputs
165 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
166 * @param environment An optional yaml-format string to specify environmental parameters
167 * @param files a Map<String, Object> for listing child template IDs
168 * @param heatFiles a Map<String, Object> for listing get_file entries (fileName, fileBody)
169 * @return A StackInfo object
170 * @throws MsoException Thrown if the Openstack API call returns an exception.
173 public StackInfo updateStack (String cloudSiteId,
177 Map <String, Object> stackInputs,
178 boolean pollForCompletion,
181 Map <String, Object> files,
182 Map <String, Object> heatFiles) throws MsoException {
183 boolean heatEnvtVariable = true;
184 if (environment == null || "".equalsIgnoreCase (environment.trim ())) {
185 heatEnvtVariable = false;
187 boolean haveFiles = true;
188 if (files == null || files.isEmpty ()) {
191 boolean haveHeatFiles = true;
192 if (heatFiles == null || heatFiles.isEmpty ()) {
193 haveHeatFiles = false;
196 // Obtain the cloud site information where we will create the stack
197 CloudSite cloudSite = cloudConfig.getCloudSite (cloudSiteId);
198 if (cloudSite == null) {
199 throw new MsoCloudSiteNotFound (cloudSiteId);
201 // Get a Heat client. They are cached between calls (keyed by tenantId:cloudId)
202 // This could throw MsoTenantNotFound or MsoOpenstackException (both propagated)
203 Heat heatClient = getHeatClient (cloudSite, tenantId);
205 // Perform a query first to get the current status
206 Stack heatStack = queryHeatStack (heatClient, stackName);
207 if (heatStack == null || "DELETE_COMPLETE".equals (heatStack.getStackStatus ())) {
208 // Not found. Return a StackInfo with status NOTFOUND
209 throw new MsoStackNotFound (stackName, tenantId, cloudSiteId);
212 // Use canonical name "<stack name>/<stack-id>" to update the stack.
213 // Otherwise, update by name returns a 302 redirect.
214 // NOTE: This is specific to the v1 Orchestration API.
215 String canonicalName = heatStack.getStackName () + "/" + heatStack.getId ();
217 LOGGER.debug ("Ready to Update Stack (" + canonicalName + ") with input params: " + stackInputs);
219 // Build up the stack update parameters
220 // Disable auto-rollback, because error reason is lost. Always rollback in the code.
221 UpdateStackParam stack = new UpdateStackParam ();
222 stack.setTimeoutMinutes (timeoutMinutes);
223 stack.setParameters (stackInputs);
224 stack.setTemplate (heatTemplate);
225 stack.setDisableRollback (true);
226 // TJM add envt to stack
227 if (heatEnvtVariable) {
228 stack.setEnvironment (environment);
231 // Handle nested templates & get_files here. if we have both - must combine
232 // and then add to stack (both are part of "files:" being added to stack)
233 if (haveFiles && haveHeatFiles) {
234 // Let's do this here - not in the bean
235 LOGGER.debug ("Found files AND heatFiles - combine and add!");
236 Map <String, Object> combinedFiles = new HashMap <String, Object> ();
237 for (String keyString : files.keySet ()) {
238 combinedFiles.put (keyString, files.get (keyString));
240 for (String keyString : heatFiles.keySet ()) {
241 combinedFiles.put (keyString, heatFiles.get (keyString));
243 stack.setFiles (combinedFiles);
245 // Handle case where we have one or neither
247 stack.setFiles (files);
250 // setFiles method modified to handle adding a map.
251 stack.setFiles (heatFiles);
256 // Execute the actual Openstack command to update the Heat stack
257 OpenStackRequest <Void> request = heatClient.getStacks ().update (canonicalName, stack);
258 executeAndRecordOpenstackRequest (request, msoProps);
259 } catch (OpenStackBaseException e) {
260 // Since this came on the 'Update Stack' command, nothing was changed
261 // in the cloud. Rethrow the error as an MSO exception.
262 throw heatExceptionToMsoException (e, UPDATE_STACK);
263 } catch (RuntimeException e) {
265 throw runtimeExceptionToMsoException (e, UPDATE_STACK);
268 // If client has requested a final response, poll for stack completion
269 Stack updateStack = null;
270 if (pollForCompletion) {
271 // Set a time limit on overall polling.
272 // Use the resource (template) timeout for Openstack (expressed in minutes)
273 // and add one poll interval to give Openstack a chance to fail on its own.
274 int createPollInterval = msoProps.getIntProperty (createPollIntervalProp, createPollIntervalDefault);
275 int pollTimeout = (timeoutMinutes * 60) + createPollInterval;
277 boolean loopAgain = true;
280 updateStack = queryHeatStack (heatClient, canonicalName);
281 LOGGER.debug (updateStack.getStackStatus () + " (" + canonicalName + ")");
283 LOGGER.debug("Current stack " + this.getOutputsAsStringBuilder(heatStack).toString());
284 } catch (Exception e) {
285 LOGGER.debug("an error occurred trying to print out the current outputs of the stack", e);
289 if ("UPDATE_IN_PROGRESS".equals (updateStack.getStackStatus ())) {
290 // Stack update is still running.
291 // Sleep and try again unless timeout has been reached
292 if (pollTimeout <= 0) {
293 // Note that this should not occur, since there is a timeout specified
294 // in the Openstack call.
295 LOGGER.error (MessageEnum.RA_UPDATE_STACK_TIMEOUT, cloudSiteId, tenantId, stackName, updateStack.getStackStatus(), "", "", MsoLogger.ErrorCode.AvailabilityError, "Update stack timeout");
299 Thread.sleep (createPollInterval * 1000L);
300 } catch (InterruptedException e) {
301 // If we are interrupted, we should stop ASAP.
303 // Set again the interrupted flag
304 Thread.currentThread().interrupt();
307 pollTimeout -= createPollInterval;
308 LOGGER.debug("pollTimeout remaining: " + pollTimeout);
312 } catch (MsoException e) {
313 // Cannot query the stack. Something is wrong.
315 // TODO: No way to roll back the stack at this point. What to do?
316 e.addContext (UPDATE_STACK);
321 if (!"UPDATE_COMPLETE".equals (updateStack.getStackStatus ())) {
322 LOGGER.error (MessageEnum.RA_UPDATE_STACK_ERR, updateStack.getStackStatus(), updateStack.getStackStatusReason(), "", "", MsoLogger.ErrorCode.DataError, "Update Stack error");
324 // TODO: No way to roll back the stack at this point. What to do?
325 // Throw a 'special case' of MsoOpenstackException to report the Heat status
326 MsoOpenstackException me = null;
327 if ("UPDATE_IN_PROGRESS".equals (updateStack.getStackStatus ())) {
328 me = new MsoOpenstackException (0, "", "Stack Update Timeout");
330 String error = "Stack error (" + updateStack.getStackStatus ()
332 + updateStack.getStackStatusReason ();
333 me = new MsoOpenstackException (0, "", error);
335 me.addContext (UPDATE_STACK);
340 // Return the current status.
341 updateStack = queryHeatStack (heatClient, canonicalName);
342 if (updateStack != null) {
343 LOGGER.debug ("UpdateStack, status = " + updateStack.getStackStatus ());
345 LOGGER.debug ("UpdateStack, stack not found");
348 return new StackInfo (updateStack);
351 private StringBuilder getOutputsAsStringBuilder(Stack heatStack) {
352 // This should only be used as a utility to print out the stack outputs
354 StringBuilder sb = new StringBuilder("");
355 if (heatStack == null) {
356 sb.append("(heatStack is null)");
359 List<Output> outputList = heatStack.getOutputs();
360 if (outputList == null || outputList.isEmpty()) {
361 sb.append("(outputs is empty)");
364 Map<String, Object> outputs = new HashMap<String,Object>();
365 for (Output outputItem : outputList) {
366 outputs.put(outputItem.getOutputKey(), outputItem.getOutputValue());
369 sb.append("OUTPUTS:\n");
370 for (String key : outputs.keySet()) {
371 sb.append("outputs[" + counter++ + "]: " + key + "=");
372 Object obj = outputs.get(key);
373 if (obj instanceof String) {
374 sb.append((String)obj +" (a string)");
375 } else if (obj instanceof JsonNode) {
376 sb.append(this.convertNode((JsonNode)obj) + " (a JsonNode)");
377 } else if (obj instanceof java.util.LinkedHashMap) {
379 String str = JSON_MAPPER.writeValueAsString(obj);
380 sb.append(str + " (a java.util.LinkedHashMap)");
381 } catch (Exception e) {
382 LOGGER.debug("Exception :", e);
383 sb.append("(a LinkedHashMap value that would not convert nicely)");
385 } else if (obj instanceof Integer) {
388 str = obj.toString() + " (an Integer)\n";
389 } catch (Exception e) {
390 LOGGER.debug("Exception :", e);
391 str = "(an Integer unable to call .toString() on)";
394 } else if (obj instanceof ArrayList) {
397 str = obj.toString() + " (an ArrayList)";
398 } catch (Exception e) {
399 LOGGER.debug("Exception :", e);
400 str = "(an ArrayList unable to call .toString() on?)";
403 } else if (obj instanceof Boolean) {
406 str = obj.toString() + " (a Boolean)";
407 } catch (Exception e) {
408 LOGGER.debug("Exception :", e);
409 str = "(an Boolean unable to call .toString() on?)";
416 str = obj.toString() + " (unknown Object type)";
417 } catch (Exception e) {
418 LOGGER.debug("Exception :", e);
419 str = "(a value unable to call .toString() on?)";
429 private String convertNode(final JsonNode node) {
431 final Object obj = JSON_MAPPER.treeToValue(node, Object.class);
432 final String json = JSON_MAPPER.writeValueAsString(obj);
434 } catch (JsonParseException jpe) {
435 LOGGER.debug("Error converting json to string " + jpe.getMessage(), jpe);
436 } catch (Exception e) {
437 LOGGER.debug("Error converting json to string " + e.getMessage(), e);
439 return "[Error converting json to string]";