
com.hubspot.singularity.executor.shells.SingularityExecutorShellCommandRunner Maven / Gradle / Ivy
package com.hubspot.singularity.executor.shells;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import com.google.common.base.CharMatcher;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.hubspot.mesos.MesosUtils;
import com.hubspot.singularity.SingularityTaskShellCommandRequest;
import com.hubspot.singularity.SingularityTaskShellCommandUpdate.UpdateType;
import com.hubspot.singularity.executor.config.SingularityExecutorConfiguration;
import com.hubspot.singularity.executor.task.SingularityExecutorTask;
import com.hubspot.singularity.executor.task.SingularityExecutorTaskProcessCallable;
public class SingularityExecutorShellCommandRunner {
private final SingularityTaskShellCommandRequest shellRequest;
private final SingularityExecutorTask task;
private final SingularityExecutorTaskProcessCallable taskProcess;
private final ListeningExecutorService shellCommandExecutorService;
private final SingularityExecutorShellCommandUpdater shellCommandUpdater;
private final SingularityExecutorConfiguration executorConfiguration;
@SuppressWarnings("serial")
private static class InvalidShellCommandException extends RuntimeException {
public InvalidShellCommandException(String message) {
super(message);
}
}
public static String convertCommandNameToLogfileName(String str) {
return CharMatcher.WHITESPACE.or(CharMatcher.is('/')).replaceFrom(str, '-').toLowerCase();
}
public SingularityExecutorShellCommandRunner(SingularityTaskShellCommandRequest shellRequest, SingularityExecutorConfiguration executorConfiguration, SingularityExecutorTask task,
SingularityExecutorTaskProcessCallable taskProcess, ListeningExecutorService shellCommandExecutorService, SingularityExecutorShellCommandUpdater shellCommandUpdater) {
this.shellRequest = shellRequest;
this.executorConfiguration = executorConfiguration;
this.task = task;
this.taskProcess = taskProcess;
this.shellCommandUpdater = shellCommandUpdater;
this.shellCommandExecutorService = shellCommandExecutorService;
}
public SingularityTaskShellCommandRequest getShellRequest() {
return shellRequest;
}
public SingularityExecutorTask getTask() {
return task;
}
public ProcessBuilder buildProcessBuilder(List command, File outputFile) {
ProcessBuilder builder = new ProcessBuilder(command);
builder.redirectOutput(ProcessBuilder.Redirect.appendTo(outputFile));
builder.redirectError(ProcessBuilder.Redirect.appendTo(outputFile));
return builder;
}
public void start() {
List command = null;
try {
command = buildCommand();
} catch (InvalidShellCommandException isce) {
shellCommandUpdater.sendUpdate(UpdateType.INVALID, Optional.of(isce.getMessage()), Optional.absent());
return;
}
final String outputFilename = executorConfiguration.getShellCommandOutFile()
.replace("{NAME}", shellRequest.getShellCommand().getLogfileName().or(convertCommandNameToLogfileName(shellRequest.getShellCommand().getName())))
.replace("{TIMESTAMP}", Long.toString(shellRequest.getTimestamp()));
shellCommandUpdater.sendUpdate(UpdateType.ACKED, Optional.of(Joiner.on(" ").join(command)), Optional.of(outputFilename));
final File outputFile = MesosUtils.getTaskDirectoryPath(getTask().getTaskId()).resolve(outputFilename).toFile();
SingularityExecutorShellCommandRunnerCallable callable = new SingularityExecutorShellCommandRunnerCallable(task.getLog(), shellCommandUpdater, buildProcessBuilder(command, outputFile), outputFile);
ListenableFuture shellFuture = shellCommandExecutorService.submit(callable);
Futures.addCallback(shellFuture, new FutureCallback() {
@Override
public void onSuccess(Integer result) {
task.getLog().info("ShellRequest {} finished with {}", shellRequest, result);
shellCommandUpdater.sendUpdate(UpdateType.FINISHED, Optional.of(String.format("Finished with code %s", result)), Optional.absent());
}
@Override
public void onFailure(Throwable t) {
task.getLog().warn("ShellRequest {} failed", shellRequest, t);
shellCommandUpdater.sendUpdate(UpdateType.FAILED, Optional.of(String.format("Failed - %s (%s)", t.getClass().getSimpleName(), t.getMessage())), Optional.absent());
}
});
}
private List buildCommand() {
Optional matchingShellCommandDescriptor = Iterables.tryFind(executorConfiguration.getShellCommands(), new Predicate() {
@Override
public boolean apply(SingularityExecutorShellCommandDescriptor input) {
return input.getName().equals(shellRequest.getShellCommand().getName());
}
});
if (!matchingShellCommandDescriptor.isPresent()) {
throw new InvalidShellCommandException(String.format("%s not found in matching commands %s", shellRequest.getShellCommand().getName(), executorConfiguration.getShellCommands()));
}
final SingularityExecutorShellCommandDescriptor shellCommandDescriptor = matchingShellCommandDescriptor.get();
List command = new ArrayList<>();
if (!shellCommandDescriptor.isSkipCommandPrefix()) {
command.addAll(executorConfiguration.getShellCommandPrefix());
}
boolean isDocker = task.getTaskInfo().hasContainer() && task.getTaskInfo().getContainer().hasDocker();
if (isDocker) {
command.addAll(Arrays.asList("docker", "exec", String.format("%s%s", executorConfiguration.getDockerPrefix(), task.getTaskId())));
}
command.addAll(shellCommandDescriptor.getCommand());
for (int i = 0; i < command.size(); i++) {
if (command.get(i).equals(executorConfiguration.getShellCommandPidPlaceholder())) {
int pid;
Path pidFilePath = MesosUtils.getTaskDirectoryPath(getTask().getTaskId()).resolve(executorConfiguration.getShellCommandPidFile());
if (Files.exists(pidFilePath)) {
Scanner scanner = null;
try {
scanner = new Scanner(pidFilePath);
scanner.useDelimiter("\\Z");
pid = Integer.parseInt(scanner.next());
} catch (Exception e) {
throw new InvalidShellCommandException(String.format("No PID found due to exception reading pid file: %s", e.getMessage()));
} finally {
if (scanner != null) {
try {
scanner.close();
} catch (Throwable t) {}
}
}
} else if (isDocker) {
pid = 1;
} else {
if (!taskProcess.getCurrentPid().isPresent()) {
throw new InvalidShellCommandException("No PID found");
}
pid = taskProcess.getCurrentPid().get();
}
command.set(i, Integer.toString(pid));
} else if (command.get(i).equals(executorConfiguration.getShellCommandUserPlaceholder())) {
command.set(i, taskProcess.getTask().getExecutorData().getUser().or(executorConfiguration.getDefaultRunAsUser()));
}
}
if (shellRequest.getShellCommand().getOptions().isPresent()) {
for (SingularityExecutorShellCommandOptionDescriptor option : shellCommandDescriptor.getOptions()) {
if (shellRequest.getShellCommand().getOptions().get().contains(option.getName())) {
command.add(option.getFlag());
}
}
}
return command;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy