Initial OpenECOMP MSO commit
[so.git] / adapters / mso-adapter-utils / src / main / java / org / openecomp / mso / openstack / utils / MsoHeatUtilsWithUpdate.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * OPENECOMP - MSO
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
10  * 
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  * 
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=========================================================
19  */
20
21 package org.openecomp.mso.openstack.utils;
22
23
24 import java.util.HashMap;
25 import java.util.Map;
26
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;
44
45 public class MsoHeatUtilsWithUpdate extends MsoHeatUtils {
46
47     private static final String UPDATE_STACK = "UpdateStack";
48     private static MsoLogger LOGGER = MsoLogger.getMsoLogger (MsoLogger.Catalog.RA);
49
50     protected MsoJavaProperties msoProps = null;
51
52     public MsoHeatUtilsWithUpdate (String msoPropID, MsoPropertiesFactory msoPropertiesFactory, CloudConfigFactory cloudConfFactory) {
53         super (msoPropID,msoPropertiesFactory,cloudConfFactory);
54         
55         try {
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);
59                 }
60     }
61     
62     /*
63      * Keep these methods around for backward compatibility
64      */
65
66     public StackInfo updateStack (String cloudSiteId,
67                                   String tenantId,
68                                   String stackName,
69                                   String heatTemplate,
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,
76                                  tenantId,
77                                  stackName,
78                                  heatTemplate,
79                                  stackInputs,
80                                  pollForCompletion,
81                                  timeoutMinutes,
82                                  null,
83                                  null,
84                                  null);
85     }
86
87     public StackInfo updateStack (String cloudSiteId,
88                                   String tenantId,
89                                   String stackName,
90                                   String heatTemplate,
91                                   Map <String, Object> stackInputs,
92                                   boolean pollForCompletion,
93                                   int timeoutMinutes,
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,
98                                  tenantId,
99                                  stackName,
100                                  heatTemplate,
101                                  stackInputs,
102                                  pollForCompletion,
103                                  timeoutMinutes,
104                                  environment,
105                                  null,
106                                  null);
107     }
108
109     public StackInfo updateStack (String cloudSiteId,
110                                   String tenantId,
111                                   String stackName,
112                                   String heatTemplate,
113                                   Map <String, Object> stackInputs,
114                                   boolean pollForCompletion,
115                                   int timeoutMinutes,
116                                   String environment,
117                                   Map <String, Object> files) throws MsoException {
118         return this.updateStack (cloudSiteId,
119                                  tenantId,
120                                  stackName,
121                                  heatTemplate,
122                                  stackInputs,
123                                  pollForCompletion,
124                                  timeoutMinutes,
125                                  environment,
126                                  files,
127                                  null);
128     }
129
130     /**
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..
135      *
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.
140      *
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.
146      *
147      * An error will be thrown if the requested Stack already exists in the specified
148      * Tenant and Cloud.
149      *
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.
161      */
162
163     public StackInfo updateStack (String cloudSiteId,
164                                   String tenantId,
165                                   String stackName,
166                                   String heatTemplate,
167                                   Map <String, Object> stackInputs,
168                                   boolean pollForCompletion,
169                                   int timeoutMinutes,
170                                   String environment,
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;
176         }
177         boolean haveFiles = true;
178         if (files == null || files.isEmpty ()) {
179             haveFiles = false;
180         }
181         boolean haveHeatFiles = true;
182         if (heatFiles == null || heatFiles.isEmpty ()) {
183             haveHeatFiles = false;
184         }
185
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);
190         }
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);
194
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);
200         }
201
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 ();
206
207         LOGGER.debug ("Ready to Update Stack (" + canonicalName + ") with input params: " + stackInputs);
208
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);
219         }
220
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));
229             }
230             for (String keyString : heatFiles.keySet ()) {
231                 combinedFiles.put (keyString, heatFiles.get (keyString));
232             }
233             stack.setFiles (combinedFiles);
234         } else {
235             // Handle case where we have one or neither
236             if (haveFiles) {
237                 stack.setFiles (files);
238             }
239             if (haveHeatFiles) {
240                 // setFiles method modified to handle adding a map.
241                 stack.setFiles (heatFiles);
242             }
243         }
244
245         try {
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) {
254             // Catch-all
255             throw runtimeExceptionToMsoException (e, UPDATE_STACK);
256         }
257
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;
266
267             boolean loopAgain = true;
268             while (loopAgain) {
269                 try {
270                     updateStack = queryHeatStack (heatClient, canonicalName);
271                     LOGGER.debug (updateStack.getStackStatus ());
272
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");
280                             loopAgain = false;
281                         } else {
282                             try {
283                                 Thread.sleep (createPollInterval * 1000L);
284                             } catch (InterruptedException e) {
285                                 // If we are interrupted, we should stop ASAP.
286                                 loopAgain = false;
287                                 // Set again the interrupted flag
288                                 Thread.currentThread().interrupt();
289                             }
290                         }
291                         pollTimeout -= createPollInterval;
292                     } else {
293                         loopAgain = false;
294                     }
295                 } catch (MsoException e) {
296                     // Cannot query the stack. Something is wrong.
297
298                     // TODO: No way to roll back the stack at this point. What to do?
299                     e.addContext (UPDATE_STACK);
300                     throw e;
301                 }
302             }
303
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");
306
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");
312                 } else {
313                     String error = "Stack error (" + updateStack.getStackStatus ()
314                                    + "): "
315                                    + updateStack.getStackStatusReason ();
316                     me = new MsoOpenstackException (0, "", error);
317                 }
318                 me.addContext (UPDATE_STACK);
319                 throw me;
320             }
321
322         } else {
323             // Return the current status.
324             updateStack = queryHeatStack (heatClient, canonicalName);
325             if (updateStack != null) {
326                 LOGGER.debug ("UpdateStack, status = " + updateStack.getStackStatus ());
327             } else {
328                 LOGGER.debug ("UpdateStack, stack not found");
329             }
330         }
331         return new StackInfo (updateStack);
332     }
333 }