View Javadoc

1   /**
2    * Copyright The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements. See the NOTICE file distributed with this
6    * work for additional information regarding copyright ownership. The ASF
7    * licenses this file to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance with the License.
9    * 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, WITHOUT
15   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16   * License for the specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertFalse;
23  import static org.junit.Assert.assertTrue;
24  import static org.junit.Assert.fail;
25  
26  import java.io.IOException;
27  import java.util.List;
28  
29  import org.apache.commons.lang.math.RandomUtils;
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.fs.Path;
34  import org.apache.hadoop.hbase.TableName;
35  import org.apache.hadoop.hbase.HBaseTestingUtility;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.HRegionInfo;
38  import org.apache.hadoop.hbase.HTableDescriptor;
39  import org.apache.hadoop.hbase.LargeTests;
40  import org.apache.hadoop.hbase.MiniHBaseCluster;
41  import org.apache.hadoop.hbase.ServerName;
42  import org.apache.hadoop.hbase.catalog.MetaReader;
43  import org.apache.hadoop.hbase.client.HBaseAdmin;
44  import org.apache.hadoop.hbase.client.HTable;
45  import org.apache.hadoop.hbase.client.Put;
46  import org.apache.hadoop.hbase.client.Result;
47  import org.apache.hadoop.hbase.client.ResultScanner;
48  import org.apache.hadoop.hbase.client.Scan;
49  import org.apache.hadoop.hbase.exceptions.MergeRegionException;
50  import org.apache.hadoop.hbase.UnknownRegionException;
51  import org.apache.hadoop.hbase.master.AssignmentManager;
52  import org.apache.hadoop.hbase.master.HMaster;
53  import org.apache.hadoop.hbase.master.RegionStates;
54  import org.apache.hadoop.hbase.master.RegionState.State;
55  import org.apache.hadoop.hbase.util.Bytes;
56  import org.apache.hadoop.hbase.util.FSUtils;
57  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
58  import org.apache.hadoop.hbase.util.Pair;
59  import org.apache.hadoop.hbase.util.PairOfSameType;
60  import org.junit.AfterClass;
61  import org.junit.BeforeClass;
62  import org.junit.Test;
63  import org.junit.experimental.categories.Category;
64  
65  import com.google.common.base.Joiner;
66  
67  /**
68   * Like {@link TestRegionMergeTransaction} in that we're testing
69   * {@link RegionMergeTransaction} only the below tests are against a running
70   * cluster where {@link TestRegionMergeTransaction} is tests against bare
71   * {@link HRegion}.
72   */
73  @Category(LargeTests.class)
74  public class TestRegionMergeTransactionOnCluster {
75    private static final Log LOG = LogFactory
76        .getLog(TestRegionMergeTransactionOnCluster.class);
77    private static final int NB_SERVERS = 3;
78  
79    private static final byte[] FAMILYNAME = Bytes.toBytes("fam");
80    private static final byte[] QUALIFIER = Bytes.toBytes("q");
81  
82    private static byte[] ROW = Bytes.toBytes("testRow");
83    private static final int INITIAL_REGION_NUM = 10;
84    private static final int ROWSIZE = 200;
85    private static byte[][] ROWS = makeN(ROW, ROWSIZE);
86  
87    private static int waitTime = 60 * 1000;
88  
89    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
90  
91    private static HMaster master;
92    private static HBaseAdmin admin;
93  
94    @BeforeClass
95    public static void beforeAllTests() throws Exception {
96      // Start a cluster
97      TEST_UTIL.startMiniCluster(NB_SERVERS);
98      MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
99      master = cluster.getMaster();
100     master.balanceSwitch(false);
101     admin = TEST_UTIL.getHBaseAdmin();
102   }
103 
104   @AfterClass
105   public static void afterAllTests() throws Exception {
106     TEST_UTIL.shutdownMiniCluster();
107   }
108 
109   @Test
110   public void testWholesomeMerge() throws Exception {
111     LOG.info("Starting testWholesomeMerge");
112     final TableName tableName =
113         TableName.valueOf("testWholesomeMerge");
114 
115     // Create table and load data.
116     HTable table = createTableAndLoadData(master, tableName);
117     // Merge 1st and 2nd region
118     mergeRegionsAndVerifyRegionNum(master, tableName, 0, 1,
119         INITIAL_REGION_NUM - 1);
120 
121     // Merge 2nd and 3th region
122     PairOfSameType<HRegionInfo> mergedRegions =
123       mergeRegionsAndVerifyRegionNum(master, tableName, 1, 2,
124         INITIAL_REGION_NUM - 2);
125 
126     verifyRowCount(table, ROWSIZE);
127 
128     // Randomly choose one of the two merged regions
129     HRegionInfo hri = RandomUtils.nextBoolean() ?
130       mergedRegions.getFirst() : mergedRegions.getSecond();
131     MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
132     AssignmentManager am = cluster.getMaster().getAssignmentManager();
133     RegionStates regionStates = am.getRegionStates();
134     long start = EnvironmentEdgeManager.currentTimeMillis();
135     while (!regionStates.isRegionInState(hri, State.MERGED)) {
136       assertFalse("Timed out in waiting one merged region to be in state MERGED",
137         EnvironmentEdgeManager.currentTimeMillis() - start > 60000);
138       Thread.sleep(500);
139     }
140 
141     // We should not be able to assign it again
142     am.assign(hri, true, true);
143     assertFalse("Merged region can't be assigned",
144       regionStates.isRegionInTransition(hri));
145     assertTrue(regionStates.isRegionInState(hri, State.MERGED));
146 
147     // We should not be able to unassign it either
148     am.unassign(hri, true, null);
149     assertFalse("Merged region can't be unassigned",
150       regionStates.isRegionInTransition(hri));
151     assertTrue(regionStates.isRegionInState(hri, State.MERGED));
152 
153     table.close();
154   }
155 
156   @Test
157   public void testCleanMergeReference() throws Exception {
158     LOG.info("Starting testCleanMergeReference");
159     admin.enableCatalogJanitor(false);
160     try {
161       final TableName tableName =
162           TableName.valueOf("testCleanMergeReference");
163       // Create table and load data.
164       HTable table = createTableAndLoadData(master, tableName);
165       // Merge 1st and 2nd region
166       mergeRegionsAndVerifyRegionNum(master, tableName, 0, 1,
167           INITIAL_REGION_NUM - 1);
168       verifyRowCount(table, ROWSIZE);
169       table.close();
170 
171       List<Pair<HRegionInfo, ServerName>> tableRegions = MetaReader
172           .getTableRegionsAndLocations(master.getCatalogTracker(),
173               tableName);
174       HRegionInfo mergedRegionInfo = tableRegions.get(0).getFirst();
175       HTableDescriptor tableDescritor = master.getTableDescriptors().get(
176           tableName);
177       Result mergedRegionResult = MetaReader.getRegionResult(
178           master.getCatalogTracker(), mergedRegionInfo.getRegionName());
179 
180       // contains merge reference in META
181       assertTrue(mergedRegionResult.getValue(HConstants.CATALOG_FAMILY,
182           HConstants.MERGEA_QUALIFIER) != null);
183       assertTrue(mergedRegionResult.getValue(HConstants.CATALOG_FAMILY,
184           HConstants.MERGEB_QUALIFIER) != null);
185 
186       // merging regions' directory are in the file system all the same
187       HRegionInfo regionA = HRegionInfo.getHRegionInfo(mergedRegionResult,
188           HConstants.MERGEA_QUALIFIER);
189       HRegionInfo regionB = HRegionInfo.getHRegionInfo(mergedRegionResult,
190           HConstants.MERGEB_QUALIFIER);
191       FileSystem fs = master.getMasterFileSystem().getFileSystem();
192       Path rootDir = master.getMasterFileSystem().getRootDir();
193 
194       Path tabledir = FSUtils.getTableDir(rootDir, mergedRegionInfo.getTable());
195       Path regionAdir = new Path(tabledir, regionA.getEncodedName());
196       Path regionBdir = new Path(tabledir, regionB.getEncodedName());
197       assertTrue(fs.exists(regionAdir));
198       assertTrue(fs.exists(regionBdir));
199 
200       admin.compact(mergedRegionInfo.getRegionName());
201       // wait until merged region doesn't have reference file
202       long timeout = System.currentTimeMillis() + waitTime;
203       HRegionFileSystem hrfs = new HRegionFileSystem(
204           TEST_UTIL.getConfiguration(), fs, tabledir, mergedRegionInfo);
205       while (System.currentTimeMillis() < timeout) {
206         if (!hrfs.hasReferences(tableDescritor)) {
207           break;
208         }
209         Thread.sleep(50);
210       }
211       assertFalse(hrfs.hasReferences(tableDescritor));
212 
213       // run CatalogJanitor to clean merge references in hbase:meta and archive the
214       // files of merging regions
215       int cleaned = admin.runCatalogScan();
216       assertTrue(cleaned > 0);
217       assertFalse(fs.exists(regionAdir));
218       assertFalse(fs.exists(regionBdir));
219 
220       mergedRegionResult = MetaReader.getRegionResult(
221           master.getCatalogTracker(), mergedRegionInfo.getRegionName());
222       assertFalse(mergedRegionResult.getValue(HConstants.CATALOG_FAMILY,
223           HConstants.MERGEA_QUALIFIER) != null);
224       assertFalse(mergedRegionResult.getValue(HConstants.CATALOG_FAMILY,
225           HConstants.MERGEB_QUALIFIER) != null);
226 
227     } finally {
228       admin.enableCatalogJanitor(true);
229     }
230   }
231 
232   /**
233    * This test tests 1, merging region not online;
234    * 2, merging same two regions; 3, merging unknown regions.
235    * They are in one test case so that we don't have to create
236    * many tables, and these tests are simple.
237    */
238   @Test
239   public void testMerge() throws Exception {
240     LOG.info("Starting testMerge");
241     final TableName tableName = TableName.valueOf("testMerge");
242 
243     try {
244       // Create table and load data.
245       HTable table = createTableAndLoadData(master, tableName);
246       RegionStates regionStates = master.getAssignmentManager().getRegionStates();
247       List<HRegionInfo> regions = regionStates.getRegionsOfTable(tableName);
248       // Fake offline one region
249       HRegionInfo a = regions.get(0);
250       HRegionInfo b = regions.get(1);
251       regionStates.regionOffline(a);
252       try {
253         // Merge offline region. Region a is offline here
254         admin.mergeRegions(a.getEncodedNameAsBytes(), b.getEncodedNameAsBytes(), false);
255         fail("Offline regions should not be able to merge");
256       } catch (IOException ie) {
257         assertTrue("Exception should mention regions not online",
258           ie.getMessage().contains("regions not online")
259             && ie instanceof MergeRegionException);
260       }
261       try {
262         // Merge the same region: b and b.
263         admin.mergeRegions(b.getEncodedNameAsBytes(), b.getEncodedNameAsBytes(), true);
264         fail("A region should not be able to merge with itself, even forcifully");
265       } catch (IOException ie) {
266         assertTrue("Exception should mention regions not online",
267           ie.getMessage().contains("region to itself")
268             && ie instanceof MergeRegionException);
269       }
270       try {
271         // Merge unknown regions
272         admin.mergeRegions(Bytes.toBytes("-f1"), Bytes.toBytes("-f2"), true);
273         fail("Unknown region could not be merged");
274       } catch (IOException ie) {
275         assertTrue("UnknownRegionException should be thrown",
276           ie instanceof UnknownRegionException);
277       }
278       table.close();
279     } finally {
280       TEST_UTIL.deleteTable(tableName);
281     }
282   }
283 
284   private PairOfSameType<HRegionInfo> mergeRegionsAndVerifyRegionNum(
285       HMaster master, TableName tablename,
286       int regionAnum, int regionBnum, int expectedRegionNum) throws Exception {
287     PairOfSameType<HRegionInfo> mergedRegions =
288       requestMergeRegion(master, tablename, regionAnum, regionBnum);
289     waitAndVerifyRegionNum(master, tablename, expectedRegionNum);
290     return mergedRegions;
291   }
292 
293   private PairOfSameType<HRegionInfo> requestMergeRegion(
294       HMaster master, TableName tablename,
295       int regionAnum, int regionBnum) throws Exception {
296     List<Pair<HRegionInfo, ServerName>> tableRegions = MetaReader
297         .getTableRegionsAndLocations(master.getCatalogTracker(),
298             tablename);
299     HRegionInfo regionA = tableRegions.get(regionAnum).getFirst();
300     HRegionInfo regionB = tableRegions.get(regionBnum).getFirst();
301     TEST_UTIL.getHBaseAdmin().mergeRegions(
302       regionA.getEncodedNameAsBytes(),
303       regionB.getEncodedNameAsBytes(), false);
304     return new PairOfSameType<HRegionInfo>(regionA, regionB);
305   }
306 
307   private void waitAndVerifyRegionNum(HMaster master, TableName tablename,
308       int expectedRegionNum) throws Exception {
309     List<Pair<HRegionInfo, ServerName>> tableRegionsInMeta;
310     List<HRegionInfo> tableRegionsInMaster;
311     long timeout = System.currentTimeMillis() + waitTime;
312     while (System.currentTimeMillis() < timeout) {
313       tableRegionsInMeta = MetaReader.getTableRegionsAndLocations(
314           master.getCatalogTracker(), tablename);
315       tableRegionsInMaster = master.getAssignmentManager().getRegionStates()
316           .getRegionsOfTable(tablename);
317       if (tableRegionsInMeta.size() == expectedRegionNum
318           && tableRegionsInMaster.size() == expectedRegionNum) {
319         break;
320       }
321       Thread.sleep(250);
322     }
323 
324     tableRegionsInMeta = MetaReader.getTableRegionsAndLocations(
325         master.getCatalogTracker(), tablename);
326     LOG.info("Regions after merge:" + Joiner.on(',').join(tableRegionsInMeta));
327     assertEquals(expectedRegionNum, tableRegionsInMeta.size());
328   }
329 
330   private HTable createTableAndLoadData(HMaster master, TableName tablename)
331       throws Exception {
332     return createTableAndLoadData(master, tablename, INITIAL_REGION_NUM);
333   }
334 
335   private HTable createTableAndLoadData(HMaster master, TableName tablename,
336       int numRegions) throws Exception {
337     assertTrue("ROWSIZE must > numregions:" + numRegions, ROWSIZE > numRegions);
338     byte[][] splitRows = new byte[numRegions - 1][];
339     for (int i = 0; i < splitRows.length; i++) {
340       splitRows[i] = ROWS[(i + 1) * ROWSIZE / numRegions];
341     }
342 
343     HTable table = TEST_UTIL.createTable(tablename, FAMILYNAME, splitRows);
344     loadData(table);
345     verifyRowCount(table, ROWSIZE);
346 
347     // sleep here is an ugly hack to allow region transitions to finish
348     long timeout = System.currentTimeMillis() + waitTime;
349     List<Pair<HRegionInfo, ServerName>> tableRegions;
350     while (System.currentTimeMillis() < timeout) {
351       tableRegions = MetaReader.getTableRegionsAndLocations(
352           master.getCatalogTracker(), tablename);
353       if (tableRegions.size() == numRegions)
354         break;
355       Thread.sleep(250);
356     }
357 
358     tableRegions = MetaReader.getTableRegionsAndLocations(
359         master.getCatalogTracker(), tablename);
360     LOG.info("Regions after load: " + Joiner.on(',').join(tableRegions));
361     assertEquals(numRegions, tableRegions.size());
362     return table;
363   }
364 
365   private static byte[][] makeN(byte[] base, int n) {
366     byte[][] ret = new byte[n][];
367     for (int i = 0; i < n; i++) {
368       ret[i] = Bytes.add(base, Bytes.toBytes(String.format("%04d", i)));
369     }
370     return ret;
371   }
372 
373   private void loadData(HTable table) throws IOException {
374     for (int i = 0; i < ROWSIZE; i++) {
375       Put put = new Put(ROWS[i]);
376       put.add(FAMILYNAME, QUALIFIER, Bytes.toBytes(i));
377       table.put(put);
378     }
379   }
380 
381   private void verifyRowCount(HTable table, int expectedRegionNum)
382       throws IOException {
383     ResultScanner scanner = table.getScanner(new Scan());
384     int rowCount = 0;
385     while (scanner.next() != null) {
386       rowCount++;
387     }
388     assertEquals(expectedRegionNum, rowCount);
389     scanner.close();
390   }
391 }