This is an automated email from the ASF dual-hosted git repository.

elserj pushed a commit to branch branch-2.1
in repository https://gitbox.apache.org/repos/asf/hbase.git


The following commit(s) were added to refs/heads/branch-2.1 by this push:
     new 2d78dfb  HBASE-22086: Space Quota issue: Deleting snapshot doesn't 
update the usage of table
2d78dfb is described below

commit 2d78dfb2eda0517e23f310af5d2dee8479b1e7b9
Author: Sakthi <[email protected]>
AuthorDate: Thu Apr 25 19:26:51 2019 -0700

    HBASE-22086: Space Quota issue: Deleting snapshot doesn't update the usage 
of table
    
    Signed-off-by: Duo Zhang <[email protected]>
---
 .../apache/hadoop/hbase/quotas/QuotaTableUtil.java | 161 +++++++++++++++++++++
 .../hbase/quotas/SnapshotQuotaObserverChore.java   |  62 ++++++++
 .../hadoop/hbase/quotas/TestQuotaTableUtil.java    |  82 +++++++++++
 .../quotas/TestSnapshotQuotaObserverChore.java     |  89 ++++++++++++
 4 files changed, 394 insertions(+)

diff --git 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java
index 42f73e7..d21bd85 100644
--- 
a/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java
+++ 
b/hbase-client/src/main/java/org/apache/hadoop/hbase/quotas/QuotaTableUtil.java
@@ -21,11 +21,14 @@ package org.apache.hadoop.hbase.quotas;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.regex.Pattern;
 
 import org.apache.commons.lang3.StringUtils;
@@ -41,6 +44,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.hadoop.hbase.client.ClusterConnection;
 import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.client.Delete;
 import org.apache.hadoop.hbase.client.Get;
 import org.apache.hadoop.hbase.client.Put;
 import org.apache.hadoop.hbase.client.QuotaStatusCalls;
@@ -55,6 +59,14 @@ import org.apache.hadoop.hbase.filter.QualifierFilter;
 import org.apache.hadoop.hbase.filter.RegexStringComparator;
 import org.apache.hadoop.hbase.filter.RowFilter;
 import org.apache.hadoop.hbase.protobuf.ProtobufMagic;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.yetus.audience.InterfaceAudience;
+import org.apache.yetus.audience.InterfaceStability;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.hbase.thirdparty.com.google.common.collect.HashMultimap;
+import org.apache.hbase.thirdparty.com.google.common.collect.Multimap;
 import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
 import 
org.apache.hbase.thirdparty.com.google.protobuf.InvalidProtocolBufferException;
 import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
