Skip to content

Commit

Permalink
ZOOKEEPER-1962: Add a CLI command to recursively list a znode and chi…
Browse files Browse the repository at this point in the history
…ldren (Gautam Gopalakrishnan, Hongchao Deng, Enis Soztutar via phunt)

git-svn-id: https://svn.apache.org/repos/asf/zookeeper/trunk@1759904 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
phunt committed Sep 8, 2016
1 parent 32c709c commit 10dc80a
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 56 deletions.
3 changes: 3 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ NEW FEATURES:

ZOOKEEPER-2163: Introduce new ZNode type: container (Jordan Zimmerman via rgs)

ZOOKEEPER-1962: Add a CLI command to recursively list a znode and
children (Gautam Gopalakrishnan, Hongchao Deng, Enis Soztutar via phunt)

BUGFIXES:
ZOOKEEPER-1784 wrong check for COMMITANDACTIVATE in observer code, Learner.java (rgs via shralex).

Expand Down
84 changes: 66 additions & 18 deletions src/java/main/org/apache/zookeeper/ZKUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,37 @@
package org.apache.zookeeper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

import org.apache.zookeeper.AsyncCallback.StringCallback;
import org.apache.zookeeper.AsyncCallback.VoidCallback;
import org.apache.zookeeper.KeeperException.Code;
import org.apache.zookeeper.common.PathUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ZKUtil {
private static final Logger LOG = LoggerFactory.getLogger(ZKUtil.class);
/**
* Recursively delete the node with the given path.
* Recursively delete the node with the given path.
* <p>
* Important: All versions, of all nodes, under the given node are deleted.
* <p>
* If there is an error with deleting one of the sub-nodes in the tree,
* If there is an error with deleting one of the sub-nodes in the tree,
* this operation would abort and would be the responsibility of the app to handle the same.
*
*
* See {@link #delete(String, int)} for more details.
*
*
* @throws IllegalArgumentException if an invalid path is specified
*/
public static void deleteRecursive(ZooKeeper zk, final String pathRoot)
throws InterruptedException, KeeperException
{
PathUtils.validatePath(pathRoot);

List<String> tree = listSubTreeBFS(zk, pathRoot);
LOG.debug("Deleting " + tree);
LOG.debug("Deleting " + tree.size() + " subnodes ");
Expand All @@ -54,15 +57,15 @@ public static void deleteRecursive(ZooKeeper zk, final String pathRoot)
zk.delete(tree.get(i), -1); //Delete all versions of the node with -1.
}
}


/**
* Recursively delete the node with the given path. (async version).
*
*
* <p>
* Important: All versions, of all nodes, under the given node are deleted.
* <p>
* If there is an error with deleting one of the sub-nodes in the tree,
* If there is an error with deleting one of the sub-nodes in the tree,
* this operation would abort and would be the responsibility of the app to handle the same.
* <p>
* @param zk the zookeeper handle
Expand All @@ -76,7 +79,7 @@ public static void deleteRecursive(ZooKeeper zk, final String pathRoot, VoidCall
throws InterruptedException, KeeperException
{
PathUtils.validatePath(pathRoot);

List<String> tree = listSubTreeBFS(zk, pathRoot);
LOG.debug("Deleting " + tree);
LOG.debug("Deleting " + tree.size() + " subnodes ");
Expand All @@ -85,22 +88,22 @@ public static void deleteRecursive(ZooKeeper zk, final String pathRoot, VoidCall
zk.delete(tree.get(i), -1, cb, ctx); //Delete all versions of the node with -1.
}
}

/**
* BFS Traversal of the system under pathRoot, with the entries in the list, in the
* BFS Traversal of the system under pathRoot, with the entries in the list, in the
* same order as that of the traversal.
* <p>
* <b>Important:</b> This is <i>not an atomic snapshot</i> of the tree ever, but the
* state as it exists across multiple RPCs from zkClient to the ensemble.
* For practical purposes, it is suggested to bring the clients to the ensemble
* down (i.e. prevent writes to pathRoot) to 'simulate' a snapshot behavior.
*
* For practical purposes, it is suggested to bring the clients to the ensemble
* down (i.e. prevent writes to pathRoot) to 'simulate' a snapshot behavior.
*
* @param zk the zookeeper handle
* @param pathRoot The znode path, for which the entire subtree needs to be listed.
* @throws InterruptedException
* @throws KeeperException
* @throws InterruptedException
* @throws KeeperException
*/
public static List<String> listSubTreeBFS(ZooKeeper zk, final String pathRoot) throws
public static List<String> listSubTreeBFS(ZooKeeper zk, final String pathRoot) throws
KeeperException, InterruptedException {
Deque<String> queue = new LinkedList<String>();
List<String> tree = new ArrayList<String>();
Expand All @@ -120,4 +123,49 @@ public static List<String> listSubTreeBFS(ZooKeeper zk, final String pathRoot) t
}
return tree;
}

