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.HBaseTestingUtil; 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.CommonFSUtils; 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 HBaseTestingUtil 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 HBaseTestingUtil(); 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 = CommonFSUtils.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. The 102 * idea is to slow down the process of adding a store file to the manifest while triggering 103 * compactions on the region, allowing the store files to be marked for archival while snapshot 104 * operation is running. This test checks for the correct behavior in such a case that the 105 * compacted files should not be moved around if a snapshot operation is in progress. See 106 * HBASE-18398 107 */ 108 @Test 109 public void testAddRegionWithCompactions() throws Exception { 110 final TableName tableName = TableName.valueOf("test_table"); 111 Table table = setupTable(tableName); 112 113 List<HRegion> hRegions = TEST_UTIL.getHBaseCluster().getRegions(tableName); 114 115 final SnapshotProtos.SnapshotDescription snapshot = 116 SnapshotProtos.SnapshotDescription.newBuilder().setTable(tableName.getNameAsString()) 117 .setType(SnapshotProtos.SnapshotDescription.Type.FLUSH).setName("test_table_snapshot") 118 .setVersion(SnapshotManifestV2.DESCRIPTOR_VERSION).build(); 119 ForeignExceptionDispatcher monitor = new ForeignExceptionDispatcher(snapshot.getName()); 120 121 final HRegion region = spy(hRegions.get(0)); 122 123 Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(snapshot, rootDir, conf); 124 final SnapshotManifest manifest = 125 SnapshotManifest.create(conf, fs, workingDir, snapshot, monitor); 126 manifest.addTableDescriptor(table.getDescriptor()); 127 128 if (!fs.exists(workingDir)) { 129 fs.mkdirs(workingDir); 130 } 131 assertTrue(fs.exists(workingDir)); 132 SnapshotDescriptionUtils.writeSnapshotInfo(snapshot, workingDir, fs); 133 134 doAnswer(__ -> { 135 addRegionToSnapshot(snapshot, region, manifest); 136 return null; 137 }).when(region).addRegionToSnapshot(snapshot, monitor); 138 139 FlushSnapshotSubprocedure.RegionSnapshotTask snapshotTask = 140 new FlushSnapshotSubprocedure.RegionSnapshotTask(region, snapshot, true, monitor); 141 ExecutorService executor = Executors.newFixedThreadPool(1); 142 Future f = executor.submit(snapshotTask); 143 144 // Trigger major compaction and wait for snaphot operation to finish 145 LOG.info("Starting major compaction"); 146 region.compact(true); 147 LOG.info("Finished major compaction"); 148 f.get(); 149 150 // Consolidate region manifests into a single snapshot manifest 151 manifest.consolidate(); 152 153 // Make sure that the region manifest exists, which means the snapshot operation succeeded 154 assertNotNull(manifest.getRegionManifests()); 155 // Sanity check, there should be only one region 156 assertEquals(1, manifest.getRegionManifests().size()); 157 158 // Make sure that no files went missing after the snapshot operation 159 SnapshotReferenceUtil.verifySnapshot(conf, fs, manifest); 160 } 161 162 private void addRegionToSnapshot(SnapshotProtos.SnapshotDescription snapshot, HRegion region, 163 SnapshotManifest manifest) throws Exception { 164 LOG.info("Adding region to snapshot: " + region.getRegionInfo().getRegionNameAsString()); 165 Path workingDir = SnapshotDescriptionUtils.getWorkingSnapshotDir(snapshot, rootDir, conf); 166 SnapshotManifest.RegionVisitor visitor = createRegionVisitorWithDelay(snapshot, workingDir); 167 manifest.addRegion(region, visitor); 168 LOG.info("Added the region to snapshot: " + region.getRegionInfo().getRegionNameAsString()); 169 } 170 171 private SnapshotManifest.RegionVisitor 172 createRegionVisitorWithDelay(SnapshotProtos.SnapshotDescription desc, Path workingDir) { 173 return new SnapshotManifestV2.ManifestBuilder(conf, fs, workingDir) { 174 @Override 175 public void storeFile(final SnapshotProtos.SnapshotRegionManifest.Builder region, 176 final SnapshotProtos.SnapshotRegionManifest.FamilyFiles.Builder family, 177 final StoreFileInfo storeFile) throws IOException { 178 try { 179 LOG.debug("Introducing delay before adding store file to manifest"); 180 Thread.sleep(2000); 181 } catch (InterruptedException ex) { 182 LOG.error("Interrupted due to error: " + ex); 183 } 184 super.storeFile(region, family, storeFile); 185 } 186 }; 187 } 188 189 private Table setupTable(TableName tableName) throws Exception { 190 TableDescriptorBuilder builder = TableDescriptorBuilder.newBuilder(tableName); 191 // Flush many files, but do not compact immediately 192 // Make sure that the region does not split 193 builder.setMemStoreFlushSize(5000) 194 .setRegionSplitPolicyClassName(ConstantSizeRegionSplitPolicy.class.getName()) 195 .setMaxFileSize(100 * 1024 * 1024).setValue("hbase.hstore.compactionThreshold", "250"); 196 197 TableDescriptor td = builder.build(); 198 byte[] fam = Bytes.toBytes("fam"); 199 Table table = TEST_UTIL.createTable(td, new byte[][] { fam }, TEST_UTIL.getConfiguration()); 200 TEST_UTIL.loadTable(table, fam); 201 return table; 202 } 203}