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.Matchers.anyInt;
25 import static org.mockito.Matchers.eq;
26 import static org.mockito.Mockito.*;
27
28 import org.apache.hadoop.hbase.MockRegionServerServicesWithWALs;
29 import org.apache.hadoop.hbase.ServerName;
30 import org.apache.hadoop.hbase.testclassification.MediumTests;
31 import org.apache.hadoop.hbase.wal.RegionGroupingProvider;
32 import org.junit.runner.RunWith;
33 import org.junit.runners.Parameterized;
34 import org.mockito.Mockito;
35
36 import java.io.IOException;
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.List;
40
41 import org.apache.hadoop.conf.Configuration;
42 import org.apache.hadoop.fs.FileSystem;
43 import org.apache.hadoop.fs.Path;
44 import org.apache.hadoop.hbase.Cell;
45 import org.apache.hadoop.hbase.HBaseTestingUtility;
46 import org.apache.hadoop.hbase.HColumnDescriptor;
47 import org.apache.hadoop.hbase.HConstants;
48 import org.apache.hadoop.hbase.HRegionInfo;
49 import org.apache.hadoop.hbase.HTableDescriptor;
50 import org.apache.hadoop.hbase.Server;
51 import org.apache.hadoop.hbase.TableName;
52 import org.apache.hadoop.hbase.client.Scan;
53 import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;
54 import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
55 import org.apache.hadoop.hbase.coprocessor.ObserverContext;
56 import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
57 import org.apache.hadoop.hbase.io.hfile.CacheConfig;
58 import org.apache.hadoop.hbase.io.hfile.LruBlockCache;
59 import org.apache.hadoop.hbase.util.Bytes;
60 import org.apache.hadoop.hbase.util.FSUtils;
61 import org.apache.hadoop.hbase.util.PairOfSameType;
62 import org.apache.hadoop.hbase.wal.WALFactory;
63 import org.apache.zookeeper.KeeperException;
64 import org.junit.After;
65 import org.junit.Before;
66 import org.junit.Test;
67 import org.junit.experimental.categories.Category;
68
69 import com.google.common.collect.ImmutableList;
70
71
72
73
74
75 @RunWith(Parameterized.class)
76 @Category(MediumTests.class)
77 public class TestSplitTransaction {
78 private final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
79 private final Path testdir =
80 TEST_UTIL.getDataTestDir(this.getClass().getName());
81 private HRegion parent;
82 private WALFactory wals;
83 private FileSystem fs;
84 private static final byte [] STARTROW = new byte [] {'a', 'a', 'a'};
85
86 private static final byte [] ENDROW = new byte [] {'{', '{', '{'};
87 private static final byte [] GOOD_SPLIT_ROW = new byte [] {'d', 'd', 'd'};
88 private static final byte [] CF = HConstants.CATALOG_FAMILY;
89
90 private static boolean preRollBackCalled = false;
91 private static boolean postRollBackCalled = false;
92
93 private String walProvider;
94 private String strategy;
95
96 @Parameterized.Parameters
97 public static final Collection<Object[]> parameters() {
98 List<Object[]> params = new ArrayList<>(4);
99 params.add(new Object[] { "filesystem", "" });
100 params.add(new Object[] { "multiwal", "identity" });
101 params.add(new Object[] { "multiwal", "bounded" });
102 params.add(new Object[] { "multiwal", "namespace" });
103 return params;
104 }
105
106 public TestSplitTransaction(String walProvider, String strategy) {
107 this.walProvider = walProvider;
108 this.strategy = strategy;
109 }
110
111 @Before
112 public void setup() throws IOException {
113 this.fs = FileSystem.get(TEST_UTIL.getConfiguration());
114 TEST_UTIL.getConfiguration().set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, CustomObserver.class.getName());
115 this.fs.delete(this.testdir, true);
116 TEST_UTIL.getConfiguration().set(WALFactory.WAL_PROVIDER, walProvider);
117 if (!strategy.isEmpty()) {
118 TEST_UTIL.getConfiguration().set(RegionGroupingProvider.REGION_GROUPING_STRATEGY, strategy);
119 }
120 final Configuration walConf = new Configuration(TEST_UTIL.getConfiguration());
121 FSUtils.setRootDir(walConf, this.testdir);
122 this.wals = new WALFactory(walConf, null, this.getClass().getName());
123 this.parent = createRegion(this.testdir, this.wals);
124 RegionCoprocessorHost host = new RegionCoprocessorHost(this.parent, null, TEST_UTIL.getConfiguration());
125 this.parent.setCoprocessorHost(host);
126 TEST_UTIL.getConfiguration().setBoolean("hbase.testing.nocluster", true);
127 }
128
129 @After
130 public void teardown() throws IOException {
131 if (this.parent != null && !this.parent.isClosed()) this.parent.close();
132 if (this.parent != null) {
133 Path regionDir = this.parent.getRegionFileSystem().getRegionDir();
134 if (this.fs.exists(regionDir) && !this.fs.delete(regionDir, true)) {
135 throw new IOException("Failed delete of " + regionDir);
136 }
137 }
138 if (this.wals != null) {
139 this.wals.close();
140 }
141 this.fs.delete(this.testdir, true);
142 }
143
144 @Test
145 public void testFailAfterPONR() throws IOException, KeeperException {
146 final int rowcount = TEST_UTIL.loadRegion(this.parent, CF);
147 assertTrue(rowcount > 0);
148 int parentRowCount = countRows(this.parent);
149 assertEquals(rowcount, parentRowCount);
150
151
152 SplitTransactionImpl st = prepareGOOD_SPLIT_ROW();
153 SplitTransactionImpl spiedUponSt = spy(st);
154 Mockito
155 .doThrow(new MockedFailedDaughterOpen())
156 .when(spiedUponSt)
157 .openDaughterRegion((Server) Mockito.anyObject(),
158 (HRegion) Mockito.anyObject());
159
160
161 boolean expectedException = false;
162 Server mockServer = Mockito.mock(Server.class);
163 when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
164 try {
165 spiedUponSt.execute(mockServer, null);
166 } catch (IOException e) {
167 if (e.getCause() != null &&
168 e.getCause() instanceof MockedFailedDaughterOpen) {
169 expectedException = true;
170 }
171 }
172 assertTrue(expectedException);
173
174 assertFalse(spiedUponSt.rollback(null, null));
175
176
177
178 Path tableDir = this.parent.getRegionFileSystem().getTableDir();
179 Path daughterADir = new Path(tableDir, spiedUponSt.getFirstDaughter().getEncodedName());
180 Path daughterBDir = new Path(tableDir, spiedUponSt.getSecondDaughter().getEncodedName());
181 assertTrue(TEST_UTIL.getTestFileSystem().exists(daughterADir));
182 assertTrue(TEST_UTIL.getTestFileSystem().exists(daughterBDir));
183 }
184
185
186
187
188
189 @Test
190 public void testPrepare() throws IOException {
191 prepareGOOD_SPLIT_ROW();
192 }
193
194 private SplitTransactionImpl prepareGOOD_SPLIT_ROW() throws IOException {
195 return prepareGOOD_SPLIT_ROW(this.parent);
196 }
197
198 private SplitTransactionImpl prepareGOOD_SPLIT_ROW(final HRegion parentRegion)
199 throws IOException {
200 SplitTransactionImpl st = new SplitTransactionImpl(parentRegion, GOOD_SPLIT_ROW);
201 assertTrue(st.prepare());
202 return st;
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.parent.stores.put(Bytes.toBytes(""), storeMock);
215
216 SplitTransactionImpl st = new SplitTransactionImpl(this.parent, GOOD_SPLIT_ROW);
217
218 assertFalse("a region should not be splittable if it has instances of store file references",
219 st.prepare());
220 }
221
222
223
224
225 @Test
226 public void testSplitTransactionListener() throws IOException {
227 SplitTransactionImpl st = new SplitTransactionImpl(this.parent, GOOD_SPLIT_ROW);
228 SplitTransaction.TransactionListener listener =
229 Mockito.mock(SplitTransaction.TransactionListener.class);
230 st.registerTransactionListener(listener);
231 st.prepare();
232 Server mockServer = Mockito.mock(Server.class);
233 when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
234 PairOfSameType<Region> daughters = st.execute(mockServer, null);
235 verify(listener).transition(st, SplitTransaction.SplitTransactionPhase.STARTED,
236 SplitTransaction.SplitTransactionPhase.PREPARED);
237 verify(listener, times(15)).transition(any(SplitTransaction.class),
238 any(SplitTransaction.SplitTransactionPhase.class),
239 any(SplitTransaction.SplitTransactionPhase.class));
240 verifyNoMoreInteractions(listener);
241 }
242
243
244
245
246 @Test
247 public void testPrepareWithBadSplitRow() throws IOException {
248
249 SplitTransactionImpl st = new SplitTransactionImpl(this.parent, STARTROW);
250 assertFalse(st.prepare());
251 st = new SplitTransactionImpl(this.parent, HConstants.EMPTY_BYTE_ARRAY);
252 assertFalse(st.prepare());
253 st = new SplitTransactionImpl(this.parent, new byte [] {'A', 'A', 'A'});
254 assertFalse(st.prepare());
255 st = new SplitTransactionImpl(this.parent, ENDROW);
256 assertFalse(st.prepare());
257 }
258
259 @Test
260 public void testPrepareWithClosedRegion() throws IOException {
261 this.parent.close();
262 SplitTransactionImpl st = new SplitTransactionImpl(this.parent, GOOD_SPLIT_ROW);
263 assertFalse(st.prepare());
264 }
265
266 @Test
267 public void testWholesomeSplit() throws IOException {
268 final int rowcount = TEST_UTIL.loadRegion(this.parent, CF, true);
269 assertTrue(rowcount > 0);
270 int parentRowCount = countRows(this.parent);
271 assertEquals(rowcount, parentRowCount);
272
273
274
275 CacheConfig cacheConf = new CacheConfig(TEST_UTIL.getConfiguration());
276 ((LruBlockCache) cacheConf.getBlockCache()).clearCache();
277
278
279 SplitTransactionImpl st = prepareGOOD_SPLIT_ROW();
280
281
282 Server mockServer = Mockito.mock(Server.class);
283 when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
284 PairOfSameType<Region> daughters = st.execute(mockServer, null);
285
286 assertTrue(this.fs.exists(this.parent.getRegionFileSystem().getSplitsDir()));
287
288 assertTrue(this.parent.isClosed());
289
290
291
292 assertEquals(0, this.fs.listStatus(this.parent.getRegionFileSystem().getSplitsDir()).length);
293
294 assertTrue(Bytes.equals(parent.getRegionInfo().getStartKey(),
295 daughters.getFirst().getRegionInfo().getStartKey()));
296 assertTrue(Bytes.equals(GOOD_SPLIT_ROW, daughters.getFirst().getRegionInfo().getEndKey()));
297 assertTrue(Bytes.equals(daughters.getSecond().getRegionInfo().getStartKey(), GOOD_SPLIT_ROW));
298 assertTrue(Bytes.equals(parent.getRegionInfo().getEndKey(),
299 daughters.getSecond().getRegionInfo().getEndKey()));
300
301 int daughtersRowCount = 0;
302 for (Region openRegion: daughters) {
303 try {
304 int count = countRows(openRegion);
305 assertTrue(count > 0 && count != rowcount);
306 daughtersRowCount += count;
307 } finally {
308 ((HRegion) openRegion).close();
309 }
310 }
311 assertEquals(rowcount, daughtersRowCount);
312
313 assertTrue(!this.parent.lock.writeLock().isHeldByCurrentThread());
314 }
315
316 @Test
317 public void testCountReferencesFailsSplit() throws IOException {
318 final int rowcount = TEST_UTIL.loadRegion(this.parent, CF);
319 assertTrue(rowcount > 0);
320 int parentRowCount = countRows(this.parent);
321 assertEquals(rowcount, parentRowCount);
322
323
324 HRegion spiedRegion = spy(this.parent);
325 SplitTransactionImpl st = prepareGOOD_SPLIT_ROW(spiedRegion);
326 SplitTransactionImpl spiedUponSt = spy(st);
327 doThrow(new IOException("Failing split. Expected reference file count isn't equal."))
328 .when(spiedUponSt).assertReferenceFileCount(anyInt(),
329 eq(new Path(this.parent.getRegionFileSystem().getTableDir(),
330 st.getSecondDaughter().getEncodedName())));
331
332
333 boolean expectedException = false;
334 Server mockServer = Mockito.mock(Server.class);
335 when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
336 try {
337 spiedUponSt.execute(mockServer, null);
338 } catch (IOException e) {
339 expectedException = true;
340 }
341 assertTrue(expectedException);
342 }
343
344 @Test
345 public void testRollback() throws IOException {
346 final int rowcount = TEST_UTIL.loadRegion(this.parent, CF);
347 assertTrue(rowcount > 0);
348 int parentRowCount = countRows(this.parent);
349 assertEquals(rowcount, parentRowCount);
350
351
352 HRegion spiedRegion = spy(this.parent);
353 SplitTransactionImpl st = prepareGOOD_SPLIT_ROW(spiedRegion);
354 SplitTransactionImpl spiedUponSt = spy(st);
355 doNothing().when(spiedUponSt).assertReferenceFileCount(anyInt(),
356 eq(parent.getRegionFileSystem().getSplitsDir(st.getFirstDaughter())));
357 when(spiedRegion.createDaughterRegionFromSplits(spiedUponSt.getSecondDaughter())).
358 thenThrow(new MockedFailedDaughterCreation());
359
360 boolean expectedException = false;
361 Server mockServer = Mockito.mock(Server.class);
362 when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
363 try {
364 spiedUponSt.execute(mockServer, null);
365 } catch (MockedFailedDaughterCreation e) {
366 expectedException = true;
367 }
368 assertTrue(expectedException);
369
370 assertTrue(spiedUponSt.rollback(null, null));
371
372
373 int parentRowCount2 = countRows(this.parent);
374 assertEquals(parentRowCount, parentRowCount2);
375
376
377 assertTrue(!this.fs.exists(FSUtils.getRegionDirFromRootDir(this.testdir,
378 st.getFirstDaughter())));
379 assertTrue(!this.fs.exists(FSUtils.getRegionDirFromRootDir(this.testdir,
380 st.getSecondDaughter())));
381 assertTrue(!this.parent.lock.writeLock().isHeldByCurrentThread());
382
383
384 assertTrue(st.prepare());
385 PairOfSameType<Region> daughters = st.execute(mockServer, null);
386
387 int daughtersRowCount = 0;
388 for (Region openRegion: daughters) {
389 try {
390 int count = countRows(openRegion);
391 assertTrue(count > 0 && count != rowcount);
392 daughtersRowCount += count;
393 } finally {
394 ((HRegion) openRegion).close();
395 }
396 }
397 assertEquals(rowcount, daughtersRowCount);
398
399 assertTrue(!this.parent.lock.writeLock().isHeldByCurrentThread());
400 assertTrue("Rollback hooks should be called.", wasRollBackHookCalled());
401 }
402
403 private boolean wasRollBackHookCalled(){
404 return (preRollBackCalled && postRollBackCalled);
405 }
406
407
408
409
410 @SuppressWarnings("serial")
411 private class MockedFailedDaughterCreation extends IOException {}
412 private class MockedFailedDaughterOpen extends IOException {}
413
414 private int countRows(final Region r) throws IOException {
415 int rowcount = 0;
416 InternalScanner scanner = r.getScanner(new Scan());
417 try {
418 List<Cell> kvs = new ArrayList<Cell>();
419 boolean hasNext = true;
420 while (hasNext) {
421 hasNext = scanner.next(kvs);
422 if (!kvs.isEmpty()) rowcount++;
423 }
424 } finally {
425 scanner.close();
426 }
427 return rowcount;
428 }
429
430 HRegion createRegion(final Path testdir, final WALFactory wals)
431 throws IOException {
432
433
434 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("table"));
435 HColumnDescriptor hcd = new HColumnDescriptor(CF);
436 htd.addFamily(hcd);
437 HRegionInfo hri = new HRegionInfo(htd.getTableName(), STARTROW, ENDROW);
438 Configuration conf = TEST_UTIL.getConfiguration();
439 HRegion r = HRegion.createHRegion(hri, testdir, conf, htd);
440 HRegion.closeHRegion(r);
441 ServerName sn = ServerName.valueOf("testSplitTransaction", 100, 42);
442 final RegionServerServices rss = TEST_UTIL.createMockRegionServerService(sn);
443 MockRegionServerServicesWithWALs rsw =
444 new MockRegionServerServicesWithWALs(rss, wals.getWALProvider());
445 return HRegion.openHRegion(testdir, hri, htd,
446 wals.getWAL(hri.getEncodedNameAsBytes(), hri.getTable().getNamespace()),
447 conf, rsw, null);
448 }
449
450 public static class CustomObserver extends BaseRegionObserver{
451 @Override
452 public void preRollBackSplit(
453 ObserverContext<RegionCoprocessorEnvironment> ctx) throws IOException {
454 preRollBackCalled = true;
455 }
456
457 @Override
458 public void postRollBackSplit(
459 ObserverContext<RegionCoprocessorEnvironment> ctx) throws IOException {
460 postRollBackCalled = true;
461 }
462 }
463 }