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.assertTrue;
023
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.HashMap;
027import java.util.HashSet;
028import java.util.List;
029import java.util.Map;
030import java.util.Set;
031import java.util.concurrent.TimeUnit;
032
033import org.apache.hadoop.hbase.Cell;
034import org.apache.hadoop.hbase.CellScanner;
035import org.apache.hadoop.hbase.HBaseClassTestRule;
036import org.apache.hadoop.hbase.HBaseTestingUtility;
037import org.apache.hadoop.hbase.HConstants;
038import org.apache.hadoop.hbase.NamespaceDescriptor;
039import org.apache.hadoop.hbase.TableName;
040import org.apache.hadoop.hbase.client.Connection;
041import org.apache.hadoop.hbase.client.ConnectionFactory;
042import org.apache.hadoop.hbase.client.Delete;
043import org.apache.hadoop.hbase.client.Put;
044import org.apache.hadoop.hbase.client.Result;
045import org.apache.hadoop.hbase.client.ResultScanner;
046import org.apache.hadoop.hbase.client.Table;
047import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot.SpaceQuotaStatus;
048import org.apache.hadoop.hbase.testclassification.MasterTests;
049import org.apache.hadoop.hbase.testclassification.MediumTests;
050import org.junit.After;
051import org.junit.AfterClass;
052import org.junit.Before;
053import org.junit.BeforeClass;
054import org.junit.ClassRule;
055import org.junit.Rule;
056import org.junit.Test;
057import org.junit.experimental.categories.Category;
058import org.junit.rules.TestName;
059
060import org.apache.hbase.thirdparty.com.google.common.collect.HashMultimap;
061import org.apache.hbase.thirdparty.com.google.common.collect.Multimap;
062import org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations;
063
064import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
065import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos;
066import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas;
067import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Throttle;
068
069/**
070 * Test the quota table helpers (e.g. CRUD operations)
071 */
072@Category({MasterTests.class, MediumTests.class})
073public class TestQuotaTableUtil {
074
075  @ClassRule
076  public static final HBaseClassTestRule CLASS_RULE =
077      HBaseClassTestRule.forClass(TestQuotaTableUtil.class);
078
079  private final static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
080  private Connection connection;
081  private int tableNameCounter;
082
083  @Rule
084  public TestName testName = new TestName();
085
086  @Rule
087  public TestName name = new TestName();
088
089  @BeforeClass
090  public static void setUpBeforeClass() throws Exception {
091    TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, true);
092    TEST_UTIL.getConfiguration().setInt(QuotaCache.REFRESH_CONF_KEY, 2000);
093    TEST_UTIL.getConfiguration().setInt("hbase.hstore.compactionThreshold", 10);
094    TEST_UTIL.getConfiguration().setInt("hbase.regionserver.msginterval", 100);
095    TEST_UTIL.getConfiguration().setInt("hbase.client.pause", 250);
096    TEST_UTIL.getConfiguration().setInt(HConstants.HBASE_CLIENT_RETRIES_NUMBER, 6);
097    TEST_UTIL.getConfiguration().setBoolean("hbase.master.enabletable.roundrobin", true);
098    TEST_UTIL.startMiniCluster(1);
099    TEST_UTIL.waitTableAvailable(QuotaTableUtil.QUOTA_TABLE_NAME);
100  }
101
102  @AfterClass
103  public static void tearDownAfterClass() throws Exception {
104    TEST_UTIL.shutdownMiniCluster();
105  }
106
107  @Before
108  public void before() throws IOException {
109    this.connection = ConnectionFactory.createConnection(TEST_UTIL.getConfiguration());
110    this.tableNameCounter = 0;
111  }
112
113  @After
114  public void after() throws IOException {
115    this.connection.close();
116  }
117
118  @Test
119  public void testDeleteSnapshots() throws Exception {
120    TableName tn = TableName.valueOf(name.getMethodName());
121    try (Table t = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) {
122      Quotas quota = Quotas.newBuilder().setSpace(
123          QuotaProtos.SpaceQuota.newBuilder().setSoftLimit(7L)
124              .setViolationPolicy(QuotaProtos.SpaceViolationPolicy.NO_WRITES).build()).build();
125      QuotaUtil.addTableQuota(connection, tn, quota);
126
127      String snapshotName = name.getMethodName() + "_snapshot";
128      t.put(QuotaTableUtil.createPutForSnapshotSize(tn, snapshotName, 3L));
129      t.put(QuotaTableUtil.createPutForSnapshotSize(tn, snapshotName, 5L));
130      assertEquals(1, QuotaTableUtil.getObservedSnapshotSizes(connection).size());
131
132      List<Delete> deletes = QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(connection);
133      assertEquals(1, deletes.size());
134
135      t.delete(deletes);
136      assertEquals(0, QuotaTableUtil.getObservedSnapshotSizes(connection).size());
137
138      String ns = name.getMethodName();
139      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns, 5L));
140      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns, 3L));
141      assertEquals(3L, QuotaTableUtil.getNamespaceSnapshotSize(connection, ns));
142
143      deletes = QuotaTableUtil.createDeletesForExistingNamespaceSnapshotSizes(connection);
144      assertEquals(1, deletes.size());
145
146      t.delete(deletes);
147      assertEquals(0L, QuotaTableUtil.getNamespaceSnapshotSize(connection, ns));
148
149      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t1"), "s1", 3L));
150      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t2"), "s2", 3L));
151      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t3"), "s3", 3L));
152      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t4"), "s4", 3L));
153      t.put(QuotaTableUtil.createPutForSnapshotSize(TableName.valueOf("t1"), "s5", 3L));
154
155      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize("ns1", 3L));
156      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize("ns2", 3L));
157      t.put(QuotaTableUtil.createPutForNamespaceSnapshotSize("ns3", 3L));
158
159      assertEquals(5,QuotaTableUtil.getTableSnapshots(connection).size());
160      assertEquals(3,QuotaTableUtil.getNamespaceSnapshots(connection).size());
161
162      Multimap<TableName, String> tableSnapshotEntriesToRemove = HashMultimap.create();
163      tableSnapshotEntriesToRemove.put(TableName.valueOf("t1"), "s1");
164      tableSnapshotEntriesToRemove.put(TableName.valueOf("t3"), "s3");
165      tableSnapshotEntriesToRemove.put(TableName.valueOf("t4"), "s4");
166
167      Set<String> namespaceSnapshotEntriesToRemove = new HashSet<>();
168      namespaceSnapshotEntriesToRemove.add("ns2");
169      namespaceSnapshotEntriesToRemove.add("ns1");
170
171      deletes =
172          QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(tableSnapshotEntriesToRemove);
173      assertEquals(3, deletes.size());
174      deletes = QuotaTableUtil
175          .createDeletesForExistingNamespaceSnapshotSizes(namespaceSnapshotEntriesToRemove);
176      assertEquals(2, deletes.size());
177    }
178  }
179
180  @Test
181  public void testTableQuotaUtil() throws Exception {
182    final TableName tableName = TableName.valueOf(name.getMethodName());
183
184    Quotas quota = Quotas.newBuilder()
185              .setThrottle(Throttle.newBuilder()
186                .setReqNum(ProtobufUtil.toTimedQuota(1000, TimeUnit.SECONDS, QuotaScope.MACHINE))
187                .setWriteNum(ProtobufUtil.toTimedQuota(600, TimeUnit.SECONDS, QuotaScope.MACHINE))
188                .setReadSize(ProtobufUtil.toTimedQuota(8192, TimeUnit.SECONDS, QuotaScope.MACHINE))
189                .build())
190              .build();
191
192    // Add user quota and verify it
193    QuotaUtil.addTableQuota(this.connection, tableName, quota);
194    Quotas resQuota = QuotaUtil.getTableQuota(this.connection, tableName);
195    assertEquals(quota, resQuota);
196
197    // Remove user quota and verify it
198    QuotaUtil.deleteTableQuota(this.connection, tableName);
199    resQuota = QuotaUtil.getTableQuota(this.connection, tableName);
200    assertEquals(null, resQuota);
201  }
202
203  @Test
204  public void testNamespaceQuotaUtil() throws Exception {
205    final String namespace = "testNamespaceQuotaUtilNS";
206
207    Quotas quota = Quotas.newBuilder()
208              .setThrottle(Throttle.newBuilder()
209                .setReqNum(ProtobufUtil.toTimedQuota(1000, TimeUnit.SECONDS, QuotaScope.MACHINE))
210                .setWriteNum(ProtobufUtil.toTimedQuota(600, TimeUnit.SECONDS, QuotaScope.MACHINE))
211                .setReadSize(ProtobufUtil.toTimedQuota(8192, TimeUnit.SECONDS, QuotaScope.MACHINE))
212                .build())
213              .build();
214
215    // Add user quota and verify it
216    QuotaUtil.addNamespaceQuota(this.connection, namespace, quota);
217    Quotas resQuota = QuotaUtil.getNamespaceQuota(this.connection, namespace);
218    assertEquals(quota, resQuota);
219
220    // Remove user quota and verify it
221    QuotaUtil.deleteNamespaceQuota(this.connection, namespace);
222    resQuota = QuotaUtil.getNamespaceQuota(this.connection, namespace);
223    assertEquals(null, resQuota);
224  }
225
226  @Test
227  public void testUserQuotaUtil() throws Exception {
228    final TableName tableName = TableName.valueOf(name.getMethodName());
229    final String namespace = "testNS";
230    final String user = "testUser";
231
232    Quotas quotaNamespace = Quotas.newBuilder()
233              .setThrottle(Throttle.newBuilder()
234                .setReqNum(ProtobufUtil.toTimedQuota(50000, TimeUnit.SECONDS, QuotaScope.MACHINE))
235                .build())
236              .build();
237    Quotas quotaTable = Quotas.newBuilder()
238              .setThrottle(Throttle.newBuilder()
239                .setReqNum(ProtobufUtil.toTimedQuota(1000, TimeUnit.SECONDS, QuotaScope.MACHINE))
240                .setWriteNum(ProtobufUtil.toTimedQuota(600, TimeUnit.SECONDS, QuotaScope.MACHINE))
241                .setReadSize(ProtobufUtil.toTimedQuota(10000, TimeUnit.SECONDS, QuotaScope.MACHINE))
242                .build())
243              .build();
244    Quotas quota = Quotas.newBuilder()
245              .setThrottle(Throttle.newBuilder()
246                .setReqSize(ProtobufUtil.toTimedQuota(8192, TimeUnit.SECONDS, QuotaScope.MACHINE))
247                .setWriteSize(ProtobufUtil.toTimedQuota(4096, TimeUnit.SECONDS, QuotaScope.MACHINE))
248                .setReadNum(ProtobufUtil.toTimedQuota(1000, TimeUnit.SECONDS, QuotaScope.MACHINE))
249                .build())
250              .build();
251
252    // Add user global quota
253    QuotaUtil.addUserQuota(this.connection, user, quota);
254    Quotas resQuota = QuotaUtil.getUserQuota(this.connection, user);
255    assertEquals(quota, resQuota);
256
257    // Add user quota for table
258    QuotaUtil.addUserQuota(this.connection, user, tableName, quotaTable);
259    Quotas resQuotaTable = QuotaUtil.getUserQuota(this.connection, user, tableName);
260    assertEquals(quotaTable, resQuotaTable);
261
262    // Add user quota for namespace
263    QuotaUtil.addUserQuota(this.connection, user, namespace, quotaNamespace);
264    Quotas resQuotaNS = QuotaUtil.getUserQuota(this.connection, user, namespace);
265    assertEquals(quotaNamespace, resQuotaNS);
266
267    // Delete user global quota
268    QuotaUtil.deleteUserQuota(this.connection, user);
269    resQuota = QuotaUtil.getUserQuota(this.connection, user);
270    assertEquals(null, resQuota);
271
272    // Delete user quota for table
273    QuotaUtil.deleteUserQuota(this.connection, user, tableName);
274    resQuotaTable = QuotaUtil.getUserQuota(this.connection, user, tableName);
275    assertEquals(null, resQuotaTable);
276
277    // Delete user quota for namespace
278    QuotaUtil.deleteUserQuota(this.connection, user, namespace);
279    resQuotaNS = QuotaUtil.getUserQuota(this.connection, user, namespace);
280    assertEquals(null, resQuotaNS);
281  }
282
283  @Test
284  public void testSerDeViolationPolicies() throws Exception {
285    final TableName tn1 = getUniqueTableName();
286    final SpaceQuotaSnapshot snapshot1 = new SpaceQuotaSnapshot(
287        new SpaceQuotaStatus(SpaceViolationPolicy.DISABLE), 512L, 1024L);
288    final TableName tn2 = getUniqueTableName();
289    final SpaceQuotaSnapshot snapshot2 = new SpaceQuotaSnapshot(
290        new SpaceQuotaStatus(SpaceViolationPolicy.NO_INSERTS), 512L, 1024L);
291    final TableName tn3 = getUniqueTableName();
292    final SpaceQuotaSnapshot snapshot3 = new SpaceQuotaSnapshot(
293        new SpaceQuotaStatus(SpaceViolationPolicy.NO_WRITES), 512L, 1024L);
294    List<Put> puts = new ArrayList<>();
295    puts.add(QuotaTableUtil.createPutForSpaceSnapshot(tn1, snapshot1));
296    puts.add(QuotaTableUtil.createPutForSpaceSnapshot(tn2, snapshot2));
297    puts.add(QuotaTableUtil.createPutForSpaceSnapshot(tn3, snapshot3));
298    final Map<TableName,SpaceQuotaSnapshot> expectedPolicies = new HashMap<>();
299    expectedPolicies.put(tn1, snapshot1);
300    expectedPolicies.put(tn2, snapshot2);
301    expectedPolicies.put(tn3, snapshot3);
302
303    final Map<TableName,SpaceQuotaSnapshot> actualPolicies = new HashMap<>();
304    try (Table quotaTable = connection.getTable(QuotaUtil.QUOTA_TABLE_NAME)) {
305      quotaTable.put(puts);
306      ResultScanner scanner = quotaTable.getScanner(QuotaTableUtil.makeQuotaSnapshotScan());
307      for (Result r : scanner) {
308        QuotaTableUtil.extractQuotaSnapshot(r, actualPolicies);
309      }
310      scanner.close();
311    }
312
313    assertEquals(expectedPolicies, actualPolicies);
314  }
315
316  @Test
317  public void testSerdeTableSnapshotSizes() throws Exception {
318    TableName tn1 = TableName.valueOf("tn1");
319    TableName tn2 = TableName.valueOf("tn2");
320    try (Table quotaTable = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) {
321      for (int i = 0; i < 3; i++) {
322        Put p = QuotaTableUtil.createPutForSnapshotSize(tn1, "tn1snap" + i, 1024L * (1+i));
323        quotaTable.put(p);
324      }
325      for (int i = 0; i < 3; i++) {
326        Put p = QuotaTableUtil.createPutForSnapshotSize(tn2, "tn2snap" + i, 2048L * (1+i));
327        quotaTable.put(p);
328      }
329
330      verifyTableSnapshotSize(quotaTable, tn1, "tn1snap0", 1024L);
331      verifyTableSnapshotSize(quotaTable, tn1, "tn1snap1", 2048L);
332      verifyTableSnapshotSize(quotaTable, tn1, "tn1snap2", 3072L);
333
334      verifyTableSnapshotSize(quotaTable, tn2, "tn2snap0", 2048L);
335      verifyTableSnapshotSize(quotaTable, tn2, "tn2snap1", 4096L);
336      verifyTableSnapshotSize(quotaTable, tn2, "tn2snap2", 6144L);
337
338      cleanUpSnapshotSizes();
339    }
340  }
341
342  @Test
343  public void testReadNamespaceSnapshotSizes() throws Exception {
344    String ns1 = "ns1";
345    String ns2 = "ns2";
346    String defaultNs = NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR;
347    try (Table quotaTable = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) {
348      quotaTable.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns1, 1024L));
349      quotaTable.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(ns2, 2048L));
350      quotaTable.put(QuotaTableUtil.createPutForNamespaceSnapshotSize(defaultNs, 8192L));
351
352      assertEquals(1024L, QuotaTableUtil.getNamespaceSnapshotSize(connection, ns1));
353      assertEquals(2048L, QuotaTableUtil.getNamespaceSnapshotSize(connection, ns2));
354      assertEquals(8192L, QuotaTableUtil.getNamespaceSnapshotSize(connection, defaultNs));
355
356      cleanUpSnapshotSizes();
357    }
358  }
359
360  private TableName getUniqueTableName() {
361    return TableName.valueOf(testName.getMethodName() + "_" + tableNameCounter++);
362  }
363
364  private void verifyTableSnapshotSize(
365      Table quotaTable, TableName tn, String snapshotName, long expectedSize) throws IOException {
366    Result r = quotaTable.get(QuotaTableUtil.makeGetForSnapshotSize(tn, snapshotName));
367    CellScanner cs = r.cellScanner();
368    assertTrue(cs.advance());
369    Cell c = cs.current();
370    assertEquals(expectedSize, QuotaProtos.SpaceQuotaSnapshot.parseFrom(
371        UnsafeByteOperations.unsafeWrap(
372            c.getValueArray(), c.getValueOffset(), c.getValueLength())).getQuotaUsage());
373    assertFalse(cs.advance());
374  }
375
376  private void cleanUpSnapshotSizes() throws IOException {
377    try (Table t = connection.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)) {
378      QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(connection);
379      List<Delete> deletes =
380          QuotaTableUtil.createDeletesForExistingNamespaceSnapshotSizes(connection);
381      deletes.addAll(QuotaTableUtil.createDeletesForExistingTableSnapshotSizes(connection));
382      t.delete(deletes);
383    }
384  }
385}