SO changes for Service Intent
[so.git] / mso-api-handlers / mso-api-handler-infra / src / main / java / org / onap / so / apihandlerinfra / ServiceIntentApiHandler.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * ONAP - SO
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
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
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=========================================================
19  */
20
21 package org.onap.so.apihandlerinfra;
22
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;
61 import org.slf4j.MDC;
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;
80
81 /**
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.
86  * <p>
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
90  * applications.
91  * <p>
92  */
93 @Component
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 {
98
99     private static final Logger logger = LoggerFactory.getLogger(ServiceIntentApiHandler.class);
100
101     private static final String MSO_PROP_APIHANDLER_INFRA = "MSO_PROP_APIHANDLER_INFRA";
102
103     private static final String END_OF_THE_TRANSACTION = "End of the transaction, the final response is: ";
104
105     private static final String SAVE_TO_DB = "save instance to db";
106
107     private static final String URI_PREFIX = "/serviceIntent/";
108
109     @Autowired
110     private MsoRequest msoRequest;
111
112     @Autowired
113     private CatalogDbClient catalogDbClient;
114
115     @Autowired
116     private RequestsDbClient requestsDbClient;
117
118     @Autowired
119     private RequestHandlerUtils requestHandlerUtils;
120
121     @Autowired
122     private ResponseBuilder builder;
123
124     @Autowired
125     private CamundaClient camundaClient;
126
127     @Autowired
128     private ResponseHandler responseHandler;
129
130     // @Value("${serviceIntent.config.file}")
131     // private String serviceIntentConfigFile;
132
133     /**
134      * POST Requests for create Service Intent Instance on a version provided
135      *
136      * @throws ApiException
137      */
138
139     @POST
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));
150     }
151
152     /**
153      * PUT Requests for Service Intent Modification on a version provided
154      *
155      * @throws ApiException
156      */
157
158     @PUT
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));
171     }
172
173     /**
174      * DELETE Requests for Service Intent Instance on a specified version
175      *
176      * @throws ApiException
177      */
178
179     @DELETE
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));
192     }
193
194     /**
195      * Process Service Intent request and send request to corresponding workflow
196      *
197      * @param request
198      * @param action
199      * @param version
200      * @return
201      * @throws ApiException
202      */
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);
209
210         String instanceName = null;
211         String modelUuid = null;
212         String serviceInstanceId = null;
213
214         try {
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();
223             }
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,
228                     null, version);
229             return response;
230         }
231
232         if (serviceRequestJson != null) {
233             InfraActiveRequests currentActiveReq = createRequestObject(request, action, requestId, Status.IN_PROGRESS,
234                     requestScope, serviceRequestJson);
235
236             requestHandlerUtils.checkForDuplicateRequests(action, instanceIdMap, requestScope, currentActiveReq,
237                     instanceName);
238             try {
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();
248             }
249
250             RecipeLookupResult recipeLookupResult;
251             try {
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());
260                 return response;
261             }
262
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());
270                 return response;
271             }
272
273             String serviceInstanceType = request.getSubscriptionServiceType();
274             RequestClientParameter parameter;
275             try {
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)
288                                 .build();
289             }
290             return postBPELRequest(currentActiveReq, parameter, recipeLookupResult.getOrchestrationURI(), requestScope);
291         } else {
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);
295             return response;
296         }
297     }
298
299     /**
300      * Getting recipes from catalogDb
301      *
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
306      */
307     private RecipeLookupResult getServiceInstanceOrchestrationURI(String serviceModelUUID, Action action,
308             String defaultServiceModelName) {
309
310         RecipeLookupResult recipeLookupResult = getServiceURI(serviceModelUUID, action, defaultServiceModelName);
311
312         if (recipeLookupResult != null) {
313             logger.debug("Orchestration URI is: " + recipeLookupResult.getOrchestrationURI() + ", recipe Timeout is: "
314                     + Integer.toString(recipeLookupResult.getRecipeTimeout()));
315         } else {
316             logger.debug("No matching recipe record found");
317         }
318         return recipeLookupResult;
319     }
320
321     /**
322      * Getting recipes from catalogDb If Service recipe is not set, use default recipe, if set , use special recipe.
323      *
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.
328      */
329     private RecipeLookupResult getServiceURI(String serviceModelUUID, Action action, String defaultServiceModelName) {
330
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;
343             }
344         }
345
346         if (recipe == null) {
347             return null;
348         }
349         return new RecipeLookupResult(recipe.getOrchestrationUri(), recipe.getRecipeTimeout(), recipe.getParamXsd());
350
351     }
352
353     Function<Object, String> toString = serviceRequest -> {
354         ObjectMapper mapper = new ObjectMapper();
355         String requestAsString = null;
356         try {
357             requestAsString = mapper.writeValueAsString(serviceRequest);
358         } catch (JsonProcessingException e) {
359             logger.debug("Exception while converting service request object to String {}", e);
360         }
361         return requestAsString;
362     };
363
364     private InfraActiveRequests createRequestObject(ServiceIntentCommonRequest request, Action action, String requestId,
365             Status status, String requestScope, String requestJson) {
366         InfraActiveRequests aq = new InfraActiveRequests();
367         try {
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);
382             }
383
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);
396
397             if (!status.equals(Status.FAILED)) {
398                 throw e;
399             }
400         }
401         return aq;
402     }
403
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());
410         String jsonResponse;
411         try {
412             responseHandler.acceptedResponse(response);
413             CamundaResponse camundaResponse = responseHandler.getCamundaResponse(response);
414             String responseBody = camundaResponse.getResponse();
415             if ("Success".equalsIgnoreCase(camundaResponse.getMessage())) {
416                 jsonResponse = responseBody;
417             } else {
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());
422                 throw bpmnException;
423             }
424         } catch (ApiException e) {
425             requestHandlerUtils.updateStatus(currentActiveReq, Status.FAILED, e.getMessage());
426             throw e;
427         }
428         return builder.buildResponse(HttpStatus.SC_ACCEPTED, parameter.getRequestId(), jsonResponse,
429                 parameter.getApiVersion());
430     }
431 }