Java tutorial
/* * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.opentable.etcd; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.lang.ProcessBuilder.Redirect; import java.net.InetAddress; import java.net.ServerSocket; import java.net.UnknownHostException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.nio.file.attribute.PosixFilePermissions; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.concurrent.TimeUnit; import javax.annotation.concurrent.GuardedBy; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import com.opentable.io.DeleteRecursively; public class EtcdInstance implements Closeable { private static final Logger LOGGER = LoggerFactory.getLogger(EtcdInstance.class); private static final String ETCD_PACKAGE_PATH_FMT = "/etcd/etcd-%s"; private static final String ETCD_LOCATION; static { final String etcdPath = String.format(ETCD_PACKAGE_PATH_FMT, (System.getProperty("os.name") + "-" + System.getProperty("os.arch")).replaceAll(" ", "") .toLowerCase(Locale.ROOT)); final boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows"); Path etcdFile = null; try { etcdFile = Files.createTempFile("etcd", "bin"); try (InputStream etcd = EtcdInstance.class.getResourceAsStream(etcdPath)) { if (etcd == null) { throw new IllegalStateException("Could not find " + etcdPath + " on classpath"); } Files.copy(etcd, etcdFile, StandardCopyOption.REPLACE_EXISTING); if (!isWindows) { Files.setPosixFilePermissions(etcdFile, PosixFilePermissions.fromString("r-x------")); } } etcdFile.toFile().deleteOnExit(); ETCD_LOCATION = etcdFile.toString(); } catch (IOException e) { if (etcdFile != null) { try { Files.delete(etcdFile); } catch (IOException e1) { throw new ExceptionInInitializerError(e1); } } throw new ExceptionInInitializerError(e); } } public EtcdInstance(EtcdConfiguration configuration) { this.configuration = configuration; this.id = ObjectUtils.firstNonNull(configuration.getNodeName(), RandomStringUtils.randomAlphabetic(10)); } private final EtcdConfiguration configuration; private final String id; @GuardedBy("this") private Process etcdServer; @GuardedBy("this") private int clientPort; @GuardedBy("this") private int peerPort; public void startUnchecked() { try { start(); } catch (IOException e) { throw new RuntimeException(e); } } @SuppressFBWarnings("SWL_SLEEP_WITH_LOCK_HELD") public synchronized void start() throws IOException { if (etcdServer != null) { throw new IllegalStateException("already started"); } if (clientPort == 0) { clientPort = findPort(configuration.getClientPort()); } if (peerPort == 0) { peerPort = findPort(configuration.getPeerPort()); } final String hostname = configuration.getHostname() != null ? configuration.getHostname() : findHostname(); final String clientAddr = hostname + ':' + clientPort; final String peerAddr = hostname + ':' + peerPort; final List<String> arguments = new ArrayList<>(); arguments.addAll(Arrays.asList(ETCD_LOCATION, "-data-dir", configuration.getDataDirectory().toString(), "-name", id, "-max-wals", "1", "-max-snapshots", "1", "-listen-client-urls", "http://0.0.0.0:" + clientPort, "-advertise-client-urls", "http://" + clientAddr, "-initial-advertise-peer-urls", "http://" + peerAddr, "-listen-peer-urls", "http://0.0.0.0:" + peerPort)); if (configuration.getInitialCluster() != null) { arguments.add("-initial-cluster"); arguments.add(configuration.getInitialCluster()); } if (configuration.isVerbose()) { arguments.add("-v"); } if (configuration.getDiscoveryUri() != null) { arguments.add("-discovery"); arguments.add(configuration.getDiscoveryUri()); } if (configuration.getSnapshotCount() != null) { arguments.add("-snapshot-count=" + configuration.getSnapshotCount()); } LOGGER.info("Launching etcd: {}", arguments); etcdServer = new ProcessBuilder(arguments).redirectOutput(Redirect.INHERIT).redirectError(Redirect.INHERIT) .start(); try { Thread.sleep(200); } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } LOGGER.info("etcd server launched: {}", etcdServer); } @Override public synchronized void close() { stop(); try { if (configuration.isDestroyNodeOnExit()) { Files.walkFileTree(configuration.getDataDirectory(), DeleteRecursively.INSTANCE); } } catch (IOException e) { LOGGER.warn("Failed to delete etcd data directoy", e); } } public synchronized void stop() { if (etcdServer == null) { return; } if (!etcdServer.isAlive()) { LOGGER.error("etcd server is already dead?!"); } etcdServer.destroy(); try { if (!etcdServer.waitFor(10, TimeUnit.SECONDS)) { LOGGER.warn("etcd server failed to exit after 10 seconds"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new RuntimeException(e); } final int exitValue = etcdServer.exitValue(); etcdServer = null; LOGGER.info("etcd server terminated {}", exitValue == 0 || exitValue == 143 ? "normally" : "with code " + exitValue); } public synchronized int getClientPort() { return clientPort; } public synchronized int getPeerPort() { return peerPort; } public synchronized boolean isRunning() { return etcdServer != null; } private static int findPort(int configuredPort) throws IOException { if (configuredPort != 0) { return configuredPort; } try (ServerSocket socket = new ServerSocket(0)) { return socket.getLocalPort(); } } private static String findHostname() throws UnknownHostException { return InetAddress.getLocalHost().getHostName(); } }