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 = QuotaTableUtil.getMasterReportedTableSizes(TEST_UTIL.getConnection()); 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 Map<TableName, SpaceQuotaSnapshot> snapshots = QuotaTableUtil.getRegionServerQuotaSnapshots( 146 TEST_UTIL.getConnection(), rs.getServerName()); 147 SpaceQuotaSnapshot snapshot = snapshots.get(tn); 148 assertNotNull("Did not find snapshot for " + tn, snapshot); 149 assertTrue( 150 "Observed table usage was " + snapshot.getUsage(), 151 snapshot.getUsage() >= tableSize); 152 assertEquals(sizeLimit, snapshot.getLimit()); 153 SpaceQuotaStatus pbStatus = snapshot.getQuotaStatus(); 154 assertFalse(pbStatus.isInViolation()); 155 } 156 157 @Test 158 public void testQuotaEnforcementsFromRS() throws Exception { 159 final long sizeLimit = 1024L * 8L; // 8KB 160 final long tableSize = 1024L * 10L; // 10KB 161 final int numRegions = 10; 162 final TableName tn = helper.createTableWithRegions(numRegions); 163 164 // Define the quota 165 QuotaSettings settings = QuotaSettingsFactory.limitTableSpace( 166 tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS); 167 TEST_UTIL.getAdmin().setQuota(settings); 168 169 // Write at least `tableSize` data 170 try { 171 helper.writeData(tn, tableSize); 172 } catch (RetriesExhaustedWithDetailsException | SpaceLimitingException e) { 173 // Pass 174 } 175 176 final HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0); 177 final RegionServerSpaceQuotaManager manager = rs.getRegionServerSpaceQuotaManager(); 178 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 179 @Override 180 public boolean evaluate() throws Exception { 181 ActivePolicyEnforcement enforcements = manager.getActiveEnforcements(); 182 SpaceViolationPolicyEnforcement enforcement = enforcements.getPolicyEnforcement(tn); 183 // Signifies that we're waiting on the quota snapshot to be fetched 184 if (enforcement instanceof MissingSnapshotViolationPolicyEnforcement) { 185 return false; 186 } 187 return enforcement.getQuotaSnapshot().getQuotaStatus().isInViolation(); 188 } 189 }); 190 191 // We obtain the violations for a RegionServer by observing the snapshots 192 Map<TableName,SpaceQuotaSnapshot> snapshots = 193 QuotaTableUtil.getRegionServerQuotaSnapshots(TEST_UTIL.getConnection(), rs.getServerName()); 194 SpaceQuotaSnapshot snapshot = snapshots.get(tn); 195 assertNotNull("Did not find snapshot for " + tn, snapshot); 196 assertTrue(snapshot.getQuotaStatus().isInViolation()); 197 assertEquals(SpaceViolationPolicy.NO_INSERTS, snapshot.getQuotaStatus().getPolicy()); 198 } 199 200 @Test 201 public void testQuotaStatusFromMaster() throws Exception { 202 final long sizeLimit = 1024L * 25L; // 25KB 203 // As of 2.0.0-beta-2, this 1KB of "Cells" actually results in about 15KB on disk (HFiles) 204 // This is skewed a bit since we're writing such little data, so the test needs to keep 205 // this in mind; else, the quota will be in violation before the test expects it to be. 206 final long tableSize = 1024L * 1; // 1KB 207 final long nsLimit = Long.MAX_VALUE; 208 final int numRegions = 10; 209 final TableName tn = helper.createTableWithRegions(numRegions); 210 211 // Define the quota 212 QuotaSettings settings = QuotaSettingsFactory.limitTableSpace( 213 tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS); 214 TEST_UTIL.getAdmin().setQuota(settings); 215 QuotaSettings nsSettings = QuotaSettingsFactory.limitNamespaceSpace( 216 tn.getNamespaceAsString(), nsLimit, SpaceViolationPolicy.NO_INSERTS); 217 TEST_UTIL.getAdmin().setQuota(nsSettings); 218 219 // Write at least `tableSize` data 220 helper.writeData(tn, tableSize); 221 222 final Connection conn = TEST_UTIL.getConnection(); 223 // Make sure the master has a snapshot for our table 224 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 225 @Override 226 public boolean evaluate() throws Exception { 227 SpaceQuotaSnapshot snapshot = QuotaTableUtil.getCurrentSnapshot(conn, tn); 228 LOG.info("Table snapshot after initial ingest: " + snapshot); 229 if (snapshot == null) { 230 return false; 231 } 232 return snapshot.getLimit() == sizeLimit && snapshot.getUsage() > 0L; 233 } 234 }); 235 final AtomicReference<Long> nsUsage = new AtomicReference<>(); 236 // If we saw the table snapshot, we should also see the namespace snapshot 237 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000 * 1000, new Predicate<Exception>() { 238 @Override 239 public boolean evaluate() throws Exception { 240 SpaceQuotaSnapshot snapshot = QuotaTableUtil.getCurrentSnapshot( 241 conn, tn.getNamespaceAsString()); 242 LOG.debug("Namespace snapshot after initial ingest: " + snapshot); 243 if (snapshot == null) { 244 return false; 245 } 246 nsUsage.set(snapshot.getUsage()); 247 return snapshot.getLimit() == nsLimit && snapshot.getUsage() > 0; 248 } 249 }); 250 251 // Sanity check: the below assertions will fail if we somehow write too much data 252 // and force the table to move into violation before we write the second bit of data. 253 SpaceQuotaSnapshot snapshot = QuotaTableUtil.getCurrentSnapshot(conn, tn); 254 assertTrue("QuotaSnapshot for " + tn + " should be non-null and not in violation", 255 snapshot != null && !snapshot.getQuotaStatus().isInViolation()); 256 257 try { 258 helper.writeData(tn, tableSize * 2L); 259 } catch (RetriesExhaustedWithDetailsException | SpaceLimitingException e) { 260 // Pass 261 } 262 263 // Wait for the status to move to violation 264 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 265 @Override 266 public boolean evaluate() throws Exception { 267 SpaceQuotaSnapshot snapshot = QuotaTableUtil.getCurrentSnapshot(conn, tn); 268 LOG.info("Table snapshot after second ingest: " + snapshot); 269 if (snapshot == null) { 270 return false; 271 } 272 return snapshot.getQuotaStatus().isInViolation(); 273 } 274 }); 275 // The namespace should still not be in violation, but have a larger usage than previously 276 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 277 @Override 278 public boolean evaluate() throws Exception { 279 SpaceQuotaSnapshot snapshot = QuotaTableUtil.getCurrentSnapshot( 280 conn, tn.getNamespaceAsString()); 281 LOG.debug("Namespace snapshot after second ingest: " + snapshot); 282 if (snapshot == null) { 283 return false; 284 } 285 return snapshot.getUsage() > nsUsage.get() && !snapshot.getQuotaStatus().isInViolation(); 286 } 287 }); 288 } 289 290 private int countRegionsForTable(TableName tn, Map<RegionInfo,Long> regionSizes) { 291 int size = 0; 292 for (RegionInfo regionInfo : regionSizes.keySet()) { 293 if (tn.equals(regionInfo.getTable())) { 294 size++; 295 } 296 } 297 return size; 298 } 299 300 private int getTableSize(TableName tn, Map<RegionInfo,Long> regionSizes) { 301 int tableSize = 0; 302 for (Entry<RegionInfo,Long> entry : regionSizes.entrySet()) { 303 RegionInfo regionInfo = entry.getKey(); 304 long regionSize = entry.getValue(); 305 if (tn.equals(regionInfo.getTable())) { 306 tableSize += regionSize; 307 } 308 } 309 return tableSize; 310 } 311}