1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.regionserver;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertFalse;
24  import static org.junit.Assert.assertTrue;
25  import static org.mockito.Mockito.spy;
26  import static org.mockito.Mockito.when;
27  
28  import java.io.IOException;
29  import java.util.ArrayList;
30  import java.util.List;
31  
32  import org.apache.hadoop.fs.FileSystem;
33  import org.apache.hadoop.fs.Path;
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.client.Scan;
42  import org.apache.hadoop.hbase.regionserver.wal.HLog;
43  import org.apache.hadoop.hbase.util.Bytes;
44  import org.apache.hadoop.hbase.util.PairOfSameType;
45  import org.junit.After;
46  import org.junit.Before;
47  import org.junit.Test;
48  import org.mockito.Mockito;
49  
50  /**
51   * Test the {@link SplitTransaction} class against an HRegion (as opposed to
52   * running cluster).
53   */
54  public class TestSplitTransaction {
55    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
56    private final Path testdir =
57      HBaseTestingUtility.getTestDir(this.getClass().getName());
58    private HRegion parent;
59    private HLog wal;
60    private FileSystem fs;
61    private static final byte [] STARTROW = new byte [] {'a', 'a', 'a'};
62    // '{' is next ascii after 'z'.
63    private static final byte [] ENDROW = new byte [] {'{', '{', '{'};
64    private static final byte [] GOOD_SPLIT_ROW = new byte [] {'d', 'd', 'd'};
65    private static final byte [] CF = HConstants.CATALOG_FAMILY;
66  
67    @Before public void setup() throws IOException {
68      this.fs = FileSystem.get(TEST_UTIL.getConfiguration());
69      this.fs.delete(this.testdir, true);
70      this.wal = new HLog(fs, new Path(this.testdir, "logs"),
71        new Path(this.testdir, "archive"),
72        TEST_UTIL.getConfiguration());
73      this.parent = createRegion(this.testdir, this.wal);
74      TEST_UTIL.getConfiguration().setBoolean("hbase.testing.nocluster", true);
75    }
76  
77    @After public void teardown() throws IOException {
78      if (this.parent != null && !this.parent.isClosed()) this.parent.close();
79      if (this.fs.exists(this.parent.getRegionDir()) &&
80          !this.fs.delete(this.parent.getRegionDir(), true)) {
81        throw new IOException("Failed delete of " + this.parent.getRegionDir());
82      }
83      if (this.wal != null) this.wal.closeAndDelete();
84      this.fs.delete(this.testdir, true);
85    }
86  
87    /**
88     * Test straight prepare works.  Tries to split on {@link #GOOD_SPLIT_ROW}
89     * @throws IOException
90     */
91    @Test public void testPrepare() throws IOException {
92      prepareGOOD_SPLIT_ROW();
93    }
94  
95    private SplitTransaction prepareGOOD_SPLIT_ROW() {
96      SplitTransaction st = new SplitTransaction(this.parent, GOOD_SPLIT_ROW);
97      assertTrue(st.prepare());
98      return st;
99    }
100 
101   /**
102    * Pass an unreasonable split row.
103    */
104   @Test public void testPrepareWithBadSplitRow() throws IOException {
105     // Pass start row as split key.
106     SplitTransaction st = new SplitTransaction(this.parent, STARTROW);
107     assertFalse(st.prepare());
108     st = new SplitTransaction(this.parent, HConstants.EMPTY_BYTE_ARRAY);
109     assertFalse(st.prepare());
110     st = new SplitTransaction(this.parent, new byte [] {'A', 'A', 'A'});
111     assertFalse(st.prepare());
112     st = new SplitTransaction(this.parent, ENDROW);
113     assertFalse(st.prepare());
114   }
115 
116   @Test public void testPrepareWithClosedRegion() throws IOException {
117     this.parent.close();
118     SplitTransaction st = new SplitTransaction(this.parent, GOOD_SPLIT_ROW);
119     assertFalse(st.prepare());
120   }
121 
122   @Test public void testWholesomeSplit() throws IOException {
123     final int rowcount = TEST_UTIL.loadRegion(this.parent, CF);
124     assertTrue(rowcount > 0);
125     int parentRowCount = countRows(this.parent);
126     assertEquals(rowcount, parentRowCount);
127 
128     // Start transaction.
129     SplitTransaction st = prepareGOOD_SPLIT_ROW();
130 
131     // Run the execute.  Look at what it returns.
132     Server mockServer = Mockito.mock(Server.class);
133     when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
134     PairOfSameType<HRegion> daughters = st.execute(mockServer, null);
135     // Do some assertions about execution.
136     assertTrue(this.fs.exists(st.getSplitDir()));
137     // Assert the parent region is closed.
138     assertTrue(this.parent.isClosed());
139 
140     // Assert splitdir is empty -- because its content will have been moved out
141     // to be under the daughter region dirs.
142     assertEquals(0, this.fs.listStatus(st.getSplitDir()).length);
143     // Check daughters have correct key span.
144     assertTrue(Bytes.equals(this.parent.getStartKey(),
145       daughters.getFirst().getStartKey()));
146     assertTrue(Bytes.equals(GOOD_SPLIT_ROW,
147       daughters.getFirst().getEndKey()));
148     assertTrue(Bytes.equals(daughters.getSecond().getStartKey(),
149       GOOD_SPLIT_ROW));
150     assertTrue(Bytes.equals(this.parent.getEndKey(),
151       daughters.getSecond().getEndKey()));
152     // Count rows.
153     int daughtersRowCount = 0;
154     for (HRegion r: daughters) {
155       // Open so can count its content.
156       HRegion openRegion = HRegion.openHRegion(r.getRegionInfo(),
157         r.getLog(), r.getConf());
158       try {
159         int count = countRows(openRegion);
160         assertTrue(count > 0 && count != rowcount);
161         daughtersRowCount += count;
162       } finally {
163         openRegion.close();
164       }
165     }
166     assertEquals(rowcount, daughtersRowCount);
167     // Assert the write lock is no longer held on parent
168     assertTrue(!this.parent.lock.writeLock().isHeldByCurrentThread());
169   }
170 
171   @Test public void testRollback() throws IOException {
172     final int rowcount = TEST_UTIL.loadRegion(this.parent, CF);
173     assertTrue(rowcount > 0);
174     int parentRowCount = countRows(this.parent);
175     assertEquals(rowcount, parentRowCount);
176 
177     // Start transaction.
178     SplitTransaction st = prepareGOOD_SPLIT_ROW();
179     SplitTransaction spiedUponSt = spy(st);
180     when(spiedUponSt.createDaughterRegion(spiedUponSt.getSecondDaughter(), null)).
181       thenThrow(new MockedFailedDaughterCreation());
182     // Run the execute.  Look at what it returns.
183     boolean expectedException = false;
184     Server mockServer = Mockito.mock(Server.class);
185     when(mockServer.getConfiguration()).thenReturn(TEST_UTIL.getConfiguration());
186     try {
187       spiedUponSt.execute(mockServer, null);
188     } catch (MockedFailedDaughterCreation e) {
189       expectedException = true;
190     }
191     assertTrue(expectedException);
192     // Run rollback
193     spiedUponSt.rollback(null);
194 
195     // Assert I can scan parent.
196     int parentRowCount2 = countRows(this.parent);
197     assertEquals(parentRowCount, parentRowCount2);
198 
199     // Assert rollback cleaned up stuff in fs
200     assertTrue(!this.fs.exists(HRegion.getRegionDir(this.testdir, st.getFirstDaughter())));
201     assertTrue(!this.fs.exists(HRegion.getRegionDir(this.testdir, st.getSecondDaughter())));
202     assertTrue(!this.parent.lock.writeLock().isHeldByCurrentThread());
203 
204     // Now retry the split but do not throw an exception this time.
205     assertTrue(st.prepare());
206     PairOfSameType<HRegion> daughters = st.execute(mockServer, null);
207     // Count rows.
208     int daughtersRowCount = 0;
209     for (HRegion r: daughters) {
210       // Open so can count its content.
211       HRegion openRegion = HRegion.openHRegion(r.getRegionInfo(),
212         r.getLog(), r.getConf());
213       try {
214         int count = countRows(openRegion);
215         assertTrue(count > 0 && count != rowcount);
216         daughtersRowCount += count;
217       } finally {
218         openRegion.close();
219       }
220     }
221     assertEquals(rowcount, daughtersRowCount);
222     // Assert the write lock is no longer held on parent
223     assertTrue(!this.parent.lock.writeLock().isHeldByCurrentThread());
224   }
225 
226   /**
227    * Exception used in this class only.
228    */
229   @SuppressWarnings("serial")
230   private class MockedFailedDaughterCreation extends IOException {}
231 
232   private int countRows(final HRegion r) throws IOException {
233     int rowcount = 0;
234     InternalScanner scanner = r.getScanner(new Scan());
235     try {
236       List<KeyValue> kvs = new ArrayList<KeyValue>();
237       boolean hasNext = true;
238       while (hasNext) {
239         hasNext = scanner.next(kvs);
240         if (!kvs.isEmpty()) rowcount++;
241       }
242     } finally {
243       scanner.close();
244     }
245     return rowcount;
246   }
247 
248   static HRegion createRegion(final Path testdir, final HLog wal)
249   throws IOException {
250     // Make a region with start and end keys. Use 'aaa', to 'AAA'.  The load
251     // region utility will add rows between 'aaa' and 'zzz'.
252     HTableDescriptor htd = new HTableDescriptor("table");
253     HColumnDescriptor hcd = new HColumnDescriptor(CF);
254     htd.addFamily(hcd);
255     HRegionInfo hri = new HRegionInfo(htd, STARTROW, ENDROW);
256     return HRegion.openHRegion(hri, wal, TEST_UTIL.getConfiguration());
257   }
258 }