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;
71 public interface SoCallback {
72 public void onSoResponseWrapper(SoResponseWrapper wrapper);
76 * Default constructor.
78 public SoManager(String url, String user, String password) {
81 this.password = password;
82 restManager = new RestManager();
86 * Create a service instance in SO.
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
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);
104 // Process the response from SO
105 SoResponse response = waitForSoOperationCompletion(urlBase, username, password, url, httpResponse);
106 if (SO_RESPONSE_ERROR != response.getHttpResponseCode()) {
114 * Works just like SOManager#asyncSORestCall(String, WorkingMemory, String, String, String, SORequest)
115 * except the vfModuleInstanceId is always null.
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);
125 * This method makes an asynchronous Rest call to MSO and inserts the response into
126 * Drools working memory.
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
134 * @param request the SO request
135 * @return a concurrent Future for the thread that handles the request
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));
148 * This class handles an asynchronous request to SO as a thread.
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;
159 final String password;
162 * Constructor, sets the context of the request.
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
171 private AsyncSoRestCallThread(final String requestId,
172 final SoCallback callback, final String serviceInstanceId,
173 final String vnfInstanceId, final String vfModuleInstanceId,
174 final SoRequest request,
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;
186 this.password = password;
190 * Process the asynchronous SO request.
193 public SoResponse call() {
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;
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(),
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(),
218 // Process the response from SO
219 SoResponse response = waitForSoOperationCompletion(this.baseUrl, this.user, this.password, initialUrl,
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);
233 * Wait for the SO operation we have ordered to complete.
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
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()) {
251 // The SO URL to use to get the status of orchestration requests
252 String urlGet = urlBaseSo + "/orchestrationRequests/v5/" + response.getRequestReferences().getRequestId();
254 // The HTTP status code of the latest response
255 Pair<Integer, String> latestHttpResponse = initialHttpResponse;
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
262 // issuing any other requests
263 if (isRequestStateFinished(latestHttpResponse, response)) {
267 // Wait for the defined interval before issuing a get
269 Thread.sleep(restGetTimeout);
270 } catch (InterruptedException e) {
271 logger.error("Interrupted exception: ", e);
272 Thread.currentThread().interrupt();
273 response.setHttpResponseCode(SO_RESPONSE_ERROR);
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());
283 response = processSoResponse(urlGet, httpResponse);
284 if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
288 // Our latest HTTP response code
289 latestHttpResponse = httpResponse;
292 // We have timed out on the SO request
293 response.setHttpResponseCode(SO_RESPONSE_ERROR);
298 * Parse the response message from SO into a SOResponse object.
300 * @param requestURL The URL of the HTTP request
301 * @param httpResponse The HTTP message returned from SO
302 * @return The parsed response
304 private SoResponse processSoResponse(final String requestUrl, final Pair<Integer, String> httpResponse) {
305 SoResponse response = new SoResponse();
307 // A null httpDetails indicates a HTTP problem, a valid response from SO must be
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);
316 // Parse the JSON of the response into our POJO
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);
325 // Set the HTTP response code of the response if needed
326 if (response.getHttpResponseCode() == 0) {
327 response.setHttpResponseCode(httpResponse.first);
330 NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, requestUrl, httpResponse.second);
332 if (logger.isDebugEnabled()) {
333 logger.debug("***** Response to SO Request to URL {}:", requestUrl);
334 logger.debug(httpResponse.second);
341 * Method to allow tuning of REST get timeout.
343 * @param restGetTimeout the timeout value
345 protected void setRestGetTimeout(final long restGetTimeout) {
346 this.restGetTimeout = restGetTimeout;
350 * Check that the request state of a response is defined.
352 * @param response The response to check
353 * @return true if the request for the response is defined
355 private boolean isRequestStateDefined(final SoResponse response) {
356 return response != null && response.getRequest() != null && response.getRequest().getRequestStatus() != null
357 && response.getRequest().getRequestStatus().getRequestState() != null;
361 * Check that the request state of a response is finished.
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
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);
377 * Check that a HTTP operation result has no nulls.
379 * @param httpOperationResult the result to check
380 * @return true if no nulls are found
382 private boolean httpResultIsNullFree(Pair<Integer, String> httpOperationResult) {
383 return httpOperationResult != null && httpOperationResult.first != null && httpOperationResult.second != null;
387 * Create simple HTTP headers for unauthenticated requests to SO.
389 * @return the HTTP headers
391 private Map<String, String> createSimpleHeaders() {
392 Map<String, String> headers = new HashMap<>();
393 headers.put("Accept", MEDIA_TYPE);