/** * 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; import java.io.File; import java.io.IOException; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.apache.hadoop.classification.InterfaceAudience; import org.apache.hadoop.hbase.HBaseClusterManager.CommandProvider.Operation; import org.apache.hadoop.hbase.util.Pair; import org.apache.hadoop.util.Shell; /** * A default cluster manager for HBase. Uses SSH, and hbase shell scripts * to manage the cluster. Assumes Unix-like commands are available like 'ps', * 'kill', etc. Also assumes the user running the test has enough "power" to start & stop * servers on the remote machines (for example, the test user could be the same user as the * user the daemon isrunning as) */ @InterfaceAudience.Private public class HBaseClusterManager extends ClusterManager { /** * Executes commands over SSH */ static class RemoteShell extends Shell.ShellCommandExecutor { private String hostname; private String sshCmd = "/usr/bin/ssh"; private String sshOptions = System.getenv("HBASE_SSH_OPTS"); //from conf/hbase-env.sh public RemoteShell(String hostname, String[] execString, File dir, Map<String, String> env, long timeout) { super(execString, dir, env, timeout); this.hostname = hostname; } public RemoteShell(String hostname, String[] execString, File dir, Map<String, String> env) { super(execString, dir, env); this.hostname = hostname; } public RemoteShell(String hostname, String[] execString, File dir) { super(execString, dir); this.hostname = hostname; } public RemoteShell(String hostname, String[] execString) { super(execString); this.hostname = hostname; } @Override public String[] getExecString() { return new String[] { "bash", "-c", StringUtils.join(new String[] { sshCmd, sshOptions == null ? "" : sshOptions, hostname, "\"" + StringUtils.join(super.getExecString(), " ") + "\"" }, " ")}; } @Override public void execute() throws IOException { super.execute(); } public void setSshCmd(String sshCmd) { this.sshCmd = sshCmd; } public void setSshOptions(String sshOptions) { this.sshOptions = sshOptions; } public String getSshCmd() { return sshCmd; } public String getSshOptions() { return sshOptions; } } /** * Provides command strings for services to be executed by Shell. CommandProviders are * pluggable, and different deployments(windows, bigtop, etc) can be managed by * plugging-in custom CommandProvider's or ClusterManager's. */ static abstract class CommandProvider { enum Operation { START, STOP, RESTART } public abstract String getCommand(ServiceType service, Operation op); public String isRunningCommand(ServiceType service) { return findPidCommand(service); } protected String findPidCommand(ServiceType service) { return String.format("ps aux | grep proc_%s | grep -v grep | tr -s ' ' | cut -d ' ' -f2", service); } public String signalCommand(ServiceType service, String signal) { return String.format("%s | xargs kill -s %s", findPidCommand(service), signal); } } /** * CommandProvider to manage the service using bin/hbase-* scripts */ static class HBaseShellCommandProvider extends CommandProvider { private String getHBaseHome() { return System.getenv("HBASE_HOME"); } private String getConfig() { String confDir = System.getenv("HBASE_CONF_DIR"); if (confDir != null) { return String.format("--config %s", confDir); } return ""; } @Override public String getCommand(ServiceType service, Operation op) { return String.format("%s/bin/hbase-daemon.sh %s %s %s", getHBaseHome(), getConfig(), op.toString().toLowerCase(), service); } } public HBaseClusterManager() { super(); } protected CommandProvider getCommandProvider(ServiceType service) { //TODO: make it pluggable, or auto-detect the best command provider, should work with //hadoop daemons as well return new HBaseShellCommandProvider(); } /** * Execute the given command on the host using SSH * @return pair of exit code and command output * @throws IOException if something goes wrong. */ private Pair<Integer, String> exec(String hostname, String... cmd) throws IOException { LOG.info("Executing remote command: " + StringUtils.join(cmd, " ") + " , hostname:" + hostname); RemoteShell shell = new RemoteShell(hostname, cmd); shell.execute(); LOG.info("Executed remote command, exit code:" + shell.getExitCode() + " , output:" + shell.getOutput()); return new Pair<Integer, String>(shell.getExitCode(), shell.getOutput()); } private void exec(String hostname, ServiceType service, Operation op) throws IOException { exec(hostname, getCommandProvider(service).getCommand(service, op)); } @Override public void start(ServiceType service, String hostname) throws IOException { exec(hostname, service, Operation.START); } @Override public void stop(ServiceType service, String hostname) throws IOException { exec(hostname, service, Operation.STOP); } @Override public void restart(ServiceType service, String hostname) throws IOException { exec(hostname, service, Operation.RESTART); } @Override public void signal(ServiceType service, String signal, String hostname) throws IOException { exec(hostname, getCommandProvider(service).signalCommand(service, signal)); } @Override public boolean isRunning(ServiceType service, String hostname) throws IOException { String ret = exec(hostname, getCommandProvider(service).isRunningCommand(service)) .getSecond(); return ret.length() > 0; } }