-
Notifications
You must be signed in to change notification settings - Fork 3.4k
HBASE-20821 Space Quota: Re-creating a dropped namespace and contained table inherits previously set space quota settings #571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -30,6 +30,7 @@ | |
import org.apache.hadoop.hbase.HColumnDescriptor; | ||
import org.apache.hadoop.hbase.HConstants; | ||
import org.apache.hadoop.hbase.HTableDescriptor; | ||
import org.apache.hadoop.hbase.NamespaceNotFoundException; | ||
import org.apache.hadoop.hbase.TableName; | ||
import org.apache.hadoop.hbase.TableNotDisabledException; | ||
import org.apache.hadoop.hbase.TableNotEnabledException; | ||
|
@@ -117,6 +118,19 @@ public static void addNamespaceQuota(final Connection connection, final String n | |
|
||
public static void deleteNamespaceQuota(final Connection connection, final String namespace) | ||
throws IOException { | ||
// Before removing namespace quota , remove quota from the tables inside the namespace | ||
// which does not have explicit space quotas defined on them. | ||
TableName[] tableNames = QuotaUtil.listTableNamesByNamepsace(connection, namespace); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This case should never happen. You can't delete a namespace which has tables in it. If we're cleaning up a namespace, we shouldn't have any table-level quotas hanging around. Please log a |
||
if (tableNames != null) { | ||
for (int i = 0; i < tableNames.length; i++) { | ||
QuotaSettings quotaSettings = getTableSpaceQuota(connection, tableNames[i]); | ||
// if quotasettings is null, no space quota explicitly set on it. | ||
// Remove entry from 'hbase:quota' | ||
if (quotaSettings == null) { | ||
deleteQuotas(connection, getTableRowKey(tableNames[i])); | ||
} | ||
} | ||
} | ||
deleteQuotas(connection, getNamespaceRowKey(namespace)); | ||
} | ||
|
||
|
@@ -456,6 +470,46 @@ private static interface KeyFromRow<T> { | |
double getFactor(T t); | ||
} | ||
|
||
/** | ||
* Method to return the space quotas defined on a given table. | ||
* | ||
* @param conn connection | ||
* @param tn tablename | ||
* @return returns space quota settings defined on the table tn otherwise null. | ||
* @throws IOException throws IOException | ||
*/ | ||
public static QuotaSettings getTableSpaceQuota(Connection conn, TableName tn) throws IOException { | ||
try (QuotaRetriever scanner = QuotaRetriever | ||
.open(conn.getConfiguration(), new QuotaFilter().setTableFilter(tn.getNameAsString()))) { | ||
for (QuotaSettings setting : scanner) { | ||
if (setting.getTableName().equals(tn) && setting.getQuotaType() == QuotaType.SPACE) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Checking the table name is unnecessary if you set the table filter above. |
||
return setting; | ||
} | ||
} | ||
return null; | ||
} | ||
} | ||
|
||
/** | ||
* Retrieve list of tables for the given namespace. | ||
* | ||
* @param connection Connection | ||
* @param namespace name of the namespace | ||
* @return list of tables present inside the namespace otherwise returns null. | ||
* @throws IOException throws IOException | ||
*/ | ||
public static TableName[] listTableNamesByNamepsace(final Connection connection, String namespace) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems unnecessary to break this out into its own method if the only consumer is |
||
throws IOException { | ||
try { | ||
TableName[] tableNames = connection.getAdmin().listTableNamesByNamespace(namespace); | ||
return tableNames; | ||
} catch (NamespaceNotFoundException ns) { | ||
LOG.warn( | ||
" Namespace " + namespace + "does not exist. Can't return tables inside the namespace"); | ||
return null; | ||
} | ||
} | ||
|
||
/* ========================================================================= | ||
* HTable helpers | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -271,6 +271,17 @@ void setQuotaLimit(final TableName tn, SpaceViolationPolicy policy, long sizeInM | |
LOG.debug("Quota limit set for table = {}, limit = {}", tn, sizeLimit); | ||
} | ||
|
||
/** | ||
* Sets the given quota (policy & limit) on the passed namespace. | ||
*/ | ||
void setQuotaLimitNamespace(final String namespace, SpaceViolationPolicy policy, long sizeInMBs) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you please fix e.g. |
||
throws Exception { | ||
final long sizeLimit = sizeInMBs * SpaceQuotaHelperForTests.ONE_MEGABYTE; | ||
QuotaSettings settings = QuotaSettingsFactory.limitNamespaceSpace(namespace, sizeLimit, policy); | ||
testUtil.getAdmin().setQuota(settings); | ||
LOG.debug("Quota limit set for namespace = {}, limit = {}", namespace, sizeLimit); | ||
} | ||
|
||
/** | ||
* Removes the space quota from the given table | ||
*/ | ||
|
@@ -280,6 +291,15 @@ void removeQuotaFromtable(final TableName tn) throws Exception { | |
LOG.debug("Space quota settings removed from the table ", tn); | ||
} | ||
|
||
/** | ||
* Removes the space quota from the given namespace | ||
*/ | ||
void removeQuotaFromNamespace(final String namespace) throws Exception { | ||
QuotaSettings removeQuota = QuotaSettingsFactory.removeNamespaceSpaceLimit(namespace); | ||
testUtil.getAdmin().setQuota(removeQuota); | ||
LOG.debug("Space quota settings removed from the namespace ", namespace); | ||
} | ||
|
||
/** | ||
* Removes all quotas defined in the HBase quota table. | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/** | ||
* Licensed to the Apache Software Foundation (ASF) under one | ||
* or more contributor license agreements. See the NOTICE file | ||
* distributed with this work for additional information | ||
* regarding copyright ownership. The ASF licenses this file | ||
* to you under the Apache License, Version 2.0 (the | ||
* "License"); you may not use this file except in compliance | ||
* with the License. You may obtain a copy of the License at | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package org.apache.hadoop.hbase.quotas; | ||
|
||
|
||
import java.util.Map; | ||
import java.util.concurrent.atomic.AtomicLong; | ||
|
||
import org.apache.hadoop.conf.Configuration; | ||
import org.apache.hadoop.hbase.HBaseClassTestRule; | ||
import org.apache.hadoop.hbase.HBaseTestingUtility; | ||
import org.apache.hadoop.hbase.NamespaceDescriptor; | ||
import org.apache.hadoop.hbase.TableName; | ||
import org.apache.hadoop.hbase.testclassification.MediumTests; | ||
import org.junit.After; | ||
import org.junit.AfterClass; | ||
import org.junit.Assert; | ||
import org.junit.Before; | ||
import org.junit.BeforeClass; | ||
import org.junit.ClassRule; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.experimental.categories.Category; | ||
import org.junit.rules.TestName; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
@Category(MediumTests.class) | ||
public class TestSpaceQuotasAtNamespaceLevel { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't feel like this is testing the bug you originally described. I would expect to see a test:
|
||
|
||
@ClassRule | ||
public static final HBaseClassTestRule CLASS_RULE = | ||
HBaseClassTestRule.forClass(TestSpaceQuotasAtNamespaceLevel.class); | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(TestSpaceQuotasAtNamespaceLevel.class); | ||
private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); | ||
|
||
@Rule | ||
public TestName testName = new TestName(); | ||
private SpaceQuotaHelperForTests helper; | ||
|
||
@BeforeClass | ||
public static void setUp() throws Exception { | ||
Configuration conf = TEST_UTIL.getConfiguration(); | ||
SpaceQuotaHelperForTests.updateConfigForQuotas(conf); | ||
TEST_UTIL.startMiniCluster(1); | ||
} | ||
|
||
@AfterClass | ||
public static void tearDown() throws Exception { | ||
TEST_UTIL.shutdownMiniCluster(); | ||
} | ||
|
||
@Before | ||
public void removeAllQuotas() throws Exception { | ||
helper = new SpaceQuotaHelperForTests(TEST_UTIL, testName, new AtomicLong(0)); | ||
helper.removeAllQuotas(); | ||
} | ||
|
||
@After | ||
public void removeQuotas() throws Exception { | ||
helper.removeAllQuotas(); | ||
} | ||
|
||
@Test | ||
public void testSetNamespaceQuotaAndRemove() throws Exception { | ||
NamespaceDescriptor nd = helper.createNamespace(); | ||
TableName table = helper.createTableInNamespace(nd); | ||
|
||
// Set quota on namespace. | ||
helper.setQuotaLimitNamespace(nd.getName(), SpaceViolationPolicy.NO_WRITES, 2L); | ||
|
||
// Sufficient time for all the chores to run | ||
Thread.sleep(5000); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👎 please change this to explicitly poll the state that you expect to see. There are lots of other quota test examples which show how to wait on the state to change (e.g. |
||
|
||
// Get Current Snapshot from 'hbase:quota' | ||
Map<TableName, SpaceQuotaSnapshot> snapshotMap = | ||
QuotaTableUtil.getSnapshots(TEST_UTIL.getConnection()); | ||
|
||
// After setting quota on namespace, 'hbase:quota' should have some entries present. | ||
Assert.assertEquals(1, snapshotMap.size()); | ||
|
||
helper.removeQuotaFromNamespace(nd.getName()); | ||
|
||
// Get Current Snapshot from 'hbase:quota' | ||
snapshotMap = QuotaTableUtil.getSnapshots(TEST_UTIL.getConnection()); | ||
|
||
// After removing quota on namespace, 'hbase:quota' should not have any entry present. | ||
Assert.assertEquals(0, snapshotMap.size()); | ||
|
||
// drop table and namespace. | ||
TEST_UTIL.getAdmin().disableTable(table); | ||
TEST_UTIL.getAdmin().deleteTable(table); | ||
TEST_UTIL.getAdmin().deleteNamespace(nd.getName()); | ||
} | ||
|
||
@Test | ||
public void testDropTableInNamespaceQuota() throws Exception { | ||
NamespaceDescriptor nd = helper.createNamespace(); | ||
TableName table = helper.createTableInNamespace(nd); | ||
|
||
// Set quota on namespace. | ||
helper.setQuotaLimitNamespace(nd.getName(), SpaceViolationPolicy.NO_WRITES, 2L); | ||
|
||
// write some data. | ||
helper.writeData(table,SpaceQuotaHelperForTests.ONE_KILOBYTE); | ||
|
||
// Sufficient time for all the chores to run | ||
Thread.sleep(5000); | ||
|
||
// Get Current Snapshot from 'hbase:quota' | ||
Map<TableName, SpaceQuotaSnapshot> snapshotMap = | ||
QuotaTableUtil.getSnapshots(TEST_UTIL.getConnection()); | ||
|
||
// Table before drop should have entry in 'hbase:quota' | ||
Assert.assertTrue(snapshotMap.containsKey(table)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please validate that the |
||
|
||
TEST_UTIL.getAdmin().disableTable(table); | ||
TEST_UTIL.getAdmin().deleteTable(table); | ||
|
||
// Get Current Snapshot from 'hbase:quota' | ||
snapshotMap = QuotaTableUtil.getSnapshots(TEST_UTIL.getConnection()); | ||
|
||
// Table after drop should not have entry in 'hbase:quota' | ||
Assert.assertFalse(snapshotMap.containsKey(table)); | ||
|
||
//drop Namepsace. | ||
TEST_UTIL.getAdmin().deleteNamespace(nd.getName()); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this in
postDeleteTable
? The quota should be dropped if the namespace is dropped, which should happen inpostDeleteNamespace
, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because even though table is dropped it's entry is still present in the 'hbase:quota' table because of the namespace quota. If we create another table with same name inside that namespace it inherits the earlier quota settings. So during dropping the table we need to remove that entry/setting from the hbase:quota for our table even though quota was present at namespace level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's by design, not a bug. If you set a quota on a namespace (as opposed to a quota on the table), you would automatically get that quota for all tables in that namespace.
This allows an organization to be given free-rule over some namespace while still enforcing an upper-bound on the allowed resources in HBase. e.g. a namespace space quota of 200G would allow 200G of data in any number of tables in that namespace, not mattering which table uses that quota.
Now, if you drop a namespace and the quota still remains, that's a bug, but I don't think that's what you're trying to solve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi Josh, thanks for the review , I have one doubt.
Is this the intended behaviour?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So let's say you have
t1
andt1'
which are the two instances of the tables as you describe.If I had to guess, when you create
t1'
, the system would still see thet1
's region size reports for a period of time (untilt1'
reports its for the first time).If this is indeed what's happening, the fix would be to purge the region size reports for a table when it's deleted, not try to alter the state of
hbase:quota
. Region size reports are held in memory inside of the active HBase Master inside of the classMasterQuotaManager
. There is no quota defined at the table level, so, again, I'm not sure what you expect this change to be doing directly. If your test works, I can only imagine that it's by circumstance.