2 * ============LICENSE_START=======================================================
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
23 package org.onap.policy.so;
25 import com.google.gson.GsonBuilder;
26 import com.google.gson.JsonSyntaxException;
27 import java.util.HashMap;
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;
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
49 public final class SoManager {
50 private static final Logger logger = LoggerFactory.getLogger(SoManager.class);
52 private static ExecutorService executors = Executors.newCachedThreadPool();
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();
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;
62 // The REST manager used for processing REST calls for this VFC manager
63 private RestManager restManager;
65 private long restGetTimeout = GET_REQUEST_WAIT_INTERVAL;
69 private String password;
72 public interface SoCallback {
73 public void onSoResponseWrapper(SoResponseWrapper wrapper);
77 * Default constructor.
79 public SoManager(String url, String user, String password) {
82 this.password = password;
83 restManager = new RestManager();
87 * Create a service instance in SO.
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
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);
105 // Process the response from SO
106 SoResponse response = waitForSoOperationCompletion(urlBase, username, password, url, httpResponse);
107 if (SO_RESPONSE_ERROR != response.getHttpResponseCode()) {
115 * Works just like SOManager#asyncSORestCall(String, WorkingMemory, String, String, String, SORequest)
116 * except the vfModuleInstanceId is always null.
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);
126 * This method makes an asynchronous Rest call to MSO and inserts the response into
127 * Drools working memory.
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
135 * @param request the SO request
136 * @return a concurrent Future for the thread that handles the request
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));
149 * This class handles an asynchronous request to SO as a thread.
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;
160 final String password;
163 * Constructor, sets the context of the request.
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
172 private AsyncSoRestCallThread(final String requestId,
173 final SoCallback callback, final String serviceInstanceId,
174 final String vnfInstanceId, final String vfModuleInstanceId,
175 final SoRequest request,
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;
187 this.password = password;
191 * Process the asynchronous SO request.
194 public SoResponse call() {
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;
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(),
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(),
219 // Process the response from SO
220 SoResponse response = waitForSoOperationCompletion(this.baseUrl, this.user, this.password, initialUrl,
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);
234 * Wait for the SO operation we have ordered to complete.
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
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()) {
252 // The SO URL to use to get the status of orchestration requests
253 String urlGet = urlBaseSo + "/orchestrationRequests/v5/" + response.getRequestReferences().getRequestId();
255 // The HTTP status code of the latest response
256 Pair<Integer, String> latestHttpResponse = initialHttpResponse;
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
263 // issuing any other requests
264 if (isRequestStateFinished(latestHttpResponse, response)) {
268 // Wait for the defined interval before issuing a get
270 Thread.sleep(restGetTimeout);
271 } catch (InterruptedException e) {
272 logger.error("Interrupted exception: ", e);
273 Thread.currentThread().interrupt();
274 response.setHttpResponseCode(SO_RESPONSE_ERROR);
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());
284 response = processSoResponse(urlGet, httpResponse);
285 if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
289 // Our latest HTTP response code
290 latestHttpResponse = httpResponse;
293 // We have timed out on the SO request
294 response.setHttpResponseCode(SO_RESPONSE_ERROR);
299 * Parse the response message from SO into a SOResponse object.
301 * @param requestURL The URL of the HTTP request
302 * @param httpResponse The HTTP message returned from SO
303 * @return The parsed response
305 private SoResponse processSoResponse(final String requestUrl, final Pair<Integer, String> httpResponse) {
306 SoResponse response = new SoResponse();
308 // A null httpDetails indicates a HTTP problem, a valid response from SO must be
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);
317 // Parse the JSON of the response into our POJO
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);
326 // Set the HTTP response code of the response if needed
327 if (response.getHttpResponseCode() == 0) {
328 response.setHttpResponseCode(httpResponse.first);
331 NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, requestUrl, httpResponse.second);
333 if (logger.isDebugEnabled()) {
334 logger.debug("***** Response to SO Request to URL {}:", requestUrl);
335 logger.debug(httpResponse.second);
342 * Method to allow tuning of REST get timeout.
344 * @param restGetTimeout the timeout value
346 protected void setRestGetTimeout(final long restGetTimeout) {
347 this.restGetTimeout = restGetTimeout;
351 * Check that the request state of a response is defined.
353 * @param response The response to check
354 * @return true if the request for the response is defined
356 private boolean isRequestStateDefined(final SoResponse response) {
357 return response != null && response.getRequest() != null && response.getRequest().getRequestStatus() != null
358 && response.getRequest().getRequestStatus().getRequestState() != null;
362 * Check that the request state of a response is finished.
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
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);
378 * Check that a HTTP operation result has no nulls.
380 * @param httpOperationResult the result to check
381 * @return true if no nulls are found
383 private boolean httpResultIsNullFree(Pair<Integer, String> httpOperationResult) {
384 return httpOperationResult != null && httpOperationResult.first != null && httpOperationResult.second != null;
388 * Create simple HTTP headers for unauthenticated requests to SO.
390 * @return the HTTP headers
392 private Map<String, String> createSimpleHeaders() {
393 Map<String, String> headers = new HashMap<>();
394 headers.put("Accept", MEDIA_TYPE);