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