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}