Remove drools PDP dependency
[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     public interface SoCallback {
72         public void onSoResponseWrapper(SoResponseWrapper wrapper);
73     }
74
75     /**
76      * Default constructor.
77      */
78     public SoManager(String url, String user, String password) {
79         this.url = url;
80         this.user = user;
81         this.password = password;
82         restManager = new RestManager();
83     }
84
85     /**
86      * Create a service instance in SO.
87      *
88      * @param url the SO URL
89      * @param urlBase the base URL
90      * @param username user name on SO
91      * @param password password on SO
92      * @param request the request to issue to SO
93      * @return the SO Response object
94      */
95     public SoResponse createModuleInstance(final String url, final String urlBase, final String username,
96                     final String password, final SoRequest request) {
97         // Issue the HTTP POST request to SO to create the service instance
98         String requestJson = Serialization.gsonPretty.toJson(request);
99         NetLoggerUtil.getNetworkLogger().info("[OUT|{}|{}|{}|{}|{}|{}|]{}{}", "SO", url, username, password,
100                         createSimpleHeaders(), MEDIA_TYPE, LINE_SEPARATOR, requestJson);
101         Pair<Integer, String> httpResponse =
102                         restManager.post(url, username, password, createSimpleHeaders(), MEDIA_TYPE, requestJson);
103
104         // Process the response from SO
105         SoResponse response = waitForSoOperationCompletion(urlBase, username, password, url, httpResponse);
106         if (SO_RESPONSE_ERROR != response.getHttpResponseCode()) {
107             return response;
108         } else {
109             return null;
110         }
111     }
112
113     /**
114      * Works just like SOManager#asyncSORestCall(String, WorkingMemory, String, String, String, SORequest)
115      * except the vfModuleInstanceId is always null.
116      *
117      */
118     public Future<SoResponse> asyncSoRestCall(final String requestId, final SoCallback callback,
119                                               final String serviceInstanceId, final String vnfInstanceId,
120                                               final SoRequest request) {
121         return asyncSoRestCall(requestId, callback, serviceInstanceId, vnfInstanceId, null, request);
122     }
123
124     /**
125      * This method makes an asynchronous Rest call to MSO and inserts the response into
126      * Drools working memory.
127      *
128      * @param requestId          the request id
129      * @param callback           callback method
130      * @param serviceInstanceId  service instance id to construct the request url
131      * @param vnfInstanceId      vnf instance id to construct the request url
132      * @param vfModuleInstanceId vfModule instance id to construct the request url (required in case of delete vf
133      *                           module)
134      * @param request            the SO request
135      * @return a concurrent Future for the thread that handles the request
136      */
137     public Future<SoResponse> asyncSoRestCall(final String requestId,
138             final SoCallback callback,
139             final String serviceInstanceId,
140             final String vnfInstanceId,
141             final String vfModuleInstanceId,
142             final SoRequest request) {
143         return executors.submit(new AsyncSoRestCallThread(requestId, callback, serviceInstanceId, vnfInstanceId,
144                 vfModuleInstanceId, request, this.url, this.user, this.password));
145     }
146
147     /**
148      * This class handles an asynchronous request to SO as a thread.
149      */
150     private class AsyncSoRestCallThread implements Callable<SoResponse> {
151         final String requestId;
152         final SoCallback callback;
153         final String serviceInstanceId;
154         final String vnfInstanceId;
155         final String vfModuleInstanceId;
156         final SoRequest request;
157         final String baseUrl;
158         final String user;
159         final String password;
160
161         /**
162          * Constructor, sets the context of the request.
163          *
164          * @param requestID          The request ID
165          * @param wm                 reference to the Drools working memory
166          * @param serviceInstanceId  the service instance in SO to use
167          * @param vnfInstanceId      the VNF instance that is the subject of the request
168          * @param vfModuleInstanceId the vf module instance id (not null in case of delete vf module request)
169          * @param request            the request itself
170          */
171         private AsyncSoRestCallThread(final String requestId,
172                 final SoCallback callback, final String serviceInstanceId,
173                 final String vnfInstanceId, final String vfModuleInstanceId,
174                 final SoRequest request,
175                 final String url,
176                 final String user,
177                 final String password) {
178             this.requestId = requestId;
179             this.callback = callback;
180             this.serviceInstanceId = serviceInstanceId;
181             this.vnfInstanceId = vnfInstanceId;
182             this.vfModuleInstanceId = vfModuleInstanceId;
183             this.request = request;
184             this.baseUrl = url;
185             this.user = user;
186             this.password = password;
187         }
188
189         /**
190          * Process the asynchronous SO request.
191          */
192         @Override
193         public SoResponse call() {
194
195             // Create a JSON representation of the request
196             String soJson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create().toJson(request);
197             String initialUrl = null;
198             Pair<Integer, String> httpResponse = null;
199
200             if (request.getOperationType() != null && request.getOperationType()
201                     .equals(SoOperationType.SCALE_OUT)) {
202                 initialUrl = this.baseUrl + "/serviceInstantiation/v7/serviceInstances/" + serviceInstanceId + "/vnfs/"
203                                 + vnfInstanceId + "/vfModules/scaleOut";
204                 NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, initialUrl, soJson);
205                 httpResponse = restManager.post(initialUrl, this.user, this.password, createSimpleHeaders(),
206                         MEDIA_TYPE, soJson);
207             } else if (request.getOperationType() != null && request.getOperationType()
208                     .equals(SoOperationType.DELETE_VF_MODULE)) {
209                 initialUrl = this.baseUrl + "/serviceInstances/v7/" + serviceInstanceId + "/vnfs/" + vnfInstanceId
210                         + "/vfModules/" + vfModuleInstanceId;
211                 NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, initialUrl, soJson);
212                 httpResponse = restManager.delete(initialUrl, this.user, this.password, createSimpleHeaders(),
213                         MEDIA_TYPE, soJson);
214             } else {
215                 return null;
216             }
217
218             // Process the response from SO
219             SoResponse response = waitForSoOperationCompletion(this.baseUrl, this.user, this.password, initialUrl,
220                     httpResponse);
221
222             // Return the response to Drools in its working memory
223             SoResponseWrapper soWrapper = new SoResponseWrapper(response, requestId);
224             if (this.callback != null) {
225                 this.callback.onSoResponseWrapper(soWrapper);
226             }
227
228             return response;
229         }
230     }
231
232     /**
233      * Wait for the SO operation we have ordered to complete.
234      *
235      * @param urlBaseSo The base URL for SO
236      * @param username user name on SO
237      * @param password password on SO
238      * @param initialRequestUrl The URL of the initial HTTP request
239      * @param initialHttpResponse The initial HTTP message returned from SO
240      * @return The parsed final response of SO to the request
241      */
242     private SoResponse waitForSoOperationCompletion(final String urlBaseSo, final String username,
243                     final String password, final String initialRequestUrl,
244                     final Pair<Integer, String> initialHttpResponse) {
245         // Process the initial response from SO, the response to a post
246         SoResponse response = processSoResponse(initialRequestUrl, initialHttpResponse);
247         if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
248             return response;
249         }
250
251         // The SO URL to use to get the status of orchestration requests
252         String urlGet = urlBaseSo + "/orchestrationRequests/v5/" + response.getRequestReferences().getRequestId();
253
254         // The HTTP status code of the latest response
255         Pair<Integer, String> latestHttpResponse = initialHttpResponse;
256
257         // Wait for the response from SO
258         for (int attemptsLeft = GET_REQUESTS_BEFORE_TIMEOUT; attemptsLeft >= 0; attemptsLeft--) {
259             // The SO request may have completed even on the first request so we check the
260             // response
261             // here before
262             // issuing any other requests
263             if (isRequestStateFinished(latestHttpResponse, response)) {
264                 return response;
265             }
266
267             // Wait for the defined interval before issuing a get
268             try {
269                 Thread.sleep(restGetTimeout);
270             } catch (InterruptedException e) {
271                 logger.error("Interrupted exception: ", e);
272                 Thread.currentThread().interrupt();
273                 response.setHttpResponseCode(SO_RESPONSE_ERROR);
274                 return response;
275             }
276
277             // Issue a GET to find the current status of our request
278             NetLoggerUtil.getNetworkLogger().info("[OUT|{}|{}|{}|{}|{}|{}|]{}", "SO", urlGet, username, password,
279                             createSimpleHeaders(), MEDIA_TYPE, LINE_SEPARATOR);
280             Pair<Integer, String> httpResponse = restManager.get(urlGet, username, password, createSimpleHeaders());
281
282             // Get our response
283             response = processSoResponse(urlGet, httpResponse);
284             if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
285                 return response;
286             }
287
288             // Our latest HTTP response code
289             latestHttpResponse = httpResponse;
290         }
291
292         // We have timed out on the SO request
293         response.setHttpResponseCode(SO_RESPONSE_ERROR);
294         return response;
295     }
296
297     /**
298      * Parse the response message from SO into a SOResponse object.
299      *
300      * @param requestURL The URL of the HTTP request
301      * @param httpResponse The HTTP message returned from SO
302      * @return The parsed response
303      */
304     private SoResponse processSoResponse(final String requestUrl, final Pair<Integer, String> httpResponse) {
305         SoResponse response = new SoResponse();
306
307         // A null httpDetails indicates a HTTP problem, a valid response from SO must be
308         // either 200
309         // or 202
310         if (!httpResultIsNullFree(httpResponse) || (httpResponse.first != 200 && httpResponse.first != 202)) {
311             logger.error("Invalid HTTP response received from SO");
312             response.setHttpResponseCode(SO_RESPONSE_ERROR);
313             return response;
314         }
315
316         // Parse the JSON of the response into our POJO
317         try {
318             response = Serialization.gsonPretty.fromJson(httpResponse.second, SoResponse.class);
319         } catch (JsonSyntaxException e) {
320             logger.error("Failed to deserialize HTTP response into SOResponse: ", e);
321             response.setHttpResponseCode(SO_RESPONSE_ERROR);
322             return response;
323         }
324
325         // Set the HTTP response code of the response if needed
326         if (response.getHttpResponseCode() == 0) {
327             response.setHttpResponseCode(httpResponse.first);
328         }
329
330         NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, requestUrl, httpResponse.second);
331
332         if (logger.isDebugEnabled()) {
333             logger.debug("***** Response to SO Request to URL {}:", requestUrl);
334             logger.debug(httpResponse.second);
335         }
336
337         return response;
338     }
339
340     /**
341      * Method to allow tuning of REST get timeout.
342      *
343      * @param restGetTimeout the timeout value
344      */
345     protected void setRestGetTimeout(final long restGetTimeout) {
346         this.restGetTimeout = restGetTimeout;
347     }
348
349     /**
350      * Check that the request state of a response is defined.
351      *
352      * @param response The response to check
353      * @return true if the request for the response is defined
354      */
355     private boolean isRequestStateDefined(final SoResponse response) {
356         return response != null && response.getRequest() != null && response.getRequest().getRequestStatus() != null
357                         && response.getRequest().getRequestStatus().getRequestState() != null;
358     }
359
360     /**
361      * Check that the request state of a response is finished.
362      *
363      * @param latestHttpDetails the HTTP details of the response
364      * @param response The response to check
365      * @return true if the request for the response is finished
366      */
367     private boolean isRequestStateFinished(final Pair<Integer, String> latestHttpDetails, final SoResponse response) {
368         if (latestHttpDetails != null && 200 == latestHttpDetails.first && isRequestStateDefined(response)) {
369             String requestState = response.getRequest().getRequestStatus().getRequestState();
370             return "COMPLETE".equalsIgnoreCase(requestState) || "FAILED".equalsIgnoreCase(requestState);
371         } else {
372             return false;
373         }
374     }
375
376     /**
377      * Check that a HTTP operation result has no nulls.
378      *
379      * @param httpOperationResult the result to check
380      * @return true if no nulls are found
381      */
382     private boolean httpResultIsNullFree(Pair<Integer, String> httpOperationResult) {
383         return httpOperationResult != null && httpOperationResult.first != null && httpOperationResult.second != null;
384     }
385
386     /**
387      * Create simple HTTP headers for unauthenticated requests to SO.
388      *
389      * @return the HTTP headers
390      */
391     private Map<String, String> createSimpleHeaders() {
392         Map<String, String> headers = new HashMap<>();
393         headers.put("Accept", MEDIA_TYPE);
394         return headers;
395     }
396 }