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     @Override
159     public void waiting() {
160       // TODO Auto-generated method stub
161     }
162   }
163 
164   @BeforeClass
165   public static void beforeAllTests() throws Exception {
166     TEST_UTIL.startMiniCluster(2);
167   }
168 
169   @AfterClass
170   public static void afterAllTests() throws Exception {
171     TEST_UTIL.shutdownMiniCluster();
172   }
173 
174   @Test(timeout=60000)
175   public void testServerListener() throws IOException, InterruptedException {
176     ServerManager serverManager = TEST_UTIL.getHBaseCluster().getMaster().getServerManager();
177 
178     DummyServerListener listener = new DummyServerListener();
179     serverManager.registerListener(listener);
180     try {
181       MiniHBaseCluster miniCluster = TEST_UTIL.getMiniHBaseCluster();
182 
183       // Start a new Region Server
184       miniCluster.startRegionServer();
185       listener.awaitModifications(1);
186       assertEquals(1, listener.getAddedCount());
187       assertEquals(0, listener.getRemovedCount());
188 
189       // Start another Region Server
190       listener.reset();
191       miniCluster.startRegionServer();
192       listener.awaitModifications(1);
193       assertEquals(1, listener.getAddedCount());
194       assertEquals(0, listener.getRemovedCount());
195 
196       int nrs = miniCluster.getRegionServerThreads().size();
197 
198       // Stop a Region Server
199       listener.reset();
200       miniCluster.stopRegionServer(nrs - 1);
201       listener.awaitModifications(1);
202       assertEquals(0, listener.getAddedCount());
203       assertEquals(1, listener.getRemovedCount());
204 
205       // Stop another Region Server
206       listener.reset();
207       miniCluster.stopRegionServer(nrs - 2);
208       listener.awaitModifications(1);
209       assertEquals(0, listener.getAddedCount());
210       assertEquals(1, listener.getRemovedCount());
211     } finally {
212       serverManager.unregisterListener(listener);
213     }
214   }
215 
216   @Test(timeout=60000)
217   public void testAssignmentListener() throws IOException, InterruptedException {
218     AssignmentManager am = TEST_UTIL.getHBaseCluster().getMaster().getAssignmentManager();
219     Admin admin = TEST_UTIL.getHBaseAdmin();
220 
221     DummyAssignmentListener listener = new DummyAssignmentListener();
222     am.registerListener(listener);
223     try {
224       final String TABLE_NAME_STR = "testtb";
225       final TableName TABLE_NAME = TableName.valueOf(TABLE_NAME_STR);
226       final byte[] FAMILY = Bytes.toBytes("cf");
227 
228       // Create a new table, with a single region
229       LOG.info("Create Table");
230       TEST_UTIL.createTable(TABLE_NAME, FAMILY);
231       listener.awaitModifications(1);
232       assertEquals(1, listener.getLoadCount());
233       assertEquals(0, listener.getCloseCount());
234 
235       // Add some data
236       Table table = new HTable(TEST_UTIL.getConfiguration(), TABLE_NAME);
237       try {
238         for (int i = 0; i < 10; ++i) {
239           byte[] key = Bytes.toBytes("row-" + i);
240           Put put = new Put(key);
241           put.add(FAMILY, null, key);
242           table.put(put);
243         }
244       } finally {
245         table.close();
246       }
247 
248       // Split the table in two
249       LOG.info("Split Table");
250       listener.reset();
251       admin.split(TABLE_NAME, Bytes.toBytes("row-3"));
252       listener.awaitModifications(3);
253       assertEquals(2, listener.getLoadCount());     // daughters added
254       assertEquals(1, listener.getCloseCount());    // parent removed
255 
256       // Wait for the Regions to be mergeable
257       MiniHBaseCluster miniCluster = TEST_UTIL.getMiniHBaseCluster();
258       int mergeable = 0;
259       while (mergeable < 2) {
260         Thread.sleep(100);
261         admin.majorCompact(TABLE_NAME);
262 
263         // Waiting for compaction to finish
264         RetryCounter retrier = new RetryCounter(30, 1, TimeUnit.SECONDS);
265         while (CompactionState.NONE != admin.getCompactionState(TABLE_NAME)
266             && retrier.shouldRetry()) {
267           retrier.sleepUntilNextRetry();
268         }
269 
270         // Making sure references are getting cleaned after compaction.
271         // This is taken care by discharger
272         for (HRegion region : TEST_UTIL.getHBaseCluster().getRegions(TABLE_NAME)) {
273           region.getStores().get(0).closeAndArchiveCompactedFiles();
274         }
275 
276         mergeable = 0;
277         for (JVMClusterUtil.RegionServerThread regionThread: miniCluster.getRegionServerThreads()) {
278           for (Region region: regionThread.getRegionServer().getOnlineRegions(TABLE_NAME)) {
279             mergeable += ((HRegion)region).isMergeable() ? 1 : 0;
280           }
281         }
282       }
283 
284       // Merge the two regions
285       LOG.info("Merge Regions");
286       listener.reset();
287       List<HRegionInfo> regions = admin.getTableRegions(TABLE_NAME);
288       assertEquals(2, regions.size());
289       admin.mergeRegions(regions.get(0).getEncodedNameAsBytes(),
290         regions.get(1).getEncodedNameAsBytes(), true);
291       listener.awaitModifications(3);
292       assertEquals(1, admin.getTableRegions(TABLE_NAME).size());
293       assertEquals(1, listener.getLoadCount());     // new merged region added
294       assertEquals(2, listener.getCloseCount());    // daughters removed
295 
296       // Delete the table
297       LOG.info("Drop Table");
298       listener.reset();
299       TEST_UTIL.deleteTable(TABLE_NAME);
300       listener.awaitModifications(1);
301       assertEquals(0, listener.getLoadCount());
302       assertEquals(1, listener.getCloseCount());
303     } finally {
304       am.unregisterListener(listener);
305     }
306   }
307 
308   @Test
309   public void testAddNewServerThatExistsInDraining() throws Exception {
310     // Under certain circumstances, such as when we failover to the Backup
311     // HMaster, the DrainingServerTracker is started with existing servers in
312     // draining before all of the Region Servers register with the
313     // ServerManager as "online".  This test is to ensure that Region Servers
314     // are properly added to the ServerManager.drainingServers when they
315     // register with the ServerManager under these circumstances.
316     Configuration conf = TEST_UTIL.getConfiguration();
317     ZooKeeperWatcher zooKeeper = new ZooKeeperWatcher(conf,
318         "zkWatcher-NewServerDrainTest", abortable, true);
319     String baseZNode = conf.get(HConstants.ZOOKEEPER_ZNODE_PARENT,
320         HConstants.DEFAULT_ZOOKEEPER_ZNODE_PARENT);
321     String drainingZNode = ZKUtil.joinZNode(baseZNode,
322         conf.get("zookeeper.znode.draining.rs", "draining"));
323 
324     MasterServices services = Mockito.mock(MasterServices.class);
325     HMaster master = Mockito.mock(HMaster.class);
326     Mockito.when(master.getConfiguration()).thenReturn(conf);
327 
328     ServerName SERVERNAME_A = ServerName.valueOf("mockserverbulk_a.org", 1000, 8000);
329     ServerName SERVERNAME_B = ServerName.valueOf("mockserverbulk_b.org", 1001, 8000);
330     ServerName SERVERNAME_C = ServerName.valueOf("mockserverbulk_c.org", 1002, 8000);
331 
332     // We'll start with 2 servers in draining that existed before the
333     // HMaster started.
334     ArrayList<ServerName> drainingServers = new ArrayList<ServerName>();
335     drainingServers.add(SERVERNAME_A);
336     drainingServers.add(SERVERNAME_B);
337 
338     // We'll have 2 servers that come online AFTER the DrainingServerTracker
339     // is started (just as we see when we failover to the Backup HMaster).
340     // One of these will already be a draining server.
341     HashMap<ServerName, ServerLoad> onlineServers = new HashMap<ServerName, ServerLoad>();
342     onlineServers.put(SERVERNAME_A, ServerLoad.EMPTY_SERVERLOAD);
343     onlineServers.put(SERVERNAME_C, ServerLoad.EMPTY_SERVERLOAD);
344 
345     // Create draining znodes for the draining servers, which would have been
346     // performed when the previous HMaster was running.
347     for (ServerName sn : drainingServers) {
348       String znode = ZKUtil.joinZNode(drainingZNode, sn.getServerName());
349       ZKUtil.createAndFailSilent(zooKeeper, znode);
350     }
351 
352     // Now, we follow the same order of steps that the HMaster does to setup
353     // the ServerManager, RegionServerTracker, and DrainingServerTracker.
354     ServerManager serverManager = new ServerManager(master, services);
355     RegionServerTracker regionServerTracker = new RegionServerTracker(
356         zooKeeper, master, serverManager);
357     regionServerTracker.start();
358 
359     DrainingServerTracker drainingServerTracker = new DrainingServerTracker(
360         zooKeeper, master, serverManager);
361     drainingServerTracker.start();
362 
363     // Confirm our ServerManager lists are empty.
364     Assert.assertEquals(serverManager.getOnlineServers(),
365         new HashMap<ServerName, ServerLoad>());
366     Assert.assertEquals(serverManager.getDrainingServersList(),
367         new ArrayList<ServerName>());
368 
369     // checkAndRecordNewServer() is how servers are added to the ServerManager.
370     ArrayList<ServerName> onlineDrainingServers = new ArrayList<ServerName>();
371     for (ServerName sn : onlineServers.keySet()){
372       // Here's the actual test.
373       serverManager.checkAndRecordNewServer(sn, onlineServers.get(sn));
374       if (drainingServers.contains(sn)){
375         onlineDrainingServers.add(sn);  // keeping track for later verification
376       }
377     }
378 
379     // Verify the ServerManager lists are correctly updated.
380     Assert.assertEquals(serverManager.getOnlineServers(), onlineServers);
381     Assert.assertEquals(serverManager.getDrainingServersList(),
382         onlineDrainingServers);
383   }
384 }