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.Mockito.spy;
25  import static org.mockito.Mockito.when;
26  
27  import java.io.IOException;
28  import java.util.ArrayList;
29  import java.util.List;
30  
31  import org.apache.hadoop.fs.FileSystem;
32  import org.apache.hadoop.fs.Path;
33  import org.apache.hadoop.hbase.Cell;
34  import org.apache.hadoop.hbase.HBaseTestingUtility;
35  import org.apache.hadoop.hbase.HColumnDescriptor;
36  import org.apache.hadoop.hbase.HConstants;
37  import org.apache.hadoop.hbase.HRegionInfo;
38  import org.apache.hadoop.hbase.HTableDescriptor;
39  import org.apache.hadoop.hbase.KeyValue;
40  import org.apache.hadoop.hbase.Server;
41  import org.apache.hadoop.hbase.SmallTests;
42  import org.apache.hadoop.hbase.TableName;
43  import org.apache.hadoop.hbase.io.hfile.CacheConfig;
44  import org.apache.hadoop.hbase.client.Scan;
45  import org.apache.hadoop.hbase.io.hfile.LruBlockCache;
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.regionserver.wal.HLog;
51  import org.apache.hadoop.hbase.regionserver.wal.HLogFactory;
52  import org.apache.hadoop.hbase.util.Bytes;
53  import org.apache.hadoop.hbase.util.PairOfSameType;
54  import org.apache.zookeeper.KeeperException;
55  import org.junit.After;
56  import org.junit.Before;
57  import org.junit.Test;
58  import org.junit.experimental.categories.Category;
59  import org.mockito.Mockito;
60  
61  import com.google.common.collect.ImmutableList;
62  
63  /**
64   * Test the {@link SplitTransaction} class against an HRegion (as opposed to
65   * running cluster).
66   */
67  @Category(SmallTests.class)
68  public class TestSplitTransaction {
69    private final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
70    private final Path testdir =
71      TEST_UTIL.getDataTestDir(this.getClass().getName());
72    private HRegion parent;
73    private HLog wal;
74    private FileSystem fs;
75    private static final byte [] STARTROW = new byte [] {'a', 'a', 'a'};
76    // '{' is next ascii after 'z'.
77    private static final byte [] ENDROW = new byte [] {'{', '{', '{'};
78    private static final byte [] GOOD_SPLIT_ROW = new byte [] {'d', 'd', 'd'};
79    private static final byte [] CF = HConstants.CATALOG_FAMILY;
80    
81    private static boolean preRollBackCalled = false;
82    private static boolean postRollBackCalled = false;
83    
84    @Before public void setup() throws IOException {
85      this.fs = FileSystem.get(TEST_UTIL.getConfiguration());
86      TEST_UTIL.getConfiguration().set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, CustomObserver.class.getName());
87      this.fs.delete(this.testdir, true);
88      this.wal = HLogFactory.createHLog(fs, this.testdir, "logs",
89        TEST_UTIL.getConfiguration());
90      
91      this.parent = createRegion(this.testdir, this.wal);
92      RegionCoprocessorHost host = new RegionCoprocessorHost(this.parent, null, TEST_UTIL.getConfiguration());
93      this.parent.setCoprocessorHost(host);
94      TEST_UTIL.getConfiguration().setBoolean("hbase.testing.nocluster", true);
95    }
96  
97    @After public void teardown() throws IOException {
98      if (this.parent != null && !this.parent.isClosed()) this.parent.close();
99      Path regionDir = this.parent.getRegionFileSystem().getRegionDir();
100     if (this.fs.exists(regionDir) && !this.fs.delete(regionDir, true)) {
101       throw new IOException("Failed delete of " + regionDir);
102     }
103     if (this.wal != null) this.wal.closeAndDelete();
104     this.fs.delete(this.testdir, true);
105   }
106 
107   @Test public void testFailAfterPONR() throws IOException, KeeperException {
108     final int rowcount = TEST_UTIL.loadRegion(this.parent, CF);
109     assertTrue(rowcount > 0);
110     int parentRowCount = countRows(this.parent);
111     assertEquals(rowcount, parentRowCount);
112 
113     // Start transaction.
114     SplitTransaction st = prepareGOOD_SPLIT_ROW();
115     SplitTransaction spiedUponSt = spy(st);
116     Mockito
117         .doThrow(new MockedFailedDaughterOpen())
118         .when(spiedUponSt)
119         .openDaughterRegion((Server) Mockito.anyObject(),
120             (HRegion) Mockito.anyObject());
121 
122     // Run the execute.  Look at what it returns.
123     boolean expectedException = false;
124     Server mockServer = Mockito.mock(Server.class);
125     when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
126     try {
127       spiedUponSt.execute(mockServer, null);
128     } catch (IOException e) {
129       if (e.getCause() != null &&
130           e.getCause() instanceof MockedFailedDaughterOpen) {
131         expectedException = true;
132       }
133     }
134     assertTrue(expectedException);
135     // Run rollback returns that we should restart.
136     assertFalse(spiedUponSt.rollback(null, null));
137     // Make sure that region a and region b are still in the filesystem, that
138     // they have not been removed; this is supposed to be the case if we go
139     // past point of no return.
140     Path tableDir =  this.parent.getRegionFileSystem().getTableDir();
141     Path daughterADir = new Path(tableDir, spiedUponSt.getFirstDaughter().getEncodedName());
142     Path daughterBDir = new Path(tableDir, spiedUponSt.getSecondDaughter().getEncodedName());
143     assertTrue(TEST_UTIL.getTestFileSystem().exists(daughterADir));
144     assertTrue(TEST_UTIL.getTestFileSystem().exists(daughterBDir));
145   }
146 
147   /**
148    * Test straight prepare works.  Tries to split on {@link #GOOD_SPLIT_ROW}
149    * @throws IOException
150    */
151   @Test public void testPrepare() throws IOException {
152     prepareGOOD_SPLIT_ROW();
153   }
154 
155   private SplitTransaction prepareGOOD_SPLIT_ROW() {
156     return prepareGOOD_SPLIT_ROW(this.parent);
157   }
158 
159   private SplitTransaction prepareGOOD_SPLIT_ROW(final HRegion parentRegion) {
160     SplitTransaction st = new SplitTransaction(parentRegion, GOOD_SPLIT_ROW);
161     assertTrue(st.prepare());
162     return st;
163   }
164 
165   /**
166    * Pass a reference store
167    */
168   @Test public void testPrepareWithRegionsWithReference() throws IOException {
169     HStore storeMock = Mockito.mock(HStore.class);
170     when(storeMock.hasReferences()).thenReturn(true);
171     when(storeMock.getFamily()).thenReturn(new HColumnDescriptor("cf"));
172     when(storeMock.close()).thenReturn(ImmutableList.<StoreFile>of());
173     this.parent.stores.put(Bytes.toBytes(""), storeMock);
174 
175     SplitTransaction st = new SplitTransaction(this.parent, GOOD_SPLIT_ROW);
176 
177     assertFalse("a region should not be splittable if it has instances of store file references",
178                 st.prepare());
179   }
180 
181   /**
182    * Pass an unreasonable split row.
183    */
184   @Test public void testPrepareWithBadSplitRow() throws IOException {
185     // Pass start row as split key.
186     SplitTransaction st = new SplitTransaction(this.parent, STARTROW);
187     assertFalse(st.prepare());
188     st = new SplitTransaction(this.parent, HConstants.EMPTY_BYTE_ARRAY);
189     assertFalse(st.prepare());
190     st = new SplitTransaction(this.parent, new byte [] {'A', 'A', 'A'});
191     assertFalse(st.prepare());
192     st = new SplitTransaction(this.parent, ENDROW);
193     assertFalse(st.prepare());
194   }
195 
196   @Test public void testPrepareWithClosedRegion() throws IOException {
197     this.parent.close();
198     SplitTransaction st = new SplitTransaction(this.parent, GOOD_SPLIT_ROW);
199     assertFalse(st.prepare());
200   }
201 
202   @Test public void testWholesomeSplit() throws IOException {
203     final int rowcount = TEST_UTIL.loadRegion(this.parent, CF, true);
204     assertTrue(rowcount > 0);
205     int parentRowCount = countRows(this.parent);
206     assertEquals(rowcount, parentRowCount);
207 
208     // Pretend region's blocks are not in the cache, used for
209     // testWholesomeSplitWithHFileV1
210     CacheConfig cacheConf = new CacheConfig(TEST_UTIL.getConfiguration());
211     ((LruBlockCache) cacheConf.getBlockCache()).clearCache();
212 
213     // Start transaction.
214     SplitTransaction st = prepareGOOD_SPLIT_ROW();
215 
216     // Run the execute.  Look at what it returns.
217     Server mockServer = Mockito.mock(Server.class);
218     when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
219     PairOfSameType<HRegion> daughters = st.execute(mockServer, null);
220     // Do some assertions about execution.
221     assertTrue(this.fs.exists(this.parent.getRegionFileSystem().getSplitsDir()));
222     // Assert the parent region is closed.
223     assertTrue(this.parent.isClosed());
224 
225     // Assert splitdir is empty -- because its content will have been moved out
226     // to be under the daughter region dirs.
227     assertEquals(0, this.fs.listStatus(this.parent.getRegionFileSystem().getSplitsDir()).length);
228     // Check daughters have correct key span.
229     assertTrue(Bytes.equals(this.parent.getStartKey(), daughters.getFirst().getStartKey()));
230     assertTrue(Bytes.equals(GOOD_SPLIT_ROW, daughters.getFirst().getEndKey()));
231     assertTrue(Bytes.equals(daughters.getSecond().getStartKey(), GOOD_SPLIT_ROW));
232     assertTrue(Bytes.equals(this.parent.getEndKey(), daughters.getSecond().getEndKey()));
233     // Count rows. daughters are already open
234     int daughtersRowCount = 0;
235     for (HRegion openRegion: daughters) {
236       try {
237         int count = countRows(openRegion);
238         assertTrue(count > 0 && count != rowcount);
239         daughtersRowCount += count;
240       } finally {
241         HRegion.closeHRegion(openRegion);
242       }
243     }
244     assertEquals(rowcount, daughtersRowCount);
245     // Assert the write lock is no longer held on parent
246     assertTrue(!this.parent.lock.writeLock().isHeldByCurrentThread());
247   }
248 
249   @Test public void testRollback() throws IOException {
250     final int rowcount = TEST_UTIL.loadRegion(this.parent, CF);
251     assertTrue(rowcount > 0);
252     int parentRowCount = countRows(this.parent);
253     assertEquals(rowcount, parentRowCount);
254 
255     // Start transaction.
256     HRegion spiedRegion = spy(this.parent);
257     SplitTransaction st = prepareGOOD_SPLIT_ROW(spiedRegion);
258     SplitTransaction spiedUponSt = spy(st);
259     when(spiedRegion.createDaughterRegionFromSplits(spiedUponSt.getSecondDaughter())).
260       thenThrow(new MockedFailedDaughterCreation());
261     // Run the execute.  Look at what it returns.
262     boolean expectedException = false;
263     Server mockServer = Mockito.mock(Server.class);
264     when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
265     try {
266       spiedUponSt.execute(mockServer, null);
267     } catch (MockedFailedDaughterCreation e) {
268       expectedException = true;
269     }
270     assertTrue(expectedException);
271     // Run rollback
272     assertTrue(spiedUponSt.rollback(null, null));
273 
274     // Assert I can scan parent.
275     int parentRowCount2 = countRows(this.parent);
276     assertEquals(parentRowCount, parentRowCount2);
277 
278     // Assert rollback cleaned up stuff in fs
279     assertTrue(!this.fs.exists(HRegion.getRegionDir(this.testdir, st.getFirstDaughter())));
280     assertTrue(!this.fs.exists(HRegion.getRegionDir(this.testdir, st.getSecondDaughter())));
281     assertTrue(!this.parent.lock.writeLock().isHeldByCurrentThread());
282 
283     // Now retry the split but do not throw an exception this time.
284     assertTrue(st.prepare());
285     PairOfSameType<HRegion> daughters = st.execute(mockServer, null);
286     // Count rows. daughters are already open
287     int daughtersRowCount = 0;
288     for (HRegion openRegion: daughters) {
289       try {
290         int count = countRows(openRegion);
291         assertTrue(count > 0 && count != rowcount);
292         daughtersRowCount += count;
293       } finally {
294         HRegion.closeHRegion(openRegion);
295       }
296     }
297     assertEquals(rowcount, daughtersRowCount);
298     // Assert the write lock is no longer held on parent
299     assertTrue(!this.parent.lock.writeLock().isHeldByCurrentThread());
300     assertTrue("Rollback hooks should be called.", wasRollBackHookCalled());
301   }
302   
303   private boolean wasRollBackHookCalled(){
304     return (preRollBackCalled && postRollBackCalled);
305   }
306 
307   /**
308    * Exception used in this class only.
309    */
310   @SuppressWarnings("serial")
311   private class MockedFailedDaughterCreation extends IOException {}
312   private class MockedFailedDaughterOpen extends IOException {}
313 
314   private int countRows(final HRegion r) throws IOException {
315     int rowcount = 0;
316     InternalScanner scanner = r.getScanner(new Scan());
317     try {
318       List<Cell> kvs = new ArrayList<Cell>();
319       boolean hasNext = true;
320       while (hasNext) {
321         hasNext = scanner.next(kvs);
322         if (!kvs.isEmpty()) rowcount++;
323       }
324     } finally {
325       scanner.close();
326     }
327     return rowcount;
328   }
329 
330   HRegion createRegion(final Path testdir, final HLog wal)
331   throws IOException {
332     // Make a region with start and end keys. Use 'aaa', to 'AAA'.  The load
333     // region utility will add rows between 'aaa' and 'zzz'.
334     HTableDescriptor htd = new HTableDescriptor(TableName.valueOf("table"));
335     HColumnDescriptor hcd = new HColumnDescriptor(CF);
336     htd.addFamily(hcd);
337     HRegionInfo hri = new HRegionInfo(htd.getTableName(), STARTROW, ENDROW);
338     HRegion r = HRegion.createHRegion(hri, testdir, TEST_UTIL.getConfiguration(), htd);
339     HRegion.closeHRegion(r);
340     return HRegion.openHRegion(testdir, hri, htd, wal,
341       TEST_UTIL.getConfiguration());
342   }
343   
344   public static class CustomObserver extends BaseRegionObserver{
345     @Override
346     public void preRollBackSplit(
347         ObserverContext<RegionCoprocessorEnvironment> ctx) throws IOException {
348       preRollBackCalled = true;
349     }
350     
351     @Override
352     public void postRollBackSplit(
353         ObserverContext<RegionCoprocessorEnvironment> ctx) throws IOException {
354       postRollBackCalled = true;
355     }
356   }
357 
358 }
359