浏览代码

ZOOKEEPER-343. add tests that specifically verify the zkmain and qpmain classes. (phunt via mahadev)

git-svn-id: https://svn.apache.org/repos/asf/hadoop/zookeeper/trunk@762532 13f79535-47bb-0310-9956-ffa450edef68
Mahadev Konar 16 年之前
父节点
当前提交
b6320b80a4

+ 3 - 0
CHANGES.txt

@@ -72,6 +72,9 @@ mahadev)
   ZOOKEEPER-60. Get cppunit tests running as part of Hudson CI. (girish via
 mahadev)
 
+  ZOOKEEPER-343. add tests that specifically verify the zkmain and 
+qpmain classes. (phunt via mahadev)
+
 NEW FEATURES:
 
 

+ 1 - 0
src/java/main/org/apache/zookeeper/ZooKeeperMain.java

@@ -194,6 +194,7 @@ public class ZooKeeperMain {
            data = zk.getData(quotaPath, false, new Stat());
         } catch(KeeperException.NoNodeException ne) {
             System.err.println("quota does not exist for " + path);
+            return true;
         }
         StatsTrack strack = new StatsTrack(new String(data));
         if (bytes && !numNodes) {

+ 52 - 53
src/java/main/org/apache/zookeeper/server/ServerConfig.java

@@ -18,64 +18,63 @@
 
 package org.apache.zookeeper.server;
 
+import java.util.Arrays;
+
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
+
+/**
+ * Server configuration storage.
+ *
+ * We use this instead of Properties as it's typed.
+ *
+ */
 public class ServerConfig {
-    private int clientPort;
-    private String dataDir;
-    private String dataLogDir;
-    private int tickTime;
-    
-    protected ServerConfig(int port, String dataDir,String dataLogDir, int tickTime) {
-        this.clientPort = port;
-        this.dataDir = dataDir;
-        this.dataLogDir=dataLogDir;
-        this.tickTime = tickTime;
-    }
-    
-    protected boolean isStandaloneServer(){
-        return true;
-    }
+    protected int clientPort;
+    protected String dataDir;
+    protected String dataLogDir;
+    protected int tickTime = ZooKeeperServer.DEFAULT_TICK_TIME;
 
-    public static int getClientPort(){
-        assert instance!=null;
-        return instance.clientPort;
-    }
-    public static String getDataDir(){
-        assert instance!=null;
-        return instance.dataDir;
-    }
-    public static String getDataLogDir(){
-        assert instance!=null;
-        return instance.dataLogDir;
-    }
-    public static boolean isStandalone(){
-        assert instance!=null;
-        return instance.isStandaloneServer();
-    }
-    
-    public static int getTickTime() {
-        assert instance != null;
-        return instance.tickTime;
-    }
-    
-    protected static ServerConfig instance=null;
-    
-    public static void parse(String[] args) throws Exception {
-        if(instance!=null)
-            return;
-        if (args.length < 2) {
-            throw new IllegalArgumentException("Invalid usage.");
+    /**
+     * Parse arguments for server configuration
+     * @param args clientPort dataDir and optional tickTime
+     * @return ServerConfig configured wrt arguments
+     * @throws IllegalArgumentException on invalid usage
+     */
+    public void parse(String[] args) {
+        if (args.length < 2 || args.length > 3) {
+            throw new IllegalArgumentException("Invalid args:"
+                    + Arrays.toString(args));
         }
-        int tickTime = ZooKeeperServer.DEFAULT_TICK_TIME;
-        if (args.length > 2) {
-            // the last parameter ticktime is optional
+
+        clientPort = Integer.parseInt(args[0]);
+        dataDir = args[1];
+        dataLogDir = dataDir;
+        if (args.length == 3) {
             tickTime = Integer.parseInt(args[2]);
         }
-        try {
-              instance=new ServerConfig(Integer.parseInt(args[0]),args[1],
-                      args[1], tickTime);
-        } catch (NumberFormatException e) {
-            throw new IllegalArgumentException(args[0] + " is not a valid port number");
-        }
     }
 
+    /**
+     * Parse a ZooKeeper configuration file
+     * @param path the patch of the configuration file
+     * @return ServerConfig configured wrt arguments
+     * @throws ConfigException error processing configuration
+     */
+    public void parse(String path) throws ConfigException {
+        QuorumPeerConfig config = new QuorumPeerConfig();
+        config.parse(path);
+
+        // let qpconfig parse the file and then pull the stuff we are
+        // interested in
+        clientPort = config.getClientPort();
+        dataDir = config.getDataDir();
+        dataLogDir = config.getDataLogDir();
+        tickTime = config.getTickTime();
+    }
+
+    public int getClientPort() { return clientPort; }
+    public String getDataDir() { return dataDir; }
+    public String getDataLogDir() { return dataLogDir; }
+    public int getTickTime() { return tickTime; }
 }

+ 60 - 43
src/java/main/org/apache/zookeeper/server/ZooKeeperServerMain.java

@@ -26,74 +26,91 @@ import javax.management.JMException;
 import org.apache.log4j.Logger;
 import org.apache.zookeeper.jmx.ManagedUtil;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
-import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
 
 /**
  * This class starts and runs a standalone ZooKeeperServer.
  */
 public class ZooKeeperServerMain {
+    private static final Logger LOG =
+        Logger.getLogger(ZooKeeperServerMain.class);
+
+    private static final String USAGE =
+        "Usage: ZooKeeperServerMain configfile | port datadir [ticktime]";
+
+    private NIOServerCnxn.Factory cnxnFactory;
 
-    private static final Logger LOG = Logger.getLogger(ZooKeeperServerMain.class);
-    private static final String USAGE = "Usage: ZooKeeperServerMain configfile | port datadir [ticktime]";
     /*
      * Start up the ZooKeeper server.
      *
-     * @param args the port and data directory
+     * @param args the configfile or the port datadir [ticktime]
      */
     public static void main(String[] args) {
+        ZooKeeperServerMain main = new ZooKeeperServerMain();
+        try {
+            main.initializeAndRun(args);
+        } catch (IllegalArgumentException e) {
+            LOG.fatal("Invalid arguments, exiting abnormally", e);
+            LOG.info(USAGE);
+            System.err.println(USAGE);
+            System.exit(2);
+        } catch (ConfigException e) {
+            LOG.fatal("Invalid config, exiting abnormally", e);
+            System.err.println("Invalid config, exiting abnormally");
+            System.exit(2);
+        } catch (Exception e) {
+            LOG.fatal("Unexpected exception, exiting abnormally", e);
+            System.exit(1);
+        }
+        LOG.info("Exiting normally");
+        System.exit(0);
+    }
+
+    protected void initializeAndRun(String[] args)
+        throws ConfigException, IOException
+    {
         try {
             ManagedUtil.registerLog4jMBeans();
         } catch (JMException e) {
             LOG.warn("Unable to register log4j JMX control", e);
         }
 
-        try {
-            if (args.length == 1) {
-                QuorumPeerConfig.parse(args);
-            } else {
-                ServerConfig.parse(args);
-            }
-        } catch(Exception e) {
-            LOG.fatal("Error in config", e);
-            LOG.info(USAGE);
-            System.exit(2);
+        ServerConfig config = new ServerConfig();
+        if (args.length == 1) {
+            config.parse(args[0]);
+        } else {
+            config.parse(args);
         }
-        runStandalone(new ZooKeeperServer.Factory() {
-            public NIOServerCnxn.Factory createConnectionFactory() throws IOException {
-                return new NIOServerCnxn.Factory(ServerConfig.getClientPort());
-            }
-
-            public ZooKeeperServer createServer() throws IOException {
-                // create a file logger url from the command line args
-                ZooKeeperServer zks = new ZooKeeperServer();
-
-               FileTxnSnapLog ftxn = new FileTxnSnapLog(new 
-                       File(ServerConfig.getDataLogDir()),
-                        new File(ServerConfig.getDataDir()));
-               zks.setTxnLogFactory(ftxn);
-               zks.setTickTime(ServerConfig.getTickTime());
-               return zks;
-            }
-        });
-    }
 
-    public static void runStandalone(ZooKeeperServer.Factory serverFactory) {
+        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.
-            ZooKeeperServer zk = serverFactory.createServer();
-            zk.startup();
-            NIOServerCnxn.Factory cnxnFactory =
-                serverFactory.createConnectionFactory();
-            cnxnFactory.setZooKeeperServer(zk);
+            // create a file logger url from the command line args
+            ZooKeeperServer zkServer = new ZooKeeperServer();
+
+            FileTxnSnapLog ftxn = new FileTxnSnapLog(new
+                   File(config.dataLogDir), new File(config.dataDir));
+            zkServer.setTxnLogFactory(ftxn);
+            zkServer.setTickTime(config.tickTime);
+            zkServer.startup();
+            cnxnFactory = new NIOServerCnxn.Factory(config.clientPort);
+            cnxnFactory.setZooKeeperServer(zkServer);
             cnxnFactory.join();
-            if (zk.isRunning()) {
-                zk.shutdown();
+            if (zkServer.isRunning()) {
+                zkServer.shutdown();
             }
-        } catch (Exception e) {
-            LOG.fatal("Unexpected exception",e);
+        } catch (InterruptedException e) {
+            // warn, but generally this is ok
+            LOG.warn("Server interrupted", e);
         }
-        System.exit(0);
+    }
+
+    /**
+     * Shutdown the serving instance
+     */
+    protected void shutdown() {
+        cnxnFactory.shutdown();
     }
 }

+ 75 - 87
src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerConfig.java

@@ -22,63 +22,78 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileReader;
+import java.io.IOException;
 import java.net.InetSocketAddress;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 import java.util.Map.Entry;
 
 import org.apache.log4j.Logger;
-import org.apache.zookeeper.server.ServerConfig;
+import org.apache.zookeeper.server.ZooKeeperServer;
 import org.apache.zookeeper.server.quorum.QuorumPeer.QuorumServer;
 
-public class QuorumPeerConfig extends ServerConfig {
+public class QuorumPeerConfig {
     private static final Logger LOG = Logger.getLogger(QuorumPeerConfig.class);
 
-    private int initLimit;
-    private int syncLimit;
-    private int electionAlg;
-    private int electionPort;
-    private HashMap<Long,QuorumServer> servers = null;
-    private long serverId;
-
-    protected QuorumPeerConfig(int port, String dataDir, String dataLogDir,
-            int tickTime)
-    {
-        super(port, dataDir, dataLogDir, tickTime);
-    }
+    protected int clientPort;
+    protected String dataDir;
+    protected String dataLogDir;
+    protected int tickTime = ZooKeeperServer.DEFAULT_TICK_TIME;
+
+    protected int initLimit;
+    protected int syncLimit;
+    protected int electionAlg;
+    protected int electionPort;
+    protected final HashMap<Long,QuorumServer> servers =
+        new HashMap<Long, QuorumServer>();
 
-    public static void parse(String[] args) throws Exception {
-        if (instance != null)
-            return;
-        if (args.length != 1) {
-            throw new IllegalArgumentException("Invalid usage.");
+    protected long serverId;
+
+    @SuppressWarnings("serial")
+    public static class ConfigException extends Exception {
+        public ConfigException(String msg) {
+            super(msg);
         }
-        File zooCfgFile = new File(args[0]);
-        if (!zooCfgFile.exists()) {
-            throw new IllegalArgumentException(zooCfgFile.toString()
-                    + " file is missing");
+        public ConfigException(String msg, Exception e) {
+            super(msg, e);
         }
-        Properties cfg = new Properties();
-        FileInputStream zooCfgStream = new FileInputStream(zooCfgFile);
+    }
+
+    /**
+     * Parse a ZooKeeper configuration file
+     * @param path the patch of the configuration file
+     * @throws ConfigException error processing configuration
+     */
+    public void parse(String path) throws ConfigException {
+        File configFile = new File(path);
+
+        LOG.info("Reading configuration from: " + configFile);
+
         try {
-            cfg.load(zooCfgStream);
-        } finally {
-            zooCfgStream.close();
+            if (!configFile.exists()) {
+                throw new IllegalArgumentException(configFile.toString()
+                        + " file is missing");
+            }
+    
+            Properties cfg = new Properties();
+            FileInputStream in = new FileInputStream(configFile);
+            try {
+                cfg.load(in);
+            } finally {
+                in.close();
+            }
+    
+            parseProperties(cfg);
+        } catch (IOException e) {
+            throw new ConfigException("Error processing " + path, e);
+        } catch (IllegalArgumentException e) {
+            throw new ConfigException("Error processing " + path, e);
         }
-
-        parseProperties(cfg);
     }
 
-    protected static void parseProperties(Properties zkProp) throws Exception {
-        HashMap<Long, QuorumServer> servers = new HashMap<Long, QuorumServer>();
-        String dataDir = null;
-        String dataLogDir = null;
-        int clientPort = 0;
-        int tickTime = 0;
-        int initLimit = 0;
-        int syncLimit = 0;
-        int electionAlg = 3;
-        int electionPort = 2182;
+    protected void parseProperties(Properties zkProp) throws IOException {
         for (Entry<Object, Object> entry : zkProp.entrySet()) {
             String key = entry.getKey().toString();
             String value = entry.getValue().toString();
@@ -106,9 +121,9 @@ public class QuorumPeerConfig extends ServerConfig {
                 }
                 InetSocketAddress addr = new InetSocketAddress(parts[0],
                         Integer.parseInt(parts[1]));
-                if (parts.length == 2)
+                if (parts.length == 2) {
                     servers.put(Long.valueOf(sid), new QuorumServer(sid, addr));
-                else if (parts.length == 3) {
+                } else if (parts.length == 3) {
                     InetSocketAddress electionAddr = new InetSocketAddress(
                             parts[0], Integer.parseInt(parts[2]));
                     servers.put(Long.valueOf(sid), new QuorumServer(sid, addr,
@@ -135,19 +150,13 @@ public class QuorumPeerConfig extends ServerConfig {
         if (tickTime == 0) {
             throw new IllegalArgumentException("tickTime is not set");
         }
-        if (servers.size() > 1 && initLimit == 0) {
-            throw new IllegalArgumentException("initLimit is not set");
-        }
-        if (servers.size() > 1 && syncLimit == 0) {
-            throw new IllegalArgumentException("syncLimit is not set");
-        }
-        QuorumPeerConfig conf = new QuorumPeerConfig(clientPort, dataDir,
-                dataLogDir, tickTime);
-        conf.initLimit = initLimit;
-        conf.syncLimit = syncLimit;
-        conf.electionAlg = electionAlg;
-        conf.servers = servers;
         if (servers.size() > 1) {
+            if (initLimit == 0) {
+                throw new IllegalArgumentException("initLimit is not set");
+            }
+            if (syncLimit == 0) {
+                throw new IllegalArgumentException("syncLimit is not set");
+            }
             /*
              * If using FLE, then every server requires a separate election
              * port.
@@ -173,48 +182,27 @@ public class QuorumPeerConfig extends ServerConfig {
                 br.close();
             }
             try {
-                conf.serverId = Long.parseLong(myIdString);
+                serverId = Long.parseLong(myIdString);
             } catch (NumberFormatException e) {
                 throw new IllegalArgumentException("serverid " + myIdString
                         + " is not a number");
             }
         }
-        instance = conf;
-       
-    }
-
-    @Override
-    protected boolean isStandaloneServer(){
-        return QuorumPeerConfig.getServers().size() <= 1;
     }
 
-    public static int getInitLimit() {
-        assert instance instanceof QuorumPeerConfig;
-        return ((QuorumPeerConfig)instance).initLimit;
-    }
+    public int getClientPort() { return clientPort; }
+    public String getDataDir() { return dataDir; }
+    public String getDataLogDir() { return dataLogDir; }
+    public int getTickTime() { return tickTime; }
 
-    public static int getSyncLimit() {
-        assert instance instanceof QuorumPeerConfig;
-        return ((QuorumPeerConfig)instance).syncLimit;
-    }
+    public int getInitLimit() { return initLimit; }
+    public int getSyncLimit() { return syncLimit; }
+    public int getElectionAlg() { return electionAlg; }
+    public int getElectionPort() { return electionPort; }
 
-    public static int getElectionAlg() {
-        assert instance instanceof QuorumPeerConfig;
-        return ((QuorumPeerConfig)instance).electionAlg;
-    }
-    
-    public static HashMap<Long,QuorumServer> getServers() {
-        assert instance instanceof QuorumPeerConfig;
-        return ((QuorumPeerConfig)instance).servers;
+    public Map<Long,QuorumServer> getServers() {
+        return Collections.unmodifiableMap(servers);
     }
 
-    public static int getQuorumSize(){
-        assert instance instanceof QuorumPeerConfig;
-        return ((QuorumPeerConfig)instance).servers.size();
-    }
-
-    public static long getServerId() {
-        assert instance instanceof QuorumPeerConfig;
-        return ((QuorumPeerConfig)instance).serverId;
-    }
+    public long getServerId() { return serverId; }
 }

+ 78 - 58
src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java

@@ -17,8 +17,6 @@
  */
 package org.apache.zookeeper.server.quorum;
 
-import static org.apache.zookeeper.server.ServerConfig.getClientPort;
-
 import java.io.File;
 import java.io.IOException;
 
@@ -27,97 +25,119 @@ import javax.management.JMException;
 import org.apache.log4j.Logger;
 import org.apache.zookeeper.jmx.ManagedUtil;
 import org.apache.zookeeper.server.NIOServerCnxn;
-import org.apache.zookeeper.server.ServerConfig;
 import org.apache.zookeeper.server.ZooKeeperServerMain;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
 
 /**
- * 
+ *
  * <h2>Configuration file</h2>
  *
- * When the main() method of this class is used to start the program, the file
- * "zoo.cfg" in the current directory will be used to obtain configuration
- * information. zoo.cfg is a Properties file, so keys and values are separated
- * by equals (=) and the key/value pairs are separated by new lines. The
- * following keys are used in the configuration file:
+ * When the main() method of this class is used to start the program, the first
+ * argument is used as a path to the config file, which will be used to obtain
+ * configuration information. This file is a Properties file, so keys and
+ * values are separated by equals (=) and the key/value pairs are separated
+ * by new lines. The following is a general summary of keys used in the
+ * configuration file. For full details on this see the documentation in
+ * docs/index.html
  * <ol>
- * <li>dataDir - The directory where the zookeeper data is stored.</li>
+ * <li>dataDir - The directory where the ZooKeeper data is stored.</li>
+ * <li>dataLogDir - The directory where the ZooKeeper transaction log is stored.</li>
  * <li>clientPort - The port used to communicate with clients.</li>
  * <li>tickTime - The duration of a tick in milliseconds. This is the basic
- * unit of time in zookeeper.</li>
+ * unit of time in ZooKeeper.</li>
  * <li>initLimit - The maximum number of ticks that a follower will wait to
  * initially synchronize with a leader.</li>
  * <li>syncLimit - The maximum number of ticks that a follower will wait for a
  * message (including heartbeats) from the leader.</li>
- * <li>server.<i>id</i> - This is the host:port that the server with the
+ * <li>server.<i>id</i> - This is the host:port[:port] that the server with the
  * given id will use for the quorum protocol.</li>
  * </ol>
- * In addition to the zoo.cfg file. There is a file in the data directory called
+ * In addition to the config file. There is a file in the data directory called
  * "myid" that contains the server id as an ASCII decimal value.
- * 
+ *
  */
 public class QuorumPeerMain {
-    
     private static final Logger LOG = Logger.getLogger(QuorumPeerMain.class);
 
+    private static final String USAGE = "Usage: QuorumPeerMain configfile";
+
+    private QuorumPeer quorumPeer;
+
     /**
-     * To start the replicated server specify the configuration file name on the
-     * command line.
-     * @param args command line
+     * To start the replicated server specify the configuration file name on
+     * the command line.
+     * @param args path to the configfile
      */
     public static void main(String[] args) {
-        if (args.length == 2) {
-            ZooKeeperServerMain.main(args);
-            return;
-        }
+        QuorumPeerMain main = new QuorumPeerMain();
         try {
-            QuorumPeerConfig.parse(args);
-        } catch(Exception e) {
-            LOG.fatal("Error in config", e);
+            main.initializeAndRun(args);
+        } catch (IllegalArgumentException e) {
+            LOG.fatal("Invalid arguments, exiting abnormally", e);
+            LOG.info(USAGE);
+            System.err.println(USAGE);
+            System.exit(2);
+        } catch (ConfigException e) {
+            LOG.fatal("Invalid config, exiting abnormally", e);
+            System.err.println("Invalid config, exiting abnormally");
             System.exit(2);
+        } catch (Exception e) {
+            LOG.fatal("Unexpected exception, exiting abnormally", e);
+            System.exit(1);
+        }
+        LOG.info("Exiting normally");
+        System.exit(0);
+    }
+
+    protected void initializeAndRun(String[] args)
+        throws ConfigException, IOException
+    {
+        QuorumPeerConfig config = new QuorumPeerConfig();
+        if (args.length == 1) {
+            config.parse(args[0]);
         }
-        if (!QuorumPeerConfig.isStandalone()) {
+
+        if (args.length == 1 && config.servers.size() > 0) {
             try {
                 ManagedUtil.registerLog4jMBeans();
             } catch (JMException e) {
                 LOG.warn("Unable to register log4j JMX control", e);
             }
 
-            runPeer(new QuorumPeer.Factory() {
-                public QuorumPeer create(NIOServerCnxn.Factory cnxnFactory) throws IOException {
-                    QuorumPeer peer = new QuorumPeer();
-                    peer.setClientPort(ServerConfig.getClientPort());
-                    peer.setTxnFactory(new FileTxnSnapLog(
-                                new File(QuorumPeerConfig.getDataLogDir()), 
-                                new File(QuorumPeerConfig.getDataDir())));
-                    peer.setQuorumPeers(QuorumPeerConfig.getServers());
-                    peer.setElectionType(QuorumPeerConfig.getElectionAlg());
-                    peer.setMyid(QuorumPeerConfig.getServerId());
-                    peer.setTickTime(QuorumPeerConfig.getTickTime());
-                    peer.setInitLimit(QuorumPeerConfig.getInitLimit());
-                    peer.setSyncLimit(QuorumPeerConfig.getSyncLimit());
-                    peer.setCnxnFactory(cnxnFactory);
-                    return peer;
-                }
-                public NIOServerCnxn.Factory createConnectionFactory() throws IOException {
-                    return new NIOServerCnxn.Factory(getClientPort());
-                }
-            });
-        }else{
+            LOG.info("Starting quorum peer");
+            try {
+                NIOServerCnxn.Factory cnxnFactory =
+                    new NIOServerCnxn.Factory(config.getClientPort());
+
+                quorumPeer = new QuorumPeer();
+                quorumPeer.setClientPort(config.getClientPort());
+                quorumPeer.setTxnFactory(new FileTxnSnapLog(
+                            new File(config.getDataLogDir()),
+                            new File(config.getDataDir())));
+                quorumPeer.setQuorumPeers(config.getServers());
+                quorumPeer.setElectionType(config.getElectionAlg());
+                quorumPeer.setMyid(config.getServerId());
+                quorumPeer.setTickTime(config.getTickTime());
+                quorumPeer.setInitLimit(config.getInitLimit());
+                quorumPeer.setSyncLimit(config.getSyncLimit());
+                quorumPeer.setCnxnFactory(cnxnFactory);
+
+                quorumPeer.start();
+                quorumPeer.join();
+            } catch (InterruptedException e) {
+                // warn, but generally this is ok
+                LOG.warn("Quorum Peer interrupted", e);
+            }
+        } else {
+            LOG.warn("Either no config or no quorum defined in config, running "
+                    + " in standalone mode");
             // there is only server in the quorum -- run as standalone
             ZooKeeperServerMain.main(args);
         }
     }
-    
-    public static void runPeer(QuorumPeer.Factory qpFactory) {
-        try {
-            QuorumPeer self = qpFactory.create(qpFactory.createConnectionFactory());
-            self.start();
-            self.join();
-        } catch (Exception e) {
-            LOG.fatal("Unexpected exception",e);
-        }
-        System.exit(2);
-    }
 
+    protected void shutdown() {
+        quorumPeer.shutdown();
+    }
 }

+ 131 - 0
src/java/test/org/apache/zookeeper/server/ZooKeeperServerMainTest.java

@@ -0,0 +1,131 @@
+/**
+ * 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.
+ */
+
+package org.apache.zookeeper.server;
+
+import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.Watcher;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.ZooDefs.Ids;
+import org.apache.zookeeper.test.ClientBase;
+import org.junit.Test;
+
+/**
+ * Test stand-alone server.
+ *
+ */
+public class ZooKeeperServerMainTest extends TestCase implements Watcher {
+    protected static final Logger LOG =
+        Logger.getLogger(ZooKeeperServerMainTest.class);
+
+    public static class MainThread extends Thread {
+        final File confFile;
+        final TestZKSMain main;
+
+        public MainThread(int clientPort) throws IOException {
+            super("Standalone server with clientPort:" + clientPort);
+            File tmpDir = ClientBase.createTmpDir();
+            confFile = new File(tmpDir, "zoo.cfg");
+
+            FileWriter fwriter = new FileWriter(confFile);
+            fwriter.write("tickTime=2000\n");
+            fwriter.write("initLimit=10\n");
+            fwriter.write("syncLimit=5\n");
+
+            File dataDir = new File(tmpDir, "data");
+            if (!dataDir.mkdir()) {
+                throw new IOException("unable to mkdir " + dataDir);
+            }
+            fwriter.write("dataDir=" + dataDir.toString() + "\n");
+
+            fwriter.write("clientPort=" + clientPort + "\n");
+            fwriter.flush();
+            fwriter.close();
+
+            main = new TestZKSMain();
+        }
+
+        public void run() {
+            String args[] = new String[1];
+            args[0] = confFile.toString();
+            try {
+                main.initializeAndRun(args);
+            } catch (Exception e) {
+                // test will still fail even though we just log/ignore
+                LOG.error("unexpected exception in run", e);
+            }
+        }
+
+        public void shutdown() {
+            main.shutdown();
+        }
+    }
+
+    public static  class TestZKSMain extends ZooKeeperServerMain {
+        public void shutdown() {
+            super.shutdown();
+        }
+    }
+
+    /**
+     * Verify the ability to start a standalone server instance.
+     */
+    @Test
+    public void testStandalone() throws Exception {
+        LOG.info("STARTING " + getName());
+        ClientBase.setupTestEnv();
+
+        final int CLIENT_PORT = 3181;
+
+        MainThread main = new MainThread(CLIENT_PORT);
+        main.start();
+
+        assertTrue("waiting for server being up",
+                ClientBase.waitForServerUp("localhost:" + CLIENT_PORT,
+                        CONNECTION_TIMEOUT));
+
+
+        ZooKeeper zk = new ZooKeeper("localhost:" + CLIENT_PORT,
+                ClientBase.CONNECTION_TIMEOUT, this);
+
+        zk.create("/foo", "foobar".getBytes(), Ids.OPEN_ACL_UNSAFE,
+                CreateMode.PERSISTENT);
+        assertEquals(new String(zk.getData("/foo", null, null)), "foobar");
+        zk.close();
+
+        main.shutdown();
+
+        assertTrue("waiting for server down",
+                ClientBase.waitForServerDown("localhost:" + CLIENT_PORT,
+                        ClientBase.CONNECTION_TIMEOUT));
+    }
+
+    public void process(WatchedEvent event) {
+        // ignore for this test
+    }
+}

+ 165 - 0
src/java/test/org/apache/zookeeper/server/quorum/QuorumPeerMainTest.java

@@ -0,0 +1,165 @@
+/**
+ * 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.
+ */
+
+package org.apache.zookeeper.server.quorum;
+
+import static org.apache.zookeeper.test.ClientBase.CONNECTION_TIMEOUT;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.WatchedEvent;
+import org.apache.zookeeper.Watcher;
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.ZooDefs.Ids;
+import org.apache.zookeeper.test.ClientBase;
+import org.junit.Test;
+
+/**
+ * Test stand-alone server.
+ *
+ */
+public class QuorumPeerMainTest extends TestCase implements Watcher {
+    protected static final Logger LOG =
+        Logger.getLogger(QuorumPeerMainTest.class);
+
+    public static class MainThread extends Thread {
+        final File confFile;
+        final TestQPMain main;
+
+        public MainThread(int myid, int clientPort, String quorumCfgSection)
+            throws IOException
+        {
+            super("QuorumPeer with myid:" + myid
+                    + " and clientPort:" + clientPort);
+            File tmpDir = ClientBase.createTmpDir();
+            confFile = new File(tmpDir, "zoo.cfg");
+
+            FileWriter fwriter = new FileWriter(confFile);
+            fwriter.write("tickTime=2000\n");
+            fwriter.write("initLimit=10\n");
+            fwriter.write("syncLimit=5\n");
+
+            File dataDir = new File(tmpDir, "data");
+            if (!dataDir.mkdir()) {
+                throw new IOException("Unable to mkdir " + dataDir);
+            }
+            fwriter.write("dataDir=" + dataDir.toString() + "\n");
+
+            fwriter.write("clientPort=" + clientPort + "\n");
+            fwriter.write(quorumCfgSection + "\n");
+            fwriter.flush();
+            fwriter.close();
+
+            File myidFile = new File(dataDir, "myid");
+            fwriter = new FileWriter(myidFile);
+            fwriter.write(Integer.toString(myid));
+            fwriter.flush();
+            fwriter.close();
+
+            main = new TestQPMain();
+        }
+
+        public void run() {
+            String args[] = new String[1];
+            args[0] = confFile.toString();
+            try {
+                main.initializeAndRun(args);
+            } catch (Exception e) {
+                // test will still fail even though we just log/ignore
+                LOG.error("unexpected exception in run", e);
+            }
+        }
+
+        public void shutdown() {
+            main.shutdown();
+        }
+    }
+
+    public static  class TestQPMain extends QuorumPeerMain {
+        public void shutdown() {
+            super.shutdown();
+        }
+    }
+
+    /**
+     * Verify the ability to start a cluster.
+     */
+    @Test
+    public void testQuorum() throws Exception {
+        LOG.info("STARTING " + getName());
+        ClientBase.setupTestEnv();
+
+        final int CLIENT_PORT_QP1 = 3181;
+        final int CLIENT_PORT_QP2 = CLIENT_PORT_QP1 + 3;
+
+        String quorumCfgSection =
+            "server.1=localhost:" + (CLIENT_PORT_QP1 + 1)
+            + ":" + (CLIENT_PORT_QP1 + 2)
+            + "\nserver.2=localhost:" + (CLIENT_PORT_QP2 + 1)
+            + ":" + (CLIENT_PORT_QP2 + 2);
+
+        MainThread q1 = new MainThread(1, CLIENT_PORT_QP1, quorumCfgSection);
+        MainThread q2 = new MainThread(2, CLIENT_PORT_QP2, quorumCfgSection);
+        q1.start();
+        q2.start();
+
+        assertTrue("waiting for server 1 being up",
+                ClientBase.waitForServerUp("localhost:" + CLIENT_PORT_QP1,
+                        CONNECTION_TIMEOUT));
+        assertTrue("waiting for server 2 being up",
+                ClientBase.waitForServerUp("localhost:" + CLIENT_PORT_QP2,
+                        CONNECTION_TIMEOUT));
+
+
+        ZooKeeper zk = new ZooKeeper("localhost:" + CLIENT_PORT_QP1,
+                ClientBase.CONNECTION_TIMEOUT, this);
+
+        zk.create("/foo_q1", "foobar1".getBytes(), Ids.OPEN_ACL_UNSAFE,
+                CreateMode.PERSISTENT);
+        assertEquals(new String(zk.getData("/foo_q1", null, null)), "foobar1");
+        zk.close();
+
+        zk = new ZooKeeper("localhost:" + CLIENT_PORT_QP2,
+                ClientBase.CONNECTION_TIMEOUT, this);
+
+        zk.create("/foo_q2", "foobar2".getBytes(), Ids.OPEN_ACL_UNSAFE,
+                CreateMode.PERSISTENT);
+        assertEquals(new String(zk.getData("/foo_q2", null, null)), "foobar2");
+        zk.close();
+
+        q1.shutdown();
+        q2.shutdown();
+
+        assertTrue("waiting for server 1 down",
+                ClientBase.waitForServerDown("localhost:" + CLIENT_PORT_QP1,
+                        ClientBase.CONNECTION_TIMEOUT));
+        assertTrue("waiting for server 2 down",
+                ClientBase.waitForServerDown("localhost:" + CLIENT_PORT_QP2,
+                        ClientBase.CONNECTION_TIMEOUT));
+    }
+
+    public void process(WatchedEvent event) {
+        // ignore for this test
+    }
+}