View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.zookeeper;
22  
23  import static org.junit.Assert.assertEquals;
24  import static org.junit.Assert.assertFalse;
25  import static org.junit.Assert.assertTrue;
26  
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.Collections;
30  import java.util.LinkedList;
31  import java.util.List;
32  
33  import org.apache.commons.logging.Log;
34  import org.apache.commons.logging.LogFactory;
35  import org.apache.hadoop.conf.Configuration;
36  import org.apache.hadoop.hbase.Abortable;
37  import org.apache.hadoop.hbase.HBaseTestingUtility;
38  import org.apache.hadoop.hbase.testclassification.MediumTests;
39  import org.apache.hadoop.hbase.util.Bytes;
40  import org.apache.hadoop.hbase.zookeeper.ZKUtil.ZKUtilOp;
41  import org.apache.zookeeper.CreateMode;
42  import org.apache.zookeeper.KeeperException;
43  import org.apache.zookeeper.Op;
44  import org.apache.zookeeper.ZooDefs.Ids;
45  import org.junit.AfterClass;
46  import org.junit.BeforeClass;
47  import org.junit.Test;
48  import org.junit.experimental.categories.Category;
49  
50  /**
51   * Test ZooKeeper multi-update functionality
52   */
53  @Category(MediumTests.class)
54  public class TestZKMulti {
55    private static final Log LOG = LogFactory.getLog(TestZKMulti.class);
56    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
57    private static ZooKeeperWatcher zkw = null;
58  
59    private static class ZKMultiAbortable implements Abortable {
60      @Override
61      public void abort(String why, Throwable e) {
62        LOG.info(why, e);
63      }
64  
65      @Override
66      public boolean isAborted() {
67        return false;
68      }
69    }
70  
71    @BeforeClass
72    public static void setUpBeforeClass() throws Exception {
73      TEST_UTIL.startMiniZKCluster();
74      Configuration conf = TEST_UTIL.getConfiguration();
75      conf.setBoolean("hbase.zookeeper.useMulti", true);
76      Abortable abortable = new Abortable() {
77        @Override
78        public void abort(String why, Throwable e) {
79          LOG.info(why, e);
80        }
81  
82        @Override
83        public boolean isAborted() {
84          return false;
85        }
86      };
87      zkw = new ZooKeeperWatcher(conf,
88        "TestZKMulti", abortable, true);
89    }
90  
91    @AfterClass
92    public static void tearDownAfterClass() throws Exception {
93      TEST_UTIL.shutdownMiniZKCluster();
94    }
95  
96    @Test (timeout=60000)
97    public void testSimpleMulti() throws Exception {
98      // null multi
99      ZKUtil.multiOrSequential(zkw, null, false);
100 
101     // empty multi
102     ZKUtil.multiOrSequential(zkw, new LinkedList<ZKUtilOp>(), false);
103 
104     // single create
105     String path = ZKUtil.joinZNode(zkw.baseZNode, "testSimpleMulti");
106     LinkedList<ZKUtilOp> singleCreate = new LinkedList<ZKUtilOp>();
107     singleCreate.add(ZKUtilOp.createAndFailSilent(path, new byte[0]));
108     ZKUtil.multiOrSequential(zkw, singleCreate, false);
109     assertTrue(ZKUtil.checkExists(zkw, path) != -1);
110 
111     // single setdata
112     LinkedList<ZKUtilOp> singleSetData = new LinkedList<ZKUtilOp>();
113     byte [] data = Bytes.toBytes("foobar");
114     singleSetData.add(ZKUtilOp.setData(path, data));
115     ZKUtil.multiOrSequential(zkw, singleSetData, false);
116     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path), data));
117 
118     // single delete
119     LinkedList<ZKUtilOp> singleDelete = new LinkedList<ZKUtilOp>();
120     singleDelete.add(ZKUtilOp.deleteNodeFailSilent(path));
121     ZKUtil.multiOrSequential(zkw, singleDelete, false);
122     assertTrue(ZKUtil.checkExists(zkw, path) == -1);
123   }
124 
125   @Test (timeout=60000)
126   public void testComplexMulti() throws Exception {
127     String path1 = ZKUtil.joinZNode(zkw.baseZNode, "testComplexMulti1");
128     String path2 = ZKUtil.joinZNode(zkw.baseZNode, "testComplexMulti2");
129     String path3 = ZKUtil.joinZNode(zkw.baseZNode, "testComplexMulti3");
130     String path4 = ZKUtil.joinZNode(zkw.baseZNode, "testComplexMulti4");
131     String path5 = ZKUtil.joinZNode(zkw.baseZNode, "testComplexMulti5");
132     String path6 = ZKUtil.joinZNode(zkw.baseZNode, "testComplexMulti6");
133     // create 4 nodes that we'll setData on or delete later
134     LinkedList<ZKUtilOp> create4Nodes = new LinkedList<ZKUtilOp>();
135     create4Nodes.add(ZKUtilOp.createAndFailSilent(path1, Bytes.toBytes(path1)));
136     create4Nodes.add(ZKUtilOp.createAndFailSilent(path2, Bytes.toBytes(path2)));
137     create4Nodes.add(ZKUtilOp.createAndFailSilent(path3, Bytes.toBytes(path3)));
138     create4Nodes.add(ZKUtilOp.createAndFailSilent(path4, Bytes.toBytes(path4)));
139     ZKUtil.multiOrSequential(zkw, create4Nodes, false);
140     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path1), Bytes.toBytes(path1)));
141     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path2), Bytes.toBytes(path2)));
142     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path3), Bytes.toBytes(path3)));
143     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path4), Bytes.toBytes(path4)));
144 
145     // do multiple of each operation (setData, delete, create)
146     LinkedList<ZKUtilOp> ops = new LinkedList<ZKUtilOp>();
147     // setData
148     ops.add(ZKUtilOp.setData(path1, Bytes.add(Bytes.toBytes(path1), Bytes.toBytes(path1))));
149     ops.add(ZKUtilOp.setData(path2, Bytes.add(Bytes.toBytes(path2), Bytes.toBytes(path2))));
150     // delete
151     ops.add(ZKUtilOp.deleteNodeFailSilent(path3));
152     ops.add(ZKUtilOp.deleteNodeFailSilent(path4));
153     // create
154     ops.add(ZKUtilOp.createAndFailSilent(path5, Bytes.toBytes(path5)));
155     ops.add(ZKUtilOp.createAndFailSilent(path6, Bytes.toBytes(path6)));
156     ZKUtil.multiOrSequential(zkw, ops, false);
157     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path1),
158       Bytes.add(Bytes.toBytes(path1), Bytes.toBytes(path1))));
159     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path2),
160       Bytes.add(Bytes.toBytes(path2), Bytes.toBytes(path2))));
161     assertTrue(ZKUtil.checkExists(zkw, path3) == -1);
162     assertTrue(ZKUtil.checkExists(zkw, path4) == -1);
163     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path5), Bytes.toBytes(path5)));
164     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path6), Bytes.toBytes(path6)));
165   }
166 
167   @Test (timeout=60000)
168   public void testSingleFailure() throws Exception {
169     // try to delete a node that doesn't exist
170     boolean caughtNoNode = false;
171     String path = ZKUtil.joinZNode(zkw.baseZNode, "testSingleFailureZ");
172     LinkedList<ZKUtilOp> ops = new LinkedList<ZKUtilOp>();
173     ops.add(ZKUtilOp.deleteNodeFailSilent(path));
174     try {
175       ZKUtil.multiOrSequential(zkw, ops, false);
176     } catch (KeeperException.NoNodeException nne) {
177       caughtNoNode = true;
178     }
179     assertTrue(caughtNoNode);
180 
181     // try to setData on a node that doesn't exist
182     caughtNoNode = false;
183     ops = new LinkedList<ZKUtilOp>();
184     ops.add(ZKUtilOp.setData(path, Bytes.toBytes(path)));
185     try {
186       ZKUtil.multiOrSequential(zkw, ops, false);
187     } catch (KeeperException.NoNodeException nne) {
188       caughtNoNode = true;
189     }
190     assertTrue(caughtNoNode);
191 
192     // try to create on a node that already exists
193     boolean caughtNodeExists = false;
194     ops = new LinkedList<ZKUtilOp>();
195     ops.add(ZKUtilOp.createAndFailSilent(path, Bytes.toBytes(path)));
196     ZKUtil.multiOrSequential(zkw, ops, false);
197     try {
198       ZKUtil.multiOrSequential(zkw, ops, false);
199     } catch (KeeperException.NodeExistsException nee) {
200       caughtNodeExists = true;
201     }
202     assertTrue(caughtNodeExists);
203   }
204 
205   @Test (timeout=60000)
206   public void testSingleFailureInMulti() throws Exception {
207     // try a multi where all but one operation succeeds
208     String pathA = ZKUtil.joinZNode(zkw.baseZNode, "testSingleFailureInMultiA");
209     String pathB = ZKUtil.joinZNode(zkw.baseZNode, "testSingleFailureInMultiB");
210     String pathC = ZKUtil.joinZNode(zkw.baseZNode, "testSingleFailureInMultiC");
211     LinkedList<ZKUtilOp> ops = new LinkedList<ZKUtilOp>();
212     ops.add(ZKUtilOp.createAndFailSilent(pathA, Bytes.toBytes(pathA)));
213     ops.add(ZKUtilOp.createAndFailSilent(pathB, Bytes.toBytes(pathB)));
214     ops.add(ZKUtilOp.deleteNodeFailSilent(pathC));
215     boolean caughtNoNode = false;
216     try {
217       ZKUtil.multiOrSequential(zkw, ops, false);
218     } catch (KeeperException.NoNodeException nne) {
219       caughtNoNode = true;
220     }
221     assertTrue(caughtNoNode);
222     // assert that none of the operations succeeded
223     assertTrue(ZKUtil.checkExists(zkw, pathA) == -1);
224     assertTrue(ZKUtil.checkExists(zkw, pathB) == -1);
225     assertTrue(ZKUtil.checkExists(zkw, pathC) == -1);
226   }
227 
228   @Test (timeout=60000)
229   public void testMultiFailure() throws Exception {
230     String pathX = ZKUtil.joinZNode(zkw.baseZNode, "testMultiFailureX");
231     String pathY = ZKUtil.joinZNode(zkw.baseZNode, "testMultiFailureY");
232     String pathZ = ZKUtil.joinZNode(zkw.baseZNode, "testMultiFailureZ");
233     // create X that we will use to fail create later
234     LinkedList<ZKUtilOp> ops = new LinkedList<ZKUtilOp>();
235     ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathX)));
236     ZKUtil.multiOrSequential(zkw, ops, false);
237 
238     // fail one of each create ,setData, delete
239     String pathV = ZKUtil.joinZNode(zkw.baseZNode, "testMultiFailureV");
240     String pathW = ZKUtil.joinZNode(zkw.baseZNode, "testMultiFailureW");
241     ops = new LinkedList<ZKUtilOp>();
242     ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathX))); // fail  -- already exists
243     ops.add(ZKUtilOp.setData(pathY, Bytes.toBytes(pathY))); // fail -- doesn't exist
244     ops.add(ZKUtilOp.deleteNodeFailSilent(pathZ)); // fail -- doesn't exist
245     ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathV))); // pass
246     ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathW))); // pass
247     boolean caughtNodeExists = false;
248     try {
249       ZKUtil.multiOrSequential(zkw, ops, false);
250     } catch (KeeperException.NodeExistsException nee) {
251       // check first operation that fails throws exception
252       caughtNodeExists = true;
253     }
254     assertTrue(caughtNodeExists);
255     // check that no modifications were made
256     assertFalse(ZKUtil.checkExists(zkw, pathX) == -1);
257     assertTrue(ZKUtil.checkExists(zkw, pathY) == -1);
258     assertTrue(ZKUtil.checkExists(zkw, pathZ) == -1);
259     assertTrue(ZKUtil.checkExists(zkw, pathW) == -1);
260     assertTrue(ZKUtil.checkExists(zkw, pathV) == -1);
261 
262     // test that with multiple failures, throws an exception corresponding to first failure in list
263     ops = new LinkedList<ZKUtilOp>();
264     ops.add(ZKUtilOp.setData(pathY, Bytes.toBytes(pathY))); // fail -- doesn't exist
265     ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathX))); // fail -- exists
266     boolean caughtNoNode = false;
267     try {
268       ZKUtil.multiOrSequential(zkw, ops, false);
269     } catch (KeeperException.NoNodeException nne) {
270       // check first operation that fails throws exception
271       caughtNoNode = true;
272     }
273     assertTrue(caughtNoNode);
274     // check that no modifications were made
275     assertFalse(ZKUtil.checkExists(zkw, pathX) == -1);
276     assertTrue(ZKUtil.checkExists(zkw, pathY) == -1);
277     assertTrue(ZKUtil.checkExists(zkw, pathZ) == -1);
278     assertTrue(ZKUtil.checkExists(zkw, pathW) == -1);
279     assertTrue(ZKUtil.checkExists(zkw, pathV) == -1);
280   }
281 
282   @Test (timeout=60000)
283   public void testRunSequentialOnMultiFailure() throws Exception {
284     String path1 = ZKUtil.joinZNode(zkw.baseZNode, "runSequential1");
285     String path2 = ZKUtil.joinZNode(zkw.baseZNode, "runSequential2");
286     String path3 = ZKUtil.joinZNode(zkw.baseZNode, "runSequential3");
287     String path4 = ZKUtil.joinZNode(zkw.baseZNode, "runSequential4");
288 
289     // create some nodes that we will use later
290     LinkedList<ZKUtilOp> ops = new LinkedList<ZKUtilOp>();
291     ops.add(ZKUtilOp.createAndFailSilent(path1, Bytes.toBytes(path1)));
292     ops.add(ZKUtilOp.createAndFailSilent(path2, Bytes.toBytes(path2)));
293     ZKUtil.multiOrSequential(zkw, ops, false);
294 
295     // test that, even with operations that fail, the ones that would pass will pass
296     // with runSequentialOnMultiFailure
297     ops = new LinkedList<ZKUtilOp>();
298     ops.add(ZKUtilOp.setData(path1, Bytes.add(Bytes.toBytes(path1), Bytes.toBytes(path1)))); // pass
299     ops.add(ZKUtilOp.deleteNodeFailSilent(path2)); // pass
300     ops.add(ZKUtilOp.deleteNodeFailSilent(path3)); // fail -- node doesn't exist
301     ops.add(ZKUtilOp.createAndFailSilent(path4,
302       Bytes.add(Bytes.toBytes(path4), Bytes.toBytes(path4)))); // pass
303     ZKUtil.multiOrSequential(zkw, ops, true);
304     assertTrue(Bytes.equals(ZKUtil.getData(zkw, path1),
305       Bytes.add(Bytes.toBytes(path1), Bytes.toBytes(path1))));
306     assertTrue(ZKUtil.checkExists(zkw, path2) == -1);
307     assertTrue(ZKUtil.checkExists(zkw, path3) == -1);
308     assertFalse(ZKUtil.checkExists(zkw, path4) == -1);
309   }
310 
311   /**
312    * Verifies that for the given root node, it should delete all the child nodes
313    * recursively using multi-update api.
314    */
315   @Test (timeout=60000)
316   public void testdeleteChildrenRecursivelyMulti() throws Exception {
317     String parentZNode = "/testRootMulti";
318     createZNodeTree(parentZNode);
319 
320     ZKUtil.deleteChildrenRecursivelyMultiOrSequential(zkw, true, parentZNode);
321 
322     assertTrue("Wrongly deleted parent znode!",
323         ZKUtil.checkExists(zkw, parentZNode) > -1);
324     List<String> children = zkw.getRecoverableZooKeeper().getChildren(
325         parentZNode, false);
326     assertTrue("Failed to delete child znodes!", 0 == children.size());
327   }
328 
329   /**
330    * Verifies that for the given root node, it should delete all the child nodes
331    * recursively using normal sequential way.
332    */
333   @Test (timeout=60000)
334   public void testdeleteChildrenRecursivelySequential() throws Exception {
335     String parentZNode = "/testRootSeq";
336     createZNodeTree(parentZNode);
337     boolean useMulti = zkw.getConfiguration().getBoolean(
338         "hbase.zookeeper.useMulti", false);
339     zkw.getConfiguration().setBoolean("hbase.zookeeper.useMulti", false);
340     try {
341       // disables the multi-update api execution
342       ZKUtil.deleteChildrenRecursivelyMultiOrSequential(zkw, true, parentZNode);
343 
344       assertTrue("Wrongly deleted parent znode!",
345           ZKUtil.checkExists(zkw, parentZNode) > -1);
346       List<String> children = zkw.getRecoverableZooKeeper().getChildren(
347           parentZNode, false);
348       assertTrue("Failed to delete child znodes!", 0 == children.size());
349     } finally {
350       // sets back the multi-update api execution
351       zkw.getConfiguration().setBoolean("hbase.zookeeper.useMulti", useMulti);
352     }
353   }
354 
355   /**
356    * Verifies that for the given root node, it should delete all the nodes recursively using
357    * multi-update api.
358    */
359   @Test(timeout = 60000)
360   public void testDeleteNodeRecursivelyMulti() throws Exception {
361     String parentZNode = "/testdeleteNodeRecursivelyMulti";
362     createZNodeTree(parentZNode);
363 
364     ZKUtil.deleteNodeRecursively(zkw, parentZNode);
365     assertTrue("Parent znode should be deleted.", ZKUtil.checkExists(zkw, parentZNode) == -1);
366   }
367 
368   /**
369    * Verifies that for the given root node, it should delete all the nodes recursively using
370    * normal sequential way.
371    */
372   @Test(timeout = 60000)
373   public void testDeleteNodeRecursivelySequential() throws Exception {
374     String parentZNode = "/testdeleteNodeRecursivelySequential";
375     createZNodeTree(parentZNode);
376     boolean useMulti = zkw.getConfiguration().getBoolean("hbase.zookeeper.useMulti", false);
377     zkw.getConfiguration().setBoolean("hbase.zookeeper.useMulti", false);
378     try {
379       // disables the multi-update api execution
380       ZKUtil.deleteNodeRecursively(zkw, parentZNode);
381       assertTrue("Parent znode should be deleted.", ZKUtil.checkExists(zkw, parentZNode) == -1);
382     } finally {
383       // sets back the multi-update api execution
384       zkw.getConfiguration().setBoolean("hbase.zookeeper.useMulti", useMulti);
385     }
386   }
387 
388   @Test(timeout = 60000)
389   public void testDeleteNodeRecursivelyMultiOrSequential() throws Exception {
390     String parentZNode1 = "/testdeleteNode1";
391     String parentZNode2 = "/testdeleteNode2";
392     String parentZNode3 = "/testdeleteNode3";
393     createZNodeTree(parentZNode1);
394     createZNodeTree(parentZNode2);
395     createZNodeTree(parentZNode3);
396 
397     ZKUtil.deleteNodeRecursivelyMultiOrSequential(zkw, false, parentZNode1, parentZNode2,
398       parentZNode3);
399     assertTrue("Parent znode 1 should be deleted.", ZKUtil.checkExists(zkw, parentZNode1) == -1);
400     assertTrue("Parent znode 2 should be deleted.", ZKUtil.checkExists(zkw, parentZNode2) == -1);
401     assertTrue("Parent znode 3 should be deleted.", ZKUtil.checkExists(zkw, parentZNode3) == -1);
402   }
403 
404   @Test(timeout = 60000)
405   public void testDeleteChildrenRecursivelyMultiOrSequential() throws Exception {
406     String parentZNode1 = "/testdeleteChildren1";
407     String parentZNode2 = "/testdeleteChildren2";
408     String parentZNode3 = "/testdeleteChildren3";
409     createZNodeTree(parentZNode1);
410     createZNodeTree(parentZNode2);
411     createZNodeTree(parentZNode3);
412 
413     ZKUtil.deleteChildrenRecursivelyMultiOrSequential(zkw, true, parentZNode1, parentZNode2,
414       parentZNode3);
415 
416     assertTrue("Wrongly deleted parent znode 1!", ZKUtil.checkExists(zkw, parentZNode1) > -1);
417     List<String> children = zkw.getRecoverableZooKeeper().getChildren(parentZNode1, false);
418     assertTrue("Failed to delete child znodes of parent znode 1!", 0 == children.size());
419 
420     assertTrue("Wrongly deleted parent znode 2!", ZKUtil.checkExists(zkw, parentZNode2) > -1);
421     children = zkw.getRecoverableZooKeeper().getChildren(parentZNode2, false);
422     assertTrue("Failed to delete child znodes of parent znode 1!", 0 == children.size());
423 
424     assertTrue("Wrongly deleted parent znode 3!", ZKUtil.checkExists(zkw, parentZNode3) > -1);
425     children = zkw.getRecoverableZooKeeper().getChildren(parentZNode3, false);
426     assertTrue("Failed to delete child znodes of parent znode 1!", 0 == children.size());
427   }
428 
429   @Test
430   public void testBatchedDeletesOfWideZNodes() throws Exception {
431     // Batch every 50bytes
432     final int batchSize = 50;
433     Configuration localConf = new Configuration(TEST_UTIL.getConfiguration());
434     localConf.setInt("zookeeper.multi.max.size", batchSize);
435     try (ZooKeeperWatcher customZkw = new ZooKeeperWatcher(localConf,
436       "TestZKMulti_Custom", new ZKMultiAbortable(), true)) {
437 
438       // With a parent znode like this, we'll get batches of 2-3 elements
439       final String parent1 = "/batchedDeletes1";
440       final String parent2 = "/batchedDeletes2";
441       final byte[] EMPTY_BYTES = new byte[0];
442 
443       // Write one node
444       List<Op> ops = new ArrayList<>();
445       ops.add(Op.create(parent1, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
446       for (int i = 0; i < batchSize * 2; i++) {
447         ops.add(Op.create(
448             parent1 + "/" + i, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
449       }
450       customZkw.getRecoverableZooKeeper().multi(ops);
451 
452       // Write into a second node
453       ops.clear();
454       ops.add(Op.create(parent2, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
455       for (int i = 0; i < batchSize * 4; i++) {
456         ops.add(Op.create(
457             parent2 + "/" + i, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
458       }
459       customZkw.getRecoverableZooKeeper().multi(ops);
460 
461       // These should return successfully
462       ZKUtil.deleteChildrenRecursively(customZkw, parent1);
463       ZKUtil.deleteChildrenRecursively(customZkw, parent2);
464     }
465   }
466 
467   @Test
468   public void testListPartitioning() {
469     // 10 Bytes
470     ZKUtilOp tenByteOp = ZKUtilOp.deleteNodeFailSilent("/123456789");
471 
472     // Simple, single element case
473     assertEquals(Collections.singletonList(Collections.singletonList(tenByteOp)),
474         ZKUtil.partitionOps(Collections.singletonList(tenByteOp), 15));
475 
476     // Simple case where we exceed the limit, but must make the list
477     assertEquals(Collections.singletonList(Collections.singletonList(tenByteOp)),
478         ZKUtil.partitionOps(Collections.singletonList(tenByteOp), 5));
479 
480     // Each gets its own bucket
481     assertEquals(
482         Arrays.asList(Arrays.asList(tenByteOp), Arrays.asList(tenByteOp), Arrays.asList(tenByteOp)),
483         ZKUtil.partitionOps(Arrays.asList(tenByteOp, tenByteOp, tenByteOp), 15));
484 
485     // Test internal boundary
486     assertEquals(
487         Arrays.asList(Arrays.asList(tenByteOp,tenByteOp), Arrays.asList(tenByteOp)),
488         ZKUtil.partitionOps(Arrays.asList(tenByteOp, tenByteOp, tenByteOp), 20));
489 
490     // Plenty of space for one partition
491     assertEquals(
492         Arrays.asList(Arrays.asList(tenByteOp, tenByteOp, tenByteOp)),
493         ZKUtil.partitionOps(Arrays.asList(tenByteOp, tenByteOp, tenByteOp), 50));
494   }
495 
496   private void createZNodeTree(String rootZNode) throws KeeperException,
497       InterruptedException {
498     List<Op> opList = new ArrayList<Op>();
499     opList.add(Op.create(rootZNode, new byte[0], Ids.OPEN_ACL_UNSAFE,
500         CreateMode.PERSISTENT));
501     int level = 0;
502     String parentZNode = rootZNode;
503     while (level < 10) {
504       // define parent node
505       parentZNode = parentZNode + "/" + level;
506       opList.add(Op.create(parentZNode, new byte[0], Ids.OPEN_ACL_UNSAFE,
507           CreateMode.PERSISTENT));
508       int elements = 0;
509       // add elements to the parent node
510       while (elements < level) {
511         opList.add(Op.create(parentZNode + "/" + elements, new byte[0],
512             Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
513         elements++;
514       }
515       level++;
516     }
517     zkw.getRecoverableZooKeeper().multi(opList);
518   }
519 }