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.assertFalse;
23 import static org.junit.Assert.assertTrue;
24 import static org.mockito.Matchers.anyObject;
25 import static org.mockito.Mockito.doAnswer;
26 import static org.mockito.Mockito.doReturn;
27 import static org.mockito.Mockito.doThrow;
28 import static org.mockito.Mockito.mock;
29 import static org.mockito.Mockito.spy;
30
31 import java.io.IOException;
32 import java.util.Arrays;
33 import java.util.Map;
34
35 import org.apache.commons.logging.Log;
36 import org.apache.commons.logging.LogFactory;
37 import org.apache.hadoop.hbase.*;
38 import org.apache.hadoop.hbase.client.Connection;
39 import org.apache.hadoop.hbase.client.ConnectionFactory;
40 import org.apache.hadoop.hbase.client.Put;
41 import org.apache.hadoop.hbase.client.Result;
42 import org.apache.hadoop.hbase.client.ResultScanner;
43 import org.apache.hadoop.hbase.client.Scan;
44 import org.apache.hadoop.hbase.client.Table;
45 import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
46 import org.apache.hadoop.hbase.filter.Filter;
47 import org.apache.hadoop.hbase.filter.RegexStringComparator;
48 import org.apache.hadoop.hbase.filter.RowFilter;
49 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
50 import org.apache.hadoop.hbase.testclassification.LargeTests;
51 import org.apache.hadoop.hbase.util.Bytes;
52 import org.apache.hadoop.io.NullWritable;
53 import org.apache.hadoop.mapred.JobConf;
54 import org.apache.hadoop.mapred.JobConfigurable;
55 import org.apache.hadoop.mapred.MiniMRCluster;
56 import org.apache.hadoop.mapreduce.InputFormat;
57 import org.apache.hadoop.mapreduce.Job;
58 import org.apache.hadoop.mapreduce.JobContext;
59 import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat;
60 import org.junit.AfterClass;
61 import org.junit.Before;
62 import org.junit.BeforeClass;
63 import org.junit.Test;
64 import org.junit.experimental.categories.Category;
65 import org.mockito.invocation.InvocationOnMock;
66 import org.mockito.stubbing.Answer;
67
68
69
70
71
72 @Category(LargeTests.class)
73 public class TestTableInputFormat {
74
75 private static final Log LOG = LogFactory.getLog(TestTableInputFormat.class);
76
77 private final static HBaseTestingUtility UTIL = new HBaseTestingUtility();
78 private static MiniMRCluster mrCluster;
79 static final byte[] FAMILY = Bytes.toBytes("family");
80
81 private static final byte[][] columns = new byte[][] { FAMILY };
82
83 @BeforeClass
84 public static void beforeClass() throws Exception {
85 UTIL.startMiniCluster();
86 }
87
88 @AfterClass
89 public static void afterClass() throws Exception {
90 UTIL.shutdownMiniCluster();
91 }
92
93 @Before
94 public void before() throws IOException {
95 LOG.info("before");
96 UTIL.ensureSomeRegionServersAvailable(1);
97 LOG.info("before done");
98 }
99
100
101
102
103
104
105
106
107 public static Table createTable(byte[] tableName) throws IOException {
108 return createTable(tableName, new byte[][] { FAMILY });
109 }
110
111
112
113
114
115
116
117
118 public static Table createTable(byte[] tableName, byte[][] families) throws IOException {
119 Table table = UTIL.createTable(TableName.valueOf(tableName), families);
120 Put p = new Put("aaa".getBytes());
121 for (byte[] family : families) {
122 p.addColumn(family, null, "value aaa".getBytes());
123 }
124 table.put(p);
125 p = new Put("bbb".getBytes());
126 for (byte[] family : families) {
127 p.addColumn(family, null, "value bbb".getBytes());
128 }
129 table.put(p);
130 return table;
131 }
132
133
134
135
136
137
138
139
140
141
142 static boolean checkResult(Result r, ImmutableBytesWritable key,
143 byte[] expectedKey, byte[] expectedValue) {
144 assertEquals(0, key.compareTo(expectedKey));
145 Map<byte[], byte[]> vals = r.getFamilyMap(FAMILY);
146 byte[] value = vals.values().iterator().next();
147 assertTrue(Arrays.equals(value, expectedValue));
148 return true;
149 }
150
151
152
153
154
155
156
157
158
159 static void runTestMapreduce(Table table) throws IOException,
160 InterruptedException {
161 org.apache.hadoop.hbase.mapreduce.TableRecordReaderImpl trr =
162 new org.apache.hadoop.hbase.mapreduce.TableRecordReaderImpl();
163 Scan s = new Scan();
164 s.setStartRow("aaa".getBytes());
165 s.setStopRow("zzz".getBytes());
166 s.addFamily(FAMILY);
167 trr.setScan(s);
168 trr.setHTable(table);
169
170 trr.initialize(null, null);
171 Result r = new Result();
172 ImmutableBytesWritable key = new ImmutableBytesWritable();
173
174 boolean more = trr.nextKeyValue();
175 assertTrue(more);
176 key = trr.getCurrentKey();
177 r = trr.getCurrentValue();
178 checkResult(r, key, "aaa".getBytes(), "value aaa".getBytes());
179
180 more = trr.nextKeyValue();
181 assertTrue(more);
182 key = trr.getCurrentKey();
183 r = trr.getCurrentValue();
184 checkResult(r, key, "bbb".getBytes(), "value bbb".getBytes());
185
186
187 more = trr.nextKeyValue();
188 assertFalse(more);
189 }
190
191
192
193
194
195
196 static Table createIOEScannerTable(byte[] name, final int failCnt)
197 throws IOException {
198
199 Answer<ResultScanner> a = new Answer<ResultScanner>() {
200 int cnt = 0;
201
202 @Override
203 public ResultScanner answer(InvocationOnMock invocation) throws Throwable {
204
205 if (cnt++ < failCnt) {
206
207 Scan scan = mock(Scan.class);
208 doReturn("bogus".getBytes()).when(scan).getStartRow();
209 ResultScanner scanner = mock(ResultScanner.class);
210
211 doThrow(new IOException("Injected exception")).when(scanner).next();
212 return scanner;
213 }
214
215
216 return (ResultScanner) invocation.callRealMethod();
217 }
218 };
219
220 Table htable = spy(createTable(name));
221 doAnswer(a).when(htable).getScanner((Scan) anyObject());
222 return htable;
223 }
224
225
226
227
228
229
230
231 static Table createDNRIOEScannerTable(byte[] name, final int failCnt)
232 throws IOException {
233
234 Answer<ResultScanner> a = new Answer<ResultScanner>() {
235 int cnt = 0;
236
237 @Override
238 public ResultScanner answer(InvocationOnMock invocation) throws Throwable {
239
240 if (cnt++ < failCnt) {
241
242 Scan scan = mock(Scan.class);
243 doReturn("bogus".getBytes()).when(scan).getStartRow();
244 ResultScanner scanner = mock(ResultScanner.class);
245
246 invocation.callRealMethod();
247 doThrow(
248 new NotServingRegionException("Injected simulated TimeoutException"))
249 .when(scanner).next();
250 return scanner;
251 }
252
253
254 return (ResultScanner) invocation.callRealMethod();
255 }
256 };
257
258 Table htable = spy(createTable(name));
259 doAnswer(a).when(htable).getScanner((Scan) anyObject());
260 return htable;
261 }
262
263
264
265
266
267
268
269 @Test
270 public void testTableRecordReaderMapreduce() throws IOException,
271 InterruptedException {
272 Table table = createTable("table1-mr".getBytes());
273 runTestMapreduce(table);
274 }
275
276
277
278
279
280
281
282 @Test
283 public void testTableRecordReaderScannerFailMapreduce() throws IOException,
284 InterruptedException {
285 Table htable = createIOEScannerTable("table2-mr".getBytes(), 1);
286 runTestMapreduce(htable);
287 }
288
289
290
291
292
293
294
295 @Test(expected = IOException.class)
296 public void testTableRecordReaderScannerFailMapreduceTwice() throws IOException,
297 InterruptedException {
298 Table htable = createIOEScannerTable("table3-mr".getBytes(), 2);
299 runTestMapreduce(htable);
300 }
301
302
303
304
305
306
307
308 @Test
309 public void testTableRecordReaderScannerTimeoutMapreduce()
310 throws IOException, InterruptedException {
311 Table htable = createDNRIOEScannerTable("table4-mr".getBytes(), 1);
312 runTestMapreduce(htable);
313 }
314
315
316
317
318
319
320
321 @Test(expected = org.apache.hadoop.hbase.NotServingRegionException.class)
322 public void testTableRecordReaderScannerTimeoutMapreduceTwice()
323 throws IOException, InterruptedException {
324 Table htable = createDNRIOEScannerTable("table5-mr".getBytes(), 2);
325 runTestMapreduce(htable);
326 }
327
328
329
330
331 @Test
332 public void testExtensionOfTableInputFormatBase()
333 throws IOException, InterruptedException, ClassNotFoundException {
334 LOG.info("testing use of an InputFormat taht extends InputFormatBase");
335 final Table htable = createTable(Bytes.toBytes("exampleTable"),
336 new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") });
337 testInputFormat(ExampleTIF.class);
338 }
339
340 @Test
341 public void testJobConfigurableExtensionOfTableInputFormatBase()
342 throws IOException, InterruptedException, ClassNotFoundException {
343 LOG.info("testing use of an InputFormat taht extends InputFormatBase, " +
344 "using JobConfigurable.");
345 final Table htable = createTable(Bytes.toBytes("exampleJobConfigurableTable"),
346 new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") });
347 testInputFormat(ExampleJobConfigurableTIF.class);
348 }
349
350 @Test
351 public void testDeprecatedExtensionOfTableInputFormatBase()
352 throws IOException, InterruptedException, ClassNotFoundException {
353 LOG.info("testing use of an InputFormat taht extends InputFormatBase, " +
354 "using the approach documented in 0.98.");
355 final Table htable = createTable(Bytes.toBytes("exampleDeprecatedTable"),
356 new byte[][] { Bytes.toBytes("columnA"), Bytes.toBytes("columnB") });
357 testInputFormat(ExampleDeprecatedTIF.class);
358 }
359
360 void testInputFormat(Class<? extends InputFormat> clazz)
361 throws IOException, InterruptedException, ClassNotFoundException {
362 final Job job = MapreduceTestingShim.createJob(UTIL.getConfiguration());
363 job.setInputFormatClass(clazz);
364 job.setOutputFormatClass(NullOutputFormat.class);
365 job.setMapperClass(ExampleVerifier.class);
366 job.setNumReduceTasks(0);
367
368 LOG.debug("submitting job.");
369 assertTrue("job failed!", job.waitForCompletion(true));
370 assertEquals("Saw the wrong number of instances of the filtered-for row.", 2, job.getCounters()
371 .findCounter(TestTableInputFormat.class.getName() + ":row", "aaa").getValue());
372 assertEquals("Saw any instances of the filtered out row.", 0, job.getCounters()
373 .findCounter(TestTableInputFormat.class.getName() + ":row", "bbb").getValue());
374 assertEquals("Saw the wrong number of instances of columnA.", 1, job.getCounters()
375 .findCounter(TestTableInputFormat.class.getName() + ":family", "columnA").getValue());
376 assertEquals("Saw the wrong number of instances of columnB.", 1, job.getCounters()
377 .findCounter(TestTableInputFormat.class.getName() + ":family", "columnB").getValue());
378 assertEquals("Saw the wrong count of values for the filtered-for row.", 2, job.getCounters()
379 .findCounter(TestTableInputFormat.class.getName() + ":value", "value aaa").getValue());
380 assertEquals("Saw the wrong count of values for the filtered-out row.", 0, job.getCounters()
381 .findCounter(TestTableInputFormat.class.getName() + ":value", "value bbb").getValue());
382 }
383
384 public static class ExampleVerifier extends TableMapper<NullWritable, NullWritable> {
385
386 @Override
387 public void map(ImmutableBytesWritable key, Result value, Context context)
388 throws IOException {
389 for (Cell cell : value.listCells()) {
390 context.getCounter(TestTableInputFormat.class.getName() + ":row",
391 Bytes.toString(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength()))
392 .increment(1l);
393 context.getCounter(TestTableInputFormat.class.getName() + ":family",
394 Bytes.toString(cell.getFamilyArray(), cell.getFamilyOffset(), cell.getFamilyLength()))
395 .increment(1l);
396 context.getCounter(TestTableInputFormat.class.getName() + ":value",
397 Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()))
398 .increment(1l);
399 }
400 }
401
402 }
403
404 public static class ExampleDeprecatedTIF extends TableInputFormatBase implements JobConfigurable {
405
406 @Override
407 public void configure(JobConf job) {
408 try {
409 Connection connection = ConnectionFactory.createConnection(job);
410 Table exampleTable = connection.getTable(TableName.valueOf(("exampleDeprecatedTable")));
411
412 initializeTable(connection, exampleTable.getName());
413 byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"),
414 Bytes.toBytes("columnB") };
415
416 Scan scan = new Scan();
417 for (byte[] family : inputColumns) {
418 scan.addFamily(family);
419 }
420 Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*"));
421 scan.setFilter(exampleFilter);
422 setScan(scan);
423 } catch (IOException exception) {
424 throw new RuntimeException("Failed to configure for job.", exception);
425 }
426 }
427
428 }
429
430
431 public static class ExampleJobConfigurableTIF extends TableInputFormatBase
432 implements JobConfigurable {
433
434 @Override
435 public void configure(JobConf job) {
436 try {
437 Connection connection = ConnectionFactory.createConnection(HBaseConfiguration.create(job));
438 TableName tableName = TableName.valueOf("exampleJobConfigurableTable");
439
440 initializeTable(connection, tableName);
441 byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"),
442 Bytes.toBytes("columnB") };
443
444 Scan scan = new Scan();
445 for (byte[] family : inputColumns) {
446 scan.addFamily(family);
447 }
448 Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*"));
449 scan.setFilter(exampleFilter);
450 setScan(scan);
451 } catch (IOException exception) {
452 throw new RuntimeException("Failed to initialize.", exception);
453 }
454 }
455 }
456
457
458 public static class ExampleTIF extends TableInputFormatBase {
459
460 @Override
461 protected void initialize(JobContext job) throws IOException {
462 Connection connection = ConnectionFactory.createConnection(HBaseConfiguration.create(
463 job.getConfiguration()));
464 TableName tableName = TableName.valueOf("exampleTable");
465
466 initializeTable(connection, tableName);
467 byte[][] inputColumns = new byte [][] { Bytes.toBytes("columnA"),
468 Bytes.toBytes("columnB") };
469
470 Scan scan = new Scan();
471 for (byte[] family : inputColumns) {
472 scan.addFamily(family);
473 }
474 Filter exampleFilter = new RowFilter(CompareOp.EQUAL, new RegexStringComparator("aa.*"));
475 scan.setFilter(exampleFilter);
476 setScan(scan);
477 }
478
479 }
480 }
481