blob: de7dd4aa9b1328c5fcbdd4ae0c0e461394d00542 [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
mblighe6c995f2007-10-26 19:43:01 +0000234 cmd = [self.conmux_attach, self.__conmux_hostname(), 'cat - >> %s' % logfilename]
mbligh3409ee72007-10-16 23:58:33 +0000235 logger = subprocess.Popen(cmd,
236 stderr=open('/dev/null', 'w'),
237 preexec_fn=lambda: os.setpgid(0, 0))
238 self.logger_pid = logger.pid
239
240
mblighe6c995f2007-10-26 19:43:01 +0000241 def __start_warning_log(self, logfilename):
242 """
243 Log the output of the warning monitor to a specified file
244 """
mbligh8b49a322007-11-05 20:36:54 +0000245 if logfilename == None or not os.path.isdir('debug'):
mblighe6c995f2007-10-26 19:43:01 +0000246 return
247 script_path = os.path.join(self.serverdir, 'warning_monitor')
mbligh7c5452d2007-11-05 18:35:31 +0000248 script_cmd = 'expect %s %s >> %s' % (script_path,
249 self.hostname,
250 logfilename)
mblighe6c995f2007-10-26 19:43:01 +0000251 if self.conmux_server:
252 to = '%s/%s'
253 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
254 logger = subprocess.Popen(cmd,
mbligh7c5452d2007-11-05 18:35:31 +0000255 stderr=open('debug/conmux.log', 'a', 0),
mblighe6c995f2007-10-26 19:43:01 +0000256 preexec_fn=lambda: os.setpgid(0, 0))
257 self.warning_pid = logger.pid
258
259
mbligh3409ee72007-10-16 23:58:33 +0000260 def __find_console_attach(self, conmux_attach):
261 if conmux_attach:
262 return conmux_attach
263 try:
264 res = utils.run('which conmux-attach')
265 if res.exit_status == 0:
266 return res.stdout.strip()
267 except errors.AutoservRunError, e:
268 pass
mbligh9708f732007-10-18 03:18:54 +0000269 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000270 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000271 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000272 '..', 'autotest',
273 'conmux', 'conmux-attach')
274 locations = [autotest_conmux,
275 autotest_conmux_alt,
276 '/usr/local/conmux/bin/conmux-attach',
277 '/usr/bin/conmux-attach']
278 for l in locations:
279 if os.path.exists(l):
280 return l
281
282 print "WARNING: conmux-attach not found on autoserv server"
283 return None
284
285
286 def __console_run(self, cmd):
287 """
288 Send a command to the conmux session
289 """
290 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
291 return False
mbligh3409ee72007-10-16 23:58:33 +0000292 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000293 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000294 cmd)
295 result = os.system(cmd)
296 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000297
298
mbligh31a49de2007-11-05 18:41:19 +0000299 def __record(self, status_code, subdir, operation, status = ''):
300 if self.job:
301 self.job.record(status_code, subdir, operation, status)
302 else:
303 if not subdir:
304 subdir = "----"
305 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
306 sys.stderr.write(msg + "\n")
307
308
mblighe6647d12007-10-17 00:00:01 +0000309 def ssh_command(self):
310 """Construct an ssh command with proper args for this host."""
mbligh0faf91f2007-10-18 03:10:48 +0000311 return r'%s -l %s -p %d %s' % (self.SSH_BASE_COMMAND,
312 self.user,
313 self.port,
314 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000315
316
mblighcf965b02007-07-25 16:49:45 +0000317 def run(self, command, timeout=None, ignore_status=False):
mbligh7d2bde82007-08-02 16:26:10 +0000318 """
319 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000320
321 Args:
322 command: the command line string
323 timeout: time limit in seconds before attempting to
324 kill the running process. The run() function
325 will take a few seconds longer than 'timeout'
326 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000327 ignore_status: do not raise an exception, no matter
328 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000329
330 Returns:
331 a hosts.base_classes.CmdResult object
332
333 Raises:
334 AutoservRunError: the exit code of the command
335 execution was not 0
336 """
337 #~ print "running %s" % (command,)
mblighe6647d12007-10-17 00:00:01 +0000338 result= utils.run(r'%s "%s"' % (self.ssh_command(),
339 utils.sh_escape(command)),
340 timeout, ignore_status)
mblighdcd57a82007-07-11 23:06:47 +0000341 return result
mbligh7d2bde82007-08-02 16:26:10 +0000342
343
mbligh80d20772007-10-29 17:10:10 +0000344 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
345 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000346 """
347 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000348
mbligha0452c82007-08-08 20:24:57 +0000349 Args:
350 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000351 """
mblighde384372007-10-17 04:25:37 +0000352 # forcibly include the "netconsole" kernel arg
353 if self.__netconsole_param:
354 if kernel_args is None:
355 kernel_args = self.__netconsole_param
356 else:
357 kernel_args += " " + self.__netconsole_param
358 # unload the (possibly loaded) module to avoid shutdown issues
359 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000360 if label or kernel_args:
361 self.bootloader.install_boottool()
362 if label:
363 self.bootloader.set_default(label)
364 if kernel_args:
365 if not label:
366 default = int(self.bootloader.get_default())
367 label = self.bootloader.get_titles()[default]
368 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000369 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000370 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000371 try:
mblighf3b78932007-11-07 16:52:47 +0000372 self.run('(sleep 5; reboot) </dev/null >/dev/null 2>&1 &')
373 except errors.AutoservRunError:
374 self.__record("ABORT", None, "reboot.start",
375 "reboot command failed")
mblighcf3d83a2007-11-05 19:21:39 +0000376 raise
mbligha0452c82007-08-08 20:24:57 +0000377 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000378 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000379 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000380
mbligh7d2bde82007-08-02 16:26:10 +0000381
mblighdcd57a82007-07-11 23:06:47 +0000382 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000383 """
384 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000385
386 Directories will be copied recursively.
387 If a source component is a directory with a trailing slash,
388 the content of the directory will be copied, otherwise, the
389 directory itself and its content will be copied. This
390 behavior is similar to that of the program 'rsync'.
391
392 Args:
393 source: either
394 1) a single file or directory, as a string
395 2) a list of one or more (possibly mixed)
396 files or directories
397 dest: a file or a directory (if source contains a
398 directory or more than one element, you must
399 supply a directory dest)
400
401 Raises:
402 AutoservRunError: the scp command failed
403 """
404 if isinstance(source, types.StringTypes):
405 source= [source]
406
407 processed_source= []
408 for entry in source:
409 if entry.endswith('/'):
410 format_string= '%s@%s:"%s*"'
411 else:
412 format_string= '%s@%s:"%s"'
413 entry= format_string % (self.user, self.hostname,
414 utils.scp_remote_escape(entry))
415 processed_source.append(entry)
416
417 processed_dest= os.path.abspath(dest)
418 if os.path.isdir(dest):
419 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
420 else:
421 processed_dest= utils.sh_escape(processed_dest)
422
423 utils.run('scp -rpq %s "%s"' % (
424 " ".join(processed_source),
425 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000426
427
mblighdcd57a82007-07-11 23:06:47 +0000428 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000429 """
430 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000431
432 Directories will be copied recursively.
433 If a source component is a directory with a trailing slash,
434 the content of the directory will be copied, otherwise, the
435 directory itself and its content will be copied. This
436 behavior is similar to that of the program 'rsync'.
437
438 Args:
439 source: either
440 1) a single file or directory, as a string
441 2) a list of one or more (possibly mixed)
442 files or directories
443 dest: a file or a directory (if source contains a
444 directory or more than one element, you must
445 supply a directory dest)
446
447 Raises:
448 AutoservRunError: the scp command failed
449 """
450 if isinstance(source, types.StringTypes):
451 source= [source]
452
453 processed_source= []
454 for entry in source:
455 if entry.endswith('/'):
456 format_string= '"%s/"*'
457 else:
458 format_string= '"%s"'
459 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
460 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000461
mblighe6647d12007-10-17 00:00:01 +0000462 result = utils.run(r'%s rsync -h' % self.ssh_command(),
463 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000464
mbligh0faf91f2007-10-18 03:10:48 +0000465 remote_dest = '%s@%s:"%s"' % (
466 self.user, self.hostname,
467 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000468 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000469 utils.run('rsync --rsh="%s" -az %s %s' % (
470 self.SSH_BASE_COMMAND, " ".join(processed_source),
471 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000472 else:
mbligh0faf91f2007-10-18 03:10:48 +0000473 utils.run('scp -rpq %s %s' % (
474 " ".join(processed_source),
475 remote_dest))
mblighc42141f2007-11-05 20:25:46 +0000476 self.run('find "%s" -type d | xargs -r chmod o+rx' % dest)
477 self.run('find "%s" -type f | xargs -r chmod o+r' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000478
mblighdcd57a82007-07-11 23:06:47 +0000479 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000480 """
481 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000482 for temporary file storage.
483
484 The directory and its content will be deleted automatically
485 on the destruction of the Host object that was used to obtain
486 it.
487 """
mbligha25b29e2007-08-26 13:58:04 +0000488 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000489 self.tmp_dirs.append(dir_name)
490 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000491
492
mblighdcd57a82007-07-11 23:06:47 +0000493 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000494 """
495 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000496
497 Returns:
498 True if the remote host is up, False otherwise
499 """
500 try:
501 result= self.run("true", timeout=10)
502 except errors.AutoservRunError:
503 return False
504 else:
505 if result.exit_status == 0:
506 return True
507 else:
mbligh7d2bde82007-08-02 16:26:10 +0000508
mblighdcd57a82007-07-11 23:06:47 +0000509 return False
mbligh7d2bde82007-08-02 16:26:10 +0000510
mblighdcd57a82007-07-11 23:06:47 +0000511 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000512 """
513 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000514
515 In fact, it will wait until an ssh connection to the remote
516 host can be established.
517
518 Args:
519 timeout: time limit in seconds before returning even
520 if the host is not up.
521
522 Returns:
523 True if the host was found to be up, False otherwise
524 """
525 if timeout:
526 end_time= time.time() + timeout
527
528 while not timeout or time.time() < end_time:
529 try:
mblighe9cf9d42007-08-31 08:56:00 +0000530 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000531 result= self.run("true", timeout=run_timeout)
532 except errors.AutoservRunError:
533 pass
534 else:
535 if result.exit_status == 0:
536 return True
537 time.sleep(1)
538
539 return False
mbligh7d2bde82007-08-02 16:26:10 +0000540
541
mblighdcd57a82007-07-11 23:06:47 +0000542 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000543 """
544 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000545
546 In fact, it will wait until an ssh connection to the remote
547 host fails.
548
549 Args:
550 timeout: time limit in seconds before returning even
551 if the host is not up.
552
553 Returns:
554 True if the host was found to be down, False otherwise
555 """
556 if timeout:
557 end_time= time.time() + timeout
558
559 while not timeout or time.time() < end_time:
560 try:
mbligh7e1e9642007-07-31 18:00:45 +0000561 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000562 result= self.run("true", timeout=run_timeout)
563 except errors.AutoservRunError:
564 return True
565 else:
566 if result.aborted:
567 return True
568 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()
mbligh01aa4c02007-11-05 23:29:33 +0000583 except errors.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
587 raise errors.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
650
651 def ssh_ping(self):
652 self.run('ls', timeout=30)