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