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.jupiter.api.Assertions.assertEquals; 021import static org.junit.jupiter.api.Assertions.assertFalse; 022import static org.junit.jupiter.api.Assertions.assertTrue; 023 024import java.io.IOException; 025import java.util.ArrayList; 026import java.util.HashMap; 027import java.util.HashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031import java.util.concurrent.TimeUnit; 032import org.apache.hadoop.hbase.Cell; 033import org.apache.hadoop.hbase.CellScanner; 034import org.apache.hadoop.hbase.HBaseTestingUtil; 035import org.apache.hadoop.hbase.HConstants; 036import org.apache.hadoop.hbase.NamespaceDescriptor; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.client.Connection; 039import org.apache.hadoop.hbase.client.ConnectionFactory; 040import org.apache.hadoop.hbase.client.Delete; 041import org.apache.hadoop.hbase.client.Put; 042import org.apache.hadoop.hbase.client.Result; 043import org.apache.hadoop.hbase.client.ResultScanner; 044import org.apache.hadoop.hbase.client.Table; 045import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot.SpaceQuotaStatus; 046import org.apache.hadoop.hbase.testclassification.MasterTests; 047import org.apache.hadoop.hbase.testclassification.MediumTests; 048import org.junit.jupiter.api.AfterAll; 049import org.junit.jupiter.api.AfterEach; 050import org.junit.jupiter.api.BeforeAll; 051import org.junit.jupiter.api.BeforeEach; 052import org.junit.jupiter.api.Tag; 053import org.junit.jupiter.api.Test; 054import org.junit.jupiter.api.TestInfo; 055 056import org.apache.hbase.thirdparty.com.google.common.collect.HashMultimap; 057import org.apache.hbase.thirdparty.com.google.common.collect.Multimap; 058import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations; 059 060import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 061import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos; 062import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; 063import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Throttle; 064 065/** 066 * Test the quota table helpers (e.g. CRUD operations) 067 */ 068@Tag(MasterTests.TAG) 069@Tag(MediumTests.TAG) 070public class TestQuotaTableUtil { 071 072 private final static HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 073 private Connection connection; 074 private int tableNameCounter; 075 076 @BeforeAll 077 public static void setUpBeforeClass() throws Exception { 078 TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, true); 079 TEST_UTIL.getConfiguration().setInt(QuotaCache.REFRESH_CONF_KEY, 2000); 080 TEST_UTIL.getConfiguration().setInt("hbase.hstore.compactionThreshold", 10); 081 TEST_UTIL.getConfiguration().setInt("hbase.regionserver.msginterval", 100); 082 TEST_UTIL.getConfiguration().setInt("hbase.client.pause", 250); 083 TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6); 084 TEST_UTIL.getConfiguration().setBoolean("hbase.master.enabletable.roundrobin", true); 085 TEST_UTIL.startMiniCluster(1); 086 TEST_UTIL.waitTableAvailable(QuotaTableUtil.QUOTA_TABLE_NAME); 087 } 088 089 @AfterAll 090 public static void tearDownAfterClass() throws Exception { 091 TEST_UTIL.shutdownMiniCluster(); 092 } 093 094 @BeforeEach 095 public void before() throws IOException { 096 this.connection = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration()); 097 this.tableNameCounter = 0; 098 } 099 100 @AfterEach 101 public void after() throws IOException { 102 this.connection.close(); 103 } 104 105 @Test 106 public void testDeleteSnapshots(TestInfo testInfo) throws Exception { 107 String methodName = testInfo.getTestMethod().get().getName(); 108 TableName tn = TableName.valueOf(methodName); 109 try (Table t = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) { 110 Quotas quota = 111 Quotas.newBuilder().setSpace(QuotaProtos.SpaceQuota.newBuilder().setSoftLimit(7L) 112 .setViolationPolicy(QuotaProtos.SpaceViolationPolicy.NO_WRITES).build()).build(); 113 QuotaUtil.addTableQuota(connection, tn, quota); 114 115 String snapshotName = methodName + "_snapshot"; 116 t.put(QuotaTableUtil.createPutForSnapshotSize(tn, snapshotName, 3L)); 117 t.put(QuotaTableUtil.createPutForSnapshotSize(tn, snapshotName, 5L)); 118 assertEquals(1, QuotaTableUtil.getObservedSnapshotSizes(connection).size()); 119 120 List<Delete> deletes = QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(connection); 121 assertEquals(1, deletes.size()); 122 123 t.delete(deletes); 124 assertEquals(0, QuotaTableUtil.getObservedSnapshotSizes(connection).size()); 125 126 String ns = methodName; 127 t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns, 5L)); 128 t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns, 3L)); 129 assertEquals(3L, QuotaTableUtil.getNamespaceSnapshotSize(connection, ns)); 130 131 deletes = QuotaTableUtil.createDeletesForExistingNamespaceSnapshotSizes(connection); 132 assertEquals(1, deletes.size()); 133 134 t.delete(deletes); 135 assertEquals(0L, QuotaTableUtil.getNamespaceSnapshotSize(connection, ns)); 136 137 t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t1"), "s1", 3L)); 138 t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t2"), "s2", 3L)); 139 t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t3"), "s3", 3L)); 140 t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t4"), "s4", 3L)); 141 t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t1"), "s5", 3L)); 142 143 t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize("ns1", 3L)); 144 t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize("ns2", 3L)); 145 t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize("ns3", 3L)); 146 147 assertEquals(5, QuotaTableUtil.getTableSnapshots(connection).size()); 148 assertEquals(3, QuotaTableUtil.getNamespaceSnapshots(connection).size()); 149 150 Multimap<TableName, String> tableSnapshotEntriesToRemove = HashMultimap.create(); 151 tableSnapshotEntriesToRemove.put(TableName.valueOf("t1"), "s1"); 152 tableSnapshotEntriesToRemove.put(TableName.valueOf("t3"), "s3"); 153 tableSnapshotEntriesToRemove.put(TableName.valueOf("t4"), "s4"); 154 155 Set<String> namespaceSnapshotEntriesToRemove = new HashSet<>(); 156 namespaceSnapshotEntriesToRemove.add("ns2"); 157 namespaceSnapshotEntriesToRemove.add("ns1"); 158 159 deletes = 160 QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(tableSnapshotEntriesToRemove); 161 assertEquals(3, deletes.size()); 162 deletes = QuotaTableUtil 163 .createDeletesForExistingNamespaceSnapshotSizes(namespaceSnapshotEntriesToRemove); 164 assertEquals(2, deletes.size()); 165 } 166 } 167 168 @Test 169 public void testTableQuotaUtil(TestInfo testInfo) throws Exception { 170 final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName()); 171 172 Quotas quota = Quotas.newBuilder() 173 .setThrottle(Throttle.newBuilder() 174 .setReqNum(ProtobufUtil.toTimedQuota(1000, TimeUnit.SECONDS, QuotaScope.MACHINE)) 175 .setWriteNum(ProtobufUtil.toTimedQuota(600, TimeUnit.SECONDS, QuotaScope.MACHINE)) 176 .setReadSize(ProtobufUtil.toTimedQuota(8192, TimeUnit.SECONDS, QuotaScope.MACHINE)).build()) 177 .build(); 178 179 // Add user quota and verify it 180 QuotaUtil.addTableQuota(this.connection, tableName, quota); 181 Quotas resQuota = QuotaUtil.getTableQuota(this.connection, tableName); 182 assertEquals(quota, resQuota); 183 184 // Remove user quota and verify it 185 QuotaUtil.deleteTableQuota(this.connection, tableName); 186 resQuota = QuotaUtil.getTableQuota(this.connection, tableName); 187 assertEquals(null, resQuota); 188 } 189 190 @Test 191 public void testNamespaceQuotaUtil() throws Exception { 192 final String namespace = "testNamespaceQuotaUtilNS"; 193 194 Quotas quota = Quotas.newBuilder() 195 .setThrottle(Throttle.newBuilder() 196 .setReqNum(ProtobufUtil.toTimedQuota(1000, TimeUnit.SECONDS, QuotaScope.MACHINE)) 197 .setWriteNum(ProtobufUtil.toTimedQuota(600, TimeUnit.SECONDS, QuotaScope.MACHINE)) 198 .setReadSize(ProtobufUtil.toTimedQuota(8192, TimeUnit.SECONDS, QuotaScope.MACHINE)).build()) 199 .build(); 200 201 // Add user quota and verify it 202 QuotaUtil.addNamespaceQuota(this.connection, namespace, quota); 203 Quotas resQuota = QuotaUtil.getNamespaceQuota(this.connection, namespace); 204 assertEquals(quota, resQuota); 205 206 // Remove user quota and verify it 207 QuotaUtil.deleteNamespaceQuota(this.connection, namespace); 208 resQuota = QuotaUtil.getNamespaceQuota(this.connection, namespace); 209 assertEquals(null, resQuota); 210 } 211 212 @Test 213 public void testUserQuotaUtil(TestInfo testInfo) throws Exception { 214 final TableName tableName = TableName.valueOf(testInfo.getTestMethod().get().getName()); 215 final String namespace = "testNS"; 216 final String user = "testUser"; 217 218 Quotas quotaNamespace = Quotas.newBuilder() 219 .setThrottle(Throttle.newBuilder() 220 .setReqNum(ProtobufUtil.toTimedQuota(50000, TimeUnit.SECONDS, QuotaScope.MACHINE)).build()) 221 .build(); 222 Quotas quotaTable = Quotas.newBuilder().setThrottle(Throttle.newBuilder() 223 .setReqNum(ProtobufUtil.toTimedQuota(1000, TimeUnit.SECONDS, QuotaScope.MACHINE)) 224 .setWriteNum(ProtobufUtil.toTimedQuota(600, TimeUnit.SECONDS, QuotaScope.MACHINE)) 225 .setReadSize(ProtobufUtil.toTimedQuota(10000, TimeUnit.SECONDS, QuotaScope.MACHINE)).build()) 226 .build(); 227 Quotas quota = Quotas.newBuilder() 228 .setThrottle(Throttle.newBuilder() 229 .setReqSize(ProtobufUtil.toTimedQuota(8192, TimeUnit.SECONDS, QuotaScope.MACHINE)) 230 .setWriteSize(ProtobufUtil.toTimedQuota(4096, TimeUnit.SECONDS, QuotaScope.MACHINE)) 231 .setReadNum(ProtobufUtil.toTimedQuota(1000, TimeUnit.SECONDS, QuotaScope.MACHINE)).build()) 232 .build(); 233 234 // Add user global quota 235 QuotaUtil.addUserQuota(this.connection, user, quota); 236 Quotas resQuota = QuotaUtil.getUserQuota(this.connection, user); 237 assertEquals(quota, resQuota); 238 239 // Add user quota for table 240 QuotaUtil.addUserQuota(this.connection, user, tableName, quotaTable); 241 Quotas resQuotaTable = QuotaUtil.getUserQuota(this.connection, user, tableName); 242 assertEquals(quotaTable, resQuotaTable); 243 244 // Add user quota for namespace 245 QuotaUtil.addUserQuota(this.connection, user, namespace, quotaNamespace); 246 Quotas resQuotaNS = QuotaUtil.getUserQuota(this.connection, user, namespace); 247 assertEquals(quotaNamespace, resQuotaNS); 248 249 // Delete user global quota 250 QuotaUtil.deleteUserQuota(this.connection, user); 251 resQuota = QuotaUtil.getUserQuota(this.connection, user); 252 assertEquals(null, resQuota); 253 254 // Delete user quota for table 255 QuotaUtil.deleteUserQuota(this.connection, user, tableName); 256 resQuotaTable = QuotaUtil.getUserQuota(this.connection, user, tableName); 257 assertEquals(null, resQuotaTable); 258 259 // Delete user quota for namespace 260 QuotaUtil.deleteUserQuota(this.connection, user, namespace); 261 resQuotaNS = QuotaUtil.getUserQuota(this.connection, user, namespace); 262 assertEquals(null, resQuotaNS); 263 } 264 265 @Test 266 public void testSerDeViolationPolicies(TestInfo testInfo) throws Exception { 267 final TableName tn1 = getUniqueTableName(testInfo); 268 final SpaceQuotaSnapshot snapshot1 = 269 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.DISABLE), 512L, 1024L); 270 final TableName tn2 = getUniqueTableName(testInfo); 271 final SpaceQuotaSnapshot snapshot2 = 272 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_INSERTS), 512L, 1024L); 273 final TableName tn3 = getUniqueTableName(testInfo); 274 final SpaceQuotaSnapshot snapshot3 = 275 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_WRITES), 512L, 1024L); 276 List<Put> puts = new ArrayList<>(); 277 puts.add(QuotaTableUtil.createPutForSpaceSnapshot(tn1, snapshot1)); 278 puts.add(QuotaTableUtil.createPutForSpaceSnapshot(tn2, snapshot2)); 279 puts.add(QuotaTableUtil.createPutForSpaceSnapshot(tn3, snapshot3)); 280 final Map<TableName, SpaceQuotaSnapshot> expectedPolicies = new HashMap<>(); 281 expectedPolicies.put(tn1, snapshot1); 282 expectedPolicies.put(tn2, snapshot2); 283 expectedPolicies.put(tn3, snapshot3); 284 285 final Map<TableName, SpaceQuotaSnapshot> actualPolicies = new HashMap<>(); 286 try (Table quotaTable = connection.getTable(QuotaUtil.QUOTA_TABLE_NAME)) { 287 quotaTable.put(puts); 288 ResultScanner scanner = quotaTable.getScanner(QuotaTableUtil.makeQuotaSnapshotScan()); 289 for (Result r : scanner) { 290 QuotaTableUtil.extractQuotaSnapshot(r, actualPolicies); 291 } 292 scanner.close(); 293 } 294 295 assertEquals(expectedPolicies, actualPolicies); 296 } 297 298 @Test 299 public void testSerdeTableSnapshotSizes() throws Exception { 300 TableName tn1 = TableName.valueOf("tn1"); 301 TableName tn2 = TableName.valueOf("tn2"); 302 try (Table quotaTable = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) { 303 for (int i = 0; i < 3; i++) { 304 Put p = QuotaTableUtil.createPutForSnapshotSize(tn1, "tn1snap" + i, 1024L * (1 + i)); 305 quotaTable.put(p); 306 } 307 for (int i = 0; i < 3; i++) { 308 Put p = QuotaTableUtil.createPutForSnapshotSize(tn2, "tn2snap" + i, 2048L * (1 + i)); 309 quotaTable.put(p); 310 } 311 312 verifyTableSnapshotSize(quotaTable, tn1, "tn1snap0", 1024L); 313 verifyTableSnapshotSize(quotaTable, tn1, "tn1snap1", 2048L); 314 verifyTableSnapshotSize(quotaTable, tn1, "tn1snap2", 3072L); 315 316 verifyTableSnapshotSize(quotaTable, tn2, "tn2snap0", 2048L); 317 verifyTableSnapshotSize(quotaTable, tn2, "tn2snap1", 4096L); 318 verifyTableSnapshotSize(quotaTable, tn2, "tn2snap2", 6144L); 319 320 cleanUpSnapshotSizes(); 321 } 322 } 323 324 @Test 325 public void testReadNamespaceSnapshotSizes() throws Exception { 326 String ns1 = "ns1"; 327 String ns2 = "ns2"; 328 String defaultNs = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR; 329 try (Table quotaTable = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) { 330 quotaTable.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns1, 1024L)); 331 quotaTable.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns2, 2048L)); 332 quotaTable.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(defaultNs, 8192L)); 333 334 assertEquals(1024L, QuotaTableUtil.getNamespaceSnapshotSize(connection, ns1)); 335 assertEquals(2048L, QuotaTableUtil.getNamespaceSnapshotSize(connection, ns2)); 336 assertEquals(8192L, QuotaTableUtil.getNamespaceSnapshotSize(connection, defaultNs)); 337 338 cleanUpSnapshotSizes(); 339 } 340 } 341 342 private TableName getUniqueTableName(TestInfo testInfo) { 343 return TableName.valueOf(testInfo.getTestMethod().get().getName() + "_" + tableNameCounter++); 344 } 345 346 private void verifyTableSnapshotSize(Table quotaTable, TableName tn, String snapshotName, 347 long expectedSize) throws IOException { 348 Result r = quotaTable.get(QuotaTableUtil.makeGetForSnapshotSize(tn, snapshotName)); 349 CellScanner cs = r.cellScanner(); 350 assertTrue(cs.advance()); 351 Cell c = cs.current(); 352 assertEquals(expectedSize, 353 QuotaProtos.SpaceQuotaSnapshot.parseFrom( 354 UnsafeByteOperations.unsafeWrap(c.getValueArray(), c.getValueOffset(), c.getValueLength())) 355 .getQuotaUsage()); 356 assertFalse(cs.advance()); 357 } 358 359 private void cleanUpSnapshotSizes() throws IOException { 360 try (Table t = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) { 361 QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(connection); 362 List<Delete> deletes = 363 QuotaTableUtil.createDeletesForExistingNamespaceSnapshotSizes(connection); 364 deletes.addAll(QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(connection)); 365 t.delete(deletes); 366 } 367 } 368}