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