View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
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   * Test the {@link SplitTransactionImpl} class against an HRegion (as opposed to
73   * running cluster).
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    // '{' is next ascii after 'z'.
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     // Start transaction.
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     // Run the execute.  Look at what it returns.
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     // Run rollback returns that we should restart.
174     assertFalse(spiedUponSt.rollback(null, null));
175     // Make sure that region a and region b are still in the filesystem, that
176     // they have not been removed; this is supposed to be the case if we go
177     // past point of no return.
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    * Test straight prepare works.  Tries to split on {@link #GOOD_SPLIT_ROW}
187    * @throws IOException
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    * Pass a reference store
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    * Test SplitTransactionListener
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    * Pass an unreasonable split row.
245    */
246   @Test
247   public void testPrepareWithBadSplitRow() throws IOException {
248     // Pass start row as split key.
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     // Pretend region's blocks are not in the cache, used for
274     // testWholesomeSplitWithHFileV1
275     CacheConfig cacheConf = new CacheConfig(TEST_UTIL.getConfiguration());
276     ((LruBlockCache) cacheConf.getBlockCache()).clearCache();
277 
278     // Start transaction.
279     SplitTransactionImpl st = prepareGOOD_SPLIT_ROW();
280 
281     // Run the execute.  Look at what it returns.
282     Server mockServer = Mockito.mock(Server.class);
283     when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
284     PairOfSameType<Region> daughters = st.execute(mockServer, null);
285     // Do some assertions about execution.
286     assertTrue(this.fs.exists(this.parent.getRegionFileSystem().getSplitsDir()));
287     // Assert the parent region is closed.
288     assertTrue(this.parent.isClosed());
289 
290     // Assert splitdir is empty -- because its content will have been moved out
291     // to be under the daughter region dirs.
292     assertEquals(0, this.fs.listStatus(this.parent.getRegionFileSystem().getSplitsDir()).length);
293     // Check daughters have correct key span.
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     // Count rows. daughters are already open
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     // Assert the write lock is no longer held on parent
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     // Start transaction.
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     // Run the execute.  Look at what it returns.
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     // Start transaction.
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     // Run the execute.  Look at what it returns.
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     // Run rollback
370     assertTrue(spiedUponSt.rollback(null, null));
371 
372     // Assert I can scan parent.
373     int parentRowCount2 = countRows(this.parent);
374     assertEquals(parentRowCount, parentRowCount2);
375 
376     // Assert rollback cleaned up stuff in fs
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     // Now retry the split but do not throw an exception this time.
384     assertTrue(st.prepare());
385     PairOfSameType<Region> daughters = st.execute(mockServer, null);
386     // Count rows. daughters are already open
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     // Assert the write lock is no longer held on parent
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    * Exception used in this class only.
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     // Make a region with start and end keys. Use 'aaa', to 'AAA'.  The load
433     // region utility will add rows between 'aaa' and 'zzz'.
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 }