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