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.mockito.Mockito.*;
25
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Collection;
29 import java.util.List;
30
31 import org.apache.hadoop.conf.Configuration;
32 import org.apache.hadoop.fs.FileSystem;
33 import org.apache.hadoop.fs.Path;
34 import org.apache.hadoop.hbase.Cell;
35 import org.apache.hadoop.hbase.CoordinatedStateManager;
36 import org.apache.hadoop.hbase.CoordinatedStateManagerFactory;
37 import org.apache.hadoop.hbase.HBaseTestingUtility;
38 import org.apache.hadoop.hbase.HColumnDescriptor;
39 import org.apache.hadoop.hbase.HConstants;
40 import org.apache.hadoop.hbase.HRegionInfo;
41 import org.apache.hadoop.hbase.HTableDescriptor;
42 import org.apache.hadoop.hbase.MockRegionServerServicesWithWALs;
43 import org.apache.hadoop.hbase.Server;
44 import org.apache.hadoop.hbase.ServerName;
45 import org.apache.hadoop.hbase.TableName;
46 import org.apache.hadoop.hbase.client.Durability;
47 import org.apache.hadoop.hbase.client.Put;
48 import org.apache.hadoop.hbase.client.Scan;
49 import org.apache.hadoop.hbase.testclassification.SmallTests;
50 import org.apache.hadoop.hbase.util.Bytes;
51 import org.apache.hadoop.hbase.util.FSUtils;
52 import org.apache.hadoop.hbase.wal.RegionGroupingProvider;
53 import org.apache.hadoop.hbase.wal.WALFactory;
54 import org.apache.zookeeper.KeeperException;
55 import org.junit.After;
56 import org.junit.Before;
57 import org.junit.Test;
58 import org.junit.experimental.categories.Category;
59 import org.junit.runner.RunWith;
60 import org.junit.runners.Parameterized;
61 import org.mockito.Mockito;
62
63 import com.google.common.collect.ImmutableList;
64
65
66
67
68
69 @RunWith(Parameterized.class)
70 @Category(SmallTests.class)
71 public class TestRegionMergeTransaction {
72 private final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
73 private final Path testdir = TEST_UTIL.getDataTestDir(this.getClass()
74 .getName());
75 private HRegion region_a;
76 private HRegion region_b;
77 private HRegion region_c;
78 private WALFactory wals;
79 private FileSystem fs;
80
81 private static final byte[] STARTROW_A = new byte[] { 'a', 'a', 'a' };
82 private static final byte[] STARTROW_B = new byte[] { 'g', 'g', 'g' };
83 private static final byte[] STARTROW_C = new byte[] { 'w', 'w', 'w' };
84 private static final byte[] ENDROW = new byte[] { '{', '{', '{' };
85 private static final byte[] CF = HConstants.CATALOG_FAMILY;
86
87 private MockRegionServerServicesWithWALs rsw;
88
89 private String walProvider;
90 private String strategy;
91
92 @Parameterized.Parameters
93 public static final Collection<Object[]> parameters() {
94 List<Object[]> params = new ArrayList<>(4);
95 params.add(new Object[] { "filesystem", "" });
96 params.add(new Object[] { "multiwal", "identity" });
97 params.add(new Object[] { "multiwal", "bounded" });
98 params.add(new Object[] { "multiwal", "namespace" });
99 return params;
100 }
101
102 public TestRegionMergeTransaction(String walProvider, String strategy) {
103 this.walProvider = walProvider;
104 this.strategy = strategy;
105 }
106
107 @Before
108 public void setup() throws IOException {
109 this.fs = FileSystem.get(TEST_UTIL.getConfiguration());
110 this.fs.delete(this.testdir, true);
111 TEST_UTIL.getConfiguration().set(WALFactory.WAL_PROVIDER, walProvider);
112 if (!strategy.isEmpty()) {
113 TEST_UTIL.getConfiguration().set(RegionGroupingProvider.REGION_GROUPING_STRATEGY, strategy);
114 }
115 final Configuration walConf = new Configuration(TEST_UTIL.getConfiguration());
116 FSUtils.setRootDir(walConf, this.testdir);
117 this.wals = new WALFactory(walConf, null, TestRegionMergeTransaction.class.getName());
118 ServerName sn = ServerName.valueOf("testRegionMergeTransaction", 10, 66);
119 final RegionServerServices rss = TEST_UTIL.createMockRegionServerService(sn);
120 this.rsw = new MockRegionServerServicesWithWALs(rss, wals.getWALProvider());
121 this.region_a = createRegion(this.testdir, this.wals, STARTROW_A, STARTROW_B);
122 this.region_b = createRegion(this.testdir, this.wals, STARTROW_B, STARTROW_C);
123 this.region_c = createRegion(this.testdir, this.wals, STARTROW_C, ENDROW);
124 assert region_a != null && region_b != null && region_c != null;
125 TEST_UTIL.getConfiguration().setBoolean("hbase.testing.nocluster", true);
126 }
127
128 @After
129 public void teardown() throws IOException {
130 for (HRegion region : new HRegion[] { region_a, region_b, region_c }) {
131 if (region != null && !region.isClosed()) region.close();
132 if (region != null && this.fs.exists(region.getRegionFileSystem().getRegionDir())
133 && !this.fs.delete(region.getRegionFileSystem().getRegionDir(), true)) {
134 throw new IOException("Failed deleting of "
135 + region.getRegionFileSystem().getRegionDir());
136 }
137 }
138 if (this.wals != null) {
139 this.wals.close();
140 }
141 this.fs.delete(this.testdir, true);
142 }
143
144
145
146
147
148
149 @Test
150 public void testPrepare() throws IOException {
151 prepareOnGoodRegions();
152 }
153
154 private RegionMergeTransactionImpl prepareOnGoodRegions() throws IOException {
155 RegionMergeTransactionImpl mt = new RegionMergeTransactionImpl(region_a, region_b,
156 false);
157 RegionMergeTransactionImpl spyMT = Mockito.spy(mt);
158 doReturn(false).when(spyMT).hasMergeQualifierInMeta(null,
159 region_a.getRegionInfo().getRegionName());
160 doReturn(false).when(spyMT).hasMergeQualifierInMeta(null,
161 region_b.getRegionInfo().getRegionName());
162 assertTrue(spyMT.prepare(null));
163 return spyMT;
164 }
165
166
167
168
169 @Test
170 public void testPrepareWithSameRegion() throws IOException {
171 RegionMergeTransactionImpl mt = new RegionMergeTransactionImpl(this.region_a,
172 this.region_a, true);
173 assertFalse("should not merge the same region even if it is forcible ",
174 mt.prepare(null));
175 }
176
177
178
179
180 @Test
181 public void testPrepareWithRegionsNotAdjacent() throws IOException {
182 RegionMergeTransactionImpl mt = new RegionMergeTransactionImpl(this.region_a,
183 this.region_c, false);
184 assertFalse("should not merge two regions if they are adjacent except it is forcible",
185 mt.prepare(null));
186 }
187
188
189
190
191 @Test
192 public void testPrepareWithRegionsNotAdjacentUnderCompulsory()
193 throws IOException {
194 RegionMergeTransactionImpl mt = new RegionMergeTransactionImpl(region_a, region_c,
195 true);
196 RegionMergeTransactionImpl spyMT = Mockito.spy(mt);
197 doReturn(false).when(spyMT).hasMergeQualifierInMeta(null,
198 region_a.getRegionInfo().getRegionName());
199 doReturn(false).when(spyMT).hasMergeQualifierInMeta(null,
200 region_c.getRegionInfo().getRegionName());
201 assertTrue("Since focible is true, should merge two regions even if they are not adjacent",
202 spyMT.prepare(null));
203 }
204
205
206
207
208 @Test
209 public void testPrepareWithRegionsWithReference() throws IOException {
210 HStore storeMock = Mockito.mock(HStore.class);
211 when(storeMock.hasReferences()).thenReturn(true);
212 when(storeMock.getFamily()).thenReturn(new HColumnDescriptor("cf"));
213 when(storeMock.close()).thenReturn(ImmutableList.<StoreFile>of());
214 this.region_a.stores.put(Bytes.toBytes(""), storeMock);
215 RegionMergeTransactionImpl mt = new RegionMergeTransactionImpl(this.region_a,
216 this.region_b, false);
217 assertFalse(
218 "a region should not be mergeable if it has instances of store file references",
219 mt.prepare(null));
220 }
221
222 @Test
223 public void testPrepareWithClosedRegion() throws IOException {
224 this.region_a.close();
225 RegionMergeTransactionImpl mt = new RegionMergeTransactionImpl(this.region_a,
226 this.region_b, false);
227 assertFalse(mt.prepare(null));
228 }
229
230
231
232
233
234 @Test
235 public void testPrepareWithRegionsWithMergeReference() throws IOException {
236 RegionMergeTransactionImpl mt = new RegionMergeTransactionImpl(region_a, region_b,
237 false);
238 RegionMergeTransactionImpl spyMT = Mockito.spy(mt);
239 doReturn(true).when(spyMT).hasMergeQualifierInMeta(null,
240 region_a.getRegionInfo().getRegionName());
241 doReturn(true).when(spyMT).hasMergeQualifierInMeta(null,
242 region_b.getRegionInfo().getRegionName());
243 assertFalse(spyMT.prepare(null));
244 }
245
246
247
248
249 @Test public void testRegionMergeTransactionListener() throws Exception {
250 RegionMergeTransactionImpl mt = new RegionMergeTransactionImpl(region_a, region_b,
251 false);
252 RegionMergeTransactionImpl spyMT = Mockito.spy(mt);
253 doReturn(false).when(spyMT).hasMergeQualifierInMeta(null,
254 region_a.getRegionInfo().getRegionName());
255 doReturn(false).when(spyMT).hasMergeQualifierInMeta(null,
256 region_b.getRegionInfo().getRegionName());
257 RegionMergeTransaction.TransactionListener listener =
258 Mockito.mock(RegionMergeTransaction.TransactionListener.class);
259 mt.registerTransactionListener(listener);
260 mt.prepare(null);
261 TEST_UTIL.getConfiguration().setInt(HConstants.REGIONSERVER_PORT, 0);
262 CoordinatedStateManager cp = CoordinatedStateManagerFactory.getCoordinatedStateManager(
263 TEST_UTIL.getConfiguration());
264 Server mockServer = new HRegionServer(TEST_UTIL.getConfiguration(), cp);
265 mt.execute(mockServer, null);
266 verify(listener).transition(mt,
267 RegionMergeTransaction.RegionMergeTransactionPhase.STARTED,
268 RegionMergeTransaction.RegionMergeTransactionPhase.PREPARED);
269 verify(listener, times(10)).transition(any(RegionMergeTransaction.class),
270 any(RegionMergeTransaction.RegionMergeTransactionPhase.class),
271 any(RegionMergeTransaction.RegionMergeTransactionPhase.class));
272 verifyNoMoreInteractions(listener);
273 }
274
275 @Test
276 public void testWholesomeMerge() throws IOException, InterruptedException {
277 final int rowCountOfRegionA = loadRegion(this.region_a, CF, true);
278 final int rowCountOfRegionB = loadRegion(this.region_b, CF, true);
279 assertTrue(rowCountOfRegionA > 0 && rowCountOfRegionB > 0);
280 assertEquals(rowCountOfRegionA, countRows(this.region_a));
281 assertEquals(rowCountOfRegionB, countRows(this.region_b));
282
283
284 RegionMergeTransactionImpl mt = prepareOnGoodRegions();
285
286
287 TEST_UTIL.getConfiguration().setInt(HConstants.REGIONSERVER_PORT, 0);
288 CoordinatedStateManager cp = CoordinatedStateManagerFactory.getCoordinatedStateManager(
289 TEST_UTIL.getConfiguration());
290 Server mockServer = new HRegionServer(TEST_UTIL.getConfiguration(), cp);
291 HRegion mergedRegion = (HRegion)mt.execute(mockServer, null);
292
293 assertTrue(this.fs.exists(mt.getMergesDir()));
294
295 assertTrue(region_a.isClosed());
296 assertTrue(region_b.isClosed());
297
298
299
300 assertEquals(0, this.fs.listStatus(mt.getMergesDir()).length);
301
302 assertTrue(Bytes.equals(this.region_a.getRegionInfo().getStartKey(),
303 mergedRegion.getRegionInfo().getStartKey()));
304 assertTrue(Bytes.equals(this.region_b.getRegionInfo().getEndKey(),
305 mergedRegion.getRegionInfo().getEndKey()));
306
307 try {
308 int mergedRegionRowCount = countRows(mergedRegion);
309 assertEquals((rowCountOfRegionA + rowCountOfRegionB),
310 mergedRegionRowCount);
311 } finally {
312 mergedRegion.close();
313 }
314
315 assertTrue(!this.region_a.lock.writeLock().isHeldByCurrentThread());
316 assertTrue(!this.region_b.lock.writeLock().isHeldByCurrentThread());
317 }
318
319 @Test
320 public void testRollback() throws IOException, InterruptedException {
321 final int rowCountOfRegionA = loadRegion(this.region_a, CF, true);
322 final int rowCountOfRegionB = loadRegion(this.region_b, CF, true);
323 assertTrue(rowCountOfRegionA > 0 && rowCountOfRegionB > 0);
324 assertEquals(rowCountOfRegionA, countRows(this.region_a));
325 assertEquals(rowCountOfRegionB, countRows(this.region_b));
326
327
328 RegionMergeTransactionImpl mt = prepareOnGoodRegions();
329
330 when(mt.createMergedRegionFromMerges(region_a, region_b,
331 mt.getMergedRegionInfo())).thenThrow(
332 new MockedFailedMergedRegionCreation());
333
334
335 boolean expectedException = false;
336 TEST_UTIL.getConfiguration().setInt(HConstants.REGIONSERVER_PORT, 0);
337 CoordinatedStateManager cp = CoordinatedStateManagerFactory.getCoordinatedStateManager(
338 TEST_UTIL.getConfiguration());
339 Server mockServer = new HRegionServer(TEST_UTIL.getConfiguration(), cp);
340 try {
341 mt.execute(mockServer, null);
342 } catch (MockedFailedMergedRegionCreation e) {
343 expectedException = true;
344 }
345 assertTrue(expectedException);
346
347 assertTrue(mt.rollback(null, null));
348
349
350 int rowCountOfRegionA2 = countRows(this.region_a);
351 assertEquals(rowCountOfRegionA, rowCountOfRegionA2);
352 int rowCountOfRegionB2 = countRows(this.region_b);
353 assertEquals(rowCountOfRegionB, rowCountOfRegionB2);
354
355
356 assertTrue(!this.fs.exists(FSUtils.getRegionDirFromRootDir(this.testdir,
357 mt.getMergedRegionInfo())));
358
359 assertTrue(!this.region_a.lock.writeLock().isHeldByCurrentThread());
360 assertTrue(!this.region_b.lock.writeLock().isHeldByCurrentThread());
361
362
363 assertTrue(mt.prepare(null));
364 HRegion mergedRegion = (HRegion)mt.execute(mockServer, null);
365
366
367 try {
368 int mergedRegionRowCount = countRows(mergedRegion);
369 assertEquals((rowCountOfRegionA + rowCountOfRegionB),
370 mergedRegionRowCount);
371 } finally {
372 mergedRegion.close();
373 }
374
375 assertTrue(!this.region_a.lock.writeLock().isHeldByCurrentThread());
376 assertTrue(!this.region_b.lock.writeLock().isHeldByCurrentThread());
377 }
378
379 @Test
380 public void testFailAfterPONR() throws IOException, KeeperException, InterruptedException {
381 final int rowCountOfRegionA = loadRegion(this.region_a, CF, true);
382 final int rowCountOfRegionB = loadRegion(this.region_b, CF, true);
383 assertTrue(rowCountOfRegionA > 0 && rowCountOfRegionB > 0);
384 assertEquals(rowCountOfRegionA, countRows(this.region_a));
385 assertEquals(rowCountOfRegionB, countRows(this.region_b));
386
387
388 RegionMergeTransactionImpl mt = prepareOnGoodRegions();
389 Mockito.doThrow(new MockedFailedMergedRegionOpen())
390 .when(mt)
391 .openMergedRegion((Server) Mockito.anyObject(),
392 (RegionServerServices) Mockito.anyObject(),
393 (HRegion) Mockito.anyObject());
394
395
396 boolean expectedException = false;
397 TEST_UTIL.getConfiguration().setInt(HConstants.REGIONSERVER_PORT, 0);
398 CoordinatedStateManager cp = CoordinatedStateManagerFactory.getCoordinatedStateManager(
399 TEST_UTIL.getConfiguration());
400 Server mockServer = new HRegionServer(TEST_UTIL.getConfiguration(), cp);
401 try {
402 mt.execute(mockServer, null);
403 } catch (MockedFailedMergedRegionOpen e) {
404 expectedException = true;
405 }
406 assertTrue(expectedException);
407
408 assertFalse(mt.rollback(null, null));
409
410
411
412 Path tableDir = this.region_a.getRegionFileSystem().getRegionDir()
413 .getParent();
414 Path mergedRegionDir = new Path(tableDir, mt.getMergedRegionInfo()
415 .getEncodedName());
416 assertTrue(TEST_UTIL.getTestFileSystem().exists(mergedRegionDir));
417 }
418
419 @Test
420 public void testMergedRegionBoundary() {
421 TableName tableName =
422 TableName.valueOf("testMergedRegionBoundary");
423 byte[] a = Bytes.toBytes("a");
424 byte[] b = Bytes.toBytes("b");
425 byte[] z = Bytes.toBytes("z");
426 HRegionInfo r1 = new HRegionInfo(tableName);
427 HRegionInfo r2 = new HRegionInfo(tableName, a, z);
428 HRegionInfo m = RegionMergeTransactionImpl.getMergedRegionInfo(r1, r2);
429 assertTrue(Bytes.equals(m.getStartKey(), r1.getStartKey())
430 && Bytes.equals(m.getEndKey(), r1.getEndKey()));
431
432 r1 = new HRegionInfo(tableName, null, a);
433 r2 = new HRegionInfo(tableName, a, z);
434 m = RegionMergeTransactionImpl.getMergedRegionInfo(r1, r2);
435 assertTrue(Bytes.equals(m.getStartKey(), r1.getStartKey())
436 && Bytes.equals(m.getEndKey(), r2.getEndKey()));
437
438 r1 = new HRegionInfo(tableName, null, a);
439 r2 = new HRegionInfo(tableName, z, null);
440 m = RegionMergeTransactionImpl.getMergedRegionInfo(r1, r2);
441 assertTrue(Bytes.equals(m.getStartKey(), r1.getStartKey())
442 && Bytes.equals(m.getEndKey(), r2.getEndKey()));
443
444 r1 = new HRegionInfo(tableName, a, z);
445 r2 = new HRegionInfo(tableName, z, null);
446 m = RegionMergeTransactionImpl.getMergedRegionInfo(r1, r2);
447 assertTrue(Bytes.equals(m.getStartKey(), r1.getStartKey())
448 && Bytes.equals(m.getEndKey(), r2.getEndKey()));
449
450 r1 = new HRegionInfo(tableName, a, b);
451 r2 = new HRegionInfo(tableName, b, z);
452 m = RegionMergeTransactionImpl.getMergedRegionInfo(r1, r2);
453 assertTrue(Bytes.equals(m.getStartKey(), r1.getStartKey())
454 && Bytes.equals(m.getEndKey(), r2.getEndKey()));
455 }
456
457
458
459
460 @SuppressWarnings("serial")
461 private class MockedFailedMergedRegionCreation extends IOException {
462 }
463
464 @SuppressWarnings("serial")
465 private class MockedFailedMergedRegionOpen extends IOException {
466 }
467
468 private HRegion createRegion(final Path testdir, final WALFactory wals,
469 final byte[] startrow, final byte[] endrow)
470 throws IOException {
471
472 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("table"));
473 HColumnDescriptor hcd = new HColumnDescriptor(CF);
474 htd.addFamily(hcd);
475 HRegionInfo hri = new HRegionInfo(htd.getTableName(), startrow, endrow);
476 HRegion a = HRegion.createHRegion(hri, testdir,
477 TEST_UTIL.getConfiguration(), htd);
478 HRegion.closeHRegion(a);
479 return HRegion.openHRegion(testdir, hri, htd,
480 wals.getWAL(hri.getEncodedNameAsBytes(), hri.getTable().getNamespace()),
481 TEST_UTIL.getConfiguration(), rsw, null);
482 }
483
484 private int countRows(final HRegion r) throws IOException {
485 int rowcount = 0;
486 InternalScanner scanner = r.getScanner(new Scan());
487 try {
488 List<Cell> kvs = new ArrayList<Cell>();
489 boolean hasNext = true;
490 while (hasNext) {
491 hasNext = scanner.next(kvs);
492 if (!kvs.isEmpty())
493 rowcount++;
494 }
495 } finally {
496 scanner.close();
497 }
498 return rowcount;
499 }
500
501
502
503
504
505
506
507
508
509
510 private int loadRegion(final HRegion r, final byte[] f, final boolean flush)
511 throws IOException {
512 byte[] k = new byte[3];
513 int rowCount = 0;
514 for (byte b1 = 'a'; b1 <= 'z'; b1++) {
515 for (byte b2 = 'a'; b2 <= 'z'; b2++) {
516 for (byte b3 = 'a'; b3 <= 'z'; b3++) {
517 k[0] = b1;
518 k[1] = b2;
519 k[2] = b3;
520 if (!HRegion.rowIsInRange(r.getRegionInfo(), k)) {
521 continue;
522 }
523 Put put = new Put(k);
524 put.add(f, null, k);
525 if (r.getWAL() == null)
526 put.setDurability(Durability.SKIP_WAL);
527 r.put(put);
528 rowCount++;
529 }
530 }
531 if (flush) {
532 r.flush(true);
533 }
534 }
535 return rowCount;
536 }
537
538 }