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.assertNotNull; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024 025import java.util.Map; 026import java.util.Map.Entry; 027import java.util.concurrent.atomic.AtomicLong; 028import java.util.concurrent.atomic.AtomicReference; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.HBaseTestingUtil; 031import org.apache.hadoop.hbase.TableName; 032import org.apache.hadoop.hbase.Waiter; 033import org.apache.hadoop.hbase.Waiter.Predicate; 034import org.apache.hadoop.hbase.client.Connection; 035import org.apache.hadoop.hbase.client.RegionInfo; 036import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException; 037import org.apache.hadoop.hbase.master.HMaster; 038import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot.SpaceQuotaStatus; 039import org.apache.hadoop.hbase.quotas.policies.MissingSnapshotViolationPolicyEnforcement; 040import org.apache.hadoop.hbase.regionserver.HRegionServer; 041import org.apache.hadoop.hbase.testclassification.MediumTests; 042import org.junit.jupiter.api.AfterAll; 043import org.junit.jupiter.api.BeforeAll; 044import org.junit.jupiter.api.BeforeEach; 045import org.junit.jupiter.api.Tag; 046import org.junit.jupiter.api.Test; 047import org.junit.jupiter.api.TestInfo; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050 051/** 052 * Test class for the quota status RPCs in the master and regionserver. 053 */ 054@Tag(MediumTests.TAG) 055public class TestQuotaStatusRPCs { 056 057 private static final Logger LOG = LoggerFactory.getLogger(TestQuotaStatusRPCs.class); 058 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 059 private static final AtomicLong COUNTER = new AtomicLong(0); 060 061 private SpaceQuotaHelperForTests helper; 062 063 @BeforeAll 064 public static void setUp() throws Exception { 065 Configuration conf = TEST_UTIL.getConfiguration(); 066 // Increase the frequency of some of the chores for responsiveness of the test 067 SpaceQuotaHelperForTests.updateConfigForQuotas(conf); 068 TEST_UTIL.startMiniCluster(1); 069 } 070 071 @AfterAll 072 public static void tearDown() throws Exception { 073 TEST_UTIL.shutdownMiniCluster(); 074 } 075 076 @BeforeEach 077 public void setupForTest(TestInfo testInfo) throws Exception { 078 helper = new SpaceQuotaHelperForTests(TEST_UTIL, () -> testInfo.getTestMethod().get().getName(), 079 COUNTER); 080 } 081 082 @Test 083 public void testRegionSizesFromMaster() throws Exception { 084 final long tableSize = 1024L * 10L; // 10KB 085 final int numRegions = 10; 086 final TableName tn = helper.createTableWithRegions(numRegions); 087 // Will write at least `tableSize` data 088 helper.writeData(tn, tableSize); 089 090 final HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster(); 091 final MasterQuotaManager quotaManager = master.getMasterQuotaManager(); 092 // Make sure the master has all of the reports 093 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 094 @Override 095 public boolean evaluate() throws Exception { 096 Map<RegionInfo, Long> regionSizes = quotaManager.snapshotRegionSizes(); 097 LOG.trace("Region sizes=" + regionSizes); 098 return numRegions == countRegionsForTable(tn, regionSizes) 099 && tableSize <= getTableSize(tn, regionSizes); 100 } 101 }); 102 103 Map<TableName, Long> sizes = TEST_UTIL.getAdmin().getSpaceQuotaTableSizes(); 104 Long size = sizes.get(tn); 105 assertNotNull(size, "No reported size for " + tn); 106 assertTrue(size.longValue() >= tableSize, "Reported table size was " + size); 107 } 108 109 @Test 110 public void testQuotaSnapshotsFromRS() throws Exception { 111 final long sizeLimit = 1024L * 1024L; // 1MB 112 final long tableSize = 1024L * 10L; // 10KB 113 final int numRegions = 10; 114 final TableName tn = helper.createTableWithRegions(numRegions); 115 116 // Define the quota 117 QuotaSettings settings = 118 QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS); 119 TEST_UTIL.getAdmin().setQuota(settings); 120 121 // Write at least `tableSize` data 122 helper.writeData(tn, tableSize); 123 124 final HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0); 125 final RegionServerSpaceQuotaManager manager = rs.getRegionServerSpaceQuotaManager(); 126 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 127 @Override 128 public boolean evaluate() throws Exception { 129 SpaceQuotaSnapshot snapshot = manager.copyQuotaSnapshots().get(tn); 130 if (snapshot == null) { 131 return false; 132 } 133 return snapshot.getUsage() >= tableSize; 134 } 135 }); 136 137 @SuppressWarnings("unchecked") 138 Map<TableName, SpaceQuotaSnapshot> snapshots = (Map<TableName, SpaceQuotaSnapshot>) TEST_UTIL 139 .getAdmin().getRegionServerSpaceQuotaSnapshots(rs.getServerName()); 140 SpaceQuotaSnapshot snapshot = snapshots.get(tn); 141 assertNotNull(snapshot, "Did not find snapshot for " + tn); 142 assertTrue(snapshot.getUsage() >= tableSize, "Observed table usage was " + snapshot.getUsage()); 143 assertEquals(sizeLimit, snapshot.getLimit()); 144 SpaceQuotaStatus pbStatus = snapshot.getQuotaStatus(); 145 assertFalse(pbStatus.isInViolation()); 146 } 147 148 @Test 149 public void testQuotaEnforcementsFromRS() throws Exception { 150 final long sizeLimit = 1024L * 8L; // 8KB 151 final long tableSize = 1024L * 10L; // 10KB 152 final int numRegions = 10; 153 final TableName tn = helper.createTableWithRegions(numRegions); 154 155 // Define the quota 156 QuotaSettings settings = 157 QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS); 158 TEST_UTIL.getAdmin().setQuota(settings); 159 160 // Write at least `tableSize` data 161 try { 162 helper.writeData(tn, tableSize); 163 } catch (RetriesExhaustedWithDetailsException | SpaceLimitingException e) { 164 // Pass 165 } 166 167 final HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0); 168 final RegionServerSpaceQuotaManager manager = rs.getRegionServerSpaceQuotaManager(); 169 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 170 @Override 171 public boolean evaluate() throws Exception { 172 ActivePolicyEnforcement enforcements = manager.getActiveEnforcements(); 173 SpaceViolationPolicyEnforcement enforcement = enforcements.getPolicyEnforcement(tn); 174 // Signifies that we're waiting on the quota snapshot to be fetched 175 if (enforcement instanceof MissingSnapshotViolationPolicyEnforcement) { 176 return false; 177 } 178 return enforcement.getQuotaSnapshot().getQuotaStatus().isInViolation(); 179 } 180 }); 181 182 // We obtain the violations for a RegionServer by observing the snapshots 183 @SuppressWarnings("unchecked") 184 Map<TableName, SpaceQuotaSnapshot> snapshots = (Map<TableName, SpaceQuotaSnapshot>) TEST_UTIL 185 .getAdmin().getRegionServerSpaceQuotaSnapshots(rs.getServerName()); 186 SpaceQuotaSnapshot snapshot = snapshots.get(tn); 187 assertNotNull(snapshot, "Did not find snapshot for " + tn); 188 assertTrue(snapshot.getQuotaStatus().isInViolation()); 189 assertEquals(SpaceViolationPolicy.NO_INSERTS, snapshot.getQuotaStatus().getPolicy().get()); 190 } 191 192 @Test 193 public void testQuotaStatusFromMaster() throws Exception { 194 final long sizeLimit = 1024L * 25L; // 25KB 195 // As of 2.0.0-beta-2, this 1KB of "Cells" actually results in about 15KB on disk (HFiles) 196 // This is skewed a bit since we're writing such little data, so the test needs to keep 197 // this in mind; else, the quota will be in violation before the test expects it to be. 198 final long tableSize = 1024L * 1; // 1KB 199 final long nsLimit = Long.MAX_VALUE; 200 final int numRegions = 10; 201 final TableName tn = helper.createTableWithRegions(numRegions); 202 203 // Define the quota 204 QuotaSettings settings = 205 QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS); 206 TEST_UTIL.getAdmin().setQuota(settings); 207 QuotaSettings nsSettings = QuotaSettingsFactory.limitNamespaceSpace(tn.getNamespaceAsString(), 208 nsLimit, SpaceViolationPolicy.NO_INSERTS); 209 TEST_UTIL.getAdmin().setQuota(nsSettings); 210 211 // Write at least `tableSize` data 212 helper.writeData(tn, tableSize); 213 214 final Connection conn = TEST_UTIL.getConnection(); 215 // Make sure the master has a snapshot for our table 216 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 217 @Override 218 public boolean evaluate() throws Exception { 219 SpaceQuotaSnapshot snapshot = 220 (SpaceQuotaSnapshot) conn.getAdmin().getCurrentSpaceQuotaSnapshot(tn); 221 LOG.info("Table snapshot after initial ingest: " + snapshot); 222 if (snapshot == null) { 223 return false; 224 } 225 return snapshot.getLimit() == sizeLimit && snapshot.getUsage() > 0L; 226 } 227 }); 228 final AtomicReference<Long> nsUsage = new AtomicReference<>(); 229 // If we saw the table snapshot, we should also see the namespace snapshot 230 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000 * 1000, new Predicate<Exception>() { 231 @Override 232 public boolean evaluate() throws Exception { 233 SpaceQuotaSnapshot snapshot = (SpaceQuotaSnapshot) conn.getAdmin() 234 .getCurrentSpaceQuotaSnapshot(tn.getNamespaceAsString()); 235 LOG.debug("Namespace snapshot after initial ingest: " + snapshot); 236 if (snapshot == null) { 237 return false; 238 } 239 nsUsage.set(snapshot.getUsage()); 240 return snapshot.getLimit() == nsLimit && snapshot.getUsage() > 0; 241 } 242 }); 243 244 // Sanity check: the below assertions will fail if we somehow write too much data 245 // and force the table to move into violation before we write the second bit of data. 246 SpaceQuotaSnapshot snapshot = 247 (SpaceQuotaSnapshot) conn.getAdmin().getCurrentSpaceQuotaSnapshot(tn); 248 assertTrue(snapshot != null && !snapshot.getQuotaStatus().isInViolation(), 249 "QuotaSnapshot for " + tn + " should be non-null and not in violation"); 250 251 try { 252 helper.writeData(tn, tableSize * 2L); 253 } catch (RetriesExhaustedWithDetailsException | SpaceLimitingException e) { 254 // Pass 255 } 256 257 // Wait for the status to move to violation 258 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 259 @Override 260 public boolean evaluate() throws Exception { 261 SpaceQuotaSnapshot snapshot = 262 (SpaceQuotaSnapshot) conn.getAdmin().getCurrentSpaceQuotaSnapshot(tn); 263 LOG.info("Table snapshot after second ingest: " + snapshot); 264 if (snapshot == null) { 265 return false; 266 } 267 return snapshot.getQuotaStatus().isInViolation(); 268 } 269 }); 270 // The namespace should still not be in violation, but have a larger usage than previously 271 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 272 @Override 273 public boolean evaluate() throws Exception { 274 SpaceQuotaSnapshot snapshot = (SpaceQuotaSnapshot) conn.getAdmin() 275 .getCurrentSpaceQuotaSnapshot(tn.getNamespaceAsString()); 276 LOG.debug("Namespace snapshot after second ingest: " + snapshot); 277 if (snapshot == null) { 278 return false; 279 } 280 return snapshot.getUsage() > nsUsage.get() && !snapshot.getQuotaStatus().isInViolation(); 281 } 282 }); 283 } 284 285 private int countRegionsForTable(TableName tn, Map<RegionInfo, Long> regionSizes) { 286 int size = 0; 287 for (RegionInfo regionInfo : regionSizes.keySet()) { 288 if (tn.equals(regionInfo.getTable())) { 289 size++; 290 } 291 } 292 return size; 293 } 294 295 private int getTableSize(TableName tn, Map<RegionInfo, Long> regionSizes) { 296 int tableSize = 0; 297 for (Entry<RegionInfo, Long> entry : regionSizes.entrySet()) { 298 RegionInfo regionInfo = entry.getKey(); 299 long regionSize = entry.getValue(); 300 if (tn.equals(regionInfo.getTable())) { 301 tableSize += regionSize; 302 } 303 } 304 return tableSize; 305 } 306}