Merge "added test case to ResponseHandlerTest.java"
[so.git] / adapters / mso-adapter-utils / src / main / java / org / onap / so / openstack / utils / MsoMulticloudUtils.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
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
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.onap.so.openstack.utils;
22
23 import java.net.MalformedURLException;
24 import java.util.HashMap;
25 import java.util.Map;
26
27 import javax.ws.rs.core.UriBuilder;
28 import javax.ws.rs.core.UriBuilderException;
29 import javax.ws.rs.core.Response;
30
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;
57
58 import com.woorea.openstack.heat.model.CreateStackParam;
59 import com.woorea.openstack.heat.model.Stack;
60
61 @Component
62 public class MsoMulticloudUtils extends MsoHeatUtils implements VduPlugin{
63
64     @Autowired
65     protected CloudConfig cloudConfig;
66
67     @Autowired
68     private Environment env;
69
70     private static final String ONAP_IP = "ONAP_IP";
71
72     private static final String DEFAULT_MSB_IP = "127.0.0.1";
73
74     private static final Integer DEFAULT_MSB_PORT = 80;
75
76     private static final Logger logger = LoggerFactory.getLogger(MsoMulticloudUtils.class);
77
78     /******************************************************************************
79      *
80      * Methods (and associated utilities) to implement the VduPlugin interface
81      *
82      *******************************************************************************/
83
84     /**
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..
89      *
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.
94      *
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.
100      *
101      * An error will be thrown if the requested Stack already exists in the specified
102      * Tenant and Cloud.
103      *
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.
108      *
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
117      *        Template id)
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.
122      */
123
124     @SuppressWarnings("unchecked")
125     @Override
126     public StackInfo createStack (String cloudSiteId,
127                                   String tenantId,
128                                   String stackName,
129                                   String heatTemplate,
130                                   Map <String, ?> stackInputs,
131                                   boolean pollForCompletion,
132                                   int timeoutMinutes,
133                                   String environment,
134                                   Map <String, Object> files,
135                                   Map <String, Object> heatFiles,
136                                   boolean backout) throws MsoException {
137
138         // Get the directives, if present.
139         String oofDirectives = null;
140         String sdncDirectives = null;
141         String genericVnfId = null;
142         String vfModuleId = null;
143
144         String key = "oof_directives";
145         if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
146             oofDirectives = (String) stackInputs.get(key);
147             stackInputs.remove(key);
148         }
149         key = "sdnc_directives";
150         if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
151             sdncDirectives = (String) stackInputs.get(key);
152             stackInputs.remove(key);
153         }
154         key = "generic_vnf_id";
155         if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
156             genericVnfId = (String) stackInputs.get(key);
157             stackInputs.remove(key);
158         }
159         key = "vf_module_id";
160         if (!stackInputs.isEmpty() && stackInputs.containsKey(key)) {
161             vfModuleId = (String) stackInputs.get(key);
162             stackInputs.remove(key);
163         }
164
165         // create the multicloud payload
166         CreateStackParam stack = createStackParam(stackName, heatTemplate, stackInputs, timeoutMinutes, environment, files, heatFiles);
167
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());
175
176
177         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, null);
178         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
179
180         if (multicloudClient != null) {
181             Response res = multicloudClient.post(multicloudParam);
182             logger.debug("Multicloud Post response is: " + res);
183         }
184
185         Stack responseStack = new Stack();
186         responseStack.setStackStatus(HeatStatus.CREATED.toString());
187
188         return new StackInfoMapper(responseStack).map();
189     }
190
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) {
196             return null;
197         }
198         return heatStack.getOutputs();
199     }
200
201     /**
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.
205      *
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.
211      */
212     @Override
213     public StackInfo queryStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
214         logger.debug ("Query multicloud HEAT stack: " + stackName + " in tenant " + tenantId);
215
216         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackName);
217
218         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
219
220         if (multicloudClient != null) {
221             Response response = multicloudClient.get();
222             logger.debug("Multicloud Get response is: " + response);
223
224             return new StackInfo (stackName, HeatStatus.CREATED);
225         }
226
227         return new StackInfo (stackName, HeatStatus.NOTFOUND);
228     }
229
230     public StackInfo deleteStack (String cloudSiteId, String tenantId, String stackName) throws MsoException {
231         logger.debug ("Delete multicloud HEAT stack: " + stackName + " in tenant " + tenantId);
232
233         String multicloudEndpoint = getMulticloudEndpoint(cloudSiteId, stackName);
234
235         RestClient multicloudClient = getMulticloudClient(multicloudEndpoint);
236
237         if (multicloudClient != null) {
238             Response response = multicloudClient.delete();
239             logger.debug("Multicloud Get response is: " + response);
240
241             return new StackInfo (stackName, HeatStatus.DELETING);
242         }
243
244         return new StackInfo (stackName, HeatStatus.FAILED);
245     }
246
247     // ---------------------------------------------------------------
248     // PRIVATE FUNCTIONS FOR USE WITHIN THIS CLASS
249
250
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);
254
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);
258         }
259         Integer msbPort = env.getProperty("mso.msb-port", Integer.class, DEFAULT_MSB_PORT);
260
261         return UriBuilder.fromPath("").host(msbIp).port(msbPort).scheme("http").build().toString();
262     }
263
264     private String getMulticloudEndpoint(String cloudSiteId, String workloadId) throws MsoCloudSiteNotFound {
265
266         CloudSite cloudSite = cloudConfig.getCloudSite(cloudSiteId).orElseThrow(() -> new MsoCloudSiteNotFound(cloudSiteId));
267         String endpoint = getMsbHost() + cloudSite.getIdentityService().getIdentityUrl();
268
269         if (workloadId != null) {
270             return endpoint + workloadId;
271         } else {
272             return endpoint;
273         }
274     }
275
276     private RestClient getMulticloudClient(String endpoint) {
277         RestClient client = null;
278         try {
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());
287         }
288         return client;
289     }
290
291     /**
292      * VduPlugin interface for instantiate function.
293      *
294      * Translate the VduPlugin parameters to the corresponding 'createStack' parameters,
295      * and then invoke the existing function.
296      */
297     @Override
298     public VduInstance instantiateVdu (
299             CloudInfo cloudInfo,
300             String instanceName,
301             Map<String,Object> inputs,
302             VduModelInfo vduModel,
303             boolean rollbackOnFailure)
304         throws VduException
305     {
306         String cloudSiteId = cloudInfo.getCloudSiteId();
307         String tenantId = cloudInfo.getTenantId();
308
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;
317
318         for (VduArtifact vduArtifact: vduModel.getArtifacts()) {
319             if (vduArtifact.getType() == ArtifactType.MAIN_TEMPLATE) {
320                 heatTemplate = new String(vduArtifact.getContent());
321             }
322             else if (vduArtifact.getType() == ArtifactType.NESTED_TEMPLATE) {
323                 nestedTemplates.put(vduArtifact.getName(), new String(vduArtifact.getContent()));
324             }
325             else if (vduArtifact.getType() == ArtifactType.ENVIRONMENT) {
326                 heatEnvironment = new String(vduArtifact.getContent());
327             }
328         }
329
330         try {
331             StackInfo stackInfo = createStack (cloudSiteId,
332                     tenantId,
333                     instanceName,
334                     heatTemplate,
335                     inputs,
336                     true,    // poll for completion
337                     vduModel.getTimeoutMinutes(),
338                     heatEnvironment,
339                     nestedTemplates,
340                     files,
341                     rollbackOnFailure);
342             // Populate a vduInstance from the StackInfo
343             return stackInfoToVduInstance(stackInfo);
344         }
345         catch (Exception e) {
346             throw new VduException ("MsoMulticloudUtils (instantiateVDU): createStack Exception", e);
347         }
348     }
349
350
351     /**
352      * VduPlugin interface for query function.
353      */
354     @Override
355     public VduInstance queryVdu (CloudInfo cloudInfo, String instanceId)
356         throws VduException
357     {
358         String cloudSiteId = cloudInfo.getCloudSiteId();
359         String tenantId = cloudInfo.getTenantId();
360
361         try {
362             // Query the Cloudify Deployment object and  populate a VduInstance
363             StackInfo stackInfo = queryStack (cloudSiteId, tenantId, instanceId);
364
365             return stackInfoToVduInstance(stackInfo);
366         }
367         catch (Exception e) {
368             throw new VduException ("MsoMulticloudUtils (queryVdu): queryStack Exception ", e);
369         }
370     }
371
372
373     /**
374      * VduPlugin interface for delete function.
375      */
376     @Override
377     public VduInstance deleteVdu (CloudInfo cloudInfo, String instanceId, int timeoutMinutes)
378         throws VduException
379     {
380         String cloudSiteId = cloudInfo.getCloudSiteId();
381         String tenantId = cloudInfo.getTenantId();
382
383         try {
384             // Delete the Multicloud stack
385             StackInfo stackInfo = deleteStack (tenantId, cloudSiteId, instanceId, true);
386
387             // Populate a VduInstance based on the deleted Cloudify Deployment object
388             VduInstance vduInstance = stackInfoToVduInstance(stackInfo);
389
390             // Override return state to DELETED (MulticloudUtils sets to NOTFOUND)
391             vduInstance.getStatus().setState(VduStateType.DELETED);
392
393             return vduInstance;
394         }
395         catch (Exception e) {
396             throw new VduException ("Delete VDU Exception", e);
397         }
398     }
399
400
401     /**
402      * VduPlugin interface for update function.
403      *
404      * Update is currently not supported in the MsoMulticloudUtils implementation of VduPlugin.
405      * Just return a VduException.
406      *
407      */
408     @Override
409     public VduInstance updateVdu (
410             CloudInfo cloudInfo,
411             String instanceId,
412             Map<String,Object> inputs,
413             VduModelInfo vduModel,
414             boolean rollbackOnFailure)
415         throws VduException
416     {
417         throw new VduException ("MsoMulticloudUtils: updateVdu interface not supported");
418     }
419
420
421     /*
422      * Convert the local DeploymentInfo object (Cloudify-specific) to a generic VduInstance object
423      */
424     protected VduInstance stackInfoToVduInstance (StackInfo stackInfo)
425     {
426         VduInstance vduInstance = new VduInstance();
427
428         // The full canonical name as the instance UUID
429         vduInstance.setVduInstanceId(stackInfo.getCanonicalName());
430         vduInstance.setVduInstanceName(stackInfo.getName());
431
432         // Copy inputs and outputs
433         vduInstance.setInputs(stackInfo.getParameters());
434         vduInstance.setOutputs(stackInfo.getOutputs());
435
436         // Translate the status elements
437         vduInstance.setStatus(stackStatusToVduStatus (stackInfo));
438
439         return vduInstance;
440     }
441
442     private VduStatus stackStatusToVduStatus (StackInfo stackInfo)
443     {
444         VduStatus vduStatus = new VduStatus();
445
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();
450
451         if (heatStatus == HeatStatus.INIT  ||  heatStatus == HeatStatus.BUILDING) {
452             vduStatus.setState(VduStateType.INSTANTIATING);
453             vduStatus.setLastAction((new PluginAction ("create", "in_progress", statusMessage)));
454         }
455         else if (heatStatus == HeatStatus.NOTFOUND) {
456             vduStatus.setState(VduStateType.NOTFOUND);
457         }
458         else if (heatStatus == HeatStatus.CREATED) {
459             vduStatus.setState(VduStateType.INSTANTIATED);
460             vduStatus.setLastAction((new PluginAction ("create", "complete", statusMessage)));
461         }
462         else if (heatStatus == HeatStatus.UPDATED) {
463             vduStatus.setState(VduStateType.INSTANTIATED);
464             vduStatus.setLastAction((new PluginAction ("update", "complete", statusMessage)));
465         }
466         else if (heatStatus == HeatStatus.UPDATING) {
467             vduStatus.setState(VduStateType.UPDATING);
468             vduStatus.setLastAction((new PluginAction ("update", "in_progress", statusMessage)));
469         }
470         else if (heatStatus == HeatStatus.DELETING) {
471             vduStatus.setState(VduStateType.DELETING);
472             vduStatus.setLastAction((new PluginAction ("delete", "in_progress", statusMessage)));
473         }
474         else if (heatStatus == HeatStatus.FAILED) {
475             vduStatus.setState(VduStateType.FAILED);
476             vduStatus.setErrorMessage(stackInfo.getStatusMessage());
477         } else {
478             vduStatus.setState(VduStateType.UNKNOWN);
479         }
480
481         return vduStatus;
482     }
483 }