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.
358 lines
14 KiB
358 lines
14 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.server.worker.runner; |
|
|
|
import org.apache.dolphinscheduler.common.enums.Event; |
|
import org.apache.dolphinscheduler.common.enums.ExecutionStatus; |
|
import org.apache.dolphinscheduler.common.enums.TaskType; |
|
import org.apache.dolphinscheduler.common.process.Property; |
|
import org.apache.dolphinscheduler.common.utils.CommonUtils; |
|
import org.apache.dolphinscheduler.common.utils.DateUtils; |
|
import org.apache.dolphinscheduler.common.utils.HadoopUtils; |
|
import org.apache.dolphinscheduler.common.utils.JSONUtils; |
|
import org.apache.dolphinscheduler.common.utils.OSUtils; |
|
import org.apache.dolphinscheduler.common.utils.RetryerUtils; |
|
import org.apache.dolphinscheduler.common.utils.StringUtils; |
|
import org.apache.dolphinscheduler.remote.command.Command; |
|
import org.apache.dolphinscheduler.remote.command.TaskExecuteAckCommand; |
|
import org.apache.dolphinscheduler.remote.command.TaskExecuteResponseCommand; |
|
import org.apache.dolphinscheduler.server.entity.TaskExecutionContext; |
|
import org.apache.dolphinscheduler.server.worker.cache.ResponceCache; |
|
import org.apache.dolphinscheduler.server.worker.cache.TaskExecutionContextCacheManager; |
|
import org.apache.dolphinscheduler.server.worker.cache.impl.TaskExecutionContextCacheManagerImpl; |
|
import org.apache.dolphinscheduler.server.worker.plugin.TaskPluginManager; |
|
import org.apache.dolphinscheduler.server.worker.processor.TaskCallbackService; |
|
import org.apache.dolphinscheduler.service.alert.AlertClientService; |
|
import org.apache.dolphinscheduler.service.bean.SpringApplicationContext; |
|
import org.apache.dolphinscheduler.spi.task.AbstractTask; |
|
import org.apache.dolphinscheduler.spi.task.TaskChannel; |
|
import org.apache.dolphinscheduler.spi.task.TaskRequest; |
|
|
|
import org.apache.commons.collections.MapUtils; |
|
|
|
import java.io.File; |
|
import java.io.IOException; |
|
import java.util.Date; |
|
import java.util.HashMap; |
|
import java.util.List; |
|
import java.util.Map; |
|
import java.util.Set; |
|
import java.util.concurrent.Delayed; |
|
import java.util.concurrent.ExecutionException; |
|
import java.util.concurrent.TimeUnit; |
|
import java.util.stream.Collectors; |
|
|
|
import org.apache.dolphinscheduler.spi.task.AbstractTask; |
|
import org.apache.dolphinscheduler.spi.task.TaskChannel; |
|
import org.apache.dolphinscheduler.spi.task.TaskRequest; |
|
import org.slf4j.Logger; |
|
import org.slf4j.LoggerFactory; |
|
|
|
import com.github.rholder.retry.RetryException; |
|
|
|
/** |
|
* task scheduler thread |
|
*/ |
|
public class TaskExecuteThread implements Runnable, Delayed { |
|
|
|
/** |
|
* logger |
|
*/ |
|
private final Logger logger = LoggerFactory.getLogger(TaskExecuteThread.class); |
|
|
|
/** |
|
* task instance |
|
*/ |
|
private TaskExecutionContext taskExecutionContext; |
|
|
|
/** |
|
* abstract task |
|
*/ |
|
private AbstractTask task; |
|
|
|
/** |
|
* task callback service |
|
*/ |
|
private TaskCallbackService taskCallbackService; |
|
|
|
/** |
|
* taskExecutionContextCacheManager |
|
*/ |
|
private TaskExecutionContextCacheManager taskExecutionContextCacheManager; |
|
|
|
/** |
|
* task logger |
|
*/ |
|
private Logger taskLogger; |
|
|
|
/** |
|
* alert client server |
|
*/ |
|
private AlertClientService alertClientService; |
|
|
|
private TaskPluginManager taskPluginManager; |
|
|
|
/** |
|
* constructor |
|
* @param taskExecutionContext taskExecutionContext |
|
* @param taskCallbackService taskCallbackService |
|
*/ |
|
public TaskExecuteThread(TaskExecutionContext taskExecutionContext, |
|
TaskCallbackService taskCallbackService, |
|
AlertClientService alertClientService) { |
|
this.taskExecutionContext = taskExecutionContext; |
|
this.taskCallbackService = taskCallbackService; |
|
this.taskExecutionContextCacheManager = SpringApplicationContext.getBean(TaskExecutionContextCacheManagerImpl.class); |
|
this.alertClientService = alertClientService; |
|
} |
|
|
|
public TaskExecuteThread(TaskExecutionContext taskExecutionContext, |
|
TaskCallbackService taskCallbackService, |
|
AlertClientService alertClientService, |
|
TaskPluginManager taskPluginManager) { |
|
this.taskExecutionContext = taskExecutionContext; |
|
this.taskCallbackService = taskCallbackService; |
|
this.taskExecutionContextCacheManager = SpringApplicationContext.getBean(TaskExecutionContextCacheManagerImpl.class); |
|
this.alertClientService = alertClientService; |
|
this.taskPluginManager = taskPluginManager; |
|
} |
|
|
|
@Override |
|
public void run() { |
|
|
|
TaskExecuteResponseCommand responseCommand = new TaskExecuteResponseCommand(taskExecutionContext.getTaskInstanceId()); |
|
try { |
|
logger.info("script path : {}", taskExecutionContext.getExecutePath()); |
|
// check if the OS user exists |
|
if (!OSUtils.getUserList().contains(taskExecutionContext.getTenantCode())) { |
|
String errorLog = String.format("tenantCode: %s does not exist", taskExecutionContext.getTenantCode()); |
|
taskLogger.error(errorLog); |
|
responseCommand.setStatus(ExecutionStatus.FAILURE.getCode()); |
|
responseCommand.setEndTime(new Date()); |
|
return; |
|
} |
|
|
|
if (taskExecutionContext.getStartTime() == null) { |
|
taskExecutionContext.setStartTime(new Date()); |
|
} |
|
if (taskExecutionContext.getCurrentExecutionStatus() != ExecutionStatus.RUNNING_EXECUTION) { |
|
changeTaskExecutionStatusToRunning(); |
|
} |
|
logger.info("the task begins to execute. task instance id: {}", taskExecutionContext.getTaskInstanceId()); |
|
|
|
// copy hdfs/minio file to local |
|
downloadResource(taskExecutionContext.getExecutePath(), |
|
taskExecutionContext.getResources(), |
|
logger); |
|
|
|
taskExecutionContext.setEnvFile(CommonUtils.getSystemEnvPath()); |
|
taskExecutionContext.setDefinedParams(getGlobalParamsMap()); |
|
|
|
taskExecutionContext.setTaskAppId(String.format("%s_%s", |
|
taskExecutionContext.getProcessInstanceId(), |
|
taskExecutionContext.getTaskInstanceId())); |
|
|
|
TaskChannel taskChannel = taskPluginManager.getTaskChannelMap().get(taskExecutionContext.getTaskType()); |
|
|
|
//TODO Temporary operation, To be adjusted |
|
TaskRequest taskRequest = JSONUtils.parseObject(JSONUtils.toJsonString(taskExecutionContext), TaskRequest.class); |
|
|
|
task = taskChannel.createTask(taskRequest); |
|
// task init |
|
this.task.init(); |
|
//init varPool |
|
this.task.getParameters().setVarPool(taskExecutionContext.getVarPool()); |
|
// task handle |
|
this.task.handle(); |
|
|
|
// task result process |
|
this.task.after(); |
|
|
|
responseCommand.setStatus(this.task.getExitStatus().getCode()); |
|
responseCommand.setEndTime(new Date()); |
|
responseCommand.setProcessId(this.task.getProcessId()); |
|
responseCommand.setAppIds(this.task.getAppIds()); |
|
responseCommand.setVarPool(JSONUtils.toJsonString(this.task.getParameters().getVarPool())); |
|
logger.info("task instance id : {},task final status : {}", taskExecutionContext.getTaskInstanceId(), this.task.getExitStatus()); |
|
} catch (Throwable e) { |
|
|
|
logger.error("task scheduler failure", e); |
|
kill(); |
|
responseCommand.setStatus(ExecutionStatus.FAILURE.getCode()); |
|
responseCommand.setEndTime(new Date()); |
|
responseCommand.setProcessId(task.getProcessId()); |
|
responseCommand.setAppIds(task.getAppIds()); |
|
} finally { |
|
taskExecutionContextCacheManager.removeByTaskInstanceId(taskExecutionContext.getTaskInstanceId()); |
|
ResponceCache.get().cache(taskExecutionContext.getTaskInstanceId(), responseCommand.convert2Command(), Event.RESULT); |
|
taskCallbackService.sendResult(taskExecutionContext.getTaskInstanceId(), responseCommand.convert2Command()); |
|
clearTaskExecPath(); |
|
} |
|
} |
|
|
|
/** |
|
* when task finish, clear execute path. |
|
*/ |
|
private void clearTaskExecPath() { |
|
logger.info("develop mode is: {}", CommonUtils.isDevelopMode()); |
|
|
|
if (!CommonUtils.isDevelopMode()) { |
|
// get exec dir |
|
String execLocalPath = taskExecutionContext.getExecutePath(); |
|
|
|
if (StringUtils.isEmpty(execLocalPath)) { |
|
logger.warn("task: {} exec local path is empty.", taskExecutionContext.getTaskName()); |
|
return; |
|
} |
|
|
|
if ("/".equals(execLocalPath)) { |
|
logger.warn("task: {} exec local path is '/', direct deletion is not allowed", taskExecutionContext.getTaskName()); |
|
return; |
|
} |
|
|
|
try { |
|
org.apache.commons.io.FileUtils.deleteDirectory(new File(execLocalPath)); |
|
logger.info("exec local path: {} cleared.", execLocalPath); |
|
} catch (IOException e) { |
|
logger.error("delete exec dir failed : {}", e.getMessage(), e); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* get global paras map |
|
* @return map |
|
*/ |
|
private Map<String, String> getGlobalParamsMap() { |
|
Map<String, String> globalParamsMap = new HashMap<>(16); |
|
|
|
// global params string |
|
String globalParamsStr = taskExecutionContext.getGlobalParams(); |
|
if (globalParamsStr != null) { |
|
List<Property> globalParamsList = JSONUtils.toList(globalParamsStr, Property.class); |
|
globalParamsMap.putAll(globalParamsList.stream().collect(Collectors.toMap(Property::getProp, Property::getValue))); |
|
} |
|
return globalParamsMap; |
|
} |
|
|
|
/** |
|
* kill task |
|
*/ |
|
public void kill() { |
|
if (task != null) { |
|
try { |
|
task.cancelApplication(true); |
|
} catch (Exception e) { |
|
logger.error(e.getMessage(),e); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* download resource file |
|
* |
|
* @param execLocalPath execLocalPath |
|
* @param projectRes projectRes |
|
* @param logger logger |
|
*/ |
|
private void downloadResource(String execLocalPath, Map<String, String> projectRes, Logger logger) { |
|
if (MapUtils.isEmpty(projectRes)) { |
|
return; |
|
} |
|
|
|
Set<Map.Entry<String, String>> resEntries = projectRes.entrySet(); |
|
|
|
for (Map.Entry<String,String> resource : resEntries) { |
|
String fullName = resource.getKey(); |
|
String tenantCode = resource.getValue(); |
|
File resFile = new File(execLocalPath, fullName); |
|
if (!resFile.exists()) { |
|
try { |
|
// query the tenant code of the resource according to the name of the resource |
|
String resHdfsPath = HadoopUtils.getHdfsResourceFileName(tenantCode, fullName); |
|
|
|
logger.info("get resource file from hdfs :{}", resHdfsPath); |
|
HadoopUtils.getInstance().copyHdfsToLocal(resHdfsPath, execLocalPath + File.separator + fullName, false, true); |
|
} catch (Exception e) { |
|
logger.error(e.getMessage(),e); |
|
throw new RuntimeException(e.getMessage()); |
|
} |
|
} else { |
|
logger.info("file : {} exists ", resFile.getName()); |
|
} |
|
} |
|
} |
|
|
|
/** |
|
* send an ack to change the status of the task. |
|
*/ |
|
private void changeTaskExecutionStatusToRunning() { |
|
taskExecutionContext.setCurrentExecutionStatus(ExecutionStatus.RUNNING_EXECUTION); |
|
Command ackCommand = buildAckCommand().convert2Command(); |
|
try { |
|
RetryerUtils.retryCall(() -> { |
|
taskCallbackService.sendAck(taskExecutionContext.getTaskInstanceId(), ackCommand); |
|
return Boolean.TRUE; |
|
}); |
|
} catch (ExecutionException | RetryException e) { |
|
logger.error(e.getMessage(), e); |
|
} |
|
} |
|
|
|
/** |
|
* build ack command. |
|
* |
|
* @return TaskExecuteAckCommand |
|
*/ |
|
private TaskExecuteAckCommand buildAckCommand() { |
|
TaskExecuteAckCommand ackCommand = new TaskExecuteAckCommand(); |
|
ackCommand.setTaskInstanceId(taskExecutionContext.getTaskInstanceId()); |
|
ackCommand.setStatus(taskExecutionContext.getCurrentExecutionStatus().getCode()); |
|
ackCommand.setStartTime(taskExecutionContext.getStartTime()); |
|
ackCommand.setLogPath(taskExecutionContext.getLogPath()); |
|
ackCommand.setHost(taskExecutionContext.getHost()); |
|
if (TaskType.SQL.getDesc().equalsIgnoreCase(taskExecutionContext.getTaskType()) || TaskType.PROCEDURE.getDesc().equalsIgnoreCase(taskExecutionContext.getTaskType())) { |
|
ackCommand.setExecutePath(null); |
|
} else { |
|
ackCommand.setExecutePath(taskExecutionContext.getExecutePath()); |
|
} |
|
return ackCommand; |
|
} |
|
|
|
/** |
|
* get current TaskExecutionContext |
|
* @return TaskExecutionContext |
|
*/ |
|
public TaskExecutionContext getTaskExecutionContext() { |
|
return this.taskExecutionContext; |
|
} |
|
|
|
@Override |
|
public long getDelay(TimeUnit unit) { |
|
return unit.convert(DateUtils.getRemainTime(taskExecutionContext.getFirstSubmitTime(), |
|
taskExecutionContext.getDelayTime() * 60L), TimeUnit.SECONDS); |
|
} |
|
|
|
@Override |
|
public int compareTo(Delayed o) { |
|
if (o == null) { |
|
return 1; |
|
} |
|
return Long.compare(this.getDelay(TimeUnit.MILLISECONDS), o.getDelay(TimeUnit.MILLISECONDS)); |
|
} |
|
}
|
|
|