package org.apache.solr.cloud; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with this * work for additional information regarding copyright ownership. The ASF * licenses this file to You 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. */ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import javax.management.JMException; import org.apache.zookeeper.jmx.ManagedUtil; import org.apache.zookeeper.server.ServerCnxnFactory; import org.apache.zookeeper.server.ServerConfig; import org.apache.zookeeper.server.SessionTracker.Session; import org.apache.zookeeper.server.ZKDatabase; import org.apache.zookeeper.server.ZooKeeperServer; import org.apache.zookeeper.server.persistence.FileTxnSnapLog; import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ZkTestServer { public static final int TICK_TIME = 1000; private static Logger log = LoggerFactory.getLogger(ZkTestServer.class); protected final ZKServerMain zkServer = new ZKServerMain(); private String zkDir; private int clientPort; private Thread zooThread; private int theTickTime = TICK_TIME; class ZKServerMain { private ServerCnxnFactory cnxnFactory; private ZooKeeperServer zooKeeperServer; protected void initializeAndRun(String[] args) throws ConfigException, IOException { try { ManagedUtil.registerLog4jMBeans(); } catch (JMException e) { log.warn("Unable to register log4j JMX control", e); } ServerConfig config = new ServerConfig(); if (args.length == 1) { config.parse(args[0]); } else { config.parse(args); } runFromConfig(config); } /** * Run from a ServerConfig. * @param config ServerConfig to use. * @throws IOException If there is a low-level I/O error. */ public void runFromConfig(ServerConfig config) throws IOException { log.info("Starting server"); try { // Note that this thread isn't going to be doing anything else, // so rather than spawning another thread, we will just call // run() in this thread. // create a file logger url from the command line args zooKeeperServer = new ZooKeeperServer(); FileTxnSnapLog ftxn = new FileTxnSnapLog(new File( config.getDataLogDir()), new File(config.getDataDir())); zooKeeperServer.setTxnLogFactory(ftxn); zooKeeperServer.setTickTime(config.getTickTime()); zooKeeperServer.setMinSessionTimeout(config.getMinSessionTimeout()); zooKeeperServer.setMaxSessionTimeout(config.getMaxSessionTimeout()); cnxnFactory = ServerCnxnFactory.createFactory(); cnxnFactory.configure(config.getClientPortAddress(), config.getMaxClientCnxns()); cnxnFactory.startup(zooKeeperServer); cnxnFactory.join(); if (zooKeeperServer.isRunning()) { zkServer.shutdown(); } } catch (InterruptedException e) { // warn, but generally this is ok log.warn("Server interrupted", e); } } /** * Shutdown the serving instance * @throws IOException If there is a low-level I/O error. */ protected void shutdown() throws IOException { zooKeeperServer.shutdown(); ZKDatabase zkDb = zooKeeperServer.getZKDatabase(); if (zkDb != null) { zkDb.close(); } if (cnxnFactory != null && cnxnFactory.getLocalPort() != 0) { waitForServerDown(getZkHost() + ":" + getPort(), 5000); } if (cnxnFactory != null) { cnxnFactory.shutdown(); } } public int getLocalPort() { if (cnxnFactory == null) { throw new IllegalStateException("A port has not yet been selected"); } int port; try { port = cnxnFactory.getLocalPort(); } catch (NullPointerException e) { throw new IllegalStateException("A port has not yet been selected"); } if (port == 0) { throw new IllegalStateException("A port has not yet been selected"); } return port; } } public ZkTestServer(String zkDir) { this.zkDir = zkDir; } public ZkTestServer(String zkDir, int port) { this.zkDir = zkDir; this.clientPort = port; } public String getZkHost() { return "127.0.0.1:" + zkServer.getLocalPort(); } public String getZkAddress() { return "127.0.0.1:" + zkServer.getLocalPort() + "/solr"; } public int getPort() { return zkServer.getLocalPort(); } public void expire(final long sessionId) { zkServer.zooKeeperServer.expire(new Session() { @Override public long getSessionId() { return sessionId; } @Override public int getTimeout() { return 4000; } @Override public boolean isClosing() { return false; }}); } public void run() throws InterruptedException { log.info("STARTING ZK TEST SERVER"); // we don't call super.setUp zooThread = new Thread() { @Override public void run() { ServerConfig config = new ServerConfig() { { setClientPort(ZkTestServer.this.clientPort); this.dataDir = zkDir; this.dataLogDir = zkDir; this.tickTime = theTickTime; } public void setClientPort(int clientPort) { if (clientPortAddress != null) { try { this.clientPortAddress = new InetSocketAddress( InetAddress.getByName(clientPortAddress.getHostName()), clientPort); } catch (UnknownHostException e) { throw new RuntimeException(e); } } else { this.clientPortAddress = new InetSocketAddress(clientPort); } System.out.println("client port:" + this.clientPortAddress); } }; try { zkServer.runFromConfig(config); } catch (Throwable e) { throw new RuntimeException(e); } } }; zooThread.setDaemon(true); zooThread.start(); int cnt = 0; int port = -1; try { port = getPort(); } catch(IllegalStateException e) { } while (port < 1) { Thread.sleep(100); try { port = getPort(); } catch(IllegalStateException e) { } if (cnt == 500) { throw new RuntimeException("Could not get the port for ZooKeeper server"); } cnt++; } log.info("start zk server on port:" + port); } @SuppressWarnings("deprecation") public void shutdown() throws IOException { // TODO: this can log an exception while trying to unregister a JMX MBean zkServer.shutdown(); } public static boolean waitForServerDown(String hp, long timeout) { long start = System.currentTimeMillis(); while (true) { try { HostPort hpobj = parseHostPortList(hp).get(0); send4LetterWord(hpobj.host, hpobj.port, "stat"); } catch (IOException e) { return true; } if (System.currentTimeMillis() > start + timeout) { break; } try { Thread.sleep(250); } catch (InterruptedException e) { // ignore } } return false; } public static class HostPort { String host; int port; HostPort(String host, int port) { this.host = host; this.port = port; } } /** * Send the 4letterword * @param host the destination host * @param port the destination port * @param cmd the 4letterword * @return server response */ public static String send4LetterWord(String host, int port, String cmd) throws IOException { log.info("connecting to " + host + " " + port); Socket sock = new Socket(host, port); BufferedReader reader = null; try { OutputStream outstream = sock.getOutputStream(); outstream.write(cmd.getBytes("US-ASCII")); outstream.flush(); // this replicates NC - close the output stream before reading sock.shutdownOutput(); reader = new BufferedReader( new InputStreamReader(sock.getInputStream(), "US-ASCII")); StringBuilder sb = new StringBuilder(); String line; while((line = reader.readLine()) != null) { sb.append(line + "\n"); } return sb.toString(); } finally { sock.close(); if (reader != null) { reader.close(); } } } public static List<HostPort> parseHostPortList(String hplist) { ArrayList<HostPort> alist = new ArrayList<>(); 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 int getTheTickTime() { return theTickTime; } public void setTheTickTime(int theTickTime) { this.theTickTime = theTickTime; } public String getZkDir() { return zkDir; } }