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