blob: 298b216f3bc6c39a5182c15519174c5430111dfb [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,
55 netconsole_log=None, netconsole_port=6666):
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
mbligh91334902007-09-28 01:47:59 +000070
mbligh9708f732007-10-18 03:18:54 +000071 super(SSHHost, self).__init__()
72
mbligh3409ee72007-10-16 23:58:33 +000073 self.conmux_server = conmux_server
74 self.conmux_attach = self.__find_console_attach(conmux_attach)
75 self.logger_pid = None
mblighde384372007-10-17 04:25:37 +000076 self.__start_console_log(conmux_log)
mblighe6c995f2007-10-26 19:43:01 +000077 self.warning_pid = None
78 self.__start_warning_log(conmux_warnings)
mbligh3409ee72007-10-16 23:58:33 +000079
mbligha0452c82007-08-08 20:24:57 +000080 self.bootloader = bootloader.Bootloader(self)
mbligh7d2bde82007-08-02 16:26:10 +000081
mblighc0e92392007-11-05 19:10:10 +000082 self.__netconsole_param = ""
mblighde384372007-10-17 04:25:37 +000083 self.netlogger_pid = None
mblighc0e92392007-11-05 19:10:10 +000084 if netconsole_log:
85 self.__init_netconsole_params(netconsole_port)
86 self.__start_netconsole_log(netconsole_log, netconsole_port)
87 self.__load_netconsole_module()
mblighde384372007-10-17 04:25:37 +000088
mbligh7d2bde82007-08-02 16:26:10 +000089
mblighdcd57a82007-07-11 23:06:47 +000090 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +000091 """
92 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000093 """
94 for dir in self.tmp_dirs:
95 try:
96 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
mbligh03f4fc72007-11-29 20:56:14 +000097 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +000098 pass
mblighde384372007-10-17 04:25:37 +000099 # kill the console logger
mbligh7364ae42007-10-18 03:20:34 +0000100 if getattr(self, 'logger_pid', None):
mbligh3409ee72007-10-16 23:58:33 +0000101 try:
102 pgid = os.getpgid(self.logger_pid)
103 os.killpg(pgid, signal.SIGTERM)
104 except OSError:
105 pass
mblighde384372007-10-17 04:25:37 +0000106 # kill the netconsole logger
mbligh7364ae42007-10-18 03:20:34 +0000107 if getattr(self, 'netlogger_pid', None):
mblighe6c995f2007-10-26 19:43:01 +0000108 self.__unload_netconsole_module()
mblighde384372007-10-17 04:25:37 +0000109 try:
110 os.kill(self.netlogger_pid, signal.SIGTERM)
111 except OSError:
112 pass
mblighe6c995f2007-10-26 19:43:01 +0000113 # kill the warning logger
114 if getattr(self, 'warning_pid', None):
115 try:
116 pgid = os.getpgid(self.warning_pid)
117 os.killpg(pgid, signal.SIGTERM)
118 except OSError:
119 pass
mblighde384372007-10-17 04:25:37 +0000120
121
122 def __init_netconsole_params(self, port):
123 """
124 Connect to the remote machine and determine the values to use for the
125 required netconsole parameters.
126 """
mblighde384372007-10-17 04:25:37 +0000127 # PROBLEM: on machines with multiple IPs this may not make any sense
128 # It also doesn't work with IPv6
129 remote_ip = socket.gethostbyname(self.hostname)
130 local_ip = socket.gethostbyname(socket.gethostname())
131 # Get the gateway of the remote machine
132 try:
133 traceroute = self.run('traceroute -n %s' % local_ip)
mbligh03f4fc72007-11-29 20:56:14 +0000134 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000135 return
136 first_node = traceroute.stdout.split("\n")[0]
137 match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
138 if match:
139 router_ip = match.group(1)
140 else:
141 return
142 # Look up the MAC address of the gateway
143 try:
144 self.run('ping -c 1 %s' % router_ip)
145 arp = self.run('arp -n -a %s' % router_ip)
mbligh03f4fc72007-11-29 20:56:14 +0000146 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000147 return
148 match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
149 if match:
150 gateway_mac = match.group(1)
151 else:
152 return
153 self.__netconsole_param = 'netconsole=@%s/,%s@%s/%s' % (remote_ip,
154 port,
155 local_ip,
156 gateway_mac)
157
158
159 def __start_netconsole_log(self, logfilename, port):
160 """
161 Log the output of netconsole to a specified file
162 """
163 if logfilename == None:
164 return
165 cmd = ['nc', '-u', '-l', '-p', str(port)]
mblighd2fc50f2007-10-23 22:38:00 +0000166 logger = subprocess.Popen(cmd, stdout=open(logfilename, "a", 0))
mblighde384372007-10-17 04:25:37 +0000167 self.netlogger_pid = logger.pid
168
169
170 def __load_netconsole_module(self):
171 """
172 Make a best effort to load the netconsole module.
173
174 Note that loading the module can fail even when the remote machine is
175 working correctly if netconsole is already compiled into the kernel
176 and started.
177 """
mblighc0e92392007-11-05 19:10:10 +0000178 if not self.__netconsole_param:
179 return
mblighde384372007-10-17 04:25:37 +0000180 try:
181 self.run('modprobe netconsole %s' % self.__netconsole_param)
mbligh03f4fc72007-11-29 20:56:14 +0000182 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000183 # if it fails there isn't much we can do, just keep going
184 pass
185
186
187 def __unload_netconsole_module(self):
188 try:
189 self.run('modprobe -r netconsole')
mbligh03f4fc72007-11-29 20:56:14 +0000190 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000191 pass
mbligh3409ee72007-10-16 23:58:33 +0000192
193
mbligh6a0010f2007-10-25 15:45:21 +0000194 def _wait_for_restart(self, timeout):
mblighd567f722007-10-30 15:37:33 +0000195 if not self.wait_down(300): # Make sure he's dead, Jim
mblighf3b78932007-11-07 16:52:47 +0000196 self.__record("ABORT", None, "reboot.verify", "shutdown failed")
mbligh03f4fc72007-11-29 20:56:14 +0000197 raise AutoservRebootError("Host did not shut down")
mbligh3409ee72007-10-16 23:58:33 +0000198 self.wait_up(timeout)
199 time.sleep(2) # this is needed for complete reliability
mblighcf3d83a2007-11-05 19:21:39 +0000200 if self.wait_up(timeout):
mbligh30270302007-11-05 20:33:52 +0000201 self.__record("GOOD", None, "reboot.verify")
mblighcf3d83a2007-11-05 19:21:39 +0000202 else:
mblighf3b78932007-11-07 16:52:47 +0000203 self.__record("ABORT", None, "reboot.verify", "bringup failed")
mbligh03f4fc72007-11-29 20:56:14 +0000204 raise AutoservRebootError("Host did not return from reboot")
mbligh3409ee72007-10-16 23:58:33 +0000205 print "Reboot complete"
206
207
mbligh80d20772007-10-29 17:10:10 +0000208 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000209 """
210 Reach out and slap the box in the power switch
211 """
mblighf3b78932007-11-07 16:52:47 +0000212 self.__record("GOOD", None, "reboot.start", "hard reset")
213 if not self.__console_run(r"'~$hardreset'"):
214 self.__record("ABORT", None, "reboot.start", "hard reset unavailable")
mbligh03f4fc72007-11-29 20:56:14 +0000215 raise AutoservUnsupportedError
mblighba81c682007-10-25 15:35:59 +0000216 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000217 self._wait_for_restart(timeout)
mbligh3409ee72007-10-16 23:58:33 +0000218
219
mblighe6c995f2007-10-26 19:43:01 +0000220 def __conmux_hostname(self):
221 if self.conmux_server:
222 return '%s/%s' % (self.conmux_server, self.hostname)
223 else:
224 return self.hostname
225
226
mbligh3409ee72007-10-16 23:58:33 +0000227 def __start_console_log(self, logfilename):
228 """
229 Log the output of the console session to a specified file
230 """
231 if logfilename == None:
232 return
233 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
234 return
mblighe6c995f2007-10-26 19:43:01 +0000235 cmd = [self.conmux_attach, self.__conmux_hostname(), 'cat - >> %s' % logfilename]
mbligh3409ee72007-10-16 23:58:33 +0000236 logger = subprocess.Popen(cmd,
237 stderr=open('/dev/null', 'w'),
238 preexec_fn=lambda: os.setpgid(0, 0))
239 self.logger_pid = logger.pid
240
241
mbligh94befff2007-12-10 18:03:14 +0000242 def __start_warning_log(self, logfilename):
mblighe6c995f2007-10-26 19:43:01 +0000243 """
244 Log the output of the warning monitor to a specified file
245 """
mbligh8bfa9f92007-11-24 19:29:30 +0000246 if logfilename == None or not os.path.isdir('debug'):
mblighe6c995f2007-10-26 19:43:01 +0000247 return
248 script_path = os.path.join(self.serverdir, 'warning_monitor')
mbligh7c5452d2007-11-05 18:35:31 +0000249 script_cmd = 'expect %s %s >> %s' % (script_path,
250 self.hostname,
251 logfilename)
mblighe6c995f2007-10-26 19:43:01 +0000252 if self.conmux_server:
253 to = '%s/%s'
254 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
255 logger = subprocess.Popen(cmd,
mbligh7c5452d2007-11-05 18:35:31 +0000256 stderr=open('debug/conmux.log', 'a', 0),
mblighe6c995f2007-10-26 19:43:01 +0000257 preexec_fn=lambda: os.setpgid(0, 0))
258 self.warning_pid = logger.pid
259
260
mbligh3409ee72007-10-16 23:58:33 +0000261 def __find_console_attach(self, conmux_attach):
262 if conmux_attach:
263 return conmux_attach
264 try:
265 res = utils.run('which conmux-attach')
266 if res.exit_status == 0:
267 return res.stdout.strip()
mbligh03f4fc72007-11-29 20:56:14 +0000268 except AutoservRunError, e:
mbligh3409ee72007-10-16 23:58:33 +0000269 pass
mbligh9708f732007-10-18 03:18:54 +0000270 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000271 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000272 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000273 '..', 'autotest',
274 'conmux', 'conmux-attach')
275 locations = [autotest_conmux,
276 autotest_conmux_alt,
277 '/usr/local/conmux/bin/conmux-attach',
278 '/usr/bin/conmux-attach']
279 for l in locations:
280 if os.path.exists(l):
281 return l
282
283 print "WARNING: conmux-attach not found on autoserv server"
284 return None
285
286
287 def __console_run(self, cmd):
288 """
289 Send a command to the conmux session
290 """
291 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
292 return False
mbligh3409ee72007-10-16 23:58:33 +0000293 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000294 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000295 cmd)
296 result = os.system(cmd)
297 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000298
299
mbligh31a49de2007-11-05 18:41:19 +0000300 def __record(self, status_code, subdir, operation, status = ''):
301 if self.job:
302 self.job.record(status_code, subdir, operation, status)
303 else:
304 if not subdir:
305 subdir = "----"
306 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
307 sys.stderr.write(msg + "\n")
308
309
mblighfa971602008-01-03 01:57:20 +0000310 def ssh_base_command(self, connect_timeout=30):
311 SSH_BASE_COMMAND = '/usr/bin/ssh -a -x -o ' + \
312 'BatchMode=yes -o ConnectTimeout=%d'
313 assert isinstance(connect_timeout, (int, long))
314 assert connect_timeout > 0 # can't disable the timeout
315 return SSH_BASE_COMMAND % connect_timeout
316
317
318 def ssh_command(self, connect_timeout=30):
mblighe6647d12007-10-17 00:00:01 +0000319 """Construct an ssh command with proper args for this host."""
mblighfa971602008-01-03 01:57:20 +0000320 ssh = self.ssh_base_command(connect_timeout)
321 return r'%s -l %s -p %d %s' % (ssh,
mbligh0faf91f2007-10-18 03:10:48 +0000322 self.user,
323 self.port,
324 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000325
326
mblighfa971602008-01-03 01:57:20 +0000327 def run(self, command, timeout=30, ignore_status=False,
328 stdout_tee=None, stderr_tee=None, connect_timeout=30):
mbligh7d2bde82007-08-02 16:26:10 +0000329 """
330 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000331
332 Args:
333 command: the command line string
334 timeout: time limit in seconds before attempting to
335 kill the running process. The run() function
336 will take a few seconds longer than 'timeout'
337 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000338 ignore_status: do not raise an exception, no matter
339 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000340
341 Returns:
342 a hosts.base_classes.CmdResult object
343
344 Raises:
345 AutoservRunError: the exit code of the command
346 execution was not 0
347 """
mblighadf2aab2007-11-29 18:16:43 +0000348 stdout = stdout_tee or sys.stdout
349 stderr = stderr_tee or sys.stderr
mbligh7995cc62007-11-30 15:53:23 +0000350 print "ssh: %s" % (command,)
mblighadf2aab2007-11-29 18:16:43 +0000351 env = " ".join("=".join(pair) for pair in self.env.iteritems())
mblighfa971602008-01-03 01:57:20 +0000352 full_cmd = '%s "%s %s"' % (self.ssh_command(connect_timeout), env,
mblighadf2aab2007-11-29 18:16:43 +0000353 utils.sh_escape(command))
354 result = utils.run(full_cmd, timeout, ignore_status,
355 stdout, stderr)
mblighdcd57a82007-07-11 23:06:47 +0000356 return result
mbligh7d2bde82007-08-02 16:26:10 +0000357
358
mbligh80d20772007-10-29 17:10:10 +0000359 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
360 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000361 """
362 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000363
mbligha0452c82007-08-08 20:24:57 +0000364 Args:
365 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000366 """
mbligh33ae0902007-11-24 19:27:08 +0000367 self.reboot_setup()
368
mblighde384372007-10-17 04:25:37 +0000369 # forcibly include the "netconsole" kernel arg
370 if self.__netconsole_param:
371 if kernel_args is None:
372 kernel_args = self.__netconsole_param
373 else:
374 kernel_args += " " + self.__netconsole_param
375 # unload the (possibly loaded) module to avoid shutdown issues
376 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000377 if label or kernel_args:
378 self.bootloader.install_boottool()
379 if label:
380 self.bootloader.set_default(label)
381 if kernel_args:
382 if not label:
383 default = int(self.bootloader.get_default())
384 label = self.bootloader.get_titles()[default]
385 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000386 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000387 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000388 try:
mblighf3b78932007-11-07 16:52:47 +0000389 self.run('(sleep 5; reboot) </dev/null >/dev/null 2>&1 &')
mbligh03f4fc72007-11-29 20:56:14 +0000390 except AutoservRunError:
mblighf3b78932007-11-07 16:52:47 +0000391 self.__record("ABORT", None, "reboot.start",
392 "reboot command failed")
mblighcf3d83a2007-11-05 19:21:39 +0000393 raise
mbligha0452c82007-08-08 20:24:57 +0000394 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000395 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000396 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000397
mbligh7d2bde82007-08-02 16:26:10 +0000398
mblighdcd57a82007-07-11 23:06:47 +0000399 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000400 """
401 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000402
403 Directories will be copied recursively.
404 If a source component is a directory with a trailing slash,
405 the content of the directory will be copied, otherwise, the
406 directory itself and its content will be copied. This
407 behavior is similar to that of the program 'rsync'.
408
409 Args:
410 source: either
411 1) a single file or directory, as a string
412 2) a list of one or more (possibly mixed)
413 files or directories
414 dest: a file or a directory (if source contains a
415 directory or more than one element, you must
416 supply a directory dest)
417
418 Raises:
419 AutoservRunError: the scp command failed
420 """
421 if isinstance(source, types.StringTypes):
422 source= [source]
423
424 processed_source= []
425 for entry in source:
426 if entry.endswith('/'):
427 format_string= '%s@%s:"%s*"'
428 else:
429 format_string= '%s@%s:"%s"'
430 entry= format_string % (self.user, self.hostname,
431 utils.scp_remote_escape(entry))
432 processed_source.append(entry)
433
434 processed_dest= os.path.abspath(dest)
435 if os.path.isdir(dest):
436 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
437 else:
438 processed_dest= utils.sh_escape(processed_dest)
439
440 utils.run('scp -rpq %s "%s"' % (
441 " ".join(processed_source),
442 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000443
444
mblighdcd57a82007-07-11 23:06:47 +0000445 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000446 """
447 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000448
449 Directories will be copied recursively.
450 If a source component is a directory with a trailing slash,
451 the content of the directory will be copied, otherwise, the
452 directory itself and its content will be copied. This
453 behavior is similar to that of the program 'rsync'.
454
455 Args:
456 source: either
457 1) a single file or directory, as a string
458 2) a list of one or more (possibly mixed)
459 files or directories
460 dest: a file or a directory (if source contains a
461 directory or more than one element, you must
462 supply a directory dest)
463
464 Raises:
465 AutoservRunError: the scp command failed
466 """
467 if isinstance(source, types.StringTypes):
468 source= [source]
469
470 processed_source= []
471 for entry in source:
472 if entry.endswith('/'):
473 format_string= '"%s/"*'
474 else:
475 format_string= '"%s"'
476 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
477 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000478
mbligh94befff2007-12-10 18:03:14 +0000479 result = utils.run(r'%s rsync -h' % self.ssh_command(),
480 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000481
mbligh0faf91f2007-10-18 03:10:48 +0000482 remote_dest = '%s@%s:"%s"' % (
483 self.user, self.hostname,
484 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000485 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000486 utils.run('rsync --rsh="%s" -az %s %s' % (
mblighfa971602008-01-03 01:57:20 +0000487 self.ssh_base_command(), " ".join(processed_source),
mbligh0faf91f2007-10-18 03:10:48 +0000488 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000489 else:
mbligh0faf91f2007-10-18 03:10:48 +0000490 utils.run('scp -rpq %s %s' % (
491 " ".join(processed_source),
492 remote_dest))
mblighc42141f2007-11-05 20:25:46 +0000493 self.run('find "%s" -type d | xargs -r chmod o+rx' % dest)
494 self.run('find "%s" -type f | xargs -r chmod o+r' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000495
mblighdcd57a82007-07-11 23:06:47 +0000496 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000497 """
498 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000499 for temporary file storage.
500
501 The directory and its content will be deleted automatically
502 on the destruction of the Host object that was used to obtain
503 it.
504 """
mbligha25b29e2007-08-26 13:58:04 +0000505 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000506 self.tmp_dirs.append(dir_name)
507 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000508
509
mblighdcd57a82007-07-11 23:06:47 +0000510 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000511 """
512 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000513
514 Returns:
515 True if the remote host is up, False otherwise
516 """
517 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000518 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000519 except:
mblighdcd57a82007-07-11 23:06:47 +0000520 return False
mbligheadfbb12007-11-26 23:03:12 +0000521 return True
mbligh7d2bde82007-08-02 16:26:10 +0000522
mbligh7d2bde82007-08-02 16:26:10 +0000523
mblighdcd57a82007-07-11 23:06:47 +0000524 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000525 """
526 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000527
528 In fact, it will wait until an ssh connection to the remote
529 host can be established.
530
531 Args:
532 timeout: time limit in seconds before returning even
533 if the host is not up.
534
535 Returns:
536 True if the host was found to be up, False otherwise
537 """
538 if timeout:
539 end_time= time.time() + timeout
540
541 while not timeout or time.time() < end_time:
542 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000543 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000544 except:
mblighdcd57a82007-07-11 23:06:47 +0000545 pass
546 else:
mbligheadfbb12007-11-26 23:03:12 +0000547 return True
mblighdcd57a82007-07-11 23:06:47 +0000548 time.sleep(1)
549
550 return False
mbligh7d2bde82007-08-02 16:26:10 +0000551
552
mblighdcd57a82007-07-11 23:06:47 +0000553 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000554 """
555 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000556
557 In fact, it will wait until an ssh connection to the remote
558 host fails.
559
560 Args:
561 timeout: time limit in seconds before returning even
562 if the host is not up.
563
564 Returns:
565 True if the host was found to be down, False otherwise
566 """
567 if timeout:
568 end_time= time.time() + timeout
569
570 while not timeout or time.time() < end_time:
571 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000572 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000573 except:
mblighdcd57a82007-07-11 23:06:47 +0000574 return True
mblighdcd57a82007-07-11 23:06:47 +0000575 time.sleep(1)
576
577 return False
mbligh7d2bde82007-08-02 16:26:10 +0000578
579
mblighdbe4a382007-07-26 19:41:28 +0000580 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000581 """
582 Ensure the host is up if it is not then do not proceed;
583 this prevents cacading failures of tests
584 """
mbligha0452c82007-08-08 20:24:57 +0000585 print 'Ensuring that %s is up before continuing' % self.hostname
586 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000587 print "Performing a hardreset on %s" % self.hostname
mbligh4ba0b462007-11-05 23:05:40 +0000588 try:
589 self.hardreset()
mbligh03f4fc72007-11-29 20:56:14 +0000590 except AutoservUnsupportedError:
mbligh4ba0b462007-11-05 23:05:40 +0000591 print "Hardreset is unsupported on %s" % self.hostname
mbligha9563b92007-10-25 14:45:56 +0000592 if not self.wait_up(60 * 30):
593 # 30 minutes should be more than enough
mbligh03f4fc72007-11-29 20:56:14 +0000594 raise AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000595 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000596
597
mblighdcd57a82007-07-11 23:06:47 +0000598 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000599 """
600 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000601 /proc/cpuinfo.
602
603 Returns:
604 The number of CPUs
605 """
606
mbligh5f876ad2007-10-12 23:59:53 +0000607 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000608 cpus = 0
609 for line in proc_cpuinfo.splitlines():
610 if line.startswith('processor'):
611 cpus += 1
612 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000613
614
615 def check_uptime(self):
616 """
617 Check that uptime is available and monotonically increasing.
618 """
619 if not self.ping():
620 raise "Client is not pingable"
621 result = self.run("/bin/cat /proc/uptime", 30)
622 return result.stdout.strip().split()[0]
623
624
625 def get_arch(self):
626 """
627 Get the hardware architecture of the remote machine
628 """
629 arch = self.run('/bin/uname -m').stdout.rstrip()
630 if re.match(r'i\d86$', arch):
631 arch = 'i386'
632 return arch
633
634
635 def get_kernel_ver(self):
636 """
637 Get the kernel version of the remote machine
638 """
639 return self.run('/bin/uname -r').stdout.rstrip()
640
641
642 def get_cmdline(self):
643 """
644 Get the kernel command line of the remote machine
645 """
646 return self.run('cat /proc/cmdline').stdout.rstrip()
647
648
649 def ping(self):
650 """
651 Ping the remote system, and return whether it's available
652 """
653 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
654 rc = utils.system(fpingcmd, ignore_status = 1)
655 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000656
mblighf014ff42007-11-26 21:33:11 +0000657
mbligh4cfa76a2007-11-26 20:45:16 +0000658 def ssh_ping(self, timeout = 60):
mblighfa971602008-01-03 01:57:20 +0000659 self.run('true', connect_timeout = timeout)