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.master; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022import static org.junit.jupiter.api.Assertions.assertNotNull; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024import static org.mockito.ArgumentMatchers.any; 025import static org.mockito.Mockito.when; 026 027import java.io.IOException; 028import java.io.InterruptedIOException; 029import java.util.ArrayList; 030import java.util.List; 031import java.util.concurrent.Semaphore; 032import org.apache.hadoop.conf.Configuration; 033import org.apache.hadoop.hbase.HBaseTestingUtil; 034import org.apache.hadoop.hbase.ServerName; 035import org.apache.hadoop.hbase.monitoring.MonitoredTask; 036import org.apache.hadoop.hbase.monitoring.TaskGroup; 037import org.apache.hadoop.hbase.testclassification.MasterTests; 038import org.apache.hadoop.hbase.testclassification.MediumTests; 039import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 040import org.apache.hadoop.hbase.util.MockServer; 041import org.apache.hadoop.hbase.zookeeper.ClusterStatusTracker; 042import org.apache.hadoop.hbase.zookeeper.MasterAddressTracker; 043import org.apache.hadoop.hbase.zookeeper.ZKListener; 044import org.apache.hadoop.hbase.zookeeper.ZKUtil; 045import org.apache.hadoop.hbase.zookeeper.ZKWatcher; 046import org.apache.hadoop.hbase.zookeeper.ZNodePaths; 047import org.apache.zookeeper.KeeperException; 048import org.junit.jupiter.api.AfterAll; 049import org.junit.jupiter.api.BeforeAll; 050import org.junit.jupiter.api.Tag; 051import org.junit.jupiter.api.Test; 052import org.mockito.Mockito; 053import org.slf4j.Logger; 054import org.slf4j.LoggerFactory; 055 056/** 057 * Test the {@link ActiveMasterManager}. 058 */ 059@Tag(MasterTests.TAG) 060@Tag(MediumTests.TAG) 061public class TestActiveMasterManager { 062 063 private final static Logger LOG = LoggerFactory.getLogger(TestActiveMasterManager.class); 064 private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 065 066 @BeforeAll 067 public static void setUpBeforeClass() throws Exception { 068 TEST_UTIL.startMiniZKCluster(); 069 } 070 071 @AfterAll 072 public static void tearDownAfterClass() throws Exception { 073 TEST_UTIL.shutdownMiniZKCluster(); 074 } 075 076 @Test 077 public void testRestartMaster() throws IOException, KeeperException { 078 try (ZKWatcher zk = 079 new ZKWatcher(TEST_UTIL.getConfiguration(), "testActiveMasterManagerFromZK", null, true)) { 080 try { 081 ZKUtil.deleteNode(zk, zk.getZNodePaths().masterAddressZNode); 082 ZKUtil.deleteNode(zk, zk.getZNodePaths().clusterStateZNode); 083 } catch (KeeperException.NoNodeException nne) { 084 } 085 086 // Create the master node with a dummy address 087 ServerName master = ServerName.valueOf("localhost", 1, EnvironmentEdgeManager.currentTime()); 088 // Should not have a master yet 089 DummyMaster dummyMaster = new DummyMaster(zk, master); 090 ClusterStatusTracker clusterStatusTracker = dummyMaster.getClusterStatusTracker(); 091 ActiveMasterManager activeMasterManager = dummyMaster.getActiveMasterManager(); 092 assertFalse(activeMasterManager.clusterHasActiveMaster.get()); 093 assertFalse(activeMasterManager.getActiveMasterServerName().isPresent()); 094 095 // First test becoming the active master uninterrupted 096 TaskGroup status = mockTaskGroup(); 097 clusterStatusTracker.setClusterUp(); 098 099 activeMasterManager.blockUntilBecomingActiveMaster(100, status); 100 assertTrue(activeMasterManager.clusterHasActiveMaster.get()); 101 assertMaster(zk, master); 102 assertMaster(zk, activeMasterManager.getActiveMasterServerName().get()); 103 104 // Now pretend master restart 105 DummyMaster secondDummyMaster = new DummyMaster(zk, master); 106 ActiveMasterManager secondActiveMasterManager = secondDummyMaster.getActiveMasterManager(); 107 assertFalse(secondActiveMasterManager.clusterHasActiveMaster.get()); 108 activeMasterManager.blockUntilBecomingActiveMaster(100, status); 109 assertTrue(activeMasterManager.clusterHasActiveMaster.get()); 110 assertMaster(zk, master); 111 assertMaster(zk, activeMasterManager.getActiveMasterServerName().get()); 112 assertMaster(zk, secondActiveMasterManager.getActiveMasterServerName().get()); 113 } 114 } 115 116 /** 117 * Unit tests that uses ZooKeeper but does not use the master-side methods but rather acts 118 * directly on ZK. 119 */ 120 @Test 121 public void testActiveMasterManagerFromZK() throws Exception { 122 try (ZKWatcher zk = 123 new ZKWatcher(TEST_UTIL.getConfiguration(), "testActiveMasterManagerFromZK", null, true)) { 124 try { 125 ZKUtil.deleteNode(zk, zk.getZNodePaths().masterAddressZNode); 126 ZKUtil.deleteNode(zk, zk.getZNodePaths().clusterStateZNode); 127 } catch (KeeperException.NoNodeException nne) { 128 } 129 130 // Create the master node with a dummy address 131 ServerName firstMasterAddress = 132 ServerName.valueOf("localhost", 1, EnvironmentEdgeManager.currentTime()); 133 ServerName secondMasterAddress = 134 ServerName.valueOf("localhost", 2, EnvironmentEdgeManager.currentTime()); 135 136 // Should not have a master yet 137 DummyMaster ms1 = new DummyMaster(zk, firstMasterAddress); 138 ActiveMasterManager activeMasterManager = ms1.getActiveMasterManager(); 139 assertFalse(activeMasterManager.clusterHasActiveMaster.get()); 140 assertFalse(activeMasterManager.getActiveMasterServerName().isPresent()); 141 142 // First test becoming the active master uninterrupted 143 ClusterStatusTracker clusterStatusTracker = ms1.getClusterStatusTracker(); 144 clusterStatusTracker.setClusterUp(); 145 146 activeMasterManager.blockUntilBecomingActiveMaster(100, mockTaskGroup()); 147 assertTrue(activeMasterManager.clusterHasActiveMaster.get()); 148 assertMaster(zk, firstMasterAddress); 149 assertMaster(zk, activeMasterManager.getActiveMasterServerName().get()); 150 151 // New manager will now try to become the active master in another thread 152 WaitToBeMasterThread t = new WaitToBeMasterThread(zk, secondMasterAddress); 153 t.start(); 154 // Wait for this guy to figure out there is another active master 155 // Wait for 1 second at most 156 int sleeps = 0; 157 while (!t.manager.clusterHasActiveMaster.get() && sleeps < 100) { 158 Thread.sleep(10); 159 sleeps++; 160 } 161 162 // Both should see that there is an active master 163 assertTrue(activeMasterManager.clusterHasActiveMaster.get()); 164 assertTrue(t.manager.clusterHasActiveMaster.get()); 165 // But secondary one should not be the active master 166 assertFalse(t.isActiveMaster); 167 // Verify the active master ServerName is populated in standby master. 168 assertEquals(firstMasterAddress, t.manager.getActiveMasterServerName().get()); 169 170 // Close the first server and delete it's master node 171 ms1.stop("stopping first server"); 172 173 // Use a listener to capture when the node is actually deleted 174 NodeDeletionListener listener = 175 new NodeDeletionListener(zk, zk.getZNodePaths().masterAddressZNode); 176 zk.registerListener(listener); 177 178 LOG.info("Deleting master node"); 179 ZKUtil.deleteNode(zk, zk.getZNodePaths().masterAddressZNode); 180 181 // Wait for the node to be deleted 182 LOG.info("Waiting for active master manager to be notified"); 183 listener.waitForDeletion(); 184 LOG.info("Master node deleted"); 185 186 // Now we expect the secondary manager to have and be the active master 187 // Wait for 1 second at most 188 sleeps = 0; 189 while (!t.isActiveMaster && sleeps < 100) { 190 Thread.sleep(10); 191 sleeps++; 192 } 193 LOG.debug("Slept " + sleeps + " times"); 194 195 assertTrue(t.manager.clusterHasActiveMaster.get()); 196 assertTrue(t.isActiveMaster); 197 assertEquals(secondMasterAddress, t.manager.getActiveMasterServerName().get()); 198 199 LOG.info("Deleting master node"); 200 201 ZKUtil.deleteNode(zk, zk.getZNodePaths().masterAddressZNode); 202 } 203 } 204 205 @Test 206 public void testBackupMasterUpdates() throws Exception { 207 Configuration conf = TEST_UTIL.getConfiguration(); 208 try (ZKWatcher zk = new ZKWatcher(conf, "testBackupMasterUpdates", null, true)) { 209 ServerName sn1 = ServerName.valueOf("localhost", 1, -1); 210 DummyMaster master1 = new DummyMaster(zk, sn1); 211 ActiveMasterManager activeMasterManager = master1.getActiveMasterManager(); 212 activeMasterManager.blockUntilBecomingActiveMaster(100, mockTaskGroup()); 213 assertEquals(sn1, activeMasterManager.getActiveMasterServerName().get()); 214 assertEquals(0, activeMasterManager.getBackupMasters().size()); 215 // Add backup masters 216 List<String> backupZNodes = new ArrayList<>(); 217 for (int i = 1; i <= 10; i++) { 218 ServerName backupSn = ServerName.valueOf("localhost", 1000 + i, -1); 219 String backupZn = 220 ZNodePaths.joinZNode(zk.getZNodePaths().backupMasterAddressesZNode, backupSn.toString()); 221 backupZNodes.add(backupZn); 222 MasterAddressTracker.setMasterAddress(zk, backupZn, backupSn, 1234); 223 TEST_UTIL.waitFor(10000, 224 () -> activeMasterManager.getBackupMasters().size() == backupZNodes.size()); 225 } 226 // Remove backup masters 227 int numBackups = backupZNodes.size(); 228 for (String backupZNode : backupZNodes) { 229 ZKUtil.deleteNode(zk, backupZNode); 230 final int currentBackups = --numBackups; 231 TEST_UTIL.waitFor(10000, 232 () -> activeMasterManager.getBackupMasters().size() == currentBackups); 233 } 234 } 235 } 236 237 /** 238 * Assert there is an active master and that it has the specified address. 239 * @param zk single Zookeeper watcher 240 * @param expectedAddress the expected address of the master 241 * @throws KeeperException unexpected Zookeeper exception 242 * @throws IOException if an IO problem is encountered 243 */ 244 private void assertMaster(ZKWatcher zk, ServerName expectedAddress) 245 throws KeeperException, IOException { 246 ServerName readAddress = MasterAddressTracker.getMasterAddress(zk); 247 assertNotNull(readAddress); 248 assertEquals(expectedAddress, readAddress); 249 } 250 251 public static class WaitToBeMasterThread extends Thread { 252 253 ActiveMasterManager manager; 254 DummyMaster dummyMaster; 255 boolean isActiveMaster; 256 257 public WaitToBeMasterThread(ZKWatcher zk, ServerName address) throws InterruptedIOException { 258 this.dummyMaster = new DummyMaster(zk, address); 259 this.manager = this.dummyMaster.getActiveMasterManager(); 260 isActiveMaster = false; 261 } 262 263 @Override 264 public void run() { 265 manager.blockUntilBecomingActiveMaster(100, mockTaskGroup()); 266 LOG.info("Second master has become the active master!"); 267 isActiveMaster = true; 268 } 269 } 270 271 private static TaskGroup mockTaskGroup() { 272 TaskGroup taskGroup = Mockito.mock(TaskGroup.class); 273 MonitoredTask task = Mockito.mock(MonitoredTask.class); 274 when(taskGroup.addTask(any())).thenReturn(task); 275 return taskGroup; 276 } 277 278 public static class NodeDeletionListener extends ZKListener { 279 private static final Logger LOG = LoggerFactory.getLogger(NodeDeletionListener.class); 280 281 private Semaphore lock; 282 private String node; 283 284 public NodeDeletionListener(ZKWatcher watcher, String node) { 285 super(watcher); 286 lock = new Semaphore(0); 287 this.node = node; 288 } 289 290 @Override 291 public void nodeDeleted(String path) { 292 if (path.equals(node)) { 293 LOG.debug("nodeDeleted(" + path + ")"); 294 lock.release(); 295 } 296 } 297 298 public void waitForDeletion() throws InterruptedException { 299 lock.acquire(); 300 } 301 } 302 303 /** 304 * Dummy Master Implementation. 305 */ 306 public static class DummyMaster extends MockServer { 307 private ClusterStatusTracker clusterStatusTracker; 308 private ActiveMasterManager activeMasterManager; 309 310 public DummyMaster(ZKWatcher zk, ServerName master) throws InterruptedIOException { 311 this.clusterStatusTracker = new ClusterStatusTracker(zk, this); 312 clusterStatusTracker.start(); 313 314 this.activeMasterManager = new ActiveMasterManager(zk, master, this); 315 zk.registerListener(activeMasterManager); 316 } 317 318 public ClusterStatusTracker getClusterStatusTracker() { 319 return clusterStatusTracker; 320 } 321 322 public ActiveMasterManager getActiveMasterManager() { 323 return activeMasterManager; 324 } 325 } 326}