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.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022 023import java.util.ArrayList; 024import java.util.Collections; 025import java.util.List; 026import org.apache.hadoop.conf.Configuration; 027import org.apache.hadoop.fs.FileSystem; 028import org.apache.hadoop.fs.Path; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.HBaseTestingUtility; 031import org.apache.hadoop.hbase.HColumnDescriptor; 032import org.apache.hadoop.hbase.HConstants; 033import org.apache.hadoop.hbase.HTableDescriptor; 034import org.apache.hadoop.hbase.TableName; 035import org.apache.hadoop.hbase.io.encoding.DataBlockEncoding; 036import org.apache.hadoop.hbase.master.snapshot.SnapshotManager; 037import org.apache.hadoop.hbase.regionserver.BloomType; 038import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy; 039import org.apache.hadoop.hbase.snapshot.SnapshotTestingUtils; 040import org.apache.hadoop.hbase.testclassification.ClientTests; 041import org.apache.hadoop.hbase.testclassification.MediumTests; 042import org.apache.hadoop.hbase.util.Bytes; 043import org.junit.After; 044import org.junit.AfterClass; 045import org.junit.Before; 046import org.junit.BeforeClass; 047import org.junit.ClassRule; 048import org.junit.Test; 049import org.junit.experimental.categories.Category; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053/** 054 * Test class to verify that metadata is consistent before and after a snapshot attempt. 055 */ 056@Category({MediumTests.class, ClientTests.class}) 057public class TestSnapshotMetadata { 058 059 @ClassRule 060 public static final HBaseClassTestRule CLASS_RULE = 061 HBaseClassTestRule.forClass(TestSnapshotMetadata.class); 062 063 private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotMetadata.class); 064 065 private static final HBaseTestingUtility UTIL = new HBaseTestingUtility(); 066 private static final int NUM_RS = 2; 067 private static final String STRING_TABLE_NAME = "TestSnapshotMetadata"; 068 069 private static final String MAX_VERSIONS_FAM_STR = "fam_max_columns"; 070 private static final byte[] MAX_VERSIONS_FAM = Bytes.toBytes(MAX_VERSIONS_FAM_STR); 071 072 private static final String COMPRESSED_FAM_STR = "fam_compressed"; 073 private static final byte[] COMPRESSED_FAM = Bytes.toBytes(COMPRESSED_FAM_STR); 074 075 private static final String BLOCKSIZE_FAM_STR = "fam_blocksize"; 076 private static final byte[] BLOCKSIZE_FAM = Bytes.toBytes(BLOCKSIZE_FAM_STR); 077 078 private static final String BLOOMFILTER_FAM_STR = "fam_bloomfilter"; 079 private static final byte[] BLOOMFILTER_FAM = Bytes.toBytes(BLOOMFILTER_FAM_STR); 080 081 private static final String TEST_CONF_CUSTOM_VALUE = "TestCustomConf"; 082 private static final String TEST_CUSTOM_VALUE = "TestCustomValue"; 083 084 private static final byte[][] families = { 085 MAX_VERSIONS_FAM, BLOOMFILTER_FAM, COMPRESSED_FAM, BLOCKSIZE_FAM 086 }; 087 088 private static final DataBlockEncoding DATA_BLOCK_ENCODING_TYPE = DataBlockEncoding.FAST_DIFF; 089 private static final BloomType BLOOM_TYPE = BloomType.ROW; 090 private static final int BLOCK_SIZE = 98; 091 private static final int MAX_VERSIONS = 8; 092 093 private Admin admin; 094 private String originalTableDescription; 095 private HTableDescriptor originalTableDescriptor; 096 TableName originalTableName; 097 098 private static FileSystem fs; 099 private static Path rootDir; 100 101 @BeforeClass 102 public static void setupCluster() throws Exception { 103 setupConf(UTIL.getConfiguration()); 104 UTIL.startMiniCluster(NUM_RS); 105 106 fs = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getFileSystem(); 107 rootDir = UTIL.getHBaseCluster().getMaster().getMasterFileSystem().getRootDir(); 108 } 109 110 @AfterClass 111 public static void cleanupTest() throws Exception { 112 try { 113 UTIL.shutdownMiniCluster(); 114 } catch (Exception e) { 115 LOG.warn("failure shutting down cluster", e); 116 } 117 } 118 119 private static void setupConf(Configuration conf) { 120 // enable snapshot support 121 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true); 122 // disable the ui 123 conf.setInt("hbase.regionsever.info.port", -1); 124 // change the flush size to a small amount, regulating number of store files 125 conf.setInt("hbase.hregion.memstore.flush.size", 25000); 126 // so make sure we get a compaction when doing a load, but keep around 127 // some files in the store 128 conf.setInt("hbase.hstore.compaction.min", 10); 129 conf.setInt("hbase.hstore.compactionThreshold", 10); 130 // block writes if we get to 12 store files 131 conf.setInt("hbase.hstore.blockingStoreFiles", 12); 132 conf.setInt("hbase.regionserver.msginterval", 100); 133 conf.setBoolean("hbase.master.enabletable.roundrobin", true); 134 // Avoid potentially aggressive splitting which would cause snapshot to fail 135 conf.set(HConstants.HBASE_REGION_SPLIT_POLICY_KEY, 136 ConstantSizeRegionSplitPolicy.class.getName()); 137 } 138 139 @Before 140 public void setup() throws Exception { 141 admin = UTIL.getAdmin(); 142 createTableWithNonDefaultProperties(); 143 } 144 145 @After 146 public void tearDown() throws Exception { 147 SnapshotTestingUtils.deleteAllSnapshots(admin); 148 } 149 150 /* 151 * Create a table that has non-default properties so we can see if they hold 152 */ 153 private void createTableWithNonDefaultProperties() throws Exception { 154 final long startTime = System.currentTimeMillis(); 155 final String sourceTableNameAsString = STRING_TABLE_NAME + startTime; 156 originalTableName = TableName.valueOf(sourceTableNameAsString); 157 158 // enable replication on a column family 159 HColumnDescriptor maxVersionsColumn = new HColumnDescriptor(MAX_VERSIONS_FAM); 160 HColumnDescriptor bloomFilterColumn = new HColumnDescriptor(BLOOMFILTER_FAM); 161 HColumnDescriptor dataBlockColumn = new HColumnDescriptor(COMPRESSED_FAM); 162 HColumnDescriptor blockSizeColumn = new HColumnDescriptor(BLOCKSIZE_FAM); 163 164 maxVersionsColumn.setMaxVersions(MAX_VERSIONS); 165 bloomFilterColumn.setBloomFilterType(BLOOM_TYPE); 166 dataBlockColumn.setDataBlockEncoding(DATA_BLOCK_ENCODING_TYPE); 167 blockSizeColumn.setBlocksize(BLOCK_SIZE); 168 169 HTableDescriptor htd = new HTableDescriptor(TableName.valueOf(sourceTableNameAsString)); 170 htd.addFamily(maxVersionsColumn); 171 htd.addFamily(bloomFilterColumn); 172 htd.addFamily(dataBlockColumn); 173 htd.addFamily(blockSizeColumn); 174 htd.setValue(TEST_CUSTOM_VALUE, TEST_CUSTOM_VALUE); 175 htd.setConfiguration(TEST_CONF_CUSTOM_VALUE, TEST_CONF_CUSTOM_VALUE); 176 assertTrue(htd.getConfiguration().size() > 0); 177 178 admin.createTable(htd); 179 Table original = UTIL.getConnection().getTable(originalTableName); 180 originalTableName = TableName.valueOf(sourceTableNameAsString); 181 originalTableDescriptor = admin.getTableDescriptor(originalTableName); 182 originalTableDescription = originalTableDescriptor.toStringCustomizedValues(); 183 184 original.close(); 185 } 186 187 188 /** 189 * Verify that the describe for a cloned table matches the describe from the original. 190 */ 191 @Test 192 public void testDescribeMatchesAfterClone() throws Exception { 193 // Clone the original table 194 final String clonedTableNameAsString = "clone" + originalTableName; 195 final TableName clonedTableName = TableName.valueOf(clonedTableNameAsString); 196 final String snapshotNameAsString = "snapshot" + originalTableName 197 + System.currentTimeMillis(); 198 final byte[] snapshotName = Bytes.toBytes(snapshotNameAsString); 199 200 // restore the snapshot into a cloned table and examine the output 201 List<byte[]> familiesList = new ArrayList<>(); 202 Collections.addAll(familiesList, families); 203 204 // Create a snapshot in which all families are empty 205 SnapshotTestingUtils.createSnapshotAndValidate(admin, originalTableName, null, 206 familiesList, snapshotNameAsString, rootDir, fs, /* onlineSnapshot= */ false); 207 208 admin.cloneSnapshot(snapshotName, clonedTableName); 209 Table clonedTable = UTIL.getConnection().getTable(clonedTableName); 210 HTableDescriptor cloneHtd = admin.getTableDescriptor(clonedTableName); 211 assertEquals( 212 originalTableDescription.replace(originalTableName.getNameAsString(),clonedTableNameAsString), 213 cloneHtd.toStringCustomizedValues()); 214 215 // Verify the custom fields 216 assertEquals(originalTableDescriptor.getValues().size(), 217 cloneHtd.getValues().size()); 218 assertEquals(originalTableDescriptor.getConfiguration().size(), 219 cloneHtd.getConfiguration().size()); 220 assertEquals(TEST_CUSTOM_VALUE, cloneHtd.getValue(TEST_CUSTOM_VALUE)); 221 assertEquals(TEST_CONF_CUSTOM_VALUE, cloneHtd.getConfigurationValue(TEST_CONF_CUSTOM_VALUE)); 222 assertEquals(originalTableDescriptor.getValues(), cloneHtd.getValues()); 223 assertEquals(originalTableDescriptor.getConfiguration(), cloneHtd.getConfiguration()); 224 225 admin.enableTable(originalTableName); 226 clonedTable.close(); 227 } 228 229 /** 230 * Verify that the describe for a restored table matches the describe for one the original. 231 */ 232 @Test 233 public void testDescribeMatchesAfterRestore() throws Exception { 234 runRestoreWithAdditionalMetadata(false); 235 } 236 237 /** 238 * Verify that if metadata changed after a snapshot was taken, that the old metadata replaces the 239 * new metadata during a restore 240 */ 241 @Test 242 public void testDescribeMatchesAfterMetadataChangeAndRestore() throws Exception { 243 runRestoreWithAdditionalMetadata(true); 244 } 245 246 /** 247 * Verify that when the table is empty, making metadata changes after the restore does not affect 248 * the restored table's original metadata 249 * @throws Exception 250 */ 251 @Test 252 public void testDescribeOnEmptyTableMatchesAfterMetadataChangeAndRestore() throws Exception { 253 runRestoreWithAdditionalMetadata(true, false); 254 } 255 256 private void runRestoreWithAdditionalMetadata(boolean changeMetadata) throws Exception { 257 runRestoreWithAdditionalMetadata(changeMetadata, true); 258 } 259 260 private void runRestoreWithAdditionalMetadata(boolean changeMetadata, boolean addData) 261 throws Exception { 262 263 if (admin.isTableDisabled(originalTableName)) { 264 admin.enableTable(originalTableName); 265 } 266 267 // populate it with data 268 final byte[] familyForUpdate = BLOCKSIZE_FAM; 269 270 List<byte[]> familiesWithDataList = new ArrayList<>(); 271 List<byte[]> emptyFamiliesList = new ArrayList<>(); 272 if (addData) { 273 Table original = UTIL.getConnection().getTable(originalTableName); 274 UTIL.loadTable(original, familyForUpdate); // family arbitrarily chosen 275 original.close(); 276 277 for (byte[] family : families) { 278 if (family != familyForUpdate) { 279 emptyFamiliesList.add(family); 280 } 281 } 282 familiesWithDataList.add(familyForUpdate); 283 } else { 284 Collections.addAll(emptyFamiliesList, families); 285 } 286 287 // take a "disabled" snapshot 288 final String snapshotNameAsString = "snapshot" + originalTableName 289 + System.currentTimeMillis(); 290 final byte[] snapshotName = Bytes.toBytes(snapshotNameAsString); 291 292 SnapshotTestingUtils.createSnapshotAndValidate(admin, originalTableName, 293 familiesWithDataList, emptyFamiliesList, snapshotNameAsString, rootDir, fs, 294 /* onlineSnapshot= */ false); 295 296 admin.enableTable(originalTableName); 297 298 if (changeMetadata) { 299 final String newFamilyNameAsString = "newFamily" + System.currentTimeMillis(); 300 final byte[] newFamilyName = Bytes.toBytes(newFamilyNameAsString); 301 302 admin.disableTable(originalTableName); 303 HColumnDescriptor hcd = new HColumnDescriptor(newFamilyName); 304 admin.addColumnFamily(originalTableName, hcd); 305 assertTrue("New column family was not added.", 306 admin.getTableDescriptor(originalTableName).toString().contains(newFamilyNameAsString)); 307 } 308 309 // restore it 310 if (!admin.isTableDisabled(originalTableName)) { 311 admin.disableTable(originalTableName); 312 } 313 314 admin.restoreSnapshot(snapshotName); 315 admin.enableTable(originalTableName); 316 317 // verify that the descrption is reverted 318 Table original = UTIL.getConnection().getTable(originalTableName); 319 try { 320 assertTrue(originalTableDescriptor.equals(admin.getTableDescriptor(originalTableName))); 321 assertTrue(originalTableDescriptor.equals(original.getTableDescriptor())); 322 } finally { 323 original.close(); 324 } 325 } 326}