@@ -492,6 +504,87 @@ public class QuotaTableUtil {
   }
 
   /**
+   * Returns a list of {@code Delete} to remove given table snapshot
+   * entries to remove from quota table
+   * @param snapshotEntriesToRemove the entries to remove
+   */
+  static List<Delete> createDeletesForExistingTableSnapshotSizes(
+      Multimap<TableName, String> snapshotEntriesToRemove) {
+    List<Delete> deletes = new ArrayList<>();
+    for (Map.Entry<TableName, Collection<String>> entry : 
snapshotEntriesToRemove.asMap()
+        .entrySet()) {
+      for (String snapshot : entry.getValue()) {
+        Delete d = new Delete(getTableRowKey(entry.getKey()));
+        d.addColumns(QUOTA_FAMILY_USAGE,
+            Bytes.add(QUOTA_SNAPSHOT_SIZE_QUALIFIER, Bytes.toBytes(snapshot)));
+        deletes.add(d);
+      }
+    }
+    return deletes;
+  }
+
+  /**
+   * Returns a list of {@code Delete} to remove all table snapshot entries 
from quota table.
+   * @param connection connection to re-use
+   */
+  static List<Delete> createDeletesForExistingTableSnapshotSizes(Connection 
connection)
+      throws IOException {
+    return createDeletesForExistingSnapshotsFromScan(connection, 
createScanForSpaceSnapshotSizes());
+  }
+
+  /**
+   * Returns a list of {@code Delete} to remove given namespace snapshot
+   * entries to removefrom quota table
+   * @param snapshotEntriesToRemove the entries to remove
+   */
+  static List<Delete> createDeletesForExistingNamespaceSnapshotSizes(
+      Set<String> snapshotEntriesToRemove) {
+    List<Delete> deletes = new ArrayList<>();
+    for (String snapshot : snapshotEntriesToRemove) {
+      Delete d = new Delete(getNamespaceRowKey(snapshot));
+      d.addColumns(QUOTA_FAMILY_USAGE, QUOTA_SNAPSHOT_SIZE_QUALIFIER);
+      deletes.add(d);
+    }
+    return deletes;
+  }
+
+  /**
+   * Returns a list of {@code Delete} to remove all namespace snapshot entries 
from quota table.
+   * @param connection connection to re-use
+   */
+  static List<Delete> 
createDeletesForExistingNamespaceSnapshotSizes(Connection connection)
+      throws IOException {
+    return createDeletesForExistingSnapshotsFromScan(connection,
+        createScanForNamespaceSnapshotSizes());
+  }
+
+  /**
+   * Returns a list of {@code Delete} to remove all entries returned by the 
passed scanner.
+   * @param connection connection to re-use
+   * @param scan the scanner to use to generate the list of deletes
+   */
+  static List<Delete> createDeletesForExistingSnapshotsFromScan(Connection 
connection, Scan scan)
+      throws IOException {
+    List<Delete> deletes = new ArrayList<>();
+    try (Table quotaTable = connection.getTable(QUOTA_TABLE_NAME);
+        ResultScanner rs = quotaTable.getScanner(scan)) {
+      for (Result r : rs) {
+        CellScanner cs = r.cellScanner();
+        while (cs.advance()) {
+          Cell c = cs.current();
+          byte[] family = Bytes.copy(c.getFamilyArray(), c.getFamilyOffset(), 
c.getFamilyLength());
+          byte[] qual =
+              Bytes.copy(c.getQualifierArray(), c.getQualifierOffset(), 
c.getQualifierLength());
+          Delete d = new Delete(r.getRow());
+          d.addColumns(family, qual);
+          deletes.add(d);
+        }
+      }
+      return deletes;
+    }
+  }
+
+  /**
    * Fetches the computed size of all snapshots against tables in a namespace 
for space quotas.
    */
   static long getNamespaceSnapshotSize(
@@ -526,6 +619,34 @@ public class QuotaTableUtil {
     return QuotaProtos.SpaceQuotaSnapshot.parseFrom(bs).getQuotaUsage();
   }
 
+  /**
+   * Returns a scanner for all existing namespace snapshot entries.
+   */
+  static Scan createScanForNamespaceSnapshotSizes() {
+    return createScanForNamespaceSnapshotSizes(null);
+  }
+
+  /**
+   * Returns a scanner for all namespace snapshot entries of the given 
namespace
+   * @param namespace name of the namespace whose snapshot entries are to be 
scanned
+   */
+  static Scan createScanForNamespaceSnapshotSizes(String namespace) {
+    Scan s = new Scan();
+    if (namespace == null || namespace.isEmpty()) {
+      // Read all namespaces, just look at the row prefix
+      s.setRowPrefixFilter(QUOTA_NAMESPACE_ROW_KEY_PREFIX);
+    } else {
+      // Fetch the exact row for the table
+      byte[] rowkey = getNamespaceRowKey(namespace);
+      // Fetch just this one row
+      s.withStartRow(rowkey).withStopRow(rowkey, true);
+    }
+
+    // Just the usage family and only the snapshot size qualifiers
+    return s.addFamily(QUOTA_FAMILY_USAGE)
+        .setFilter(new ColumnPrefixFilter(QUOTA_SNAPSHOT_SIZE_QUALIFIER));
+  }
+
   static Scan createScanForSpaceSnapshotSizes() {
     return createScanForSpaceSnapshotSizes(null);
   }
@@ -572,6 +693,46 @@ public class QuotaTableUtil {
     }
   }
 
