blob: 30ef81fe5f48cffd7290df434b0b5571cabe6057 [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
mblighadf2aab2007-11-29 18:16:43 +000049 SSH_BASE_COMMAND = '/usr/bin/ssh -a -x -o ' + \
50 'BatchMode=yes -o ConnectTimeout=30'
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,
mbligh7c5452d2007-11-05 18:35:31 +000055 conmux_log="console.log", conmux_warnings="status.log",
mblighe6c995f2007-10-26 19:43:01 +000056 conmux_server=None, conmux_attach=None,
57 netconsole_log=None, netconsole_port=6666):
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
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
76 self.conmux_attach = self.__find_console_attach(conmux_attach)
77 self.logger_pid = None
mblighde384372007-10-17 04:25:37 +000078 self.__start_console_log(conmux_log)
mblighe6c995f2007-10-26 19:43:01 +000079 self.warning_pid = None
80 self.__start_warning_log(conmux_warnings)
mbligh3409ee72007-10-16 23:58:33 +000081
mbligha0452c82007-08-08 20:24:57 +000082 self.bootloader = bootloader.Bootloader(self)
mbligh7d2bde82007-08-02 16:26:10 +000083
mblighc0e92392007-11-05 19:10:10 +000084 self.__netconsole_param = ""
mblighde384372007-10-17 04:25:37 +000085 self.netlogger_pid = None
mblighc0e92392007-11-05 19:10:10 +000086 if netconsole_log:
87 self.__init_netconsole_params(netconsole_port)
88 self.__start_netconsole_log(netconsole_log, netconsole_port)
89 self.__load_netconsole_module()
mblighde384372007-10-17 04:25:37 +000090
mbligh7d2bde82007-08-02 16:26:10 +000091
mblighdcd57a82007-07-11 23:06:47 +000092 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +000093 """
94 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000095 """
96 for dir in self.tmp_dirs:
97 try:
98 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
mbligh03f4fc72007-11-29 20:56:14 +000099 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000100 pass
mblighde384372007-10-17 04:25:37 +0000101 # kill the console logger
mbligh7364ae42007-10-18 03:20:34 +0000102 if getattr(self, 'logger_pid', None):
mbligh3409ee72007-10-16 23:58:33 +0000103 try:
104 pgid = os.getpgid(self.logger_pid)
105 os.killpg(pgid, signal.SIGTERM)
106 except OSError:
107 pass
mblighde384372007-10-17 04:25:37 +0000108 # kill the netconsole logger
mbligh7364ae42007-10-18 03:20:34 +0000109 if getattr(self, 'netlogger_pid', None):
mblighe6c995f2007-10-26 19:43:01 +0000110 self.__unload_netconsole_module()
mblighde384372007-10-17 04:25:37 +0000111 try:
112 os.kill(self.netlogger_pid, signal.SIGTERM)
113 except OSError:
114 pass
mblighe6c995f2007-10-26 19:43:01 +0000115 # kill the warning logger
116 if getattr(self, 'warning_pid', None):
117 try:
118 pgid = os.getpgid(self.warning_pid)
119 os.killpg(pgid, signal.SIGTERM)
120 except OSError:
121 pass
mblighde384372007-10-17 04:25:37 +0000122
123
124 def __init_netconsole_params(self, port):
125 """
126 Connect to the remote machine and determine the values to use for the
127 required netconsole parameters.
128 """
mblighde384372007-10-17 04:25:37 +0000129 # PROBLEM: on machines with multiple IPs this may not make any sense
130 # It also doesn't work with IPv6
131 remote_ip = socket.gethostbyname(self.hostname)
132 local_ip = socket.gethostbyname(socket.gethostname())
133 # Get the gateway of the remote machine
134 try:
135 traceroute = self.run('traceroute -n %s' % local_ip)
mbligh03f4fc72007-11-29 20:56:14 +0000136 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000137 return
138 first_node = traceroute.stdout.split("\n")[0]
139 match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
140 if match:
141 router_ip = match.group(1)
142 else:
143 return
144 # Look up the MAC address of the gateway
145 try:
146 self.run('ping -c 1 %s' % router_ip)
147 arp = self.run('arp -n -a %s' % router_ip)
mbligh03f4fc72007-11-29 20:56:14 +0000148 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000149 return
150 match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
151 if match:
152 gateway_mac = match.group(1)
153 else:
154 return
155 self.__netconsole_param = 'netconsole=@%s/,%s@%s/%s' % (remote_ip,
156 port,
157 local_ip,
158 gateway_mac)
159
160
161 def __start_netconsole_log(self, logfilename, port):
162 """
163 Log the output of netconsole to a specified file
164 """
165 if logfilename == None:
166 return
167 cmd = ['nc', '-u', '-l', '-p', str(port)]
mblighd2fc50f2007-10-23 22:38:00 +0000168 logger = subprocess.Popen(cmd, stdout=open(logfilename, "a", 0))
mblighde384372007-10-17 04:25:37 +0000169 self.netlogger_pid = logger.pid
170
171
172 def __load_netconsole_module(self):
173 """
174 Make a best effort to load the netconsole module.
175
176 Note that loading the module can fail even when the remote machine is
177 working correctly if netconsole is already compiled into the kernel
178 and started.
179 """
mblighc0e92392007-11-05 19:10:10 +0000180 if not self.__netconsole_param:
181 return
mblighde384372007-10-17 04:25:37 +0000182 try:
183 self.run('modprobe netconsole %s' % self.__netconsole_param)
mbligh03f4fc72007-11-29 20:56:14 +0000184 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000185 # if it fails there isn't much we can do, just keep going
186 pass
187
188
189 def __unload_netconsole_module(self):
190 try:
191 self.run('modprobe -r netconsole')
mbligh03f4fc72007-11-29 20:56:14 +0000192 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000193 pass
mbligh3409ee72007-10-16 23:58:33 +0000194
195
mbligh6a0010f2007-10-25 15:45:21 +0000196 def _wait_for_restart(self, timeout):
mblighd567f722007-10-30 15:37:33 +0000197 if not self.wait_down(300): # Make sure he's dead, Jim
mblighf3b78932007-11-07 16:52:47 +0000198 self.__record("ABORT", None, "reboot.verify", "shutdown failed")
mbligh03f4fc72007-11-29 20:56:14 +0000199 raise AutoservRebootError("Host did not shut down")
mbligh3409ee72007-10-16 23:58:33 +0000200 self.wait_up(timeout)
201 time.sleep(2) # this is needed for complete reliability
mblighcf3d83a2007-11-05 19:21:39 +0000202 if self.wait_up(timeout):
mbligh30270302007-11-05 20:33:52 +0000203 self.__record("GOOD", None, "reboot.verify")
mblighcf3d83a2007-11-05 19:21:39 +0000204 else:
mblighf3b78932007-11-07 16:52:47 +0000205 self.__record("ABORT", None, "reboot.verify", "bringup failed")
mbligh03f4fc72007-11-29 20:56:14 +0000206 raise AutoservRebootError("Host did not return from reboot")
mbligh3409ee72007-10-16 23:58:33 +0000207 print "Reboot complete"
208
209
mbligh80d20772007-10-29 17:10:10 +0000210 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000211 """
212 Reach out and slap the box in the power switch
213 """
mblighf3b78932007-11-07 16:52:47 +0000214 self.__record("GOOD", None, "reboot.start", "hard reset")
215 if not self.__console_run(r"'~$hardreset'"):
216 self.__record("ABORT", None, "reboot.start", "hard reset unavailable")
mbligh03f4fc72007-11-29 20:56:14 +0000217 raise AutoservUnsupportedError
mblighba81c682007-10-25 15:35:59 +0000218 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000219 self._wait_for_restart(timeout)
mbligh3409ee72007-10-16 23:58:33 +0000220
221
mblighe6c995f2007-10-26 19:43:01 +0000222 def __conmux_hostname(self):
223 if self.conmux_server:
224 return '%s/%s' % (self.conmux_server, self.hostname)
225 else:
226 return self.hostname
227
228
mbligh3409ee72007-10-16 23:58:33 +0000229 def __start_console_log(self, logfilename):
230 """
231 Log the output of the console session to a specified file
232 """
233 if logfilename == None:
234 return
235 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
236 return
mblighe6c995f2007-10-26 19:43:01 +0000237 cmd = [self.conmux_attach, self.__conmux_hostname(), 'cat - >> %s' % logfilename]
mbligh3409ee72007-10-16 23:58:33 +0000238 logger = subprocess.Popen(cmd,
239 stderr=open('/dev/null', 'w'),
240 preexec_fn=lambda: os.setpgid(0, 0))
241 self.logger_pid = logger.pid
242
243
mblighe6c995f2007-10-26 19:43:01 +0000244 def __start_warning_log(self, logfilename):
245 """
246 Log the output of the warning monitor to a specified file
247 """
mbligh8bfa9f92007-11-24 19:29:30 +0000248 if logfilename == None or not os.path.isdir('debug'):
mblighe6c995f2007-10-26 19:43:01 +0000249 return
250 script_path = os.path.join(self.serverdir, 'warning_monitor')
mbligh7c5452d2007-11-05 18:35:31 +0000251 script_cmd = 'expect %s %s >> %s' % (script_path,
252 self.hostname,
253 logfilename)
mblighe6c995f2007-10-26 19:43:01 +0000254 if self.conmux_server:
255 to = '%s/%s'
256 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
257 logger = subprocess.Popen(cmd,
mbligh7c5452d2007-11-05 18:35:31 +0000258 stderr=open('debug/conmux.log', 'a', 0),
mblighe6c995f2007-10-26 19:43:01 +0000259 preexec_fn=lambda: os.setpgid(0, 0))
260 self.warning_pid = logger.pid
261
262
mbligh3409ee72007-10-16 23:58:33 +0000263 def __find_console_attach(self, conmux_attach):
264 if conmux_attach:
265 return conmux_attach
266 try:
267 res = utils.run('which conmux-attach')
268 if res.exit_status == 0:
269 return res.stdout.strip()
mbligh03f4fc72007-11-29 20:56:14 +0000270 except AutoservRunError, e:
mbligh3409ee72007-10-16 23:58:33 +0000271 pass
mbligh9708f732007-10-18 03:18:54 +0000272 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000273 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000274 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000275 '..', 'autotest',
276 'conmux', 'conmux-attach')
277 locations = [autotest_conmux,
278 autotest_conmux_alt,
279 '/usr/local/conmux/bin/conmux-attach',
280 '/usr/bin/conmux-attach']
281 for l in locations:
282 if os.path.exists(l):
283 return l
284
285 print "WARNING: conmux-attach not found on autoserv server"
286 return None
287
288
289 def __console_run(self, cmd):
290 """
291 Send a command to the conmux session
292 """
293 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
294 return False
mbligh3409ee72007-10-16 23:58:33 +0000295 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000296 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000297 cmd)
298 result = os.system(cmd)
299 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000300
301
mbligh31a49de2007-11-05 18:41:19 +0000302 def __record(self, status_code, subdir, operation, status = ''):
303 if self.job:
304 self.job.record(status_code, subdir, operation, status)
305 else:
306 if not subdir:
307 subdir = "----"
308 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
309 sys.stderr.write(msg + "\n")
310
311
mblighe6647d12007-10-17 00:00:01 +0000312 def ssh_command(self):
313 """Construct an ssh command with proper args for this host."""
mbligh0faf91f2007-10-18 03:10:48 +0000314 return r'%s -l %s -p %d %s' % (self.SSH_BASE_COMMAND,
315 self.user,
316 self.port,
317 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000318
319
mblighadf2aab2007-11-29 18:16:43 +0000320 def run(self, command, timeout=None, ignore_status=False,
321 stdout_tee=None, stderr_tee=None):
mbligh7d2bde82007-08-02 16:26:10 +0000322 """
323 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000324
325 Args:
326 command: the command line string
327 timeout: time limit in seconds before attempting to
328 kill the running process. The run() function
329 will take a few seconds longer than 'timeout'
330 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000331 ignore_status: do not raise an exception, no matter
332 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000333
334 Returns:
335 a hosts.base_classes.CmdResult object
336
337 Raises:
338 AutoservRunError: the exit code of the command
339 execution was not 0
340 """
mblighadf2aab2007-11-29 18:16:43 +0000341 stdout = stdout_tee or sys.stdout
342 stderr = stderr_tee or sys.stderr
mbligh7995cc62007-11-30 15:53:23 +0000343 print "ssh: %s" % (command,)
mblighadf2aab2007-11-29 18:16:43 +0000344 env = " ".join("=".join(pair) for pair in self.env.iteritems())
345 full_cmd = '%s "%s %s"' % (self.ssh_command(), env,
346 utils.sh_escape(command))
347 result = utils.run(full_cmd, timeout, ignore_status,
348 stdout, stderr)
mblighdcd57a82007-07-11 23:06:47 +0000349 return result
mbligh7d2bde82007-08-02 16:26:10 +0000350
351
mbligh80d20772007-10-29 17:10:10 +0000352 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
353 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000354 """
355 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000356
mbligha0452c82007-08-08 20:24:57 +0000357 Args:
358 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000359 """
mbligh33ae0902007-11-24 19:27:08 +0000360 self.reboot_setup()
361
mblighde384372007-10-17 04:25:37 +0000362 # forcibly include the "netconsole" kernel arg
363 if self.__netconsole_param:
364 if kernel_args is None:
365 kernel_args = self.__netconsole_param
366 else:
367 kernel_args += " " + self.__netconsole_param
368 # unload the (possibly loaded) module to avoid shutdown issues
369 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000370 if label or kernel_args:
371 self.bootloader.install_boottool()
372 if label:
373 self.bootloader.set_default(label)
374 if kernel_args:
375 if not label:
376 default = int(self.bootloader.get_default())
377 label = self.bootloader.get_titles()[default]
378 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000379 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000380 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000381 try:
mblighf3b78932007-11-07 16:52:47 +0000382 self.run('(sleep 5; reboot) </dev/null >/dev/null 2>&1 &')
mbligh03f4fc72007-11-29 20:56:14 +0000383 except AutoservRunError:
mblighf3b78932007-11-07 16:52:47 +0000384 self.__record("ABORT", None, "reboot.start",
385 "reboot command failed")
mblighcf3d83a2007-11-05 19:21:39 +0000386 raise
mbligha0452c82007-08-08 20:24:57 +0000387 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000388 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000389 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000390
mbligh7d2bde82007-08-02 16:26:10 +0000391
mblighdcd57a82007-07-11 23:06:47 +0000392 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000393 """
394 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000395
396 Directories will be copied recursively.
397 If a source component is a directory with a trailing slash,
398 the content of the directory will be copied, otherwise, the
399 directory itself and its content will be copied. This
400 behavior is similar to that of the program 'rsync'.
401
402 Args:
403 source: either
404 1) a single file or directory, as a string
405 2) a list of one or more (possibly mixed)
406 files or directories
407 dest: a file or a directory (if source contains a
408 directory or more than one element, you must
409 supply a directory dest)
410
411 Raises:
412 AutoservRunError: the scp command failed
413 """
414 if isinstance(source, types.StringTypes):
415 source= [source]
416
417 processed_source= []
418 for entry in source:
419 if entry.endswith('/'):
420 format_string= '%s@%s:"%s*"'
421 else:
422 format_string= '%s@%s:"%s"'
423 entry= format_string % (self.user, self.hostname,
424 utils.scp_remote_escape(entry))
425 processed_source.append(entry)
426
427 processed_dest= os.path.abspath(dest)
428 if os.path.isdir(dest):
429 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
430 else:
431 processed_dest= utils.sh_escape(processed_dest)
432
433 utils.run('scp -rpq %s "%s"' % (
434 " ".join(processed_source),
435 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000436
437
mblighdcd57a82007-07-11 23:06:47 +0000438 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000439 """
440 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000441
442 Directories will be copied recursively.
443 If a source component is a directory with a trailing slash,
444 the content of the directory will be copied, otherwise, the
445 directory itself and its content will be copied. This
446 behavior is similar to that of the program 'rsync'.
447
448 Args:
449 source: either
450 1) a single file or directory, as a string
451 2) a list of one or more (possibly mixed)
452 files or directories
453 dest: a file or a directory (if source contains a
454 directory or more than one element, you must
455 supply a directory dest)
456
457 Raises:
458 AutoservRunError: the scp command failed
459 """
460 if isinstance(source, types.StringTypes):
461 source= [source]
462
463 processed_source= []
464 for entry in source:
465 if entry.endswith('/'):
466 format_string= '"%s/"*'
467 else:
468 format_string= '"%s"'
469 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
470 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000471
mblighe6647d12007-10-17 00:00:01 +0000472 result = utils.run(r'%s rsync -h' % self.ssh_command(),
473 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000474
mbligh0faf91f2007-10-18 03:10:48 +0000475 remote_dest = '%s@%s:"%s"' % (
476 self.user, self.hostname,
477 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000478 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000479 utils.run('rsync --rsh="%s" -az %s %s' % (
480 self.SSH_BASE_COMMAND, " ".join(processed_source),
481 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000482 else:
mbligh0faf91f2007-10-18 03:10:48 +0000483 utils.run('scp -rpq %s %s' % (
484 " ".join(processed_source),
485 remote_dest))
mblighc42141f2007-11-05 20:25:46 +0000486 self.run('find "%s" -type d | xargs -r chmod o+rx' % dest)
487 self.run('find "%s" -type f | xargs -r chmod o+r' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000488
mblighdcd57a82007-07-11 23:06:47 +0000489 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000490 """
491 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000492 for temporary file storage.
493
494 The directory and its content will be deleted automatically
495 on the destruction of the Host object that was used to obtain
496 it.
497 """
mbligha25b29e2007-08-26 13:58:04 +0000498 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000499 self.tmp_dirs.append(dir_name)
500 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000501
502
mblighdcd57a82007-07-11 23:06:47 +0000503 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000504 """
505 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000506
507 Returns:
508 True if the remote host is up, False otherwise
509 """
510 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000511 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000512 except:
mblighdcd57a82007-07-11 23:06:47 +0000513 return False
mbligheadfbb12007-11-26 23:03:12 +0000514 return True
mbligh7d2bde82007-08-02 16:26:10 +0000515
mbligh7d2bde82007-08-02 16:26:10 +0000516
mblighdcd57a82007-07-11 23:06:47 +0000517 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000518 """
519 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000520
521 In fact, it will wait until an ssh connection to the remote
522 host can be established.
523
524 Args:
525 timeout: time limit in seconds before returning even
526 if the host is not up.
527
528 Returns:
529 True if the host was found to be up, False otherwise
530 """
531 if timeout:
532 end_time= time.time() + timeout
533
534 while not timeout or time.time() < end_time:
535 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000536 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000537 except:
mblighdcd57a82007-07-11 23:06:47 +0000538 pass
539 else:
mbligheadfbb12007-11-26 23:03:12 +0000540 return True
mblighdcd57a82007-07-11 23:06:47 +0000541 time.sleep(1)
542
543 return False
mbligh7d2bde82007-08-02 16:26:10 +0000544
545
mblighdcd57a82007-07-11 23:06:47 +0000546 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000547 """
548 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000549
550 In fact, it will wait until an ssh connection to the remote
551 host fails.
552
553 Args:
554 timeout: time limit in seconds before returning even
555 if the host is not up.
556
557 Returns:
558 True if the host was found to be down, False otherwise
559 """
560 if timeout:
561 end_time= time.time() + timeout
562
563 while not timeout or time.time() < end_time:
564 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000565 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000566 except:
mblighdcd57a82007-07-11 23:06:47 +0000567 return True
mblighdcd57a82007-07-11 23:06:47 +0000568 time.sleep(1)
569
570 return False
mbligh7d2bde82007-08-02 16:26:10 +0000571
572
mblighdbe4a382007-07-26 19:41:28 +0000573 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000574 """
575 Ensure the host is up if it is not then do not proceed;
576 this prevents cacading failures of tests
577 """
mbligha0452c82007-08-08 20:24:57 +0000578 print 'Ensuring that %s is up before continuing' % self.hostname
579 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000580 print "Performing a hardreset on %s" % self.hostname
mbligh4ba0b462007-11-05 23:05:40 +0000581 try:
582 self.hardreset()
mbligh03f4fc72007-11-29 20:56:14 +0000583 except AutoservUnsupportedError:
mbligh4ba0b462007-11-05 23:05:40 +0000584 print "Hardreset is unsupported on %s" % self.hostname
mbligha9563b92007-10-25 14:45:56 +0000585 if not self.wait_up(60 * 30):
586 # 30 minutes should be more than enough
mbligh03f4fc72007-11-29 20:56:14 +0000587 raise AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000588 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000589
590
mblighdcd57a82007-07-11 23:06:47 +0000591 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000592 """
593 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000594 /proc/cpuinfo.
595
596 Returns:
597 The number of CPUs
598 """
599
mbligh5f876ad2007-10-12 23:59:53 +0000600 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000601 cpus = 0
602 for line in proc_cpuinfo.splitlines():
603 if line.startswith('processor'):
604 cpus += 1
605 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000606
607
608 def check_uptime(self):
609 """
610 Check that uptime is available and monotonically increasing.
611 """
612 if not self.ping():
613 raise "Client is not pingable"
614 result = self.run("/bin/cat /proc/uptime", 30)
615 return result.stdout.strip().split()[0]
616
617
618 def get_arch(self):
619 """
620 Get the hardware architecture of the remote machine
621 """
622 arch = self.run('/bin/uname -m').stdout.rstrip()
623 if re.match(r'i\d86$', arch):
624 arch = 'i386'
625 return arch
626
627
628 def get_kernel_ver(self):
629 """
630 Get the kernel version of the remote machine
631 """
632 return self.run('/bin/uname -r').stdout.rstrip()
633
634
635 def get_cmdline(self):
636 """
637 Get the kernel command line of the remote machine
638 """
639 return self.run('cat /proc/cmdline').stdout.rstrip()
640
641
642 def ping(self):
643 """
644 Ping the remote system, and return whether it's available
645 """
646 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
647 rc = utils.system(fpingcmd, ignore_status = 1)
648 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000649
mblighf014ff42007-11-26 21:33:11 +0000650
mbligh4cfa76a2007-11-26 20:45:16 +0000651 def ssh_ping(self, timeout = 60):
652 self.run('true', timeout = timeout)