1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
54
55
56
57
58
59
60
61
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
79
80 System.setProperty("hbase.tests.use.shortcircuit.reads", "false");
81
82
83 TEST_UTIL.enableDebug(TableInputFormat.class);
84 TEST_UTIL.enableDebug(TableInputFormatBase.class);
85 TEST_UTIL.setJobWithoutMRCluster();
86
87 TEST_UTIL.startMiniCluster(3);
88
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
100
101 public static class ScanMapper
102 extends TableMapper<ImmutableBytesWritable, ImmutableBytesWritable> {
103
104
105
106
107
108
109
110
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
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
176
177
178
179
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
213
214
215
216
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);
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
251
252
253
254
255
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
279
280
281
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
300
301 assertEquals("Saw the wrong count of mappers per region", expectedNumOfSplits,
302 job.getCounters().findCounter(TaskCounter.SHUFFLED_MAPS).getValue());
303 }
304
305
306
307
308
309 public void testAutobalanceNumOfSplit() throws IOException {
310
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