3c84be33ad6fda8fbedd3adc47805483d7d38f11
[so.git] /
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     private static final ObjectMapper mapper = new ObjectMapper();
101
102     private static final String MSO_PROP_APIHANDLER_INFRA = "MSO_PROP_APIHANDLER_INFRA";
103
104     private static final String END_OF_THE_TRANSACTION = "End of the transaction, the final response is: ";
105
106     private static final String SAVE_TO_DB = "save instance to db";
107
108     private static final String URI_PREFIX = "/serviceIntent/";
109
110     @Autowired
111     private MsoRequest msoRequest;
112
113     @Autowired
114     private CatalogDbClient catalogDbClient;
115
116     @Autowired
117     private RequestsDbClient requestsDbClient;
118
119     @Autowired
120     private RequestHandlerUtils requestHandlerUtils;
121
122     @Autowired
123     private ResponseBuilder builder;
124
125     @Autowired
126     private CamundaClient camundaClient;
127
128     @Autowired
129     private ResponseHandler responseHandler;
130
131     // @Value("${serviceIntent.config.file}")
132     // private String serviceIntentConfigFile;
133
134     /**
135      * POST Requests for create Service Intent Instance on a version provided
136      *
137      * @throws ApiException
138      */
139
140     @POST
141     @Path("/{version:[vV][1]}/create")
142     @Consumes(MediaType.APPLICATION_JSON)
143     @Produces(MediaType.APPLICATION_JSON)
144     @Operation(description = "Create a SI Instance on a version provided", responses = @ApiResponse(
145             content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))))
146     public Response createServiceIntentInstance(ServiceIntentCreationRequest request,
147             @PathParam("version") String version, @Context ContainerRequestContext requestContext) throws ApiException {
148         String requestId = requestHandlerUtils.getRequestId(requestContext);
149         return processServiceIntentRequest(request, Action.createInstance, version, requestId, null,
150                 requestHandlerUtils.getRequestUri(requestContext, URI_PREFIX));
151     }
152
153     /**
154      * PUT Requests for Service Intent Modification on a version provided
155      *
156      * @throws ApiException
157      */
158
159     @PUT
160     @Path("/{version:[vV][1]}/modify")
161     @Consumes(MediaType.APPLICATION_JSON)
162     @Produces(MediaType.APPLICATION_JSON)
163     @Operation(description = "Modify a SI Instance on a version provided", responses = @ApiResponse(
164             content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))))
165     public Response modifyServiceIntentInstance(ServiceIntentModificationRequest request,
166             @PathParam("version") String version, @Context ContainerRequestContext requestContext) throws ApiException {
167         String requestId = requestHandlerUtils.getRequestId(requestContext);
168         HashMap<String, String> instanceIdMap = new HashMap<>();
169         instanceIdMap.put("serviceInstanceId", request.getServiceInstanceID());
170         return processServiceIntentRequest(request, Action.updateInstance, version, requestId, instanceIdMap,
171                 requestHandlerUtils.getRequestUri(requestContext, URI_PREFIX));
172     }
173
174     /**
175      * DELETE Requests for Service Intent Instance on a specified version
176      *
177      * @throws ApiException
178      */
179
180     @DELETE
181     @Path("/{version:[vV][1]}/delete")
182     @Consumes(MediaType.APPLICATION_JSON)
183     @Produces(MediaType.APPLICATION_JSON)
184     @Operation(description = "Terminate/Delete a SI Service Instance on a version provided", responses = @ApiResponse(
185             content = @Content(array = @ArraySchema(schema = @Schema(implementation = Response.class)))))
186     public Response deleteServiceIntentInstance(ServiceIntentDeletionRequest request,
187             @PathParam("version") String version, @Context ContainerRequestContext requestContext) throws ApiException {
188         String requestId = requestHandlerUtils.getRequestId(requestContext);
189         HashMap<String, String> instanceIdMap = new HashMap<>();
190         instanceIdMap.put("serviceInstanceId", request.getServiceInstanceID());
191         return processServiceIntentRequest(request, Action.deleteInstance, version, requestId, instanceIdMap,
192                 requestHandlerUtils.getRequestUri(requestContext, URI_PREFIX));
193     }
194
195     /**
196      * Process Service Intent request and send request to corresponding workflow
197      *
198      * @param request
199      * @param action
200      * @param version
201      * @return
202      * @throws ApiException
203      */
204     private Response processServiceIntentRequest(ServiceIntentCommonRequest request, Action action, String version,
205             String requestId, HashMap<String, String> instanceIdMap, String requestUri) throws ApiException {
206         String defaultServiceModelName = "COMMON_SI_DEFAULT";
207         String requestScope = ModelType.service.name();
208         String apiVersion = version.substring(1);
209         String serviceRequestJson = toString.apply(request);
210
211         String instanceName = null;
212         String modelUuid = null;
213         String serviceInstanceId = null;
214
215         try {
216             if (action == Action.createInstance) {
217                 instanceName = ((ServiceIntentCreationRequest) request).getName();
218                 modelUuid = ((ServiceIntentCreationRequest) request).getModelUuid();
219             } else if (action == Action.updateInstance) {
220                 instanceName = ((ServiceIntentModificationRequest) request).getName();
221                 serviceInstanceId = ((ServiceIntentModificationRequest) request).getServiceInstanceID();
222             } else if (action == Action.deleteInstance) {
223                 serviceInstanceId = ((ServiceIntentDeletionRequest) request).getServiceInstanceID();
224             }
225         } catch (Exception e) {
226             logger.error("ERROR: processCllServiceRequest: Exception: ", e);
227             Response response = msoRequest.buildServiceErrorResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR,
228                     MsoException.ServiceException, "processCllServiceRequest error", ErrorNumbers.SVC_BAD_PARAMETER,
229                     null, version);
230             return response;
231         }
232
233         if (serviceRequestJson != null) {
234             InfraActiveRequests currentActiveReq = createRequestObject(request, action, requestId, Status.IN_PROGRESS,
235                     requestScope, serviceRequestJson);
236
237             requestHandlerUtils.checkForDuplicateRequests(action, instanceIdMap, requestScope, currentActiveReq,
238                     instanceName);
239             try {
240                 requestsDbClient.save(currentActiveReq);
241             } catch (Exception e) {
242                 logger.error("Exception occurred", e);
243                 ErrorLoggerInfo errorLoggerInfo =
244                         new ErrorLoggerInfo.Builder(MessageEnum.APIH_DB_ACCESS_EXC, ErrorCode.DataError)
245                                 .errorSource(Constants.MSO_PROP_APIHANDLER_INFRA).build();
246                 throw new RequestDbFailureException.Builder(SAVE_TO_DB, e.toString(),
247                         HttpStatus.SC_INTERNAL_SERVER_ERROR, ErrorNumbers.SVC_DETAILED_SERVICE_ERROR).cause(e)
248                                 .errorInfo(errorLoggerInfo).build();
249             }
250
251             RecipeLookupResult recipeLookupResult;
252             try {
253                 recipeLookupResult = getServiceInstanceOrchestrationURI(modelUuid, action, defaultServiceModelName);
254             } catch (Exception e) {
255                 logger.error(LoggingAnchor.FOUR, MessageEnum.APIH_DB_ACCESS_EXC.toString(), MSO_PROP_APIHANDLER_INFRA,
256                         ErrorCode.AvailabilityError.getValue(), "Exception while communicate with Catalog DB", e);
257                 Response response = msoRequest.buildServiceErrorResponse(HttpStatus.SC_NOT_FOUND,
258                         MsoException.ServiceException, "No " + "communication to catalog DB " + e.getMessage(),
259                         ErrorNumbers.SVC_NO_SERVER_RESOURCES, null, version);
260                 logger.debug(END_OF_THE_TRANSACTION + response.getEntity());
261                 return response;
262             }
263
264             if (recipeLookupResult == null) {
265                 logger.error(LoggingAnchor.FOUR, MessageEnum.APIH_DB_ATTRIBUTE_NOT_FOUND.toString(),
266                         MSO_PROP_APIHANDLER_INFRA, ErrorCode.DataError.getValue(), "No recipe found in DB");
267                 Response response = msoRequest.buildServiceErrorResponse(HttpStatus.SC_NOT_FOUND,
268                         MsoException.ServiceException, "Recipe does " + "not exist in catalog DB",
269                         ErrorNumbers.SVC_GENERAL_SERVICE_ERROR, null, version);
270                 logger.debug(END_OF_THE_TRANSACTION + response.getEntity());
271                 return response;
272             }
273
274             String serviceInstanceType = request.getSubscriptionServiceType();
275             RequestClientParameter parameter;
276             try {
277                 parameter = new RequestClientParameter.Builder().setRequestId(requestId).setBaseVfModule(false)
278                         .setRecipeTimeout(recipeLookupResult.getRecipeTimeout()).setRequestAction(action.name())
279                         .setServiceInstanceId(serviceInstanceId).setServiceType(serviceInstanceType)
280                         .setRequestDetails(serviceRequestJson).setApiVersion(version).setALaCarte(false)
281                         .setRecipeParamXsd(recipeLookupResult.getRecipeParamXsd()).setApiVersion(apiVersion).build();
282             } catch (Exception e) {
283                 logger.error("Exception occurred", e);
284                 ErrorLoggerInfo errorLoggerInfo =
285                         new ErrorLoggerInfo.Builder(MessageEnum.APIH_BPEL_RESPONSE_ERROR, ErrorCode.SchemaError)
286                                 .errorSource(Constants.MSO_PROP_APIHANDLER_INFRA).build();
287                 throw new ValidateException.Builder("Unable to generate RequestClientParameter object" + e.getMessage(),
288                         HttpStatus.SC_INTERNAL_SERVER_ERROR, ErrorNumbers.SVC_BAD_PARAMETER).errorInfo(errorLoggerInfo)
289                                 .build();
290             }
291             return postBPELRequest(currentActiveReq, parameter, recipeLookupResult.getOrchestrationURI(), requestScope);
292         } else {
293             Response response = msoRequest.buildServiceErrorResponse(HttpStatus.SC_INTERNAL_SERVER_ERROR,
294                     MsoException.ServiceException, "JsonProcessingException occurred - " + "serviceRequestJson is null",
295                     ErrorNumbers.SVC_BAD_PARAMETER, null, version);
296             return response;
297         }
298     }
299
300     /**
301      * Getting recipes from catalogDb
302      *
303      * @param serviceModelUUID the service model version uuid
304      * @param action the action for the service
305      * @param defaultServiceModelName default service name
306      * @return the service recipe result
307      */
308     private RecipeLookupResult getServiceInstanceOrchestrationURI(String serviceModelUUID, Action action,
309             String defaultServiceModelName) {
310
311         RecipeLookupResult recipeLookupResult = getServiceURI(serviceModelUUID, action, defaultServiceModelName);
312
313         if (recipeLookupResult != null) {
314             logger.debug("Orchestration URI is: " + recipeLookupResult.getOrchestrationURI() + ", recipe Timeout is: "
315                     + Integer.toString(recipeLookupResult.getRecipeTimeout()));
316         } else {
317             logger.debug("No matching recipe record found");
318         }
319         return recipeLookupResult;
320     }
321
322     /**
323      * Getting recipes from catalogDb If Service recipe is not set, use default recipe, if set , use special recipe.
324      *
325      * @param serviceModelUUID the service version uuid
326      * @param action the action of the service.
327      * @param defaultServiceModelName default service name
328      * @return the service recipe result.
329      */
330     private RecipeLookupResult getServiceURI(String serviceModelUUID, Action action, String defaultServiceModelName) {
331
332         Service defaultServiceRecord =
333                 catalogDbClient.getFirstByModelNameOrderByModelVersionDesc(defaultServiceModelName);
334         // set recipe as default generic recipe
335         ServiceRecipe recipe =
336                 catalogDbClient.getFirstByServiceModelUUIDAndAction(defaultServiceRecord.getModelUUID(), action.name());
337         // check the service special recipe
338         if (null != serviceModelUUID && !serviceModelUUID.isEmpty()) {
339             ServiceRecipe serviceSpecialRecipe =
340                     catalogDbClient.getFirstByServiceModelUUIDAndAction(serviceModelUUID, action.name());
341             if (null != serviceSpecialRecipe) {
342                 // set service special recipe.
343                 recipe = serviceSpecialRecipe;
344             }
345         }
346
347         if (recipe == null) {
348             return null;
349         }
350         return new RecipeLookupResult(recipe.getOrchestrationUri(), recipe.getRecipeTimeout(), recipe.getParamXsd());
351
352     }
353
354     Function<Object, String> toString = serviceRequest -> {
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 }