2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018 Intel Corp. All rights reserved.
6 * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
7 * ================================================================================
8 * Modifications Copyright (c) 2019 Samsung
9 * ================================================================================
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 * ============LICENSE_END=========================================================
24 package org.onap.so.openstack.utils;
27 import com.fasterxml.jackson.core.type.TypeReference;
28 import com.fasterxml.jackson.databind.ObjectMapper;
29 import com.woorea.openstack.base.client.OpenStackBaseException;
30 import com.woorea.openstack.base.client.OpenStackConnectException;
31 import com.woorea.openstack.base.client.OpenStackRequest;
32 import com.woorea.openstack.base.client.OpenStackResponseException;
33 import com.woorea.openstack.heat.model.CreateStackParam;
34 import com.woorea.openstack.heat.model.Explanation;
35 import com.woorea.openstack.keystone.model.Error;
36 import com.woorea.openstack.quantum.model.NeutronError;
37 import java.io.IOException;
38 import java.util.HashMap;
40 import java.util.Map.Entry;
41 import org.onap.so.config.beans.PoConfig;
42 import org.onap.so.logger.ErrorCode;
43 import org.onap.so.logger.MessageEnum;
44 import org.onap.so.openstack.exceptions.MsoAdapterException;
45 import org.onap.so.openstack.exceptions.MsoException;
46 import org.onap.so.openstack.exceptions.MsoExceptionCategory;
47 import org.onap.so.openstack.exceptions.MsoIOException;
48 import org.onap.so.openstack.exceptions.MsoOpenstackException;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51 import org.springframework.beans.factory.annotation.Autowired;
52 import org.springframework.stereotype.Component;
54 @Component("CommonUtils")
55 public class MsoCommonUtils {
57 private static Logger logger = LoggerFactory.getLogger(MsoCommonUtils.class);
61 private PoConfig poConfig;
63 * Method to execute an Openstack command and track its execution time.
64 * For the metrics log, a category of "Openstack" is used along with a
65 * sub-category that identifies the specific call (using the real
66 * openstack-java-sdk classname of the OpenStackRequest<T> parameter).
69 protected <T> T executeAndRecordOpenstackRequest (OpenStackRequest <T> request) {
73 long start = System.currentTimeMillis ();
75 if (request.getClass ().getEnclosingClass () != null) {
76 requestType = request.getClass ().getEnclosingClass ().getSimpleName () + "."
77 + request.getClass ().getSimpleName ();
79 requestType = request.getClass ().getSimpleName ();
82 int retryDelay = poConfig.getRetryDelay();
83 int retryCount = poConfig.getRetryCount();
84 String retryCodes = poConfig.getRetryCodes();
86 // Run the actual command. All exceptions will be propagated
90 return request.execute ();
92 catch (OpenStackResponseException e) {
93 boolean retry = false;
94 if (retryCodes != null ) {
95 int code = e.getStatus();
96 logger.debug("Config values RetryDelay:{} RetryCount:{} RetryCodes:{} ResponseCode:{}", retryDelay,
97 retryCount, retryCodes, code);
98 for (String rCode : retryCodes.split (",")) {
100 if (retryCount > 0 && code == Integer.parseInt (rCode))
105 "OpenStackResponseException ResponseCode: {} request:{} Retry indicated. Attempts remaining:{}",
106 code, requestType, retryCount);
109 } catch (NumberFormatException e1) {
110 logger.error("{} No retries. Exception in parsing retry code in config:{} {} {}",
111 MessageEnum.RA_CONFIG_EXC, rCode, ErrorCode.SchemaError.getValue(),
112 "Exception in parsing retry code in config");
120 Thread.sleep (retryDelay * 1000L);
121 } catch (InterruptedException e1) {
122 logger.debug ("Thread interrupted while sleeping", e1);
123 Thread.currentThread().interrupt();
127 throw e; // exceeded retryCount or code is not retryable
129 catch (OpenStackConnectException e) {
130 // Connection to Openstack failed
134 logger.debug (" request: {} Retry indicated. Attempts remaining:{}", requestType, retryCount);
136 Thread.sleep (retryDelay * 1000L);
137 } catch (InterruptedException e1) {
138 logger.debug ("Thread interrupted while sleeping", e1);
139 Thread.currentThread().interrupt();
150 * Convert an Openstack Exception on a Keystone call to an MsoException.
151 * This method supports both OpenstackResponseException and OpenStackConnectException.
153 protected MsoException keystoneErrorToMsoException (OpenStackBaseException e, String context) {
154 MsoException me = null;
156 if (e instanceof OpenStackResponseException) {
157 OpenStackResponseException re = (OpenStackResponseException) e;
160 // Failed Keystone calls return an Error entity body.
161 Error error = re.getResponse ().getErrorEntity (Error.class);
162 logger.error("{} {} Openstack Keystone Error on {}: {}",
163 MessageEnum.RA_CONNECTION_EXCEPTION, ErrorCode.DataError.getValue(), context, error);
164 me = new MsoOpenstackException (error.getCode (), error.getTitle (), error.getMessage ());
165 } catch (Exception e2) {
166 // Can't parse the body as an "Error". Report the HTTP error
167 logger.error("{} {} HTTP Error on {}: {}, {}", MessageEnum.RA_CONNECTION_EXCEPTION,
168 ErrorCode.DataError.getValue(), context, re.getStatus(), re.getMessage(), e2);
169 me = new MsoOpenstackException (re.getStatus (), re.getMessage (), "");
172 // Add the context of the error
173 me.addContext (context);
175 // Generate an alarm for 5XX and higher errors.
176 if (re.getStatus () >= 500) {
179 } else if (e instanceof OpenStackConnectException) {
180 OpenStackConnectException ce = (OpenStackConnectException) e;
182 me = new MsoIOException (ce.getMessage ());
183 me.addContext (context);
185 // Generate an alarm for all connection errors.
186 logger.error("{} {} Openstack Keystone connection error on {}: ", MessageEnum.RA_GENERAL_EXCEPTION_ARG,
187 ErrorCode.DataError.getValue(), context, e);
194 * Convert an Openstack Exception on a Heat call to an MsoOpenstackException.
195 * This method supports both OpenstackResponseException and OpenStackConnectException.
197 protected MsoException heatExceptionToMsoException (OpenStackBaseException e, String context) {
198 MsoException me = null;
200 if (e instanceof OpenStackResponseException) {
201 OpenStackResponseException re = (OpenStackResponseException) e;
204 // Failed Heat calls return an Explanation entity body.
205 Explanation explanation = re.getResponse ().getErrorEntity (Explanation.class);
206 logger.error("{} {} Exception - Openstack Error on {} : {}", MessageEnum.RA_CONNECTION_EXCEPTION,
207 ErrorCode.DataError.getValue(), context, explanation.toString());
208 String fullError = explanation.getExplanation() + ", error.type=" + explanation.getError().getType() + ", error.message=" + explanation.getError().getMessage();
209 logger.debug(fullError);
210 me = new MsoOpenstackException (explanation.getCode (),
211 explanation.getTitle (),
212 //explanation.getExplanation ());
214 } catch (Exception e2) {
215 // Couldn't parse the body as an "Explanation". Report the original HTTP error.
216 logger.error("{} {} Exception - HTTP Error on {}: {}, ", MessageEnum.RA_CONNECTION_EXCEPTION,
217 ErrorCode.DataError.getValue(), context, re.getStatus(), e.getMessage(), e2);
218 me = new MsoOpenstackException (re.getStatus (), re.getMessage (), "");
221 // Add the context of the error
222 me.addContext (context);
224 // Generate an alarm for 5XX and higher errors.
225 if (re.getStatus () >= 500) {
228 } else if (e instanceof OpenStackConnectException) {
229 OpenStackConnectException ce = (OpenStackConnectException) e;
231 me = new MsoIOException (ce.getMessage ());
232 me.addContext (context);
234 // Generate an alarm for all connection errors.
236 logger.error("{} {} Openstack Heat connection error on {}: ", MessageEnum.RA_CONNECTION_EXCEPTION,
237 ErrorCode.DataError.getValue(), context, e);
244 * Convert an Openstack Exception on a Neutron call to an MsoOpenstackException.
245 * This method supports both OpenstackResponseException and OpenStackConnectException.
247 protected MsoException neutronExceptionToMsoException (OpenStackBaseException e, String context) {
248 MsoException me = null;
250 if (e instanceof OpenStackResponseException) {
251 OpenStackResponseException re = (OpenStackResponseException) e;
254 // Failed Neutron calls return an NeutronError entity body
255 NeutronError error = re.getResponse ().getErrorEntity (NeutronError.class);
256 logger.error("{} {} Openstack Neutron Error on {} {}", MessageEnum.RA_CONNECTION_EXCEPTION,
257 ErrorCode.DataError.getValue(), context, error);
258 me = new MsoOpenstackException (re.getStatus (), error.getType (), error.getMessage ());
259 } catch (Exception e2) {
260 // Couldn't parse body as a NeutronError. Report the HTTP error.
261 logger.error("{} {} Openstack HTTP Error on {}: {}, {}", MessageEnum.RA_CONNECTION_EXCEPTION,
262 ErrorCode.DataError.getValue(), context, re.getStatus(), e.getMessage(), e2);
263 me = new MsoOpenstackException (re.getStatus (), re.getMessage (), null);
266 // Add the context of the error
267 me.addContext (context);
269 // Generate an alarm for 5XX and higher errors.
270 if (re.getStatus () >= 500) {
273 } else if (e instanceof OpenStackConnectException) {
274 OpenStackConnectException ce = (OpenStackConnectException) e;
276 me = new MsoIOException (ce.getMessage ());
277 me.addContext (context);
279 // Generate an alarm for all connection errors.
281 logger.error("{} {} Openstack Neutron Connection error on {}: ", MessageEnum.RA_CONNECTION_EXCEPTION,
282 ErrorCode.DataError.getValue(), context, e);
289 * Convert a Java Runtime Exception to an MsoException.
290 * All Runtime exceptions will be translated into an MsoAdapterException,
291 * which captures internal errors.
292 * Alarms will be generated on all such exceptions.
294 protected MsoException runtimeExceptionToMsoException (RuntimeException e, String context) {
295 MsoAdapterException me = new MsoAdapterException (e.getMessage (), e);
296 me.addContext (context);
297 me.setCategory (MsoExceptionCategory.INTERNAL);
299 // Always generate an alarm for internal exceptions
300 logger.error("{} {} An exception occured on {}: ", MessageEnum.RA_GENERAL_EXCEPTION_ARG,
301 ErrorCode.DataError.getValue(), context, e);
306 protected MsoException ioExceptionToMsoException(IOException e, String context) {
307 MsoAdapterException me = new MsoAdapterException (e.getMessage (), e);
308 me.addContext (context);
309 me.setCategory (MsoExceptionCategory.INTERNAL);
311 // Always generate an alarm for internal exceptions
312 logger.error("{} {} An exception occured on {}: ", MessageEnum.RA_GENERAL_EXCEPTION_ARG,
313 ErrorCode.DataError.getValue(), context, e);
318 public boolean isNullOrEmpty (String s) {
319 return s == null || s.isEmpty();
323 protected CreateStackParam createStackParam(String stackName,
325 Map <String, ?> stackInputs,
328 Map <String, Object> files,
329 Map <String, Object> heatFiles) {
331 // Create local variables checking to see if we have an environment, nested, get_files
332 // Could later add some checks to see if it's valid.
333 boolean haveEnvtVariable = true;
334 if (environment == null || "".equalsIgnoreCase (environment.trim ())) {
335 haveEnvtVariable = false;
336 logger.debug ("createStackParam called with no environment variable");
338 logger.debug("createStackParam called with an environment variable: {}", environment);
341 boolean haveFiles = true;
342 if (files == null || files.isEmpty ()) {
344 logger.debug ("createStackParam called with no files / child template ids");
346 logger.debug("createStackParam called with {} files / child template ids", files.size());
349 boolean haveHeatFiles = true;
350 if (heatFiles == null || heatFiles.isEmpty ()) {
351 haveHeatFiles = false;
352 logger.debug ("createStackParam called with no heatFiles");
354 logger.debug("createStackParam called with {} heatFiles", heatFiles.size());
357 //force entire stackInput object to generic Map<String, Object> for openstack compatibility
358 ObjectMapper mapper = new ObjectMapper();
359 Map<String, Object> normalized = new HashMap<>();
361 normalized = mapper.readValue(mapper.writeValueAsString(stackInputs), new TypeReference<HashMap<String,Object>>() {});
362 } catch (IOException e1) {
363 logger.debug("could not map json", e1);
366 // Build up the stack to create
367 // Disable auto-rollback, because error reason is lost. Always rollback in the code.
368 CreateStackParam stack = new CreateStackParam ();
369 stack.setStackName (stackName);
370 stack.setTimeoutMinutes (timeoutMinutes);
371 stack.setParameters (normalized);
372 stack.setTemplate (heatTemplate);
373 stack.setDisableRollback (true);
374 // TJM New for PO Adapter - add envt variable
375 if (haveEnvtVariable) {
376 logger.debug("Found an environment variable - value: {}", environment);
377 stack.setEnvironment (environment);
379 // Now handle nested templates or get_files - have to combine if we have both
380 // as they're both treated as "files:" on the stack.
381 if (haveFiles && haveHeatFiles) {
382 // Let's do this here - not in the bean
383 logger.debug ("Found files AND heatFiles - combine and add!");
384 Map <String, Object> combinedFiles = new HashMap <> ();
385 for (Entry<String, Object> entry : files.entrySet()) {
386 combinedFiles.put(entry.getKey(), entry.getValue());
388 for (Entry<String, Object> entry : heatFiles.entrySet()) {
389 combinedFiles.put(entry.getKey(), entry.getValue());
391 stack.setFiles (combinedFiles);
393 // Handle if we only have one or neither:
395 logger.debug ("Found files - adding to stack");
396 stack.setFiles (files);
399 logger.debug ("Found heatFiles - adding to stack");
400 // the setFiles was modified to handle adding the entries
401 stack.setFiles (heatFiles);
405 // 1802 - attempt to add better formatted printout of request to openstack
407 Map<String, Object> inputs = new HashMap<>();
408 for (Entry<String, ?> entry : stackInputs.entrySet()) {
409 if (entry.getValue() != null) {
410 inputs.put(entry.getKey(), entry.getValue());
413 logger.debug("stack request: {}", stack.toString());
414 } catch (Exception e) {
415 // that's okay - this is a nice-to-have
416 logger.debug("(had an issue printing nicely formatted request to debuglog) {}", e.getMessage());