View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.master;
20  
21  import static org.junit.Assert.assertEquals;
22  
23  import java.io.IOException;
24  import java.util.ArrayList;
25  import java.util.HashMap;
26  import java.util.List;
27  import java.util.concurrent.atomic.AtomicInteger;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.Abortable;
33  import org.apache.hadoop.hbase.HBaseTestingUtility;
34  import org.apache.hadoop.hbase.HConstants;
35  import org.apache.hadoop.hbase.HRegionInfo;
36  import org.apache.hadoop.hbase.testclassification.MediumTests;
37  import org.apache.hadoop.hbase.MiniHBaseCluster;
38  import org.apache.hadoop.hbase.TableName;
39  import org.apache.hadoop.hbase.ServerLoad;
40  import org.apache.hadoop.hbase.ServerName;
41  import org.apache.hadoop.hbase.client.Admin;
42  import org.apache.hadoop.hbase.client.HTable;
43  import org.apache.hadoop.hbase.client.Put;
44  import org.apache.hadoop.hbase.client.Table;
45  import org.apache.hadoop.hbase.regionserver.HRegion;
46  import org.apache.hadoop.hbase.regionserver.Region;
47  import org.apache.hadoop.hbase.util.Bytes;
48  import org.apache.hadoop.hbase.util.JVMClusterUtil;
49  import org.apache.hadoop.hbase.zookeeper.DrainingServerTracker;
50  import org.apache.hadoop.hbase.zookeeper.RegionServerTracker;
51  import org.apache.hadoop.hbase.zookeeper.ZKUtil;
52  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
53  import org.junit.AfterClass;
54  import org.junit.Assert;
55  import org.junit.BeforeClass;
56  import org.junit.Test;
57  import org.junit.experimental.categories.Category;
58  import org.mockito.Mockito;
59  
60  @Category(MediumTests.class)
61  public class TestAssignmentListener {
62    private static final Log LOG = LogFactory.getLog(TestAssignmentListener.class);
63  
64    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
65    private static final Abortable abortable = new Abortable() {
66      @Override
67      public boolean isAborted() {
68        return false;
69      }
70  
71      @Override
72      public void abort(String why, Throwable e) {
73      }
74    };
75  
76    static class DummyListener {
77      protected AtomicInteger modified = new AtomicInteger(0);
78  
79      public void awaitModifications(int count) throws InterruptedException {
80        while (!modified.compareAndSet(count, 0)) {
81          Thread.sleep(100);
82        }
83      }
84    }
85  
86    static class DummyAssignmentListener extends DummyListener implements AssignmentListener {
87      private AtomicInteger closeCount = new AtomicInteger(0);
88      private AtomicInteger openCount = new AtomicInteger(0);
89  
90      public DummyAssignmentListener() {
91      }
92  
93      @Override
94      public void regionOpened(final HRegionInfo regionInfo, final ServerName serverName) {
95        LOG.info("Assignment open region=" + regionInfo + " server=" + serverName);
96        openCount.incrementAndGet();
97        modified.incrementAndGet();
98      }
99  
100     @Override
101     public void regionClosed(final HRegionInfo regionInfo) {
102       LOG.info("Assignment close region=" + regionInfo);
103       closeCount.incrementAndGet();
104       modified.incrementAndGet();
105     }
106 
107     public void reset() {
108       openCount.set(0);
109       closeCount.set(0);
110     }
111 
112     public int getLoadCount() {
113       return openCount.get();
114     }
115 
116     public int getCloseCount() {
117       return closeCount.get();
118     }
119   }
120 
121   static class DummyServerListener extends DummyListener implements ServerListener {
122     private AtomicInteger removedCount = new AtomicInteger(0);
123     private AtomicInteger addedCount = new AtomicInteger(0);
124 
125     public DummyServerListener() {
126     }
127 
128     @Override
129     public void serverAdded(final ServerName serverName) {
130       LOG.info("Server added " + serverName);
131       addedCount.incrementAndGet();
132       modified.incrementAndGet();
133     }
134 
135     @Override
136     public void serverRemoved(final ServerName serverName) {
137       LOG.info("Server removed " + serverName);
138       removedCount.incrementAndGet();
139       modified.incrementAndGet();
140     }
141 
142     public void reset() {
143       addedCount.set(0);
144       removedCount.set(0);
145     }
146 
147     public int getAddedCount() {
148       return addedCount.get();
149     }
150 
151     public int getRemovedCount() {
152       return removedCount.get();
153     }
154   }
155 
156   @BeforeClass
157   public static void beforeAllTests() throws Exception {
158     TEST_UTIL.startMiniCluster(2);
159   }
160 
161   @AfterClass
162   public static void afterAllTests() throws Exception {
163     TEST_UTIL.shutdownMiniCluster();
164   }
165 
166   @Test(timeout=60000)
167   public void testServerListener() throws IOException, InterruptedException {
168     ServerManager serverManager = TEST_UTIL.getHBaseCluster().getMaster().getServerManager();
169 
170     DummyServerListener listener = new DummyServerListener();
171     serverManager.registerListener(listener);
172     try {
173       MiniHBaseCluster miniCluster = TEST_UTIL.getMiniHBaseCluster();
174 
175       // Start a new Region Server
176       miniCluster.startRegionServer();
177       listener.awaitModifications(1);
178       assertEquals(1, listener.getAddedCount());
179       assertEquals(0, listener.getRemovedCount());
180 
181       // Start another Region Server
182       listener.reset();
183       miniCluster.startRegionServer();
184       listener.awaitModifications(1);
185       assertEquals(1, listener.getAddedCount());
186       assertEquals(0, listener.getRemovedCount());
187 
188       int nrs = miniCluster.getRegionServerThreads().size();
189 
190       // Stop a Region Server
191       listener.reset();
192       miniCluster.stopRegionServer(nrs - 1);
193       listener.awaitModifications(1);
194       assertEquals(0, listener.getAddedCount());
195       assertEquals(1, listener.getRemovedCount());
196 
197       // Stop another Region Server
198       listener.reset();
199       miniCluster.stopRegionServer(nrs - 2);
200       listener.awaitModifications(1);
201       assertEquals(0, listener.getAddedCount());
202       assertEquals(1, listener.getRemovedCount());
203     } finally {
204       serverManager.unregisterListener(listener);
205     }
206   }
207 
208   @Test(timeout=60000)
209   public void testAssignmentListener() throws IOException, InterruptedException {
210     AssignmentManager am = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager();
211     Admin admin = TEST_UTIL.getHBaseAdmin();
212 
213     DummyAssignmentListener listener = new DummyAssignmentListener();
214     am.registerListener(listener);
215     try {
216       final String TABLE_NAME_STR = "testtb";
217       final TableName TABLE_NAME = TableName.valueOf(TABLE_NAME_STR);
218       final byte[] FAMILY = Bytes.toBytes("cf");
219 
220       // Create a new table, with a single region
221       LOG.info("Create Table");
222       TEST_UTIL.createTable(TABLE_NAME, FAMILY);
223       listener.awaitModifications(1);
224       assertEquals(1, listener.getLoadCount());
225       assertEquals(0, listener.getCloseCount());
226 
227       // Add some data
228       Table table = new HTable(TEST_UTIL.getConfiguration(), TABLE_NAME);
229       try {
230         for (int i = 0; i < 10; ++i) {
231           byte[] key = Bytes.toBytes("row-" + i);
232           Put put = new Put(key);
233           put.add(FAMILY, null, key);
234           table.put(put);
235         }
236       } finally {
237         table.close();
238       }
239 
240       // Split the table in two
241       LOG.info("Split Table");
242       listener.reset();
243       admin.split(TABLE_NAME, Bytes.toBytes("row-3"));
244       listener.awaitModifications(3);
245       assertEquals(2, listener.getLoadCount());     // daughters added
246       assertEquals(1, listener.getCloseCount());    // parent removed
247 
248       // Wait for the Regions to be mergeable
249       MiniHBaseCluster miniCluster = TEST_UTIL.getMiniHBaseCluster();
250       int mergeable = 0;
251       while (mergeable < 2) {
252         Thread.sleep(100);
253         admin.majorCompact(TABLE_NAME);
254         mergeable = 0;
255         for (JVMClusterUtil.RegionServerThread regionThread: miniCluster.getRegionServerThreads()) {
256           for (Region region: regionThread.getRegionServer().getOnlineRegions(TABLE_NAME)) {
257             mergeable += ((HRegion)region).isMergeable() ? 1 : 0;
258           }
259         }
260       }
261 
262       // Merge the two regions
263       LOG.info("Merge Regions");
264       listener.reset();
265       List<HRegionInfo> regions = admin.getTableRegions(TABLE_NAME);
266       assertEquals(2, regions.size());
267       admin.mergeRegions(regions.get(0).getEncodedNameAsBytes(),
268         regions.get(1).getEncodedNameAsBytes(), true);
269       listener.awaitModifications(3);
270       assertEquals(1, admin.getTableRegions(TABLE_NAME).size());
271       assertEquals(1, listener.getLoadCount());     // new merged region added
272       assertEquals(2, listener.getCloseCount());    // daughters removed
273 
274       // Delete the table
275       LOG.info("Drop Table");
276       listener.reset();
277       TEST_UTIL.deleteTable(TABLE_NAME);
278       listener.awaitModifications(1);
279       assertEquals(0, listener.getLoadCount());
280       assertEquals(1, listener.getCloseCount());
281     } finally {
282       am.unregisterListener(listener);
283     }
284   }
285 
286   @Test
287   public void testAddNewServerThatExistsInDraining() throws Exception {
288     // Under certain circumstances, such as when we failover to the Backup
289     // HMaster, the DrainingServerTracker is started with existing servers in
290     // draining before all of the Region Servers register with the
291     // ServerManager as "online".  This test is to ensure that Region Servers
292     // are properly added to the ServerManager.drainingServers when they
293     // register with the ServerManager under these circumstances.
294     Configuration conf = TEST_UTIL.getConfiguration();
295     ZooKeeperWatcher zooKeeper = new ZooKeeperWatcher(conf,
296         "zkWatcher-NewServerDrainTest", abortable, true);
297     String baseZNode = conf.get(HConstants.ZOOKEEPER_ZNODE_PARENT,
298         HConstants.DEFAULT_ZOOKEEPER_ZNODE_PARENT);
299     String drainingZNode = ZKUtil.joinZNode(baseZNode,
300         conf.get("zookeeper.znode.draining.rs", "draining"));
301 
302     MasterServices services = Mockito.mock(MasterServices.class);
303     HMaster master = Mockito.mock(HMaster.class);
304     Mockito.when(master.getConfiguration()).thenReturn(conf);
305 
306     ServerName SERVERNAME_A = ServerName.valueOf("mockserverbulk_a.org", 1000, 8000);
307     ServerName SERVERNAME_B = ServerName.valueOf("mockserverbulk_b.org", 1001, 8000);
308     ServerName SERVERNAME_C = ServerName.valueOf("mockserverbulk_c.org", 1002, 8000);
309 
310     // We'll start with 2 servers in draining that existed before the
311     // HMaster started.
312     ArrayList<ServerName> drainingServers = new ArrayList<ServerName>();
313     drainingServers.add(SERVERNAME_A);
314     drainingServers.add(SERVERNAME_B);
315 
316     // We'll have 2 servers that come online AFTER the DrainingServerTracker
317     // is started (just as we see when we failover to the Backup HMaster).
318     // One of these will already be a draining server.
319     HashMap<ServerName, ServerLoad> onlineServers = new HashMap<ServerName, ServerLoad>();
320     onlineServers.put(SERVERNAME_A, ServerLoad.EMPTY_SERVERLOAD);
321     onlineServers.put(SERVERNAME_C, ServerLoad.EMPTY_SERVERLOAD);
322 
323     // Create draining znodes for the draining servers, which would have been
324     // performed when the previous HMaster was running.
325     for (ServerName sn : drainingServers) {
326       String znode = ZKUtil.joinZNode(drainingZNode, sn.getServerName());
327       ZKUtil.createAndFailSilent(zooKeeper, znode);
328     }
329 
330     // Now, we follow the same order of steps that the HMaster does to setup
331     // the ServerManager, RegionServerTracker, and DrainingServerTracker.
332     ServerManager serverManager = new ServerManager(master, services);
333 
334     RegionServerTracker regionServerTracker = new RegionServerTracker(
335         zooKeeper, master, serverManager);
336     regionServerTracker.start();
337 
338     DrainingServerTracker drainingServerTracker = new DrainingServerTracker(
339         zooKeeper, master, serverManager);
340     drainingServerTracker.start();
341 
342     // Confirm our ServerManager lists are empty.
343     Assert.assertEquals(serverManager.getOnlineServers(),
344         new HashMap<ServerName, ServerLoad>());
345     Assert.assertEquals(serverManager.getDrainingServersList(),
346         new ArrayList<ServerName>());
347 
348     // checkAndRecordNewServer() is how servers are added to the ServerManager.
349     ArrayList<ServerName> onlineDrainingServers = new ArrayList<ServerName>();
350     for (ServerName sn : onlineServers.keySet()){
351       // Here's the actual test.
352       serverManager.checkAndRecordNewServer(sn, onlineServers.get(sn));
353       if (drainingServers.contains(sn)){
354         onlineDrainingServers.add(sn);  // keeping track for later verification
355       }
356     }
357 
358     // Verify the ServerManager lists are correctly updated.
359     Assert.assertEquals(serverManager.getOnlineServers(), onlineServers);
360     Assert.assertEquals(serverManager.getDrainingServersList(),
361         onlineDrainingServers);
362   }
363 }