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.assertNotNull; 022import static org.junit.jupiter.api.Assertions.assertNull; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024import static org.junit.jupiter.api.Assertions.fail; 025import static org.mockito.ArgumentMatchers.any; 026import static org.mockito.ArgumentMatchers.anyBoolean; 027import static org.mockito.ArgumentMatchers.anyList; 028import static org.mockito.ArgumentMatchers.anyString; 029import static org.mockito.Mockito.doAnswer; 030import static org.mockito.Mockito.mock; 031import static org.mockito.Mockito.spy; 032import static org.mockito.Mockito.times; 033import static org.mockito.Mockito.verify; 034import static org.mockito.Mockito.when; 035 036import java.io.IOException; 037import java.util.Arrays; 038import java.util.List; 039import java.util.concurrent.Callable; 040import java.util.concurrent.atomic.AtomicBoolean; 041import org.apache.hadoop.conf.Configuration; 042import org.apache.hadoop.hbase.Abortable; 043import org.apache.hadoop.hbase.HBaseZKTestingUtil; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.hbase.testclassification.ZKTests; 046import org.apache.hadoop.hbase.util.Bytes; 047import org.apache.hadoop.hbase.util.Threads; 048import org.apache.hadoop.hbase.zookeeper.ZKUtil.ZKUtilOp; 049import org.apache.zookeeper.CreateMode; 050import org.apache.zookeeper.KeeperException; 051import org.apache.zookeeper.ZooDefs; 052import org.apache.zookeeper.ZooKeeper; 053import org.apache.zookeeper.data.ACL; 054import org.apache.zookeeper.data.Stat; 055import org.junit.jupiter.api.AfterAll; 056import org.junit.jupiter.api.BeforeAll; 057import org.junit.jupiter.api.Tag; 058import org.junit.jupiter.api.Test; 059import org.mockito.AdditionalAnswers; 060import org.slf4j.Logger; 061import org.slf4j.LoggerFactory; 062 063import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList; 064import org.apache.hbase.thirdparty.com.google.common.io.Closeables; 065 066@Tag(ZKTests.TAG) 067@Tag(MediumTests.TAG) 068public class TestZKUtil { 069 070 private static final Logger LOG = LoggerFactory.getLogger(TestZKUtil.class); 071 072 private static HBaseZKTestingUtil UTIL = new HBaseZKTestingUtil(); 073 074 private static ZKWatcher ZKW; 075 076 @BeforeAll 077 public static void setUp() throws Exception { 078 UTIL.startMiniZKCluster().getClientPort(); 079 ZKW = new ZKWatcher(new Configuration(UTIL.getConfiguration()), TestZKUtil.class.getName(), 080 new WarnOnlyAbortable()); 081 } 082 083 @AfterAll 084 public static void tearDown() throws IOException { 085 Closeables.close(ZKW, true); 086 UTIL.shutdownMiniZKCluster(); 087 UTIL.cleanupTestDir(); 088 } 089 090 /** 091 * Create a znode with data 092 */ 093 @Test 094 public void testCreateWithParents() throws KeeperException, InterruptedException { 095 byte[] expectedData = new byte[] { 1, 2, 3 }; 096 ZKUtil.createWithParents(ZKW, "/l1/l2/l3/l4/testCreateWithParents", expectedData); 097 byte[] data = ZKUtil.getData(ZKW, "/l1/l2/l3/l4/testCreateWithParents"); 098 assertTrue(Bytes.equals(expectedData, data)); 099 ZKUtil.deleteNodeRecursively(ZKW, "/l1"); 100 101 ZKUtil.createWithParents(ZKW, "/testCreateWithParents", expectedData); 102 data = ZKUtil.getData(ZKW, "/testCreateWithParents"); 103 assertTrue(Bytes.equals(expectedData, data)); 104 ZKUtil.deleteNodeRecursively(ZKW, "/testCreateWithParents"); 105 } 106 107 /** 108 * Create a bunch of znodes in a hierarchy, try deleting one that has childs (it will fail), then 109 * delete it recursively, then delete the last znode 110 */ 111 @Test 112 public void testZNodeDeletes() throws Exception { 113 ZKUtil.createWithParents(ZKW, "/l1/l2/l3/l4"); 114 try { 115 ZKUtil.deleteNode(ZKW, "/l1/l2"); 116 fail("We should not be able to delete if znode has childs"); 117 } catch (KeeperException ex) { 118 assertNotNull(ZKUtil.getDataNoWatch(ZKW, "/l1/l2/l3/l4", null)); 119 } 120 ZKUtil.deleteNodeRecursively(ZKW, "/l1/l2"); 121 // make sure it really is deleted 122 assertNull(ZKUtil.getDataNoWatch(ZKW, "/l1/l2/l3/l4", null)); 123 124 // do the same delete again and make sure it doesn't crash 125 ZKUtil.deleteNodeRecursively(ZKW, "/l1/l2"); 126 127 ZKUtil.deleteNode(ZKW, "/l1"); 128 assertNull(ZKUtil.getDataNoWatch(ZKW, "/l1/l2", null)); 129 } 130 131 private int getZNodeDataVersion(String znode) throws KeeperException { 132 Stat stat = new Stat(); 133 ZKUtil.getDataNoWatch(ZKW, znode, stat); 134 return stat.getVersion(); 135 } 136 137 @Test 138 public void testSetDataWithVersion() throws Exception { 139 ZKUtil.createWithParents(ZKW, "/s1/s2/s3"); 140 int v0 = getZNodeDataVersion("/s1/s2/s3"); 141 assertEquals(0, v0); 142 143 ZKUtil.setData(ZKW, "/s1/s2/s3", Bytes.toBytes(12L)); 144 int v1 = getZNodeDataVersion("/s1/s2/s3"); 145 assertEquals(1, v1); 146 147 ZKUtil.multiOrSequential(ZKW, 148 ImmutableList.of(ZKUtilOp.setData("/s1/s2/s3", Bytes.toBytes(13L), v1)), false); 149 int v2 = getZNodeDataVersion("/s1/s2/s3"); 150 assertEquals(2, v2); 151 } 152 153 private <V> V callAndIgnoreTransientError(Callable<V> action) throws Exception { 154 for (;;) { 155 try { 156 return action.call(); 157 } catch (KeeperException e) { 158 switch (e.code()) { 159 case CONNECTIONLOSS: 160 case SESSIONEXPIRED: 161 case OPERATIONTIMEOUT: 162 LOG.warn("Possibly transient ZooKeeper exception", e); 163 Threads.sleep(100); 164 break; 165 default: 166 throw e; 167 } 168 } 169 } 170 } 171 172 /** 173 * A test for HBASE-3238 174 */ 175 @Test 176 public void testCreateSilentIsReallySilent() throws Exception { 177 Configuration c = UTIL.getConfiguration(); 178 179 String aclZnode = "/aclRoot"; 180 String quorumServers = ZKConfig.getZKQuorumServersString(c); 181 int sessionTimeout = 5 * 1000; // 5 seconds 182 try (ZooKeeper zk = new ZooKeeper(quorumServers, sessionTimeout, EmptyWatcher.instance)) { 183 zk.addAuthInfo("digest", Bytes.toBytes("hbase:rox")); 184 185 // Save the previous ACL 186 List<ACL> oldACL = callAndIgnoreTransientError(() -> zk.getACL("/", new Stat())); 187 188 // I set this acl after the attempted creation of the cluster home node. 189 // Add retries in case of retryable zk exceptions. 190 callAndIgnoreTransientError(() -> zk.setACL("/", ZooDefs.Ids.CREATOR_ALL_ACL, -1)); 191 192 ZKWatcher watcher = spy(ZKW); 193 RecoverableZooKeeper rzk = mock(RecoverableZooKeeper.class, 194 AdditionalAnswers.delegatesTo(ZKW.getRecoverableZooKeeper())); 195 when(watcher.getRecoverableZooKeeper()).thenReturn(rzk); 196 AtomicBoolean firstExists = new AtomicBoolean(true); 197 doAnswer(inv -> { 198 String path = inv.getArgument(0); 199 boolean watch = inv.getArgument(1); 200 Stat stat = ZKW.getRecoverableZooKeeper().exists(path, watch); 201 // create the znode after first exists check, this is to simulate that we enter the create 202 // branch but we have no permission for creation, but the znode has been created by others 203 if (firstExists.compareAndSet(true, false)) { 204 callAndIgnoreTransientError(() -> zk.create(aclZnode, null, 205 Arrays.asList(new ACL(ZooDefs.Perms.READ, ZooDefs.Ids.ANYONE_ID_UNSAFE), 206 new ACL(ZooDefs.Perms.ALL, ZooDefs.Ids.AUTH_IDS)), 207 CreateMode.PERSISTENT)); 208 } 209 return stat; 210 }).when(rzk).exists(any(), anyBoolean()); 211 ZKUtil.createAndFailSilent(watcher, aclZnode); 212 // make sure we call the exists method twice and create once 213 verify(rzk, times(2)).exists(any(), anyBoolean()); 214 verify(rzk).create(anyString(), any(), anyList(), any()); 215 // Restore the ACL 216 zk.addAuthInfo("digest", Bytes.toBytes("hbase:rox")); 217 zk.setACL("/", oldACL, -1); 218 } 219 } 220 221 /** 222 * Test should not fail with NPE when getChildDataAndWatchForNewChildren invoked with wrongNode 223 */ 224 @Test 225 @SuppressWarnings("deprecation") 226 public void testGetChildDataAndWatchForNewChildrenShouldNotThrowNPE() throws Exception { 227 ZKUtil.getChildDataAndWatchForNewChildren(ZKW, "/wrongNode"); 228 } 229 230 private static class WarnOnlyAbortable implements Abortable { 231 232 @Override 233 public void abort(String why, Throwable e) { 234 LOG.warn("ZKWatcher received abort, ignoring. Reason: " + why); 235 if (LOG.isDebugEnabled()) { 236 LOG.debug(e.toString(), e); 237 } 238 } 239 240 @Override 241 public boolean isAborted() { 242 return false; 243 } 244 } 245}