You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zookeeper.apache.org by ph...@apache.org on 2014/07/18 02:19:03 UTC

svn commit: r1611516 [2/3] - in /zookeeper/trunk: ./ docs/ src/docs/src/documentation/content/xdocs/ src/java/main/org/apache/zookeeper/server/ src/java/main/org/apache/zookeeper/server/admin/ src/java/main/org/apache/zookeeper/server/quorum/ src/java/...

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/AdminServerFactory.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/AdminServerFactory.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/AdminServerFactory.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/AdminServerFactory.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,63 @@
+/**
+ * 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.admin;
+
+import java.lang.reflect.InvocationTargetException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Factory class for creating an AdminServer.
+ */
+public class AdminServerFactory {
+    private static final Logger LOG = LoggerFactory.getLogger(AdminServerFactory.class);
+
+    /**
+     * This method encapsulates the logic for whether we should use a
+     * JettyAdminServer (i.e., the AdminServer is enabled) or a DummyAdminServer
+     * (i.e., the AdminServer is disabled). It uses reflection when attempting
+     * to create a JettyAdminServer, rather than referencing the class directly,
+     * so that it's ok to omit Jetty from the classpath if a user doesn't wish
+     * to pull in Jetty with ZooKeeper.
+     */
+    public static AdminServer createAdminServer() {
+        if (!"false".equals(System.getProperty("zookeeper.admin.enableServer"))) {
+            try {
+                Class<?> jettyAdminServerC = Class.forName("org.apache.zookeeper.server.admin.JettyAdminServer");
+                Object adminServer = jettyAdminServerC.getConstructor().newInstance();
+                return (AdminServer) adminServer;
+
+            } catch (ClassNotFoundException e) {
+                LOG.warn("Unable to start JettyAdminServer", e);
+            } catch (InstantiationException e) {
+                LOG.warn("Unable to start JettyAdminServer", e);
+            } catch (IllegalAccessException e) {
+                LOG.warn("Unable to start JettyAdminServer", e);
+            } catch (InvocationTargetException e) {
+                LOG.warn("Unable to start JettyAdminServer", e);
+            } catch (NoSuchMethodException e) {
+                LOG.warn("Unable to start JettyAdminServer", e);
+            } catch (NoClassDefFoundError e) {
+                LOG.warn("Unable to load jetty, not starting JettyAdminServer", e);
+            }
+        }
+        return new DummyAdminServer();
+    }
+}

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/Command.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/Command.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/Command.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/Command.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,68 @@
+/**
+ * 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.admin;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.zookeeper.server.ZooKeeperServer;
+
+/**
+ * Interface implemented by all commands runnable by JettyAdminServer.
+ *
+ * @see CommandBase
+ * @see Commands
+ * @see JettyAdminServer
+ */
+public interface Command {
+    /**
+     * The set of all names that can be used to refer to this command (e.g.,
+     * "configuration", "config", and "conf").
+     */
+    Set<String> getNames();
+
+    /**
+     * The name that is returned with the command response and that appears in
+     * the list of all commands. This should be a member of the set returned by
+     * getNames().
+     */
+    String getPrimaryName();
+
+    /**
+     * A string documentating this command (e.g., what it does, any arguments it
+     * takes).
+     */
+    String getDoc();
+
+    /**
+     * Run this command. Commands take a ZooKeeperServer and String-valued
+     * keyword arguments and return a map containing any information
+     * constituting the response to the command. Commands are responsible for
+     * parsing keyword arguments and performing any error handling if necessary.
+     * Errors should be reported by setting the "error" entry of the returned
+     * map with an appropriate message rather than throwing an exception.
+     *
+     * @param zkServer
+     * @param kwargs keyword -> argument value mapping
+     * @return Map representing response to command containing at minimum:
+     *    - "command" key containing the command's primary name
+     *    - "error" key containing a String error message or null if no error
+     */
+    CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs);
+}

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandBase.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandBase.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandBase.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandBase.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,66 @@
+/**
+ * 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.admin;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public abstract class CommandBase implements Command {
+    private final String primaryName;
+    private final Set<String> names;
+    private final String doc;
+
+    /**
+     * @param names The possible names of this command, with the primary name first.
+     */
+    protected CommandBase(List<String> names) {
+        this(names, null);
+    }
+
+    protected CommandBase(List<String> names, String doc) {
+        this.primaryName = names.get(0);
+        this.names = new HashSet<String>(names);
+        this.doc = doc;
+    }
+
+    @Override
+    public String getPrimaryName() {
+        return primaryName;
+    }
+
+    @Override
+    public Set<String> getNames() {
+        return names;
+    }
+
+    @Override
+    public String getDoc() {
+        return doc;
+    }
+
+    /**
+     * @return A response with the command set to the primary name and the
+     *         error set to null (these are the two entries that all command
+     *         responses are required to include).
+     */
+    protected CommandResponse initializeResponse() {
+        return new CommandResponse(primaryName);
+    }
+}

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandOutputter.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandOutputter.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandOutputter.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandOutputter.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,35 @@
+/**
+ * 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.admin;
+
+import java.io.PrintWriter;
+import java.util.Map;
+
+/**
+ * CommandOutputters are used to format the responses from Commands.
+ *
+ * @see Command
+ * @see JettyAdminServer
+ */
+public interface CommandOutputter {
+    /** The MIME type of this output (e.g., "application/json") */
+    String getContentType();
+
+    void output(CommandResponse response, PrintWriter pw);
+}

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandResponse.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandResponse.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandResponse.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/CommandResponse.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,112 @@
+/*
+ * 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.admin;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A response from running a {@link Command}.
+ */
+public class CommandResponse {
+
+    /**
+     * The key in the map returned by {@link #toMap()} for the command name.
+     */
+    public static final String KEY_COMMAND = "command";
+    /**
+     * The key in the map returned by {@link #toMap()} for the error string.
+     */
+    public static final String KEY_ERROR = "error";
+
+    private final String command;
+    private final String error;
+    private final Map<String, Object> data;
+
+    /**
+     * Creates a new response with no error string.
+     *
+     * @param command command name
+     */
+    public CommandResponse(String command) {
+        this(command, null);
+    }
+    /**
+     * Creates a new response.
+     *
+     * @param command command name
+     * @param error error string (may be null)
+     */
+    public CommandResponse(String command, String error) {
+        this.command = command;
+        this.error = error;
+        data = new LinkedHashMap<String, Object>();
+    }
+
+    /**
+     * Gets the command name.
+     *
+     * @return command name
+     */
+    public String getCommand() {
+        return command;
+    }
+
+    /**
+     * Gets the error string (may be null).
+     *
+     * @return error string
+     */
+    public String getError() {
+        return error;
+    }
+
+    /**
+     * Adds a key/value pair to this response.
+     *
+     * @param key key
+     * @param value value
+     * @return prior value for key, or null if none
+     */
+    public Object put(String key, Object value) {
+        return data.put(key, value);
+    }
+
+    /**
+     * Adds all key/value pairs in the given map to this response.
+     *
+     * @param m map of key/value pairs
+     */
+    public void putAll(Map<? extends String,?> m) {
+        data.putAll(m);
+    }
+
+    /**
+     * Converts this response to a map. The returned map is mutable, and
+     * changes to it do not reflect back into this response.
+     *
+     * @return map representation of response
+     */
+    public Map<String, Object> toMap() {
+        Map<String, Object> m = new LinkedHashMap<String, Object>(data);
+        m.put(KEY_COMMAND, command);
+        m.put(KEY_ERROR, error);
+        m.putAll(data);
+        return m;
+    }
+}

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/Commands.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/Commands.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/Commands.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/Commands.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,501 @@
+/**
+ * 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.admin;
+
+import java.lang.management.ManagementFactory;
+import java.lang.management.OperatingSystemMXBean;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.zookeeper.Environment;
+import org.apache.zookeeper.Environment.Entry;
+import org.apache.zookeeper.Version;
+import org.apache.zookeeper.server.DataTree;
+import org.apache.zookeeper.server.ServerStats;
+import org.apache.zookeeper.server.ZKDatabase;
+import org.apache.zookeeper.server.ZooKeeperServer;
+import org.apache.zookeeper.server.ZooTrace;
+import org.apache.zookeeper.server.quorum.Leader;
+import org.apache.zookeeper.server.quorum.LeaderZooKeeperServer;
+import org.apache.zookeeper.server.quorum.ReadOnlyZooKeeperServer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.sun.management.UnixOperatingSystemMXBean;
+
+/**
+ * Class containing static methods for registering and running Commands, as well
+ * as default Command definitions.
+ *
+ * @see Command
+ * @see JettyAdminServer
+ */
+public class Commands {
+    static final Logger LOG = LoggerFactory.getLogger(Commands.class);
+
+    /** Maps command names to Command instances */
+    private static Map<String, Command> commands = new HashMap<String, Command>();
+    private static Set<String> primaryNames = new HashSet<String>();
+
+    /**
+     * Registers the given command. Registered commands can be run by passing
+     * any of their names to runCommand.
+     */
+    public static void registerCommand(Command command) {
+        for (String name : command.getNames()) {
+            Command prev = commands.put(name, command);
+            if (prev != null) {
+                LOG.warn("Re-registering command %s (primary name = %s)", name, command.getPrimaryName());
+            }
+        }
+        primaryNames.add(command.getPrimaryName());
+    }
+
+    /**
+     * Run the registered command with name cmdName. Commands should not produce
+     * any exceptions; any (anticipated) errors should be reported in the
+     * "error" entry of the returned map. Likewise, if no command with the given
+     * name is registered, this will be noted in the "error" entry.
+     *
+     * @param cmdName
+     * @param zkServer
+     * @param kwargs String-valued keyword arguments to the command
+     *        (may be null if command requires no additional arguments)
+     * @return Map representing response to command containing at minimum:
+     *    - "command" key containing the command's primary name
+     *    - "error" key containing a String error message or null if no error
+     */
+    public static CommandResponse runCommand(String cmdName, ZooKeeperServer zkServer, Map<String, String> kwargs) {
+        if (!commands.containsKey(cmdName)) {
+            return new CommandResponse(cmdName, "Unknown command: " + cmdName);
+        }
+        if (zkServer == null) {
+            return new CommandResponse(cmdName, "This ZooKeeper instance is not currently serving requests");
+        }
+        return commands.get(cmdName).run(zkServer, kwargs);
+    }
+
+    /**
+     * Returns the primary names of all registered commands.
+     */
+    public static Set<String> getPrimaryNames() {
+        return primaryNames;
+    }
+
+    /**
+     * Returns the commands registered under cmdName with registerCommand, or
+     * null if no command is registered with that name.
+     */
+    public static Command getCommand(String cmdName) {
+        return commands.get(cmdName);
+    }
+
+    static {
+        registerCommand(new CnxnStatResetCommand());
+        registerCommand(new ConfCommand());
+        registerCommand(new ConsCommand());
+        registerCommand(new DumpCommand());
+        registerCommand(new EnvCommand());
+        registerCommand(new GetTraceMaskCommand());
+        registerCommand(new IsroCommand());
+        registerCommand(new MonitorCommand());
+        registerCommand(new RuokCommand());
+        registerCommand(new SetTraceMaskCommand());
+        registerCommand(new SrvrCommand());
+        registerCommand(new StatCommand());
+        registerCommand(new StatResetCommand());
+        registerCommand(new WatchCommand());
+        registerCommand(new WatchesByPathCommand());
+        registerCommand(new WatchSummaryCommand());
+    }
+
+    /**
+     * Reset all connection statistics.
+     */
+    public static class CnxnStatResetCommand extends CommandBase {
+        public CnxnStatResetCommand() {
+            super(Arrays.asList("connection_stat_reset", "crst"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            zkServer.getServerCnxnFactory().resetAllConnectionStats();
+            return response;
+
+        }
+    }
+
+    /**
+     * Server configuration parameters.
+     * @see ZooKeeperServer#getConf()
+     */
+    public static class ConfCommand extends CommandBase {
+        public ConfCommand() {
+            super(Arrays.asList("configuration", "conf", "config"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            response.putAll(zkServer.getConf().toMap());
+            return response;
+        }
+    }
+
+    /**
+     * Information on client connections to server. Returned Map contains:
+     *   - "connections": list of connection info objects
+     * @see org.apache.zookeeper.server.ServerCnxn#getConnectionInfo(boolean)
+     */
+    public static class ConsCommand extends CommandBase {
+        public ConsCommand() {
+            super(Arrays.asList("connections", "cons"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            response.put("connections", zkServer.getServerCnxnFactory().getAllConnectionInfo(false));
+            return response;
+        }
+    }
+
+    /**
+     * Information on session expirations and ephemerals. Returned map contains:
+     *   - "expiry_time_to_session_ids": Map<Long, Set<Long>>
+     *                                   time -> sessions IDs of sessions that expire at time
+     *   - "sesssion_id_to_ephemeral_paths": Map<Long, Set<String>>
+     *                                       session ID -> ephemeral paths created by that session
+     * @see ZooKeeperServer#getSessionExpiryMap()
+     * @see ZooKeeperServer#getEphemerals()
+     */
+    public static class DumpCommand extends CommandBase {
+        public DumpCommand() {
+            super(Arrays.asList("dump"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            response.put("expiry_time_to_session_ids", zkServer.getSessionExpiryMap());
+            response.put("session_id_to_ephemeral_paths", zkServer.getEphemerals());
+            return response;
+        }
+    }
+
+    /**
+     * All defined environment variables.
+     */
+    public static class EnvCommand extends CommandBase {
+        public EnvCommand() {
+            super(Arrays.asList("environment", "env", "envi"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            for (Entry e : Environment.list()) {
+                response.put(e.getKey(), e.getValue());
+            }
+            return response;
+        }
+    }
+
+    /**
+     * The current trace mask. Returned map contains:
+     *   - "tracemask": Long
+     */
+    public static class GetTraceMaskCommand extends CommandBase {
+        public GetTraceMaskCommand() {
+            super(Arrays.asList("get_trace_mask", "gtmk"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            response.put("tracemask", ZooTrace.getTextTraceLevel());
+            return response;
+        }
+    }
+
+    /**
+     * Is this server in read-only mode. Returned map contains:
+     *   - "is_read_only": Boolean
+     */
+    public static class IsroCommand extends CommandBase {
+        public IsroCommand() {
+            super(Arrays.asList("is_read_only", "isro"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            response.put("read_only", zkServer instanceof ReadOnlyZooKeeperServer);
+            return response;
+        }
+    }
+
+    /**
+     * Some useful info for monitoring. Returned map contains:
+     *   - "version": String
+     *                server version
+     *   - "avg_latency": Long
+     *   - "max_latency": Long
+     *   - "min_latency": Long
+     *   - "packets_received": Long
+     *   - "packets_sents": Long
+     *   - "num_alive_connections": Integer
+     *   - "outstanding_requests": Long
+     *                             number of unprocessed requests
+     *   - "server_state": "leader", "follower", or "standalone"
+     *   - "znode_count": Integer
+     *   - "watch_count": Integer
+     *   - "ephemerals_count": Integer
+     *   - "approximate_data_size": Long
+     *   - "open_file_descriptor_count": Long (unix only)
+     *   - "max_file_descritpor_count": Long (unix only)
+     *   - "followers": Integer (leader only)
+     *   - "synced_followers": Integer (leader only)
+     *   - "pending_syncs": Integer (leader only)
+     */
+    public static class MonitorCommand extends CommandBase {
+        public MonitorCommand() {
+            super(Arrays.asList("monitor", "mntr"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            ZKDatabase zkdb = zkServer.getZKDatabase();
+            ServerStats stats = zkServer.serverStats();
+
+            CommandResponse response = initializeResponse();
+
+            response.put("version", Version.getFullVersion());
+
+            response.put("avg_latency", stats.getAvgLatency());
+            response.put("max_latency", stats.getMaxLatency());
+            response.put("min_latency", stats.getMinLatency());
+
+            response.put("packets_received", stats.getPacketsReceived());
+            response.put("packets_sent", stats.getPacketsSent());
+            response.put("num_alive_connections", stats.getNumAliveClientConnections());
+
+            response.put("outstanding_requests", stats.getOutstandingRequests());
+
+            response.put("server_state", stats.getServerState());
+            response.put("znode_count", zkdb.getNodeCount());
+
+            response.put("watch_count", zkdb.getDataTree().getWatchCount());
+            response.put("ephemerals_count", zkdb.getDataTree().getEphemeralsCount());
+            response.put("approximate_data_size", zkdb.getDataTree().approximateDataSize());
+
+            OperatingSystemMXBean osMbean = ManagementFactory.getOperatingSystemMXBean();
+            if (osMbean != null && osMbean instanceof UnixOperatingSystemMXBean) {
+                UnixOperatingSystemMXBean unixos = (UnixOperatingSystemMXBean) osMbean;
+
+                response.put("open_file_descriptor_count", unixos.getOpenFileDescriptorCount());
+                response.put("max_file_descriptor_count", unixos.getMaxFileDescriptorCount());
+            }
+
+            if (zkServer instanceof LeaderZooKeeperServer) {
+                Leader leader = ((LeaderZooKeeperServer) zkServer).getLeader();
+
+                response.put("followers", leader.getLearners().size());
+                response.put("synced_followers", leader.getForwardingFollowers().size());
+                response.put("pending_syncs", leader.getNumPendingSyncs());
+            }
+
+            return response;
+
+        }}
+
+    /**
+     * No-op command, check if the server is running
+     */
+    public static class RuokCommand extends CommandBase {
+        public RuokCommand() {
+            super(Arrays.asList("ruok"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            return initializeResponse();
+        }
+    }
+
+    /**
+     * Sets the trace mask. Required arguments:
+     *   - "traceMask": Long
+     *  Returned Map contains:
+     *   - "tracemask": Long
+     */
+    public static class SetTraceMaskCommand extends CommandBase {
+        public SetTraceMaskCommand() {
+            super(Arrays.asList("set_trace_mask", "stmk"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            long traceMask;
+            if (!kwargs.containsKey("traceMask")) {
+                response.put("error", "setTraceMask requires long traceMask argument");
+                return response;
+            }
+            try {
+                traceMask = Long.parseLong(kwargs.get("traceMask"));
+            } catch (NumberFormatException e) {
+                response.put("error", "setTraceMask requires long traceMask argument, got "
+                                      + kwargs.get("traceMask"));
+                return response;
+            }
+
+            ZooTrace.setTextTraceLevel(traceMask);
+            response.put("tracemask", traceMask);
+            return response;
+        }
+    }
+
+    /**
+     * Server information. Returned map contains:
+     *   - "version": String
+     *                version of server
+     *   - "read_only": Boolean
+     *                  is server in read-only mode
+     *   - "server_stats": ServerStats object
+     *   - "node_count": Integer
+     */
+    public static class SrvrCommand extends CommandBase {
+        public SrvrCommand() {
+            super(Arrays.asList("server_stats", "srvr"));
+        }
+
+        // Allow subclasses (e.g. StatCommand) to specify their own names
+        protected SrvrCommand(List<String> names) {
+            super(names);
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            LOG.info("running stat");
+            response.put("version", Version.getFullVersion());
+            response.put("read_only", zkServer instanceof ReadOnlyZooKeeperServer);
+            response.put("server_stats", zkServer.serverStats());
+            response.put("node_count", zkServer.getZKDatabase().getNodeCount());
+            return response;
+
+        }
+    }
+
+    /**
+     * Same as SrvrCommand but has extra "connections" entry.
+     */
+    public static class StatCommand extends SrvrCommand {
+        public StatCommand() {
+            super(Arrays.asList("stats", "stat"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = super.run(zkServer, kwargs);
+            response.put("connections", zkServer.getServerCnxnFactory().getAllConnectionInfo(true));
+            return response;
+        }
+    }
+
+    /**
+     * Resets server statistics.
+     */
+    public static class StatResetCommand extends CommandBase {
+        public StatResetCommand() {
+            super(Arrays.asList("stat_reset", "srst"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            CommandResponse response = initializeResponse();
+            zkServer.serverStats().reset();
+            return response;
+        }
+    }
+
+    /**
+     * Watch information aggregated by session. Returned Map contains:
+     *   - "session_id_to_watched_paths": Map<Long, Set<String>> session ID -> watched paths
+     * @see DataTree#getWatches()
+     */
+    public static class WatchCommand extends CommandBase {
+        public WatchCommand() {
+            super(Arrays.asList("watches", "wchc"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            DataTree dt = zkServer.getZKDatabase().getDataTree();
+            CommandResponse response = initializeResponse();
+            response.put("session_id_to_watched_paths", dt.getWatches().toMap());
+            return response;
+        }
+    }
+
+    /**
+     * Watch information aggregated by path. Returned Map contains:
+     *   - "path_to_session_ids": Map<String, Set<Long>> path -> session IDs of sessions watching path
+     * @see DataTree#getWatchesByPath()
+     */
+    public static class WatchesByPathCommand extends CommandBase {
+        public WatchesByPathCommand() {
+            super(Arrays.asList("watches_by_path", "wchp"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            DataTree dt = zkServer.getZKDatabase().getDataTree();
+            CommandResponse response = initializeResponse();
+            response.put("path_to_session_ids", dt.getWatchesByPath().toMap());
+            return response;
+        }
+    }
+
+    /**
+     * Summarized watch information.
+     * @see DataTree#getWatchesSummary()
+     */
+    public static class WatchSummaryCommand extends CommandBase {
+        public WatchSummaryCommand() {
+            super(Arrays.asList("watch_summary", "wchs"));
+        }
+
+        @Override
+        public CommandResponse run(ZooKeeperServer zkServer, Map<String, String> kwargs) {
+            DataTree dt = zkServer.getZKDatabase().getDataTree();
+            CommandResponse response = initializeResponse();
+            response.putAll(dt.getWatchesSummary().toMap());
+            return response;
+        }
+    }
+
+    private Commands() {}
+}

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/DummyAdminServer.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/DummyAdminServer.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/DummyAdminServer.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/DummyAdminServer.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,39 @@
+/**
+ * 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.admin;
+
+import org.apache.zookeeper.server.ZooKeeperServer;
+
+/**
+ * An AdminServer that does nothing.
+ *
+ * We use this class when we wish to disable the AdminServer. (This way we only
+ * have to consider whether the server is enabled when we create the
+ * AdminServer, which is handled by AdminServerFactory.)
+ */
+public class DummyAdminServer implements AdminServer {
+    @Override
+    public void start() throws AdminServerException {}
+
+    @Override
+    public void shutdown() throws AdminServerException {}
+
+    @Override
+    public void setZooKeeperServer(ZooKeeperServer zkServer) {}
+}

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/JettyAdminServer.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/JettyAdminServer.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/JettyAdminServer.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/JettyAdminServer.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,176 @@
+/**
+ * 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.admin;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.zookeeper.server.ZooKeeperServer;
+import org.mortbay.jetty.Server;
+import org.mortbay.jetty.servlet.Context;
+import org.mortbay.jetty.servlet.ServletHolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class encapsulates a Jetty server for running Commands.
+ *
+ * Given the default settings, start a ZooKeeper server and visit
+ * http://<hostname>:8080/commands for links to all registered commands. Visiting
+ * http://<hostname>:8080/commands/<commandname> will execute the associated
+ * Command and return the result in the body of the response. Any keyword
+ * arguments to the command are specified with URL parameters (e.g.,
+ * http://localhost:8080/commands/set_trace_mask?traceMask=306).
+ *
+ * @see Commands
+ * @see CommandOutputter
+ */
+public class JettyAdminServer implements AdminServer {
+    static final Logger LOG = LoggerFactory.getLogger(JettyAdminServer.class);
+
+    public static final int DEFAULT_PORT = 8080;
+    public static final String DEFAULT_COMMAND_URL = "/commands";
+
+    private final Server server;
+    private ZooKeeperServer zkServer;
+    private final int port;
+    private final String commandUrl;
+
+    public JettyAdminServer() throws AdminServerException {
+        this(Integer.getInteger("zookeeper.admin.serverPort", DEFAULT_PORT),
+             System.getProperty("zookeeper.admin.commandURL", DEFAULT_COMMAND_URL));
+    }
+
+    public JettyAdminServer(int port, String commandUrl) {
+        this.port = port;
+        this.commandUrl = commandUrl;
+
+        server = new Server(port);
+        Context context = new Context(server, "/");
+        server.setHandler(context);
+        context.addServlet(new ServletHolder(new CommandServlet()), commandUrl + "/*");
+    }
+
+    /**
+     * Start the embedded Jetty server.
+     */
+    @Override
+    public void start() throws AdminServerException {
+        try {
+            server.start();
+        } catch (Exception e) {
+            // Server.start() only throws Exception, so let's at least wrap it
+            // in an identifiable subclass
+            throw new AdminServerException(
+                    String.format("Problem starting AdminServer on port %d, command URL %s",
+                                  port, commandUrl), e);
+        }
+        LOG.info(String.format("Started AdminServer on port %d, command URL %s",
+                               port, commandUrl));
+    }
+
+    /**
+     * Stop the embedded Jetty server.
+     *
+     * This is not very important except for tests where multiple
+     * JettyAdminServers are started and may try to bind to the same ports if
+     * previous servers aren't shut down.
+     */
+    @Override
+    public void shutdown() throws AdminServerException {
+        try {
+            server.stop();
+        } catch (Exception e) {
+            throw new AdminServerException(
+                    String.format("Problem stopping AdminServer on port %d, command URL %s",
+                                  port, commandUrl), e);
+        }
+    }
+
+    /**
+     * Set the ZooKeeperServer that will be used to run Commands.
+     *
+     * It is not necessary to set the ZK server before calling
+     * AdminServer.start(), and the ZK server can be set to null when, e.g.,
+     * that server is being shut down. If the ZK server is not set or set to
+     * null, the AdminServer will still be able to issue Commands, but they will
+     * return an error until a ZK server is set.
+     */
+    @Override
+    public void setZooKeeperServer(ZooKeeperServer zkServer) {
+        this.zkServer = zkServer;
+    }
+
+    private class CommandServlet extends HttpServlet {
+        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
+            // Capture the command name from the URL
+            String cmd = request.getPathInfo();
+            if (cmd == null || cmd.equals("/")) {
+                // No command specified, print links to all commands instead
+                for (String link : commandLinks()) {
+                    response.getWriter().println(link);
+                    response.getWriter().println("<br />");
+                }
+                return;
+            }
+            // Strip leading "/"
+            cmd = cmd.substring(1);
+
+            // Extract keyword arguments to command from request parameters
+            @SuppressWarnings("unchecked")
+            Map<String, String[]> parameterMap = request.getParameterMap();
+            Map<String, String> kwargs = new HashMap<String, String>();
+            for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
+                kwargs.put(entry.getKey(), entry.getValue()[0]);
+            }
+
+            // Run the command
+            CommandResponse cmdResponse = Commands.runCommand(cmd, zkServer, kwargs);
+
+            // Format and print the output of the command
+            CommandOutputter outputter = new JsonOutputter();
+            response.setStatus(HttpServletResponse.SC_OK);
+            response.setContentType(outputter.getContentType());
+            outputter.output(cmdResponse, response.getWriter());
+        }
+    }
+
+    /**
+     * Returns a list of URLs to each registered Command.
+     */
+    private List<String> commandLinks() {
+        List<String> links = new ArrayList<String>();
+        List<String> commands = new ArrayList<String>(Commands.getPrimaryNames());
+        Collections.sort(commands);
+        for (String command : commands) {
+            String url = commandUrl + "/" + command;
+            links.add(String.format("<a href=\"%s\">%s</a>", url, command));
+        }
+        return links;
+    }
+}

Added: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/JsonOutputter.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/JsonOutputter.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/JsonOutputter.java (added)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/admin/JsonOutputter.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,68 @@
+/**
+ * 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.admin;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+
+import org.codehaus.jackson.JsonGenerationException;
+import org.codehaus.jackson.map.JsonMappingException;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.map.PropertyNamingStrategy;
+import org.codehaus.jackson.map.SerializationConfig;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JsonOutputter implements CommandOutputter {
+    static final Logger LOG = LoggerFactory.getLogger(JsonOutputter.class);
+
+    public static final String ERROR_RESPONSE = "{\"error\": \"Exception writing command response to JSON\"}";
+
+    private ObjectMapper mapper;
+
+    public JsonOutputter() {
+        mapper = new ObjectMapper();
+        mapper.configure(SerializationConfig.Feature.WRITE_ENUMS_USING_TO_STRING, true);
+        mapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
+        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
+    }
+
+    @Override
+    public String getContentType() {
+        return "application/json";
+    }
+
+    @Override
+    public void output(CommandResponse response, PrintWriter pw) {
+        try {
+            mapper.writeValue(pw, response.toMap());
+        } catch (JsonGenerationException e) {
+            LOG.warn("Exception writing command response to JSON:", e);
+            pw.write(ERROR_RESPONSE);
+        } catch (JsonMappingException e) {
+            LOG.warn("Exception writing command response to JSON:", e);
+            pw.write(ERROR_RESPONSE);
+        } catch (IOException e) {
+            LOG.warn("Exception writing command response to JSON:", e);
+            pw.write(ERROR_RESPONSE);
+        }
+    }
+
+}

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/Leader.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/Leader.java?rev=1611516&r1=1611515&r2=1611516&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/Leader.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/Leader.java Fri Jul 18 00:19:01 2014
@@ -533,6 +533,9 @@ public class Leader {
             if (!System.getProperty("zookeeper.leaderServes", "yes").equals("no")) {
                 self.cnxnFactory.setZooKeeperServer(zk);
             }
+
+            self.adminServer.setZooKeeperServer(zk);
+
             // Everything is a go, simply start counting the ticks
             // WARNING: I couldn't find any wait statement on a synchronized
             // block that would be notified by this notifyAll() call, so
@@ -619,6 +622,7 @@ public class Leader {
 
         // NIO should not accept conenctions
         self.cnxnFactory.setZooKeeperServer(null);
+        self.adminServer.setZooKeeperServer(null);
         try {
             ss.close();
         } catch (IOException e) {

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/LeaderSessionTracker.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/LeaderSessionTracker.java?rev=1611516&r1=1611515&r2=1611516&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/LeaderSessionTracker.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/LeaderSessionTracker.java Fri Jul 18 00:19:01 2014
@@ -18,6 +18,9 @@
 package org.apache.zookeeper.server.quorum;
 
 import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.ConcurrentMap;
 
 import org.apache.zookeeper.KeeperException.SessionExpiredException;
@@ -200,4 +203,17 @@ public class LeaderSessionTracker extend
         }
         globalSessionTracker.setSessionClosing(sessionId);
     }
+
+    public Map<Long, Set<Long>> getSessionExpiryMap() {
+        Map<Long, Set<Long>> sessionExpiryMap;
+        // combine local and global sessions, getting local first so upgrades
+        // to global are caught
+        if (localSessionTracker != null) {
+            sessionExpiryMap = localSessionTracker.getSessionExpiryMap();
+        } else {
+            sessionExpiryMap = new TreeMap<Long, Set<Long>>();
+        }
+        sessionExpiryMap.putAll(globalSessionTracker.getSessionExpiryMap());
+        return sessionExpiryMap;
+    }
 }

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/Learner.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/Learner.java?rev=1611516&r1=1611515&r2=1611516&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/Learner.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/Learner.java Fri Jul 18 00:19:01 2014
@@ -469,6 +469,7 @@ public class Learner {       
                         self.setCurrentEpoch(newEpoch);
                     }
                     self.cnxnFactory.setZooKeeperServer(zk);
+                    self.adminServer.setZooKeeperServer(zk);
                     break outerLoop;
                 case Leader.NEWLEADER: // it will be NEWLEADER in v1.0        
                    LOG.info("Learner received NEWLEADER message");
@@ -583,6 +584,7 @@ public class Learner {       
         self.cnxnFactory.setZooKeeperServer(null);
         // clear all the connections
         self.cnxnFactory.closeAll();
+        self.adminServer.setZooKeeperServer(null);
         // shutdown previous zookeeper
         if (zk != null) {
             zk.shutdown();

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/LearnerSessionTracker.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/LearnerSessionTracker.java?rev=1611516&r1=1611515&r2=1611516&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/LearnerSessionTracker.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/LearnerSessionTracker.java Fri Jul 18 00:19:01 2014
@@ -18,7 +18,9 @@
 package org.apache.zookeeper.server.quorum;
 
 import java.io.PrintWriter;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
@@ -213,4 +215,9 @@ public class LearnerSessionTracker exten
             localSessionTracker.setSessionClosing(sessionId);
         }
     }
+
+    @Override
+    public Map<Long, Set<Long>> getSessionExpiryMap() {
+        return new HashMap<Long, Set<Long>>();
+    }
 }

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java?rev=1611516&r1=1611515&r2=1611516&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeer.java Fri Jul 18 00:19:01 2014
@@ -57,6 +57,9 @@ import org.apache.zookeeper.server.Serve
 import org.apache.zookeeper.server.ZKDatabase;
 import org.apache.zookeeper.server.ZooKeeperServer;
 import org.apache.zookeeper.server.ZooKeeperThread;
+import org.apache.zookeeper.server.admin.AdminServer;
+import org.apache.zookeeper.server.admin.AdminServer.AdminServerException;
+import org.apache.zookeeper.server.admin.AdminServerFactory;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
 import org.apache.zookeeper.server.quorum.flexible.QuorumMaj;
@@ -584,10 +587,13 @@ public class QuorumPeer extends ZooKeepe
 
     private final QuorumStats quorumStats;
 
+    AdminServer adminServer;
+
     public QuorumPeer() {
         super("QuorumPeer");
         quorumStats = new QuorumStats(this);
         jmxRemotePeerBean = new HashMap<Long, RemotePeerBean>();
+        adminServer = AdminServerFactory.createAdminServer();
     }
 
 
@@ -623,6 +629,7 @@ public class QuorumPeer extends ZooKeepe
         this.dynamicConfigFilename = (memFilename != null) ? memFilename : "zoo_replicated" + myid + ".dynamic";
         if(quorumConfig == null) quorumConfig = new QuorumMaj(quorumPeers);
         setQuorumVerifier(quorumConfig, false);
+        adminServer = AdminServerFactory.createAdminServer();
     }
 
     QuorumStats quorumStats() {
@@ -636,6 +643,12 @@ public class QuorumPeer extends ZooKeepe
          }
         loadDataBase();
         cnxnFactory.start();
+        try {
+            adminServer.start();
+        } catch (AdminServerException e) {
+            LOG.warn("Problem starting AdminServer", e);
+            System.out.println(e);
+        }
         startLeaderElection();
         super.start();
     }
@@ -1054,6 +1067,12 @@ public class QuorumPeer extends ZooKeepe
             udpSocket.close();
         }
 
+        try {
+            adminServer.shutdown();
+        } catch (AdminServerException e) {
+            LOG.warn("Problem stopping AdminServer", e);
+        }
+
         if(getElectionAlg() != null){
             this.interrupt();
             getElectionAlg().shutdown();

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java?rev=1611516&r1=1611515&r2=1611516&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/QuorumPeerMain.java Fri Jul 18 00:19:01 2014
@@ -28,6 +28,7 @@ import org.apache.zookeeper.server.Serve
 import org.apache.zookeeper.server.ZKDatabase;
 import org.apache.zookeeper.server.DatadirCleanupManager;
 import org.apache.zookeeper.server.ZooKeeperServerMain;
+import org.apache.zookeeper.server.admin.AdminServer.AdminServerException;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog;
 import org.apache.zookeeper.server.persistence.FileTxnSnapLog.DatadirException;
 import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
@@ -89,6 +90,10 @@ public class QuorumPeerMain {
             LOG.error("Unable to access datadir, exiting abnormally", e);
             System.err.println("Unable to access datadir, exiting abnormally");
             System.exit(3);
+        } catch (AdminServerException e) {
+            LOG.error("Unable to start AdminServer, exiting abnormally", e);
+            System.err.println("Unable to start AdminServer, exiting abnormally");
+            System.exit(4);
         } catch (Exception e) {
             LOG.error("Unexpected exception, exiting abnormally", e);
             System.exit(1);
@@ -98,7 +103,7 @@ public class QuorumPeerMain {
     }
 
     protected void initializeAndRun(String[] args)
-        throws ConfigException, IOException
+        throws ConfigException, IOException, AdminServerException
     {
         QuorumPeerConfig config = new QuorumPeerConfig();
         if (args.length == 1) {
@@ -121,7 +126,7 @@ public class QuorumPeerMain {
         }
     }
 
-    public void runFromConfig(QuorumPeerConfig config) throws IOException {
+    public void runFromConfig(QuorumPeerConfig config) throws IOException, AdminServerException {
       try {
           ManagedUtil.registerLog4jMBeans();
       } catch (JMException e) {

Modified: zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java?rev=1611516&r1=1611515&r2=1611516&view=diff
==============================================================================
--- zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java (original)
+++ zookeeper/trunk/src/java/main/org/apache/zookeeper/server/quorum/ReadOnlyZooKeeperServer.java Fri Jul 18 00:19:01 2014
@@ -69,6 +69,7 @@ public class ReadOnlyZooKeeperServer ext
         registerJMX(new ReadOnlyBean(this), self.jmxLocalPeerBean);
         super.startup();
         self.cnxnFactory.setZooKeeperServer(this);
+        self.adminServer.setZooKeeperServer(this);
         LOG.info("Read-only server started");
     }
 
@@ -144,6 +145,8 @@ public class ReadOnlyZooKeeperServer ext
         // clear all the connections
         self.cnxnFactory.closeAll();
 
+        self.adminServer.setZooKeeperServer(null);
+
         // shutdown the server itself
         super.shutdown();
     }

Modified: zookeeper/trunk/src/java/test/org/apache/zookeeper/ZKTestCase.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/ZKTestCase.java?rev=1611516&r1=1611515&r2=1611516&view=diff
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/ZKTestCase.java (original)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/ZKTestCase.java Fri Jul 18 00:19:01 2014
@@ -47,6 +47,10 @@ public class ZKTestCase {
     public MethodRule watchman = new TestWatchman() {
         @Override
         public void starting(FrameworkMethod method) {
+            // By default, disable starting a JettyAdminServer in tests to avoid
+            // accidentally attempting to start multiple admin servers on the
+            // same port.
+            System.setProperty("zookeeper.admin.enableServer", "false");
             testName = method.getName();
             LOG.info("STARTING " + testName);
         }

Modified: zookeeper/trunk/src/java/test/org/apache/zookeeper/server/PrepRequestProcessorTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/server/PrepRequestProcessorTest.java?rev=1611516&r1=1611515&r2=1611516&view=diff
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/server/PrepRequestProcessorTest.java (original)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/server/PrepRequestProcessorTest.java Fri Jul 18 00:19:01 2014
@@ -23,6 +23,9 @@ import static org.junit.Assert.*;
 import java.io.File;
 import java.io.PrintWriter;
 import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 
 import org.apache.zookeeper.PortAssignment;
@@ -145,5 +148,9 @@ public class PrepRequestProcessorTest ex
                 throws SessionExpiredException, SessionMovedException {
             // TODO Auto-generated method stub
         }
+        @Override
+        public Map<Long, Set<Long>> getSessionExpiryMap() {
+            return new HashMap<Long, Set<Long>>();
+        }
     }
 }

Added: zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesPathReportTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesPathReportTest.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesPathReportTest.java (added)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesPathReportTest.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,59 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class WatchesPathReportTest {
+    private Map<String, Set<Long>> m;
+    private WatchesPathReport r;
+    @Before public void setUp() {
+        m = new HashMap<String, Set<Long>>();
+        Set<Long> s = new HashSet<Long>();
+        s.add(101L);
+        s.add(102L);
+        m.put("path1", s);
+        s = new HashSet<Long>();
+        s.add(201L);
+        m.put("path2", s);
+        r = new WatchesPathReport(m);
+    }
+    @Test public void testHasSessions() {
+        assertTrue(r.hasSessions("path1"));
+        assertTrue(r.hasSessions("path2"));
+        assertFalse(r.hasSessions("path3"));
+    }
+    @Test public void testGetSessions() {
+        Set<Long> s = r.getSessions("path1");
+        assertEquals(2, s.size());
+        assertTrue(s.contains(101L));
+        assertTrue(s.contains(102L));
+        s = r.getSessions("path2");
+        assertEquals(1, s.size());
+        assertTrue(s.contains(201L));
+        assertNull(r.getSessions("path3"));
+    }
+    @Test public void testToMap() {
+        assertEquals(m, r.toMap());
+    }
+}

Added: zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesReportTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesReportTest.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesReportTest.java (added)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesReportTest.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,59 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class WatchesReportTest {
+    private Map<Long, Set<String>> m;
+    private WatchesReport r;
+    @Before public void setUp() {
+        m = new HashMap<Long, Set<String>>();
+        Set<String> s = new HashSet<String>();
+        s.add("path1a");
+        s.add("path1b");
+        m.put(1L, s);
+        s = new HashSet<String>();
+        s.add("path2a");
+        m.put(2L, s);
+        r = new WatchesReport(m);
+    }
+    @Test public void testHasPaths() {
+        assertTrue(r.hasPaths(1L));
+        assertTrue(r.hasPaths(2L));
+        assertFalse(r.hasPaths(3L));
+    }
+    @Test public void testGetPaths() {
+        Set<String> s = r.getPaths(1L);
+        assertEquals(2, s.size());
+        assertTrue(s.contains("path1a"));
+        assertTrue(s.contains("path1b"));
+        s = r.getPaths(2L);
+        assertEquals(1, s.size());
+        assertTrue(s.contains("path2a"));
+        assertNull(r.getPaths(3L));
+    }
+    @Test public void testToMap() {
+        assertEquals(m, r.toMap());
+    }
+}

Added: zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesSummaryTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesSummaryTest.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesSummaryTest.java (added)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/server/WatchesSummaryTest.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,41 @@
+/*
+ * 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 java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class WatchesSummaryTest {
+    private WatchesSummary s;
+    @Before public void setUp() {
+        s = new WatchesSummary(1, 2, 3);
+    }
+    @Test public void testGetters() {
+        assertEquals(1, s.getNumConnections());
+        assertEquals(2, s.getNumPaths());
+        assertEquals(3, s.getTotalWatches());
+    }
+    @Test public void testToMap() {
+        Map<String, Object> m = s.toMap();
+        assertEquals(3, m.size());
+        assertEquals(Integer.valueOf(1), m.get(WatchesSummary.KEY_NUM_CONNECTIONS));
+        assertEquals(Integer.valueOf(2), m.get(WatchesSummary.KEY_NUM_PATHS));
+        assertEquals(Integer.valueOf(3), m.get(WatchesSummary.KEY_NUM_TOTAL_WATCHES));
+    }
+}

Added: zookeeper/trunk/src/java/test/org/apache/zookeeper/server/ZooKeeperServerConfTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/server/ZooKeeperServerConfTest.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/server/ZooKeeperServerConfTest.java (added)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/server/ZooKeeperServerConfTest.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,51 @@
+/*
+ * 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 java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+public class ZooKeeperServerConfTest {
+    private ZooKeeperServerConf c;
+    @Before public void setUp() {
+        c = new ZooKeeperServerConf(1, "a", "b", 2, 3, 4, 5, 6L);
+    }
+    @Test public void testGetters() {
+        assertEquals(1, c.getClientPort());
+        assertEquals("a", c.getDataDir());
+        assertEquals("b", c.getDataLogDir());
+        assertEquals(2, c.getTickTime());
+        assertEquals(3, c.getMaxClientCnxnsPerHost());
+        assertEquals(4, c.getMinSessionTimeout());
+        assertEquals(5, c.getMaxSessionTimeout());
+        assertEquals(6L, c.getServerId());
+    }
+    @Test public void testToMap() {
+        Map<String, Object> m = c.toMap();
+        assertEquals(8, m.size());
+        assertEquals(Integer.valueOf(1), m.get(ZooKeeperServerConf.KEY_CLIENT_PORT));
+        assertEquals("a", m.get(ZooKeeperServerConf.KEY_DATA_DIR));
+        assertEquals("b", m.get(ZooKeeperServerConf.KEY_DATA_LOG_DIR));
+        assertEquals(Integer.valueOf(2), m.get(ZooKeeperServerConf.KEY_TICK_TIME));
+        assertEquals(Integer.valueOf(3), m.get(ZooKeeperServerConf.KEY_MAX_CLIENT_CNXNS));
+        assertEquals(Integer.valueOf(4), m.get(ZooKeeperServerConf.KEY_MIN_SESSION_TIMEOUT));
+        assertEquals(Integer.valueOf(5), m.get(ZooKeeperServerConf.KEY_MAX_SESSION_TIMEOUT));
+        assertEquals(Long.valueOf(6L), m.get(ZooKeeperServerConf.KEY_SERVER_ID));
+    }
+}

Added: zookeeper/trunk/src/java/test/org/apache/zookeeper/server/admin/CommandResponseTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/server/admin/CommandResponseTest.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/server/admin/CommandResponseTest.java (added)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/server/admin/CommandResponseTest.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,53 @@
+/*
+ * 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.admin;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+public class CommandResponseTest {
+    private CommandResponse r;
+
+    @Before public void setUp() throws Exception {
+        r = new CommandResponse("makemeasandwich", "makeityourself");
+    }
+
+    @Test public void testGetters() {
+        assertEquals("makemeasandwich", r.getCommand());
+        assertEquals("makeityourself", r.getError());
+    }
+
+    @Test public void testMap() {
+        r.put("missing", "sudo");
+        Map<String, Object> m = new HashMap<String, Object>();
+        m.put("origin", "xkcd");
+        m.put("url", "http://xkcd.com/149/");
+        r.putAll(m);
+
+        Map<String, Object> rmap = r.toMap();
+        assertEquals(5, rmap.size());
+        assertEquals("makemeasandwich", rmap.get(CommandResponse.KEY_COMMAND));
+        assertEquals("makeityourself", rmap.get(CommandResponse.KEY_ERROR));
+        assertEquals("sudo", rmap.get("missing"));
+        assertEquals("xkcd", rmap.get("origin"));
+        assertEquals("http://xkcd.com/149/", rmap.get("url"));
+    }
+}

Added: zookeeper/trunk/src/java/test/org/apache/zookeeper/server/admin/CommandsTest.java
URL: http://svn.apache.org/viewvc/zookeeper/trunk/src/java/test/org/apache/zookeeper/server/admin/CommandsTest.java?rev=1611516&view=auto
==============================================================================
--- zookeeper/trunk/src/java/test/org/apache/zookeeper/server/admin/CommandsTest.java (added)
+++ zookeeper/trunk/src/java/test/org/apache/zookeeper/server/admin/CommandsTest.java Fri Jul 18 00:19:01 2014
@@ -0,0 +1,236 @@
+/**
+ * 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.admin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.zookeeper.server.ServerStats;
+import org.apache.zookeeper.server.ZooKeeperServer;
+import org.apache.zookeeper.test.ClientBase;
+import org.junit.Test;
+
+public class CommandsTest extends ClientBase {
+    /**
+     * Checks that running a given Command returns the expected Map. Asserts
+     * that all specified keys are present with values of the specified types
+     * and that there are no extra entries.
+     *
+     * @param cmdName
+     *            - the primary name of the command
+     * @param kwargs
+     *            - keyword arguments to the command
+     * @param keys
+     *            - the keys that are expected in the returned Map
+     * @param types
+     *            - the classes of the values in the returned Map. types[i] is
+     *            the type of the value for keys[i].
+     * @throws IOException
+     * @throws InterruptedException
+     */
+    public void testCommand(String cmdName, Map<String, String> kwargs, Field... fields)
+            throws IOException, InterruptedException {
+        ZooKeeperServer zks = serverFactory.getZooKeeperServer();
+        Map<String, Object> result = Commands.runCommand(cmdName, zks, kwargs).toMap();
+
+        assertTrue(result.containsKey("command"));
+        // This is only true because we're setting cmdName to the primary name
+        assertEquals(cmdName, result.remove("command"));
+        assertTrue(result.containsKey("error"));
+        assertNull("error: " + result.get("error"), result.remove("error"));
+
+        for (Field field : fields) {
+            String k = field.key;
+            assertTrue("Result from command " + cmdName + " missing field \"" + k + "\""
+                       + "\n" + result,
+                       result.containsKey(k));
+            Class<?> t = field.type;
+            Object v = result.remove(k);
+            assertTrue("\"" + k + "\" field from command " + cmdName + " should be of type " + t
+                       + ", is actually of type " + v.getClass(),
+                       t.isAssignableFrom(v.getClass()));
+        }
+
+        assertTrue("Result from command " + cmdName + " contains extra fields: " + result,
+                   result.isEmpty());
+    }
+
+    public void testCommand(String cmdName, Field... fields)
+            throws IOException, InterruptedException {
+        testCommand(cmdName, new HashMap<String, String>(), fields);
+    }
+
+    private static class Field {
+        String key;
+        Class<?> type;
+        Field(String key, Class<?> type) {
+            this.key = key;
+            this.type = type;
+        }
+    }
+
+    @Test
+    public void testConfiguration() throws IOException, InterruptedException {
+        testCommand("configuration",
+                    new Field("client_port", Integer.class),
+                    new Field("data_dir", String.class),
+                    new Field("data_log_dir", String.class),
+                    new Field("tick_time", Integer.class),
+                    new Field("max_client_cnxns", Integer.class),
+                    new Field("min_session_timeout", Integer.class),
+                    new Field("max_session_timeout", Integer.class),
+                    new Field("server_id", Long.class));
+    }
+
+    @Test
+    public void testConnections() throws IOException, InterruptedException {
+        testCommand("connections",
+                    new Field("connections", Iterable.class));
+    }
+
+    @Test
+    public void testConnectionStatReset() throws IOException, InterruptedException {
+        testCommand("connection_stat_reset");
+    }
+
+    @Test
+    public void testDump() throws IOException, InterruptedException {
+        testCommand("dump",
+                    new Field("expiry_time_to_session_ids", Map.class),
+                    new Field("session_id_to_ephemeral_paths", Map.class));
+    }
+
+    @Test
+    public void testEnvironment() throws IOException, InterruptedException {
+        testCommand("environment",
+                    new Field("zookeeper.version", String.class),
+                    new Field("host.name", String.class),
+                    new Field("java.version", String.class),
+                    new Field("java.vendor", String.class),
+                    new Field("java.home", String.class),
+                    new Field("java.class.path", String.class),
+                    new Field("java.library.path", String.class),
+                    new Field("java.io.tmpdir", String.class),
+                    new Field("java.compiler", String.class),
+                    new Field("os.name", String.class),
+                    new Field("os.arch", String.class),
+                    new Field("os.version", String.class),
+                    new Field("user.name", String.class),
+                    new Field("user.home", String.class),
+                    new Field("user.dir", String.class),
+                    new Field("os.memory.free", String.class),
+                    new Field("os.memory.max", String.class),
+                    new Field("os.memory.total", String.class));
+    }
+
+    @Test
+    public void testGetTraceMask() throws IOException, InterruptedException {
+        testCommand("get_trace_mask",
+                    new Field("tracemask", Long.class));
+    }
+
+    @Test
+    public void testIsReadOnly() throws IOException, InterruptedException {
+        testCommand("is_read_only",
+                    new Field("read_only", Boolean.class));
+    }
+
+    @Test
+    public void testMonitor() throws IOException, InterruptedException {
+        testCommand("monitor",
+                    new Field("version", String.class),
+                    new Field("avg_latency", Long.class),
+                    new Field("max_latency", Long.class),
+                    new Field("min_latency", Long.class),
+                    new Field("packets_received", Long.class),
+                    new Field("packets_sent", Long.class),
+                    new Field("num_alive_connections", Integer.class),
+                    new Field("outstanding_requests", Long.class),
+                    new Field("server_state", String.class),
+                    new Field("znode_count", Integer.class),
+                    new Field("watch_count", Integer.class),
+                    new Field("ephemerals_count", Integer.class),
+                    new Field("approximate_data_size", Long.class),
+                    new Field("open_file_descriptor_count", Long.class),
+                    new Field("max_file_descriptor_count", Long.class));
+    }
+
+    @Test
+    public void testRuok() throws IOException, InterruptedException {
+        testCommand("ruok");
+    }
+
+    @Test
+    public void testServerStats() throws IOException, InterruptedException {
+        testCommand("server_stats",
+                new Field("version", String.class),
+                new Field("read_only", Boolean.class),
+                new Field("server_stats", ServerStats.class),
+                new Field("node_count", Integer.class));
+    }
+
+    @Test
+    public void testSetTraceMask() throws IOException, InterruptedException {
+        Map<String, String> kwargs = new HashMap<String, String>();
+        kwargs.put("traceMask", "1");
+        testCommand("set_trace_mask", kwargs,
+                    new Field("tracemask", Long.class));
+    }
+
+    @Test
+    public void testStat() throws IOException, InterruptedException {
+        testCommand("stats",
+                    new Field("version", String.class),
+                    new Field("read_only", Boolean.class),
+                    new Field("server_stats", ServerStats.class),
+                    new Field("node_count", Integer.class),
+                    new Field("connections", Iterable.class));
+    }
+
+    @Test
+    public void testStatReset() throws IOException, InterruptedException {
+        testCommand("stat_reset");
+    }
+
+    @Test
+    public void testWatches() throws IOException, InterruptedException {
+        testCommand("watches",
+                    new Field("session_id_to_watched_paths", Map.class));
+    }
+
+    @Test
+    public void testWatchesByPath() throws IOException, InterruptedException {
+        testCommand("watches_by_path",
+                    new Field("path_to_session_ids", Map.class));
+    }
+
+    @Test
+    public void testWatchSummary() throws IOException, InterruptedException {
+        testCommand("watch_summary",
+                    new Field("num_connections", Integer.class),
+                    new Field("num_paths", Integer.class),
+                    new Field("num_total_watches", Integer.class));
+    }
+
+}