package io.fathom.auto.fathomcloud;
import io.fathom.auto.TimeSpan;
import io.fathom.auto.fathomcloud.config.ConfigurationFileTemplate;
import io.fathom.auto.fathomcloud.config.LogConfigurationTemplate;
import io.fathom.auto.processes.Pid;
import io.fathom.auto.processes.ProcFs;
import io.fathom.auto.processes.ProcessExecution;
import io.fathom.auto.processes.Processes;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.io.Files;
public class CloudServerProcess {
private static final String MAIN_CLASS_NAME = "io.fathom.cloud.CloudServer";
private static final Logger log = LoggerFactory.getLogger(CloudServerProcess.class);
private final Pid pid;
final File instanceDir;
private final FathomCloudConfig config;
final File installDir;
public CloudServerProcess(FathomCloudConfig config, Pid pid) {
this.instanceDir = config.getInstanceDir();
this.installDir = config.getInstallDir();
this.config = config;
this.pid = pid;
}
static File getPidFile(File instanceDir) {
return new File(instanceDir, "fathomcloud.pid");
}
static File getLogFile(File instanceDir) {
return new File(instanceDir, "fathomcloud.log");
}
static File getConfigFile(File instanceDir) {
return new File(instanceDir, "configuration.properties");
}
static File getLogConfigFile(File instanceDir) {
return new File(instanceDir, "logback.xml");
}
public static CloudServerProcess start(FathomCloudConfig config) throws IOException {
File instanceDir = config.getInstanceDir();
configureInstance(config);
ProcessBuilder pb = buildLauncherProcess(config);
ProcessExecution execution = Processes.run(pb, TimeSpan.minutes(1));
if (!execution.didExit()) {
throw new IOException("Timeout while starting Process");
} else {
if (execution.getExitCode() == 0) {
log.info("Process started OK");
// TODO: Poll loop with timeout?
TimeSpan.seconds(2).sleep();
File pidFile = getPidFile(instanceDir);
Pid pid = Pid.read(pidFile);
if (pid == null) {
throw new IOException("Process started, but could not read pid from file");
}
return new CloudServerProcess(config, pid);
} else {
throw new IOException("Error starting Process process");
}
}
}
private static void configureInstance(FathomCloudConfig config) throws IOException {
File instanceDir = config.getInstanceDir();
{
ConfigurationFileTemplate template = new ConfigurationFileTemplate();
template.write(getConfigFile(instanceDir), config);
}
{
LogConfigurationTemplate template = new LogConfigurationTemplate();
template.write(getLogConfigFile(instanceDir), config);
}
}
private static ProcessBuilder buildLauncherProcess(FathomCloudConfig config) throws IOException {
File instanceDir = config.getInstanceDir();
File logFile = getLogFile(instanceDir);
File pidFile = getPidFile(instanceDir);
File tmpDir = new File("/tmp");
// Be sure we don't end up as the process parent...
File trampolineScript = new File(tmpDir, "run-fathomcloud.sh");
{
StringWriter stringWriter = new StringWriter();
try (PrintWriter s = new PrintWriter(stringWriter)) {
s.println("#!/bin/bash");
s.println("");
List<String> args = Lists.newArrayList();
args.add("su");
args.add("fathomcloud");
args.add("-c");
args.add("\"java");
args.add("-cp '/opt/fathomcloud/lib/*'");
args.add("-Dconf=/var/fathomcloud/configuration.properties");
args.add("-Dlogback.configurationFile=/var/fathomcloud/logback.xml");
args.add("-Dzookeeper.jmx.log4j.disable=true");
args.add(MAIN_CLASS_NAME + "\"");
args.add(">");
args.add(logFile.getAbsolutePath());
args.add("2>&1");
args.add("&");
s.println(Joiner.on(" ").join(args));
s.println("pid=$!");
s.println("echo ${pid} > " + pidFile.getAbsolutePath());
}
Files.write(stringWriter.toString(), trampolineScript, Charsets.UTF_8);
}
List<String> args = Lists.newArrayList();
args.add("/bin/bash"); // Avoids need to chmod
args.add(trampolineScript.getAbsolutePath());
ProcessBuilder pb = new ProcessBuilder(args);
return pb;
}
public static CloudServerProcess find(FathomCloudConfig config) throws IOException {
File pidFile = getPidFile(config.getInstanceDir());
Pid pid = Pid.read(pidFile);
if (pid == null) {
return null;
}
CloudServerProcess process = new CloudServerProcess(config, pid);
if (!process.isRunning()) {
log.info("Found process in pid file, but was not our process: {}", pid);
pidFile.delete();
process = null;
} else {
log.info("Found existing process: {}", pid);
}
return process;
}
public boolean isRunning() throws IOException {
ProcFs.Process process = ProcFs.findProcess(pid);
if (process == null) {
log.info("Process went away (no process with pid)");
return false;
}
List<String> cmdline = process.getCmdline();
return isOurProcess(cmdline);
}
private boolean isOurProcess(List<String> cmdline) {
if (cmdline == null) {
// Process stopped
log.info("Process went away (no process with pid)");
return false;
}
if (cmdline.isEmpty()) {
log.info("Process went away (cmdline empty)");
return false;
}
String c = Joiner.on(" ").join(cmdline);
if (c.indexOf(MAIN_CLASS_NAME) == -1) {
log.info("Process went away (cmdline is {})", c);
return false;
}
return true;
}
}