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.hamcrest.CoreMatchers.instanceOf; 021import static org.junit.Assert.assertArrayEquals; 022import static org.junit.Assert.assertEquals; 023import static org.junit.Assert.assertNotEquals; 024import static org.junit.Assert.assertNotNull; 025import static org.junit.Assert.assertNotSame; 026import static org.junit.Assert.assertNull; 027import static org.junit.Assert.assertSame; 028import static org.junit.Assert.assertThat; 029import static org.junit.Assert.fail; 030import static org.mockito.ArgumentMatchers.any; 031import static org.mockito.ArgumentMatchers.anyBoolean; 032import static org.mockito.ArgumentMatchers.anyString; 033import static org.mockito.Mockito.doAnswer; 034import static org.mockito.Mockito.mock; 035import static org.mockito.Mockito.never; 036import static org.mockito.Mockito.times; 037import static org.mockito.Mockito.verify; 038import static org.mockito.Mockito.when; 039 040import java.io.IOException; 041import java.util.concurrent.CompletableFuture; 042import java.util.concurrent.Exchanger; 043import java.util.concurrent.ExecutionException; 044import java.util.concurrent.ThreadLocalRandom; 045import org.apache.hadoop.conf.Configuration; 046import org.apache.hadoop.hbase.HBaseClassTestRule; 047import org.apache.hadoop.hbase.HBaseZKTestingUtility; 048import org.apache.hadoop.hbase.HConstants; 049import org.apache.hadoop.hbase.Waiter.ExplainingPredicate; 050import org.apache.hadoop.hbase.testclassification.MediumTests; 051import org.apache.hadoop.hbase.testclassification.ZKTests; 052import org.apache.zookeeper.AsyncCallback; 053import org.apache.zookeeper.CreateMode; 054import org.apache.zookeeper.KeeperException; 055import org.apache.zookeeper.KeeperException.Code; 056import org.apache.zookeeper.ZooDefs; 057import org.apache.zookeeper.ZooKeeper; 058import org.junit.AfterClass; 059import org.junit.BeforeClass; 060import org.junit.ClassRule; 061import org.junit.Test; 062import org.junit.experimental.categories.Category; 063 064@Category({ ZKTests.class, MediumTests.class }) 065public class TestReadOnlyZKClient { 066 067 @ClassRule 068 public static final HBaseClassTestRule CLASS_RULE = 069 HBaseClassTestRule.forClass(TestReadOnlyZKClient.class); 070 071 private static HBaseZKTestingUtility UTIL = new HBaseZKTestingUtility(); 072 073 private static int PORT; 074 075 private static String PATH = "/test"; 076 077 private static byte[] DATA; 078 079 private static int CHILDREN = 5; 080 081 private static ReadOnlyZKClient RO_ZK; 082 083 @BeforeClass 084 public static void setUp() throws Exception { 085 PORT = UTIL.startMiniZKCluster().getClientPort(); 086 087 ZooKeeper zk = ZooKeeperHelper.getConnectedZooKeeper("localhost:" + PORT, 10000); 088 DATA = new byte[10]; 089 ThreadLocalRandom.current().nextBytes(DATA); 090 zk.create(PATH, DATA, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 091 for (int i = 0; i < CHILDREN; i++) { 092 zk.create(PATH + "/c" + i, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); 093 } 094 zk.close(); 095 Configuration conf = UTIL.getConfiguration(); 096 conf.set(HConstants.ZOOKEEPER_QUORUM, "localhost:" + PORT); 097 conf.setInt(ReadOnlyZKClient.RECOVERY_RETRY, 3); 098 conf.setInt(ReadOnlyZKClient.RECOVERY_RETRY_INTERVAL_MILLIS, 100); 099 conf.setInt(ReadOnlyZKClient.KEEPALIVE_MILLIS, 3000); 100 RO_ZK = new ReadOnlyZKClient(conf); 101 // only connect when necessary 102 assertNull(RO_ZK.zookeeper); 103 } 104 105 @AfterClass 106 public static void tearDown() throws IOException { 107 RO_ZK.close(); 108 UTIL.shutdownMiniZKCluster(); 109 UTIL.cleanupTestDir(); 110 } 111 112 private void waitForIdleConnectionClosed() throws Exception { 113 // The zookeeper client should be closed finally after the keep alive time elapsed 114 UTIL.waitFor(10000, new ExplainingPredicate<Exception>() { 115 116 @Override 117 public boolean evaluate() throws Exception { 118 return RO_ZK.zookeeper == null; 119 } 120 121 @Override 122 public String explainFailure() throws Exception { 123 return "Connection to zookeeper is still alive"; 124 } 125 }); 126 } 127 128 @Test 129 public void testGetAndExists() throws Exception { 130 assertArrayEquals(DATA, RO_ZK.get(PATH).get()); 131 assertEquals(CHILDREN, RO_ZK.exists(PATH).get().getNumChildren()); 132 assertNotNull(RO_ZK.zookeeper); 133 waitForIdleConnectionClosed(); 134 } 135 136 @Test 137 public void testNoNode() throws InterruptedException, ExecutionException { 138 String pathNotExists = PATH + "_whatever"; 139 try { 140 RO_ZK.get(pathNotExists).get(); 141 fail("should fail because of " + pathNotExists + " does not exist"); 142 } catch (ExecutionException e) { 143 assertThat(e.getCause(), instanceOf(KeeperException.class)); 144 KeeperException ke = (KeeperException) e.getCause(); 145 assertEquals(Code.NONODE, ke.code()); 146 assertEquals(pathNotExists, ke.getPath()); 147 } 148 // exists will not throw exception. 149 assertNull(RO_ZK.exists(pathNotExists).get()); 150 } 151 152 @Test 153 public void testSessionExpire() throws Exception { 154 assertArrayEquals(DATA, RO_ZK.get(PATH).get()); 155 ZooKeeper zk = RO_ZK.zookeeper; 156 long sessionId = zk.getSessionId(); 157 UTIL.getZkCluster().getZooKeeperServers().get(0).closeSession(sessionId); 158 // should not reach keep alive so still the same instance 159 assertSame(zk, RO_ZK.zookeeper); 160 byte[] got = RO_ZK.get(PATH).get(); 161 assertArrayEquals(DATA, got); 162 assertNotNull(RO_ZK.zookeeper); 163 assertNotSame(zk, RO_ZK.zookeeper); 164 assertNotEquals(sessionId, RO_ZK.zookeeper.getSessionId()); 165 } 166 167 @Test 168 public void testNotCloseZkWhenPending() throws Exception { 169 ZooKeeper mockedZK = mock(ZooKeeper.class); 170 Exchanger<AsyncCallback.DataCallback> exchanger = new Exchanger<>(); 171 doAnswer(i -> { 172 exchanger.exchange(i.getArgument(2)); 173 return null; 174 }).when(mockedZK).getData(anyString(), anyBoolean(), 175 any(AsyncCallback.DataCallback.class), any()); 176 doAnswer(i -> null).when(mockedZK).close(); 177 when(mockedZK.getState()).thenReturn(ZooKeeper.States.CONNECTED); 178 RO_ZK.zookeeper = mockedZK; 179 CompletableFuture<byte[]> future = RO_ZK.get(PATH); 180 AsyncCallback.DataCallback callback = exchanger.exchange(null); 181 // 2 * keep alive time to ensure that we will not close the zk when there are pending requests 182 Thread.sleep(6000); 183 assertNotNull(RO_ZK.zookeeper); 184 verify(mockedZK, never()).close(); 185 callback.processResult(Code.OK.intValue(), PATH, null, DATA, null); 186 assertArrayEquals(DATA, future.get()); 187 // now we will close the idle connection. 188 waitForIdleConnectionClosed(); 189 verify(mockedZK, times(1)).close(); 190 } 191}