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.assertNull; 022import static org.junit.Assert.fail; 023 024import java.io.IOException; 025import java.security.PrivilegedExceptionAction; 026import java.util.Map; 027import java.util.concurrent.Callable; 028import java.util.concurrent.atomic.AtomicLong; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.DoNotRetryIOException; 031import org.apache.hadoop.hbase.HBaseClassTestRule; 032import org.apache.hadoop.hbase.HBaseTestingUtility; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.Waiter; 035import org.apache.hadoop.hbase.Waiter.Predicate; 036import org.apache.hadoop.hbase.client.Admin; 037import org.apache.hadoop.hbase.client.Connection; 038import org.apache.hadoop.hbase.client.ConnectionFactory; 039import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 040import org.apache.hadoop.hbase.regionserver.HRegionServer; 041import org.apache.hadoop.hbase.security.access.AccessControlClient; 042import org.apache.hadoop.hbase.security.access.AccessController; 043import org.apache.hadoop.hbase.security.access.Permission.Action; 044import org.apache.hadoop.hbase.testclassification.MediumTests; 045import org.apache.hadoop.security.UserGroupInformation; 046import org.junit.AfterClass; 047import org.junit.Before; 048import org.junit.BeforeClass; 049import org.junit.ClassRule; 050import org.junit.Rule; 051import org.junit.Test; 052import org.junit.experimental.categories.Category; 053import org.junit.rules.TestName; 054import org.slf4j.Logger; 055import org.slf4j.LoggerFactory; 056 057/** 058 * Test class to verify that the HBase superuser can override quotas. 059 */ 060@Category(MediumTests.class) 061public class TestSuperUserQuotaPermissions { 062 063 @ClassRule 064 public static final HBaseClassTestRule CLASS_RULE = 065 HBaseClassTestRule.forClass(TestSuperUserQuotaPermissions.class); 066 067 private static final Logger LOG = LoggerFactory.getLogger(TestSuperUserQuotaPermissions.class); 068 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 069 // Default to the user running the tests 070 private static final String SUPERUSER_NAME = System.getProperty("user.name"); 071 private static final UserGroupInformation SUPERUSER_UGI = 072 UserGroupInformation.createUserForTesting(SUPERUSER_NAME, new String[0]); 073 private static final String REGULARUSER_NAME = "quota_regularuser"; 074 private static final UserGroupInformation REGULARUSER_UGI = 075 UserGroupInformation.createUserForTesting(REGULARUSER_NAME, new String[0]); 076 private static final AtomicLong COUNTER = new AtomicLong(0); 077 078 @Rule 079 public TestName testName = new TestName(); 080 private SpaceQuotaHelperForTests helper; 081 082 @BeforeClass 083 public static void setupMiniCluster() throws Exception { 084 Configuration conf = TEST_UTIL.getConfiguration(); 085 // Increase the frequency of some of the chores for responsiveness of the test 086 SpaceQuotaHelperForTests.updateConfigForQuotas(conf); 087 088 conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); 089 conf.set(CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, AccessController.class.getName()); 090 conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, AccessController.class.getName()); 091 conf.setBoolean("hbase.security.exec.permission.checks", true); 092 conf.setBoolean("hbase.security.authorization", true); 093 conf.set("hbase.superuser", SUPERUSER_NAME); 094 095 TEST_UTIL.startMiniCluster(1); 096 } 097 098 @AfterClass 099 public static void tearDown() throws Exception { 100 TEST_UTIL.shutdownMiniCluster(); 101 } 102 103 @Before 104 public void removeAllQuotas() throws Exception { 105 final Connection conn = TEST_UTIL.getConnection(); 106 if (helper == null) { 107 helper = new SpaceQuotaHelperForTests(TEST_UTIL, testName, COUNTER); 108 } 109 // Wait for the quota table to be created 110 if (!conn.getAdmin().tableExists(QuotaUtil.QUOTA_TABLE_NAME)) { 111 helper.waitForQuotaTable(conn); 112 } else { 113 // Or, clean up any quotas from previous test runs. 114 helper.removeAllQuotas(conn); 115 assertEquals(0, helper.listNumDefinedQuotas(conn)); 116 } 117 } 118 119 @Test 120 public void testSuperUserCanStillCompact() throws Exception { 121 // Create a table and write enough data to push it into quota violation 122 final TableName tn = doAsSuperUser(new Callable<TableName>() { 123 @Override 124 public TableName call() throws Exception { 125 try (Connection conn = getConnection()) { 126 Admin admin = conn.getAdmin(); 127 final TableName tn = helper.createTableWithRegions(admin, 5); 128 final long sizeLimit = 2L * SpaceQuotaHelperForTests.ONE_MEGABYTE; 129 QuotaSettings settings = QuotaSettingsFactory.limitTableSpace( 130 tn, sizeLimit, SpaceViolationPolicy.NO_WRITES_COMPACTIONS); 131 admin.setQuota(settings); 132 // Grant the normal user permissions 133 try { 134 AccessControlClient.grant( 135 conn, tn, REGULARUSER_NAME, null, null, Action.READ, Action.WRITE); 136 } catch (Throwable t) { 137 if (t instanceof Exception) { 138 throw (Exception) t; 139 } 140 throw new Exception(t); 141 } 142 return tn; 143 } 144 } 145 }); 146 147 // Write a bunch of data as our end-user 148 doAsRegularUser(new Callable<Void>() { 149 @Override 150 public Void call() throws Exception { 151 try (Connection conn = getConnection()) { 152 helper.writeData(tn, 3L * SpaceQuotaHelperForTests.ONE_MEGABYTE); 153 return null; 154 } 155 } 156 }); 157 158 waitForTableToEnterQuotaViolation(tn); 159 160 // Should throw an exception, unprivileged users cannot compact due to the quota 161 try { 162 doAsRegularUser(new Callable<Void>() { 163 @Override 164 public Void call() throws Exception { 165 try (Connection conn = getConnection()) { 166 conn.getAdmin().majorCompact(tn); 167 return null; 168 } 169 } 170 }); 171 fail("Expected an exception trying to compact a table with a quota violation"); 172 } catch (DoNotRetryIOException e) { 173 // Expected 174 } 175 176 // Should not throw an exception (superuser can do anything) 177 doAsSuperUser(new Callable<Void>() { 178 @Override 179 public Void call() throws Exception { 180 try (Connection conn = getConnection()) { 181 conn.getAdmin().majorCompact(tn); 182 return null; 183 } 184 } 185 }); 186 } 187 188 @Test 189 public void testSuperuserCanRemoveQuota() throws Exception { 190 // Create a table and write enough data to push it into quota violation 191 final TableName tn = doAsSuperUser(new Callable<TableName>() { 192 @Override 193 public TableName call() throws Exception { 194 try (Connection conn = getConnection()) { 195 final Admin admin = conn.getAdmin(); 196 final TableName tn = helper.createTableWithRegions(admin, 5); 197 final long sizeLimit = 2L * SpaceQuotaHelperForTests.ONE_MEGABYTE; 198 QuotaSettings settings = QuotaSettingsFactory.limitTableSpace( 199 tn, sizeLimit, SpaceViolationPolicy.NO_WRITES_COMPACTIONS); 200 admin.setQuota(settings); 201 // Grant the normal user permission to create a table and set a quota 202 try { 203 AccessControlClient.grant( 204 conn, tn, REGULARUSER_NAME, null, null, Action.READ, Action.WRITE); 205 } catch (Throwable t) { 206 if (t instanceof Exception) { 207 throw (Exception) t; 208 } 209 throw new Exception(t); 210 } 211 return tn; 212 } 213 } 214 }); 215 216 // Write a bunch of data as our end-user 217 doAsRegularUser(new Callable<Void>() { 218 @Override 219 public Void call() throws Exception { 220 try (Connection conn = getConnection()) { 221 helper.writeData(tn, 3L * SpaceQuotaHelperForTests.ONE_MEGABYTE); 222 return null; 223 } 224 } 225 }); 226 227 // Wait for the table to hit quota violation 228 waitForTableToEnterQuotaViolation(tn); 229 230 // Try to be "bad" and remove the quota as the end user (we want to write more data!) 231 doAsRegularUser(new Callable<Void>() { 232 @Override 233 public Void call() throws Exception { 234 try (Connection conn = getConnection()) { 235 final Admin admin = conn.getAdmin(); 236 QuotaSettings qs = QuotaSettingsFactory.removeTableSpaceLimit(tn); 237 try { 238 admin.setQuota(qs); 239 fail("Expected that an unprivileged user should not be allowed to remove a quota"); 240 } catch (Exception e) { 241 // pass 242 } 243 return null; 244 } 245 } 246 }); 247 248 // Verify that the superuser can remove the quota 249 doAsSuperUser(new Callable<Void>() { 250 @Override 251 public Void call() throws Exception { 252 try (Connection conn = getConnection()) { 253 final Admin admin = conn.getAdmin(); 254 QuotaSettings qs = QuotaSettingsFactory.removeTableSpaceLimit(tn); 255 admin.setQuota(qs); 256 assertNull(helper.getTableSpaceQuota(conn, tn)); 257 return null; 258 } 259 } 260 }); 261 } 262 263 private Connection getConnection() throws IOException { 264 return ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); 265 } 266 267 private <T> T doAsSuperUser(Callable<T> task) throws Exception { 268 return doAsUser(SUPERUSER_UGI, task); 269 } 270 271 private <T> T doAsRegularUser(Callable<T> task) throws Exception { 272 return doAsUser(REGULARUSER_UGI, task); 273 } 274 275 private <T> T doAsUser(UserGroupInformation ugi, Callable<T> task) throws Exception { 276 return ugi.doAs(new PrivilegedExceptionAction<T>() { 277 @Override 278 public T run() throws Exception { 279 return task.call(); 280 } 281 }); 282 } 283 284 private void waitForTableToEnterQuotaViolation(TableName tn) throws Exception { 285 // Verify that the RegionServer has the quota in violation 286 final HRegionServer rs = TEST_UTIL.getHBaseCluster().getRegionServer(0); 287 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, 1000, new Predicate<Exception>() { 288 @Override 289 public boolean evaluate() throws Exception { 290 Map<TableName,SpaceQuotaSnapshot> snapshots = 291 rs.getRegionServerSpaceQuotaManager().copyQuotaSnapshots(); 292 SpaceQuotaSnapshot snapshot = snapshots.get(tn); 293 if (snapshot == null) { 294 LOG.info("Found no snapshot for " + tn); 295 return false; 296 } 297 LOG.info("Found snapshot " + snapshot); 298 return snapshot.getQuotaStatus().isInViolation(); 299 } 300 }); 301 } 302}