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