View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase;
20  
21  import java.io.File;
22  import java.io.IOException;
23  import java.util.Map;
24  
25  import org.apache.commons.lang.StringUtils;
26  import org.apache.hadoop.classification.InterfaceAudience;
27  import org.apache.hadoop.conf.Configuration;
28  import org.apache.hadoop.hbase.HBaseClusterManager.CommandProvider.Operation;
29  import org.apache.hadoop.hbase.util.Pair;
30  import org.apache.hadoop.util.Shell;
31  
32  /**
33   * A default cluster manager for HBase. Uses SSH, and hbase shell scripts
34   * to manage the cluster. Assumes Unix-like commands are available like 'ps',
35   * 'kill', etc. Also assumes the user running the test has enough "power" to start & stop
36   * servers on the remote machines (for example, the test user could be the same user as the
37   * user the daemon isrunning as)
38   */
39  @InterfaceAudience.Private
40  public class HBaseClusterManager extends ClusterManager {
41    private String sshUserName;
42    private String sshOptions;
43  
44    /**
45     * The command format that is used to execute the remote command. Arguments:
46     * 1 SSH options, 2 user name , 3 "@" if username is set, 4 host, 5 original command.
47     */
48    private static final String DEFAULT_TUNNEL_CMD = "/usr/bin/ssh %1$s %2$s%3$s%4$s \"%5$s\"";
49    private String tunnelCmd;
50  
51    @Override
52    public void setConf(Configuration conf) {
53      super.setConf(conf);
54      if (conf == null) {
55        // Configured gets passed null before real conf. Why? I don't know.
56        return;
57      }
58      sshUserName = conf.get("hbase.it.clustermanager.ssh.user", "");
59      String extraSshOptions = conf.get("hbase.it.clustermanager.ssh.opts", "");
60      sshOptions = System.getenv("HBASE_SSH_OPTS");
61      if (!extraSshOptions.isEmpty()) {
62        sshOptions = StringUtils.join(new Object[] { sshOptions, extraSshOptions }, " ");
63      }
64      sshOptions = (sshOptions == null) ? "" : sshOptions;
65      tunnelCmd = conf.get("hbase.it.clustermanager.ssh.cmd", DEFAULT_TUNNEL_CMD);
66      // Print out ssh special config if any.
67      if ((sshUserName != null && sshUserName.length() > 0) ||
68          (sshOptions != null && sshOptions.length() > 0)) {
69        LOG.info("Running with SSH user [" + sshUserName + "] and options [" + sshOptions + "]");
70      }
71    }
72  
73    /**
74     * Executes commands over SSH
75     */
76    protected class RemoteShell extends Shell.ShellCommandExecutor {
77      private String hostname;
78  
79      public RemoteShell(String hostname, String[] execString, File dir, Map<String, String> env,
80          long timeout) {
81        super(execString, dir, env, timeout);
82        this.hostname = hostname;
83      }
84  
85      public RemoteShell(String hostname, String[] execString, File dir, Map<String, String> env) {
86        super(execString, dir, env);
87        this.hostname = hostname;
88      }
89  
90      public RemoteShell(String hostname, String[] execString, File dir) {
91        super(execString, dir);
92        this.hostname = hostname;
93      }
94  
95      public RemoteShell(String hostname, String[] execString) {
96        super(execString);
97        this.hostname = hostname;
98      }
99  
100     @Override
101     public String[] getExecString() {
102       String at = sshUserName.isEmpty() ? "" : "@";
103       String remoteCmd = StringUtils.join(super.getExecString(), " ");
104       String cmd = String.format(tunnelCmd, sshOptions, sshUserName, at, hostname, remoteCmd);
105       LOG.info("Executing full command [" + cmd + "]");
106       return new String[] { "/usr/bin/env", "bash", "-c", cmd };
107     }
108 
109     @Override
110     public void execute() throws IOException {
111       super.execute();
112     }
113   }
114 
115   /**
116    * Provides command strings for services to be executed by Shell. CommandProviders are
117    * pluggable, and different deployments(windows, bigtop, etc) can be managed by
118    * plugging-in custom CommandProvider's or ClusterManager's.
119    */
120   static abstract class CommandProvider {
121 
122     enum Operation {
123       START, STOP, RESTART
124     }
125 
126     public abstract String getCommand(ServiceType service, Operation op);
127 
128     public String isRunningCommand(ServiceType service) {
129       return findPidCommand(service);
130     }
131 
132     protected String findPidCommand(ServiceType service) {
133       return String.format("ps aux | grep proc_%s | grep -v grep | tr -s ' ' | cut -d ' ' -f2",
134           service);
135     }
136 
137     public String signalCommand(ServiceType service, String signal) {
138       return String.format("%s | xargs kill -s %s", findPidCommand(service), signal);
139     }
140   }
141 
142   /**
143    * CommandProvider to manage the service using bin/hbase-* scripts
144    */
145   static class HBaseShellCommandProvider extends CommandProvider {
146     private final String hbaseHome;
147     private final String confDir;
148 
149     HBaseShellCommandProvider(Configuration conf) {
150       hbaseHome = conf.get("hbase.it.clustermanager.hbase.home",
151         System.getenv("HBASE_HOME"));
152       String tmp = conf.get("hbase.it.clustermanager.hbase.conf.dir",
153         System.getenv("HBASE_CONF_DIR"));
154       if (tmp != null) {
155         confDir = String.format("--config %s", tmp);
156       } else {
157         confDir = "";
158       }
159     }
160 
161     @Override
162     public String getCommand(ServiceType service, Operation op) {
163       return String.format("%s/bin/hbase-daemon.sh %s %s %s", hbaseHome, confDir,
164           op.toString().toLowerCase(), service);
165     }
166   }
167 
168   public HBaseClusterManager() {
169     super();
170   }
171 
172   protected CommandProvider getCommandProvider(ServiceType service) {
173     //TODO: make it pluggable, or auto-detect the best command provider, should work with
174     //hadoop daemons as well
175     return new HBaseShellCommandProvider(getConf());
176   }
177 
178   /**
179    * Execute the given command on the host using SSH
180    * @return pair of exit code and command output
181    * @throws IOException if something goes wrong.
182    */
183   private Pair<Integer, String> exec(String hostname, String... cmd) throws IOException {
184     LOG.info("Executing remote command: " + StringUtils.join(cmd, " ") + " , hostname:" + hostname);
185 
186     RemoteShell shell = new RemoteShell(hostname, cmd);
187     shell.execute();
188 
189     LOG.info("Executed remote command, exit code:" + shell.getExitCode()
190         + " , output:" + shell.getOutput());
191 
192     return new Pair<Integer, String>(shell.getExitCode(), shell.getOutput());
193   }
194 
195   private void exec(String hostname, ServiceType service, Operation op) throws IOException {
196     exec(hostname, getCommandProvider(service).getCommand(service, op));
197   }
198 
199   @Override
200   public void start(ServiceType service, String hostname) throws IOException {
201     exec(hostname, service, Operation.START);
202   }
203 
204   @Override
205   public void stop(ServiceType service, String hostname) throws IOException {
206     exec(hostname, service, Operation.STOP);
207   }
208 
209   @Override
210   public void restart(ServiceType service, String hostname) throws IOException {
211     exec(hostname, service, Operation.RESTART);
212   }
213 
214   @Override
215   public void signal(ServiceType service, String signal, String hostname) throws IOException {
216     exec(hostname, getCommandProvider(service).signalCommand(service, signal));
217   }
218 
219   @Override
220   public boolean isRunning(ServiceType service, String hostname) throws IOException {
221     String ret = exec(hostname, getCommandProvider(service).isRunningCommand(service))
222         .getSecond();
223     return ret.length() > 0;
224   }
225 
226 }