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.quotas;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertTrue;
024
025import java.io.IOException;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.HashSet;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Set;
032import java.util.concurrent.atomic.AtomicLong;
033import java.util.concurrent.atomic.AtomicReference;
034
035import org.apache.hadoop.conf.Configuration;
036import org.apache.hadoop.fs.FileSystem;
037import org.apache.hadoop.fs.Path;
038import org.apache.hadoop.hbase.Cell;
039import org.apache.hadoop.hbase.HBaseClassTestRule;
040import org.apache.hadoop.hbase.HBaseTestingUtility;
041import org.apache.hadoop.hbase.HConstants;
042import org.apache.hadoop.hbase.NamespaceDescriptor;
043import org.apache.hadoop.hbase.TableName;
044import org.apache.hadoop.hbase.Waiter.Predicate;
045import org.apache.hadoop.hbase.client.Admin;
046import org.apache.hadoop.hbase.client.Connection;
047import org.apache.hadoop.hbase.client.Get;
048import org.apache.hadoop.hbase.client.RegionInfo;
049import org.apache.hadoop.hbase.client.Result;
050import org.apache.hadoop.hbase.client.SnapshotDescription;
051import org.apache.hadoop.hbase.client.SnapshotType;
052import org.apache.hadoop.hbase.client.Table;
053import org.apache.hadoop.hbase.master.HMaster;
054import org.apache.hadoop.hbase.quotas.SpaceQuotaHelperForTests.NoFilesToDischarge;
055import org.apache.hadoop.hbase.quotas.SpaceQuotaHelperForTests.SpaceQuotaSnapshotPredicate;
056import org.apache.hadoop.hbase.regionserver.HStore;
057import org.apache.hadoop.hbase.snapshot.SnapshotReferenceUtil;
058import org.apache.hadoop.hbase.snapshot.SnapshotReferenceUtil.SnapshotVisitor;
059import org.apache.hadoop.hbase.testclassification.MediumTests;
060import org.junit.AfterClass;
061import org.junit.Before;
062import org.junit.BeforeClass;
063import org.junit.ClassRule;
064import org.junit.Rule;
065import org.junit.Test;
066import org.junit.experimental.categories.Category;
067import org.junit.rules.TestName;
068import org.slf4j.Logger;
069import org.slf4j.LoggerFactory;
070
071import org.apache.hbase.thirdparty.com.google.common.collect.HashMultimap;
072import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap;
073import org.apache.hbase.thirdparty.com.google.common.collect.Multimap;
074
075import org.apache.hadoop.hbase.shaded.protobuf.generated.SnapshotProtos.SnapshotRegionManifest.StoreFile;
076
077/**
078 * Test class for the {@link SnapshotQuotaObserverChore}.
079 */
080@Category(MediumTests.class)
081public class TestSnapshotQuotaObserverChore {
082
083  @ClassRule
084  public static final HBaseClassTestRule CLASS_RULE =
085      HBaseClassTestRule.forClass(TestSnapshotQuotaObserverChore.class);
086
087  private static final Logger LOG = LoggerFactory.getLogger(TestSnapshotQuotaObserverChore.class);
088  private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
089  private static final AtomicLong COUNTER = new AtomicLong();
090
091  @Rule
092  public TestName testName = new TestName();
093
094  private Connection conn;
095  private Admin admin;
096  private SpaceQuotaHelperForTests helper;
097  private HMaster master;
098  private SnapshotQuotaObserverChore testChore;
099
100  @BeforeClass
101  public static void setUp() throws Exception {
102    Configuration conf = TEST_UTIL.getConfiguration();
103    SpaceQuotaHelperForTests.updateConfigForQuotas(conf);
104    // Clean up the compacted files faster than normal (15s instead of 2mins)
105    conf.setInt("hbase.hfile.compaction.discharger.interval", 15 * 1000);
106    TEST_UTIL.startMiniCluster(1);
107  }
108
109  @AfterClass
110  public static void tearDown() throws Exception {
111    TEST_UTIL.shutdownMiniCluster();
112  }
113
114  @Before
115  public void setup() throws Exception {
116    conn = TEST_UTIL.getConnection();
117    admin = TEST_UTIL.getAdmin();
118    helper = new SpaceQuotaHelperForTests(TEST_UTIL, testName, COUNTER);
119    master = TEST_UTIL.getHBaseCluster().getMaster();
120    helper.removeAllQuotas(conn);
121    testChore = new SnapshotQuotaObserverChore(
122        TEST_UTIL.getConnection(), TEST_UTIL.getConfiguration(), master.getFileSystem(), master,
123        null);
124  }
125
126  @Test
127  public void testSnapshotsFromTables() throws Exception {
128    TableName tn1 = helper.createTableWithRegions(1);
129    TableName tn2 = helper.createTableWithRegions(1);
130    TableName tn3 = helper.createTableWithRegions(1);
131
132    // Set a space quota on table 1 and 2 (but not 3)
133    admin.setQuota(QuotaSettingsFactory.limitTableSpace(
134        tn1, SpaceQuotaHelperForTests.ONE_GIGABYTE, SpaceViolationPolicy.NO_INSERTS));
135    admin.setQuota(QuotaSettingsFactory.limitTableSpace(
136        tn2, SpaceQuotaHelperForTests.ONE_GIGABYTE, SpaceViolationPolicy.NO_INSERTS));
137
138    // Create snapshots on each table (we didn't write any data, so just skipflush)
139    admin.snapshot(new SnapshotDescription(tn1 + "snapshot", tn1, SnapshotType.SKIPFLUSH));
140    admin.snapshot(new SnapshotDescription(tn2 + "snapshot", tn2, SnapshotType.SKIPFLUSH));
141    admin.snapshot(new SnapshotDescription(tn3 + "snapshot", tn3, SnapshotType.SKIPFLUSH));
142
143    Multimap<TableName,String> mapping = testChore.getSnapshotsToComputeSize();
144    assertEquals(2, mapping.size());
145    assertEquals(1, mapping.get(tn1).size());
146    assertEquals(tn1 + "snapshot", mapping.get(tn1).iterator().next());
147    assertEquals(1, mapping.get(tn2).size());
148    assertEquals(tn2 + "snapshot", mapping.get(tn2).iterator().next());
149
150    admin.snapshot(new SnapshotDescription(tn2 + "snapshot1", tn2, SnapshotType.SKIPFLUSH));
151    admin.snapshot(new SnapshotDescription(tn3 + "snapshot1", tn3, SnapshotType.SKIPFLUSH));
152
153    mapping = testChore.getSnapshotsToComputeSize();
154    assertEquals(3, mapping.size());
155    assertEquals(1, mapping.get(tn1).size());
156    assertEquals(tn1 + "snapshot", mapping.get(tn1).iterator().next());
157    assertEquals(2, mapping.get(tn2).size());
158    assertEquals(
159        new HashSet<String>(Arrays.asList(tn2 + "snapshot", tn2 + "snapshot1")), mapping.get(tn2));
160  }
161
162  @Test
163  public void testSnapshotsFromNamespaces() throws Exception {
164    NamespaceDescriptor ns = NamespaceDescriptor.create("snapshots_from_namespaces").build();
165    admin.createNamespace(ns);
166
167    TableName tn1 = helper.createTableWithRegions(ns.getName(), 1);
168    TableName tn2 = helper.createTableWithRegions(ns.getName(), 1);
169    TableName tn3 = helper.createTableWithRegions(1);
170
171    // Set a space quota on the namespace
172    admin.setQuota(QuotaSettingsFactory.limitNamespaceSpace(
173        ns.getName(), SpaceQuotaHelperForTests.ONE_GIGABYTE, SpaceViolationPolicy.NO_INSERTS));
174
175    // Create snapshots on each table (we didn't write any data, so just skipflush)
176    admin.snapshot(new SnapshotDescription(
177        tn1.getQualifierAsString() + "snapshot", tn1, SnapshotType.SKIPFLUSH));
178    admin.snapshot(new SnapshotDescription(
179        tn2.getQualifierAsString() + "snapshot", tn2, SnapshotType.SKIPFLUSH));
180    admin.snapshot(new SnapshotDescription(
181        tn3.getQualifierAsString() + "snapshot", tn3, SnapshotType.SKIPFLUSH));
182
183    Multimap<TableName,String> mapping = testChore.getSnapshotsToComputeSize();
184    assertEquals(2, mapping.size());
185    assertEquals(1, mapping.get(tn1).size());
186    assertEquals(tn1.getQualifierAsString() + "snapshot", mapping.get(tn1).iterator().next());
187    assertEquals(1, mapping.get(tn2).size());
188    assertEquals(tn2.getQualifierAsString() + "snapshot", mapping.get(tn2).iterator().next());
189
190    admin.snapshot(new SnapshotDescription(
191        tn2.getQualifierAsString() + "snapshot1", tn2, SnapshotType.SKIPFLUSH));
192    admin.snapshot(new SnapshotDescription(
193        tn3.getQualifierAsString() + "snapshot2", tn3, SnapshotType.SKIPFLUSH));
194
195    mapping = testChore.getSnapshotsToComputeSize();
196    assertEquals(3, mapping.size());
197    assertEquals(1, mapping.get(tn1).size());
198    assertEquals(tn1.getQualifierAsString() + "snapshot", mapping.get(tn1).iterator().next());
199    assertEquals(2, mapping.get(tn2).size());
200    assertEquals(
201        new HashSet<String>(Arrays.asList(tn2.getQualifierAsString() + "snapshot",
202            tn2.getQualifierAsString() + "snapshot1")), mapping.get(tn2));
203  }
204
205  @Test
206  public void testSnapshotSize() throws Exception {
207    // Create a table and set a quota
208    TableName tn1 = helper.createTableWithRegions(5);
209    admin.setQuota(QuotaSettingsFactory.limitTableSpace(
210        tn1, SpaceQuotaHelperForTests.ONE_GIGABYTE, SpaceViolationPolicy.NO_INSERTS));
211
212    // Write some data and flush it
213    helper.writeData(tn1, 256L * SpaceQuotaHelperForTests.ONE_KILOBYTE);
214    admin.flush(tn1);
215
216    final long snapshotSize = TEST_UTIL.getMiniHBaseCluster().getRegions(tn1).stream()
217        .flatMap(r -> r.getStores().stream()).mapToLong(HStore::getHFilesSize).sum();
218
219    // Wait for the Master chore to run to see the usage (with a fudge factor)
220    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
221      @Override
222      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
223        return snapshot.getUsage() == snapshotSize;
224      }
225    });
226
227    // Create a snapshot on the table
228    final String snapshotName = tn1 + "snapshot";
229    admin.snapshot(new SnapshotDescription(snapshotName, tn1, SnapshotType.SKIPFLUSH));
230
231    // Get the snapshots
232    Multimap<TableName,String> snapshotsToCompute = testChore.getSnapshotsToComputeSize();
233    assertEquals(
234        "Expected to see the single snapshot: " + snapshotsToCompute, 1, snapshotsToCompute.size());
235
236    // Get the size of our snapshot
237    Map<String,Long> namespaceSnapshotSizes = testChore.computeSnapshotSizes(
238        snapshotsToCompute);
239    assertEquals(1, namespaceSnapshotSizes.size());
240    Long size = namespaceSnapshotSizes.get(tn1.getNamespaceAsString());
241    assertNotNull(size);
242    // The snapshot should take up no space since the table refers to it completely
243    assertEquals(0, size.longValue());
244
245    // Write some more data, flush it, and then major_compact the table
246    helper.writeData(tn1, 256L * SpaceQuotaHelperForTests.ONE_KILOBYTE);
247    admin.flush(tn1);
248    TEST_UTIL.compact(tn1, true);
249
250    // Test table should reflect it's original size since ingest was deterministic
251    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
252      private final long regionSize = TEST_UTIL.getMiniHBaseCluster().getRegions(tn1).stream()
253          .flatMap(r -> r.getStores().stream()).mapToLong(HStore::getHFilesSize).sum();
254
255      @Override
256      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
257        LOG.debug("Current usage=" + snapshot.getUsage() + " snapshotSize=" + snapshotSize);
258        // The usage of table space consists of region size and snapshot size
259        return closeInSize(snapshot.getUsage(), snapshotSize + regionSize,
260            SpaceQuotaHelperForTests.ONE_KILOBYTE);
261      }
262    });
263
264    // Wait for no compacted files on the regions of our table
265    TEST_UTIL.waitFor(30_000, new NoFilesToDischarge(TEST_UTIL.getMiniHBaseCluster(), tn1));
266
267    // Still should see only one snapshot
268    snapshotsToCompute = testChore.getSnapshotsToComputeSize();
269    assertEquals(
270        "Expected to see the single snapshot: " + snapshotsToCompute, 1, snapshotsToCompute.size());
271    namespaceSnapshotSizes = testChore.computeSnapshotSizes(
272            snapshotsToCompute);
273    assertEquals(1, namespaceSnapshotSizes.size());
274    size = namespaceSnapshotSizes.get(tn1.getNamespaceAsString());
275    assertNotNull(size);
276    // The snapshot should take up the size the table originally took up
277    assertEquals(snapshotSize, size.longValue());
278  }
279
280  @Test
281  public void testPersistingSnapshotsForNamespaces() throws Exception {
282    TableName tn1 = TableName.valueOf("ns1:tn1");
283    TableName tn2 = TableName.valueOf("ns1:tn2");
284    TableName tn3 = TableName.valueOf("ns2:tn1");
285    TableName tn4 = TableName.valueOf("ns2:tn2");
286    TableName tn5 = TableName.valueOf("tn1");
287    // Shim in a custom factory to avoid computing snapshot sizes.
288    FileArchiverNotifierFactory test = new FileArchiverNotifierFactory() {
289      Map<TableName,Long> tableToSize = ImmutableMap.of(
290          tn1, 1024L, tn2, 1024L, tn3, 512L, tn4, 1024L, tn5, 3072L);
291      @Override
292      public FileArchiverNotifier get(
293          Connection conn, Configuration conf, FileSystem fs, TableName tn) {
294        return new FileArchiverNotifier() {
295          @Override public void addArchivedFiles(Set<Entry<String,Long>> fileSizes)
296              throws IOException {}
297
298          @Override
299          public long computeAndStoreSnapshotSizes(Collection<String> currentSnapshots)
300              throws IOException {
301            return tableToSize.get(tn);
302          }
303        };
304      }
305    };
306    try {
307      FileArchiverNotifierFactoryImpl.setInstance(test);
308
309      Multimap<TableName,String> snapshotsToCompute = HashMultimap.create();
310      snapshotsToCompute.put(tn1, "");
311      snapshotsToCompute.put(tn2, "");
312      snapshotsToCompute.put(tn3, "");
313      snapshotsToCompute.put(tn4, "");
314      snapshotsToCompute.put(tn5, "");
315      Map<String,Long> nsSizes = testChore.computeSnapshotSizes(snapshotsToCompute);
316      assertEquals(3, nsSizes.size());
317      assertEquals(2048L, (long) nsSizes.get("ns1"));
318      assertEquals(1536L, (long) nsSizes.get("ns2"));
319      assertEquals(3072L, (long) nsSizes.get(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR));
320    } finally {
321      FileArchiverNotifierFactoryImpl.reset();
322    }
323  }
324
325  @Test
326  public void testRemovedSnapshots() throws Exception {
327    // Create a table and set a quota
328    TableName tn1 = helper.createTableWithRegions(1);
329    admin.setQuota(QuotaSettingsFactory.limitTableSpace(tn1, SpaceQuotaHelperForTests.ONE_GIGABYTE,
330        SpaceViolationPolicy.NO_INSERTS));
331
332    // Write some data and flush it
333    helper.writeData(tn1, 256L * SpaceQuotaHelperForTests.ONE_KILOBYTE); // 256 KB
334
335    final AtomicReference<Long> lastSeenSize = new AtomicReference<>();
336    // Wait for the Master chore to run to see the usage (with a fudge factor)
337    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
338      @Override
339      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
340        lastSeenSize.set(snapshot.getUsage());
341        return snapshot.getUsage() > 230L * SpaceQuotaHelperForTests.ONE_KILOBYTE;
342      }
343    });
344
345    // Create a snapshot on the table
346    final String snapshotName1 = tn1 + "snapshot1";
347    admin.snapshot(new SnapshotDescription(snapshotName1, tn1, SnapshotType.SKIPFLUSH));
348
349    // Snapshot size has to be 0 as the snapshot shares the data with the table
350    final Table quotaTable = conn.getTable(QuotaUtil.QUOTA_TABLE_NAME);
351    TEST_UTIL.waitFor(30_000, new Predicate<Exception>() {
352      @Override
353      public boolean evaluate() throws Exception {
354        Get g = QuotaTableUtil.makeGetForSnapshotSize(tn1, snapshotName1);
355        Result r = quotaTable.get(g);
356        if (r == null || r.isEmpty()) {
357          return false;
358        }
359        r.advance();
360        Cell c = r.current();
361        return QuotaTableUtil.parseSnapshotSize(c) == 0;
362      }
363    });
364    // Total usage has to remain same as what we saw before taking a snapshot
365    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
366      @Override
367      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
368        return snapshot.getUsage() == lastSeenSize.get();
369      }
370    });
371
372    // Major compact the table to force a rewrite
373    TEST_UTIL.compact(tn1, true);
374    // Now the snapshot size has to prev total size
375    TEST_UTIL.waitFor(30_000, new Predicate<Exception>() {
376      @Override
377      public boolean evaluate() throws Exception {
378        Get g = QuotaTableUtil.makeGetForSnapshotSize(tn1, snapshotName1);
379        Result r = quotaTable.get(g);
380        if (r == null || r.isEmpty()) {
381          return false;
382        }
383        r.advance();
384        Cell c = r.current();
385        // The compaction result file has an additional compaction event tracker
386        return lastSeenSize.get() == QuotaTableUtil.parseSnapshotSize(c);
387      }
388    });
389    // The total size now has to be equal/more than double of prev total size
390    // as double the number of store files exist now.
391    final AtomicReference<Long> sizeAfterCompaction = new AtomicReference<>();
392    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
393      @Override
394      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
395        sizeAfterCompaction.set(snapshot.getUsage());
396        return snapshot.getUsage() >= 2 * lastSeenSize.get();
397      }
398    });
399
400    // Delete the snapshot
401    admin.deleteSnapshot(snapshotName1);
402    // Total size has to come down to prev totalsize - snapshot size(which was removed)
403    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
404      @Override
405      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
406        return snapshot.getUsage() == (sizeAfterCompaction.get() - lastSeenSize.get());
407      }
408    });
409  }
410
411  @Test
412  public void testBucketingFilesToSnapshots() throws Exception {
413    // Create a table and set a quota
414    TableName tn1 = helper.createTableWithRegions(1);
415    admin.setQuota(QuotaSettingsFactory.limitTableSpace(
416        tn1, SpaceQuotaHelperForTests.ONE_GIGABYTE, SpaceViolationPolicy.NO_INSERTS));
417
418    // Write some data and flush it
419    helper.writeData(tn1, 256L * SpaceQuotaHelperForTests.ONE_KILOBYTE);
420    admin.flush(tn1);
421
422    final AtomicReference<Long> lastSeenSize = new AtomicReference<>();
423    // Wait for the Master chore to run to see the usage (with a fudge factor)
424    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
425      @Override
426      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
427        lastSeenSize.set(snapshot.getUsage());
428        return snapshot.getUsage() > 230L * SpaceQuotaHelperForTests.ONE_KILOBYTE;
429      }
430    });
431
432    // Create a snapshot on the table
433    final String snapshotName1 = tn1 + "snapshot1";
434    admin.snapshot(new SnapshotDescription(snapshotName1, tn1, SnapshotType.SKIPFLUSH));
435    // Major compact the table to force a rewrite
436    TEST_UTIL.compact(tn1, true);
437
438    // Make sure that the snapshot owns the size
439    final Table quotaTable = conn.getTable(QuotaUtil.QUOTA_TABLE_NAME);
440    TEST_UTIL.waitFor(30_000, new Predicate<Exception>() {
441      @Override
442      public boolean evaluate() throws Exception {
443        LOG.info("Waiting to see quota snapshot1 size");
444        debugFilesForSnapshot(tn1, snapshotName1);
445        Get g = QuotaTableUtil.makeGetForSnapshotSize(tn1, snapshotName1);
446        Result r = quotaTable.get(g);
447        if (r == null || r.isEmpty()) {
448          return false;
449        }
450        r.advance();
451        Cell c = r.current();
452        // The compaction result file has an additional compaction event tracker
453        return lastSeenSize.get() == QuotaTableUtil.parseSnapshotSize(c);
454      }
455    });
456
457    LOG.info("Snapshotting table again");
458    // Create another snapshot on the table
459    final String snapshotName2 = tn1 + "snapshot2";
460    admin.snapshot(new SnapshotDescription(snapshotName2, tn1, SnapshotType.SKIPFLUSH));
461    LOG.info("Compacting table");
462    // Major compact the table to force a rewrite
463    TEST_UTIL.compact(tn1, true);
464
465    // Make sure that the snapshot owns the size
466    TEST_UTIL.waitFor(30_000, new Predicate<Exception>() {
467      @Override
468      public boolean evaluate() throws Exception {
469        LOG.info("Waiting to see quota snapshot2 size");
470        debugFilesForSnapshot(tn1, snapshotName2);
471        Get g = QuotaTableUtil.makeGetForSnapshotSize(tn1, snapshotName2);
472        Result r = quotaTable.get(g);
473        if (r == null || r.isEmpty()) {
474          return false;
475        }
476        r.advance();
477        Cell c = r.current();
478        return closeInSize(lastSeenSize.get(),
479            QuotaTableUtil.parseSnapshotSize(c), SpaceQuotaHelperForTests.ONE_KILOBYTE);
480      }
481    });
482
483    Get g = QuotaTableUtil.createGetNamespaceSnapshotSize(tn1.getNamespaceAsString());
484    Result r = quotaTable.get(g);
485    assertNotNull(r);
486    assertFalse(r.isEmpty());
487    r.advance();
488    long size = QuotaTableUtil.parseSnapshotSize(r.current());
489    // Two snapshots of equal size.
490    assertTrue(closeInSize(lastSeenSize.get() * 2, size, SpaceQuotaHelperForTests.ONE_KILOBYTE));
491  }
492
493  /**
494   * Prints details about every file referenced by the snapshot with the given name.
495   */
496  void debugFilesForSnapshot(TableName table, String snapshot) throws IOException {
497    final Configuration conf = TEST_UTIL.getConfiguration();
498    final FileSystem fs = TEST_UTIL.getTestFileSystem();
499    final Path snapshotDir = new Path(conf.get("hbase.rootdir"), HConstants.SNAPSHOT_DIR_NAME);
500    SnapshotReferenceUtil.visitReferencedFiles(conf, fs, new Path(snapshotDir, snapshot),
501        new SnapshotVisitor() {
502          @Override
503          public void storeFile(
504              RegionInfo regionInfo, String familyName, StoreFile storeFile) throws IOException {
505            LOG.info("Snapshot={} references file={}, size={}", snapshot, storeFile.getName(),
506                storeFile.getFileSize());
507          }
508        }
509    );
510  }
511
512  /**
513   * Computes if {@code size2} is within {@code delta} of {@code size1}, inclusive.
514   *
515   * The size of our store files will change after the first major compaction as the last
516   * compaction gets serialized into the store file (see the fields referenced by
517   * COMPACTION_EVENT_KEY in HFilePrettyPrinter).
518   */
519  boolean closeInSize(long size1, long size2, long delta) {
520    long lower = size1 - delta;
521    long upper = size1 + delta;
522    return lower <= size2 && size2 <= upper;
523  }
524}