http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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/43d71c2e/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..085e44d --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/client/ConnectStringParser.java @@ -0,0 +1,90 @@ +/** + * 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 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>(); + + /** + * + * @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; + int pidx = host.lastIndexOf(':'); + if (pidx >= 0) { + // otherwise : is at the end of the string, ignore + if (pidx < host.length() - 1) { + port = Integer.parseInt(host.substring(pidx + 1)); + } + host = host.substring(0, pidx); + } + 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/43d71c2e/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/43d71c2e/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/43d71c2e/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; + } + +} http://git-wip-us.apache.org/repos/asf/zookeeper/blob/43d71c2e/zookeeper-server/src/main/java/org/apache/zookeeper/client/ZKClientConfig.java ---------------------------------------------------------------------- diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/client/ZKClientConfig.java b/zookeeper-server/src/main/java/org/apache/zookeeper/client/ZKClientConfig.java new file mode 100644 index 0000000..097f2f0 --- /dev/null +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/client/ZKClientConfig.java @@ -0,0 +1,140 @@ +/** + * 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.File; + +import org.apache.yetus.audience.InterfaceAudience; +import org.apache.zookeeper.ZooKeeper; +import org.apache.zookeeper.common.ZKConfig; +import org.apache.zookeeper.server.quorum.QuorumPeerConfig.ConfigException; + +/** + * Handles client specific properties + * @since 3.5.2 + */ +@InterfaceAudience.Public +public class ZKClientConfig extends ZKConfig { + public static final String ZK_SASL_CLIENT_USERNAME = "zookeeper.sasl.client.username"; + public static final String ZK_SASL_CLIENT_USERNAME_DEFAULT = "zookeeper"; + @SuppressWarnings("deprecation") + public static final String LOGIN_CONTEXT_NAME_KEY = ZooKeeperSaslClient.LOGIN_CONTEXT_NAME_KEY;; + public static final String LOGIN_CONTEXT_NAME_KEY_DEFAULT = "Client"; + @SuppressWarnings("deprecation") + public static final String ENABLE_CLIENT_SASL_KEY = ZooKeeperSaslClient.ENABLE_CLIENT_SASL_KEY; + @SuppressWarnings("deprecation") + public static final String ENABLE_CLIENT_SASL_DEFAULT = ZooKeeperSaslClient.ENABLE_CLIENT_SASL_DEFAULT; + public static final String ZOOKEEPER_SERVER_REALM = "zookeeper.server.realm"; + /** + * This controls whether automatic watch resetting is enabled. Clients + * automatically reset watches during session reconnect, this option allows + * the client to turn off this behavior by setting the property + * "zookeeper.disableAutoWatchReset" to "true" + */ + public static final String DISABLE_AUTO_WATCH_RESET = "zookeeper.disableAutoWatchReset"; + @SuppressWarnings("deprecation") + public static final String ZOOKEEPER_CLIENT_CNXN_SOCKET = ZooKeeper.ZOOKEEPER_CLIENT_CNXN_SOCKET; + /** + * Setting this to "true" will enable encrypted client-server communication. + */ + @SuppressWarnings("deprecation") + public static final String SECURE_CLIENT = ZooKeeper.SECURE_CLIENT; + public static final int CLIENT_MAX_PACKET_LENGTH_DEFAULT = 4096 * 1024; /* 4 MB */ + public static final String ZOOKEEPER_REQUEST_TIMEOUT = "zookeeper.request.timeout"; + /** + * Feature is disabled by default. + */ + public static final long ZOOKEEPER_REQUEST_TIMEOUT_DEFAULT = 0; + + public ZKClientConfig() { + super(); + initFromJavaSystemProperties(); + } + + public ZKClientConfig(File configFile) throws ConfigException { + super(configFile); + } + + public ZKClientConfig(String configPath) throws ConfigException { + super(configPath); + } + + /** + * Initialize all the ZooKeeper client properties which are configurable as + * java system property + */ + private void initFromJavaSystemProperties() { + setProperty(ZOOKEEPER_REQUEST_TIMEOUT, + System.getProperty(ZOOKEEPER_REQUEST_TIMEOUT)); + } + + @Override + protected void handleBackwardCompatibility() { + /** + * backward compatibility for properties which are common to both client + * and server + */ + super.handleBackwardCompatibility(); + + /** + * backward compatibility for client specific properties + */ + setProperty(ZK_SASL_CLIENT_USERNAME, System.getProperty(ZK_SASL_CLIENT_USERNAME)); + setProperty(LOGIN_CONTEXT_NAME_KEY, System.getProperty(LOGIN_CONTEXT_NAME_KEY)); + setProperty(ENABLE_CLIENT_SASL_KEY, System.getProperty(ENABLE_CLIENT_SASL_KEY)); + setProperty(ZOOKEEPER_SERVER_REALM, System.getProperty(ZOOKEEPER_SERVER_REALM)); + setProperty(DISABLE_AUTO_WATCH_RESET, System.getProperty(DISABLE_AUTO_WATCH_RESET)); + setProperty(ZOOKEEPER_CLIENT_CNXN_SOCKET, System.getProperty(ZOOKEEPER_CLIENT_CNXN_SOCKET)); + setProperty(SECURE_CLIENT, System.getProperty(SECURE_CLIENT)); + } + + /** + * Returns true if the SASL client is enabled. By default, the client is + * enabled but can be disabled by setting the system property + * <code>zookeeper.sasl.client</code> to <code>false</code>. See + * ZOOKEEPER-1657 for more information. + * + * @return true if the SASL client is enabled. + */ + public boolean isSaslClientEnabled() { + return Boolean.valueOf(getProperty(ENABLE_CLIENT_SASL_KEY, ENABLE_CLIENT_SASL_DEFAULT)); + } + + /** + * Get the value of the <code>key</code> property as an <code>long</code>. + * If property is not set, the provided <code>defaultValue</code> is + * returned + * + * @param key + * property key. + * @param defaultValue + * default value. + * @throws NumberFormatException + * when the value is invalid + * @return return property value as an <code>long</code>, or + * <code>defaultValue</code> + */ + public long getLong(String key, long defaultValue) { + String value = getProperty(key); + if (value != null) { + return Long.parseLong(value.trim()); + } + return defaultValue; + } +}