/*
* 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.test.functional;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.accumulo.core.client.Connector;
import org.apache.accumulo.core.client.Scanner;
import org.apache.accumulo.core.client.TableNotFoundException;
import org.apache.accumulo.core.conf.Property;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.metadata.MetadataTable;
import org.apache.accumulo.core.metadata.schema.MetadataSchema;
import org.apache.accumulo.core.security.Authorizations;
import org.apache.accumulo.minicluster.impl.MiniAccumuloConfigImpl;
import org.apache.accumulo.server.master.balancer.RegexGroupBalancer;
import org.apache.accumulo.server.master.state.TServerInstance;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.hadoop.io.Text;
import org.junit.Test;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
public class RegexGroupBalanceIT extends ConfigurableMacBase {
@Override
public void beforeClusterStart(MiniAccumuloConfigImpl cfg) throws Exception {
cfg.setNumTservers(4);
}
@Test(timeout = 120000)
public void testBalancing() throws Exception {
Connector conn = getConnector();
String tablename = getUniqueNames(1)[0];
conn.tableOperations().create(tablename);
SortedSet<Text> splits = new TreeSet<>();
splits.add(new Text("01a"));
splits.add(new Text("01m"));
splits.add(new Text("01z"));
splits.add(new Text("02a"));
splits.add(new Text("02f"));
splits.add(new Text("02r"));
splits.add(new Text("02z"));
splits.add(new Text("03a"));
splits.add(new Text("03f"));
splits.add(new Text("03m"));
splits.add(new Text("03r"));
conn.tableOperations().setProperty(tablename, RegexGroupBalancer.REGEX_PROPERTY, "(\\d\\d).*");
conn.tableOperations().setProperty(tablename, RegexGroupBalancer.DEFAUT_GROUP_PROPERTY, "03");
conn.tableOperations().setProperty(tablename, RegexGroupBalancer.WAIT_TIME_PROPERTY, "50ms");
conn.tableOperations().setProperty(tablename, Property.TABLE_LOAD_BALANCER.getKey(), RegexGroupBalancer.class.getName());
conn.tableOperations().addSplits(tablename, splits);
while (true) {
Thread.sleep(250);
Table<String,String,MutableInt> groupLocationCounts = getCounts(conn, tablename);
boolean allGood = true;
allGood &= checkGroup(groupLocationCounts, "01", 1, 1, 3);
allGood &= checkGroup(groupLocationCounts, "02", 1, 1, 4);
allGood &= checkGroup(groupLocationCounts, "03", 1, 2, 4);
allGood &= checkTabletsPerTserver(groupLocationCounts, 3, 3, 4);
if (allGood) {
break;
}
}
splits.clear();
splits.add(new Text("01b"));
splits.add(new Text("01f"));
splits.add(new Text("01l"));
splits.add(new Text("01r"));
conn.tableOperations().addSplits(tablename, splits);
while (true) {
Thread.sleep(250);
Table<String,String,MutableInt> groupLocationCounts = getCounts(conn, tablename);
boolean allGood = true;
allGood &= checkGroup(groupLocationCounts, "01", 1, 2, 4);
allGood &= checkGroup(groupLocationCounts, "02", 1, 1, 4);
allGood &= checkGroup(groupLocationCounts, "03", 1, 2, 4);
allGood &= checkTabletsPerTserver(groupLocationCounts, 4, 4, 4);
if (allGood) {
break;
}
}
// merge group 01 down to one tablet
conn.tableOperations().merge(tablename, null, new Text("01z"));
while (true) {
Thread.sleep(250);
Table<String,String,MutableInt> groupLocationCounts = getCounts(conn, tablename);
boolean allGood = true;
allGood &= checkGroup(groupLocationCounts, "01", 1, 1, 1);
allGood &= checkGroup(groupLocationCounts, "02", 1, 1, 4);
allGood &= checkGroup(groupLocationCounts, "03", 1, 2, 4);
allGood &= checkTabletsPerTserver(groupLocationCounts, 2, 3, 4);
if (allGood) {
break;
}
}
}
private boolean checkTabletsPerTserver(Table<String,String,MutableInt> groupLocationCounts, int minTabletPerTserver, int maxTabletsPerTserver,
int totalTservser) {
// check that each tserver has between min and max tablets
for (Map<String,MutableInt> groups : groupLocationCounts.columnMap().values()) {
int sum = 0;
for (MutableInt mi : groups.values()) {
sum += mi.intValue();
}
if (sum < minTabletPerTserver || sum > maxTabletsPerTserver) {
return false;
}
}
return groupLocationCounts.columnKeySet().size() == totalTservser;
}
private boolean checkGroup(Table<String,String,MutableInt> groupLocationCounts, String group, int min, int max, int tsevers) {
Collection<MutableInt> counts = groupLocationCounts.row(group).values();
if (counts.size() == 0) {
return min == 0 && max == 0 && tsevers == 0;
}
return min == Collections.min(counts).intValue() && max == Collections.max(counts).intValue() && counts.size() == tsevers;
}
private Table<String,String,MutableInt> getCounts(Connector conn, String tablename) throws TableNotFoundException {
Scanner s = conn.createScanner(MetadataTable.NAME, Authorizations.EMPTY);
s.fetchColumnFamily(MetadataSchema.TabletsSection.CurrentLocationColumnFamily.NAME);
String tableId = conn.tableOperations().tableIdMap().get(tablename);
s.setRange(MetadataSchema.TabletsSection.getRange(tableId));
Table<String,String,MutableInt> groupLocationCounts = HashBasedTable.create();
for (Entry<Key,Value> entry : s) {
String group = entry.getKey().getRow().toString();
if (group.endsWith("<")) {
group = "03";
} else {
group = group.substring(tableId.length() + 1).substring(0, 2);
}
String loc = new TServerInstance(entry.getValue(), entry.getKey().getColumnQualifier()).toString();
MutableInt count = groupLocationCounts.get(group, loc);
if (count == null) {
count = new MutableInt(0);
groupLocationCounts.put(group, loc, count);
}
count.increment();
}
return groupLocationCounts;
}
}