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.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertFalse;
022import static org.junit.jupiter.api.Assertions.assertTrue;
023
024import java.io.IOException;
025import java.util.Arrays;
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.TableName;
032import org.apache.hadoop.hbase.client.Admin;
033import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
034import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
035import org.apache.hadoop.hbase.client.CompactionState;
036import org.apache.hadoop.hbase.client.Put;
037import org.apache.hadoop.hbase.client.Result;
038import org.apache.hadoop.hbase.client.ResultScanner;
039import org.apache.hadoop.hbase.client.Table;
040import org.apache.hadoop.hbase.client.TableDescriptor;
041import org.apache.hadoop.hbase.master.cleaner.TimeToLiveHFileCleaner;
042import org.apache.hadoop.hbase.testclassification.MediumTests;
043import org.apache.hadoop.hbase.util.Bytes;
044import org.junit.jupiter.api.AfterEach;
045import org.junit.jupiter.api.BeforeEach;
046import org.junit.jupiter.api.Tag;
047import org.junit.jupiter.api.Test;
048import org.slf4j.Logger;
049import org.slf4j.LoggerFactory;
050
051/**
052 * Mob file cleaner chore test. 1. Creates MOB table 2. Load MOB data and flushes it N times 3. Runs
053 * major MOB compaction (N MOB files go to archive) 4. Verifies that number of MOB files in a mob
054 * directory is N+1 5. Waits for a period of time larger than minimum age to archive 6. Runs Mob
055 * cleaner chore 7 Verifies that number of MOB files in a mob directory is 1.
056 */
057@Tag(MediumTests.TAG)
058public class TestMobFileCleanupUtil {
059  private static final Logger LOG = LoggerFactory.getLogger(TestMobFileCleanupUtil.class);
060
061  private HBaseTestingUtil HTU;
062
063  private final static String famStr = "f1";
064  private final static byte[] fam = Bytes.toBytes(famStr);
065  private final static byte[] qualifier = Bytes.toBytes("q1");
066  private final static long mobLen = 10;
067  private final static byte[] mobVal = Bytes
068    .toBytes("01234567890123456789012345678901234567890123456789012345678901234567890123456789");
069
070  private Configuration conf;
071  private TableDescriptor tableDescriptor;
072  private ColumnFamilyDescriptor familyDescriptor;
073  private Admin admin;
074  private Table table = null;
075  private long minAgeToArchive = 10000;
076
077  public TestMobFileCleanupUtil() {
078  }
079
080  @BeforeEach
081  public void setUp() throws Exception {
082    HTU = new HBaseTestingUtil();
083    conf = HTU.getConfiguration();
084
085    initConf();
086
087    HTU.startMiniCluster();
088    admin = HTU.getAdmin();
089    familyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(fam).setMobEnabled(true)
090      .setMobThreshold(mobLen).setMaxVersions(1).build();
091    tableDescriptor = HTU.createModifyableTableDescriptor("testMobCompactTable")
092      .setColumnFamily(familyDescriptor).build();
093    table = HTU.createTable(tableDescriptor, null);
094  }
095
096  private void initConf() {
097
098    conf.setInt("hfile.format.version", 3);
099    conf.setLong(TimeToLiveHFileCleaner.TTL_CONF_KEY, 0);
100    conf.setInt("hbase.client.retries.number", 100);
101    conf.setInt("hbase.hregion.max.filesize", 200000000);
102    conf.setInt("hbase.hregion.memstore.flush.size", 800000);
103    conf.setInt("hbase.hstore.blockingStoreFiles", 150);
104    conf.setInt("hbase.hstore.compaction.throughput.lower.bound", 52428800);
105    conf.setInt("hbase.hstore.compaction.throughput.higher.bound", 2 * 52428800);
106    // conf.set(MobStoreEngine.DEFAULT_MOB_COMPACTOR_CLASS_KEY,
107    // FaultyMobStoreCompactor.class.getName());
108    // Disable automatic MOB compaction
109    conf.setLong(MobConstants.MOB_COMPACTION_CHORE_PERIOD, 0);
110    // Disable automatic MOB file cleaner chore
111    conf.setLong(MobConstants.MOB_CLEANER_PERIOD, 0);
112    // Set minimum age to archive to 10 sec
113    conf.setLong(MobConstants.MIN_AGE_TO_ARCHIVE_KEY, minAgeToArchive);
114    // Set compacted file discharger interval to a half minAgeToArchive
115    conf.setLong("hbase.hfile.compaction.discharger.interval", minAgeToArchive / 2);
116  }
117
118  private void loadData(int start, int num) {
119    try {
120
121      for (int i = 0; i < num; i++) {
122        Put p = new Put(Bytes.toBytes(start + i));
123        p.addColumn(fam, qualifier, mobVal);
124        table.put(p);
125      }
126      admin.flush(table.getName());
127    } catch (Exception e) {
128      LOG.error("MOB file cleaner chore test FAILED", e);
129      assertTrue(false);
130    }
131  }
132
133  @AfterEach
134  public void tearDown() throws Exception {
135    admin.disableTable(tableDescriptor.getTableName());
136    admin.deleteTable(tableDescriptor.getTableName());
137    HTU.shutdownMiniCluster();
138  }
139
140  @Test
141  public void testMobFileCleanerChore() throws InterruptedException, IOException {
142
143    loadData(0, 10);
144    loadData(10, 10);
145    loadData(20, 10);
146    long num = getNumberOfMobFiles(conf, table.getName(), new String(fam));
147    assertEquals(3, num);
148    // Major compact
149    admin.majorCompact(tableDescriptor.getTableName(), fam);
150    // wait until compaction is complete
151    while (admin.getCompactionState(tableDescriptor.getTableName()) != CompactionState.NONE) {
152      Thread.sleep(100);
153    }
154
155    num = getNumberOfMobFiles(conf, table.getName(), new String(fam));
156    assertEquals(4, num);
157    // We have guarantee, that compcated file discharger will run during this pause
158    // because it has interval less than this wait time
159    LOG.info("Waiting for {}ms", minAgeToArchive + 1000);
160
161    Thread.sleep(minAgeToArchive + 1000);
162    LOG.info("Cleaning up MOB files");
163    // Cleanup
164    MobFileCleanupUtil.cleanupObsoleteMobFiles(conf, table.getName(), admin);
165
166    // verify that nothing have happened
167    num = getNumberOfMobFiles(conf, table.getName(), new String(fam));
168    assertEquals(4, num);
169
170    long scanned = scanTable();
171    assertEquals(30, scanned);
172
173    // add a MOB file to with a name refering to a non-existing region
174    Path extraMOBFile = MobTestUtil.generateMOBFileForRegion(conf, table.getName(),
175      familyDescriptor, "nonExistentRegion");
176    num = getNumberOfMobFiles(conf, table.getName(), new String(fam));
177    assertEquals(5, num);
178
179    LOG.info("Waiting for {}ms", minAgeToArchive + 1000);
180
181    Thread.sleep(minAgeToArchive + 1000);
182    LOG.info("Cleaning up MOB files");
183    MobFileCleanupUtil.cleanupObsoleteMobFiles(conf, table.getName(), admin);
184
185    // check that the extra file got deleted
186    num = getNumberOfMobFiles(conf, table.getName(), new String(fam));
187    assertEquals(4, num);
188
189    FileSystem fs = FileSystem.get(conf);
190    assertFalse(fs.exists(extraMOBFile));
191
192    scanned = scanTable();
193    assertEquals(30, scanned);
194
195  }
196
197  private long getNumberOfMobFiles(Configuration conf, TableName tableName, String family)
198    throws IOException {
199    FileSystem fs = FileSystem.get(conf);
200    Path dir = MobUtils.getMobFamilyPath(conf, tableName, family);
201    FileStatus[] stat = fs.listStatus(dir);
202    for (FileStatus st : stat) {
203      LOG.debug("DDDD MOB Directory content: {} size={}", st.getPath(), st.getLen());
204    }
205    LOG.debug("MOB Directory content total files: {}", stat.length);
206
207    return stat.length;
208  }
209
210  private long scanTable() {
211    try {
212
213      Result result;
214      ResultScanner scanner = table.getScanner(fam);
215      long counter = 0;
216      while ((result = scanner.next()) != null) {
217        assertTrue(Arrays.equals(result.getValue(fam, qualifier), mobVal));
218        counter++;
219      }
220      return counter;
221    } catch (Exception e) {
222      e.printStackTrace();
223      LOG.error("MOB file cleaner chore test FAILED");
224      if (HTU != null) {
225        assertTrue(false);
226      } else {
227        System.exit(-1);
228      }
229    }
230    return 0;
231  }
232}