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.client; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertTrue; 022 023import java.util.List; 024import java.util.regex.Pattern; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.fs.FileSystem; 027import org.apache.hadoop.fs.Path; 028import org.apache.hadoop.hbase.HBaseTestingUtil; 029import org.apache.hadoop.hbase.HConstants; 030import org.apache.hadoop.hbase.TableName; 031import org.apache.hadoop.hbase.master.snapshot.SnapshotManager; 032import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy; 033import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils; 034import org.apache.hadoop.hbase.util.Bytes; 035import org.apache.hadoop.hbase.util.EnvironmentEdgeManager; 036import org.apache.hadoop.hbase.util.Threads; 037import org.junit.jupiter.api.AfterAll; 038import org.junit.jupiter.api.AfterEach; 039import org.junit.jupiter.api.BeforeEach; 040import org.junit.jupiter.api.Test; 041import org.junit.jupiter.api.TestInfo; 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044 045/** 046 * Test to verify that the cloned table is independent of the table from which it was cloned 047 */ 048public abstract class SnapshotCloneIndependenceTestBase { 049 050 private static final Logger LOG = 051 LoggerFactory.getLogger(SnapshotCloneIndependenceTestBase.class); 052 053 protected static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 054 055 protected static final int NUM_RS = 2; 056 private static final String TEST_FAM_STR = "fam"; 057 protected static final byte[] TEST_FAM = Bytes.toBytes(TEST_FAM_STR); 058 private static final int CLEANER_INTERVAL = 100; 059 060 private FileSystem fs; 061 private Path rootDir; 062 private Admin admin; 063 private TableName originalTableName; 064 private Table originalTable; 065 private TableName cloneTableName; 066 private int countOriginalTable; 067 String snapshotNameAsString; 068 String snapshotName; 069 070 protected static void setupConf(Configuration conf) { 071 // Up the handlers; this test needs more than usual. 072 conf.setInt(HConstants.REGION_SERVER_HIGH_PRIORITY_HANDLER_COUNT, 15); 073 // enable snapshot support 074 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true); 075 // change the flush size to a small amount, regulating number of store files 076 conf.setInt("hbase.hregion.memstore.flush.size", 25000); 077 // so make sure we get a compaction when doing a load, but keep around 078 // some files in the store 079 conf.setInt("hbase.hstore.compaction.min", 10); 080 conf.setInt("hbase.hstore.compactionThreshold", 10); 081 // block writes if we get to 12 store files 082 conf.setInt("hbase.hstore.blockingStoreFiles", 12); 083 conf.setInt("hbase.regionserver.msginterval", 100); 084 conf.setBoolean("hbase.master.enabletable.roundrobin", true); 085 // Avoid potentially aggressive splitting which would cause snapshot to fail 086 conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, 087 ConstantSizeRegionSplitPolicy.class.getName()); 088 // Execute cleaner frequently to induce failures 089 conf.setInt("hbase.master.cleaner.interval", CLEANER_INTERVAL); 090 conf.setInt("hbase.master.hfilecleaner.plugins.snapshot.period", CLEANER_INTERVAL); 091 // Effectively disable TimeToLiveHFileCleaner. Don't want to fully disable it because that 092 // will even trigger races between creating the directory containing back references and 093 // the back reference itself. 094 conf.setInt("hbase.master.hfilecleaner.ttl", CLEANER_INTERVAL); 095 } 096 097 @BeforeEach 098 public void setup(TestInfo testInfo) throws Exception { 099 fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem(); 100 rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); 101 102 admin = UTIL.getAdmin(); 103 originalTableName = TableName.valueOf("test" + testInfo.getTestMethod().get().getName()); 104 cloneTableName = TableName.valueOf("test-clone-" + originalTableName); 105 snapshotNameAsString = "snapshot_" + originalTableName; 106 snapshotName = snapshotNameAsString; 107 108 originalTable = createTable(originalTableName, TEST_FAM); 109 loadData(originalTable, TEST_FAM); 110 countOriginalTable = countRows(originalTable); 111 LOG.info("Original table has: " + countOriginalTable + " rows"); 112 } 113 114 @AfterEach 115 public void tearDown() throws Exception { 116 UTIL.deleteTable(originalTableName); 117 UTIL.deleteTable(cloneTableName); 118 SnapshotTestingUtils.deleteAllSnapshots(UTIL.getAdmin()); 119 SnapshotTestingUtils.deleteArchiveDirectory(UTIL); 120 } 121 122 @AfterAll 123 public static void cleanupTest() throws Exception { 124 try { 125 UTIL.shutdownMiniCluster(); 126 } catch (Exception e) { 127 LOG.warn("failure shutting down cluster", e); 128 } 129 } 130 131 /** 132 * Verify that adding data to the cloned table will not affect the original, and vice-versa when 133 * it is taken as an online snapshot. 134 */ 135 @Test 136 public void testOnlineSnapshotAppendIndependent() throws Exception { 137 createAndCloneSnapshot(true); 138 runTestSnapshotAppendIndependent(); 139 } 140 141 /** 142 * Verify that adding data to the cloned table will not affect the original, and vice-versa when 143 * it is taken as an offline snapshot. 144 */ 145 @Test 146 public void testOfflineSnapshotAppendIndependent() throws Exception { 147 createAndCloneSnapshot(false); 148 runTestSnapshotAppendIndependent(); 149 } 150 151 /** 152 * Verify that adding metadata to the cloned table will not affect the original, and vice-versa 153 * when it is taken as an online snapshot. 154 */ 155 @Test 156 public void testOnlineSnapshotMetadataChangesIndependent() throws Exception { 157 createAndCloneSnapshot(true); 158 runTestSnapshotMetadataChangesIndependent(); 159 } 160 161 /** 162 * Verify that adding netadata to the cloned table will not affect the original, and vice-versa 163 * when is taken as an online snapshot. 164 */ 165 @Test 166 public void testOfflineSnapshotMetadataChangesIndependent() throws Exception { 167 createAndCloneSnapshot(false); 168 runTestSnapshotMetadataChangesIndependent(); 169 } 170 171 /** 172 * Verify that region operations, in this case splitting a region, are independent between the 173 * cloned table and the original. 174 */ 175 @Test 176 public void testOfflineSnapshotRegionOperationsIndependent() throws Exception { 177 createAndCloneSnapshot(false); 178 runTestRegionOperationsIndependent(); 179 } 180 181 /** 182 * Verify that region operations, in this case splitting a region, are independent between the 183 * cloned table and the original. 184 */ 185 @Test 186 public void testOnlineSnapshotRegionOperationsIndependent() throws Exception { 187 createAndCloneSnapshot(true); 188 runTestRegionOperationsIndependent(); 189 } 190 191 @Test 192 public void testOfflineSnapshotDeleteIndependent() throws Exception { 193 createAndCloneSnapshot(false); 194 runTestSnapshotDeleteIndependent(); 195 } 196 197 @Test 198 public void testOnlineSnapshotDeleteIndependent() throws Exception { 199 createAndCloneSnapshot(true); 200 runTestSnapshotDeleteIndependent(); 201 } 202 203 private static void waitOnSplit(Connection c, final Table t, int originalCount) throws Exception { 204 for (int i = 0; i < 200; i++) { 205 Threads.sleepWithoutInterrupt(500); 206 try (RegionLocator locator = c.getRegionLocator(t.getName())) { 207 if (locator.getAllRegionLocations().size() > originalCount) { 208 return; 209 } 210 } 211 } 212 throw new Exception("Split did not increase the number of regions"); 213 } 214 215 /** 216 * Takes the snapshot of originalTable and clones the snapshot to another tables. If 217 * {@code online} is false, the original table is disabled during taking snapshot, so also enables 218 * it again. 219 * @param online - Whether the table is online or not during the snapshot 220 */ 221 private void createAndCloneSnapshot(boolean online) throws Exception { 222 SnapshotTestingUtils.createSnapshotAndValidate(admin, originalTableName, TEST_FAM_STR, 223 snapshotNameAsString, rootDir, fs, online); 224 225 // If offline, enable the table disabled by snapshot testing util. 226 if (!online) { 227 admin.enableTable(originalTableName); 228 UTIL.waitTableAvailable(originalTableName); 229 } 230 231 admin.cloneSnapshot(snapshotName, cloneTableName); 232 UTIL.waitUntilAllRegionsAssigned(cloneTableName); 233 } 234 235 /** 236 * Verify that adding data to original table or clone table doesn't affect other table. 237 */ 238 private void runTestSnapshotAppendIndependent() throws Exception { 239 try (Table clonedTable = UTIL.getConnection().getTable(cloneTableName)) { 240 final int clonedTableRowCount = countRows(clonedTable); 241 242 assertEquals(countOriginalTable, clonedTableRowCount, 243 "The line counts of original and cloned tables do not match after clone. "); 244 245 // Attempt to add data to the test 246 Put p = new Put(Bytes.toBytes("new-row-" + EnvironmentEdgeManager.currentTime())); 247 p.addColumn(TEST_FAM, Bytes.toBytes("someQualifier"), Bytes.toBytes("someString")); 248 originalTable.put(p); 249 250 // Verify that the new row is not in the restored table 251 assertEquals(countOriginalTable + 1, countRows(originalTable), 252 "The row count of the original table was not modified by the put"); 253 assertEquals(clonedTableRowCount, countRows(clonedTable), 254 "The row count of the cloned table changed as a result of addition to the original"); 255 256 Put p2 = new Put(Bytes.toBytes("new-row-" + EnvironmentEdgeManager.currentTime())); 257 p2.addColumn(TEST_FAM, Bytes.toBytes("someQualifier"), Bytes.toBytes("someString")); 258 clonedTable.put(p2); 259 260 // Verify that the row is not added to the original table. 261 assertEquals(countOriginalTable + 1, countRows(originalTable), 262 "The row count of the original table was modified by the put to the clone"); 263 assertEquals(clonedTableRowCount + 1, countRows(clonedTable), 264 "The row count of the cloned table was not modified by the put"); 265 } 266 } 267 268 /** 269 * Do a split, and verify that this only affects one table 270 */ 271 private void runTestRegionOperationsIndependent() throws Exception { 272 // Verify that region information is the same pre-split 273 UTIL.getConnection().clearRegionLocationCache(); 274 List<RegionInfo> originalTableHRegions = admin.getRegions(originalTableName); 275 276 final int originalRegionCount = originalTableHRegions.size(); 277 final int cloneTableRegionCount = admin.getRegions(cloneTableName).size(); 278 assertEquals(originalRegionCount, cloneTableRegionCount, 279 "The number of regions in the cloned table is different than in the original table."); 280 281 // Split a region on the parent table 282 admin.splitRegionAsync(originalTableHRegions.get(0).getRegionName()).get(); 283 waitOnSplit(UTIL.getConnection(), originalTable, originalRegionCount); 284 285 // Verify that the cloned table region is not split 286 final int cloneTableRegionCount2 = admin.getRegions(cloneTableName).size(); 287 assertEquals(cloneTableRegionCount, cloneTableRegionCount2, 288 "The number of regions in the cloned table changed though none of its regions were split."); 289 } 290 291 /** 292 * Add metadata, and verify that this only affects one table 293 */ 294 private void runTestSnapshotMetadataChangesIndependent() throws Exception { 295 // Add a new column family to the original table 296 byte[] TEST_FAM_2 = Bytes.toBytes("fam2"); 297 ColumnFamilyDescriptor familyDescriptor = ColumnFamilyDescriptorBuilder.of(TEST_FAM_2); 298 299 admin.disableTable(originalTableName); 300 admin.addColumnFamily(originalTableName, familyDescriptor); 301 302 // Verify that it is not in the snapshot 303 admin.enableTable(originalTableName); 304 UTIL.waitTableAvailable(originalTableName); 305 306 // get a description of the cloned table 307 // get a list of its families 308 // assert that the family is there 309 TableDescriptor originalTableDescriptor = originalTable.getDescriptor(); 310 TableDescriptor clonedTableDescriptor = admin.getDescriptor(cloneTableName); 311 312 assertTrue(originalTableDescriptor.hasColumnFamily(TEST_FAM), 313 "The original family was not found. There is something wrong. "); 314 assertTrue(clonedTableDescriptor.hasColumnFamily(TEST_FAM), 315 "The original family was not found in the clone. There is something wrong. "); 316 317 assertTrue(originalTableDescriptor.hasColumnFamily(TEST_FAM_2), 318 "The new family was not found. "); 319 assertTrue(!clonedTableDescriptor.hasColumnFamily(TEST_FAM_2), 320 "The new family was not found. "); 321 } 322 323 /** 324 * Verify that deleting the snapshot does not affect either table. 325 */ 326 private void runTestSnapshotDeleteIndependent() throws Exception { 327 // Ensure the original table does not reference the HFiles anymore 328 admin.majorCompact(originalTableName); 329 330 // Deleting the snapshot used to break the cloned table by deleting in-use HFiles 331 admin.deleteSnapshot(snapshotName); 332 333 // Wait for cleaner run and DFS heartbeats so that anything that is deletable is fully deleted 334 Pattern pattern = Pattern.compile(snapshotNameAsString); 335 do { 336 Thread.sleep(5000); 337 } while (!admin.listSnapshots(pattern).isEmpty()); 338 339 try (Table original = UTIL.getConnection().getTable(originalTableName)) { 340 try (Table clonedTable = UTIL.getConnection().getTable(cloneTableName)) { 341 // Verify that all regions of both tables are readable 342 final int origTableRowCount = countRows(original); 343 final int clonedTableRowCount = countRows(clonedTable); 344 assertEquals(origTableRowCount, clonedTableRowCount); 345 } 346 } 347 } 348 349 protected Table createTable(final TableName table, byte[] family) throws Exception { 350 Table t = UTIL.createTable(table, family); 351 // Wait for everything to be ready with the table 352 UTIL.waitUntilAllRegionsAssigned(table); 353 354 // At this point the table should be good to go. 355 return t; 356 } 357 358 public void loadData(final Table table, byte[]... families) throws Exception { 359 UTIL.loadTable(originalTable, TEST_FAM); 360 } 361 362 protected int countRows(final Table table, final byte[]... families) throws Exception { 363 return UTIL.countRows(table, families); 364 } 365}