View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.mapreduce;
20  
21  import static org.junit.Assert.assertEquals;
22  import static org.junit.Assert.assertTrue;
23  import static org.junit.Assert.fail;
24  
25  import java.io.ByteArrayOutputStream;
26  import java.io.IOException;
27  import java.io.PrintStream;
28  import java.util.ArrayList;
29  
30  import org.apache.commons.logging.Log;
31  import org.apache.commons.logging.LogFactory;
32  import org.apache.hadoop.conf.Configuration;
33  import org.apache.hadoop.hbase.CategoryBasedTimeout;
34  import org.apache.hadoop.hbase.HBaseTestingUtility;
35  import org.apache.hadoop.hbase.testclassification.LargeTests;
36  import org.apache.hadoop.hbase.TableName;
37  import org.apache.hadoop.hbase.client.HTable;
38  import org.apache.hadoop.hbase.client.Put;
39  import org.apache.hadoop.hbase.client.Table;
40  import org.apache.hadoop.hbase.mapreduce.RowCounter.RowCounterMapper;
41  import org.apache.hadoop.hbase.util.Bytes;
42  import org.apache.hadoop.hbase.util.LauncherSecurityManager;
43  import org.apache.hadoop.mapreduce.Counter;
44  import org.apache.hadoop.mapreduce.Job;
45  import org.apache.hadoop.util.GenericOptionsParser;
46  import org.junit.AfterClass;
47  import org.junit.BeforeClass;
48  import org.junit.Rule;
49  import org.junit.Test;
50  import org.junit.experimental.categories.Category;
51  import org.junit.rules.TestRule;
52  
53  /**
54   * Test the rowcounter map reduce job.
55   */
56  @Category(LargeTests.class)
57  public class TestRowCounter {
58    @Rule public final TestRule timeout = CategoryBasedTimeout.builder().
59        withTimeout(this.getClass()).withLookingForStuckThread(true).build();
60    private static final Log LOG = LogFactory.getLog(TestRowCounter.class);
61    private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
62  
63    private final static String TABLE_NAME = "testRowCounter";
64  
65    private final static String COL_FAM = "col_fam";
66  
67    private final static String COL1 = "c1";
68  
69    private final static String COL2 = "c2";
70  
71    private final static String COMPOSITE_COLUMN = "C:A:A";
72  
73    private final static int TOTAL_ROWS = 10;
74  
75    private final static int ROWS_WITH_ONE_COL = 2;
76  
77    /**
78     * @throws java.lang.Exception
79     */
80    @BeforeClass
81    public static void setUpBeforeClass()
82        throws Exception {
83      TEST_UTIL.setJobWithoutMRCluster();
84      TEST_UTIL.startMiniCluster();
85      Table table = TEST_UTIL.createTable(TableName.valueOf(TABLE_NAME), Bytes.toBytes(COL_FAM));
86      writeRows(table, TOTAL_ROWS, ROWS_WITH_ONE_COL);
87      table.close();
88    }
89  
90    /**
91     * @throws java.lang.Exception
92     */
93    @AfterClass
94    public static void tearDownAfterClass()
95        throws Exception {
96      TEST_UTIL.shutdownMiniCluster();
97    }
98  
99    /**
100    * Test a case when no column was specified in command line arguments.
101    *
102    * @throws Exception
103    */
104   @Test
105   public void testRowCounterNoColumn()
106       throws Exception {
107     String[] args = new String[] {TABLE_NAME};
108     runRowCount(args, 10);
109   }
110 
111   /**
112    * Test a case when the column specified in command line arguments is
113    * exclusive for few rows.
114    *
115    * @throws Exception
116    */
117   @Test
118   public void testRowCounterExclusiveColumn()
119       throws Exception {
120     String[] args = new String[] {TABLE_NAME, COL_FAM + ":" + COL1};
121     runRowCount(args, 8);
122   }
123 
124   /**
125    * Test a case when the column specified in command line arguments is
126    * one for which the qualifier contains colons.
127    *
128    * @throws Exception
129    */
130   @Test
131   public void testRowCounterColumnWithColonInQualifier()
132       throws Exception {
133     String[] args = new String[] {TABLE_NAME, COL_FAM + ":" + COMPOSITE_COLUMN};
134     runRowCount(args, 8);
135   }
136 
137   /**
138    * Test a case when the column specified in command line arguments is not part
139    * of first KV for a row.
140    *
141    * @throws Exception
142    */
143   @Test
144   public void testRowCounterHiddenColumn()
145       throws Exception {
146     String[] args = new String[] {TABLE_NAME, COL_FAM + ":" + COL2};
147     runRowCount(args, 10);
148   }
149 
150   /**
151    * Test a case when a range is specified with single range of start-end keys
152    * @throws Exception
153    */
154   @Test
155   public void testRowCounterRowSingleRange() throws Exception {
156     String[] args = new String[] {
157         TABLE_NAME, "--range=row1,row3"
158     };
159     runRowCount(args, 2);
160   }
161 
162   /**
163    * Test a case when a range is specified with single range with end key only
164    * @throws Exception
165    */
166   @Test
167   public void testRowCounterRowSingleRangeUpperBound() throws Exception {
168     String[] args = new String[] {
169         TABLE_NAME, "--range=,row3"
170     };
171     runRowCount(args, 3);
172   }
173 
174   /**
175    * Test a case when a range is specified with two ranges where one range is with end key only
176    * @throws Exception
177    */
178   @Test
179   public void testRowCounterRowMultiRangeUpperBound() throws Exception {
180     String[] args = new String[] {
181         TABLE_NAME, "--range=,row3;row5,row7"
182     };
183     runRowCount(args, 5);
184   }
185   /**
186    * Test a case when a range is specified with multiple ranges of start-end keys
187    * @throws Exception
188    */
189   @Test
190   public void testRowCounterRowMultiRange() throws Exception {
191     String[] args = new String[] {
192         TABLE_NAME, "--range=row1,row3;row5,row8"
193     };
194     runRowCount(args, 5);
195   }
196 
197   /**
198    * Test a case when a range is specified with multiple ranges of start-end keys;
199    * one range is filled, another two are not
200    * @throws Exception
201    */
202   @Test
203   public void testRowCounterRowMultiEmptyRange() throws Exception {
204     String[] args = new String[] {
205         TABLE_NAME, "--range=row1,row3;;"
206     };
207     runRowCount(args, 2);
208   }
209 
210   @Test
211   public void testRowCounter10kRowRange() throws Exception {
212     String tableName = TABLE_NAME + "10k";
213 
214     try (Table table = TEST_UTIL.createTable(
215         TableName.valueOf(tableName), Bytes.toBytes(COL_FAM))) {
216       writeRows(table, 10000, 0);
217     }
218     String[] args = new String[] {
219         tableName, "--range=row9872,row9875"
220     };
221     runRowCount(args, 3);
222   }
223 
224   /**
225    * test case for HBASE-15287
226    * @throws Exception
227    */
228   @Test
229   public void testRowCounterRowRangeBinary() throws Exception {
230     String tableName = TABLE_NAME + "Binary";
231     try (Table table = TEST_UTIL.createTable(
232         TableName.valueOf(tableName), Bytes.toBytes(COL_FAM))) {
233       writeRows(table, 10, 0, true);
234     }
235     String[] args = new String[] {
236         tableName, "--range=\\x00row5,\\x00row8"
237     };
238     runRowCount(args, 3);
239   }
240 
241   /**
242    * Test a case when the timerange is specified with --starttime and --endtime options
243    *
244    * @throws Exception
245    */
246   @Test
247   public void testRowCounterTimeRange()
248       throws Exception {
249     final String tableName = TABLE_NAME + "TimeRange";
250     final byte[] family = Bytes.toBytes(COL_FAM);
251     final byte[] col1 = Bytes.toBytes(COL1);
252     Put put1 = new Put(Bytes.toBytes("row_timerange_" + 1));
253     Put put2 = new Put(Bytes.toBytes("row_timerange_" + 2));
254     Put put3 = new Put(Bytes.toBytes("row_timerange_" + 3));
255 
256     long ts;
257 
258     // clean up content of TABLE_NAME
259 
260     try (Table table = TEST_UTIL.createTable(
261       TableName.valueOf(tableName), Bytes.toBytes(COL_FAM))) {
262 
263       ts = System.currentTimeMillis();
264       put1.add(family, col1, ts, Bytes.toBytes("val1"));
265       table.put(put1);
266       Thread.sleep(100);
267 
268       ts = System.currentTimeMillis();
269       put2.add(family, col1, ts, Bytes.toBytes("val2"));
270       put3.add(family, col1, ts, Bytes.toBytes("val3"));
271       table.put(put2);
272       table.put(put3);
273     }
274 
275     String[] args = new String[] {tableName, COL_FAM + ":" + COL1, "--starttime=" + 0,
276                                      "--endtime=" + ts};
277     runRowCount(args, 1);
278 
279     args = new String[] {tableName, COL_FAM + ":" + COL1, "--starttime=" + 0,
280                             "--endtime=" + (ts - 10)};
281     runRowCount(args, 1);
282 
283     args = new String[] {tableName, COL_FAM + ":" + COL1, "--starttime=" + ts,
284                             "--endtime=" + (ts + 1000)};
285     runRowCount(args, 2);
286 
287     args = new String[] {tableName, COL_FAM + ":" + COL1, "--starttime=" + (ts - 30 * 1000),
288                             "--endtime=" + (ts + 30 * 1000),};
289     runRowCount(args, 3);
290   }
291 
292   /**
293    * Run the RowCounter map reduce job and verify the row count.
294    *
295    * @param args the command line arguments to be used for rowcounter job.
296    * @param expectedCount the expected row count (result of map reduce job).
297    * @throws Exception
298    */
299   private void runRowCount(String[] args, int expectedCount)
300       throws Exception {
301     GenericOptionsParser opts = new GenericOptionsParser(TEST_UTIL.getConfiguration(), args);
302     Configuration conf = opts.getConfiguration();
303     args = opts.getRemainingArgs();
304     Job job = RowCounter.createSubmittableJob(conf, args);
305     long start = System.currentTimeMillis();
306     job.waitForCompletion(true);
307     long duration = System.currentTimeMillis() - start;
308     LOG.debug("row count duration (ms): " + duration);
309     assertTrue(job.isSuccessful());
310     Counter counter = job.getCounters().findCounter(RowCounterMapper.Counters.ROWS);
311     assertEquals(expectedCount, counter.getValue());
312   }
313 
314   private static void writeRows(Table table, int totalRows, int rowsWithOneCol)
315       throws IOException {
316     writeRows(table, totalRows, rowsWithOneCol, false);
317   }
318 
319   /**
320    * Writes TOTAL_ROWS number of distinct rows in to the table. Few rows have
321    * two columns, Few have one.
322    * @param table
323    * @param totalRows total number of rows to be added to the table
324    * @param rowsWithOneCol number of rows with one column to be added to the table
325    * @param writeBinary whether row prefix has to have \x00 in the beginning
326    * @throws IOException
327    */
328   private static void writeRows(Table table, int totalRows, int rowsWithOneCol, boolean writeBinary)
329       throws IOException {
330     final String rowPrefix = writeBinary ? "\\x00row" : "row";
331     final byte[] family = Bytes.toBytes(COL_FAM);
332     final byte[] value = Bytes.toBytes("abcd");
333     final byte[] col1 = Bytes.toBytes(COL1);
334     final byte[] col2 = Bytes.toBytes(COL2);
335     final byte[] col3 = Bytes.toBytes(COMPOSITE_COLUMN);
336     ArrayList<Put> rowsUpdate = new ArrayList<Put>();
337     // write few rows with two columns
338     int i = 0;
339     for (; i < totalRows - rowsWithOneCol; i++) {
340       byte[] row = Bytes.toBytesBinary(rowPrefix + i);
341       Put put = new Put(row);
342       put.add(family, col1, value);
343       put.add(family, col2, value);
344       put.add(family, col3, value);
345       rowsUpdate.add(put);
346     }
347 
348     // write few rows with only one column
349     for (; i < totalRows; i++) {
350       byte[] row = Bytes.toBytesBinary(rowPrefix + i);
351       Put put = new Put(row);
352       put.add(family, col2, value);
353       rowsUpdate.add(put);
354     }
355     table.put(rowsUpdate);
356   }
357 
358   /**
359    * test main method. Import should print help and call System.exit
360    */
361   @Test
362   public void testImportMain()
363       throws Exception {
364     PrintStream oldPrintStream = System.err;
365     SecurityManager SECURITY_MANAGER = System.getSecurityManager();
366     LauncherSecurityManager newSecurityManager = new LauncherSecurityManager();
367     System.setSecurityManager(newSecurityManager);
368     ByteArrayOutputStream data = new ByteArrayOutputStream();
369     String[] args = {};
370     System.setErr(new PrintStream(data));
371     try {
372       System.setErr(new PrintStream(data));
373 
374       try {
375         RowCounter.main(args);
376         fail("should be SecurityException");
377       } catch (SecurityException e) {
378         assertEquals(-1, newSecurityManager.getExitCode());
379         assertTrue(data.toString().contains("Wrong number of parameters:"));
380         assertTrue(data.toString().contains("Usage: RowCounter [options] <tablename> " +
381                                                 "[--starttime=[start] --endtime=[end] " +
382                                                 "[--range=[startKey],[endKey]" +
383                                                 "[;[startKey],[endKey]...]] " +
384                                                 "[<column1> <column2>...]"));
385         assertTrue(data.toString().contains("-Dhbase.client.scanner.caching=100"));
386         assertTrue(data.toString().contains("-Dmapreduce.map.speculative=false"));
387       }
388       data.reset();
389       try {
390         args = new String[2];
391         args[0] = "table";
392         args[1] = "--range=1";
393         RowCounter.main(args);
394         fail("should be SecurityException");
395       } catch (SecurityException e) {
396         assertEquals(-1, newSecurityManager.getExitCode());
397         assertTrue(data.toString().contains("Please specify range in such format as \"--range=a,b\" or, with only one boundary," +
398 
399                                                 " \"--range=,b\" or \"--range=a,\""));
400         assertTrue(data.toString().contains("Usage: RowCounter [options] <tablename> " +
401                                                 "[--starttime=[start] --endtime=[end] " +
402                                                 "[--range=[startKey],[endKey]" +
403                                                 "[;[startKey],[endKey]...]] " +
404                                                 "[<column1> <column2>...]"));
405       }
406 
407     } finally {
408       System.setErr(oldPrintStream);
409       System.setSecurityManager(SECURITY_MANAGER);
410     }
411 
412   }
413 }