/** * Copyright (C) 2015 Orange * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.francetelecom.clara.cloud.paas.activation.v1; import com.francetelecom.clara.cloud.commons.NotFoundException; import com.francetelecom.clara.cloud.commons.TechnicalException; import com.francetelecom.clara.cloud.commons.tasks.TaskStatus; import com.francetelecom.clara.cloud.commons.tasks.TaskStatusEnum; import com.francetelecom.clara.cloud.model.DeploymentStateEnum; import com.francetelecom.clara.cloud.model.TechnicalDeploymentInstance; import com.francetelecom.clara.cloud.model.TechnicalDeploymentInstanceRepository; import com.francetelecom.clara.cloud.paas.activation.ActivationStepEnum; import com.francetelecom.clara.cloud.paas.activation.ManagePaasActivation; import com.francetelecom.clara.cloud.paas.activation.TaskStatusActivation; import org.activiti.engine.ActivitiException; import org.activiti.engine.ProcessEngine; import org.activiti.engine.history.HistoricActivityInstance; import org.activiti.engine.history.HistoricDetail; import org.activiti.engine.history.HistoricProcessInstance; import org.activiti.engine.runtime.ProcessInstance; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.task.TaskExecutor; import org.springframework.jms.connection.SynchedLocalTransactionFailedException; import java.util.HashMap; import java.util.Map; public class ManagePaasActivationActivitiImpl implements ManagePaasActivation { private static Logger logger = LoggerFactory.getLogger(ManagePaasActivationActivitiImpl.class.getName()); private Map<Long, TaskStatusActivation> taskStatusMap = new HashMap<Long, TaskStatusActivation>(); @Autowired private TaskExecutor taskExecutor; @Autowired private TechnicalDeploymentInstanceRepository technicalDeploymentInstanceRepository; @Autowired private ManagePaasActivationActivitiUtilsImpl managePaasActivationActivitiUtilsImpl; @Autowired protected ProcessEngine processEngine; @Override public TaskStatusActivation activate(final int tdiId) { logger.info("Starting TechnicalDeploymentInstance#" + tdiId + " activation"); final TaskStatusActivitiProcess status; synchronized (taskStatusMap) { status = new TaskStatusActivitiProcess(System.currentTimeMillis()); taskStatusMap.put(status.getTaskId(), status); } status.setTechnicalDeploymentInstanceId(tdiId); status.setTitle("Starting activate appliance TDI : TechnicalDeploymentInstance#" + tdiId); status.setStartTime(System.currentTimeMillis()); status.setTaskStatus(TaskStatusEnum.STARTED); status.setFinalState(DeploymentStateEnum.STARTED); status.setPercent(0); final Map<String, String> mdcContext = MDC.getCopyOfContextMap(); this.taskExecutor.execute(new ActivitiRunnableThread(ActivationStepEnum.ACTIVATE, tdiId, mdcContext, status)); return giveCurrentTaskStatus(status); } @Override public TaskStatusActivation start(final int tdiId) { final TaskStatusActivitiProcess status; synchronized (taskStatusMap) { status = new TaskStatusActivitiProcess(System.currentTimeMillis()); taskStatusMap.put(status.getTaskId(), status); } status.setTechnicalDeploymentInstanceId(tdiId); status.setTitle("Starting TechnicalDeploymentInstance#" + tdiId); status.setStartTime(System.currentTimeMillis()); status.setTaskStatus(TaskStatusEnum.STARTED); status.setFinalState(DeploymentStateEnum.STARTED); status.setPercent(0); final Map<String, String> mdcContext = MDC.getCopyOfContextMap(); this.taskExecutor.execute(new ActivitiRunnableThread(ActivationStepEnum.START, tdiId, mdcContext, status)); return giveCurrentTaskStatus(status); } @Override public TaskStatusActivation stop(final int tdiId) { final TaskStatusActivitiProcess status; synchronized (taskStatusMap) { status = new TaskStatusActivitiProcess(System.currentTimeMillis()); taskStatusMap.put(status.getTaskId(), status); } status.setTechnicalDeploymentInstanceId(tdiId); status.setTitle("Stopping TechnicalDeploymentInstance#" + tdiId); status.setStartTime(System.currentTimeMillis()); status.setTaskStatus(TaskStatusEnum.STARTED); status.setFinalState(DeploymentStateEnum.STOPPED); status.setPercent(0); final Map<String, String> mdcContext = MDC.getCopyOfContextMap(); this.taskExecutor.execute(new ActivitiRunnableThread(ActivationStepEnum.STOP, tdiId, mdcContext, status)); return giveCurrentTaskStatus(status); } @Override public TaskStatusActivation delete(final int tdiId) { final TaskStatusActivitiProcess status; synchronized (taskStatusMap) { status = new TaskStatusActivitiProcess(System.currentTimeMillis()); taskStatusMap.put(status.getTaskId(), status); } status.setTechnicalDeploymentInstanceId(tdiId); status.setTitle("Deleting TechnicalDeploymentInstance#" + tdiId); status.setStartTime(System.currentTimeMillis()); status.setTaskStatus(TaskStatusEnum.STARTED); status.setFinalState(DeploymentStateEnum.REMOVED); status.setPercent(0); final Map<String, String> mdcContext = MDC.getCopyOfContextMap(); this.taskExecutor.execute(new ActivitiRunnableThread(ActivationStepEnum.DELETE, tdiId, mdcContext, status)); return giveCurrentTaskStatus(status); } @Override public TaskStatusActivation giveCurrentTaskStatus(TaskStatusActivation taskStatus) { TaskStatusActivation newStatus = null; if (taskStatus.getClass().equals(TaskStatusActivation.class)) { TaskStatusActivation currentStatus = taskStatusMap.get(taskStatus.getTaskId()); newStatus = new TaskStatusActivation(taskStatus.getTaskId()); newStatus.setTechnicalDeploymentInstanceId(currentStatus.getTechnicalDeploymentInstanceId()); newStatus.setStartTime(currentStatus.getStartTime()); newStatus.setEndTime(currentStatus.getEndTime()); newStatus.setTaskStatus(currentStatus.getTaskStatus()); newStatus.setTitle(currentStatus.getTitle()); newStatus.setSubtitle(currentStatus.getSubtitle()); newStatus.setPercent(currentStatus.getPercent()); newStatus.setErrorMessage(currentStatus.getErrorMessage()); newStatus.setMaxPercent(currentStatus.getMaxPercent()); // We have to call addSubtask method in order to update global state // of the task for (TaskStatus subTask : currentStatus.listSubtasks()) { if (subTask.getClass().equals(TaskStatusActivation.class) || subTask.getClass().equals(TaskStatusTemplatesGeneration.class)) { newStatus.addSubtask(subTask); } else if (subTask.getClass().equals(TaskStatusActivitiProcess.class)) { newStatus.addSubtask(giveCurrentTaskStatus((TaskStatusActivation) subTask)); } else { throw new TechnicalException("Not a supported TaskStatus."); } } } else if (taskStatus.getClass().equals(TaskStatusActivitiProcess.class)) { // Copy it TaskStatusActivation currentStatus = taskStatusMap.get(taskStatus.getTaskId()); newStatus = new TaskStatusActivitiProcess((TaskStatusActivitiProcess) currentStatus); // If ProcessInstanceId is null, the process activity hasn't started yet // Return the copy of task String processInstanceId = ((TaskStatusActivitiProcess) newStatus).getProcessInstanceId(); if (processInstanceId != null) { // Then update it ProcessInstance pi = processEngine.getRuntimeService().createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); if (pi == null || pi.isEnded()) { // Process is complete // Update TDI state and task status try { HistoricProcessInstance hpi = processEngine.getHistoryService().createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult(); if (hpi != null && hpi.getEndActivityId() != null && "endglobal".equals(hpi.getEndActivityId())) { final TechnicalDeploymentInstance technicalDeploymentInstance = technicalDeploymentInstanceRepository.findOne(newStatus.getTechnicalDeploymentInstanceId()); if (technicalDeploymentInstance == null) throw (new NotFoundException("Cannot find TDI id=" + newStatus.getTechnicalDeploymentInstanceId())); technicalDeploymentInstance.setDeploymentState(((TaskStatusActivitiProcess) newStatus).getFinalState()); technicalDeploymentInstanceRepository.save(technicalDeploymentInstance); newStatus.setEndTime(hpi.getEndTime().getTime()); newStatus.setPercent(100); newStatus.setTaskStatus(TaskStatusEnum.FINISHED_OK); } else { // TODO find a way to get error message from activiti StringBuffer error = new StringBuffer("Activiti process ended but not in expected task. HistoricProcessInstance (hpi): "); if (hpi != null) { error.append(" hpi.id="); error.append(hpi.getId()); error.append(" hpi.deleteReason="); error.append(hpi.getDeleteReason()); error.append(" hpi.processDefinitionId="); error.append(hpi.getProcessDefinitionId()); error.append(" hpi.startTime="); error.append(hpi.getStartTime()); error.append(" hpi.endTime="); error.append(hpi.getEndTime()); newStatus.setEndTime(hpi.getEndTime() != null ? hpi.getEndTime().getTime() : System.currentTimeMillis()); } else { error.append(" hpi=NULL"); } final TechnicalDeploymentInstance technicalDeploymentInstance = technicalDeploymentInstanceRepository.findOne(newStatus.getTechnicalDeploymentInstanceId()); if (technicalDeploymentInstance == null) throw (new NotFoundException("Cannot find TDI id=" + newStatus.getTechnicalDeploymentInstanceId())); technicalDeploymentInstance.setDeploymentState(DeploymentStateEnum.UNKNOWN); technicalDeploymentInstanceRepository.save(technicalDeploymentInstance); newStatus.setTaskStatus(TaskStatusEnum.FINISHED_FAILED); newStatus.setErrorMessage(error.toString()); logger.debug(error.toString() + " - Following is an HistoricDetailQuery:"); for (HistoricDetail detail : processEngine.getHistoryService().createHistoricDetailQuery().processInstanceId(processInstanceId).list()) { logger.debug(" - " + detail.toString()); } logger.debug("End of HistoricDetailQuery:"); } logger.debug("End of process for TechnicalDeploymentInstance#" + newStatus.getTechnicalDeploymentInstanceId() + " => " + newStatus.getTaskStatus().name()); } catch (NotFoundException e) { newStatus.setErrorMessage("Cannot find TDI id=" + newStatus.getTechnicalDeploymentInstanceId()); newStatus.setTaskStatus(TaskStatusEnum.FINISHED_FAILED); } } else { // Process is in progress newStatus.setTaskStatus(TaskStatusEnum.STARTED); try { for (HistoricActivityInstance ai : processEngine.getHistoryService().createHistoricActivityInstanceQuery() .processInstanceId(processInstanceId).orderByHistoricActivityInstanceEndTime().desc().list()) { // logger.debug("Activiti id="+ai.getActivityId()+" type="+ai.getActivityType()+" start="+ai.getStartTime()+" end="+ai.getEndTime()); if (ai.getActivityName() != null) { newStatus.setSubtitle(ai.getActivityName()); break; } } //logger.debug("-----------------------------"); } catch (ActivitiException e) { // Ignore since it can be due to end of process // and because it doesn't really matter } } } } return newStatus; } private class ActivitiRunnableThread implements Runnable { private final ActivationStepEnum step; private int tdiId; private Map<String, String> mdcContext; private TaskStatusActivitiProcess status; public ActivitiRunnableThread(final ActivationStepEnum step, final int tdiId, Map<String, String> mdcContext, TaskStatusActivitiProcess status) { this.step = step; this.tdiId = tdiId; this.mdcContext = mdcContext; this.status = status; } @Override public void run() { String action = "process "+step.name()+" on TDI#"+tdiId; String curAction = action; try { // Copy current log context informations to the new thread if (mdcContext != null) MDC.setContextMap(mdcContext); Thread.currentThread().setName("tdi#"+tdiId); curAction = action + " getTDI"; TechnicalDeploymentInstance tdi = managePaasActivationActivitiUtilsImpl.getTDI(tdiId); curAction = action + " createProcess"; String processId = managePaasActivationActivitiUtilsImpl.createProcess(tdi, step); try { curAction = action + " runProcess"; ProcessInstance processInstance = managePaasActivationActivitiUtilsImpl.runProcess(tdiId, step, processId); status.setProcessInstanceId(processInstance.getId()); } catch (SynchedLocalTransactionFailedException synchedLocalTransactionFailedException) { // we do not update environment in this case because activiti workflow continue // cf. anomalie #103144 && [ anomalie #103832 ] ElPaaso transactionnal aspect : local or global String errorMessage = "[anomalie #103832] Internal error while " + action; String endUserErrorMessage = "INTERNAL ERROR DURING ACTIVATION PROCESS " + synchedLocalTransactionFailedException.getMessage(); logger.warn(errorMessage, synchedLocalTransactionFailedException); status.setTaskStatus(TaskStatusEnum.FINISHED_FAILED); status.setEndTime(System.currentTimeMillis()); status.setErrorMessage(endUserErrorMessage); } catch (Throwable exc) { managePaasActivationActivitiUtilsImpl.handleError(action + " : exception while activation process", exc, status, tdiId); } } catch (NotFoundException e) { managePaasActivationActivitiUtilsImpl.handleError(action + " : unable to find the technical model", e, status, tdiId); } catch (Throwable exc) { managePaasActivationActivitiUtilsImpl.handleError(curAction, exc, status, tdiId); } finally { // Clear all context informations so the thread can be re-used MDC.clear(); } } } }