/* * Copyright © 2014 Cask Data, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package co.cask.cdap.hbase.wd; import co.cask.cdap.common.utils.Networks; import co.cask.cdap.test.XSlowTests; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.Cell; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.client.Get; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.Result; import org.apache.hadoop.hbase.client.ResultScanner; import org.apache.hadoop.hbase.client.Scan; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil; import org.apache.hadoop.hbase.mapreduce.TableMapper; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.mapreduce.Job; import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; import java.io.IOException; import java.util.concurrent.Executors; /** * Provides basic tests for row key distributor */ @Category(XSlowTests.class) public abstract class RowKeyDistributorTestBase { // Controls for test suite for whether to run BeforeClass/AfterClass public static boolean runBefore = true; public static boolean runAfter = true; protected static final String TABLE_NAME = "table"; protected static final byte[] TABLE = Bytes.toBytes(TABLE_NAME); protected static final byte[] CF = Bytes.toBytes("colfam"); protected static final byte[] QUAL = Bytes.toBytes("qual"); private final AbstractRowKeyDistributor keyDistributor; private static HBaseTestingUtility testingUtility; private static HTable hTable; public RowKeyDistributorTestBase(AbstractRowKeyDistributor keyDistributor) { this.keyDistributor = keyDistributor; } @BeforeClass public static void beforeClass() throws Exception { if (!runBefore) { return; } testingUtility = new HBaseTestingUtility(); Configuration hConf = testingUtility.getConfiguration(); hConf.set("yarn.is.minicluster", "true"); // Tune down the connection thread pool size hConf.setInt("hbase.hconnection.threads.core", 5); hConf.setInt("hbase.hconnection.threads.max", 10); // Tunn down handler threads in regionserver hConf.setInt("hbase.regionserver.handler.count", 10); // Set to random port hConf.setInt("hbase.master.port", Networks.getRandomPort()); hConf.setInt("hbase.master.info.port", Networks.getRandomPort()); hConf.setInt("hbase.regionserver.port", Networks.getRandomPort()); hConf.setInt("hbase.regionserver.info.port", Networks.getRandomPort()); testingUtility.startMiniCluster(); hTable = testingUtility.createTable(TABLE, CF); } @AfterClass public static void afterClass() throws Exception { if (!runAfter) { return; } testingUtility.shutdownMiniCluster(); } @After public void after() throws Exception { testingUtility.truncateTable(hTable.getTableName()); } /** Testing simple get. */ @Test public void testGet() throws IOException, InterruptedException { // Testing simple get byte[] key = new byte[] {123, 124, 122}; byte[] distributedKey = keyDistributor.getDistributedKey(key); byte[] value = Bytes.toBytes("some"); hTable.put(new Put(distributedKey).add(CF, QUAL, value)); Result result = hTable.get(new Get(distributedKey)); Assert.assertArrayEquals(key, keyDistributor.getOriginalKey(result.getRow())); Assert.assertArrayEquals(value, result.getValue(CF, QUAL)); } /** Test scan with start and stop key. */ @Test public void testSimpleScanBounded() throws IOException { long origKeyPrefix = System.currentTimeMillis(); int seekIntervalMinValue = 100; int seekIntervalMaxValue = 899; byte[] startKey = Bytes.toBytes(origKeyPrefix + seekIntervalMinValue); byte[] stopKey = Bytes.toBytes(origKeyPrefix + seekIntervalMaxValue + 1); Scan scan = new Scan(startKey, stopKey); testSimpleScanInternal(origKeyPrefix, scan, 500, 500, seekIntervalMinValue, seekIntervalMaxValue); } /** Test scan over the whole table. */ @Test public void testSimpleScanUnbounded() throws IOException { long origKeyPrefix = System.currentTimeMillis(); testSimpleScanInternal(origKeyPrefix, new Scan(), 500, 500, 0, 999); } /** Test scan without stop key. */ @Test public void testSimpleScanWithoutStopKey() throws IOException { long origKeyPrefix = System.currentTimeMillis(); int seekIntervalMinValue = 100; byte[] startKey = Bytes.toBytes(origKeyPrefix + seekIntervalMinValue); testSimpleScanInternal(origKeyPrefix, new Scan(startKey), 500, 500, 100, 999); } /** Test scan with start and stop key. */ @Test public void testMapReduceBounded() throws IOException, InterruptedException, ClassNotFoundException { long origKeyPrefix = System.currentTimeMillis(); int seekIntervalMinValue = 100; int seekIntervalMaxValue = 899; byte[] startKey = Bytes.toBytes(origKeyPrefix + seekIntervalMinValue); byte[] stopKey = Bytes.toBytes(origKeyPrefix + seekIntervalMaxValue + 1); Scan scan = new Scan(startKey, stopKey); testMapReduceInternal(origKeyPrefix, scan, 500, 500, seekIntervalMinValue, seekIntervalMaxValue); } /** Test scan over the whole table. */ @Test public void testMapReduceUnbounded() throws IOException, InterruptedException, ClassNotFoundException { long origKeyPrefix = System.currentTimeMillis(); testMapReduceInternal(origKeyPrefix, new Scan(), 500, 500, 0, 999); } private int writeTestData(long origKeyPrefix, int numRows, int rowKeySeed, int seekIntervalMinValue, int seekIntervalMaxValue) throws IOException { int valuesCountInSeekInterval = 0; for (int i = 0; i < numRows; i++) { int val = rowKeySeed + i - i * (i % 2) * 2; // i.e. 500, 499, 502, 497, 504, ... valuesCountInSeekInterval += (val >= seekIntervalMinValue && val <= seekIntervalMaxValue) ? 1 : 0; byte[] key = Bytes.toBytes(origKeyPrefix + val); byte[] distributedKey = keyDistributor.getDistributedKey(key); byte[] value = Bytes.toBytes(val); hTable.put(new Put(distributedKey).add(CF, QUAL, value)); } return valuesCountInSeekInterval; } private void testSimpleScanInternal(long origKeyPrefix, Scan scan, int numValues, int startWithValue, int seekIntervalMinValue, int seekIntervalMaxValue) throws IOException { int valuesCountInSeekInterval = writeTestData(origKeyPrefix, numValues, startWithValue, seekIntervalMinValue, seekIntervalMaxValue); // TODO: add some filters to the scan for better testing ResultScanner distributedScanner = DistributedScanner.create(hTable, scan, keyDistributor, Executors.newFixedThreadPool(2)); Result previous = null; int countMatched = 0; for (Result current : distributedScanner) { countMatched++; if (previous != null) { byte[] currentRowOrigKey = keyDistributor.getOriginalKey(current.getRow()); byte[] previousRowOrigKey = keyDistributor.getOriginalKey(previous.getRow()); Assert.assertTrue(Bytes.compareTo(currentRowOrigKey, previousRowOrigKey) >= 0); int currentValue = Bytes.toInt(current.getValue(CF, QUAL)); Assert.assertTrue(currentValue >= seekIntervalMinValue); Assert.assertTrue(currentValue <= seekIntervalMaxValue); } previous = current; } Assert.assertEquals(valuesCountInSeekInterval, countMatched); } private void testMapReduceInternal(long origKeyPrefix, Scan scan, int numValues, int startWithValue, int seekIntervalMinValue, int seekIntervalMaxValue) throws IOException, InterruptedException, ClassNotFoundException { int valuesCountInSeekInterval = writeTestData(origKeyPrefix, numValues, startWithValue, seekIntervalMinValue, seekIntervalMaxValue); // Reading data Configuration conf = new Configuration(testingUtility.getConfiguration()); conf.set("fs.defaultFS", "file:///"); conf.set("fs.default.name", "file:///"); conf.setInt("mapreduce.local.map.tasks.maximum", 16); conf.setInt("mapreduce.local.reduce.tasks.maximum", 16); Job job = Job.getInstance(conf, "testMapReduceInternal()-Job"); TableMapReduceUtil.initTableMapperJob(TABLE_NAME, scan, RowCounterMapper.class, ImmutableBytesWritable.class, Result.class, job); // Substituting standard TableInputFormat which was set in TableMapReduceUtil.initTableMapperJob(...) job.setInputFormatClass(WdTableInputFormat.class); keyDistributor.addInfo(job.getConfiguration()); job.setOutputFormatClass(NullOutputFormat.class); job.setNumReduceTasks(0); boolean succeeded = job.waitForCompletion(true); Assert.assertTrue(succeeded); long mapInputRecords = job.getCounters().findCounter(RowCounterMapper.Counters.ROWS).getValue(); Assert.assertEquals(valuesCountInSeekInterval, mapInputRecords); // Need to kill the job after completion, after it could leave MRAppMaster running not terminated. // Not sure what causing this, but maybe problem in MiniYarnCluster job.killJob(); } /** * Mapper that runs the count. * NOTE: it was copied from RowCounter class */ static class RowCounterMapper extends TableMapper<ImmutableBytesWritable, Result> { /** Counter enumeration to count the actual rows. */ public enum Counters { ROWS } @Override public void map(ImmutableBytesWritable row, Result values, Context context) throws IOException { for (Cell cell : values.rawCells()) { if (cell.getValueLength() > 0) { context.getCounter(Counters.ROWS).increment(1); break; } } } } }