Merge "Remove actor and recipe checks from ControlLoopCompiler.java"
[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));
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 SoManager callingSoManager) {
177             this.requestId = requestId;
178             this.callback = callback;
179             this.serviceInstanceId = serviceInstanceId;
180             this.vnfInstanceId = vnfInstanceId;
181             this.vfModuleInstanceId = vfModuleInstanceId;
182             this.request = request;
183             this.baseUrl = callingSoManager.url;
184             this.user = callingSoManager.user;
185             this.password = callingSoManager.password;
186         }
187
188         /**
189          * Process the asynchronous SO request.
190          */
191         @Override
192         public SoResponse call() {
193
194             // Create a JSON representation of the request
195             String soJson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create().toJson(request);
196             String initialUrl = null;
197             Pair<Integer, String> httpResponse = null;
198
199             if (request.getOperationType() != null && request.getOperationType()
200                     .equals(SoOperationType.SCALE_OUT)) {
201                 initialUrl = this.baseUrl + "/serviceInstantiation/v7/serviceInstances/" + serviceInstanceId + "/vnfs/"
202                                 + vnfInstanceId + "/vfModules/scaleOut";
203                 NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, initialUrl, soJson);
204                 httpResponse = restManager.post(initialUrl, this.user, this.password, createSimpleHeaders(),
205                         MEDIA_TYPE, soJson);
206             } else if (request.getOperationType() != null && request.getOperationType()
207                     .equals(SoOperationType.DELETE_VF_MODULE)) {
208                 initialUrl = this.baseUrl + "/serviceInstances/v7/" + serviceInstanceId + "/vnfs/" + vnfInstanceId
209                         + "/vfModules/" + vfModuleInstanceId;
210                 NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, initialUrl, soJson);
211                 httpResponse = restManager.delete(initialUrl, this.user, this.password, createSimpleHeaders(),
212                         MEDIA_TYPE, soJson);
213             } else {
214                 return null;
215             }
216
217             // Process the response from SO
218             SoResponse response = waitForSoOperationCompletion(this.baseUrl, this.user, this.password, initialUrl,
219                     httpResponse);
220
221             // Return the response to Drools in its working memory
222             SoResponseWrapper soWrapper = new SoResponseWrapper(response, requestId);
223             if (this.callback != null) {
224                 this.callback.onSoResponseWrapper(soWrapper);
225             }
226
227             return response;
228         }
229     }
230
231     /**
232      * Wait for the SO operation we have ordered to complete.
233      *
234      * @param urlBaseSo The base URL for SO
235      * @param username user name on SO
236      * @param password password on SO
237      * @param initialRequestUrl The URL of the initial HTTP request
238      * @param initialHttpResponse The initial HTTP message returned from SO
239      * @return The parsed final response of SO to the request
240      */
241     private SoResponse waitForSoOperationCompletion(final String urlBaseSo, final String username,
242                     final String password, final String initialRequestUrl,
243                     final Pair<Integer, String> initialHttpResponse) {
244         // Process the initial response from SO, the response to a post
245         SoResponse response = processSoResponse(initialRequestUrl, initialHttpResponse);
246         if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
247             return response;
248         }
249
250         // The SO URL to use to get the status of orchestration requests
251         String urlGet = urlBaseSo + "/orchestrationRequests/v5/" + response.getRequestReferences().getRequestId();
252
253         // The HTTP status code of the latest response
254         Pair<Integer, String> latestHttpResponse = initialHttpResponse;
255
256         // Wait for the response from SO
257         for (int attemptsLeft = GET_REQUESTS_BEFORE_TIMEOUT; attemptsLeft >= 0; attemptsLeft--) {
258             // The SO request may have completed even on the first request so we check the
259             // response
260             // here before
261             // issuing any other requests
262             if (isRequestStateFinished(latestHttpResponse, response)) {
263                 return response;
264             }
265
266             // Wait for the defined interval before issuing a get
267             try {
268                 Thread.sleep(restGetTimeout);
269             } catch (InterruptedException e) {
270                 logger.error("Interrupted exception: ", e);
271                 Thread.currentThread().interrupt();
272                 response.setHttpResponseCode(SO_RESPONSE_ERROR);
273                 return response;
274             }
275
276             // Issue a GET to find the current status of our request
277             NetLoggerUtil.getNetworkLogger().info("[OUT|{}|{}|{}|{}|{}|{}|]{}", "SO", urlGet, username, password,
278                             createSimpleHeaders(), MEDIA_TYPE, LINE_SEPARATOR);
279             Pair<Integer, String> httpResponse = restManager.get(urlGet, username, password, createSimpleHeaders());
280
281             // Get our response
282             response = processSoResponse(urlGet, httpResponse);
283             if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
284                 return response;
285             }
286
287             // Our latest HTTP response code
288             latestHttpResponse = httpResponse;
289         }
290
291         // We have timed out on the SO request
292         response.setHttpResponseCode(SO_RESPONSE_ERROR);
293         return response;
294     }
295
296     /**
297      * Parse the response message from SO into a SOResponse object.
298      *
299      * @param requestURL The URL of the HTTP request
300      * @param httpResponse The HTTP message returned from SO
301      * @return The parsed response
302      */
303     private SoResponse processSoResponse(final String requestUrl, final Pair<Integer, String> httpResponse) {
304         SoResponse response = new SoResponse();
305
306         // A null httpDetails indicates a HTTP problem, a valid response from SO must be
307         // either 200
308         // or 202
309         if (!httpResultIsNullFree(httpResponse) || (httpResponse.first != 200 && httpResponse.first != 202)) {
310             logger.error("Invalid HTTP response received from SO");
311             response.setHttpResponseCode(SO_RESPONSE_ERROR);
312             return response;
313         }
314
315         // Parse the JSON of the response into our POJO
316         try {
317             response = Serialization.gsonPretty.fromJson(httpResponse.second, SoResponse.class);
318         } catch (JsonSyntaxException e) {
319             logger.error("Failed to deserialize HTTP response into SOResponse: ", e);
320             response.setHttpResponseCode(SO_RESPONSE_ERROR);
321             return response;
322         }
323
324         // Set the HTTP response code of the response if needed
325         if (response.getHttpResponseCode() == 0) {
326             response.setHttpResponseCode(httpResponse.first);
327         }
328
329         NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, requestUrl, httpResponse.second);
330
331         if (logger.isDebugEnabled()) {
332             logger.debug("***** Response to SO Request to URL {}:", requestUrl);
333             logger.debug(httpResponse.second);
334         }
335
336         return response;
337     }
338
339     /**
340      * Method to allow tuning of REST get timeout.
341      *
342      * @param restGetTimeout the timeout value
343      */
344     protected void setRestGetTimeout(final long restGetTimeout) {
345         this.restGetTimeout = restGetTimeout;
346     }
347
348     /**
349      * Check that the request state of a response is defined.
350      *
351      * @param response The response to check
352      * @return true if the request for the response is defined
353      */
354     private boolean isRequestStateDefined(final SoResponse response) {
355         return response != null && response.getRequest() != null && response.getRequest().getRequestStatus() != null
356                         && response.getRequest().getRequestStatus().getRequestState() != null;
357     }
358
359     /**
360      * Check that the request state of a response is finished.
361      *
362      * @param latestHttpDetails the HTTP details of the response
363      * @param response The response to check
364      * @return true if the request for the response is finished
365      */
366     private boolean isRequestStateFinished(final Pair<Integer, String> latestHttpDetails, final SoResponse response) {
367         if (latestHttpDetails != null && 200 == latestHttpDetails.first && isRequestStateDefined(response)) {
368             String requestState = response.getRequest().getRequestStatus().getRequestState();
369             return "COMPLETE".equalsIgnoreCase(requestState) || "FAILED".equalsIgnoreCase(requestState);
370         } else {
371             return false;
372         }
373     }
374
375     /**
376      * Check that a HTTP operation result has no nulls.
377      *
378      * @param httpOperationResult the result to check
379      * @return true if no nulls are found
380      */
381     private boolean httpResultIsNullFree(Pair<Integer, String> httpOperationResult) {
382         return httpOperationResult != null && httpOperationResult.first != null && httpOperationResult.second != null;
383     }
384
385     /**
386      * Create simple HTTP headers for unauthenticated requests to SO.
387      *
388      * @return the HTTP headers
389      */
390     private Map<String, String> createSimpleHeaders() {
391         Map<String, String> headers = new HashMap<>();
392         headers.put("Accept", MEDIA_TYPE);
393         return headers;
394     }
395 }