2  * ============LICENSE_START=======================================================
 
   3  *  Copyright (C) 2020 Nordix Foundation.
 
   4  * ================================================================================
 
   5  * Licensed under the Apache License, Version 2.0 (the "License");
 
   6  * you may not use this file except in compliance with the License.
 
   7  * You may obtain a copy of the License at
 
   9  *      http://www.apache.org/licenses/LICENSE-2.0
 
  11  * Unless required by applicable law or agreed to in writing, software
 
  12  * distributed under the License is distributed on an "AS IS" BASIS,
 
  13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
  14  * See the License for the specific language governing permissions and
 
  15  * limitations under the License.
 
  17  * SPDX-License-Identifier: Apache-2.0
 
  18  * ============LICENSE_END=========================================================
 
  20 package org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.service;
 
  22 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.CamundaVariableNameConstants.CREATE_NS_REQUEST_PARAM_NAME;
 
  23 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.CamundaVariableNameConstants.GLOBAL_CUSTOMER_ID_PARAM_NAME;
 
  24 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.CamundaVariableNameConstants.INSTANTIATE_NS_REQUEST_PARAM_NAME;
 
  25 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.CamundaVariableNameConstants.JOB_ID_PARAM_NAME;
 
  26 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.CamundaVariableNameConstants.NS_INSTANCE_ID_PARAM_NAME;
 
  27 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.CamundaVariableNameConstants.OCC_ID_PARAM_NAME;
 
  28 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.CamundaVariableNameConstants.SERVICE_TYPE_PARAM_NAME;
 
  29 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.CamundaVariableNameConstants.TERMINATE_NS_REQUEST_PARAM_NAME;
 
  30 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.Constants.CREATE_NS_WORKFLOW_NAME;
 
  31 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.Constants.DELETE_NS_WORKFLOW_NAME;
 
  32 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.Constants.INSTANTIATE_NS_WORKFLOW_NAME;
 
  33 import static org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.Constants.TERMINATE_NS_WORKFLOW_NAME;
 
  34 import static org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobAction.DELETE;
 
  35 import static org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobAction.INSTANTIATE;
 
  36 import static org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobAction.TERMINATE;
 
  37 import static org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobStatusEnum.ERROR;
 
  38 import static org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobStatusEnum.FINISHED;
 
  39 import static org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobStatusEnum.FINISHED_WITH_ERROR;
 
  40 import static org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobStatusEnum.IN_PROGRESS;
 
  41 import static org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobStatusEnum.STARTING;
 
  42 import static org.slf4j.LoggerFactory.getLogger;
 
  43 import java.time.Instant;
 
  44 import java.time.LocalDateTime;
 
  45 import java.util.HashMap;
 
  47 import java.util.Optional;
 
  48 import java.util.concurrent.TimeUnit;
 
  49 import org.apache.commons.lang3.tuple.ImmutablePair;
 
  50 import org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.GsonProvider;
 
  51 import org.onap.so.etsi.nfvo.ns.lcm.bpmn.flows.exceptions.NsRequestProcessingException;
 
  52 import org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobAction;
 
  53 import org.onap.so.etsi.nfvo.ns.lcm.database.beans.JobStatusEnum;
 
  54 import org.onap.so.etsi.nfvo.ns.lcm.database.beans.NfvoJob;
 
  55 import org.onap.so.etsi.nfvo.ns.lcm.database.beans.NfvoNsInst;
 
  56 import org.onap.so.etsi.nfvo.ns.lcm.database.beans.NsLcmOpOcc;
 
  57 import org.onap.so.etsi.nfvo.ns.lcm.database.beans.NsLcmOpType;
 
  58 import org.onap.so.etsi.nfvo.ns.lcm.database.beans.OperationStateEnum;
 
  59 import org.onap.so.etsi.nfvo.ns.lcm.database.beans.State;
 
  60 import org.onap.so.etsi.nfvo.ns.lcm.database.service.DatabaseServiceProvider;
 
  61 import org.onap.so.etsi.nfvo.ns.lcm.model.CreateNsRequest;
 
  62 import org.onap.so.etsi.nfvo.ns.lcm.model.InlineResponse400;
 
  63 import org.onap.so.etsi.nfvo.ns.lcm.model.InstantiateNsRequest;
 
  64 import org.onap.so.etsi.nfvo.ns.lcm.model.NsInstancesNsInstance;
 
  65 import org.onap.so.etsi.nfvo.ns.lcm.model.TerminateNsRequest;
 
  66 import org.slf4j.Logger;
 
  67 import org.springframework.beans.factory.annotation.Autowired;
 
  68 import org.springframework.beans.factory.annotation.Value;
 
  69 import org.springframework.stereotype.Service;
 
  70 import com.google.common.collect.ImmutableSet;
 
  71 import com.google.gson.Gson;
 
  74  * @author Waqas Ikram (waqas.ikram@est.tech)
 
  78 public class JobExecutorService {
 
  80     private static final Logger logger = getLogger(JobExecutorService.class);
 
  82     private static final ImmutableSet<JobStatusEnum> JOB_FINISHED_STATES =
 
  83             ImmutableSet.of(FINISHED, ERROR, FINISHED_WITH_ERROR);
 
  85     private static final int SLEEP_TIME_IN_SECONDS = 5;
 
  87     @Value("${so-etsi-ns-lcm-workflow-engine.requesttimeout.timeoutInSeconds:300}")
 
  88     private int timeOutInSeconds;
 
  90     private final DatabaseServiceProvider databaseServiceProvider;
 
  91     private final WorkflowExecutorService workflowExecutorService;
 
  92     private final WorkflowQueryService workflowQueryService;
 
  96     public JobExecutorService(final DatabaseServiceProvider databaseServiceProvider,
 
  97             final WorkflowExecutorService workflowExecutorService, final WorkflowQueryService workflowQueryService,
 
  98             final GsonProvider gsonProvider) {
 
  99         this.databaseServiceProvider = databaseServiceProvider;
 
 100         this.workflowExecutorService = workflowExecutorService;
 
 101         this.workflowQueryService = workflowQueryService;
 
 102         gson = gsonProvider.getGson();
 
 105     public NsInstancesNsInstance runCreateNsJob(final CreateNsRequest createNsRequest, final String globalCustomerId,
 
 106             final String serviceType) {
 
 107         logger.info("Starting 'Create NS' workflow job for request:\n{}", createNsRequest);
 
 108         final NfvoJob newJob = new NfvoJob().startTime(LocalDateTime.now()).jobType("NS").jobAction(JobAction.CREATE)
 
 109                 .resourceId(createNsRequest.getNsdId()).resourceName(createNsRequest.getNsName())
 
 110                 .status(JobStatusEnum.STARTING).progress(5);
 
 111         databaseServiceProvider.addJob(newJob);
 
 113         logger.info("New job created in database :\n{}", newJob);
 
 115         workflowExecutorService.executeWorkflow(newJob.getJobId(), CREATE_NS_WORKFLOW_NAME,
 
 116                 getVariables(newJob.getJobId(), createNsRequest, globalCustomerId, serviceType));
 
 118         final ImmutablePair<String, JobStatusEnum> immutablePair =
 
 119                 waitForJobToFinish(newJob.getJobId(), JOB_FINISHED_STATES);
 
 121         if (immutablePair.getRight() == null) {
 
 122             final String message = "Failed to create NS for request: \n" + createNsRequest;
 
 123             logger.error(message);
 
 124             throw new NsRequestProcessingException(message);
 
 126         final JobStatusEnum finalJobStatus = immutablePair.getRight();
 
 127         final String processInstanceId = immutablePair.getLeft();
 
 129         if (!FINISHED.equals(finalJobStatus)) {
 
 131             final Optional<InlineResponse400> optional = workflowQueryService.getProblemDetails(processInstanceId);
 
 132             if (optional.isPresent()) {
 
 133                 final InlineResponse400 problemDetails = optional.get();
 
 134                 final String message =
 
 135                         "Failed to create NS for request: \n" + createNsRequest + " due to \n" + problemDetails;
 
 136                 logger.error(message);
 
 137                 throw new NsRequestProcessingException(message, problemDetails);
 
 140             final String message = "Received unexpected Job Status: " + finalJobStatus
 
 141                     + " Failed to Create NS for request: \n" + createNsRequest;
 
 142             logger.error(message);
 
 143             throw new NsRequestProcessingException(message);
 
 146         logger.debug("Will query for CreateNsResponse using processInstanceId:{}", processInstanceId);
 
 147         final Optional<NsInstancesNsInstance> optional = workflowQueryService.getCreateNsResponse(processInstanceId);
 
 148         if (optional.isEmpty()) {
 
 149             final String message =
 
 150                     "Unable to find CreateNsReponse in Camunda History for process instance: " + processInstanceId;
 
 151             logger.error(message);
 
 152             throw new NsRequestProcessingException(message);
 
 154         return optional.get();
 
 157     public String runInstantiateNsJob(final String nsInstanceId, final InstantiateNsRequest instantiateNsRequest) {
 
 159         final NfvoJob newJob = new NfvoJob().startTime(LocalDateTime.now()).jobType("NS").jobAction(INSTANTIATE)
 
 160                 .resourceId(nsInstanceId).status(STARTING).progress(0);
 
 161         databaseServiceProvider.addJob(newJob);
 
 162         logger.info("New job created in database :\n{}", newJob);
 
 164         final LocalDateTime currentDateTime = LocalDateTime.now();
 
 165         final NsLcmOpOcc newNsLcmOpOcc = new NsLcmOpOcc().id(nsInstanceId).operation(NsLcmOpType.INSTANTIATE)
 
 166                 .operationState(OperationStateEnum.PROCESSING).stateEnteredTime(currentDateTime)
 
 167                 .startTime(currentDateTime).nfvoNsInst(getNfvoNsInst(nsInstanceId)).isAutoInvocation(false)
 
 168                 .isCancelPending(false).operationParams(gson.toJson(instantiateNsRequest));
 
 169         databaseServiceProvider.addNSLcmOpOcc(newNsLcmOpOcc);
 
 170         logger.info("New NSLcmOpOcc created in database :\n{}", newNsLcmOpOcc);
 
 172         workflowExecutorService.executeWorkflow(newJob.getJobId(), INSTANTIATE_NS_WORKFLOW_NAME,
 
 173                 getVariables(nsInstanceId, newJob.getJobId(), newNsLcmOpOcc.getId(), instantiateNsRequest));
 
 175         final ImmutableSet<JobStatusEnum> jobFinishedStates =
 
 176                 ImmutableSet.of(FINISHED, ERROR, FINISHED_WITH_ERROR, IN_PROGRESS);
 
 177         final ImmutablePair<String, JobStatusEnum> immutablePair =
 
 178                 waitForJobToFinish(newJob.getJobId(), jobFinishedStates);
 
 180         if (immutablePair.getRight() == null) {
 
 181             final String message = "Failed to Instantiate NS for request: \n" + instantiateNsRequest;
 
 182             logger.error(message);
 
 183             throw new NsRequestProcessingException(message);
 
 186         final JobStatusEnum finalJobStatus = immutablePair.getRight();
 
 188         if (IN_PROGRESS.equals(finalJobStatus) || FINISHED.equals(finalJobStatus)) {
 
 189             logger.info("Instantiation Job status: {}", finalJobStatus);
 
 192             return newNsLcmOpOcc.getId();
 
 195         final String message = "Received unexpected Job Status: " + finalJobStatus
 
 196                 + " Failed to instantiate NS for request: \n" + instantiateNsRequest;
 
 197         logger.error(message);
 
 198         throw new NsRequestProcessingException(message);
 
 201     public String runTerminateNsJob(final String nsInstanceId, final TerminateNsRequest terminateNsRequest) {
 
 202         doInitialTerminateChecks(nsInstanceId, terminateNsRequest);
 
 204         final NfvoJob nfvoJob = new NfvoJob().startTime(LocalDateTime.now()).jobType("NS").jobAction(TERMINATE)
 
 205                 .resourceId(nsInstanceId).status(STARTING).progress(0);
 
 206         databaseServiceProvider.addJob(nfvoJob);
 
 207         logger.info("New job created in database :\n{}", nfvoJob);
 
 209         final LocalDateTime currentDateTime = LocalDateTime.now();
 
 210         final NsLcmOpOcc nsLcmOpOcc = new NsLcmOpOcc().id(nsInstanceId).operation(NsLcmOpType.TERMINATE)
 
 211                 .operationState(OperationStateEnum.PROCESSING).stateEnteredTime(currentDateTime)
 
 212                 .startTime(currentDateTime).nfvoNsInst(getNfvoNsInst(nsInstanceId)).isAutoInvocation(false)
 
 213                 .isCancelPending(false).operationParams(gson.toJson(terminateNsRequest));
 
 214         databaseServiceProvider.addNSLcmOpOcc(nsLcmOpOcc);
 
 215         logger.info("New NSLcmOpOcc created in database :\n{}", nsLcmOpOcc);
 
 217         workflowExecutorService.executeWorkflow(nfvoJob.getJobId(), TERMINATE_NS_WORKFLOW_NAME,
 
 218                 getVariables(nsInstanceId, nfvoJob.getJobId(), nsLcmOpOcc.getId(), terminateNsRequest));
 
 220         final ImmutableSet<JobStatusEnum> jobFinishedStates =
 
 221                 ImmutableSet.of(FINISHED, ERROR, FINISHED_WITH_ERROR, IN_PROGRESS);
 
 222         final ImmutablePair<String, JobStatusEnum> immutablePair =
 
 223                 waitForJobToFinish(nfvoJob.getJobId(), jobFinishedStates);
 
 225         if (immutablePair.getRight() == null) {
 
 226             final String message =
 
 227                     "Failed to Terminate NS with id: " + nsInstanceId + " for request: \n" + terminateNsRequest;
 
 228             logger.error(message);
 
 229             throw new NsRequestProcessingException(message);
 
 232         final JobStatusEnum finalJobStatus = immutablePair.getRight();
 
 234         if (IN_PROGRESS.equals(finalJobStatus) || FINISHED.equals(finalJobStatus)) {
 
 235             logger.info("Termination Job status: {}", finalJobStatus);
 
 236             return nsLcmOpOcc.getId();
 
 239         final String message = "Received unexpected Job Status: " + finalJobStatus + " Failed to Terminate NS with id: "
 
 240                 + nsInstanceId + " for request: \n" + terminateNsRequest;
 
 241         logger.error(message);
 
 242         throw new NsRequestProcessingException(message);
 
 245     public void runDeleteNsJob(final String nsInstanceId) {
 
 246         final NfvoJob nfvoJob = new NfvoJob().startTime(LocalDateTime.now()).jobType("NS").jobAction(DELETE)
 
 247                 .resourceId(nsInstanceId).status(STARTING).progress(0);
 
 248         databaseServiceProvider.addJob(nfvoJob);
 
 249         logger.info("New job created in database :\n{}", nfvoJob);
 
 251         workflowExecutorService.executeWorkflow(nfvoJob.getJobId(), DELETE_NS_WORKFLOW_NAME,
 
 252                 getVariables(nsInstanceId, nfvoJob.getJobId()));
 
 254         final ImmutablePair<String, JobStatusEnum> immutablePair =
 
 255                 waitForJobToFinish(nfvoJob.getJobId(), JOB_FINISHED_STATES);
 
 257         if (immutablePair.getRight() == null) {
 
 258             final String message = "Failed to Delete NS with id: " + nsInstanceId;
 
 259             logger.error(message);
 
 260             throw new NsRequestProcessingException(message);
 
 263         final JobStatusEnum finalJobStatus = immutablePair.getRight();
 
 264         final String processInstanceId = immutablePair.getLeft();
 
 266         if (FINISHED.equals(finalJobStatus)) {
 
 267             logger.info("Delete Job status: {}", finalJobStatus);
 
 271         final Optional<InlineResponse400> optional = workflowQueryService.getProblemDetails(processInstanceId);
 
 272         if (optional.isPresent()) {
 
 273             final InlineResponse400 problemDetails = optional.get();
 
 274             final String message = "Failed to Delete NS with id: " + nsInstanceId + " due to:\n" + problemDetails;
 
 275             logger.error(message);
 
 276             throw new NsRequestProcessingException(message, problemDetails);
 
 279         final String message =
 
 280                 "Received unexpected Job Status: " + finalJobStatus + " Failed to Delete NS with id: " + nsInstanceId;
 
 281         logger.error(message);
 
 282         throw new NsRequestProcessingException(message);
 
 285     private void doInitialTerminateChecks(final String nsInstanceId, final TerminateNsRequest terminateNsRequest) {
 
 286         if (isNotImmediateTerminateRequest(terminateNsRequest)) {
 
 287             final String message = "TerminateNsRequest received with terminateTime: "
 
 288                     + terminateNsRequest.getTerminationTime()
 
 289                     + "\nOnly immediate Terminate requests are currently supported \n(i.e., terminateTime field must not be set).";
 
 290             logger.error(message);
 
 291             throw new NsRequestProcessingException(message);
 
 294         final NfvoNsInst nfvoNsInst = getNfvoNsInst(nsInstanceId);
 
 295         if (isNotInstantiated(nfvoNsInst)) {
 
 296             final String message = "TerminateNsRequest received: " + terminateNsRequest + " for nsInstanceId: "
 
 297                     + nsInstanceId + "\nUnable to terminate.  NS Instance is already in NOT_INSTANTIATED state."
 
 298                     + "\nThis method can only be used with an NS instance in the INSTANTIATED state.";
 
 299             logger.error(message);
 
 300             throw new NsRequestProcessingException(message);
 
 304     private boolean isNotImmediateTerminateRequest(final TerminateNsRequest terminateNsRequest) {
 
 305         return terminateNsRequest != null && terminateNsRequest.getTerminationTime() != null;
 
 308     private boolean isNotInstantiated(final NfvoNsInst nfvoNsInst) {
 
 309         return State.NOT_INSTANTIATED.equals(nfvoNsInst.getStatus());
 
 312     private NfvoNsInst getNfvoNsInst(final String nsInstId) {
 
 313         logger.info("Getting NfvoNsInst with nsInstId: {}", nsInstId);
 
 314         final Optional<NfvoNsInst> optionalNfvoNsInst = databaseServiceProvider.getNfvoNsInst(nsInstId);
 
 316         if (optionalNfvoNsInst.isEmpty()) {
 
 317             final String message = "No matching NS Instance for id: " + nsInstId + " found in database.";
 
 318             throw new NsRequestProcessingException(message);
 
 321         return optionalNfvoNsInst.get();
 
 324     private ImmutablePair<String, JobStatusEnum> waitForJobToFinish(final String jobId,
 
 325             final ImmutableSet<JobStatusEnum> jobFinishedStates) {
 
 327             final long startTimeInMillis = System.currentTimeMillis();
 
 328             final long timeOutTime = startTimeInMillis + TimeUnit.SECONDS.toMillis(timeOutInSeconds);
 
 330             logger.info("Will wait till {} for {} job to finish", Instant.ofEpochMilli(timeOutTime).toString(), jobId);
 
 331             JobStatusEnum currentJobStatus = null;
 
 332             while (timeOutTime > System.currentTimeMillis()) {
 
 334                 final Optional<NfvoJob> optional = databaseServiceProvider.getRefreshedJob(jobId);
 
 336                 if (optional.isEmpty()) {
 
 337                     logger.error("Unable to find Job using jobId: {}", jobId);
 
 338                     return ImmutablePair.nullPair();
 
 341                 final NfvoJob nfvoJob = optional.get();
 
 342                 currentJobStatus = nfvoJob.getStatus();
 
 343                 logger.debug("Received job status response: \n {}", nfvoJob);
 
 344                 if (jobFinishedStates.contains(currentJobStatus)) {
 
 345                     logger.info("Job finished \n {}", currentJobStatus);
 
 346                     return ImmutablePair.of(nfvoJob.getProcessInstanceId(), currentJobStatus);
 
 349                 logger.info("Haven't received one of finish state {} yet, will try again in {} seconds",
 
 350                         jobFinishedStates, SLEEP_TIME_IN_SECONDS);
 
 351                 TimeUnit.SECONDS.sleep(SLEEP_TIME_IN_SECONDS);
 
 354             logger.warn("Timeout current job status: {}", currentJobStatus);
 
 355             return ImmutablePair.nullPair();
 
 356         } catch (final InterruptedException interruptedException) {
 
 357             Thread.currentThread().interrupt();
 
 358             logger.error("Sleep was interrupted", interruptedException);
 
 359             return ImmutablePair.nullPair();
 
 363     private Map<String, Object> getVariables(final String jobId, final CreateNsRequest createNsRequest,
 
 364             final String globalCustomerId, final String serviceType) {
 
 365         final Map<String, Object> variables = new HashMap<>();
 
 366         variables.put(JOB_ID_PARAM_NAME, jobId);
 
 367         variables.put(CREATE_NS_REQUEST_PARAM_NAME, createNsRequest);
 
 368         variables.put(GLOBAL_CUSTOMER_ID_PARAM_NAME, globalCustomerId);
 
 369         variables.put(SERVICE_TYPE_PARAM_NAME, serviceType);
 
 373     private Map<String, Object> getVariables(final String nsInstanceId, final String jobId, final String occId,
 
 374             final InstantiateNsRequest instantiateNsRequest) {
 
 375         final Map<String, Object> variables = new HashMap<>();
 
 376         variables.put(NS_INSTANCE_ID_PARAM_NAME, nsInstanceId);
 
 377         variables.put(JOB_ID_PARAM_NAME, jobId);
 
 378         variables.put(OCC_ID_PARAM_NAME, occId);
 
 379         variables.put(INSTANTIATE_NS_REQUEST_PARAM_NAME, instantiateNsRequest);
 
 383     private Map<String, Object> getVariables(final String nsInstanceId, final String jobId, final String occId,
 
 384             final TerminateNsRequest terminateNsRequest) {
 
 385         final Map<String, Object> variables = new HashMap<>();
 
 386         variables.put(NS_INSTANCE_ID_PARAM_NAME, nsInstanceId);
 
 387         variables.put(JOB_ID_PARAM_NAME, jobId);
 
 388         variables.put(OCC_ID_PARAM_NAME, occId);
 
 389         variables.put(TERMINATE_NS_REQUEST_PARAM_NAME, terminateNsRequest);
 
 393     private Map<String, Object> getVariables(final String nsInstanceId, final String jobId) {
 
 394         final Map<String, Object> variables = new HashMap<>();
 
 395         variables.put(NS_INSTANCE_ID_PARAM_NAME, nsInstanceId);
 
 396         variables.put(JOB_ID_PARAM_NAME, jobId);