| /** |
| * 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; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.NoSuchElementException; |
| |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.apache.zookeeper.ZooDefs.Ids; |
| import org.apache.zookeeper.data.Stat; |
| |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.commons.cli.ParseException; |
| import org.apache.zookeeper.cli.AddAuthCommand; |
| import org.apache.zookeeper.cli.CliCommand; |
| import org.apache.zookeeper.cli.CloseCommand; |
| import org.apache.zookeeper.cli.CreateCommand; |
| import org.apache.zookeeper.cli.DelQuotaCommand; |
| import org.apache.zookeeper.cli.DeleteAllCommand; |
| import org.apache.zookeeper.cli.DeleteCommand; |
| import org.apache.zookeeper.cli.RemoveWatchesCommand; |
| import org.apache.zookeeper.cli.GetAclCommand; |
| import org.apache.zookeeper.cli.GetCommand; |
| import org.apache.zookeeper.cli.GetConfigCommand; |
| import org.apache.zookeeper.cli.ListQuotaCommand; |
| import org.apache.zookeeper.cli.Ls2Command; |
| import org.apache.zookeeper.cli.LsCommand; |
| import org.apache.zookeeper.cli.ReconfigCommand; |
| import org.apache.zookeeper.cli.SetAclCommand; |
| import org.apache.zookeeper.cli.SetCommand; |
| import org.apache.zookeeper.cli.SetQuotaCommand; |
| import org.apache.zookeeper.cli.StatCommand; |
| import org.apache.zookeeper.cli.SyncCommand; |
| import org.apache.zookeeper.client.ZKClientConfig; |
| |
| /** |
| * The command line client to ZooKeeper. |
| * |
| */ |
| public class ZooKeeperMain { |
| private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperMain.class); |
| protected static final Map<String,String> commandMap = new HashMap<String,String>( ); |
| protected static final Map<String,CliCommand> commandMapCli = |
| new HashMap<String,CliCommand>( ); |
| |
| protected MyCommandOptions cl = new MyCommandOptions(); |
| protected HashMap<Integer,String> history = new HashMap<Integer,String>( ); |
| protected int commandCount = 0; |
| protected boolean printWatches = true; |
| |
| protected ZooKeeper zk; |
| protected String host = ""; |
| |
| public boolean getPrintWatches( ) { |
| return printWatches; |
| } |
| |
| static { |
| commandMap.put("connect", "host:port"); |
| commandMap.put("history",""); |
| commandMap.put("redo","cmdno"); |
| commandMap.put("printwatches", "on|off"); |
| commandMap.put("quit", ""); |
| |
| new CloseCommand().addToMap(commandMapCli); |
| new CreateCommand().addToMap(commandMapCli); |
| new DeleteCommand().addToMap(commandMapCli); |
| new DeleteAllCommand().addToMap(commandMapCli); |
| // Depricated: rmr |
| new DeleteAllCommand("rmr").addToMap(commandMapCli); |
| new SetCommand().addToMap(commandMapCli); |
| new GetCommand().addToMap(commandMapCli); |
| new LsCommand().addToMap(commandMapCli); |
| new Ls2Command().addToMap(commandMapCli); |
| new GetAclCommand().addToMap(commandMapCli); |
| new SetAclCommand().addToMap(commandMapCli); |
| new StatCommand().addToMap(commandMapCli); |
| new SyncCommand().addToMap(commandMapCli); |
| new SetQuotaCommand().addToMap(commandMapCli); |
| new ListQuotaCommand().addToMap(commandMapCli); |
| new DelQuotaCommand().addToMap(commandMapCli); |
| new AddAuthCommand().addToMap(commandMapCli); |
| new ReconfigCommand().addToMap(commandMapCli); |
| new GetConfigCommand().addToMap(commandMapCli); |
| new RemoveWatchesCommand().addToMap(commandMapCli); |
| |
| // add all to commandMap |
| for (Entry<String, CliCommand> entry : commandMapCli.entrySet()) { |
| commandMap.put(entry.getKey(), entry.getValue().getOptionStr()); |
| } |
| } |
| |
| static void usage() { |
| System.err.println("ZooKeeper -server host:port cmd args"); |
| List<String> cmdList = new ArrayList<String>(commandMap.keySet()); |
| Collections.sort(cmdList); |
| for (String cmd : cmdList) { |
| System.err.println("\t"+cmd+ " " + commandMap.get(cmd)); |
| } |
| } |
| |
| private class MyWatcher implements Watcher { |
| public void process(WatchedEvent event) { |
| if (getPrintWatches()) { |
| ZooKeeperMain.printMessage("WATCHER::"); |
| ZooKeeperMain.printMessage(event.toString()); |
| } |
| } |
| } |
| |
| /** |
| * A storage class for both command line options and shell commands. |
| * |
| */ |
| static class MyCommandOptions { |
| |
| private Map<String,String> options = new HashMap<String,String>(); |
| private List<String> cmdArgs = null; |
| private String command = null; |
| public static final Pattern ARGS_PATTERN = Pattern.compile("\\s*([^\"\']\\S*|\"[^\"]*\"|'[^']*')\\s*"); |
| public static final Pattern QUOTED_PATTERN = Pattern.compile("^([\'\"])(.*)(\\1)$"); |
| |
| public MyCommandOptions() { |
| options.put("server", "localhost:2181"); |
| options.put("timeout", "30000"); |
| } |
| |
| public String getOption(String opt) { |
| return options.get(opt); |
| } |
| |
| public String getCommand( ) { |
| return command; |
| } |
| |
| public String getCmdArgument( int index ) { |
| return cmdArgs.get(index); |
| } |
| |
| public int getNumArguments( ) { |
| return cmdArgs.size(); |
| } |
| |
| public String[] getArgArray() { |
| return cmdArgs.toArray(new String[0]); |
| } |
| |
| /** |
| * Parses a command line that may contain one or more flags |
| * before an optional command string |
| * @param args command line arguments |
| * @return true if parsing succeeded, false otherwise. |
| */ |
| public boolean parseOptions(String[] args) { |
| List<String> argList = Arrays.asList(args); |
| Iterator<String> it = argList.iterator(); |
| |
| while (it.hasNext()) { |
| String opt = it.next(); |
| try { |
| if (opt.equals("-server")) { |
| options.put("server", it.next()); |
| } else if (opt.equals("-timeout")) { |
| options.put("timeout", it.next()); |
| } else if (opt.equals("-r")) { |
| options.put("readonly", "true"); |
| } |
| } catch (NoSuchElementException e){ |
| System.err.println("Error: no argument found for option " |
| + opt); |
| return false; |
| } |
| |
| if (!opt.startsWith("-")) { |
| command = opt; |
| cmdArgs = new ArrayList<String>( ); |
| cmdArgs.add( command ); |
| while (it.hasNext()) { |
| cmdArgs.add(it.next()); |
| } |
| return true; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Breaks a string into command + arguments. |
| * @param cmdstring string of form "cmd arg1 arg2..etc" |
| * @return true if parsing succeeded. |
| */ |
| public boolean parseCommand( String cmdstring ) { |
| Matcher matcher = ARGS_PATTERN.matcher(cmdstring); |
| |
| List<String> args = new LinkedList<String>(); |
| while (matcher.find()) { |
| String value = matcher.group(1); |
| if (QUOTED_PATTERN.matcher(value).matches()) { |
| // Strip off the surrounding quotes |
| value = value.substring(1, value.length() - 1); |
| } |
| args.add(value); |
| } |
| if (args.isEmpty()){ |
| return false; |
| } |
| command = args.get(0); |
| cmdArgs = args; |
| return true; |
| } |
| } |
| |
| |
| /** |
| * Makes a list of possible completions, either for commands |
| * or for zk nodes if the token to complete begins with / |
| * |
| */ |
| |
| |
| protected void addToHistory(int i,String cmd) { |
| history.put(i, cmd); |
| } |
| |
| public static List<String> getCommands() { |
| List<String> cmdList = new ArrayList<String>(commandMap.keySet()); |
| Collections.sort(cmdList); |
| return cmdList; |
| } |
| |
| protected String getPrompt() { |
| return "[zk: " + host + "("+zk.getState()+")" + " " + commandCount + "] "; |
| } |
| |
| public static void printMessage(String msg) { |
| System.out.println("\n"+msg); |
| } |
| |
| protected void connectToZK(String newHost) throws InterruptedException, IOException { |
| if (zk != null && zk.getState().isAlive()) { |
| zk.close(); |
| } |
| host = newHost; |
| boolean readOnly = cl.getOption("readonly") != null; |
| if (cl.getOption("secure") != null) { |
| System.setProperty(ZKClientConfig.SECURE_CLIENT, "true"); |
| System.out.println("Secure connection is enabled"); |
| } |
| zk = new ZooKeeper(host, |
| Integer.parseInt(cl.getOption("timeout")), |
| new MyWatcher(), readOnly); |
| } |
| |
| public static void main(String args[]) |
| throws KeeperException, IOException, InterruptedException |
| { |
| ZooKeeperMain main = new ZooKeeperMain(args); |
| main.run(); |
| } |
| |
| public ZooKeeperMain(String args[]) throws IOException, InterruptedException { |
| cl.parseOptions(args); |
| System.out.println("Connecting to " + cl.getOption("server")); |
| connectToZK(cl.getOption("server")); |
| //zk = new ZooKeeper(cl.getOption("server"), |
| // Integer.parseInt(cl.getOption("timeout")), new MyWatcher()); |
| } |
| |
| public ZooKeeperMain(ZooKeeper zk) { |
| this.zk = zk; |
| } |
| |
| void run() throws KeeperException, IOException, InterruptedException { |
| if (cl.getCommand() == null) { |
| System.out.println("Welcome to ZooKeeper!"); |
| |
| boolean jlinemissing = false; |
| // only use jline if it's in the classpath |
| try { |
| Class<?> consoleC = Class.forName("jline.console.ConsoleReader"); |
| Class<?> completorC = |
| Class.forName("org.apache.zookeeper.JLineZNodeCompleter"); |
| |
| System.out.println("JLine support is enabled"); |
| |
| Object console = |
| consoleC.getConstructor().newInstance(); |
| |
| Object completor = |
| completorC.getConstructor(ZooKeeper.class).newInstance(zk); |
| Method addCompletor = consoleC.getMethod("addCompleter", |
| Class.forName("jline.console.completer.Completer")); |
| addCompletor.invoke(console, completor); |
| |
| String line; |
| Method readLine = consoleC.getMethod("readLine", String.class); |
| while ((line = (String)readLine.invoke(console, getPrompt())) != null) { |
| executeLine(line); |
| } |
| } catch (ClassNotFoundException e) { |
| LOG.debug("Unable to start jline", e); |
| jlinemissing = true; |
| } catch (NoSuchMethodException e) { |
| LOG.debug("Unable to start jline", e); |
| jlinemissing = true; |
| } catch (InvocationTargetException e) { |
| LOG.debug("Unable to start jline", e); |
| jlinemissing = true; |
| } catch (IllegalAccessException e) { |
| LOG.debug("Unable to start jline", e); |
| jlinemissing = true; |
| } catch (InstantiationException e) { |
| LOG.debug("Unable to start jline", e); |
| jlinemissing = true; |
| } |
| |
| if (jlinemissing) { |
| System.out.println("JLine support is disabled"); |
| BufferedReader br = |
| new BufferedReader(new InputStreamReader(System.in)); |
| |
| String line; |
| while ((line = br.readLine()) != null) { |
| executeLine(line); |
| } |
| } |
| } else { |
| // Command line args non-null. Run what was passed. |
| processCmd(cl); |
| } |
| } |
| |
| public void executeLine(String line) |
| throws InterruptedException, IOException, KeeperException { |
| if (!line.equals("")) { |
| cl.parseCommand(line); |
| addToHistory(commandCount,line); |
| processCmd(cl); |
| commandCount++; |
| } |
| } |
| |
| /** |
| * trim the quota tree to recover unwanted tree elements |
| * in the quota's tree |
| * @param zk the zookeeper client |
| * @param path the path to start from and go up and see if their |
| * is any unwanted parent in the path. |
| * @return true if sucessful |
| * @throws KeeperException |
| * @throws IOException |
| * @throws InterruptedException |
| */ |
| private static boolean trimProcQuotas(ZooKeeper zk, String path) |
| throws KeeperException, IOException, InterruptedException |
| { |
| if (Quotas.quotaZookeeper.equals(path)) { |
| return true; |
| } |
| List<String> children = zk.getChildren(path, false); |
| if (children.size() == 0) { |
| zk.delete(path, -1); |
| String parent = path.substring(0, path.lastIndexOf('/')); |
| return trimProcQuotas(zk, parent); |
| } else { |
| return true; |
| } |
| } |
| |
| /** |
| * this method deletes quota for a node. |
| * @param zk the zookeeper client |
| * @param path the path to delete quota for |
| * @param bytes true if number of bytes needs to |
| * be unset |
| * @param numNodes true if number of nodes needs |
| * to be unset |
| * @return true if quota deletion is successful |
| * @throws KeeperException |
| * @throws IOException |
| * @throws InterruptedException |
| */ |
| public static boolean delQuota(ZooKeeper zk, String path, |
| boolean bytes, boolean numNodes) |
| throws KeeperException, IOException, InterruptedException |
| { |
| String parentPath = Quotas.quotaZookeeper + path; |
| String quotaPath = Quotas.quotaZookeeper + path + "/" + Quotas.limitNode; |
| if (zk.exists(quotaPath, false) == null) { |
| System.out.println("Quota does not exist for " + path); |
| return true; |
| } |
| byte[] data = null; |
| try { |
| data = zk.getData(quotaPath, false, new Stat()); |
| } catch(KeeperException.NoNodeException ne) { |
| System.err.println("quota does not exist for " + path); |
| return true; |
| } |
| StatsTrack strack = new StatsTrack(new String(data)); |
| if (bytes && !numNodes) { |
| strack.setBytes(-1L); |
| zk.setData(quotaPath, strack.toString().getBytes(), -1); |
| } else if (!bytes && numNodes) { |
| strack.setCount(-1); |
| zk.setData(quotaPath, strack.toString().getBytes(), -1); |
| } else if (bytes && numNodes) { |
| // delete till you can find a node with more than |
| // one child |
| List<String> children = zk.getChildren(parentPath, false); |
| /// delete the direct children first |
| for (String child: children) { |
| zk.delete(parentPath + "/" + child, -1); |
| } |
| // cut the tree till their is more than one child |
| trimProcQuotas(zk, parentPath); |
| } |
| 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"); |
| } |
| } |
| } |
| } |
| |
| /** |
| * this method creates a quota node for the path |
| * @param zk the ZooKeeper client |
| * @param path the path for which quota needs to be created |
| * @param bytes the limit of bytes on this path |
| * @param numNodes the limit of number of nodes on this path |
| * @return true if its successful and false if not. |
| */ |
| public static boolean createQuota(ZooKeeper zk, String path, |
| long bytes, int numNodes) |
| throws KeeperException, IOException, InterruptedException |
| { |
| // check if the path exists. We cannot create |
| // quota for a path that already exists in zookeeper |
| // for now. |
| Stat initStat = zk.exists(path, false); |
| 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, Ids.OPEN_ACL_UNSAFE, |
| CreateMode.PERSISTENT); |
| zk.create(Quotas.quotaZookeeper, null, 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, 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(), |
| Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); |
| StatsTrack stats = new StatsTrack(null); |
| stats.setBytes(0L); |
| stats.setCount(0); |
| zk.create(statPath, stats.toString().getBytes(), |
| 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; |
| } |
| |
| protected boolean processCmd(MyCommandOptions co) |
| throws KeeperException, IOException, InterruptedException |
| { |
| try { |
| return processZKCmd(co); |
| } catch (IllegalArgumentException e) { |
| System.err.println("Command failed: " + e); |
| } catch (KeeperException.NoNodeException e) { |
| System.err.println("Node does not exist: " + e.getPath()); |
| } catch (KeeperException.NoChildrenForEphemeralsException e) { |
| System.err.println("Ephemerals cannot have children: " |
| + e.getPath()); |
| } catch (KeeperException.NodeExistsException e) { |
| System.err.println("Node already exists: " + e.getPath()); |
| } catch (KeeperException.NotEmptyException e) { |
| System.err.println("Node not empty: " + e.getPath()); |
| } catch (KeeperException.NotReadOnlyException e) { |
| System.err.println("Not a read-only call: " + e.getPath()); |
| }catch (KeeperException.InvalidACLException e) { |
| System.err.println("Acl is not valid : "+e.getPath()); |
| }catch (KeeperException.NoAuthException e) { |
| System.err.println("Authentication is not valid : "+e.getPath()); |
| }catch (KeeperException.BadArgumentsException e) { |
| System.err.println("Arguments are not valid : "+e.getPath()); |
| }catch (KeeperException.BadVersionException e) { |
| System.err.println("version No is not valid : "+e.getPath()); |
| }catch (KeeperException.ReconfigInProgress e) { |
| System.err.println("Another reconfiguration is in progress -- concurrent " + |
| "reconfigs not supported (yet)"); |
| }catch (KeeperException.NewConfigNoQuorum e) { |
| System.err.println("No quorum of new config is connected and " + |
| "up-to-date with the leader of last commmitted config - try invoking reconfiguration after " + |
| "new servers are connected and synced"); |
| } |
| return false; |
| } |
| |
| protected boolean processZKCmd(MyCommandOptions co) |
| throws KeeperException, IOException, InterruptedException |
| { |
| String[] args = co.getArgArray(); |
| String cmd = co.getCommand(); |
| if (args.length < 1) { |
| usage(); |
| return false; |
| } |
| |
| if (!commandMap.containsKey(cmd)) { |
| usage(); |
| return false; |
| } |
| |
| boolean watch = false; |
| LOG.debug("Processing " + cmd); |
| |
| try { |
| if (cmd.equals("quit")) { |
| zk.close(); |
| System.exit(0); |
| } else if (cmd.equals("redo") && args.length >= 2) { |
| Integer i = Integer.decode(args[1]); |
| if (commandCount <= i){ // don't allow redoing this redo |
| System.out.println("Command index out of range"); |
| return false; |
| } |
| cl.parseCommand(history.get(i)); |
| if (cl.getCommand().equals( "redo" )){ |
| System.out.println("No redoing redos"); |
| return false; |
| } |
| history.put(commandCount, history.get(i)); |
| processCmd( cl); |
| } else if (cmd.equals("history")) { |
| for (int i=commandCount - 10;i<=commandCount;++i) { |
| if (i < 0) continue; |
| System.out.println(i + " - " + history.get(i)); |
| } |
| } else if (cmd.equals("printwatches")) { |
| if (args.length == 1) { |
| System.out.println("printwatches is " + (printWatches ? "on" : "off")); |
| } else { |
| printWatches = args[1].equals("on"); |
| } |
| } else if (cmd.equals("connect")) { |
| if (args.length >=2) { |
| connectToZK(args[1]); |
| } else { |
| connectToZK(host); |
| } |
| } |
| |
| // Below commands all need a live connection |
| if (zk == null || !zk.getState().isAlive()) { |
| System.out.println("Not connected"); |
| return false; |
| } |
| |
| // execute from commandMap |
| CliCommand cliCmd = commandMapCli.get(cmd); |
| if(cliCmd != null) { |
| cliCmd.setZk(zk); |
| watch = cliCmd.parse(args).exec(); |
| } else if (!commandMap.containsKey(cmd)) { |
| usage(); |
| } |
| |
| } catch (ParseException ex) { |
| System.err.println(ex.getMessage()); |
| usage(); |
| return false; |
| } |
| return watch; |
| } |
| } |