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 java.util.Arrays;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.HBaseClassTestRule;
031import org.apache.hadoop.hbase.HBaseTestingUtil;
032import org.apache.hadoop.hbase.TableName;
033import org.apache.hadoop.hbase.client.Delete;
034import org.apache.hadoop.hbase.client.Put;
035import org.apache.hadoop.hbase.client.Table;
036import org.apache.hadoop.hbase.testclassification.LargeTests;
037import org.apache.hadoop.hbase.testclassification.MapReduceTests;
038import org.apache.hadoop.hbase.util.Bytes;
039import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
040import org.apache.hadoop.hbase.util.LauncherSecurityManager;
041import org.apache.hadoop.mapreduce.Counter;
042import org.apache.hadoop.mapreduce.Counters;
043import org.apache.hadoop.mapreduce.Job;
044import org.junit.AfterClass;
045import org.junit.BeforeClass;
046import org.junit.ClassRule;
047import org.junit.Test;
048import org.junit.experimental.categories.Category;
049import org.slf4j.Logger;
050import org.slf4j.LoggerFactory;
051
052/**
053 * Test the rowcounter map reduce job.
054 */
055@Category({ MapReduceTests.class, LargeTests.class })
056public class TestRowCounter {
057
058  @ClassRule
059  public static final HBaseClassTestRule CLASS_RULE =
060    HBaseClassTestRule.forClass(TestRowCounter.class);
061
062  private static final Logger LOG = LoggerFactory.getLogger(TestRowCounter.class);
063  private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil();
064  private final static String TABLE_NAME = "testRowCounter";
065  private final static String TABLE_NAME_TS_RANGE = "testRowCounter_ts_range";
066  private final static String COL_FAM = "col_fam";
067  private final static String COL1 = "c1";
068  private final static String COL2 = "c2";
069  private final static String COMPOSITE_COLUMN = "C:A:A";
070  private final static int TOTAL_ROWS = 10;
071  private final static int ROWS_WITH_ONE_COL = 2;
072
073  /**
074   * @throws java.lang.Exception
075   */
076  @BeforeClass
077  public static void setUpBeforeClass() throws Exception {
078    TEST_UTIL.startMiniCluster();
079    Table table = TEST_UTIL.createTable(TableName.valueOf(TABLE_NAME), Bytes.toBytes(COL_FAM));
080    writeRows(table, TOTAL_ROWS, ROWS_WITH_ONE_COL);
081    table.close();
082  }
083
084  /**
085   * @throws java.lang.Exception
086   */
087  @AfterClass
088  public static void tearDownAfterClass() throws Exception {
089    TEST_UTIL.shutdownMiniCluster();
090  }
091
092  /**
093   * Test a case when no column was specified in command line arguments.
094   */
095  @Test
096  public void testRowCounterNoColumn() throws Exception {
097    String[] args = new String[] { TABLE_NAME };
098    runRowCount(args, 10);
099  }
100
101  /**
102   * Test a case when the column specified in command line arguments is exclusive for few rows.
103   */
104  @Test
105  public void testRowCounterExclusiveColumn() throws Exception {
106    String[] args = new String[] { TABLE_NAME, COL_FAM + ":" + COL1 };
107    runRowCount(args, 8);
108  }
109
110  /**
111   * Test a case when the column specified in command line arguments is one for which the qualifier
112   * contains colons.
113   */
114  @Test
115  public void testRowCounterColumnWithColonInQualifier() throws Exception {
116    String[] args = new String[] { TABLE_NAME, COL_FAM + ":" + COMPOSITE_COLUMN };
117    runRowCount(args, 8);
118  }
119
120  /**
121   * Test a case when the column specified in command line arguments is not part of first KV for a
122   * row.
123   */
124  @Test
125  public void testRowCounterHiddenColumn() throws Exception {
126    String[] args = new String[] { TABLE_NAME, COL_FAM + ":" + COL2 };
127    runRowCount(args, 10);
128  }
129
130  /**
131   * Test a case when the column specified in command line arguments is exclusive for few rows and
132   * also a row range filter is specified
133   */
134  @Test
135  public void testRowCounterColumnAndRowRange() throws Exception {
136    String[] args = new String[] { TABLE_NAME, "--range=\\x00rov,\\x00rox", COL_FAM + ":" + COL1 };
137    runRowCount(args, 8);
138  }
139
140  /**
141   * Test a case when a range is specified with single range of start-end keys
142   */
143  @Test
144  public void testRowCounterRowSingleRange() throws Exception {
145    String[] args = new String[] { TABLE_NAME, "--range=\\x00row1,\\x00row3" };
146    runRowCount(args, 2);
147  }
148
149  /**
150   * Test a case when a range is specified with single range with end key only
151   */
152  @Test
153  public void testRowCounterRowSingleRangeUpperBound() throws Exception {
154    String[] args = new String[] { TABLE_NAME, "--range=,\\x00row3" };
155    runRowCount(args, 3);
156  }
157
158  /**
159   * Test a case when a range is specified with two ranges where one range is with end key only
160   */
161  @Test
162  public void testRowCounterRowMultiRangeUpperBound() throws Exception {
163    String[] args = new String[] { TABLE_NAME, "--range=,\\x00row3;\\x00row5,\\x00row7" };
164    runRowCount(args, 5);
165  }
166
167  /**
168   * Test a case when a range is specified with multiple ranges of start-end keys
169   */
170  @Test
171  public void testRowCounterRowMultiRange() throws Exception {
172    String[] args = new String[] { TABLE_NAME, "--range=\\x00row1,\\x00row3;\\x00row5,\\x00row8" };
173    runRowCount(args, 5);
174  }
175
176  /**
177   * Test a case when a range is specified with multiple ranges of start-end keys; one range is
178   * filled, another two are not
179   */
180  @Test
181  public void testRowCounterRowMultiEmptyRange() throws Exception {
182    String[] args = new String[] { TABLE_NAME, "--range=\\x00row1,\\x00row3;;" };
183    runRowCount(args, 2);
184  }
185
186  @Test
187  public void testRowCounter10kRowRange() throws Exception {
188    String tableName = TABLE_NAME + "10k";
189
190    try (
191      Table table = TEST_UTIL.createTable(TableName.valueOf(tableName), Bytes.toBytes(COL_FAM))) {
192      writeRows(table, 10000, 0);
193    }
194    String[] args = new String[] { tableName, "--range=\\x00row9872,\\x00row9875" };
195    runRowCount(args, 3);
196  }
197
198  /**
199   * Test a case when the timerange is specified with --starttime and --endtime options
200   */
201  @Test
202  public void testRowCounterTimeRange() throws Exception {
203    final byte[] family = Bytes.toBytes(COL_FAM);
204    final byte[] col1 = Bytes.toBytes(COL1);
205    Put put1 = new Put(Bytes.toBytes("row_timerange_" + 1));
206    Put put2 = new Put(Bytes.toBytes("row_timerange_" + 2));
207    Put put3 = new Put(Bytes.toBytes("row_timerange_" + 3));
208
209    long ts;
210
211    // clean up content of TABLE_NAME
212    Table table =
213      TEST_UTIL.createTable(TableName.valueOf(TABLE_NAME_TS_RANGE), Bytes.toBytes(COL_FAM));
214
215    ts = EnvironmentEdgeManager.currentTime();
216    put1.addColumn(family, col1, ts, Bytes.toBytes("val1"));
217    table.put(put1);
218    Thread.sleep(100);
219
220    ts = EnvironmentEdgeManager.currentTime();
221    put2.addColumn(family, col1, ts, Bytes.toBytes("val2"));
222    put3.addColumn(family, col1, ts, Bytes.toBytes("val3"));
223    table.put(put2);
224    table.put(put3);
225    table.close();
226
227    String[] args = new String[] { TABLE_NAME_TS_RANGE, COL_FAM + ":" + COL1, "--starttime=" + 0,
228      "--endtime=" + ts };
229    runRowCount(args, 1);
230
231    args = new String[] { TABLE_NAME_TS_RANGE, COL_FAM + ":" + COL1, "--starttime=" + 0,
232      "--endtime=" + (ts - 10) };
233    runRowCount(args, 1);
234
235    args = new String[] { TABLE_NAME_TS_RANGE, COL_FAM + ":" + COL1, "--starttime=" + ts,
236      "--endtime=" + (ts + 1000) };
237    runRowCount(args, 2);
238
239    args = new String[] { TABLE_NAME_TS_RANGE, COL_FAM + ":" + COL1,
240      "--starttime=" + (ts - 30 * 1000), "--endtime=" + (ts + 30 * 1000), };
241    runRowCount(args, 3);
242  }
243
244  /**
245   * Run the RowCounter map reduce job and verify the row count.
246   * @param args          the command line arguments to be used for rowcounter job.
247   * @param expectedCount the expected row count (result of map reduce job).
248   */
249  private void runRowCount(String[] args, int expectedCount) throws Exception {
250    RowCounter rowCounter = new RowCounter();
251    rowCounter.setConf(TEST_UTIL.getConfiguration());
252    args = Arrays.copyOf(args, args.length + 1);
253    args[args.length - 1] = "--expectedCount=" + expectedCount;
254    long start = EnvironmentEdgeManager.currentTime();
255    int result = rowCounter.run(args);
256    long duration = EnvironmentEdgeManager.currentTime() - start;
257    LOG.debug("row count duration (ms): " + duration);
258    assertTrue(result == 0);
259  }
260
261  /**
262   * Run the RowCounter map reduce job and verify the row count.
263   * @param args          the command line arguments to be used for rowcounter job.
264   * @param expectedCount the expected row count (result of map reduce job).
265   * @throws Exception in case of any unexpected error.
266   */
267  private void runCreateSubmittableJobWithArgs(String[] args, int expectedCount) throws Exception {
268    Job job = RowCounter.createSubmittableJob(TEST_UTIL.getConfiguration(), args);
269    long start = EnvironmentEdgeManager.currentTime();
270    job.waitForCompletion(true);
271    long duration = EnvironmentEdgeManager.currentTime() - start;
272    LOG.debug("row count duration (ms): " + duration);
273    assertTrue(job.isSuccessful());
274    Counter counter = job.getCounters().findCounter(RowCounter.RowCounterMapper.Counters.ROWS);
275    assertEquals(expectedCount, counter.getValue());
276  }
277
278  @Test
279  public void testCreateSubmittableJobWithArgsNoColumn() throws Exception {
280    String[] args = new String[] { TABLE_NAME };
281    runCreateSubmittableJobWithArgs(args, 10);
282  }
283
284  /**
285   * Test a case when the column specified in command line arguments is exclusive for few rows.
286   * @throws Exception in case of any unexpected error.
287   */
288  @Test
289  public void testCreateSubmittableJobWithArgsExclusiveColumn() throws Exception {
290    String[] args = new String[] { TABLE_NAME, COL_FAM + ":" + COL1 };
291    runCreateSubmittableJobWithArgs(args, 8);
292  }
293
294  /**
295   * Test a case when the column specified in command line arguments is one for which the qualifier
296   * contains colons.
297   * @throws Exception in case of any unexpected error.
298   */
299  @Test
300  public void testCreateSubmittableJobWithArgsColumnWithColonInQualifier() throws Exception {
301    String[] args = new String[] { TABLE_NAME, COL_FAM + ":" + COMPOSITE_COLUMN };
302    runCreateSubmittableJobWithArgs(args, 8);
303  }
304
305  /**
306   * Test a case when the column specified in command line arguments is not part of first KV for a
307   * row.
308   * @throws Exception in case of any unexpected error.
309   */
310  @Test
311  public void testCreateSubmittableJobWithArgsHiddenColumn() throws Exception {
312    String[] args = new String[] { TABLE_NAME, COL_FAM + ":" + COL2 };
313    runCreateSubmittableJobWithArgs(args, 10);
314  }
315
316  /**
317   * Test a case when the column specified in command line arguments is exclusive for few rows and
318   * also a row range filter is specified
319   * @throws Exception in case of any unexpected error.
320   */
321  @Test
322  public void testCreateSubmittableJobWithArgsColumnAndRowRange() throws Exception {
323    String[] args = new String[] { TABLE_NAME, "--range=\\x00rov,\\x00rox", COL_FAM + ":" + COL1 };
324    runCreateSubmittableJobWithArgs(args, 8);
325  }
326
327  /**
328   * Test a case when a range is specified with single range of start-end keys
329   * @throws Exception in case of any unexpected error.
330   */
331  @Test
332  public void testCreateSubmittableJobWithArgsRowSingleRange() throws Exception {
333    String[] args = new String[] { TABLE_NAME, "--range=\\x00row1,\\x00row3" };
334    runCreateSubmittableJobWithArgs(args, 2);
335  }
336
337  /**
338   * Test a case when a range is specified with single range with end key only
339   * @throws Exception in case of any unexpected error.
340   */
341  @Test
342  public void testCreateSubmittableJobWithArgsRowSingleRangeUpperBound() throws Exception {
343    String[] args = new String[] { TABLE_NAME, "--range=,\\x00row3" };
344    runCreateSubmittableJobWithArgs(args, 3);
345  }
346
347  /**
348   * Test a case when a range is specified with two ranges where one range is with end key only
349   * @throws Exception in case of any unexpected error.
350   */
351  @Test
352  public void testCreateSubmittableJobWithArgsRowMultiRangeUpperBound() throws Exception {
353    String[] args = new String[] { TABLE_NAME, "--range=,\\x00row3;\\x00row5,\\x00row7" };
354    runCreateSubmittableJobWithArgs(args, 5);
355  }
356
357  /**
358   * Test a case when a range is specified with multiple ranges of start-end keys
359   * @throws Exception in case of any unexpected error.
360   */
361  @Test
362  public void testCreateSubmittableJobWithArgsRowMultiRange() throws Exception {
363    String[] args = new String[] { TABLE_NAME, "--range=\\x00row1,\\x00row3;\\x00row5,\\x00row8" };
364    runCreateSubmittableJobWithArgs(args, 5);
365  }
366
367  /**
368   * Test a case when a range is specified with multiple ranges of start-end keys; one range is
369   * filled, another two are not
370   * @throws Exception in case of any unexpected error.
371   */
372  @Test
373  public void testCreateSubmittableJobWithArgsRowMultiEmptyRange() throws Exception {
374    String[] args = new String[] { TABLE_NAME, "--range=\\x00row1,\\x00row3;;" };
375    runCreateSubmittableJobWithArgs(args, 2);
376  }
377
378  @Test
379  public void testCreateSubmittableJobWithArgs10kRowRange() throws Exception {
380    String tableName = TABLE_NAME + "CreateSubmittableJobWithArgs10kRowRange";
381
382    try (
383      Table table = TEST_UTIL.createTable(TableName.valueOf(tableName), Bytes.toBytes(COL_FAM))) {
384      writeRows(table, 10000, 0);
385    }
386    String[] args = new String[] { tableName, "--range=\\x00row9872,\\x00row9875" };
387    runCreateSubmittableJobWithArgs(args, 3);
388  }
389
390  /**
391   * Test a case when the timerange is specified with --starttime and --endtime options
392   * @throws Exception in case of any unexpected error.
393   */
394  @Test
395  public void testCreateSubmittableJobWithArgsTimeRange() throws Exception {
396    final byte[] family = Bytes.toBytes(COL_FAM);
397    final byte[] col1 = Bytes.toBytes(COL1);
398    Put put1 = new Put(Bytes.toBytes("row_timerange_" + 1));
399    Put put2 = new Put(Bytes.toBytes("row_timerange_" + 2));
400    Put put3 = new Put(Bytes.toBytes("row_timerange_" + 3));
401
402    long ts;
403
404    String tableName = TABLE_NAME_TS_RANGE + "CreateSubmittableJobWithArgs";
405    // clean up content of TABLE_NAME
406    Table table = TEST_UTIL.createTable(TableName.valueOf(tableName), Bytes.toBytes(COL_FAM));
407
408    ts = EnvironmentEdgeManager.currentTime();
409    put1.addColumn(family, col1, ts, Bytes.toBytes("val1"));
410    table.put(put1);
411    Thread.sleep(100);
412
413    ts = EnvironmentEdgeManager.currentTime();
414    put2.addColumn(family, col1, ts, Bytes.toBytes("val2"));
415    put3.addColumn(family, col1, ts, Bytes.toBytes("val3"));
416    table.put(put2);
417    table.put(put3);
418    table.close();
419
420    String[] args =
421      new String[] { tableName, COL_FAM + ":" + COL1, "--starttime=" + 0, "--endtime=" + ts };
422    runCreateSubmittableJobWithArgs(args, 1);
423
424    args = new String[] { tableName, COL_FAM + ":" + COL1, "--starttime=" + 0,
425      "--endtime=" + (ts - 10) };
426    runCreateSubmittableJobWithArgs(args, 1);
427
428    args = new String[] { tableName, COL_FAM + ":" + COL1, "--starttime=" + ts,
429      "--endtime=" + (ts + 1000) };
430    runCreateSubmittableJobWithArgs(args, 2);
431
432    args = new String[] { tableName, COL_FAM + ":" + COL1, "--starttime=" + (ts - 30 * 1000),
433      "--endtime=" + (ts + 30 * 1000), };
434    runCreateSubmittableJobWithArgs(args, 3);
435  }
436
437  /**
438   * Writes TOTAL_ROWS number of distinct rows in to the table. Few rows have two columns, Few have
439   * one.
440   */
441  private static void writeRows(Table table, int totalRows, int rowsWithOneCol) throws IOException {
442    final byte[] family = Bytes.toBytes(COL_FAM);
443    final byte[] value = Bytes.toBytes("abcd");
444    final byte[] col1 = Bytes.toBytes(COL1);
445    final byte[] col2 = Bytes.toBytes(COL2);
446    final byte[] col3 = Bytes.toBytes(COMPOSITE_COLUMN);
447    ArrayList<Put> rowsUpdate = new ArrayList<>();
448    // write few rows with two columns
449    int i = 0;
450    for (; i < totalRows - rowsWithOneCol; i++) {
451      // Use binary rows values to test for HBASE-15287.
452      byte[] row = Bytes.toBytesBinary("\\x00row" + i);
453      Put put = new Put(row);
454      put.addColumn(family, col1, value);
455      put.addColumn(family, col2, value);
456      put.addColumn(family, col3, value);
457      rowsUpdate.add(put);
458    }
459
460    // write few rows with only one column
461    for (; i < totalRows; i++) {
462      byte[] row = Bytes.toBytes("row" + i);
463      Put put = new Put(row);
464      put.addColumn(family, col2, value);
465      rowsUpdate.add(put);
466    }
467    table.put(rowsUpdate);
468  }
469
470  /**
471   * test main method. Import should print help and call System.exit
472   */
473  @Test
474  public void testImportMain() throws Exception {
475    SecurityManager SECURITY_MANAGER = System.getSecurityManager();
476    LauncherSecurityManager newSecurityManager = new LauncherSecurityManager();
477    System.setSecurityManager(newSecurityManager);
478    String[] args = {};
479    try {
480      try {
481        RowCounter.main(args);
482        fail("should be SecurityException");
483      } catch (SecurityException e) {
484        assertEquals(RowCounter.EXIT_FAILURE, newSecurityManager.getExitCode());
485      }
486      try {
487        args = new String[2];
488        args[0] = "table";
489        args[1] = "--range=1";
490        RowCounter.main(args);
491        fail("should be SecurityException");
492      } catch (SecurityException e) {
493        assertEquals(RowCounter.EXIT_FAILURE, newSecurityManager.getExitCode());
494      }
495
496    } finally {
497      System.setSecurityManager(SECURITY_MANAGER);
498    }
499  }
500
501  @Test
502  public void testHelp() throws Exception {
503    PrintStream oldPrintStream = System.out;
504    try {
505      ByteArrayOutputStream data = new ByteArrayOutputStream();
506      PrintStream stream = new PrintStream(data);
507      System.setOut(stream);
508      String[] args = { "-h" };
509      runRowCount(args, 0);
510      assertUsageContent(data.toString());
511      args = new String[] { "--help" };
512      runRowCount(args, 0);
513      assertUsageContent(data.toString());
514    } finally {
515      System.setOut(oldPrintStream);
516    }
517  }
518
519  @Test
520  public void testInvalidTable() throws Exception {
521    try {
522      String[] args = { "invalid" };
523      runRowCount(args, 0);
524      fail("RowCounter should had failed with invalid table.");
525    } catch (Throwable e) {
526      assertTrue(e instanceof AssertionError);
527    }
528  }
529
530  /**
531   * Step 1: Add 10 rows(row1, row2, row3, row4, row5, row6, row7, row8, row9, row10) to a table.
532   * Each row contains 1 column family and 4 columns and values for two different timestamps - 5 &
533   * 10.
534   * <p>
535   * Step 2: Delete the latest version of column A for row1. --> 1 X Delete
536   * <p>
537   * Step 3: Delete the cell for timestamp 5 of column B for row1. --> 1 X Delete
538   * <p>
539   * Step 4: Delete a column family for row2 and row4. --> 2 X DeleteFamily
540   * <p>
541   * Step 5: Delete all versions of a specific column for row3, row5 and row6. --> 3 X DeleteColumn
542   * <p>
543   * Step 6: Delete all columns for timestamp 5 for row 7. --> 1 X DeleteFamilyVersion
544   * <p>
545   * Case 1: Run row counter without countDeleteMarkers and validate counter values.
546   * <p>
547   * Case 2: Run row counter with countDeleteMarkers flag and validate counter values.
548   * <p>
549   * Case 3: Run row counter with countDeleteMarkers flag for a row range and validate counter
550   * values.
551   */
552  @Test
553  public void testRowCounterWithCountDeleteMarkersOption() throws Exception {
554    // Test Setup
555
556    final TableName tableName =
557      TableName.valueOf(TABLE_NAME + "_" + "withCountDeleteMarkersOption");
558    // Row keys are represented in this way because of HBASE-15287
559    final byte[][] rowKeys = { Bytes.toBytesBinary("\\x00row1"), Bytes.toBytesBinary("\\x00row2"),
560      Bytes.toBytesBinary("\\x00row3"), Bytes.toBytesBinary("\\x00row4"),
561      Bytes.toBytesBinary("\\x00row5"), Bytes.toBytesBinary("\\x00row6"),
562      Bytes.toBytesBinary("\\x00row7"), Bytes.toBytesBinary("\\x00row8"),
563      Bytes.toBytesBinary("\\x00row9"), Bytes.toBytesBinary("\\x00row10") };
564    final byte[] columnFamily = Bytes.toBytes("cf");
565    final byte[][] columns =
566      { Bytes.toBytes("A"), Bytes.toBytes("B"), Bytes.toBytes("C"), Bytes.toBytes("D") };
567    final byte[][] values = { Bytes.toBytes("a"), Bytes.toBytes("b") };
568
569    try (Table table = TEST_UTIL.createTable(tableName, columnFamily)) {
570      // Step 1: Insert rows with columns
571      for (byte[] rowKey : rowKeys) {
572        Put put = new Put(rowKey);
573        for (byte[] col : columns) {
574          long timestamp = 5L;
575          for (byte[] value : values) {
576            put.addColumn(columnFamily, col, timestamp, value);
577            timestamp += 5L;
578          }
579        }
580        table.put(put);
581      }
582      TEST_UTIL.getAdmin().flush(tableName);
583
584      // Steps 2-6
585      Delete deleteA = new Delete(rowKeys[0]).addColumn(columnFamily, columns[0]);
586      Delete deleteB = new Delete(rowKeys[0]).addColumn(columnFamily, columns[1], 5L);
587      Delete deleteC = new Delete(rowKeys[1]).addFamily(columnFamily);
588      Delete deleteD = new Delete(rowKeys[2]).addColumns(columnFamily, columns[0]);
589      Delete deleteE = new Delete(rowKeys[3]).addFamily(columnFamily);
590      Delete deleteF = new Delete(rowKeys[4]).addColumns(columnFamily, columns[0]);
591      Delete deleteG = new Delete(rowKeys[5]).addColumns(columnFamily, columns[0]);
592      Delete deleteH = new Delete(rowKeys[6]).addFamilyVersion(columnFamily, 5L);
593
594      table.delete(deleteA);
595      table.delete(deleteB);
596      table.delete(deleteC);
597      table.delete(deleteD);
598      table.delete(deleteE);
599      table.delete(deleteF);
600      table.delete(deleteG);
601      table.delete(deleteH);
602      TEST_UTIL.getAdmin().flush(tableName);
603    }
604
605    RowCounter rowCounterWithoutCountDeleteMarkers = new RowCounter();
606    RowCounter rowCounterWithCountDeleteMarkers = new RowCounter();
607    RowCounter rowCounterForRangeWithCountDeleteMarkers = new RowCounter();
608    rowCounterWithoutCountDeleteMarkers.setConf(new Configuration(TEST_UTIL.getConfiguration()));
609    rowCounterWithCountDeleteMarkers.setConf(new Configuration(TEST_UTIL.getConfiguration()));
610    rowCounterForRangeWithCountDeleteMarkers
611      .setConf(new Configuration(TEST_UTIL.getConfiguration()));
612
613    // Invocation
614
615    rowCounterWithoutCountDeleteMarkers.run(new String[] { tableName.getNameAsString() });
616    rowCounterWithCountDeleteMarkers
617      .run(new String[] { tableName.getNameAsString(), "--countDeleteMarkers" });
618    rowCounterForRangeWithCountDeleteMarkers.run(new String[] { tableName.getNameAsString(),
619      "--countDeleteMarkers", "--range=\\x00row8,\\x00row9" });
620
621    // Validation
622
623    // Case 1:
624    validateCounterCounts(rowCounterWithoutCountDeleteMarkers.getMapReduceJob().getCounters(), 8, 0,
625      0, 0, 0, 0);
626
627    // Case 2:
628    validateCounterCounts(rowCounterWithCountDeleteMarkers.getMapReduceJob().getCounters(), 10, 7,
629      2, 3, 2, 1);
630
631    // Case 3:
632    validateCounterCounts(rowCounterForRangeWithCountDeleteMarkers.getMapReduceJob().getCounters(),
633      1, 0, 0, 0, 0, 0);
634  }
635
636  private void validateCounterCounts(Counters counters, long rowCount,
637    long rowsWithDeleteMarkersCount, long deleteCount, long deleteColumnCount,
638    long deleteFamilyCount, long deleteFamilyVersionCount) {
639
640    long actualRowCount =
641      counters.findCounter(RowCounter.RowCounterMapper.Counters.ROWS).getValue();
642    long actualRowsWithDeleteMarkersCount =
643      counters.findCounter(RowCounter.RowCounterMapper.Counters.ROWS_WITH_DELETE_MARKER).getValue();
644    long actualDeleteCount =
645      counters.findCounter(RowCounter.RowCounterMapper.Counters.DELETE).getValue();
646    long actualDeleteColumnCount =
647      counters.findCounter(RowCounter.RowCounterMapper.Counters.DELETE_COLUMN).getValue();
648    long actualDeleteFamilyCount =
649      counters.findCounter(RowCounter.RowCounterMapper.Counters.DELETE_FAMILY).getValue();
650    long actualDeleteFamilyVersionCount =
651      counters.findCounter(RowCounter.RowCounterMapper.Counters.DELETE_FAMILY_VERSION).getValue();
652
653    assertEquals(rowCount, actualRowCount);
654    assertEquals(rowsWithDeleteMarkersCount, actualRowsWithDeleteMarkersCount);
655    assertEquals(deleteCount, actualDeleteCount);
656    assertEquals(deleteColumnCount, actualDeleteColumnCount);
657    assertEquals(deleteFamilyCount, actualDeleteFamilyCount);
658    assertEquals(deleteFamilyVersionCount, actualDeleteFamilyVersionCount);
659  }
660
661  private void assertUsageContent(String usage) {
662    assertTrue(usage
663      .contains("usage: hbase rowcounter " + "<tablename> [options] [<column1> <column2>...]"));
664    assertTrue(usage.contains("Options:\n"));
665    assertTrue(usage.contains(
666      "--starttime=<arg>       " + "starting time filter to start counting rows from.\n"));
667    assertTrue(usage.contains("--endtime=<arg>         "
668      + "end time filter limit, to only count rows up to this timestamp.\n"));
669    assertTrue(usage
670      .contains("--range=<arg>           " + "[startKey],[endKey][;[startKey],[endKey]...]]\n"));
671    assertTrue(usage.contains("--expectedCount=<arg>   expected number of rows to be count.\n"));
672    assertTrue(
673      usage.contains("For performance, " + "consider the following configuration properties:\n"));
674    assertTrue(usage.contains("-Dhbase.client.scanner.caching=100\n"));
675    assertTrue(usage.contains("-Dmapreduce.map.speculative=false\n"));
676  }
677
678}