/**
* 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.blur.manager.indexserver;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
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.TreeMap;
import org.apache.blur.utils.ShardUtil;
import org.junit.Before;
import org.junit.Test;
public class MasterBasedLevelerTest {
private static final String TABLE = "test";
private static final int MAX_SERVERS = 10000;
private static final int MAX_SHARDS = 100000;
private long _seed;
private Random _random;
@Before
public void setup() {
_random = new Random();
_seed = _random.nextLong();
// _seed = 9095818143550884754L;
}
@Test
public void testLeveler1() {
try {
Random random = new Random(_seed);
int totalShardServers = atLeastOne(random.nextInt(MAX_SERVERS));
int shards = atLeastOne(random.nextInt(MAX_SHARDS));
Map<String, String> newLayoutMap = new TreeMap<String, String>();
List<String> onlineServers = getOnlineServers(totalShardServers);
populateCurrentLayout(random, newLayoutMap, shards, onlineServers);
testLeveler(random, shards, newLayoutMap, onlineServers);
// now test adding a server. no more that ceil(opt) should move
onlineServers.add("newserver");
{
float opt = shards / (float) onlineServers.size();
int moves = testLeveler(random, shards, newLayoutMap, onlineServers);
assertTrue(moves <= Math.ceil(opt));
}
// now test removing a server. no more that ceil(opt) should move
int index = random.nextInt(onlineServers.size());
String serverToRemove = onlineServers.remove(index);
System.out.println("removing [" + serverToRemove + "]");
reassign(serverToRemove, newLayoutMap, onlineServers);
{
float opt = shards / (float) onlineServers.size();
int moves = testLeveler(random, shards, newLayoutMap, onlineServers);
assertTrue(moves <= Math.ceil(opt));
}
} catch (Throwable t) {
t.printStackTrace();
fail("Seed [" + _seed + "] exception");
}
}
private int atLeastOne(int i) {
if (i == 0) {
return 1;
}
return i;
}
private void reassign(String serverToRemove, Map<String, String> newLayoutMap, List<String> onlineServers) {
System.out.println("reassign - starting");
Set<String> offlineShards = new HashSet<String>();
for (Entry<String, String> entry : newLayoutMap.entrySet()) {
if (entry.getValue().equals(serverToRemove)) {
offlineShards.add(entry.getKey());
}
}
Map<String, Integer> counts = populateCounts(newLayoutMap, onlineServers);
counts.remove(serverToRemove);
for (String offlineShard : offlineShards) {
int count = Integer.MAX_VALUE;
String server = null;
for (Entry<String, Integer> e : counts.entrySet()) {
if (e.getValue() < count) {
count = e.getValue();
server = e.getKey();
}
}
if (server == null) {
fail("Seed [" + _seed + "]");
}
newLayoutMap.put(offlineShard, server);
Integer serverCount = counts.get(server);
if (serverCount == null) {
counts.put(server, 1);
} else {
counts.put(server, count + 1);
}
}
System.out.println("reassign - ending");
}
private int testLeveler(Random random, int shards, Map<String, String> newLayoutMap, List<String> onlineServers) {
int totalShardServers = onlineServers.size();
float opt = shards / (float) totalShardServers;
Map<String, Integer> beforeOnlineServerShardCount = populateCounts(newLayoutMap, onlineServers);
int beforeLowCount = getLowCount(beforeOnlineServerShardCount);
int beforeHighCount = getHighCount(beforeOnlineServerShardCount);
System.out.println("Opt [" + opt + "] Before Low [" + beforeLowCount + "] High [" + beforeHighCount + "]");
long s = System.nanoTime();
int moves = MasterBasedLeveler.level(shards, totalShardServers, beforeOnlineServerShardCount, newLayoutMap, TABLE,
random);
long e = System.nanoTime();
Map<String, Integer> afterOnlineServerShardCount = populateCounts(newLayoutMap, onlineServers);
int afterLowCount = getLowCount(afterOnlineServerShardCount);
int afterHighCount = getHighCount(afterOnlineServerShardCount);
System.out.println("Opt [" + opt + "] After Low [" + afterLowCount + "] High [" + afterHighCount + "]");
System.out.println("Total servers [" + totalShardServers + "] Total Shards [" + shards + "] Total moves [" + moves
+ "] in [" + (e - s) / 1000000.0 + " ms]");
if (afterLowCount == afterHighCount) {
assertEquals("Seed [" + _seed + "]", Math.round(opt), afterLowCount);
} else if (afterLowCount + 1 == afterHighCount) {
assertEquals("Seed [" + _seed + "]", (int) opt, afterLowCount);
} else {
fail("Seed [" + _seed + "]");
}
return moves;
}
@Test
public void testLeveler2() {
testLeveler1();
}
@Test
public void testLeveler3() {
testLeveler1();
}
@Test
public void testLeveler4() {
testLeveler1();
}
@Test
public void testLeveler5() {
testLeveler1();
}
@Test
public void testLeveler6() {
testLeveler1();
}
@Test
public void testLeveler7() {
testLeveler1();
}
@Test
public void testLeveler8() {
testLeveler1();
}
@Test
public void testLeveler9() {
testLeveler1();
}
@Test
public void testLeveler10() {
testLeveler1();
}
public void testLevelerALot() {
for (int i = 0; i < 1000; i++) {
_seed = _random.nextLong();
testLeveler1();
}
}
private List<String> getOnlineServers(int totalShardServers) {
List<String> servers = new ArrayList<String>();
for (int i = 0; i < totalShardServers; i++) {
servers.add("server-" + i);
}
return servers;
}
private int getLowCount(Map<String, Integer> onlineServerShardCount) {
int lowCount = Integer.MAX_VALUE;
for (Entry<String, Integer> e : onlineServerShardCount.entrySet()) {
int count = e.getValue();
if (lowCount > count) {
lowCount = count;
}
}
return lowCount;
}
private int getHighCount(Map<String, Integer> onlineServerShardCount) {
int highCount = Integer.MIN_VALUE;
for (Entry<String, Integer> e : onlineServerShardCount.entrySet()) {
int count = e.getValue();
if (highCount < count) {
highCount = count;
}
}
return highCount;
}
private Map<String, Integer> populateCounts(Map<String, String> newLayoutMap, List<String> servers) {
Map<String, Integer> onlineServerShardCount = new TreeMap<String, Integer>();
for (String server : servers) {
onlineServerShardCount.put(server, 0);
}
for (Entry<String, String> e : newLayoutMap.entrySet()) {
String value = e.getValue();
Integer count = onlineServerShardCount.get(value);
if (count == null) {
onlineServerShardCount.put(value, 1);
} else {
onlineServerShardCount.put(value, count + 1);
}
}
return onlineServerShardCount;
}
private void populateCurrentLayout(Random random, Map<String, String> newLayoutMap, int shards, List<String> servers) {
for (int i = 0; i < shards; i++) {
String shardName = ShardUtil.getShardName(i);
int server = random.nextInt(servers.size());
newLayoutMap.put(shardName, servers.get(server));
}
}
}