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.cleaner;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertTrue;
023
024import java.io.IOException;
025import org.apache.hadoop.conf.Configuration;
026import org.apache.hadoop.fs.FileStatus;
027import org.apache.hadoop.fs.FileSystem;
028import org.apache.hadoop.fs.Path;
029import org.apache.hadoop.hbase.ChoreService;
030import org.apache.hadoop.hbase.CoordinatedStateManager;
031import org.apache.hadoop.hbase.HBaseClassTestRule;
032import org.apache.hadoop.hbase.HBaseTestingUtility;
033import org.apache.hadoop.hbase.Server;
034import org.apache.hadoop.hbase.ServerName;
035import org.apache.hadoop.hbase.TableName;
036import org.apache.hadoop.hbase.client.ClusterConnection;
037import org.apache.hadoop.hbase.client.Connection;
038import org.apache.hadoop.hbase.client.RegionInfo;
039import org.apache.hadoop.hbase.client.RegionInfoBuilder;
040import org.apache.hadoop.hbase.io.HFileLink;
041import org.apache.hadoop.hbase.testclassification.MasterTests;
042import org.apache.hadoop.hbase.testclassification.MediumTests;
043import org.apache.hadoop.hbase.util.CommonFSUtils;
044import org.apache.hadoop.hbase.util.HFileArchiveUtil;
045import org.apache.hadoop.hbase.zookeeper.ZKWatcher;
046import org.junit.After;
047import org.junit.AfterClass;
048import org.junit.Before;
049import org.junit.BeforeClass;
050import org.junit.ClassRule;
051import org.junit.Rule;
052import org.junit.Test;
053import org.junit.experimental.categories.Category;
054import org.junit.rules.TestName;
055
056/**
057 * Test the HFileLink Cleaner. HFiles with links cannot be deleted until a link is present.
058 */
059@Category({ MasterTests.class, MediumTests.class })
060public class TestHFileLinkCleaner {
061
062  @ClassRule
063  public static final HBaseClassTestRule CLASS_RULE =
064    HBaseClassTestRule.forClass(TestHFileLinkCleaner.class);
065
066  private Configuration conf;
067  private Path rootDir;
068  private FileSystem fs;
069  private TableName tableName;
070  private TableName tableLinkName;
071  private String hfileName;
072  private String familyName;
073  private RegionInfo hri;
074  private RegionInfo hriLink;
075  private Path archiveDir;
076  private Path archiveStoreDir;
077  private Path familyPath;
078  private Path hfilePath;
079  private Path familyLinkPath;
080  private String hfileLinkName;
081  private Path linkBackRefDir;
082  private Path linkBackRef;
083  private FileStatus[] backRefs;
084  private HFileCleaner cleaner;
085  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
086  private static DirScanPool POOL;
087  private static final long TTL = 1000;
088
089  @Rule
090  public TestName name = new TestName();
091
092  @BeforeClass
093  public static void setUp() {
094    POOL = DirScanPool.getHFileCleanerScanPool(TEST_UTIL.getConfiguration());
095  }
096
097  @AfterClass
098  public static void tearDown() {
099    POOL.shutdownNow();
100  }
101
102  @Before
103  public void configureDirectoriesAndLinks() throws IOException {
104    conf = TEST_UTIL.getConfiguration();
105    CommonFSUtils.setRootDir(conf, TEST_UTIL.getDataTestDir());
106    conf.set(HFileCleaner.MASTER_HFILE_CLEANER_PLUGINS, HFileLinkCleaner.class.getName());
107    rootDir = CommonFSUtils.getRootDir(conf);
108    fs = FileSystem.get(conf);
109
110    tableName = TableName.valueOf(name.getMethodName());
111    tableLinkName = TableName.valueOf(name.getMethodName() + "-link");
112    hfileName = "1234567890";
113    familyName = "cf";
114
115    hri = RegionInfoBuilder.newBuilder(tableName).build();
116    hriLink = RegionInfoBuilder.newBuilder(tableLinkName).build();
117
118    archiveDir = HFileArchiveUtil.getArchivePath(conf);
119    archiveStoreDir =
120      HFileArchiveUtil.getStoreArchivePath(conf, tableName, hri.getEncodedName(), familyName);
121
122    // Create hfile /hbase/table-link/region/cf/getEncodedName.HFILE(conf);
123    familyPath = getFamilyDirPath(archiveDir, tableName, hri.getEncodedName(), familyName);
124    fs.mkdirs(familyPath);
125    hfilePath = new Path(familyPath, hfileName);
126    fs.createNewFile(hfilePath);
127
128    createLink(true);
129
130    // Initialize cleaner
131    conf.setLong(TimeToLiveHFileCleaner.TTL_CONF_KEY, TTL);
132    Server server = new DummyServer();
133    cleaner = new HFileCleaner(1000, server, conf, fs, archiveDir, POOL);
134  }
135
136  private void createLink(boolean createBackReference) throws IOException {
137    // Create link to hfile
138    familyLinkPath = getFamilyDirPath(rootDir, tableLinkName, hriLink.getEncodedName(), familyName);
139    fs.mkdirs(familyLinkPath);
140    hfileLinkName = HFileLink.create(conf, fs, familyLinkPath, hri, hfileName, createBackReference);
141    linkBackRefDir = HFileLink.getBackReferencesDir(archiveStoreDir, hfileName);
142    assertTrue(fs.exists(linkBackRefDir));
143    backRefs = fs.listStatus(linkBackRefDir);
144    assertEquals(1, backRefs.length);
145    linkBackRef = backRefs[0].getPath();
146  }
147
148  @After
149  public void cleanup() throws IOException, InterruptedException {
150    // HFile can be removed
151    Thread.sleep(TTL * 2);
152    cleaner.chore();
153    assertFalse("HFile should be deleted", fs.exists(hfilePath));
154    // Remove everything
155    for (int i = 0; i < 4; ++i) {
156      Thread.sleep(TTL * 2);
157      cleaner.chore();
158    }
159    assertFalse("HFile should be deleted",
160      fs.exists(CommonFSUtils.getTableDir(archiveDir, tableName)));
161    assertFalse("Link should be deleted",
162      fs.exists(CommonFSUtils.getTableDir(archiveDir, tableLinkName)));
163  }
164
165  @Test
166  public void testHFileLinkCleaning() throws Exception {
167    // Link backref cannot be removed
168    cleaner.chore();
169    assertTrue(fs.exists(linkBackRef));
170    assertTrue(fs.exists(hfilePath));
171
172    // Link backref can be removed
173    fs.rename(CommonFSUtils.getTableDir(rootDir, tableLinkName),
174      CommonFSUtils.getTableDir(archiveDir, tableLinkName));
175    cleaner.chore();
176    assertFalse("Link should be deleted", fs.exists(linkBackRef));
177  }
178
179  @Test
180  public void testHFileLinkByRemovingReference() throws Exception {
181    // Link backref cannot be removed
182    cleaner.chore();
183    assertTrue(fs.exists(linkBackRef));
184    assertTrue(fs.exists(hfilePath));
185
186    // simulate after removing the reference in data directory, the Link backref can be removed
187    fs.delete(new Path(familyLinkPath, hfileLinkName), false);
188    cleaner.chore();
189    assertFalse("Link should be deleted", fs.exists(linkBackRef));
190  }
191
192  @Test
193  public void testHFileLinkEmptyBackReferenceDirectory() throws Exception {
194    // simulate and remove the back reference
195    fs.delete(linkBackRef, false);
196    assertTrue("back reference directory still exists", fs.exists(linkBackRefDir));
197    cleaner.chore();
198    assertFalse("back reference directory should be deleted", fs.exists(linkBackRefDir));
199  }
200
201  private static Path getFamilyDirPath(final Path rootDir, final TableName table,
202    final String region, final String family) {
203    return new Path(new Path(CommonFSUtils.getTableDir(rootDir, table), region), family);
204  }
205
206  static class DummyServer implements Server {
207
208    @Override
209    public Configuration getConfiguration() {
210      return TEST_UTIL.getConfiguration();
211    }
212
213    @Override
214    public ZKWatcher getZooKeeper() {
215      try {
216        return new ZKWatcher(getConfiguration(), "dummy server", this);
217      } catch (IOException e) {
218        e.printStackTrace();
219      }
220      return null;
221    }
222
223    @Override
224    public CoordinatedStateManager getCoordinatedStateManager() {
225      return null;
226    }
227
228    @Override
229    public ClusterConnection getConnection() {
230      return null;
231    }
232
233    @Override
234    public ServerName getServerName() {
235      return ServerName.valueOf("regionserver,60020,000000");
236    }
237
238    @Override
239    public void abort(String why, Throwable e) {
240    }
241
242    @Override
243    public boolean isAborted() {
244      return false;
245    }
246
247    @Override
248    public void stop(String why) {
249    }
250
251    @Override
252    public boolean isStopped() {
253      return false;
254    }
255
256    @Override
257    public ChoreService getChoreService() {
258      return null;
259    }
260
261    @Override
262    public ClusterConnection getClusterConnection() {
263      // TODO Auto-generated method stub
264      return null;
265    }
266
267    @Override
268    public FileSystem getFileSystem() {
269      return null;
270    }
271
272    @Override
273    public boolean isStopping() {
274      return false;
275    }
276
277    @Override
278    public Connection createConnection(Configuration conf) throws IOException {
279      return null;
280    }
281  }
282}