package com.vip.saturn.job.shell;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.PumpStreamHandler;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.fastjson.JSON;
import com.vip.saturn.job.SaturnJobReturn;
import com.vip.saturn.job.SaturnSystemErrorGroup;
import com.vip.saturn.job.SaturnSystemReturnCode;
import com.vip.saturn.job.basic.AbstractSaturnJob;
import com.vip.saturn.job.basic.SaturnExecutionContext;
import com.vip.saturn.job.utils.ScriptPidUtils;
import com.vip.saturn.job.utils.SystemEnvProperties;
public class ScriptJobRunner {
static Logger log = LoggerFactory.getLogger(ScriptJobRunner.class);
private static final String PREFIX_COMAND = " source /etc/profile; ";
private Map<String, String> envMap = new HashMap<String, String>();
private AbstractSaturnJob job;
private Integer item;
private String itemValue;
private SaturnExecutionContext saturnExecutionContext;
private String jobName;
private SaturnExecuteWatchdog watchdog;
private boolean businessReturned = false;
private File saturnOutputFile;
public ScriptJobRunner(Map<String, String> envMap, AbstractSaturnJob job, Integer item, String itemValue, SaturnExecutionContext saturnExecutionContext) {
if (envMap != null) {
this.envMap.putAll(envMap);
}
this.job = job;
this.item = item;
this.itemValue = itemValue;
this.saturnExecutionContext = saturnExecutionContext;
if(job != null) {
this.jobName = job.getJobName();
}
}
public boolean isBusinessReturned() {
return businessReturned;
}
private void createSaturnJobReturnFile() throws IOException {
if (envMap.containsKey(SystemEnvProperties.NAME_VIP_SATURN_OUTPUT_PATH)) {
String saturnOutputPath = envMap.get(SystemEnvProperties.NAME_VIP_SATURN_OUTPUT_PATH);
saturnOutputFile = new File(saturnOutputPath);
if (!saturnOutputFile.exists()) {
FileUtils.forceMkdir(saturnOutputFile.getParentFile());
saturnOutputFile.createNewFile();//NOSONAR
}
}
}
private CommandLine createCommandLine(Map<String, String> env) {
StringBuilder envStringBuilder = new StringBuilder();
if (envMap != null && !envMap.isEmpty()) {
for (Entry<String, String> envEntrySet : envMap.entrySet()) {
envStringBuilder.append("export " + envEntrySet.getKey() + "=" + envEntrySet.getValue()).append(";");
}
}
String execParameter = envStringBuilder.toString() + PREFIX_COMAND + ScriptPidUtils.filterEnvInCmdStr(env, itemValue);
// CommandLine cmdLine = CommandLine.parse(execParameter);
final CommandLine cmdLine = new CommandLine("/bin/sh");
cmdLine.addArguments(new String[]{"-c", execParameter}, false);
return cmdLine;
}
private SaturnJobReturn readSaturnJobReturn() {
SaturnJobReturn tmp = null;
if(saturnOutputFile != null && saturnOutputFile.exists()) {
try {
String fileContents = FileUtils.readFileToString(saturnOutputFile);
if (fileContents != null && !fileContents.trim().isEmpty()) {
tmp = JSON.parseObject(fileContents.trim(), SaturnJobReturn.class);
businessReturned = true; // 脚本成功返回数据
}
} catch (Throwable t) {
log.error("[{" + jobName + "}] msg={" + jobName + "}-{" + item + "} read SaturnJobReturn from {" + saturnOutputFile.getAbsolutePath() + "} error", t);
tmp = new SaturnJobReturn(SaturnSystemReturnCode.USER_FAIL, "Exception: " + t.toString(), SaturnSystemErrorGroup.FAIL);
}
}
return tmp;
}
public SaturnExecuteWatchdog getWatchdog() {
if(watchdog == null) {
long timeoutSeconds = saturnExecutionContext.getTimetoutSeconds();
if (timeoutSeconds > 0) {
watchdog = new SaturnExecuteWatchdog(timeoutSeconds * 1000, jobName, item, itemValue);
log.info("[{}] msg=Job {} enable timeout control : {} s ", jobName, jobName, timeoutSeconds);
} else { //需要指定超时值,才会启用watchdog: 强行指定为5年
watchdog = new SaturnExecuteWatchdog(5L * 365 * 24 * 3600 * 1000, jobName, item, itemValue);
if(log.isDebugEnabled()){
log.debug("[{}] msg=Job {} disable timeout control", jobName, jobName);
}
}
watchdog.setExecutorName(job.getExecutorName());
}
return watchdog;
}
public SaturnJobReturn runJob() {
SaturnJobReturn saturnJobReturn = null;
//String jobName = job.getJobName();
long timeoutSeconds = saturnExecutionContext.getTimetoutSeconds();
try {
createSaturnJobReturnFile();
ProcessOutputStream processOutputStream = new ProcessOutputStream(1);
DefaultExecutor executor = new DefaultExecutor();
PumpStreamHandler streamHandler = new PumpStreamHandler(processOutputStream);
streamHandler.setStopTimeout(timeoutSeconds * 1000); //关闭线程等待时间, (注意commons-exec会固定增加2秒的addition)
executor.setExitValue(0);
executor.setStreamHandler(streamHandler);
if(watchdog == null) {
getWatchdog();
}
executor.setWatchdog(watchdog);
// filter env key in execParameter. like cd ${mypath} -> cd /root/my.
Map<String, String> env = ScriptPidUtils.loadEnv();
CommandLine commandLine = createCommandLine(env);
try {
long start = System.currentTimeMillis();
log.info("[{}] msg=Begin executing {}-{} {}", jobName, jobName, item, commandLine);
int exitValue = executor.execute(commandLine, env);
long end = System.currentTimeMillis();
log.info("[{}] msg=Finish executing {}-{} {}, the exit value is {}, cost={}ms", jobName, jobName, item, commandLine, exitValue, (end - start));
SaturnJobReturn tmp = readSaturnJobReturn();
if(tmp == null) {
tmp = new SaturnJobReturn("the exit value is " + exitValue);
}
saturnJobReturn = tmp;
} catch (Exception e) {
ExecuteWatchdog watchDog = executor.getWatchdog();
String errMsg = e.toString();
if (watchDog != null && watchDog.killedProcess()) { // 超时
saturnJobReturn = new SaturnJobReturn(SaturnSystemReturnCode.SYSTEM_FAIL, "Timeout(" + timeoutSeconds + "s): " + errMsg, SaturnSystemErrorGroup.TIMEOUT);
log.error("[{}] msg={}-{} Timeout: {}", jobName, jobName, item, errMsg);
} else { // 出错
saturnJobReturn = new SaturnJobReturn(SaturnSystemReturnCode.USER_FAIL, "Exception: " + errMsg, SaturnSystemErrorGroup.FAIL);
log.error("[{" + jobName + "}] msg={" + jobName + "}-{" + item + "} Exception: " + errMsg, e);
}
} finally {
try {
// 将日志set进jobLog, 写不写zk再由ExecutionService控制
String jobLog = processOutputStream.getJobLog();
saturnExecutionContext.putJobLog(item, jobLog);
// 提供给saturn-job-executor.log日志输出shell命令jobLog,以后若改为重定向到日志,则可删除此输出
System.out.println("[" + jobName + "] msg=" + jobName + "-" + item + ":" + jobLog);//NOSONAR
log.info("[{}] msg={}-{}: {}", jobName, jobName, item, jobLog);
processOutputStream.close();
} catch (Exception ex) {
log.error("[{}] msg={}-{} Error at closing output stream. Should not be concern: {}", jobName, jobName, item, ex);
}
try {
streamHandler.stop();
} catch (IOException ex) {
log.debug("[{}] msg={}-{} Error at closing log stream. Should not be concern: {}", jobName, jobName, item, ex);
}
ScriptPidUtils.removePidFile(job.getExecutorName(), jobName,""+ item, watchdog.getProcessId());
}
} catch (Throwable t) {
log.error("[{" + jobName + "}] msg={" + jobName + "}-{" + item + "} Exception", t);
saturnJobReturn = new SaturnJobReturn(SaturnSystemReturnCode.SYSTEM_FAIL, "Exception: " + t.toString(), SaturnSystemErrorGroup.FAIL);
} finally {
FileUtils.deleteQuietly(saturnOutputFile.getParentFile());
}
if(saturnJobReturn.getProp() == null){
saturnJobReturn.setProp(new HashMap());
}
return saturnJobReturn;
}
}