blob: c6beccea34eb87b61a5790e6255d4d003bac5ce9 [file] [log] [blame]
/*
* 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);
}
}