2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2018 Intel Corp. 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.onap.so.openstack.utils;
23 import java.net.MalformedURLException;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.HashMap;
27 import java.util.List;
29 import java.util.Scanner;
31 import javax.ws.rs.core.UriBuilder;
32 import javax.ws.rs.core.UriBuilderException;
33 import javax.ws.rs.core.Response.StatusType;
34 import javax.ws.rs.core.Response;
36 import org.apache.http.HttpStatus;
37 import org.onap.so.db.catalog.beans.CloudIdentity;
38 import org.onap.so.utils.CryptoUtils;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41 import org.onap.so.adapters.vdu.CloudInfo;
42 import org.onap.so.adapters.vdu.PluginAction;
43 import org.onap.so.adapters.vdu.VduArtifact;
44 import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
45 import org.onap.so.adapters.vdu.VduException;
46 import org.onap.so.adapters.vdu.VduInstance;
47 import org.onap.so.adapters.vdu.VduModelInfo;
48 import org.onap.so.adapters.vdu.VduPlugin;
49 import org.onap.so.adapters.vdu.VduStateType;
50 import org.onap.so.adapters.vdu.VduStatus;
51 import org.onap.so.openstack.beans.HeatStatus;
52 import org.onap.so.openstack.beans.StackInfo;
53 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
54 import org.onap.so.openstack.exceptions.MsoException;
55 import org.onap.so.openstack.exceptions.MsoOpenstackException;
56 import org.onap.so.openstack.mappers.StackInfoMapper;
57 import org.onap.so.client.HttpClient;
58 import org.onap.so.client.RestClient;
59 import org.onap.so.db.catalog.beans.CloudSite;
60 import org.onap.so.utils.TargetEntity;
61 import org.springframework.http.HttpEntity;
62 import org.springframework.http.HttpHeaders;
63 import org.springframework.http.MediaType;
64 import org.springframework.stereotype.Component;
66 import com.fasterxml.jackson.databind.ObjectMapper;
67 import com.woorea.openstack.heat.model.CreateStackParam;
68 import com.woorea.openstack.heat.model.Stack;
71 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
73 public static final String OOF_DIRECTIVES = "oof_directives";
74 public static final String SDNC_DIRECTIVES = "sdnc_directives";
75 public static final String GENERIC_VNF_ID = "generic_vnf_id";
76 public static final String VF_MODULE_ID = "vf_module_id";
77 public static final String TEMPLATE_TYPE = "template_type";
78 public static final List<String> MULTICLOUD_INPUTS =
79 Arrays.asList(OOF_DIRECTIVES, SDNC_DIRECTIVES, GENERIC_VNF_ID, VF_MODULE_ID, TEMPLATE_TYPE);
81 private static final String ONAP_IP = "ONAP_IP";
83 private static final String DEFAULT_MSB_IP = "127.0.0.1";
85 private static final Integer DEFAULT_MSB_PORT = 80;
87 private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
89 private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
91 /******************************************************************************
93 * Methods (and associated utilities) to implement the VduPlugin interface
95 *******************************************************************************/
98 * Create a new Stack in the specified cloud location and tenant. The Heat template
99 * and parameter map are passed in as arguments, along with the cloud access credentials.
100 * It is expected that parameters have been validated and contain at minimum the required
101 * parameters for the given template with no extra (undefined) parameters..
103 * The Stack name supplied by the caller must be unique in the scope of this tenant.
104 * However, it should also be globally unique, as it will be the identifier for the
105 * resource going forward in Inventory. This latter is managed by the higher levels
106 * invoking this function.
108 * The caller may choose to let this function poll Openstack for completion of the
109 * stack creation, or may handle polling itself via separate calls to query the status.
110 * In either case, a StackInfo object will be returned containing the current status.
111 * When polling is enabled, a status of CREATED is expected. When not polling, a
112 * status of BUILDING is expected.
114 * An error will be thrown if the requested Stack already exists in the specified
117 * For 1510 - add "environment", "files" (nested templates), and "heatFiles" (get_files) as
118 * parameters for createStack. If environment is non-null, it will be added to the stack.
119 * The nested templates and get_file entries both end up being added to the "files" on the
120 * stack. We must combine them before we add them to the stack if they're both non-null.
122 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
123 * @param tenantId The Openstack ID of the tenant in which to create the Stack
124 * @param stackName The name of the stack to create
125 * @param heatTemplate The Heat template
126 * @param stackInputs A map of key/value inputs
127 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
128 * @param environment An optional yaml-format string to specify environmental parameters
129 * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
131 * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
132 * @param backout Donot delete stack on create Failure - defaulted to True
133 * @return A StackInfo object
134 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
137 @SuppressWarnings("unchecked")
139 public StackInfo createStack (String cloudSiteId,
143 Map <String, ?> stackInputs,
144 boolean pollForCompletion,
147 Map <String, Object> files,
148 Map <String, Object> heatFiles,
149 boolean backout) throws MsoException {
151 logger.trace("Started MsoMulticloudUtils.createStack");
153 // Get the directives, if present.
154 String oofDirectives = "";
155 String sdncDirectives = "";
156 String genericVnfId = "";
157 String vfModuleId = "";
158 String templateType = "";
160 for (String key: MULTICLOUD_INPUTS) {
161 if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
162 if ( key == OOF_DIRECTIVES) {oofDirectives = (String) stackInputs.get(key);}
163 if ( key == SDNC_DIRECTIVES) {sdncDirectives = (String) stackInputs.get(key);}
164 if ( key == GENERIC_VNF_ID) {genericVnfId = (String) stackInputs.get(key);}
165 if ( key == VF_MODULE_ID) {vfModuleId = (String) stackInputs.get(key);}
166 if ( key == TEMPLATE_TYPE) {templateType = (String) stackInputs.get(key);}
167 if (logger.isDebugEnabled()) {
168 logger.debug(String.format("Found %s: %s", key, stackInputs.get(key)));
170 stackInputs.remove(key);
174 // create the multicloud payload
175 CreateStackParam stack = createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
177 MulticloudRequest multicloudRequest= new MulticloudRequest();
178 HttpEntity<MulticloudRequest> request = null;
181 multicloudRequest.setGenericVnfId(genericVnfId);
182 multicloudRequest.setVfModuleId(vfModuleId);
183 multicloudRequest.setOofDirectives(oofDirectives);
184 multicloudRequest.setSdncDirectives(sdncDirectives);
185 multicloudRequest.setTemplateType(templateType);
186 if (logger.isDebugEnabled()) {
187 logger.debug(String.format("Stack Template Data is: %s", stack.toString().substring(16)));
189 multicloudRequest.setTemplateData(JSON_MAPPER.writeValueAsString(stack));
190 if (logger.isDebugEnabled()) {
191 logger.debug(String.format("Multicloud Request is: %s", multicloudRequest.toString()));
194 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() ->
195 new MsoCloudSiteNotFound(cloudSiteId));
196 CloudIdentity cloudIdentity = cloudSite.getIdentityService();
197 HttpHeaders headers = new HttpHeaders();
198 headers.set ("X-Auth-User", cloudIdentity.getMsoId ());
199 headers.set ("X-Auth-Key", CryptoUtils.decryptCloudConfigPassword(cloudIdentity.getMsoPass ()));
200 headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON.toString());
201 headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString());
203 if (logger.isDebugEnabled()) {
204 logger.debug(String.format("Multicloud Request Headers: %s", headers.toString()));
206 request = new HttpEntity<>(multicloudRequest, headers);
207 } catch (Exception e) {
208 logger.debug("ERROR making multicloud JSON body ", e);
210 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, null);
211 if (logger.isDebugEnabled()) {
212 logger.debug(String.format("Multicloud Endpoint is: %s", multicloudEndpoint));
214 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
216 Response response = multicloudClient.post(request);
218 StackInfo responseStackInfo = new StackInfo();
219 responseStackInfo.setName(stackName);
220 responseStackInfo.setStatus(mapResponseToHeatStatus(response));
222 MulticloudCreateResponse multicloudResponseBody = null;
223 if (response.getStatus() == Response.Status.CREATED.getStatusCode() && response.hasEntity()) {
224 multicloudResponseBody = getCreateBody((java.io.InputStream)response.getEntity());
225 responseStackInfo.setCanonicalName(multicloudResponseBody.getWorkloadId());
226 if (logger.isDebugEnabled()) {
227 logger.debug("Multicloud Create Response Body: " + multicloudResponseBody);
231 return responseStackInfo;
235 public Map<String, Object> queryStackForOutputs(String cloudSiteId,
236 String tenantId, String stackName) throws MsoException {
237 logger.debug("MsoHeatUtils.queryStackForOutputs)");
238 StackInfo heatStack = this.queryStack(cloudSiteId, tenantId, stackName);
239 if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
242 return heatStack.getOutputs();
246 * Query for a single stack (by Name) in a tenant. This call will always return a
247 * StackInfo object. If the stack does not exist, an "empty" StackInfo will be
248 * returned - containing only the stack name and a status of NOTFOUND.
250 * @param tenantId The Openstack ID of the tenant in which to query
251 * @param cloudSiteId The cloud identifier (may be a region) in which to query
252 * @param stackName The name of the stack to query (may be simple or canonical)
253 * @return A StackInfo object
254 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
257 public StackInfo queryStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
258 if (logger.isDebugEnabled()) {
259 logger.debug (String.format("Query multicloud HEAT stack: %s in tenant %s", stackName, tenantId));
262 StackInfo returnInfo = new StackInfo();
263 returnInfo.setName(stackName);
265 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackName);
266 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
268 if (multicloudClient != null) {
269 Response response = multicloudClient.get();
270 if (logger.isDebugEnabled()) {
271 logger.debug (String.format("Mulicloud GET Response: %s", response.toString()));
274 returnInfo.setStatus(mapResponseToHeatStatus(response));
276 MulticloudQueryResponse multicloudQueryBody = null;
277 if (response.getStatus() == Response.Status.OK.getStatusCode() && response.hasEntity()) {
278 multicloudQueryBody = getQueryBody((java.io.InputStream)response.getEntity());
279 returnInfo.setCanonicalName(multicloudQueryBody.getWorkloadId());
280 if (logger.isDebugEnabled()) {
281 logger.debug("Multicloud Create Response Body: " + multicloudQueryBody.toString());
290 public StackInfo deleteStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
291 if (logger.isDebugEnabled()) {
292 logger.debug (String.format("Delete multicloud HEAT stack: %s in tenant %s", stackName, tenantId));
294 StackInfo returnInfo = new StackInfo();
295 returnInfo.setName(stackName);
296 Response response = null;
298 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackName);
299 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
301 if (multicloudClient != null) {
302 response = multicloudClient.delete();
303 if (logger.isDebugEnabled()) {
304 logger.debug(String.format("Multicloud Delete response is: %s", response.getEntity().toString()));
307 returnInfo.setStatus(mapResponseToHeatStatus(response));
311 // ---------------------------------------------------------------
312 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
313 private HeatStatus mapResponseToHeatStatus(Response response) {
314 if (response.getStatusInfo().getStatusCode() == Response.Status.OK.getStatusCode()) {
315 return HeatStatus.CREATED;
316 } else if (response.getStatusInfo().getStatusCode() == Response.Status.CREATED.getStatusCode()) {
317 return HeatStatus.CREATED;
318 } else if (response.getStatusInfo().getStatusCode() == Response.Status.NO_CONTENT.getStatusCode()) {
319 return HeatStatus.CREATED;
320 } else if (response.getStatusInfo().getStatusCode() == Response.Status.BAD_REQUEST.getStatusCode()) {
321 return HeatStatus.FAILED;
322 } else if (response.getStatusInfo().getStatusCode() == Response.Status.UNAUTHORIZED.getStatusCode()) {
323 return HeatStatus.FAILED;
324 } else if (response.getStatusInfo().getStatusCode() == Response.Status.NOT_FOUND.getStatusCode()) {
325 return HeatStatus.NOTFOUND;
326 } else if (response.getStatusInfo().getStatusCode() == Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()) {
327 return HeatStatus.FAILED;
329 return HeatStatus.UNKNOWN;
333 private MulticloudCreateResponse getCreateBody(java.io.InputStream in) {
334 Scanner scanner = new Scanner(in);
335 scanner.useDelimiter("\\Z");
337 if (scanner.hasNext()) {
338 body = scanner.next();
343 return new ObjectMapper().readerFor(MulticloudCreateResponse.class).readValue(body);
344 } catch (Exception e) {
345 logger.debug("Exception retrieving multicloud vfModule POST response body " + e);
350 private MulticloudQueryResponse getQueryBody(java.io.InputStream in) {
351 Scanner scanner = new Scanner(in);
352 scanner.useDelimiter("\\Z");
354 if (scanner.hasNext()) {
355 body = scanner.next();
360 return new ObjectMapper().readerFor(MulticloudQueryResponse.class).readValue(body);
361 } catch (Exception e) {
362 logger.debug("Exception retrieving multicloud workload query response body " + e);
367 private String getMulticloudEndpoint(String cloudSiteId, String workloadId) throws MsoCloudSiteNotFound {
369 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
370 String endpoint = cloudSite.getIdentityService().getIdentityUrl();
372 if (workloadId != null) {
373 if (logger.isDebugEnabled()) {
374 logger.debug(String.format("Multicloud Endpoint is: %s/%s", endpoint, workloadId));
376 return String.format("%s/%s", endpoint, workloadId);
378 if (logger.isDebugEnabled()) {
379 logger.debug(String.format("Multicloud Endpoint is: %s", endpoint));
385 private RestClient getMulticloudClient(String endpoint) {
386 RestClient client = null;
388 client= new HttpClient(UriBuilder.fromUri(endpoint).build().toURL(),
389 MediaType.APPLICATION_JSON.toString(), TargetEntity.MULTICLOUD);
390 } catch (MalformedURLException e) {
391 logger.debug(String.format("Encountered malformed URL error getting multicloud rest client %s", e.getMessage()));
392 } catch (IllegalArgumentException e) {
393 logger.debug(String.format("Encountered illegal argument getting multicloud rest client %s",e.getMessage()));
394 } catch (UriBuilderException e) {
395 logger.debug(String.format("Encountered URI builder error getting multicloud rest client %s", e.getMessage()));
401 * VduPlugin interface for instantiate function.
403 * Translate the VduPlugin parameters to the corresponding 'createStack' parameters,
404 * and then invoke the existing function.
407 public VduInstance instantiateVdu (
410 Map<String,Object> inputs,
411 VduModelInfo vduModel,
412 boolean rollbackOnFailure)
415 String cloudSiteId = cloudInfo.getCloudSiteId();
416 String tenantId = cloudInfo.getTenantId();
418 // Translate the VDU ModelInformation structure to that which is needed for
419 // creating the Heat stack. Loop through the artifacts, looking specifically
420 // for MAIN_TEMPLATE and ENVIRONMENT. Any other artifact will
421 // be attached as a FILE.
422 String heatTemplate = null;
423 Map<String,Object> nestedTemplates = new HashMap<>();
424 Map<String,Object> files = new HashMap<>();
425 String heatEnvironment = null;
427 for (VduArtifact vduArtifact: vduModel.getArtifacts()) {
428 if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
429 heatTemplate = new String(vduArtifact.getContent());
431 else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
432 nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
434 else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
435 heatEnvironment = new String(vduArtifact.getContent());
440 StackInfo stackInfo = createStack (cloudSiteId,
445 true, // poll for completion
446 vduModel.getTimeoutMinutes(),
451 // Populate a vduInstance from the StackInfo
452 return stackInfoToVduInstance(stackInfo);
454 catch (Exception e) {
455 throw new VduException ("MsoMulticloudUtils (instantiateVDU): createStack Exception", e);
461 * VduPlugin interface for query function.
464 public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
467 String cloudSiteId = cloudInfo.getCloudSiteId();
468 String tenantId = cloudInfo.getTenantId();
471 // Query the Cloudify Deployment object and populate a VduInstance
472 StackInfo stackInfo = queryStack (cloudSiteId, tenantId, instanceId);
474 return stackInfoToVduInstance(stackInfo);
476 catch (Exception e) {
477 throw new VduException ("MsoMulticloudUtils (queryVdu): queryStack Exception ", e);
483 * VduPlugin interface for delete function.
486 public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
489 String cloudSiteId = cloudInfo.getCloudSiteId();
490 String tenantId = cloudInfo.getTenantId();
493 // Delete the Multicloud stack
494 StackInfo stackInfo = deleteStack (tenantId, cloudSiteId, instanceId);
496 // Populate a VduInstance based on the deleted Cloudify Deployment object
497 VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
499 // Override return state to DELETED (MulticloudUtils sets to NOTFOUND)
500 vduInstance.getStatus().setState(VduStateType.DELETED);
504 catch (Exception e) {
505 throw new VduException ("Delete VDU Exception", e);
511 * VduPlugin interface for update function.
513 * Update is currently not supported in the MsoMulticloudUtils implementation of VduPlugin.
514 * Just return a VduException.
518 public VduInstance updateVdu (
521 Map<String,Object> inputs,
522 VduModelInfo vduModel,
523 boolean rollbackOnFailure)
526 throw new VduException ("MsoMulticloudUtils: updateVdu interface not supported");
531 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
533 protected VduInstance stackInfoToVduInstance (StackInfo stackInfo)
535 VduInstance vduInstance = new VduInstance();
537 if (logger.isDebugEnabled()) {
538 logger.debug(String.format("StackInfo to convert: %s", stackInfo.getParameters().toString()));
540 // The full canonical name as the instance UUID
541 vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
542 vduInstance.setVduInstanceName(stackInfo.getName());
544 // Copy inputs and outputs
545 vduInstance.setInputs(stackInfo.getParameters());
546 vduInstance.setOutputs(stackInfo.getOutputs());
548 // Translate the status elements
549 vduInstance.setStatus(stackStatusToVduStatus (stackInfo));
554 private VduStatus stackStatusToVduStatus (StackInfo stackInfo)
556 VduStatus vduStatus = new VduStatus();
558 // Map the status fields to more generic VduStatus.
559 // There are lots of HeatStatus values, so this is a bit long...
560 HeatStatus heatStatus = stackInfo.getStatus();
561 String statusMessage = stackInfo.getStatusMessage();
562 logger.debug("HeatStatus = " + heatStatus + " msg = " + statusMessage);
564 if (logger.isDebugEnabled()) {
565 logger.debug(String.format("Stack Status: %s", heatStatus.toString()));
566 logger.debug(String.format("Stack Status Message: %s", statusMessage));
569 if (heatStatus == HeatStatus.INIT || heatStatus == HeatStatus.BUILDING) {
570 vduStatus.setState(VduStateType.INSTANTIATING);
571 vduStatus.setLastAction((new PluginAction ("create", "in_progress", statusMessage)));
573 else if (heatStatus == HeatStatus.NOTFOUND) {
574 vduStatus.setState(VduStateType.NOTFOUND);
576 else if (heatStatus == HeatStatus.CREATED) {
577 vduStatus.setState(VduStateType.INSTANTIATED);
578 vduStatus.setLastAction((new PluginAction ("create", "complete", statusMessage)));
580 else if (heatStatus == HeatStatus.UPDATED) {
581 vduStatus.setState(VduStateType.INSTANTIATED);
582 vduStatus.setLastAction((new PluginAction ("update", "complete", statusMessage)));
584 else if (heatStatus == HeatStatus.UPDATING) {
585 vduStatus.setState(VduStateType.UPDATING);
586 vduStatus.setLastAction((new PluginAction ("update", "in_progress", statusMessage)));
588 else if (heatStatus == HeatStatus.DELETING) {
589 vduStatus.setState(VduStateType.DELETING);
590 vduStatus.setLastAction((new PluginAction ("delete", "in_progress", statusMessage)));
592 else if (heatStatus == HeatStatus.FAILED) {
593 vduStatus.setState(VduStateType.FAILED);
594 vduStatus.setErrorMessage(stackInfo.getStatusMessage());
596 vduStatus.setState(VduStateType.UNKNOWN);