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 = 126 QuotaSettingsFactory.limitTableSpace(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("Observed table usage was " + snapshot.getUsage(), snapshot.getUsage() >= tableSize); 151 assertEquals(sizeLimit, snapshot.getLimit()); 152 SpaceQuotaStatus pbStatus = snapshot.getQuotaStatus(); 153 assertFalse(pbStatus.isInViolation()); 154 } 155 156 @Test 157 public void testQuotaEnforcementsFromRS() throws Exception { 158 final long sizeLimit = 1024L * 8L; // 8KB 159 final long tableSize = 1024L * 10L; // 10KB 160 final int numRegions = 10; 161 final TableName tn = helper.createTableWithRegions(numRegions); 162 163 // Define the quota 164 QuotaSettings settings = 165 QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS); 166 TEST_UTIL.getAdmin().setQuota(settings); 167 168 // Write at least `tableSize` data 169 try { 170 helper.writeData(tn, tableSize); 171 } catch (RetriesExhaustedWithDetailsException | SpaceLimitingException e) { 172 // Pass 173 } 174 175 final HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0); 176 final RegionServerSpaceQuotaManager manager = rs.getRegionServerSpaceQuotaManager(); 177 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 178 @Override 179 public boolean evaluate() throws Exception { 180 ActivePolicyEnforcement enforcements = manager.getActiveEnforcements(); 181 SpaceViolationPolicyEnforcement enforcement = enforcements.getPolicyEnforcement(tn); 182 // Signifies that we're waiting on the quota snapshot to be fetched 183 if (enforcement instanceof MissingSnapshotViolationPolicyEnforcement) { 184 return false; 185 } 186 return enforcement.getQuotaSnapshot().getQuotaStatus().isInViolation(); 187 } 188 }); 189 190 // We obtain the violations for a RegionServer by observing the snapshots 191 @SuppressWarnings("unchecked") 192 Map<TableName, SpaceQuotaSnapshot> snapshots = (Map<TableName, SpaceQuotaSnapshot>) TEST_UTIL 193 .getAdmin().getRegionServerSpaceQuotaSnapshots(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().get()); 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 = 213 QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS); 214 TEST_UTIL.getAdmin().setQuota(settings); 215 QuotaSettings nsSettings = QuotaSettingsFactory.limitNamespaceSpace(tn.getNamespaceAsString(), 216 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 = 228 (SpaceQuotaSnapshot) conn.getAdmin().getCurrentSpaceQuotaSnapshot(tn); 229 LOG.info("Table snapshot after initial ingest: " + snapshot); 230 if (snapshot == null) { 231 return false; 232 } 233 return snapshot.getLimit() == sizeLimit && snapshot.getUsage() > 0L; 234 } 235 }); 236 final AtomicReference<Long> nsUsage = new AtomicReference<>(); 237 // If we saw the table snapshot, we should also see the namespace snapshot 238 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000 * 1000, new Predicate<Exception>() { 239 @Override 240 public boolean evaluate() throws Exception { 241 SpaceQuotaSnapshot snapshot = (SpaceQuotaSnapshot) conn.getAdmin() 242 .getCurrentSpaceQuotaSnapshot(tn.getNamespaceAsString()); 243 LOG.debug("Namespace snapshot after initial ingest: " + snapshot); 244 if (snapshot == null) { 245 return false; 246 } 247 nsUsage.set(snapshot.getUsage()); 248 return snapshot.getLimit() == nsLimit && snapshot.getUsage() > 0; 249 } 250 }); 251 252 // Sanity check: the below assertions will fail if we somehow write too much data 253 // and force the table to move into violation before we write the second bit of data. 254 SpaceQuotaSnapshot snapshot = 255 (SpaceQuotaSnapshot) conn.getAdmin().getCurrentSpaceQuotaSnapshot(tn); 256 assertTrue("QuotaSnapshot for " + tn + " should be non-null and not in violation", 257 snapshot != null && !snapshot.getQuotaStatus().isInViolation()); 258 259 try { 260 helper.writeData(tn, tableSize * 2L); 261 } catch (RetriesExhaustedWithDetailsException | SpaceLimitingException e) { 262 // Pass 263 } 264 265 // Wait for the status to move to violation 266 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 267 @Override 268 public boolean evaluate() throws Exception { 269 SpaceQuotaSnapshot snapshot = 270 (SpaceQuotaSnapshot) conn.getAdmin().getCurrentSpaceQuotaSnapshot(tn); 271 LOG.info("Table snapshot after second ingest: " + snapshot); 272 if (snapshot == null) { 273 return false; 274 } 275 return snapshot.getQuotaStatus().isInViolation(); 276 } 277 }); 278 // The namespace should still not be in violation, but have a larger usage than previously 279 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() { 280 @Override 281 public boolean evaluate() throws Exception { 282 SpaceQuotaSnapshot snapshot = (SpaceQuotaSnapshot) conn.getAdmin() 283 .getCurrentSpaceQuotaSnapshot(tn.getNamespaceAsString()); 284 LOG.debug("Namespace snapshot after second ingest: " + snapshot); 285 if (snapshot == null) { 286 return false; 287 } 288 return snapshot.getUsage() > nsUsage.get() && !snapshot.getQuotaStatus().isInViolation(); 289 } 290 }); 291 } 292 293 private int countRegionsForTable(TableName tn, Map<RegionInfo, Long> regionSizes) { 294 int size = 0; 295 for (RegionInfo regionInfo : regionSizes.keySet()) { 296 if (tn.equals(regionInfo.getTable())) { 297 size++; 298 } 299 } 300 return size; 301 } 302 303 private int getTableSize(TableName tn, Map<RegionInfo, Long> regionSizes) { 304 int tableSize = 0; 305 for (Entry<RegionInfo, Long> entry : regionSizes.entrySet()) { 306 RegionInfo regionInfo = entry.getKey(); 307 long regionSize = entry.getValue(); 308 if (tn.equals(regionInfo.getTable())) { 309 tableSize += regionSize; 310 } 311 } 312 return tableSize; 313 } 314}