package io.fathom.auto.haproxy; import io.fathom.auto.TimeSpan; import io.fathom.auto.processes.ProcessExecution; import io.fathom.auto.processes.Processes; import java.io.File; import java.io.IOException; import java.util.List; import java.util.concurrent.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.io.Files; public class HaproxyProcess { private static final Logger log = LoggerFactory.getLogger(HaproxyProcess.class); private static final File LOG_FILE = new File("/var/log/haproxy.log"); private static final File PID_FILE = new File("/var/run/haproxy"); public static final File CONFIG_FILE = new File("/opt/haproxy/haproxy.cfg"); private static final File HAPROXY_CMD = new File("/opt/haproxy/haproxy"); private String pids; public HaproxyProcess(String pids) { this.pids = pids; } public static boolean validate(File configFile) throws IOException { log.info("Doing haproxy validate"); ProcessBuilder pb = new ProcessBuilder(HAPROXY_CMD.getAbsolutePath(), "-c", "-f", configFile.getAbsolutePath()); ProcessExecution execution = Processes.run(pb, TimeSpan.seconds(10)); if (!execution.didExit()) { log.warn("Timeout while validating haproxy config."); } else { int exitCode = execution.getExitCode(); if (exitCode == 0) { log.info("Validated config file"); return true; } else { log.warn("Error validating haproxy config. Exit code {}", exitCode); String config = Files.toString(configFile, Charsets.UTF_8); log.warn("Bad haproxy config: {}", config); } } log.warn("stdout: {}", execution.getStdout()); log.warn("stderr: {}", execution.getStderr()); return false; } public static HaproxyProcess start() throws IOException { return start(null); } private static HaproxyProcess start(Long pid) throws IOException { File logFile = LOG_FILE; File pidFile = PID_FILE; File configFile = CONFIG_FILE; List<String> args = Lists.newArrayList(); args.add(HAPROXY_CMD.getAbsolutePath()); // Daemon args.add("-D"); // pid file args.add("-p"); args.add(pidFile.getAbsolutePath()); // config file args.add("-f"); args.add(configFile.getAbsolutePath()); // reload if (pid != null) { args.add("-sf"); args.add(pid.toString()); } ProcessBuilder pb = new ProcessBuilder(args); pb.redirectOutput(logFile); pb.redirectError(logFile); if (pid == null) { log.info("Starting haproxy: {}", Joiner.on(" ").join(args)); } else { log.info("Reloading haproxy: {}", Joiner.on(" ").join(args)); } Process process = pb.start(); int exitCode = -1; try { exitCode = Processes.waitForExit(process, TimeSpan.seconds(5)); } catch (TimeoutException e) { log.warn("Timeout waiting for haproxy to daemonize"); return null; } if (exitCode != 0) { log.warn("HaProxy exited with error code. Exit code {}", exitCode); return null; } String pids = Files.toString(pidFile, Charsets.UTF_8); log.info("Started haproxy; seems to be stable. Pids {}", pids); return new HaproxyProcess(pids); } public static HaproxyProcess find() throws IOException { if (!PID_FILE.exists()) { return null; } String pids = Files.toString(PID_FILE, Charsets.UTF_8); pids = pids.trim(); if (Strings.isNullOrEmpty(pids)) { return null; } log.info("Found existing haproxy: {}", pids); return new HaproxyProcess(pids); } public void reload() throws IOException, InterruptedException { log.warn("Reloading haproxy"); String pidString = this.pids.trim(); if (pidString.contains("\n")) { pidString = pidString.substring(0, pidString.indexOf('\n')); pidString = pidString.trim(); } long pid = Long.valueOf(pidString); HaproxyProcess process = HaproxyProcess.start(pid); this.pids = process.pids; } }