zookeeper-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From an...@apache.org
Subject [17/51] [partial] zookeeper git commit: ZOOKEEPER-3032: MAVEN MIGRATION - zookeeper-server
Date Fri, 19 Oct 2018 12:40:15 GMT
http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/GetConfigCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/GetConfigCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/GetConfigCommand.java
new file mode 100644
index 0000000..dda6281
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/GetConfigCommand.java
@@ -0,0 +1,84 @@
+/**
+ * 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.cli;
+
+import org.apache.commons.cli.*;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.data.Stat;
+import org.apache.zookeeper.server.util.ConfigUtils;
+
+/**
+ * get command for cli
+ */
+public class GetConfigCommand extends CliCommand {
+
+    private static Options options = new Options();
+    private String args[];
+    private CommandLine cl;
+
+    {
+        options.addOption("s", false, "stats");
+        options.addOption("w", false, "watch");
+        options.addOption("c", false, "client connection string");
+    }
+
+    public GetConfigCommand() {
+        super("config", "[-c] [-w] [-s]");
+    }
+
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+
+        Parser parser = new PosixParser();
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        args = cl.getArgs();
+        if (args.length < 1) {
+            throw new CliParseException(getUsageStr());
+        }
+
+        return this;
+    }
+
+    @Override
+    public boolean exec() throws CliException {
+        boolean watch = cl.hasOption("w");        
+        Stat stat = new Stat();
+        byte data[];
+        try {
+            data = zk.getConfig(watch, stat);
+        } catch (KeeperException|InterruptedException ex) {
+            throw new CliWrapperException(ex);
+        }
+        data = (data == null) ? "null".getBytes() : data;
+        if (cl.hasOption("c")) {
+            out.println(ConfigUtils.getClientConfigStr(new String(data)));
+        } else {
+            out.println(new String(data));
+        }
+        
+        if (cl.hasOption("s")) {
+            new StatPrinter(out).print(stat);
+        }                
+        
+        return watch;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ListQuotaCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ListQuotaCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ListQuotaCommand.java
new file mode 100644
index 0000000..8c51c26
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ListQuotaCommand.java
@@ -0,0 +1,82 @@
+/**
+ * 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.cli;
+
+import org.apache.commons.cli.*;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.Quotas;
+import org.apache.zookeeper.StatsTrack;
+import org.apache.zookeeper.data.Stat;
+
+/**
+ * listQuta command for cli
+ */
+public class ListQuotaCommand extends CliCommand {
+
+    private static Options options = new Options();
+    private String[] args;
+    
+    public ListQuotaCommand() {
+        super("listquota", "path");
+    }
+
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        Parser parser = new PosixParser();
+        CommandLine cl;
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        args = cl.getArgs();
+        if(args.length < 2) {
+            throw new CliParseException(getUsageStr());
+        }
+        
+        return this;
+    }
+    
+    @Override
+    public boolean exec() throws CliException {
+        String path = args[1];
+        String absolutePath = Quotas.quotaZookeeper + path + "/"
+                + Quotas.limitNode;
+        try {
+            err.println("absolute path is " + absolutePath);
+            Stat stat = new Stat();
+            byte[] data = zk.getData(absolutePath, false, stat);
+            StatsTrack st = new StatsTrack(new String(data));
+            out.println("Output quota for " + path + " "
+                    + st.toString());
+
+            data = zk.getData(Quotas.quotaZookeeper + path + "/"
+                    + Quotas.statNode, false, stat);
+            out.println("Output stat for " + path + " "
+                    + new StatsTrack(new String(data)).toString());
+        } catch (IllegalArgumentException ex) {
+            throw new MalformedPathException(ex.getMessage());
+        } catch (KeeperException.NoNodeException ne) {
+            err.println("quota for " + path + " does not exist.");
+        } catch (KeeperException|InterruptedException ex) {
+            throw new CliWrapperException(ex);
+        }
+        
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/Ls2Command.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/Ls2Command.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/Ls2Command.java
new file mode 100644
index 0000000..aed1b0e
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/Ls2Command.java
@@ -0,0 +1,72 @@
+/**
+ * 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.cli;
+
+import java.util.List;
+import org.apache.commons.cli.*;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.data.Stat;
+
+/**
+ * ls2 command for cli
+ */
+public class Ls2Command extends CliCommand {
+
+    private static Options options = new Options();
+    private String args[];
+    
+    public Ls2Command() {
+        super("ls2", "path [watch]");
+    }
+    
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        Parser parser = new PosixParser();
+        CommandLine cl;
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        args = cl.getArgs();
+        if (args.length < 2) {
+            throw new CliParseException(getUsageStr());
+        }
+        
+        return this;
+    }
+
+    @Override
+    public boolean exec() throws CliException {
+        err.println("'ls2' has been deprecated. "
+                  + "Please use 'ls [-s] path' instead.");
+        String path = args[1];
+        boolean watch = args.length > 2;
+        Stat stat = new Stat();
+        List<String> children;
+        try {
+            children = zk.getChildren(path, watch, stat);
+        } catch (IllegalArgumentException ex) {
+            throw new MalformedPathException(ex.getMessage());
+        } catch (KeeperException|InterruptedException ex) {
+            throw new CliWrapperException(ex);
+        }
+        out.println(children);
+        new StatPrinter(out).print(stat);
+        return watch;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/LsCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/LsCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/LsCommand.java
new file mode 100644
index 0000000..9e53d5d
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/LsCommand.java
@@ -0,0 +1,137 @@
+/**
+ * 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.cli;
+
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.cli.*;
+import org.apache.zookeeper.AsyncCallback.StringCallback;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZKUtil;
+import org.apache.zookeeper.data.Stat;
+
+/**
+ * ls command for cli
+ */
+public class LsCommand extends CliCommand {
+
+    private static Options options = new Options();
+    private String args[];
+    private CommandLine cl;
+
+    {
+        options.addOption("?", false, "help");
+        options.addOption("s", false, "stat");
+        options.addOption("w", false, "watch");
+        options.addOption("R", false, "recurse");
+    }
+
+    public LsCommand() {
+        super("ls", "[-s] [-w] [-R] path");
+    }
+
+    private void printHelp() {
+        HelpFormatter formatter = new HelpFormatter();
+        formatter.printHelp("ls [options] path", options);
+    }
+
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        Parser parser = new PosixParser();
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+
+        args = cl.getArgs();
+        if (cl.hasOption("?")) {
+            printHelp();
+        }
+
+        retainCompatibility(cmdArgs);
+
+        return this;
+    }
+
+    private void retainCompatibility(String[] cmdArgs) throws CliParseException {
+        // get path [watch]
+        if (args.length > 2) {
+            // rewrite to option
+            cmdArgs[2] = "-w";
+            err.println("'ls path [watch]' has been deprecated. "
+                    + "Please use 'ls [-w] path' instead.");
+            Parser parser = new PosixParser();
+            try {
+                cl = parser.parse(options, cmdArgs);
+            } catch (ParseException ex) {
+                throw new CliParseException(ex);
+            }
+            args = cl.getArgs();
+        }
+    }
+
+    @Override
+    public boolean exec() throws CliException {
+        if (args.length < 2) {
+            throw new MalformedCommandException(getUsageStr());
+        }
+
+        String path = args[1];
+        boolean watch = cl.hasOption("w");
+        boolean withStat = cl.hasOption("s");
+        boolean recursive = cl.hasOption("R");
+        try {
+            if (recursive) {
+                ZKUtil.visitSubTreeDFS(zk, path, watch, new StringCallback() {
+                    @Override
+                    public void processResult(int rc, String path, Object ctx, String name) {
+                        out.println(path);
+                    }
+                });
+            } else {
+                Stat stat = withStat ? new Stat() : null;
+                List<String> children = zk.getChildren(path, watch, stat);
+                printChildren(children, stat);
+            }
+        } catch (IllegalArgumentException ex) {
+            throw new MalformedPathException(ex.getMessage());
+        } catch (KeeperException|InterruptedException ex) {
+            throw new CliWrapperException(ex);
+        }
+        return watch;
+    }
+
+    private void printChildren(List<String> children, Stat stat) {
+        Collections.sort(children);
+        out.append("[");
+        boolean first = true;
+        for (String child : children) {
+            if (!first) {
+                out.append(", ");
+            } else {
+                first = false;
+            }
+            out.append(child);
+        }
+        out.append("]");
+        if (stat != null) {
+            new StatPrinter(out).print(stat);
+        }
+        out.append("\n");
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/MalformedCommandException.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/MalformedCommandException.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/MalformedCommandException.java
new file mode 100644
index 0000000..72b19ef
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/MalformedCommandException.java
@@ -0,0 +1,25 @@
+/**
+ * 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.cli;
+
+@SuppressWarnings("serial")
+public class MalformedCommandException extends CliException {
+    public MalformedCommandException(String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/MalformedPathException.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/MalformedPathException.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/MalformedPathException.java
new file mode 100644
index 0000000..e65765b
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/MalformedPathException.java
@@ -0,0 +1,25 @@
+/**
+ * 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.cli;
+
+@SuppressWarnings("serial")
+public class MalformedPathException extends CliException {
+    public MalformedPathException(String message) {
+        super(message);
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ReconfigCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ReconfigCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ReconfigCommand.java
new file mode 100644
index 0000000..342f5d2
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/ReconfigCommand.java
@@ -0,0 +1,169 @@
+/**
+ * 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.cli;
+
+import java.io.FileInputStream;
+import java.util.Properties;
+
+import org.apache.commons.cli.*;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.admin.ZooKeeperAdmin;
+import org.apache.zookeeper.data.Stat;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig;
+
+/**
+ * reconfig command for cli
+ */
+public class ReconfigCommand extends CliCommand {
+
+    private static Options options = new Options();
+
+    /* joining - comma separated list of server config strings for servers to be added to the ensemble.
+     * Each entry is identical in syntax as it would appear in a configuration file. Only used for 
+     * incremental reconfigurations.
+     */
+    private String joining;
+
+    /* leaving - comma separated list of server IDs to be removed from the ensemble. Only used for
+     * incremental reconfigurations.
+     */
+    private String leaving;
+
+    /* members - comma separated list of new membership information (e.g., contents of a membership
+     * configuration file) - for use only with a non-incremental reconfiguration. This may be specified
+     * manually via the -members flag or it will automatically be filled in by reading the contents
+     * of an actual configuration file using the -file flag.
+     */
+    private String members;
+
+    /* version - version of config from which we want to reconfigure - if current config is different
+     * reconfiguration will fail. Should be ommitted from the CLI to disable this option.
+     */
+    long version = -1;
+    private CommandLine cl;
+
+    {
+        options.addOption("s", false, "stats");
+        options.addOption("v", true, "required current config version");
+        options.addOption("file", true, "path of config file to parse for membership");
+        options.addOption("members", true, "comma-separated list of config strings for " +
+        		"non-incremental reconfig");
+        options.addOption("add", true, "comma-separated list of config strings for " +
+        		"new servers");
+        options.addOption("remove", true, "comma-separated list of server IDs to remove");
+    }
+
+    public ReconfigCommand() {
+        super("reconfig", "[-s] " +
+        		"[-v version] " +
+        		"[[-file path] | " +
+        		"[-members serverID=host:port1:port2;port3[,...]*]] | " +
+        		"[-add serverId=host:port1:port2;port3[,...]]* " +
+        		"[-remove serverId[,...]*]");
+    }
+
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        joining = null;
+        leaving = null;
+        members = null;
+        Parser parser = new PosixParser();
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        if (!(cl.hasOption("file") || cl.hasOption("members")) && !cl.hasOption("add") && !cl.hasOption("remove")) {
+            throw new CliParseException(getUsageStr());
+        }
+        if (cl.hasOption("v")) {
+            try{ 
+                version = Long.parseLong(cl.getOptionValue("v"), 16);
+            } catch (NumberFormatException e){
+                throw new CliParseException("-v must be followed by a long (configuration version)");
+            }
+        } else {
+            version = -1;
+        }
+
+        // Simple error checking for conflicting modes
+        if ((cl.hasOption("file") || cl.hasOption("members")) && (cl.hasOption("add") || cl.hasOption("remove"))) {
+            throw new CliParseException("Can't use -file or -members together with -add or -remove (mixing incremental" +
+            		" and non-incremental modes is not allowed)");
+        }
+        if (cl.hasOption("file") && cl.hasOption("members"))
+        {
+            throw new CliParseException("Can't use -file and -members together (conflicting non-incremental modes)");
+        }
+
+        // Set the joining/leaving/members values based on the mode we're in
+        if (cl.hasOption("add")) {
+           joining = cl.getOptionValue("add").toLowerCase();
+        }
+        if (cl.hasOption("remove")) {
+           leaving = cl.getOptionValue("remove").toLowerCase();
+        }
+        if (cl.hasOption("members")) {
+           members = cl.getOptionValue("members").toLowerCase();
+        }
+        if (cl.hasOption("file")) {
+            try {           
+                FileInputStream inConfig = new FileInputStream(cl.getOptionValue("file"));
+                Properties dynamicCfg = new Properties();
+                try {
+                    dynamicCfg.load(inConfig);
+                } finally {
+                    inConfig.close();
+                }
+                //check that membership makes sense; leader will make these checks again
+                //don't check for leader election ports since 
+                //client doesn't know what leader election alg is used
+                members = QuorumPeerConfig.parseDynamicConfig(dynamicCfg, 0, true, false).toString();
+            } catch (Exception e) {
+                throw new CliParseException("Error processing " + cl.getOptionValue("file") + e.getMessage());
+            } 
+        }
+        return this;
+    }
+
+    @Override
+    public boolean exec() throws CliException {
+        try {
+            Stat stat = new Stat();
+            if (!(zk instanceof ZooKeeperAdmin)) {
+                // This should never happen when executing reconfig command line,
+                // because it is guaranteed that we have a ZooKeeperAdmin instance ready
+                // to use in CliCommand stack.
+                // The only exception would be in test code where clients can directly set
+                // ZooKeeper object to ZooKeeperMain.
+                return false;
+            }
+
+            byte[] curConfig = ((ZooKeeperAdmin)zk).reconfigure(joining,
+                    leaving, members, version, stat);
+            out.println("Committed new configuration:\n" + new String(curConfig));
+            
+            if (cl.hasOption("s")) {
+                new StatPrinter(out).print(stat);
+            }
+        } catch (KeeperException|InterruptedException ex) {
+            throw new CliWrapperException(ex);
+        }
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/RemoveWatchesCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/RemoveWatchesCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/RemoveWatchesCommand.java
new file mode 100644
index 0000000..2863443
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/RemoveWatchesCommand.java
@@ -0,0 +1,89 @@
+/**
+ * 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.cli;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.Parser;
+import org.apache.commons.cli.PosixParser;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.Watcher.WatcherType;
+
+/**
+ * Remove watches command for cli
+ */
+public class RemoveWatchesCommand extends CliCommand {
+
+    private static Options options = new Options();
+    private String[] args;
+    private CommandLine cl;
+
+    {
+        options.addOption("c", false, "child watcher type");
+        options.addOption("d", false, "data watcher type");
+        options.addOption("a", false, "any watcher type");
+        options.addOption("l", false,
+                "remove locally when there is no server connection");
+    }
+
+    public RemoveWatchesCommand() {
+        super("removewatches", "path [-c|-d|-a] [-l]");
+    }
+
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        Parser parser = new PosixParser();
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        args = cl.getArgs();
+        if (args.length < 2) {
+            throw new CliParseException(getUsageStr());
+        }
+        return this;
+    }
+
+    @Override
+    public boolean exec() throws CliWrapperException, MalformedPathException {
+        String path = args[1];
+        WatcherType wtype = WatcherType.Any;
+        // if no matching option -c or -d or -a is specified, we remove
+        // the watches of the given node by choosing WatcherType.Any
+        if (cl.hasOption("c")) {
+            wtype = WatcherType.Children;
+        } else if (cl.hasOption("d")) {
+            wtype = WatcherType.Data;
+        } else if (cl.hasOption("a")) {
+            wtype = WatcherType.Any;
+        }
+        // whether to remove the watches locally
+        boolean local = cl.hasOption("l");
+
+        try {
+            zk.removeAllWatches(path, wtype, local);
+        } catch (IllegalArgumentException ex) {
+            throw new MalformedPathException(ex.getMessage());
+        } catch (KeeperException|InterruptedException ex) {
+            throw new CliWrapperException(ex);
+        }
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetAclCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetAclCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetAclCommand.java
new file mode 100644
index 0000000..9d1b460
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetAclCommand.java
@@ -0,0 +1,104 @@
+/**
+ * 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.cli;
+
+import java.util.List;
+import org.apache.commons.cli.*;
+import org.apache.zookeeper.AsyncCallback.StringCallback;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.ZKUtil;
+import org.apache.zookeeper.data.ACL;
+import org.apache.zookeeper.data.Stat;
+
+/**
+ * setAcl command for cli.
+ * Available options are s for printing znode's stats, v for set version of znode(s), R for
+ * recursive setting. User can combine v and R options together, but not s and R considering the
+ * number of znodes could be large.
+ */
+public class SetAclCommand extends CliCommand {
+
+    private static Options options = new Options();
+    private String[] args;
+    private CommandLine cl;
+
+    {
+        options.addOption("s", false, "stats");
+        options.addOption("v", true, "version");
+        options.addOption("R", false, "recursive");
+    }
+
+    public SetAclCommand() {
+        super("setAcl", "[-s] [-v version] [-R] path acl");
+    }
+
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        Parser parser = new PosixParser();
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        args = cl.getArgs();
+        if (args.length < 3) {
+            throw new CliParseException(getUsageStr());
+        }
+
+        return this;
+    }
+
+    @Override
+    public boolean exec() throws CliException {
+        String path = args[1];
+        String aclStr = args[2];
+        List<ACL> acl = AclParser.parse(aclStr);
+        int version;
+        if (cl.hasOption("v")) {
+            version = Integer.parseInt(cl.getOptionValue("v"));
+        } else {
+            version = -1;
+        }
+        try {
+            if (cl.hasOption("R")) {
+                ZKUtil.visitSubTreeDFS(zk, path, false, new StringCallback() {
+                    @Override
+                    public void processResult(int rc, String p, Object ctx, String name) {
+                        try {
+                            zk.setACL(p, acl, version);
+                        } catch (KeeperException | InterruptedException e) {
+                            out.print(e.getMessage());
+                        }
+                    }
+                });
+            } else {
+                Stat stat = zk.setACL(path, acl, version);
+                if (cl.hasOption("s")) {
+                    new StatPrinter(out).print(stat);
+                }
+            }
+        } catch (IllegalArgumentException ex) {
+            throw new MalformedPathException(ex.getMessage());
+        } catch (KeeperException|InterruptedException ex) {
+            throw new CliWrapperException(ex);
+        }
+
+        return false;
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetCommand.java
new file mode 100644
index 0000000..43ca2e1
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetCommand.java
@@ -0,0 +1,81 @@
+/**
+ * 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.cli;
+
+import org.apache.commons.cli.*;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.data.Stat;
+
+/**
+ * set command for cli
+ */
+public class SetCommand extends CliCommand {
+
+    private static Options options = new Options();
+    private String[] args;
+    private CommandLine cl;
+
+    {
+        options.addOption("s", false, "stats");
+        options.addOption("v", true, "version");
+    }
+
+    public SetCommand() {
+        super("set", "[-s] [-v version] path data");
+    }
+
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        Parser parser = new PosixParser();
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        args = cl.getArgs();
+        if (args.length < 3) {
+            throw new CliParseException(getUsageStr());
+        }
+
+        return this;
+    }
+
+    @Override
+    public boolean exec() throws CliException {
+        String path = args[1];
+        byte[] data = args[2].getBytes();
+        int version;
+        if (cl.hasOption("v")) {
+            version = Integer.parseInt(cl.getOptionValue("v"));
+        } else {
+            version = -1;
+        }
+
+        try {
+            Stat stat = zk.setData(path, data, version);
+            if (cl.hasOption("s")) {
+                new StatPrinter(out).print(stat);
+            }
+        } catch (IllegalArgumentException ex) {
+            throw new MalformedPathException(ex.getMessage());
+        } catch (KeeperException|InterruptedException ex) {
+            throw new CliWrapperException(ex);
+        }
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetQuotaCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetQuotaCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetQuotaCommand.java
new file mode 100644
index 0000000..7df5667
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SetQuotaCommand.java
@@ -0,0 +1,216 @@
+/**
+ * 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.cli;
+
+import java.io.IOException;
+import java.util.List;
+import org.apache.commons.cli.*;
+import org.apache.zookeeper.*;
+import org.apache.zookeeper.data.Stat;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * setQuota command for cli
+ */
+public class SetQuotaCommand extends CliCommand {
+
+    private static final Logger LOG = LoggerFactory.getLogger(SetQuotaCommand.class);
+    private Options options = new Options();
+    private String[] args;
+    private CommandLine cl;
+
+    public SetQuotaCommand() {
+        super("setquota", "-n|-b val path");
+        
+        OptionGroup og1 = new OptionGroup();
+        og1.addOption(new Option("b", true, "bytes quota"));
+        og1.addOption(new Option("n", true, "num quota"));
+        og1.setRequired(true);
+        options.addOptionGroup(og1);
+   }
+
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        Parser parser = new PosixParser();
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        args = cl.getArgs();
+        if (args.length < 2) {
+            throw new CliParseException(getUsageStr());
+        }
+
+        return this;
+    }
+
+    @Override
+    public boolean exec() throws CliException {
+        // get the args
+        String path = args[1];
+
+        if (cl.hasOption("b")) {
+            // we are setting the bytes quota
+            long bytes = Long.parseLong(cl.getOptionValue("b"));
+            try {
+                createQuota(zk, path, bytes, -1);
+            } catch (KeeperException|IOException|InterruptedException ex) {
+                throw new CliWrapperException(ex);
+            }
+        } else if (cl.hasOption("n")) {
+            // we are setting the num quota
+            int numNodes = Integer.parseInt(cl.getOptionValue("n"));
+            try {
+                createQuota(zk, path, -1L, numNodes);
+            } catch (KeeperException|IOException|InterruptedException ex) {
+                throw new CliWrapperException(ex);
+            }
+        } else {
+            throw new MalformedCommandException(getUsageStr());
+        }
+
+        return false;
+    }
+
+    public static boolean createQuota(ZooKeeper zk, String path,
+            long bytes, int numNodes)
+            throws KeeperException, IOException, InterruptedException, MalformedPathException {
+        // check if the path exists. We cannot create
+        // quota for a path that already exists in zookeeper
+        // for now.
+        Stat initStat;
+        try {
+            initStat = zk.exists(path, false);
+        } catch (IllegalArgumentException ex) {
+            throw new MalformedPathException(ex.getMessage());
+        }
+        if (initStat == null) {
+            throw new IllegalArgumentException(path + " does not exist.");
+        }
+        // now check if their is already existing
+        // parent or child that has quota
+
+        String quotaPath = Quotas.quotaZookeeper;
+        // check for more than 2 children --
+        // if zookeeper_stats and zookeeper_qutoas
+        // are not the children then this path
+        // is an ancestor of some path that
+        // already has quota
+        String realPath = Quotas.quotaZookeeper + path;
+        try {
+            List<String> children = zk.getChildren(realPath, false);
+            for (String child : children) {
+                if (!child.startsWith("zookeeper_")) {
+                    throw new IllegalArgumentException(path + " has child "
+                            + child + " which has a quota");
+                }
+            }
+        } catch (KeeperException.NoNodeException ne) {
+            // this is fine
+        }
+
+        //check for any parent that has been quota
+        checkIfParentQuota(zk, path);
+
+        // this is valid node for quota
+        // start creating all the parents
+        if (zk.exists(quotaPath, false) == null) {
+            try {
+                zk.create(Quotas.procZookeeper, null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
+                        CreateMode.PERSISTENT);
+                zk.create(Quotas.quotaZookeeper, null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
+                        CreateMode.PERSISTENT);
+            } catch (KeeperException.NodeExistsException ne) {
+                // do nothing
+            }
+        }
+
+        // now create the direct children
+        // and the stat and quota nodes
+        String[] splits = path.split("/");
+        StringBuilder sb = new StringBuilder();
+        sb.append(quotaPath);
+        for (int i = 1; i < splits.length; i++) {
+            sb.append("/" + splits[i]);
+            quotaPath = sb.toString();
+            try {
+                zk.create(quotaPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
+                        CreateMode.PERSISTENT);
+            } catch (KeeperException.NodeExistsException ne) {
+                //do nothing
+            }
+        }
+        String statPath = quotaPath + "/" + Quotas.statNode;
+        quotaPath = quotaPath + "/" + Quotas.limitNode;
+        StatsTrack strack = new StatsTrack(null);
+        strack.setBytes(bytes);
+        strack.setCount(numNodes);
+        try {
+            zk.create(quotaPath, strack.toString().getBytes(),
+                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+            StatsTrack stats = new StatsTrack(null);
+            stats.setBytes(0L);
+            stats.setCount(0);
+            zk.create(statPath, stats.toString().getBytes(),
+                    ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+        } catch (KeeperException.NodeExistsException ne) {
+            byte[] data = zk.getData(quotaPath, false, new Stat());
+            StatsTrack strackC = new StatsTrack(new String(data));
+            if (bytes != -1L) {
+                strackC.setBytes(bytes);
+            }
+            if (numNodes != -1) {
+                strackC.setCount(numNodes);
+            }
+            zk.setData(quotaPath, strackC.toString().getBytes(), -1);
+        }
+        return true;
+    }
+
+    private static void checkIfParentQuota(ZooKeeper zk, String path)
+            throws InterruptedException, KeeperException {
+        final String[] splits = path.split("/");
+        String quotaPath = Quotas.quotaZookeeper;
+        for (String str : splits) {
+            if (str.length() == 0) {
+                // this should only be for the beginning of the path
+                // i.e. "/..." - split(path)[0] is empty string before first '/'
+                continue;
+            }
+            quotaPath += "/" + str;
+            List<String> children = null;
+            try {
+                children = zk.getChildren(quotaPath, false);
+            } catch (KeeperException.NoNodeException ne) {
+                LOG.debug("child removed during quota check", ne);
+                return;
+            }
+            if (children.size() == 0) {
+                return;
+            }
+            for (String child : children) {
+                if (Quotas.limitNode.equals(child)) {
+                    throw new IllegalArgumentException(path + " has a parent "
+                            + quotaPath + " which has a quota");
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/StatCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/StatCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/StatCommand.java
new file mode 100644
index 0000000..33d8e87
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/StatCommand.java
@@ -0,0 +1,95 @@
+/**
+ * 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.cli;
+
+import org.apache.commons.cli.*;
+import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.data.Stat;
+
+/**
+ * stat command for cli
+ */
+public class StatCommand extends CliCommand {
+
+    private static final Options options = new Options();
+    private String[] args;
+    private CommandLine cl;
+
+    static {
+        options.addOption("w", false, "watch");
+    }
+    
+    public StatCommand() {
+        super("stat", "[-w] path");
+    }
+
+    
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        Parser parser = new PosixParser();
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        args = cl.getArgs();
+        if(args.length < 2) {
+            throw new CliParseException(getUsageStr());
+        }    
+        
+        retainCompatibility(cmdArgs);
+
+        return this;
+    }
+
+    private void retainCompatibility(String[] cmdArgs) throws CliParseException {
+        // stat path [watch]
+        if (args.length > 2) {
+            // rewrite to option
+            cmdArgs[2] = "-w";
+            err.println("'stat path [watch]' has been deprecated. "
+                    + "Please use 'stat [-w] path' instead.");
+            Parser parser = new PosixParser();
+            try {
+                cl = parser.parse(options, cmdArgs);
+            } catch (ParseException ex) {
+                throw new CliParseException(ex);
+            }
+            args = cl.getArgs();
+        }
+    }
+    
+    @Override
+    public boolean exec() throws CliException {
+        String path = args[1];
+        boolean watch = cl.hasOption("w");
+        Stat stat;
+        try {
+            stat = zk.exists(path, watch);
+        } catch (IllegalArgumentException ex) {
+            throw new MalformedPathException(ex.getMessage());
+        } catch (KeeperException|InterruptedException ex) {
+            throw new CliWrapperException(ex);
+        }
+        if (stat == null) {
+            throw new CliWrapperException(new KeeperException.NoNodeException(path));
+        }
+        new StatPrinter(out).print(stat);
+        return watch;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/StatPrinter.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/StatPrinter.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/StatPrinter.java
new file mode 100644
index 0000000..c803b20
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/StatPrinter.java
@@ -0,0 +1,49 @@
+/**
+ * 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.cli;
+
+import java.io.PrintStream;
+import java.util.Date;
+import org.apache.zookeeper.data.Stat;
+
+/**
+ * utility for printing stat values s
+ */
+public class StatPrinter {
+
+    protected PrintStream out;
+
+    public StatPrinter(PrintStream out) {
+        this.out = out;
+    }
+
+    public void print(Stat stat) {
+        out.println("cZxid = 0x" + Long.toHexString(stat.getCzxid()));
+        out.println("ctime = " + new Date(stat.getCtime()).toString());
+        out.println("mZxid = 0x" + Long.toHexString(stat.getMzxid()));
+        out.println("mtime = " + new Date(stat.getMtime()).toString());
+        out.println("pZxid = 0x" + Long.toHexString(stat.getPzxid()));
+        out.println("cversion = " + stat.getCversion());
+        out.println("dataVersion = " + stat.getVersion());
+        out.println("aclVersion = " + stat.getAversion());
+        out.println("ephemeralOwner = 0x"
+                + Long.toHexString(stat.getEphemeralOwner()));
+        out.println("dataLength = " + stat.getDataLength());
+        out.println("numChildren = " + stat.getNumChildren());
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SyncCommand.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SyncCommand.java b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SyncCommand.java
new file mode 100644
index 0000000..74affd2
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/cli/SyncCommand.java
@@ -0,0 +1,74 @@
+/**
+ * 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.cli;
+
+import java.io.IOException;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+import org.apache.commons.cli.Parser;
+import org.apache.commons.cli.PosixParser;
+import org.apache.zookeeper.AsyncCallback;
+import org.apache.zookeeper.KeeperException;
+
+/**
+ * sync command for cli
+ */
+public class SyncCommand extends CliCommand {
+
+    private static Options options = new Options();
+    private String[] args;
+
+    public SyncCommand() {
+        super("sync", "path");
+    }
+
+    @Override
+    public CliCommand parse(String[] cmdArgs) throws CliParseException {
+        Parser parser = new PosixParser();
+        CommandLine cl;
+        try {
+            cl = parser.parse(options, cmdArgs);
+        } catch (ParseException ex) {
+            throw new CliParseException(ex);
+        }
+        args = cl.getArgs();
+        if (args.length < 2) {
+            throw new CliParseException(getUsageStr());
+        }
+
+        return this;
+    }
+
+    @Override
+    public boolean exec() throws CliException {
+        String path = args[1];
+        try {
+            zk.sync(path, new AsyncCallback.VoidCallback() {
+
+                public void processResult(int rc, String path, Object ctx) {
+                    out.println("Sync returned " + rc);
+                }
+            }, null);
+        } catch (IllegalArgumentException ex) {
+            throw new MalformedPathException(ex.getMessage());
+        }
+
+
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/client/ConnectStringParser.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/client/ConnectStringParser.java b/zookeeper-server/src/main/java/org/apache/zookeeper/client/ConnectStringParser.java
new file mode 100644
index 0000000..1bdff96
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/client/ConnectStringParser.java
@@ -0,0 +1,95 @@
+/**
+ * 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.client;
+
+import org.apache.zookeeper.common.PathUtils;
+import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException;
+import org.apache.zookeeper.server.util.ConfigUtils;
+
+import java.net.InetSocketAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.apache.zookeeper.common.StringUtils.split;
+
+/**
+ * A parser for ZooKeeper Client connect strings.
+ * 
+ * This class is not meant to be seen or used outside of ZooKeeper itself.
+ * 
+ * The chrootPath member should be replaced by a Path object in issue
+ * ZOOKEEPER-849.
+ * 
+ * @see org.apache.zookeeper.ZooKeeper
+ */
+public final class ConnectStringParser {
+    private static final int DEFAULT_PORT = 2181;
+
+    private final String chrootPath;
+
+    private final ArrayList<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>();
+
+    /**
+     * Parse host and port by spliting client connectString
+     * with support for IPv6 literals
+     * @throws IllegalArgumentException
+     *             for an invalid chroot path.
+     */
+    public ConnectStringParser(String connectString) {
+        // parse out chroot, if any
+        int off = connectString.indexOf('/');
+        if (off >= 0) {
+            String chrootPath = connectString.substring(off);
+            // ignore "/" chroot spec, same as null
+            if (chrootPath.length() == 1) {
+                this.chrootPath = null;
+            } else {
+                PathUtils.validatePath(chrootPath);
+                this.chrootPath = chrootPath;
+            }
+            connectString = connectString.substring(0, off);
+        } else {
+            this.chrootPath = null;
+        }
+
+        List<String> hostsList = split(connectString,",");
+        for (String host : hostsList) {
+            int port = DEFAULT_PORT;
+            try {
+                String[] hostAndPort = ConfigUtils.getHostAndPort(host);
+                host = hostAndPort[0];
+                if (hostAndPort.length == 2) {
+                    port = Integer.parseInt(hostAndPort[1]);
+                }
+            } catch (ConfigException e) {
+                e.printStackTrace();
+            }
+		    
+            serverAddresses.add(InetSocketAddress.createUnresolved(host, port));
+        }
+    }
+
+    public String getChrootPath() {
+        return chrootPath;
+    }
+
+    public ArrayList<InetSocketAddress> getServerAddresses() {
+        return serverAddresses;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/client/FourLetterWordMain.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/client/FourLetterWordMain.java b/zookeeper-server/src/main/java/org/apache/zookeeper/client/FourLetterWordMain.java
new file mode 100644
index 0000000..41f5e9d
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/client/FourLetterWordMain.java
@@ -0,0 +1,146 @@
+/**
+ * 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.client;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+import org.apache.zookeeper.common.ClientX509Util;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.zookeeper.common.X509Exception.SSLContextException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@InterfaceAudience.Public
+public class FourLetterWordMain {
+    //in milliseconds, socket should connect/read within this period otherwise SocketTimeoutException
+    private static final int DEFAULT_SOCKET_TIMEOUT = 5000;
+    protected static final Logger LOG = LoggerFactory.getLogger(FourLetterWordMain.class);
+    /**
+     * Send the 4letterword
+     * @param host the destination host
+     * @param port the destination port
+     * @param cmd the 4letterword
+     * @return server response
+     * @throws java.io.IOException
+     * @throws SSLContextException
+     */
+    public static String send4LetterWord(String host, int port, String cmd)
+            throws IOException, SSLContextException {
+        return send4LetterWord(host, port, cmd, false, DEFAULT_SOCKET_TIMEOUT);
+    }
+
+    /**
+     * Send the 4letterword
+     * @param host the destination host
+     * @param port the destination port
+     * @param cmd the 4letterword
+     * @param secure whether to use SSL
+     * @return server response
+     * @throws java.io.IOException
+     * @throws SSLContextException
+     */
+    public static String send4LetterWord(String host, int port, String cmd, boolean secure)
+            throws IOException, SSLContextException {
+        return send4LetterWord(host, port, cmd, secure, DEFAULT_SOCKET_TIMEOUT);
+    }
+
+    /**
+     * Send the 4letterword
+     * @param host the destination host
+     * @param port the destination port
+     * @param cmd the 4letterword
+     * @param secure whether to use SSL
+     * @param timeout in milliseconds, maximum time to wait while connecting/reading data
+     * @return server response
+     * @throws java.io.IOException
+     * @throws SSLContextException
+     */
+    public static String send4LetterWord(String host, int port, String cmd, boolean secure, int timeout)
+            throws IOException, SSLContextException {
+        LOG.info("connecting to {} {}", host, port);
+        Socket sock;
+        InetSocketAddress hostaddress= host != null ? new InetSocketAddress(host, port) :
+            new InetSocketAddress(InetAddress.getByName(null), port);
+        if (secure) {
+            LOG.info("using secure socket");
+            SSLContext sslContext = new ClientX509Util().getDefaultSSLContext();
+            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
+            SSLSocket sslSock = (SSLSocket) socketFactory.createSocket();
+            sslSock.connect(hostaddress, timeout);
+            sslSock.startHandshake();
+            sock = sslSock;
+        } else {
+            sock = new Socket();
+            sock.connect(hostaddress, timeout);
+        }
+        sock.setSoTimeout(timeout);
+        BufferedReader reader = null;
+        try {
+            OutputStream outstream = sock.getOutputStream();
+            outstream.write(cmd.getBytes());
+            outstream.flush();
+
+            // this replicates NC - close the output stream before reading
+            if (!secure) {
+                // SSL prohibits unilateral half-close
+                sock.shutdownOutput();
+            }
+
+            reader =
+                    new BufferedReader(
+                            new InputStreamReader(sock.getInputStream()));
+            StringBuilder sb = new StringBuilder();
+            String line;
+            while((line = reader.readLine()) != null) {
+                sb.append(line + "\n");
+            }
+            return sb.toString();
+        } catch (SocketTimeoutException e) {
+            throw new IOException("Exception while executing four letter word: " + cmd, e);
+        } finally {
+            sock.close();
+            if (reader != null) {
+                reader.close();
+            }
+        }
+    }
+    
+    public static void main(String[] args)
+            throws IOException, SSLContextException
+    {
+        if (args.length == 3) {
+            System.out.println(send4LetterWord(args[0], Integer.parseInt(args[1]), args[2]));
+        } else if (args.length == 4) {
+            System.out.println(send4LetterWord(args[0], Integer.parseInt(args[1]), args[2], Boolean.parseBoolean(args[3])));
+        } else {
+            System.out.println("Usage: FourLetterWordMain <host> <port> <cmd> <secure(optional)>");
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/client/HostProvider.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/client/HostProvider.java b/zookeeper-server/src/main/java/org/apache/zookeeper/client/HostProvider.java
new file mode 100644
index 0000000..caeedcc
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/client/HostProvider.java
@@ -0,0 +1,76 @@
+/**
+ * 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.client;
+
+import org.apache.yetus.audience.InterfaceAudience;
+
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.Collection;
+
+/**
+ * A set of hosts a ZooKeeper client should connect to.
+ * 
+ * Classes implementing this interface must guarantee the following:
+ * 
+ * * Every call to next() returns an InetSocketAddress. So the iterator never
+ * ends.
+ * 
+ * * The size() of a HostProvider may never be zero.
+ * 
+ * A HostProvider must return resolved InetSocketAddress instances on next() if the next address is resolvable.
+ * In that case, it's up to the HostProvider, whether it returns the next resolvable address in the list or return
+ * the next one as UnResolved.
+ * 
+ * Different HostProvider could be imagined:
+ * 
+ * * A HostProvider that loads the list of Hosts from an URL or from DNS 
+ * * A HostProvider that re-resolves the InetSocketAddress after a timeout. 
+ * * A HostProvider that prefers nearby hosts.
+ */
+@InterfaceAudience.Public
+public interface HostProvider {
+    public int size();
+
+    /**
+     * The next host to try to connect to.
+     * 
+     * For a spinDelay of 0 there should be no wait.
+     * 
+     * @param spinDelay
+     *            Milliseconds to wait if all hosts have been tried once.
+     */
+    public InetSocketAddress next(long spinDelay);
+
+    /**
+     * Notify the HostProvider of a successful connection.
+     * 
+     * The HostProvider may use this notification to reset it's inner state.
+     */
+    public void onConnected();
+
+    /**
+     * Update the list of servers. This returns true if changing connections is necessary for load-balancing, false otherwise.
+     * @param serverAddresses new host list
+     * @param currentHost the host to which this client is currently connected
+     * @return true if changing connections is necessary for load-balancing, false otherwise  
+     */
+    boolean updateServerList(Collection<InetSocketAddress> serverAddresses,
+        InetSocketAddress currentHost);
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/cb9f303b/zookeeper-server/src/main/java/org/apache/zookeeper/client/StaticHostProvider.java
----------------------------------------------------------------------
diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/client/StaticHostProvider.java b/zookeeper-server/src/main/java/org/apache/zookeeper/client/StaticHostProvider.java
new file mode 100644
index 0000000..0602103
--- /dev/null
+++ b/zookeeper-server/src/main/java/org/apache/zookeeper/client/StaticHostProvider.java
@@ -0,0 +1,383 @@
+/**
+ * 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.client;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.yetus.audience.InterfaceAudience;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Most simple HostProvider, resolves on every next() call.
+ *
+ * Please be aware that although this class doesn't do any DNS caching, there're multiple levels of caching already
+ * present across the stack like in JVM, OS level, hardware, etc. The best we could do here is to get the most recent
+ * address from the underlying system which is considered up-to-date.
+ *
+ */
+@InterfaceAudience.Public
+public final class StaticHostProvider implements HostProvider {
+    public interface Resolver {
+        InetAddress[] getAllByName(String name) throws UnknownHostException;
+    }
+
+    private static final Logger LOG = LoggerFactory
+            .getLogger(StaticHostProvider.class);
+
+    private List<InetSocketAddress> serverAddresses = new ArrayList<InetSocketAddress>(
+            5);
+
+    private Random sourceOfRandomness;
+    private int lastIndex = -1;
+
+    private int currentIndex = -1;
+
+    /**
+     * The following fields are used to migrate clients during reconfiguration
+     */
+    private boolean reconfigMode = false;
+
+    private final List<InetSocketAddress> oldServers = new ArrayList<InetSocketAddress>(
+            5);
+
+    private final List<InetSocketAddress> newServers = new ArrayList<InetSocketAddress>(
+            5);
+
+    private int currentIndexOld = -1;
+    private int currentIndexNew = -1;
+
+    private float pOld, pNew;
+
+    private Resolver resolver;
+
+    /**
+     * Constructs a SimpleHostSet.
+     * 
+     * @param serverAddresses
+     *            possibly unresolved ZooKeeper server addresses
+     * @throws IllegalArgumentException
+     *             if serverAddresses is empty or resolves to an empty list
+     */
+    public StaticHostProvider(Collection<InetSocketAddress> serverAddresses) {
+        init(serverAddresses,
+                System.currentTimeMillis() ^ this.hashCode(),
+                new Resolver() {
+            @Override
+            public InetAddress[] getAllByName(String name) throws UnknownHostException {
+                return InetAddress.getAllByName(name);
+            }
+        });
+    }
+
+    /**
+     * Constructs a SimpleHostSet.
+     *
+     * Introduced for testing purposes. getAllByName() is a static method of InetAddress, therefore cannot be easily mocked.
+     * By abstraction of Resolver interface we can easily inject a mocked implementation in tests.
+     *
+     * @param serverAddresses
+     *              possibly unresolved ZooKeeper server addresses
+     * @param resolver
+     *              custom resolver implementation
+     */
+    public StaticHostProvider(Collection<InetSocketAddress> serverAddresses, Resolver resolver) {
+        init(serverAddresses, System.currentTimeMillis() ^ this.hashCode(), resolver);
+    }
+
+    /**
+     * Constructs a SimpleHostSet. This constructor is used from StaticHostProviderTest to produce deterministic test results
+     * by initializing sourceOfRandomness with the same seed
+     * 
+     * @param serverAddresses
+     *            possibly unresolved ZooKeeper server addresses
+     * @param randomnessSeed a seed used to initialize sourceOfRandomnes
+     * @throws IllegalArgumentException
+     *             if serverAddresses is empty or resolves to an empty list
+     */
+    public StaticHostProvider(Collection<InetSocketAddress> serverAddresses,
+        long randomnessSeed) {
+        init(serverAddresses, randomnessSeed, new Resolver() {
+            @Override
+            public InetAddress[] getAllByName(String name) throws UnknownHostException {
+                return InetAddress.getAllByName(name);
+            }
+        });
+    }
+
+    private void init(Collection<InetSocketAddress> serverAddresses, long randomnessSeed, Resolver resolver) {
+        this.sourceOfRandomness = new Random(randomnessSeed);
+        this.resolver = resolver;
+        if (serverAddresses.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "A HostProvider may not be empty!");
+        }
+        this.serverAddresses = shuffle(serverAddresses);
+        currentIndex = -1;
+        lastIndex = -1;
+    }
+
+    private InetSocketAddress resolve(InetSocketAddress address) {
+        try {
+            String curHostString = address.getHostString();
+            List<InetAddress> resolvedAddresses = new ArrayList<>(Arrays.asList(this.resolver.getAllByName(curHostString)));
+            if (resolvedAddresses.isEmpty()) {
+                return address;
+            }
+            Collections.shuffle(resolvedAddresses);
+            return new InetSocketAddress(resolvedAddresses.get(0), address.getPort());
+        } catch (UnknownHostException e) {
+            LOG.error("Unable to resolve address: {}", address.toString(), e);
+            return address;
+        }
+    }
+
+    private List<InetSocketAddress> shuffle(Collection<InetSocketAddress> serverAddresses) {
+        List<InetSocketAddress> tmpList = new ArrayList<>(serverAddresses.size());
+        tmpList.addAll(serverAddresses);
+        Collections.shuffle(tmpList, sourceOfRandomness);
+        return tmpList;
+    }
+
+    /**
+     * Update the list of servers. This returns true if changing connections is necessary for load-balancing, false
+	 * otherwise. Changing connections is necessary if one of the following holds: 
+     * a) the host to which this client is currently connected is not in serverAddresses.
+     *    Otherwise (if currentHost is in the new list serverAddresses):   
+     * b) the number of servers in the cluster is increasing - in this case the load on currentHost should decrease,
+     *    which means that SOME of the clients connected to it will migrate to the new servers. The decision whether
+     *    this client migrates or not (i.e., whether true or false is returned) is probabilistic so that the expected 
+     *    number of clients connected to each server is the same.
+     *    
+     * If true is returned, the function sets pOld and pNew that correspond to the probability to migrate to ones of the
+     * new servers in serverAddresses or one of the old servers (migrating to one of the old servers is done only
+     * if our client's currentHost is not in serverAddresses). See nextHostInReconfigMode for the selection logic.
+     *
+     * See <a href="https://issues.apache.org/jira/browse/ZOOKEEPER-1355">ZOOKEEPER-1355</a>
+     * for the protocol and its evaluation, and StaticHostProviderTest for the tests that illustrate how load balancing
+     * works with this policy.
+     *
+     * @param serverAddresses new host list
+     * @param currentHost the host to which this client is currently connected
+     * @return true if changing connections is necessary for load-balancing, false otherwise  
+     */
+    @Override
+    public synchronized boolean updateServerList(
+            Collection<InetSocketAddress> serverAddresses,
+            InetSocketAddress currentHost) {
+        List<InetSocketAddress> shuffledList = shuffle(serverAddresses);
+        if (shuffledList.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "A HostProvider may not be empty!");
+        }
+        // Check if client's current server is in the new list of servers
+        boolean myServerInNewConfig = false;
+
+        InetSocketAddress myServer = currentHost;
+
+        // choose "current" server according to the client rebalancing algorithm
+        if (reconfigMode) {
+            myServer = next(0);
+        }
+
+        // if the client is not currently connected to any server
+        if (myServer == null) {
+            // reconfigMode = false (next shouldn't return null).
+            if (lastIndex >= 0) {
+                // take the last server to which we were connected
+                myServer = this.serverAddresses.get(lastIndex);
+            } else {
+                // take the first server on the list
+                myServer = this.serverAddresses.get(0);
+            }
+        }
+
+        for (InetSocketAddress addr : shuffledList) {
+            if (addr.getPort() == myServer.getPort()
+                    && ((addr.getAddress() != null
+                            && myServer.getAddress() != null && addr
+                            .getAddress().equals(myServer.getAddress())) || addr
+                            .getHostString().equals(myServer.getHostString()))) {
+                myServerInNewConfig = true;
+                break;
+            }
+        }
+
+        reconfigMode = true;
+
+        newServers.clear();
+        oldServers.clear();
+        // Divide the new servers into oldServers that were in the previous list
+        // and newServers that were not in the previous list
+        for (InetSocketAddress address : shuffledList) {
+            if (this.serverAddresses.contains(address)) {
+                oldServers.add(address);
+            } else {
+                newServers.add(address);
+            }
+        }
+
+        int numOld = oldServers.size();
+        int numNew = newServers.size();
+
+        // number of servers increased
+        if (numOld + numNew > this.serverAddresses.size()) {
+            if (myServerInNewConfig) {
+                // my server is in new config, but load should be decreased.
+                // Need to decide if this client
+                // is moving to one of the new servers
+                if (sourceOfRandomness.nextFloat() <= (1 - ((float) this.serverAddresses
+                        .size()) / (numOld + numNew))) {
+                    pNew = 1;
+                    pOld = 0;
+                } else {
+                    // do nothing special - stay with the current server
+                    reconfigMode = false;
+                }
+            } else {
+                // my server is not in new config, and load on old servers must
+                // be decreased, so connect to
+                // one of the new servers
+                pNew = 1;
+                pOld = 0;
+            }
+        } else { // number of servers stayed the same or decreased
+            if (myServerInNewConfig) {
+                // my server is in new config, and load should be increased, so
+                // stay with this server and do nothing special
+                reconfigMode = false;
+            } else {
+                pOld = ((float) (numOld * (this.serverAddresses.size() - (numOld + numNew))))
+                        / ((numOld + numNew) * (this.serverAddresses.size() - numOld));
+                pNew = 1 - pOld;
+            }
+        }
+
+        if (!reconfigMode) {
+            currentIndex = shuffledList.indexOf(getServerAtCurrentIndex());
+        } else {
+            currentIndex = -1;
+        }
+        this.serverAddresses = shuffledList;
+        currentIndexOld = -1;
+        currentIndexNew = -1;
+        lastIndex = currentIndex;
+        return reconfigMode;
+    }
+
+    public synchronized InetSocketAddress getServerAtIndex(int i) {
+    	if (i < 0 || i >= serverAddresses.size()) return null;
+    	return serverAddresses.get(i);
+    }
+    
+    public synchronized InetSocketAddress getServerAtCurrentIndex() {
+    	return getServerAtIndex(currentIndex);
+    }
+
+    public synchronized int size() {
+        return serverAddresses.size();
+    }
+
+    /**
+     * Get the next server to connect to, when in "reconfigMode", which means that 
+     * you've just updated the server list, and now trying to find some server to connect to. 
+     * Once onConnected() is called, reconfigMode is set to false. Similarly, if we tried to connect
+     * to all servers in new config and failed, reconfigMode is set to false.
+     * 
+     * While in reconfigMode, we should connect to a server in newServers with probability pNew and to servers in
+     * oldServers with probability pOld (which is just 1-pNew). If we tried out all servers in either oldServers
+     * or newServers we continue to try servers from the other set, regardless of pNew or pOld. If we tried all servers
+     * we give up and go back to the normal round robin mode
+     *
+     * When called, this should be protected by synchronized(this)
+     */
+    private InetSocketAddress nextHostInReconfigMode() {
+        boolean takeNew = (sourceOfRandomness.nextFloat() <= pNew);
+
+        // take one of the new servers if it is possible (there are still such
+        // servers we didn't try),
+        // and either the probability tells us to connect to one of the new
+        // servers or if we already
+        // tried all the old servers
+        if (((currentIndexNew + 1) < newServers.size())
+                && (takeNew || (currentIndexOld + 1) >= oldServers.size())) {
+            ++currentIndexNew;
+            return newServers.get(currentIndexNew);
+        }
+
+        // start taking old servers
+        if ((currentIndexOld + 1) < oldServers.size()) {
+            ++currentIndexOld;
+            return oldServers.get(currentIndexOld);
+        }
+
+        return null;
+    }
+
+    public InetSocketAddress next(long spinDelay) {
+        boolean needToSleep = false;
+        InetSocketAddress addr;
+
+        synchronized(this) {
+            if (reconfigMode) {
+                addr = nextHostInReconfigMode();
+                if (addr != null) {
+                	currentIndex = serverAddresses.indexOf(addr);
+                	return resolve(addr);
+                }
+                //tried all servers and couldn't connect
+                reconfigMode = false;
+                needToSleep = (spinDelay > 0);
+            }        
+            ++currentIndex;
+            if (currentIndex == serverAddresses.size()) {
+                currentIndex = 0;
+            }            
+            addr = serverAddresses.get(currentIndex);
+            needToSleep = needToSleep || (currentIndex == lastIndex && spinDelay > 0);
+            if (lastIndex == -1) { 
+                // We don't want to sleep on the first ever connect attempt.
+                lastIndex = 0;
+            }
+        }
+        if (needToSleep) {
+            try {
+                Thread.sleep(spinDelay);
+            } catch (InterruptedException e) {
+                LOG.warn("Unexpected exception", e);
+            }
+        }
+
+        return resolve(addr);
+    }
+
+    public synchronized void onConnected() {
+        lastIndex = currentIndex;
+        reconfigMode = false;
+    }
+
+}


Mime
View raw message