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