001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.zookeeper;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertNotEquals;
022import static org.junit.Assert.assertTrue;
023
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collections;
027import java.util.LinkedList;
028import java.util.List;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.Abortable;
031import org.apache.hadoop.hbase.HBaseClassTestRule;
032import org.apache.hadoop.hbase.HBaseZKTestingUtility;
033import org.apache.hadoop.hbase.testclassification.MediumTests;
034import org.apache.hadoop.hbase.testclassification.ZKTests;
035import org.apache.hadoop.hbase.util.Bytes;
036import org.apache.hadoop.hbase.zookeeper.ZKUtil.ZKUtilOp;
037import org.apache.zookeeper.CreateMode;
038import org.apache.zookeeper.KeeperException;
039import org.apache.zookeeper.Op;
040import org.apache.zookeeper.ZooDefs.Ids;
041import org.junit.AfterClass;
042import org.junit.BeforeClass;
043import org.junit.ClassRule;
044import org.junit.Test;
045import org.junit.experimental.categories.Category;
046import org.slf4j.Logger;
047import org.slf4j.LoggerFactory;
048
049/**
050 * Test ZooKeeper multi-update functionality.
051 */
052@Category({ ZKTests.class, MediumTests.class })
053public class TestZKMulti {
054  @ClassRule
055  public static final HBaseClassTestRule CLASS_RULE =
056    HBaseClassTestRule.forClass(TestZKMulti.class);
057
058  private static final Logger LOG = LoggerFactory.getLogger(TestZKMulti.class);
059  private final static HBaseZKTestingUtility TEST_UTIL = new HBaseZKTestingUtility();
060  private static ZKWatcher zkw = null;
061
062  private static class ZKMultiAbortable implements Abortable {
063    @Override
064    public void abort(String why, Throwable e) {
065      LOG.info(why, e);
066    }
067
068    @Override
069    public boolean isAborted() {
070      return false;
071    }
072  }
073
074  @BeforeClass
075  public static void setUpBeforeClass() throws Exception {
076    TEST_UTIL.startMiniZKCluster();
077    Configuration conf = TEST_UTIL.getConfiguration();
078    Abortable abortable = new ZKMultiAbortable();
079    zkw = new ZKWatcher(conf, "TestZKMulti", abortable, true);
080  }
081
082  @AfterClass
083  public static void tearDownAfterClass() throws Exception {
084    TEST_UTIL.shutdownMiniZKCluster();
085  }
086
087  @Test
088  public void testSimpleMulti() throws Exception {
089    // null multi
090    ZKUtil.multiOrSequential(zkw, null, false);
091
092    // empty multi
093    ZKUtil.multiOrSequential(zkw, new LinkedList<>(), false);
094
095    // single create
096    String path = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testSimpleMulti");
097    LinkedList<ZKUtilOp> singleCreate = new LinkedList<>();
098    singleCreate.add(ZKUtilOp.createAndFailSilent(path, new byte[0]));
099    ZKUtil.multiOrSequential(zkw, singleCreate, false);
100    assertTrue(ZKUtil.checkExists(zkw, path) != -1);
101
102    // single setdata
103    LinkedList<ZKUtilOp> singleSetData = new LinkedList<>();
104    byte[] data = Bytes.toBytes("foobar");
105    singleSetData.add(ZKUtilOp.setData(path, data));
106    ZKUtil.multiOrSequential(zkw, singleSetData, false);
107    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path), data));
108
109    // single delete
110    LinkedList<ZKUtilOp> singleDelete = new LinkedList<>();
111    singleDelete.add(ZKUtilOp.deleteNodeFailSilent(path));
112    ZKUtil.multiOrSequential(zkw, singleDelete, false);
113    assertEquals(-1, ZKUtil.checkExists(zkw, path));
114  }
115
116  @Test
117  public void testComplexMulti() throws Exception {
118    String path1 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testComplexMulti1");
119    String path2 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testComplexMulti2");
120    String path3 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testComplexMulti3");
121    String path4 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testComplexMulti4");
122    String path5 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testComplexMulti5");
123    String path6 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testComplexMulti6");
124    // create 4 nodes that we'll setData on or delete later
125    LinkedList<ZKUtilOp> create4Nodes = new LinkedList<>();
126    create4Nodes.add(ZKUtilOp.createAndFailSilent(path1, Bytes.toBytes(path1)));
127    create4Nodes.add(ZKUtilOp.createAndFailSilent(path2, Bytes.toBytes(path2)));
128    create4Nodes.add(ZKUtilOp.createAndFailSilent(path3, Bytes.toBytes(path3)));
129    create4Nodes.add(ZKUtilOp.createAndFailSilent(path4, Bytes.toBytes(path4)));
130    ZKUtil.multiOrSequential(zkw, create4Nodes, false);
131    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path1), Bytes.toBytes(path1)));
132    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path2), Bytes.toBytes(path2)));
133    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path3), Bytes.toBytes(path3)));
134    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path4), Bytes.toBytes(path4)));
135
136    // do multiple of each operation (setData, delete, create)
137    LinkedList<ZKUtilOp> ops = new LinkedList<>();
138    // setData
139    ops.add(ZKUtilOp.setData(path1, Bytes.add(Bytes.toBytes(path1), Bytes.toBytes(path1))));
140    ops.add(ZKUtilOp.setData(path2, Bytes.add(Bytes.toBytes(path2), Bytes.toBytes(path2))));
141    // delete
142    ops.add(ZKUtilOp.deleteNodeFailSilent(path3));
143    ops.add(ZKUtilOp.deleteNodeFailSilent(path4));
144    // create
145    ops.add(ZKUtilOp.createAndFailSilent(path5, Bytes.toBytes(path5)));
146    ops.add(ZKUtilOp.createAndFailSilent(path6, Bytes.toBytes(path6)));
147    ZKUtil.multiOrSequential(zkw, ops, false);
148    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path1),
149      Bytes.add(Bytes.toBytes(path1), Bytes.toBytes(path1))));
150    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path2),
151      Bytes.add(Bytes.toBytes(path2), Bytes.toBytes(path2))));
152    assertEquals(-1, ZKUtil.checkExists(zkw, path3));
153    assertEquals(-1, ZKUtil.checkExists(zkw, path4));
154    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path5), Bytes.toBytes(path5)));
155    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path6), Bytes.toBytes(path6)));
156  }
157
158  @Test
159  public void testSingleFailure() throws Exception {
160    // try to delete a node that doesn't exist
161    boolean caughtNoNode = false;
162    String path = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testSingleFailureZ");
163    LinkedList<ZKUtilOp> ops = new LinkedList<>();
164    ops.add(ZKUtilOp.deleteNodeFailSilent(path));
165    try {
166      ZKUtil.multiOrSequential(zkw, ops, false);
167    } catch (KeeperException.NoNodeException nne) {
168      caughtNoNode = true;
169    }
170    assertTrue(caughtNoNode);
171
172    // try to setData on a node that doesn't exist
173    caughtNoNode = false;
174    ops = new LinkedList<>();
175    ops.add(ZKUtilOp.setData(path, Bytes.toBytes(path)));
176    try {
177      ZKUtil.multiOrSequential(zkw, ops, false);
178    } catch (KeeperException.NoNodeException nne) {
179      caughtNoNode = true;
180    }
181    assertTrue(caughtNoNode);
182
183    // try to create on a node that already exists
184    boolean caughtNodeExists = false;
185    ops = new LinkedList<>();
186    ops.add(ZKUtilOp.createAndFailSilent(path, Bytes.toBytes(path)));
187    ZKUtil.multiOrSequential(zkw, ops, false);
188    try {
189      ZKUtil.multiOrSequential(zkw, ops, false);
190    } catch (KeeperException.NodeExistsException nee) {
191      caughtNodeExists = true;
192    }
193    assertTrue(caughtNodeExists);
194  }
195
196  @Test
197  public void testSingleFailureInMulti() throws Exception {
198    // try a multi where all but one operation succeeds
199    String pathA = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testSingleFailureInMultiA");
200    String pathB = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testSingleFailureInMultiB");
201    String pathC = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testSingleFailureInMultiC");
202    LinkedList<ZKUtilOp> ops = new LinkedList<>();
203    ops.add(ZKUtilOp.createAndFailSilent(pathA, Bytes.toBytes(pathA)));
204    ops.add(ZKUtilOp.createAndFailSilent(pathB, Bytes.toBytes(pathB)));
205    ops.add(ZKUtilOp.deleteNodeFailSilent(pathC));
206    boolean caughtNoNode = false;
207    try {
208      ZKUtil.multiOrSequential(zkw, ops, false);
209    } catch (KeeperException.NoNodeException nne) {
210      caughtNoNode = true;
211    }
212    assertTrue(caughtNoNode);
213    // assert that none of the operations succeeded
214    assertEquals(-1, ZKUtil.checkExists(zkw, pathA));
215    assertEquals(-1, ZKUtil.checkExists(zkw, pathB));
216    assertEquals(-1, ZKUtil.checkExists(zkw, pathC));
217  }
218
219  @Test
220  public void testMultiFailure() throws Exception {
221    String pathX = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testMultiFailureX");
222    String pathY = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testMultiFailureY");
223    String pathZ = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testMultiFailureZ");
224    // create X that we will use to fail create later
225    LinkedList<ZKUtilOp> ops = new LinkedList<>();
226    ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathX)));
227    ZKUtil.multiOrSequential(zkw, ops, false);
228
229    // fail one of each create ,setData, delete
230    String pathV = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testMultiFailureV");
231    String pathW = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "testMultiFailureW");
232    ops = new LinkedList<>();
233    ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathX))); // fail -- already exists
234    ops.add(ZKUtilOp.setData(pathY, Bytes.toBytes(pathY))); // fail -- doesn't exist
235    ops.add(ZKUtilOp.deleteNodeFailSilent(pathZ)); // fail -- doesn't exist
236    ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathV))); // pass
237    ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathW))); // pass
238    boolean caughtNodeExists = false;
239    try {
240      ZKUtil.multiOrSequential(zkw, ops, false);
241    } catch (KeeperException.NodeExistsException nee) {
242      // check first operation that fails throws exception
243      caughtNodeExists = true;
244    }
245    assertTrue(caughtNodeExists);
246    // check that no modifications were made
247    assertNotEquals(-1, ZKUtil.checkExists(zkw, pathX));
248    assertEquals(-1, ZKUtil.checkExists(zkw, pathY));
249    assertEquals(-1, ZKUtil.checkExists(zkw, pathZ));
250    assertEquals(-1, ZKUtil.checkExists(zkw, pathW));
251    assertEquals(-1, ZKUtil.checkExists(zkw, pathV));
252
253    // test that with multiple failures, throws an exception corresponding to first failure in list
254    ops = new LinkedList<>();
255    ops.add(ZKUtilOp.setData(pathY, Bytes.toBytes(pathY))); // fail -- doesn't exist
256    ops.add(ZKUtilOp.createAndFailSilent(pathX, Bytes.toBytes(pathX))); // fail -- exists
257    boolean caughtNoNode = false;
258    try {
259      ZKUtil.multiOrSequential(zkw, ops, false);
260    } catch (KeeperException.NoNodeException nne) {
261      // check first operation that fails throws exception
262      caughtNoNode = true;
263    }
264    assertTrue(caughtNoNode);
265    // check that no modifications were made
266    assertNotEquals(-1, ZKUtil.checkExists(zkw, pathX));
267    assertEquals(-1, ZKUtil.checkExists(zkw, pathY));
268    assertEquals(-1, ZKUtil.checkExists(zkw, pathZ));
269    assertEquals(-1, ZKUtil.checkExists(zkw, pathW));
270    assertEquals(-1, ZKUtil.checkExists(zkw, pathV));
271  }
272
273  @Test
274  public void testRunSequentialOnMultiFailure() throws Exception {
275    String path1 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "runSequential1");
276    String path2 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "runSequential2");
277    String path3 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "runSequential3");
278    String path4 = ZNodePaths.joinZNode(zkw.getZNodePaths().baseZNode, "runSequential4");
279
280    // create some nodes that we will use later
281    LinkedList<ZKUtilOp> ops = new LinkedList<>();
282    ops.add(ZKUtilOp.createAndFailSilent(path1, Bytes.toBytes(path1)));
283    ops.add(ZKUtilOp.createAndFailSilent(path2, Bytes.toBytes(path2)));
284    ZKUtil.multiOrSequential(zkw, ops, false);
285
286    // test that, even with operations that fail, the ones that would pass will pass
287    // with runSequentialOnMultiFailure
288    ops = new LinkedList<>();
289    // pass
290    ops.add(ZKUtilOp.setData(path1, Bytes.add(Bytes.toBytes(path1), Bytes.toBytes(path1))));
291    // pass
292    ops.add(ZKUtilOp.deleteNodeFailSilent(path2));
293    // fail -- node doesn't exist
294    ops.add(ZKUtilOp.deleteNodeFailSilent(path3));
295    // pass
296    ops.add(
297      ZKUtilOp.createAndFailSilent(path4, Bytes.add(Bytes.toBytes(path4), Bytes.toBytes(path4))));
298    ZKUtil.multiOrSequential(zkw, ops, true);
299    assertTrue(Bytes.equals(ZKUtil.getData(zkw, path1),
300      Bytes.add(Bytes.toBytes(path1), Bytes.toBytes(path1))));
301    assertEquals(-1, ZKUtil.checkExists(zkw, path2));
302    assertEquals(-1, ZKUtil.checkExists(zkw, path3));
303    assertNotEquals(-1, ZKUtil.checkExists(zkw, path4));
304  }
305
306  /**
307   * Verifies that for the given root node, it should delete all the child nodes recursively using
308   * multi-update api.
309   */
310  @Test
311  public void testdeleteChildrenRecursivelyMulti() throws Exception {
312    String parentZNode = "/testRootMulti";
313    createZNodeTree(parentZNode);
314
315    ZKUtil.deleteChildrenRecursivelyMultiOrSequential(zkw, true, parentZNode);
316
317    assertTrue("Wrongly deleted parent znode!", ZKUtil.checkExists(zkw, parentZNode) > -1);
318    List<String> children = zkw.getRecoverableZooKeeper().getChildren(parentZNode, false);
319    assertEquals("Failed to delete child znodes!", 0, children.size());
320  }
321
322  /**
323   * Verifies that for the given root node, it should delete all the nodes recursively using
324   * multi-update api.
325   */
326  @Test
327  public void testDeleteNodeRecursivelyMulti() throws Exception {
328    String parentZNode = "/testdeleteNodeRecursivelyMulti";
329    createZNodeTree(parentZNode);
330
331    ZKUtil.deleteNodeRecursively(zkw, parentZNode);
332    assertEquals("Parent znode should be deleted.", -1, ZKUtil.checkExists(zkw, parentZNode));
333  }
334
335  @Test
336  public void testDeleteNodeRecursivelyMultiOrSequential() throws Exception {
337    String parentZNode1 = "/testdeleteNode1";
338    String parentZNode2 = "/testdeleteNode2";
339    String parentZNode3 = "/testdeleteNode3";
340    createZNodeTree(parentZNode1);
341    createZNodeTree(parentZNode2);
342    createZNodeTree(parentZNode3);
343
344    ZKUtil.deleteNodeRecursivelyMultiOrSequential(zkw, false, parentZNode1, parentZNode2,
345      parentZNode3);
346    assertEquals("Parent znode 1 should be deleted.", -1, ZKUtil.checkExists(zkw, parentZNode1));
347    assertEquals("Parent znode 2 should be deleted.", -1, ZKUtil.checkExists(zkw, parentZNode2));
348    assertEquals("Parent znode 3 should be deleted.", -1, ZKUtil.checkExists(zkw, parentZNode3));
349  }
350
351  @Test
352  public void testDeleteChildrenRecursivelyMultiOrSequential() throws Exception {
353    String parentZNode1 = "/testdeleteChildren1";
354    String parentZNode2 = "/testdeleteChildren2";
355    String parentZNode3 = "/testdeleteChildren3";
356    createZNodeTree(parentZNode1);
357    createZNodeTree(parentZNode2);
358    createZNodeTree(parentZNode3);
359
360    ZKUtil.deleteChildrenRecursivelyMultiOrSequential(zkw, true, parentZNode1, parentZNode2,
361      parentZNode3);
362
363    assertTrue("Wrongly deleted parent znode 1!", ZKUtil.checkExists(zkw, parentZNode1) > -1);
364    List<String> children = zkw.getRecoverableZooKeeper().getChildren(parentZNode1, false);
365    assertEquals("Failed to delete child znodes of parent znode 1!", 0, children.size());
366
367    assertTrue("Wrongly deleted parent znode 2!", ZKUtil.checkExists(zkw, parentZNode2) > -1);
368    children = zkw.getRecoverableZooKeeper().getChildren(parentZNode2, false);
369    assertEquals("Failed to delete child znodes of parent znode 1!", 0, children.size());
370
371    assertTrue("Wrongly deleted parent znode 3!", ZKUtil.checkExists(zkw, parentZNode3) > -1);
372    children = zkw.getRecoverableZooKeeper().getChildren(parentZNode3, false);
373    assertEquals("Failed to delete child znodes of parent znode 1!", 0, children.size());
374  }
375
376  @Test
377  public void testBatchedDeletesOfWideZNodes() throws Exception {
378    // Batch every 50bytes
379    final int batchSize = 50;
380    Configuration localConf = new Configuration(TEST_UTIL.getConfiguration());
381    localConf.setInt("zookeeper.multi.max.size", batchSize);
382    try (ZKWatcher customZkw =
383      new ZKWatcher(localConf, "TestZKMulti_Custom", new ZKMultiAbortable(), true)) {
384
385      // With a parent znode like this, we'll get batches of 2-3 elements
386      final String parent1 = "/batchedDeletes1";
387      final String parent2 = "/batchedDeletes2";
388      final byte[] EMPTY_BYTES = new byte[0];
389
390      // Write one node
391      List<Op> ops = new ArrayList<>();
392      ops.add(Op.create(parent1, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
393      for (int i = 0; i < batchSize * 2; i++) {
394        ops.add(
395          Op.create(parent1 + "/" + i, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
396      }
397      customZkw.getRecoverableZooKeeper().multi(ops);
398
399      // Write into a second node
400      ops.clear();
401      ops.add(Op.create(parent2, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
402      for (int i = 0; i < batchSize * 4; i++) {
403        ops.add(
404          Op.create(parent2 + "/" + i, EMPTY_BYTES, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
405      }
406      customZkw.getRecoverableZooKeeper().multi(ops);
407
408      // These should return successfully
409      ZKUtil.deleteChildrenRecursively(customZkw, parent1);
410      ZKUtil.deleteChildrenRecursively(customZkw, parent2);
411    }
412  }
413
414  @Test
415  public void testListPartitioning() {
416    // 10 Bytes
417    ZKUtilOp tenByteOp = ZKUtilOp.deleteNodeFailSilent("/123456789");
418
419    // Simple, single element case
420    assertEquals(Collections.singletonList(Collections.singletonList(tenByteOp)),
421      ZKUtil.partitionOps(Collections.singletonList(tenByteOp), 15));
422
423    // Simple case where we exceed the limit, but must make the list
424    assertEquals(Collections.singletonList(Collections.singletonList(tenByteOp)),
425      ZKUtil.partitionOps(Collections.singletonList(tenByteOp), 5));
426
427    // Each gets its own bucket
428    assertEquals(
429      Arrays.asList(Collections.singletonList(tenByteOp), Collections.singletonList(tenByteOp),
430        Collections.singletonList(tenByteOp)),
431      ZKUtil.partitionOps(Arrays.asList(tenByteOp, tenByteOp, tenByteOp), 15));
432
433    // Test internal boundary
434    assertEquals(
435      Arrays.asList(Arrays.asList(tenByteOp, tenByteOp), Collections.singletonList(tenByteOp)),
436      ZKUtil.partitionOps(Arrays.asList(tenByteOp, tenByteOp, tenByteOp), 20));
437
438    // Plenty of space for one partition
439    assertEquals(Collections.singletonList(Arrays.asList(tenByteOp, tenByteOp, tenByteOp)),
440      ZKUtil.partitionOps(Arrays.asList(tenByteOp, tenByteOp, tenByteOp), 50));
441  }
442
443  private void createZNodeTree(String rootZNode) throws KeeperException, InterruptedException {
444    List<Op> opList = new ArrayList<>();
445    opList.add(Op.create(rootZNode, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
446    int level = 0;
447    String parentZNode = rootZNode;
448    while (level < 10) {
449      // define parent node
450      parentZNode = parentZNode + "/" + level;
451      opList.add(Op.create(parentZNode, new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT));
452      int elements = 0;
453      // add elements to the parent node
454      while (elements < level) {
455        opList.add(Op.create(parentZNode + "/" + elements, new byte[0], Ids.OPEN_ACL_UNSAFE,
456          CreateMode.PERSISTENT));
457        elements++;
458      }
459      level++;
460    }
461    zkw.getRecoverableZooKeeper().multi(opList);
462  }
463}