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}