blob: 91b9262a3f5bd2e4304dd76da6853ac42f2ecff9 [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.hadoop.hbase.rsgroup;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Pattern;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.ClusterMetrics;
import org.apache.hadoop.hbase.ClusterMetrics.Option;
import org.apache.hadoop.hbase.HBaseCluster;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.MiniHBaseCluster;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.ServerName;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.Waiter;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.RegionInfo;
import org.apache.hadoop.hbase.client.TableDescriptor;
import org.apache.hadoop.hbase.coprocessor.CoprocessorHost;
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor;
import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment;
import org.apache.hadoop.hbase.coprocessor.MasterObserver;
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
import org.apache.hadoop.hbase.master.HMaster;
import org.apache.hadoop.hbase.master.MasterCoprocessorHost;
import org.apache.hadoop.hbase.master.ServerManager;
import org.apache.hadoop.hbase.master.snapshot.SnapshotManager;
import org.apache.hadoop.hbase.net.Address;
import org.apache.hadoop.hbase.quotas.QuotaUtil;
import org.junit.Rule;
import org.junit.rules.TestName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hbase.thirdparty.com.google.common.collect.Maps;
import org.apache.hbase.thirdparty.com.google.common.collect.Sets;
public abstract class TestRSGroupsBase {
protected static final Logger LOG = LoggerFactory.getLogger(TestRSGroupsBase.class);
// shared
protected static final String GROUP_PREFIX = "Group";
protected static final String TABLE_PREFIX = "Group";
// shared, cluster type specific
protected static HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
protected static Admin ADMIN;
protected static HBaseCluster CLUSTER;
protected static HMaster MASTER;
protected boolean INIT = false;
protected static CPMasterObserver OBSERVER;
public final static long WAIT_TIMEOUT = 60000;
public final static int NUM_SLAVES_BASE = 4; // number of slaves for the smallest cluster
public static int NUM_DEAD_SERVERS = 0;
// Per test variables
@Rule
public TestName name = new TestName();
protected TableName tableName;
public static String getNameWithoutIndex(String name) {
return name.split("\\[")[0];
}
public static void setUpTestBeforeClass() throws Exception {
Configuration conf = TEST_UTIL.getConfiguration();
conf.setFloat("hbase.master.balancer.stochastic.tableSkewCost", 6000);
if (conf.get(RSGroupUtil.RS_GROUP_ENABLED) == null) {
RSGroupUtil.enableRSGroup(conf);
}
if (conf.get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY) != null) {
conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY,
conf.get(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY) + "," +
CPMasterObserver.class.getName());
} else {
conf.set(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, CPMasterObserver.class.getName());
}
conf.setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART,
NUM_SLAVES_BASE - 1);
conf.setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
conf.setInt("hbase.rpc.timeout", 100000);
TEST_UTIL.startMiniCluster(NUM_SLAVES_BASE - 1);
initialize();
}
protected static void initialize() throws Exception {
ADMIN = new VerifyingRSGroupAdmin(TEST_UTIL.getConfiguration());
CLUSTER = TEST_UTIL.getHBaseCluster();
MASTER = TEST_UTIL.getMiniHBaseCluster().getMaster();
// wait for balancer to come online
TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
@Override
public boolean evaluate() throws Exception {
return MASTER.isInitialized() &&
((RSGroupBasedLoadBalancer) MASTER.getLoadBalancer()).isOnline();
}
});
ADMIN.balancerSwitch(false, true);
MasterCoprocessorHost host = MASTER.getMasterCoprocessorHost();
OBSERVER = (CPMasterObserver) host.findCoprocessor(CPMasterObserver.class.getName());
}
public static void tearDownAfterClass() throws Exception {
TEST_UTIL.shutdownMiniCluster();
}
public void setUpBeforeMethod() throws Exception {
LOG.info(name.getMethodName());
tableName = TableName.valueOf(TABLE_PREFIX + "_" + name.getMethodName().split("\\[")[0]);
if (!INIT) {
INIT = true;
tearDownAfterMethod();
}
OBSERVER.resetFlags();
}
public void tearDownAfterMethod() throws Exception {
deleteTableIfNecessary();
deleteNamespaceIfNecessary();
deleteGroups();
for (ServerName sn : ADMIN.listDecommissionedRegionServers()) {
ADMIN.recommissionRegionServer(sn, null);
}
assertTrue(ADMIN.listDecommissionedRegionServers().isEmpty());
int missing = NUM_SLAVES_BASE - getNumServers();
LOG.info("Restoring servers: " + missing);
for (int i = 0; i < missing; i++) {
((MiniHBaseCluster) CLUSTER).startRegionServer();
}
ADMIN.addRSGroup("master");
ServerName masterServerName = ((MiniHBaseCluster) CLUSTER).getMaster().getServerName();
try {
ADMIN.moveServersToRSGroup(Sets.newHashSet(masterServerName.getAddress()), "master");
} catch (Exception ex) {
LOG.warn("Got this on setup, FYI", ex);
}
assertTrue(OBSERVER.preMoveServersCalled);
TEST_UTIL.waitFor(WAIT_TIMEOUT, new Waiter.Predicate<Exception>() {
@Override
public boolean evaluate() throws Exception {
LOG.info("Waiting for cleanup to finish " + ADMIN.listRSGroups());
// Might be greater since moving servers back to default
// is after starting a server
return ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP).getServers().size() == NUM_SLAVES_BASE;
}
});
}
protected final RSGroupInfo addGroup(String groupName, int serverCount)
throws IOException, InterruptedException {
RSGroupInfo defaultInfo = ADMIN.getRSGroup(RSGroupInfo.DEFAULT_GROUP);
ADMIN.addRSGroup(groupName);
Set<Address> set = new HashSet<>();
for (Address server : defaultInfo.getServers()) {
if (set.size() == serverCount) {
break;
}
set.add(server);
}
ADMIN.moveServersToRSGroup(set, groupName);
RSGroupInfo result = ADMIN.getRSGroup(groupName);
return result;
}
protected final void removeGroup(String groupName) throws IOException {
Set<TableName> tables = new HashSet<>();
for (TableDescriptor td : ADMIN.listTableDescriptors(true)) {
RSGroupInfo groupInfo = ADMIN.getRSGroup(td.getTableName());
if (groupInfo != null && groupInfo.getName().equals(groupName)) {
tables.add(td.getTableName());
}
}
ADMIN.setRSGroup(tables, RSGroupInfo.DEFAULT_GROUP);
for (NamespaceDescriptor nd : ADMIN.listNamespaceDescriptors()) {
if (groupName.equals(nd.getConfigurationValue(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP))) {
nd.removeConfiguration(RSGroupInfo.NAMESPACE_DESC_PROP_GROUP);
ADMIN.modifyNamespace(nd);
}
}
RSGroupInfo groupInfo = ADMIN.getRSGroup(groupName);
ADMIN.moveServersToRSGroup(groupInfo.getServers(), RSGroupInfo.DEFAULT_GROUP);
ADMIN.removeRSGroup(groupName);
}
protected final void deleteTableIfNecessary() throws IOException {
for (TableDescriptor desc : TEST_UTIL.getAdmin()
.listTableDescriptors(Pattern.compile(TABLE_PREFIX + ".*"))) {
TEST_UTIL.deleteTable(desc.getTableName());
}
}
protected final void deleteNamespaceIfNecessary() throws IOException {
for (NamespaceDescriptor desc : TEST_UTIL.getAdmin().listNamespaceDescriptors()) {
if (desc.getName().startsWith(TABLE_PREFIX)) {
ADMIN.deleteNamespace(desc.getName());
}
}
}
protected final void deleteGroups() throws IOException {
for (RSGroupInfo groupInfo : ADMIN.listRSGroups()) {
if (!groupInfo.getName().equals(RSGroupInfo.DEFAULT_GROUP)) {
removeGroup(groupInfo.getName());
}
}
}
protected Map<TableName, List<String>> getTableRegionMap() throws IOException {
Map<TableName, List<String>> map = Maps.newTreeMap();
Map<TableName, Map<ServerName, List<String>>> tableServerRegionMap = getTableServerRegionMap();
for (TableName tableName : tableServerRegionMap.keySet()) {
if (!map.containsKey(tableName)) {
map.put(tableName, new LinkedList<>());
}
for (List<String> subset : tableServerRegionMap.get(tableName).values()) {
map.get(tableName).addAll(subset);
}
}
return map;
}
protected Map<TableName, Map<ServerName, List<String>>> getTableServerRegionMap()
throws IOException {
Map<TableName, Map<ServerName, List<String>>> map = Maps.newTreeMap();
Admin admin = TEST_UTIL.getAdmin();
ClusterMetrics metrics =
admin.getClusterMetrics(EnumSet.of(ClusterMetrics.Option.SERVERS_NAME));
for (ServerName serverName : metrics.getServersName()) {
for (RegionInfo region : admin.getRegions(serverName)) {
TableName tableName = region.getTable();
map.computeIfAbsent(tableName, k -> new TreeMap<>())
.computeIfAbsent(serverName, k -> new ArrayList<>()).add(region.getRegionNameAsString());
}
}
return map;
}
// return the real number of region servers, excluding the master embedded region server in 2.0+
protected int getNumServers() throws IOException {
ClusterMetrics status = ADMIN.getClusterMetrics(EnumSet.of(Option.MASTER, Option.LIVE_SERVERS));
ServerName masterName = status.getMasterName();
int count = 0;
for (ServerName sn : status.getLiveServerMetrics().keySet()) {
if (!sn.equals(masterName)) {
count++;
}
}
return count;
}
protected final String getGroupName(String baseName) {
return GROUP_PREFIX + "_" + getNameWithoutIndex(baseName) + "_" +
ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);
}
/**
* The server name in group does not contain the start code, this method will find out the start
* code and construct the ServerName object.
*/
protected final ServerName getServerName(Address addr) {
return TEST_UTIL.getMiniHBaseCluster().getRegionServerThreads().stream()
.map(t -> t.getRegionServer().getServerName()).filter(sn -> sn.getAddress().equals(addr))
.findFirst().get();
}
protected final void toggleQuotaCheckAndRestartMiniCluster(boolean enable) throws Exception {
TEST_UTIL.shutdownMiniCluster();
TEST_UTIL.getConfiguration().setBoolean(QuotaUtil.QUOTA_CONF_KEY, enable);
TEST_UTIL.startMiniCluster(NUM_SLAVES_BASE - 1);
TEST_UTIL.getConfiguration().setInt(ServerManager.WAIT_ON_REGIONSERVERS_MINTOSTART,
NUM_SLAVES_BASE - 1);
TEST_UTIL.getConfiguration().setBoolean(SnapshotManager.HBASE_SNAPSHOT_ENABLED, true);
initialize();
}
public static class CPMasterObserver implements MasterCoprocessor, MasterObserver {
boolean preBalanceRSGroupCalled = false;
boolean postBalanceRSGroupCalled = false;
boolean preMoveServersCalled = false;
boolean postMoveServersCalled = false;
boolean preMoveTablesCalled = false;
boolean postMoveTablesCalled = false;
boolean preAddRSGroupCalled = false;
boolean postAddRSGroupCalled = false;
boolean preRemoveRSGroupCalled = false;
boolean postRemoveRSGroupCalled = false;
boolean preRemoveServersCalled = false;
boolean postRemoveServersCalled = false;
boolean preMoveServersAndTables = false;
boolean postMoveServersAndTables = false;
boolean preGetRSGroupInfoCalled = false;
boolean postGetRSGroupInfoCalled = false;
boolean preGetRSGroupInfoOfTableCalled = false;
boolean postGetRSGroupInfoOfTableCalled = false;
boolean preListRSGroupsCalled = false;
boolean postListRSGroupsCalled = false;
boolean preGetRSGroupInfoOfServerCalled = false;
boolean postGetRSGroupInfoOfServerCalled = false;
boolean preSetRSGroupForTablesCalled = false;
boolean postSetRSGroupForTablesCalled = false;
boolean preListTablesInRSGroupCalled = false;
boolean postListTablesInRSGroupCalled = false;
boolean preGetConfiguredNamespacesAndTablesInRSGroupCalled = false;
boolean postGetConfiguredNamespacesAndTablesInRSGroupCalled = false;
boolean preRenameRSGroup = false;
boolean postRenameRSGroup = false;
public void resetFlags() {
preBalanceRSGroupCalled = false;
postBalanceRSGroupCalled = false;
preMoveServersCalled = false;
postMoveServersCalled = false;
preMoveTablesCalled = false;
postMoveTablesCalled = false;
preAddRSGroupCalled = false;
postAddRSGroupCalled = false;
preRemoveRSGroupCalled = false;
postRemoveRSGroupCalled = false;
preRemoveServersCalled = false;
postRemoveServersCalled = false;
preMoveServersAndTables = false;
postMoveServersAndTables = false;
preGetRSGroupInfoCalled = false;
postGetRSGroupInfoCalled = false;
preGetRSGroupInfoOfTableCalled = false;
postGetRSGroupInfoOfTableCalled = false;
preListRSGroupsCalled = false;
postListRSGroupsCalled = false;
preGetRSGroupInfoOfServerCalled = false;
postGetRSGroupInfoOfServerCalled = false;
preSetRSGroupForTablesCalled = false;
postSetRSGroupForTablesCalled = false;
preListTablesInRSGroupCalled = false;
postListTablesInRSGroupCalled = false;
preGetConfiguredNamespacesAndTablesInRSGroupCalled = false;
postGetConfiguredNamespacesAndTablesInRSGroupCalled = false;
preRenameRSGroup = false;
postRenameRSGroup = false;
}
@Override
public Optional<MasterObserver> getMasterObserver() {
return Optional.of(this);
}
@Override
public void preMoveServersAndTables(final ObserverContext<MasterCoprocessorEnvironment> ctx,
Set<Address> servers, Set<TableName> tables, String targetGroup) throws IOException {
preMoveServersAndTables = true;
}
@Override
public void postMoveServersAndTables(final ObserverContext<MasterCoprocessorEnvironment> ctx,
Set<Address> servers, Set<TableName> tables, String targetGroup) throws IOException {
postMoveServersAndTables = true;
}
@Override
public void preRemoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
Set<Address> servers) throws IOException {
preRemoveServersCalled = true;
}
@Override
public void postRemoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
Set<Address> servers) throws IOException {
postRemoveServersCalled = true;
}
@Override
public void preRemoveRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
String name) throws IOException {
preRemoveRSGroupCalled = true;
}
@Override
public void postRemoveRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
String name) throws IOException {
postRemoveRSGroupCalled = true;
}
@Override
public void preAddRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx, String name)
throws IOException {
preAddRSGroupCalled = true;
}
@Override
public void postAddRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx, String name)
throws IOException {
postAddRSGroupCalled = true;
}
@Override
public void preMoveTables(final ObserverContext<MasterCoprocessorEnvironment> ctx,
Set<TableName> tables, String targetGroup) throws IOException {
preMoveTablesCalled = true;
}
@Override
public void postMoveTables(final ObserverContext<MasterCoprocessorEnvironment> ctx,
Set<TableName> tables, String targetGroup) throws IOException {
postMoveTablesCalled = true;
}
@Override
public void preMoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
Set<Address> servers, String targetGroup) throws IOException {
preMoveServersCalled = true;
}
@Override
public void postMoveServers(final ObserverContext<MasterCoprocessorEnvironment> ctx,
Set<Address> servers, String targetGroup) throws IOException {
postMoveServersCalled = true;
}
@Override
public void preBalanceRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
String groupName) throws IOException {
preBalanceRSGroupCalled = true;
}
@Override
public void postBalanceRSGroup(final ObserverContext<MasterCoprocessorEnvironment> ctx,
String groupName, boolean balancerRan) throws IOException {
postBalanceRSGroupCalled = true;
}
@Override
public void preGetRSGroupInfo(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final String groupName) throws IOException {
preGetRSGroupInfoCalled = true;
}
@Override
public void postGetRSGroupInfo(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final String groupName) throws IOException {
postGetRSGroupInfoCalled = true;
}
@Override
public void preGetRSGroupInfoOfTable(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final TableName tableName) throws IOException {
preGetRSGroupInfoOfTableCalled = true;
}
@Override
public void postGetRSGroupInfoOfTable(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final TableName tableName) throws IOException {
postGetRSGroupInfoOfTableCalled = true;
}
@Override
public void preListRSGroups(final ObserverContext<MasterCoprocessorEnvironment> ctx)
throws IOException {
preListRSGroupsCalled = true;
}
@Override
public void postListRSGroups(final ObserverContext<MasterCoprocessorEnvironment> ctx)
throws IOException {
postListRSGroupsCalled = true;
}
@Override
public void preGetRSGroupInfoOfServer(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final Address server) throws IOException {
preGetRSGroupInfoOfServerCalled = true;
}
@Override
public void postGetRSGroupInfoOfServer(final ObserverContext<MasterCoprocessorEnvironment> ctx,
final Address server) throws IOException {
postGetRSGroupInfoOfServerCalled = true;
}
@Override
public void preListTablesInRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
String groupName) throws IOException {
preListTablesInRSGroupCalled = true;
}
@Override
public void postListTablesInRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx,
String groupName) throws IOException {
postListTablesInRSGroupCalled = true;
}
@Override
public void preGetConfiguredNamespacesAndTablesInRSGroup(
ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName) throws IOException {
preGetConfiguredNamespacesAndTablesInRSGroupCalled = true;
}
@Override
public void postGetConfiguredNamespacesAndTablesInRSGroup(
ObserverContext<MasterCoprocessorEnvironment> ctx, String groupName) throws IOException {
postGetConfiguredNamespacesAndTablesInRSGroupCalled = true;
}
@Override
public void preRenameRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String oldName,
String newName) throws IOException {
preRenameRSGroup = true;
}
@Override
public void postRenameRSGroup(ObserverContext<MasterCoprocessorEnvironment> ctx, String oldName,
String newName) throws IOException {
postRenameRSGroup = true;
}
}
}