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.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   * Test the {@link SplitTransactionImpl} class against an HRegion (as opposed to
67   * running cluster).
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    // '{' is next ascii after 'z'.
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     // Start transaction.
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     // Run the execute.  Look at what it returns.
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     // Run rollback returns that we should restart.
141     assertFalse(spiedUponSt.rollback(null, null));
142     // Make sure that region a and region b are still in the filesystem, that
143     // they have not been removed; this is supposed to be the case if we go
144     // past point of no return.
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    * Test straight prepare works.  Tries to split on {@link #GOOD_SPLIT_ROW}
154    * @throws IOException
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    * Pass a reference store
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    * Test SplitTransactionListener
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    * Pass an unreasonable split row.
209    */
210   @Test public void testPrepareWithBadSplitRow() throws IOException {
211     // Pass start row as split key.
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     // Pretend region's blocks are not in the cache, used for
235     // testWholesomeSplitWithHFileV1
236     CacheConfig cacheConf = new CacheConfig(TEST_UTIL.getConfiguration());
237     ((LruBlockCache) cacheConf.getBlockCache()).clearCache();
238 
239     // Start transaction.
240     SplitTransactionImpl st = prepareGOOD_SPLIT_ROW();
241 
242     // Run the execute.  Look at what it returns.
243     Server mockServer = Mockito.mock(Server.class);
244     when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
245     PairOfSameType<Region> daughters = st.execute(mockServer, null);
246     // Do some assertions about execution.
247     assertTrue(this.fs.exists(this.parent.getRegionFileSystem().getSplitsDir()));
248     // Assert the parent region is closed.
249     assertTrue(this.parent.isClosed());
250 
251     // Assert splitdir is empty -- because its content will have been moved out
252     // to be under the daughter region dirs.
253     assertEquals(0, this.fs.listStatus(this.parent.getRegionFileSystem().getSplitsDir()).length);
254     // Check daughters have correct key span.
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     // Count rows. daughters are already open
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     // Assert the write lock is no longer held on parent
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     // Start transaction.
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     // Run the execute.  Look at what it returns.
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     // Start transaction.
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     // Run the execute.  Look at what it returns.
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     // Run rollback
331     assertTrue(spiedUponSt.rollback(null, null));
332 
333     // Assert I can scan parent.
334     int parentRowCount2 = countRows(this.parent);
335     assertEquals(parentRowCount, parentRowCount2);
336 
337     // Assert rollback cleaned up stuff in fs
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     // Now retry the split but do not throw an exception this time.
343     assertTrue(st.prepare());
344     PairOfSameType<Region> daughters = st.execute(mockServer, null);
345     // Count rows. daughters are already open
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     // Assert the write lock is no longer held on parent
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    * Exception used in this class only.
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     // Make a region with start and end keys. Use 'aaa', to 'AAA'.  The load
392     // region utility will add rows between 'aaa' and 'zzz'.
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