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.junit.Assert.assertEquals; 021import static org.junit.Assert.assertNotNull; 022import static org.mockito.ArgumentMatchers.any; 023import static org.mockito.Mockito.doAnswer; 024import static org.mockito.Mockito.doReturn; 025import static org.mockito.Mockito.mock; 026import static org.mockito.Mockito.when; 027 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.Collection; 031import java.util.Collections; 032import java.util.Iterator; 033import java.util.List; 034import java.util.Map; 035import java.util.concurrent.TimeUnit; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.HBaseClassTestRule; 038import org.apache.hadoop.hbase.HBaseConfiguration; 039import org.apache.hadoop.hbase.client.RegionInfo; 040import org.apache.hadoop.hbase.regionserver.HRegionServer; 041import org.apache.hadoop.hbase.regionserver.Region; 042import org.apache.hadoop.hbase.regionserver.Store; 043import org.apache.hadoop.hbase.testclassification.SmallTests; 044import org.junit.ClassRule; 045import org.junit.Test; 046import org.junit.experimental.categories.Category; 047import org.mockito.invocation.InvocationOnMock; 048import org.mockito.stubbing.Answer; 049 050/** 051 * Test class for {@link FileSystemUtilizationChore}. 052 */ 053@Category(SmallTests.class) 054public class TestFileSystemUtilizationChore { 055 056 @ClassRule 057 public static final HBaseClassTestRule CLASS_RULE = 058 HBaseClassTestRule.forClass(TestFileSystemUtilizationChore.class); 059 060 @Test 061 public void testNoOnlineRegions() { 062 // One region with a store size of one. 063 final List<Long> regionSizes = Collections.emptyList(); 064 final Configuration conf = getDefaultHBaseConfiguration(); 065 final HRegionServer rs = mockRegionServer(conf); 066 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs); 067 doAnswer(new ExpectedRegionSizeSummationAnswer(sum(regionSizes))).when(rs) 068 .reportRegionSizesForQuotas(any(RegionSizeStore.class)); 069 070 final Region region = mockRegionWithSize(regionSizes); 071 doReturn(Arrays.asList(region)).when(rs).getRegions(); 072 chore.chore(); 073 } 074 075 @Test 076 public void testRegionSizes() { 077 // One region with a store size of one. 078 final List<Long> regionSizes = Arrays.asList(1024L); 079 final Configuration conf = getDefaultHBaseConfiguration(); 080 final HRegionServer rs = mockRegionServer(conf); 081 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs); 082 doAnswer(new ExpectedRegionSizeSummationAnswer(sum(regionSizes))).when(rs) 083 .reportRegionSizesForQuotas(any(RegionSizeStore.class)); 084 085 final Region region = mockRegionWithSize(regionSizes); 086 doReturn(Arrays.asList(region)).when(rs).getRegions(); 087 chore.chore(); 088 } 089 090 @Test 091 public void testMultipleRegionSizes() { 092 final Configuration conf = getDefaultHBaseConfiguration(); 093 final HRegionServer rs = mockRegionServer(conf); 094 095 // Three regions with multiple store sizes 096 final List<Long> r1Sizes = Arrays.asList(1024L, 2048L); 097 final long r1Sum = sum(r1Sizes); 098 final List<Long> r2Sizes = Arrays.asList(1024L * 1024L); 099 final long r2Sum = sum(r2Sizes); 100 final List<Long> r3Sizes = Arrays.asList(10L * 1024L * 1024L); 101 final long r3Sum = sum(r3Sizes); 102 103 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs); 104 doAnswer(new ExpectedRegionSizeSummationAnswer(sum(Arrays.asList(r1Sum, r2Sum, r3Sum)))) 105 .when(rs).reportRegionSizesForQuotas(any(RegionSizeStore.class)); 106 107 final Region r1 = mockRegionWithSize(r1Sizes); 108 final Region r2 = mockRegionWithSize(r2Sizes); 109 final Region r3 = mockRegionWithSize(r3Sizes); 110 doReturn(Arrays.asList(r1, r2, r3)).when(rs).getRegions(); 111 chore.chore(); 112 } 113 114 @Test 115 public void testDefaultConfigurationProperties() { 116 final Configuration conf = getDefaultHBaseConfiguration(); 117 final HRegionServer rs = mockRegionServer(conf); 118 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs); 119 // Verify that the expected default values are actually represented. 120 assertEquals(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_PERIOD_DEFAULT, chore.getPeriod()); 121 assertEquals(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_DELAY_DEFAULT, 122 chore.getInitialDelay()); 123 assertEquals(TimeUnit.valueOf(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_TIMEUNIT_DEFAULT), 124 chore.getTimeUnit()); 125 } 126 127 @Test 128 public void testNonDefaultConfigurationProperties() { 129 final Configuration conf = getDefaultHBaseConfiguration(); 130 // Override the default values 131 final int period = 60 * 10; 132 final long delay = 30L; 133 final TimeUnit timeUnit = TimeUnit.SECONDS; 134 conf.setInt(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_PERIOD_KEY, period); 135 conf.setLong(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_DELAY_KEY, delay); 136 conf.set(FileSystemUtilizationChore.FS_UTILIZATION_CHORE_TIMEUNIT_KEY, timeUnit.name()); 137 138 // Verify that the chore reports these non-default values 139 final HRegionServer rs = mockRegionServer(conf); 140 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs); 141 assertEquals(period, chore.getPeriod()); 142 assertEquals(delay, chore.getInitialDelay()); 143 assertEquals(timeUnit, chore.getTimeUnit()); 144 } 145 146 @Test 147 public void testProcessingLeftoverRegions() { 148 final Configuration conf = getDefaultHBaseConfiguration(); 149 final HRegionServer rs = mockRegionServer(conf); 150 151 // Some leftover regions from a previous chore() 152 final List<Long> leftover1Sizes = Arrays.asList(1024L, 4096L); 153 final long leftover1Sum = sum(leftover1Sizes); 154 final List<Long> leftover2Sizes = Arrays.asList(2048L); 155 final long leftover2Sum = sum(leftover2Sizes); 156 157 final Region lr1 = mockRegionWithSize(leftover1Sizes); 158 final Region lr2 = mockRegionWithSize(leftover2Sizes); 159 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs) { 160 @Override 161 Iterator<Region> getLeftoverRegions() { 162 return Arrays.asList(lr1, lr2).iterator(); 163 } 164 }; 165 doAnswer(new ExpectedRegionSizeSummationAnswer(sum(Arrays.asList(leftover1Sum, leftover2Sum)))) 166 .when(rs).reportRegionSizesForQuotas(any(RegionSizeStore.class)); 167 168 // We shouldn't compute all of these region sizes, just the leftovers 169 final Region r1 = mockRegionWithSize(Arrays.asList(1024L, 2048L)); 170 final Region r2 = mockRegionWithSize(Arrays.asList(1024L * 1024L)); 171 final Region r3 = mockRegionWithSize(Arrays.asList(10L * 1024L * 1024L)); 172 doReturn(Arrays.asList(r1, r2, r3, lr1, lr2)).when(rs).getRegions(); 173 174 chore.chore(); 175 } 176 177 @Test 178 public void testProcessingNowOfflineLeftoversAreIgnored() { 179 final Configuration conf = getDefaultHBaseConfiguration(); 180 final HRegionServer rs = mockRegionServer(conf); 181 182 // Some leftover regions from a previous chore() 183 final List<Long> leftover1Sizes = Arrays.asList(1024L, 4096L); 184 final long leftover1Sum = sum(leftover1Sizes); 185 final List<Long> leftover2Sizes = Arrays.asList(2048L); 186 187 final Region lr1 = mockRegionWithSize(leftover1Sizes); 188 final Region lr2 = mockRegionWithSize(leftover2Sizes); 189 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs) { 190 @Override 191 Iterator<Region> getLeftoverRegions() { 192 return Arrays.asList(lr1, lr2).iterator(); 193 } 194 }; 195 doAnswer(new ExpectedRegionSizeSummationAnswer(sum(Arrays.asList(leftover1Sum)))).when(rs) 196 .reportRegionSizesForQuotas(any(RegionSizeStore.class)); 197 198 // We shouldn't compute all of these region sizes, just the leftovers 199 final Region r1 = mockRegionWithSize(Arrays.asList(1024L, 2048L)); 200 final Region r2 = mockRegionWithSize(Arrays.asList(1024L * 1024L)); 201 final Region r3 = mockRegionWithSize(Arrays.asList(10L * 1024L * 1024L)); 202 // lr2 is no longer online, so it should be ignored 203 doReturn(Arrays.asList(r1, r2, r3, lr1)).when(rs).getRegions(); 204 205 chore.chore(); 206 } 207 208 @Test 209 public void testIgnoreSplitParents() { 210 final Configuration conf = getDefaultHBaseConfiguration(); 211 final HRegionServer rs = mockRegionServer(conf); 212 213 // Three regions with multiple store sizes 214 final List<Long> r1Sizes = Arrays.asList(1024L, 2048L); 215 final long r1Sum = sum(r1Sizes); 216 final List<Long> r2Sizes = Arrays.asList(1024L * 1024L); 217 218 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs); 219 doAnswer(new ExpectedRegionSizeSummationAnswer(sum(Arrays.asList(r1Sum)))).when(rs) 220 .reportRegionSizesForQuotas(any(RegionSizeStore.class)); 221 222 final Region r1 = mockRegionWithSize(r1Sizes); 223 final Region r2 = mockSplitParentRegionWithSize(r2Sizes); 224 doReturn(Arrays.asList(r1, r2)).when(rs).getRegions(); 225 chore.chore(); 226 } 227 228 @Test 229 public void testIgnoreRegionReplicas() { 230 final Configuration conf = getDefaultHBaseConfiguration(); 231 final HRegionServer rs = mockRegionServer(conf); 232 233 // Two regions with multiple store sizes 234 final List<Long> r1Sizes = Arrays.asList(1024L, 2048L); 235 final long r1Sum = sum(r1Sizes); 236 final List<Long> r2Sizes = Arrays.asList(1024L * 1024L); 237 238 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs); 239 doAnswer(new ExpectedRegionSizeSummationAnswer(r1Sum)).when(rs) 240 .reportRegionSizesForQuotas(any(RegionSizeStore.class)); 241 242 final Region r1 = mockRegionWithSize(r1Sizes); 243 final Region r2 = mockRegionReplicaWithSize(r2Sizes); 244 doReturn(Arrays.asList(r1, r2)).when(rs).getRegions(); 245 chore.chore(); 246 } 247 248 @Test 249 public void testNonHFilesAreIgnored() { 250 final Configuration conf = getDefaultHBaseConfiguration(); 251 final HRegionServer rs = mockRegionServer(conf); 252 253 // Region r1 has two store files, one hfile link and one hfile 254 final List<Long> r1StoreFileSizes = Arrays.asList(1024L, 2048L); 255 final List<Long> r1HFileSizes = Arrays.asList(0L, 2048L); 256 final long r1HFileSizeSum = sum(r1HFileSizes); 257 // Region r2 has one store file which is a hfile link 258 final List<Long> r2StoreFileSizes = Arrays.asList(1024L * 1024L); 259 final List<Long> r2HFileSizes = Arrays.asList(0L); 260 final long r2HFileSizeSum = sum(r2HFileSizes); 261 262 // We expect that only the hfiles would be counted (hfile links are ignored) 263 final FileSystemUtilizationChore chore = new FileSystemUtilizationChore(rs); 264 doAnswer( 265 new ExpectedRegionSizeSummationAnswer(sum(Arrays.asList(r1HFileSizeSum, r2HFileSizeSum)))) 266 .when(rs).reportRegionSizesForQuotas(any(RegionSizeStore.class)); 267 268 final Region r1 = mockRegionWithHFileLinks(r1StoreFileSizes, r1HFileSizes); 269 final Region r2 = mockRegionWithHFileLinks(r2StoreFileSizes, r2HFileSizes); 270 doReturn(Arrays.asList(r1, r2)).when(rs).getRegions(); 271 chore.chore(); 272 } 273 274 /** 275 * Creates an HBase Configuration object for the default values. 276 */ 277 private Configuration getDefaultHBaseConfiguration() { 278 final Configuration conf = HBaseConfiguration.create(); 279 conf.addResource("hbase-default.xml"); 280 return conf; 281 } 282 283 /** 284 * Creates an HRegionServer using the given Configuration. 285 */ 286 private HRegionServer mockRegionServer(Configuration conf) { 287 final HRegionServer rs = mock(HRegionServer.class); 288 final RegionServerSpaceQuotaManager quotaManager = mock(RegionServerSpaceQuotaManager.class); 289 when(rs.getConfiguration()).thenReturn(conf); 290 when(rs.getRegionServerSpaceQuotaManager()).thenReturn(quotaManager); 291 when(quotaManager.getRegionSizeStore()).thenReturn(new RegionSizeStoreImpl()); 292 return rs; 293 } 294 295 /** 296 * Sums the collection of non-null numbers. 297 */ 298 private long sum(Collection<Long> values) { 299 long sum = 0L; 300 for (Long value : values) { 301 assertNotNull(value); 302 sum += value; 303 } 304 return sum; 305 } 306 307 /** 308 * Creates a region with a number of Stores equal to the length of {@code storeSizes}. Each 309 * {@link Store} will have a reported size corresponding to the element in {@code storeSizes}. 310 * @param storeSizes A list of sizes for each Store. 311 * @return A mocked Region. 312 */ 313 private Region mockRegionWithSize(Collection<Long> storeSizes) { 314 final Region r = mock(Region.class); 315 final RegionInfo info = mock(RegionInfo.class); 316 when(r.getRegionInfo()).thenReturn(info); 317 List<Store> stores = new ArrayList<>(); 318 when(r.getStores()).thenReturn((List) stores); 319 for (Long storeSize : storeSizes) { 320 final Store s = mock(Store.class); 321 stores.add(s); 322 when(s.getHFilesSize()).thenReturn(storeSize); 323 } 324 return r; 325 } 326 327 private Region mockRegionWithHFileLinks(Collection<Long> storeSizes, 328 Collection<Long> hfileSizes) { 329 final Region r = mock(Region.class); 330 final RegionInfo info = mock(RegionInfo.class); 331 when(r.getRegionInfo()).thenReturn(info); 332 List<Store> stores = new ArrayList<>(); 333 when(r.getStores()).thenReturn((List) stores); 334 assertEquals("Logic error, storeSizes and linkSizes must be equal in size", storeSizes.size(), 335 hfileSizes.size()); 336 Iterator<Long> storeSizeIter = storeSizes.iterator(); 337 Iterator<Long> hfileSizeIter = hfileSizes.iterator(); 338 while (storeSizeIter.hasNext() && hfileSizeIter.hasNext()) { 339 final long storeSize = storeSizeIter.next(); 340 final long hfileSize = hfileSizeIter.next(); 341 final Store s = mock(Store.class); 342 stores.add(s); 343 when(s.getStorefilesSize()).thenReturn(storeSize); 344 when(s.getHFilesSize()).thenReturn(hfileSize); 345 } 346 return r; 347 } 348 349 /** 350 * Creates a region which is the parent of a split. 351 * @param storeSizes A list of sizes for each Store. 352 * @return A mocked Region. 353 */ 354 private Region mockSplitParentRegionWithSize(Collection<Long> storeSizes) { 355 final Region r = mockRegionWithSize(storeSizes); 356 final RegionInfo info = r.getRegionInfo(); 357 when(info.isSplitParent()).thenReturn(true); 358 return r; 359 } 360 361 /** 362 * Creates a region who has a replicaId of <code>1</code>. 363 * @param storeSizes A list of sizes for each Store. 364 * @return A mocked Region. 365 */ 366 private Region mockRegionReplicaWithSize(Collection<Long> storeSizes) { 367 final Region r = mockRegionWithSize(storeSizes); 368 final RegionInfo info = r.getRegionInfo(); 369 when(info.getReplicaId()).thenReturn(1); 370 return r; 371 } 372 373 /** 374 * An Answer implementation which verifies the sum of the Region sizes to report is as expected. 375 */ 376 private static class ExpectedRegionSizeSummationAnswer implements Answer<Void> { 377 private final long expectedSize; 378 379 public ExpectedRegionSizeSummationAnswer(long expectedSize) { 380 this.expectedSize = expectedSize; 381 } 382 383 @Override 384 public Void answer(InvocationOnMock invocation) throws Throwable { 385 Object[] args = invocation.getArguments(); 386 assertEquals(1, args.length); 387 @SuppressWarnings("unchecked") 388 Map<RegionInfo, Long> regionSizes = (Map<RegionInfo, Long>) args[0]; 389 long sum = 0L; 390 for (Long regionSize : regionSizes.values()) { 391 sum += regionSize; 392 } 393 assertEquals(expectedSize, sum); 394 return null; 395 } 396 } 397}