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