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