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  
22  import java.io.IOException;
23  import java.util.Arrays;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Map.Entry;
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.HColumnDescriptor;
34  import org.apache.hadoop.hbase.HRegionInfo;
35  import org.apache.hadoop.hbase.HTableDescriptor;
36  import org.apache.hadoop.hbase.TableName;
37  import org.apache.hadoop.hbase.Waiter;
38  import org.apache.hadoop.hbase.client.Connection;
39  import org.apache.hadoop.hbase.client.HBaseAdmin;
40  import org.apache.hadoop.hbase.client.Put;
41  import org.apache.hadoop.hbase.client.Result;
42  import org.apache.hadoop.hbase.client.ResultScanner;
43  import org.apache.hadoop.hbase.client.Table;
44  import org.apache.hadoop.hbase.master.HMaster;
45  import org.apache.hadoop.hbase.testclassification.LargeTests;
46  import org.apache.hadoop.hbase.util.Bytes;
47  import org.junit.After;
48  import org.junit.Before;
49  import org.junit.Rule;
50  import org.junit.Test;
51  import org.junit.experimental.categories.Category;
52  import org.junit.rules.TestName;
53  
54  /**
55   * A test case to verify that region reports are expired when they are not sent.
56   */
57  @Category(LargeTests.class)
58  public class TestQuotaObserverChoreRegionReports {
59    private static final Log LOG = LogFactory.getLog(TestQuotaObserverChoreRegionReports.class);
60    private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
61  
62    @Rule
63    public TestName testName = new TestName();
64  
65    @Before
66    public void setUp() throws Exception {
67      Configuration conf = TEST_UTIL.getConfiguration();
68      conf.setInt(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_DELAY_KEY, 1000);
69      conf.setInt(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_PERIOD_KEY, 1000);
70      conf.setInt(QuotaObserverChore.QUOTA_OBSERVER_CHORE_DELAY_KEY, 1000);
71      conf.setInt(QuotaObserverChore.QUOTA_OBSERVER_CHORE_PERIOD_KEY, 1000);
72      conf.setBoolean(QuotaUtil.QUOTA_CONF_KEY, true);
73      conf.setInt(QuotaObserverChore.REGION_REPORT_RETENTION_DURATION_KEY, 1000);
74    }
75  
76    @After
77    public void tearDown() throws Exception {
78      TEST_UTIL.shutdownMiniCluster();
79    }
80    
81    @Test
82    public void testReportExpiration() throws Exception {
83      Configuration conf = TEST_UTIL.getConfiguration();
84      // Send reports every 30 seconds
85      conf.setInt(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_PERIOD_KEY, 25000);
86      // Expire the reports after 5 seconds
87      conf.setInt(QuotaObserverChore.REGION_REPORT_RETENTION_DURATION_KEY, 5000);
88      TEST_UTIL.startMiniCluster(1);
89  
90      final String FAM1 = "f1";
91      final HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster();
92      // Wait for the master to finish initialization.
93      while (master.getMasterQuotaManager() == null) {
94        LOG.debug("MasterQuotaManager is null, waiting...");
95        Thread.sleep(500);
96      }
97      final MasterQuotaManager quotaManager = master.getMasterQuotaManager();
98  
99      // Create a table
100     final TableName tn = TableName.valueOf("reportExpiration");
101     HTableDescriptor tableDesc = new HTableDescriptor(tn);
102     tableDesc.addFamily(new HColumnDescriptor(FAM1));
103     TEST_UTIL.getHBaseAdmin().createTable(tableDesc);
104 
105     // No reports right after we created this table.
106     assertEquals(0, getRegionReportsForTable(quotaManager.snapshotRegionSizes(), tn));
107 
108     // Set a quota
109     final long sizeLimit = 100L * SpaceQuotaHelperForTests.ONE_MEGABYTE;
110     final SpaceViolationPolicy violationPolicy = SpaceViolationPolicy.NO_INSERTS;
111     QuotaSettings settings = QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, violationPolicy);
112     TEST_UTIL.getHBaseAdmin().setQuota(settings);
113 
114     // We should get one report for the one region we have.
115     Waiter.waitFor(TEST_UTIL.getConfiguration(), 45000, 1000, new Waiter.Predicate<Exception>() {
116       @Override
117       public boolean evaluate() throws Exception {
118         int numReports = getRegionReportsForTable(quotaManager.snapshotRegionSizes(), tn);
119         LOG.debug("Saw " + numReports + " reports for " + tn + " while waiting for 1");
120         return numReports == 1;
121       }
122     });
123 
124     // We should then see no reports for the single region
125     Waiter.waitFor(TEST_UTIL.getConfiguration(), 15000, 1000, new Waiter.Predicate<Exception>() {
126       @Override
127       public boolean evaluate() throws Exception {
128         int numReports = getRegionReportsForTable(quotaManager.snapshotRegionSizes(), tn);
129         LOG.debug("Saw " + numReports + " reports for " + tn + " while waiting for none");
130         return numReports == 0;
131       }
132     });
133   }
134 
135   @Test
136   public void testMissingReportsRemovesQuota() throws Exception {
137     Configuration conf = TEST_UTIL.getConfiguration();
138     // Expire the reports after 5 seconds
139     conf.setInt(QuotaObserverChore.REGION_REPORT_RETENTION_DURATION_KEY, 5000);
140     TEST_UTIL.startMiniCluster(1);
141 
142     final String FAM1 = "f1";
143 
144     // Create a table
145     final TableName tn = TableName.valueOf("quotaAcceptanceWithoutReports");
146     HTableDescriptor tableDesc = new HTableDescriptor(tn);
147     tableDesc.addFamily(new HColumnDescriptor(FAM1));
148     TEST_UTIL.getHBaseAdmin().createTable(tableDesc);
149 
150     // Set a quota
151     final long sizeLimit = 1L * SpaceQuotaHelperForTests.ONE_KILOBYTE;
152     final SpaceViolationPolicy violationPolicy = SpaceViolationPolicy.NO_INSERTS;
153     QuotaSettings settings = QuotaSettingsFactory.limitTableSpace(tn, sizeLimit, violationPolicy);
154     final HBaseAdmin admin = TEST_UTIL.getHBaseAdmin();
155     admin.setQuota(settings);
156     final Connection conn = TEST_UTIL.getConnection();
157 
158     // Write enough data to invalidate the quota
159     Put p = new Put(Bytes.toBytes("row1"));
160     byte[] bytes = new byte[10];
161     Arrays.fill(bytes, (byte) 2);
162     for (int i = 0; i < 200; i++) {
163       p.addColumn(Bytes.toBytes(FAM1), Bytes.toBytes("qual" + i), bytes);
164     }
165     conn.getTable(tn).put(p);
166     admin.flush(tn);
167 
168     // Wait for the table to move into violation
169     Waiter.waitFor(TEST_UTIL.getConfiguration(), 30000, 1000, new Waiter.Predicate<Exception>() {
170       @Override
171       public boolean evaluate() throws Exception {
172         SpaceQuotaSnapshot snapshot = getSnapshotForTable(conn, tn);
173         if (snapshot == null) {
174           return false;
175         }
176         return snapshot.getQuotaStatus().isInViolation();
177       }
178     });
179 
180     // Close the region, prevent the server from sending new status reports.
181     List<HRegionInfo> regions = admin.getTableRegions(tn);
182     assertEquals(1, regions.size());
183     HRegionInfo hri = regions.get(0);
184     admin.closeRegion(TEST_UTIL.getMiniHBaseCluster().getRegionServer(0).getServerName(), hri);
185 
186     // We should see this table move out of violation after the report expires.
187     Waiter.waitFor(TEST_UTIL.getConfiguration(), 30000, 1000, new Waiter.Predicate<Exception>() {
188       @Override
189       public boolean evaluate() throws Exception {
190         SpaceQuotaSnapshot snapshot = getSnapshotForTable(conn, tn);
191         if (snapshot == null) {
192           return false;
193         }
194         return !snapshot.getQuotaStatus().isInViolation();
195       }
196     });
197 
198     // The QuotaObserverChore's memory should also show it not in violation.
199     final HMaster master = TEST_UTIL.getMiniHBaseCluster().getMaster();
200     QuotaSnapshotStore<TableName> tableStore =
201         master.getQuotaObserverChore().getTableSnapshotStore();
202     SpaceQuotaSnapshot snapshot = tableStore.getCurrentState(tn);
203     assertFalse("Quota should not be in violation", snapshot.getQuotaStatus().isInViolation());
204   }
205 
206   private SpaceQuotaSnapshot getSnapshotForTable(
207       Connection conn, TableName tn) throws IOException {
208     try (Table quotaTable = conn.getTable(QuotaUtil.QUOTA_TABLE_NAME);
209         ResultScanner scanner = quotaTable.getScanner(QuotaTableUtil.makeQuotaSnapshotScan())) {
210       Map<TableName,SpaceQuotaSnapshot> activeViolations = new HashMap<>();
211       for (Result result : scanner) {
212         try {
213           QuotaTableUtil.extractQuotaSnapshot(result, activeViolations);
214         } catch (IllegalArgumentException e) {
215           final String msg = "Failed to parse result for row " + Bytes.toString(result.getRow());
216           LOG.error(msg, e);
217           throw new IOException(msg, e);
218         }
219       }
220       return activeViolations.get(tn);
221     }
222   }
223 
224   private int getRegionReportsForTable(Map<HRegionInfo,Long> reports, TableName tn) {
225     int numReports = 0;
226     for (Entry<HRegionInfo,Long> entry : reports.entrySet()) {
227       if (tn.equals(entry.getKey().getTable())) {
228         numReports++;
229       }
230     }
231     return numReports;
232   }
233 }