/**
* Visits the subtree with root as given path and calls the passed callback with each znode
* found during the search. It performs a depth-first, pre-order traversal of the tree.
* <p>
* <b>Important:</b> This is <i>not an atomic snapshot</i> of the tree ever, but the
* state as it exists across multiple RPCs from zkClient to the ensemble.
* For practical purposes, it is suggested to bring the clients to the ensemble
* down (i.e. prevent writes to pathRoot) to 'simulate' a snapshot behavior.
*/
public static void visitSubTreeDFS(ZooKeeper zk, final String path, boolean watch,
StringCallback cb) throws KeeperException, InterruptedException {
PathUtils.validatePath(path);

zk.getData(path, watch, null);
cb.processResult(Code.OK.intValue(), path, null, path);
visitSubTreeDFSHelper(zk, path, watch, cb);
}

@SuppressWarnings("unchecked")
private static void visitSubTreeDFSHelper(ZooKeeper zk, final String path,
boolean watch, StringCallback cb)
throws KeeperException, InterruptedException {
// we've already validated, therefore if the path is of length 1 it's the root
final boolean isRoot = path.length() == 1;
try {
List<String> children = zk.getChildren(path, watch, null);
Collections.sort(children);

for (String child : children) {
String childPath = (isRoot ? path : path + "/") + child;
cb.processResult(Code.OK.intValue(), childPath, null, child);
}

for (String child : children) {
String childPath = (isRoot ? path : path + "/") + child;
visitSubTreeDFSHelper(zk, childPath, watch, cb);
}
}
catch (KeeperException.NoNodeException e) {
// Handle race condition where a node is listed
// but gets deleted before it can be queried
return; // ignore
}
}
}
45 changes: 25 additions & 20 deletions src/java/main/org/apache/zookeeper/cli/LsCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
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;

/**
Expand All @@ -35,10 +37,11 @@ public class LsCommand extends CliCommand {
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] path");
super("ls", "[-s] [-w] [-R] path");
}

private void printHelp() {
Expand All @@ -61,7 +64,7 @@ public CliCommand parse(String[] cmdArgs) throws CliParseException {
}

retainCompatibility(cmdArgs);

return this;
}

Expand Down Expand Up @@ -91,40 +94,42 @@ public boolean exec() throws CliException {
String path = args[1];
boolean watch = cl.hasOption("w");
boolean withStat = cl.hasOption("s");
boolean recursive = cl.hasOption("R");
try {
Stat stat = new Stat();
List<String> children;
if (withStat) {
// with stat
children = zk.getChildren(path, watch, stat);
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 {
// without stat
children = zk.getChildren(path, watch);
}
out.println(printChildren(children));
if (withStat) {
new StatPrinter(out).print(stat);
Stat stat = withStat ? new Stat() : null;
List<String> children = zk.getChildren(path, watch, stat);
printChildren(children, stat);
}
} catch (KeeperException|InterruptedException ex) {
throw new CliWrapperException(ex);
}
return watch;
}

private String printChildren(List<String> children) {
private void printChildren(List<String> children, Stat stat) {
Collections.sort(children);
StringBuilder sb = new StringBuilder();
sb.append("[");
out.append("[");
boolean first = true;
for (String child : children) {
if (!first) {
sb.append(", ");
out.append(", ");
} else {
first = false;
}
sb.append(child);
out.append(child);
}
out.append("]");
if (stat != null) {
new StatPrinter(out).print(stat);
}
sb.append("]");
return sb.toString();
out.append("\n");
}
}
Loading

0 comments on commit 10dc80a

Please sign in to comment.