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));
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,
176 final SoManager callingSoManager) {
177 this.requestId = requestId;
178 this.callback = callback;
179 this.serviceInstanceId = serviceInstanceId;
180 this.vnfInstanceId = vnfInstanceId;
181 this.vfModuleInstanceId = vfModuleInstanceId;
182 this.request = request;
183 this.baseUrl = callingSoManager.url;
184 this.user = callingSoManager.user;
185 this.password = callingSoManager.password;
189 * Process the asynchronous SO request.
192 public SoResponse call() {
194 // Create a JSON representation of the request
195 String soJson = new GsonBuilder().disableHtmlEscaping().setPrettyPrinting().create().toJson(request);
196 String initialUrl = null;
197 Pair<Integer, String> httpResponse = null;
199 if (request.getOperationType() != null && request.getOperationType()
200 .equals(SoOperationType.SCALE_OUT)) {
201 initialUrl = this.baseUrl + "/serviceInstantiation/v7/serviceInstances/" + serviceInstanceId + "/vnfs/"
202 + vnfInstanceId + "/vfModules/scaleOut";
203 NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, initialUrl, soJson);
204 httpResponse = restManager.post(initialUrl, this.user, this.password, createSimpleHeaders(),
206 } else if (request.getOperationType() != null && request.getOperationType()
207 .equals(SoOperationType.DELETE_VF_MODULE)) {
208 initialUrl = this.baseUrl + "/serviceInstances/v7/" + serviceInstanceId + "/vnfs/" + vnfInstanceId
209 + "/vfModules/" + vfModuleInstanceId;
210 NetLoggerUtil.log(EventType.OUT, CommInfrastructure.REST, initialUrl, soJson);
211 httpResponse = restManager.delete(initialUrl, this.user, this.password, createSimpleHeaders(),
217 // Process the response from SO
218 SoResponse response = waitForSoOperationCompletion(this.baseUrl, this.user, this.password, initialUrl,
221 // Return the response to Drools in its working memory
222 SoResponseWrapper soWrapper = new SoResponseWrapper(response, requestId);
223 if (this.callback != null) {
224 this.callback.onSoResponseWrapper(soWrapper);
232 * Wait for the SO operation we have ordered to complete.
234 * @param urlBaseSo The base URL for SO
235 * @param username user name on SO
236 * @param password password on SO
237 * @param initialRequestUrl The URL of the initial HTTP request
238 * @param initialHttpResponse The initial HTTP message returned from SO
239 * @return The parsed final response of SO to the request
241 private SoResponse waitForSoOperationCompletion(final String urlBaseSo, final String username,
242 final String password, final String initialRequestUrl,
243 final Pair<Integer, String> initialHttpResponse) {
244 // Process the initial response from SO, the response to a post
245 SoResponse response = processSoResponse(initialRequestUrl, initialHttpResponse);
246 if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
250 // The SO URL to use to get the status of orchestration requests
251 String urlGet = urlBaseSo + "/orchestrationRequests/v5/" + response.getRequestReferences().getRequestId();
253 // The HTTP status code of the latest response
254 Pair<Integer, String> latestHttpResponse = initialHttpResponse;
256 // Wait for the response from SO
257 for (int attemptsLeft = GET_REQUESTS_BEFORE_TIMEOUT; attemptsLeft >= 0; attemptsLeft--) {
258 // The SO request may have completed even on the first request so we check the
261 // issuing any other requests
262 if (isRequestStateFinished(latestHttpResponse, response)) {
266 // Wait for the defined interval before issuing a get
268 Thread.sleep(restGetTimeout);
269 } catch (InterruptedException e) {
270 logger.error("Interrupted exception: ", e);
271 Thread.currentThread().interrupt();
272 response.setHttpResponseCode(SO_RESPONSE_ERROR);
276 // Issue a GET to find the current status of our request
277 NetLoggerUtil.getNetworkLogger().info("[OUT|{}|{}|{}|{}|{}|{}|]{}", "SO", urlGet, username, password,
278 createSimpleHeaders(), MEDIA_TYPE, LINE_SEPARATOR);
279 Pair<Integer, String> httpResponse = restManager.get(urlGet, username, password, createSimpleHeaders());
282 response = processSoResponse(urlGet, httpResponse);
283 if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
287 // Our latest HTTP response code
288 latestHttpResponse = httpResponse;
291 // We have timed out on the SO request
292 response.setHttpResponseCode(SO_RESPONSE_ERROR);
297 * Parse the response message from SO into a SOResponse object.
299 * @param requestURL The URL of the HTTP request
300 * @param httpResponse The HTTP message returned from SO
301 * @return The parsed response
303 private SoResponse processSoResponse(final String requestUrl, final Pair<Integer, String> httpResponse) {
304 SoResponse response = new SoResponse();
306 // A null httpDetails indicates a HTTP problem, a valid response from SO must be
309 if (!httpResultIsNullFree(httpResponse) || (httpResponse.first != 200 && httpResponse.first != 202)) {
310 logger.error("Invalid HTTP response received from SO");
311 response.setHttpResponseCode(SO_RESPONSE_ERROR);
315 // Parse the JSON of the response into our POJO
317 response = Serialization.gsonPretty.fromJson(httpResponse.second, SoResponse.class);
318 } catch (JsonSyntaxException e) {
319 logger.error("Failed to deserialize HTTP response into SOResponse: ", e);
320 response.setHttpResponseCode(SO_RESPONSE_ERROR);
324 // Set the HTTP response code of the response if needed
325 if (response.getHttpResponseCode() == 0) {
326 response.setHttpResponseCode(httpResponse.first);
329 NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, requestUrl, httpResponse.second);
331 if (logger.isDebugEnabled()) {
332 logger.debug("***** Response to SO Request to URL {}:", requestUrl);
333 logger.debug(httpResponse.second);
340 * Method to allow tuning of REST get timeout.
342 * @param restGetTimeout the timeout value
344 protected void setRestGetTimeout(final long restGetTimeout) {
345 this.restGetTimeout = restGetTimeout;
349 * Check that the request state of a response is defined.
351 * @param response The response to check
352 * @return true if the request for the response is defined
354 private boolean isRequestStateDefined(final SoResponse response) {
355 return response != null && response.getRequest() != null && response.getRequest().getRequestStatus() != null
356 && response.getRequest().getRequestStatus().getRequestState() != null;
360 * Check that the request state of a response is finished.
362 * @param latestHttpDetails the HTTP details of the response
363 * @param response The response to check
364 * @return true if the request for the response is finished
366 private boolean isRequestStateFinished(final Pair<Integer, String> latestHttpDetails, final SoResponse response) {
367 if (latestHttpDetails != null && 200 == latestHttpDetails.first && isRequestStateDefined(response)) {
368 String requestState = response.getRequest().getRequestStatus().getRequestState();
369 return "COMPLETE".equalsIgnoreCase(requestState) || "FAILED".equalsIgnoreCase(requestState);
376 * Check that a HTTP operation result has no nulls.
378 * @param httpOperationResult the result to check
379 * @return true if no nulls are found
381 private boolean httpResultIsNullFree(Pair<Integer, String> httpOperationResult) {
382 return httpOperationResult != null && httpOperationResult.first != null && httpOperationResult.second != null;
386 * Create simple HTTP headers for unauthenticated requests to SO.
388 * @return the HTTP headers
390 private Map<String, String> createSimpleHeaders() {
391 Map<String, String> headers = new HashMap<>();
392 headers.put("Accept", MEDIA_TYPE);