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