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.HBaseTestingUtility;
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 HBaseTestingUtility UTIL = new HBaseTestingUtility();
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(UTIL.getConfiguration()));
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}