+  /**
+   * Returns a multimap for all existing table snapshot entries.
+   * @param conn connection to re-use
+   */
+  public static Multimap<TableName, String> getTableSnapshots(Connection conn) 
throws IOException {
+    try (Table quotaTable = conn.getTable(QUOTA_TABLE_NAME);
+        ResultScanner rs = 
quotaTable.getScanner(createScanForSpaceSnapshotSizes())) {
+      Multimap<TableName, String> snapshots = HashMultimap.create();
+      for (Result r : rs) {
+        CellScanner cs = r.cellScanner();
+        while (cs.advance()) {
+          Cell c = cs.current();
+
+          final String snapshot = extractSnapshotNameFromSizeCell(c);
+          snapshots.put(getTableFromRowKey(r.getRow()), snapshot);
+        }
+      }
+      return snapshots;
+    }
+  }
+
+  /**
+   * Returns a set of the names of all namespaces containing snapshot entries.
+   * @param conn connection to re-use
+   */
+  public static Set<String> getNamespaceSnapshots(Connection conn) throws 
IOException {
+    try (Table quotaTable = conn.getTable(QUOTA_TABLE_NAME);
+        ResultScanner rs = 
quotaTable.getScanner(createScanForNamespaceSnapshotSizes())) {
+      Set<String> snapshots = new HashSet<>();
+      for (Result r : rs) {
+        CellScanner cs = r.cellScanner();
+        while (cs.advance()) {
+          cs.current();
+          snapshots.add(getNamespaceFromRowKey(r.getRow()));
+        }
+      }
+      return snapshots;
+    }
+  }
+
   /* =========================================================================
    *  Space quota status RPC helpers
    */
diff --git 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/SnapshotQuotaObserverChore.java
 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/SnapshotQuotaObserverChore.java
index d90d1b3..e41888e 100644
--- 
a/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/SnapshotQuotaObserverChore.java
+++ 
b/hbase-server/src/main/java/org/apache/hadoop/hbase/quotas/SnapshotQuotaObserverChore.java
@@ -45,6 +45,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.hadoop.hbase.client.Admin;
 import org.apache.hadoop.hbase.client.Connection;
+import org.apache.hadoop.hbase.client.Delete;
 import org.apache.hadoop.hbase.client.Table;
 import org.apache.hadoop.hbase.master.HMaster;
 import org.apache.hadoop.hbase.master.MetricsMaster;
@@ -127,6 +128,12 @@ public class SnapshotQuotaObserverChore extends 
ScheduledChore {
       metrics.incrementSnapshotFetchTime((System.nanoTime() - start) / 
1_000_000);
     }
 
+    // Remove old table snapshots data
+    pruneTableSnapshots(snapshotsToComputeSize);
+
+    // Remove old namespace snapshots data
+    pruneNamespaceSnapshots(snapshotsToComputeSize);
+
     // For each table, compute the size of each snapshot
     Multimap<TableName,SnapshotWithSize> snapshotsWithSize = 
computeSnapshotSizes(
         snapshotsToComputeSize);
