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}