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