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.HashMap;
27 import javax.ws.rs.core.UriBuilder;
28 import javax.ws.rs.core.UriBuilderException;
29 import javax.ws.rs.core.Response;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 import org.onap.so.adapters.vdu.CloudInfo;
34 import org.onap.so.adapters.vdu.PluginAction;
35 import org.onap.so.adapters.vdu.VduArtifact;
36 import org.onap.so.adapters.vdu.VduArtifact.ArtifactType;
37 import org.onap.so.adapters.vdu.VduException;
38 import org.onap.so.adapters.vdu.VduInstance;
39 import org.onap.so.adapters.vdu.VduModelInfo;
40 import org.onap.so.adapters.vdu.VduPlugin;
41 import org.onap.so.adapters.vdu.VduStateType;
42 import org.onap.so.adapters.vdu.VduStatus;
43 import org.onap.so.openstack.beans.HeatStatus;
44 import org.onap.so.openstack.beans.StackInfo;
45 import org.onap.so.openstack.exceptions.MsoCloudSiteNotFound;
46 import org.onap.so.openstack.exceptions.MsoException;
47 import org.onap.so.openstack.exceptions.MsoOpenstackException;
48 import org.onap.so.openstack.mappers.StackInfoMapper;
49 import org.onap.so.client.HttpClient;
50 import org.onap.so.client.RestClient;
51 import org.onap.so.cloud.CloudConfig;
52 import org.onap.so.db.catalog.beans.CloudSite;
53 import org.onap.so.utils.TargetEntity;
54 import org.springframework.beans.factory.annotation.Autowired;
55 import org.springframework.core.env.Environment;
56 import org.springframework.stereotype.Component;
58 import com.woorea.openstack.heat.model.CreateStackParam;
59 import com.woorea.openstack.heat.model.Stack;
62 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
65 protected CloudConfig cloudConfig;
68 private Environment env;
70 private static final String ONAP_IP = "ONAP_IP";
72 private static final String DEFAULT_MSB_IP = "127.0.0.1";
74 private static final Integer DEFAULT_MSB_PORT = 80;
76 private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
78 /******************************************************************************
80 * Methods (and associated utilities) to implement the VduPlugin interface
82 *******************************************************************************/
85 * Create a new Stack in the specified cloud location and tenant. The Heat template
86 * and parameter map are passed in as arguments, along with the cloud access credentials.
87 * It is expected that parameters have been validated and contain at minimum the required
88 * parameters for the given template with no extra (undefined) parameters..
90 * The Stack name supplied by the caller must be unique in the scope of this tenant.
91 * However, it should also be globally unique, as it will be the identifier for the
92 * resource going forward in Inventory. This latter is managed by the higher levels
93 * invoking this function.
95 * The caller may choose to let this function poll Openstack for completion of the
96 * stack creation, or may handle polling itself via separate calls to query the status.
97 * In either case, a StackInfo object will be returned containing the current status.
98 * When polling is enabled, a status of CREATED is expected. When not polling, a
99 * status of BUILDING is expected.
101 * An error will be thrown if the requested Stack already exists in the specified
104 * For 1510 - add "environment", "files" (nested templates), and "heatFiles" (get_files) as
105 * parameters for createStack. If environment is non-null, it will be added to the stack.
106 * The nested templates and get_file entries both end up being added to the "files" on the
107 * stack. We must combine them before we add them to the stack if they're both non-null.
109 * @param cloudSiteId The cloud (may be a region) in which to create the stack.
110 * @param tenantId The Openstack ID of the tenant in which to create the Stack
111 * @param stackName The name of the stack to create
112 * @param heatTemplate The Heat template
113 * @param stackInputs A map of key/value inputs
114 * @param pollForCompletion Indicator that polling should be handled in Java vs. in the client
115 * @param environment An optional yaml-format string to specify environmental parameters
116 * @param files a Map<String, Object> that lists the child template IDs (file is the string, object is an int of
118 * @param heatFiles a Map<String, Object> that lists the get_file entries (fileName, fileBody)
119 * @param backout Donot delete stack on create Failure - defaulted to True
120 * @return A StackInfo object
121 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
124 @SuppressWarnings("unchecked")
126 public StackInfo createStack (String cloudSiteId,
130 Map <String, ?> stackInputs,
131 boolean pollForCompletion,
134 Map <String, Object> files,
135 Map <String, Object> heatFiles,
136 boolean backout) throws MsoException {
138 // Get the directives, if present.
139 String oofDirectives = null;
140 String sdncDirectives = null;
141 String genericVnfId = null;
142 String vfModuleId = null;
144 String key = "oof_directives";
145 if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
146 oofDirectives = (String) stackInputs.get(key);
147 stackInputs.remove(key);
149 key = "sdnc_directives";
150 if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
151 sdncDirectives = (String) stackInputs.get(key);
152 stackInputs.remove(key);
154 key = "generic_vnf_id";
155 if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
156 genericVnfId = (String) stackInputs.get(key);
157 stackInputs.remove(key);
159 key = "vf_module_id";
160 if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
161 vfModuleId = (String) stackInputs.get(key);
162 stackInputs.remove(key);
165 // create the multicloud payload
166 CreateStackParam stack = createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
168 MsoMulticloudParam multicloudParam = new MsoMulticloudParam();
169 multicloudParam.setGenericVnfId(genericVnfId);
170 multicloudParam.setVfModuleId(vfModuleId);
171 multicloudParam.setOofDirectives(oofDirectives);
172 multicloudParam.setSdncDirectives(sdncDirectives);
173 multicloudParam.setTemplateType("heat");
174 multicloudParam.setTemplateData(stack.toString());
177 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, null);
178 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
180 if (multicloudClient != null) {
181 Response res = multicloudClient.post(multicloudParam);
182 logger.debug("Multicloud Post response is: " + res);
185 Stack responseStack = new Stack();
186 responseStack.setStackStatus(HeatStatus.CREATED.toString());
188 return new StackInfoMapper(responseStack).map();
191 public Map<String, Object> queryStackForOutputs(String cloudSiteId,
192 String tenantId, String stackName) throws MsoException {
193 logger.debug("MsoHeatUtils.queryStackForOutputs)");
194 StackInfo heatStack = this.queryStack(cloudSiteId, tenantId, stackName);
195 if (heatStack == null || heatStack.getStatus() == HeatStatus.NOTFOUND) {
198 return heatStack.getOutputs();
202 * Query for a single stack (by Name) in a tenant. This call will always return a
203 * StackInfo object. If the stack does not exist, an "empty" StackInfo will be
204 * returned - containing only the stack name and a status of NOTFOUND.
206 * @param tenantId The Openstack ID of the tenant in which to query
207 * @param cloudSiteId The cloud identifier (may be a region) in which to query
208 * @param stackName The name of the stack to query (may be simple or canonical)
209 * @return A StackInfo object
210 * @throws MsoOpenstackException Thrown if the Openstack API call returns an exception.
213 public StackInfo queryStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
214 logger.debug ("Query multicloud HEAT stack: " + stackName + " in tenant " + tenantId);
216 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackName);
218 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
220 if (multicloudClient != null) {
221 Response response = multicloudClient.get();
222 logger.debug("Multicloud Get response is: " + response);
224 return new StackInfo (stackName, HeatStatus.CREATED);
227 return new StackInfo (stackName, HeatStatus.NOTFOUND);
230 public StackInfo deleteStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
231 logger.debug ("Delete multicloud HEAT stack: " + stackName + " in tenant " + tenantId);
233 String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackName);
235 RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
237 if (multicloudClient != null) {
238 Response response = multicloudClient.delete();
239 logger.debug("Multicloud Get response is: " + response);
241 return new StackInfo (stackName, HeatStatus.DELETING);
244 return new StackInfo (stackName, HeatStatus.FAILED);
247 // ---------------------------------------------------------------
248 // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
251 private String getMsbHost() {
252 // MSB_IP will be set as ONAP_IP environment parameter in install flow.
253 String msbIp = System.getenv().get(ONAP_IP);
255 // if ONAP IP is not set. get it from config file.
256 if (null == msbIp || msbIp.isEmpty()) {
257 msbIp = env.getProperty("mso.msb-ip", DEFAULT_MSB_IP);
259 Integer msbPort = env.getProperty("mso.msb-port", Integer.class, DEFAULT_MSB_PORT);
261 return UriBuilder.fromPath("").host(msbIp).port(msbPort).scheme("http").build().toString();
264 private String getMulticloudEndpoint(String cloudSiteId, String workloadId) throws MsoCloudSiteNotFound {
266 CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
267 String endpoint = getMsbHost() + cloudSite.getIdentityService().getIdentityUrl();
269 if (workloadId != null) {
270 return endpoint + workloadId;
276 private RestClient getMulticloudClient(String endpoint) {
277 RestClient client = null;
279 client= new HttpClient(UriBuilder.fromUri(endpoint).build().toURL(),
280 "application/json", TargetEntity.OPENSTACK_ADAPTER);
281 } catch (MalformedURLException e) {
282 logger.debug("Encountered malformed URL error getting multicloud rest client " + e.getMessage());
283 } catch (IllegalArgumentException e) {
284 logger.debug("Encountered illegal argument getting multicloud rest client " + e.getMessage());
285 } catch (UriBuilderException e) {
286 logger.debug("Encountered URI builder error getting multicloud rest client " + e.getMessage());
292 * VduPlugin interface for instantiate function.
294 * Translate the VduPlugin parameters to the corresponding 'createStack' parameters,
295 * and then invoke the existing function.
298 public VduInstance instantiateVdu (
301 Map<String,Object> inputs,
302 VduModelInfo vduModel,
303 boolean rollbackOnFailure)
306 String cloudSiteId = cloudInfo.getCloudSiteId();
307 String tenantId = cloudInfo.getTenantId();
309 // Translate the VDU ModelInformation structure to that which is needed for
310 // creating the Heat stack. Loop through the artifacts, looking specifically
311 // for MAIN_TEMPLATE and ENVIRONMENT. Any other artifact will
312 // be attached as a FILE.
313 String heatTemplate = null;
314 Map<String,Object> nestedTemplates = new HashMap<>();
315 Map<String,Object> files = new HashMap<>();
316 String heatEnvironment = null;
318 for (VduArtifact vduArtifact: vduModel.getArtifacts()) {
319 if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
320 heatTemplate = new String(vduArtifact.getContent());
322 else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
323 nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
325 else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
326 heatEnvironment = new String(vduArtifact.getContent());
331 StackInfo stackInfo = createStack (cloudSiteId,
336 true, // poll for completion
337 vduModel.getTimeoutMinutes(),
342 // Populate a vduInstance from the StackInfo
343 return stackInfoToVduInstance(stackInfo);
345 catch (Exception e) {
346 throw new VduException ("MsoMulticloudUtils (instantiateVDU): createStack Exception", e);
352 * VduPlugin interface for query function.
355 public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
358 String cloudSiteId = cloudInfo.getCloudSiteId();
359 String tenantId = cloudInfo.getTenantId();
362 // Query the Cloudify Deployment object and populate a VduInstance
363 StackInfo stackInfo = queryStack (cloudSiteId, tenantId, instanceId);
365 return stackInfoToVduInstance(stackInfo);
367 catch (Exception e) {
368 throw new VduException ("MsoMulticloudUtils (queryVdu): queryStack Exception ", e);
374 * VduPlugin interface for delete function.
377 public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
380 String cloudSiteId = cloudInfo.getCloudSiteId();
381 String tenantId = cloudInfo.getTenantId();
384 // Delete the Multicloud stack
385 StackInfo stackInfo = deleteStack (tenantId, cloudSiteId, instanceId, true);
387 // Populate a VduInstance based on the deleted Cloudify Deployment object
388 VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
390 // Override return state to DELETED (MulticloudUtils sets to NOTFOUND)
391 vduInstance.getStatus().setState(VduStateType.DELETED);
395 catch (Exception e) {
396 throw new VduException ("Delete VDU Exception", e);
402 * VduPlugin interface for update function.
404 * Update is currently not supported in the MsoMulticloudUtils implementation of VduPlugin.
405 * Just return a VduException.
409 public VduInstance updateVdu (
412 Map<String,Object> inputs,
413 VduModelInfo vduModel,
414 boolean rollbackOnFailure)
417 throw new VduException ("MsoMulticloudUtils: updateVdu interface not supported");
422 * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
424 protected VduInstance stackInfoToVduInstance (StackInfo stackInfo)
426 VduInstance vduInstance = new VduInstance();
428 // The full canonical name as the instance UUID
429 vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
430 vduInstance.setVduInstanceName(stackInfo.getName());
432 // Copy inputs and outputs
433 vduInstance.setInputs(stackInfo.getParameters());
434 vduInstance.setOutputs(stackInfo.getOutputs());
436 // Translate the status elements
437 vduInstance.setStatus(stackStatusToVduStatus (stackInfo));
442 private VduStatus stackStatusToVduStatus (StackInfo stackInfo)
444 VduStatus vduStatus = new VduStatus();
446 // Map the status fields to more generic VduStatus.
447 // There are lots of HeatStatus values, so this is a bit long...
448 HeatStatus heatStatus = stackInfo.getStatus();
449 String statusMessage = stackInfo.getStatusMessage();
451 if (heatStatus == HeatStatus.INIT || heatStatus == HeatStatus.BUILDING) {
452 vduStatus.setState(VduStateType.INSTANTIATING);
453 vduStatus.setLastAction((new PluginAction ("create", "in_progress", statusMessage)));
455 else if (heatStatus == HeatStatus.NOTFOUND) {
456 vduStatus.setState(VduStateType.NOTFOUND);
458 else if (heatStatus == HeatStatus.CREATED) {
459 vduStatus.setState(VduStateType.INSTANTIATED);
460 vduStatus.setLastAction((new PluginAction ("create", "complete", statusMessage)));
462 else if (heatStatus == HeatStatus.UPDATED) {
463 vduStatus.setState(VduStateType.INSTANTIATED);
464 vduStatus.setLastAction((new PluginAction ("update", "complete", statusMessage)));
466 else if (heatStatus == HeatStatus.UPDATING) {
467 vduStatus.setState(VduStateType.UPDATING);
468 vduStatus.setLastAction((new PluginAction ("update", "in_progress", statusMessage)));
470 else if (heatStatus == HeatStatus.DELETING) {
471 vduStatus.setState(VduStateType.DELETING);
472 vduStatus.setLastAction((new PluginAction ("delete", "in_progress", statusMessage)));
474 else if (heatStatus == HeatStatus.FAILED) {
475 vduStatus.setState(VduStateType.FAILED);
476 vduStatus.setErrorMessage(stackInfo.getStatusMessage());
478 vduStatus.setState(VduStateType.UNKNOWN);