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.util; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertTrue; 022 023import java.util.ArrayList; 024import java.util.Collection; 025import java.util.Comparator; 026import java.util.List; 027import java.util.SortedSet; 028import java.util.UUID; 029import org.apache.hadoop.hbase.HBaseClassTestRule; 030import org.apache.hadoop.hbase.testclassification.MiscTests; 031import org.apache.hadoop.hbase.testclassification.SmallTests; 032import org.junit.ClassRule; 033import org.junit.Test; 034import org.junit.experimental.categories.Category; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import org.apache.hbase.thirdparty.com.google.common.collect.ComparisonChain; 039import org.apache.hbase.thirdparty.com.google.common.collect.Multimap; 040 041@Category({MiscTests.class, SmallTests.class}) 042public class TestRegionSplitCalculator { 043 044 @ClassRule 045 public static final HBaseClassTestRule CLASS_RULE = 046 HBaseClassTestRule.forClass(TestRegionSplitCalculator.class); 047 048 private static final Logger LOG = LoggerFactory.getLogger(TestRegionSplitCalculator.class); 049 050 /** 051 * This is range uses a user specified start and end keys. It also has an 052 * extra tiebreaker so that different ranges with the same start/end key pair 053 * count as different regions. 054 */ 055 static class SimpleRange implements KeyRange { 056 byte[] start, end; 057 UUID tiebreaker; 058 059 SimpleRange(byte[] start, byte[] end) { 060 this.start = start; 061 this.end = end; 062 this.tiebreaker = UUID.randomUUID(); 063 } 064 065 @Override 066 public byte[] getStartKey() { 067 return start; 068 } 069 070 @Override 071 public byte[] getEndKey() { 072 return end; 073 } 074 075 @Override 076 public String toString() { 077 return "[" + Bytes.toString(start) + ", " + Bytes.toString(end) + "]"; 078 } 079 } 080 081 Comparator<SimpleRange> cmp = new Comparator<SimpleRange>() { 082 @Override 083 public int compare(SimpleRange sr1, SimpleRange sr2) { 084 ComparisonChain cc = ComparisonChain.start(); 085 cc = cc.compare(sr1.getStartKey(), sr2.getStartKey(), 086 Bytes.BYTES_COMPARATOR); 087 cc = cc.compare(sr1.getEndKey(), sr2.getEndKey(), 088 RegionSplitCalculator.BYTES_COMPARATOR); 089 cc = cc.compare(sr1.tiebreaker, sr2.tiebreaker); 090 return cc.result(); 091 } 092 }; 093 094 /** 095 * Check the "depth" (number of regions included at a split) of a generated 096 * split calculation 097 */ 098 void checkDepths(SortedSet<byte[]> splits, 099 Multimap<byte[], SimpleRange> regions, Integer... depths) { 100 assertEquals(splits.size(), depths.length); 101 int i = 0; 102 for (byte[] k : splits) { 103 Collection<SimpleRange> rs = regions.get(k); 104 int sz = rs == null ? 0 : rs.size(); 105 assertEquals((int) depths[i], sz); 106 i++; 107 } 108 } 109 110 /** 111 * This dumps data in a visually reasonable way for visual debugging. It has 112 * the basic iteration structure. 113 */ 114 String dump(SortedSet<byte[]> splits, Multimap<byte[], SimpleRange> regions) { 115 // we display this way because the last end key should be displayed as well. 116 StringBuilder sb = new StringBuilder(); 117 for (byte[] k : splits) { 118 sb.append(Bytes.toString(k)).append(":\t"); 119 for (SimpleRange r : regions.get(k)) { 120 sb.append(r.toString()).append("\t"); 121 } 122 sb.append("\n"); 123 } 124 String s = sb.toString(); 125 LOG.info("\n" + s); 126 return s; 127 } 128 129 @Test 130 public void testSplitCalculator() { 131 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")); 132 SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C")); 133 SimpleRange c = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("D")); 134 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 135 sc.add(a); 136 sc.add(b); 137 sc.add(c); 138 139 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 140 LOG.info("Standard"); 141 String res = dump(sc.getSplits(), regions); 142 checkDepths(sc.getSplits(), regions, 1, 1, 1, 0); 143 assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t[C, D]\t\nD:\t\n", res); 144 } 145 146 @Test 147 public void testSplitCalculatorNoEdge() { 148 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 149 150 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 151 LOG.info("Empty"); 152 String res = dump(sc.getSplits(), regions); 153 checkDepths(sc.getSplits(), regions); 154 assertEquals("", res); 155 } 156 157 @Test 158 public void testSplitCalculatorSingleEdge() { 159 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")); 160 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 161 sc.add(a); 162 163 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 164 LOG.info("Single edge"); 165 String res = dump(sc.getSplits(), regions); 166 checkDepths(sc.getSplits(), regions, 1, 0); 167 assertEquals("A:\t[A, B]\t\n" + "B:\t\n", res); 168 } 169 170 @Test 171 public void testSplitCalculatorDegenerateEdge() { 172 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("A")); 173 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 174 sc.add(a); 175 176 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 177 LOG.info("Single empty edge"); 178 String res = dump(sc.getSplits(), regions); 179 checkDepths(sc.getSplits(), regions, 1); 180 assertEquals("A:\t[A, A]\t\n", res); 181 } 182 183 @Test 184 public void testSplitCalculatorCoverSplit() { 185 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")); 186 SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C")); 187 SimpleRange c = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")); 188 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 189 sc.add(a); 190 sc.add(b); 191 sc.add(c); 192 193 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 194 LOG.info("AC covers AB, BC"); 195 String res = dump(sc.getSplits(), regions); 196 checkDepths(sc.getSplits(), regions, 2, 2, 0); 197 assertEquals("A:\t[A, B]\t[A, C]\t\n" + "B:\t[A, C]\t[B, C]\t\n" 198 + "C:\t\n", res); 199 } 200 201 @Test 202 public void testSplitCalculatorOverEndpoint() { 203 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")); 204 SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C")); 205 SimpleRange c = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("D")); 206 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 207 sc.add(a); 208 sc.add(b); 209 sc.add(c); 210 211 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 212 LOG.info("AB, BD covers BC"); 213 String res = dump(sc.getSplits(), regions); 214 checkDepths(sc.getSplits(), regions, 1, 2, 1, 0); 215 assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t[B, D]\t\n" 216 + "C:\t[B, D]\t\n" + "D:\t\n", res); 217 } 218 219 @Test 220 public void testSplitCalculatorHoles() { 221 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")); 222 SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C")); 223 SimpleRange c = new SimpleRange(Bytes.toBytes("E"), Bytes.toBytes("F")); 224 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 225 sc.add(a); 226 sc.add(b); 227 sc.add(c); 228 229 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 230 LOG.info("Hole between C and E"); 231 String res = dump(sc.getSplits(), regions); 232 checkDepths(sc.getSplits(), regions, 1, 1, 0, 1, 0); 233 assertEquals("A:\t[A, B]\t\n" + "B:\t[B, C]\t\n" + "C:\t\n" 234 + "E:\t[E, F]\t\n" + "F:\t\n", res); 235 } 236 237 @Test 238 public void testSplitCalculatorOverreach() { 239 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")); 240 SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("D")); 241 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 242 sc.add(a); 243 sc.add(b); 244 245 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 246 LOG.info("AC and BD overlap but share no start/end keys"); 247 String res = dump(sc.getSplits(), regions); 248 checkDepths(sc.getSplits(), regions, 1, 2, 1, 0); 249 assertEquals("A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, D]\t\n" 250 + "C:\t[B, D]\t\n" + "D:\t\n", res); 251 } 252 253 @Test 254 public void testSplitCalculatorFloor() { 255 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")); 256 SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B")); 257 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 258 sc.add(a); 259 sc.add(b); 260 261 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 262 LOG.info("AC and AB overlap in the beginning"); 263 String res = dump(sc.getSplits(), regions); 264 checkDepths(sc.getSplits(), regions, 2, 1, 0); 265 assertEquals("A:\t[A, B]\t[A, C]\t\n" + "B:\t[A, C]\t\n" + "C:\t\n", res); 266 } 267 268 @Test 269 public void testSplitCalculatorCeil() { 270 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")); 271 SimpleRange b = new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C")); 272 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 273 sc.add(a); 274 sc.add(b); 275 276 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 277 LOG.info("AC and BC overlap in the end"); 278 String res = dump(sc.getSplits(), regions); 279 checkDepths(sc.getSplits(), regions, 1, 2, 0); 280 assertEquals("A:\t[A, C]\t\n" + "B:\t[A, C]\t[B, C]\t\n" + "C:\t\n", res); 281 } 282 283 @Test 284 public void testSplitCalculatorEq() { 285 SimpleRange a = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")); 286 SimpleRange b = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")); 287 288 LOG.info(a.tiebreaker + " - " + b.tiebreaker); 289 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 290 sc.add(a); 291 sc.add(b); 292 293 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 294 LOG.info("AC and AC overlap completely"); 295 String res = dump(sc.getSplits(), regions); 296 checkDepths(sc.getSplits(), regions, 2, 0); 297 assertEquals("A:\t[A, C]\t[A, C]\t\n" + "C:\t\n", res); 298 } 299 300 @Test 301 public void testSplitCalculatorBackwards() { 302 SimpleRange a = new SimpleRange(Bytes.toBytes("C"), Bytes.toBytes("A")); 303 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 304 sc.add(a); 305 306 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 307 LOG.info("CA is backwards"); 308 String res = dump(sc.getSplits(), regions); 309 checkDepths(sc.getSplits(), regions); // expect nothing 310 assertEquals("", res); 311 } 312 313 @Test 314 public void testComplex() { 315 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 316 sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("Am"))); 317 sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"))); 318 sc.add(new SimpleRange(Bytes.toBytes("Am"), Bytes.toBytes("C"))); 319 sc.add(new SimpleRange(Bytes.toBytes("D"), Bytes.toBytes("E"))); 320 sc.add(new SimpleRange(Bytes.toBytes("F"), Bytes.toBytes("G"))); 321 sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("E"))); 322 sc.add(new SimpleRange(Bytes.toBytes("H"), Bytes.toBytes("I"))); 323 sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"))); 324 325 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 326 LOG.info("Something fairly complex"); 327 String res = dump(sc.getSplits(), regions); 328 checkDepths(sc.getSplits(), regions, 3, 3, 3, 1, 2, 0, 1, 0, 1, 0); 329 assertEquals("A:\t[A, Am]\t[A, B]\t[A, C]\t\n" 330 + "Am:\t[A, B]\t[A, C]\t[Am, C]\t\n" 331 + "B:\t[A, C]\t[Am, C]\t[B, E]\t\n" + "C:\t[B, E]\t\n" 332 + "D:\t[B, E]\t[D, E]\t\n" + "E:\t\n" + "F:\t[F, G]\t\n" + "G:\t\n" 333 + "H:\t[H, I]\t\n" + "I:\t\n", res); 334 } 335 336 @Test 337 public void testBeginEndMarker() { 338 RegionSplitCalculator<SimpleRange> sc = new RegionSplitCalculator<>(cmp); 339 sc.add(new SimpleRange(Bytes.toBytes(""), Bytes.toBytes("A"))); 340 sc.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"))); 341 sc.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes(""))); 342 343 Multimap<byte[], SimpleRange> regions = sc.calcCoverage(); 344 LOG.info("Special cases -- empty"); 345 String res = dump(sc.getSplits(), regions); 346 checkDepths(sc.getSplits(), regions, 1, 1, 1, 0); 347 assertEquals(":\t[, A]\t\n" + "A:\t[A, B]\t\n" + "B:\t[B, ]\t\n" 348 + "null:\t\n", res); 349 } 350 351 @Test 352 public void testBigRanges() { 353 SimpleRange ai = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("I")); 354 SimpleRange ae = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("E")); 355 SimpleRange ac = new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C")); 356 357 Collection<SimpleRange> bigOverlap = new ArrayList<>(8); 358 bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("E"))); 359 bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("C"))); 360 bigOverlap.add(new SimpleRange(Bytes.toBytes("A"), Bytes.toBytes("B"))); 361 bigOverlap.add(new SimpleRange(Bytes.toBytes("B"), Bytes.toBytes("C"))); 362 bigOverlap.add(new SimpleRange(Bytes.toBytes("E"), Bytes.toBytes("H"))); 363 bigOverlap.add(ai); 364 bigOverlap.add(ae); 365 bigOverlap.add(ac); 366 367 // Expect 1 range to be returned: ai 368 List<SimpleRange> bigRanges = RegionSplitCalculator.findBigRanges(bigOverlap, 1); 369 assertEquals(1, bigRanges.size()); 370 assertEquals(ai, bigRanges.get(0)); 371 372 // Expect 3 ranges to be returned: ai, ae and ac 373 bigRanges = RegionSplitCalculator.findBigRanges(bigOverlap, 3); 374 assertEquals(3, bigRanges.size()); 375 assertEquals(ai, bigRanges.get(0)); 376 377 SimpleRange r1 = bigRanges.get(1); 378 SimpleRange r2 = bigRanges.get(2); 379 assertEquals("A", Bytes.toString(r1.start)); 380 assertEquals("A", Bytes.toString(r2.start)); 381 String r1e = Bytes.toString(r1.end); 382 String r2e = Bytes.toString(r2.end); 383 assertTrue((r1e.equals("C") && r2e.equals("E")) 384 || (r1e.equals("E") && r2e.equals("C"))); 385 } 386 387} 388