分布式调度框架。
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1207 lines
57 KiB

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dolphinscheduler.api.service.impl;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant.WORKFLOW_START;
import static org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_COMPLEMENT_DATA_END_DATE;
import static org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_COMPLEMENT_DATA_SCHEDULE_DATE_LIST;
import static org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_COMPLEMENT_DATA_START_DATE;
import static org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_RECOVER_PROCESS_ID_STRING;
import static org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_START_NODES;
import static org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_START_PARAMS;
import static org.apache.dolphinscheduler.common.constants.CommandKeyConstants.CMD_PARAM_SUB_PROCESS_DEFINE_CODE;
import static org.apache.dolphinscheduler.common.constants.Constants.COMMA;
import static org.apache.dolphinscheduler.common.constants.Constants.MAX_TASK_TIMEOUT;
import static org.apache.dolphinscheduler.common.constants.Constants.SCHEDULE_TIME_MAX_LENGTH;
import org.apache.dolphinscheduler.api.constants.ApiFuncIdentificationConstant;
import org.apache.dolphinscheduler.api.dto.workflowInstance.WorkflowExecuteResponse;
import org.apache.dolphinscheduler.api.enums.ExecuteType;
import org.apache.dolphinscheduler.api.enums.Status;
import org.apache.dolphinscheduler.api.exceptions.ServiceException;
import org.apache.dolphinscheduler.api.executor.ExecuteClient;
import org.apache.dolphinscheduler.api.executor.ExecuteContext;
import org.apache.dolphinscheduler.api.service.ExecutorService;
import org.apache.dolphinscheduler.api.service.MonitorService;
import org.apache.dolphinscheduler.api.service.ProcessDefinitionService;
import org.apache.dolphinscheduler.api.service.ProjectService;
import org.apache.dolphinscheduler.api.service.WorkerGroupService;
import org.apache.dolphinscheduler.common.constants.Constants;
import org.apache.dolphinscheduler.common.enums.ApiTriggerType;
import org.apache.dolphinscheduler.common.enums.CommandType;
import org.apache.dolphinscheduler.common.enums.ComplementDependentMode;
import org.apache.dolphinscheduler.common.enums.CycleEnum;
import org.apache.dolphinscheduler.common.enums.ExecutionOrder;
import org.apache.dolphinscheduler.common.enums.FailureStrategy;
import org.apache.dolphinscheduler.common.enums.Flag;
import org.apache.dolphinscheduler.common.enums.Priority;
import org.apache.dolphinscheduler.common.enums.ReleaseState;
import org.apache.dolphinscheduler.common.enums.RunMode;
import org.apache.dolphinscheduler.common.enums.TaskDependType;
import org.apache.dolphinscheduler.common.enums.TaskGroupQueueStatus;
import org.apache.dolphinscheduler.common.enums.WarningType;
import org.apache.dolphinscheduler.common.enums.WorkflowExecutionStatus;
import org.apache.dolphinscheduler.common.model.Server;
import org.apache.dolphinscheduler.common.utils.CodeGenerateUtils;
import org.apache.dolphinscheduler.common.utils.DateUtils;
import org.apache.dolphinscheduler.common.utils.JSONUtils;
import org.apache.dolphinscheduler.dao.entity.Command;
import org.apache.dolphinscheduler.dao.entity.DependentProcessDefinition;
import org.apache.dolphinscheduler.dao.entity.ProcessDefinition;
import org.apache.dolphinscheduler.dao.entity.ProcessInstance;
import org.apache.dolphinscheduler.dao.entity.ProcessTaskRelation;
import org.apache.dolphinscheduler.dao.entity.Project;
import org.apache.dolphinscheduler.dao.entity.Schedule;
import org.apache.dolphinscheduler.dao.entity.TaskDefinition;
import org.apache.dolphinscheduler.dao.entity.TaskGroupQueue;
import org.apache.dolphinscheduler.dao.entity.Tenant;
import org.apache.dolphinscheduler.dao.entity.User;
import org.apache.dolphinscheduler.dao.mapper.ProcessDefinitionMapper;
import org.apache.dolphinscheduler.dao.mapper.ProcessInstanceMapper;
import org.apache.dolphinscheduler.dao.mapper.ProcessTaskRelationMapper;
import org.apache.dolphinscheduler.dao.mapper.ProjectMapper;
import org.apache.dolphinscheduler.dao.mapper.TaskDefinitionLogMapper;
import org.apache.dolphinscheduler.dao.mapper.TaskDefinitionMapper;
import org.apache.dolphinscheduler.dao.mapper.TaskGroupQueueMapper;
import org.apache.dolphinscheduler.dao.mapper.TenantMapper;
import org.apache.dolphinscheduler.dao.repository.ProcessInstanceDao;
import org.apache.dolphinscheduler.extract.base.client.SingletonJdkDynamicRpcClientProxyFactory;
import org.apache.dolphinscheduler.extract.master.ILogicTaskInstanceOperator;
import org.apache.dolphinscheduler.extract.master.IStreamingTaskOperator;
import org.apache.dolphinscheduler.extract.master.ITaskInstanceExecutionEventListener;
import org.apache.dolphinscheduler.extract.master.IWorkflowInstanceService;
import org.apache.dolphinscheduler.extract.master.dto.WorkflowExecuteDto;
import org.apache.dolphinscheduler.extract.master.transportor.StreamingTaskTriggerRequest;
import org.apache.dolphinscheduler.extract.master.transportor.StreamingTaskTriggerResponse;
import org.apache.dolphinscheduler.extract.master.transportor.TaskInstanceForceStartRequest;
import org.apache.dolphinscheduler.extract.master.transportor.WorkflowInstanceStateChangeEvent;
import org.apache.dolphinscheduler.plugin.task.api.TaskConstants;
import org.apache.dolphinscheduler.service.command.CommandService;
import org.apache.dolphinscheduler.service.cron.CronUtils;
import org.apache.dolphinscheduler.service.exceptions.CronParseException;
import org.apache.dolphinscheduler.service.process.ProcessService;
import org.apache.dolphinscheduler.service.process.TriggerRelationService;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.base.Splitter;
/**
* executor service impl
*/
@Service
@Slf4j
public class ExecutorServiceImpl extends BaseServiceImpl implements ExecutorService {
@Autowired
private ProjectMapper projectMapper;
@Autowired
private ProjectService projectService;
@Autowired
private ProcessDefinitionMapper processDefinitionMapper;
@Autowired
ProcessDefinitionMapper processDefineMapper;
@Autowired
private MonitorService monitorService;
@Autowired
private ProcessInstanceMapper processInstanceMapper;
@Lazy()
@Autowired
private ProcessService processService;
@Autowired
private ProcessInstanceDao processInstanceDao;
@Autowired
private ProcessDefinitionService processDefinitionService;
@Autowired
private CommandService commandService;
@Autowired
private TaskDefinitionLogMapper taskDefinitionLogMapper;
@Autowired
private TaskDefinitionMapper taskDefinitionMapper;
@Autowired
private ProcessTaskRelationMapper processTaskRelationMapper;
@Autowired
private TaskGroupQueueMapper taskGroupQueueMapper;
@Autowired
private WorkerGroupService workerGroupService;
@Autowired
private TriggerRelationService triggerRelationService;
@Autowired
private ExecuteClient executeClient;
@Autowired
private TenantMapper tenantMapper;
/**
* execute process instance
*
* @param loginUser login user
* @param projectCode project code
* @param processDefinitionCode process definition code
* @param cronTime cron time
* @param commandType command type
* @param failureStrategy failure strategy
* @param startNodeList start nodelist
* @param taskDependType node dependency type
* @param warningType warning type
* @param warningGroupId notify group id
* @param processInstancePriority process instance priority
* @param workerGroup worker group name
* @param tenantCode tenant code
* @param environmentCode environment code
* @param runMode run mode
* @param timeout timeout
* @param startParams the global param values which pass to new process instance
* @param expectedParallelismNumber the expected parallelism number when execute complement in parallel mode
* @param testFlag testFlag
* @param executionOrder the execution order when complementing data
* @return execute process instance code
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> execProcessInstance(User loginUser, long projectCode, long processDefinitionCode,
String cronTime, CommandType commandType,
FailureStrategy failureStrategy, String startNodeList,
TaskDependType taskDependType, WarningType warningType,
Integer warningGroupId, RunMode runMode,
Priority processInstancePriority, String workerGroup,
String tenantCode,
Long environmentCode, Integer timeout,
Map<String, String> startParams, Integer expectedParallelismNumber,
int dryRun, int testFlag,
ComplementDependentMode complementDependentMode, Integer version,
boolean allLevelDependent, ExecutionOrder executionOrder) {
Project project = projectMapper.queryByCode(projectCode);
// check user access for project
Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_START);
if (result.get(Constants.STATUS) != Status.SUCCESS) {
return result;
}
// timeout is invalid
if (timeout <= 0 || timeout > MAX_TASK_TIMEOUT) {
log.warn("Parameter timeout is invalid, timeout:{}.", timeout);
putMsg(result, Status.TASK_TIMEOUT_PARAMS_ERROR);
return result;
}
if (Objects.nonNull(expectedParallelismNumber) && expectedParallelismNumber <= 0) {
log.warn("Parameter expectedParallelismNumber is invalid, expectedParallelismNumber:{}.",
expectedParallelismNumber);
putMsg(result, Status.TASK_PARALLELISM_PARAMS_ERROR);
return result;
}
checkValidTenant(tenantCode);
ProcessDefinition processDefinition;
if (null != version) {
processDefinition = processService.findProcessDefinition(processDefinitionCode, version);
} else {
processDefinition = processDefinitionMapper.queryByCode(processDefinitionCode);
}
// check process define release state
this.checkProcessDefinitionValid(projectCode, processDefinition, processDefinitionCode,
processDefinition.getVersion());
// check current version whether include startNodeList
checkStartNodeList(startNodeList, processDefinitionCode, processDefinition.getVersion());
checkScheduleTimeNumExceed(commandType, cronTime);
checkMasterExists();
long triggerCode = CodeGenerateUtils.getInstance().genCode();
/**
* create command
*/
int create =
this.createCommand(triggerCode, commandType, processDefinition.getCode(), taskDependType,
failureStrategy,
startNodeList,
cronTime, warningType, loginUser.getId(), warningGroupId, runMode, processInstancePriority,
workerGroup, tenantCode,
environmentCode, startParams, expectedParallelismNumber, dryRun, testFlag,
complementDependentMode, allLevelDependent, executionOrder);
if (create > 0) {
processDefinition.setWarningGroupId(warningGroupId);
processDefinitionMapper.updateById(processDefinition);
log.info("Create command complete, processDefinitionCode:{}, commandCount:{}.",
processDefinition.getCode(), create);
result.put(Constants.DATA_LIST, triggerCode);
putMsg(result, Status.SUCCESS);
} else {
log.error("Start process instance failed because create command error, processDefinitionCode:{}.",
processDefinition.getCode());
putMsg(result, Status.START_PROCESS_INSTANCE_ERROR);
}
return result;
}
private void checkMasterExists() {
// check master server exists
List<Server> masterServers = monitorService.getServerListFromRegistry(true);
// no master
if (masterServers.isEmpty()) {
throw new ServiceException(Status.MASTER_NOT_EXISTS);
}
}
private void checkScheduleTimeNumExceed(CommandType complementData, String cronTime) {
if (!CommandType.COMPLEMENT_DATA.equals(complementData)) {
return;
}
if (cronTime == null) {
return;
}
Map<String, String> cronMap = JSONUtils.toMap(cronTime);
if (cronMap.containsKey(CMD_PARAM_COMPLEMENT_DATA_SCHEDULE_DATE_LIST)) {
String[] stringDates = cronMap.get(CMD_PARAM_COMPLEMENT_DATA_SCHEDULE_DATE_LIST).split(COMMA);
if (stringDates.length > SCHEDULE_TIME_MAX_LENGTH) {
log.warn("Parameter cornTime is bigger than {}.", SCHEDULE_TIME_MAX_LENGTH);
throw new ServiceException(Status.SCHEDULE_TIME_NUMBER_EXCEED);
}
}
}
/**
* check whether the process definition can be executed
*
* @param projectCode project code
* @param processDefinition process definition
*/
@Override
public void checkProcessDefinitionValid(long projectCode, ProcessDefinition processDefinition,
long processDefineCode, Integer version) {
// check process definition exists
if (projectCode != processDefinition.getProjectCode()) {
throw new ServiceException(Status.PROCESS_DEFINE_NOT_EXIST, processDefinition.getCode());
}
// check process definition online
if (processDefinition.getReleaseState() != ReleaseState.ONLINE) {
throw new ServiceException(Status.PROCESS_DEFINE_NOT_RELEASE, processDefinition.getCode(),
processDefinition.getVersion());
}
// check sub process definition online
if (!checkSubProcessDefinitionValid(processDefinition)) {
throw new ServiceException(Status.SUB_PROCESS_DEFINE_NOT_RELEASE);
}
}
/**
* check whether the current process has subprocesses and validate all subprocesses
*
* @param processDefinition
* @return check result
*/
@Override
public boolean checkSubProcessDefinitionValid(ProcessDefinition processDefinition) {
// query all subprocesses under the current process
List<ProcessTaskRelation> processTaskRelations =
processTaskRelationMapper.queryDownstreamByProcessDefinitionCode(processDefinition.getCode());
if (processTaskRelations.isEmpty()) {
return true;
}
Set<Long> relationCodes =
processTaskRelations.stream().map(ProcessTaskRelation::getPostTaskCode).collect(Collectors.toSet());
List<TaskDefinition> taskDefinitions = taskDefinitionMapper.queryByCodeList(relationCodes);
// find out the process definition code
Set<Long> processDefinitionCodeSet = new HashSet<>();
taskDefinitions.stream()
.filter(task -> TaskConstants.TASK_TYPE_SUB_PROCESS.equalsIgnoreCase(task.getTaskType())).forEach(
taskDefinition -> processDefinitionCodeSet.add(Long.valueOf(
JSONUtils.getNodeString(taskDefinition.getTaskParams(),
CMD_PARAM_SUB_PROCESS_DEFINE_CODE))));
if (processDefinitionCodeSet.isEmpty()) {
return true;
}
// check sub releaseState
List<ProcessDefinition> processDefinitions = processDefinitionMapper.queryByCodes(processDefinitionCodeSet);
return processDefinitions.stream()
.filter(definition -> definition.getReleaseState().equals(ReleaseState.OFFLINE))
.collect(Collectors.toSet())
.isEmpty();
}
/**
* check valid tenant
*
* @param tenantCode
*/
private void checkValidTenant(String tenantCode) {
if (!Constants.DEFAULT.equals(tenantCode)) {
Tenant tenant = tenantMapper.queryByTenantCode(tenantCode);
if (tenant == null) {
throw new ServiceException(Status.TENANT_NOT_EXIST, tenantCode);
}
}
}
/**
* do action to process instance:pause, stop, repeat, recover from pause, recover from stop,rerun failed task
*
* @param loginUser login user
* @param projectCode project code
* @param processInstanceId process instance id
* @param executeType execute type
* @return execute result code
*/
@Override
public Map<String, Object> execute(User loginUser,
long projectCode,
Integer processInstanceId,
ExecuteType executeType) {
checkNotNull(processInstanceId, "workflowInstanceId cannot be null");
checkNotNull(executeType, "executeType cannot be null");
// check user access for project
projectService.checkProjectAndAuthThrowException(loginUser, projectCode,
ApiFuncIdentificationConstant.map.get(executeType));
checkMasterExists();
ProcessInstance workflowInstance = processInstanceDao.queryOptionalById(processInstanceId)
.orElseThrow(() -> new ServiceException(Status.PROCESS_INSTANCE_NOT_EXIST, processInstanceId));
checkState(workflowInstance.getProjectCode() == projectCode,
"The workflow instance's project code doesn't equals to the given project");
ProcessDefinition processDefinition = processDefinitionService.queryWorkflowDefinitionThrowExceptionIfNotFound(
workflowInstance.getProcessDefinitionCode(), workflowInstance.getProcessDefinitionVersion());
executeClient.executeWorkflowInstance(new ExecuteContext(
workflowInstance,
processDefinition,
loginUser,
executeType));
Map<String, Object> result = new HashMap<>();
result.put(Constants.STATUS, Status.SUCCESS);
return result;
}
/**
* do action to workflow instance:pause, stop, repeat, recover from pause, recover from stop,rerun failed task
*
* @param loginUser login user
* @param workflowInstanceId workflow instance id
* @param executeType execute type
* @return execute result code
*/
@Override
public Map<String, Object> execute(User loginUser, Integer workflowInstanceId, ExecuteType executeType) {
ProcessInstance processInstance = processInstanceMapper.selectById(workflowInstanceId);
return execute(loginUser, processInstance.getProjectCode(), workflowInstanceId, executeType);
}
/**
* do action to execute task in process instance
*
* @param loginUser login user
* @param projectCode project code
* @param processInstanceId process instance id
* @param startNodeList start node list
* @param taskDependType task depend type
* @return execute result code
*/
@Override
public WorkflowExecuteResponse executeTask(User loginUser, long projectCode, Integer processInstanceId,
String startNodeList, TaskDependType taskDependType) {
WorkflowExecuteResponse response = new WorkflowExecuteResponse();
Project project = projectMapper.queryByCode(projectCode);
// check user access for project
projectService.checkProjectAndAuthThrowException(loginUser, project,
ApiFuncIdentificationConstant.map.get(ExecuteType.EXECUTE_TASK));
ProcessInstance processInstance = processService.findProcessInstanceDetailById(processInstanceId)
.orElseThrow(() -> new ServiceException(Status.PROCESS_INSTANCE_NOT_EXIST, processInstanceId));
if (!processInstance.getState().isFinished()) {
log.error("Can not execute task for process instance which is not finished, processInstanceId:{}.",
processInstanceId);
putMsg(response, Status.WORKFLOW_INSTANCE_IS_NOT_FINISHED);
return response;
}
ProcessDefinition processDefinition =
processService.findProcessDefinition(processInstance.getProcessDefinitionCode(),
processInstance.getProcessDefinitionVersion());
processDefinition.setReleaseState(ReleaseState.ONLINE);
this.checkProcessDefinitionValid(projectCode, processDefinition, processInstance.getProcessDefinitionCode(),
processInstance.getProcessDefinitionVersion());
// get the startParams user specified at the first starting while repeat running is needed
long startNodeListLong;
try {
startNodeListLong = Long.parseLong(startNodeList);
} catch (NumberFormatException e) {
log.error("startNodeList is not a number");
putMsg(response, Status.REQUEST_PARAMS_NOT_VALID_ERROR, startNodeList);
return response;
}
if (taskDefinitionLogMapper.queryMaxVersionForDefinition(startNodeListLong) == null) {
putMsg(response, Status.EXECUTE_NOT_DEFINE_TASK);
return response;
}
// To add startParams only when repeat running is needed
Map<String, Object> cmdParam = new HashMap<>();
cmdParam.put(CMD_PARAM_RECOVER_PROCESS_ID_STRING, processInstanceId);
// Add StartNodeList
cmdParam.put(CMD_PARAM_START_NODES, startNodeList);
Command command = new Command();
command.setCommandType(CommandType.EXECUTE_TASK);
command.setProcessDefinitionCode(processDefinition.getCode());
command.setCommandParam(JSONUtils.toJsonString(cmdParam));
command.setExecutorId(loginUser.getId());
command.setProcessDefinitionVersion(processDefinition.getVersion());
command.setProcessInstanceId(processInstanceId);
command.setTestFlag(processInstance.getTestFlag());
// Add taskDependType
command.setTaskDependType(taskDependType);
if (!commandService.verifyIsNeedCreateCommand(command)) {
log.warn(
"Process instance is executing the command, processDefinitionCode:{}, processDefinitionVersion:{}, processInstanceId:{}.",
processDefinition.getCode(), processDefinition.getVersion(), processInstanceId);
putMsg(response, Status.PROCESS_INSTANCE_EXECUTING_COMMAND,
String.valueOf(processDefinition.getCode()));
return response;
}
log.info("Creating command, commandInfo:{}.", command);
int create = commandService.createCommand(command);
if (create > 0) {
log.info("Create {} command complete, processDefinitionCode:{}, processDefinitionVersion:{}.",
command.getCommandType().getDescp(), command.getProcessDefinitionCode(),
processDefinition.getVersion());
putMsg(response, Status.SUCCESS);
} else {
log.error(
"Execute process instance failed because create {} command error, processDefinitionCode:{}, processDefinitionVersion:{}, processInstanceId:{}.",
command.getCommandType().getDescp(), command.getProcessDefinitionCode(),
processDefinition.getVersion(),
processInstanceId);
putMsg(response, Status.EXECUTE_PROCESS_INSTANCE_ERROR);
}
return response;
}
@Override
public Map<String, Object> forceStartTaskInstance(User loginUser, int queueId) {
Map<String, Object> result = new HashMap<>();
TaskGroupQueue taskGroupQueue = taskGroupQueueMapper.selectById(queueId);
// check process instance exist
ProcessInstance processInstance = processInstanceMapper.selectById(taskGroupQueue.getProcessId());
if (processInstance == null) {
log.error("Process instance does not exist, projectCode:{}, processInstanceId:{}.",
taskGroupQueue.getProjectCode(), taskGroupQueue.getProcessId());
putMsg(result, Status.PROCESS_INSTANCE_NOT_EXIST, taskGroupQueue.getProcessId());
return result;
}
checkMasterExists();
return forceStart(processInstance, taskGroupQueue);
}
public void checkStartNodeList(String startNodeList, Long processDefinitionCode, int version) {
if (StringUtils.isNotEmpty(startNodeList)) {
List<ProcessTaskRelation> processTaskRelations =
processService.findRelationByCode(processDefinitionCode, version);
List<Long> existsNodes = processTaskRelations.stream().map(ProcessTaskRelation::getPostTaskCode)
.collect(Collectors.toList());
for (String startNode : startNodeList.split(Constants.COMMA)) {
if (!existsNodes.contains(Long.valueOf(startNode))) {
throw new ServiceException(Status.START_NODE_NOT_EXIST_IN_LAST_PROCESS, startNode);
}
}
}
}
/**
* Check the state of process instance and the type of operation match
*
* @param processInstance process instance
* @param executeType execute type
* @return check result code
*/
private Map<String, Object> checkExecuteType(ProcessInstance processInstance, ExecuteType executeType) {
Map<String, Object> result = new HashMap<>();
WorkflowExecutionStatus executionStatus = processInstance.getState();
boolean checkResult = false;
switch (executeType) {
case PAUSE:
if (executionStatus.isRunning()) {
checkResult = true;
}
break;
case STOP:
if (executionStatus.canStop()) {
checkResult = true;
}
break;
case REPEAT_RUNNING:
if (executionStatus.isFinished()) {
checkResult = true;
}
break;
case START_FAILURE_TASK_PROCESS:
if (executionStatus.isFailure()) {
checkResult = true;
}
break;
case RECOVER_SUSPENDED_PROCESS:
if (executionStatus.isPause() || executionStatus.isStop()) {
checkResult = true;
}
break;
default:
break;
}
if (!checkResult) {
putMsg(result, Status.PROCESS_INSTANCE_STATE_OPERATION_ERROR, processInstance.getName(),
executionStatus.toString(), executeType.toString());
} else {
putMsg(result, Status.SUCCESS);
}
return result;
}
/**
* prepare to update process instance command type and status
*
* @param processInstance process instance
* @param commandType command type
* @param executionStatus execute status
* @return update result
*/
private Map<String, Object> updateProcessInstancePrepare(ProcessInstance processInstance, CommandType commandType,
WorkflowExecutionStatus executionStatus) {
Map<String, Object> result = new HashMap<>();
processInstance.setCommandType(commandType);
processInstance.addHistoryCmd(commandType);
processInstance.setStateWithDesc(executionStatus, commandType.getDescp() + "by ui");
boolean update = processInstanceDao.updateById(processInstance);
// determine whether the process is normal
if (update) {
log.info("Process instance state is updated to {} in database, processInstanceName:{}.",
executionStatus.getDesc(), processInstance.getName());
// directly send the process instance state change event to target master, not guarantee the event send
// success
WorkflowInstanceStateChangeEvent workflowStateEventChangeRequest = new WorkflowInstanceStateChangeEvent(
processInstance.getId(), 0, processInstance.getState(), processInstance.getId(), 0);
ITaskInstanceExecutionEventListener iTaskInstanceExecutionEventListener =
SingletonJdkDynamicRpcClientProxyFactory
.getProxyClient(processInstance.getHost(), ITaskInstanceExecutionEventListener.class);
iTaskInstanceExecutionEventListener.onWorkflowInstanceInstanceStateChange(workflowStateEventChangeRequest);
putMsg(result, Status.SUCCESS);
} else {
log.error("Process instance state update error, processInstanceName:{}.", processInstance.getName());
putMsg(result, Status.EXECUTE_PROCESS_INSTANCE_ERROR);
}
return result;
}
/**
* prepare to update process instance command type and status
*
* @param processInstance process instance
* @return update result
*/
private Map<String, Object> forceStart(ProcessInstance processInstance, TaskGroupQueue taskGroupQueue) {
Map<String, Object> result = new HashMap<>();
if (taskGroupQueue.getStatus() != TaskGroupQueueStatus.WAIT_QUEUE) {
log.warn("Task group queue already starts, taskGroupQueueId:{}.", taskGroupQueue.getId());
putMsg(result, Status.TASK_GROUP_QUEUE_ALREADY_START);
return result;
}
taskGroupQueue.setForceStart(Flag.YES.getCode());
processService.updateTaskGroupQueue(taskGroupQueue);
log.info("Sending force start command to master: {}.", processInstance.getHost());
ILogicTaskInstanceOperator iLogicTaskInstanceOperator = SingletonJdkDynamicRpcClientProxyFactory
.getProxyClient(processInstance.getHost(), ILogicTaskInstanceOperator.class);
iLogicTaskInstanceOperator.forceStartTaskInstance(
new TaskInstanceForceStartRequest(processInstance.getId(), taskGroupQueue.getTaskId()));
putMsg(result, Status.SUCCESS);
return result;
}
/**
* check whether sub processes are offline before starting process definition
*
* @param processDefinitionCode process definition code
* @return check result code
*/
@Override
public Map<String, Object> startCheckByProcessDefinedCode(long processDefinitionCode) {
Map<String, Object> result = new HashMap<>();
ProcessDefinition processDefinition = processDefinitionMapper.queryByCode(processDefinitionCode);
if (processDefinition == null) {
log.error("Process definition is not be found, processDefinitionCode:{}.", processDefinitionCode);
putMsg(result, Status.REQUEST_PARAMS_NOT_VALID_ERROR, "processDefinitionCode");
return result;
}
List<Long> codes = new ArrayList<>();
processService.recurseFindSubProcess(processDefinition.getCode(), codes);
if (!codes.isEmpty()) {
List<ProcessDefinition> processDefinitionList = processDefinitionMapper.queryByCodes(codes);
if (processDefinitionList != null) {
for (ProcessDefinition processDefinitionTmp : processDefinitionList) {
/**
* if there is no online process, exit directly
*/
if (processDefinitionTmp.getReleaseState() != ReleaseState.ONLINE) {
log.warn("Subprocess definition {} of process definition {} is not {}.",
processDefinitionTmp.getName(),
processDefinition.getName(), ReleaseState.ONLINE.getDescp());
putMsg(result, Status.PROCESS_DEFINE_NOT_RELEASE, processDefinitionTmp.getName());
return result;
}
}
}
}
putMsg(result, Status.SUCCESS);
return result;
}
/**
* create command
*
* @param commandType commandType
* @param processDefineCode processDefineCode
* @param nodeDep nodeDep
* @param failureStrategy failureStrategy
* @param startNodeList startNodeList
* @param schedule schedule
* @param warningType warningType
* @param executorId executorId
* @param warningGroupId warningGroupId
* @param runMode runMode
* @param processInstancePriority processInstancePriority
* @param workerGroup workerGroup
* @param testFlag testFlag
* @param environmentCode environmentCode
* @param allLevelDependent allLevelDependent
* @param executionOrder executionOrder
* @return command id
*/
private int createCommand(Long triggerCode, CommandType commandType, long processDefineCode, TaskDependType nodeDep,
FailureStrategy failureStrategy, String startNodeList, String schedule,
WarningType warningType, int executorId, Integer warningGroupId, RunMode runMode,
Priority processInstancePriority, String workerGroup, String tenantCode,
Long environmentCode,
Map<String, String> startParams, Integer expectedParallelismNumber, int dryRun,
int testFlag, ComplementDependentMode complementDependentMode,
boolean allLevelDependent, ExecutionOrder executionOrder) {
/**
* instantiate command schedule instance
*/
Command command = new Command();
Map<String, String> cmdParam = new HashMap<>();
if (commandType == null) {
command.setCommandType(CommandType.START_PROCESS);
} else {
command.setCommandType(commandType);
}
command.setProcessDefinitionCode(processDefineCode);
if (nodeDep != null) {
command.setTaskDependType(nodeDep);
}
if (failureStrategy != null) {
command.setFailureStrategy(failureStrategy);
}
if (!StringUtils.isEmpty(startNodeList)) {
cmdParam.put(CMD_PARAM_START_NODES, startNodeList);
}
if (warningType != null) {
command.setWarningType(warningType);
}
if (startParams != null && startParams.size() > 0) {
cmdParam.put(CMD_PARAM_START_PARAMS, JSONUtils.toJsonString(startParams));
}
command.setCommandParam(JSONUtils.toJsonString(cmdParam));
command.setExecutorId(executorId);
command.setWarningGroupId(warningGroupId);
command.setProcessInstancePriority(processInstancePriority);
command.setWorkerGroup(workerGroup);
command.setTenantCode(tenantCode);
command.setEnvironmentCode(environmentCode);
command.setDryRun(dryRun);
command.setTestFlag(testFlag);
ProcessDefinition processDefinition = processService.findProcessDefinitionByCode(processDefineCode);
if (processDefinition != null) {
command.setProcessDefinitionVersion(processDefinition.getVersion());
}
command.setProcessInstanceId(0);
// determine whether to complement
if (commandType == CommandType.COMPLEMENT_DATA) {
if (schedule == null || StringUtils.isEmpty(schedule)) {
log.error("Create {} type command error because parameter schedule is invalid.",
command.getCommandType().getDescp());
return 0;
}
if (!isValidateScheduleTime(schedule)) {
return 0;
}
try {
log.info("Start to create {} command, processDefinitionCode:{}.",
command.getCommandType().getDescp(), processDefineCode);
return createComplementCommandList(triggerCode, schedule, runMode, command, expectedParallelismNumber,
complementDependentMode, allLevelDependent, executionOrder);
} catch (CronParseException cronParseException) {
// We catch the exception here just to make compiler happy, since we have already validated the schedule
// cron expression before
return 0;
}
} else {
command.setCommandParam(JSONUtils.toJsonString(cmdParam));
int count = commandService.createCommand(command);
if (count > 0) {
triggerRelationService.saveTriggerToDb(ApiTriggerType.COMMAND, triggerCode, command.getId());
}
return count;
}
}
private int createComplementCommand(Long triggerCode, Command command, Map<String, String> cmdParam,
List<ZonedDateTime> dateTimeList, List<Schedule> schedules,
ComplementDependentMode complementDependentMode, boolean allLevelDependent) {
String dateTimeListStr = dateTimeList.stream()
.map(item -> DateUtils.dateToString(item))
.collect(Collectors.joining(COMMA));
cmdParam.put(CMD_PARAM_COMPLEMENT_DATA_SCHEDULE_DATE_LIST, dateTimeListStr);
command.setCommandParam(JSONUtils.toJsonString(cmdParam));
log.info("Creating command, commandInfo:{}.", command);
int createCount = commandService.createCommand(command);
if (createCount > 0) {
log.info("Create {} command complete, processDefinitionCode:{}",
command.getCommandType().getDescp(), command.getProcessDefinitionCode());
} else {
log.error("Create {} command error, processDefinitionCode:{}",
command.getCommandType().getDescp(), command.getProcessDefinitionCode());
}
if (schedules.isEmpty() || complementDependentMode == ComplementDependentMode.OFF_MODE) {
log.info(
"Complement dependent mode is off mode or Scheduler is empty, so skip create complement dependent command, processDefinitionCode:{}.",
command.getProcessDefinitionCode());
} else {
log.info(
"Complement dependent mode is all dependent and Scheduler is not empty, need create complement dependent command, processDefinitionCode:{}.",
command.getProcessDefinitionCode());
createComplementDependentCommand(schedules, command, allLevelDependent);
}
if (createCount > 0) {
triggerRelationService.saveTriggerToDb(ApiTriggerType.COMMAND, triggerCode, command.getId());
}
return createCount;
}
/**
* create complement command
* close left and close right
*
* @param scheduleTimeParam
* @param runMode
* @param executionOrder
* @return
*/
protected int createComplementCommandList(Long triggerCode, String scheduleTimeParam, RunMode runMode,
Command command,
Integer expectedParallelismNumber,
ComplementDependentMode complementDependentMode,
boolean allLevelDependent,
ExecutionOrder executionOrder) throws CronParseException {
int createCount = 0;
int dependentProcessDefinitionCreateCount = 0;
runMode = (runMode == null) ? RunMode.RUN_MODE_SERIAL : runMode;
Map<String, String> cmdParam = JSONUtils.toMap(command.getCommandParam());
Map<String, String> scheduleParam = JSONUtils.toMap(scheduleTimeParam);
if (Objects.isNull(executionOrder)) {
executionOrder = ExecutionOrder.DESC_ORDER;
}
List<Schedule> schedules = processService.queryReleaseSchedulerListByProcessDefinitionCode(
command.getProcessDefinitionCode());
List<ZonedDateTime> listDate = new ArrayList<>();
if (scheduleParam.containsKey(CMD_PARAM_COMPLEMENT_DATA_START_DATE) && scheduleParam.containsKey(
CMD_PARAM_COMPLEMENT_DATA_END_DATE)) {
String startDate = scheduleParam.get(CMD_PARAM_COMPLEMENT_DATA_START_DATE);
String endDate = scheduleParam.get(CMD_PARAM_COMPLEMENT_DATA_END_DATE);
if (startDate != null && endDate != null) {
listDate = CronUtils.getSelfFireDateList(
DateUtils.stringToZoneDateTime(startDate),
DateUtils.stringToZoneDateTime(endDate),
schedules);
}
}
if (scheduleParam.containsKey(CMD_PARAM_COMPLEMENT_DATA_SCHEDULE_DATE_LIST)) {
String dateList = scheduleParam.get(CMD_PARAM_COMPLEMENT_DATA_SCHEDULE_DATE_LIST);
if (StringUtils.isNotBlank(dateList)) {
listDate = Splitter.on(COMMA).splitToStream(dateList)
.map(item -> DateUtils.stringToZoneDateTime(item.trim()))
.distinct()
.collect(Collectors.toList());
}
}
if (CollectionUtils.isEmpty(listDate)) {
throw new ServiceException(Status.TASK_COMPLEMENT_DATA_DATE_ERROR);
}
if (executionOrder.equals(ExecutionOrder.DESC_ORDER)) {
Collections.sort(listDate, Collections.reverseOrder());
} else {
Collections.sort(listDate);
}
switch (runMode) {
case RUN_MODE_SERIAL: {
log.info("RunMode of {} command is serial run, processDefinitionCode:{}.",
command.getCommandType().getDescp(), command.getProcessDefinitionCode());
createCount = createComplementCommand(triggerCode, command, cmdParam, listDate, schedules,
complementDependentMode, allLevelDependent);
break;
}
case RUN_MODE_PARALLEL: {
log.info("RunMode of {} command is parallel run, processDefinitionCode:{}.",
command.getCommandType().getDescp(), command.getProcessDefinitionCode());
int queueNum = 0;
if (CollectionUtils.isNotEmpty(listDate)) {
queueNum = listDate.size();
if (expectedParallelismNumber != null && expectedParallelismNumber != 0) {
queueNum = Math.min(queueNum, expectedParallelismNumber);
}
log.info("Complement command run in parallel mode, current expectedParallelismNumber:{}.",
queueNum);
List[] queues = new List[queueNum];
for (int i = 0; i < listDate.size(); i++) {
if (Objects.isNull(queues[i % queueNum])) {
queues[i % queueNum] = new ArrayList();
}
queues[i % queueNum].add(listDate.get(i));
}
for (List queue : queues) {
createCount = createComplementCommand(triggerCode, command, cmdParam, queue, schedules,
complementDependentMode, allLevelDependent);
}
}
break;
}
default:
break;
}
log.info("Create complement command count:{}, Create dependent complement command count:{}", createCount,
dependentProcessDefinitionCreateCount);
return createCount;
}
/**
* create complement dependent command
*/
public int createComplementDependentCommand(List<Schedule> schedules, Command command, boolean allLevelDependent) {
int dependentProcessDefinitionCreateCount = 0;
Command dependentCommand;
try {
dependentCommand = (Command) BeanUtils.cloneBean(command);
} catch (Exception e) {
log.error("Copy dependent command error.", e);
return dependentProcessDefinitionCreateCount;
}
List<DependentProcessDefinition> dependentProcessDefinitionList =
getComplementDependentDefinitionList(dependentCommand.getProcessDefinitionCode(),
CronUtils.getMaxCycle(schedules.get(0).getCrontab()), dependentCommand.getWorkerGroup(),
allLevelDependent);
dependentCommand.setTaskDependType(TaskDependType.TASK_POST);
for (DependentProcessDefinition dependentProcessDefinition : dependentProcessDefinitionList) {
// If the id is Integer, the auto-increment id will be obtained by mybatis-plus
// and causing duplicate when clone it.
dependentCommand.setId(null);
dependentCommand.setProcessDefinitionCode(dependentProcessDefinition.getProcessDefinitionCode());
dependentCommand.setProcessDefinitionVersion(dependentProcessDefinition.getProcessDefinitionVersion());
dependentCommand.setWorkerGroup(dependentProcessDefinition.getWorkerGroup());
Map<String, String> cmdParam = JSONUtils.toMap(dependentCommand.getCommandParam());
cmdParam.put(CMD_PARAM_START_NODES, String.valueOf(dependentProcessDefinition.getTaskDefinitionCode()));
dependentCommand.setCommandParam(JSONUtils.toJsonString(cmdParam));
log.info("Creating complement dependent command, commandInfo:{}.", command);
dependentProcessDefinitionCreateCount += commandService.createCommand(dependentCommand);
}
return dependentProcessDefinitionCreateCount;
}
/**
* get complement dependent online process definition list
*/
private List<DependentProcessDefinition> getComplementDependentDefinitionList(long processDefinitionCode,
CycleEnum processDefinitionCycle,
String workerGroup,
boolean allLevelDependent) {
List<DependentProcessDefinition> dependentProcessDefinitionList =
checkDependentProcessDefinitionValid(
processService.queryDependentProcessDefinitionByProcessDefinitionCode(processDefinitionCode),
processDefinitionCycle, workerGroup,
processDefinitionCode);
if (dependentProcessDefinitionList.isEmpty()) {
return dependentProcessDefinitionList;
}
if (allLevelDependent) {
List<DependentProcessDefinition> childList = new ArrayList<>(dependentProcessDefinitionList);
while (true) {
List<DependentProcessDefinition> childDependentList = childList
.stream()
.flatMap(dependentProcessDefinition -> checkDependentProcessDefinitionValid(
processService.queryDependentProcessDefinitionByProcessDefinitionCode(
dependentProcessDefinition.getProcessDefinitionCode()),
processDefinitionCycle,
workerGroup,
dependentProcessDefinition.getProcessDefinitionCode()).stream())
.collect(Collectors.toList());
if (childDependentList.isEmpty()) {
break;
}
dependentProcessDefinitionList.addAll(childDependentList);
childList = new ArrayList<>(childDependentList);
}
}
return dependentProcessDefinitionList;
}
/**
* Check whether the dependency cycle of the dependent node is consistent with the schedule cycle of
* the dependent process definition and if there is no worker group in the schedule, use the complement selection's
* worker group
*/
private List<DependentProcessDefinition> checkDependentProcessDefinitionValid(
List<DependentProcessDefinition> dependentProcessDefinitionList,
CycleEnum processDefinitionCycle,
String workerGroup,
long upstreamProcessDefinitionCode) {
List<DependentProcessDefinition> validDependentProcessDefinitionList = new ArrayList<>();
List<Long> processDefinitionCodeList =
dependentProcessDefinitionList.stream().map(DependentProcessDefinition::getProcessDefinitionCode)
.collect(Collectors.toList());
Map<Long, String> processDefinitionWorkerGroupMap =
workerGroupService.queryWorkerGroupByProcessDefinitionCodes(processDefinitionCodeList);
for (DependentProcessDefinition dependentProcessDefinition : dependentProcessDefinitionList) {
if (dependentProcessDefinition.getDependentCycle(upstreamProcessDefinitionCode) == processDefinitionCycle) {
if (processDefinitionWorkerGroupMap
.get(dependentProcessDefinition.getProcessDefinitionCode()) == null) {
dependentProcessDefinition.setWorkerGroup(workerGroup);
}
validDependentProcessDefinitionList.add(dependentProcessDefinition);
}
}
return validDependentProcessDefinitionList;
}
/**
* @param schedule
* @return check error return 0, otherwise 1
*/
private boolean isValidateScheduleTime(String schedule) {
Map<String, String> scheduleResult = JSONUtils.toMap(schedule);
if (scheduleResult == null) {
return false;
}
if (scheduleResult.containsKey(CMD_PARAM_COMPLEMENT_DATA_SCHEDULE_DATE_LIST)) {
if (scheduleResult.get(CMD_PARAM_COMPLEMENT_DATA_SCHEDULE_DATE_LIST) == null) {
return false;
}
}
if (scheduleResult.containsKey(CMD_PARAM_COMPLEMENT_DATA_START_DATE)) {
String startDate = scheduleResult.get(CMD_PARAM_COMPLEMENT_DATA_START_DATE);
String endDate = scheduleResult.get(CMD_PARAM_COMPLEMENT_DATA_END_DATE);
if (startDate == null || endDate == null) {
return false;
}
try {
ZonedDateTime start = DateUtils.stringToZoneDateTime(startDate);
ZonedDateTime end = DateUtils.stringToZoneDateTime(endDate);
if (start == null || end == null) {
return false;
}
if (start.isAfter(end)) {
log.error(
"Complement data parameter error, start time should be before end time, startDate:{}, endDate:{}.",
start, end);
return false;
}
} catch (Exception ex) {
log.warn("Parse schedule time error, startDate:{}, endDate:{}.", startDate, endDate);
return false;
}
}
return true;
}
/**
* @param scheduleTimeList
* @return remove duplicate date list
*/
private String removeDuplicates(String scheduleTimeList) {
if (StringUtils.isNotEmpty(scheduleTimeList)) {
return Arrays.stream(scheduleTimeList.split(COMMA)).map(String::trim).distinct()
.collect(Collectors.joining(COMMA));
}
return null;
}
/**
* query executing data of processInstance by master
* @param processInstanceId
* @return
*/
@Override
public WorkflowExecuteDto queryExecutingWorkflowByProcessInstanceId(Integer processInstanceId) {
ProcessInstance processInstance = processService.findProcessInstanceDetailById(processInstanceId).orElse(null);
if (processInstance == null) {
log.error("Process instance does not exist, processInstanceId:{}.", processInstanceId);
return null;
}
IWorkflowInstanceService iWorkflowInstanceService = SingletonJdkDynamicRpcClientProxyFactory
.getProxyClient(processInstance.getHost(), IWorkflowInstanceService.class);
return iWorkflowInstanceService.getWorkflowExecutingData(processInstanceId);
}
@Override
public Map<String, Object> execStreamTaskInstance(User loginUser, long projectCode, long taskDefinitionCode,
int taskDefinitionVersion,
int warningGroupId, String workerGroup, String tenantCode,
Long environmentCode,
Map<String, String> startParams, int dryRun) {
Project project = projectMapper.queryByCode(projectCode);
// check user access for project
Map<String, Object> result =
projectService.checkProjectAndAuth(loginUser, project, projectCode, WORKFLOW_START);
if (result.get(Constants.STATUS) != Status.SUCCESS) {
return result;
}
checkValidTenant(tenantCode);
checkMasterExists();
// todo dispatch improvement
List<Server> masterServerList = monitorService.getServerListFromRegistry(true);
Server server = masterServerList.get(0);
StreamingTaskTriggerRequest taskExecuteStartMessage = new StreamingTaskTriggerRequest();
taskExecuteStartMessage.setExecutorId(loginUser.getId());
taskExecuteStartMessage.setExecutorName(loginUser.getUserName());
taskExecuteStartMessage.setProjectCode(projectCode);
taskExecuteStartMessage.setTaskDefinitionCode(taskDefinitionCode);
taskExecuteStartMessage.setTaskDefinitionVersion(taskDefinitionVersion);
taskExecuteStartMessage.setWorkerGroup(workerGroup);
taskExecuteStartMessage.setTenantCode(tenantCode);
taskExecuteStartMessage.setWarningGroupId(warningGroupId);
taskExecuteStartMessage.setEnvironmentCode(environmentCode);
taskExecuteStartMessage.setStartParams(startParams);
taskExecuteStartMessage.setDryRun(dryRun);
IStreamingTaskOperator streamingTaskOperator = SingletonJdkDynamicRpcClientProxyFactory
.getProxyClient(server.getHost() + ":" + server.getPort(), IStreamingTaskOperator.class);
StreamingTaskTriggerResponse streamingTaskTriggerResponse =
streamingTaskOperator.triggerStreamingTask(taskExecuteStartMessage);
if (streamingTaskTriggerResponse.isSuccess()) {
log.info("Send task execute start command complete, response is {}.", streamingTaskOperator);
putMsg(result, Status.SUCCESS);
} else {
log.error(
"Start to execute stream task instance error, projectCode:{}, taskDefinitionCode:{}, taskVersion:{}, response: {}.",
projectCode, taskDefinitionCode, taskDefinitionVersion, streamingTaskTriggerResponse);
putMsg(result, Status.START_TASK_INSTANCE_ERROR);
}
return result;
}
}