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 java.io.IOException; 023import java.util.Arrays; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.concurrent.TimeUnit; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.hbase.HBaseClassTestRule; 031import org.apache.hadoop.hbase.HBaseTestingUtility; 032import org.apache.hadoop.hbase.MetaTableAccessor; 033import org.apache.hadoop.hbase.TableName; 034import org.apache.hadoop.hbase.Waiter; 035import org.apache.hadoop.hbase.client.Admin; 036import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 037import org.apache.hadoop.hbase.client.Connection; 038import org.apache.hadoop.hbase.client.Put; 039import org.apache.hadoop.hbase.client.RegionInfo; 040import org.apache.hadoop.hbase.client.Result; 041import org.apache.hadoop.hbase.client.ResultScanner; 042import org.apache.hadoop.hbase.client.Table; 043import org.apache.hadoop.hbase.client.TableDescriptor; 044import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 045import org.apache.hadoop.hbase.master.HMaster; 046import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos; 047import org.apache.hadoop.hbase.testclassification.LargeTests; 048import org.apache.hadoop.hbase.util.Bytes; 049import org.junit.After; 050import org.junit.Before; 051import org.junit.ClassRule; 052import org.junit.Rule; 053import org.junit.Test; 054import org.junit.experimental.categories.Category; 055import org.junit.rules.TestName; 056import org.slf4j.Logger; 057import org.slf4j.LoggerFactory; 058 059/** 060 * A test case to verify that region reports are expired when they are not sent. 061 */ 062@Category(LargeTests.class) 063public class TestQuotaObserverChoreRegionReports { 064 065 @ClassRule 066 public static final HBaseClassTestRule CLASS_RULE = 067 HBaseClassTestRule.forClass(TestQuotaObserverChoreRegionReports.class); 068 069 private static final Logger LOG = 070 LoggerFactory.getLogger(TestQuotaObserverChoreRegionReports.class); 071 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 072 073 @Rule 074 public TestName testName = new TestName(); 075 076 @Before 077 public void setUp() throws Exception { 078 Configuration conf = TEST_UTIL.getConfiguration(); 079 // Increase the frequency of some of the chores for responsiveness of the test 080 SpaceQuotaHelperForTests.updateConfigForQuotas(conf); 081 conf.setInt(QuotaObserverChore.REGION_REPORT_RETENTION_DURATION_KEY, 1000); 082 } 083 084 @After 085 public void tearDown() throws Exception { 086 TEST_UTIL.shutdownMiniCluster(); 087 } 088 089 @Test 090 public void testReportExpiration() throws Exception { 091 Configuration conf = TEST_UTIL.getConfiguration(); 092 // Send reports every 25 seconds 093 conf.setInt(RegionSizeReportingChore.REGION_SIZE_REPORTING_CHORE_PERIOD_KEY, 25000); 094 // Expire the reports after 5 seconds 095 conf.setInt(QuotaObserverChore.REGION_REPORT_RETENTION_DURATION_KEY, 5000); 096 TEST_UTIL.startMiniCluster(1); 097 // Wait till quota table onlined. 098 TEST_UTIL.waitFor(10000, new Waiter.Predicate<Exception>() { 099 @Override public boolean evaluate() throws Exception { 100 return MetaTableAccessor.tableExists(TEST_UTIL.getConnection(), 101 QuotaTableUtil.QUOTA_TABLE_NAME); 102 } 103 }); 104 105 final String FAM1 = "f1"; 106 final HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster(); 107 // Wait for the master to finish initialization. 108 while (master.getMasterQuotaManager() == null) { 109 LOG.debug("MasterQuotaManager is null, waiting..."); 110 Thread.sleep(500); 111 } 112 final MasterQuotaManager quotaManager = master.getMasterQuotaManager(); 113 114 // Create a table 115 final TableName tn = TableName.valueOf("reportExpiration"); 116 TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tn).addColumnFamily( 117 ColumnFamilyDescriptorBuilder.of(FAM1)).build(); 118 TEST_UTIL.getAdmin().createTable(tableDesc); 119 120 // No reports right after we created this table. 121 assertEquals(0, getRegionReportsForTable(quotaManager.snapshotRegionSizes(), tn)); 122 123 // Set a quota 124 final long sizeLimit = 100L * SpaceQuotaHelperForTests.ONE_MEGABYTE; 125 final SpaceViolationPolicy violationPolicy = SpaceViolationPolicy.NO_INSERTS; 126 QuotaSettings settings = QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, violationPolicy); 127 TEST_UTIL.getAdmin().setQuota(settings); 128 129 // We should get one report for the one region we have. 130 Waiter.waitFor(TEST_UTIL.getConfiguration(), 45000, 1000, new Waiter.Predicate<Exception>() { 131 @Override 132 public boolean evaluate() throws Exception { 133 int numReports = getRegionReportsForTable(quotaManager.snapshotRegionSizes(), tn); 134 LOG.debug("Saw " + numReports + " reports for " + tn + " while waiting for 1"); 135 return numReports == 1; 136 } 137 }); 138 139 // We should then see no reports for the single region 140 Waiter.waitFor(TEST_UTIL.getConfiguration(), 15000, 1000, new Waiter.Predicate<Exception>() { 141 @Override 142 public boolean evaluate() throws Exception { 143 int numReports = getRegionReportsForTable(quotaManager.snapshotRegionSizes(), tn); 144 LOG.debug("Saw " + numReports + " reports for " + tn + " while waiting for none"); 145 return numReports == 0; 146 } 147 }); 148 } 149 150 @Test 151 public void testMissingReportsRemovesQuota() throws Exception { 152 Configuration conf = TEST_UTIL.getConfiguration(); 153 // Expire the reports after 5 seconds 154 conf.setInt(QuotaObserverChore.REGION_REPORT_RETENTION_DURATION_KEY, 5000); 155 TEST_UTIL.startMiniCluster(1); 156 // Wait till quota table onlined. 157 TEST_UTIL.waitFor(10000, new Waiter.Predicate<Exception>() { 158 @Override public boolean evaluate() throws Exception { 159 return MetaTableAccessor.tableExists(TEST_UTIL.getConnection(), 160 QuotaTableUtil.QUOTA_TABLE_NAME); 161 } 162 }); 163 164 final String FAM1 = "f1"; 165 166 // Create a table 167 final TableName tn = TableName.valueOf("quotaAcceptanceWithoutReports"); 168 TableDescriptor tableDesc = TableDescriptorBuilder.newBuilder(tn).addColumnFamily( 169 ColumnFamilyDescriptorBuilder.of(FAM1)).build(); 170 TEST_UTIL.getAdmin().createTable(tableDesc); 171 172 // Set a quota 173 final long sizeLimit = 1L * SpaceQuotaHelperForTests.ONE_KILOBYTE; 174 final SpaceViolationPolicy violationPolicy = SpaceViolationPolicy.NO_INSERTS; 175 QuotaSettings settings = QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, violationPolicy); 176 final Admin admin = TEST_UTIL.getAdmin(); 177 LOG.info("SET QUOTA"); 178 admin.setQuota(settings); 179 final Connection conn = TEST_UTIL.getConnection(); 180 181 // Write enough data to invalidate the quota 182 Put p = new Put(Bytes.toBytes("row1")); 183 byte[] bytes = new byte[10]; 184 Arrays.fill(bytes, (byte) 2); 185 for (int i = 0; i < 200; i++) { 186 p.addColumn(Bytes.toBytes(FAM1), Bytes.toBytes("qual" + i), bytes); 187 } 188 conn.getTable(tn).put(p); 189 admin.flush(tn); 190 191 // Wait for the table to move into violation 192 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30000, 1000, new Waiter.Predicate<Exception>() { 193 @Override 194 public boolean evaluate() throws Exception { 195 SpaceQuotaSnapshot snapshot = getSnapshotForTable(conn, tn); 196 if (snapshot == null) { 197 return false; 198 } 199 return snapshot.getQuotaStatus().isInViolation(); 200 } 201 }); 202 203 // Close the region, prevent the server from sending new status reports. 204 List<RegionInfo> regions = admin.getRegions(tn); 205 assertEquals(1, regions.size()); 206 RegionInfo hri = regions.get(0); 207 admin.unassign(hri.getRegionName(), true); 208 209 // We should see this table move out of violation after the report expires. 210 Waiter.waitFor(TEST_UTIL.getConfiguration(), 30000, 1000, new Waiter.Predicate<Exception>() { 211 @Override 212 public boolean evaluate() throws Exception { 213 SpaceQuotaSnapshot snapshot = getSnapshotForTable(conn, tn); 214 if (snapshot == null) { 215 return false; 216 } 217 return !snapshot.getQuotaStatus().isInViolation(); 218 } 219 }); 220 221 // The QuotaObserverChore's memory should also show it not in violation. 222 final HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster(); 223 QuotaSnapshotStore<TableName> tableStore = 224 master.getQuotaObserverChore().getTableSnapshotStore(); 225 SpaceQuotaSnapshot snapshot = tableStore.getCurrentState(tn); 226 assertFalse("Quota should not be in violation", snapshot.getQuotaStatus().isInViolation()); 227 } 228 229 private SpaceQuotaSnapshot getSnapshotForTable( 230 Connection conn, TableName tn) throws IOException { 231 try (Table quotaTable = conn.getTable(QuotaUtil.QUOTA_TABLE_NAME); 232 ResultScanner scanner = quotaTable.getScanner(QuotaTableUtil.makeQuotaSnapshotScan())) { 233 Map<TableName,SpaceQuotaSnapshot> activeViolations = new HashMap<>(); 234 for (Result result : scanner) { 235 try { 236 QuotaTableUtil.extractQuotaSnapshot(result, activeViolations); 237 } catch (IllegalArgumentException e) { 238 final String msg = "Failed to parse result for row " + Bytes.toString(result.getRow()); 239 LOG.error(msg, e); 240 throw new IOException(msg, e); 241 } 242 } 243 return activeViolations.get(tn); 244 } 245 } 246 247 private int getRegionReportsForTable(Map<RegionInfo,Long> reports, TableName tn) { 248 int numReports = 0; 249 for (Entry<RegionInfo,Long> entry : reports.entrySet()) { 250 if (tn.equals(entry.getKey().getTable())) { 251 numReports++; 252 } 253 } 254 return numReports; 255 } 256}