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