View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to you under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.hadoop.hbase.quotas;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertFalse;
21  import static org.junit.Assert.assertNotNull;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.util.Map;
25  import java.util.Map.Entry;
26  import java.util.concurrent.atomic.AtomicLong;
27  import java.util.concurrent.atomic.AtomicReference;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.HBaseTestingUtility;
33  import org.apache.hadoop.hbase.HRegionInfo;
34  import org.apache.hadoop.hbase.TableName;
35  import org.apache.hadoop.hbase.Waiter;
36  import org.apache.hadoop.hbase.Waiter.Predicate;
37  import org.apache.hadoop.hbase.client.Connection;
38  import org.apache.hadoop.hbase.client.RetriesExhaustedWithDetailsException;
39  import org.apache.hadoop.hbase.master.HMaster;
40  import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot.SpaceQuotaStatus;
41  import org.apache.hadoop.hbase.quotas.policies.MissingSnapshotViolationPolicyEnforcement;
42  import org.apache.hadoop.hbase.regionserver.HRegionServer;
43  import org.apache.hadoop.hbase.testclassification.MediumTests;
44  import org.junit.AfterClass;
45  import org.junit.Before;
46  import org.junit.BeforeClass;
47  import org.junit.Rule;
48  import org.junit.Test;
49  import org.junit.experimental.categories.Category;
50  import org.junit.rules.TestName;
51  
52  /**
53   * Test class for the quota status RPCs in the master and regionserver.
54   */
55  @Category({MediumTests.class})
56  public class TestQuotaStatusRPCs {
57    private static final Log LOG = LogFactory.getLog(TestQuotaStatusRPCs.class);
58    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
59    private static final AtomicLong COUNTER = new AtomicLong(0);
60  
61    @Rule
62    public TestName testName = new TestName();
63    private SpaceQuotaHelperForTests helper;
64  
65    @BeforeClass
66    public static void setUp() throws Exception {
67      Configuration conf = TEST_UTIL.getConfiguration();
68      // Increase the frequency of some of the chores for responsiveness of the test
69      conf.setInt(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_DELAY_KEY, 1000);
70      conf.setInt(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_PERIOD_KEY, 1000);
71      conf.setInt(QuotaObserverChore.QUOTA_OBSERVER_CHORE_DELAY_KEY, 1000);
72      conf.setInt(QuotaObserverChore.QUOTA_OBSERVER_CHORE_PERIOD_KEY, 1000);
73      conf.setInt(SpaceQuotaRefresherChore.POLICY_REFRESHER_CHORE_DELAY_KEY, 1000);
74      conf.setInt(SpaceQuotaRefresherChore.POLICY_REFRESHER_CHORE_PERIOD_KEY, 1000);
75      conf.setBoolean(QuotaUtil.QUOTA_CONF_KEY, true);
76      TEST_UTIL.startMiniCluster(1);
77    }
78  
79    @AfterClass
80    public static void tearDown() throws Exception {
81      TEST_UTIL.shutdownMiniCluster();
82    }
83  
84    @Before
85    public void setupForTest() throws Exception {
86      helper = new SpaceQuotaHelperForTests(TEST_UTIL, testName, COUNTER);
87    }
88  
89    @Test
90    public void testRegionSizesFromMaster() throws Exception {
91      final long tableSize = 1024L * 10L; // 10KB
92      final int numRegions = 10;
93      final TableName tn = helper.createTableWithRegions(numRegions);
94      // Will write at least `tableSize` data
95      helper.writeData(tn, tableSize);
96  
97      final HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster();
98      final MasterQuotaManager quotaManager = master.getMasterQuotaManager();
99      // Make sure the master has all of the reports
100     Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() {
101       @Override
102       public boolean evaluate() throws Exception {
103         Map<HRegionInfo,Long> regionSizes = quotaManager.snapshotRegionSizes();
104         LOG.trace("Region sizes=" + regionSizes);
105         return numRegions == countRegionsForTable(tn, regionSizes) &&
106             tableSize <= getTableSize(tn, regionSizes);
107       }
108     });
109 
110     Map<TableName,Long> sizes = QuotaTableUtil.getMasterReportedTableSizes(TEST_UTIL.getConnection());
111     Long size = sizes.get(tn);
112     assertNotNull("No reported size for " + tn, size);
113     assertTrue("Reported table size was " + size, size.longValue() >= tableSize);
114   }
115 
116   @Test
117   public void testQuotaSnapshotsFromRS() throws Exception {
118     final long sizeLimit = 1024L * 1024L; // 1MB
119     final long tableSize = 1024L * 10L; // 10KB
120     final int numRegions = 10;
121     final TableName tn = helper.createTableWithRegions(numRegions);
122 
123     // Define the quota
124     QuotaSettings settings = QuotaSettingsFactory.limitTableSpace(
125         tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS);
126     TEST_UTIL.getHBaseAdmin().setQuota(settings);
127 
128     // Write at least `tableSize` data
129     helper.writeData(tn, tableSize);
130 
131     final HRegionServer rs = TEST_UTIL.getMiniHBaseCluster().getRegionServer(0);
132     final RegionServerSpaceQuotaManager manager = rs.getRegionServerSpaceQuotaManager();
133     Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() {
134       @Override
135       public boolean evaluate() throws Exception {
136         SpaceQuotaSnapshot snapshot = manager.copyQuotaSnapshots().get(tn);
137         if (snapshot == null) {
138           return false;
139         }
140         return snapshot.getUsage() >= tableSize;
141       }
142     });
143 
144     Map<TableName, SpaceQuotaSnapshot> snapshots = QuotaTableUtil.getRegionServerQuotaSnapshots(
145         TEST_UTIL.getConnection(), rs.getServerName());
146     SpaceQuotaSnapshot snapshot = snapshots.get(tn);
147     assertNotNull("Did not find snapshot for " + tn, snapshot);
148     assertTrue(
149         "Observed table usage was " + snapshot.getUsage(),
150         snapshot.getUsage() >= tableSize);
151     assertEquals(snapshot.getLimit(), sizeLimit);
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 = QuotaSettingsFactory.limitTableSpace(
165         tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS);
166     TEST_UTIL.getHBaseAdmin().setQuota(settings);
167 
168     // Write at least `tableSize` data
169     try {
170       helper.writeData(tn, tableSize);
171     } catch (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     Map<TableName,SpaceQuotaSnapshot> snapshots =
192         QuotaTableUtil.getRegionServerQuotaSnapshots(TEST_UTIL.getConnection(), rs.getServerName());
193     SpaceQuotaSnapshot snapshot = snapshots.get(tn);
194     assertNotNull("Did not find snapshot for " + tn, snapshot);
195     assertTrue(snapshot.getQuotaStatus().isInViolation());
196     assertEquals(SpaceViolationPolicy.NO_INSERTS, snapshot.getQuotaStatus().getPolicy());
197   }
198 
199   @Test
200   public void testQuotaStatusFromMaster() throws Exception {
201     final long sizeLimit = 1024L * 10L; // 10KB
202     final long tableSize = 1024L * 5; // 5KB
203     final long nsLimit = Long.MAX_VALUE;
204     final int numRegions = 10;
205     final TableName tn = helper.createTableWithRegions(numRegions);
206 
207     // Define the quota
208     QuotaSettings settings = QuotaSettingsFactory.limitTableSpace(
209         tn, sizeLimit, SpaceViolationPolicy.NO_INSERTS);
210     TEST_UTIL.getHBaseAdmin().setQuota(settings);
211     QuotaSettings nsSettings = QuotaSettingsFactory.limitNamespaceSpace(
212         tn.getNamespaceAsString(), nsLimit, SpaceViolationPolicy.NO_INSERTS);
213     TEST_UTIL.getHBaseAdmin().setQuota(nsSettings);
214 
215     // Write at least `tableSize` data
216     helper.writeData(tn, tableSize);
217 
218     final Connection conn = TEST_UTIL.getConnection();
219     // Make sure the master has a snapshot for our table
220     Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() {
221       @Override
222       public boolean evaluate() throws Exception {
223         SpaceQuotaSnapshot snapshot = QuotaTableUtil.getCurrentSnapshot(conn, tn);
224         LOG.info("Table snapshot after initial ingest: " + snapshot);
225         if (snapshot == null) {
226           return false;
227         }
228         return snapshot.getLimit() == sizeLimit && snapshot.getUsage() > 0L;
229       }
230     });
231     final AtomicReference<Long> nsUsage = new AtomicReference<>();
232     // If we saw the table snapshot, we should also see the namespace snapshot
233     Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000 * 1000, new Predicate<Exception>() {
234       @Override
235       public boolean evaluate() throws Exception {
236         SpaceQuotaSnapshot snapshot = QuotaTableUtil.getCurrentSnapshot(
237             conn, tn.getNamespaceAsString());
238         LOG.debug("Namespace snapshot after initial ingest: " + snapshot);
239         if (snapshot == null) {
240           return false;
241         }
242         nsUsage.set(snapshot.getUsage());
243         return snapshot.getLimit() == nsLimit && snapshot.getUsage() > 0;
244       }
245     });
246 
247     try {
248       helper.writeData(tn, tableSize * 2L);
249     } catch (SpaceLimitingException e) {
250       // Pass
251     } catch (RetriesExhaustedWithDetailsException e) {
252       // Pass
253       String msg = e.getMessage();
254       assertTrue(
255           "Exception message did not contain expected text. " + msg,
256           msg.contains("Puts are disallowed due to a space quota"));
257     }
258 
259     // Wait for the status to move to violation
260     Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() {
261       @Override
262       public boolean evaluate() throws Exception {
263         SpaceQuotaSnapshot snapshot = QuotaTableUtil.getCurrentSnapshot(conn, tn);
264         LOG.info("Table snapshot after second ingest: " + snapshot);
265         if (snapshot == null) {
266           return false;
267         }
268         return snapshot.getQuotaStatus().isInViolation();
269       }
270     });
271     // The namespace should still not be in violation, but have a larger usage than previously
272     Waiter.waitFor(TEST_UTIL.getConfiguration(), 30 * 1000, new Predicate<Exception>() {
273       @Override
274       public boolean evaluate() throws Exception {
275         SpaceQuotaSnapshot snapshot = QuotaTableUtil.getCurrentSnapshot(
276             conn, tn.getNamespaceAsString());
277         LOG.debug("Namespace snapshot after second ingest: " + snapshot);
278         if (snapshot == null) {
279           return false;
280         }
281         return snapshot.getUsage() > nsUsage.get() && !snapshot.getQuotaStatus().isInViolation();
282       }
283     });
284   }
285 
286   private int countRegionsForTable(TableName tn, Map<HRegionInfo,Long> regionSizes) {
287     int size = 0;
288     for (HRegionInfo regionInfo : regionSizes.keySet()) {
289       if (tn.equals(regionInfo.getTable())) {
290         size++;
291       }
292     }
293     return size;
294   }
295 
296   private int getTableSize(TableName tn, Map<HRegionInfo,Long> regionSizes) {
297     int tableSize = 0;
298     for (Entry<HRegionInfo,Long> entry : regionSizes.entrySet()) {
299       HRegionInfo regionInfo = entry.getKey();
300       long regionSize = entry.getValue();
301       if (tn.equals(regionInfo.getTable())) {
302         tableSize += regionSize;
303       }
304     }
305     return tableSize;
306   }
307 }