/**
* 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.test.system.process;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.Shell.ShellCommandExecutor;
/**
* The concrete class which implements the start up and shut down based routines
* based on the hadoop-daemon.sh. <br/>
*
* Class requires two keys to be present in the Configuration objects passed to
* it. Look at <code>CONF_HADOOPHOME</code> and
* <code>CONF_HADOOPCONFDIR</code> for the names of the
* configuration keys.
*
* Following will be the format which the final command execution would look :
* <br/>
* <code>
* ssh host 'hadoop-home/bin/hadoop-daemon.sh --script scriptName
* --config HADOOP_CONF_DIR (start|stop) command'
* </code>
*/
public abstract class HadoopDaemonRemoteCluster
implements ClusterProcessManager {
private static final Log LOG = LogFactory
.getLog(HadoopDaemonRemoteCluster.class.getName());
public static final String CONF_HADOOPNEWCONFDIR =
"test.system.hdrc.hadoopnewconfdir";
/**
* Key used to configure the HADOOP_HOME to be used by the
* HadoopDaemonRemoteCluster.
*/
public final static String CONF_HADOOPHOME =
"test.system.hdrc.hadoophome";
public final static String CONF_SCRIPTDIR =
"test.system.hdrc.deployed.scripts.dir";
/**
* Key used to configure the HADOOP_CONF_DIR to be used by the
* HadoopDaemonRemoteCluster.
*/
public final static String CONF_HADOOPCONFDIR =
"test.system.hdrc.hadoopconfdir";
public final static String CONF_DEPLOYED_HADOOPCONFDIR =
"test.system.hdrc.deployed.hadoopconfdir";
private String hadoopHome;
protected String hadoopConfDir;
protected String scriptsDir;
protected String hadoopNewConfDir;
private final Set<Enum<?>> roles;
private final List<HadoopDaemonInfo> daemonInfos;
private List<RemoteProcess> processes;
protected Configuration conf;
public static class HadoopDaemonInfo {
public final String cmd;
public final Enum<?> role;
public final List<String> hostNames;
public HadoopDaemonInfo(String cmd, Enum<?> role, List<String> hostNames) {
super();
this.cmd = cmd;
this.role = role;
this.hostNames = hostNames;
}
public HadoopDaemonInfo(String cmd, Enum<?> role, String hostFile)
throws IOException {
super();
this.cmd = cmd;
this.role = role;
File file = new File(getDeployedHadoopConfDir(), hostFile);
BufferedReader reader = null;
hostNames = new ArrayList<String>();
try {
reader = new BufferedReader(new FileReader(file));
String host = null;
while ((host = reader.readLine()) != null) {
if (host.trim().isEmpty() || host.startsWith("#")) {
// Skip empty and possible comment lines
// throw new IllegalArgumentException(
// "Hostname could not be found in file " + hostFile);
continue;
}
hostNames.add(host.trim());
}
if (hostNames.size() < 1) {
throw new IllegalArgumentException("At least one hostname "
+
"is required to be present in file - " + hostFile);
}
} finally {
try {
if(reader != null) {
reader.close();
}
} catch (IOException e) {
LOG.warn("Could not close reader", e);
}
}
LOG.info("Created HadoopDaemonInfo for " + cmd + " " + role + " from "
+ hostFile);
}
}
@Override
public String pushConfig(String localDir) throws IOException {
for (RemoteProcess process : processes){
process.pushConfig(localDir);
}
return hadoopNewConfDir;
}
@Override
public RemoteProcess getDaemonProcess(String hostname,Enum<?> role) {
RemoteProcess daemon=null;
for (RemoteProcess p : processes) {
if(p.getHostName().equals(hostname) && p.getRole() == role){
daemon =p;
break;
}
}
return daemon;
}
public HadoopDaemonRemoteCluster(List<HadoopDaemonInfo> daemonInfos) {
this.daemonInfos = daemonInfos;
this.roles = new HashSet<Enum<?>>();
for (HadoopDaemonInfo info : daemonInfos) {
this.roles.add(info.role);
}
}
@Override
public void init(Configuration conf) throws IOException {
this.conf = conf;
populateDirectories(conf);
this.processes = new ArrayList<RemoteProcess>();
populateDaemons();
}
@Override
public List<RemoteProcess> getAllProcesses() {
return processes;
}
@Override
public Set<Enum<?>> getRoles() {
return roles;
}
/**
* Method to populate the hadoop home and hadoop configuration directories.
*
* @param conf
* Configuration object containing values for
* CONF_HADOOPHOME and
* CONF_HADOOPCONFDIR
*
* @throws IllegalArgumentException
* if the configuration or system property set does not contain
* values for the required keys.
*/
protected void populateDirectories(Configuration conf) {
hadoopHome = conf.get(CONF_HADOOPHOME);
hadoopConfDir = conf.get(CONF_HADOOPCONFDIR);
scriptsDir = conf.get(CONF_SCRIPTDIR);
hadoopNewConfDir = conf.get(CONF_HADOOPNEWCONFDIR);
if (hadoopHome == null || hadoopConfDir == null || hadoopHome.isEmpty()
|| hadoopConfDir.isEmpty()) {
LOG.error("No configuration "
+ "for the HADOOP_HOME and HADOOP_CONF_DIR passed");
throw new IllegalArgumentException(
"No Configuration passed for hadoop home " +
"and hadoop conf directories");
}
}
public static String getDeployedHadoopConfDir() {
String dir = System.getProperty(CONF_DEPLOYED_HADOOPCONFDIR);
if (dir == null || dir.isEmpty()) {
LOG.error("No configuration "
+ "for the CONF_DEPLOYED_HADOOPCONFDIR passed");
throw new IllegalArgumentException(
"No Configuration passed for hadoop deployed conf directory");
}
return dir;
}
@Override
public void start() throws IOException {
for (RemoteProcess process : processes) {
process.start();
}
}
@Override
public void start(String newConfLocation)throws IOException {
for (RemoteProcess process : processes) {
process.start(newConfLocation);
}
}
@Override
public void stop() throws IOException {
for (RemoteProcess process : processes) {
process.kill();
}
}
@Override
public void stop(String newConfLocation) throws IOException {
for (RemoteProcess process : processes) {
process.kill(newConfLocation);
}
}
protected void populateDaemon(HadoopDaemonInfo info) throws IOException {
for (String host : info.hostNames) {
InetAddress addr = InetAddress.getByName(host);
RemoteProcess process = getProcessManager(info,
addr.getCanonicalHostName());
processes.add(process);
}
}
protected void populateDaemons() throws IOException {
for (HadoopDaemonInfo info : daemonInfos) {
populateDaemon(info);
}
}
@Override
public boolean isMultiUserSupported() throws IOException {
return false;
}
protected RemoteProcess getProcessManager(
HadoopDaemonInfo info, String hostName) {
RemoteProcess process = new ScriptDaemon(info.cmd, hostName, info.role);
return process;
}
/**
* The core daemon class which actually implements the remote process
* management of actual daemon processes in the cluster.
*
*/
class ScriptDaemon implements RemoteProcess {
private static final String STOP_COMMAND = "stop";
private static final String START_COMMAND = "start";
private static final String SCRIPT_NAME = "hadoop-daemon.sh";
private static final String PUSH_CONFIG ="pushConfig.sh";
protected final String daemonName;
protected final String hostName;
private final Enum<?> role;
public ScriptDaemon(String daemonName, String hostName, Enum<?> role) {
this.daemonName = daemonName;
this.hostName = hostName;
this.role = role;
}
@Override
public String getHostName() {
return hostName;
}
private String[] getPushConfigCommand(String localDir, String remoteDir,
File scriptDir) throws IOException{
ArrayList<String> cmdArgs = new ArrayList<String>();
cmdArgs.add(scriptDir.getAbsolutePath() + File.separator + PUSH_CONFIG);
cmdArgs.add(localDir);
cmdArgs.add(hostName);
cmdArgs.add(remoteDir);
cmdArgs.add(hadoopConfDir);
return (String[]) cmdArgs.toArray(new String[cmdArgs.size()]);
}
private ShellCommandExecutor buildPushConfig(String local, String remote )
throws IOException {
File scriptDir = new File(scriptsDir);
String[] commandArgs = getPushConfigCommand(local, remote, scriptDir);
HashMap<String, String> env = new HashMap<String, String>();
ShellCommandExecutor executor = new ShellCommandExecutor(commandArgs,
scriptDir, env);
LOG.info(executor.toString());
return executor;
}
private ShellCommandExecutor createNewConfDir() throws IOException {
ArrayList<String> cmdArgs = new ArrayList<String>();
cmdArgs.add("ssh");
cmdArgs.add(hostName);
cmdArgs.add("if [ -d "+ hadoopNewConfDir+
" ];\n then echo Will remove existing directory; rm -rf "+
hadoopNewConfDir+";\nmkdir "+ hadoopNewConfDir+"; else \n"+
"echo " + hadoopNewConfDir + " doesnt exist hence creating" +
"; mkdir " + hadoopNewConfDir + ";\n fi");
String[] cmd = (String[]) cmdArgs.toArray(new String[cmdArgs.size()]);
ShellCommandExecutor executor = new ShellCommandExecutor(cmd);
LOG.info(executor.toString());
return executor;
}
@Override
public String pushConfig(String localDir) throws IOException {
createNewConfDir().execute();
buildPushConfig(localDir, hadoopNewConfDir).execute();
return hadoopNewConfDir;
}
private ShellCommandExecutor buildCommandExecutor(String command,
String confDir) {
String[] commandArgs = getCommand(command, confDir);
File cwd = new File(".");
HashMap<String, String> env = new HashMap<String, String>();
env.put("HADOOP_CONF_DIR", confDir);
ShellCommandExecutor executor
= new ShellCommandExecutor(commandArgs, cwd, env);
LOG.info(executor.toString());
return executor;
}
private File getBinDir() {
File binDir = new File(hadoopHome, "bin");
return binDir;
}
protected String[] getCommand(String command, String confDir) {
ArrayList<String> cmdArgs = new ArrayList<String>();
File binDir = getBinDir();
cmdArgs.add("ssh");
cmdArgs.add(hostName);
cmdArgs.add(binDir.getAbsolutePath() + File.separator + SCRIPT_NAME);
cmdArgs.add("--config");
cmdArgs.add(confDir);
// XXX Twenty internal version does not support --script option.
cmdArgs.add(command);
cmdArgs.add(daemonName);
return (String[]) cmdArgs.toArray(new String[cmdArgs.size()]);
}
@Override
public void kill() throws IOException {
kill(hadoopConfDir);
}
@Override
public void start() throws IOException {
start(hadoopConfDir);
}
public void start(String newConfLocation) throws IOException {
ShellCommandExecutor cme = buildCommandExecutor(START_COMMAND,
newConfLocation);
cme.execute();
String output = cme.getOutput();
if (!output.isEmpty()) { //getOutput() never returns null value
if (output.toLowerCase().contains("error")) {
LOG.warn("Error is detected.");
throw new IOException("Start error\n" + output);
}
}
}
public void kill(String newConfLocation) throws IOException {
ShellCommandExecutor cme
= buildCommandExecutor(STOP_COMMAND, newConfLocation);
cme.execute();
String output = cme.getOutput();
if (!output.isEmpty()) { //getOutput() never returns null value
if (output.toLowerCase().contains("error")) {
LOG.info("Error is detected.");
throw new IOException("Kill error\n" + output);
}
}
}
@Override
public Enum<?> getRole() {
return role;
}
}
}