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.master.snapshot; 019 020import static org.junit.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022import static org.junit.jupiter.api.Assertions.assertTrue; 023import static org.junit.jupiter.api.Assertions.fail; 024 025import java.io.IOException; 026import org.apache.hadoop.conf.Configuration; 027import org.apache.hadoop.fs.FileStatus; 028import org.apache.hadoop.fs.FileSystem; 029import org.apache.hadoop.fs.Path; 030import org.apache.hadoop.hbase.HBaseTestingUtil; 031import org.apache.hadoop.hbase.Stoppable; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 034import org.apache.hadoop.hbase.client.RegionInfo; 035import org.apache.hadoop.hbase.client.RegionInfoBuilder; 036import org.apache.hadoop.hbase.executor.ExecutorService; 037import org.apache.hadoop.hbase.io.HFileLink; 038import org.apache.hadoop.hbase.master.MasterFileSystem; 039import org.apache.hadoop.hbase.master.MasterServices; 040import org.apache.hadoop.hbase.master.cleaner.DirScanPool; 041import org.apache.hadoop.hbase.master.cleaner.HFileCleaner; 042import org.apache.hadoop.hbase.master.cleaner.HFileLinkCleaner; 043import org.apache.hadoop.hbase.procedure.ProcedureCoordinator; 044import org.apache.hadoop.hbase.regionserver.HRegionFileSystem; 045import org.apache.hadoop.hbase.regionserver.StoreContext; 046import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker; 047import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory; 048import org.apache.hadoop.hbase.snapshot.SnapshotDescriptionUtils; 049import org.apache.hadoop.hbase.testclassification.MasterTests; 050import org.apache.hadoop.hbase.testclassification.SmallTests; 051import org.apache.hadoop.hbase.util.CommonFSUtils; 052import org.apache.hadoop.hbase.util.HFileArchiveUtil; 053import org.apache.zookeeper.KeeperException; 054import org.junit.jupiter.api.Tag; 055import org.junit.jupiter.api.Test; 056import org.junit.jupiter.api.TestInfo; 057import org.mockito.Mockito; 058 059/** 060 * Test basic snapshot manager functionality 061 */ 062@Tag(MasterTests.TAG) 063@Tag(SmallTests.TAG) 064public class TestSnapshotManager { 065 066 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 067 068 private String currentTestName; 069 070 MasterServices services = Mockito.mock(MasterServices.class); 071 ProcedureCoordinator coordinator = Mockito.mock(ProcedureCoordinator.class); 072 ExecutorService pool = Mockito.mock(ExecutorService.class); 073 MasterFileSystem mfs = Mockito.mock(MasterFileSystem.class); 074 FileSystem fs; 075 { 076 try { 077 fs = UTIL.getTestFileSystem(); 078 } catch (IOException e) { 079 throw new RuntimeException("Couldn't get test filesystem", e); 080 } 081 } 082 083 private SnapshotManager getNewManager() throws IOException, KeeperException { 084 return getNewManager(UTIL.getConfiguration()); 085 } 086 087 private SnapshotManager getNewManager(Configuration conf) throws IOException, KeeperException { 088 return getNewManager(conf, 1); 089 } 090 091 private SnapshotManager getNewManager(Configuration conf, int intervalSeconds) 092 throws IOException, KeeperException { 093 Mockito.reset(services); 094 Mockito.when(services.getConfiguration()).thenReturn(conf); 095 Mockito.when(services.getMasterFileSystem()).thenReturn(mfs); 096 Mockito.when(mfs.getFileSystem()).thenReturn(fs); 097 Mockito.when(mfs.getRootDir()).thenReturn(UTIL.getDataTestDir()); 098 return new SnapshotManager(services, coordinator, pool, intervalSeconds); 099 } 100 101 @Test 102 public void testCleanFinishedHandler(TestInfo testInfo) throws Exception { 103 currentTestName = testInfo.getTestMethod().get().getName(); 104 TableName tableName = TableName.valueOf(currentTestName); 105 Configuration conf = UTIL.getConfiguration(); 106 try { 107 conf.setLong(SnapshotManager.HBASE_SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT_MILLIS, 5 * 1000L); 108 SnapshotManager manager = getNewManager(conf, 1); 109 TakeSnapshotHandler handler = Mockito.mock(TakeSnapshotHandler.class); 110 assertFalse(manager.isTakingSnapshot(tableName), 111 "Manager is in process when there is no current handler"); 112 manager.setSnapshotHandlerForTesting(tableName, handler); 113 Mockito.when(handler.isFinished()).thenReturn(false); 114 assertTrue(manager.isTakingAnySnapshot()); 115 assertTrue(manager.isTakingSnapshot(tableName), 116 "Manager isn't in process when handler is running"); 117 Mockito.when(handler.isFinished()).thenReturn(true); 118 assertFalse(manager.isTakingSnapshot(tableName), 119 "Manager is process when handler isn't running"); 120 assertTrue(manager.isTakingAnySnapshot()); 121 Thread.sleep(6 * 1000); 122 assertFalse(manager.isTakingAnySnapshot()); 123 } finally { 124 conf.unset(SnapshotManager.HBASE_SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT_MILLIS); 125 } 126 } 127 128 @Test 129 public void testInProcess(TestInfo testInfo) throws KeeperException, IOException { 130 currentTestName = testInfo.getTestMethod().get().getName(); 131 final TableName tableName = TableName.valueOf(currentTestName); 132 SnapshotManager manager = getNewManager(); 133 TakeSnapshotHandler handler = Mockito.mock(TakeSnapshotHandler.class); 134 assertFalse(manager.isTakingSnapshot(tableName), 135 "Manager is in process when there is no current handler"); 136 manager.setSnapshotHandlerForTesting(tableName, handler); 137 Mockito.when(handler.isFinished()).thenReturn(false); 138 assertTrue(manager.isTakingSnapshot(tableName), 139 "Manager isn't in process when handler is running"); 140 Mockito.when(handler.isFinished()).thenReturn(true); 141 assertFalse(manager.isTakingSnapshot(tableName), 142 "Manager is process when handler isn't running"); 143 } 144 145 /** 146 * Verify the snapshot support based on the configuration. 147 */ 148 @Test 149 public void testSnapshotSupportConfiguration() throws Exception { 150 // No configuration (no cleaners, not enabled): snapshot feature disabled 151 Configuration conf = new Configuration(); 152 SnapshotManager manager = getNewManager(conf); 153 assertFalse(isSnapshotSupported(manager), "Snapshot should be disabled with no configuration"); 154 155 // force snapshot feature to be enabled 156 conf = new Configuration(); 157 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true); 158 manager = getNewManager(conf); 159 assertTrue(isSnapshotSupported(manager), "Snapshot should be enabled"); 160 161 // force snapshot feature to be disabled 162 conf = new Configuration(); 163 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, false); 164 manager = getNewManager(conf); 165 assertFalse(isSnapshotSupported(manager), "Snapshot should be disabled"); 166 167 // force snapshot feature to be disabled, even if cleaners are present 168 conf = new Configuration(); 169 conf.setStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, SnapshotHFileCleaner.class.getName(), 170 HFileLinkCleaner.class.getName()); 171 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, false); 172 manager = getNewManager(conf); 173 assertFalse(isSnapshotSupported(manager), "Snapshot should be disabled"); 174 175 // cleaners are present, but missing snapshot enabled property 176 conf = new Configuration(); 177 conf.setStrings(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, SnapshotHFileCleaner.class.getName(), 178 HFileLinkCleaner.class.getName()); 179 manager = getNewManager(conf); 180 assertTrue(isSnapshotSupported(manager), 181 "Snapshot should be enabled, because cleaners are present"); 182 183 // Create a "test snapshot" 184 Path rootDir = UTIL.getDataTestDir(); 185 Path testSnapshotDir = 186 SnapshotDescriptionUtils.getCompletedSnapshotDir("testSnapshotSupportConfiguration", rootDir); 187 fs.mkdirs(testSnapshotDir); 188 try { 189 // force snapshot feature to be disabled, but snapshots are present 190 conf = new Configuration(); 191 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, false); 192 manager = getNewManager(conf); 193 fail("Master should not start when snapshot is disabled, but snapshots are present"); 194 } catch (UnsupportedOperationException e) { 195 // expected 196 } finally { 197 fs.delete(testSnapshotDir, true); 198 } 199 } 200 201 @Test 202 public void testDisableSnapshotAndNotDeleteBackReference(TestInfo testInfo) throws Exception { 203 currentTestName = testInfo.getTestMethod().get().getName(); 204 Configuration conf = new Configuration(); 205 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, false); 206 SnapshotManager manager = getNewManager(conf); 207 String cleaners = conf.get(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS); 208 assertTrue(cleaners != null && cleaners.contains(HFileLinkCleaner.class.getName())); 209 Path rootDir = UTIL.getDataTestDir(); 210 CommonFSUtils.setRootDir(conf, rootDir); 211 212 TableName tableName = TableName.valueOf(currentTestName); 213 TableName tableLinkName = TableName.valueOf(currentTestName + "-link"); 214 String hfileName = "1234567890"; 215 String familyName = "cf"; 216 RegionInfo hri = RegionInfoBuilder.newBuilder(tableName).build(); 217 RegionInfo hriLink = RegionInfoBuilder.newBuilder(tableLinkName).build(); 218 Path archiveDir = HFileArchiveUtil.getArchivePath(conf); 219 Path archiveStoreDir = 220 HFileArchiveUtil.getStoreArchivePath(conf, tableName, hri.getEncodedName(), familyName); 221 222 // Create hfile /hbase/table-link/region/cf/getEncodedName.HFILE(conf); 223 Path familyPath = getFamilyDirPath(archiveDir, tableName, hri.getEncodedName(), familyName); 224 Path hfilePath = new Path(familyPath, hfileName); 225 fs.createNewFile(hfilePath); 226 // Create link to hfile 227 HRegionFileSystem regionFS = HRegionFileSystem.create(conf, fs, 228 CommonFSUtils.getTableDir(rootDir, tableLinkName), hriLink); 229 StoreFileTracker sft = StoreFileTrackerFactory.create(conf, true, 230 StoreContext.getBuilder() 231 .withFamilyStoreDirectoryPath(new Path(regionFS.getRegionDir(), familyName)) 232 .withColumnFamilyDescriptor(ColumnFamilyDescriptorBuilder.of(familyName)) 233 .withRegionFileSystem(regionFS).build()); 234 sft.createHFileLink(hri.getTable(), hri.getEncodedName(), hfileName, true); 235 Path linkBackRefDir = HFileLink.getBackReferencesDir(archiveStoreDir, hfileName); 236 assertTrue(fs.exists(linkBackRefDir)); 237 FileStatus[] backRefs = fs.listStatus(linkBackRefDir); 238 assertEquals(1, backRefs.length); 239 Path linkBackRef = backRefs[0].getPath(); 240 241 // Initialize cleaner 242 HFileCleaner cleaner = new HFileCleaner(10000, Mockito.mock(Stoppable.class), conf, fs, 243 archiveDir, DirScanPool.getHFileCleanerScanPool(conf)); 244 // Link backref and HFile cannot be removed 245 cleaner.choreForTesting(); 246 assertTrue(fs.exists(linkBackRef)); 247 assertTrue(fs.exists(hfilePath)); 248 249 fs.rename(CommonFSUtils.getTableDir(rootDir, tableLinkName), 250 CommonFSUtils.getTableDir(archiveDir, tableLinkName)); 251 // Link backref can be removed 252 cleaner.choreForTesting(); 253 assertFalse(fs.exists(linkBackRef), "Link should be deleted"); 254 // HFile can be removed 255 cleaner.choreForTesting(); 256 assertFalse(fs.exists(hfilePath), "HFile should be deleted"); 257 } 258 259 private Path getFamilyDirPath(final Path rootDir, final TableName table, final String region, 260 final String family) { 261 return new Path(new Path(CommonFSUtils.getTableDir(rootDir, table), region), family); 262 } 263 264 private boolean isSnapshotSupported(final SnapshotManager manager) { 265 try { 266 manager.checkSnapshotSupport(); 267 return true; 268 } catch (UnsupportedOperationException e) { 269 return false; 270 } 271 } 272}