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