/**
* 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.regionserver;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.LargeTests;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetRegionInfoResponse.CompactionState;
import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequest;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/** Unit tests to test retrieving table/region compaction state*/
@Category(LargeTests.class)
public class TestCompactionState {
final static Log LOG = LogFactory.getLog(TestCompactionState.class);
private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private final static Random random = new Random();
@BeforeClass
public static void setUpBeforeClass() throws Exception {
TEST_UTIL.startMiniCluster();
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
TEST_UTIL.shutdownMiniCluster();
}
@Test(timeout=60000)
public void testMajorCompaction() throws IOException, InterruptedException {
compaction("testMajorCompaction", 8, CompactionState.MAJOR, false);
}
@Test(timeout=60000)
public void testMinorCompaction() throws IOException, InterruptedException {
compaction("testMinorCompaction", 15, CompactionState.MINOR, false);
}
@Test(timeout=60000)
public void testMajorCompactionOnFamily() throws IOException, InterruptedException {
compaction("testMajorCompactionOnFamily", 8, CompactionState.MAJOR, true);
}
@Test(timeout=60000)
public void testMinorCompactionOnFamily() throws IOException, InterruptedException {
compaction("testMinorCompactionOnFamily", 15, CompactionState.MINOR, true);
}
@Test
public void testInvalidColumnFamily() throws IOException, InterruptedException {
byte [] table = Bytes.toBytes("testInvalidColumnFamily");
byte [] family = Bytes.toBytes("family");
byte [] fakecf = Bytes.toBytes("fakecf");
boolean caughtMinorCompact = false;
boolean caughtMajorCompact = false;
HTable ht = null;
try {
ht = TEST_UTIL.createTable(table, family);
HBaseAdmin admin = new HBaseAdmin(TEST_UTIL.getConfiguration());
try {
admin.compact(table, fakecf);
} catch (IOException ioe) {
caughtMinorCompact = true;
}
try {
admin.majorCompact(table, fakecf);
} catch (IOException ioe) {
caughtMajorCompact = true;
}
} finally {
if (ht != null) {
TEST_UTIL.deleteTable(table);
}
assertTrue(caughtMinorCompact);
assertTrue(caughtMajorCompact);
}
}
/**
* Load data to a table, flush it to disk, trigger compaction,
* confirm the compaction state is right and wait till it is done.
*
* @param tableName
* @param flushes
* @param expectedState
* @param singleFamily otherwise, run compaction on all cfs
* @throws IOException
* @throws InterruptedException
*/
private void compaction(final String tableName, final int flushes,
final CompactionState expectedState, boolean singleFamily)
throws IOException, InterruptedException {
// Create a table with regions
byte [] table = Bytes.toBytes(tableName);
byte [] family = Bytes.toBytes("family");
byte [][] families =
{family, Bytes.add(family, Bytes.toBytes("2")), Bytes.add(family, Bytes.toBytes("3"))};
HTable ht = null;
try {
ht = TEST_UTIL.createTable(table, families);
loadData(ht, families, 3000, flushes);
HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0);
List<HRegion> regions = rs.getOnlineRegions(table);
int countBefore = countStoreFilesInFamilies(regions, families);
int countBeforeSingleFamily = countStoreFilesInFamily(regions, family);
assertTrue(countBefore > 0); // there should be some data files
HBaseAdmin admin = new HBaseAdmin(TEST_UTIL.getConfiguration());
if (expectedState == CompactionState.MINOR) {
if (singleFamily) {
admin.compact(table, family);
} else {
admin.compact(table);
}
} else {
if (singleFamily) {
admin.majorCompact(table, family);
} else {
admin.majorCompact(table);
}
}
long curt = System.currentTimeMillis();
long waitTime = 5000;
long endt = curt + waitTime;
CompactionState state = admin.getCompactionState(table);
while (state == CompactionState.NONE && curt < endt) {
Thread.sleep(10);
state = admin.getCompactionState(table);
curt = System.currentTimeMillis();
}
// Now, should have the right compaction state,
// otherwise, the compaction should have already been done
if (expectedState != state) {
for (HRegion region: regions) {
state = CompactionRequest.getCompactionState(region.getRegionId());
assertEquals(CompactionState.NONE, state);
}
} else {
// Wait until the compaction is done
state = admin.getCompactionState(table);
while (state != CompactionState.NONE && curt < endt) {
Thread.sleep(10);
state = admin.getCompactionState(table);
}
// Now, compaction should be done.
assertEquals(CompactionState.NONE, state);
}
int countAfter = countStoreFilesInFamilies(regions, families);
int countAfterSingleFamily = countStoreFilesInFamily(regions, family);
assertTrue(countAfter < countBefore);
if (!singleFamily) {
if (expectedState == CompactionState.MAJOR) assertTrue(families.length == countAfter);
else assertTrue(families.length < countAfter);
} else {
int singleFamDiff = countBeforeSingleFamily - countAfterSingleFamily;
// assert only change was to single column family
assertTrue(singleFamDiff == (countBefore - countAfter));
if (expectedState == CompactionState.MAJOR) {
assertTrue(1 == countAfterSingleFamily);
} else {
assertTrue(1 < countAfterSingleFamily);
}
}
} finally {
if (ht != null) {
TEST_UTIL.deleteTable(table);
}
}
}
private static int countStoreFilesInFamily(
List<HRegion> regions, final byte[] family) {
return countStoreFilesInFamilies(regions, new byte[][]{family});
}
private static int countStoreFilesInFamilies(List<HRegion> regions, final byte[][] families) {
int count = 0;
for (HRegion region: regions) {
count += region.getStoreFileList(families).size();
}
return count;
}
private static void loadData(final HTable ht, final byte[][] families,
final int rows, final int flushes) throws IOException {
List<Put> puts = new ArrayList<Put>(rows);
byte[] qualifier = Bytes.toBytes("val");
for (int i = 0; i < flushes; i++) {
for (int k = 0; k < rows; k++) {
byte[] row = Bytes.toBytes(random.nextLong());
Put p = new Put(row);
for (int j = 0; j < families.length; ++j) {
p.add(families[ j ], qualifier, row);
}
puts.add(p);
}
ht.put(puts);
ht.flushCommits();
TEST_UTIL.flush();
puts.clear();
}
}
}