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.apache.hbase.thirdparty.com.google.common.collect.Iterables.size; 021import static org.junit.jupiter.api.Assertions.assertEquals; 022import static org.junit.jupiter.api.Assertions.assertNull; 023import static org.mockito.ArgumentMatchers.any; 024import static org.mockito.Mockito.mock; 025import static org.mockito.Mockito.when; 026 027import java.io.IOException; 028import java.util.HashMap; 029import java.util.Map; 030import java.util.concurrent.atomic.AtomicReference; 031import org.apache.hadoop.hbase.NamespaceDescriptor; 032import org.apache.hadoop.hbase.TableName; 033import org.apache.hadoop.hbase.client.Connection; 034import org.apache.hadoop.hbase.client.Get; 035import org.apache.hadoop.hbase.client.RegionInfo; 036import org.apache.hadoop.hbase.client.RegionInfoBuilder; 037import org.apache.hadoop.hbase.client.Result; 038import org.apache.hadoop.hbase.client.Table; 039import org.apache.hadoop.hbase.testclassification.SmallTests; 040import org.apache.hadoop.hbase.util.Bytes; 041import org.junit.jupiter.api.BeforeEach; 042import org.junit.jupiter.api.Tag; 043import org.junit.jupiter.api.Test; 044import org.mockito.invocation.InvocationOnMock; 045import org.mockito.stubbing.Answer; 046 047import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil; 048import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos; 049import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.Quotas; 050import org.apache.hadoop.hbase.shaded.protobuf.generated.QuotaProtos.SpaceQuota; 051 052/** 053 * Test class for {@link NamespaceQuotaSnapshotStore}. 054 */ 055@Tag(SmallTests.TAG) 056public class TestNamespaceQuotaViolationStore { 057 058 private static final long ONE_MEGABYTE = 1024L * 1024L; 059 060 private Connection conn; 061 private QuotaObserverChore chore; 062 private Map<RegionInfo, Long> regionReports; 063 private NamespaceQuotaSnapshotStore store; 064 065 @BeforeEach 066 public void setup() { 067 conn = mock(Connection.class); 068 chore = mock(QuotaObserverChore.class); 069 regionReports = new HashMap<>(); 070 store = new NamespaceQuotaSnapshotStore(conn, chore, regionReports); 071 } 072 073 @Test 074 public void testGetSpaceQuota() throws Exception { 075 NamespaceQuotaSnapshotStore mockStore = mock(NamespaceQuotaSnapshotStore.class); 076 when(mockStore.getSpaceQuota(any())).thenCallRealMethod(); 077 078 Quotas quotaWithSpace = Quotas.newBuilder().setSpace(SpaceQuota.newBuilder().setSoftLimit(1024L) 079 .setViolationPolicy(QuotaProtos.SpaceViolationPolicy.DISABLE).build()).build(); 080 Quotas quotaWithoutSpace = Quotas.newBuilder().build(); 081 082 AtomicReference<Quotas> quotaRef = new AtomicReference<>(); 083 when(mockStore.getQuotaForNamespace(any())).then(new Answer<Quotas>() { 084 @Override 085 public Quotas answer(InvocationOnMock invocation) throws Throwable { 086 return quotaRef.get(); 087 } 088 }); 089 090 quotaRef.set(quotaWithSpace); 091 assertEquals(quotaWithSpace.getSpace(), mockStore.getSpaceQuota("ns")); 092 quotaRef.set(quotaWithoutSpace); 093 assertNull(mockStore.getSpaceQuota("ns")); 094 } 095 096 @Test 097 public void testTargetViolationState() throws IOException { 098 mockNoSnapshotSizes(); 099 final String NS = "ns"; 100 TableName tn1 = TableName.valueOf(NS, "tn1"); 101 TableName tn2 = TableName.valueOf(NS, "tn2"); 102 TableName tn3 = TableName.valueOf("tn3"); 103 SpaceQuota quota = SpaceQuota.newBuilder().setSoftLimit(ONE_MEGABYTE) 104 .setViolationPolicy(ProtobufUtil.toProtoViolationPolicy(SpaceViolationPolicy.DISABLE)) 105 .build(); 106 107 // Create some junk data to filter. Makes sure it's so large that it would 108 // immediately violate the quota. 109 for (int i = 0; i < 3; i++) { 110 111 regionReports.put(RegionInfoBuilder.newBuilder(tn3).setStartKey(Bytes.toBytes(i)) 112 .setEndKey(Bytes.toBytes(i + 1)).build(), 5L * ONE_MEGABYTE); 113 } 114 115 regionReports.put(RegionInfoBuilder.newBuilder(tn1).setStartKey(Bytes.toBytes(0)) 116 .setEndKey(Bytes.toBytes(1)).build(), 1024L * 512L); 117 regionReports.put(RegionInfoBuilder.newBuilder(tn1).setStartKey(Bytes.toBytes(1)) 118 .setEndKey(Bytes.toBytes(2)).build(), 1024L * 256L); 119 120 // Below the quota 121 assertEquals(false, store.getTargetState(NS, quota).getQuotaStatus().isInViolation()); 122 123 regionReports.put(RegionInfoBuilder.newBuilder(tn2).setStartKey(Bytes.toBytes(2)) 124 .setEndKey(Bytes.toBytes(3)).build(), 1024L * 256L); 125 126 // Equal to the quota is still in observance 127 assertEquals(false, store.getTargetState(NS, quota).getQuotaStatus().isInViolation()); 128 129 regionReports.put(RegionInfoBuilder.newBuilder(tn2).setStartKey(Bytes.toBytes(3)) 130 .setEndKey(Bytes.toBytes(4)).build(), 1024L); 131 132 // Exceeds the quota, should be in violation 133 assertEquals(true, store.getTargetState(NS, quota).getQuotaStatus().isInViolation()); 134 assertEquals(SpaceViolationPolicy.DISABLE, 135 store.getTargetState(NS, quota).getQuotaStatus().getPolicy().get()); 136 } 137 138 @Test 139 public void testFilterRegionsByNamespace() { 140 TableName tn1 = TableName.valueOf("foo"); 141 TableName tn2 = TableName.valueOf("sn", "bar"); 142 TableName tn3 = TableName.valueOf("ns", "foo"); 143 TableName tn4 = TableName.valueOf("ns", "bar"); 144 145 assertEquals(0, size(store.filterBySubject("asdf"))); 146 147 for (int i = 0; i < 5; i++) { 148 regionReports.put(RegionInfoBuilder.newBuilder(tn1).setStartKey(Bytes.toBytes(i)) 149 .setEndKey(Bytes.toBytes(i + 1)).build(), 0L); 150 } 151 for (int i = 0; i < 3; i++) { 152 regionReports.put(RegionInfoBuilder.newBuilder(tn2).setStartKey(Bytes.toBytes(i)) 153 .setEndKey(Bytes.toBytes(i + 1)).build(), 0L); 154 } 155 for (int i = 0; i < 10; i++) { 156 regionReports.put(RegionInfoBuilder.newBuilder(tn3).setStartKey(Bytes.toBytes(i)) 157 .setEndKey(Bytes.toBytes(i + 1)).build(), 0L); 158 } 159 for (int i = 0; i < 8; i++) { 160 regionReports.put(RegionInfoBuilder.newBuilder(tn4).setStartKey(Bytes.toBytes(i)) 161 .setEndKey(Bytes.toBytes(i + 1)).build(), 0L); 162 } 163 assertEquals(26, regionReports.size()); 164 assertEquals(5, size(store.filterBySubject(NamespaceDescriptor.DEFAULT_NAMESPACE_NAME_STR))); 165 assertEquals(3, size(store.filterBySubject("sn"))); 166 assertEquals(18, size(store.filterBySubject("ns"))); 167 } 168 169 void mockNoSnapshotSizes() throws IOException { 170 Table quotaTable = mock(Table.class); 171 when(conn.getTable(QuotaTableUtil.QUOTA_TABLE_NAME)).thenReturn(quotaTable); 172 when(quotaTable.get(any(Get.class))).thenReturn(new Result()); 173 } 174}