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.assertTrue;
022import static org.junit.Assert.fail;
023import static org.mockito.Mockito.mock;
024import static org.mockito.Mockito.when;
025
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.HashSet;
031import java.util.Map;
032import java.util.Set;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.hbase.HBaseClassTestRule;
035import org.apache.hadoop.hbase.HBaseConfiguration;
036import org.apache.hadoop.hbase.TableName;
037import org.apache.hadoop.hbase.client.Admin;
038import org.apache.hadoop.hbase.client.Connection;
039import org.apache.hadoop.hbase.client.RegionInfo;
040import org.apache.hadoop.hbase.quotas.QuotaObserverChore.TablesWithQuotas;
041import org.apache.hadoop.hbase.testclassification.SmallTests;
042import org.junit.Before;
043import org.junit.ClassRule;
044import org.junit.Test;
045import org.junit.experimental.categories.Category;
046
047import org.apache.hbase.thirdparty.com.google.common.collect.Multimap;
048
049/**
050 * Non-HBase cluster unit tests for {@link TablesWithQuotas}.
051 */
052@Category(SmallTests.class)
053public class TestTablesWithQuotas {
054
055  @ClassRule
056  public static final HBaseClassTestRule CLASS_RULE =
057      HBaseClassTestRule.forClass(TestTablesWithQuotas.class);
058
059  private Connection conn;
060  private Configuration conf;
061
062  @Before
063  public void setup() throws Exception {
064    conn = mock(Connection.class);
065    conf = HBaseConfiguration.create();
066  }
067
068  @Test
069  public void testImmutableGetters() {
070    Set<TableName> tablesWithTableQuotas = new HashSet<>();
071    Set<TableName> tablesWithNamespaceQuotas = new HashSet<>();
072    final TablesWithQuotas tables = new TablesWithQuotas(conn, conf);
073    for (int i = 0; i < 5; i++) {
074      TableName tn = TableName.valueOf("tn" + i);
075      tablesWithTableQuotas.add(tn);
076      tables.addTableQuotaTable(tn);
077    }
078    for (int i = 0; i < 3; i++) {
079      TableName tn = TableName.valueOf("tn_ns" + i);
080      tablesWithNamespaceQuotas.add(tn);
081      tables.addNamespaceQuotaTable(tn);
082    }
083    Set<TableName> actualTableQuotaTables = tables.getTableQuotaTables();
084    Set<TableName> actualNamespaceQuotaTables = tables.getNamespaceQuotaTables();
085    assertEquals(tablesWithTableQuotas, actualTableQuotaTables);
086    assertEquals(tablesWithNamespaceQuotas, actualNamespaceQuotaTables);
087    try {
088      actualTableQuotaTables.add(null);
089      fail("Should not be able to add an element");
090    } catch (UnsupportedOperationException e) {
091      // pass
092    }
093    try {
094      actualNamespaceQuotaTables.add(null);
095      fail("Should not be able to add an element");
096    } catch (UnsupportedOperationException e) {
097      // pass
098    }
099  }
100
101  @Test
102  public void testInsufficientlyReportedTableFiltering() throws Exception {
103    final Map<TableName,Integer> reportedRegions = new HashMap<>();
104    final Map<TableName,Integer> actualRegions = new HashMap<>();
105    final Configuration conf = HBaseConfiguration.create();
106    conf.setDouble(QuotaObserverChore.QUOTA_OBSERVER_CHORE_REPORT_PERCENT_KEY, 0.95);
107
108    TableName tooFewRegionsTable = TableName.valueOf("tn1");
109    TableName sufficientRegionsTable = TableName.valueOf("tn2");
110    TableName tooFewRegionsNamespaceTable = TableName.valueOf("ns1", "tn2");
111    TableName sufficientRegionsNamespaceTable = TableName.valueOf("ns1", "tn2");
112    final TablesWithQuotas tables = new TablesWithQuotas(conn, conf) {
113      @Override
114      Configuration getConfiguration() {
115        return conf;
116      }
117
118      @Override
119      int getNumRegions(TableName tableName) {
120        return actualRegions.get(tableName);
121      }
122
123      @Override
124      int getNumReportedRegions(TableName table, QuotaSnapshotStore<TableName> tableStore) {
125        return reportedRegions.get(table);
126      }
127    };
128    tables.addTableQuotaTable(tooFewRegionsTable);
129    tables.addTableQuotaTable(sufficientRegionsTable);
130    tables.addNamespaceQuotaTable(tooFewRegionsNamespaceTable);
131    tables.addNamespaceQuotaTable(sufficientRegionsNamespaceTable);
132
133    reportedRegions.put(tooFewRegionsTable, 5);
134    actualRegions.put(tooFewRegionsTable, 10);
135    reportedRegions.put(sufficientRegionsTable, 19);
136    actualRegions.put(sufficientRegionsTable, 20);
137    reportedRegions.put(tooFewRegionsNamespaceTable, 9);
138    actualRegions.put(tooFewRegionsNamespaceTable, 10);
139    reportedRegions.put(sufficientRegionsNamespaceTable, 98);
140    actualRegions.put(sufficientRegionsNamespaceTable, 100);
141
142    // Unused argument
143    tables.filterInsufficientlyReportedTables(null);
144    Set<TableName> filteredTablesWithTableQuotas = tables.getTableQuotaTables();
145    assertEquals(Collections.singleton(sufficientRegionsTable), filteredTablesWithTableQuotas);
146    Set<TableName> filteredTablesWithNamespaceQutoas = tables.getNamespaceQuotaTables();
147    assertEquals(Collections.singleton(sufficientRegionsNamespaceTable), filteredTablesWithNamespaceQutoas);
148  }
149
150  @Test
151  public void testGetTablesByNamespace() {
152    final TablesWithQuotas tables = new TablesWithQuotas(conn, conf);
153    tables.addTableQuotaTable(TableName.valueOf("ignored1"));
154    tables.addTableQuotaTable(TableName.valueOf("ignored2"));
155    tables.addNamespaceQuotaTable(TableName.valueOf("ns1", "t1"));
156    tables.addNamespaceQuotaTable(TableName.valueOf("ns1", "t2"));
157    tables.addNamespaceQuotaTable(TableName.valueOf("ns1", "t3"));
158    tables.addNamespaceQuotaTable(TableName.valueOf("ns2", "t1"));
159    tables.addNamespaceQuotaTable(TableName.valueOf("ns2", "t2"));
160
161    Multimap<String,TableName> tablesByNamespace = tables.getTablesByNamespace();
162    Collection<TableName> tablesInNs = tablesByNamespace.get("ns1");
163    assertEquals(3, tablesInNs.size());
164    assertTrue("Unexpected results for ns1: " + tablesInNs,
165        tablesInNs.containsAll(Arrays.asList(
166            TableName.valueOf("ns1", "t1"),
167            TableName.valueOf("ns1", "t2"),
168            TableName.valueOf("ns1", "t3"))));
169    tablesInNs = tablesByNamespace.get("ns2");
170    assertEquals(2, tablesInNs.size());
171    assertTrue("Unexpected results for ns2: " + tablesInNs,
172        tablesInNs.containsAll(Arrays.asList(
173            TableName.valueOf("ns2", "t1"),
174            TableName.valueOf("ns2", "t2"))));
175  }
176
177  @Test
178  public void testFilteringMissingTables() throws Exception {
179    final TableName missingTable = TableName.valueOf("doesNotExist");
180    // Set up Admin to return null (match the implementation)
181    Admin admin = mock(Admin.class);
182    when(conn.getAdmin()).thenReturn(admin);
183    when(admin.getTableRegions(missingTable)).thenReturn(null);
184
185    QuotaObserverChore chore = mock(QuotaObserverChore.class);
186    Map<RegionInfo,Long> regionUsage = new HashMap<>();
187    TableQuotaSnapshotStore store = new TableQuotaSnapshotStore(conn, chore, regionUsage);
188
189    // A super dirty hack to verify that, after getting no regions for our table,
190    // we bail out and start processing the next element (which there is none).
191    final TablesWithQuotas tables = new TablesWithQuotas(conn, conf) {
192      @Override
193      int getNumReportedRegions(TableName table, QuotaSnapshotStore<TableName> tableStore) {
194        throw new RuntimeException("Should should not reach here");
195      }
196    };
197    tables.addTableQuotaTable(missingTable);
198
199    tables.filterInsufficientlyReportedTables(store);
200
201    final Set<TableName> tablesWithQuotas = tables.getTableQuotaTables();
202    assertTrue(
203        "Expected to find no tables, but found " + tablesWithQuotas, tablesWithQuotas.isEmpty());
204  }
205}