blob: ac6c5f9a61fc1a30321a62323cd8ea56f62f4239 [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
mbligha9986b82007-11-24 19:45:45 +000047 SSH_BASE_COMMAND = '/usr/bin/ssh -a -o BatchMode=yes -o ConnectTimeout=30'
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 """
mbligh8bfa9f92007-11-24 19:29:30 +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 """
mbligh33ae0902007-11-24 19:27:08 +0000352 self.reboot_setup()
353
mblighde384372007-10-17 04:25:37 +0000354 # forcibly include the "netconsole" kernel arg
355 if self.__netconsole_param:
356 if kernel_args is None:
357 kernel_args = self.__netconsole_param
358 else:
359 kernel_args += " " + self.__netconsole_param
360 # unload the (possibly loaded) module to avoid shutdown issues
361 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000362 if label or kernel_args:
363 self.bootloader.install_boottool()
364 if label:
365 self.bootloader.set_default(label)
366 if kernel_args:
367 if not label:
368 default = int(self.bootloader.get_default())
369 label = self.bootloader.get_titles()[default]
370 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000371 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000372 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000373 try:
mblighf3b78932007-11-07 16:52:47 +0000374 self.run('(sleep 5; reboot) </dev/null >/dev/null 2>&1 &')
375 except errors.AutoservRunError:
376 self.__record("ABORT", None, "reboot.start",
377 "reboot command failed")
mblighcf3d83a2007-11-05 19:21:39 +0000378 raise
mbligha0452c82007-08-08 20:24:57 +0000379 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000380 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000381 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000382
mbligh7d2bde82007-08-02 16:26:10 +0000383
mblighdcd57a82007-07-11 23:06:47 +0000384 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000385 """
386 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000387
388 Directories will be copied recursively.
389 If a source component is a directory with a trailing slash,
390 the content of the directory will be copied, otherwise, the
391 directory itself and its content will be copied. This
392 behavior is similar to that of the program 'rsync'.
393
394 Args:
395 source: either
396 1) a single file or directory, as a string
397 2) a list of one or more (possibly mixed)
398 files or directories
399 dest: a file or a directory (if source contains a
400 directory or more than one element, you must
401 supply a directory dest)
402
403 Raises:
404 AutoservRunError: the scp command failed
405 """
406 if isinstance(source, types.StringTypes):
407 source= [source]
408
409 processed_source= []
410 for entry in source:
411 if entry.endswith('/'):
412 format_string= '%s@%s:"%s*"'
413 else:
414 format_string= '%s@%s:"%s"'
415 entry= format_string % (self.user, self.hostname,
416 utils.scp_remote_escape(entry))
417 processed_source.append(entry)
418
419 processed_dest= os.path.abspath(dest)
420 if os.path.isdir(dest):
421 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
422 else:
423 processed_dest= utils.sh_escape(processed_dest)
424
425 utils.run('scp -rpq %s "%s"' % (
426 " ".join(processed_source),
427 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000428
429
mblighdcd57a82007-07-11 23:06:47 +0000430 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000431 """
432 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000433
434 Directories will be copied recursively.
435 If a source component is a directory with a trailing slash,
436 the content of the directory will be copied, otherwise, the
437 directory itself and its content will be copied. This
438 behavior is similar to that of the program 'rsync'.
439
440 Args:
441 source: either
442 1) a single file or directory, as a string
443 2) a list of one or more (possibly mixed)
444 files or directories
445 dest: a file or a directory (if source contains a
446 directory or more than one element, you must
447 supply a directory dest)
448
449 Raises:
450 AutoservRunError: the scp command failed
451 """
452 if isinstance(source, types.StringTypes):
453 source= [source]
454
455 processed_source= []
456 for entry in source:
457 if entry.endswith('/'):
458 format_string= '"%s/"*'
459 else:
460 format_string= '"%s"'
461 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
462 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000463
mblighe6647d12007-10-17 00:00:01 +0000464 result = utils.run(r'%s rsync -h' % self.ssh_command(),
465 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000466
mbligh0faf91f2007-10-18 03:10:48 +0000467 remote_dest = '%s@%s:"%s"' % (
468 self.user, self.hostname,
469 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000470 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000471 utils.run('rsync --rsh="%s" -az %s %s' % (
472 self.SSH_BASE_COMMAND, " ".join(processed_source),
473 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000474 else:
mbligh0faf91f2007-10-18 03:10:48 +0000475 utils.run('scp -rpq %s %s' % (
476 " ".join(processed_source),
477 remote_dest))
mblighc42141f2007-11-05 20:25:46 +0000478 self.run('find "%s" -type d | xargs -r chmod o+rx' % dest)
479 self.run('find "%s" -type f | xargs -r chmod o+r' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000480
mblighdcd57a82007-07-11 23:06:47 +0000481 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000482 """
483 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000484 for temporary file storage.
485
486 The directory and its content will be deleted automatically
487 on the destruction of the Host object that was used to obtain
488 it.
489 """
mbligha25b29e2007-08-26 13:58:04 +0000490 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000491 self.tmp_dirs.append(dir_name)
492 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000493
494
mblighdcd57a82007-07-11 23:06:47 +0000495 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000496 """
497 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000498
499 Returns:
500 True if the remote host is up, False otherwise
501 """
502 try:
503 result= self.run("true", timeout=10)
504 except errors.AutoservRunError:
505 return False
506 else:
507 if result.exit_status == 0:
508 return True
509 else:
mbligh7d2bde82007-08-02 16:26:10 +0000510
mblighdcd57a82007-07-11 23:06:47 +0000511 return False
mbligh7d2bde82007-08-02 16:26:10 +0000512
mblighdcd57a82007-07-11 23:06:47 +0000513 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000514 """
515 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000516
517 In fact, it will wait until an ssh connection to the remote
518 host can be established.
519
520 Args:
521 timeout: time limit in seconds before returning even
522 if the host is not up.
523
524 Returns:
525 True if the host was found to be up, False otherwise
526 """
527 if timeout:
528 end_time= time.time() + timeout
529
530 while not timeout or time.time() < end_time:
531 try:
mblighe9cf9d42007-08-31 08:56:00 +0000532 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000533 result= self.run("true", timeout=run_timeout)
534 except errors.AutoservRunError:
535 pass
536 else:
537 if result.exit_status == 0:
538 return True
539 time.sleep(1)
540
541 return False
mbligh7d2bde82007-08-02 16:26:10 +0000542
543
mblighdcd57a82007-07-11 23:06:47 +0000544 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000545 """
546 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000547
548 In fact, it will wait until an ssh connection to the remote
549 host fails.
550
551 Args:
552 timeout: time limit in seconds before returning even
553 if the host is not up.
554
555 Returns:
556 True if the host was found to be down, False otherwise
557 """
558 if timeout:
559 end_time= time.time() + timeout
560
561 while not timeout or time.time() < end_time:
562 try:
mbligh7e1e9642007-07-31 18:00:45 +0000563 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000564 result= self.run("true", timeout=run_timeout)
565 except errors.AutoservRunError:
566 return True
567 else:
568 if result.aborted:
569 return True
570 time.sleep(1)
571
572 return False
mbligh7d2bde82007-08-02 16:26:10 +0000573
574
mblighdbe4a382007-07-26 19:41:28 +0000575 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000576 """
577 Ensure the host is up if it is not then do not proceed;
578 this prevents cacading failures of tests
579 """
mbligha0452c82007-08-08 20:24:57 +0000580 print 'Ensuring that %s is up before continuing' % self.hostname
581 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000582 print "Performing a hardreset on %s" % self.hostname
mbligh4ba0b462007-11-05 23:05:40 +0000583 try:
584 self.hardreset()
mbligh01aa4c02007-11-05 23:29:33 +0000585 except errors.AutoservUnsupportedError:
mbligh4ba0b462007-11-05 23:05:40 +0000586 print "Hardreset is unsupported on %s" % self.hostname
mbligha9563b92007-10-25 14:45:56 +0000587 if not self.wait_up(60 * 30):
588 # 30 minutes should be more than enough
589 raise errors.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000590 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000591
592
mblighdcd57a82007-07-11 23:06:47 +0000593 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000594 """
595 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000596 /proc/cpuinfo.
597
598 Returns:
599 The number of CPUs
600 """
601
mbligh5f876ad2007-10-12 23:59:53 +0000602 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000603 cpus = 0
604 for line in proc_cpuinfo.splitlines():
605 if line.startswith('processor'):
606 cpus += 1
607 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000608
609
610 def check_uptime(self):
611 """
612 Check that uptime is available and monotonically increasing.
613 """
614 if not self.ping():
615 raise "Client is not pingable"
616 result = self.run("/bin/cat /proc/uptime", 30)
617 return result.stdout.strip().split()[0]
618
619
620 def get_arch(self):
621 """
622 Get the hardware architecture of the remote machine
623 """
624 arch = self.run('/bin/uname -m').stdout.rstrip()
625 if re.match(r'i\d86$', arch):
626 arch = 'i386'
627 return arch
628
629
630 def get_kernel_ver(self):
631 """
632 Get the kernel version of the remote machine
633 """
634 return self.run('/bin/uname -r').stdout.rstrip()
635
636
637 def get_cmdline(self):
638 """
639 Get the kernel command line of the remote machine
640 """
641 return self.run('cat /proc/cmdline').stdout.rstrip()
642
643
644 def ping(self):
645 """
646 Ping the remote system, and return whether it's available
647 """
648 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
649 rc = utils.system(fpingcmd, ignore_status = 1)
650 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000651
mbligh7f86e0b2007-11-24 19:45:07 +0000652 def ssh_ping(self, timeout = 30):
653 self.run('ls', timeout = timeout)