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