/** * Copyright 2017 Confluent Inc. * * 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 io.confluent.admin.utils; import org.apache.zookeeper.client.FourLetterWordMain; import org.apache.zookeeper.common.X509Exception.SSLContextException; import org.apache.zookeeper.server.quorum.Election; import org.apache.zookeeper.server.quorum.QuorumPeer; import org.apache.zookeeper.test.ClientBase; import org.apache.zookeeper.test.JMXEnv; import org.junit.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.file.Files; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeoutException; /** * This class is based on code from Apache Zookeeper unit tests listed below. * * src/java/test/org/apache/zookeeper/test/ClientBase.java * src/java/test/org/apache/zookeeper/test/SaslClientTest.java * src/java/test/org/apache/zookeeper/test/SaslAuthTest.java */ public class EmbeddedZookeeperEnsemble { private static final Logger log = LoggerFactory.getLogger(EmbeddedZookeeperEnsemble.class); private static int CONNECTION_TIMEOUT = 30000; private static String LOCAL_ADDR = "localhost"; private final Map<Integer, QuorumPeer> quorumPeersById = new ConcurrentHashMap<>(); private int basePort = 11111; private String hostPort = ""; private int tickTime = 2000; private int initLimit = 3; private int syncLimit = 3; private boolean isRunning = false; private int numNodes; public EmbeddedZookeeperEnsemble(int numNodes) throws IOException { this(numNodes, 11111); } public EmbeddedZookeeperEnsemble(int numNodes, int basePort) throws IOException { this.numNodes = numNodes; this.basePort = basePort; initialize(); } private void initialize() throws IOException { // org.apache.zookeeper.test.ClientBase relies on 4lw and the whitelist only contains `srvr` // in ZooKeeper 3.5.3 and later (it was less restrictive in previous versions) System.setProperty("zookeeper.4lw.commands.whitelist", "*"); HashMap peers = new HashMap(); for (int i = 0; i < numNodes; i++) { int port = basePort++; int portLE = basePort++; peers.put(Long.valueOf(i), new QuorumPeer.QuorumServer( Long.valueOf(i).longValue(), new InetSocketAddress(LOCAL_ADDR, port + 1000), new InetSocketAddress(LOCAL_ADDR, portLE + 1000), QuorumPeer.LearnerType.PARTICIPANT )); } for (int i = 0; i < numNodes; i++) { File dir = Files.createTempDirectory("zk" + i).toFile(); int portClient = basePort++; log.info("creating QuorumPeer " + i + " port " + portClient); QuorumPeer s = new QuorumPeer(peers, dir, dir, portClient, 3, i, tickTime, initLimit, syncLimit ); Assert.assertEquals(portClient, s.getClientPort()); quorumPeersById.put(i, s); if (i == 0) { hostPort = LOCAL_ADDR + ":" + portClient; } else { hostPort = hostPort + "," + LOCAL_ADDR + ":" + portClient; } } } public String connectString() { return hostPort; } public void start() throws IOException { JMXEnv.setUp(); for (int i = 0; i < numNodes; i++) { log.info("start QuorumPeer " + i); QuorumPeer s = quorumPeersById.get(i); s.start(); } log.info("Checking ports " + hostPort); for (String hp : hostPort.split(",")) { Assert.assertTrue( "waiting for server up", ClientBase.waitForServerUp( hp, CONNECTION_TIMEOUT ) ); log.info(hp + " is accepting client connections"); try { log.info(send4LW(hp, CONNECTION_TIMEOUT, "stat")); } catch (TimeoutException | SSLContextException e) { log.error(e.getMessage(), e); } } JMXEnv.dump(); isRunning = true; } public String send4LW(String hp, long timeout, String FourLW) throws TimeoutException, SSLContextException { long start = System.currentTimeMillis(); while (true) { try { HostPort e = parseHostPortList(hp).get(0); String result = FourLetterWordMain.send4LetterWord(e.host, e.port, FourLW); return result; } catch (IOException var7) { log.info("server " + hp + " not up " + var7); } if (System.currentTimeMillis() > start + timeout) { throw new TimeoutException(); } try { Thread.sleep(250L); } catch (InterruptedException e) { //Ignore } } } private List<HostPort> parseHostPortList(String hplist) { ArrayList<HostPort> alist = new ArrayList<HostPort>(); for (String hp : hplist.split(",")) { int idx = hp.lastIndexOf(':'); String host = hp.substring(0, idx); int port; try { port = Integer.parseInt(hp.substring(idx + 1)); } catch (RuntimeException e) { throw new RuntimeException("Problem parsing " + hp + e.toString()); } alist.add(new HostPort(host, port)); } return alist; } public void shutdown() { for (int i = 0; i < quorumPeersById.size(); i++) { shutdown(quorumPeersById.get(i)); } String[] hostPorts = this.hostPort.split(","); int numServers = hostPorts.length; for (int i = 0; i < numServers; ++i) { String hp = hostPorts[i]; Assert.assertTrue("waiting for server down", ClientBase.waitForServerDown(hp, (long) ClientBase.CONNECTION_TIMEOUT)); log.info(hp + " is no longer accepting client connections"); } JMXEnv.tearDown(); isRunning = false; } private void shutdown(QuorumPeer qp) { try { log.info("Shutting down quorum peer " + qp.getName()); qp.shutdown(); Election e = qp.getElectionAlg(); if (e != null) { log.info("Shutting down leader election " + qp.getName()); e.shutdown(); } else { log.info("No election available to shutdown " + qp.getName()); } log.info("Waiting for " + qp.getName() + " to exit thread"); qp.join(30000L); if (qp.isAlive()) { Assert.fail("QP failed to shutdown in 30 seconds: " + qp.getName()); } } catch (InterruptedException var2) { log.debug("QP interrupted: " + qp.getName(), var2); } } public boolean isRunning() { return isRunning; } public static class HostPort { String host; int port; public HostPort(String host, int port) { this.host = host; this.port = port; } } }