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.mob;
019
020import static org.junit.Assert.assertEquals;
021
022import java.util.Random;
023import org.apache.hadoop.fs.FileStatus;
024import org.apache.hadoop.fs.Path;
025import org.apache.hadoop.hbase.HBaseClassTestRule;
026import org.apache.hadoop.hbase.HBaseTestingUtility;
027import org.apache.hadoop.hbase.HColumnDescriptor;
028import org.apache.hadoop.hbase.HTableDescriptor;
029import org.apache.hadoop.hbase.TableName;
030import org.apache.hadoop.hbase.client.Admin;
031import org.apache.hadoop.hbase.client.BufferedMutator;
032import org.apache.hadoop.hbase.client.ConnectionFactory;
033import org.apache.hadoop.hbase.client.Put;
034import org.apache.hadoop.hbase.testclassification.MediumTests;
035import org.apache.hadoop.hbase.util.Bytes;
036import org.apache.hadoop.util.ToolRunner;
037import org.junit.After;
038import org.junit.AfterClass;
039import org.junit.Before;
040import org.junit.BeforeClass;
041import org.junit.ClassRule;
042import org.junit.Test;
043import org.junit.experimental.categories.Category;
044
045@Category(MediumTests.class)
046public class TestExpiredMobFileCleaner {
047
048  @ClassRule
049  public static final HBaseClassTestRule CLASS_RULE =
050      HBaseClassTestRule.forClass(TestExpiredMobFileCleaner.class);
051
052  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
053  private final static TableName tableName = TableName.valueOf("TestExpiredMobFileCleaner");
054  private final static String family = "family";
055  private final static byte[] row1 = Bytes.toBytes("row1");
056  private final static byte[] row2 = Bytes.toBytes("row2");
057  private final static byte[] qf = Bytes.toBytes("qf");
058
059  private static BufferedMutator table;
060  private static Admin admin;
061
062  @BeforeClass
063  public static void setUpBeforeClass() throws Exception {
064    TEST_UTIL.getConfiguration().setInt("hfile.format.version", 3);
065  }
066
067  @AfterClass
068  public static void tearDownAfterClass() throws Exception {
069
070  }
071
072  @Before
073  public void setUp() throws Exception {
074    TEST_UTIL.startMiniCluster(1);
075  }
076
077  @After
078  public void tearDown() throws Exception {
079    admin.disableTable(tableName);
080    admin.deleteTable(tableName);
081    admin.close();
082    TEST_UTIL.shutdownMiniCluster();
083    TEST_UTIL.getTestFileSystem().delete(TEST_UTIL.getDataTestDir(), true);
084  }
085
086  private void init() throws Exception {
087    HTableDescriptor desc = new HTableDescriptor(tableName);
088    HColumnDescriptor hcd = new HColumnDescriptor(family);
089    hcd.setMobEnabled(true);
090    hcd.setMobThreshold(3L);
091    hcd.setMaxVersions(4);
092    desc.addFamily(hcd);
093
094    admin = TEST_UTIL.getAdmin();
095    admin.createTable(desc);
096    table = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration())
097            .getBufferedMutator(tableName);
098  }
099
100  private void modifyColumnExpiryDays(int expireDays) throws Exception {
101    HColumnDescriptor hcd = new HColumnDescriptor(family);
102    hcd.setMobEnabled(true);
103    hcd.setMobThreshold(3L);
104    // change ttl as expire days to make some row expired
105    int timeToLive = expireDays * secondsOfDay();
106    hcd.setTimeToLive(timeToLive);
107
108    admin.modifyColumnFamily(tableName, hcd);
109  }
110
111  private void putKVAndFlush(BufferedMutator table, byte[] row, byte[] value, long ts)
112      throws Exception {
113
114    Put put = new Put(row, ts);
115    put.addColumn(Bytes.toBytes(family), qf, value);
116    table.mutate(put);
117
118    table.flush();
119    admin.flush(tableName);
120  }
121
122  /**
123   * Creates a 3 day old hfile and an 1 day old hfile then sets expiry to 2 days.
124   * Verifies that the 3 day old hfile is removed but the 1 day one is still present
125   * after the expiry based cleaner is run.
126   */
127  @Test
128  public void testCleaner() throws Exception {
129    init();
130
131    Path mobDirPath = MobUtils.getMobFamilyPath(TEST_UTIL.getConfiguration(), tableName, family);
132
133    byte[] dummyData = makeDummyData(600);
134    long ts = System.currentTimeMillis() - 3 * secondsOfDay() * 1000; // 3 days before
135    putKVAndFlush(table, row1, dummyData, ts);
136    FileStatus[] firstFiles = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath);
137    //the first mob file
138    assertEquals("Before cleanup without delay 1", 1, firstFiles.length);
139    String firstFile = firstFiles[0].getPath().getName();
140
141    ts = System.currentTimeMillis() - 1 * secondsOfDay() * 1000; // 1 day before
142    putKVAndFlush(table, row2, dummyData, ts);
143    FileStatus[] secondFiles = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath);
144    //now there are 2 mob files
145    assertEquals("Before cleanup without delay 2", 2, secondFiles.length);
146    String f1 = secondFiles[0].getPath().getName();
147    String f2 = secondFiles[1].getPath().getName();
148    String secondFile = f1.equals(firstFile) ? f2 : f1;
149
150    modifyColumnExpiryDays(2); // ttl = 2, make the first row expired
151
152    //run the cleaner
153    String[] args = new String[2];
154    args[0] = tableName.getNameAsString();
155    args[1] = family;
156    ToolRunner.run(TEST_UTIL.getConfiguration(), new ExpiredMobFileCleaner(), args);
157
158    FileStatus[] filesAfterClean = TEST_UTIL.getTestFileSystem().listStatus(mobDirPath);
159    String lastFile = filesAfterClean[0].getPath().getName();
160    //the first mob fie is removed
161    assertEquals("After cleanup without delay 1", 1, filesAfterClean.length);
162    assertEquals("After cleanup without delay 2", secondFile, lastFile);
163  }
164
165  private int secondsOfDay() {
166    return 24 * 3600;
167  }
168
169  private byte[] makeDummyData(int size) {
170    byte [] dummyData = new byte[size];
171    new Random().nextBytes(dummyData);
172    return dummyData;
173  }
174}