1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase.master.normalizer;
20
21 import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.DEFAULT_MERGE_MIN_REGION_AGE_DAYS;
22 import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_ENABLED_KEY;
23 import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_MIN_REGION_AGE_DAYS_KEY;
24 import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MERGE_MIN_REGION_SIZE_MB_KEY;
25 import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.MIN_REGION_COUNT_KEY;
26 import static org.apache.hadoop.hbase.master.normalizer.SimpleRegionNormalizer.SPLIT_ENABLED_KEY;
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertFalse;
29 import static org.junit.Assert.assertTrue;
30 import static org.mockito.Matchers.any;
31 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
32 import static org.mockito.Mockito.when;
33 import com.google.protobuf.RpcController;
34 import com.google.protobuf.ServiceException;
35 import java.sql.Timestamp;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.concurrent.TimeUnit;
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.apache.hadoop.conf.Configuration;
45 import org.apache.hadoop.hbase.HBaseConfiguration;
46 import org.apache.hadoop.hbase.HBaseIOException;
47 import org.apache.hadoop.hbase.HRegionInfo;
48 import org.apache.hadoop.hbase.RegionLoad;
49 import org.apache.hadoop.hbase.ServerName;
50 import org.apache.hadoop.hbase.TableName;
51 import org.apache.hadoop.hbase.master.MasterRpcServices;
52 import org.apache.hadoop.hbase.master.MasterServices;
53 import org.apache.hadoop.hbase.master.RegionState;
54 import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSplitOrMergeEnabledRequest;
55 import org.apache.hadoop.hbase.protobuf.generated.MasterProtos.IsSplitOrMergeEnabledResponse;
56 import org.apache.hadoop.hbase.testclassification.SmallTests;
57 import org.apache.hadoop.hbase.util.Bytes;
58 import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
59 import org.junit.Before;
60 import org.junit.Rule;
61 import org.junit.Test;
62 import org.junit.experimental.categories.Category;
63 import org.junit.rules.TestName;
64 import org.mockito.Mockito;
65
66
67
68
69 @Category(SmallTests.class)
70 public class TestSimpleRegionNormalizer {
71 private static final Log LOG = LogFactory.getLog(TestSimpleRegionNormalizer.class);
72
73 private static SimpleRegionNormalizer normalizer;
74 private static Configuration conf;
75
76
77 private static MasterServices masterServices;
78 private static MasterRpcServices masterRpcServices;
79
80 @Rule
81 public TestName name = new TestName();
82
83 @Before
84 public void before() {
85 conf = HBaseConfiguration.create();
86 }
87
88 @Test
89 public void testNoNormalizationForMetaTable() throws HBaseIOException {
90 TableName testTable = TableName.META_TABLE_NAME;
91 List<HRegionInfo> hris = new ArrayList<>();
92 Map<byte[], Integer> regionSizes = new HashMap<>();
93
94 setupMocksForNormalizer(regionSizes, hris);
95 List<NormalizationPlan> plans = normalizer.computePlansForTable(testTable);
96 assertTrue(plans.isEmpty());
97 }
98
99 @Test
100 public void testNoNormalizationIfTooFewRegions() throws HBaseIOException {
101 TableName testTable = TableName.valueOf(name.getMethodName());
102 final List<HRegionInfo> regionInfos = createRegionInfos(testTable, 2);
103 Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 10, 15);
104 setupMocksForNormalizer(regionSizes, regionInfos);
105 List<NormalizationPlan> plans = normalizer.computePlansForTable(testTable);
106
107 assertTrue(plans.isEmpty());
108 }
109
110 @Test
111 public void testNoNormalizationOnNormalizedCluster() throws HBaseIOException {
112 TableName testTable = TableName.valueOf(name.getMethodName());
113 List<HRegionInfo> regionInfos = createRegionInfos(testTable, 4);
114 Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 10, 15, 8, 10);
115
116 setupMocksForNormalizer(regionSizes, regionInfos);
117 List<NormalizationPlan> plans = normalizer.computePlansForTable(testTable);
118 assertTrue(plans.isEmpty());
119 }
120
121 @Test
122 public void testMergeOfSmallRegions() throws HBaseIOException {
123 TableName testTable = TableName.valueOf(name.getMethodName());
124 List<HRegionInfo> regionInfos = createRegionInfos(testTable, 5);
125 Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 15, 5, 5, 15, 16);
126
127 setupMocksForNormalizer(regionSizes, regionInfos);
128 List<NormalizationPlan> plans = normalizer.computePlansForTable(testTable);
129
130 NormalizationPlan plan = plans.get(0);
131 assertTrue(plan instanceof MergeNormalizationPlan);
132 assertEquals(regionInfos.get(1), ((MergeNormalizationPlan) plan).getFirstRegion());
133 assertEquals(regionInfos.get(2), ((MergeNormalizationPlan) plan).getSecondRegion());
134 }
135
136
137 @Test
138 public void testMergeOfSecondSmallestRegions() throws HBaseIOException {
139 TableName testTable = TableName.valueOf(name.getMethodName());
140 List<HRegionInfo> regionInfos = createRegionInfos(testTable, 6);
141 Map<byte[], Integer> regionSizes =
142 createRegionSizesMap(regionInfos, 1, 10000, 10000, 10000, 2700, 2700);
143
144 setupMocksForNormalizer(regionSizes, regionInfos);
145 List<NormalizationPlan> plans = normalizer.computePlansForTable(testTable);
146 NormalizationPlan plan = plans.get(0);
147
148 assertTrue(plan instanceof MergeNormalizationPlan);
149 assertEquals(regionInfos.get(4), ((MergeNormalizationPlan) plan).getFirstRegion());
150 assertEquals(regionInfos.get(5), ((MergeNormalizationPlan) plan).getSecondRegion());
151 }
152
153 @Test
154 public void testMergeOfSmallNonAdjacentRegions() throws HBaseIOException {
155 TableName testTable = TableName.valueOf(name.getMethodName());
156 List<HRegionInfo> regionInfos = createRegionInfos(testTable, 5);
157 Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 15, 5, 16, 15, 5);
158
159 setupMocksForNormalizer(regionSizes, regionInfos);
160 List<NormalizationPlan> plans = normalizer.computePlansForTable(testTable);
161
162 assertTrue(plans.isEmpty());
163 }
164
165 @Test
166 public void testSplitOfLargeRegion() throws HBaseIOException {
167 TableName testTable = TableName.valueOf(name.getMethodName());
168 List<HRegionInfo> regionInfos = createRegionInfos(testTable, 4);
169 Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 8, 6, 10, 30);
170
171 setupMocksForNormalizer(regionSizes, regionInfos);
172 List<NormalizationPlan> plans = normalizer.computePlansForTable(testTable);
173 NormalizationPlan plan = plans.get(0);
174
175 assertTrue(plan instanceof SplitNormalizationPlan);
176 assertEquals(regionInfos.get(3), ((SplitNormalizationPlan) plan).getRegionInfo());
177 }
178
179 @Test
180 public void testSplitWithTargetRegionCount() throws Exception {
181 final TableName tableName = TableName.valueOf(name.getMethodName());
182 List<HRegionInfo> regionInfo = createRegionInfos(tableName, 6);
183 Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfo, 20, 40, 60, 80, 100, 120);
184 setupMocksForNormalizer(regionSizes, regionInfo);
185
186
187 when(
188 masterServices.getTableDescriptors().get((TableName) any()).getNormalizerTargetRegionSize())
189 .thenReturn(20L);
190 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
191 assertEquals(4, plans.size());
192
193 for (NormalizationPlan plan : plans) {
194 assertTrue(plan instanceof SplitNormalizationPlan);
195 }
196
197
198 when(
199 masterServices.getTableDescriptors().get((TableName) any()).getNormalizerTargetRegionSize())
200 .thenReturn(200L);
201 plans = normalizer.computePlansForTable(tableName);
202 assertEquals(2, plans.size());
203 NormalizationPlan plan = plans.get(0);
204 assertTrue(plan instanceof MergeNormalizationPlan);
205 assertEquals(regionInfo.get(0), ((MergeNormalizationPlan) plan).getFirstRegion());
206 assertEquals(regionInfo.get(1), ((MergeNormalizationPlan) plan).getSecondRegion());
207 }
208
209 @Test
210 public void testSplitWithTargetRegionSize() throws Exception {
211 final TableName tableName = TableName.valueOf(name.getMethodName());
212 final List<HRegionInfo> regionInfos = createRegionInfos(tableName, 4);
213 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 20, 40, 60, 80);
214 setupMocksForNormalizer(regionSizes, regionInfos);
215
216
217 when(
218 masterServices.getTableDescriptors().get((TableName) any()).getNormalizerTargetRegionCount())
219 .thenReturn(8);
220 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
221 assertEquals(2, plans.size());
222
223 for (NormalizationPlan plan : plans) {
224 assertTrue(plan instanceof SplitNormalizationPlan);
225 }
226
227
228 when(
229 masterServices.getTableDescriptors().get((TableName) any()).getNormalizerTargetRegionCount())
230 .thenReturn(3);
231 plans = normalizer.computePlansForTable(tableName);
232 assertEquals(1, plans.size());
233 NormalizationPlan plan = plans.get(0);
234 assertTrue(plan instanceof MergeNormalizationPlan);
235 assertEquals(regionInfos.get(0), ((MergeNormalizationPlan) plan).getFirstRegion());
236 assertEquals(regionInfos.get(1), ((MergeNormalizationPlan) plan).getSecondRegion());
237 }
238
239 @Test
240 public void testHonorsSplitEnabled() throws HBaseIOException {
241 conf.setBoolean(SPLIT_ENABLED_KEY, true);
242 final TableName tableName = TableName.valueOf(name.getMethodName());
243 final List<HRegionInfo> regionInfos = createRegionInfos(tableName, 5);
244 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 5, 5, 20, 5, 5);
245 setupMocksForNormalizer(regionSizes, regionInfos);
246 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
247 boolean present = false;
248 for (NormalizationPlan plan : plans) {
249 if (plan instanceof SplitNormalizationPlan) {
250 present = true;
251 break;
252 }
253 }
254 assertTrue(present);
255 conf.setBoolean(SPLIT_ENABLED_KEY, false);
256 setupMocksForNormalizer(regionSizes, regionInfos);
257 plans = normalizer.computePlansForTable(tableName);
258 assertTrue(plans.isEmpty());
259 }
260
261 @Test
262 public void testHonorsMergeEnabled() throws HBaseIOException {
263 conf.setBoolean(MERGE_ENABLED_KEY, true);
264 final TableName tableName = TableName.valueOf(name.getMethodName());
265 final List<HRegionInfo> regionInfos = createRegionInfos(tableName, 5);
266 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 20, 5, 5, 20, 20);
267 setupMocksForNormalizer(regionSizes, regionInfos);
268 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
269 boolean present = false;
270 for (NormalizationPlan plan : plans) {
271 if (plan instanceof MergeNormalizationPlan) {
272 present = true;
273 break;
274 }
275 }
276 assertTrue(present);
277 conf.setBoolean(MERGE_ENABLED_KEY, false);
278 setupMocksForNormalizer(regionSizes, regionInfos);
279 plans = normalizer.computePlansForTable(tableName);
280 assertTrue(plans.isEmpty());
281 }
282
283 @Test
284 public void testHonorsMinimumRegionCount() throws HBaseIOException {
285 conf.setInt(MIN_REGION_COUNT_KEY, 1);
286 final TableName tableName = TableName.valueOf(name.getMethodName());
287 final List<HRegionInfo> regionInfos = createRegionInfos(tableName, 3);
288
289
290
291 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 1, 10);
292 setupMocksForNormalizer(regionSizes, regionInfos);
293
294 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
295 boolean splitPlanPresent = false;
296 boolean mergePlanPresent = false;
297 for (NormalizationPlan plan : plans) {
298 if (plan instanceof MergeNormalizationPlan) {
299 mergePlanPresent = true;
300 break;
301 } else if (plan instanceof SplitNormalizationPlan) {
302 splitPlanPresent = true;
303 }
304 }
305 assertTrue(splitPlanPresent && mergePlanPresent);
306 SplitNormalizationPlan splitPlan = (SplitNormalizationPlan) plans.get(0);
307 assertEquals(regionInfos.get(2), splitPlan.getRegionInfo());
308 MergeNormalizationPlan mergePlan = (MergeNormalizationPlan) plans.get(1);
309 assertEquals(regionInfos.get(0), mergePlan.getFirstRegion());
310 assertEquals(regionInfos.get(1), mergePlan.getSecondRegion());
311
312
313 conf.setInt(MIN_REGION_COUNT_KEY, 4);
314 setupMocksForNormalizer(regionSizes, regionInfos);
315 plans = normalizer.computePlansForTable(tableName);
316 splitPlanPresent = false;
317 for (NormalizationPlan plan : plans) {
318 if (plan instanceof SplitNormalizationPlan) {
319 splitPlanPresent = true;
320 break;
321 }
322 }
323 assertTrue(splitPlanPresent);
324 splitPlan = (SplitNormalizationPlan) plans.get(0);
325 assertEquals(regionInfos.get(2), splitPlan.getRegionInfo());
326 }
327
328 @Test
329 public void testHonorsMergeMinRegionAge() throws HBaseIOException {
330 conf.setInt(MERGE_MIN_REGION_AGE_DAYS_KEY, 7);
331 final TableName tableName = TableName.valueOf(name.getMethodName());
332 final List<HRegionInfo> regionInfos = createRegionInfos(tableName, 4);
333 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 1, 10, 10);
334 setupMocksForNormalizer(regionSizes, regionInfos);
335 assertEquals(7, normalizer.getMergeMinRegionAge());
336 final List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
337 for (NormalizationPlan plan : plans) {
338 assertFalse(plan instanceof MergeNormalizationPlan);
339 }
340
341 conf.unset(MERGE_MIN_REGION_AGE_DAYS_KEY);
342 setupMocksForNormalizer(regionSizes, regionInfos);
343 assertEquals(DEFAULT_MERGE_MIN_REGION_AGE_DAYS, normalizer.getMergeMinRegionAge());
344 final List<NormalizationPlan> plans1 = normalizer.computePlansForTable(tableName);
345 assertTrue(!plans1.isEmpty());
346 for (NormalizationPlan plan : plans) {
347 assertTrue(plan instanceof MergeNormalizationPlan);
348 }
349 }
350
351 @Test
352 public void testHonorsMergeMinRegionSize() throws HBaseIOException {
353 conf.setBoolean(SPLIT_ENABLED_KEY, false);
354 final TableName tableName = TableName.valueOf(name.getMethodName());
355 final List<HRegionInfo> regionInfos = createRegionInfos(tableName, 5);
356 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 1, 2, 0, 10, 10);
357 setupMocksForNormalizer(regionSizes, regionInfos);
358
359 assertFalse(normalizer.isSplitEnabled());
360 assertEquals(1, normalizer.getMergeMinRegionSizeMb());
361 final List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
362 for (NormalizationPlan plan : plans) {
363 assertTrue(plan instanceof MergeNormalizationPlan);
364 }
365 assertEquals(plans.size(), 1);
366 final MergeNormalizationPlan plan = (MergeNormalizationPlan) plans.get(0);
367 assertEquals(regionInfos.get(0), plan.getFirstRegion());
368 assertEquals(regionInfos.get(1), plan.getSecondRegion());
369
370 conf.setInt(MERGE_MIN_REGION_SIZE_MB_KEY, 3);
371 setupMocksForNormalizer(regionSizes, regionInfos);
372 assertEquals(3, normalizer.getMergeMinRegionSizeMb());
373 assertTrue(normalizer.computePlansForTable(tableName).isEmpty());
374 }
375
376
377 @Test
378 public void testNormalizerCannotMergeNonAdjacentRegions() throws HBaseIOException {
379 final TableName tableName = TableName.valueOf(name.getMethodName());
380
381
382
383
384 final byte[][] keys = { null, Bytes.toBytes("aa"), Bytes.toBytes("aa1!"), Bytes.toBytes("aa1"),
385 Bytes.toBytes("aa2"), null, };
386 final List<HRegionInfo> regionInfos = createRegionInfos(tableName, keys);
387 final Map<byte[], Integer> regionSizes = createRegionSizesMap(regionInfos, 3, 1, 1, 3, 5);
388 setupMocksForNormalizer(regionSizes, regionInfos);
389
390
391 List<NormalizationPlan> plans = normalizer.computePlansForTable(tableName);
392 assertTrue(plans.isEmpty());
393 }
394
395 @SuppressWarnings("MockitoCast")
396 protected void setupMocksForNormalizer(Map<byte[], Integer> regionSizes, List<HRegionInfo> hris) {
397 masterServices = Mockito.mock(MasterServices.class, RETURNS_DEEP_STUBS);
398 masterRpcServices = Mockito.mock(MasterRpcServices.class, RETURNS_DEEP_STUBS);
399
400
401 ServerName sn = ServerName.valueOf("localhost", 0, 1L);
402 when(masterServices.getAssignmentManager().getRegionStates()
403 .getRegionsOfTable(any(TableName.class))).thenReturn(hris);
404 when(masterServices.getAssignmentManager().getRegionStates()
405 .getRegionServerOfRegion(any(HRegionInfo.class))).thenReturn(sn);
406 when(masterServices.getAssignmentManager().getRegionStates()
407 .isRegionInState(any(HRegionInfo.class), any(RegionState.State.class))).thenReturn(true);
408
409 for (Map.Entry<byte[], Integer> region : regionSizes.entrySet()) {
410 RegionLoad regionLoad = Mockito.mock(RegionLoad.class);
411 when(regionLoad.getName()).thenReturn(region.getKey());
412 when(regionLoad.getStorefileSizeMB()).thenReturn(region.getValue());
413
414
415
416
417 when((Object) masterServices.getServerManager().getLoad(sn).getRegionsLoad()
418 .get(region.getKey())).thenReturn(regionLoad);
419 }
420 try {
421 when(masterRpcServices.isSplitOrMergeEnabled(any(RpcController.class),
422 any(IsSplitOrMergeEnabledRequest.class)))
423 .thenReturn(IsSplitOrMergeEnabledResponse.newBuilder().setEnabled(true).build());
424 } catch (ServiceException se) {
425 LOG.debug("error setting isSplitOrMergeEnabled switch", se);
426 }
427
428 normalizer = new SimpleRegionNormalizer();
429 normalizer.setMasterServices(masterServices);
430 normalizer.setMasterRpcServices(masterRpcServices);
431 normalizer.setConf(conf);
432 }
433
434
435
436
437 private static List<HRegionInfo> createRegionInfos(final TableName tableName, final int length) {
438 if (length < 1) {
439 throw new IllegalStateException("length must be greater than or equal to 1.");
440 }
441
442 final byte[] startKey = Bytes.toBytes("aaaaa");
443 final byte[] endKey = Bytes.toBytes("zzzzz");
444 if (length == 1) {
445 return Collections.singletonList(createRegionInfo(tableName, startKey, endKey));
446 }
447
448 final byte[][] splitKeys = Bytes.split(startKey, endKey, length - 1);
449 final List<HRegionInfo> ret = new ArrayList<>(length);
450 for (int i = 0; i < splitKeys.length - 1; i++) {
451 ret.add(createRegionInfo(tableName, splitKeys[i], splitKeys[i + 1]));
452 }
453 return ret;
454 }
455
456 private static HRegionInfo createRegionInfo(final TableName tableName, final byte[] startKey,
457 final byte[] endKey) {
458 return new HRegionInfo(tableName, startKey, endKey, false, generateRegionId());
459 }
460
461 private static long generateRegionId() {
462 final Timestamp currentTime = new Timestamp(EnvironmentEdgeManager.currentTime());
463 return new Timestamp(
464 currentTime.getTime() - TimeUnit.DAYS.toMillis(DEFAULT_MERGE_MIN_REGION_AGE_DAYS + 1))
465 .getTime();
466 }
467
468 private static List<HRegionInfo> createRegionInfos(final TableName tableName,
469 final byte[][] splitKeys) {
470 final List<HRegionInfo> ret = new ArrayList<>(splitKeys.length);
471 for (int i = 0; i < splitKeys.length - 1; i++) {
472 ret.add(createRegionInfo(tableName, splitKeys[i], splitKeys[i + 1]));
473 }
474 return ret;
475 }
476
477 private static Map<byte[], Integer> createRegionSizesMap(final List<HRegionInfo> regionInfos,
478 int... sizes) {
479 if (regionInfos.size() != sizes.length) {
480 throw new IllegalStateException("Parameter lengths must match.");
481 }
482
483 final Map<byte[], Integer> ret = new HashMap<>(regionInfos.size());
484 for (int i = 0; i < regionInfos.size(); i++) {
485 ret.put(regionInfos.get(i).getRegionName(), sizes[i]);
486 }
487 return ret;
488 }
489 }