001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.mapreduce; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022import static org.junit.Assert.fail; 023 024import java.io.ByteArrayOutputStream; 025import java.io.IOException; 026import java.io.PrintStream; 027import java.util.ArrayList; 028import org.apache.hadoop.hbase.HBaseClassTestRule; 029import org.apache.hadoop.hbase.HBaseTestingUtility; 030import org.apache.hadoop.hbase.TableName; 031import org.apache.hadoop.hbase.client.Put; 032import org.apache.hadoop.hbase.client.Table; 033import org.apache.hadoop.hbase.testclassification.LargeTests; 034import org.apache.hadoop.hbase.testclassification.MapReduceTests; 035import org.apache.hadoop.hbase.util.Bytes; 036import org.apache.hadoop.hbase.util.LauncherSecurityManager; 037import org.apache.hadoop.mapreduce.Counter; 038import org.apache.hadoop.mapreduce.Job; 039import org.junit.AfterClass; 040import org.junit.BeforeClass; 041import org.junit.ClassRule; 042import org.junit.Test; 043import org.junit.experimental.categories.Category; 044import org.slf4j.Logger; 045import org.slf4j.LoggerFactory; 046 047/** 048 * Test the rowcounter map reduce job. 049 */ 050@Category({MapReduceTests.class, LargeTests.class}) 051public class TestRowCounter { 052 053 @ClassRule 054 public static final HBaseClassTestRule CLASS_RULE = 055 HBaseClassTestRule.forClass(TestRowCounter.class); 056 057 private static final Logger LOG = LoggerFactory.getLogger(TestRowCounter.class); 058 private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 059 private final static String TABLE_NAME = "testRowCounter"; 060 private final static String TABLE_NAME_TS_RANGE = "testRowCounter_ts_range"; 061 private final static String COL_FAM = "col_fam"; 062 private final static String COL1 = "c1"; 063 private final static String COL2 = "c2"; 064 private final static String COMPOSITE_COLUMN = "C:A:A"; 065 private final static int TOTAL_ROWS = 10; 066 private final static int ROWS_WITH_ONE_COL = 2; 067 068 /** 069 * @throws java.lang.Exception 070 */ 071 @BeforeClass 072 public static void setUpBeforeClass() throws Exception { 073 TEST_UTIL.startMiniCluster(); 074 Table table = TEST_UTIL.createTable(TableName.valueOf(TABLE_NAME), Bytes.toBytes(COL_FAM)); 075 writeRows(table, TOTAL_ROWS, ROWS_WITH_ONE_COL); 076 table.close(); 077 } 078 079 /** 080 * @throws java.lang.Exception 081 */ 082 @AfterClass 083 public static void tearDownAfterClass() throws Exception { 084 TEST_UTIL.shutdownMiniCluster(); 085 } 086 087 /** 088 * Test a case when no column was specified in command line arguments. 089 * 090 * @throws Exception 091 */ 092 @Test 093 public void testRowCounterNoColumn() throws Exception { 094 String[] args = new String[] { 095 TABLE_NAME 096 }; 097 runRowCount(args, 10); 098 } 099 100 /** 101 * Test a case when the column specified in command line arguments is 102 * exclusive for few rows. 103 * 104 * @throws Exception 105 */ 106 @Test 107 public void testRowCounterExclusiveColumn() throws Exception { 108 String[] args = new String[] { 109 TABLE_NAME, COL_FAM + ":" + COL1 110 }; 111 runRowCount(args, 8); 112 } 113 114 /** 115 * Test a case when the column specified in command line arguments is 116 * one for which the qualifier contains colons. 117 * 118 * @throws Exception 119 */ 120 @Test 121 public void testRowCounterColumnWithColonInQualifier() throws Exception { 122 String[] args = new String[] { 123 TABLE_NAME, COL_FAM + ":" + COMPOSITE_COLUMN 124 }; 125 runRowCount(args, 8); 126 } 127 128 /** 129 * Test a case when the column specified in command line arguments is not part 130 * of first KV for a row. 131 * 132 * @throws Exception 133 */ 134 @Test 135 public void testRowCounterHiddenColumn() throws Exception { 136 String[] args = new String[] { 137 TABLE_NAME, COL_FAM + ":" + COL2 138 }; 139 runRowCount(args, 10); 140 } 141 142 143 /** 144 * Test a case when the column specified in command line arguments is 145 * exclusive for few rows and also a row range filter is specified 146 * 147 * @throws Exception 148 */ 149 @Test 150 public void testRowCounterColumnAndRowRange() throws Exception { 151 String[] args = new String[] { 152 TABLE_NAME, "--range=\\x00rov,\\x00rox", COL_FAM + ":" + COL1 153 }; 154 runRowCount(args, 8); 155 } 156 157 /** 158 * Test a case when a range is specified with single range of start-end keys 159 * @throws Exception 160 */ 161 @Test 162 public void testRowCounterRowSingleRange() throws Exception { 163 String[] args = new String[] { 164 TABLE_NAME, "--range=\\x00row1,\\x00row3" 165 }; 166 runRowCount(args, 2); 167 } 168 169 /** 170 * Test a case when a range is specified with single range with end key only 171 * @throws Exception 172 */ 173 @Test 174 public void testRowCounterRowSingleRangeUpperBound() throws Exception { 175 String[] args = new String[] { 176 TABLE_NAME, "--range=,\\x00row3" 177 }; 178 runRowCount(args, 3); 179 } 180 181 /** 182 * Test a case when a range is specified with two ranges where one range is with end key only 183 * @throws Exception 184 */ 185 @Test 186 public void testRowCounterRowMultiRangeUpperBound() throws Exception { 187 String[] args = new String[] { 188 TABLE_NAME, "--range=,\\x00row3;\\x00row5,\\x00row7" 189 }; 190 runRowCount(args, 5); 191 } 192 193 /** 194 * Test a case when a range is specified with multiple ranges of start-end keys 195 * @throws Exception 196 */ 197 @Test 198 public void testRowCounterRowMultiRange() throws Exception { 199 String[] args = new String[] { 200 TABLE_NAME, "--range=\\x00row1,\\x00row3;\\x00row5,\\x00row8" 201 }; 202 runRowCount(args, 5); 203 } 204 205 /** 206 * Test a case when a range is specified with multiple ranges of start-end keys; 207 * one range is filled, another two are not 208 * @throws Exception 209 */ 210 @Test 211 public void testRowCounterRowMultiEmptyRange() throws Exception { 212 String[] args = new String[] { 213 TABLE_NAME, "--range=\\x00row1,\\x00row3;;" 214 }; 215 runRowCount(args, 2); 216 } 217 218 @Test 219 public void testRowCounter10kRowRange() throws Exception { 220 String tableName = TABLE_NAME + "10k"; 221 222 try (Table table = TEST_UTIL.createTable( 223 TableName.valueOf(tableName), Bytes.toBytes(COL_FAM))) { 224 writeRows(table, 10000, 0); 225 } 226 String[] args = new String[] { 227 tableName, "--range=\\x00row9872,\\x00row9875" 228 }; 229 runRowCount(args, 3); 230 } 231 232 /** 233 * Test a case when the timerange is specified with --starttime and --endtime options 234 * 235 * @throws Exception 236 */ 237 @Test 238 public void testRowCounterTimeRange() throws Exception { 239 final byte[] family = Bytes.toBytes(COL_FAM); 240 final byte[] col1 = Bytes.toBytes(COL1); 241 Put put1 = new Put(Bytes.toBytes("row_timerange_" + 1)); 242 Put put2 = new Put(Bytes.toBytes("row_timerange_" + 2)); 243 Put put3 = new Put(Bytes.toBytes("row_timerange_" + 3)); 244 245 long ts; 246 247 // clean up content of TABLE_NAME 248 Table table = TEST_UTIL.createTable(TableName.valueOf(TABLE_NAME_TS_RANGE), Bytes.toBytes(COL_FAM)); 249 250 ts = System.currentTimeMillis(); 251 put1.addColumn(family, col1, ts, Bytes.toBytes("val1")); 252 table.put(put1); 253 Thread.sleep(100); 254 255 ts = System.currentTimeMillis(); 256 put2.addColumn(family, col1, ts, Bytes.toBytes("val2")); 257 put3.addColumn(family, col1, ts, Bytes.toBytes("val3")); 258 table.put(put2); 259 table.put(put3); 260 table.close(); 261 262 String[] args = new String[] { 263 TABLE_NAME_TS_RANGE, COL_FAM + ":" + COL1, 264 "--starttime=" + 0, 265 "--endtime=" + ts 266 }; 267 runRowCount(args, 1); 268 269 args = new String[] { 270 TABLE_NAME_TS_RANGE, COL_FAM + ":" + COL1, 271 "--starttime=" + 0, 272 "--endtime=" + (ts - 10) 273 }; 274 runRowCount(args, 1); 275 276 args = new String[] { 277 TABLE_NAME_TS_RANGE, COL_FAM + ":" + COL1, 278 "--starttime=" + ts, 279 "--endtime=" + (ts + 1000) 280 }; 281 runRowCount(args, 2); 282 283 args = new String[] { 284 TABLE_NAME_TS_RANGE, COL_FAM + ":" + COL1, 285 "--starttime=" + (ts - 30 * 1000), 286 "--endtime=" + (ts + 30 * 1000), 287 }; 288 runRowCount(args, 3); 289 } 290 291 /** 292 * Run the RowCounter map reduce job and verify the row count. 293 * 294 * @param args the command line arguments to be used for rowcounter job. 295 * @param expectedCount the expected row count (result of map reduce job). 296 * @throws Exception 297 */ 298 private void runRowCount(String[] args, int expectedCount) throws Exception { 299 Job job = RowCounter.createSubmittableJob(TEST_UTIL.getConfiguration(), args); 300 long start = System.currentTimeMillis(); 301 job.waitForCompletion(true); 302 long duration = System.currentTimeMillis() - start; 303 LOG.debug("row count duration (ms): " + duration); 304 assertTrue(job.isSuccessful()); 305 Counter counter = job.getCounters().findCounter(RowCounter.RowCounterMapper.Counters.ROWS); 306 assertEquals(expectedCount, counter.getValue()); 307 } 308 309 /** 310 * Writes TOTAL_ROWS number of distinct rows in to the table. Few rows have 311 * two columns, Few have one. 312 * 313 * @param table 314 * @throws IOException 315 */ 316 private static void writeRows(Table table, int totalRows, int rowsWithOneCol) throws IOException { 317 final byte[] family = Bytes.toBytes(COL_FAM); 318 final byte[] value = Bytes.toBytes("abcd"); 319 final byte[] col1 = Bytes.toBytes(COL1); 320 final byte[] col2 = Bytes.toBytes(COL2); 321 final byte[] col3 = Bytes.toBytes(COMPOSITE_COLUMN); 322 ArrayList<Put> rowsUpdate = new ArrayList<>(); 323 // write few rows with two columns 324 int i = 0; 325 for (; i < totalRows - rowsWithOneCol; i++) { 326 // Use binary rows values to test for HBASE-15287. 327 byte[] row = Bytes.toBytesBinary("\\x00row" + i); 328 Put put = new Put(row); 329 put.addColumn(family, col1, value); 330 put.addColumn(family, col2, value); 331 put.addColumn(family, col3, value); 332 rowsUpdate.add(put); 333 } 334 335 // write few rows with only one column 336 for (; i < totalRows; i++) { 337 byte[] row = Bytes.toBytes("row" + i); 338 Put put = new Put(row); 339 put.addColumn(family, col2, value); 340 rowsUpdate.add(put); 341 } 342 table.put(rowsUpdate); 343 } 344 345 /** 346 * test main method. Import should print help and call System.exit 347 */ 348 @Test 349 public void testImportMain() throws Exception { 350 PrintStream oldPrintStream = System.err; 351 SecurityManager SECURITY_MANAGER = System.getSecurityManager(); 352 LauncherSecurityManager newSecurityManager= new LauncherSecurityManager(); 353 System.setSecurityManager(newSecurityManager); 354 ByteArrayOutputStream data = new ByteArrayOutputStream(); 355 String[] args = {}; 356 System.setErr(new PrintStream(data)); 357 try { 358 System.setErr(new PrintStream(data)); 359 360 try { 361 RowCounter.main(args); 362 fail("should be SecurityException"); 363 } catch (SecurityException e) { 364 assertEquals(-1, newSecurityManager.getExitCode()); 365 assertTrue(data.toString().contains("Wrong number of parameters:")); 366 assertUsageContent(data.toString()); 367 } 368 data.reset(); 369 try { 370 args = new String[2]; 371 args[0] = "table"; 372 args[1] = "--range=1"; 373 RowCounter.main(args); 374 fail("should be SecurityException"); 375 } catch (SecurityException e) { 376 assertEquals(-1, newSecurityManager.getExitCode()); 377 assertTrue(data.toString().contains( 378 "Please specify range in such format as \"--range=a,b\" or, with only one boundary," + 379 " \"--range=,b\" or \"--range=a,\"")); 380 assertUsageContent(data.toString()); 381 } 382 383 } finally { 384 System.setErr(oldPrintStream); 385 System.setSecurityManager(SECURITY_MANAGER); 386 } 387 } 388 389 private void assertUsageContent(String usage) { 390 assertTrue(usage.contains("Usage: hbase rowcounter [options] <tablename> " 391 + "[--starttime=<start> --endtime=<end>] " 392 + "[--range=[startKey],[endKey][;[startKey],[endKey]...]] [<column1> <column2>...]")); 393 assertTrue(usage.contains("For performance consider the following options:")); 394 assertTrue(usage.contains("-Dhbase.client.scanner.caching=100")); 395 assertTrue(usage.contains("-Dmapreduce.map.speculative=false")); 396 } 397 398}