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