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