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