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