package org.zookeeper.flume; import org.apache.zookeeper.AsyncCallback; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.apache.zookeeper.data.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.zookeeper.Executor; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.Optional; import java.util.Properties; /** * @author keyki */ public class ConfigWatcher implements Watcher, AsyncCallback.StatCallback, Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(ConfigWatcher.class); private final ZooKeeper zooKeeper; private final String root; private final String flumeHome; private Process process; public ConfigWatcher(String hostPort, String root, String flumeHome) throws IOException { this.root = root; this.flumeHome = flumeHome; this.zooKeeper = new ZooKeeper(hostPort, 3000, this); } @Override public void run() { try { synchronized (this) { while (true) { wait(); } } } catch (InterruptedException e) { LOGGER.error("Error during run", e); } } @Override public void process(WatchedEvent event) { if (event.getType() == Event.EventType.None && event.getState() == Event.KeeperState.SyncConnected) { LOGGER.info("Connected to zookeeper"); zooKeeper.exists(root, true, this, null); } else { String path = event.getPath(); if (path != null) { LOGGER.info("Change happened on {}", path); zooKeeper.exists(path, true, this, null); } } } @Override public void processResult(int rc, String path, Object ctx, Stat stat) { LOGGER.info("Stat arrived for node {}", path); switch (KeeperException.Code.get(rc)) { case OK: runProcess(path); break; case NONODE: case SESSIONEXPIRED: case NOAUTH: break; default: zooKeeper.exists(path, true, this, null); } } private void runProcess(String path) { try { byte[] data = zooKeeper.getData(path, false, null); if (data != null) { killProcess(); FlumeProperties properties = loadProperties(data); replaceSinkPath(properties, path); String absolutePath = writePropertiesToFile(properties); String exec = "flume-ng agent -n " + getAgentName(properties) + " -f " + absolutePath + " -c " + flumeHome + "/conf"; LOGGER.info("Running process {}", exec); process = Runtime.getRuntime().exec(exec); new Executor.StreamWriter(process.getInputStream(), System.out); new Executor.StreamWriter(process.getErrorStream(), System.out); } } catch (KeeperException | InterruptedException | IOException e) { LOGGER.error("Error occurred during process run.", e); } } private String writePropertiesToFile(FlumeProperties properties) throws IOException { String fileName = root.substring(1).replaceAll("/", "-") + ".conf"; try (FileOutputStream fileOutputStream = new FileOutputStream(fileName)) { fileOutputStream.write(properties.toString().getBytes()); } String absolutePath = new File(fileName).getAbsolutePath(); LOGGER.info("Properties saved to {}", absolutePath); return absolutePath; } private void replaceSinkPath(Properties properties, String path) { getSinkKey(properties).ifPresent(key -> { if (isHdfsSink(properties, key)) { String fsPath = properties.getProperty(key + ".hdfs.path"); String newPath = fsPath.substring(0, fsPath.lastIndexOf('/')) + path; properties.setProperty(key + ".hdfs.path", newPath); } }); } private boolean isHdfsSink(Properties properties, String sinkKey) { return "hdfs".equals(properties.getProperty(sinkKey + ".type")); } private Optional<String> getSinkKey(Properties properties) { return properties.keySet().stream().findAny().map(k -> { String agent = getAgentName(properties); String sink = properties.getProperty(agent + ".sinks"); return agent + ".sinks." + sink; }); } private String getAgentName(Properties properties) { String agent = ""; Optional<String> optional = properties.keySet().stream().findAny().map(k -> { String key = String.valueOf(k); return key.substring(0, key.indexOf('.')); }); if (optional.isPresent()) { agent = optional.get(); } return agent; } private FlumeProperties loadProperties(byte[] data) { FlumeProperties properties = new FlumeProperties(); try (ByteArrayInputStream stream = new ByteArrayInputStream(data)) { properties.load(stream); } catch (IOException e) { LOGGER.error("Error parsing the config file", e); } return properties; } private void killProcess() { if (process != null) { LOGGER.info("Killing process.."); process.destroy(); try { process.waitFor(); LOGGER.info("Successfully killed the process."); } catch (InterruptedException e) { LOGGER.error("Error killing the process", e); } } } public static void main(String[] args) throws IOException { new ConfigWatcher("localhost:2181", "/companyA/flume", "/usr/local/apache-flume-1.5.0-SNAPSHOT-bin").run(); } }