1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.hadoop.hbase;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.Locale;
24 import java.util.Map;
25
26 import org.apache.commons.lang.StringUtils;
27 import org.apache.hadoop.hbase.classification.InterfaceAudience;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.hadoop.conf.Configuration;
31 import org.apache.hadoop.conf.Configured;
32 import org.apache.hadoop.hbase.HBaseClusterManager.CommandProvider.Operation;
33 import org.apache.hadoop.hbase.util.Pair;
34 import org.apache.hadoop.hbase.util.RetryCounter;
35 import org.apache.hadoop.hbase.util.RetryCounter.RetryConfig;
36 import org.apache.hadoop.hbase.util.RetryCounterFactory;
37 import org.apache.hadoop.util.Shell;
38
39
40
41
42
43
44
45
46 @InterfaceAudience.Private
47 public class HBaseClusterManager extends Configured implements ClusterManager {
48 private static final String SIGKILL = "SIGKILL";
49 private static final String SIGSTOP = "SIGSTOP";
50 private static final String SIGCONT = "SIGCONT";
51
52 protected static final Log LOG = LogFactory.getLog(HBaseClusterManager.class);
53 private String sshUserName;
54 private String sshOptions;
55
56
57
58
59
60
61 private static final String DEFAULT_TUNNEL_CMD =
62 "/usr/bin/ssh %1$s %2$s%3$s%4$s \"sudo -u %6$s %5$s\"";
63 private String tunnelCmd;
64
65 private static final String RETRY_ATTEMPTS_KEY = "hbase.it.clustermanager.retry.attempts";
66 private static final int DEFAULT_RETRY_ATTEMPTS = 5;
67
68 private static final String RETRY_SLEEP_INTERVAL_KEY = "hbase.it.clustermanager.retry.sleep.interval";
69 private static final int DEFAULT_RETRY_SLEEP_INTERVAL = 1000;
70
71 protected RetryCounterFactory retryCounterFactory;
72
73 @Override
74 public void setConf(Configuration conf) {
75 super.setConf(conf);
76 if (conf == null) {
77
78 return;
79 }
80 sshUserName = conf.get("hbase.it.clustermanager.ssh.user", "");
81 String extraSshOptions = conf.get("hbase.it.clustermanager.ssh.opts", "");
82 sshOptions = System.getenv("HBASE_SSH_OPTS");
83 if (!extraSshOptions.isEmpty()) {
84 sshOptions = StringUtils.join(new Object[] { sshOptions, extraSshOptions }, " ");
85 }
86 sshOptions = (sshOptions == null) ? "" : sshOptions;
87 sshUserName = (sshUserName == null) ? "" : sshUserName;
88 tunnelCmd = conf.get("hbase.it.clustermanager.ssh.cmd", DEFAULT_TUNNEL_CMD);
89
90 if ((sshUserName != null && sshUserName.length() > 0) ||
91 (sshOptions != null && sshOptions.length() > 0)) {
92 LOG.info("Running with SSH user [" + sshUserName + "] and options [" + sshOptions + "]");
93 }
94
95 this.retryCounterFactory = new RetryCounterFactory(new RetryConfig()
96 .setMaxAttempts(conf.getInt(RETRY_ATTEMPTS_KEY, DEFAULT_RETRY_ATTEMPTS))
97 .setSleepInterval(conf.getLong(RETRY_SLEEP_INTERVAL_KEY, DEFAULT_RETRY_SLEEP_INTERVAL)));
98 }
99
100 private String getServiceUser(ServiceType service) {
101 Configuration conf = getConf();
102 switch (service) {
103 case HADOOP_DATANODE:
104 return conf.get("hbase.it.clustermanager.hadoop.hdfs.user", "hdfs");
105 case ZOOKEEPER_SERVER:
106 return conf.get("hbase.it.clustermanager.zookeeper.user", "zookeeper");
107 default:
108 return conf.get("hbase.it.clustermanager.hbase.user", "hbase");
109 }
110 }
111
112
113
114
115 protected class RemoteShell extends Shell.ShellCommandExecutor {
116 private String hostname;
117 private String user;
118
119 public RemoteShell(String hostname, String[] execString, File dir, Map<String, String> env,
120 long timeout) {
121 super(execString, dir, env, timeout);
122 this.hostname = hostname;
123 }
124
125 public RemoteShell(String hostname, String[] execString, File dir, Map<String, String> env) {
126 super(execString, dir, env);
127 this.hostname = hostname;
128 }
129
130 public RemoteShell(String hostname, String[] execString, File dir) {
131 super(execString, dir);
132 this.hostname = hostname;
133 }
134
135 public RemoteShell(String hostname, String[] execString) {
136 super(execString);
137 this.hostname = hostname;
138 }
139
140 public RemoteShell(String hostname, String user, String[] execString) {
141 super(execString);
142 this.hostname = hostname;
143 this.user = user;
144 }
145
146 @Override
147 public String[] getExecString() {
148 String at = sshUserName.isEmpty() ? "" : "@";
149 String remoteCmd = StringUtils.join(super.getExecString(), " ");
150 String cmd = String.format(tunnelCmd, sshOptions, sshUserName, at, hostname, remoteCmd, user);
151 LOG.info("Executing full command [" + cmd + "]");
152 return new String[] { "/usr/bin/env", "bash", "-c", cmd };
153 }
154
155 @Override
156 public void execute() throws IOException {
157 super.execute();
158 }
159 }
160
161
162
163
164
165
166 static abstract class CommandProvider {
167
168 enum Operation {
169 START, STOP, RESTART
170 }
171
172 public abstract String getCommand(ServiceType service, Operation op);
173
174 public String isRunningCommand(ServiceType service) {
175 return findPidCommand(service);
176 }
177
178 protected String findPidCommand(ServiceType service) {
179 return String.format("ps ux | grep proc_%s | grep -v grep | tr -s ' ' | cut -d ' ' -f2",
180 service);
181 }
182
183 public String signalCommand(ServiceType service, String signal) {
184 return String.format("%s | xargs kill -s %s", findPidCommand(service), signal);
185 }
186 }
187
188
189
190
191 static class HBaseShellCommandProvider extends CommandProvider {
192 private final String hbaseHome;
193 private final String confDir;
194
195 HBaseShellCommandProvider(Configuration conf) {
196 hbaseHome = conf.get("hbase.it.clustermanager.hbase.home",
197 System.getenv("HBASE_HOME"));
198 String tmp = conf.get("hbase.it.clustermanager.hbase.conf.dir",
199 System.getenv("HBASE_CONF_DIR"));
200 if (tmp != null) {
201 confDir = String.format("--config %s", tmp);
202 } else {
203 confDir = "";
204 }
205 }
206
207 @Override
208 public String getCommand(ServiceType service, Operation op) {
209 return String.format("%s/bin/hbase-daemon.sh %s %s %s", hbaseHome, confDir,
210 op.toString().toLowerCase(Locale.ROOT), service);
211 }
212 }
213
214
215
216
217 static class HadoopShellCommandProvider extends CommandProvider {
218 private final String hadoopHome;
219 private final String confDir;
220
221 HadoopShellCommandProvider(Configuration conf) throws IOException {
222 hadoopHome = conf.get("hbase.it.clustermanager.hadoop.home",
223 System.getenv("HADOOP_HOME"));
224 String tmp = conf.get("hbase.it.clustermanager.hadoop.conf.dir",
225 System.getenv("HADOOP_CONF_DIR"));
226 if (hadoopHome == null) {
227 throw new IOException("Hadoop home configuration parameter i.e. " +
228 "'hbase.it.clustermanager.hadoop.home' is not configured properly.");
229 }
230 if (tmp != null) {
231 confDir = String.format("--config %s", tmp);
232 } else {
233 confDir = "";
234 }
235 }
236
237 @Override
238 public String getCommand(ServiceType service, Operation op) {
239 return String.format("%s/sbin/hadoop-daemon.sh %s %s %s", hadoopHome, confDir,
240 op.toString().toLowerCase(Locale.ROOT), service);
241 }
242 }
243
244
245
246
247 static class ZookeeperShellCommandProvider extends CommandProvider {
248 private final String zookeeperHome;
249 private final String confDir;
250
251 ZookeeperShellCommandProvider(Configuration conf) throws IOException {
252 zookeeperHome = conf.get("hbase.it.clustermanager.zookeeper.home",
253 System.getenv("ZOOBINDIR"));
254 String tmp = conf.get("hbase.it.clustermanager.zookeeper.conf.dir",
255 System.getenv("ZOOCFGDIR"));
256 if (zookeeperHome == null) {
257 throw new IOException("Zookeeper home configuration parameter i.e. " +
258 "'hbase.it.clustermanager.zookeeper.home' is not configured properly.");
259 }
260 if (tmp != null) {
261 confDir = String.format("--config %s", tmp);
262 } else {
263 confDir = "";
264 }
265 }
266
267 @Override
268 public String getCommand(ServiceType service, Operation op) {
269 return String.format("%s/bin/zkServer.sh %s", zookeeperHome, op.toString().toLowerCase(Locale.ROOT));
270 }
271
272 @Override
273 protected String findPidCommand(ServiceType service) {
274 return String.format("ps ux | grep %s | grep -v grep | tr -s ' ' | cut -d ' ' -f2",
275 service);
276 }
277 }
278
279 public HBaseClusterManager() {
280 }
281
282 protected CommandProvider getCommandProvider(ServiceType service) throws IOException {
283 switch (service) {
284 case HADOOP_DATANODE:
285 return new HadoopShellCommandProvider(getConf());
286 case ZOOKEEPER_SERVER:
287 return new ZookeeperShellCommandProvider(getConf());
288 default:
289 return new HBaseShellCommandProvider(getConf());
290 }
291 }
292
293
294
295
296
297
298 private Pair<Integer, String> exec(String hostname, ServiceType service, String... cmd)
299 throws IOException {
300 LOG.info("Executing remote command: " + StringUtils.join(cmd, " ") + " , hostname:" + hostname);
301
302 RemoteShell shell = new RemoteShell(hostname, getServiceUser(service), cmd);
303 try {
304 shell.execute();
305 } catch (Shell.ExitCodeException ex) {
306
307 String output = shell.getOutput();
308
309 throw new Shell.ExitCodeException(ex.getExitCode(), "stderr: " + ex.getMessage()
310 + ", stdout: " + output);
311 }
312
313 LOG.info("Executed remote command, exit code:" + shell.getExitCode()
314 + " , output:" + shell.getOutput());
315
316 return new Pair<Integer, String>(shell.getExitCode(), shell.getOutput());
317 }
318
319 private Pair<Integer, String> execWithRetries(String hostname, ServiceType service, String... cmd)
320 throws IOException {
321 RetryCounter retryCounter = retryCounterFactory.create();
322 while (true) {
323 try {
324 return exec(hostname, service, cmd);
325 } catch (IOException e) {
326 retryOrThrow(retryCounter, e, hostname, cmd);
327 }
328 try {
329 retryCounter.sleepUntilNextRetry();
330 } catch (InterruptedException ex) {
331
332 LOG.warn("Sleep Interrupted:" + ex);
333 }
334 }
335 }
336
337 private <E extends Exception> void retryOrThrow(RetryCounter retryCounter, E ex,
338 String hostname, String[] cmd) throws E {
339 if (retryCounter.shouldRetry()) {
340 LOG.warn("Remote command: " + StringUtils.join(cmd, " ") + " , hostname:" + hostname
341 + " failed at attempt " + retryCounter.getAttemptTimes() + ". Retrying until maxAttempts: "
342 + retryCounter.getMaxAttempts() + ". Exception: " + ex.getMessage());
343 return;
344 }
345 throw ex;
346 }
347
348 private void exec(String hostname, ServiceType service, Operation op) throws IOException {
349 execWithRetries(hostname, service, getCommandProvider(service).getCommand(service, op));
350 }
351
352 @Override
353 public void start(ServiceType service, String hostname, int port) throws IOException {
354 exec(hostname, service, Operation.START);
355 }
356
357 @Override
358 public void stop(ServiceType service, String hostname, int port) throws IOException {
359 exec(hostname, service, Operation.STOP);
360 }
361
362 @Override
363 public void restart(ServiceType service, String hostname, int port) throws IOException {
364 exec(hostname, service, Operation.RESTART);
365 }
366
367 public void signal(ServiceType service, String signal, String hostname) throws IOException {
368 execWithRetries(hostname, service, getCommandProvider(service).signalCommand(service, signal));
369 }
370
371 @Override
372 public boolean isRunning(ServiceType service, String hostname, int port) throws IOException {
373 String ret = execWithRetries(hostname, service,
374 getCommandProvider(service).isRunningCommand(service)).getSecond();
375 return ret.length() > 0;
376 }
377
378 @Override
379 public void kill(ServiceType service, String hostname, int port) throws IOException {
380 signal(service, SIGKILL, hostname);
381 }
382
383 @Override
384 public void suspend(ServiceType service, String hostname, int port) throws IOException {
385 signal(service, SIGSTOP, hostname);
386 }
387
388 @Override
389 public void resume(ServiceType service, String hostname, int port) throws IOException {
390 signal(service, SIGCONT, hostname);
391 }
392 }