1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
69
70
71
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
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
116 HTable table = createTableAndLoadData(master, tableName);
117
118 mergeRegionsAndVerifyRegionNum(master, tableName, 0, 1,
119 INITIAL_REGION_NUM - 1);
120
121
122 PairOfSameType<HRegionInfo> mergedRegions =
123 mergeRegionsAndVerifyRegionNum(master, tableName, 1, 2,
124 INITIAL_REGION_NUM - 2);
125
126 verifyRowCount(table, ROWSIZE);
127
128
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
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
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
164 HTable table = createTableAndLoadData(master, tableName);
165
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
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
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
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
214
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
234
235
236
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
245 HTable table = createTableAndLoadData(master, tableName);
246 RegionStates regionStates = master.getAssignmentManager().getRegionStates();
247 List<HRegionInfo> regions = regionStates.getRegionsOfTable(tableName);
248
249 HRegionInfo a = regions.get(0);
250 HRegionInfo b = regions.get(1);
251 regionStates.regionOffline(a);
252 try {
253
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
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
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
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 }