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