| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.accumulo.server.master.balancer; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| |
| import java.security.SecureRandom; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Random; |
| import java.util.Set; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| import java.util.function.Function; |
| |
| import org.apache.accumulo.core.data.TableId; |
| import org.apache.accumulo.core.dataImpl.KeyExtent; |
| import org.apache.accumulo.core.master.thrift.TabletServerStatus; |
| import org.apache.accumulo.core.metadata.TServerInstance; |
| import org.apache.accumulo.core.util.MapCounter; |
| import org.apache.accumulo.server.master.state.TabletMigration; |
| import org.apache.hadoop.io.Text; |
| import org.junit.Test; |
| |
| @Deprecated(since = "2.1.0") |
| public class GroupBalancerTest { |
| |
| private static Function<KeyExtent,String> partitioner = new Function<>() { |
| |
| @Override |
| public String apply(KeyExtent input) { |
| return (input == null || input.endRow() == null) ? null |
| : input.endRow().toString().substring(0, 2); |
| } |
| }; |
| |
| public static class TabletServers { |
| private final Set<TServerInstance> tservers = new HashSet<>(); |
| private final Map<KeyExtent,TServerInstance> tabletLocs = new HashMap<>(); |
| |
| public void addTservers(String... locs) { |
| for (String loc : locs) { |
| addTserver(loc); |
| } |
| } |
| |
| public void addTserver(String loc) { |
| tservers.add(new TServerInstance(loc, 6)); |
| } |
| |
| public void addTablet(String er, String location) { |
| TServerInstance tsi = new TServerInstance(location, 6); |
| tabletLocs.put(new KeyExtent(TableId.of("b"), er == null ? null : new Text(er), null), |
| new TServerInstance(location, 6)); |
| tservers.add(tsi); |
| } |
| |
| public void balance() { |
| balance(10000); |
| } |
| |
| public void balance(final int maxMigrations) { |
| GroupBalancer balancer = new GroupBalancer(TableId.of("1")) { |
| |
| @Override |
| protected Map<KeyExtent,TServerInstance> getLocationProvider() { |
| return tabletLocs; |
| } |
| |
| @Override |
| protected Function<KeyExtent,String> getPartitioner() { |
| return partitioner; |
| } |
| |
| @Override |
| protected long getWaitTime() { |
| return 0; |
| } |
| |
| @Override |
| protected int getMaxMigrations() { |
| return maxMigrations; |
| } |
| }; |
| |
| balance(balancer, maxMigrations); |
| } |
| |
| public void balance(TabletBalancer balancer, int maxMigrations) { |
| |
| while (true) { |
| Set<KeyExtent> migrations = new HashSet<>(); |
| List<TabletMigration> migrationsOut = new ArrayList<>(); |
| SortedMap<TServerInstance,TabletServerStatus> current = new TreeMap<>(); |
| |
| for (TServerInstance tsi : tservers) { |
| current.put(tsi, new TabletServerStatus()); |
| } |
| |
| balancer.balance(current, migrations, migrationsOut); |
| |
| assertTrue("Max Migration exceeded " + maxMigrations + " " + migrationsOut.size(), |
| migrationsOut.size() <= (maxMigrations + 5)); |
| |
| for (TabletMigration tabletMigration : migrationsOut) { |
| assertEquals(tabletLocs.get(tabletMigration.tablet), tabletMigration.oldServer); |
| assertTrue(tservers.contains(tabletMigration.newServer)); |
| |
| tabletLocs.put(tabletMigration.tablet, tabletMigration.newServer); |
| } |
| |
| if (migrationsOut.isEmpty()) { |
| break; |
| } |
| } |
| |
| checkBalance(); |
| } |
| |
| void checkBalance() { |
| MapCounter<String> groupCounts = new MapCounter<>(); |
| Map<TServerInstance,MapCounter<String>> tserverGroupCounts = new HashMap<>(); |
| |
| for (Entry<KeyExtent,TServerInstance> entry : tabletLocs.entrySet()) { |
| String group = partitioner.apply(entry.getKey()); |
| TServerInstance loc = entry.getValue(); |
| |
| groupCounts.increment(group, 1); |
| MapCounter<String> tgc = tserverGroupCounts.get(loc); |
| if (tgc == null) { |
| tgc = new MapCounter<>(); |
| tserverGroupCounts.put(loc, tgc); |
| } |
| |
| tgc.increment(group, 1); |
| } |
| |
| Map<String,Integer> expectedCounts = new HashMap<>(); |
| |
| int totalExtra = 0; |
| for (String group : groupCounts.keySet()) { |
| long groupCount = groupCounts.get(group); |
| totalExtra += groupCount % tservers.size(); |
| expectedCounts.put(group, (int) (groupCount / tservers.size())); |
| } |
| |
| // The number of extra tablets from all groups that each tserver must have. |
| int expectedExtra = totalExtra / tservers.size(); |
| int maxExtraGroups = expectedExtra + ((totalExtra % tservers.size() > 0) ? 1 : 0); |
| |
| for (Entry<TServerInstance,MapCounter<String>> entry : tserverGroupCounts.entrySet()) { |
| MapCounter<String> tgc = entry.getValue(); |
| int tserverExtra = 0; |
| for (String group : groupCounts.keySet()) { |
| assertTrue(tgc.get(group) >= expectedCounts.get(group)); |
| assertTrue( |
| "Group counts not as expected group:" + group + " actual:" + tgc.get(group) |
| + " expected:" + (expectedCounts.get(group) + 1) + " tserver:" + entry.getKey(), |
| tgc.get(group) <= expectedCounts.get(group) + 1); |
| tserverExtra += tgc.get(group) - expectedCounts.get(group); |
| } |
| |
| assertTrue(tserverExtra >= expectedExtra); |
| assertTrue(tserverExtra <= maxExtraGroups); |
| } |
| } |
| |
| Map<KeyExtent,TServerInstance> getLocations() { |
| return tabletLocs; |
| } |
| } |
| |
| @Test |
| public void testSingleGroup() { |
| |
| String[][] tests = {new String[] {"a", "b", "c", "d"}, new String[] {"a", "b", "c"}, |
| new String[] {"a", "b", "c", "d", "e"}, new String[] {"a", "b", "c", "d", "e", "f", "g"}, |
| new String[] {"a", "b", "c", "d", "e", "f", "g", "h"}, |
| new String[] {"a", "b", "c", "d", "e", "f", "g", "h", "i"}, new String[] {"a"}}; |
| |
| for (String[] suffixes : tests) { |
| for (int maxTS = 1; maxTS <= 4; maxTS++) { |
| TabletServers tservers = new TabletServers(); |
| tservers = new TabletServers(); |
| int ts = 0; |
| for (String s : suffixes) { |
| tservers.addTablet("01" + s, "192.168.1." + ((ts++ % maxTS) + 1) + ":9997"); |
| } |
| |
| tservers.addTservers("192.168.1.2:9997", "192.168.1.3:9997", "192.168.1.4:9997"); |
| tservers.balance(); |
| tservers.balance(); |
| } |
| } |
| } |
| |
| @Test |
| public void testTwoGroups() { |
| String[][] tests = {new String[] {"a", "b", "c", "d"}, new String[] {"a", "b", "c"}, |
| new String[] {"a", "b", "c", "d", "e"}, new String[] {"a", "b", "c", "d", "e", "f", "g"}, |
| new String[] {"a", "b", "c", "d", "e", "f", "g", "h"}, |
| new String[] {"a", "b", "c", "d", "e", "f", "g", "h", "i"}, new String[] {"a"}}; |
| |
| for (String[] suffixes1 : tests) { |
| for (String[] suffixes2 : tests) { |
| for (int maxTS = 1; maxTS <= 4; maxTS++) { |
| TabletServers tservers = new TabletServers(); |
| tservers = new TabletServers(); |
| int ts = 0; |
| for (String s : suffixes1) { |
| tservers.addTablet("01" + s, "192.168.1." + ((ts++ % maxTS) + 1) + ":9997"); |
| } |
| |
| for (String s : suffixes2) { |
| tservers.addTablet("02" + s, "192.168.1." + ((ts++ % maxTS) + 1) + ":9997"); |
| } |
| |
| tservers.addTservers("192.168.1.2:9997", "192.168.1.3:9997", "192.168.1.4:9997"); |
| tservers.balance(); |
| tservers.balance(); |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testThreeGroups() { |
| String[][] tests = {new String[] {"a", "b", "c", "d"}, new String[] {"a", "b", "c"}, |
| new String[] {"a", "b", "c", "d", "e"}, new String[] {"a", "b", "c", "d", "e", "f", "g"}, |
| new String[] {"a", "b", "c", "d", "e", "f", "g", "h"}, |
| new String[] {"a", "b", "c", "d", "e", "f", "g", "h", "i"}, new String[] {"a"}}; |
| |
| for (String[] suffixes1 : tests) { |
| for (String[] suffixes2 : tests) { |
| for (String[] suffixes3 : tests) { |
| for (int maxTS = 1; maxTS <= 4; maxTS++) { |
| TabletServers tservers = new TabletServers(); |
| tservers = new TabletServers(); |
| int ts = 0; |
| for (String s : suffixes1) { |
| tservers.addTablet("01" + s, "192.168.1." + ((ts++ % maxTS) + 1) + ":9997"); |
| } |
| |
| for (String s : suffixes2) { |
| tservers.addTablet("02" + s, "192.168.1." + ((ts++ % maxTS) + 1) + ":9997"); |
| } |
| |
| for (String s : suffixes3) { |
| tservers.addTablet("03" + s, "192.168.1." + ((ts++ % maxTS) + 1) + ":9997"); |
| } |
| |
| tservers.addTservers("192.168.1.2:9997", "192.168.1.3:9997", "192.168.1.4:9997"); |
| tservers.balance(); |
| tservers.balance(); |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testManySingleTabletGroups() { |
| |
| for (int numGroups = 1; numGroups <= 13; numGroups++) { |
| for (int maxTS = 1; maxTS <= 4; maxTS++) { |
| TabletServers tservers = new TabletServers(); |
| tservers = new TabletServers(); |
| int ts = 0; |
| |
| for (int group = 1; group <= numGroups; group++) { |
| tservers.addTablet(String.format("%02d:p", group), |
| "192.168.1." + ((ts++ % maxTS) + 1) + ":9997"); |
| } |
| |
| tservers.addTservers("192.168.1.2:9997", "192.168.1.3:9997", "192.168.1.4:9997"); |
| |
| tservers.balance(); |
| tservers.balance(); |
| } |
| } |
| } |
| |
| @Test |
| public void testMaxMigrations() { |
| |
| for (int max : new int[] {1, 2, 3, 7, 10, 30}) { |
| TabletServers tservers = new TabletServers(); |
| |
| for (int i = 1; i <= 9; i++) { |
| tservers.addTablet("01" + i, "192.168.1.1:9997"); |
| } |
| |
| for (int i = 1; i <= 4; i++) { |
| tservers.addTablet("02" + i, "192.168.1.2:9997"); |
| } |
| |
| for (int i = 1; i <= 5; i++) { |
| tservers.addTablet("03" + i, "192.168.1.3:9997"); |
| } |
| |
| tservers.addTservers("192.168.1.4:9997", "192.168.1.5:9997"); |
| |
| tservers.balance(max); |
| } |
| } |
| |
| @Test |
| public void bigTest() { |
| TabletServers tservers = new TabletServers(); |
| Random rand = new SecureRandom(); |
| |
| for (int g = 1; g <= 60; g++) { |
| for (int t = 1; t <= 241; t++) { |
| tservers.addTablet(String.format("%02d:%d", g, t), |
| "192.168.1." + (rand.nextInt(249) + 1) + ":9997"); |
| } |
| } |
| |
| for (int i = 1; i <= 250; i++) { |
| tservers.addTserver("192.168.1." + i + ":9997"); |
| } |
| |
| tservers.balance(1000); |
| } |
| |
| @Test |
| public void bigTest2() { |
| TabletServers tservers = new TabletServers(); |
| Random rand = new SecureRandom(); |
| |
| for (int g = 1; g <= 60; g++) { |
| for (int t = 1; t <= rand.nextInt(1000); t++) { |
| tservers.addTablet(String.format("%02d:%d", g, t), |
| "192.168.1." + (rand.nextInt(249) + 1) + ":9997"); |
| } |
| } |
| |
| for (int i = 1; i <= 250; i++) { |
| tservers.addTserver("192.168.1." + i + ":9997"); |
| } |
| |
| tservers.balance(1000); |
| } |
| } |