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.mapreduce;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertNotEquals;
23  import static org.junit.Assert.assertTrue;
24  
25  import java.io.IOException;
26  import java.util.*;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.fs.Path;
32  import org.apache.hadoop.hbase.HBaseTestingUtility;
33  import org.apache.hadoop.hbase.TableName;
34  import org.apache.hadoop.hbase.client.HTable;
35  import org.apache.hadoop.hbase.client.Result;
36  import org.apache.hadoop.hbase.client.Scan;
37  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
38  import org.apache.hadoop.hbase.util.Bytes;
39  import org.apache.hadoop.io.NullWritable;
40  import org.apache.hadoop.mapred.JobConf;
41  import org.apache.hadoop.mapreduce.InputSplit;
42  import org.apache.hadoop.mapreduce.Job;
43  import org.apache.hadoop.mapreduce.Reducer;
44  import org.apache.hadoop.mapreduce.TaskCounter;
45  import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
46  import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat;
47  import org.junit.AfterClass;
48  import org.junit.Assert;
49  import org.junit.BeforeClass;
50  
51  
52  /**
53   * <p>
54   * Tests various scan start and stop row scenarios. This is set in a scan and
55   * tested in a MapReduce job to see if that is handed over and done properly
56   * too.
57   * </p>
58   * <p>
59   * This test is broken into two parts in order to side-step the test timeout
60   * period of 900, as documented in HBASE-8326.
61   * </p>
62   */
63  public abstract class TestTableInputFormatScanBase {
64  
65    private static final Log LOG = LogFactory.getLog(TestTableInputFormatScanBase.class);
66    static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
67  
68    static final byte[] TABLE_NAME = Bytes.toBytes("scantest");
69    static final byte[] INPUT_FAMILY = Bytes.toBytes("contents");
70    static final byte[][] INPUT_FAMILYS = {Bytes.toBytes("content1"), Bytes.toBytes("content2")};
71    static final String KEY_STARTROW = "startRow";
72    static final String KEY_LASTROW = "stpRow";
73  
74    private static HTable table = null;
75  
76    @BeforeClass
77    public static void setUpBeforeClass() throws Exception {
78      // test intermittently fails under hadoop2 (2.0.2-alpha) if shortcircuit-read (scr) is on.
79      // this turns it off for this test.  TODO: Figure out why scr breaks recovery. 
80      System.setProperty("hbase.tests.use.shortcircuit.reads", "false");
81  
82      // switch TIF to log at DEBUG level
83      TEST_UTIL.enableDebug(TableInputFormat.class);
84      TEST_UTIL.enableDebug(TableInputFormatBase.class);
85      TEST_UTIL.setJobWithoutMRCluster();
86      // start mini hbase cluster
87      TEST_UTIL.startMiniCluster(3);
88      // create and fill table
89      table = TEST_UTIL.createMultiRegionTable(TableName.valueOf(TABLE_NAME), INPUT_FAMILY);
90      TEST_UTIL.loadTable(table, INPUT_FAMILY, false);
91    }
92  
93    @AfterClass
94    public static void tearDownAfterClass() throws Exception {
95      TEST_UTIL.shutdownMiniCluster();
96    }
97  
98    /**
99     * Pass the key and value to reduce.
100    */
101   public static class ScanMapper
102   extends TableMapper<ImmutableBytesWritable, ImmutableBytesWritable> {
103 
104     /**
105      * Pass the key and value to reduce.
106      *
107      * @param key  The key, here "aaa", "aab" etc.
108      * @param value  The value is the same as the key.
109      * @param context  The task context.
110      * @throws IOException When reading the rows fails.
111      */
112     @Override
113     public void map(ImmutableBytesWritable key, Result value,
114       Context context)
115     throws IOException, InterruptedException {
116       if (value.size() != 1) {
117         throw new IOException("There should only be one input column");
118       }
119       Map<byte[], NavigableMap<byte[], NavigableMap<Long, byte[]>>>
120         cf = value.getMap();
121       if(!cf.containsKey(INPUT_FAMILY)) {
122         throw new IOException("Wrong input columns. Missing: '" +
123           Bytes.toString(INPUT_FAMILY) + "'.");
124       }
125       String val = Bytes.toStringBinary(value.getValue(INPUT_FAMILY, null));
126       LOG.info("map: key -> " + Bytes.toStringBinary(key.get()) +
127         ", value -> " + val);
128       context.write(key, key);
129     }
130 
131   }
132 
133   /**
134    * Checks the last and first key seen against the scanner boundaries.
135    */
136   public static class ScanReducer
137   extends Reducer<ImmutableBytesWritable, ImmutableBytesWritable,
138                   NullWritable, NullWritable> {
139 
140     private String first = null;
141     private String last = null;
142 
143     protected void reduce(ImmutableBytesWritable key,
144         Iterable<ImmutableBytesWritable> values, Context context)
145     throws IOException ,InterruptedException {
146       int count = 0;
147       for (ImmutableBytesWritable value : values) {
148         String val = Bytes.toStringBinary(value.get());
149         LOG.info("reduce: key[" + count + "] -> " +
150           Bytes.toStringBinary(key.get()) + ", value -> " + val);
151         if (first == null) first = val;
152         last = val;
153         count++;
154       }
155     }
156 
157     protected void cleanup(Context context)
158     throws IOException, InterruptedException {
159       Configuration c = context.getConfiguration();
160       String startRow = c.get(KEY_STARTROW);
161       String lastRow = c.get(KEY_LASTROW);
162       LOG.info("cleanup: first -> \"" + first + "\", start row -> \"" + startRow + "\"");
163       LOG.info("cleanup: last -> \"" + last + "\", last row -> \"" + lastRow + "\"");
164       if (startRow != null && startRow.length() > 0) {
165         assertEquals(startRow, first);
166       }
167       if (lastRow != null && lastRow.length() > 0) {
168         assertEquals(lastRow, last);
169       }
170     }
171 
172   }
173 
174   /**
175    * Tests an MR Scan initialized from properties set in the Configuration.
176    * 
177    * @throws IOException
178    * @throws ClassNotFoundException
179    * @throws InterruptedException
180    */
181   protected void testScanFromConfiguration(String start, String stop, String last)
182   throws IOException, InterruptedException, ClassNotFoundException {
183     String jobName = "ScanFromConfig" + (start != null ? start.toUpperCase(Locale.ROOT) : "Empty") +
184       "To" + (stop != null ? stop.toUpperCase(Locale.ROOT) : "Empty");
185     Configuration c = new Configuration(TEST_UTIL.getConfiguration());
186     c.set(TableInputFormat.INPUT_TABLE, Bytes.toString(TABLE_NAME));
187     c.set(TableInputFormat.SCAN_COLUMN_FAMILY, Bytes.toString(INPUT_FAMILY));
188     c.set(KEY_STARTROW, start != null ? start : "");
189     c.set(KEY_LASTROW, last != null ? last : "");
190 
191     if (start != null) {
192       c.set(TableInputFormat.SCAN_ROW_START, start);
193     }
194 
195     if (stop != null) {
196       c.set(TableInputFormat.SCAN_ROW_STOP, stop);
197     }
198 
199     Job job = new Job(c, jobName);
200     job.setMapperClass(ScanMapper.class);
201     job.setReducerClass(ScanReducer.class);
202     job.setMapOutputKeyClass(ImmutableBytesWritable.class);
203     job.setMapOutputValueClass(ImmutableBytesWritable.class);
204     job.setInputFormatClass(TableInputFormat.class);
205     job.setNumReduceTasks(1);
206     FileOutputFormat.setOutputPath(job, new Path(job.getJobName()));
207     TableMapReduceUtil.addDependencyJars(job);
208     assertTrue(job.waitForCompletion(true));
209   }
210 
211   /**
212    * Tests a MR scan using specific start and stop rows.
213    *
214    * @throws IOException
215    * @throws ClassNotFoundException
216    * @throws InterruptedException
217    */
218   protected void testScan(String start, String stop, String last)
219   throws IOException, InterruptedException, ClassNotFoundException {
220     String jobName = "Scan" + (start != null ? start.toUpperCase(Locale.ROOT) : "Empty") +
221       "To" + (stop != null ? stop.toUpperCase(Locale.ROOT) : "Empty");
222     LOG.info("Before map/reduce startup - job " + jobName);
223     Configuration c = new Configuration(TEST_UTIL.getConfiguration());
224     Scan scan = new Scan();
225     scan.addFamily(INPUT_FAMILY);
226     if (start != null) {
227       scan.setStartRow(Bytes.toBytes(start));
228     }
229     c.set(KEY_STARTROW, start != null ? start : "");
230     if (stop != null) {
231       scan.setStopRow(Bytes.toBytes(stop));
232     }
233     c.set(KEY_LASTROW, last != null ? last : "");
234     LOG.info("scan before: " + scan);
235     Job job = new Job(c, jobName);
236     TableMapReduceUtil.initTableMapperJob(
237       Bytes.toString(TABLE_NAME), scan, ScanMapper.class,
238       ImmutableBytesWritable.class, ImmutableBytesWritable.class, job);
239     job.setReducerClass(ScanReducer.class);
240     job.setNumReduceTasks(1); // one to get final "first" and "last" key
241     FileOutputFormat.setOutputPath(job,
242         new Path(TEST_UTIL.getDataTestDir(), job.getJobName()));
243     LOG.info("Started " + job.getJobName());
244     assertTrue(job.waitForCompletion(true));
245     LOG.info("After map/reduce completion - job " + jobName);
246   }
247 
248 
249   /**
250    * Tests Number of inputSplits for MR job when specify number of mappers for TableInputFormatXXX
251    * This test does not run MR job
252    *
253    * @throws IOException
254    * @throws ClassNotFoundException
255    * @throws InterruptedException
256    */
257   public void testNumOfSplits(int splitsPerRegion, int expectedNumOfSplits) throws IOException,
258       InterruptedException,
259       ClassNotFoundException {
260     String jobName = "TestJobForNumOfSplits";
261     LOG.info("Before map/reduce startup - job " + jobName);
262     Configuration c = new Configuration(TEST_UTIL.getConfiguration());
263     Scan scan = new Scan();
264     scan.addFamily(INPUT_FAMILY);
265     c.setInt("hbase.mapreduce.input.mappers.per.region", splitsPerRegion);
266     c.set(KEY_STARTROW, "");
267     c.set(KEY_LASTROW, "");
268     Job job = new Job(c, jobName);
269     TableMapReduceUtil.initTableMapperJob(TableName.valueOf(TABLE_NAME).getNameAsString(), scan, ScanMapper.class,
270         ImmutableBytesWritable.class, ImmutableBytesWritable.class, job);
271     TableInputFormat tif = new TableInputFormat();
272     tif.setConf(job.getConfiguration());
273     List<InputSplit> splits = tif.getSplits(job);
274     Assert.assertEquals(expectedNumOfSplits, splits.size());
275   }
276 
277   /**
278    * Run MR job to check the number of mapper = expectedNumOfSplits
279    * @throws IOException
280    * @throws InterruptedException
281    * @throws ClassNotFoundException
282    */
283   public void testNumOfSplitsMR(int splitsPerRegion, int expectedNumOfSplits) throws IOException,
284       InterruptedException,
285       ClassNotFoundException {
286     String jobName = "TestJobForNumOfSplits-MR";
287     LOG.info("Before map/reduce startup - job " + jobName);
288     Configuration c = new Configuration(TEST_UTIL.getConfiguration());
289     Scan scan = new Scan();
290     scan.addFamily(INPUT_FAMILY);
291     c.setInt("hbase.mapreduce.input.mappers.per.region", splitsPerRegion);
292     Job job = new Job(c, jobName);
293     TableMapReduceUtil.initTableMapperJob(Bytes.toString(TABLE_NAME), scan, ScanMapper.class,
294         ImmutableBytesWritable.class, ImmutableBytesWritable.class, job);
295     job.setReducerClass(ScanReducer.class);
296     job.setNumReduceTasks(1);
297     job.setOutputFormatClass(NullOutputFormat.class);
298     assertTrue("job failed!", job.waitForCompletion(true));
299     // for some reason, hbase does not expose JobCounter.TOTAL_LAUNCHED_MAPS,
300     // we use TaskCounter.SHUFFLED_MAPS to get total launched maps
301     assertEquals("Saw the wrong count of mappers per region", expectedNumOfSplits,
302         job.getCounters().findCounter(TaskCounter.SHUFFLED_MAPS).getValue());
303   }
304 
305   /**
306    * Run MR job to test autobalance for setting number of mappers for TIF
307    * This does not run real MR job
308    */
309   public void testAutobalanceNumOfSplit() throws IOException {
310     // set up splits for testing
311     List<InputSplit> splits = new ArrayList<>(5);
312     int[] regionLen = {100, 200, 200, 400, 600};
313     for (int i = 0; i < 5; i++) {
314       InputSplit split = new TableSplit(TableName.valueOf(TABLE_NAME), new Scan(),
315           Bytes.toBytes(i), Bytes.toBytes(i + 1), "", "", regionLen[i] * 1048576);
316       splits.add(split);
317     }
318     TableInputFormat tif = new TableInputFormat();
319     List<InputSplit> res = tif.calculateAutoBalancedSplits(splits, 1073741824);
320 
321     assertEquals("Saw the wrong number of splits", 5, res.size());
322     TableSplit ts1 = (TableSplit) res.get(0);
323     assertEquals("The first split end key should be", 2, Bytes.toInt(ts1.getEndRow()));
324     TableSplit ts2 = (TableSplit) res.get(1);
325     assertEquals("The second split regionsize should be", 200 * 1048576, ts2.getLength());
326     TableSplit ts3 = (TableSplit) res.get(2);
327     assertEquals("The third split start key should be", 3, Bytes.toInt(ts3.getStartRow()));
328     TableSplit ts4 = (TableSplit) res.get(4);
329     assertNotEquals("The seventh split start key should not be", 4, Bytes.toInt(ts4.getStartRow()));
330   }
331 }
332