2 * ============LICENSE_START=======================================================
4 * ================================================================================
5 * Copyright (C) 2021 Huawei Technologies.
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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=========================================================
21 package org.onap.so.apihandlerinfra;
23 import com.fasterxml.jackson.core.JsonProcessingException;
24 import com.fasterxml.jackson.databind.ObjectMapper;
25 import io.swagger.v3.oas.annotations.OpenAPIDefinition;
26 import io.swagger.v3.oas.annotations.Operation;
27 import io.swagger.v3.oas.annotations.info.Info;
28 import io.swagger.v3.oas.annotations.media.ArraySchema;
29 import io.swagger.v3.oas.annotations.media.Content;
30 import io.swagger.v3.oas.annotations.media.Schema;
31 import io.swagger.v3.oas.annotations.responses.ApiResponse;
32 import org.apache.http.HttpStatus;
33 import org.onap.logging.filter.base.ErrorCode;
34 import org.onap.so.apihandler.camundabeans.CamundaResponse;
35 import org.onap.so.apihandler.common.CamundaClient;
36 import org.onap.so.apihandler.common.ErrorNumbers;
37 import org.onap.so.apihandler.common.RequestClientParameter;
38 import org.onap.so.apihandler.common.ResponseBuilder;
39 import org.onap.so.apihandler.common.ResponseHandler;
40 import org.onap.so.apihandlerinfra.exceptions.ApiException;
41 import org.onap.so.apihandlerinfra.exceptions.BPMNFailureException;
42 import org.onap.so.apihandlerinfra.exceptions.RequestDbFailureException;
43 import org.onap.so.apihandlerinfra.exceptions.ValidateException;
44 import org.onap.so.apihandlerinfra.logging.ErrorLoggerInfo;
45 import org.onap.so.apihandlerinfra.serviceintentinstancebeans.ServiceIntentCommonRequest;
46 import org.onap.so.apihandlerinfra.serviceintentinstancebeans.ServiceIntentCreationRequest;
47 import org.onap.so.apihandlerinfra.serviceintentinstancebeans.ServiceIntentDeletionRequest;
48 import org.onap.so.apihandlerinfra.serviceintentinstancebeans.ServiceIntentModificationRequest;
49 import org.onap.so.constants.Status;
50 import org.onap.so.db.catalog.beans.Service;
51 import org.onap.so.db.catalog.beans.ServiceRecipe;
52 import org.onap.so.db.catalog.client.CatalogDbClient;
53 import org.onap.so.db.request.beans.InfraActiveRequests;
54 import org.onap.so.db.request.client.RequestsDbClient;
55 import org.onap.so.logger.LogConstants;
56 import org.onap.so.logger.LoggingAnchor;
57 import org.onap.so.logger.MessageEnum;
58 import org.onap.so.serviceinstancebeans.ModelType;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
62 import org.springframework.beans.factory.annotation.Autowired;
63 import org.springframework.beans.factory.annotation.Value;
64 import org.springframework.http.ResponseEntity;
65 import org.springframework.stereotype.Component;
66 import javax.ws.rs.Consumes;
67 import javax.ws.rs.DELETE;
68 import javax.ws.rs.POST;
69 import javax.ws.rs.PUT;
70 import javax.ws.rs.Path;
71 import javax.ws.rs.PathParam;
72 import javax.ws.rs.Produces;
73 import javax.ws.rs.container.ContainerRequestContext;
74 import javax.ws.rs.core.Context;
75 import javax.ws.rs.core.MediaType;
76 import javax.ws.rs.core.Response;
77 import java.sql.Timestamp;
78 import java.util.HashMap;
79 import java.util.function.Function;
82 * This class serves as the entry point for Service Intent APIs. Unlike User Intent, which describes Intent using
83 * natural languages, Service Intent describes Intent through technology-agnostic and model-driven APIs. The Service
84 * Intent APIs have the following format: {service-Intent-Root}/{operation}, where {operation} may be "create",
85 * "delete", "modify", etc. And the parameters of the Intent service instance are specified in the payloads of the APIs.
87 * For scalability, these APIs are designed to be generic, and thus support all the service Intent use-cases. i.e., The
88 * actual intent use-case/application, e.g., Cloud Leased Line, is differentiated by the "serviceType" parameter in the
89 * payload, rather than by specific APIs. Thus, this class does not need to grow when we add new Intent use-cases or
94 @Path("/onap/so/infra/serviceIntent")
95 @OpenAPIDefinition(info = @Info(title = "/onap/so/infra/serviceIntent",
96 description = "API Requests for Intent services and " + "applications"))
97 public class ServiceIntentApiHandler {
99 private static final Logger logger = LoggerFactory.getLogger(ServiceIntentApiHandler.class);
101 private static final String MSO_PROP_APIHANDLER_INFRA = "MSO_PROP_APIHANDLER_INFRA";
103 private static final String END_OF_THE_TRANSACTION = "End of the transaction, the final response is: ";
105 private static final String SAVE_TO_DB = "save instance to db";
107 private static final String URI_PREFIX = "/serviceIntent/";
110 private MsoRequest msoRequest;
113 private CatalogDbClient catalogDbClient;
116 private RequestsDbClient requestsDbClient;
119 private RequestHandlerUtils requestHandlerUtils;
122 private ResponseBuilder builder;
125 private CamundaClient camundaClient;
128 private ResponseHandler responseHandler;
130 // @Value("${serviceIntent.config.file}")
131 // private String serviceIntentConfigFile;
134 * POST Requests for create Service Intent Instance on a version provided
136 * @throws ApiException
140 @Path("/{version:[vV][1]}/create")
141 @Consumes(MediaType.APPLICATION_JSON)
142 @Produces(MediaType.APPLICATION_JSON)
143 @Operation(description = "Create a SI Instance on a version provided", responses = @ApiResponse(
144 content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))))
145 public Response createServiceIntentInstance(ServiceIntentCreationRequest request,
146 @PathParam("version") String version, @Context ContainerRequestContext requestContext) throws ApiException {
147 String requestId = requestHandlerUtils.getRequestId(requestContext);
148 return processServiceIntentRequest(request, Action.createInstance, version, requestId, null,
149 requestHandlerUtils.getRequestUri(requestContext, URI_PREFIX));
153 * PUT Requests for Service Intent Modification on a version provided
155 * @throws ApiException
159 @Path("/{version:[vV][1]}/modify")
160 @Consumes(MediaType.APPLICATION_JSON)
161 @Produces(MediaType.APPLICATION_JSON)
162 @Operation(description = "Modify a SI Instance on a version provided", responses = @ApiResponse(
163 content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))))
164 public Response modifyServiceIntentInstance(ServiceIntentModificationRequest request,
165 @PathParam("version") String version, @Context ContainerRequestContext requestContext) throws ApiException {
166 String requestId = requestHandlerUtils.getRequestId(requestContext);
167 HashMap<String, String> instanceIdMap = new HashMap<>();
168 instanceIdMap.put("serviceInstanceId", request.getServiceInstanceID());
169 return processServiceIntentRequest(request, Action.updateInstance, version, requestId, instanceIdMap,
170 requestHandlerUtils.getRequestUri(requestContext, URI_PREFIX));
174 * DELETE Requests for Service Intent Instance on a specified version
176 * @throws ApiException
180 @Path("/{version:[vV][1]}/delete")
181 @Consumes(MediaType.APPLICATION_JSON)
182 @Produces(MediaType.APPLICATION_JSON)
183 @Operation(description = "Terminate/Delete a SI Service Instance on a version provided", responses = @ApiResponse(
184 content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))))
185 public Response deleteServiceIntentInstance(ServiceIntentDeletionRequest request,
186 @PathParam("version") String version, @Context ContainerRequestContext requestContext) throws ApiException {
187 String requestId = requestHandlerUtils.getRequestId(requestContext);
188 HashMap<String, String> instanceIdMap = new HashMap<>();
189 instanceIdMap.put("serviceInstanceId", request.getServiceInstanceID());
190 return processServiceIntentRequest(request, Action.deleteInstance, version, requestId, instanceIdMap,
191 requestHandlerUtils.getRequestUri(requestContext, URI_PREFIX));
195 * Process Service Intent request and send request to corresponding workflow
201 * @throws ApiException
203 private Response processServiceIntentRequest(ServiceIntentCommonRequest request, Action action, String version,
204 String requestId, HashMap<String, String> instanceIdMap, String requestUri) throws ApiException {
205 String defaultServiceModelName = "COMMON_SI_DEFAULT";
206 String requestScope = ModelType.service.name();
207 String apiVersion = version.substring(1);
208 String serviceRequestJson = toString.apply(request);
210 String instanceName = null;
211 String modelUuid = null;
212 String serviceInstanceId = null;
215 if (action == Action.createInstance) {
216 instanceName = ((ServiceIntentCreationRequest) request).getName();
217 modelUuid = ((ServiceIntentCreationRequest) request).getModelUuid();
218 } else if (action == Action.updateInstance) {
219 instanceName = ((ServiceIntentModificationRequest) request).getName();
220 serviceInstanceId = ((ServiceIntentModificationRequest) request).getServiceInstanceID();
221 } else if (action == Action.deleteInstance) {
222 serviceInstanceId = ((ServiceIntentDeletionRequest) request).getServiceInstanceID();
224 } catch (Exception e) {
225 logger.error("ERROR: processCllServiceRequest: Exception: ", e);
226 Response response = msoRequest.buildServiceErrorResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR,
227 MsoException.ServiceException, "processCllServiceRequest error", ErrorNumbers.SVC_BAD_PARAMETER,
232 if (serviceRequestJson != null) {
233 InfraActiveRequests currentActiveReq = createRequestObject(request, action, requestId, Status.IN_PROGRESS,
234 requestScope, serviceRequestJson);
236 requestHandlerUtils.checkForDuplicateRequests(action, instanceIdMap, requestScope, currentActiveReq,
239 requestsDbClient.save(currentActiveReq);
240 } catch (Exception e) {
241 logger.error("Exception occurred", e);
242 ErrorLoggerInfo errorLoggerInfo =
243 new ErrorLoggerInfo.Builder(MessageEnum.APIH_DB_ACCESS_EXC, ErrorCode.DataError)
244 .errorSource(Constants.MSO_PROP_APIHANDLER_INFRA).build();
245 throw new RequestDbFailureException.Builder(SAVE_TO_DB, e.toString(),
246 HttpStatus.SC_INTERNAL_SERVER_ERROR, ErrorNumbers.SVC_DETAILED_SERVICE_ERROR).cause(e)
247 .errorInfo(errorLoggerInfo).build();
250 RecipeLookupResult recipeLookupResult;
252 recipeLookupResult = getServiceInstanceOrchestrationURI(modelUuid, action, defaultServiceModelName);
253 } catch (Exception e) {
254 logger.error(LoggingAnchor.FOUR, MessageEnum.APIH_DB_ACCESS_EXC.toString(), MSO_PROP_APIHANDLER_INFRA,
255 ErrorCode.AvailabilityError.getValue(), "Exception while communicate with Catalog DB", e);
256 Response response = msoRequest.buildServiceErrorResponse(HttpStatus.SC_NOT_FOUND,
257 MsoException.ServiceException, "No " + "communication to catalog DB " + e.getMessage(),
258 ErrorNumbers.SVC_NO_SERVER_RESOURCES, null, version);
259 logger.debug(END_OF_THE_TRANSACTION + response.getEntity());
263 if (recipeLookupResult == null) {
264 logger.error(LoggingAnchor.FOUR, MessageEnum.APIH_DB_ATTRIBUTE_NOT_FOUND.toString(),
265 MSO_PROP_APIHANDLER_INFRA, ErrorCode.DataError.getValue(), "No recipe found in DB");
266 Response response = msoRequest.buildServiceErrorResponse(HttpStatus.SC_NOT_FOUND,
267 MsoException.ServiceException, "Recipe does " + "not exist in catalog DB",
268 ErrorNumbers.SVC_GENERAL_SERVICE_ERROR, null, version);
269 logger.debug(END_OF_THE_TRANSACTION + response.getEntity());
273 String serviceInstanceType = request.getSubscriptionServiceType();
274 RequestClientParameter parameter;
276 parameter = new RequestClientParameter.Builder().setRequestId(requestId).setBaseVfModule(false)
277 .setRecipeTimeout(recipeLookupResult.getRecipeTimeout()).setRequestAction(action.name())
278 .setServiceInstanceId(serviceInstanceId).setServiceType(serviceInstanceType)
279 .setRequestDetails(serviceRequestJson).setApiVersion(version).setALaCarte(false)
280 .setRecipeParamXsd(recipeLookupResult.getRecipeParamXsd()).setApiVersion(apiVersion).build();
281 } catch (Exception e) {
282 logger.error("Exception occurred", e);
283 ErrorLoggerInfo errorLoggerInfo =
284 new ErrorLoggerInfo.Builder(MessageEnum.APIH_BPEL_RESPONSE_ERROR, ErrorCode.SchemaError)
285 .errorSource(Constants.MSO_PROP_APIHANDLER_INFRA).build();
286 throw new ValidateException.Builder("Unable to generate RequestClientParameter object" + e.getMessage(),
287 HttpStatus.SC_INTERNAL_SERVER_ERROR, ErrorNumbers.SVC_BAD_PARAMETER).errorInfo(errorLoggerInfo)
290 return postBPELRequest(currentActiveReq, parameter, recipeLookupResult.getOrchestrationURI(), requestScope);
292 Response response = msoRequest.buildServiceErrorResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR,
293 MsoException.ServiceException, "JsonProcessingException occurred - " + "serviceRequestJson is null",
294 ErrorNumbers.SVC_BAD_PARAMETER, null, version);
300 * Getting recipes from catalogDb
302 * @param serviceModelUUID the service model version uuid
303 * @param action the action for the service
304 * @param defaultServiceModelName default service name
305 * @return the service recipe result
307 private RecipeLookupResult getServiceInstanceOrchestrationURI(String serviceModelUUID, Action action,
308 String defaultServiceModelName) {
310 RecipeLookupResult recipeLookupResult = getServiceURI(serviceModelUUID, action, defaultServiceModelName);
312 if (recipeLookupResult != null) {
313 logger.debug("Orchestration URI is: " + recipeLookupResult.getOrchestrationURI() + ", recipe Timeout is: "
314 + Integer.toString(recipeLookupResult.getRecipeTimeout()));
316 logger.debug("No matching recipe record found");
318 return recipeLookupResult;
322 * Getting recipes from catalogDb If Service recipe is not set, use default recipe, if set , use special recipe.
324 * @param serviceModelUUID the service version uuid
325 * @param action the action of the service.
326 * @param defaultServiceModelName default service name
327 * @return the service recipe result.
329 private RecipeLookupResult getServiceURI(String serviceModelUUID, Action action, String defaultServiceModelName) {
331 Service defaultServiceRecord =
332 catalogDbClient.getFirstByModelNameOrderByModelVersionDesc(defaultServiceModelName);
333 // set recipe as default generic recipe
334 ServiceRecipe recipe =
335 catalogDbClient.getFirstByServiceModelUUIDAndAction(defaultServiceRecord.getModelUUID(), action.name());
336 // check the service special recipe
337 if (null != serviceModelUUID && !serviceModelUUID.isEmpty()) {
338 ServiceRecipe serviceSpecialRecipe =
339 catalogDbClient.getFirstByServiceModelUUIDAndAction(serviceModelUUID, action.name());
340 if (null != serviceSpecialRecipe) {
341 // set service special recipe.
342 recipe = serviceSpecialRecipe;
346 if (recipe == null) {
349 return new RecipeLookupResult(recipe.getOrchestrationUri(), recipe.getRecipeTimeout(), recipe.getParamXsd());
353 Function<Object, String> toString = serviceRequest -> {
354 ObjectMapper mapper = new ObjectMapper();
355 String requestAsString = null;
357 requestAsString = mapper.writeValueAsString(serviceRequest);
358 } catch (JsonProcessingException e) {
359 logger.debug("Exception while converting service request object to String {}", e);
361 return requestAsString;
364 private InfraActiveRequests createRequestObject(ServiceIntentCommonRequest request, Action action, String requestId,
365 Status status, String requestScope, String requestJson) {
366 InfraActiveRequests aq = new InfraActiveRequests();
368 String serviceInstanceName = null;
369 String serviceInstanceId = null;
370 String serviceType = request.getServiceType();
371 if (action.name().equals("createInstance")) {
372 serviceInstanceName = ((ServiceIntentCreationRequest) request).getName();
373 aq.setServiceInstanceName(serviceInstanceName);
374 } else if (action.name().equals("updateInstance")) {
375 serviceInstanceName = ((ServiceIntentModificationRequest) request).getName();
376 serviceInstanceId = ((ServiceIntentModificationRequest) request).getServiceInstanceID();
377 aq.setServiceInstanceName(serviceInstanceName);
378 aq.setServiceInstanceId(serviceInstanceId);
379 } else if (action.name().equals("deleteInstance")) {
380 serviceInstanceId = ((ServiceIntentDeletionRequest) request).getServiceInstanceID();
381 aq.setServiceInstanceId(serviceInstanceId);
384 aq.setRequestId(requestId);
385 aq.setRequestAction(action.toString());
386 aq.setRequestUrl(MDC.get(LogConstants.HTTP_URL));
387 Timestamp startTimeStamp = new Timestamp(System.currentTimeMillis());
388 aq.setStartTime(startTimeStamp);
389 aq.setRequestScope(requestScope);
390 aq.setRequestBody(requestJson);
391 aq.setRequestStatus(status.toString());
392 aq.setLastModifiedBy(Constants.MODIFIED_BY_APIHANDLER);
393 aq.setServiceType(serviceType);
394 } catch (Exception e) {
395 logger.error("Exception when creation record request", e);
397 if (!status.equals(Status.FAILED)) {
404 private Response postBPELRequest(InfraActiveRequests currentActiveReq, RequestClientParameter parameter,
405 String orchestrationURI, String requestScope) throws ApiException {
406 ResponseEntity<String> response =
407 requestHandlerUtils.postRequest(currentActiveReq, parameter, orchestrationURI);
408 logger.debug("BPEL response : " + response);
409 int bpelStatus = responseHandler.setStatus(response.getStatusCodeValue());
412 responseHandler.acceptedResponse(response);
413 CamundaResponse camundaResponse = responseHandler.getCamundaResponse(response);
414 String responseBody = camundaResponse.getResponse();
415 if ("Success".equalsIgnoreCase(camundaResponse.getMessage())) {
416 jsonResponse = responseBody;
418 BPMNFailureException bpmnException =
419 new BPMNFailureException.Builder(String.valueOf(bpelStatus) + responseBody, bpelStatus,
420 ErrorNumbers.SVC_DETAILED_SERVICE_ERROR).build();
421 requestHandlerUtils.updateStatus(currentActiveReq, Status.FAILED, bpmnException.getMessage());
424 } catch (ApiException e) {
425 requestHandlerUtils.updateStatus(currentActiveReq, Status.FAILED, e.getMessage());
428 return builder.buildResponse(HttpStatus.SC_ACCEPTED, parameter.getRequestId(), jsonResponse,
429 parameter.getApiVersion());