package org.apache.fullmatix.mysql.tools;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.fullmatix.mysql.MySQLAdmin;
import org.apache.fullmatix.mysql.MySQLConstants;
import org.apache.helix.HelixAdmin;
import org.apache.helix.HelixManager;
import org.apache.helix.HelixManagerFactory;
import org.apache.helix.ZNRecord;
import org.apache.helix.manager.zk.ZKHelixAdmin;
import org.apache.helix.model.HelixConfigScope;
import org.apache.helix.model.HelixConfigScope.ConfigScopeProperty;
import org.apache.helix.model.IdealState;
import org.apache.helix.model.InstanceConfig;
import org.apache.helix.model.StateModelDefinition;
import org.apache.helix.model.IdealState.RebalanceMode;
import org.apache.helix.model.builder.AutoModeISBuilder;
import org.apache.helix.model.builder.HelixConfigScopeBuilder;
import org.apache.helix.tools.StateModelConfigGenerator;
import org.apache.log4j.Logger;
public class ClusterAdmin {
private static final Logger LOG = Logger.getLogger(ClusterAdmin.class);
private HelixAdmin _helixAdmin;
private static String createCluster = "createCluster";
private static String createDatabase = "createDatabase";
private static String createTable = "createTable";
private static String configureMysqlAgent = "configureMysqlAgent";
private static String rebalanceCluster = "rebalanceCluster";
public ClusterAdmin(HelixAdmin helixAdmin) {
_helixAdmin = helixAdmin;
}
public void createCluster(String clusterName) {
_helixAdmin.addCluster(clusterName);
ZNRecord masterSlave = StateModelConfigGenerator.generateConfigForMasterSlave();
_helixAdmin.addStateModelDef(clusterName, masterSlave.getId(), new StateModelDefinition(
masterSlave));
ZNRecord onlineOffline = StateModelConfigGenerator.generateConfigForOnlineOffline();
_helixAdmin.addStateModelDef(clusterName, onlineOffline.getId(), new StateModelDefinition(
onlineOffline));
ZNRecord leaderStandby = StateModelConfigGenerator.generateConfigForLeaderStandby();
_helixAdmin.addStateModelDef(clusterName, leaderStandby.getId(), new StateModelDefinition(
leaderStandby));
}
public void addMysqlAgent(String clusterName, String agentName, String mysqlHost,
String mysqlPort, String mysqlSuperUser, String mysqlPassword) {
InstanceConfig config = new InstanceConfig(agentName);
config.setHostName(mysqlHost);
config.setPort(mysqlPort);
config.getRecord().setSimpleField(MySQLConstants.MYSQL_HOST, mysqlHost);
config.getRecord().setSimpleField(MySQLConstants.MYSQL_PORT, mysqlPort);
config.getRecord().setSimpleField(MySQLConstants.MYSQL_SUPER_USER, mysqlSuperUser);
config.getRecord().setSimpleField(MySQLConstants.MYSQL_SUPER_PASSWORD, mysqlPassword);
_helixAdmin.addInstance(clusterName, config);
}
public void createDatabase(String clusterName, String dbName, int numPartitions,
String databaseSpec) {
IdealState masterSlaveIdealState =
_helixAdmin.getResourceIdealState(clusterName, MySQLConstants.MASTER_SLAVE_RESOURCE_NAME);
int numSlices = masterSlaveIdealState.getNumPartitions();
AutoModeISBuilder builder = new AutoModeISBuilder(dbName);
builder.setRebalancerMode(RebalanceMode.SEMI_AUTO);
builder.setNumPartitions(numPartitions);
builder.setNumReplica(Integer.parseInt(masterSlaveIdealState.getReplicas()));
builder.setStateModel("OnlineOffline");
builder.setStateModelFactoryName("DatabaseTransitionHandlerFactory");
for (int i = 0; i < numPartitions; i++) {
int sliceId = i % numSlices;
String slicePartition = MySQLConstants.MASTER_SLAVE_RESOURCE_NAME + "_" + sliceId;
Set<String> instanceSet = masterSlaveIdealState.getInstanceSet(slicePartition);
String dbPartitionName = dbName + "_" + i;
builder.add(dbPartitionName);
String[] instanceNames = new String[instanceSet.size()];
instanceSet.toArray(instanceNames);
builder.assignPreferenceList(dbPartitionName, instanceNames);
Map<String, String> properties = new HashMap<String, String>();
properties.put("sliceId", "" + sliceId);
HelixConfigScope scope =
new HelixConfigScopeBuilder(ConfigScopeProperty.PARTITION).forCluster(clusterName)
.forResource(dbName).forPartition(dbPartitionName).build();
_helixAdmin.setConfig(scope, properties);
}
// set the databaseSpec
Map<String, String> properties = new HashMap<String, String>();
properties.put("database_spec", databaseSpec);
properties.put("type", "DATABASE");
HelixConfigScope scope =
new HelixConfigScopeBuilder(ConfigScopeProperty.RESOURCE).forCluster(clusterName)
.forResource(dbName).build();
_helixAdmin.setConfig(scope, properties);
IdealState idealState = builder.build();
_helixAdmin.setResourceIdealState(clusterName, dbName, idealState);
}
public void createTable(String clusterName, String dbName, String table, String tableSpec) {
LOG.info("Creating table:" + table + " with table_spec: " + tableSpec + " in dbName:" + dbName);
IdealState dbIdealState = _helixAdmin.getResourceIdealState(clusterName, dbName);
int numPartitions = dbIdealState.getNumPartitions();
String dbTableName = dbName + "." + table;
AutoModeISBuilder builder = new AutoModeISBuilder(dbTableName);
builder.setRebalancerMode(RebalanceMode.SEMI_AUTO);
builder.setNumPartitions(numPartitions);
builder.setNumReplica(Integer.parseInt(dbIdealState.getReplicas()));
builder.setStateModel("OnlineOffline");
builder.setStateModelFactoryName("TableTransitionHandlerFactory");
for (String dbPartitionName : dbIdealState.getPartitionSet()) {
String tablePartitionName = dbPartitionName + "." + table;
Set<String> instanceSet = dbIdealState.getInstanceSet(dbPartitionName);
builder.add(tablePartitionName);
String[] instanceNames = new String[instanceSet.size()];
instanceSet.toArray(instanceNames);
builder.assignPreferenceList(tablePartitionName, instanceNames);
}
IdealState idealState = builder.build();
// before setting the idealstate, set the configuration
Map<String, String> properties = new HashMap<String, String>();
properties.put("table_spec", tableSpec);
properties.put("type", "TABLE");
HelixConfigScope scope =
new HelixConfigScopeBuilder(ConfigScopeProperty.RESOURCE).forCluster(clusterName)
.forResource(dbTableName).build();
_helixAdmin.setConfig(scope, properties);
_helixAdmin.setResourceIdealState(clusterName, dbTableName, idealState);
}
public IdealState doInitialAssignment(String clusterName, List<String> instanceNames,
int replicationFactor) {
IdealState idealState = new IdealState(MySQLConstants.MASTER_SLAVE_RESOURCE_NAME);
idealState.setStateModelDefRef("MasterSlave");
idealState.setRebalanceMode(RebalanceMode.CUSTOMIZED);
if (instanceNames.size() % replicationFactor != 0) {
LOG.error(String.format(
"Number of instances (%s) in the cluster must be a multiple of replication factor (%s)",
instanceNames.size(), replicationFactor));
return null;
}
int numSlices = instanceNames.size() / replicationFactor;
idealState.setNumPartitions(numSlices);
idealState.setReplicas(String.valueOf(replicationFactor));
Collections.sort(instanceNames);
for (int i = 0; i < numSlices; i++) {
for (int j = 0; j < replicationFactor; j++) {
idealState.setPartitionState(MySQLConstants.MASTER_SLAVE_RESOURCE_NAME + "_" + i,
instanceNames.get(i * replicationFactor + j), (j == 0) ? "MASTER" : "SLAVE");
}
}
LOG.info("Creating initial assignment \n" + idealState);
_helixAdmin.setResourceIdealState(clusterName, MySQLConstants.MASTER_SLAVE_RESOURCE_NAME,
idealState);
return idealState;
}
@SuppressWarnings("static-access")
public static void main(String[] args) throws Exception {
Option zkServerOption =
OptionBuilder.withLongOpt("zookeeperAddress").withDescription("zookeeper address").create();
zkServerOption.setArgs(1);
zkServerOption.setRequired(true);
zkServerOption.setArgName("zookeeperAddress(Required)");
OptionGroup group = new OptionGroup();
group.setRequired(true);
// create cluster
Option createClusterOption =
OptionBuilder.withLongOpt(createCluster).withDescription("Creates a new cluster").create();
createClusterOption.setArgs(1);
createClusterOption.setRequired(false);
createClusterOption.setArgName("clusterName");
// add new agent
Option createMysqlAgent =
OptionBuilder.withLongOpt(configureMysqlAgent)
.withDescription("Configures a new MysqlAgent [one needed per each mysql instance]")
.create();
createMysqlAgent.setArgs(6);
createMysqlAgent.setRequired(false);
createMysqlAgent
.setArgName("clusterName agentName[must be unique in a cluster] mysqlHost mysqlPort userName password");
// rebalance
// create cluster
Option rebalanceClusterOption =
OptionBuilder.withLongOpt(rebalanceCluster).withDescription("Creates a new cluster")
.create();
rebalanceClusterOption.setArgs(2);
createClusterOption.setRequired(false);
createClusterOption.setArgName("clusterName replicationFactor");
// create database
Option createDatabaseOption =
OptionBuilder.withLongOpt(createDatabase).withDescription("Creates a new database")
.create();
createDatabaseOption.setArgs(4);
createDatabaseOption.setRequired(false);
createDatabaseOption.setArgName("clusterName databaseName numPartitions databaseSpec");
// create table
Option createTableOption =
OptionBuilder.withLongOpt(createTable)
.withDescription("Creates a new table within a database").create();
createTableOption.setArgs(4);
createTableOption.setRequired(false);
createTableOption.setArgName("clusterName databaseName numPartitions databaseSpec");
group.addOption(createClusterOption);
group.addOption(createDatabaseOption);
group.addOption(createTableOption);
group.addOption(createMysqlAgent);
group.addOption(rebalanceClusterOption);
Options options = new Options();
options.addOption(zkServerOption);
options.addOptionGroup(group);
CommandLine cliParser = new GnuParser().parse(options, args);
String zkAddress = cliParser.getOptionValue("zookeeperAddress");
HelixAdmin helixAdmin = null;
if (zkAddress != null) {
helixAdmin = new ZKHelixAdmin(zkAddress);
}
ClusterAdmin admin = new ClusterAdmin(helixAdmin);
if (cliParser.hasOption(createCluster)) {
String clusterName = cliParser.getOptionValue(createCluster);
admin.createCluster(clusterName);
}
if (cliParser.hasOption(rebalanceCluster)) {
String clusterName = cliParser.getOptionValues(rebalanceCluster)[0];
int replicationFactor = Integer.parseInt(cliParser.getOptionValues(rebalanceCluster)[1]);
List<String> instanceNames = helixAdmin.getInstancesInCluster(clusterName);
admin.doInitialAssignment(clusterName, instanceNames, replicationFactor);
}
if (cliParser.hasOption(configureMysqlAgent)) {
String clusterName = cliParser.getOptionValues(configureMysqlAgent)[0];
String agentName = cliParser.getOptionValues(configureMysqlAgent)[1];
String mysqlHost = cliParser.getOptionValues(configureMysqlAgent)[2];
String mysqlPort = cliParser.getOptionValues(configureMysqlAgent)[3];
String mysqlSuperUser = cliParser.getOptionValues(configureMysqlAgent)[4];
String mysqlPassword = cliParser.getOptionValues(configureMysqlAgent)[5];
admin.addMysqlAgent(clusterName, agentName, mysqlHost, mysqlPort, mysqlSuperUser,
mysqlPassword);
}
if (cliParser.hasOption(createDatabase)) {
String clusterName = cliParser.getOptionValues(createDatabase)[0];
String databaseName = cliParser.getOptionValues(createDatabase)[1];
int numPartitions = Integer.parseInt(cliParser.getOptionValues(createDatabase)[2]);
String createDatabaseDDL = cliParser.getOptionValues(createDatabase)[3];
admin.createDatabase(clusterName, databaseName, numPartitions, createDatabaseDDL);
}
if (cliParser.hasOption(createTable)) {
String clusterName = cliParser.getOptionValues(createTable)[0];
String databaseName = cliParser.getOptionValues(createTable)[1];
String tableName = cliParser.getOptionValues(createTable)[2];
String createTableDDL = cliParser.getOptionValues(createTable)[3];
admin.createTable(clusterName, databaseName, tableName, createTableDDL);
}
}
}