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. For the metrics log, a category of
64 * "Openstack" is used along with a sub-category that identifies the specific call (using the real
65 * openstack-java-sdk classname of the OpenStackRequest<T> parameter).
68 protected <T> T executeAndRecordOpenstackRequest(OpenStackRequest<T> request) {
72 long start = System.currentTimeMillis();
74 if (request.getClass().getEnclosingClass() != null) {
76 request.getClass().getEnclosingClass().getSimpleName() + "." + request.getClass().getSimpleName();
78 requestType = request.getClass().getSimpleName();
81 int retryDelay = poConfig.getRetryDelay();
82 int retryCount = poConfig.getRetryCount();
83 String retryCodes = poConfig.getRetryCodes();
85 // Run the actual command. All exceptions will be propagated
88 return request.execute();
89 } catch (OpenStackResponseException e) {
90 boolean retry = false;
91 if (retryCodes != null) {
92 int code = e.getStatus();
93 logger.debug("Config values RetryDelay:{} RetryCount:{} RetryCodes:{} ResponseCode:{}", retryDelay,
94 retryCount, retryCodes, code);
95 for (String rCode : retryCodes.split(",")) {
97 if (retryCount > 0 && code == Integer.parseInt(rCode)) {
101 "OpenStackResponseException ResponseCode: {} request:{} Retry indicated. Attempts remaining:{}",
102 code, requestType, retryCount);
105 } catch (NumberFormatException e1) {
106 logger.error("{} No retries. Exception in parsing retry code in config:{} {} {}",
107 MessageEnum.RA_CONFIG_EXC, rCode, ErrorCode.SchemaError.getValue(),
108 "Exception in parsing retry code in config");
115 Thread.sleep(retryDelay * 1000L);
116 } catch (InterruptedException e1) {
117 logger.debug("Thread interrupted while sleeping", e1);
118 Thread.currentThread().interrupt();
121 throw e; // exceeded retryCount or code is not retryable
122 } catch (OpenStackConnectException e) {
123 // Connection to Openstack failed
124 if (retryCount > 0) {
126 logger.debug(" request: {} Retry indicated. Attempts remaining:{}", requestType, retryCount);
128 Thread.sleep(retryDelay * 1000L);
129 } catch (InterruptedException e1) {
130 logger.debug("Thread interrupted while sleeping", e1);
131 Thread.currentThread().interrupt();
141 * Convert an Openstack Exception on a Keystone call to an MsoException. This method supports both
142 * OpenstackResponseException and OpenStackConnectException.
144 protected MsoException keystoneErrorToMsoException(OpenStackBaseException e, String context) {
145 MsoException me = null;
147 if (e instanceof OpenStackResponseException) {
148 OpenStackResponseException re = (OpenStackResponseException) e;
151 // Failed Keystone calls return an Error entity body.
152 Error error = re.getResponse().getErrorEntity(Error.class);
153 logger.error("{} {} Openstack Keystone Error on {}: {}", MessageEnum.RA_CONNECTION_EXCEPTION,
154 ErrorCode.DataError.getValue(), context, error);
155 me = new MsoOpenstackException(error.getCode(), error.getTitle(), error.getMessage());
156 } catch (Exception e2) {
157 // Can't parse the body as an "Error". Report the HTTP error
158 logger.error("{} {} HTTP Error on {}: {}, {}", MessageEnum.RA_CONNECTION_EXCEPTION,
159 ErrorCode.DataError.getValue(), context, re.getStatus(), re.getMessage(), e2);
160 me = new MsoOpenstackException(re.getStatus(), re.getMessage(), "");
163 // Add the context of the error
164 me.addContext(context);
166 // Generate an alarm for 5XX and higher errors.
167 if (re.getStatus() >= 500) {
170 } else if (e instanceof OpenStackConnectException) {
171 OpenStackConnectException ce = (OpenStackConnectException) e;
173 me = new MsoIOException(ce.getMessage());
174 me.addContext(context);
176 // Generate an alarm for all connection errors.
177 logger.error("{} {} Openstack Keystone connection error on {}: ", MessageEnum.RA_GENERAL_EXCEPTION_ARG,
178 ErrorCode.DataError.getValue(), context, e);
185 * Convert an Openstack Exception on a Heat call to an MsoOpenstackException. This method supports both
186 * OpenstackResponseException and OpenStackConnectException.
188 protected MsoException heatExceptionToMsoException(OpenStackBaseException e, String context) {
189 MsoException me = null;
191 if (e instanceof OpenStackResponseException) {
192 OpenStackResponseException re = (OpenStackResponseException) e;
195 // Failed Heat calls return an Explanation entity body.
196 Explanation explanation = re.getResponse().getErrorEntity(Explanation.class);
197 logger.error("{} {} Exception - Openstack Error on {} : {}", MessageEnum.RA_CONNECTION_EXCEPTION,
198 ErrorCode.DataError.getValue(), context, explanation.toString());
199 String fullError = explanation.getExplanation() + ", error.type=" + explanation.getError().getType()
200 + ", error.message=" + explanation.getError().getMessage();
201 logger.debug(fullError);
202 me = new MsoOpenstackException(explanation.getCode(), explanation.getTitle(),
203 // explanation.getExplanation ());
205 } catch (Exception e2) {
206 // Couldn't parse the body as an "Explanation". Report the original HTTP error.
207 logger.error("{} {} Exception - HTTP Error on {}: {}, ", MessageEnum.RA_CONNECTION_EXCEPTION,
208 ErrorCode.DataError.getValue(), context, re.getStatus(), e.getMessage(), e2);
209 me = new MsoOpenstackException(re.getStatus(), re.getMessage(), "");
212 // Add the context of the error
213 me.addContext(context);
215 // Generate an alarm for 5XX and higher errors.
216 if (re.getStatus() >= 500) {
219 } else if (e instanceof OpenStackConnectException) {
220 OpenStackConnectException ce = (OpenStackConnectException) e;
222 me = new MsoIOException(ce.getMessage());
223 me.addContext(context);
225 // Generate an alarm for all connection errors.
227 logger.error("{} {} Openstack Heat connection error on {}: ", MessageEnum.RA_CONNECTION_EXCEPTION,
228 ErrorCode.DataError.getValue(), context, e);
235 * Convert an Openstack Exception on a Neutron call to an MsoOpenstackException. This method supports both
236 * OpenstackResponseException and OpenStackConnectException.
238 protected MsoException neutronExceptionToMsoException(OpenStackBaseException e, String context) {
239 MsoException me = null;
241 if (e instanceof OpenStackResponseException) {
242 OpenStackResponseException re = (OpenStackResponseException) e;
245 // Failed Neutron calls return an NeutronError entity body
246 NeutronError error = re.getResponse().getErrorEntity(NeutronError.class);
247 logger.error("{} {} Openstack Neutron Error on {} {}", MessageEnum.RA_CONNECTION_EXCEPTION,
248 ErrorCode.DataError.getValue(), context, error);
249 me = new MsoOpenstackException(re.getStatus(), error.getType(), error.getMessage());
250 } catch (Exception e2) {
251 // Couldn't parse body as a NeutronError. Report the HTTP error.
252 logger.error("{} {} Openstack HTTP Error on {}: {}, {}", MessageEnum.RA_CONNECTION_EXCEPTION,
253 ErrorCode.DataError.getValue(), context, re.getStatus(), e.getMessage(), e2);
254 me = new MsoOpenstackException(re.getStatus(), re.getMessage(), null);
257 // Add the context of the error
258 me.addContext(context);
260 // Generate an alarm for 5XX and higher errors.
261 if (re.getStatus() >= 500) {
264 } else if (e instanceof OpenStackConnectException) {
265 OpenStackConnectException ce = (OpenStackConnectException) e;
267 me = new MsoIOException(ce.getMessage());
268 me.addContext(context);
270 // Generate an alarm for all connection errors.
272 logger.error("{} {} Openstack Neutron Connection error on {}: ", MessageEnum.RA_CONNECTION_EXCEPTION,
273 ErrorCode.DataError.getValue(), context, e);
280 * Convert a Java Runtime Exception to an MsoException. All Runtime exceptions will be translated into an
281 * MsoAdapterException, which captures internal errors. Alarms will be generated on all such exceptions.
283 protected MsoException runtimeExceptionToMsoException(RuntimeException e, String context) {
284 MsoAdapterException me = new MsoAdapterException(e.getMessage(), e);
285 me.addContext(context);
286 me.setCategory(MsoExceptionCategory.INTERNAL);
288 // Always generate an alarm for internal exceptions
289 logger.error("{} {} An exception occured on {}: ", MessageEnum.RA_GENERAL_EXCEPTION_ARG,
290 ErrorCode.DataError.getValue(), context, e);
295 protected MsoException ioExceptionToMsoException(IOException e, String context) {
296 MsoAdapterException me = new MsoAdapterException(e.getMessage(), e);
297 me.addContext(context);
298 me.setCategory(MsoExceptionCategory.INTERNAL);
300 // Always generate an alarm for internal exceptions
301 logger.error("{} {} An exception occured on {}: ", MessageEnum.RA_GENERAL_EXCEPTION_ARG,
302 ErrorCode.DataError.getValue(), context, e);
307 public boolean isNullOrEmpty(String s) {
308 return s == null || s.isEmpty();
312 protected CreateStackParam createStackParam(String stackName, String heatTemplate, Map<String, ?> stackInputs,
313 int timeoutMinutes, String environment, Map<String, Object> files, Map<String, Object> heatFiles) {
315 // Create local variables checking to see if we have an environment, nested, get_files
316 // Could later add some checks to see if it's valid.
317 boolean haveEnvtVariable = true;
318 if (environment == null || "".equalsIgnoreCase(environment.trim())) {
319 haveEnvtVariable = false;
320 logger.debug("createStackParam called with no environment variable");
322 logger.debug("createStackParam called with an environment variable: {}", environment);
325 boolean haveFiles = true;
326 if (files == null || files.isEmpty()) {
328 logger.debug("createStackParam called with no files / child template ids");
330 logger.debug("createStackParam called with {} files / child template ids", files.size());
333 boolean haveHeatFiles = true;
334 if (heatFiles == null || heatFiles.isEmpty()) {
335 haveHeatFiles = false;
336 logger.debug("createStackParam called with no heatFiles");
338 logger.debug("createStackParam called with {} heatFiles", heatFiles.size());
341 // force entire stackInput object to generic Map<String, Object> for openstack compatibility
342 ObjectMapper mapper = new ObjectMapper();
343 Map<String, Object> normalized = new HashMap<>();
345 normalized = mapper.readValue(mapper.writeValueAsString(stackInputs),
346 new TypeReference<HashMap<String, Object>>() {});
347 } catch (IOException e1) {
348 logger.debug("could not map json", e1);
351 // Build up the stack to create
352 // Disable auto-rollback, because error reason is lost. Always rollback in the code.
353 CreateStackParam stack = new CreateStackParam();
354 stack.setStackName(stackName);
355 stack.setTimeoutMinutes(timeoutMinutes);
356 stack.setParameters(normalized);
357 stack.setTemplate(heatTemplate);
358 stack.setDisableRollback(true);
359 // TJM New for PO Adapter - add envt variable
360 if (haveEnvtVariable) {
361 logger.debug("Found an environment variable - value: {}", environment);
362 stack.setEnvironment(environment);
364 // Now handle nested templates or get_files - have to combine if we have both
365 // as they're both treated as "files:" on the stack.
366 if (haveFiles && haveHeatFiles) {
367 // Let's do this here - not in the bean
368 logger.debug("Found files AND heatFiles - combine and add!");
369 Map<String, Object> combinedFiles = new HashMap<>();
370 for (Entry<String, Object> entry : files.entrySet()) {
371 combinedFiles.put(entry.getKey(), entry.getValue());
373 for (Entry<String, Object> entry : heatFiles.entrySet()) {
374 combinedFiles.put(entry.getKey(), entry.getValue());
376 stack.setFiles(combinedFiles);
378 // Handle if we only have one or neither:
380 logger.debug("Found files - adding to stack");
381 stack.setFiles(files);
384 logger.debug("Found heatFiles - adding to stack");
385 // the setFiles was modified to handle adding the entries
386 stack.setFiles(heatFiles);
390 // 1802 - attempt to add better formatted printout of request to openstack
392 Map<String, Object> inputs = new HashMap<>();
393 for (Entry<String, ?> entry : stackInputs.entrySet()) {
394 if (entry.getValue() != null) {
395 inputs.put(entry.getKey(), entry.getValue());
398 logger.debug("stack request: {}", stack.toString());
399 } catch (Exception e) {
400 // that's okay - this is a nice-to-have
401 logger.debug("(had an issue printing nicely formatted request to debuglog) {}", e.getMessage());