Fix sonar issues in models: sdc to vfc
[policy/models.git] / models-interactions / model-impl / so / src / main / java / org / onap / policy / so / SoManager.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * so
4  * ================================================================================
5  * Copyright (C) 2017-2019 AT&T Intellectual Property. All rights reserved.
6  * Modifications Copyright (C) 2019 Nordix Foundation.
7  * Modifications Copyright (C) 2019 Samsung Electronics Co., Ltd.
8  * ================================================================================
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  * ============LICENSE_END=========================================================
21  */
22
23 package org.onap.policy.so;
24
25 import com.google.gson.GsonBuilder;
26 import com.google.gson.JsonSyntaxException;
27 import java.util.HashMap;
28 import java.util.Map;
29 import java.util.concurrent.Callable;
30 import java.util.concurrent.ExecutorService;
31 import java.util.concurrent.Executors;
32 import java.util.concurrent.Future;
33 import org.onap.policy.common.endpoints.event.comm.Topic.CommInfrastructure;
34 import org.onap.policy.common.endpoints.utils.NetLoggerUtil;
35 import org.onap.policy.common.endpoints.utils.NetLoggerUtil.EventType;
36 import org.onap.policy.rest.RestManager;
37 import org.onap.policy.rest.RestManager.Pair;
38 import org.onap.policy.so.util.Serialization;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
41
42
43 /**
44  * This class handles the interface towards SO (Service Orchestrator) for the ONAP Policy
45  * Framework. The SO API is defined at this link:
46  * http://onap.readthedocs.io/en/latest/submodules/so.git/docs/SO_R1_Interface.html#get-orchestration-request
47  *
48  */
49 public final class SoManager {
50     private static final Logger logger = LoggerFactory.getLogger(SoManager.class);
51
52     private static ExecutorService executors = Executors.newCachedThreadPool();
53
54     private static final int SO_RESPONSE_ERROR = 999;
55     private static final String MEDIA_TYPE = "application/json";
56     private static final String LINE_SEPARATOR = System.lineSeparator();
57
58     // REST get timeout value in milliseconds
59     private static final int GET_REQUESTS_BEFORE_TIMEOUT = 20;
60     private static final long GET_REQUEST_WAIT_INTERVAL = 20000;
61
62     // The REST manager used for processing REST calls for this VFC manager
63     private RestManager restManager;
64
65     private long restGetTimeout = GET_REQUEST_WAIT_INTERVAL;
66
67     private String url;
68     private String user;
69     private String password;
70
71     @FunctionalInterface
72     public interface SoCallback {
73         public void onSoResponseWrapper(SoResponseWrapper wrapper);
74     }
75
76     /**
77      * Default constructor.
78      */
79     public SoManager(String url, String user, String password) {
80         this.url = url;
81         this.user = user;
82         this.password = password;
83         restManager = new RestManager();
84     }
85
86     /**
87      * Create a service instance in SO.
88      *
89      * @param url the SO URL
90      * @param urlBase the base URL
91      * @param username user name on SO
92      * @param password password on SO
93      * @param request the request to issue to SO
94      * @return the SO Response object
95      */
96     public SoResponse createModuleInstance(final String url, final String urlBase, final String username,
97                     final String password, final SoRequest request) {
98         // Issue the HTTP POST request to SO to create the service instance
99         String requestJson = Serialization.gsonPretty.toJson(request);
100         NetLoggerUtil.getNetworkLogger().info("[OUT|{}|{}|{}|{}|{}|{}|]{}{}", "SO", url, username, password,
101                         createSimpleHeaders(), MEDIA_TYPE, LINE_SEPARATOR, requestJson);
102         Pair<Integer, String> httpResponse =
103                         restManager.post(url, username, password, createSimpleHeaders(), MEDIA_TYPE, requestJson);
104
105         // Process the response from SO
106         SoResponse response = waitForSoOperationCompletion(urlBase, username, password, url, httpResponse);
107         if (SO_RESPONSE_ERROR != response.getHttpResponseCode()) {
108             return response;
109         } else {
110             return null;
111         }
112     }
113
114     /**
115      * Works just like SOManager#asyncSORestCall(String, WorkingMemory, String, String, String, SORequest)
116      * except the vfModuleInstanceId is always null.
117      *
118      */
119     public Future<SoResponse> asyncSoRestCall(final String requestId, final SoCallback callback,
120                                               final String serviceInstanceId, final String vnfInstanceId,
121                                               final SoRequest request) {
122         return asyncSoRestCall(requestId, callback, serviceInstanceId, vnfInstanceId, null, request);
123     }
124
125     /**
126      * This method makes an asynchronous Rest call to MSO and inserts the response into
127      * Drools working memory.
128      *
129      * @param requestId          the request id
130      * @param callback           callback method
131      * @param serviceInstanceId  service instance id to construct the request url
132      * @param vnfInstanceId      vnf instance id to construct the request url
133      * @param vfModuleInstanceId vfModule instance id to construct the request url (required in case of delete vf
134      *                           module)
135      * @param request            the SO request
136      * @return a concurrent Future for the thread that handles the request
137      */
138     public Future<SoResponse> asyncSoRestCall(final String requestId,
139             final SoCallback callback,
140             final String serviceInstanceId,
141             final String vnfInstanceId,
142             final String vfModuleInstanceId,
143             final SoRequest request) {
144         return executors.submit(new AsyncSoRestCallThread(requestId, callback, serviceInstanceId, vnfInstanceId,
145                 vfModuleInstanceId, request, this.url, this.user, this.password));
146     }
147
148     /**
149      * This class handles an asynchronous request to SO as a thread.
150      */
151     private class AsyncSoRestCallThread implements Callable<SoResponse> {
152         final String requestId;
153         final SoCallback callback;
154         final String serviceInstanceId;
155         final String vnfInstanceId;
156         final String vfModuleInstanceId;
157         final SoRequest request;
158         final String baseUrl;
159         final String user;
160         final String password;
161
162         /**
163          * Constructor, sets the context of the request.
164          *
165          * @param requestID          The request ID
166          * @param wm                 reference to the Drools working memory
167          * @param serviceInstanceId  the service instance in SO to use
168          * @param vnfInstanceId      the VNF instance that is the subject of the request
169          * @param vfModuleInstanceId the vf module instance id (not null in case of delete vf module request)
170          * @param request            the request itself
171          */
172         private AsyncSoRestCallThread(final String requestId,
173                 final SoCallback callback, final String serviceInstanceId,
174                 final String vnfInstanceId, final String vfModuleInstanceId,
175                 final SoRequest request,
176                 final String url,
177                 final String user,
178                 final String password) {
179             this.requestId = requestId;
180             this.callback = callback;
181             this.serviceInstanceId = serviceInstanceId;
182             this.vnfInstanceId = vnfInstanceId;
183             this.vfModuleInstanceId = vfModuleInstanceId;
184             this.request = request;
185             this.baseUrl = url;
186             this.user = user;
187             this.password = password;
188         }
189
190         /**
191          * Process the asynchronous SO request.
192          */
193         @Override
194         public SoResponse call() {
195
196             // Create a JSON representation of the request
197             String soJson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create().toJson(request);
198             String initialUrl = null;
199             Pair<Integer, String> httpResponse = null;
200
201             if (request.getOperationType() != null && request.getOperationType()
202                     .equals(SoOperationType.SCALE_OUT)) {
203                 initialUrl = this.baseUrl + "/serviceInstantiation/v7/serviceInstances/" + serviceInstanceId + "/vnfs/"
204                                 + vnfInstanceId + "/vfModules/scaleOut";
205                 NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, initialUrl, soJson);
206                 httpResponse = restManager.post(initialUrl, this.user, this.password, createSimpleHeaders(),
207                         MEDIA_TYPE, soJson);
208             } else if (request.getOperationType() != null && request.getOperationType()
209                     .equals(SoOperationType.DELETE_VF_MODULE)) {
210                 initialUrl = this.baseUrl + "/serviceInstances/v7/" + serviceInstanceId + "/vnfs/" + vnfInstanceId
211                         + "/vfModules/" + vfModuleInstanceId;
212                 NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, initialUrl, soJson);
213                 httpResponse = restManager.delete(initialUrl, this.user, this.password, createSimpleHeaders(),
214                         MEDIA_TYPE, soJson);
215             } else {
216                 return null;
217             }
218
219             // Process the response from SO
220             SoResponse response = waitForSoOperationCompletion(this.baseUrl, this.user, this.password, initialUrl,
221                     httpResponse);
222
223             // Return the response to Drools in its working memory
224             SoResponseWrapper soWrapper = new SoResponseWrapper(response, requestId);
225             if (this.callback != null) {
226                 this.callback.onSoResponseWrapper(soWrapper);
227             }
228
229             return response;
230         }
231     }
232
233     /**
234      * Wait for the SO operation we have ordered to complete.
235      *
236      * @param urlBaseSo The base URL for SO
237      * @param username user name on SO
238      * @param password password on SO
239      * @param initialRequestUrl The URL of the initial HTTP request
240      * @param initialHttpResponse The initial HTTP message returned from SO
241      * @return The parsed final response of SO to the request
242      */
243     private SoResponse waitForSoOperationCompletion(final String urlBaseSo, final String username,
244                     final String password, final String initialRequestUrl,
245                     final Pair<Integer, String> initialHttpResponse) {
246         // Process the initial response from SO, the response to a post
247         SoResponse response = processSoResponse(initialRequestUrl, initialHttpResponse);
248         if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
249             return response;
250         }
251
252         // The SO URL to use to get the status of orchestration requests
253         String urlGet = urlBaseSo + "/orchestrationRequests/v5/" + response.getRequestReferences().getRequestId();
254
255         // The HTTP status code of the latest response
256         Pair<Integer, String> latestHttpResponse = initialHttpResponse;
257
258         // Wait for the response from SO
259         for (int attemptsLeft = GET_REQUESTS_BEFORE_TIMEOUT; attemptsLeft >= 0; attemptsLeft--) {
260             // The SO request may have completed even on the first request so we check the
261             // response
262             // here before
263             // issuing any other requests
264             if (isRequestStateFinished(latestHttpResponse, response)) {
265                 return response;
266             }
267
268             // Wait for the defined interval before issuing a get
269             try {
270                 Thread.sleep(restGetTimeout);
271             } catch (InterruptedException e) {
272                 logger.error("Interrupted exception: ", e);
273                 Thread.currentThread().interrupt();
274                 response.setHttpResponseCode(SO_RESPONSE_ERROR);
275                 return response;
276             }
277
278             // Issue a GET to find the current status of our request
279             NetLoggerUtil.getNetworkLogger().info("[OUT|{}|{}|{}|{}|{}|{}|]{}", "SO", urlGet, username, password,
280                             createSimpleHeaders(), MEDIA_TYPE, LINE_SEPARATOR);
281             Pair<Integer, String> httpResponse = restManager.get(urlGet, username, password, createSimpleHeaders());
282
283             // Get our response
284             response = processSoResponse(urlGet, httpResponse);
285             if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
286                 return response;
287             }
288
289             // Our latest HTTP response code
290             latestHttpResponse = httpResponse;
291         }
292
293         // We have timed out on the SO request
294         response.setHttpResponseCode(SO_RESPONSE_ERROR);
295         return response;
296     }
297
298     /**
299      * Parse the response message from SO into a SOResponse object.
300      *
301      * @param requestURL The URL of the HTTP request
302      * @param httpResponse The HTTP message returned from SO
303      * @return The parsed response
304      */
305     private SoResponse processSoResponse(final String requestUrl, final Pair<Integer, String> httpResponse) {
306         SoResponse response = new SoResponse();
307
308         // A null httpDetails indicates a HTTP problem, a valid response from SO must be
309         // either 200
310         // or 202
311         if (!httpResultIsNullFree(httpResponse) || (httpResponse.first != 200 && httpResponse.first != 202)) {
312             logger.error("Invalid HTTP response received from SO");
313             response.setHttpResponseCode(SO_RESPONSE_ERROR);
314             return response;
315         }
316
317         // Parse the JSON of the response into our POJO
318         try {
319             response = Serialization.gsonPretty.fromJson(httpResponse.second, SoResponse.class);
320         } catch (JsonSyntaxException e) {
321             logger.error("Failed to deserialize HTTP response into SOResponse: ", e);
322             response.setHttpResponseCode(SO_RESPONSE_ERROR);
323             return response;
324         }
325
326         // Set the HTTP response code of the response if needed
327         if (response.getHttpResponseCode() == 0) {
328             response.setHttpResponseCode(httpResponse.first);
329         }
330
331         NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, requestUrl, httpResponse.second);
332
333         if (logger.isDebugEnabled()) {
334             logger.debug("***** Response to SO Request to URL {}:", requestUrl);
335             logger.debug(httpResponse.second);
336         }
337
338         return response;
339     }
340
341     /**
342      * Method to allow tuning of REST get timeout.
343      *
344      * @param restGetTimeout the timeout value
345      */
346     protected void setRestGetTimeout(final long restGetTimeout) {
347         this.restGetTimeout = restGetTimeout;
348     }
349
350     /**
351      * Check that the request state of a response is defined.
352      *
353      * @param response The response to check
354      * @return true if the request for the response is defined
355      */
356     private boolean isRequestStateDefined(final SoResponse response) {
357         return response != null && response.getRequest() != null && response.getRequest().getRequestStatus() != null
358                         && response.getRequest().getRequestStatus().getRequestState() != null;
359     }
360
361     /**
362      * Check that the request state of a response is finished.
363      *
364      * @param latestHttpDetails the HTTP details of the response
365      * @param response The response to check
366      * @return true if the request for the response is finished
367      */
368     private boolean isRequestStateFinished(final Pair<Integer, String> latestHttpDetails, final SoResponse response) {
369         if (latestHttpDetails != null && 200 == latestHttpDetails.first && isRequestStateDefined(response)) {
370             String requestState = response.getRequest().getRequestStatus().getRequestState();
371             return "COMPLETE".equalsIgnoreCase(requestState) || "FAILED".equalsIgnoreCase(requestState);
372         } else {
373             return false;
374         }
375     }
376
377     /**
378      * Check that a HTTP operation result has no nulls.
379      *
380      * @param httpOperationResult the result to check
381      * @return true if no nulls are found
382      */
383     private boolean httpResultIsNullFree(Pair<Integer, String> httpOperationResult) {
384         return httpOperationResult != null && httpOperationResult.first != null && httpOperationResult.second != null;
385     }
386
387     /**
388      * Create simple HTTP headers for unauthenticated requests to SO.
389      *
390      * @return the HTTP headers
391      */
392     private Map<String, String> createSimpleHeaders() {
393         Map<String, String> headers = new HashMap<>();
394         headers.put("Accept", MEDIA_TYPE);
395         return headers;
396     }
397 }