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.hbase.HBaseClusterManager.CommandProvider.Operation;
28  import org.apache.hadoop.hbase.util.Pair;
29  import org.apache.hadoop.util.Shell;
30  
31  /**
32   * A default cluster manager for HBase. Uses SSH, and hbase shell scripts
33   * to manage the cluster. Assumes Unix-like commands are available like 'ps',
34   * 'kill', etc. Also assumes the user running the test has enough "power" to start & stop
35   * servers on the remote machines (for example, the test user could be the same user as the
36   * user the daemon isrunning as)
37   */
38  @InterfaceAudience.Private
39  public class HBaseClusterManager extends ClusterManager {
40  
41    /**
42     * Executes commands over SSH
43     */
44    static class RemoteShell extends Shell.ShellCommandExecutor {
45  
46      private String hostname;
47  
48      private String sshCmd = "/usr/bin/ssh";
49      private String sshOptions = System.getenv("HBASE_SSH_OPTS"); //from conf/hbase-env.sh
50  
51      public RemoteShell(String hostname, String[] execString, File dir, Map<String, String> env,
52          long timeout) {
53        super(execString, dir, env, timeout);
54        this.hostname = hostname;
55      }
56  
57      public RemoteShell(String hostname, String[] execString, File dir, Map<String, String> env) {
58        super(execString, dir, env);
59        this.hostname = hostname;
60      }
61  
62      public RemoteShell(String hostname, String[] execString, File dir) {
63        super(execString, dir);
64        this.hostname = hostname;
65      }
66  
67      public RemoteShell(String hostname, String[] execString) {
68        super(execString);
69        this.hostname = hostname;
70      }
71  
72      public String[] getExecString() {
73        return new String[] {
74            "bash", "-c",
75            StringUtils.join(new String[] { sshCmd,
76                sshOptions == null ? "" : sshOptions,
77                hostname,
78                "\"" + StringUtils.join(super.getExecString(), " ") + "\""
79            }, " ")};
80      }
81  
82      @Override
83      public void execute() throws IOException {
84        super.execute();
85      }
86  
87      public void setSshCmd(String sshCmd) {
88        this.sshCmd = sshCmd;
89      }
90  
91      public void setSshOptions(String sshOptions) {
92        this.sshOptions = sshOptions;
93      }
94  
95      public String getSshCmd() {
96        return sshCmd;
97      }
98  
99      public String getSshOptions() {
100       return sshOptions;
101     }
102   }
103 
104   /**
105    * Provides command strings for services to be executed by Shell. CommandProviders are
106    * pluggable, and different deployments(windows, bigtop, etc) can be managed by
107    * plugging-in custom CommandProvider's or ClusterManager's.
108    */
109   static abstract class CommandProvider {
110 
111     enum Operation {
112       START, STOP, RESTART
113     }
114 
115     public abstract String getCommand(ServiceType service, Operation op);
116 
117     public String isRunningCommand(ServiceType service) {
118       return findPidCommand(service);
119     }
120 
121     protected String findPidCommand(ServiceType service) {
122       return String.format("ps aux | grep %s | grep -v grep | tr -s ' ' | cut -d ' ' -f2",
123           service);
124     }
125 
126     public String signalCommand(ServiceType service, String signal) {
127       return String.format("%s | xargs kill -s %s", findPidCommand(service), signal);
128     }
129   }
130 
131   /**
132    * CommandProvider to manage the service using bin/hbase-* scripts
133    */
134   static class HBaseShellCommandProvider extends CommandProvider {
135     private String getHBaseHome() {
136       return System.getenv("HBASE_HOME");
137     }
138 
139     private String getConfig() {
140       String confDir = System.getenv("HBASE_CONF_DIR");
141       if (confDir != null) {
142         return String.format("--config %s", confDir);
143       }
144       return "";
145     }
146 
147     @Override
148     public String getCommand(ServiceType service, Operation op) {
149       return String.format("%s/bin/hbase-daemon.sh %s %s %s", getHBaseHome(), getConfig(),
150           op.toString().toLowerCase(), service);
151     }
152   }
153 
154   public HBaseClusterManager() {
155     super();
156   }
157 
158   protected CommandProvider getCommandProvider(ServiceType service) {
159     //TODO: make it pluggable, or auto-detect the best command provider, should work with
160     //hadoop daemons as well
161     return new HBaseShellCommandProvider();
162   }
163 
164   /**
165    * Execute the given command on the host using SSH
166    * @return pair of exit code and command output
167    * @throws IOException if something goes wrong.
168    */
169   private Pair<Integer, String> exec(String hostname, String... cmd) throws IOException {
170     LOG.info("Executing remote command: " + StringUtils.join(cmd, " ") + " , hostname:" + hostname);
171 
172     RemoteShell shell = new RemoteShell(hostname, cmd);
173     shell.execute();
174 
175     LOG.info("Executed remote command, exit code:" + shell.getExitCode()
176         + " , output:" + shell.getOutput());
177 
178     return new Pair<Integer, String>(shell.getExitCode(), shell.getOutput());
179   }
180 
181   private void exec(String hostname, ServiceType service, Operation op) throws IOException {
182     exec(hostname, getCommandProvider(service).getCommand(service, op));
183   }
184 
185   @Override
186   public void start(ServiceType service, String hostname) throws IOException {
187     exec(hostname, service, Operation.START);
188   }
189 
190   @Override
191   public void stop(ServiceType service, String hostname) throws IOException {
192     exec(hostname, service, Operation.STOP);
193   }
194 
195   @Override
196   public void restart(ServiceType service, String hostname) throws IOException {
197     exec(hostname, service, Operation.RESTART);
198   }
199 
200   @Override
201   public void signal(ServiceType service, String signal, String hostname) throws IOException {
202     exec(hostname, getCommandProvider(service).signalCommand(service, signal));
203   }
204 
205   @Override
206   public boolean isRunning(ServiceType service, String hostname) throws IOException {
207     String ret = exec(hostname, getCommandProvider(service).isRunningCommand(service))
208         .getSecond();
209     return ret.length() > 0;
210   }
211 
212 }