/*******************************************************************************
* ===========================================================
* Ankush : Big Data Cluster Management Solution
* ===========================================================
*
* (C) Copyright 2014, by Impetus Technologies
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License (LGPL v3) as
* published by the Free Software Foundation;
*
* This software is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
******************************************************************************/
package com.impetus.ankush2.cassandra.deployer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Semaphore;
import net.neoremind.sshxcute.core.SSHExec;
import net.neoremind.sshxcute.exception.TaskExecFailException;
import net.neoremind.sshxcute.task.CustomTask;
import net.neoremind.sshxcute.task.impl.ExecCommand;
import org.apache.commons.lang3.StringUtils;
import com.impetus.ankush.AppStoreWrapper;
import com.impetus.ankush.common.exception.AnkushException;
import com.impetus.ankush.common.scripting.AnkushTask;
import com.impetus.ankush.common.scripting.impl.AppendFileUsingEcho;
import com.impetus.ankush.common.scripting.impl.DeleteLineFromFile;
import com.impetus.ankush.common.scripting.impl.KillJPSProcessByName;
import com.impetus.ankush.common.scripting.impl.RunInBackground;
import com.impetus.ankush.common.service.ConfigurationManager;
import com.impetus.ankush.common.utils.CommonUtil;
import com.impetus.ankush.common.utils.FileNameUtils;
import com.impetus.ankush.common.utils.JmxMonitoringUtil;
import com.impetus.ankush2.agent.AgentConstant;
import com.impetus.ankush2.agent.AgentUtils;
import com.impetus.ankush2.agent.ComponentService;
import com.impetus.ankush2.cassandra.monitor.CassandraJMX;
import com.impetus.ankush2.cassandra.utils.CassandraConstants;
import com.impetus.ankush2.cassandra.utils.CassandraUtils;
import com.impetus.ankush2.constant.Constant;
import com.impetus.ankush2.framework.AbstractDeployer;
import com.impetus.ankush2.framework.config.ClusterConfig;
import com.impetus.ankush2.framework.config.ComponentConfig;
import com.impetus.ankush2.framework.config.NodeConfig;
import com.impetus.ankush2.logger.AnkushLogger;
import com.impetus.ankush2.utils.AnkushUtils;
import com.impetus.ankush2.utils.SSHUtils;
public class CassandraDeployer extends AbstractDeployer {
/** The clusterConf */
private ClusterConfig clusterConfig;
/** The newClusterConf */
private ClusterConfig newClusterConf = null;
/** The compConfig */
private ComponentConfig componentConfig;
/** The newCompConfig */
private ComponentConfig newCompConfig = null;
/** The advanceConf */
private Map<String, Object> advanceConf;
private static final String COULD_NOT_GET_CONNECTION = "Could not get connection";
/** The Constant CASSANDRA_FOLDER_CONF. */
private static final String CASSANDRA_FOLDER_CONF = "conf/";
/** The Constant CASSANDRA_FOLDER_BIN. */
private static final String CASSANDRA_FOLDER_BIN = "bin/";
/** The Constant LINE_SEPERATOR. */
private static final String LINE_SEPERATOR = System
.getProperty("line.separator");
/** The Cassandra logger */
private AnkushLogger logger = new AnkushLogger(CassandraDeployer.class);
/** The conf manager. */
private static ConfigurationManager confManager = new ConfigurationManager();
public CassandraDeployer(ClusterConfig clusterConf) {
this.clusterConfig = clusterConf;
this.logger = new AnkushLogger(CassandraDeployer.class);
this.componentConfig = clusterConf.getComponents().get(
getComponentName());
}
public CassandraDeployer() {
super();
}
/**
* it initializes clusterConf , compConfig and advanceConf
*
* @param clusterConf
* {@link ClusterConfig}
* @return <code>true</code>, if successful
*/
private boolean setClassVariables(ClusterConfig clusterConf)
throws AnkushException {
try {
if (clusterConf == null
|| !clusterConf.getComponents().containsKey(
getComponentName())) {
throw new AnkushException(
"Either cluster configuration is not defined or cluster does not contains "
+ getComponentName() + ".");
}
this.clusterConfig = clusterConf;
this.logger.setCluster(clusterConf);
this.componentConfig = clusterConf.getComponents().get(
getComponentName());
if (this.componentConfig == null
|| this.componentConfig.getNodes() == null
|| this.componentConfig.getNodes().isEmpty()) {
throw new AnkushException(
"Could not get component configuration.");
}
// Initializing advancedConf to use it to for getting and setting
// cluster level properties
this.advanceConf = this.componentConfig.getAdvanceConf();
return true;
} catch (AnkushException e) {
throw e;
} catch (Exception e) {
throw new AnkushException("Could not get " + getComponentName()
+ " configuration details.", e);
}
}
/**
* Adds cluster level error to clusterConf's error object and logger
*
* @param error
* {@link String}
*/
private boolean addClusterError(String error) {
return addClusterError(error, null, null);
}
/**
* Adds node level error to clusterConf's error object and logger
*
* @param error
* {@link String}
* @param host
* {@link String}
* @return <code>true</code>
*/
private boolean addClusterError(String error, String host) {
return addClusterError(error, host, null);
}
/**
* Adds cluster level error and exception to clusterConf's error object and
* logger
*
* @param error
* {@link String}
* @param t
* {@link Throwable}
* @return <code>true</code>
*/
private boolean addClusterError(String error, Throwable t) {
return addClusterError(error, null, t);
}
/**
* Adds errors
*
* @param error
* {@link String}
* @param host
* {@link String}
* @param t
* {@link Throwable}
* @return <code>true</code>
*/
private boolean addClusterError(String error, String host, Throwable t) {
if (host != null) {
this.clusterConfig.addError(host, getComponentName(), error);
logger.error(error, getComponentName(), host, t);
} else {
this.clusterConfig.addError(getComponentName(), error);
logger.error(error, getComponentName(), t);
}
return false;
}
@Override
public boolean validate(ClusterConfig conf) {
try {
// TODO: Registration validation pending
if (componentConfig.isRegister()) {
return true;
}
if (!validate(componentConfig.getNodes().keySet())) {
throw new AnkushException(getComponentName()
+ " validation failed.");
}
return true;
} catch (AnkushException e) {
return addClusterError(e.getMessage(), e);
} catch (Exception e) {
return addClusterError("Could not validate " + getComponentName()
+ ".", e);
}
}
/**
* Perform asynchronous operations nodes.
*
* @param nodeList
* {@link Collection}
* @return <code>true</code>, if successful
*/
private boolean validate(Collection<String> nodeList)
throws AnkushException {
try {
// Create semaphore to join threads
final Semaphore semaphore = new Semaphore(nodeList.size());
for (final String host : nodeList) {
final NodeConfig nodeConfig = clusterConfig.getNodes()
.get(host);
semaphore.acquire();
AppStoreWrapper.getExecutor().execute(new Runnable() {
@Override
public void run() {
nodeConfig.setStatus(new CassandraValidator(
clusterConfig, nodeConfig).validate());
if (semaphore != null) {
semaphore.release();
}
}
});
}
semaphore.acquire(nodeList.size());
} catch (Exception e) {
throw new AnkushException(getComponentName()
+ " validation failed.");
}
return AnkushUtils.getStatus(clusterConfig, nodeList);
}
@Override
public boolean deploy(ClusterConfig conf) {
try {
// creating a vendor string literal
String vendor;
if (componentConfig.getVendor().equalsIgnoreCase("Datastax")) {
vendor = CassandraConstants.Cassandra_vendors.CASSANDRA_VENDOR_DSC;
} else if (componentConfig.getVendor().equalsIgnoreCase("Apache")) {
vendor = CassandraConstants.Cassandra_vendors.CASSANDRA_VENDOR_APACHE;
} else {
throw new AnkushException(componentConfig.getVendor()
+ " vendor not supported for " + getComponentName());
}
// Setting component home
String component_home = componentConfig.getInstallPath() + vendor
+ "-cassandra-" + componentConfig.getVersion() + "/";
componentConfig.setHomeDir(component_home);
advanceConf.put(CassandraConstants.ClusterProperties.CONF_DIR,
component_home + CASSANDRA_FOLDER_CONF);
advanceConf.put(CassandraConstants.ClusterProperties.BIN_DIR,
component_home + CASSANDRA_FOLDER_BIN);
return (deployNodes(conf, componentConfig.getNodes().keySet()) && start(conf));
} catch (AnkushException e) {
return addClusterError(e.getMessage(), e);
} catch (Exception e) {
return addClusterError("Could not deploy " + getComponentName(), e);
}
}
/**
* method to deploy Cassandra packages on all nodes
*
* @param conf
* {@link ClusterConfig}
* @param nodeMap
* {@link Map}
*
* @return <code>true</code>, if successful
*
*/
private boolean deployNodes(final ClusterConfig conf, Set<String> nodeMap)
throws AnkushException {
try {
// Node Deployment process ...
final Semaphore semaphore = new Semaphore(nodeMap.size());
for (final String host : nodeMap) {
semaphore.acquire();
AppStoreWrapper.getExecutor().execute(new Runnable() {
@Override
public void run() {
conf.getNodes().get(host).setStatus(createNode(host));
if (semaphore != null) {
semaphore.release();
}
}
});
}
semaphore.acquire(nodeMap.size());
} catch (Exception e) {
throw new AnkushException("Could not deploy " + getComponentName(),
e);
}
return AnkushUtils.getStatus(conf.getNodes());
}
@Override
public boolean undeploy(final ClusterConfig conf) {
try {
// setting clusterconf, componentconf and logger
if (!setClassVariables(conf)) {
return false;
}
return removeNode(conf, componentConfig.getNodes().keySet());
} catch (Exception e) {
return addClusterError("Could not undeploy " + getComponentName(),
e);
}
}
@Override
public boolean unregister(final ClusterConfig conf) {
try {
ComponentConfig compConfig = clusterConfig.getComponents().get(
this.componentName);
if (!AnkushUtils.isMonitoredByAnkush(compConfig)) {
logger.info("Skipping " + getComponentName()
+ " unregistration for Level1", this.componentName);
return true;
}
if (!setClassVariables(conf)) {
return false;
}
final String infoMsg = "Unregistering Cassandra";
logger.info(infoMsg, getComponentName());
// Getting node map for cluster deployment
Map<String, Map<String, Object>> nodeMap = new HashMap<String, Map<String, Object>>(
componentConfig.getNodes());
// Node Registration process ...
final Semaphore semaphore = new Semaphore(nodeMap.size());
for (final String host : nodeMap.keySet()) {
semaphore.acquire();
AppStoreWrapper.getExecutor().execute(new Runnable() {
@Override
public void run() {
conf.getNodes().get(host)
.setStatus(unregisterNode(host));
if (semaphore != null) {
semaphore.release();
}
}
});
}
semaphore.acquire(nodeMap.size());
// Return false if any of the node is not deployed.
return AnkushUtils.getStatus(conf.getNodes());
} catch (AnkushException e) {
return addClusterError(e.getMessage(), e);
} catch (Exception e) {
return addClusterError(
"Could not unregister " + getComponentName(), e);
}
}
/**
* Removes Cassandra cluster related directories and environment variables
* from node
*
* @param host
* {@link String}
* @return <code>true</code>, if successful
*/
private boolean removeNode(String host) {
try {
SSHExec connection = getConnection(host);
if (connection == null) {
throw new AnkushException(COULD_NOT_GET_CONNECTION
+ " on node: " + host);
}
logger.info("Removing Cassandra...", getComponentName(), host);
StringBuilder command = new StringBuilder();
// Remove directories
command.append("rm -rf ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.DATA_DIR))
.append(" ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.LOG_DIR))
.append(" ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.SAVED_CACHES_DIR))
.append(" ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.COMMIT_LOG_DIR))
.append(" ").append(componentConfig.getInstallPath());
if (!execCustomtask(command.toString(), connection, host,
"Could not remove directories.")) {
return false;
}
// Remove path variables
command = new StringBuilder("sed -i ");
command.append("-e '/export CASSANDRA_HOME=/d' ")
.append(" -e'/export JAVA_HOME=/d' ").append(".bashrc");
return execCustomtask(command.toString(), connection, host,
"Could not remove path variables from .bashrc.");
} catch (AnkushException e) {
return addClusterError(e.getMessage(), host, e);
} catch (Exception e) {
return addClusterError(
"Could not remove Cassandra directories from node.", host,
e);
}
}
@Override
public boolean start(ClusterConfig conf) {
try {
if (!start(conf, componentConfig.getNodes().keySet())) {
return false;
}
// getting ring topology information during cluster deployment case
if (conf.getState().equals(Constant.Cluster.State.DEPLOYING)) {
checkTechnologyData();
}
return true;
} catch (AnkushException e) {
return addClusterError(e.getMessage(), e);
} catch (Exception e) {
return addClusterError("Could not start " + getComponentName()
+ " services.", e);
}
}
// @Override
// public boolean stop(ClusterConfig conf) {
// if (!setClassVariables(conf)) {
// return false;
// }
// stop(conf, componentConfig.getNodes().keySet());
// return true;
// }
/**
* Start cluster.
*
* @param host
* {@link String}
* @return <code>true</code>, if successful
*/
public boolean startNode(String host) {
try {
// command for starting cassandra
String startCassandraNode = advanceConf
.get(CassandraConstants.ClusterProperties.BIN_DIR)
+ CassandraConstants.Cassandra_executables.CASSANDRA_DAEMON_START;
logger.info("Starting cassandra...", getComponentName(), host);
SSHExec connection = getConnection(host);
if (connection == null) {
throw new AnkushException(COULD_NOT_GET_CONNECTION
+ " on node: " + host);
}
// running command on each node
AnkushTask startCassandra = new RunInBackground(startCassandraNode);
if (connection.exec(startCassandra).rc != 0) {
throw new AnkushException("Could not start "
+ getComponentName() + " on node: " + host);
}
logger.info("Cassandra Daemon started", getComponentName(), host);
return true;
} catch (AnkushException e) {
return addClusterError(e.getMessage(), host, e);
} catch (Exception e) {
return addClusterError("Could not start " + getComponentName()
+ " on node: " + host, host, e);
}
}
/**
* Stop cluster.
*
* @param host
* {@link String}
* @return <code>true</code>, if successful
*/
public boolean stopNode(String host) {
try {
SSHExec connection = getConnection(host);
if (connection == null) {
throw new AnkushException(COULD_NOT_GET_CONNECTION
+ " on node: " + host);
}
logger.info("Stopping Cassandra...", getComponentName(), host);
// Stopping the cassandra process on node
AnkushTask stopCassandra = new KillJPSProcessByName(
CassandraConstants.CASSANDRA_DAEMON);
return connection.exec(stopCassandra).rc == 0;
} catch (AnkushException e) {
return addClusterError(e.getMessage(), host, e);
} catch (Exception e) {
return addClusterError("Could not stop Cassandra on " + host, host,
e);
}
}
/**
* Method to get Cassandra technology data from agent
*
* @return <code>true</code>, if successful
*/
private boolean checkTechnologyData() throws AnkushException {
try {
// time interval to check for technology data
long sleepTime = AppStoreWrapper.getAnkushConfReader()
.getLongValue("cassandra.topologychecksleeptime");
// maximum time for getting technology data
long checkTimeout = AppStoreWrapper.getAnkushConfReader()
.getLongValue("cassandra.topologychecktimeout");
int totalTries = (int) (checkTimeout / sleepTime);
logger.info("Getting Cassandra JMX connection.");
Integer jmxPort = (Integer) advanceConf
.get(CassandraConstants.ClusterProperties.JMX_PORT);
for (int count = 0; count < totalTries; count++) {
CassandraJMX jmx = null;
for (String node : componentConfig.getNodes().keySet()) {
try {
jmx = new CassandraJMX(node, jmxPort);
break;
} catch (Exception e) {
// TODO: Continue
}
}
if (jmx != null) {
logger.debug("Created JMX connection.");
return true;
}
// Wait for few milliseconds
try {
logger.debug("Wait for " + sleepTime + " milliseconds.");
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
logger.debug(e.getMessage());
}
}
logger.info("Could not get Cassandra JMX connection.");
return false;
} catch (Exception e) {
throw new AnkushException("Could not get " + getComponentName()
+ " technology data.");
}
}
@Override
public boolean addNode(final ClusterConfig conf, ClusterConfig newConf) {
try {
// Set logger
if (!setClassVariables(conf)) {
return false;
}
this.newClusterConf = newConf;
this.newCompConfig = newConf.getComponents()
.get(getComponentName());
Map<String, Map<String, Object>> nodeMap = this.newCompConfig
.getNodes();
if (!deployNodes(newConf, nodeMap.keySet())) {
return false;
}
for (String host : nodeMap.keySet()) {
if (CassandraUtils.isSeedNode(newClusterConf.getNodes()
.get(host).getRoles())) {
updateSeedNodeValue();
break;
}
}
if (((String) advanceConf
.get(CassandraConstants.ClusterProperties.SNITCH))
.equalsIgnoreCase(CassandraConstants.Configuration_Properties.PROPERTY_FILE_SNITCH)) {
updateTopologyFile();
}
return start(newConf, newCompConfig.getNodes().keySet());
} catch (AnkushException e) {
return addClusterError(e.getMessage(), e);
} catch (Exception e) {
return addClusterError("Could not add node to "
+ getComponentName() + " cluster.", e);
}
}
/**
* Creates the node.
*
* @param host
* {@link String}
* @return <code>true</code>, if successful
*/
private boolean createNode(String host) {
try {
// getting connection object
SSHExec connection = getConnection(host);
// If connection is not created , then exiting.
if (connection == null) {
throw new AnkushException(COULD_NOT_GET_CONNECTION);
}
logger.info("Deploying Cassandra ...", getComponentName(), host);
// checking Java version as Cassandra 2.x.x needs Java 7
// support.This validation during deployment is applied for handling
// HybridCluster case.
if (!validateJavaVersion(host)) {
return false;
}
// Extracting Cassandra bundle and modifying required configuration
// files
if (!installCassandra(host, connection)
|| !configureCassandra(host, connection)) {
return false;
}
// Adding JMXMonitoring, cassandra-specific details in agent and
// Cassandra service xml file on host
return registerNode(host);
} catch (AnkushException e) {
return addClusterError(e.getMessage(), host, e);
} catch (Exception e) {
return addClusterError("Could not create Cassandra node.", host, e);
}
}
/**
* Registers the node.
*
* @param host
* {@link String}
* @return <code>true</code>, if successful
*/
private boolean registerNode(String host) {
try {
// getting connection
SSHExec connection = getConnection(host);
if (connection == null) {
throw new AnkushException(COULD_NOT_GET_CONNECTION);
}
// configuring JMX monitoring for Cassandra
if (!configureJMXMonitoring(connection, host)) {
return false;
}
// initializing nodes map as per node is prepared during deployment
// or during addnode
Map<String, Set<String>> roles;
if (newClusterConf == null) {
roles = clusterConfig.getNodes().get(host).getRoles();
} else {
roles = newClusterConf.getNodes().get(host).getRoles();
}
// service list.
List<ComponentService> services = new ArrayList<ComponentService>();
// Add CASSANDRA service in it.
services.add(new ComponentService(
CassandraConstants.Cassandra_Services.CassandraDaemon
.toString(), CassandraUtils.getNodeRole(roles),
Constant.ServiceType.JPS));
// Creating cassandra service xml using agent on node.
if (!AgentUtils.createServiceXML(connection, services,
getComponentName(), clusterConfig.getAgentHomeDir())) {
throw new AnkushException(
"Could not create cassandra service configuration in agent.");
}
// adding process list and taskable information in agent conf
addProcessInfo(host, connection);
// save audit trails
confManager
.saveConfiguration(
clusterConfig.getClusterId(),
clusterConfig.getCreatedBy(),
CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_YAML,
host, getConfigurationMap(host));
return true;
} catch (AnkushException e) {
return addClusterError(e.getMessage(), host, e);
} catch (Exception e) {
return addClusterError("Could not register " + getComponentName()
+ " node.", host, e);
}
}
/**
* unregister node
*
* @param host
* {@link String}
* @return <code>true</code>, if successful
*/
private boolean unregisterNode(String host) {
try {
// getting connection object
SSHExec connection = getConnection(host);
if (connection == null) {
throw new AnkushException("Unable to get connection on " + host);
}
// Removing Cassandra JMX json used for showing node utilization
// graphs and Cassandra service.xml
StringBuilder command = new StringBuilder();
// Remove directories
command.append("rm -rf ")
.append(clusterConfig.getAgentHomeDir()
+ AgentConstant.Relative_Path.SERVICE_CONF_DIR
+ getComponentName() + "."
+ Constant.File_Extension.XML)
.append(" ")
.append(clusterConfig.getAgentHomeDir()
+ AgentConstant.Relative_Path.JMXTRANS + "jmxJson_"
+ Constant.Process.CASSANDRA.toLowerCase()
+ ".json");
execCustomtask(command.toString(), connection, host,
"Could not remove Cassandra.xml and " + "jmxJson_"
+ Constant.Process.CASSANDRA.toLowerCase()
+ ".json file.");
// Delete from taskableconf
AnkushTask task = new DeleteLineFromFile(
CassandraConstants.CASSANDRA_MONITORABLE_CLASS_NAME,
clusterConfig.getAgentHomeDir()
+ AgentConstant.Relative_Path.TASKABLE_FILE);
execCustomtask(
task.getCommand(),
connection,
host,
"Could not remove "
+ CassandraConstants.CASSANDRA_MONITORABLE_CLASS_NAME
+ "from" + Constant.Agent.AGENT_TASKABLE_FILE_PATH
+ "file.");
task = new DeleteLineFromFile("JMX_PORT=",
clusterConfig.getAgentHomeDir()
+ AgentConstant.Relative_Path.AGENT_CONF_FILE);
execCustomtask(task.getCommand(), connection, host,
"Could not remove JMX_PORT from"
+ Constant.Agent.AGENT_PROPERTY_FILE_PATH + "file.");
task = new DeleteLineFromFile("SEEDNODE=",
Constant.Agent.AGENT_PROPERTY_FILE_PATH);
execCustomtask(task.getCommand(), connection, host,
"Could not remove SEEDNODE from"
+ Constant.Agent.AGENT_PROPERTY_FILE_PATH + "file.");
return true;
} catch (AnkushException e) {
return addClusterError(e.getMessage(), host, e);
} catch (Exception e) {
return addClusterError("Could not unregister " + getComponentName()
+ " node.", host, e);
}
}
/**
* Prepares map of configuration object
*
* @param host
* {@link String}
* @return {@link Map}
*/
public Map<String, Object> getConfigurationMap(String host) {
Map<String, Object> yamlContents = new HashMap<String, Object>();
yamlContents.put("cluster_name", clusterConfig.getName());
yamlContents.put("partitioner", advanceConf
.get(CassandraConstants.ClusterProperties.PARTITIONER));
yamlContents.put("rpc_port",
advanceConf.get(CassandraConstants.ClusterProperties.RPC_PORT));
yamlContents.put("storage_port", advanceConf
.get(CassandraConstants.ClusterProperties.STORAGE_PORT));
yamlContents.put("listen_address", host);
yamlContents.put("rpc_address", host);
yamlContents.put("endpoint_snitch",
advanceConf.get(CassandraConstants.ClusterProperties.SNITCH));
yamlContents.put("saved_caches_directory", advanceConf
.get(CassandraConstants.ClusterProperties.SAVED_CACHES_DIR));
yamlContents.put("commitlog_directory", advanceConf
.get(CassandraConstants.ClusterProperties.COMMIT_LOG_DIR));
return yamlContents;
}
/**
* Adds Cassandra jmx monitoring json to node
*
* @param connection
* {@link SSHExec}
* @param host
* {@link String}
*/
private boolean configureJMXMonitoring(SSHExec connection, String host)
throws AnkushException {
try {
logger.info(
"Configuring JMX Monitoring for Cassandra started ... ",
getComponentName(), host);
// if copying of cassandra jmx trans json to node fails, then
// exiting with return status false
if (!JmxMonitoringUtil
.copyJmxTransJson(
connection,
clusterConfig.getAuthConf(),
host,
Constant.Process.CASSANDRA,
(Integer) advanceConf
.get(CassandraConstants.ClusterProperties.JMX_PORT),
clusterConfig.getAgentHomeDir())) {
throw new AnkushException(
"Could not copy JmxTrans JSON file for Cassandra.");
}
logger.info("Configuring JMX Monitoring for Cassandra over ... ",
getComponentName(), host);
return true;
} catch (AnkushException e) {
throw e;
} catch (Exception e) {
throw new AnkushException("Could not configure JMX monitoring ");
}
}
/**
* Adds the process info.
*
* @param host
* {@link String}
* @param connection
* {@link SSHExec}
*/
private void addProcessInfo(String host, SSHExec connection)
throws AnkushException {
try {
Map<String, Set<String>> roles;
if (newClusterConf == null) {
roles = clusterConfig.getNodes().get(host).getRoles();
} else {
roles = newClusterConf.getNodes().get(host).getRoles();
}
if (CassandraUtils.isSeedNode(roles)) {
String userHome = CommonUtil.getUserHome(clusterConfig
.getAuthConf().getUsername());
String agentFile = clusterConfig.getAgentHomeDir()
+ AgentConstant.Relative_Path.AGENT_CONF_FILE;
String taskFile = clusterConfig.getAgentHomeDir()
+ AgentConstant.Relative_Path.TASKABLE_FILE;
String lineSeperator = LINE_SEPERATOR;
// jps service status monitor class name.
StringBuffer monitorConfProp = new StringBuffer();
String cassandraMonitorClassName = CassandraConstants.CASSANDRA_MONITORABLE_CLASS_NAME;
monitorConfProp.append(lineSeperator);
monitorConfProp.append(cassandraMonitorClassName).append(
lineSeperator);
StringBuffer agentConfProp = new StringBuffer();
// TODO: Either agent-based monitoring is to be removed , or
// JMX_PORT is to be replaced by CASSANDRA_JMX_PORT
agentConfProp
.append("JMX_PORT=")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.JMX_PORT))
.append(lineSeperator);
agentConfProp.append("SEEDNODE=true").append(lineSeperator);
CustomTask task = new AppendFileUsingEcho(
agentConfProp.toString(), agentFile);
connection.exec(task);
// add task information in taskable conf.
task = new AppendFileUsingEcho(monitorConfProp.toString(),
taskFile);
connection.exec(task);
}
} catch (TaskExecFailException e) {
throw new AnkushException("Could not execute task for adding "
+ getComponentName() + " related information to Agent.");
} catch (Exception e) {
throw new AnkushException("Execption occurs while adding "
+ getComponentName() + " process info to Agent.");
}
}
/**
* method to validate java version compatibility with Cassandra version
*
* @param host
* {@link String}
*
* @return true , if successful
*/
private boolean validateJavaVersion(String host) throws AnkushException {
try {
// Checking whether the major revision number of the Cassandra
// package version is 2
if (componentConfig.getVersion().split("\\.")[0].equals("2")) {
logger.info("Validating Java version for Cassandra 2.x.",
getComponentName(), host);
// If Java is already installed, then checking its version
if (clusterConfig.getJavaConf().isRegister()) {
String username = clusterConfig.getAuthConf().getUsername();
String password = clusterConfig.getAuthConf()
.isUsingPassword() ? clusterConfig.getAuthConf()
.getPassword() : clusterConfig.getAuthConf()
.getPrivateKey();
// Getting Java version
String javaVersion = SSHUtils.getJavaVersion(host,
username, password, clusterConfig.getAuthConf()
.isUsingPassword());
// Checking whether the minor revision number of Java
// version is 7 as Cassandra 2.x.x requires Java 1.7.x
if (!javaVersion.split("\\.")[1].equals("7")) {
throw new AnkushException(
"Java version should be 1.7.x for Cassandra "
+ componentConfig.getVersion() + " ...");
}
}
}
} catch (AnkushException e) {
throw e;
} catch (Exception e) {
throw new AnkushException(
"Exception while validating Java version", e);
}
return true;
}
/**
* method to create installation directory and extarcting Cassandra bundle
*
* @param host
* {@link String}
* @param connection
* {@link SSHExec}
* @return <code>true</code> , if successful
*/
private boolean installCassandra(String host, SSHExec connection)
throws AnkushException {
logger.info("Creating log directory ...", getComponentName(), host);
try {
// Create installation and log directory
if (!execCustomtask(
"mkdir -p "
+ componentConfig.getInstallPath()
+ " "
+ advanceConf
.get(CassandraConstants.ClusterProperties.LOG_DIR),
connection, host,
"Could not create " + componentConfig.getInstallPath()
+ "directory.")) {
return false;
}
logger.info("Extracting bundle", getComponentName(), host);
// Extracting Cassandra Bundle
if (!SSHUtils.getAndExtractComponent(connection, componentConfig,
this.getComponentName())) {
throw new AnkushException("Could not get/extract tarball.");
}
} catch (AnkushException e) {
throw e;
} catch (Exception e) {
throw new AnkushException("Could not install Cassandra.", e);
}
return true;
}
/**
* method to update environment variables, Cassandra yaml , topology and
* logger configuration
*
* @param host
* {@link String}
* @param connection
* {@link SSHExec}
* @return <code>true</code> , if successful
*/
private boolean configureCassandra(String host, SSHExec connection)
throws AnkushException {
try {
logger.info("Configuring Cassandra ...", getComponentName(), host);
// Updating environment variables , Cassandra configuration files
// and topology file
if (!updatePaths(host, connection)
|| !editCassandraConfigFile(host, connection)
|| !editTopologyFile(connection, host)) {
return false;
}
StringBuilder command = new StringBuilder();
String cassandralogFile = null;
// Applying version check to handle logging details
// TODO: Proper version check
if (componentConfig.getVersion().contains("2.1.")) {
// set log directory in log4j-server.properties file
cassandralogFile = advanceConf
.get(CassandraConstants.ClusterProperties.CONF_DIR)
+ CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_LOGBACK_XML;
command.append("sed -i 's/${cassandra.logdir}/")
.append(FileNameUtils
.convertToValidPath(
(String) advanceConf
.get(CassandraConstants.ClusterProperties.LOG_DIR))
.replace("/", "\\/")).append("/g' ")
.append(cassandralogFile);
} else {
// set log directory in log4j-server.properties file
cassandralogFile = advanceConf
.get(CassandraConstants.ClusterProperties.CONF_DIR)
+ CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_LOG4J_SERVER_PROPERTIES;
command.append(
"sed -i '/log4j.appender.R.File/c\\log4j.appender.R.File=")
.append(FileNameUtils.convertToValidPath((String) advanceConf
.get(CassandraConstants.ClusterProperties.LOG_DIR)))
.append("system.log").append("' ")
.append(cassandralogFile);
}
logger.info("Updating " + cassandralogFile + " file.",
getComponentName(), host);
if (execCustomtask(command.toString(), connection, host,
"Unable to set log directory in " + cassandralogFile
+ " file")) {
return true;
}
} catch (AnkushException e) {
throw e;
} catch (Exception e) {
throw new AnkushException("Could not configure "
+ getComponentName() + ".", e);
}
return false;
}
/**
* function to add java home and cassandra home to environment variables
*
* @param host
* {@link String}
* @param connection
* {@link SSHExec}
* @return <code>true</code> , if successful
*/
private boolean updatePaths(String host, SSHExec connection)
throws AnkushException {
try {
// Adding CASSANDRA_HOME and JAVA_HOME in $HOME/.bashrc file and
// JAVA_HOME in cassandra-env.sh file
String cassandraEnvFile = advanceConf
.get(CassandraConstants.ClusterProperties.CONF_DIR)
+ CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_ENV_SH;
// editing $HOME/.bashrc and cassandra environment file
return (execCustomtask("echo \"" + getBashrcContents()
+ "\" >> .bashrc", connection, host,
"Could not edit .bashrc.") && execCustomtask(
"echo \"export JAVA_HOME=" + getJavaHome() + "\" >> "
+ cassandraEnvFile,
connection,
host,
"Could not edit "
+ CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_ENV_SH
+ "."));
} catch (AnkushException e) {
throw e;
} catch (Exception e) {
throw new AnkushException(
"Could not update environment variables path for "
+ getComponentName());
}
}
/**
* Gets the bashrc contents.
*
* @return {@link String}
*/
private String getBashrcContents() {
// Preparing contents to be added in $HOME/.bashrc file
final StringBuilder sb = new StringBuilder();
sb.append("export CASSANDRA_HOME=")
.append(componentConfig.getHomeDir()).append("\n");
sb.append("export JAVA_HOME=").append(getJavaHome()).append("\n");
return sb.toString();
}
/**
* Method to get java home path from cluster conf.
*
* @return {@link String}
*/
private String getJavaHome() {
return clusterConfig.getJavaConf().getHomeDir();
}
/**
* Editing cassandra yaml configuration file
*
* @param host
* {@link String}
* @param connection
* {@link SSHExec}
* @return <code>true</code> , if successful
*/
private boolean editCassandraConfigFile(String host, SSHExec connection)
throws AnkushException {
try {
// Set auto_bootstap to true on adding new nodes.
boolean autoBootStrap = clusterConfig.getState().equals(
Constant.Cluster.State.ADD_NODE);
// Getting cassandra.yaml file location
String cassandraYaml = advanceConf
.get(CassandraConstants.ClusterProperties.CONF_DIR)
+ CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_YAML;
StringBuilder command = new StringBuilder();
int vNodeCount;
if (newCompConfig != null) {
vNodeCount = (Integer) newCompConfig.getNodes().get(host)
.get(CassandraConstants.NodeProperties.V_NODE_COUNT);
} else {
vNodeCount = (Integer) componentConfig.getNodes().get(host)
.get(CassandraConstants.NodeProperties.V_NODE_COUNT);
}
// Configuring cluster_name, num_tokens, partitioner, rpc_port,
// listen_address, storage_port, rpc_address, data_directory,
// saved_cache directory, commilog_dir, seeds info, endpoint_snitch
// and
// auto_bootstrap property in cassandra.yaml file
command.append("sed -i -e \"/^cluster_name:/c\\cluster_name: '")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.CLUSTER_NAME))
.append("'\" ");
command.append("-e '/^initial_token:/c#initial_token:' ");
command.append("-e '/num_tokens:/cnum_tokens: ").append(vNodeCount)
.append("' ");
command.append("-e '/^partitioner:/c\\partitioner: ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.PARTITIONER))
.append("' ");
command.append("-e '/^rpc_port:/c\\rpc_port: ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.RPC_PORT))
.append("' ");
command.append("-e '/^storage_port:/c\\storage_port: ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.STORAGE_PORT))
.append("' ");
command.append("-e '/^listen_address:/clisten_address: ")
.append(host).append("' ");
command.append("-e '/^rpc_address:/c\\rpc_address: ").append(host)
.append("' ");
command.append("-e 's_/var/lib/cassandra/data_")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.DATA_DIR))
.append("_g' ");
command.append(
"-e '/saved_caches_directory:/c\\saved_caches_directory: ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.SAVED_CACHES_DIR))
.append("' ");
command.append("-e '/commitlog_directory:/c\\commitlog_directory: ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.COMMIT_LOG_DIR))
.append("' ");
command.append("-e 's/- seeds: \"127.0.0.1\"/- seeds: \"")
.append(getSeedNodeString()).append("\"/' ");
command.append("-e '/^endpoint_snitch:/cendpoint_snitch: ")
.append(advanceConf
.get(CassandraConstants.ClusterProperties.SNITCH))
.append("' ");
command.append("-e '$ a\\auto_bootstrap: ").append(autoBootStrap)
.append("' ").append(cassandraYaml);
if (!execCustomtask(
command.toString(),
connection,
host,
"Unable to edit "
+ CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_YAML
+ ".")) {
return false;
}
return true;
} catch (AnkushException e) {
throw e;
} catch (Exception e) {
throw new AnkushException("Could not update " + getComponentName()
+ " configuration files.");
}
}
/**
* adding Cassandra nodes topology information
*
* @param connection
* {@link SSHExec}
* @param host
* {@link String}
* @return <code>true</code> , if successful
*/
private boolean editTopologyFile(SSHExec connection, String host)
throws AnkushException {
try {
// Getting snitch name
String snitch = (String) advanceConf
.get(CassandraConstants.ClusterProperties.SNITCH);
// if snitch is SimpleSnitch or RackInferringSnitch, exiting
// successfully with true return value
if (snitch
.equalsIgnoreCase(CassandraConstants.Configuration_Properties.SIMPLE_SNITCH)
|| snitch
.equalsIgnoreCase(CassandraConstants.Configuration_Properties.RACK_INFERRING_SNITCH)) {
return true;
}
// Topology file to update
String topologyFile = new String();
// File content to place in topology file
String fileContent = new String();
// Checking whether snitch is PropertyFileSnitch or
// GossipingPropertyFile Snitch and updating the corresponding
// configuration file
if (snitch
.equalsIgnoreCase(CassandraConstants.Configuration_Properties.PROPERTY_FILE_SNITCH)) {
// Getting Topology file name
topologyFile = advanceConf
.get(CassandraConstants.ClusterProperties.CONF_DIR)
+ CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_TOPOLOGY_PROPERTIES;
// Getting file content
fileContent = getTopologyString();
} else if (snitch
.equalsIgnoreCase(CassandraConstants.Configuration_Properties.GOSSIPING_PROPERTY_FILE_SNITCH)) {
// Getting Topology file name
topologyFile = advanceConf
.get(CassandraConstants.ClusterProperties.CONF_DIR)
+ CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_RACK_DC_PROPERTIES;
// Getting Topology file name
fileContent = getDCRackFileString(host);
}
logger.info("Update topology file " + topologyFile,
getComponentName(), host);
return execCustomtask("echo \"" + fileContent + "\" > "
+ topologyFile, connection, host,
"Unable to update topology file.");
} catch (AnkushException e) {
throw e;
} catch (Exception e) {
throw new AnkushException("Could not edit " + getComponentName()
+ " topology configuration file.");
}
}
/**
* Prepares Topology information of nodes in cluster when snitch used is
* PropertyFileSnitch
*
* @return {@link String}
*/
private String getTopologyString() throws AnkushException {
try {
StringBuilder topology = new StringBuilder();
// topology default Datacenter & Rack configuration
topology.append("default=DC1:RAC1").append("\n");
for (Map.Entry<String, NodeConfig> nodes : clusterConfig.getNodes()
.entrySet()) {
if (returnNodes().keySet().contains(nodes.getKey())) {
if (nodes.getValue().getDatacenter() != null
&& nodes.getValue().getRack() != null
|| !nodes.getValue().getDatacenter().isEmpty()
|| !nodes.getValue().getRack().isEmpty()) {
topology.append(nodes.getKey()).append("=")
.append(nodes.getValue().getDatacenter())
.append(":").append(nodes.getValue().getRack())
.append("\n");
}
}
}
return topology.toString();
} catch (Exception e) {
throw new AnkushException("Could not get topology string.");
}
}
/**
* Prepares node wise toplogy string when snitch used is
* GossipingPropertyFileSnitch
*
* @param host
* {@link String}
* @return {@link String}
*
*/
private String getDCRackFileString(String host) throws AnkushException {
try {
StringBuilder dcRack = new StringBuilder();
NodeConfig nc = clusterConfig.getNodes().get(host);
if (nc.getDatacenter() != null && !nc.getDatacenter().isEmpty()) {
dcRack.append("dc=").append(nc.getDatacenter()).append("\n");
} else {
dcRack.append("dc=DC1\n");
}
if (nc.getRack() != null && !nc.getRack().isEmpty()) {
dcRack.append("rack=").append(nc.getRack()).append("\n");
} else {
dcRack.append("rack=RAC1\n");
}
return dcRack.toString();
} catch (Exception e) {
throw new AnkushException("Could not get topology string.");
}
}
/**
* Function to update seed node information after add or remove nodes on all
* nodes in a cluster
*/
private void updateSeedNodeValue() {
// Seeds update command
String cassandraYaml = advanceConf
.get(CassandraConstants.ClusterProperties.CONF_DIR)
+ CassandraConstants.Cassandra_Configuration_Files.CASSANDRA_YAML;
final String command = "sed -i -e 's/- seeds: \".*$/- seeds: \""
+ getSeedNodeString() + "\"/' " + cassandraYaml;
Map<String, Map<String, Object>> nodes = new HashMap<String, Map<String, Object>>(
componentConfig.getNodes());
if (clusterConfig.getState().equals(Constant.Cluster.State.REMOVE_NODE)) {
nodes = new HashMap<String, Map<String, Object>>(returnNodes());
}
final Semaphore semaphore = new Semaphore(nodes.size());
try {
for (final String host : nodes.keySet()) {
semaphore.acquire();
AppStoreWrapper.getExecutor().execute(new Runnable() {
@Override
public void run() {
try {
SSHExec connection = getConnection(host);
if (connection != null) {
execCustomtask(command, connection, host,
"Could not update seeds value");
}
} catch (AnkushException e) {
addClusterError(e.getMessage(), host, e);
} catch (Exception e) {
addClusterError(
"Could not update seed node value in cassandra.yaml file.",
host, e);
}
if (semaphore != null) {
semaphore.release();
}
}
});
}
semaphore.acquire(nodes.size());
} catch (Exception e) {
logger.error("Error in editing seeds values.", e);
}
}
/**
* Function to update topology information on all nodes in cluster after add
* or remove nodes
*/
private void updateTopologyFile() {
Map<String, Map<String, Object>> nodes = new HashMap<String, Map<String, Object>>(
componentConfig.getNodes());
if (clusterConfig.getState().equals(Constant.Cluster.State.REMOVE_NODE)) {
nodes = new HashMap<String, Map<String, Object>>(returnNodes());
}
final Semaphore semaphore = new Semaphore(nodes.size());
try {
for (final String host : nodes.keySet()) {
semaphore.acquire();
AppStoreWrapper.getExecutor().execute(new Runnable() {
@Override
public void run() {
try {
SSHExec connection = getConnection(host);
if (connection != null) {
editTopologyFile(connection, host);
}
} catch (AnkushException e) {
addClusterError(e.getMessage(), host, e);
} catch (Exception e) {
addClusterError(
"Could not update topology details.", host,
e);
}
if (semaphore != null) {
semaphore.release();
}
}
});
}
semaphore.acquire(nodes.size());
} catch (Exception e) {
logger.error("Error in updating topology file.", e);
}
}
/**
* Prepares seednodes String to be updated in cassandra.yaml configuration
* file
*
* @return {@link String}
*/
private String getSeedNodeString() {
Set<String> seedNodes = new HashSet<String>();
Map<String, Set<String>> roles = new HashMap<String, Set<String>>();
for (Map.Entry<String, Map<String, Object>> node : returnNodes()
.entrySet()) {
if (clusterConfig.getNodes().containsKey(node.getKey())) {
roles = clusterConfig.getNodes().get(node.getKey()).getRoles();
} else {
roles = newClusterConf.getNodes().get(node.getKey()).getRoles();
}
if (roles.containsKey(Constant.Component.Name.CASSANDRA)
&& roles.get(Constant.Component.Name.CASSANDRA).contains(
CassandraConstants.Node_Type.CASSANDRA_SEED)) {
seedNodes.add(node.getKey());
}
}
return StringUtils.join(seedNodes, ",");
}
/**
* This function handles the node map to use to prepare seed node string and
* topology file content
*
* @return {@link Map}
**/
private Map<String, Map<String, Object>> returnNodes() {
// Getting all component nodes during deployment case
Map<String, Map<String, Object>> nodes = new HashMap<String, Map<String, Object>>(
componentConfig.getNodes());
// Modifying node map during add or remove nodes case
if (clusterConfig.getState().equals(Constant.Cluster.State.ADD_NODE)) {
// Adding nodes to be added to node map
nodes.putAll(newCompConfig.getNodes());
} else if (clusterConfig.getState().equals(
Constant.Cluster.State.REMOVE_NODE)) {
// Removing nodes to be deleted from node map
for (String nc : newCompConfig.getNodes().keySet()) {
nodes.remove(nc);
}
}
return nodes;
}
/**
* function to execute Custom Tasks and setting error if it fails
*
* @return <code>true</code>, if successful
*/
private boolean execCustomtask(String command, SSHExec connection,
String publicIp, String errMsg) throws AnkushException {
try {
CustomTask task = new ExecCommand(command);
if (connection.exec(task).rc == 0) {
return true;
}
throw new AnkushException(errMsg);
} catch (AnkushException e) {
throw e;
} catch (TaskExecFailException e) {
throw new AnkushException("Could not execute task : " + command, e);
} catch (Exception e) {
throw new AnkushException("Exception while executing command '"
+ command + "' on " + publicIp, e);
}
}
@Override
public boolean createConfig(ClusterConfig conf) {
try {
// Setting logger and cluster conf
setClassVariables(conf);
// Need to verify registration case code to consider node roles in
// NodeConfig object
if (componentConfig.isRegister()) {
if (!(new CassandraRegister(clusterConfig).createConfig())) {
throw new AnkushException("Could not create "
+ getComponentName()
+ " configuration for cluster registration");
}
} else {
// Adding clusterName key to advanceConf map to use it in
// configuring cluster_name in cassandra.yaml
advanceConf.put(
CassandraConstants.ClusterProperties.CLUSTER_NAME,
clusterConfig.getName());
// Adding a set of seed nodes to Cassandra advanceConf
advanceConf.put(
CassandraConstants.ClusterProperties.SEED_NODE_SET,
CassandraUtils.getSeedNodeSet(clusterConfig,
componentConfig));
}
return true;
} catch (AnkushException e) {
addClusterError(e.getMessage(), e);
} catch (Exception e) {
addClusterError("Could not create " + getComponentName()
+ " configuration.", e);
}
return false;
}
// /**
// * Setting roles in clustercConf node's roles set. This is to be used
// during
// * registration case.
// */
// private boolean setRole() {
// Map<String, Set<String>> roles;
// for (String compNode : compConfig.getNodes().keySet()) {
// roles = clusterConf.getNodes().get(compNode).getRoles();
// if (!roles.containsKey(Constant.Component.Name.CASSANDRA)) {
// roles.put(Constant.Component.Name.CASSANDRA,
// new LinkedHashSet<String>());
// }
// roles.get(Constant.Component.Name.CASSANDRA).add(
// CassandraUtils.getNodeRole(roles));
// }
// return true;
// }
@Override
public boolean register(final ClusterConfig conf) {
try {
ComponentConfig compConfig = clusterConfig.getComponents().get(
this.componentName);
if (!AnkushUtils.isMonitoredByAnkush(compConfig)) {
logger.info("Skipping " + getComponentName()
+ " registration for Level1", this.componentName);
return true;
}
final String infoMsg = "Registering Cassandra";
logger.info(infoMsg);
// Getting node map for cluster deployment
Map<String, Map<String, Object>> nodeMap = new HashMap<String, Map<String, Object>>(
componentConfig.getNodes());
// Node Registration process ...
final Semaphore semaphore = new Semaphore(nodeMap.size());
for (final String host : nodeMap.keySet()) {
semaphore.acquire();
AppStoreWrapper.getExecutor().execute(new Runnable() {
@Override
public void run() {
conf.getNodes().get(host).setStatus(registerNode(host));
if (semaphore != null) {
semaphore.release();
}
}
});
}
semaphore.acquire(nodeMap.size());
// Return false if any of the node is not deployed.
return AnkushUtils.getStatus(conf.getNodes());
} catch (Exception e) {
return addClusterError("Could not register " + getComponentName(),
e);
}
}
@Override
public boolean start(final ClusterConfig conf, Collection<String> nodes) {
try {
// Causing the thread to sleep for two minutes during add nodes case
// to
// update ring topology details
if (this.clusterConfig.getState().equals(
Constant.Cluster.State.ADD_NODE)) {
// starting service on all newly added nodes
for (final String host : nodes) {
// setting cluster conf nodes status
conf.getNodes().get(host).setStatus(startNode(host));
// Wait for two minutes
try {
logger.info("Waiting for two minutes...",
getComponentName(), host);
logger.debug("Wait for two minutes.", host);
Thread.sleep(120000);
} catch (InterruptedException e) {
logger.debug(e.getMessage());
}
}
} else {
final Semaphore semaphore = new Semaphore(nodes.size());
// starting service on each node in cluster
for (final String host : nodes) {
semaphore.acquire();
AppStoreWrapper.getExecutor().execute(new Runnable() {
@Override
public void run() {
// setting cluster conf nodes status
conf.getNodes().get(host)
.setStatus(startNode(host));
if (semaphore != null) {
semaphore.release();
}
}
});
}
semaphore.acquire(nodes.size());
}
// Return false if any of the node is not deployed.
return AnkushUtils.getStatus(conf.getNodes());
} catch (Exception e) {
return addClusterError("Could not start " + getComponentName()
+ " services.", e);
}
}
@Override
public boolean stop(final ClusterConfig conf, Collection<String> nodes) {
// Stop services only if cluster is not in REMOVING_NODES
if (!conf.getState().equals(Constant.Cluster.State.REMOVE_NODE)) {
final Semaphore semaphore = new Semaphore(nodes.size());
try {
// stopping service on each of the cluster nodes
for (final String host : nodes) {
semaphore.acquire();
AppStoreWrapper.getExecutor().execute(new Runnable() {
@Override
public void run() {
conf.getNodes().get(host).setStatus(stopNode(host));
if (semaphore != null) {
semaphore.release();
}
}
});
}
semaphore.acquire(nodes.size());
} catch (Exception e) {
logger.error(e.getMessage());
}
}
return true;
}
@Override
public boolean removeNode(final ClusterConfig conf, Collection<String> nodes) {
logger.info("Deleting Cassandra packages...", getComponentName());
try {
if (newClusterConf == null) {
// setting clusterconf, componentconf and logger
if (!setClassVariables(conf)) {
return false;
}
}
final Semaphore semaphore = new Semaphore(nodes.size());
// undeploying package from each node
for (final String host : nodes) {
semaphore.acquire();
AppStoreWrapper.getExecutor().execute(new Runnable() {
@Override
public void run() {
// setting nodestatus default value to false
boolean nodestatus = false;
// if service stopped successfully, then removing
// component from node
if (stopNode(host)) {
nodestatus = removeNode(host);
}
conf.getNodes().get(host).setStatus(nodestatus);
if (semaphore != null) {
semaphore.release();
}
}
});
}
semaphore.acquire(nodes.size());
return AnkushUtils.getStatus(conf.getNodes());
} catch (Exception e) {
return addClusterError("Could not remove " + getComponentName(), e);
}
}
public boolean removeNode(ClusterConfig conf, ClusterConfig newConf) {
try {
// setting clusterconf, componentconf and logger
if (!setClassVariables(conf)) {
return false;
}
this.newClusterConf = newConf;
this.newCompConfig = newConf.getComponents()
.get(getComponentName());
return removeNode(newConf, newCompConfig.getNodes().keySet());
} catch (AnkushException e) {
return addClusterError(e.getMessage(), e);
} catch (Exception e) {
return addClusterError("Could not undeploy " + getComponentName(),
e);
}
}
/**
* Provides node's connection object
*
* @param host
* {@link String}
* @return {@link SSHExec}
*/
private SSHExec getConnection(String host) throws AnkushException {
try {
if (clusterConfig.getNodes().containsKey(host)) {
return clusterConfig.getNodes().get(host).getConnection();
}
return newClusterConf.getNodes().get(host).getConnection();
} catch (Exception e) {
throw new AnkushException(COULD_NOT_GET_CONNECTION, e);
}
}
@Override
public boolean canNodeBeDeleted(ClusterConfig clusterConfig,
Collection<String> nodes) {
return true;
}
}