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