blob: fafd409f7a0f9239a645fbf3b6001fb654b4182a [file] [log] [blame]
mblighdcd57a82007-07-11 23:06:47 +00001#!/usr/bin/python
2#
3# Copyright 2007 Google Inc. Released under the GPL v2
4
mbligh7d2bde82007-08-02 16:26:10 +00005"""
6This module defines the SSHHost class.
mblighdcd57a82007-07-11 23:06:47 +00007
8Implementation details:
9You should import the "hosts" package instead of importing each type of host.
10
11 SSHHost: a remote machine with a ssh access
12"""
13
mbligh7d2bde82007-08-02 16:26:10 +000014__author__ = """
15mbligh@google.com (Martin J. Bligh),
mblighdcd57a82007-07-11 23:06:47 +000016poirier@google.com (Benjamin Poirier),
mbligh7d2bde82007-08-02 16:26:10 +000017stutsman@google.com (Ryan Stutsman)
18"""
mblighdcd57a82007-07-11 23:06:47 +000019
20
mblighf5427bb2008-04-09 15:55:57 +000021import types, os, sys, signal, subprocess, time, re, socket, pdb
22
23from autotest_lib.client.common_lib import error
24from autotest_lib.server import utils
25import remote, bootloader
mblighdcd57a82007-07-11 23:06:47 +000026
27
mblighf5427bb2008-04-09 15:55:57 +000028class SSHHost(remote.RemoteHost):
mbligh7d2bde82007-08-02 16:26:10 +000029 """
30 This class represents a remote machine controlled through an ssh
mblighdcd57a82007-07-11 23:06:47 +000031 session on which you can run programs.
mbligh7d2bde82007-08-02 16:26:10 +000032
mblighdcd57a82007-07-11 23:06:47 +000033 It is not the machine autoserv is running on. The machine must be
34 configured for password-less login, for example through public key
35 authentication.
mbligh7d2bde82007-08-02 16:26:10 +000036
mbligh3409ee72007-10-16 23:58:33 +000037 It includes support for controlling the machine through a serial
38 console on which you can run programs. If such a serial console is
39 set up on the machine then capabilities such as hard reset and
40 boot strap monitoring are available. If the machine does not have a
41 serial console available then ordinary SSH-based commands will
42 still be available, but attempts to use extensions such as
43 console logging or hard reset will fail silently.
44
mblighdcd57a82007-07-11 23:06:47 +000045 Implementation details:
46 This is a leaf class in an abstract class hierarchy, it must
47 implement the unimplemented methods in parent classes.
48 """
mbligh7d2bde82007-08-02 16:26:10 +000049
mbligh31a49de2007-11-05 18:41:19 +000050 DEFAULT_REBOOT_TIMEOUT = 1800
51 job = None
mbligh0faf91f2007-10-18 03:10:48 +000052
mblighde384372007-10-17 04:25:37 +000053 def __init__(self, hostname, user="root", port=22, initialize=True,
mblighf4e04152008-02-21 16:05:53 +000054 conmux_log="console.log",
mblighe6c995f2007-10-26 19:43:01 +000055 conmux_server=None, conmux_attach=None,
mblighda13d542008-01-03 16:28:34 +000056 netconsole_log=None, netconsole_port=6666, autodir=None):
mbligh7d2bde82007-08-02 16:26:10 +000057 """
58 Construct a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000059
60 Args:
61 hostname: network hostname or address of remote machine
62 user: user to log in as on the remote machine
63 port: port the ssh daemon is listening on on the remote
64 machine
mbligh9708f732007-10-18 03:18:54 +000065 """
mblighdcd57a82007-07-11 23:06:47 +000066 self.hostname= hostname
67 self.user= user
68 self.port= port
69 self.tmp_dirs= []
mbligh137a05c2007-10-04 15:56:51 +000070 self.initialize = initialize
mblighda13d542008-01-03 16:28:34 +000071 self.autodir = autodir
mbligh91334902007-09-28 01:47:59 +000072
mbligh9708f732007-10-18 03:18:54 +000073 super(SSHHost, self).__init__()
74
mbligh3409ee72007-10-16 23:58:33 +000075 self.conmux_server = conmux_server
mbligh70cf0ec2008-01-18 17:57:14 +000076 if conmux_attach:
77 self.conmux_attach = conmux_attach
78 else:
79 self.conmux_attach = os.path.abspath(os.path.join(
80 self.serverdir, '..',
81 'conmux', 'conmux-attach'))
mblighfbb03542008-02-11 16:27:29 +000082 self.logger_popen = None
mblighf4e04152008-02-21 16:05:53 +000083 self.warning_stream = None
mblighde384372007-10-17 04:25:37 +000084 self.__start_console_log(conmux_log)
mbligh3409ee72007-10-16 23:58:33 +000085
mbligha0452c82007-08-08 20:24:57 +000086 self.bootloader = bootloader.Bootloader(self)
mbligh7d2bde82007-08-02 16:26:10 +000087
mblighc0e92392007-11-05 19:10:10 +000088 self.__netconsole_param = ""
mblighfbb03542008-02-11 16:27:29 +000089 self.netlogger_popen = None
mblighc0e92392007-11-05 19:10:10 +000090 if netconsole_log:
91 self.__init_netconsole_params(netconsole_port)
92 self.__start_netconsole_log(netconsole_log, netconsole_port)
93 self.__load_netconsole_module()
mblighde384372007-10-17 04:25:37 +000094
mbligh7d2bde82007-08-02 16:26:10 +000095
mblighfbb03542008-02-11 16:27:29 +000096 @staticmethod
mblighf4e04152008-02-21 16:05:53 +000097 def __kill(popen):
mblighfbb03542008-02-11 16:27:29 +000098 return_code = popen.poll()
mblighf4e04152008-02-21 16:05:53 +000099 if return_code is None:
mblighfbb03542008-02-11 16:27:29 +0000100 try:
mblighf4e04152008-02-21 16:05:53 +0000101 os.kill(popen.pid, signal.SIGTERM)
mblighfbb03542008-02-11 16:27:29 +0000102 except OSError:
103 pass
104
105
mblighdcd57a82007-07-11 23:06:47 +0000106 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +0000107 """
108 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +0000109 """
110 for dir in self.tmp_dirs:
111 try:
112 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
mblighf5427bb2008-04-09 15:55:57 +0000113 except error.AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000114 pass
mblighde384372007-10-17 04:25:37 +0000115 # kill the console logger
mblighfbb03542008-02-11 16:27:29 +0000116 if getattr(self, 'logger_popen', None):
mblighf4e04152008-02-21 16:05:53 +0000117 self.__kill(self.logger_popen)
mbligh6607d192008-04-17 15:23:15 +0000118 if self.job:
119 self.job.warning_loggers.discard(
120 self.warning_stream)
mblighf4e04152008-02-21 16:05:53 +0000121 self.warning_stream.close()
mblighde384372007-10-17 04:25:37 +0000122 # kill the netconsole logger
mblighfbb03542008-02-11 16:27:29 +0000123 if getattr(self, 'netlogger_popen', None):
mblighe6c995f2007-10-26 19:43:01 +0000124 self.__unload_netconsole_module()
mblighf4e04152008-02-21 16:05:53 +0000125 self.__kill(self.netlogger_popen)
mblighde384372007-10-17 04:25:37 +0000126
127
128 def __init_netconsole_params(self, port):
129 """
130 Connect to the remote machine and determine the values to use for the
131 required netconsole parameters.
132 """
mblighde384372007-10-17 04:25:37 +0000133 # PROBLEM: on machines with multiple IPs this may not make any sense
134 # It also doesn't work with IPv6
135 remote_ip = socket.gethostbyname(self.hostname)
136 local_ip = socket.gethostbyname(socket.gethostname())
137 # Get the gateway of the remote machine
138 try:
139 traceroute = self.run('traceroute -n %s' % local_ip)
mblighf5427bb2008-04-09 15:55:57 +0000140 except error.AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000141 return
142 first_node = traceroute.stdout.split("\n")[0]
143 match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
144 if match:
145 router_ip = match.group(1)
146 else:
147 return
148 # Look up the MAC address of the gateway
149 try:
150 self.run('ping -c 1 %s' % router_ip)
151 arp = self.run('arp -n -a %s' % router_ip)
mblighf5427bb2008-04-09 15:55:57 +0000152 except error.AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000153 return
154 match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
155 if match:
156 gateway_mac = match.group(1)
157 else:
158 return
159 self.__netconsole_param = 'netconsole=@%s/,%s@%s/%s' % (remote_ip,
160 port,
161 local_ip,
162 gateway_mac)
163
164
165 def __start_netconsole_log(self, logfilename, port):
166 """
167 Log the output of netconsole to a specified file
168 """
169 if logfilename == None:
170 return
171 cmd = ['nc', '-u', '-l', '-p', str(port)]
mblighfbb03542008-02-11 16:27:29 +0000172 logfile = open(logfilename, 'a', 0)
173 self.netlogger_popen = subprocess.Popen(cmd, stdout=logfile)
mblighde384372007-10-17 04:25:37 +0000174
175
176 def __load_netconsole_module(self):
177 """
178 Make a best effort to load the netconsole module.
179
180 Note that loading the module can fail even when the remote machine is
181 working correctly if netconsole is already compiled into the kernel
182 and started.
183 """
mblighc0e92392007-11-05 19:10:10 +0000184 if not self.__netconsole_param:
185 return
mblighde384372007-10-17 04:25:37 +0000186 try:
187 self.run('modprobe netconsole %s' % self.__netconsole_param)
mblighf5427bb2008-04-09 15:55:57 +0000188 except error.AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000189 # if it fails there isn't much we can do, just keep going
190 pass
191
192
193 def __unload_netconsole_module(self):
194 try:
195 self.run('modprobe -r netconsole')
mblighf5427bb2008-04-09 15:55:57 +0000196 except error.AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000197 pass
mbligh3409ee72007-10-16 23:58:33 +0000198
199
mbligh5deff3d2008-01-04 21:21:28 +0000200 def wait_for_restart(self, timeout=DEFAULT_REBOOT_TIMEOUT):
mblighd567f722007-10-30 15:37:33 +0000201 if not self.wait_down(300): # Make sure he's dead, Jim
mblighf3b78932007-11-07 16:52:47 +0000202 self.__record("ABORT", None, "reboot.verify", "shutdown failed")
mblighf5427bb2008-04-09 15:55:57 +0000203 raise error.AutoservRebootError(
204 "Host did not shut down")
mbligh3409ee72007-10-16 23:58:33 +0000205 self.wait_up(timeout)
206 time.sleep(2) # this is needed for complete reliability
mblighcf3d83a2007-11-05 19:21:39 +0000207 if self.wait_up(timeout):
mbligh30270302007-11-05 20:33:52 +0000208 self.__record("GOOD", None, "reboot.verify")
mblighcf3d83a2007-11-05 19:21:39 +0000209 else:
mbligh71d24222008-03-11 21:31:56 +0000210 self.__record("ABORT", None, "reboot.verify", "Host did not return from reboot")
mblighf5427bb2008-04-09 15:55:57 +0000211 raise error.AutoservRebootError(
212 "Host did not return from reboot")
mbligh3409ee72007-10-16 23:58:33 +0000213 print "Reboot complete"
214
215
mbligh80d20772007-10-29 17:10:10 +0000216 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000217 """
218 Reach out and slap the box in the power switch
219 """
mblighf3b78932007-11-07 16:52:47 +0000220 if not self.__console_run(r"'~$hardreset'"):
221 self.__record("ABORT", None, "reboot.start", "hard reset unavailable")
mblighf5427bb2008-04-09 15:55:57 +0000222 raise error.AutoservUnsupportedError(
223 'Hard reset unavailable')
mbligh37d53c32008-01-14 16:16:00 +0000224
225 if wait:
226 self.wait_for_restart(timeout)
mbligha4d4f372008-01-22 15:49:50 +0000227 self.__record("GOOD", None, "reboot.start", "hard reset")
mbligh3409ee72007-10-16 23:58:33 +0000228
229
mblighe6c995f2007-10-26 19:43:01 +0000230 def __conmux_hostname(self):
231 if self.conmux_server:
232 return '%s/%s' % (self.conmux_server, self.hostname)
233 else:
234 return self.hostname
235
236
mbligh3409ee72007-10-16 23:58:33 +0000237 def __start_console_log(self, logfilename):
238 """
239 Log the output of the console session to a specified file
240 """
241 if logfilename == None:
242 return
243 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
244 return
mblighf4e04152008-02-21 16:05:53 +0000245
246 r, w = os.pipe()
247 script_path = os.path.join(self.serverdir,
248 'warning_monitor.py')
mblighfbb03542008-02-11 16:27:29 +0000249 cmd = [self.conmux_attach, self.__conmux_hostname(),
mblighf4e04152008-02-21 16:05:53 +0000250 '%s %s %s %d' % (sys.executable, script_path,
251 logfilename, w)]
mbligh0c5ce312008-02-21 16:24:11 +0000252 dev_null = open(os.devnull, 'w')
mbligh3409ee72007-10-16 23:58:33 +0000253
mblighf4e04152008-02-21 16:05:53 +0000254 self.warning_stream = os.fdopen(r, 'r', 0)
mbligh6607d192008-04-17 15:23:15 +0000255 if self.job:
256 self.job.warning_loggers.add(self.warning_stream)
mblighf4e04152008-02-21 16:05:53 +0000257 self.logger_popen = subprocess.Popen(cmd, stderr=dev_null)
258 os.close(w)
mblighe6c995f2007-10-26 19:43:01 +0000259
260
mbligh3409ee72007-10-16 23:58:33 +0000261 def __console_run(self, cmd):
262 """
263 Send a command to the conmux session
264 """
265 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
266 return False
mbligh3409ee72007-10-16 23:58:33 +0000267 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000268 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000269 cmd)
mbligh0f5ad642008-01-22 16:37:40 +0000270 result = utils.system(cmd, ignore_status=True)
mbligh3409ee72007-10-16 23:58:33 +0000271 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000272
273
mbligh31a49de2007-11-05 18:41:19 +0000274 def __record(self, status_code, subdir, operation, status = ''):
275 if self.job:
276 self.job.record(status_code, subdir, operation, status)
277 else:
278 if not subdir:
279 subdir = "----"
280 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
281 sys.stderr.write(msg + "\n")
282
283
mblighfa971602008-01-03 01:57:20 +0000284 def ssh_base_command(self, connect_timeout=30):
285 SSH_BASE_COMMAND = '/usr/bin/ssh -a -x -o ' + \
mbligh0ad21ba2008-03-14 15:06:21 +0000286 'BatchMode=yes -o ConnectTimeout=%d ' + \
287 '-o ServerAliveInterval=300'
mblighfa971602008-01-03 01:57:20 +0000288 assert isinstance(connect_timeout, (int, long))
289 assert connect_timeout > 0 # can't disable the timeout
290 return SSH_BASE_COMMAND % connect_timeout
291
292
293 def ssh_command(self, connect_timeout=30):
mblighe6647d12007-10-17 00:00:01 +0000294 """Construct an ssh command with proper args for this host."""
mblighfa971602008-01-03 01:57:20 +0000295 ssh = self.ssh_base_command(connect_timeout)
296 return r'%s -l %s -p %d %s' % (ssh,
mbligh0faf91f2007-10-18 03:10:48 +0000297 self.user,
298 self.port,
299 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000300
301
mbligh07a923f2008-01-16 17:49:04 +0000302 def run(self, command, timeout=3600, ignore_status=False,
mblighfa971602008-01-03 01:57:20 +0000303 stdout_tee=None, stderr_tee=None, connect_timeout=30):
mbligh7d2bde82007-08-02 16:26:10 +0000304 """
305 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000306
307 Args:
308 command: the command line string
309 timeout: time limit in seconds before attempting to
310 kill the running process. The run() function
311 will take a few seconds longer than 'timeout'
312 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000313 ignore_status: do not raise an exception, no matter
314 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000315
316 Returns:
317 a hosts.base_classes.CmdResult object
318
319 Raises:
320 AutoservRunError: the exit code of the command
321 execution was not 0
mblighcaa62c22008-04-07 21:51:17 +0000322 AutoservSSHTimeout: ssh connection has timed out
mblighdcd57a82007-07-11 23:06:47 +0000323 """
mblighadf2aab2007-11-29 18:16:43 +0000324 stdout = stdout_tee or sys.stdout
mbligh8d4baaa2008-03-12 14:48:24 +0000325 stderr = stderr_tee or sys.stdout
mbligh7995cc62007-11-30 15:53:23 +0000326 print "ssh: %s" % (command,)
mblighadf2aab2007-11-29 18:16:43 +0000327 env = " ".join("=".join(pair) for pair in self.env.iteritems())
mbligh34faa282008-01-16 17:44:49 +0000328 full_cmd = '%s "%s %s"' % (self.ssh_command(connect_timeout),
329 env, utils.sh_escape(command))
mbligh8ea61e22008-05-09 18:09:37 +0000330 try:
331 result = utils.run(full_cmd, timeout, True, stdout, stderr)
332 # we get a CmdError here only if there is timeout of that command.
333 # Catch that and stuff it into AutoservRunError and raise it.
334 except error.CmdError, cmderr:
335 raise error.AutoservRunError(cmderr.args[0], cmderr.args[1])
336
mbligh34faa282008-01-16 17:44:49 +0000337 if result.exit_status == 255: # ssh's exit status for timeout
338 if re.match(r'^ssh: connect to host .* port .*: ' +
339 r'Connection timed out\r$', result.stderr):
mblighf5427bb2008-04-09 15:55:57 +0000340 raise error.AutoservSSHTimeout("ssh timed out",
341 result)
mbligh34faa282008-01-16 17:44:49 +0000342 if not ignore_status and result.exit_status > 0:
mblighf5427bb2008-04-09 15:55:57 +0000343 raise error.AutoservRunError("command execution error",
344 result)
mblighdcd57a82007-07-11 23:06:47 +0000345 return result
mbligh7d2bde82007-08-02 16:26:10 +0000346
347
mblighbda9c9c2008-04-08 17:45:00 +0000348 def run_short(self, command, **kwargs):
349 """
350 Calls the run() command with a short default timeout.
351
352 Args:
353 Takes the same arguments as does run(),
354 with the exception of the timeout argument which
355 here is fixed at 60 seconds.
356 It returns the result of run.
357 """
358 return self.run(command, timeout=60, **kwargs)
359
360
mbligh78669ff2008-01-10 16:33:07 +0000361 def run_grep(self, command, timeout=30, ignore_status=False,
362 stdout_ok_regexp=None, stdout_err_regexp=None,
363 stderr_ok_regexp=None, stderr_err_regexp=None,
364 connect_timeout=30):
365 """
366 Run a command on the remote host and look for regexp
367 in stdout or stderr to determine if the command was
368 successul or not.
mbligh6a2a2df2008-01-16 17:41:55 +0000369
mbligh78669ff2008-01-10 16:33:07 +0000370 Args:
371 command: the command line string
mbligh6a2a2df2008-01-16 17:41:55 +0000372 timeout: time limit in seconds before attempting to
mbligh78669ff2008-01-10 16:33:07 +0000373 kill the running process. The run() function
374 will take a few seconds longer than 'timeout'
375 to complete if it has to kill the process.
mbligh6a2a2df2008-01-16 17:41:55 +0000376 ignore_status: do not raise an exception, no matter
mbligh78669ff2008-01-10 16:33:07 +0000377 what the exit code of the command is.
378 stdout_ok_regexp: regexp that should be in stdout
379 if the command was successul.
380 stdout_err_regexp: regexp that should be in stdout
381 if the command failed.
382 stderr_ok_regexp: regexp that should be in stderr
383 if the command was successul.
384 stderr_err_regexp: regexp that should be in stderr
385 if the command failed.
mbligh6a2a2df2008-01-16 17:41:55 +0000386
mbligh78669ff2008-01-10 16:33:07 +0000387 Returns:
388 if the command was successul, raises an exception
389 otherwise.
mbligh6a2a2df2008-01-16 17:41:55 +0000390
mbligh78669ff2008-01-10 16:33:07 +0000391 Raises:
392 AutoservRunError:
393 - the exit code of the command execution was not 0.
mbligh6a2a2df2008-01-16 17:41:55 +0000394 - If stderr_err_regexp is found in stderr,
395 - If stdout_err_regexp is found in stdout,
mbligh78669ff2008-01-10 16:33:07 +0000396 - If stderr_ok_regexp is not found in stderr.
397 - If stdout_ok_regexp is not found in stdout,
398 """
399
400 # We ignore the status, because we will handle it at the end.
401 result = self.run(command, timeout, ignore_status=True,
mbligh6a2a2df2008-01-16 17:41:55 +0000402 connect_timeout=connect_timeout)
mbligh78669ff2008-01-10 16:33:07 +0000403
404 # Look for the patterns, in order
405 for (regexp, stream) in ((stderr_err_regexp, result.stderr),
mbligh6a2a2df2008-01-16 17:41:55 +0000406 (stdout_err_regexp, result.stdout)):
mbligh78669ff2008-01-10 16:33:07 +0000407 if regexp and stream:
408 err_re = re.compile (regexp)
409 if err_re.search(stream):
mblighf5427bb2008-04-09 15:55:57 +0000410 raise error.AutoservRunError(
mbligh6a2a2df2008-01-16 17:41:55 +0000411 '%s failed, found error pattern: '
412 '"%s"' % (command, regexp), result)
mbligh78669ff2008-01-10 16:33:07 +0000413
414 for (regexp, stream) in ((stderr_ok_regexp, result.stderr),
mbligh6a2a2df2008-01-16 17:41:55 +0000415 (stdout_ok_regexp, result.stdout)):
mbligh78669ff2008-01-10 16:33:07 +0000416 if regexp and stream:
417 ok_re = re.compile (regexp)
418 if ok_re.search(stream):
419 if ok_re.search(stream):
420 return
421
422 if not ignore_status and result.exit_status > 0:
mblighf5427bb2008-04-09 15:55:57 +0000423 raise error.AutoservRunError("command execution error",
424 result)
mbligh78669ff2008-01-10 16:33:07 +0000425
426
mbligh80d20772007-10-29 17:10:10 +0000427 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
428 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000429 """
430 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000431
mbligha0452c82007-08-08 20:24:57 +0000432 Args:
433 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000434 """
mbligh33ae0902007-11-24 19:27:08 +0000435 self.reboot_setup()
436
mblighde384372007-10-17 04:25:37 +0000437 # forcibly include the "netconsole" kernel arg
438 if self.__netconsole_param:
439 if kernel_args is None:
440 kernel_args = self.__netconsole_param
441 else:
442 kernel_args += " " + self.__netconsole_param
443 # unload the (possibly loaded) module to avoid shutdown issues
444 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000445 if label or kernel_args:
446 self.bootloader.install_boottool()
447 if label:
448 self.bootloader.set_default(label)
449 if kernel_args:
450 if not label:
451 default = int(self.bootloader.get_default())
452 label = self.bootloader.get_titles()[default]
453 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000454 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000455 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000456 try:
mblighf3b78932007-11-07 16:52:47 +0000457 self.run('(sleep 5; reboot) </dev/null >/dev/null 2>&1 &')
mblighf5427bb2008-04-09 15:55:57 +0000458 except error.AutoservRunError:
mblighf3b78932007-11-07 16:52:47 +0000459 self.__record("ABORT", None, "reboot.start",
460 "reboot command failed")
mblighcf3d83a2007-11-05 19:21:39 +0000461 raise
mbligha0452c82007-08-08 20:24:57 +0000462 if wait:
mbligh7f2befb2008-04-21 20:47:10 +0000463 self.wait_for_restart(timeout)
464 self.reboot_followup()
465
466
467 def reboot_followup(self):
468 super(SSHHost, self).reboot_followup()
469 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000470
mbligh7d2bde82007-08-02 16:26:10 +0000471
mblighcfc7ab32008-01-25 16:35:28 +0000472 def __copy_files(self, sources, dest):
473 """
474 Copy files from one machine to another.
475
476 This is for internal use by other methods that intend to move
477 files between machines. It expects a list of source files and
478 a destination (a filename if the source is a single file, a
479 destination otherwise). The names must already be
480 pre-processed into the appropriate rsync/scp friendly
481 format (%s@%s:%s).
482 """
483 # wait until there are only a small number of copies running
484 # before starting this one
485 MAXIMUM_SIMULTANEOUS_COPIES = 4
486 while True:
487 copy_count = 0
488 procs = utils.system_output('ps -ef')
489 for line in procs:
490 if 'rsync ' in line or 'scp ' in line:
491 copy_count += 1
492 if copy_count < MAXIMUM_SIMULTANEOUS_COPIES:
493 break
494 time.sleep(60)
495
mbligh22fdf172008-04-07 18:34:56 +0000496 print '__copy_files: copying %s to %s' % (sources, dest)
mblighcfc7ab32008-01-25 16:35:28 +0000497 try:
498 utils.run('rsync --rsh="%s" -az %s %s' % (
499 self.ssh_base_command(), ' '.join(sources), dest))
500 except Exception:
501 utils.run('scp -rpq %s "%s"' % (
502 ' '.join(sources), dest))
503
504
mblighdcd57a82007-07-11 23:06:47 +0000505 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000506 """
507 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000508
509 Directories will be copied recursively.
510 If a source component is a directory with a trailing slash,
511 the content of the directory will be copied, otherwise, the
512 directory itself and its content will be copied. This
513 behavior is similar to that of the program 'rsync'.
514
515 Args:
516 source: either
517 1) a single file or directory, as a string
518 2) a list of one or more (possibly mixed)
519 files or directories
520 dest: a file or a directory (if source contains a
521 directory or more than one element, you must
522 supply a directory dest)
523
524 Raises:
525 AutoservRunError: the scp command failed
526 """
527 if isinstance(source, types.StringTypes):
528 source= [source]
529
530 processed_source= []
531 for entry in source:
532 if entry.endswith('/'):
533 format_string= '%s@%s:"%s*"'
534 else:
535 format_string= '%s@%s:"%s"'
536 entry= format_string % (self.user, self.hostname,
537 utils.scp_remote_escape(entry))
538 processed_source.append(entry)
539
540 processed_dest= os.path.abspath(dest)
541 if os.path.isdir(dest):
542 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
543 else:
544 processed_dest= utils.sh_escape(processed_dest)
mblighcfc7ab32008-01-25 16:35:28 +0000545
546 self.__copy_files(processed_source, processed_dest)
mbligh7d2bde82007-08-02 16:26:10 +0000547
548
mblighdcd57a82007-07-11 23:06:47 +0000549 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000550 """
551 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000552
553 Directories will be copied recursively.
554 If a source component is a directory with a trailing slash,
555 the content of the directory will be copied, otherwise, the
556 directory itself and its content will be copied. This
557 behavior is similar to that of the program 'rsync'.
558
559 Args:
560 source: either
561 1) a single file or directory, as a string
562 2) a list of one or more (possibly mixed)
563 files or directories
564 dest: a file or a directory (if source contains a
565 directory or more than one element, you must
566 supply a directory dest)
567
568 Raises:
569 AutoservRunError: the scp command failed
570 """
571 if isinstance(source, types.StringTypes):
572 source= [source]
573
574 processed_source= []
575 for entry in source:
576 if entry.endswith('/'):
577 format_string= '"%s/"*'
578 else:
579 format_string= '"%s"'
580 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
581 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000582
mbligh0faf91f2007-10-18 03:10:48 +0000583 remote_dest = '%s@%s:"%s"' % (
584 self.user, self.hostname,
585 utils.scp_remote_escape(dest))
mblighcfc7ab32008-01-25 16:35:28 +0000586
587 self.__copy_files(processed_source, remote_dest)
mbligha4eb0fa2008-04-11 15:16:50 +0000588 self.run('find "%s" -type d | xargs -i -r chmod o+rx "{}"' % dest)
589 self.run('find "%s" -type f | xargs -i -r chmod o+r "{}"' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000590
mblighdcd57a82007-07-11 23:06:47 +0000591 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000592 """
593 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000594 for temporary file storage.
595
596 The directory and its content will be deleted automatically
597 on the destruction of the Host object that was used to obtain
598 it.
599 """
mbligha25b29e2007-08-26 13:58:04 +0000600 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000601 self.tmp_dirs.append(dir_name)
602 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000603
604
mblighdcd57a82007-07-11 23:06:47 +0000605 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000606 """
607 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000608
609 Returns:
610 True if the remote host is up, False otherwise
611 """
612 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000613 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000614 except:
mblighdcd57a82007-07-11 23:06:47 +0000615 return False
mbligheadfbb12007-11-26 23:03:12 +0000616 return True
mbligh7d2bde82007-08-02 16:26:10 +0000617
mbligh7d2bde82007-08-02 16:26:10 +0000618
mblighdcd57a82007-07-11 23:06:47 +0000619 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000620 """
621 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000622
623 In fact, it will wait until an ssh connection to the remote
624 host can be established.
625
626 Args:
627 timeout: time limit in seconds before returning even
628 if the host is not up.
629
630 Returns:
631 True if the host was found to be up, False otherwise
632 """
633 if timeout:
634 end_time= time.time() + timeout
635
636 while not timeout or time.time() < end_time:
637 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000638 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000639 except:
mblighdcd57a82007-07-11 23:06:47 +0000640 pass
641 else:
mbligheadfbb12007-11-26 23:03:12 +0000642 return True
mblighdcd57a82007-07-11 23:06:47 +0000643 time.sleep(1)
644
645 return False
mbligh7d2bde82007-08-02 16:26:10 +0000646
647
mblighdcd57a82007-07-11 23:06:47 +0000648 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000649 """
650 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000651
652 In fact, it will wait until an ssh connection to the remote
653 host fails.
654
655 Args:
656 timeout: time limit in seconds before returning even
657 if the host is not up.
658
659 Returns:
660 True if the host was found to be down, False otherwise
661 """
662 if timeout:
663 end_time= time.time() + timeout
664
665 while not timeout or time.time() < end_time:
666 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000667 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000668 except:
mblighdcd57a82007-07-11 23:06:47 +0000669 return True
mblighdcd57a82007-07-11 23:06:47 +0000670 time.sleep(1)
671
672 return False
mbligh7d2bde82007-08-02 16:26:10 +0000673
674
mblighdbe4a382007-07-26 19:41:28 +0000675 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000676 """
677 Ensure the host is up if it is not then do not proceed;
678 this prevents cacading failures of tests
679 """
mbligha0452c82007-08-08 20:24:57 +0000680 print 'Ensuring that %s is up before continuing' % self.hostname
681 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000682 print "Performing a hardreset on %s" % self.hostname
mbligh4ba0b462007-11-05 23:05:40 +0000683 try:
684 self.hardreset()
mblighf5427bb2008-04-09 15:55:57 +0000685 except error.AutoservUnsupportedError:
mbligh4ba0b462007-11-05 23:05:40 +0000686 print "Hardreset is unsupported on %s" % self.hostname
mbligha9563b92007-10-25 14:45:56 +0000687 if not self.wait_up(60 * 30):
688 # 30 minutes should be more than enough
mblighf5427bb2008-04-09 15:55:57 +0000689 raise error.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000690 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000691
692
mblighdcd57a82007-07-11 23:06:47 +0000693 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000694 """
695 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000696 /proc/cpuinfo.
697
698 Returns:
699 The number of CPUs
700 """
701
mbligh0ba35792008-04-15 19:16:11 +0000702 proc_cpuinfo = self.run("cat /proc/cpuinfo",
703 stdout_tee=open('/dev/null', 'w')).stdout
mblighdcd57a82007-07-11 23:06:47 +0000704 cpus = 0
705 for line in proc_cpuinfo.splitlines():
706 if line.startswith('processor'):
707 cpus += 1
708 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000709
710
711 def check_uptime(self):
712 """
713 Check that uptime is available and monotonically increasing.
714 """
715 if not self.ping():
mblighf5427bb2008-04-09 15:55:57 +0000716 raise error.AutoservHostError('Client is not pingable')
mbligh5f876ad2007-10-12 23:59:53 +0000717 result = self.run("/bin/cat /proc/uptime", 30)
718 return result.stdout.strip().split()[0]
719
720
721 def get_arch(self):
722 """
723 Get the hardware architecture of the remote machine
724 """
725 arch = self.run('/bin/uname -m').stdout.rstrip()
726 if re.match(r'i\d86$', arch):
727 arch = 'i386'
728 return arch
729
730
731 def get_kernel_ver(self):
732 """
733 Get the kernel version of the remote machine
734 """
735 return self.run('/bin/uname -r').stdout.rstrip()
736
737
738 def get_cmdline(self):
739 """
740 Get the kernel command line of the remote machine
741 """
742 return self.run('cat /proc/cmdline').stdout.rstrip()
743
744
745 def ping(self):
746 """
747 Ping the remote system, and return whether it's available
748 """
749 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
750 rc = utils.system(fpingcmd, ignore_status = 1)
751 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000752
mblighf014ff42007-11-26 21:33:11 +0000753
mbligh4cfa76a2007-11-26 20:45:16 +0000754 def ssh_ping(self, timeout = 60):
mbligh21d7e262008-05-02 23:04:50 +0000755 try:
756 self.run('true', timeout = timeout, connect_timeout = timeout)
757 except error.AutoservSSHTimeout:
758 msg = "ssh ping timed out. timeout = %s" % timeout
759 raise error.AutoservSSHTimeout(msg)
jadmanski4c6f15e2008-05-06 20:36:09 +0000760 except error.AutoservRunError, exc:
mbligh21d7e262008-05-02 23:04:50 +0000761 msg = "command true failed in ssh ping"
jadmanski4c6f15e2008-05-06 20:36:09 +0000762 raise error.AutoservRunError(msg, exc.args[1])
mblighda13d542008-01-03 16:28:34 +0000763
764
765 def get_autodir(self):
766 return self.autodir