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