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}