blob: 34dafe30c6d938a3abececd688e00db2dd6a72e0 [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
mblighde384372007-10-17 04:25:37 +000021import types, os, sys, signal, subprocess, time, re, socket
mbligh03f4fc72007-11-29 20:56:14 +000022import base_classes, utils, bootloader
23
24from common.error import *
mblighdcd57a82007-07-11 23:06:47 +000025
26
27class SSHHost(base_classes.RemoteHost):
mbligh7d2bde82007-08-02 16:26:10 +000028 """
29 This class represents a remote machine controlled through an ssh
mblighdcd57a82007-07-11 23:06:47 +000030 session on which you can run programs.
mbligh7d2bde82007-08-02 16:26:10 +000031
mblighdcd57a82007-07-11 23:06:47 +000032 It is not the machine autoserv is running on. The machine must be
33 configured for password-less login, for example through public key
34 authentication.
mbligh7d2bde82007-08-02 16:26:10 +000035
mbligh3409ee72007-10-16 23:58:33 +000036 It includes support for controlling the machine through a serial
37 console on which you can run programs. If such a serial console is
38 set up on the machine then capabilities such as hard reset and
39 boot strap monitoring are available. If the machine does not have a
40 serial console available then ordinary SSH-based commands will
41 still be available, but attempts to use extensions such as
42 console logging or hard reset will fail silently.
43
mblighdcd57a82007-07-11 23:06:47 +000044 Implementation details:
45 This is a leaf class in an abstract class hierarchy, it must
46 implement the unimplemented methods in parent classes.
47 """
mbligh7d2bde82007-08-02 16:26:10 +000048
mbligh31a49de2007-11-05 18:41:19 +000049 DEFAULT_REBOOT_TIMEOUT = 1800
50 job = None
mbligh0faf91f2007-10-18 03:10:48 +000051
mblighde384372007-10-17 04:25:37 +000052 def __init__(self, hostname, user="root", port=22, initialize=True,
mbligh7c5452d2007-11-05 18:35:31 +000053 conmux_log="console.log", conmux_warnings="status.log",
mblighe6c995f2007-10-26 19:43:01 +000054 conmux_server=None, conmux_attach=None,
mblighda13d542008-01-03 16:28:34 +000055 netconsole_log=None, netconsole_port=6666, autodir=None):
mbligh7d2bde82007-08-02 16:26:10 +000056 """
57 Construct a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000058
59 Args:
60 hostname: network hostname or address of remote machine
61 user: user to log in as on the remote machine
62 port: port the ssh daemon is listening on on the remote
63 machine
mbligh9708f732007-10-18 03:18:54 +000064 """
mblighdcd57a82007-07-11 23:06:47 +000065 self.hostname= hostname
66 self.user= user
67 self.port= port
68 self.tmp_dirs= []
mbligh137a05c2007-10-04 15:56:51 +000069 self.initialize = initialize
mblighda13d542008-01-03 16:28:34 +000070 self.autodir = autodir
mbligh91334902007-09-28 01:47:59 +000071
mbligh9708f732007-10-18 03:18:54 +000072 super(SSHHost, self).__init__()
73
mbligh3409ee72007-10-16 23:58:33 +000074 self.conmux_server = conmux_server
75 self.conmux_attach = self.__find_console_attach(conmux_attach)
76 self.logger_pid = None
mblighde384372007-10-17 04:25:37 +000077 self.__start_console_log(conmux_log)
mblighe6c995f2007-10-26 19:43:01 +000078 self.warning_pid = None
79 self.__start_warning_log(conmux_warnings)
mbligh3409ee72007-10-16 23:58:33 +000080
mbligha0452c82007-08-08 20:24:57 +000081 self.bootloader = bootloader.Bootloader(self)
mbligh7d2bde82007-08-02 16:26:10 +000082
mblighc0e92392007-11-05 19:10:10 +000083 self.__netconsole_param = ""
mblighde384372007-10-17 04:25:37 +000084 self.netlogger_pid = None
mblighc0e92392007-11-05 19:10:10 +000085 if netconsole_log:
86 self.__init_netconsole_params(netconsole_port)
87 self.__start_netconsole_log(netconsole_log, netconsole_port)
88 self.__load_netconsole_module()
mblighde384372007-10-17 04:25:37 +000089
mbligh7d2bde82007-08-02 16:26:10 +000090
mblighdcd57a82007-07-11 23:06:47 +000091 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +000092 """
93 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000094 """
95 for dir in self.tmp_dirs:
96 try:
97 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
mbligh03f4fc72007-11-29 20:56:14 +000098 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +000099 pass
mblighde384372007-10-17 04:25:37 +0000100 # kill the console logger
mbligh7364ae42007-10-18 03:20:34 +0000101 if getattr(self, 'logger_pid', None):
mbligh3409ee72007-10-16 23:58:33 +0000102 try:
103 pgid = os.getpgid(self.logger_pid)
104 os.killpg(pgid, signal.SIGTERM)
105 except OSError:
106 pass
mblighde384372007-10-17 04:25:37 +0000107 # kill the netconsole logger
mbligh7364ae42007-10-18 03:20:34 +0000108 if getattr(self, 'netlogger_pid', None):
mblighe6c995f2007-10-26 19:43:01 +0000109 self.__unload_netconsole_module()
mblighde384372007-10-17 04:25:37 +0000110 try:
111 os.kill(self.netlogger_pid, signal.SIGTERM)
112 except OSError:
113 pass
mblighe6c995f2007-10-26 19:43:01 +0000114 # kill the warning logger
115 if getattr(self, 'warning_pid', None):
116 try:
117 pgid = os.getpgid(self.warning_pid)
118 os.killpg(pgid, signal.SIGTERM)
119 except OSError:
120 pass
mblighde384372007-10-17 04:25:37 +0000121
122
123 def __init_netconsole_params(self, port):
124 """
125 Connect to the remote machine and determine the values to use for the
126 required netconsole parameters.
127 """
mblighde384372007-10-17 04:25:37 +0000128 # PROBLEM: on machines with multiple IPs this may not make any sense
129 # It also doesn't work with IPv6
130 remote_ip = socket.gethostbyname(self.hostname)
131 local_ip = socket.gethostbyname(socket.gethostname())
132 # Get the gateway of the remote machine
133 try:
134 traceroute = self.run('traceroute -n %s' % local_ip)
mbligh03f4fc72007-11-29 20:56:14 +0000135 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000136 return
137 first_node = traceroute.stdout.split("\n")[0]
138 match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
139 if match:
140 router_ip = match.group(1)
141 else:
142 return
143 # Look up the MAC address of the gateway
144 try:
145 self.run('ping -c 1 %s' % router_ip)
146 arp = self.run('arp -n -a %s' % router_ip)
mbligh03f4fc72007-11-29 20:56:14 +0000147 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000148 return
149 match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
150 if match:
151 gateway_mac = match.group(1)
152 else:
153 return
154 self.__netconsole_param = 'netconsole=@%s/,%s@%s/%s' % (remote_ip,
155 port,
156 local_ip,
157 gateway_mac)
158
159
160 def __start_netconsole_log(self, logfilename, port):
161 """
162 Log the output of netconsole to a specified file
163 """
164 if logfilename == None:
165 return
166 cmd = ['nc', '-u', '-l', '-p', str(port)]
mblighd2fc50f2007-10-23 22:38:00 +0000167 logger = subprocess.Popen(cmd, stdout=open(logfilename, "a", 0))
mblighde384372007-10-17 04:25:37 +0000168 self.netlogger_pid = logger.pid
169
170
171 def __load_netconsole_module(self):
172 """
173 Make a best effort to load the netconsole module.
174
175 Note that loading the module can fail even when the remote machine is
176 working correctly if netconsole is already compiled into the kernel
177 and started.
178 """
mblighc0e92392007-11-05 19:10:10 +0000179 if not self.__netconsole_param:
180 return
mblighde384372007-10-17 04:25:37 +0000181 try:
182 self.run('modprobe netconsole %s' % self.__netconsole_param)
mbligh03f4fc72007-11-29 20:56:14 +0000183 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000184 # if it fails there isn't much we can do, just keep going
185 pass
186
187
188 def __unload_netconsole_module(self):
189 try:
190 self.run('modprobe -r netconsole')
mbligh03f4fc72007-11-29 20:56:14 +0000191 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000192 pass
mbligh3409ee72007-10-16 23:58:33 +0000193
194
mbligh5deff3d2008-01-04 21:21:28 +0000195 def wait_for_restart(self, timeout=DEFAULT_REBOOT_TIMEOUT):
mblighd567f722007-10-30 15:37:33 +0000196 if not self.wait_down(300): # Make sure he's dead, Jim
mblighf3b78932007-11-07 16:52:47 +0000197 self.__record("ABORT", None, "reboot.verify", "shutdown failed")
mbligh03f4fc72007-11-29 20:56:14 +0000198 raise AutoservRebootError("Host did not shut down")
mbligh3409ee72007-10-16 23:58:33 +0000199 self.wait_up(timeout)
200 time.sleep(2) # this is needed for complete reliability
mblighcf3d83a2007-11-05 19:21:39 +0000201 if self.wait_up(timeout):
mbligh30270302007-11-05 20:33:52 +0000202 self.__record("GOOD", None, "reboot.verify")
mblighcf3d83a2007-11-05 19:21:39 +0000203 else:
mblighf3b78932007-11-07 16:52:47 +0000204 self.__record("ABORT", None, "reboot.verify", "bringup failed")
mbligh03f4fc72007-11-29 20:56:14 +0000205 raise AutoservRebootError("Host did not return from reboot")
mbligh3409ee72007-10-16 23:58:33 +0000206 print "Reboot complete"
207
208
mbligh80d20772007-10-29 17:10:10 +0000209 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000210 """
211 Reach out and slap the box in the power switch
212 """
mblighf3b78932007-11-07 16:52:47 +0000213 self.__record("GOOD", None, "reboot.start", "hard reset")
214 if not self.__console_run(r"'~$hardreset'"):
215 self.__record("ABORT", None, "reboot.start", "hard reset unavailable")
mbligh03f4fc72007-11-29 20:56:14 +0000216 raise AutoservUnsupportedError
mblighba81c682007-10-25 15:35:59 +0000217 if wait:
mbligh5deff3d2008-01-04 21:21:28 +0000218 self.wait_for_restart(timeout)
mbligh3409ee72007-10-16 23:58:33 +0000219
220
mblighe6c995f2007-10-26 19:43:01 +0000221 def __conmux_hostname(self):
222 if self.conmux_server:
223 return '%s/%s' % (self.conmux_server, self.hostname)
224 else:
225 return self.hostname
226
227
mbligh3409ee72007-10-16 23:58:33 +0000228 def __start_console_log(self, logfilename):
229 """
230 Log the output of the console session to a specified file
231 """
232 if logfilename == None:
233 return
234 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
235 return
mblighe6c995f2007-10-26 19:43:01 +0000236 cmd = [self.conmux_attach, self.__conmux_hostname(), 'cat - >> %s' % logfilename]
mbligh3409ee72007-10-16 23:58:33 +0000237 logger = subprocess.Popen(cmd,
238 stderr=open('/dev/null', 'w'),
239 preexec_fn=lambda: os.setpgid(0, 0))
240 self.logger_pid = logger.pid
241
242
mbligh94befff2007-12-10 18:03:14 +0000243 def __start_warning_log(self, logfilename):
mblighe6c995f2007-10-26 19:43:01 +0000244 """
245 Log the output of the warning monitor to a specified file
246 """
mbligh8bfa9f92007-11-24 19:29:30 +0000247 if logfilename == None or not os.path.isdir('debug'):
mblighe6c995f2007-10-26 19:43:01 +0000248 return
249 script_path = os.path.join(self.serverdir, 'warning_monitor')
mbligh7c5452d2007-11-05 18:35:31 +0000250 script_cmd = 'expect %s %s >> %s' % (script_path,
251 self.hostname,
252 logfilename)
mblighe6c995f2007-10-26 19:43:01 +0000253 if self.conmux_server:
254 to = '%s/%s'
255 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
256 logger = subprocess.Popen(cmd,
mbligh7c5452d2007-11-05 18:35:31 +0000257 stderr=open('debug/conmux.log', 'a', 0),
mblighe6c995f2007-10-26 19:43:01 +0000258 preexec_fn=lambda: os.setpgid(0, 0))
259 self.warning_pid = logger.pid
260
261
mbligh3409ee72007-10-16 23:58:33 +0000262 def __find_console_attach(self, conmux_attach):
263 if conmux_attach:
264 return conmux_attach
265 try:
266 res = utils.run('which conmux-attach')
267 if res.exit_status == 0:
268 return res.stdout.strip()
mbligh03f4fc72007-11-29 20:56:14 +0000269 except AutoservRunError, e:
mbligh3409ee72007-10-16 23:58:33 +0000270 pass
mbligh9708f732007-10-18 03:18:54 +0000271 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000272 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000273 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000274 '..', 'autotest',
275 'conmux', 'conmux-attach')
276 locations = [autotest_conmux,
277 autotest_conmux_alt,
278 '/usr/local/conmux/bin/conmux-attach',
279 '/usr/bin/conmux-attach']
280 for l in locations:
281 if os.path.exists(l):
282 return l
283
284 print "WARNING: conmux-attach not found on autoserv server"
285 return None
286
287
288 def __console_run(self, cmd):
289 """
290 Send a command to the conmux session
291 """
292 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
293 return False
mbligh3409ee72007-10-16 23:58:33 +0000294 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000295 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000296 cmd)
297 result = os.system(cmd)
298 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000299
300
mbligh31a49de2007-11-05 18:41:19 +0000301 def __record(self, status_code, subdir, operation, status = ''):
302 if self.job:
303 self.job.record(status_code, subdir, operation, status)
304 else:
305 if not subdir:
306 subdir = "----"
307 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
308 sys.stderr.write(msg + "\n")
309
310
mblighfa971602008-01-03 01:57:20 +0000311 def ssh_base_command(self, connect_timeout=30):
312 SSH_BASE_COMMAND = '/usr/bin/ssh -a -x -o ' + \
313 'BatchMode=yes -o ConnectTimeout=%d'
314 assert isinstance(connect_timeout, (int, long))
315 assert connect_timeout > 0 # can't disable the timeout
316 return SSH_BASE_COMMAND % connect_timeout
317
318
319 def ssh_command(self, connect_timeout=30):
mblighe6647d12007-10-17 00:00:01 +0000320 """Construct an ssh command with proper args for this host."""
mblighfa971602008-01-03 01:57:20 +0000321 ssh = self.ssh_base_command(connect_timeout)
322 return r'%s -l %s -p %d %s' % (ssh,
mbligh0faf91f2007-10-18 03:10:48 +0000323 self.user,
324 self.port,
325 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000326
327
mblighfa971602008-01-03 01:57:20 +0000328 def run(self, command, timeout=30, ignore_status=False,
329 stdout_tee=None, stderr_tee=None, connect_timeout=30):
mbligh7d2bde82007-08-02 16:26:10 +0000330 """
331 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000332
333 Args:
334 command: the command line string
335 timeout: time limit in seconds before attempting to
336 kill the running process. The run() function
337 will take a few seconds longer than 'timeout'
338 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000339 ignore_status: do not raise an exception, no matter
340 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000341
342 Returns:
343 a hosts.base_classes.CmdResult object
344
345 Raises:
346 AutoservRunError: the exit code of the command
347 execution was not 0
348 """
mblighadf2aab2007-11-29 18:16:43 +0000349 stdout = stdout_tee or sys.stdout
350 stderr = stderr_tee or sys.stderr
mbligh7995cc62007-11-30 15:53:23 +0000351 print "ssh: %s" % (command,)
mblighadf2aab2007-11-29 18:16:43 +0000352 env = " ".join("=".join(pair) for pair in self.env.iteritems())
mblighfa971602008-01-03 01:57:20 +0000353 full_cmd = '%s "%s %s"' % (self.ssh_command(connect_timeout), env,
mblighadf2aab2007-11-29 18:16:43 +0000354 utils.sh_escape(command))
355 result = utils.run(full_cmd, timeout, ignore_status,
356 stdout, stderr)
mblighdcd57a82007-07-11 23:06:47 +0000357 return result
mbligh7d2bde82007-08-02 16:26:10 +0000358
359
mbligh78669ff2008-01-10 16:33:07 +0000360 def run_grep(self, command, timeout=30, ignore_status=False,
361 stdout_ok_regexp=None, stdout_err_regexp=None,
362 stderr_ok_regexp=None, stderr_err_regexp=None,
363 connect_timeout=30):
364 """
365 Run a command on the remote host and look for regexp
366 in stdout or stderr to determine if the command was
367 successul or not.
368
369 Args:
370 command: the command line string
371 timeout: time limit in seconds before attempting to
372 kill the running process. The run() function
373 will take a few seconds longer than 'timeout'
374 to complete if it has to kill the process.
375 ignore_status: do not raise an exception, no matter
376 what the exit code of the command is.
377 stdout_ok_regexp: regexp that should be in stdout
378 if the command was successul.
379 stdout_err_regexp: regexp that should be in stdout
380 if the command failed.
381 stderr_ok_regexp: regexp that should be in stderr
382 if the command was successul.
383 stderr_err_regexp: regexp that should be in stderr
384 if the command failed.
385
386 Returns:
387 if the command was successul, raises an exception
388 otherwise.
389
390 Raises:
391 AutoservRunError:
392 - the exit code of the command execution was not 0.
393 - If stderr_err_regexp is found in stderr,
394 - If stdout_err_regexp is found in stdout,
395 - If stderr_ok_regexp is not found in stderr.
396 - If stdout_ok_regexp is not found in stdout,
397 """
398
399 # We ignore the status, because we will handle it at the end.
400 result = self.run(command, timeout, ignore_status=True,
401 connect_timeout=connect_timeout)
402
403 # Look for the patterns, in order
404 for (regexp, stream) in ((stderr_err_regexp, result.stderr),
405 (stdout_err_regexp, result.stdout)):
406 if regexp and stream:
407 err_re = re.compile (regexp)
408 if err_re.search(stream):
409 raise AutoservRunError('%s failed, found error pattern: "%s"'
410 % (command, regexp))
411
412 for (regexp, stream) in ((stderr_ok_regexp, result.stderr),
413 (stdout_ok_regexp, result.stdout)):
414 if regexp and stream:
415 ok_re = re.compile (regexp)
416 if ok_re.search(stream):
417 if ok_re.search(stream):
418 return
419
420 if not ignore_status and result.exit_status > 0:
421 raise AutoservRunError("command execution error", result)
422
423
mbligh80d20772007-10-29 17:10:10 +0000424 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
425 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000426 """
427 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000428
mbligha0452c82007-08-08 20:24:57 +0000429 Args:
430 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000431 """
mbligh33ae0902007-11-24 19:27:08 +0000432 self.reboot_setup()
433
mblighde384372007-10-17 04:25:37 +0000434 # forcibly include the "netconsole" kernel arg
435 if self.__netconsole_param:
436 if kernel_args is None:
437 kernel_args = self.__netconsole_param
438 else:
439 kernel_args += " " + self.__netconsole_param
440 # unload the (possibly loaded) module to avoid shutdown issues
441 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000442 if label or kernel_args:
443 self.bootloader.install_boottool()
444 if label:
445 self.bootloader.set_default(label)
446 if kernel_args:
447 if not label:
448 default = int(self.bootloader.get_default())
449 label = self.bootloader.get_titles()[default]
450 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000451 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000452 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000453 try:
mblighf3b78932007-11-07 16:52:47 +0000454 self.run('(sleep 5; reboot) </dev/null >/dev/null 2>&1 &')
mbligh03f4fc72007-11-29 20:56:14 +0000455 except AutoservRunError:
mblighf3b78932007-11-07 16:52:47 +0000456 self.__record("ABORT", None, "reboot.start",
457 "reboot command failed")
mblighcf3d83a2007-11-05 19:21:39 +0000458 raise
mbligha0452c82007-08-08 20:24:57 +0000459 if wait:
mbligh5deff3d2008-01-04 21:21:28 +0000460 self.wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000461 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000462
mbligh7d2bde82007-08-02 16:26:10 +0000463
mblighdcd57a82007-07-11 23:06:47 +0000464 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000465 """
466 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000467
468 Directories will be copied recursively.
469 If a source component is a directory with a trailing slash,
470 the content of the directory will be copied, otherwise, the
471 directory itself and its content will be copied. This
472 behavior is similar to that of the program 'rsync'.
473
474 Args:
475 source: either
476 1) a single file or directory, as a string
477 2) a list of one or more (possibly mixed)
478 files or directories
479 dest: a file or a directory (if source contains a
480 directory or more than one element, you must
481 supply a directory dest)
482
483 Raises:
484 AutoservRunError: the scp command failed
485 """
486 if isinstance(source, types.StringTypes):
487 source= [source]
488
489 processed_source= []
490 for entry in source:
491 if entry.endswith('/'):
492 format_string= '%s@%s:"%s*"'
493 else:
494 format_string= '%s@%s:"%s"'
495 entry= format_string % (self.user, self.hostname,
496 utils.scp_remote_escape(entry))
497 processed_source.append(entry)
498
499 processed_dest= os.path.abspath(dest)
500 if os.path.isdir(dest):
501 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
502 else:
503 processed_dest= utils.sh_escape(processed_dest)
504
505 utils.run('scp -rpq %s "%s"' % (
506 " ".join(processed_source),
507 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000508
509
mblighdcd57a82007-07-11 23:06:47 +0000510 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000511 """
512 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000513
514 Directories will be copied recursively.
515 If a source component is a directory with a trailing slash,
516 the content of the directory will be copied, otherwise, the
517 directory itself and its content will be copied. This
518 behavior is similar to that of the program 'rsync'.
519
520 Args:
521 source: either
522 1) a single file or directory, as a string
523 2) a list of one or more (possibly mixed)
524 files or directories
525 dest: a file or a directory (if source contains a
526 directory or more than one element, you must
527 supply a directory dest)
528
529 Raises:
530 AutoservRunError: the scp command failed
531 """
532 if isinstance(source, types.StringTypes):
533 source= [source]
534
535 processed_source= []
536 for entry in source:
537 if entry.endswith('/'):
538 format_string= '"%s/"*'
539 else:
540 format_string= '"%s"'
541 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
542 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000543
mbligh94befff2007-12-10 18:03:14 +0000544 result = utils.run(r'%s rsync -h' % self.ssh_command(),
545 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000546
mbligh0faf91f2007-10-18 03:10:48 +0000547 remote_dest = '%s@%s:"%s"' % (
548 self.user, self.hostname,
549 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000550 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000551 utils.run('rsync --rsh="%s" -az %s %s' % (
mblighfa971602008-01-03 01:57:20 +0000552 self.ssh_base_command(), " ".join(processed_source),
mbligh0faf91f2007-10-18 03:10:48 +0000553 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000554 else:
mbligh0faf91f2007-10-18 03:10:48 +0000555 utils.run('scp -rpq %s %s' % (
556 " ".join(processed_source),
557 remote_dest))
mblighc42141f2007-11-05 20:25:46 +0000558 self.run('find "%s" -type d | xargs -r chmod o+rx' % dest)
559 self.run('find "%s" -type f | xargs -r chmod o+r' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000560
mblighdcd57a82007-07-11 23:06:47 +0000561 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000562 """
563 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000564 for temporary file storage.
565
566 The directory and its content will be deleted automatically
567 on the destruction of the Host object that was used to obtain
568 it.
569 """
mbligha25b29e2007-08-26 13:58:04 +0000570 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000571 self.tmp_dirs.append(dir_name)
572 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000573
574
mblighdcd57a82007-07-11 23:06:47 +0000575 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000576 """
577 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000578
579 Returns:
580 True if the remote host is up, False otherwise
581 """
582 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000583 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000584 except:
mblighdcd57a82007-07-11 23:06:47 +0000585 return False
mbligheadfbb12007-11-26 23:03:12 +0000586 return True
mbligh7d2bde82007-08-02 16:26:10 +0000587
mbligh7d2bde82007-08-02 16:26:10 +0000588
mblighdcd57a82007-07-11 23:06:47 +0000589 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000590 """
591 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000592
593 In fact, it will wait until an ssh connection to the remote
594 host can be established.
595
596 Args:
597 timeout: time limit in seconds before returning even
598 if the host is not up.
599
600 Returns:
601 True if the host was found to be up, False otherwise
602 """
603 if timeout:
604 end_time= time.time() + timeout
605
606 while not timeout or time.time() < end_time:
607 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000608 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000609 except:
mblighdcd57a82007-07-11 23:06:47 +0000610 pass
611 else:
mbligheadfbb12007-11-26 23:03:12 +0000612 return True
mblighdcd57a82007-07-11 23:06:47 +0000613 time.sleep(1)
614
615 return False
mbligh7d2bde82007-08-02 16:26:10 +0000616
617
mblighdcd57a82007-07-11 23:06:47 +0000618 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000619 """
620 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000621
622 In fact, it will wait until an ssh connection to the remote
623 host fails.
624
625 Args:
626 timeout: time limit in seconds before returning even
627 if the host is not up.
628
629 Returns:
630 True if the host was found to be down, False otherwise
631 """
632 if timeout:
633 end_time= time.time() + timeout
634
635 while not timeout or time.time() < end_time:
636 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000637 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000638 except:
mblighdcd57a82007-07-11 23:06:47 +0000639 return True
mblighdcd57a82007-07-11 23:06:47 +0000640 time.sleep(1)
641
642 return False
mbligh7d2bde82007-08-02 16:26:10 +0000643
644
mblighdbe4a382007-07-26 19:41:28 +0000645 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000646 """
647 Ensure the host is up if it is not then do not proceed;
648 this prevents cacading failures of tests
649 """
mbligha0452c82007-08-08 20:24:57 +0000650 print 'Ensuring that %s is up before continuing' % self.hostname
651 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000652 print "Performing a hardreset on %s" % self.hostname
mbligh4ba0b462007-11-05 23:05:40 +0000653 try:
654 self.hardreset()
mbligh03f4fc72007-11-29 20:56:14 +0000655 except AutoservUnsupportedError:
mbligh4ba0b462007-11-05 23:05:40 +0000656 print "Hardreset is unsupported on %s" % self.hostname
mbligha9563b92007-10-25 14:45:56 +0000657 if not self.wait_up(60 * 30):
658 # 30 minutes should be more than enough
mbligh03f4fc72007-11-29 20:56:14 +0000659 raise AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000660 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000661
662
mblighdcd57a82007-07-11 23:06:47 +0000663 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000664 """
665 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000666 /proc/cpuinfo.
667
668 Returns:
669 The number of CPUs
670 """
671
mbligh5f876ad2007-10-12 23:59:53 +0000672 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000673 cpus = 0
674 for line in proc_cpuinfo.splitlines():
675 if line.startswith('processor'):
676 cpus += 1
677 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000678
679
680 def check_uptime(self):
681 """
682 Check that uptime is available and monotonically increasing.
683 """
684 if not self.ping():
685 raise "Client is not pingable"
686 result = self.run("/bin/cat /proc/uptime", 30)
687 return result.stdout.strip().split()[0]
688
689
690 def get_arch(self):
691 """
692 Get the hardware architecture of the remote machine
693 """
694 arch = self.run('/bin/uname -m').stdout.rstrip()
695 if re.match(r'i\d86$', arch):
696 arch = 'i386'
697 return arch
698
699
700 def get_kernel_ver(self):
701 """
702 Get the kernel version of the remote machine
703 """
704 return self.run('/bin/uname -r').stdout.rstrip()
705
706
707 def get_cmdline(self):
708 """
709 Get the kernel command line of the remote machine
710 """
711 return self.run('cat /proc/cmdline').stdout.rstrip()
712
713
714 def ping(self):
715 """
716 Ping the remote system, and return whether it's available
717 """
718 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
719 rc = utils.system(fpingcmd, ignore_status = 1)
720 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000721
mblighf014ff42007-11-26 21:33:11 +0000722
mbligh4cfa76a2007-11-26 20:45:16 +0000723 def ssh_ping(self, timeout = 60):
mblighfa971602008-01-03 01:57:20 +0000724 self.run('true', connect_timeout = timeout)
mblighda13d542008-01-03 16:28:34 +0000725
726
727 def get_autodir(self):
728 return self.autodir