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.snapshot; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertNotNull; 022import static org.junit.Assert.assertTrue; 023import static org.mockito.Mockito.doAnswer; 024import static org.mockito.Mockito.spy; 025 026import java.io.IOException; 027import java.util.List; 028import java.util.concurrent.ExecutorService; 029import java.util.concurrent.Executors; 030import java.util.concurrent.Future; 031import org.apache.hadoop.conf.Configuration; 032import org.apache.hadoop.fs.FileSystem; 033import org.apache.hadoop.fs.Path; 034import org.apache.hadoop.hbase.HBaseClassTestRule; 035import org.apache.hadoop.hbase.HBaseTestingUtility; 036import org.apache.hadoop.hbase.TableName; 037import org.apache.hadoop.hbase.client.Table; 038import org.apache.hadoop.hbase.client.TableDescriptor; 039import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 040import org.apache.hadoop.hbase.errorhandling.ForeignExceptionDispatcher; 041import org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy; 042import org.apache.hadoop.hbase.regionserver.HRegion; 043import org.apache.hadoop.hbase.regionserver.StoreFileInfo; 044import org.apache.hadoop.hbase.regionserver.snapshot.FlushSnapshotSubprocedure; 045import org.apache.hadoop.hbase.testclassification.MediumTests; 046import org.apache.hadoop.hbase.testclassification.RegionServerTests; 047import org.apache.hadoop.hbase.util.Bytes; 048import org.apache.hadoop.hbase.util.FSUtils; 049import org.junit.AfterClass; 050import org.junit.BeforeClass; 051import org.junit.ClassRule; 052import org.junit.Test; 053import org.junit.experimental.categories.Category; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos; 058 059/** 060 * Testing the region snapshot task on a cluster. 061 * @see org.apache.hadoop.hbase.regionserver.snapshot.FlushSnapshotSubprocedure.RegionSnapshotTask 062 */ 063@Category({MediumTests.class, RegionServerTests.class}) 064public class TestRegionSnapshotTask { 065 066 @ClassRule 067 public static final HBaseClassTestRule CLASS_RULE = 068 HBaseClassTestRule.forClass(TestRegionSnapshotTask.class); 069 070 private final Logger LOG = LoggerFactory.getLogger(getClass()); 071 072 private static HBaseTestingUtility TEST_UTIL; 073 private static Configuration conf; 074 private static FileSystem fs; 075 private static Path rootDir; 076 077 @BeforeClass 078 public static void setupBeforeClass() throws Exception { 079 TEST_UTIL = new HBaseTestingUtility(); 080 081 conf = TEST_UTIL.getConfiguration(); 082 083 // Try to frequently clean up compacted files 084 conf.setInt("hbase.hfile.compaction.discharger.interval", 1000); 085 conf.setInt("hbase.master.hfilecleaner.ttl", 1000); 086 087 TEST_UTIL.startMiniCluster(1); 088 TEST_UTIL.getHBaseCluster().waitForActiveAndReadyMaster(); 089 TEST_UTIL.waitUntilAllRegionsAssigned(TableName.META_TABLE_NAME); 090 091 rootDir = FSUtils.getRootDir(conf); 092 fs = TEST_UTIL.getTestFileSystem(); 093 } 094 095 @AfterClass 096 public static void tearDown() throws Exception { 097 TEST_UTIL.shutdownMiniCluster(); 098 } 099 100 /** 101 * Tests adding a region to the snapshot manifest while compactions are running on the region. 102 * The idea is to slow down the process of adding a store file to the manifest while 103 * triggering compactions on the region, allowing the store files to be marked for archival while 104 * snapshot operation is running. 105 * This test checks for the correct behavior in such a case that the compacted files should 106 * not be moved around if a snapshot operation is in progress. 107 * See HBASE-18398 108 */ 109 @Test 110 public void testAddRegionWithCompactions() throws Exception { 111 final TableName tableName = TableName.valueOf("test_table"); 112 Table table = setupTable(tableName); 113 114 List<HRegion> hRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName); 115 116 final SnapshotProtos.SnapshotDescription snapshot = 117 SnapshotProtos.SnapshotDescription.newBuilder() 118 .setTable(tableName.getNameAsString()) 119 .setType(SnapshotProtos.SnapshotDescription.Type.FLUSH) 120 .setName("test_table_snapshot") 121 .setVersion(SnapshotManifestV2.DESCRIPTOR_VERSION) 122 .build(); 123 ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(snapshot.getName()); 124 125 final HRegion region = spy(hRegions.get(0)); 126 127 Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(snapshot, rootDir); 128 final SnapshotManifest manifest = 129 SnapshotManifest.create(conf, fs, workingDir, snapshot, monitor); 130 manifest.addTableDescriptor(table.getTableDescriptor()); 131 132 if (!fs.exists(workingDir)) { 133 fs.mkdirs(workingDir); 134 } 135 assertTrue(fs.exists(workingDir)); 136 SnapshotDescriptionUtils.writeSnapshotInfo(snapshot, workingDir, fs); 137 138 doAnswer(__ -> { 139 addRegionToSnapshot(snapshot, region, manifest); 140 return null; 141 }).when(region).addRegionToSnapshot(snapshot, monitor); 142 143 FlushSnapshotSubprocedure.RegionSnapshotTask snapshotTask = 144 new FlushSnapshotSubprocedure.RegionSnapshotTask(region, snapshot, true, monitor); 145 ExecutorService executor = Executors.newFixedThreadPool(1); 146 Future f = executor.submit(snapshotTask); 147 148 // Trigger major compaction and wait for snaphot operation to finish 149 LOG.info("Starting major compaction"); 150 region.compact(true); 151 LOG.info("Finished major compaction"); 152 f.get(); 153 154 // Consolidate region manifests into a single snapshot manifest 155 manifest.consolidate(); 156 157 // Make sure that the region manifest exists, which means the snapshot operation succeeded 158 assertNotNull(manifest.getRegionManifests()); 159 // Sanity check, there should be only one region 160 assertEquals(1, manifest.getRegionManifests().size()); 161 162 // Make sure that no files went missing after the snapshot operation 163 SnapshotReferenceUtil.verifySnapshot(conf, fs, manifest); 164 } 165 166 private void addRegionToSnapshot(SnapshotProtos.SnapshotDescription snapshot, 167 HRegion region, SnapshotManifest manifest) throws Exception { 168 LOG.info("Adding region to snapshot: " + region.getRegionInfo().getRegionNameAsString()); 169 Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(snapshot, rootDir); 170 SnapshotManifest.RegionVisitor visitor = createRegionVisitorWithDelay(snapshot, workingDir); 171 manifest.addRegion(region, visitor); 172 LOG.info("Added the region to snapshot: " + region.getRegionInfo().getRegionNameAsString()); 173 } 174 175 private SnapshotManifest.RegionVisitor createRegionVisitorWithDelay( 176 SnapshotProtos.SnapshotDescription desc, Path workingDir) { 177 return new SnapshotManifestV2.ManifestBuilder(conf, fs, workingDir) { 178 @Override 179 public void storeFile(final SnapshotProtos.SnapshotRegionManifest.Builder region, 180 final SnapshotProtos.SnapshotRegionManifest.FamilyFiles.Builder family, 181 final StoreFileInfo storeFile) throws IOException { 182 try { 183 LOG.debug("Introducing delay before adding store file to manifest"); 184 Thread.sleep(2000); 185 } catch (InterruptedException ex) { 186 LOG.error("Interrupted due to error: " + ex); 187 } 188 super.storeFile(region, family, storeFile); 189 } 190 }; 191 } 192 193 private Table setupTable(TableName tableName) throws Exception { 194 TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName); 195 // Flush many files, but do not compact immediately 196 // Make sure that the region does not split 197 builder 198 .setMemStoreFlushSize(5000) 199 .setRegionSplitPolicyClassName(ConstantSizeRegionSplitPolicy.class.getName()) 200 .setMaxFileSize(100 * 1024 * 1024) 201 .setValue("hbase.hstore.compactionThreshold", "250"); 202 203 TableDescriptor td = builder.build(); 204 byte[] fam = Bytes.toBytes("fam"); 205 Table table = TEST_UTIL.createTable(td, new byte[][] {fam}, 206 TEST_UTIL.getConfiguration()); 207 TEST_UTIL.loadTable(table, fam); 208 return table; 209 } 210}