2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to you under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 package org
.apache
.hadoop
.hbase
.quotas
;
19 import static org
.junit
.Assert
.assertEquals
;
20 import static org
.junit
.Assert
.assertNull
;
21 import static org
.junit
.Assert
.fail
;
23 import java
.io
.IOException
;
24 import java
.security
.PrivilegedExceptionAction
;
26 import java
.util
.concurrent
.Callable
;
27 import java
.util
.concurrent
.atomic
.AtomicLong
;
29 import org
.apache
.commons
.logging
.Log
;
30 import org
.apache
.commons
.logging
.LogFactory
;
31 import org
.apache
.hadoop
.conf
.Configuration
;
32 import org
.apache
.hadoop
.hbase
.DoNotRetryIOException
;
33 import org
.apache
.hadoop
.hbase
.HBaseTestingUtility
;
34 import org
.apache
.hadoop
.hbase
.TableName
;
35 import org
.apache
.hadoop
.hbase
.Waiter
;
36 import org
.apache
.hadoop
.hbase
.Waiter
.Predicate
;
37 import org
.apache
.hadoop
.hbase
.client
.Admin
;
38 import org
.apache
.hadoop
.hbase
.client
.Connection
;
39 import org
.apache
.hadoop
.hbase
.client
.ConnectionFactory
;
40 import org
.apache
.hadoop
.hbase
.coprocessor
.CoprocessorHost
;
41 import org
.apache
.hadoop
.hbase
.regionserver
.HRegionServer
;
42 import org
.apache
.hadoop
.hbase
.security
.access
.AccessControlClient
;
43 import org
.apache
.hadoop
.hbase
.security
.access
.AccessController
;
44 import org
.apache
.hadoop
.hbase
.security
.access
.Permission
.Action
;
45 import org
.apache
.hadoop
.hbase
.testclassification
.MediumTests
;
46 import org
.apache
.hadoop
.security
.UserGroupInformation
;
47 import org
.junit
.AfterClass
;
48 import org
.junit
.Before
;
49 import org
.junit
.BeforeClass
;
50 import org
.junit
.Rule
;
51 import org
.junit
.Test
;
52 import org
.junit
.experimental
.categories
.Category
;
53 import org
.junit
.rules
.TestName
;
56 * Test class to verify that the HBase superuser can override quotas.
58 @Category(MediumTests
.class)
59 public class TestSuperUserQuotaPermissions
{
60 private static final Log LOG
= LogFactory
.getLog(TestSuperUserQuotaPermissions
.class);
61 private static final HBaseTestingUtility TEST_UTIL
= new HBaseTestingUtility();
62 // Default to the user running the tests
63 private static final String SUPERUSER_NAME
= System
.getProperty("user.name");
64 private static final UserGroupInformation SUPERUSER_UGI
=
65 UserGroupInformation
.createUserForTesting(SUPERUSER_NAME
, new String
[0]);
66 private static final String REGULARUSER_NAME
= "quota_regularuser";
67 private static final UserGroupInformation REGULARUSER_UGI
=
68 UserGroupInformation
.createUserForTesting(REGULARUSER_NAME
, new String
[0]);
69 private static final AtomicLong COUNTER
= new AtomicLong(0);
72 public TestName testName
= new TestName();
73 private SpaceQuotaHelperForTests helper
;
76 public static void setupMiniCluster() throws Exception
{
77 Configuration conf
= TEST_UTIL
.getConfiguration();
78 // Increase the frequency of some of the chores for responsiveness of the test
79 SpaceQuotaHelperForTests
.updateConfigForQuotas(conf
);
81 conf
.set(CoprocessorHost
.MASTER_COPROCESSOR_CONF_KEY
, AccessController
.class.getName());
82 conf
.set(CoprocessorHost
.REGION_COPROCESSOR_CONF_KEY
, AccessController
.class.getName());
83 conf
.set(CoprocessorHost
.REGIONSERVER_COPROCESSOR_CONF_KEY
, AccessController
.class.getName());
84 conf
.setBoolean("hbase.security.exec.permission.checks", true);
85 conf
.setBoolean("hbase.security.authorization", true);
86 conf
.set("hbase.superuser", SUPERUSER_NAME
);
88 TEST_UTIL
.startMiniCluster(1);
92 public static void tearDown() throws Exception
{
93 TEST_UTIL
.shutdownMiniCluster();
97 public void removeAllQuotas() throws Exception
{
98 final Connection conn
= TEST_UTIL
.getConnection();
100 helper
= new SpaceQuotaHelperForTests(TEST_UTIL
, testName
, COUNTER
);
102 // Wait for the quota table to be created
103 if (!conn
.getAdmin().tableExists(QuotaUtil
.QUOTA_TABLE_NAME
)) {
104 helper
.waitForQuotaTable(conn
);
106 // Or, clean up any quotas from previous test runs.
107 helper
.removeAllQuotas(conn
);
108 assertEquals(0, helper
.listNumDefinedQuotas(conn
));
113 public void testSuperUserCanStillCompact() throws Exception
{
114 // Create a table and write enough data to push it into quota violation
115 final TableName tn
= doAsSuperUser(new Callable
<TableName
>() {
117 public TableName
call() throws Exception
{
118 try (Connection conn
= getConnection()) {
119 Admin admin
= conn
.getAdmin();
120 final TableName tn
= helper
.createTableWithRegions(admin
, 5);
121 final long sizeLimit
= 2L * SpaceQuotaHelperForTests
.ONE_MEGABYTE
;
122 QuotaSettings settings
= QuotaSettingsFactory
.limitTableSpace(
123 tn
, sizeLimit
, SpaceViolationPolicy
.NO_WRITES_COMPACTIONS
);
124 admin
.setQuota(settings
);
125 // Grant the normal user permissions
127 AccessControlClient
.grant(
128 conn
, tn
, REGULARUSER_NAME
, null, null, Action
.READ
, Action
.WRITE
);
129 } catch (Throwable t
) {
130 if (t
instanceof Exception
) {
133 throw new Exception(t
);
140 // Write a bunch of data as our end-user
141 doAsRegularUser(new Callable
<Void
>() {
143 public Void
call() throws Exception
{
144 try (Connection conn
= getConnection()) {
145 helper
.writeData(tn
, 3L * SpaceQuotaHelperForTests
.ONE_MEGABYTE
);
151 waitForTableToEnterQuotaViolation(tn
);
153 // Should throw an exception, unprivileged users cannot compact due to the quota
155 doAsRegularUser(new Callable
<Void
>() {
157 public Void
call() throws Exception
{
158 try (Connection conn
= getConnection()) {
159 conn
.getAdmin().majorCompact(tn
);
164 fail("Expected an exception trying to compact a table with a quota violation");
165 } catch (DoNotRetryIOException e
) {
169 // Should not throw an exception (superuser can do anything)
170 doAsSuperUser(new Callable
<Void
>() {
172 public Void
call() throws Exception
{
173 try (Connection conn
= getConnection()) {
174 conn
.getAdmin().majorCompact(tn
);
182 public void testSuperuserCanRemoveQuota() throws Exception
{
183 // Create a table and write enough data to push it into quota violation
184 final TableName tn
= doAsSuperUser(new Callable
<TableName
>() {
186 public TableName
call() throws Exception
{
187 try (Connection conn
= getConnection()) {
188 final Admin admin
= conn
.getAdmin();
189 final TableName tn
= helper
.createTableWithRegions(admin
, 5);
190 final long sizeLimit
= 2L * SpaceQuotaHelperForTests
.ONE_MEGABYTE
;
191 QuotaSettings settings
= QuotaSettingsFactory
.limitTableSpace(
192 tn
, sizeLimit
, SpaceViolationPolicy
.NO_WRITES_COMPACTIONS
);
193 admin
.setQuota(settings
);
194 // Grant the normal user permission to create a table and set a quota
196 AccessControlClient
.grant(
197 conn
, tn
, REGULARUSER_NAME
, null, null, Action
.READ
, Action
.WRITE
);
198 } catch (Throwable t
) {
199 if (t
instanceof Exception
) {
202 throw new Exception(t
);
209 // Write a bunch of data as our end-user
210 doAsRegularUser(new Callable
<Void
>() {
212 public Void
call() throws Exception
{
213 try (Connection conn
= getConnection()) {
214 helper
.writeData(tn
, 3L * SpaceQuotaHelperForTests
.ONE_MEGABYTE
);
220 // Wait for the table to hit quota violation
221 waitForTableToEnterQuotaViolation(tn
);
223 // Try to be "bad" and remove the quota as the end user (we want to write more data!)
224 doAsRegularUser(new Callable
<Void
>() {
226 public Void
call() throws Exception
{
227 try (Connection conn
= getConnection()) {
228 final Admin admin
= conn
.getAdmin();
229 QuotaSettings qs
= QuotaSettingsFactory
.removeTableSpaceLimit(tn
);
232 fail("Expected that an unprivileged user should not be allowed to remove a quota");
233 } catch (Exception e
) {
241 // Verify that the superuser can remove the quota
242 doAsSuperUser(new Callable
<Void
>() {
244 public Void
call() throws Exception
{
245 try (Connection conn
= getConnection()) {
246 final Admin admin
= conn
.getAdmin();
247 QuotaSettings qs
= QuotaSettingsFactory
.removeTableSpaceLimit(tn
);
249 assertNull(helper
.getTableSpaceQuota(conn
, tn
));
256 private Connection
getConnection() throws IOException
{
257 return ConnectionFactory
.createConnection(TEST_UTIL
.getConfiguration());
260 private <T
> T
doAsSuperUser(Callable
<T
> task
) throws Exception
{
261 return doAsUser(SUPERUSER_UGI
, task
);
264 private <T
> T
doAsRegularUser(Callable
<T
> task
) throws Exception
{
265 return doAsUser(REGULARUSER_UGI
, task
);
268 private <T
> T
doAsUser(UserGroupInformation ugi
, Callable
<T
> task
) throws Exception
{
269 return ugi
.doAs(new PrivilegedExceptionAction
<T
>() {
270 public T
run() throws Exception
{
276 private void waitForTableToEnterQuotaViolation(TableName tn
) throws Exception
{
277 // Verify that the RegionServer has the quota in violation
278 final HRegionServer rs
= TEST_UTIL
.getHBaseCluster().getRegionServer(0);
279 Waiter
.waitFor(TEST_UTIL
.getConfiguration(), 30 * 1000, 1000, new Predicate
<Exception
>() {
281 public boolean evaluate() throws Exception
{
282 Map
<TableName
,SpaceQuotaSnapshot
> snapshots
=
283 rs
.getRegionServerSpaceQuotaManager().copyQuotaSnapshots();
284 SpaceQuotaSnapshot snapshot
= snapshots
.get(tn
);
285 if (snapshot
== null) {
286 LOG
.info("Found no snapshot for " + tn
);
289 LOG
.info("Found snapshot " + snapshot
);
290 return snapshot
.getQuotaStatus().isInViolation();