package com.hubspot.baragon.agent.lbs; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import org.apache.commons.exec.CommandLine; import org.apache.commons.exec.DefaultExecutor; import org.apache.commons.exec.ExecuteException; import org.apache.commons.exec.ExecuteWatchdog; import org.apache.commons.exec.PumpStreamHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codahale.metrics.annotation.Timed; import com.google.common.base.Charsets; import com.google.common.base.Optional; import com.google.inject.Inject; import com.google.inject.Singleton; import com.hubspot.baragon.agent.config.LoadBalancerConfiguration; import com.hubspot.baragon.exceptions.InvalidConfigException; import com.hubspot.baragon.exceptions.LbAdapterExecuteException; import com.hubspot.baragon.exceptions.WorkerLimitReachedException; @Singleton public class LocalLbAdapter { private static final Logger LOG = LoggerFactory.getLogger(LocalLbAdapter.class); private final LoadBalancerConfiguration loadBalancerConfiguration; @Inject public LocalLbAdapter(LoadBalancerConfiguration loadBalancerConfiguration) { this.loadBalancerConfiguration = loadBalancerConfiguration; } private int executeWithTimeout(CommandLine command, int timeout) throws LbAdapterExecuteException, IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DefaultExecutor executor = new DefaultExecutor(); executor.setStreamHandler(new PumpStreamHandler(baos)); executor.setWatchdog(new ExecuteWatchdog(timeout)); try { return executor.execute(command); } catch (ExecuteException e) { throw new LbAdapterExecuteException(baos.toString(Charsets.UTF_8.name()), e, command.toString()); } } private Optional<Integer> getOutputAsInt(String command) { try { ProcessBuilder processBuilder = new ProcessBuilder(command); Process process = processBuilder.start(); try (BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8));) { List<String> output = new ArrayList<>(); String line = br.readLine(); while (line != null) { output.add(line); line = br.readLine(); } return Optional.of(Integer.parseInt(output.get(0).trim())); } catch (Exception e) { LOG.error("Could not get worker count from command {}", command, e); return Optional.absent(); } } catch (IOException ioe) { LOG.error("Could not get worker count from command {}", command, ioe); return Optional.absent(); } } @Timed public void checkConfigs() throws InvalidConfigException { try { final long start = System.currentTimeMillis(); final int exitCode = executeWithTimeout(CommandLine.parse(loadBalancerConfiguration.getCheckConfigCommand()), loadBalancerConfiguration.getCommandTimeoutMs()); LOG.info("Checked configs via '{}' in {}ms (exit code = {})", loadBalancerConfiguration.getCheckConfigCommand(), System.currentTimeMillis() - start, exitCode); } catch (LbAdapterExecuteException e) { throw new InvalidConfigException(e.getOutput()); } catch (IOException e) { throw new InvalidConfigException(e.getMessage()); } } @Timed public void reloadConfigs() throws LbAdapterExecuteException, IOException, WorkerLimitReachedException { if (loadBalancerConfiguration.isLimitWorkerCount()) { if (loadBalancerConfiguration.getWorkerCountCommand().isPresent()) { checkWorkerCount(); } else { LOG.warn("Asked to limit worker count, but no workerCountCommand was specified"); } } final long start = System.currentTimeMillis(); final int exitCode = executeWithTimeout(CommandLine.parse(loadBalancerConfiguration.getReloadConfigCommand()), loadBalancerConfiguration.getCommandTimeoutMs()); LOG.info("Reloaded configs via '{}' in {}ms (exit code = {})", loadBalancerConfiguration.getReloadConfigCommand(), System.currentTimeMillis() - start, exitCode); } private void checkWorkerCount() throws WorkerLimitReachedException { Optional<Integer> workerCount = getOutputAsInt(loadBalancerConfiguration.getWorkerCountCommand().get()); LOG.debug("Current worker count: {}", workerCount); if (!workerCount.isPresent() || workerCount.get() > loadBalancerConfiguration.getMaxLbWorkerCount()) { throw new WorkerLimitReachedException(String.format("%s LB workers currently running, wait for old workers to exit before attempting to reload configs", workerCount.get())); } } }