blob: cfa0b6d62f0b99235338acf5ba07a7f851ce12a8 [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 java.util.List;
import java.util.regex.Pattern;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseClassTestRule;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HConstants;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy;
import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils;
import org.apache.hadoop.hbase.testclassification.ClientTests;
import org.apache.hadoop.hbase.testclassification.LargeTests;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.Threads;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
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;
/**
* Test to verify that the cloned table is independent of the table from which it was cloned
*/
@Category({LargeTests.class, ClientTests.class})
public class TestSnapshotCloneIndependence {
@ClassRule
public static final HBaseClassTestRule CLASS_RULE =
HBaseClassTestRule.forClass(TestSnapshotCloneIndependence.class);
private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotCloneIndependence.class);
@Rule
public TestName testName = new TestName();
protected static final HBaseTestingUtility UTIL = new HBaseTestingUtility();
protected static final int NUM_RS = 2;
private static final String TEST_FAM_STR = "fam";
protected static final byte[] TEST_FAM = Bytes.toBytes(TEST_FAM_STR);
private static final int CLEANER_INTERVAL = 100;
private FileSystem fs;
private Path rootDir;
private Admin admin;
private TableName originalTableName;
private Table originalTable;
private TableName cloneTableName;
private int countOriginalTable;
String snapshotNameAsString;
String snapshotName;
/**
* Setup the config for the cluster and start it
*/
@BeforeClass
public static void setupCluster() throws Exception {
setupConf(UTIL.getConfiguration());
UTIL.startMiniCluster(NUM_RS);
}
static void setupConf(Configuration conf) {
// Up the handlers; this test needs more than usual.
conf.setInt(HConstants.REGION_SERVER_HIGH_PRIORITY_HANDLER_COUNT, 15);
// enable snapshot support
conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
// change the flush size to a small amount, regulating number of store files
conf.setInt("hbase.hregion.memstore.flush.size", 25000);
// so make sure we get a compaction when doing a load, but keep around
// some files in the store
conf.setInt("hbase.hstore.compaction.min", 10);
conf.setInt("hbase.hstore.compactionThreshold", 10);
// block writes if we get to 12 store files
conf.setInt("hbase.hstore.blockingStoreFiles", 12);
conf.setInt("hbase.regionserver.msginterval", 100);
conf.setBoolean("hbase.master.enabletable.roundrobin", true);
// Avoid potentially aggressive splitting which would cause snapshot to fail
conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY,
ConstantSizeRegionSplitPolicy.class.getName());
// Execute cleaner frequently to induce failures
conf.setInt("hbase.master.cleaner.interval", CLEANER_INTERVAL);
conf.setInt("hbase.master.hfilecleaner.plugins.snapshot.period", CLEANER_INTERVAL);
// Effectively disable TimeToLiveHFileCleaner. Don't want to fully disable it because that
// will even trigger races between creating the directory containing back references and
// the back reference itself.
conf.setInt("hbase.master.hfilecleaner.ttl", CLEANER_INTERVAL);
}
@Before
public void setup() throws Exception {
fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem();
rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir();
admin = UTIL.getAdmin();
originalTableName = TableName.valueOf("test" + testName.getMethodName());
cloneTableName = TableName.valueOf("test-clone-" + originalTableName);
snapshotNameAsString = "snapshot_" + originalTableName;
snapshotName = snapshotNameAsString;
originalTable = createTable(originalTableName, TEST_FAM);
loadData(originalTable, TEST_FAM);
countOriginalTable = countRows(originalTable);
System.out.println("Original table has: " + countOriginalTable + " rows");
}
@After
public void tearDown() throws Exception {
UTIL.deleteTable(originalTableName);
UTIL.deleteTable(cloneTableName);
SnapshotTestingUtils.deleteAllSnapshots(UTIL.getAdmin());
SnapshotTestingUtils.deleteArchiveDirectory(UTIL);
}
@AfterClass
public static void cleanupTest() throws Exception {
try {
UTIL.shutdownMiniCluster();
} catch (Exception e) {
LOG.warn("failure shutting down cluster", e);
}
}
/**
* Verify that adding data to the cloned table will not affect the original, and vice-versa when
* it is taken as an online snapshot.
*/
@Test
public void testOnlineSnapshotAppendIndependent() throws Exception {
createAndCloneSnapshot(true);
runTestSnapshotAppendIndependent();
}
/**
* Verify that adding data to the cloned table will not affect the original, and vice-versa when
* it is taken as an offline snapshot.
*/
@Test
public void testOfflineSnapshotAppendIndependent() throws Exception {
createAndCloneSnapshot(false);
runTestSnapshotAppendIndependent();
}
/**
* Verify that adding metadata to the cloned table will not affect the original, and vice-versa
* when it is taken as an online snapshot.
*/
@Test
public void testOnlineSnapshotMetadataChangesIndependent() throws Exception {
createAndCloneSnapshot(true);
runTestSnapshotMetadataChangesIndependent();
}
/**
* Verify that adding netadata to the cloned table will not affect the original, and vice-versa
* when is taken as an online snapshot.
*/
@Test
public void testOfflineSnapshotMetadataChangesIndependent() throws Exception {
createAndCloneSnapshot(false);
runTestSnapshotMetadataChangesIndependent();
}
/**
* Verify that region operations, in this case splitting a region, are independent between the
* cloned table and the original.
*/
@Test
public void testOfflineSnapshotRegionOperationsIndependent() throws Exception {
createAndCloneSnapshot(false);
runTestRegionOperationsIndependent();
}
/**
* Verify that region operations, in this case splitting a region, are independent between the
* cloned table and the original.
*/
@Test
public void testOnlineSnapshotRegionOperationsIndependent() throws Exception {
createAndCloneSnapshot(true);
runTestRegionOperationsIndependent();
}
@Test
public void testOfflineSnapshotDeleteIndependent() throws Exception {
createAndCloneSnapshot(false);
runTestSnapshotDeleteIndependent();
}
@Test
public void testOnlineSnapshotDeleteIndependent() throws Exception {
createAndCloneSnapshot(true);
runTestSnapshotDeleteIndependent();
}
private static void waitOnSplit(Connection c, final Table t, int originalCount) throws Exception {
for (int i = 0; i < 200; i++) {
Threads.sleepWithoutInterrupt(500);
try (RegionLocator locator = c.getRegionLocator(t.getName())) {
if (locator.getAllRegionLocations().size() > originalCount) {
return;
}
}
}
throw new Exception("Split did not increase the number of regions");
}
/**
* Takes the snapshot of originalTable and clones the snapshot to another tables.
* If {@code online} is false, the original table is disabled during taking snapshot, so also
* enables it again.
* @param online - Whether the table is online or not during the snapshot
*/
private void createAndCloneSnapshot(boolean online) throws Exception {
SnapshotTestingUtils.createSnapshotAndValidate(admin, originalTableName, TEST_FAM_STR,
snapshotNameAsString, rootDir, fs, online);
// If offline, enable the table disabled by snapshot testing util.
if (!online) {
admin.enableTable(originalTableName);
UTIL.waitTableAvailable(originalTableName);
}
admin.cloneSnapshot(snapshotName, cloneTableName);
UTIL.waitUntilAllRegionsAssigned(cloneTableName);
}
/**
* Verify that adding data to original table or clone table doesn't affect other table.
*/
private void runTestSnapshotAppendIndependent() throws Exception {
try (Table clonedTable = UTIL.getConnection().getTable(cloneTableName)) {
final int clonedTableRowCount = countRows(clonedTable);
Assert.assertEquals(
"The line counts of original and cloned tables do not match after clone. ",
countOriginalTable, clonedTableRowCount);
// Attempt to add data to the test
Put p = new Put(Bytes.toBytes("new-row-" + System.currentTimeMillis()));
p.addColumn(TEST_FAM, Bytes.toBytes("someQualifier"), Bytes.toBytes("someString"));
originalTable.put(p);
// Verify that the new row is not in the restored table
Assert.assertEquals("The row count of the original table was not modified by the put",
countOriginalTable + 1, countRows(originalTable));
Assert.assertEquals(
"The row count of the cloned table changed as a result of addition to the original",
clonedTableRowCount, countRows(clonedTable));
Put p2 = new Put(Bytes.toBytes("new-row-" + System.currentTimeMillis()));
p2.addColumn(TEST_FAM, Bytes.toBytes("someQualifier"), Bytes.toBytes("someString"));
clonedTable.put(p2);
// Verify that the row is not added to the original table.
Assert.assertEquals(
"The row count of the original table was modified by the put to the clone",
countOriginalTable + 1, countRows(originalTable));
Assert.assertEquals("The row count of the cloned table was not modified by the put",
clonedTableRowCount + 1, countRows(clonedTable));
}
}
/**
* Do a split, and verify that this only affects one table
*/
private void runTestRegionOperationsIndependent() throws Exception {
// Verify that region information is the same pre-split
UTIL.getConnection().clearRegionLocationCache();
List<RegionInfo> originalTableHRegions = admin.getRegions(originalTableName);
final int originalRegionCount = originalTableHRegions.size();
final int cloneTableRegionCount = admin.getRegions(cloneTableName).size();
Assert.assertEquals(
"The number of regions in the cloned table is different than in the original table.",
originalRegionCount, cloneTableRegionCount);
// Split a region on the parent table
admin.splitRegionAsync(originalTableHRegions.get(0).getRegionName()).get();
waitOnSplit(UTIL.getConnection(), originalTable, originalRegionCount);
// Verify that the cloned table region is not split
final int cloneTableRegionCount2 = admin.getRegions(cloneTableName).size();
Assert.assertEquals(
"The number of regions in the cloned table changed though none of its regions were split.",
cloneTableRegionCount, cloneTableRegionCount2);
}
/**
* Add metadata, and verify that this only affects one table
*/
private void runTestSnapshotMetadataChangesIndependent() throws Exception {
// Add a new column family to the original table
byte[] TEST_FAM_2 = Bytes.toBytes("fam2");
ColumnFamilyDescriptor familyDescriptor = ColumnFamilyDescriptorBuilder.of(TEST_FAM_2);
admin.disableTable(originalTableName);
admin.addColumnFamily(originalTableName, familyDescriptor);
// Verify that it is not in the snapshot
admin.enableTable(originalTableName);
UTIL.waitTableAvailable(originalTableName);
// get a description of the cloned table
// get a list of its families
// assert that the family is there
TableDescriptor originalTableDescriptor = originalTable.getDescriptor();
TableDescriptor clonedTableDescriptor = admin.getDescriptor(cloneTableName);
Assert.assertTrue("The original family was not found. There is something wrong. ",
originalTableDescriptor.hasColumnFamily(TEST_FAM));
Assert.assertTrue("The original family was not found in the clone. There is something wrong. ",
clonedTableDescriptor.hasColumnFamily(TEST_FAM));
Assert.assertTrue("The new family was not found. ",
originalTableDescriptor.hasColumnFamily(TEST_FAM_2));
Assert.assertTrue("The new family was not found. ",
!clonedTableDescriptor.hasColumnFamily(TEST_FAM_2));
}
/**
* Verify that deleting the snapshot does not affect either table.
*/
private void runTestSnapshotDeleteIndependent() throws Exception {
// Ensure the original table does not reference the HFiles anymore
admin.majorCompact(originalTableName);
// Deleting the snapshot used to break the cloned table by deleting in-use HFiles
admin.deleteSnapshot(snapshotName);
// Wait for cleaner run and DFS heartbeats so that anything that is deletable is fully deleted
Pattern pattern = Pattern.compile(snapshotNameAsString);
do {
Thread.sleep(5000);
} while (!admin.listSnapshots(pattern).isEmpty());
try (Table original = UTIL.getConnection().getTable(originalTableName)) {
try (Table clonedTable = UTIL.getConnection().getTable(cloneTableName)) {
// Verify that all regions of both tables are readable
final int origTableRowCount = countRows(original);
final int clonedTableRowCount = countRows(clonedTable);
Assert.assertEquals(origTableRowCount, clonedTableRowCount);
}
}
}
protected Table createTable(final TableName table, byte[] family) throws Exception {
Table t = UTIL.createTable(table, family);
// Wait for everything to be ready with the table
UTIL.waitUntilAllRegionsAssigned(table);
// At this point the table should be good to go.
return t;
}
public void loadData(final Table table, byte[]... families) throws Exception {
UTIL.loadTable(originalTable, TEST_FAM);
}
protected int countRows(final Table table, final byte[]... families) throws Exception {
return UTIL.countRows(table, families);
}
}