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