Index: hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/RecoverableZooKeeper.java =================================================================== --- hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/RecoverableZooKeeper.java (revision 1577027) +++ hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/RecoverableZooKeeper.java (working copy) @@ -720,4 +720,42 @@ public String getIdentifier() { return identifier; } + + /** + * BFS Traversal of all the children under path, with the entries in the list, in the same order + * as that of the traversal. This is an idempotent operation. Retry before throwing exception + * + * @return List of children znodes under the path + */ + public List getAllChildrenBFS(String path) throws KeeperException, InterruptedException { + TraceScope traceScope = null; + try { + traceScope = Trace.startSpan("RecoverableZookeeper.getAllChildrenBFS"); + RetryCounter retryCounter = retryCounterFactory.create(); + while (true) { + try { + return org.apache.zookeeper.ZKUtil.listSubTreeBFS(zk, path); + } catch (KeeperException e) { + switch (e.code()) { + case NONODE: + if (null == zk.exists(path, false)) { + LOG.warn("Given znode :" + path + " doesn't exists!"); + throw e; + } + case CONNECTIONLOSS: + case SESSIONEXPIRED: + case OPERATIONTIMEOUT: + retryOrThrow(retryCounter, e, "getAllChildrenBFS"); + break; + + default: + throw e; + } + } + retryCounter.sleepUntilNextRetry(); + } + } finally { + if (traceScope != null) traceScope.close(); + } + } } Index: hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKUtil.java =================================================================== --- hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKUtil.java (revision 1577027) +++ hbase-client/src/main/java/org/apache/hadoop/hbase/zookeeper/ZKUtil.java (working copy) @@ -1328,20 +1328,72 @@ } /** - * Delete all the children of the specified node but not the node itself. + * Delete all the children of the specified node but not the node itself. Sets no watches. + * Throws all exceptions besides dealing with deletion of children. * - * Sets no watches. Throws all exceptions besides dealing with deletion of - * children. + * If hbase.zookeeper.useMulti is true, use ZooKeeper's multi-update functionality. + * Otherwise, run the list of operations sequentially. + * + * @throws KeeperException */ public static void deleteChildrenRecursively(ZooKeeperWatcher zkw, String node) - throws KeeperException { - List children = ZKUtil.listChildrenNoWatch(zkw, node); - if (children == null || children.isEmpty()) return; - for(String child : children) { - deleteNodeRecursively(zkw, joinZNode(node, child)); + throws KeeperException { + deleteChildrenRecursivelyMultiOrSequential(zkw, false, node); + } + + /** + * Delete all the children of the specified nodes but not the nodes itself. Sets no watches. + * Throws all exceptions besides dealing with deletion of children. + * + * If hbase.zookeeper.useMulti is true, use ZooKeeper's multi-update functionality. + * Otherwise, run the list of operations sequentially. + * + * If all of the following are true: + * - runSequentialOnMultiFailure is true + * - hbase.zookeeper.useMulti is true + * - on calling multi, we get a ZooKeeper exception that can be handled by a sequential call(*) + * Then: + * - we retry the operations one-by-one (sequentially) + * + * @throws KeeperException + */ + public static void deleteChildrenRecursivelyMultiOrSequential(ZooKeeperWatcher zkw, + boolean runSequentialOnMultiFailure, String... pathRoots) throws KeeperException { + try { + if (pathRoots == null || pathRoots.length <= 0) { + LOG.warn("Given path is not valid!"); + return; + } + List opList = new ArrayList(); + List ops = new ArrayList(opList.size()); + for (String eachRoot : pathRoots) { + List children = getChildren(zkw, eachRoot); + for (String child : children) { + ops.add(ZKUtilOp.deleteNodeFailSilent(child)); + } + } + // atleast one element should exists + if (ops.size() > 0) { + multiOrSequential(zkw, ops, runSequentialOnMultiFailure); + } + } catch (InterruptedException ie) { + zkw.interruptedException(ie); } } + private static List getChildren(ZooKeeperWatcher zkw, final String path) + throws InterruptedException, KeeperException { + List tree = new ArrayList(); + tree = zkw.getRecoverableZooKeeper().getAllChildrenBFS(path); + List children = new ArrayList(tree.size()); + // Delete the leaves first and eventually get rid of the root + // Root element will be skipped and not added to this list + for (int i = tree.size() - 1; i > 0; --i) { + children.add(tree.get(i)); + } + return children; + } + /** * Represents an action taken by ZKUtil, e.g. createAndFailSilent. * These actions are higher-level than ZKOp actions, which represent Index: hbase-server/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKMulti.java =================================================================== --- hbase-server/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKMulti.java (revision 1577027) +++ hbase-server/src/test/java/org/apache/hadoop/hbase/zookeeper/TestZKMulti.java (working copy) @@ -24,7 +24,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import java.util.ArrayList; import java.util.LinkedList; +import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -34,7 +36,10 @@ import org.apache.hadoop.hbase.MediumTests; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.zookeeper.ZKUtil.ZKUtilOp; +import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.KeeperException; +import org.apache.zookeeper.Op; +import org.apache.zookeeper.ZooDefs.Ids; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; @@ -288,4 +293,64 @@ assertTrue(ZKUtil.checkExists(zkw, path3) == -1); assertFalse(ZKUtil.checkExists(zkw, path4) == -1); } + + /** + * Verifies that for the given root node, it should delete all the child nodes recursively using + * multi-update api. + */ + @Test + public void testdeleteChildrenRecursivelyMulti() throws Exception { + String parentZNode = "/testRootMulti"; + createZNodeTree(parentZNode); + + ZKUtil.deleteChildrenRecursivelyMultiOrSequential(zkw, true, parentZNode); + + assertTrue("Wrongly deleted parent znode!", ZKUtil.checkExists(zkw, parentZNode) > -1); + List children = zkw.getRecoverableZooKeeper().getChildren(parentZNode, false); + assertTrue("Failed to delete child znodes!", 0 == children.size()); + } + + /** + * Verifies that for the given root node, it should delete all the child nodes recursively using + * normal sequential way. + */ + @Test + public void testdeleteChildrenRecursivelySequential() throws Exception { + String parentZNode = "/testRootSeq"; + createZNodeTree(parentZNode); + + zkw.getConfiguration().setBoolean("hbase.zookeeper.useMulti", false); + try { + // disables the multi-update api execution + ZKUtil.deleteChildrenRecursivelyMultiOrSequential(zkw, true, parentZNode); + + assertTrue("Wrongly deleted parent znode!", ZKUtil.checkExists(zkw, parentZNode) > -1); + List children = zkw.getRecoverableZooKeeper().getChildren(parentZNode, false); + assertTrue("Failed to delete child znodes!", 0 == children.size()); + } finally { + // enables back the multi-update api execution + zkw.getConfiguration().setBoolean("hbase.zookeeper.useMulti", true); + } + } + + private void createZNodeTree(String rootZNode) throws KeeperException, InterruptedException { + List opList = new ArrayList(); + opList.add(Op.create(rootZNode, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)); + int level = 0; + String parentZNode = rootZNode; + while (level < 10) { + // define parent node + parentZNode = parentZNode + "/" + level; + opList.add(Op.create(parentZNode, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT)); + int elements = 0; + // add elements to the parent node + while (elements < level) { + opList.add(Op.create(parentZNode + "/" + elements, new byte[0], Ids.OPEN_ACL_UNSAFE, + CreateMode.PERSISTENT)); + elements++; + } + level++; + } + zkw.getRecoverableZooKeeper().multi(opList); + } }