@@ -136,6 +143,43 @@ public class SnapshotQuotaObserverChore extends 
ScheduledChore {
   }
 
   /**
+   * Removes the snapshot entries that are present in Quota table but not in 
snapshotsToComputeSize
+   *
+   * @param snapshotsToComputeSize list of snapshots to be persisted
+   */
+  void pruneTableSnapshots(Multimap<TableName, String> snapshotsToComputeSize) 
throws IOException {
+    Multimap<TableName, String> existingSnapshotEntries = 
QuotaTableUtil.getTableSnapshots(conn);
+    Multimap<TableName, String> snapshotEntriesToRemove = 
HashMultimap.create();
+    for (Entry<TableName, Collection<String>> entry : 
existingSnapshotEntries.asMap().entrySet()) {
+      TableName tn = entry.getKey();
+      Set<String> setOfSnapshots = new HashSet<>(entry.getValue());
+      for (String snapshot : snapshotsToComputeSize.get(tn)) {
+        setOfSnapshots.remove(snapshot);
+      }
+
+      for (String snapshot : setOfSnapshots) {
+        snapshotEntriesToRemove.put(tn, snapshot);
+      }
+    }
+    removeExistingTableSnapshotSizes(snapshotEntriesToRemove);
+  }
+
+  /**
+   * Removes the snapshot entries that are present in Quota table but not in 
snapshotsToComputeSize
+   *
+   * @param snapshotsToComputeSize list of snapshots to be persisted
+   */
+  void pruneNamespaceSnapshots(Multimap<TableName, String> 
snapshotsToComputeSize)
+      throws IOException {
+    Set<String> existingSnapshotEntries = 
QuotaTableUtil.getNamespaceSnapshots(conn);
+    for (TableName tableName : snapshotsToComputeSize.keySet()) {
+      existingSnapshotEntries.remove(tableName.getNamespaceAsString());
+    }
+    // here existingSnapshotEntries is left with the entries to be removed
+    removeExistingNamespaceSnapshotSizes(existingSnapshotEntries);
+  }
+
+  /**
    * Fetches each table with a quota (table or namespace quota), and then 
fetch the name of each
    * snapshot which was created from that table.
    *
@@ -506,6 +550,24 @@ public class SnapshotQuotaObserverChore extends 
ScheduledChore {
     }
   }
 
+  void removeExistingTableSnapshotSizes(Multimap<TableName, String> 
snapshotEntriesToRemove)
+      throws IOException {
+    removeExistingSnapshotSizes(
+        
QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(snapshotEntriesToRemove));
+  }
+
+  void removeExistingNamespaceSnapshotSizes(Set<String> 
snapshotEntriesToRemove)
+      throws IOException {
+    removeExistingSnapshotSizes(
+        
QuotaTableUtil.createDeletesForExistingNamespaceSnapshotSizes(snapshotEntriesToRemove));
+  }
+
+  void removeExistingSnapshotSizes(List<Delete> deletes) throws IOException {
+    try (Table quotaTable = conn.getTable(QuotaUtil.QUOTA_TABLE_NAME)) {
+      quotaTable.delete(deletes);
+    }
+  }
+
   /**
    * Extracts the period for the chore from the configuration.
    *
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaTableUtil.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaTableUtil.java
index 6aac054..fff833f 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaTableUtil.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestQuotaTableUtil.java
@@ -24,9 +24,12 @@ import static org.junit.Assert.assertTrue;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
+
 import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.CellScanner;
 import org.apache.hadoop.hbase.HBaseClassTestRule;
@@ -36,6 +39,7 @@ import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.hadoop.hbase.client.Connection;
 import org.apache.hadoop.hbase.client.ConnectionFactory;
+import org.apache.hadoop.hbase.client.Delete;
 import org.apache.hadoop.hbase.client.Put;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.ResultScanner;
@@ -53,6 +57,8 @@ import org.junit.Test;
 import org.junit.experimental.categories.Category;
 import org.junit.rules.TestName;
 
+import org.apache.hbase.thirdparty.com.google.common.collect.HashMultimap;
+import org.apache.hbase.thirdparty.com.google.common.collect.Multimap;
 import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
 
 import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
@@ -110,6 +116,68 @@ public class TestQuotaTableUtil {
   }
 
   @Test
+  public void testDeleteSnapshots() throws Exception {
+    TableName tn = TableName.valueOf(name.getMethodName());
+    try (Table t = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) {
+      Quotas quota = Quotas.newBuilder().setSpace(
+          QuotaProtos.SpaceQuota.newBuilder().setSoftLimit(7L)
+              
.setViolationPolicy(QuotaProtos.SpaceViolationPolicy.NO_WRITES).build()).build();
+      QuotaUtil.addTableQuota(connection, tn, quota);
+
+      String snapshotName = name.getMethodName() + "_snapshot";
+      t.put(QuotaTableUtil.createPutForSnapshotSize(tn, snapshotName, 3L));
+      t.put(QuotaTableUtil.createPutForSnapshotSize(tn, snapshotName, 5L));
+      assertEquals(1, 
QuotaTableUtil.getObservedSnapshotSizes(connection).size());
+
+      List<Delete> deletes = 
QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(connection);
+      assertEquals(1, deletes.size());
+
+      t.delete(deletes);
+      assertEquals(0, 
QuotaTableUtil.getObservedSnapshotSizes(connection).size());
+
+      String ns = name.getMethodName();
+      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns, 5L));
+      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns, 3L));
+      assertEquals(3L, QuotaTableUtil.getNamespaceSnapshotSize(connection, 
ns));
+
+      deletes = 
QuotaTableUtil.createDeletesForExistingNamespaceSnapshotSizes(connection);
+      assertEquals(1, deletes.size());
+
+      t.delete(deletes);
+      assertEquals(0L, QuotaTableUtil.getNamespaceSnapshotSize(connection, 
ns));
+
+      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t1"), 
"s1", 3L));
+      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t2"), 
"s2", 3L));
+      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t3"), 
"s3", 3L));
+      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t4"), 
"s4", 3L));
+      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t1"), 
"s5", 3L));
+
+      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize("ns1", 3L));
+      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize("ns2", 3L));
+      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize("ns3", 3L));
+
+      assertEquals(5,QuotaTableUtil.getTableSnapshots(connection).size());
+      assertEquals(3,QuotaTableUtil.getNamespaceSnapshots(connection).size());
+
+      Multimap<TableName, String> tableSnapshotEntriesToRemove = 
HashMultimap.create();
+      tableSnapshotEntriesToRemove.put(TableName.valueOf("t1"), "s1");
+      tableSnapshotEntriesToRemove.put(TableName.valueOf("t3"), "s3");
+      tableSnapshotEntriesToRemove.put(TableName.valueOf("t4"), "s4");
+
+      Set<String> namespaceSnapshotEntriesToRemove = new HashSet<>();
+      namespaceSnapshotEntriesToRemove.add("ns2");
+      namespaceSnapshotEntriesToRemove.add("ns1");
+
+      deletes =
+          
QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(tableSnapshotEntriesToRemove);
+      assertEquals(3, deletes.size());
+      deletes = QuotaTableUtil
+          
.createDeletesForExistingNamespaceSnapshotSizes(namespaceSnapshotEntriesToRemove);
+      assertEquals(2, deletes.size());
+    }
+  }
+
+  @Test
   public void testTableQuotaUtil() throws Exception {
     final TableName tableName = TableName.valueOf(name.getMethodName());
 
@@ -266,6 +334,8 @@ public class TestQuotaTableUtil {
       verifyTableSnapshotSize(quotaTable, tn2, "tn2snap0", 2048L);
       verifyTableSnapshotSize(quotaTable, tn2, "tn2snap1", 4096L);
       verifyTableSnapshotSize(quotaTable, tn2, "tn2snap2", 6144L);
+
+      cleanUpSnapshotSizes();
     }
   }
 
@@ -282,6 +352,8 @@ public class TestQuotaTableUtil {
       assertEquals(1024L, QuotaTableUtil.getNamespaceSnapshotSize(connection, 
ns1));
       assertEquals(2048L, QuotaTableUtil.getNamespaceSnapshotSize(connection, 
ns2));
       assertEquals(8192L, QuotaTableUtil.getNamespaceSnapshotSize(connection, 
defaultNs));
+
+      cleanUpSnapshotSizes();
     }
   }
 
@@ -300,4 +372,14 @@ public class TestQuotaTableUtil {
             c.getValueArray(), c.getValueOffset(), 
c.getValueLength())).getQuotaUsage());
     assertFalse(cs.advance());
   }
+
+  private void cleanUpSnapshotSizes() throws IOException {
+    try (Table t = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) {
+      QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(connection);
+      List<Delete> deletes =
+          
QuotaTableUtil.createDeletesForExistingNamespaceSnapshotSizes(connection);
+      
deletes.addAll(QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(connection));
+      t.delete(deletes);
+    }
+  }
 }
diff --git 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestSnapshotQuotaObserverChore.java
 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestSnapshotQuotaObserverChore.java
index 818d6ff..a81f5fe 100644
--- 
a/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestSnapshotQuotaObserverChore.java
+++ 
b/hbase-server/src/test/java/org/apache/hadoop/hbase/quotas/TestSnapshotQuotaObserverChore.java
@@ -25,6 +25,8 @@ import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
 import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.hbase.Cell;
 import org.apache.hadoop.hbase.CellScanner;
@@ -34,6 +36,7 @@ import org.apache.hadoop.hbase.HColumnDescriptor;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.NamespaceDescriptor;
 import org.apache.hadoop.hbase.TableName;
+import org.apache.hadoop.hbase.Waiter.Predicate;
 import org.apache.hadoop.hbase.client.Admin;
 import org.apache.hadoop.hbase.client.Connection;
 import org.apache.hadoop.hbase.client.Get;
@@ -333,6 +336,92 @@ public class TestSnapshotQuotaObserverChore {
     assertEquals(3072L, (long) 
nsSizes.get(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR));
   }
 
+  @Test
+  public void testRemovedSnapshots() throws Exception {
+    // Create a table and set a quota
+    TableName tn1 = helper.createTableWithRegions(1);
+    admin.setQuota(QuotaSettingsFactory.limitTableSpace(tn1, 
SpaceQuotaHelperForTests.ONE_GIGABYTE,
+        SpaceViolationPolicy.NO_INSERTS));
+
+    // Write some data and flush it
+    helper.writeData(tn1, 256L * SpaceQuotaHelperForTests.ONE_KILOBYTE); // 
256 KB
+
+    final AtomicReference<Long> lastSeenSize = new AtomicReference<>();
+    // Wait for the Master chore to run to see the usage (with a fudge factor)
+    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
+      @Override
+      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
+        lastSeenSize.set(snapshot.getUsage());
+        return snapshot.getUsage() > 230L * 
SpaceQuotaHelperForTests.ONE_KILOBYTE;
+      }
+    });
+
+    // Create a snapshot on the table
+    final String snapshotName1 = tn1 + "snapshot1";
+    admin.snapshot(new SnapshotDescription(snapshotName1, tn1, 
SnapshotType.SKIPFLUSH));
+
+    // Snapshot size has to be 0 as the snapshot shares the data with the table
+    final Table quotaTable = conn.getTable(QuotaUtil.QUOTA_TABLE_NAME);
+    TEST_UTIL.waitFor(30_000, new Predicate<Exception>() {
+      @Override
+      public boolean evaluate() throws Exception {
+        Get g = QuotaTableUtil.makeGetForSnapshotSize(tn1, snapshotName1);
+        Result r = quotaTable.get(g);
+        if (r == null || r.isEmpty()) {
+          return false;
+        }
+        r.advance();
+        Cell c = r.current();
+        return QuotaTableUtil.parseSnapshotSize(c) == 0;
+      }
+    });
+    // Total usage has to remain same as what we saw before taking a snapshot
+    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
+      @Override
+      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
+        return snapshot.getUsage() == lastSeenSize.get();
+      }
+    });
+
+    // Major compact the table to force a rewrite
+    TEST_UTIL.compact(tn1, true);
+    // Now the snapshot size has to prev total size
+    TEST_UTIL.waitFor(30_000, new Predicate<Exception>() {
+      @Override
+      public boolean evaluate() throws Exception {
+        Get g = QuotaTableUtil.makeGetForSnapshotSize(tn1, snapshotName1);
+        Result r = quotaTable.get(g);
+        if (r == null || r.isEmpty()) {
+          return false;
+        }
+        r.advance();
+        Cell c = r.current();
+        // The compaction result file has an additional compaction event 
tracker
+        return lastSeenSize.get() == QuotaTableUtil.parseSnapshotSize(c);
+      }
+    });
+    // The total size now has to be equal/more than double of prev total size
+    // as double the number of store files exist now.
+    final AtomicReference<Long> sizeAfterCompaction = new AtomicReference<>();
+    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
+      @Override
+      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
+        sizeAfterCompaction.set(snapshot.getUsage());
+        return snapshot.getUsage() >= 2 * lastSeenSize.get();
+      }
+    });
+
+    // Delete the snapshot
+    admin.deleteSnapshot(snapshotName1);
+    // Total size has to come down to prev totalsize - snapshot size(which was 
removed)
+    TEST_UTIL.waitFor(30_000, new SpaceQuotaSnapshotPredicate(conn, tn1) {
+      @Override
+      boolean evaluate(SpaceQuotaSnapshot snapshot) throws Exception {
+        return snapshot.getUsage() == (sizeAfterCompaction.get() - 
lastSeenSize.get());
+      }
+    });
+  }
+
   private long count(Table t) throws IOException {
     try (ResultScanner rs = t.getScanner(new Scan())) {
       long sum = 0;

Reply via email to