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.janitor;
019
020import static org.apache.hadoop.hbase.util.HFileArchiveTestingUtil.assertArchiveEqualToOriginal;
021import static org.junit.Assert.assertEquals;
022import static org.junit.Assert.assertFalse;
023import static org.junit.Assert.assertTrue;
024import static org.mockito.ArgumentMatchers.any;
025import static org.mockito.Mockito.doAnswer;
026import static org.mockito.Mockito.doReturn;
027import static org.mockito.Mockito.doThrow;
028import static org.mockito.Mockito.spy;
029import static org.mockito.Mockito.when;
030
031import java.io.IOException;
032import java.util.ArrayList;
033import java.util.List;
034import java.util.Map;
035import java.util.Objects;
036import java.util.SortedMap;
037import java.util.TreeMap;
038import java.util.concurrent.atomic.AtomicBoolean;
039import org.apache.hadoop.fs.FSDataOutputStream;
040import org.apache.hadoop.fs.FileStatus;
041import org.apache.hadoop.fs.FileSystem;
042import org.apache.hadoop.fs.Path;
043import org.apache.hadoop.hbase.HBaseClassTestRule;
044import org.apache.hadoop.hbase.HBaseTestingUtil;
045import org.apache.hadoop.hbase.HConstants;
046import org.apache.hadoop.hbase.MetaMockingUtil;
047import org.apache.hadoop.hbase.TableName;
048import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
049import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder;
050import org.apache.hadoop.hbase.client.RegionInfo;
051import org.apache.hadoop.hbase.client.RegionInfoBuilder;
052import org.apache.hadoop.hbase.client.Result;
053import org.apache.hadoop.hbase.client.TableDescriptor;
054import org.apache.hadoop.hbase.client.TableDescriptorBuilder;
055import org.apache.hadoop.hbase.io.Reference;
056import org.apache.hadoop.hbase.master.MasterFileSystem;
057import org.apache.hadoop.hbase.master.MasterServices;
058import org.apache.hadoop.hbase.master.assignment.MockMasterServices;
059import org.apache.hadoop.hbase.master.janitor.CatalogJanitor.SplitParentFirstComparator;
060import org.apache.hadoop.hbase.procedure2.ProcedureTestingUtility;
061import org.apache.hadoop.hbase.regionserver.ChunkCreator;
062import org.apache.hadoop.hbase.regionserver.HRegionFileSystem;
063import org.apache.hadoop.hbase.regionserver.MemStoreLAB;
064import org.apache.hadoop.hbase.regionserver.StoreContext;
065import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTracker;
066import org.apache.hadoop.hbase.regionserver.storefiletracker.StoreFileTrackerFactory;
067import org.apache.hadoop.hbase.testclassification.MasterTests;
068import org.apache.hadoop.hbase.testclassification.MediumTests;
069import org.apache.hadoop.hbase.util.Bytes;
070import org.apache.hadoop.hbase.util.CommonFSUtils;
071import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
072import org.apache.hadoop.hbase.util.HFileArchiveUtil;
073import org.junit.After;
074import org.junit.Before;
075import org.junit.BeforeClass;
076import org.junit.ClassRule;
077import org.junit.Rule;
078import org.junit.Test;
079import org.junit.experimental.categories.Category;
080import org.junit.rules.TestName;
081import org.slf4j.Logger;
082import org.slf4j.LoggerFactory;
083
084@Category({ MasterTests.class, MediumTests.class })
085public class TestCatalogJanitor {
086
087  @ClassRule
088  public static final HBaseClassTestRule CLASS_RULE =
089    HBaseClassTestRule.forClass(TestCatalogJanitor.class);
090
091  private static final Logger LOG = LoggerFactory.getLogger(TestCatalogJanitor.class);
092
093  private static final HBaseTestingUtil HTU = new HBaseTestingUtil();
094
095  @Rule
096  public final TestName name = new TestName();
097
098  private MockMasterServices masterServices;
099  private CatalogJanitor janitor;
100
101  @BeforeClass
102  public static void beforeClass() throws Exception {
103    ChunkCreator.initialize(MemStoreLAB.CHUNK_SIZE_DEFAULT, false, 0, 0, 0, null,
104      MemStoreLAB.INDEX_CHUNK_SIZE_PERCENTAGE_DEFAULT);
105  }
106
107  @Before
108  public void setup() throws Exception {
109    setRootDirAndCleanIt(HTU, this.name.getMethodName());
110    this.masterServices = new MockMasterServices(HTU.getConfiguration());
111    this.masterServices.start(10, null);
112    this.janitor = new CatalogJanitor(masterServices);
113  }
114
115  @After
116  public void teardown() {
117    this.janitor.shutdown(true);
118    this.masterServices.stop("DONE");
119  }
120
121  private RegionInfo createRegionInfo(TableName tableName, byte[] startKey, byte[] endKey) {
122    return createRegionInfo(tableName, startKey, endKey, false);
123  }
124
125  private RegionInfo createRegionInfo(TableName tableName, byte[] startKey, byte[] endKey,
126    boolean split) {
127    return RegionInfoBuilder.newBuilder(tableName).setStartKey(startKey).setEndKey(endKey)
128      .setSplit(split).build();
129  }
130
131  @Test
132  public void testCleanMerge() throws IOException {
133    TableDescriptor td = createTableDescriptorForCurrentMethod();
134    // Create regions.
135    RegionInfo merged =
136      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee"));
137    RegionInfo parenta =
138      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc"));
139    RegionInfo parentb =
140      createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee"));
141
142    List<RegionInfo> parents = new ArrayList<>();
143    parents.add(parenta);
144    parents.add(parentb);
145
146    Path rootdir = this.masterServices.getMasterFileSystem().getRootDir();
147    Path tabledir = CommonFSUtils.getTableDir(rootdir, td.getTableName());
148    Path storedir =
149      HRegionFileSystem.getStoreHomedir(tabledir, merged, td.getColumnFamilies()[0].getName());
150
151    Path parentaRef =
152      createMergeReferenceFile(storedir, tabledir, td.getColumnFamilies()[0], merged, parenta);
153    Path parentbRef =
154      createMergeReferenceFile(storedir, tabledir, td.getColumnFamilies()[0], merged, parentb);
155
156    // references exist, should not clean
157    assertFalse(CatalogJanitor.cleanMergeRegion(masterServices, merged, parents));
158
159    masterServices.getMasterFileSystem().getFileSystem().delete(parentaRef, false);
160
161    // one reference still exists, should not clean
162    assertFalse(CatalogJanitor.cleanMergeRegion(masterServices, merged, parents));
163
164    masterServices.getMasterFileSystem().getFileSystem().delete(parentbRef, false);
165
166    // all references removed, should clean
167    assertTrue(CatalogJanitor.cleanMergeRegion(masterServices, merged, parents));
168  }
169
170  @Test
171  public void testDontCleanMergeIfFileSystemException() throws IOException {
172    TableDescriptor td = createTableDescriptorForCurrentMethod();
173    // Create regions.
174    RegionInfo merged =
175      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee"));
176    RegionInfo parenta =
177      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc"));
178    RegionInfo parentb =
179      createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee"));
180
181    List<RegionInfo> parents = new ArrayList<>();
182    parents.add(parenta);
183    parents.add(parentb);
184
185    Path rootdir = this.masterServices.getMasterFileSystem().getRootDir();
186    Path tabledir = CommonFSUtils.getTableDir(rootdir, td.getTableName());
187    Path storedir =
188      HRegionFileSystem.getStoreHomedir(tabledir, merged, td.getColumnFamilies()[0].getName());
189    createMergeReferenceFile(storedir, tabledir, td.getColumnFamilies()[0], merged, parenta);
190
191    MasterServices mockedMasterServices = spy(masterServices);
192    MasterFileSystem mockedMasterFileSystem = spy(masterServices.getMasterFileSystem());
193    FileSystem mockedFileSystem = spy(masterServices.getMasterFileSystem().getFileSystem());
194
195    when(mockedMasterServices.getMasterFileSystem()).thenReturn(mockedMasterFileSystem);
196    when(mockedMasterFileSystem.getFileSystem()).thenReturn(mockedFileSystem);
197
198    // throw on the first exists check
199    doThrow(new IOException("Some exception")).when(mockedFileSystem).exists(any());
200
201    assertFalse(CatalogJanitor.cleanMergeRegion(mockedMasterServices, merged, parents));
202
203    // throw on the second exists check (within HRegionfileSystem)
204    AtomicBoolean returned = new AtomicBoolean(false);
205    doAnswer(invocationOnMock -> {
206      if (!returned.get()) {
207        returned.set(true);
208        return masterServices.getMasterFileSystem().getFileSystem()
209          .exists(invocationOnMock.getArgument(0));
210      }
211      throw new IOException("Some exception");
212    }).when(mockedFileSystem).exists(any());
213
214    assertFalse(CatalogJanitor.cleanMergeRegion(mockedMasterServices, merged, parents));
215  }
216
217  private Path createMergeReferenceFile(Path storeDir, Path tableDir,
218    ColumnFamilyDescriptor columnFamilyDescriptor, RegionInfo mergedRegion, RegionInfo parentRegion)
219    throws IOException {
220    Reference ref = Reference.createTopReference(mergedRegion.getStartKey());
221    long now = EnvironmentEdgeManager.currentTime();
222    // Reference name has this format: StoreFile#REF_NAME_PARSER
223    Path p = new Path(storeDir, Long.toString(now) + "." + parentRegion.getEncodedName());
224    FileSystem fs = this.masterServices.getMasterFileSystem().getFileSystem();
225    HRegionFileSystem mergedRegionFS =
226      HRegionFileSystem.create(fs.getConf(), fs, tableDir, mergedRegion);
227    StoreContext storeContext =
228      StoreContext.getBuilder().withColumnFamilyDescriptor(columnFamilyDescriptor)
229        .withFamilyStoreDirectoryPath(storeDir).withRegionFileSystem(mergedRegionFS).build();
230    StoreFileTracker sft = StoreFileTrackerFactory.create(fs.getConf(), false, storeContext);
231    sft.createReference(ref, p);
232    return p;
233  }
234
235  /**
236   * Test clearing a split parent.
237   */
238  @Test
239  public void testCleanParent() throws IOException, InterruptedException {
240    TableDescriptor td = createTableDescriptorForCurrentMethod();
241    // Create regions.
242    RegionInfo parent =
243      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee"));
244    RegionInfo splita =
245      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc"));
246    RegionInfo splitb =
247      createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee"));
248    // Test that when both daughter regions are in place, that we do not remove the parent.
249    Result r = createResult(parent, splita, splitb);
250    // Add a reference under splitA directory so we don't clear out the parent.
251    Path rootdir = this.masterServices.getMasterFileSystem().getRootDir();
252    Path tabledir = CommonFSUtils.getTableDir(rootdir, td.getTableName());
253    Path parentdir = new Path(tabledir, parent.getEncodedName());
254    Path storedir =
255      HRegionFileSystem.getStoreHomedir(tabledir, splita, td.getColumnFamilies()[0].getName());
256    Reference ref = Reference.createTopReference(Bytes.toBytes("ccc"));
257    long now = EnvironmentEdgeManager.currentTime();
258    // Reference name has this format: StoreFile#REF_NAME_PARSER
259    Path p = new Path(storedir, Long.toString(now) + "." + parent.getEncodedName());
260    FileSystem fs = this.masterServices.getMasterFileSystem().getFileSystem();
261    HRegionFileSystem regionFS =
262      HRegionFileSystem.create(this.masterServices.getConfiguration(), fs, tabledir, splita);
263    StoreContext storeContext =
264      StoreContext.getBuilder().withColumnFamilyDescriptor(td.getColumnFamilies()[0])
265        .withFamilyStoreDirectoryPath(storedir).withRegionFileSystem(regionFS).build();
266    StoreFileTracker sft =
267      StoreFileTrackerFactory.create(this.masterServices.getConfiguration(), false, storeContext);
268    sft.createReference(ref, p);
269    assertTrue(fs.exists(p));
270    LOG.info("Created reference " + p);
271    // Add a parentdir for kicks so can check it gets removed by the catalogjanitor.
272    fs.mkdirs(parentdir);
273    assertFalse(CatalogJanitor.cleanParent(masterServices, parent, r));
274    ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor());
275    assertTrue(fs.exists(parentdir));
276    // Remove the reference file and try again.
277    assertTrue(fs.delete(p, true));
278    assertTrue(CatalogJanitor.cleanParent(masterServices, parent, r));
279    // Parent cleanup is run async as a procedure. Make sure parentdir is removed.
280    ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor());
281    assertTrue(!fs.exists(parentdir));
282  }
283
284  /**
285   * Make sure parent gets cleaned up even if daughter is cleaned up before it.
286   */
287  @Test
288  public void testParentCleanedEvenIfDaughterGoneFirst() throws IOException, InterruptedException {
289    parentWithSpecifiedEndKeyCleanedEvenIfDaughterGoneFirst(this.name.getMethodName(),
290      Bytes.toBytes("eee"));
291  }
292
293  /**
294   * Make sure last parent with empty end key gets cleaned up even if daughter is cleaned up before
295   * it.
296   */
297  @Test
298  public void testLastParentCleanedEvenIfDaughterGoneFirst()
299    throws IOException, InterruptedException {
300    parentWithSpecifiedEndKeyCleanedEvenIfDaughterGoneFirst(this.name.getMethodName(), new byte[0]);
301  }
302
303  /**
304   * @return A TableDescriptor with a tableName of current method name and a column family that is
305   *         MockMasterServices.DEFAULT_COLUMN_FAMILY_NAME)
306   */
307  private TableDescriptor createTableDescriptorForCurrentMethod() {
308    ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder
309      .newBuilder(Bytes.toBytes(MockMasterServices.DEFAULT_COLUMN_FAMILY_NAME)).build();
310    return TableDescriptorBuilder.newBuilder(TableName.valueOf(this.name.getMethodName()))
311      .setColumnFamily(columnFamilyDescriptor).build();
312  }
313
314  /**
315   * Make sure parent with specified end key gets cleaned up even if daughter is cleaned up before
316   * it.
317   * @param rootDir    the test case name, used as the HBase testing utility root
318   * @param lastEndKey the end key of the split parent
319   */
320  private void parentWithSpecifiedEndKeyCleanedEvenIfDaughterGoneFirst(final String rootDir,
321    final byte[] lastEndKey) throws IOException, InterruptedException {
322    TableDescriptor td = createTableDescriptorForCurrentMethod();
323    // Create regions: aaa->{lastEndKey}, aaa->ccc, aaa->bbb, bbb->ccc, etc.
324    RegionInfo parent = createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), lastEndKey);
325    // Sleep a second else the encoded name on these regions comes out
326    // same for all with same start key and made in same second.
327    Thread.sleep(1001);
328
329    // Daughter a
330    RegionInfo splita =
331      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc"));
332    Thread.sleep(1001);
333    // Make daughters of daughter a; splitaa and splitab.
334    RegionInfo splitaa =
335      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("bbb"));
336    RegionInfo splitab =
337      createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), Bytes.toBytes("ccc"));
338
339    // Daughter b
340    RegionInfo splitb = createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), lastEndKey);
341    Thread.sleep(1001);
342    // Make Daughters of daughterb; splitba and splitbb.
343    RegionInfo splitba =
344      createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("ddd"));
345    RegionInfo splitbb = createRegionInfo(td.getTableName(), Bytes.toBytes("ddd"), lastEndKey);
346
347    // First test that our Comparator works right up in CatalogJanitor.
348    SortedMap<RegionInfo, Result> regions =
349      new TreeMap<>(new CatalogJanitor.SplitParentFirstComparator());
350    // Now make sure that this regions map sorts as we expect it to.
351    regions.put(parent, createResult(parent, splita, splitb));
352    regions.put(splitb, createResult(splitb, splitba, splitbb));
353    regions.put(splita, createResult(splita, splitaa, splitab));
354    // Assert its properly sorted.
355    int index = 0;
356    for (Map.Entry<RegionInfo, Result> e : regions.entrySet()) {
357      if (index == 0) {
358        assertTrue(e.getKey().getEncodedName().equals(parent.getEncodedName()));
359      } else if (index == 1) {
360        assertTrue(e.getKey().getEncodedName().equals(splita.getEncodedName()));
361      } else if (index == 2) {
362        assertTrue(e.getKey().getEncodedName().equals(splitb.getEncodedName()));
363      }
364      index++;
365    }
366
367    // Now play around with the cleanParent function. Create a ref from splita up to the parent.
368    Path splitaRef =
369      createReferences(this.masterServices, td, parent, splita, Bytes.toBytes("ccc"), false);
370    // Make sure actual super parent sticks around because splita has a ref.
371    assertFalse(CatalogJanitor.cleanParent(masterServices, parent, regions.get(parent)));
372
373    // splitba, and split bb, do not have dirs in fs. That means that if
374    // we test splitb, it should get cleaned up.
375    assertTrue(CatalogJanitor.cleanParent(masterServices, splitb, regions.get(splitb)));
376
377    // Now remove ref from splita to parent... so parent can be let go and so
378    // the daughter splita can be split (can't split if still references).
379    // BUT make the timing such that the daughter gets cleaned up before we
380    // can get a chance to let go of the parent.
381    FileSystem fs = FileSystem.get(HTU.getConfiguration());
382    assertTrue(fs.delete(splitaRef, true));
383    // Create the refs from daughters of splita.
384    Path splitaaRef =
385      createReferences(this.masterServices, td, splita, splitaa, Bytes.toBytes("bbb"), false);
386    Path splitabRef =
387      createReferences(this.masterServices, td, splita, splitab, Bytes.toBytes("bbb"), true);
388
389    // Test splita. It should stick around because references from splitab, etc.
390    assertFalse(CatalogJanitor.cleanParent(masterServices, splita, regions.get(splita)));
391
392    // Now clean up parent daughter first. Remove references from its daughters.
393    assertTrue(fs.delete(splitaaRef, true));
394    assertTrue(fs.delete(splitabRef, true));
395    assertTrue(CatalogJanitor.cleanParent(masterServices, splita, regions.get(splita)));
396
397    // Super parent should get cleaned up now both splita and splitb are gone.
398    assertTrue(CatalogJanitor.cleanParent(masterServices, parent, regions.get(parent)));
399  }
400
401  /**
402   * CatalogJanitor.scan() should not clean parent regions if their own parents are still
403   * referencing them. This ensures that grandparent regions do not point to deleted parent regions.
404   */
405  @Test
406  public void testScanDoesNotCleanRegionsWithExistingParents() throws Exception {
407    TableDescriptor td = createTableDescriptorForCurrentMethod();
408    // Create regions: aaa->{lastEndKey}, aaa->ccc, aaa->bbb, bbb->ccc, etc.
409
410    // Parent
411    RegionInfo parent =
412      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), HConstants.EMPTY_BYTE_ARRAY, true);
413    // Sleep a second else the encoded name on these regions comes out
414    // same for all with same start key and made in same second.
415    Thread.sleep(1001);
416
417    // Daughter a
418    RegionInfo splita =
419      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc"), true);
420    Thread.sleep(1001);
421
422    // Make daughters of daughter a; splitaa and splitab.
423    RegionInfo splitaa =
424      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("bbb"), false);
425    RegionInfo splitab =
426      createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), Bytes.toBytes("ccc"), false);
427
428    // Daughter b
429    RegionInfo splitb =
430      createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), HConstants.EMPTY_BYTE_ARRAY);
431    Thread.sleep(1001);
432
433    // Parent has daughters splita and splitb. Splita has daughters splitaa and splitab.
434    final Map<RegionInfo, Result> splitParents = new TreeMap<>(new SplitParentFirstComparator());
435    splitParents.put(parent, createResult(parent, splita, splitb));
436    // simulate that splita goes offline when it is split
437    splita = RegionInfoBuilder.newBuilder(splita).setOffline(true).build();
438    splitParents.put(splita, createResult(splita, splitaa, splitab));
439
440    final Map<RegionInfo, Result> mergedRegions = new TreeMap<>();
441    CatalogJanitor spy = spy(this.janitor);
442
443    CatalogJanitorReport report = new CatalogJanitorReport();
444    report.count = 10;
445    report.mergedRegions.putAll(mergedRegions);
446    report.splitParents.putAll(splitParents);
447
448    doReturn(report).when(spy).scanForReport();
449
450    // Create ref from splita to parent
451    LOG.info("parent=" + parent.getShortNameToLog() + ", splita=" + splita.getShortNameToLog());
452    Path splitaRef =
453      createReferences(this.masterServices, td, parent, splita, Bytes.toBytes("ccc"), false);
454    LOG.info("Created reference " + splitaRef);
455
456    // Parent and splita should not be removed because a reference from splita to parent.
457    int gcs = spy.scan();
458    assertEquals(0, gcs);
459
460    // Now delete the ref
461    FileSystem fs = FileSystem.get(HTU.getConfiguration());
462    assertTrue(fs.delete(splitaRef, true));
463
464    // now, both parent, and splita can be deleted
465    gcs = spy.scan();
466    assertEquals(2, gcs);
467  }
468
469  /**
470   * Test that we correctly archive all the storefiles when a region is deleted
471   */
472  @Test
473  public void testSplitParentFirstComparator() {
474    SplitParentFirstComparator comp = new SplitParentFirstComparator();
475    TableDescriptor td = createTableDescriptorForCurrentMethod();
476
477    /*
478     * Region splits: rootRegion --- firstRegion --- firstRegiona | |- firstRegionb | |- lastRegion
479     * --- lastRegiona --- lastRegionaa | |- lastRegionab |- lastRegionb rootRegion : [] - []
480     * firstRegion : [] - bbb lastRegion : bbb - [] firstRegiona : [] - aaa firstRegionb : aaa - bbb
481     * lastRegiona : bbb - ddd lastRegionb : ddd - []
482     */
483
484    // root region
485    RegionInfo rootRegion = createRegionInfo(td.getTableName(), HConstants.EMPTY_START_ROW,
486      HConstants.EMPTY_END_ROW, true);
487    RegionInfo firstRegion =
488      createRegionInfo(td.getTableName(), HConstants.EMPTY_START_ROW, Bytes.toBytes("bbb"), true);
489    RegionInfo lastRegion =
490      createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), HConstants.EMPTY_END_ROW, true);
491
492    assertTrue(comp.compare(rootRegion, rootRegion) == 0);
493    assertTrue(comp.compare(firstRegion, firstRegion) == 0);
494    assertTrue(comp.compare(lastRegion, lastRegion) == 0);
495    assertTrue(comp.compare(rootRegion, firstRegion) < 0);
496    assertTrue(comp.compare(rootRegion, lastRegion) < 0);
497    assertTrue(comp.compare(firstRegion, lastRegion) < 0);
498
499    // first region split into a, b
500    RegionInfo firstRegiona =
501      createRegionInfo(td.getTableName(), HConstants.EMPTY_START_ROW, Bytes.toBytes("aaa"), true);
502    RegionInfo firstRegionb =
503      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("bbb"), true);
504    // last region split into a, b
505    RegionInfo lastRegiona =
506      createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), Bytes.toBytes("ddd"), true);
507    RegionInfo lastRegionb =
508      createRegionInfo(td.getTableName(), Bytes.toBytes("ddd"), HConstants.EMPTY_END_ROW, true);
509
510    assertTrue(comp.compare(firstRegiona, firstRegiona) == 0);
511    assertTrue(comp.compare(firstRegionb, firstRegionb) == 0);
512    assertTrue(comp.compare(rootRegion, firstRegiona) < 0);
513    assertTrue(comp.compare(rootRegion, firstRegionb) < 0);
514    assertTrue(comp.compare(firstRegion, firstRegiona) < 0);
515    assertTrue(comp.compare(firstRegion, firstRegionb) < 0);
516    assertTrue(comp.compare(firstRegiona, firstRegionb) < 0);
517
518    assertTrue(comp.compare(lastRegiona, lastRegiona) == 0);
519    assertTrue(comp.compare(lastRegionb, lastRegionb) == 0);
520    assertTrue(comp.compare(rootRegion, lastRegiona) < 0);
521    assertTrue(comp.compare(rootRegion, lastRegionb) < 0);
522    assertTrue(comp.compare(lastRegion, lastRegiona) < 0);
523    assertTrue(comp.compare(lastRegion, lastRegionb) < 0);
524    assertTrue(comp.compare(lastRegiona, lastRegionb) < 0);
525
526    assertTrue(comp.compare(firstRegiona, lastRegiona) < 0);
527    assertTrue(comp.compare(firstRegiona, lastRegionb) < 0);
528    assertTrue(comp.compare(firstRegionb, lastRegiona) < 0);
529    assertTrue(comp.compare(firstRegionb, lastRegionb) < 0);
530
531    RegionInfo lastRegionaa =
532      createRegionInfo(td.getTableName(), Bytes.toBytes("bbb"), Bytes.toBytes("ccc"), false);
533    RegionInfo lastRegionab =
534      createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("ddd"), false);
535
536    assertTrue(comp.compare(lastRegiona, lastRegionaa) < 0);
537    assertTrue(comp.compare(lastRegiona, lastRegionab) < 0);
538    assertTrue(comp.compare(lastRegionaa, lastRegionab) < 0);
539  }
540
541  @Test
542  public void testArchiveOldRegion() throws Exception {
543    // Create regions.
544    TableDescriptor td = createTableDescriptorForCurrentMethod();
545    RegionInfo parent =
546      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee"));
547    RegionInfo splita =
548      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc"));
549    RegionInfo splitb =
550      createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee"));
551
552    // Test that when both daughter regions are in place, that we do not
553    // remove the parent.
554    Result parentMetaRow = createResult(parent, splita, splitb);
555    FileSystem fs = FileSystem.get(HTU.getConfiguration());
556    Path rootdir = this.masterServices.getMasterFileSystem().getRootDir();
557    // have to set the root directory since we use it in HFileDisposer to figure out to get to the
558    // archive directory. Otherwise, it just seems to pick the first root directory it can find (so
559    // the single test passes, but when the full suite is run, things get borked).
560    CommonFSUtils.setRootDir(fs.getConf(), rootdir);
561    Path tabledir = CommonFSUtils.getTableDir(rootdir, td.getTableName());
562    Path storedir =
563      HRegionFileSystem.getStoreHomedir(tabledir, parent, td.getColumnFamilies()[0].getName());
564    Path storeArchive = HFileArchiveUtil.getStoreArchivePath(this.masterServices.getConfiguration(),
565      parent, tabledir, td.getColumnFamilies()[0].getName());
566    LOG.debug("Table dir:" + tabledir);
567    LOG.debug("Store dir:" + storedir);
568    LOG.debug("Store archive dir:" + storeArchive);
569
570    // add a couple of store files that we can check for
571    FileStatus[] mockFiles = addMockStoreFiles(2, this.masterServices, storedir);
572    // get the current store files for comparison
573    FileStatus[] storeFiles = fs.listStatus(storedir);
574    int index = 0;
575    for (FileStatus file : storeFiles) {
576      LOG.debug("Have store file:" + file.getPath());
577      assertEquals("Got unexpected store file", mockFiles[index].getPath(),
578        storeFiles[index].getPath());
579      index++;
580    }
581
582    // do the cleaning of the parent
583    assertTrue(CatalogJanitor.cleanParent(masterServices, parent, parentMetaRow));
584    Path parentDir = new Path(tabledir, parent.getEncodedName());
585    // Cleanup procedure runs async. Wait till it done.
586    ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor());
587    assertTrue(!fs.exists(parentDir));
588    LOG.debug("Finished cleanup of parent region");
589
590    // and now check to make sure that the files have actually been archived
591    FileStatus[] archivedStoreFiles = fs.listStatus(storeArchive);
592    logFiles("archived files", storeFiles);
593    logFiles("archived files", archivedStoreFiles);
594
595    assertArchiveEqualToOriginal(storeFiles, archivedStoreFiles, fs);
596
597    // cleanup
598    CommonFSUtils.delete(fs, rootdir, true);
599  }
600
601  /**
602   * @param description description of the files for logging
603   * @param storeFiles  the status of the files to log
604   */
605  private void logFiles(String description, FileStatus[] storeFiles) {
606    LOG.debug("Current " + description + ": ");
607    for (FileStatus file : storeFiles) {
608      LOG.debug(Objects.toString(file.getPath()));
609    }
610  }
611
612  /**
613   * Test that if a store file with the same name is present as those already backed up cause the
614   * already archived files to be timestamped backup
615   */
616  @Test
617  public void testDuplicateHFileResolution() throws Exception {
618    TableDescriptor td = createTableDescriptorForCurrentMethod();
619
620    // Create regions.
621    RegionInfo parent =
622      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("eee"));
623    RegionInfo splita =
624      createRegionInfo(td.getTableName(), Bytes.toBytes("aaa"), Bytes.toBytes("ccc"));
625    RegionInfo splitb =
626      createRegionInfo(td.getTableName(), Bytes.toBytes("ccc"), Bytes.toBytes("eee"));
627    // Test that when both daughter regions are in place, that we do not
628    // remove the parent.
629    Result r = createResult(parent, splita, splitb);
630    FileSystem fs = FileSystem.get(HTU.getConfiguration());
631    Path rootdir = this.masterServices.getMasterFileSystem().getRootDir();
632    // Have to set the root directory since we use it in HFileDisposer to figure out to get to the
633    // archive directory. Otherwise, it just seems to pick the first root directory it can find (so
634    // the single test passes, but when the full suite is run, things get borked).
635    CommonFSUtils.setRootDir(fs.getConf(), rootdir);
636    Path tabledir = CommonFSUtils.getTableDir(rootdir, parent.getTable());
637    Path storedir =
638      HRegionFileSystem.getStoreHomedir(tabledir, parent, td.getColumnFamilies()[0].getName());
639    LOG.info("Old root:" + rootdir);
640    LOG.info("Old table:" + tabledir);
641    LOG.info("Old store:" + storedir);
642
643    Path storeArchive = HFileArchiveUtil.getStoreArchivePath(this.masterServices.getConfiguration(),
644      parent, tabledir, td.getColumnFamilies()[0].getName());
645    LOG.info("Old archive:" + storeArchive);
646
647    // enable archiving, make sure that files get archived
648    addMockStoreFiles(2, this.masterServices, storedir);
649    // get the current store files for comparison
650    FileStatus[] storeFiles = fs.listStatus(storedir);
651    // Do the cleaning of the parent
652    assertTrue(CatalogJanitor.cleanParent(masterServices, parent, r));
653    Path parentDir = new Path(tabledir, parent.getEncodedName());
654    ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor());
655    assertTrue(!fs.exists(parentDir));
656
657    // And now check to make sure that the files have actually been archived
658    FileStatus[] archivedStoreFiles = fs.listStatus(storeArchive);
659    assertArchiveEqualToOriginal(storeFiles, archivedStoreFiles, fs);
660
661    // now add store files with the same names as before to check backup
662    // enable archiving, make sure that files get archived
663    addMockStoreFiles(2, this.masterServices, storedir);
664
665    // Do the cleaning of the parent
666    assertTrue(CatalogJanitor.cleanParent(masterServices, parent, r));
667    // Cleanup procedure runs async. Wait till it done.
668    ProcedureTestingUtility.waitAllProcedures(masterServices.getMasterProcedureExecutor());
669    assertTrue(!fs.exists(parentDir));
670
671    // and now check to make sure that the files have actually been archived
672    archivedStoreFiles = fs.listStatus(storeArchive);
673    assertArchiveEqualToOriginal(storeFiles, archivedStoreFiles, fs, true);
674  }
675
676  @Test
677  public void testAlreadyRunningStatus() throws Exception {
678    int numberOfThreads = 2;
679    List<Integer> gcValues = new ArrayList<>();
680    Thread[] threads = new Thread[numberOfThreads];
681    for (int i = 0; i < numberOfThreads; i++) {
682      threads[i] = new Thread(() -> {
683        try {
684          gcValues.add(janitor.scan());
685        } catch (IOException e) {
686          throw new RuntimeException(e);
687        }
688      });
689    }
690    for (int i = 0; i < numberOfThreads; i++) {
691      threads[i].start();
692    }
693    for (int i = 0; i < numberOfThreads; i++) {
694      threads[i].join();
695    }
696    assertTrue("One janitor.scan() call should have returned -1", gcValues.contains(-1));
697  }
698
699  private FileStatus[] addMockStoreFiles(int count, MasterServices services, Path storedir)
700    throws IOException {
701    // get the existing store files
702    FileSystem fs = services.getMasterFileSystem().getFileSystem();
703    fs.mkdirs(storedir);
704    // create the store files in the parent
705    for (int i = 0; i < count; i++) {
706      Path storeFile = new Path(storedir, "_store" + i);
707      FSDataOutputStream dos = fs.create(storeFile, true);
708      dos.writeBytes("Some data: " + i);
709      dos.close();
710    }
711    LOG.debug("Adding " + count + " store files to the storedir:" + storedir);
712    // make sure the mock store files are there
713    FileStatus[] storeFiles = fs.listStatus(storedir);
714    assertEquals("Didn't have expected store files", count, storeFiles.length);
715    return storeFiles;
716  }
717
718  private String setRootDirAndCleanIt(final HBaseTestingUtil htu, final String subdir)
719    throws IOException {
720    Path testdir = htu.getDataTestDir(subdir);
721    FileSystem fs = FileSystem.get(htu.getConfiguration());
722    if (fs.exists(testdir)) assertTrue(fs.delete(testdir, true));
723    CommonFSUtils.setRootDir(htu.getConfiguration(), testdir);
724    return CommonFSUtils.getRootDir(htu.getConfiguration()).toString();
725  }
726
727  private Path createReferences(final MasterServices services, final TableDescriptor td,
728    final RegionInfo parent, final RegionInfo daughter, final byte[] midkey, final boolean top)
729    throws IOException {
730    Path rootdir = services.getMasterFileSystem().getRootDir();
731    Path tabledir = CommonFSUtils.getTableDir(rootdir, parent.getTable());
732    Path storedir =
733      HRegionFileSystem.getStoreHomedir(tabledir, daughter, td.getColumnFamilies()[0].getName());
734    Reference ref =
735      top ? Reference.createTopReference(midkey) : Reference.createBottomReference(midkey);
736    long now = EnvironmentEdgeManager.currentTime();
737    // Reference name has this format: StoreFile#REF_NAME_PARSER
738    Path p = new Path(storedir, Long.toString(now) + "." + parent.getEncodedName());
739    FileSystem fs = services.getMasterFileSystem().getFileSystem();
740    HRegionFileSystem regionFS =
741      HRegionFileSystem.create(services.getConfiguration(), fs, tabledir, daughter);
742    StoreContext storeContext =
743      StoreContext.getBuilder().withColumnFamilyDescriptor(td.getColumnFamilies()[0])
744        .withFamilyStoreDirectoryPath(storedir).withRegionFileSystem(regionFS).build();
745    StoreFileTracker sft =
746      StoreFileTrackerFactory.create(services.getConfiguration(), false, storeContext);
747    sft.createReference(ref, p);
748    return p;
749  }
750
751  private Result createResult(final RegionInfo parent, final RegionInfo a, final RegionInfo b)
752    throws IOException {
753    return MetaMockingUtil.getMetaTableRowResult(parent, null, a, b);
754  }
755}