blob: 237eada1d38f812c414e5bf1a1409e704e2f32be [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.hadoop.hbase.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellBuilderType;
import org.apache.hadoop.hbase.DoNotRetryIOException;
import org.apache.hadoop.hbase.ExtendedCellBuilderFactory;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
import org.apache.hadoop.hbase.coprocessor.MultiRowMutationEndpoint;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Run Increment tests that use the HBase clients; {@link TableBuilder}.
*
* Test is parameterized to run the slow and fast increment code paths. If fast, in the @before, we
* do a rolling restart of the single regionserver so that it can pick up the go fast configuration.
* Doing it this way should be faster than starting/stopping a cluster per test.
*
* Test takes a long time because spin up a cluster between each run -- ugh.
*/
@Category(LargeTests.class)
public class TestIncrementsFromClientSide {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestIncrementsFromClientSide.class);
final Logger LOG = LoggerFactory.getLogger(getClass());
protected final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private static byte [] ROW = Bytes.toBytes("testRow");
private static byte [] FAMILY = Bytes.toBytes("testFamily");
private static byte [] QUALIFIER = Bytes.toBytes("testQualifier");
// This test depends on there being only one slave running at at a time. See the @Before
// method where we do rolling restart.
protected static int SLAVES = 1;
@Rule public TestName name = new TestName();
@BeforeClass
public static void beforeClass() throws Exception {
Configuration conf = TEST_UTIL.getConfiguration();
conf.setStrings(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY,
MultiRowMutationEndpoint.class.getName());
// We need more than one region server in this test
TEST_UTIL.startMiniCluster(SLAVES);
}
/**
* @throws java.lang.Exception
*/
@AfterClass
public static void afterClass() throws Exception {
TEST_UTIL.shutdownMiniCluster();
}
/**
* Test increment result when there are duplicate rpc request.
*/
@Test
public void testDuplicateIncrement() throws Exception {
TableDescriptorBuilder builder =
TEST_UTIL.createModifyableTableDescriptor(name.getMethodName());
Map<String, String> kvs = new HashMap<>();
kvs.put(SleepAtFirstRpcCall.SLEEP_TIME_CONF_KEY, "2000");
builder.setCoprocessor(CoprocessorDescriptorBuilder
.newBuilder(SleepAtFirstRpcCall.class.getName())
.setPriority(1)
.setProperties(kvs)
.build());
TEST_UTIL.createTable(builder.build(), new byte[][] { ROW }).close();
Configuration c = new Configuration(TEST_UTIL.getConfiguration());
c.setInt(HConstants.HBASE_CLIENT_PAUSE, 50);
// Client will retry beacuse rpc timeout is small than the sleep time of first rpc call
c.setInt(HConstants.HBASE_RPC_TIMEOUT_KEY, 1500);
try (Connection connection = ConnectionFactory.createConnection(c);
Table table = connection.getTableBuilder(TableName.valueOf(name.getMethodName()), null)
.setOperationTimeout(3 * 1000).build()) {
Increment inc = new Increment(ROW);
inc.addColumn(HBaseTestingUtility.fam1, QUALIFIER, 1);
Result result = table.increment(inc);
Cell[] cells = result.rawCells();
assertEquals(1, cells.length);
assertIncrementKey(cells[0], ROW, HBaseTestingUtility.fam1, QUALIFIER, 1);
// Verify expected result
Result readResult = table.get(new Get(ROW));
cells = readResult.rawCells();
assertEquals(1, cells.length);
assertIncrementKey(cells[0], ROW, HBaseTestingUtility.fam1, QUALIFIER, 1);
}
}
/**
* Test batch increment result when there are duplicate rpc request.
*/
@Test
public void testDuplicateBatchIncrement() throws Exception {
TableDescriptorBuilder builder =
TEST_UTIL.createModifyableTableDescriptor(name.getMethodName());
Map<String, String> kvs = new HashMap<>();
kvs.put(SleepAtFirstRpcCall.SLEEP_TIME_CONF_KEY, "2000");
builder.setCoprocessor(CoprocessorDescriptorBuilder
.newBuilder(SleepAtFirstRpcCall.class.getName())
.setPriority(1)
.setProperties(kvs)
.build());
TEST_UTIL.createTable(builder.build(), new byte[][] { ROW }).close();
Configuration c = new Configuration(TEST_UTIL.getConfiguration());
c.setInt(HConstants.HBASE_CLIENT_PAUSE, 50);
// Client will retry beacuse rpc timeout is small than the sleep time of first rpc call
c.setInt(HConstants.HBASE_RPC_TIMEOUT_KEY, 1500);
try (Connection connection = ConnectionFactory.createConnection(c);
Table table = connection.getTableBuilder(TableName.valueOf(name.getMethodName()), null)
.setOperationTimeout(3 * 1000).build()) {
Increment inc = new Increment(ROW);
inc.addColumn(HBaseTestingUtility.fam1, QUALIFIER, 1);
// Batch increment
Object[] results = new Object[1];
table.batch(Collections.singletonList(inc), results);
Cell[] cells = ((Result) results[0]).rawCells();
assertEquals(1, cells.length);
assertIncrementKey(cells[0], ROW, HBaseTestingUtility.fam1, QUALIFIER, 1);
// Verify expected result
Result readResult = table.get(new Get(ROW));
cells = readResult.rawCells();
assertEquals(1, cells.length);
assertIncrementKey(cells[0], ROW, HBaseTestingUtility.fam1, QUALIFIER, 1);
}
}
@Test
public void testIncrementWithDeletes() throws Exception {
LOG.info("Starting " + this.name.getMethodName());
final TableName TABLENAME =
TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
final byte[] COLUMN = Bytes.toBytes("column");
ht.incrementColumnValue(ROW, FAMILY, COLUMN, 5);
TEST_UTIL.flush(TABLENAME);
Delete del = new Delete(ROW);
ht.delete(del);
ht.incrementColumnValue(ROW, FAMILY, COLUMN, 5);
Get get = new Get(ROW);
Result r = ht.get(get);
assertEquals(1, r.size());
assertEquals(5, Bytes.toLong(r.getValue(FAMILY, COLUMN)));
}
@Test
public void testIncrementingInvalidValue() throws Exception {
LOG.info("Starting " + this.name.getMethodName());
final TableName TABLENAME =
TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
final byte[] COLUMN = Bytes.toBytes("column");
Put p = new Put(ROW);
// write an integer here (not a Long)
p.addColumn(FAMILY, COLUMN, Bytes.toBytes(5));
ht.put(p);
try {
ht.incrementColumnValue(ROW, FAMILY, COLUMN, 5);
fail("Should have thrown DoNotRetryIOException");
} catch (DoNotRetryIOException iox) {
// success
}
Increment inc = new Increment(ROW);
inc.addColumn(FAMILY, COLUMN, 5);
try {
ht.increment(inc);
fail("Should have thrown DoNotRetryIOException");
} catch (DoNotRetryIOException iox) {
// success
}
}
@Test
public void testBatchIncrementsWithReturnResultFalse() throws Exception {
LOG.info("Starting testBatchIncrementsWithReturnResultFalse");
final TableName tableName = TableName.valueOf(name.getMethodName());
Table table = TEST_UTIL.createTable(tableName, FAMILY);
Increment inc1 = new Increment(Bytes.toBytes("row2"));
inc1.setReturnResults(false);
inc1.addColumn(FAMILY, Bytes.toBytes("f1"), 1);
Increment inc2 = new Increment(Bytes.toBytes("row2"));
inc2.setReturnResults(false);
inc2.addColumn(FAMILY, Bytes.toBytes("f1"), 1);
List<Increment> incs = new ArrayList<>();
incs.add(inc1);
incs.add(inc2);
Object[] results = new Object[2];
table.batch(incs, results);
assertTrue(results.length == 2);
for(Object r : results) {
Result result = (Result)r;
assertTrue(result.isEmpty());
}
table.close();
}
@Test
public void testIncrementInvalidArguments() throws Exception {
LOG.info("Starting " + this.name.getMethodName());
final TableName TABLENAME =
TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
final byte[] COLUMN = Bytes.toBytes("column");
try {
// try null row
ht.incrementColumnValue(null, FAMILY, COLUMN, 5);
fail("Should have thrown NPE/IOE");
} catch (NullPointerException | IOException error) {
// success
}
try {
// try null family
ht.incrementColumnValue(ROW, null, COLUMN, 5);
fail("Should have thrown NPE/IOE");
} catch (NullPointerException | IOException error) {
// success
}
// try null row
try {
Increment incNoRow = new Increment((byte[]) null);
incNoRow.addColumn(FAMILY, COLUMN, 5);
fail("Should have thrown IAE/NPE");
} catch (IllegalArgumentException | NullPointerException error) {
// success
}
// try null family
try {
Increment incNoFamily = new Increment(ROW);
incNoFamily.addColumn(null, COLUMN, 5);
fail("Should have thrown IAE");
} catch (IllegalArgumentException iax) {
// success
}
}
@Test
public void testIncrementOutOfOrder() throws Exception {
LOG.info("Starting " + this.name.getMethodName());
final TableName TABLENAME =
TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
byte [][] QUALIFIERS = new byte [][] {
Bytes.toBytes("B"), Bytes.toBytes("A"), Bytes.toBytes("C")
};
Increment inc = new Increment(ROW);
for (int i=0; i<QUALIFIERS.length; i++) {
inc.addColumn(FAMILY, QUALIFIERS[i], 1);
}
ht.increment(inc);
// Verify expected results
Get get = new Get(ROW);
Result r = ht.get(get);
Cell [] kvs = r.rawCells();
assertEquals(3, kvs.length);
assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[1], 1);
assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[0], 1);
assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 1);
// Now try multiple columns again
inc = new Increment(ROW);
for (int i=0; i<QUALIFIERS.length; i++) {
inc.addColumn(FAMILY, QUALIFIERS[i], 1);
}
ht.increment(inc);
// Verify
r = ht.get(get);
kvs = r.rawCells();
assertEquals(3, kvs.length);
assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[1], 2);
assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[0], 2);
assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 2);
}
@Test
public void testIncrementOnSameColumn() throws Exception {
LOG.info("Starting " + this.name.getMethodName());
final byte[] TABLENAME = Bytes.toBytes(filterStringSoTableNameSafe(this.name.getMethodName()));
Table ht = TEST_UTIL.createTable(TableName.valueOf(TABLENAME), FAMILY);
byte[][] QUALIFIERS =
new byte[][] { Bytes.toBytes("A"), Bytes.toBytes("B"), Bytes.toBytes("C") };
Increment inc = new Increment(ROW);
for (int i = 0; i < QUALIFIERS.length; i++) {
inc.addColumn(FAMILY, QUALIFIERS[i], 1);
inc.addColumn(FAMILY, QUALIFIERS[i], 1);
}
ht.increment(inc);
// Verify expected results
Get get = new Get(ROW);
Result r = ht.get(get);
Cell[] kvs = r.rawCells();
assertEquals(3, kvs.length);
assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[0], 1);
assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[1], 1);
assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 1);
// Now try multiple columns again
inc = new Increment(ROW);
for (int i = 0; i < QUALIFIERS.length; i++) {
inc.addColumn(FAMILY, QUALIFIERS[i], 1);
inc.addColumn(FAMILY, QUALIFIERS[i], 1);
}
ht.increment(inc);
// Verify
r = ht.get(get);
kvs = r.rawCells();
assertEquals(3, kvs.length);
assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[0], 2);
assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[1], 2);
assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 2);
ht.close();
}
@Test
public void testIncrementIncrZeroAtFirst() throws Exception {
LOG.info("Starting " + this.name.getMethodName());
final TableName TABLENAME =
TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
byte[] col1 = Bytes.toBytes("col1");
byte[] col2 = Bytes.toBytes("col2");
byte[] col3 = Bytes.toBytes("col3");
// Now increment zero at first time incr
Increment inc = new Increment(ROW);
inc.addColumn(FAMILY, col1, 0);
ht.increment(inc);
// Verify expected results
Get get = new Get(ROW);
Result r = ht.get(get);
Cell [] kvs = r.rawCells();
assertEquals(1, kvs.length);
assertNotNull(kvs[0]);
assertIncrementKey(kvs[0], ROW, FAMILY, col1, 0);
// Now try multiple columns by different amounts
inc = new Increment(ROW);
inc.addColumn(FAMILY, col1, 1);
inc.addColumn(FAMILY, col2, 0);
inc.addColumn(FAMILY, col3, 2);
ht.increment(inc);
// Verify
get = new Get(ROW);
r = ht.get(get);
kvs = r.rawCells();
assertEquals(3, kvs.length);
assertNotNull(kvs[0]);
assertNotNull(kvs[1]);
assertNotNull(kvs[2]);
assertIncrementKey(kvs[0], ROW, FAMILY, col1, 1);
assertIncrementKey(kvs[1], ROW, FAMILY, col2, 0);
assertIncrementKey(kvs[2], ROW, FAMILY, col3, 2);
}
@Test
public void testIncrement() throws Exception {
LOG.info("Starting " + this.name.getMethodName());
final TableName TABLENAME =
TableName.valueOf(filterStringSoTableNameSafe(this.name.getMethodName()));
Table ht = TEST_UTIL.createTable(TABLENAME, FAMILY);
byte [][] ROWS = new byte [][] {
Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c"),
Bytes.toBytes("d"), Bytes.toBytes("e"), Bytes.toBytes("f"),
Bytes.toBytes("g"), Bytes.toBytes("h"), Bytes.toBytes("i")
};
byte [][] QUALIFIERS = new byte [][] {
Bytes.toBytes("a"), Bytes.toBytes("b"), Bytes.toBytes("c"),
Bytes.toBytes("d"), Bytes.toBytes("e"), Bytes.toBytes("f"),
Bytes.toBytes("g"), Bytes.toBytes("h"), Bytes.toBytes("i")
};
// Do some simple single-column increments
// First with old API
ht.incrementColumnValue(ROW, FAMILY, QUALIFIERS[0], 1);
ht.incrementColumnValue(ROW, FAMILY, QUALIFIERS[1], 2);
ht.incrementColumnValue(ROW, FAMILY, QUALIFIERS[2], 3);
ht.incrementColumnValue(ROW, FAMILY, QUALIFIERS[3], 4);
// Now increment things incremented with old and do some new
Increment inc = new Increment(ROW);
inc.addColumn(FAMILY, QUALIFIERS[1], 1);
inc.addColumn(FAMILY, QUALIFIERS[3], 1);
inc.addColumn(FAMILY, QUALIFIERS[4], 1);
ht.increment(inc);
// Verify expected results
Get get = new Get(ROW);
Result r = ht.get(get);
Cell [] kvs = r.rawCells();
assertEquals(5, kvs.length);
assertIncrementKey(kvs[0], ROW, FAMILY, QUALIFIERS[0], 1);
assertIncrementKey(kvs[1], ROW, FAMILY, QUALIFIERS[1], 3);
assertIncrementKey(kvs[2], ROW, FAMILY, QUALIFIERS[2], 3);
assertIncrementKey(kvs[3], ROW, FAMILY, QUALIFIERS[3], 5);
assertIncrementKey(kvs[4], ROW, FAMILY, QUALIFIERS[4], 1);
// Now try multiple columns by different amounts
inc = new Increment(ROWS[0]);
for (int i=0;i<QUALIFIERS.length;i++) {
inc.addColumn(FAMILY, QUALIFIERS[i], i+1);
}
ht.increment(inc);
// Verify
get = new Get(ROWS[0]);
r = ht.get(get);
kvs = r.rawCells();
assertEquals(QUALIFIERS.length, kvs.length);
for (int i=0;i<QUALIFIERS.length;i++) {
assertIncrementKey(kvs[i], ROWS[0], FAMILY, QUALIFIERS[i], i+1);
}
// Re-increment them
inc = new Increment(ROWS[0]);
for (int i=0;i<QUALIFIERS.length;i++) {
inc.addColumn(FAMILY, QUALIFIERS[i], i+1);
}
ht.increment(inc);
// Verify
r = ht.get(get);
kvs = r.rawCells();
assertEquals(QUALIFIERS.length, kvs.length);
for (int i=0;i<QUALIFIERS.length;i++) {
assertIncrementKey(kvs[i], ROWS[0], FAMILY, QUALIFIERS[i], 2*(i+1));
}
// Verify that an Increment of an amount of zero, returns current count; i.e. same as for above
// test, that is: 2 * (i + 1).
inc = new Increment(ROWS[0]);
for (int i = 0; i < QUALIFIERS.length; i++) {
inc.addColumn(FAMILY, QUALIFIERS[i], 0);
}
ht.increment(inc);
r = ht.get(get);
kvs = r.rawCells();
assertEquals(QUALIFIERS.length, kvs.length);
for (int i = 0; i < QUALIFIERS.length; i++) {
assertIncrementKey(kvs[i], ROWS[0], FAMILY, QUALIFIERS[i], 2*(i+1));
}
}
@Test
public void testIncrementWithCustomTimestamp() throws IOException {
TableName TABLENAME = TableName.valueOf(name.getMethodName());
Table table = TEST_UTIL.createTable(TABLENAME, FAMILY);
long timestamp = 999;
Increment increment = new Increment(ROW);
increment.add(ExtendedCellBuilderFactory.create(CellBuilderType.DEEP_COPY)
.setRow(ROW)
.setFamily(FAMILY)
.setQualifier(QUALIFIER)
.setTimestamp(timestamp)
.setType(KeyValue.Type.Put.getCode())
.setValue(Bytes.toBytes(100L))
.build());
Result r = table.increment(increment);
assertEquals(1, r.size());
assertEquals(timestamp, r.rawCells()[0].getTimestamp());
r = table.get(new Get(ROW));
assertEquals(1, r.size());
assertEquals(timestamp, r.rawCells()[0].getTimestamp());
r = table.increment(increment);
assertEquals(1, r.size());
assertNotEquals(timestamp, r.rawCells()[0].getTimestamp());
r = table.get(new Get(ROW));
assertEquals(1, r.size());
assertNotEquals(timestamp, r.rawCells()[0].getTimestamp());
}
/**
* Call over to the adjacent class's method of same name.
*/
static void assertIncrementKey(Cell key, byte [] row, byte [] family,
byte [] qualifier, long value) throws Exception {
TestFromClientSide.assertIncrementKey(key, row, family, qualifier, value);
}
public static String filterStringSoTableNameSafe(final String str) {
return str.replaceAll("\\[fast\\=(.*)\\]", ".FAST.is.$1");
}
}