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.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertTrue; 023import static org.junit.Assert.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.HBaseClassTestRule; 031import org.apache.hadoop.hbase.HBaseTestingUtil; 032import org.apache.hadoop.hbase.Stoppable; 033import org.apache.hadoop.hbase.TableName; 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.snapshot.SnapshotDescriptionUtils; 045import org.apache.hadoop.hbase.testclassification.MasterTests; 046import org.apache.hadoop.hbase.testclassification.SmallTests; 047import org.apache.hadoop.hbase.util.CommonFSUtils; 048import org.apache.hadoop.hbase.util.HFileArchiveUtil; 049import org.apache.zookeeper.KeeperException; 050import org.junit.ClassRule; 051import org.junit.Rule; 052import org.junit.Test; 053import org.junit.experimental.categories.Category; 054import org.junit.rules.TestName; 055import org.mockito.Mockito; 056 057/** 058 * Test basic snapshot manager functionality 059 */ 060@Category({ MasterTests.class, SmallTests.class }) 061public class TestSnapshotManager { 062 063 @ClassRule 064 public static final HBaseClassTestRule CLASS_RULE = 065 HBaseClassTestRule.forClass(TestSnapshotManager.class); 066 067 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 068 069 @Rule 070 public TestName name = new TestName(); 071 072 MasterServices services = Mockito.mock(MasterServices.class); 073 ProcedureCoordinator coordinator = Mockito.mock(ProcedureCoordinator.class); 074 ExecutorService pool = Mockito.mock(ExecutorService.class); 075 MasterFileSystem mfs = Mockito.mock(MasterFileSystem.class); 076 FileSystem fs; 077 { 078 try { 079 fs = UTIL.getTestFileSystem(); 080 } catch (IOException e) { 081 throw new RuntimeException("Couldn't get test filesystem", e); 082 } 083 } 084 085 private SnapshotManager getNewManager() throws IOException, KeeperException { 086 return getNewManager(UTIL.getConfiguration()); 087 } 088 089 private SnapshotManager getNewManager(Configuration conf) throws IOException, KeeperException { 090 return getNewManager(conf, 1); 091 } 092 093 private SnapshotManager getNewManager(Configuration conf, int intervalSeconds) 094 throws IOException, KeeperException { 095 Mockito.reset(services); 096 Mockito.when(services.getConfiguration()).thenReturn(conf); 097 Mockito.when(services.getMasterFileSystem()).thenReturn(mfs); 098 Mockito.when(mfs.getFileSystem()).thenReturn(fs); 099 Mockito.when(mfs.getRootDir()).thenReturn(UTIL.getDataTestDir()); 100 return new SnapshotManager(services, coordinator, pool, intervalSeconds); 101 } 102 103 @Test 104 public void testCleanFinishedHandler() throws Exception { 105 TableName tableName = TableName.valueOf(name.getMethodName()); 106 Configuration conf = UTIL.getConfiguration(); 107 try { 108 conf.setLong(SnapshotManager.HBASE_SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT_MILLIS, 5 * 1000L); 109 SnapshotManager manager = getNewManager(conf, 1); 110 TakeSnapshotHandler handler = Mockito.mock(TakeSnapshotHandler.class); 111 assertFalse("Manager is in process when there is no current handler", 112 manager.isTakingSnapshot(tableName)); 113 manager.setSnapshotHandlerForTesting(tableName, handler); 114 Mockito.when(handler.isFinished()).thenReturn(false); 115 assertTrue(manager.isTakingAnySnapshot()); 116 assertTrue("Manager isn't in process when handler is running", 117 manager.isTakingSnapshot(tableName)); 118 Mockito.when(handler.isFinished()).thenReturn(true); 119 assertFalse("Manager is process when handler isn't running", 120 manager.isTakingSnapshot(tableName)); 121 assertTrue(manager.isTakingAnySnapshot()); 122 Thread.sleep(6 * 1000); 123 assertFalse(manager.isTakingAnySnapshot()); 124 } finally { 125 conf.unset(SnapshotManager.HBASE_SNAPSHOT_SENTINELS_CLEANUP_TIMEOUT_MILLIS); 126 } 127 } 128 129 @Test 130 public void testInProcess() throws KeeperException, IOException { 131 final TableName tableName = TableName.valueOf(name.getMethodName()); 132 SnapshotManager manager = getNewManager(); 133 TakeSnapshotHandler handler = Mockito.mock(TakeSnapshotHandler.class); 134 assertFalse("Manager is in process when there is no current handler", 135 manager.isTakingSnapshot(tableName)); 136 manager.setSnapshotHandlerForTesting(tableName, handler); 137 Mockito.when(handler.isFinished()).thenReturn(false); 138 assertTrue("Manager isn't in process when handler is running", 139 manager.isTakingSnapshot(tableName)); 140 Mockito.when(handler.isFinished()).thenReturn(true); 141 assertFalse("Manager is process when handler isn't running", 142 manager.isTakingSnapshot(tableName)); 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("Snapshot should be disabled with no configuration", isSnapshotSupported(manager)); 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("Snapshot should be enabled", isSnapshotSupported(manager)); 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("Snapshot should be disabled", isSnapshotSupported(manager)); 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("Snapshot should be disabled", isSnapshotSupported(manager)); 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("Snapshot should be enabled, because cleaners are present", 181 isSnapshotSupported(manager)); 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() throws Exception { 203 Configuration conf = new Configuration(); 204 conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, false); 205 SnapshotManager manager = getNewManager(conf); 206 String cleaners = conf.get(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS); 207 assertTrue(cleaners != null && cleaners.contains(HFileLinkCleaner.class.getName())); 208 Path rootDir = UTIL.getDataTestDir(); 209 CommonFSUtils.setRootDir(conf, rootDir); 210 211 TableName tableName = TableName.valueOf(name.getMethodName()); 212 TableName tableLinkName = TableName.valueOf(name.getMethodName() + "-link"); 213 String hfileName = "1234567890"; 214 String familyName = "cf"; 215 RegionInfo hri = RegionInfoBuilder.newBuilder(tableName).build(); 216 RegionInfo hriLink = RegionInfoBuilder.newBuilder(tableLinkName).build(); 217 Path archiveDir = HFileArchiveUtil.getArchivePath(conf); 218 Path archiveStoreDir = 219 HFileArchiveUtil.getStoreArchivePath(conf, tableName, hri.getEncodedName(), familyName); 220 221 // Create hfile /hbase/table-link/region/cf/getEncodedName.HFILE(conf); 222 Path familyPath = getFamilyDirPath(archiveDir, tableName, hri.getEncodedName(), familyName); 223 Path hfilePath = new Path(familyPath, hfileName); 224 fs.createNewFile(hfilePath); 225 // Create link to hfile 226 Path familyLinkPath = 227 getFamilyDirPath(rootDir, tableLinkName, hriLink.getEncodedName(), familyName); 228 HFileLink.create(conf, fs, familyLinkPath, hri, hfileName); 229 Path linkBackRefDir = HFileLink.getBackReferencesDir(archiveStoreDir, hfileName); 230 assertTrue(fs.exists(linkBackRefDir)); 231 FileStatus[] backRefs = fs.listStatus(linkBackRefDir); 232 assertEquals(1, backRefs.length); 233 Path linkBackRef = backRefs[0].getPath(); 234 235 // Initialize cleaner 236 HFileCleaner cleaner = new HFileCleaner(10000, Mockito.mock(Stoppable.class), conf, fs, 237 archiveDir, DirScanPool.getHFileCleanerScanPool(conf)); 238 // Link backref and HFile cannot be removed 239 cleaner.choreForTesting(); 240 assertTrue(fs.exists(linkBackRef)); 241 assertTrue(fs.exists(hfilePath)); 242 243 fs.rename(CommonFSUtils.getTableDir(rootDir, tableLinkName), 244 CommonFSUtils.getTableDir(archiveDir, tableLinkName)); 245 // Link backref can be removed 246 cleaner.choreForTesting(); 247 assertFalse("Link should be deleted", fs.exists(linkBackRef)); 248 // HFile can be removed 249 cleaner.choreForTesting(); 250 assertFalse("HFile should be deleted", fs.exists(hfilePath)); 251 } 252 253 private Path getFamilyDirPath(final Path rootDir, final TableName table, final String region, 254 final String family) { 255 return new Path(new Path(CommonFSUtils.getTableDir(rootDir, table), region), family); 256 } 257 258 private boolean isSnapshotSupported(final SnapshotManager manager) { 259 try { 260 manager.checkSnapshotSupport(); 261 return true; 262 } catch (UnsupportedOperationException e) { 263 return false; 264 } 265 } 266}