blob: 8277a367f742680f3969e9df5b5db6e86905c719 [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
mblighbda9c9c2008-04-08 17:45:00 +000028
mblighf5427bb2008-04-09 15:55:57 +000029class SSHHost(remote.RemoteHost):
mbligh7d2bde82007-08-02 16:26:10 +000030 """
31 This class represents a remote machine controlled through an ssh
mblighdcd57a82007-07-11 23:06:47 +000032 session on which you can run programs.
mbligh7d2bde82007-08-02 16:26:10 +000033
mblighdcd57a82007-07-11 23:06:47 +000034 It is not the machine autoserv is running on. The machine must be
35 configured for password-less login, for example through public key
36 authentication.
mbligh7d2bde82007-08-02 16:26:10 +000037
mbligh3409ee72007-10-16 23:58:33 +000038 It includes support for controlling the machine through a serial
39 console on which you can run programs. If such a serial console is
40 set up on the machine then capabilities such as hard reset and
41 boot strap monitoring are available. If the machine does not have a
42 serial console available then ordinary SSH-based commands will
43 still be available, but attempts to use extensions such as
44 console logging or hard reset will fail silently.
45
mblighdcd57a82007-07-11 23:06:47 +000046 Implementation details:
47 This is a leaf class in an abstract class hierarchy, it must
48 implement the unimplemented methods in parent classes.
49 """
mbligh7d2bde82007-08-02 16:26:10 +000050
mbligh31a49de2007-11-05 18:41:19 +000051 DEFAULT_REBOOT_TIMEOUT = 1800
52 job = None
mbligh0faf91f2007-10-18 03:10:48 +000053
mblighde384372007-10-17 04:25:37 +000054 def __init__(self, hostname, user="root", port=22, initialize=True,
mblighf4e04152008-02-21 16:05:53 +000055 conmux_log="console.log",
mblighe6c995f2007-10-26 19:43:01 +000056 conmux_server=None, conmux_attach=None,
mblighda13d542008-01-03 16:28:34 +000057 netconsole_log=None, netconsole_port=6666, autodir=None):
mbligh7d2bde82007-08-02 16:26:10 +000058 """
59 Construct a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000060
61 Args:
62 hostname: network hostname or address of remote machine
63 user: user to log in as on the remote machine
64 port: port the ssh daemon is listening on on the remote
65 machine
mbligh9708f732007-10-18 03:18:54 +000066 """
mblighdcd57a82007-07-11 23:06:47 +000067 self.hostname= hostname
68 self.user= user
69 self.port= port
70 self.tmp_dirs= []
mbligh137a05c2007-10-04 15:56:51 +000071 self.initialize = initialize
mblighda13d542008-01-03 16:28:34 +000072 self.autodir = autodir
mbligh91334902007-09-28 01:47:59 +000073
mbligh9708f732007-10-18 03:18:54 +000074 super(SSHHost, self).__init__()
75
mbligh3409ee72007-10-16 23:58:33 +000076 self.conmux_server = conmux_server
mbligh70cf0ec2008-01-18 17:57:14 +000077 if conmux_attach:
78 self.conmux_attach = conmux_attach
79 else:
80 self.conmux_attach = os.path.abspath(os.path.join(
81 self.serverdir, '..',
82 'conmux', 'conmux-attach'))
mblighfbb03542008-02-11 16:27:29 +000083 self.logger_popen = None
mblighf4e04152008-02-21 16:05:53 +000084 self.warning_stream = None
mblighde384372007-10-17 04:25:37 +000085 self.__start_console_log(conmux_log)
mbligh3409ee72007-10-16 23:58:33 +000086
mbligha0452c82007-08-08 20:24:57 +000087 self.bootloader = bootloader.Bootloader(self)
mbligh7d2bde82007-08-02 16:26:10 +000088
mblighc0e92392007-11-05 19:10:10 +000089 self.__netconsole_param = ""
mblighfbb03542008-02-11 16:27:29 +000090 self.netlogger_popen = None
mblighc0e92392007-11-05 19:10:10 +000091 if netconsole_log:
92 self.__init_netconsole_params(netconsole_port)
93 self.__start_netconsole_log(netconsole_log, netconsole_port)
94 self.__load_netconsole_module()
mblighde384372007-10-17 04:25:37 +000095
mbligh7d2bde82007-08-02 16:26:10 +000096
mblighfbb03542008-02-11 16:27:29 +000097 @staticmethod
mblighf4e04152008-02-21 16:05:53 +000098 def __kill(popen):
mblighfbb03542008-02-11 16:27:29 +000099 return_code = popen.poll()
mblighf4e04152008-02-21 16:05:53 +0000100 if return_code is None:
mblighfbb03542008-02-11 16:27:29 +0000101 try:
mblighf4e04152008-02-21 16:05:53 +0000102 os.kill(popen.pid, signal.SIGTERM)
mblighfbb03542008-02-11 16:27:29 +0000103 except OSError:
104 pass
105
106
mblighdcd57a82007-07-11 23:06:47 +0000107 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +0000108 """
109 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +0000110 """
111 for dir in self.tmp_dirs:
112 try:
113 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
mblighf5427bb2008-04-09 15:55:57 +0000114 except error.AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000115 pass
mblighde384372007-10-17 04:25:37 +0000116 # kill the console logger
mblighfbb03542008-02-11 16:27:29 +0000117 if getattr(self, 'logger_popen', None):
mblighf4e04152008-02-21 16:05:53 +0000118 self.__kill(self.logger_popen)
mbligh632f8382008-02-27 16:39:41 +0000119 self.job.warning_loggers.discard(self.warning_stream)
mblighf4e04152008-02-21 16:05:53 +0000120 self.warning_stream.close()
mblighde384372007-10-17 04:25:37 +0000121 # kill the netconsole logger
mblighfbb03542008-02-11 16:27:29 +0000122 if getattr(self, 'netlogger_popen', None):
mblighe6c995f2007-10-26 19:43:01 +0000123 self.__unload_netconsole_module()
mblighf4e04152008-02-21 16:05:53 +0000124 self.__kill(self.netlogger_popen)
mblighde384372007-10-17 04:25:37 +0000125
126
127 def __init_netconsole_params(self, port):
128 """
129 Connect to the remote machine and determine the values to use for the
130 required netconsole parameters.
131 """
mblighde384372007-10-17 04:25:37 +0000132 # PROBLEM: on machines with multiple IPs this may not make any sense
133 # It also doesn't work with IPv6
134 remote_ip = socket.gethostbyname(self.hostname)
135 local_ip = socket.gethostbyname(socket.gethostname())
136 # Get the gateway of the remote machine
137 try:
138 traceroute = self.run('traceroute -n %s' % local_ip)
mblighf5427bb2008-04-09 15:55:57 +0000139 except error.AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000140 return
141 first_node = traceroute.stdout.split("\n")[0]
142 match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
143 if match:
144 router_ip = match.group(1)
145 else:
146 return
147 # Look up the MAC address of the gateway
148 try:
149 self.run('ping -c 1 %s' % router_ip)
150 arp = self.run('arp -n -a %s' % router_ip)
mblighf5427bb2008-04-09 15:55:57 +0000151 except error.AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000152 return
153 match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
154 if match:
155 gateway_mac = match.group(1)
156 else:
157 return
158 self.__netconsole_param = 'netconsole=@%s/,%s@%s/%s' % (remote_ip,
159 port,
160 local_ip,
161 gateway_mac)
162
163
164 def __start_netconsole_log(self, logfilename, port):
165 """
166 Log the output of netconsole to a specified file
167 """
168 if logfilename == None:
169 return
170 cmd = ['nc', '-u', '-l', '-p', str(port)]
mblighfbb03542008-02-11 16:27:29 +0000171 logfile = open(logfilename, 'a', 0)
172 self.netlogger_popen = subprocess.Popen(cmd, stdout=logfile)
mblighde384372007-10-17 04:25:37 +0000173
174
175 def __load_netconsole_module(self):
176 """
177 Make a best effort to load the netconsole module.
178
179 Note that loading the module can fail even when the remote machine is
180 working correctly if netconsole is already compiled into the kernel
181 and started.
182 """
mblighc0e92392007-11-05 19:10:10 +0000183 if not self.__netconsole_param:
184 return
mblighde384372007-10-17 04:25:37 +0000185 try:
186 self.run('modprobe netconsole %s' % self.__netconsole_param)
mblighf5427bb2008-04-09 15:55:57 +0000187 except error.AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000188 # if it fails there isn't much we can do, just keep going
189 pass
190
191
192 def __unload_netconsole_module(self):
193 try:
194 self.run('modprobe -r netconsole')
mblighf5427bb2008-04-09 15:55:57 +0000195 except error.AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000196 pass
mbligh3409ee72007-10-16 23:58:33 +0000197
198
mbligh5deff3d2008-01-04 21:21:28 +0000199 def wait_for_restart(self, timeout=DEFAULT_REBOOT_TIMEOUT):
mblighd567f722007-10-30 15:37:33 +0000200 if not self.wait_down(300): # Make sure he's dead, Jim
mblighf3b78932007-11-07 16:52:47 +0000201 self.__record("ABORT", None, "reboot.verify", "shutdown failed")
mblighf5427bb2008-04-09 15:55:57 +0000202 raise error.AutoservRebootError(
203 "Host did not shut down")
mbligh3409ee72007-10-16 23:58:33 +0000204 self.wait_up(timeout)
205 time.sleep(2) # this is needed for complete reliability
mblighcf3d83a2007-11-05 19:21:39 +0000206 if self.wait_up(timeout):
mbligh30270302007-11-05 20:33:52 +0000207 self.__record("GOOD", None, "reboot.verify")
mblighcf3d83a2007-11-05 19:21:39 +0000208 else:
mbligh71d24222008-03-11 21:31:56 +0000209 self.__record("ABORT", None, "reboot.verify", "Host did not return from reboot")
mblighf5427bb2008-04-09 15:55:57 +0000210 raise error.AutoservRebootError(
211 "Host did not return from reboot")
mbligh3409ee72007-10-16 23:58:33 +0000212 print "Reboot complete"
213
214
mbligh80d20772007-10-29 17:10:10 +0000215 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000216 """
217 Reach out and slap the box in the power switch
218 """
mblighf3b78932007-11-07 16:52:47 +0000219 if not self.__console_run(r"'~$hardreset'"):
220 self.__record("ABORT", None, "reboot.start", "hard reset unavailable")
mblighf5427bb2008-04-09 15:55:57 +0000221 raise error.AutoservUnsupportedError(
222 'Hard reset unavailable')
mbligh37d53c32008-01-14 16:16:00 +0000223
224 if wait:
225 self.wait_for_restart(timeout)
mbligha4d4f372008-01-22 15:49:50 +0000226 self.__record("GOOD", None, "reboot.start", "hard reset")
mbligh3409ee72007-10-16 23:58:33 +0000227
228
mblighe6c995f2007-10-26 19:43:01 +0000229 def __conmux_hostname(self):
230 if self.conmux_server:
231 return '%s/%s' % (self.conmux_server, self.hostname)
232 else:
233 return self.hostname
234
235
mbligh3409ee72007-10-16 23:58:33 +0000236 def __start_console_log(self, logfilename):
237 """
238 Log the output of the console session to a specified file
239 """
240 if logfilename == None:
241 return
242 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
243 return
mblighf4e04152008-02-21 16:05:53 +0000244
245 r, w = os.pipe()
246 script_path = os.path.join(self.serverdir,
247 'warning_monitor.py')
mblighfbb03542008-02-11 16:27:29 +0000248 cmd = [self.conmux_attach, self.__conmux_hostname(),
mblighf4e04152008-02-21 16:05:53 +0000249 '%s %s %s %d' % (sys.executable, script_path,
250 logfilename, w)]
mbligh0c5ce312008-02-21 16:24:11 +0000251 dev_null = open(os.devnull, 'w')
mbligh3409ee72007-10-16 23:58:33 +0000252
mblighf4e04152008-02-21 16:05:53 +0000253 self.warning_stream = os.fdopen(r, 'r', 0)
254 self.job.warning_loggers.add(self.warning_stream)
255 self.logger_popen = subprocess.Popen(cmd, stderr=dev_null)
256 os.close(w)
mblighe6c995f2007-10-26 19:43:01 +0000257
258
mbligh3409ee72007-10-16 23:58:33 +0000259 def __console_run(self, cmd):
260 """
261 Send a command to the conmux session
262 """
263 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
264 return False
mbligh3409ee72007-10-16 23:58:33 +0000265 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000266 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000267 cmd)
mbligh0f5ad642008-01-22 16:37:40 +0000268 result = utils.system(cmd, ignore_status=True)
mbligh3409ee72007-10-16 23:58:33 +0000269 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000270
271
mbligh31a49de2007-11-05 18:41:19 +0000272 def __record(self, status_code, subdir, operation, status = ''):
273 if self.job:
274 self.job.record(status_code, subdir, operation, status)
275 else:
276 if not subdir:
277 subdir = "----"
278 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
279 sys.stderr.write(msg + "\n")
280
281
mblighfa971602008-01-03 01:57:20 +0000282 def ssh_base_command(self, connect_timeout=30):
283 SSH_BASE_COMMAND = '/usr/bin/ssh -a -x -o ' + \
mbligh0ad21ba2008-03-14 15:06:21 +0000284 'BatchMode=yes -o ConnectTimeout=%d ' + \
285 '-o ServerAliveInterval=300'
mblighfa971602008-01-03 01:57:20 +0000286 assert isinstance(connect_timeout, (int, long))
287 assert connect_timeout > 0 # can't disable the timeout
288 return SSH_BASE_COMMAND % connect_timeout
289
290
291 def ssh_command(self, connect_timeout=30):
mblighe6647d12007-10-17 00:00:01 +0000292 """Construct an ssh command with proper args for this host."""
mblighfa971602008-01-03 01:57:20 +0000293 ssh = self.ssh_base_command(connect_timeout)
294 return r'%s -l %s -p %d %s' % (ssh,
mbligh0faf91f2007-10-18 03:10:48 +0000295 self.user,
296 self.port,
297 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000298
299
mbligh07a923f2008-01-16 17:49:04 +0000300 def run(self, command, timeout=3600, ignore_status=False,
mblighfa971602008-01-03 01:57:20 +0000301 stdout_tee=None, stderr_tee=None, connect_timeout=30):
mbligh7d2bde82007-08-02 16:26:10 +0000302 """
303 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000304
305 Args:
306 command: the command line string
307 timeout: time limit in seconds before attempting to
308 kill the running process. The run() function
309 will take a few seconds longer than 'timeout'
310 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000311 ignore_status: do not raise an exception, no matter
312 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000313
314 Returns:
315 a hosts.base_classes.CmdResult object
316
317 Raises:
318 AutoservRunError: the exit code of the command
319 execution was not 0
mblighcaa62c22008-04-07 21:51:17 +0000320 AutoservSSHTimeout: ssh connection has timed out
mblighdcd57a82007-07-11 23:06:47 +0000321 """
mblighadf2aab2007-11-29 18:16:43 +0000322 stdout = stdout_tee or sys.stdout
mbligh8d4baaa2008-03-12 14:48:24 +0000323 stderr = stderr_tee or sys.stdout
mbligh7995cc62007-11-30 15:53:23 +0000324 print "ssh: %s" % (command,)
mblighadf2aab2007-11-29 18:16:43 +0000325 env = " ".join("=".join(pair) for pair in self.env.iteritems())
mbligh34faa282008-01-16 17:44:49 +0000326 full_cmd = '%s "%s %s"' % (self.ssh_command(connect_timeout),
327 env, utils.sh_escape(command))
328 result = utils.run(full_cmd, timeout, True, stdout, stderr)
329 if result.exit_status == 255: # ssh's exit status for timeout
330 if re.match(r'^ssh: connect to host .* port .*: ' +
331 r'Connection timed out\r$', result.stderr):
mblighf5427bb2008-04-09 15:55:57 +0000332 raise error.AutoservSSHTimeout("ssh timed out",
333 result)
mbligh34faa282008-01-16 17:44:49 +0000334 if not ignore_status and result.exit_status > 0:
mblighf5427bb2008-04-09 15:55:57 +0000335 raise error.AutoservRunError("command execution error",
336 result)
mblighdcd57a82007-07-11 23:06:47 +0000337 return result
mbligh7d2bde82007-08-02 16:26:10 +0000338
339
mblighbda9c9c2008-04-08 17:45:00 +0000340 def run_short(self, command, **kwargs):
341 """
342 Calls the run() command with a short default timeout.
343
344 Args:
345 Takes the same arguments as does run(),
346 with the exception of the timeout argument which
347 here is fixed at 60 seconds.
348 It returns the result of run.
349 """
350 return self.run(command, timeout=60, **kwargs)
351
352
mbligh78669ff2008-01-10 16:33:07 +0000353 def run_grep(self, command, timeout=30, ignore_status=False,
354 stdout_ok_regexp=None, stdout_err_regexp=None,
355 stderr_ok_regexp=None, stderr_err_regexp=None,
356 connect_timeout=30):
357 """
358 Run a command on the remote host and look for regexp
359 in stdout or stderr to determine if the command was
360 successul or not.
mbligh6a2a2df2008-01-16 17:41:55 +0000361
mbligh78669ff2008-01-10 16:33:07 +0000362 Args:
363 command: the command line string
mbligh6a2a2df2008-01-16 17:41:55 +0000364 timeout: time limit in seconds before attempting to
mbligh78669ff2008-01-10 16:33:07 +0000365 kill the running process. The run() function
366 will take a few seconds longer than 'timeout'
367 to complete if it has to kill the process.
mbligh6a2a2df2008-01-16 17:41:55 +0000368 ignore_status: do not raise an exception, no matter
mbligh78669ff2008-01-10 16:33:07 +0000369 what the exit code of the command is.
370 stdout_ok_regexp: regexp that should be in stdout
371 if the command was successul.
372 stdout_err_regexp: regexp that should be in stdout
373 if the command failed.
374 stderr_ok_regexp: regexp that should be in stderr
375 if the command was successul.
376 stderr_err_regexp: regexp that should be in stderr
377 if the command failed.
mbligh6a2a2df2008-01-16 17:41:55 +0000378
mbligh78669ff2008-01-10 16:33:07 +0000379 Returns:
380 if the command was successul, raises an exception
381 otherwise.
mbligh6a2a2df2008-01-16 17:41:55 +0000382
mbligh78669ff2008-01-10 16:33:07 +0000383 Raises:
384 AutoservRunError:
385 - the exit code of the command execution was not 0.
mbligh6a2a2df2008-01-16 17:41:55 +0000386 - If stderr_err_regexp is found in stderr,
387 - If stdout_err_regexp is found in stdout,
mbligh78669ff2008-01-10 16:33:07 +0000388 - If stderr_ok_regexp is not found in stderr.
389 - If stdout_ok_regexp is not found in stdout,
390 """
391
392 # We ignore the status, because we will handle it at the end.
393 result = self.run(command, timeout, ignore_status=True,
mbligh6a2a2df2008-01-16 17:41:55 +0000394 connect_timeout=connect_timeout)
mbligh78669ff2008-01-10 16:33:07 +0000395
396 # Look for the patterns, in order
397 for (regexp, stream) in ((stderr_err_regexp, result.stderr),
mbligh6a2a2df2008-01-16 17:41:55 +0000398 (stdout_err_regexp, result.stdout)):
mbligh78669ff2008-01-10 16:33:07 +0000399 if regexp and stream:
400 err_re = re.compile (regexp)
401 if err_re.search(stream):
mblighf5427bb2008-04-09 15:55:57 +0000402 raise error.AutoservRunError(
mbligh6a2a2df2008-01-16 17:41:55 +0000403 '%s failed, found error pattern: '
404 '"%s"' % (command, regexp), result)
mbligh78669ff2008-01-10 16:33:07 +0000405
406 for (regexp, stream) in ((stderr_ok_regexp, result.stderr),
mbligh6a2a2df2008-01-16 17:41:55 +0000407 (stdout_ok_regexp, result.stdout)):
mbligh78669ff2008-01-10 16:33:07 +0000408 if regexp and stream:
409 ok_re = re.compile (regexp)
410 if ok_re.search(stream):
411 if ok_re.search(stream):
412 return
413
414 if not ignore_status and result.exit_status > 0:
mblighf5427bb2008-04-09 15:55:57 +0000415 raise error.AutoservRunError("command execution error",
416 result)
mbligh78669ff2008-01-10 16:33:07 +0000417
418
mbligh80d20772007-10-29 17:10:10 +0000419 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
420 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000421 """
422 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000423
mbligha0452c82007-08-08 20:24:57 +0000424 Args:
425 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000426 """
mbligh33ae0902007-11-24 19:27:08 +0000427 self.reboot_setup()
428
mblighde384372007-10-17 04:25:37 +0000429 # forcibly include the "netconsole" kernel arg
430 if self.__netconsole_param:
431 if kernel_args is None:
432 kernel_args = self.__netconsole_param
433 else:
434 kernel_args += " " + self.__netconsole_param
435 # unload the (possibly loaded) module to avoid shutdown issues
436 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000437 if label or kernel_args:
438 self.bootloader.install_boottool()
439 if label:
440 self.bootloader.set_default(label)
441 if kernel_args:
442 if not label:
443 default = int(self.bootloader.get_default())
444 label = self.bootloader.get_titles()[default]
445 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000446 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000447 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000448 try:
mblighf3b78932007-11-07 16:52:47 +0000449 self.run('(sleep 5; reboot) </dev/null >/dev/null 2>&1 &')
mblighf5427bb2008-04-09 15:55:57 +0000450 except error.AutoservRunError:
mblighf3b78932007-11-07 16:52:47 +0000451 self.__record("ABORT", None, "reboot.start",
452 "reboot command failed")
mblighcf3d83a2007-11-05 19:21:39 +0000453 raise
mbligha0452c82007-08-08 20:24:57 +0000454 if wait:
mbligh5deff3d2008-01-04 21:21:28 +0000455 self.wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000456 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000457
mbligh7d2bde82007-08-02 16:26:10 +0000458
mblighcfc7ab32008-01-25 16:35:28 +0000459 def __copy_files(self, sources, dest):
460 """
461 Copy files from one machine to another.
462
463 This is for internal use by other methods that intend to move
464 files between machines. It expects a list of source files and
465 a destination (a filename if the source is a single file, a
466 destination otherwise). The names must already be
467 pre-processed into the appropriate rsync/scp friendly
468 format (%s@%s:%s).
469 """
470 # wait until there are only a small number of copies running
471 # before starting this one
472 MAXIMUM_SIMULTANEOUS_COPIES = 4
473 while True:
474 copy_count = 0
475 procs = utils.system_output('ps -ef')
476 for line in procs:
477 if 'rsync ' in line or 'scp ' in line:
478 copy_count += 1
479 if copy_count < MAXIMUM_SIMULTANEOUS_COPIES:
480 break
481 time.sleep(60)
482
mbligh22fdf172008-04-07 18:34:56 +0000483 print '__copy_files: copying %s to %s' % (sources, dest)
mblighcfc7ab32008-01-25 16:35:28 +0000484 try:
485 utils.run('rsync --rsh="%s" -az %s %s' % (
486 self.ssh_base_command(), ' '.join(sources), dest))
487 except Exception:
488 utils.run('scp -rpq %s "%s"' % (
489 ' '.join(sources), dest))
490
491
mblighdcd57a82007-07-11 23:06:47 +0000492 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000493 """
494 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000495
496 Directories will be copied recursively.
497 If a source component is a directory with a trailing slash,
498 the content of the directory will be copied, otherwise, the
499 directory itself and its content will be copied. This
500 behavior is similar to that of the program 'rsync'.
501
502 Args:
503 source: either
504 1) a single file or directory, as a string
505 2) a list of one or more (possibly mixed)
506 files or directories
507 dest: a file or a directory (if source contains a
508 directory or more than one element, you must
509 supply a directory dest)
510
511 Raises:
512 AutoservRunError: the scp command failed
513 """
514 if isinstance(source, types.StringTypes):
515 source= [source]
516
517 processed_source= []
518 for entry in source:
519 if entry.endswith('/'):
520 format_string= '%s@%s:"%s*"'
521 else:
522 format_string= '%s@%s:"%s"'
523 entry= format_string % (self.user, self.hostname,
524 utils.scp_remote_escape(entry))
525 processed_source.append(entry)
526
527 processed_dest= os.path.abspath(dest)
528 if os.path.isdir(dest):
529 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
530 else:
531 processed_dest= utils.sh_escape(processed_dest)
mblighcfc7ab32008-01-25 16:35:28 +0000532
533 self.__copy_files(processed_source, processed_dest)
mbligh7d2bde82007-08-02 16:26:10 +0000534
535
mblighdcd57a82007-07-11 23:06:47 +0000536 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000537 """
538 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000539
540 Directories will be copied recursively.
541 If a source component is a directory with a trailing slash,
542 the content of the directory will be copied, otherwise, the
543 directory itself and its content will be copied. This
544 behavior is similar to that of the program 'rsync'.
545
546 Args:
547 source: either
548 1) a single file or directory, as a string
549 2) a list of one or more (possibly mixed)
550 files or directories
551 dest: a file or a directory (if source contains a
552 directory or more than one element, you must
553 supply a directory dest)
554
555 Raises:
556 AutoservRunError: the scp command failed
557 """
558 if isinstance(source, types.StringTypes):
559 source= [source]
560
561 processed_source= []
562 for entry in source:
563 if entry.endswith('/'):
564 format_string= '"%s/"*'
565 else:
566 format_string= '"%s"'
567 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
568 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000569
mbligh0faf91f2007-10-18 03:10:48 +0000570 remote_dest = '%s@%s:"%s"' % (
571 self.user, self.hostname,
572 utils.scp_remote_escape(dest))
mblighcfc7ab32008-01-25 16:35:28 +0000573
574 self.__copy_files(processed_source, remote_dest)
mblighc42141f2007-11-05 20:25:46 +0000575 self.run('find "%s" -type d | xargs -r chmod o+rx' % dest)
576 self.run('find "%s" -type f | xargs -r chmod o+r' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000577
mblighdcd57a82007-07-11 23:06:47 +0000578 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000579 """
580 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000581 for temporary file storage.
582
583 The directory and its content will be deleted automatically
584 on the destruction of the Host object that was used to obtain
585 it.
586 """
mbligha25b29e2007-08-26 13:58:04 +0000587 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000588 self.tmp_dirs.append(dir_name)
589 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000590
591
mblighdcd57a82007-07-11 23:06:47 +0000592 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000593 """
594 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000595
596 Returns:
597 True if the remote host is up, False otherwise
598 """
599 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000600 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000601 except:
mblighdcd57a82007-07-11 23:06:47 +0000602 return False
mbligheadfbb12007-11-26 23:03:12 +0000603 return True
mbligh7d2bde82007-08-02 16:26:10 +0000604
mbligh7d2bde82007-08-02 16:26:10 +0000605
mblighdcd57a82007-07-11 23:06:47 +0000606 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000607 """
608 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000609
610 In fact, it will wait until an ssh connection to the remote
611 host can be established.
612
613 Args:
614 timeout: time limit in seconds before returning even
615 if the host is not up.
616
617 Returns:
618 True if the host was found to be up, False otherwise
619 """
620 if timeout:
621 end_time= time.time() + timeout
622
623 while not timeout or time.time() < end_time:
624 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000625 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000626 except:
mblighdcd57a82007-07-11 23:06:47 +0000627 pass
628 else:
mbligheadfbb12007-11-26 23:03:12 +0000629 return True
mblighdcd57a82007-07-11 23:06:47 +0000630 time.sleep(1)
631
632 return False
mbligh7d2bde82007-08-02 16:26:10 +0000633
634
mblighdcd57a82007-07-11 23:06:47 +0000635 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000636 """
637 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000638
639 In fact, it will wait until an ssh connection to the remote
640 host fails.
641
642 Args:
643 timeout: time limit in seconds before returning even
644 if the host is not up.
645
646 Returns:
647 True if the host was found to be down, False otherwise
648 """
649 if timeout:
650 end_time= time.time() + timeout
651
652 while not timeout or time.time() < end_time:
653 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000654 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000655 except:
mblighdcd57a82007-07-11 23:06:47 +0000656 return True
mblighdcd57a82007-07-11 23:06:47 +0000657 time.sleep(1)
658
659 return False
mbligh7d2bde82007-08-02 16:26:10 +0000660
661
mblighdbe4a382007-07-26 19:41:28 +0000662 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000663 """
664 Ensure the host is up if it is not then do not proceed;
665 this prevents cacading failures of tests
666 """
mbligha0452c82007-08-08 20:24:57 +0000667 print 'Ensuring that %s is up before continuing' % self.hostname
668 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000669 print "Performing a hardreset on %s" % self.hostname
mbligh4ba0b462007-11-05 23:05:40 +0000670 try:
671 self.hardreset()
mblighf5427bb2008-04-09 15:55:57 +0000672 except error.AutoservUnsupportedError:
mbligh4ba0b462007-11-05 23:05:40 +0000673 print "Hardreset is unsupported on %s" % self.hostname
mbligha9563b92007-10-25 14:45:56 +0000674 if not self.wait_up(60 * 30):
675 # 30 minutes should be more than enough
mblighf5427bb2008-04-09 15:55:57 +0000676 raise error.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000677 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000678
679
mblighdcd57a82007-07-11 23:06:47 +0000680 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000681 """
682 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000683 /proc/cpuinfo.
684
685 Returns:
686 The number of CPUs
687 """
688
mbligh5f876ad2007-10-12 23:59:53 +0000689 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000690 cpus = 0
691 for line in proc_cpuinfo.splitlines():
692 if line.startswith('processor'):
693 cpus += 1
694 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000695
696
697 def check_uptime(self):
698 """
699 Check that uptime is available and monotonically increasing.
700 """
701 if not self.ping():
mblighf5427bb2008-04-09 15:55:57 +0000702 raise error.AutoservHostError('Client is not pingable')
mbligh5f876ad2007-10-12 23:59:53 +0000703 result = self.run("/bin/cat /proc/uptime", 30)
704 return result.stdout.strip().split()[0]
705
706
707 def get_arch(self):
708 """
709 Get the hardware architecture of the remote machine
710 """
711 arch = self.run('/bin/uname -m').stdout.rstrip()
712 if re.match(r'i\d86$', arch):
713 arch = 'i386'
714 return arch
715
716
717 def get_kernel_ver(self):
718 """
719 Get the kernel version of the remote machine
720 """
721 return self.run('/bin/uname -r').stdout.rstrip()
722
723
724 def get_cmdline(self):
725 """
726 Get the kernel command line of the remote machine
727 """
728 return self.run('cat /proc/cmdline').stdout.rstrip()
729
730
731 def ping(self):
732 """
733 Ping the remote system, and return whether it's available
734 """
735 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
736 rc = utils.system(fpingcmd, ignore_status = 1)
737 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000738
mblighf014ff42007-11-26 21:33:11 +0000739
mbligh4cfa76a2007-11-26 20:45:16 +0000740 def ssh_ping(self, timeout = 60):
mbligh4ff46b02008-02-01 17:33:37 +0000741 self.run('true', timeout = timeout, connect_timeout = timeout)
mblighda13d542008-01-03 16:28:34 +0000742
743
744 def get_autodir(self):
745 return self.autodir