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