blob: d6d64551b5aa64050326a9c40d99650ea74e5d9b [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
mbligh5f876ad2007-10-12 23:59:53 +000022import base_classes, utils, errors, bootloader
mblighdcd57a82007-07-11 23:06:47 +000023
24
25class SSHHost(base_classes.RemoteHost):
mbligh7d2bde82007-08-02 16:26:10 +000026 """
27 This class represents a remote machine controlled through an ssh
mblighdcd57a82007-07-11 23:06:47 +000028 session on which you can run programs.
mbligh7d2bde82007-08-02 16:26:10 +000029
mblighdcd57a82007-07-11 23:06:47 +000030 It is not the machine autoserv is running on. The machine must be
31 configured for password-less login, for example through public key
32 authentication.
mbligh7d2bde82007-08-02 16:26:10 +000033
mbligh3409ee72007-10-16 23:58:33 +000034 It includes support for controlling the machine through a serial
35 console on which you can run programs. If such a serial console is
36 set up on the machine then capabilities such as hard reset and
37 boot strap monitoring are available. If the machine does not have a
38 serial console available then ordinary SSH-based commands will
39 still be available, but attempts to use extensions such as
40 console logging or hard reset will fail silently.
41
mblighdcd57a82007-07-11 23:06:47 +000042 Implementation details:
43 This is a leaf class in an abstract class hierarchy, it must
44 implement the unimplemented methods in parent classes.
45 """
mbligh7d2bde82007-08-02 16:26:10 +000046
mblighc0c84792007-11-05 18:37:55 +000047 SSH_BASE_COMMAND = '/usr/bin/ssh -a -o BatchMode=yes'
mbligh31a49de2007-11-05 18:41:19 +000048 DEFAULT_REBOOT_TIMEOUT = 1800
49 job = None
mbligh0faf91f2007-10-18 03:10:48 +000050
mblighde384372007-10-17 04:25:37 +000051 def __init__(self, hostname, user="root", port=22, initialize=True,
mbligh7c5452d2007-11-05 18:35:31 +000052 conmux_log="console.log", conmux_warnings="status.log",
mblighe6c995f2007-10-26 19:43:01 +000053 conmux_server=None, conmux_attach=None,
54 netconsole_log=None, netconsole_port=6666):
mbligh7d2bde82007-08-02 16:26:10 +000055 """
56 Construct a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000057
58 Args:
59 hostname: network hostname or address of remote machine
60 user: user to log in as on the remote machine
61 port: port the ssh daemon is listening on on the remote
62 machine
mbligh9708f732007-10-18 03:18:54 +000063 """
mblighdcd57a82007-07-11 23:06:47 +000064 self.hostname= hostname
65 self.user= user
66 self.port= port
67 self.tmp_dirs= []
mbligh137a05c2007-10-04 15:56:51 +000068 self.initialize = initialize
mbligh91334902007-09-28 01:47:59 +000069
mbligh9708f732007-10-18 03:18:54 +000070 super(SSHHost, self).__init__()
71
mbligh3409ee72007-10-16 23:58:33 +000072 self.conmux_server = conmux_server
73 self.conmux_attach = self.__find_console_attach(conmux_attach)
74 self.logger_pid = None
mblighde384372007-10-17 04:25:37 +000075 self.__start_console_log(conmux_log)
mblighe6c995f2007-10-26 19:43:01 +000076 self.warning_pid = None
77 self.__start_warning_log(conmux_warnings)
mbligh3409ee72007-10-16 23:58:33 +000078
mbligha0452c82007-08-08 20:24:57 +000079 self.bootloader = bootloader.Bootloader(self)
mbligh7d2bde82007-08-02 16:26:10 +000080
mblighc0e92392007-11-05 19:10:10 +000081 self.__netconsole_param = ""
mblighde384372007-10-17 04:25:37 +000082 self.netlogger_pid = None
mblighc0e92392007-11-05 19:10:10 +000083 if netconsole_log:
84 self.__init_netconsole_params(netconsole_port)
85 self.__start_netconsole_log(netconsole_log, netconsole_port)
86 self.__load_netconsole_module()
mblighde384372007-10-17 04:25:37 +000087
mbligh7d2bde82007-08-02 16:26:10 +000088
mblighdcd57a82007-07-11 23:06:47 +000089 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +000090 """
91 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000092 """
93 for dir in self.tmp_dirs:
94 try:
95 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
96 except errors.AutoservRunError:
97 pass
mblighde384372007-10-17 04:25:37 +000098 # kill the console logger
mbligh7364ae42007-10-18 03:20:34 +000099 if getattr(self, 'logger_pid', None):
mbligh3409ee72007-10-16 23:58:33 +0000100 try:
101 pgid = os.getpgid(self.logger_pid)
102 os.killpg(pgid, signal.SIGTERM)
103 except OSError:
104 pass
mblighde384372007-10-17 04:25:37 +0000105 # kill the netconsole logger
mbligh7364ae42007-10-18 03:20:34 +0000106 if getattr(self, 'netlogger_pid', None):
mblighe6c995f2007-10-26 19:43:01 +0000107 self.__unload_netconsole_module()
mblighde384372007-10-17 04:25:37 +0000108 try:
109 os.kill(self.netlogger_pid, signal.SIGTERM)
110 except OSError:
111 pass
mblighe6c995f2007-10-26 19:43:01 +0000112 # kill the warning logger
113 if getattr(self, 'warning_pid', None):
114 try:
115 pgid = os.getpgid(self.warning_pid)
116 os.killpg(pgid, signal.SIGTERM)
117 except OSError:
118 pass
mblighde384372007-10-17 04:25:37 +0000119
120
121 def __init_netconsole_params(self, port):
122 """
123 Connect to the remote machine and determine the values to use for the
124 required netconsole parameters.
125 """
mblighde384372007-10-17 04:25:37 +0000126 # PROBLEM: on machines with multiple IPs this may not make any sense
127 # It also doesn't work with IPv6
128 remote_ip = socket.gethostbyname(self.hostname)
129 local_ip = socket.gethostbyname(socket.gethostname())
130 # Get the gateway of the remote machine
131 try:
132 traceroute = self.run('traceroute -n %s' % local_ip)
133 except errors.AutoservRunError:
134 return
135 first_node = traceroute.stdout.split("\n")[0]
136 match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
137 if match:
138 router_ip = match.group(1)
139 else:
140 return
141 # Look up the MAC address of the gateway
142 try:
143 self.run('ping -c 1 %s' % router_ip)
144 arp = self.run('arp -n -a %s' % router_ip)
145 except errors.AutoservRunError:
146 return
147 match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
148 if match:
149 gateway_mac = match.group(1)
150 else:
151 return
152 self.__netconsole_param = 'netconsole=@%s/,%s@%s/%s' % (remote_ip,
153 port,
154 local_ip,
155 gateway_mac)
156
157
158 def __start_netconsole_log(self, logfilename, port):
159 """
160 Log the output of netconsole to a specified file
161 """
162 if logfilename == None:
163 return
164 cmd = ['nc', '-u', '-l', '-p', str(port)]
mblighd2fc50f2007-10-23 22:38:00 +0000165 logger = subprocess.Popen(cmd, stdout=open(logfilename, "a", 0))
mblighde384372007-10-17 04:25:37 +0000166 self.netlogger_pid = logger.pid
167
168
169 def __load_netconsole_module(self):
170 """
171 Make a best effort to load the netconsole module.
172
173 Note that loading the module can fail even when the remote machine is
174 working correctly if netconsole is already compiled into the kernel
175 and started.
176 """
mblighc0e92392007-11-05 19:10:10 +0000177 if not self.__netconsole_param:
178 return
mblighde384372007-10-17 04:25:37 +0000179 try:
180 self.run('modprobe netconsole %s' % self.__netconsole_param)
181 except errors.AutoservRunError:
182 # if it fails there isn't much we can do, just keep going
183 pass
184
185
186 def __unload_netconsole_module(self):
187 try:
188 self.run('modprobe -r netconsole')
189 except errors.AutoservRunError:
190 pass
mbligh3409ee72007-10-16 23:58:33 +0000191
192
mbligh6a0010f2007-10-25 15:45:21 +0000193 def _wait_for_restart(self, timeout):
mblighd567f722007-10-30 15:37:33 +0000194 if not self.wait_down(300): # Make sure he's dead, Jim
mblighf3b78932007-11-07 16:52:47 +0000195 self.__record("ABORT", None, "reboot.verify", "shutdown failed")
mblighcf3d83a2007-11-05 19:21:39 +0000196 raise errors.AutoservRebootError("Host did not shut down")
mbligh3409ee72007-10-16 23:58:33 +0000197 self.wait_up(timeout)
198 time.sleep(2) # this is needed for complete reliability
mblighcf3d83a2007-11-05 19:21:39 +0000199 if self.wait_up(timeout):
mbligh30270302007-11-05 20:33:52 +0000200 self.__record("GOOD", None, "reboot.verify")
mblighcf3d83a2007-11-05 19:21:39 +0000201 else:
mblighf3b78932007-11-07 16:52:47 +0000202 self.__record("ABORT", None, "reboot.verify", "bringup failed")
mblighcf3d83a2007-11-05 19:21:39 +0000203 raise errors.AutoservRebootError("Host did not return from reboot")
mbligh3409ee72007-10-16 23:58:33 +0000204 print "Reboot complete"
205
206
mbligh80d20772007-10-29 17:10:10 +0000207 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000208 """
209 Reach out and slap the box in the power switch
210 """
mblighf3b78932007-11-07 16:52:47 +0000211 self.__record("GOOD", None, "reboot.start", "hard reset")
212 if not self.__console_run(r"'~$hardreset'"):
213 self.__record("ABORT", None, "reboot.start", "hard reset unavailable")
mblighba81c682007-10-25 15:35:59 +0000214 raise errors.AutoservUnsupportedError
215 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000216 self._wait_for_restart(timeout)
mbligh3409ee72007-10-16 23:58:33 +0000217
218
mblighe6c995f2007-10-26 19:43:01 +0000219 def __conmux_hostname(self):
220 if self.conmux_server:
221 return '%s/%s' % (self.conmux_server, self.hostname)
222 else:
223 return self.hostname
224
225
mbligh3409ee72007-10-16 23:58:33 +0000226 def __start_console_log(self, logfilename):
227 """
228 Log the output of the console session to a specified file
229 """
230 if logfilename == None:
231 return
232 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
233 return
mblighc61ac532007-11-24 19:25:48 +0000234 assert os.path.isdir(os.path.dirname(logfilename)),\
235 "conmux console log directory doesn't exist. SSHHost(host, conmux_log=None) to disable conmux console logging"
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
mblighe6c995f2007-10-26 19:43:01 +0000243 def __start_warning_log(self, logfilename):
244 """
245 Log the output of the warning monitor to a specified file
246 """
mblighc61ac532007-11-24 19:25:48 +0000247 if logfilename == None:
mblighe6c995f2007-10-26 19:43:01 +0000248 return
mblighc61ac532007-11-24 19:25:48 +0000249 assert os.path.isdir(os.path.dirname(logfilename)),\
250 "conmux warning log directory doesn't exist. SSHHost(host, conmux_warnings=None) to disable logging conmux warnings"
mblighe6c995f2007-10-26 19:43:01 +0000251 script_path = os.path.join(self.serverdir, 'warning_monitor')
mbligh7c5452d2007-11-05 18:35:31 +0000252 script_cmd = 'expect %s %s >> %s' % (script_path,
253 self.hostname,
254 logfilename)
mblighe6c995f2007-10-26 19:43:01 +0000255 if self.conmux_server:
256 to = '%s/%s'
257 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
258 logger = subprocess.Popen(cmd,
mbligh7c5452d2007-11-05 18:35:31 +0000259 stderr=open('debug/conmux.log', 'a', 0),
mblighe6c995f2007-10-26 19:43:01 +0000260 preexec_fn=lambda: os.setpgid(0, 0))
261 self.warning_pid = logger.pid
262
263
mbligh3409ee72007-10-16 23:58:33 +0000264 def __find_console_attach(self, conmux_attach):
265 if conmux_attach:
266 return conmux_attach
267 try:
268 res = utils.run('which conmux-attach')
269 if res.exit_status == 0:
270 return res.stdout.strip()
271 except errors.AutoservRunError, e:
272 pass
mbligh9708f732007-10-18 03:18:54 +0000273 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000274 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000275 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000276 '..', 'autotest',
277 'conmux', 'conmux-attach')
278 locations = [autotest_conmux,
279 autotest_conmux_alt,
280 '/usr/local/conmux/bin/conmux-attach',
281 '/usr/bin/conmux-attach']
282 for l in locations:
283 if os.path.exists(l):
284 return l
285
286 print "WARNING: conmux-attach not found on autoserv server"
287 return None
288
289
290 def __console_run(self, cmd):
291 """
292 Send a command to the conmux session
293 """
294 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
295 return False
mbligh3409ee72007-10-16 23:58:33 +0000296 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000297 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000298 cmd)
299 result = os.system(cmd)
300 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000301
302
mbligh31a49de2007-11-05 18:41:19 +0000303 def __record(self, status_code, subdir, operation, status = ''):
304 if self.job:
305 self.job.record(status_code, subdir, operation, status)
306 else:
307 if not subdir:
308 subdir = "----"
309 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
310 sys.stderr.write(msg + "\n")
311
312
mblighe6647d12007-10-17 00:00:01 +0000313 def ssh_command(self):
314 """Construct an ssh command with proper args for this host."""
mbligh0faf91f2007-10-18 03:10:48 +0000315 return r'%s -l %s -p %d %s' % (self.SSH_BASE_COMMAND,
316 self.user,
317 self.port,
318 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000319
320
mblighcf965b02007-07-25 16:49:45 +0000321 def run(self, command, timeout=None, ignore_status=False):
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 """
341 #~ print "running %s" % (command,)
mblighe6647d12007-10-17 00:00:01 +0000342 result= utils.run(r'%s "%s"' % (self.ssh_command(),
343 utils.sh_escape(command)),
344 timeout, ignore_status)
mblighdcd57a82007-07-11 23:06:47 +0000345 return result
mbligh7d2bde82007-08-02 16:26:10 +0000346
347
mbligh80d20772007-10-29 17:10:10 +0000348 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
349 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000350 """
351 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000352
mbligha0452c82007-08-08 20:24:57 +0000353 Args:
354 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000355 """
mbligh33ae0902007-11-24 19:27:08 +0000356 self.reboot_setup()
357
mblighde384372007-10-17 04:25:37 +0000358 # forcibly include the "netconsole" kernel arg
359 if self.__netconsole_param:
360 if kernel_args is None:
361 kernel_args = self.__netconsole_param
362 else:
363 kernel_args += " " + self.__netconsole_param
364 # unload the (possibly loaded) module to avoid shutdown issues
365 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000366 if label or kernel_args:
367 self.bootloader.install_boottool()
368 if label:
369 self.bootloader.set_default(label)
370 if kernel_args:
371 if not label:
372 default = int(self.bootloader.get_default())
373 label = self.bootloader.get_titles()[default]
374 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000375 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000376 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000377 try:
mblighf3b78932007-11-07 16:52:47 +0000378 self.run('(sleep 5; reboot) </dev/null >/dev/null 2>&1 &')
379 except errors.AutoservRunError:
380 self.__record("ABORT", None, "reboot.start",
381 "reboot command failed")
mblighcf3d83a2007-11-05 19:21:39 +0000382 raise
mbligha0452c82007-08-08 20:24:57 +0000383 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000384 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000385 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000386
mbligh7d2bde82007-08-02 16:26:10 +0000387
mblighdcd57a82007-07-11 23:06:47 +0000388 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000389 """
390 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000391
392 Directories will be copied recursively.
393 If a source component is a directory with a trailing slash,
394 the content of the directory will be copied, otherwise, the
395 directory itself and its content will be copied. This
396 behavior is similar to that of the program 'rsync'.
397
398 Args:
399 source: either
400 1) a single file or directory, as a string
401 2) a list of one or more (possibly mixed)
402 files or directories
403 dest: a file or a directory (if source contains a
404 directory or more than one element, you must
405 supply a directory dest)
406
407 Raises:
408 AutoservRunError: the scp command failed
409 """
410 if isinstance(source, types.StringTypes):
411 source= [source]
412
413 processed_source= []
414 for entry in source:
415 if entry.endswith('/'):
416 format_string= '%s@%s:"%s*"'
417 else:
418 format_string= '%s@%s:"%s"'
419 entry= format_string % (self.user, self.hostname,
420 utils.scp_remote_escape(entry))
421 processed_source.append(entry)
422
423 processed_dest= os.path.abspath(dest)
424 if os.path.isdir(dest):
425 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
426 else:
427 processed_dest= utils.sh_escape(processed_dest)
428
429 utils.run('scp -rpq %s "%s"' % (
430 " ".join(processed_source),
431 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000432
433
mblighdcd57a82007-07-11 23:06:47 +0000434 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000435 """
436 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000437
438 Directories will be copied recursively.
439 If a source component is a directory with a trailing slash,
440 the content of the directory will be copied, otherwise, the
441 directory itself and its content will be copied. This
442 behavior is similar to that of the program 'rsync'.
443
444 Args:
445 source: either
446 1) a single file or directory, as a string
447 2) a list of one or more (possibly mixed)
448 files or directories
449 dest: a file or a directory (if source contains a
450 directory or more than one element, you must
451 supply a directory dest)
452
453 Raises:
454 AutoservRunError: the scp command failed
455 """
456 if isinstance(source, types.StringTypes):
457 source= [source]
458
459 processed_source= []
460 for entry in source:
461 if entry.endswith('/'):
462 format_string= '"%s/"*'
463 else:
464 format_string= '"%s"'
465 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
466 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000467
mblighe6647d12007-10-17 00:00:01 +0000468 result = utils.run(r'%s rsync -h' % self.ssh_command(),
469 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000470
mbligh0faf91f2007-10-18 03:10:48 +0000471 remote_dest = '%s@%s:"%s"' % (
472 self.user, self.hostname,
473 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000474 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000475 utils.run('rsync --rsh="%s" -az %s %s' % (
476 self.SSH_BASE_COMMAND, " ".join(processed_source),
477 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000478 else:
mbligh0faf91f2007-10-18 03:10:48 +0000479 utils.run('scp -rpq %s %s' % (
480 " ".join(processed_source),
481 remote_dest))
mblighc42141f2007-11-05 20:25:46 +0000482 self.run('find "%s" -type d | xargs -r chmod o+rx' % dest)
483 self.run('find "%s" -type f | xargs -r chmod o+r' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000484
mblighdcd57a82007-07-11 23:06:47 +0000485 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000486 """
487 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000488 for temporary file storage.
489
490 The directory and its content will be deleted automatically
491 on the destruction of the Host object that was used to obtain
492 it.
493 """
mbligha25b29e2007-08-26 13:58:04 +0000494 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000495 self.tmp_dirs.append(dir_name)
496 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000497
498
mblighdcd57a82007-07-11 23:06:47 +0000499 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000500 """
501 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000502
503 Returns:
504 True if the remote host is up, False otherwise
505 """
506 try:
507 result= self.run("true", timeout=10)
508 except errors.AutoservRunError:
509 return False
510 else:
511 if result.exit_status == 0:
512 return True
513 else:
mbligh7d2bde82007-08-02 16:26:10 +0000514
mblighdcd57a82007-07-11 23:06:47 +0000515 return False
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:
mblighe9cf9d42007-08-31 08:56:00 +0000536 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000537 result= self.run("true", timeout=run_timeout)
538 except errors.AutoservRunError:
539 pass
540 else:
541 if result.exit_status == 0:
542 return True
543 time.sleep(1)
544
545 return False
mbligh7d2bde82007-08-02 16:26:10 +0000546
547
mblighdcd57a82007-07-11 23:06:47 +0000548 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000549 """
550 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000551
552 In fact, it will wait until an ssh connection to the remote
553 host fails.
554
555 Args:
556 timeout: time limit in seconds before returning even
557 if the host is not up.
558
559 Returns:
560 True if the host was found to be down, False otherwise
561 """
562 if timeout:
563 end_time= time.time() + timeout
564
565 while not timeout or time.time() < end_time:
566 try:
mbligh7e1e9642007-07-31 18:00:45 +0000567 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000568 result= self.run("true", timeout=run_timeout)
569 except errors.AutoservRunError:
570 return True
571 else:
572 if result.aborted:
573 return True
574 time.sleep(1)
575
576 return False
mbligh7d2bde82007-08-02 16:26:10 +0000577
578
mblighdbe4a382007-07-26 19:41:28 +0000579 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000580 """
581 Ensure the host is up if it is not then do not proceed;
582 this prevents cacading failures of tests
583 """
mbligha0452c82007-08-08 20:24:57 +0000584 print 'Ensuring that %s is up before continuing' % self.hostname
585 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000586 print "Performing a hardreset on %s" % self.hostname
mbligh4ba0b462007-11-05 23:05:40 +0000587 try:
588 self.hardreset()
mbligh01aa4c02007-11-05 23:29:33 +0000589 except errors.AutoservUnsupportedError:
mbligh4ba0b462007-11-05 23:05:40 +0000590 print "Hardreset is unsupported on %s" % self.hostname
mbligha9563b92007-10-25 14:45:56 +0000591 if not self.wait_up(60 * 30):
592 # 30 minutes should be more than enough
593 raise errors.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000594 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000595
596
mblighdcd57a82007-07-11 23:06:47 +0000597 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000598 """
599 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000600 /proc/cpuinfo.
601
602 Returns:
603 The number of CPUs
604 """
605
mbligh5f876ad2007-10-12 23:59:53 +0000606 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000607 cpus = 0
608 for line in proc_cpuinfo.splitlines():
609 if line.startswith('processor'):
610 cpus += 1
611 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000612
613
614 def check_uptime(self):
615 """
616 Check that uptime is available and monotonically increasing.
617 """
618 if not self.ping():
619 raise "Client is not pingable"
620 result = self.run("/bin/cat /proc/uptime", 30)
621 return result.stdout.strip().split()[0]
622
623
624 def get_arch(self):
625 """
626 Get the hardware architecture of the remote machine
627 """
628 arch = self.run('/bin/uname -m').stdout.rstrip()
629 if re.match(r'i\d86$', arch):
630 arch = 'i386'
631 return arch
632
633
634 def get_kernel_ver(self):
635 """
636 Get the kernel version of the remote machine
637 """
638 return self.run('/bin/uname -r').stdout.rstrip()
639
640
641 def get_cmdline(self):
642 """
643 Get the kernel command line of the remote machine
644 """
645 return self.run('cat /proc/cmdline').stdout.rstrip()
646
647
648 def ping(self):
649 """
650 Ping the remote system, and return whether it's available
651 """
652 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
653 rc = utils.system(fpingcmd, ignore_status = 1)
654 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000655
656
657 def ssh_ping(self):
658 self.run('ls', timeout=30)