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