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 {
51 // TODO remove this class
53 private static final Logger logger = LoggerFactory.getLogger(SoManager.class);
55 private static ExecutorService executors = Executors.newCachedThreadPool();
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();
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;
65 // The REST manager used for processing REST calls for this VFC manager
66 private RestManager restManager;
68 private long restGetTimeout = GET_REQUEST_WAIT_INTERVAL;
72 private String password;
75 public interface SoCallback {
76 public void onSoResponseWrapper(SoResponseWrapper wrapper);
80 * Default constructor.
82 public SoManager(String url, String user, String password) {
85 this.password = password;
86 restManager = new RestManager();
90 * Create a service instance in SO.
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
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);
108 // Process the response from SO
109 SoResponse response = waitForSoOperationCompletion(urlBase, username, password, url, httpResponse);
110 if (SO_RESPONSE_ERROR != response.getHttpResponseCode()) {
118 * Works just like SOManager#asyncSORestCall(String, WorkingMemory, String, String, String, SORequest)
119 * except the vfModuleInstanceId is always null.
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);
129 * This method makes an asynchronous Rest call to MSO and inserts the response into
130 * Drools working memory.
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
138 * @param request the SO request
139 * @return a concurrent Future for the thread that handles the request
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));
152 * This class handles an asynchronous request to SO as a thread.
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;
163 final String password;
166 * Constructor, sets the context of the request.
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
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;
192 * Process the asynchronous SO request.
195 public SoResponse call() {
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;
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(),
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(),
220 // Process the response from SO
221 SoResponse response = waitForSoOperationCompletion(this.baseUrl, this.user, this.password, initialUrl,
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);
235 * Wait for the SO operation we have ordered to complete.
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
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()) {
253 // The SO URL to use to get the status of orchestration requests
254 String urlGet = urlBaseSo + "/orchestrationRequests/v5/" + response.getRequestReferences().getRequestId();
256 // The HTTP status code of the latest response
257 Pair<Integer, String> latestHttpResponse = initialHttpResponse;
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
264 // issuing any other requests
265 if (isRequestStateFinished(latestHttpResponse, response)) {
269 // Wait for the defined interval before issuing a get
271 Thread.sleep(restGetTimeout);
272 } catch (InterruptedException e) {
273 logger.error("Interrupted exception: ", e);
274 Thread.currentThread().interrupt();
275 response.setHttpResponseCode(SO_RESPONSE_ERROR);
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());
285 response = processSoResponse(urlGet, httpResponse);
286 if (SO_RESPONSE_ERROR == response.getHttpResponseCode()) {
290 // Our latest HTTP response code
291 latestHttpResponse = httpResponse;
294 // We have timed out on the SO request
295 response.setHttpResponseCode(SO_RESPONSE_ERROR);
300 * Parse the response message from SO into a SOResponse object.
302 * @param requestURL The URL of the HTTP request
303 * @param httpResponse The HTTP message returned from SO
304 * @return The parsed response
306 private SoResponse processSoResponse(final String requestUrl, final Pair<Integer, String> httpResponse) {
307 SoResponse response = new SoResponse();
309 // A null httpDetails indicates a HTTP problem, a valid response from SO must be
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);
318 // Parse the JSON of the response into our POJO
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);
327 // Set the HTTP response code of the response if needed
328 if (response.getHttpResponseCode() == 0) {
329 response.setHttpResponseCode(httpResponse.first);
332 NetLoggerUtil.log(EventType.IN, CommInfrastructure.REST, requestUrl, httpResponse.second);
334 if (logger.isDebugEnabled()) {
335 logger.debug("***** Response to SO Request to URL {}:", requestUrl);
336 logger.debug(httpResponse.second);
343 * Method to allow tuning of REST get timeout.
345 * @param restGetTimeout the timeout value
347 protected void setRestGetTimeout(final long restGetTimeout) {
348 this.restGetTimeout = restGetTimeout;
352 * Check that the request state of a response is defined.
354 * @param response The response to check
355 * @return true if the request for the response is defined
357 private boolean isRequestStateDefined(final SoResponse response) {
358 return response != null && response.getRequest() != null && response.getRequest().getRequestStatus() != null
359 && response.getRequest().getRequestStatus().getRequestState() != null;
363 * Check that the request state of a response is finished.
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
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);
379 * Check that a HTTP operation result has no nulls.
381 * @param httpOperationResult the result to check
382 * @return true if no nulls are found
384 private boolean httpResultIsNullFree(Pair<Integer, String> httpOperationResult) {
385 return httpOperationResult != null && httpOperationResult.first != null && httpOperationResult.second != null;
389 * Create simple HTTP headers for unauthenticated requests to SO.
391 * @return the HTTP headers
393 private Map<String, String> createSimpleHeaders() {
394 Map<String, String> headers = new HashMap<>();
395 headers.put("Accept", MEDIA_TYPE);