diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java index bdb19f4..1b211f8 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java @@ -92,9 +92,11 @@ import org.apache.hadoop.hbase.master.balancer.LoadBalancerFactory; import org.apache.hadoop.hbase.master.cleaner.HFileCleaner; import org.apache.hadoop.hbase.master.cleaner.LogCleaner; import org.apache.hadoop.hbase.master.handler.DispatchMergingRegionHandler; +import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan; import org.apache.hadoop.hbase.master.normalizer.RegionNormalizer; import org.apache.hadoop.hbase.master.normalizer.RegionNormalizerChore; import org.apache.hadoop.hbase.master.normalizer.RegionNormalizerFactory; +import org.apache.hadoop.hbase.master.normalizer.SplitNormalizationPlan; import org.apache.hadoop.hbase.master.procedure.AddColumnFamilyProcedure; import org.apache.hadoop.hbase.master.procedure.CreateNamespaceProcedure; import org.apache.hadoop.hbase.master.procedure.CreateTableProcedure; @@ -124,6 +126,7 @@ import org.apache.hadoop.hbase.protobuf.generated.AdminProtos.GetRegionInfoRespo import org.apache.hadoop.hbase.protobuf.generated.HBaseProtos.RegionServerInfo; import org.apache.hadoop.hbase.protobuf.generated.ZooKeeperProtos.SplitLogTask.RecoveryMode; import org.apache.hadoop.hbase.quotas.MasterQuotaManager; +import org.apache.hadoop.hbase.quotas.QuotaExceededException; import org.apache.hadoop.hbase.quotas.RegionStateListener; import org.apache.hadoop.hbase.regionserver.DefaultStoreEngine; import org.apache.hadoop.hbase.regionserver.HRegionServer; @@ -1326,6 +1329,11 @@ public class HMaster extends HRegionServer implements MasterServices, Server { return true; } + @VisibleForTesting + public RegionNormalizer getRegionNormalizer() { + return this.normalizer; + } + /** * Perform normalization of cluster (invoked by {@link RegionNormalizerChore}). * @@ -1353,10 +1361,11 @@ public class HMaster extends HRegionServer implements MasterServices, Server { Collections.shuffle(allEnabledTables); for (TableName table : allEnabledTables) { + boolean checkQuota = false; if (quotaManager.getNamespaceQuotaManager() != null && + quotaManager.isQuotaEnabled() && quotaManager.getNamespaceQuotaManager().getState(table.getNamespaceAsString()) != null){ - LOG.debug("Skipping normalizing " + table + " since its namespace has quota"); - continue; + checkQuota = true; } TableDescriptor tblDesc = getTableDescriptors().getDescriptor(table); if (table.isSystemTable() || (tblDesc != null && @@ -1366,7 +1375,18 @@ public class HMaster extends HRegionServer implements MasterServices, Server { + " table or doesn't have auto normalization turned on"); continue; } - this.normalizer.computePlanForTable(table).execute(clusterConnection.getAdmin()); + NormalizationPlan plan = this.normalizer.computePlanForTable(table); + if (checkQuota && plan instanceof SplitNormalizationPlan) { + HRegionInfo hri = ((SplitNormalizationPlan) plan).getRegionInfo(); + try { + quotaManager.getNamespaceQuotaManager().checkQuotaToSplitRegion(hri); + } catch (QuotaExceededException qee) { + LOG.debug("Skipping normalization for " + hri + ", as quota would be exceeded"); + normalizer.planSkipped(plan); + continue; + } + } + plan.execute(clusterConnection.getAdmin()); } } // If Region did not generate any plans, it means the cluster is already balanced. diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/EmptyNormalizationPlan.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/EmptyNormalizationPlan.java index a36dd07..5aecc48 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/EmptyNormalizationPlan.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/EmptyNormalizationPlan.java @@ -20,6 +20,7 @@ package org.apache.hadoop.hbase.master.normalizer; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan.PlanType; /** * Plan which signifies that no normalization is required, @@ -45,4 +46,9 @@ public final class EmptyNormalizationPlan implements NormalizationPlan { @Override public void execute(Admin admin) { } + + @Override + public PlanType getType() { + return PlanType.NONE; + } } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/MergeNormalizationPlan.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/MergeNormalizationPlan.java index 08a58a5..e2035bb 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/MergeNormalizationPlan.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/MergeNormalizationPlan.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan.PlanType; import java.io.IOException; @@ -41,6 +42,11 @@ public class MergeNormalizationPlan implements NormalizationPlan { this.secondRegion = secondRegion; } + @Override + public PlanType getType() { + return PlanType.MERGE; + } + HRegionInfo getFirstRegion() { return firstRegion; } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/NormalizationPlan.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/NormalizationPlan.java index 96eed8c..9f866d3 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/NormalizationPlan.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/NormalizationPlan.java @@ -26,10 +26,20 @@ import org.apache.hadoop.hbase.client.Admin; */ @InterfaceAudience.Private public interface NormalizationPlan { + enum PlanType { + SPLIT, + MERGE, + NONE + } /** * Executes normalization plan on cluster (does actual splitting/merging work). * @param admin instance of Admin */ void execute(Admin admin); + + /** + * @return the type of this plan + */ + PlanType getType(); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizer.java index 19abcf2..31e6e00 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/RegionNormalizer.java @@ -48,4 +48,17 @@ public interface RegionNormalizer { * @return Next (perhaps most urgent) normalization action to perform */ NormalizationPlan computePlanForTable(TableName table) throws HBaseIOException; + + /** + * Notification for the case where plan couldn't be executed due to constraint violation, such as + * namespace quota + * @param plan + */ + void planSkipped(NormalizationPlan plan); + + /** + * @param type + * @return the count of plans of specified type which were skipped + */ + long getSkippedCount(NormalizationPlan.PlanType type); } diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.java index fe6034b..102edc4 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SimpleRegionNormalizer.java @@ -59,6 +59,7 @@ public class SimpleRegionNormalizer implements RegionNormalizer { private static final Log LOG = LogFactory.getLog(SimpleRegionNormalizer.class); private static final int MIN_REGION_COUNT = 3; private MasterServices masterServices; + private static long[] skippedCount = new long[NormalizationPlan.PlanType.values().length]; /** * Set the master service. @@ -85,6 +86,16 @@ public class SimpleRegionNormalizer implements RegionNormalizer { } }; + @Override + public void planSkipped(NormalizationPlan plan) { + skippedCount[plan.getType().ordinal()]++; + } + + @Override + public long getSkippedCount(NormalizationPlan.PlanType type) { + return skippedCount[type.ordinal()]; + } + /** * Computes next most "urgent" normalization action on the table. * Action may be either a split, or a merge, or no action. diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SplitNormalizationPlan.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SplitNormalizationPlan.java index c96988a..b95bfb7 100644 --- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SplitNormalizationPlan.java +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/normalizer/SplitNormalizationPlan.java @@ -23,6 +23,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.hadoop.hbase.HRegionInfo; import org.apache.hadoop.hbase.classification.InterfaceAudience; import org.apache.hadoop.hbase.client.Admin; +import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan.PlanType; import java.io.IOException; import java.util.Arrays; @@ -42,6 +43,11 @@ public class SplitNormalizationPlan implements NormalizationPlan { this.splitPoint = splitPoint; } + @Override + public PlanType getType() { + return PlanType.SPLIT; + } + public HRegionInfo getRegionInfo() { return regionInfo; } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java index d24023d..3cf635b 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/master/normalizer/TestSimpleRegionNormalizerOnCluster.java @@ -25,11 +25,16 @@ import org.apache.hadoop.hbase.HConstants; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.MetaTableAccessor; import org.apache.hadoop.hbase.MiniHBaseCluster; +import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.HTable; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.master.HMaster; +import org.apache.hadoop.hbase.master.TableNamespaceManager; +import org.apache.hadoop.hbase.master.normalizer.NormalizationPlan.PlanType; +import org.apache.hadoop.hbase.namespace.TestNamespaceAuditor; +import org.apache.hadoop.hbase.quotas.QuotaUtil; import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.regionserver.Region; import org.apache.hadoop.hbase.testclassification.MasterTests; @@ -63,9 +68,11 @@ public class TestSimpleRegionNormalizerOnCluster { // we will retry operations when PleaseHoldException is thrown TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 3); TEST_UTIL.getConfiguration().setBoolean(HConstants.HBASE_NORMALIZER_ENABLED, true); + TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, true); // Start a cluster of two regionservers. TEST_UTIL.startMiniCluster(1); + TestNamespaceAuditor.waitForQuotaEnabled(TEST_UTIL); admin = TEST_UTIL.getHBaseAdmin(); } @@ -74,11 +81,26 @@ public class TestSimpleRegionNormalizerOnCluster { TEST_UTIL.shutdownMiniCluster(); } - @Test(timeout = 60000) + @Test(timeout = 90000) @SuppressWarnings("deprecation") public void testRegionNormalizationSplitOnCluster() throws Exception { - final TableName TABLENAME = - TableName.valueOf("testRegionNormalizationSplitOnCluster"); + testRegionNormalizationSplitOnCluster(false); + testRegionNormalizationSplitOnCluster(true); + } + + void testRegionNormalizationSplitOnCluster(boolean limitedByQuota) throws Exception { + TableName TABLENAME; + if (limitedByQuota) { + String nsp = "np2"; + NamespaceDescriptor nspDesc = + NamespaceDescriptor.create(nsp).addConfiguration(TableNamespaceManager.KEY_MAX_REGIONS, "5") + .addConfiguration(TableNamespaceManager.KEY_MAX_TABLES, "2").build(); + admin.createNamespace(nspDesc); + TABLENAME = TableName.valueOf(nsp + + TableName.NAMESPACE_DELIM + "testRegionNormalizationSplitOnCluster"); + } else { + TABLENAME = TableName.valueOf("testRegionNormalizationSplitOnCluster"); + } MiniHBaseCluster cluster = TEST_UTIL.getHBaseCluster(); HMaster m = cluster.getMaster(); @@ -119,21 +141,22 @@ public class TestSimpleRegionNormalizerOnCluster { admin.flush(TABLENAME); - System.out.println(admin.getTableDescriptor(TABLENAME)); - assertEquals(5, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME)); // Now trigger a split and stop when the split is in progress Thread.sleep(5000); // to let region load to update m.normalizeRegions(); - - while (MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME) < 6) { - LOG.info("Waiting for normalization split to complete"); - Thread.sleep(100); + if (limitedByQuota) { + long skippedSplitcnt = m.getRegionNormalizer().getSkippedCount(PlanType.SPLIT); + assert(skippedSplitcnt > 0); + } else { + while (MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME) < 6) { + LOG.info("Waiting for normalization split to complete"); + Thread.sleep(100); + } + assertEquals(6, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME)); } - assertEquals(6, MetaTableAccessor.getRegionCount(TEST_UTIL.getConnection(), TABLENAME)); - admin.disableTable(TABLENAME); admin.deleteTable(TABLENAME); } diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/namespace/TestNamespaceAuditor.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/namespace/TestNamespaceAuditor.java index 7ded3d3..e3b841b 100644 --- a/hbase-server/src/test/java/org/apache/hadoop/hbase/namespace/TestNamespaceAuditor.java +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/namespace/TestNamespaceAuditor.java @@ -115,7 +115,7 @@ public class TestNamespaceAuditor { conf.setClass("hbase.coprocessor.regionserver.classes", CPRegionServerObserver.class, RegionServerObserver.class); UTIL.startMiniCluster(1, 1); - waitForQuotaEnabled(); + waitForQuotaEnabled(UTIL); ADMIN = UTIL.getHBaseAdmin(); } @@ -620,11 +620,11 @@ public class TestNamespaceAuditor { .getTables().size(), after.getTables().size()); } - private static void waitForQuotaEnabled() throws Exception { - UTIL.waitFor(60000, new Waiter.Predicate() { + public static void waitForQuotaEnabled(final HBaseTestingUtility util) throws Exception { + util.waitFor(60000, new Waiter.Predicate() { @Override public boolean evaluate() throws Exception { - HMaster master = UTIL.getHBaseCluster().getMaster(); + HMaster master = util.getHBaseCluster().getMaster(); if (master == null) { return false; } @@ -638,7 +638,7 @@ public class TestNamespaceAuditor { UTIL.getHBaseCluster().getMaster(0).stop("Stopping to start again"); UTIL.getHBaseCluster().waitOnMaster(0); UTIL.getHBaseCluster().startMaster(); - waitForQuotaEnabled(); + waitForQuotaEnabled(UTIL); } private NamespaceAuditor getQuotaManager() {