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.hadoop.hbase.util.Bytes.toBytes; 021import static org.junit.jupiter.api.Assertions.fail; 022import static org.mockito.ArgumentMatchers.any; 023import static org.mockito.Mockito.doCallRealMethod; 024import static org.mockito.Mockito.mock; 025import static org.mockito.Mockito.never; 026import static org.mockito.Mockito.verify; 027import static org.mockito.Mockito.when; 028 029import java.io.IOException; 030import java.util.ArrayList; 031import java.util.Collections; 032import java.util.HashMap; 033import java.util.List; 034import java.util.Map; 035import java.util.Map.Entry; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.Cell; 038import org.apache.hadoop.hbase.HBaseConfiguration; 039import org.apache.hadoop.hbase.KeyValue; 040import org.apache.hadoop.hbase.TableName; 041import org.apache.hadoop.hbase.client.Connection; 042import org.apache.hadoop.hbase.client.Result; 043import org.apache.hadoop.hbase.client.ResultScanner; 044import org.apache.hadoop.hbase.client.Scan; 045import org.apache.hadoop.hbase.client.Table; 046import org.apache.hadoop.hbase.quotas.SpaceQuotaSnapshot.SpaceQuotaStatus; 047import org.apache.hadoop.hbase.regionserver.RegionServerServices; 048import org.apache.hadoop.hbase.testclassification.SmallTests; 049import org.junit.jupiter.api.BeforeEach; 050import org.junit.jupiter.api.Tag; 051import org.junit.jupiter.api.Test; 052 053/** 054 * Test class for {@link SpaceQuotaRefresherChore}. 055 */ 056@Tag(SmallTests.TAG) 057public class TestSpaceQuotaViolationPolicyRefresherChore { 058 059 private RegionServerSpaceQuotaManager manager; 060 private RegionServerServices rss; 061 private SpaceQuotaRefresherChore chore; 062 private Configuration conf; 063 private Connection conn; 064 065 @BeforeEach 066 public void setup() throws IOException { 067 conf = HBaseConfiguration.create(); 068 rss = mock(RegionServerServices.class); 069 manager = mock(RegionServerSpaceQuotaManager.class); 070 conn = mock(Connection.class); 071 when(manager.getRegionServerServices()).thenReturn(rss); 072 when(rss.getConfiguration()).thenReturn(conf); 073 074 chore = mock(SpaceQuotaRefresherChore.class); 075 when(chore.getConnection()).thenReturn(conn); 076 when(chore.getManager()).thenReturn(manager); 077 when(chore.checkQuotaTableExists()).thenReturn(true); 078 doCallRealMethod().when(chore).chore(); 079 when(chore.isInViolation(any())).thenCallRealMethod(); 080 doCallRealMethod().when(chore).extractQuotaSnapshot(any(), any()); 081 } 082 083 @Test 084 public void testPoliciesAreEnforced() throws IOException { 085 // Create a number of policies that should be enforced (usage > limit) 086 final Map<TableName, SpaceQuotaSnapshot> policiesToEnforce = new HashMap<>(); 087 policiesToEnforce.put(TableName.valueOf("table1"), 088 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.DISABLE), 1024L, 512L)); 089 policiesToEnforce.put(TableName.valueOf("table2"), 090 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_INSERTS), 2048L, 512L)); 091 policiesToEnforce.put(TableName.valueOf("table3"), 092 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_WRITES), 4096L, 512L)); 093 policiesToEnforce.put(TableName.valueOf("table4"), new SpaceQuotaSnapshot( 094 new SpaceQuotaStatus(SpaceViolationPolicy.NO_WRITES_COMPACTIONS), 8192L, 512L)); 095 096 // No active enforcements 097 when(manager.copyQuotaSnapshots()).thenReturn(Collections.emptyMap()); 098 // Policies to enforce 099 when(chore.fetchSnapshotsFromQuotaTable()).thenReturn(policiesToEnforce); 100 101 chore.chore(); 102 103 for (Entry<TableName, SpaceQuotaSnapshot> entry : policiesToEnforce.entrySet()) { 104 // Ensure we enforce the policy 105 verify(manager).enforceViolationPolicy(entry.getKey(), entry.getValue()); 106 // Don't disable any policies 107 verify(manager, never()).disableViolationPolicyEnforcement(entry.getKey()); 108 } 109 } 110 111 @Test 112 public void testOldPoliciesAreRemoved() throws IOException { 113 final Map<TableName, SpaceQuotaSnapshot> previousPolicies = new HashMap<>(); 114 previousPolicies.put(TableName.valueOf("table3"), 115 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_WRITES), 4096L, 512L)); 116 previousPolicies.put(TableName.valueOf("table4"), 117 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_WRITES), 8192L, 512L)); 118 119 final Map<TableName, SpaceQuotaSnapshot> policiesToEnforce = new HashMap<>(); 120 policiesToEnforce.put(TableName.valueOf("table1"), 121 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.DISABLE), 1024L, 512L)); 122 policiesToEnforce.put(TableName.valueOf("table2"), 123 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_INSERTS), 2048L, 512L)); 124 policiesToEnforce.put(TableName.valueOf("table3"), 125 new SpaceQuotaSnapshot(SpaceQuotaStatus.notInViolation(), 256L, 512L)); 126 policiesToEnforce.put(TableName.valueOf("table4"), 127 new SpaceQuotaSnapshot(SpaceQuotaStatus.notInViolation(), 128L, 512L)); 128 129 // No active enforcements 130 when(manager.copyQuotaSnapshots()).thenReturn(previousPolicies); 131 // Policies to enforce 132 when(chore.fetchSnapshotsFromQuotaTable()).thenReturn(policiesToEnforce); 133 134 chore.chore(); 135 136 verify(manager).enforceViolationPolicy(TableName.valueOf("table1"), 137 policiesToEnforce.get(TableName.valueOf("table1"))); 138 verify(manager).enforceViolationPolicy(TableName.valueOf("table2"), 139 policiesToEnforce.get(TableName.valueOf("table2"))); 140 141 verify(manager).disableViolationPolicyEnforcement(TableName.valueOf("table3")); 142 verify(manager).disableViolationPolicyEnforcement(TableName.valueOf("table4")); 143 } 144 145 @Test 146 public void testNewPolicyOverridesOld() throws IOException { 147 final Map<TableName, SpaceQuotaSnapshot> policiesToEnforce = new HashMap<>(); 148 policiesToEnforce.put(TableName.valueOf("table1"), 149 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.DISABLE), 1024L, 512L)); 150 policiesToEnforce.put(TableName.valueOf("table2"), 151 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_WRITES), 2048L, 512L)); 152 policiesToEnforce.put(TableName.valueOf("table3"), 153 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_INSERTS), 4096L, 512L)); 154 155 final Map<TableName, SpaceQuotaSnapshot> previousPolicies = new HashMap<>(); 156 previousPolicies.put(TableName.valueOf("table1"), 157 new SpaceQuotaSnapshot(new SpaceQuotaStatus(SpaceViolationPolicy.NO_WRITES), 8192L, 512L)); 158 159 // No active enforcements 160 when(manager.getActivePoliciesAsMap()).thenReturn(previousPolicies); 161 // Policies to enforce 162 when(chore.fetchSnapshotsFromQuotaTable()).thenReturn(policiesToEnforce); 163 164 chore.chore(); 165 166 for (Entry<TableName, SpaceQuotaSnapshot> entry : policiesToEnforce.entrySet()) { 167 verify(manager).enforceViolationPolicy(entry.getKey(), entry.getValue()); 168 } 169 verify(manager, never()).disableViolationPolicyEnforcement(TableName.valueOf("table1")); 170 } 171 172 @Test 173 public void testMissingAllColumns() throws IOException { 174 when(chore.fetchSnapshotsFromQuotaTable()).thenCallRealMethod(); 175 ResultScanner scanner = mock(ResultScanner.class); 176 Table quotaTable = mock(Table.class); 177 when(conn.getTable(QuotaUtil.QUOTA_TABLE_NAME)).thenReturn(quotaTable); 178 when(quotaTable.getScanner(any(Scan.class))).thenReturn(scanner); 179 180 List<Result> results = new ArrayList<>(); 181 results.add(Result.create(Collections.emptyList())); 182 when(scanner.iterator()).thenReturn(results.iterator()); 183 try { 184 chore.fetchSnapshotsFromQuotaTable(); 185 fail("Expected an IOException, but did not receive one."); 186 } catch (IOException e) { 187 // Expected an error because we had no cells in the row. 188 // This should only happen due to programmer error. 189 } 190 } 191 192 @Test 193 public void testMissingDesiredColumn() throws IOException { 194 when(chore.fetchSnapshotsFromQuotaTable()).thenCallRealMethod(); 195 ResultScanner scanner = mock(ResultScanner.class); 196 Table quotaTable = mock(Table.class); 197 when(conn.getTable(QuotaUtil.QUOTA_TABLE_NAME)).thenReturn(quotaTable); 198 when(quotaTable.getScanner(any(Scan.class))).thenReturn(scanner); 199 200 List<Result> results = new ArrayList<>(); 201 // Give a column that isn't the one we want 202 Cell c = new KeyValue(toBytes("t:inviolation"), toBytes("q"), toBytes("s"), new byte[0]); 203 results.add(Result.create(Collections.singletonList(c))); 204 when(scanner.iterator()).thenReturn(results.iterator()); 205 try { 206 chore.fetchSnapshotsFromQuotaTable(); 207 fail("Expected an IOException, but did not receive one."); 208 } catch (IOException e) { 209 // Expected an error because we were missing the column we expected in this row. 210 // This should only happen due to programmer error. 211 } 212 } 213 214 @Test 215 public void testParsingError() throws IOException { 216 when(chore.fetchSnapshotsFromQuotaTable()).thenCallRealMethod(); 217 ResultScanner scanner = mock(ResultScanner.class); 218 Table quotaTable = mock(Table.class); 219 when(conn.getTable(QuotaUtil.QUOTA_TABLE_NAME)).thenReturn(quotaTable); 220 when(quotaTable.getScanner(any(Scan.class))).thenReturn(scanner); 221 222 List<Result> results = new ArrayList<>(); 223 Cell c = new KeyValue(toBytes("t:inviolation"), toBytes("u"), toBytes("v"), new byte[0]); 224 results.add(Result.create(Collections.singletonList(c))); 225 when(scanner.iterator()).thenReturn(results.iterator()); 226 try { 227 chore.fetchSnapshotsFromQuotaTable(); 228 fail("Expected an IOException, but did not receive one."); 229 } catch (IOException e) { 230 // We provided a garbage serialized protobuf message (empty byte array), this should 231 // in turn throw an IOException 232 } 233 } 234}