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