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