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.HBaseTestingUtility;
35 import org.apache.hadoop.hbase.HConstants;
36 import org.apache.hadoop.hbase.HRegionInfo;
37 import org.apache.hadoop.hbase.HTableDescriptor;
38 import org.apache.hadoop.hbase.testclassification.LargeTests;
39 import org.apache.hadoop.hbase.MiniHBaseCluster;
40 import org.apache.hadoop.hbase.ServerName;
41 import org.apache.hadoop.hbase.TableName;
42 import org.apache.hadoop.hbase.UnknownRegionException;
43 import org.apache.hadoop.hbase.catalog.MetaReader;
44 import org.apache.hadoop.hbase.client.HBaseAdmin;
45 import org.apache.hadoop.hbase.client.HTable;
46 import org.apache.hadoop.hbase.client.Put;
47 import org.apache.hadoop.hbase.client.Result;
48 import org.apache.hadoop.hbase.client.ResultScanner;
49 import org.apache.hadoop.hbase.client.Scan;
50 import org.apache.hadoop.hbase.exceptions.MergeRegionException;
51 import org.apache.hadoop.hbase.master.AssignmentManager;
52 import org.apache.hadoop.hbase.master.HMaster;
53 import org.apache.hadoop.hbase.master.RegionState.State;
54 import org.apache.hadoop.hbase.master.RegionStates;
55 import org.apache.hadoop.hbase.util.Bytes;
56 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
57 import org.apache.hadoop.hbase.util.FSUtils;
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 static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
90
91 private static HMaster master;
92 private static HBaseAdmin admin;
93
94 static void setupOnce() throws Exception {
95
96 TEST_UTIL.startMiniCluster(NB_SERVERS);
97 MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
98 master = cluster.getMaster();
99 master.balanceSwitch(false);
100 admin = TEST_UTIL.getHBaseAdmin();
101 }
102
103 @BeforeClass
104 public static void beforeAllTests() throws Exception {
105
106 TEST_UTIL.getConfiguration().setBoolean("hbase.assignment.usezk", true);
107 setupOnce();
108 }
109
110 @AfterClass
111 public static void afterAllTests() throws Exception {
112 TEST_UTIL.shutdownMiniCluster();
113 }
114
115 @Test
116 public void testWholesomeMerge() throws Exception {
117 LOG.info("Starting testWholesomeMerge");
118 final TableName tableName =
119 TableName.valueOf("testWholesomeMerge");
120
121
122 HTable table = createTableAndLoadData(master, tableName);
123
124 mergeRegionsAndVerifyRegionNum(master, tableName, 0, 1,
125 INITIAL_REGION_NUM - 1);
126
127
128 PairOfSameType<HRegionInfo> mergedRegions =
129 mergeRegionsAndVerifyRegionNum(master, tableName, 1, 2,
130 INITIAL_REGION_NUM - 2);
131
132 verifyRowCount(table, ROWSIZE);
133
134
135 HRegionInfo hri = RandomUtils.nextBoolean() ?
136 mergedRegions.getFirst() : mergedRegions.getSecond();
137 MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster();
138 AssignmentManager am = cluster.getMaster().getAssignmentManager();
139 RegionStates regionStates = am.getRegionStates();
140 long start = EnvironmentEdgeManager.currentTimeMillis();
141 while (!regionStates.isRegionInState(hri, State.MERGED)) {
142 assertFalse("Timed out in waiting one merged region to be in state MERGED",
143 EnvironmentEdgeManager.currentTimeMillis() - start > 60000);
144 Thread.sleep(500);
145 }
146
147
148 am.assign(hri, true, true);
149 assertFalse("Merged region can't be assigned",
150 regionStates.isRegionInTransition(hri));
151 assertTrue(regionStates.isRegionInState(hri, State.MERGED));
152
153
154 am.unassign(hri, true, null);
155 assertFalse("Merged region can't be unassigned",
156 regionStates.isRegionInTransition(hri));
157 assertTrue(regionStates.isRegionInState(hri, State.MERGED));
158
159 table.close();
160 }
161
162 @Test
163 public void testCleanMergeReference() throws Exception {
164 LOG.info("Starting testCleanMergeReference");
165 admin.enableCatalogJanitor(false);
166 try {
167 final TableName tableName =
168 TableName.valueOf("testCleanMergeReference");
169
170 HTable table = createTableAndLoadData(master, tableName);
171
172 mergeRegionsAndVerifyRegionNum(master, tableName, 0, 1,
173 INITIAL_REGION_NUM - 1);
174 verifyRowCount(table, ROWSIZE);
175 table.close();
176
177 List<Pair<HRegionInfo, ServerName>> tableRegions = MetaReader
178 .getTableRegionsAndLocations(master.getCatalogTracker(),
179 tableName);
180 HRegionInfo mergedRegionInfo = tableRegions.get(0).getFirst();
181 HTableDescriptor tableDescritor = master.getTableDescriptors().get(
182 tableName);
183 Result mergedRegionResult = MetaReader.getRegionResult(
184 master.getCatalogTracker(), mergedRegionInfo.getRegionName());
185
186
187 assertTrue(mergedRegionResult.getValue(HConstants.CATALOG_FAMILY,
188 HConstants.MERGEA_QUALIFIER) != null);
189 assertTrue(mergedRegionResult.getValue(HConstants.CATALOG_FAMILY,
190 HConstants.MERGEB_QUALIFIER) != null);
191
192
193 HRegionInfo regionA = HRegionInfo.getHRegionInfo(mergedRegionResult,
194 HConstants.MERGEA_QUALIFIER);
195 HRegionInfo regionB = HRegionInfo.getHRegionInfo(mergedRegionResult,
196 HConstants.MERGEB_QUALIFIER);
197 FileSystem fs = master.getMasterFileSystem().getFileSystem();
198 Path rootDir = master.getMasterFileSystem().getRootDir();
199
200 Path tabledir = FSUtils.getTableDir(rootDir, mergedRegionInfo.getTable());
201 Path regionAdir = new Path(tabledir, regionA.getEncodedName());
202 Path regionBdir = new Path(tabledir, regionB.getEncodedName());
203 assertTrue(fs.exists(regionAdir));
204 assertTrue(fs.exists(regionBdir));
205
206 admin.compact(mergedRegionInfo.getRegionName());
207
208 long timeout = System.currentTimeMillis() + waitTime;
209 HRegionFileSystem hrfs = new HRegionFileSystem(
210 TEST_UTIL.getConfiguration(), fs, tabledir, mergedRegionInfo);
211 while (System.currentTimeMillis() < timeout) {
212 if (!hrfs.hasReferences(tableDescritor)) {
213 break;
214 }
215 Thread.sleep(50);
216 }
217 assertFalse(hrfs.hasReferences(tableDescritor));
218
219
220
221 int cleaned = admin.runCatalogScan();
222 assertTrue(cleaned > 0);
223 assertFalse(fs.exists(regionAdir));
224 assertFalse(fs.exists(regionBdir));
225
226 mergedRegionResult = MetaReader.getRegionResult(
227 master.getCatalogTracker(), mergedRegionInfo.getRegionName());
228 assertFalse(mergedRegionResult.getValue(HConstants.CATALOG_FAMILY,
229 HConstants.MERGEA_QUALIFIER) != null);
230 assertFalse(mergedRegionResult.getValue(HConstants.CATALOG_FAMILY,
231 HConstants.MERGEB_QUALIFIER) != null);
232
233 } finally {
234 admin.enableCatalogJanitor(true);
235 }
236 }
237
238
239
240
241
242
243
244 @Test
245 public void testMerge() throws Exception {
246 LOG.info("Starting testMerge");
247 final TableName tableName = TableName.valueOf("testMerge");
248
249 try {
250
251 HTable table = createTableAndLoadData(master, tableName);
252 RegionStates regionStates = master.getAssignmentManager().getRegionStates();
253 List<HRegionInfo> regions = regionStates.getRegionsOfTable(tableName);
254
255 HRegionInfo a = regions.get(0);
256 HRegionInfo b = regions.get(1);
257 regionStates.regionOffline(a);
258 try {
259
260 admin.mergeRegions(a.getEncodedNameAsBytes(), b.getEncodedNameAsBytes(), false);
261 fail("Offline regions should not be able to merge");
262 } catch (IOException ie) {
263 assertTrue("Exception should mention regions not online",
264 ie.getMessage().contains("regions not online")
265 && ie instanceof MergeRegionException);
266 }
267 try {
268
269 admin.mergeRegions(b.getEncodedNameAsBytes(), b.getEncodedNameAsBytes(), true);
270 fail("A region should not be able to merge with itself, even forcifully");
271 } catch (IOException ie) {
272 assertTrue("Exception should mention regions not online",
273 ie.getMessage().contains("region to itself")
274 && ie instanceof MergeRegionException);
275 }
276 try {
277
278 admin.mergeRegions(Bytes.toBytes("-f1"), Bytes.toBytes("-f2"), true);
279 fail("Unknown region could not be merged");
280 } catch (IOException ie) {
281 assertTrue("UnknownRegionException should be thrown",
282 ie instanceof UnknownRegionException);
283 }
284 table.close();
285 } finally {
286 TEST_UTIL.deleteTable(tableName);
287 }
288 }
289
290 private PairOfSameType<HRegionInfo> mergeRegionsAndVerifyRegionNum(
291 HMaster master, TableName tablename,
292 int regionAnum, int regionBnum, int expectedRegionNum) throws Exception {
293 PairOfSameType<HRegionInfo> mergedRegions =
294 requestMergeRegion(master, tablename, regionAnum, regionBnum);
295 waitAndVerifyRegionNum(master, tablename, expectedRegionNum);
296 return mergedRegions;
297 }
298
299 private PairOfSameType<HRegionInfo> requestMergeRegion(
300 HMaster master, TableName tablename,
301 int regionAnum, int regionBnum) throws Exception {
302 List<Pair<HRegionInfo, ServerName>> tableRegions = MetaReader
303 .getTableRegionsAndLocations(master.getCatalogTracker(),
304 tablename);
305 HRegionInfo regionA = tableRegions.get(regionAnum).getFirst();
306 HRegionInfo regionB = tableRegions.get(regionBnum).getFirst();
307 TEST_UTIL.getHBaseAdmin().mergeRegions(
308 regionA.getEncodedNameAsBytes(),
309 regionB.getEncodedNameAsBytes(), false);
310 return new PairOfSameType<HRegionInfo>(regionA, regionB);
311 }
312
313 private void waitAndVerifyRegionNum(HMaster master, TableName tablename,
314 int expectedRegionNum) throws Exception {
315 List<Pair<HRegionInfo, ServerName>> tableRegionsInMeta;
316 List<HRegionInfo> tableRegionsInMaster;
317 long timeout = System.currentTimeMillis() + waitTime;
318 while (System.currentTimeMillis() < timeout) {
319 tableRegionsInMeta = MetaReader.getTableRegionsAndLocations(
320 master.getCatalogTracker(), tablename);
321 tableRegionsInMaster = master.getAssignmentManager().getRegionStates()
322 .getRegionsOfTable(tablename);
323 if (tableRegionsInMeta.size() == expectedRegionNum
324 && tableRegionsInMaster.size() == expectedRegionNum) {
325 break;
326 }
327 Thread.sleep(250);
328 }
329
330 tableRegionsInMeta = MetaReader.getTableRegionsAndLocations(
331 master.getCatalogTracker(), tablename);
332 LOG.info("Regions after merge:" + Joiner.on(',').join(tableRegionsInMeta));
333 assertEquals(expectedRegionNum, tableRegionsInMeta.size());
334 }
335
336 private HTable createTableAndLoadData(HMaster master, TableName tablename)
337 throws Exception {
338 return createTableAndLoadData(master, tablename, INITIAL_REGION_NUM);
339 }
340
341 private HTable createTableAndLoadData(HMaster master, TableName tablename,
342 int numRegions) throws Exception {
343 assertTrue("ROWSIZE must > numregions:" + numRegions, ROWSIZE > numRegions);
344 byte[][] splitRows = new byte[numRegions - 1][];
345 for (int i = 0; i < splitRows.length; i++) {
346 splitRows[i] = ROWS[(i + 1) * ROWSIZE / numRegions];
347 }
348
349 HTable table = TEST_UTIL.createTable(tablename, FAMILYNAME, splitRows);
350 loadData(table);
351 verifyRowCount(table, ROWSIZE);
352
353
354 long timeout = System.currentTimeMillis() + waitTime;
355 List<Pair<HRegionInfo, ServerName>> tableRegions;
356 while (System.currentTimeMillis() < timeout) {
357 tableRegions = MetaReader.getTableRegionsAndLocations(
358 master.getCatalogTracker(), tablename);
359 if (tableRegions.size() == numRegions)
360 break;
361 Thread.sleep(250);
362 }
363
364 tableRegions = MetaReader.getTableRegionsAndLocations(
365 master.getCatalogTracker(), tablename);
366 LOG.info("Regions after load: " + Joiner.on(',').join(tableRegions));
367 assertEquals(numRegions, tableRegions.size());
368 return table;
369 }
370
371 private static byte[][] makeN(byte[] base, int n) {
372 byte[][] ret = new byte[n][];
373 for (int i = 0; i < n; i++) {
374 ret[i] = Bytes.add(base, Bytes.toBytes(String.format("%04d", i)));
375 }
376 return ret;
377 }
378
379 private void loadData(HTable table) throws IOException {
380 for (int i = 0; i < ROWSIZE; i++) {
381 Put put = new Put(ROWS[i]);
382 put.add(FAMILYNAME, QUALIFIER, Bytes.toBytes(i));
383 table.put(put);
384 }
385 }
386
387 private void verifyRowCount(HTable table, int expectedRegionNum)
388 throws IOException {
389 ResultScanner scanner = table.getScanner(new Scan());
390 int rowCount = 0;
391 while (scanner.next() != null) {
392 rowCount++;
393 }
394 assertEquals(expectedRegionNum, rowCount);
395 scanner.close();
396 }
397 }