blob: 1ec33f73aa0b55c85a760d2ce786c5cbcd35e30b [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
mbligh80d20772007-10-29 17:10:10 +000024DEFAULT_REBOOT_TIMEOUT = 1800
25
mblighdcd57a82007-07-11 23:06:47 +000026
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
mbligh0faf91f2007-10-18 03:10:48 +000049 SSH_BASE_COMMAND = 'ssh -a'
50
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
mblighde384372007-10-17 04:25:37 +000081 self.__init_netconsole_params(netconsole_port)
82 self.netlogger_pid = None
83 self.__start_netconsole_log(netconsole_log, netconsole_port)
84 self.__load_netconsole_module()
85
mbligh7d2bde82007-08-02 16:26:10 +000086
mblighdcd57a82007-07-11 23:06:47 +000087 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +000088 """
89 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000090 """
91 for dir in self.tmp_dirs:
92 try:
93 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
94 except errors.AutoservRunError:
95 pass
mblighde384372007-10-17 04:25:37 +000096 # kill the console logger
mbligh7364ae42007-10-18 03:20:34 +000097 if getattr(self, 'logger_pid', None):
mbligh3409ee72007-10-16 23:58:33 +000098 try:
99 pgid = os.getpgid(self.logger_pid)
100 os.killpg(pgid, signal.SIGTERM)
101 except OSError:
102 pass
mblighde384372007-10-17 04:25:37 +0000103 # kill the netconsole logger
mbligh7364ae42007-10-18 03:20:34 +0000104 if getattr(self, 'netlogger_pid', None):
mblighe6c995f2007-10-26 19:43:01 +0000105 self.__unload_netconsole_module()
mblighde384372007-10-17 04:25:37 +0000106 try:
107 os.kill(self.netlogger_pid, signal.SIGTERM)
108 except OSError:
109 pass
mblighe6c995f2007-10-26 19:43:01 +0000110 # kill the warning logger
111 if getattr(self, 'warning_pid', None):
112 try:
113 pgid = os.getpgid(self.warning_pid)
114 os.killpg(pgid, signal.SIGTERM)
115 except OSError:
116 pass
mblighde384372007-10-17 04:25:37 +0000117
118
119 def __init_netconsole_params(self, port):
120 """
121 Connect to the remote machine and determine the values to use for the
122 required netconsole parameters.
123 """
124 self.__netconsole_param = ""
125 # PROBLEM: on machines with multiple IPs this may not make any sense
126 # It also doesn't work with IPv6
127 remote_ip = socket.gethostbyname(self.hostname)
128 local_ip = socket.gethostbyname(socket.gethostname())
129 # Get the gateway of the remote machine
130 try:
131 traceroute = self.run('traceroute -n %s' % local_ip)
132 except errors.AutoservRunError:
133 return
134 first_node = traceroute.stdout.split("\n")[0]
135 match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
136 if match:
137 router_ip = match.group(1)
138 else:
139 return
140 # Look up the MAC address of the gateway
141 try:
142 self.run('ping -c 1 %s' % router_ip)
143 arp = self.run('arp -n -a %s' % router_ip)
144 except errors.AutoservRunError:
145 return
146 match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
147 if match:
148 gateway_mac = match.group(1)
149 else:
150 return
151 self.__netconsole_param = 'netconsole=@%s/,%s@%s/%s' % (remote_ip,
152 port,
153 local_ip,
154 gateway_mac)
155
156
157 def __start_netconsole_log(self, logfilename, port):
158 """
159 Log the output of netconsole to a specified file
160 """
161 if logfilename == None:
162 return
163 cmd = ['nc', '-u', '-l', '-p', str(port)]
mblighd2fc50f2007-10-23 22:38:00 +0000164 logger = subprocess.Popen(cmd, stdout=open(logfilename, "a", 0))
mblighde384372007-10-17 04:25:37 +0000165 self.netlogger_pid = logger.pid
166
167
168 def __load_netconsole_module(self):
169 """
170 Make a best effort to load the netconsole module.
171
172 Note that loading the module can fail even when the remote machine is
173 working correctly if netconsole is already compiled into the kernel
174 and started.
175 """
176 try:
177 self.run('modprobe netconsole %s' % self.__netconsole_param)
178 except errors.AutoservRunError:
179 # if it fails there isn't much we can do, just keep going
180 pass
181
182
183 def __unload_netconsole_module(self):
184 try:
185 self.run('modprobe -r netconsole')
186 except errors.AutoservRunError:
187 pass
mbligh3409ee72007-10-16 23:58:33 +0000188
189
mbligh6a0010f2007-10-25 15:45:21 +0000190 def _wait_for_restart(self, timeout):
mblighd567f722007-10-30 15:37:33 +0000191 if not self.wait_down(300): # Make sure he's dead, Jim
192 sys.stderr.write("REBOOT ERROR\n")
193 raise errors.AutoservRebootError("Host would not shut down")
mbligh3409ee72007-10-16 23:58:33 +0000194 self.wait_up(timeout)
195 time.sleep(2) # this is needed for complete reliability
mbligh87c5d882007-10-29 17:07:24 +0000196 if not self.wait_up(timeout):
197 sys.stderr.write("REBOOT ERROR\n")
mbligh3409ee72007-10-16 23:58:33 +0000198 print "Reboot complete"
199
200
mbligh80d20772007-10-29 17:10:10 +0000201 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000202 """
203 Reach out and slap the box in the power switch
204 """
mblighba81c682007-10-25 15:35:59 +0000205 command_ran = self.__console_run(r"'~$hardreset'")
206 if not command_ran:
207 raise errors.AutoservUnsupportedError
208 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000209 self._wait_for_restart(timeout)
mbligh3409ee72007-10-16 23:58:33 +0000210
211
mblighe6c995f2007-10-26 19:43:01 +0000212 def __conmux_hostname(self):
213 if self.conmux_server:
214 return '%s/%s' % (self.conmux_server, self.hostname)
215 else:
216 return self.hostname
217
218
mbligh3409ee72007-10-16 23:58:33 +0000219 def __start_console_log(self, logfilename):
220 """
221 Log the output of the console session to a specified file
222 """
223 if logfilename == None:
224 return
225 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
226 return
mblighe6c995f2007-10-26 19:43:01 +0000227 cmd = [self.conmux_attach, self.__conmux_hostname(), 'cat - >> %s' % logfilename]
mbligh3409ee72007-10-16 23:58:33 +0000228 logger = subprocess.Popen(cmd,
229 stderr=open('/dev/null', 'w'),
230 preexec_fn=lambda: os.setpgid(0, 0))
231 self.logger_pid = logger.pid
232
233
mblighe6c995f2007-10-26 19:43:01 +0000234 def __start_warning_log(self, logfilename):
235 """
236 Log the output of the warning monitor to a specified file
237 """
238 if logfilename == None:
239 return
240 script_path = os.path.join(self.serverdir, 'warning_monitor')
mbligh7c5452d2007-11-05 18:35:31 +0000241 script_cmd = 'expect %s %s >> %s' % (script_path,
242 self.hostname,
243 logfilename)
mblighe6c995f2007-10-26 19:43:01 +0000244 if self.conmux_server:
245 to = '%s/%s'
246 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
247 logger = subprocess.Popen(cmd,
mbligh7c5452d2007-11-05 18:35:31 +0000248 stderr=open('debug/conmux.log', 'a', 0),
mblighe6c995f2007-10-26 19:43:01 +0000249 preexec_fn=lambda: os.setpgid(0, 0))
250 self.warning_pid = logger.pid
251
252
mbligh3409ee72007-10-16 23:58:33 +0000253 def __find_console_attach(self, conmux_attach):
254 if conmux_attach:
255 return conmux_attach
256 try:
257 res = utils.run('which conmux-attach')
258 if res.exit_status == 0:
259 return res.stdout.strip()
260 except errors.AutoservRunError, e:
261 pass
mbligh9708f732007-10-18 03:18:54 +0000262 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000263 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000264 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000265 '..', 'autotest',
266 'conmux', 'conmux-attach')
267 locations = [autotest_conmux,
268 autotest_conmux_alt,
269 '/usr/local/conmux/bin/conmux-attach',
270 '/usr/bin/conmux-attach']
271 for l in locations:
272 if os.path.exists(l):
273 return l
274
275 print "WARNING: conmux-attach not found on autoserv server"
276 return None
277
278
279 def __console_run(self, cmd):
280 """
281 Send a command to the conmux session
282 """
283 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
284 return False
mbligh3409ee72007-10-16 23:58:33 +0000285 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000286 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000287 cmd)
288 result = os.system(cmd)
289 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000290
291
mblighe6647d12007-10-17 00:00:01 +0000292 def ssh_command(self):
293 """Construct an ssh command with proper args for this host."""
mbligh0faf91f2007-10-18 03:10:48 +0000294 return r'%s -l %s -p %d %s' % (self.SSH_BASE_COMMAND,
295 self.user,
296 self.port,
297 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000298
299
mblighcf965b02007-07-25 16:49:45 +0000300 def run(self, command, timeout=None, ignore_status=False):
mbligh7d2bde82007-08-02 16:26:10 +0000301 """
302 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000303
304 Args:
305 command: the command line string
306 timeout: time limit in seconds before attempting to
307 kill the running process. The run() function
308 will take a few seconds longer than 'timeout'
309 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000310 ignore_status: do not raise an exception, no matter
311 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000312
313 Returns:
314 a hosts.base_classes.CmdResult object
315
316 Raises:
317 AutoservRunError: the exit code of the command
318 execution was not 0
319 """
320 #~ print "running %s" % (command,)
mblighe6647d12007-10-17 00:00:01 +0000321 result= utils.run(r'%s "%s"' % (self.ssh_command(),
322 utils.sh_escape(command)),
323 timeout, ignore_status)
mblighdcd57a82007-07-11 23:06:47 +0000324 return result
mbligh7d2bde82007-08-02 16:26:10 +0000325
326
mbligh80d20772007-10-29 17:10:10 +0000327 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
328 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000329 """
330 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000331
mbligha0452c82007-08-08 20:24:57 +0000332 Args:
333 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000334 """
mblighde384372007-10-17 04:25:37 +0000335 # forcibly include the "netconsole" kernel arg
336 if self.__netconsole_param:
337 if kernel_args is None:
338 kernel_args = self.__netconsole_param
339 else:
340 kernel_args += " " + self.__netconsole_param
341 # unload the (possibly loaded) module to avoid shutdown issues
342 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000343 if label or kernel_args:
344 self.bootloader.install_boottool()
345 if label:
346 self.bootloader.set_default(label)
347 if kernel_args:
348 if not label:
349 default = int(self.bootloader.get_default())
350 label = self.bootloader.get_titles()[default]
351 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000352 print "Reboot: initiating reboot"
mbligh87c5d882007-10-29 17:07:24 +0000353 sys.stderr.write("REBOOT\n")
mblighd2e46052007-11-05 18:31:00 +0000354 self.run('(sleep 5; reboot) >/dev/null 2>&1 &')
mbligha0452c82007-08-08 20:24:57 +0000355 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000356 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000357 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000358
mbligh7d2bde82007-08-02 16:26:10 +0000359
mblighdcd57a82007-07-11 23:06:47 +0000360 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000361 """
362 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000363
364 Directories will be copied recursively.
365 If a source component is a directory with a trailing slash,
366 the content of the directory will be copied, otherwise, the
367 directory itself and its content will be copied. This
368 behavior is similar to that of the program 'rsync'.
369
370 Args:
371 source: either
372 1) a single file or directory, as a string
373 2) a list of one or more (possibly mixed)
374 files or directories
375 dest: a file or a directory (if source contains a
376 directory or more than one element, you must
377 supply a directory dest)
378
379 Raises:
380 AutoservRunError: the scp command failed
381 """
382 if isinstance(source, types.StringTypes):
383 source= [source]
384
385 processed_source= []
386 for entry in source:
387 if entry.endswith('/'):
388 format_string= '%s@%s:"%s*"'
389 else:
390 format_string= '%s@%s:"%s"'
391 entry= format_string % (self.user, self.hostname,
392 utils.scp_remote_escape(entry))
393 processed_source.append(entry)
394
395 processed_dest= os.path.abspath(dest)
396 if os.path.isdir(dest):
397 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
398 else:
399 processed_dest= utils.sh_escape(processed_dest)
400
401 utils.run('scp -rpq %s "%s"' % (
402 " ".join(processed_source),
403 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000404
405
mblighdcd57a82007-07-11 23:06:47 +0000406 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000407 """
408 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000409
410 Directories will be copied recursively.
411 If a source component is a directory with a trailing slash,
412 the content of the directory will be copied, otherwise, the
413 directory itself and its content will be copied. This
414 behavior is similar to that of the program 'rsync'.
415
416 Args:
417 source: either
418 1) a single file or directory, as a string
419 2) a list of one or more (possibly mixed)
420 files or directories
421 dest: a file or a directory (if source contains a
422 directory or more than one element, you must
423 supply a directory dest)
424
425 Raises:
426 AutoservRunError: the scp command failed
427 """
428 if isinstance(source, types.StringTypes):
429 source= [source]
430
431 processed_source= []
432 for entry in source:
433 if entry.endswith('/'):
434 format_string= '"%s/"*'
435 else:
436 format_string= '"%s"'
437 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
438 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000439
mblighe6647d12007-10-17 00:00:01 +0000440 result = utils.run(r'%s rsync -h' % self.ssh_command(),
441 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000442
mbligh0faf91f2007-10-18 03:10:48 +0000443 remote_dest = '%s@%s:"%s"' % (
444 self.user, self.hostname,
445 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000446 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000447 utils.run('rsync --rsh="%s" -az %s %s' % (
448 self.SSH_BASE_COMMAND, " ".join(processed_source),
449 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000450 else:
mbligh0faf91f2007-10-18 03:10:48 +0000451 utils.run('scp -rpq %s %s' % (
452 " ".join(processed_source),
453 remote_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000454
mblighdcd57a82007-07-11 23:06:47 +0000455 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000456 """
457 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000458 for temporary file storage.
459
460 The directory and its content will be deleted automatically
461 on the destruction of the Host object that was used to obtain
462 it.
463 """
mbligha25b29e2007-08-26 13:58:04 +0000464 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000465 self.tmp_dirs.append(dir_name)
466 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000467
468
mblighdcd57a82007-07-11 23:06:47 +0000469 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000470 """
471 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000472
473 Returns:
474 True if the remote host is up, False otherwise
475 """
476 try:
477 result= self.run("true", timeout=10)
478 except errors.AutoservRunError:
479 return False
480 else:
481 if result.exit_status == 0:
482 return True
483 else:
mbligh7d2bde82007-08-02 16:26:10 +0000484
mblighdcd57a82007-07-11 23:06:47 +0000485 return False
mbligh7d2bde82007-08-02 16:26:10 +0000486
mblighdcd57a82007-07-11 23:06:47 +0000487 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000488 """
489 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000490
491 In fact, it will wait until an ssh connection to the remote
492 host can be established.
493
494 Args:
495 timeout: time limit in seconds before returning even
496 if the host is not up.
497
498 Returns:
499 True if the host was found to be up, False otherwise
500 """
501 if timeout:
502 end_time= time.time() + timeout
503
504 while not timeout or time.time() < end_time:
505 try:
mblighe9cf9d42007-08-31 08:56:00 +0000506 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000507 result= self.run("true", timeout=run_timeout)
508 except errors.AutoservRunError:
509 pass
510 else:
511 if result.exit_status == 0:
512 return True
513 time.sleep(1)
514
515 return False
mbligh7d2bde82007-08-02 16:26:10 +0000516
517
mblighdcd57a82007-07-11 23:06:47 +0000518 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000519 """
520 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000521
522 In fact, it will wait until an ssh connection to the remote
523 host fails.
524
525 Args:
526 timeout: time limit in seconds before returning even
527 if the host is not up.
528
529 Returns:
530 True if the host was found to be down, False otherwise
531 """
532 if timeout:
533 end_time= time.time() + timeout
534
535 while not timeout or time.time() < end_time:
536 try:
mbligh7e1e9642007-07-31 18:00:45 +0000537 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000538 result= self.run("true", timeout=run_timeout)
539 except errors.AutoservRunError:
540 return True
541 else:
542 if result.aborted:
543 return True
544 time.sleep(1)
545
546 return False
mbligh7d2bde82007-08-02 16:26:10 +0000547
548
mblighdbe4a382007-07-26 19:41:28 +0000549 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000550 """
551 Ensure the host is up if it is not then do not proceed;
552 this prevents cacading failures of tests
553 """
mbligha0452c82007-08-08 20:24:57 +0000554 print 'Ensuring that %s is up before continuing' % self.hostname
555 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000556 print "Performing a hardreset on %s" % self.hostname
557 self.hardreset()
mbligha9563b92007-10-25 14:45:56 +0000558 if not self.wait_up(60 * 30):
559 # 30 minutes should be more than enough
560 raise errors.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000561 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000562
563
mblighdcd57a82007-07-11 23:06:47 +0000564 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000565 """
566 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000567 /proc/cpuinfo.
568
569 Returns:
570 The number of CPUs
571 """
572
mbligh5f876ad2007-10-12 23:59:53 +0000573 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000574 cpus = 0
575 for line in proc_cpuinfo.splitlines():
576 if line.startswith('processor'):
577 cpus += 1
578 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000579
580
581 def check_uptime(self):
582 """
583 Check that uptime is available and monotonically increasing.
584 """
585 if not self.ping():
586 raise "Client is not pingable"
587 result = self.run("/bin/cat /proc/uptime", 30)
588 return result.stdout.strip().split()[0]
589
590
591 def get_arch(self):
592 """
593 Get the hardware architecture of the remote machine
594 """
595 arch = self.run('/bin/uname -m').stdout.rstrip()
596 if re.match(r'i\d86$', arch):
597 arch = 'i386'
598 return arch
599
600
601 def get_kernel_ver(self):
602 """
603 Get the kernel version of the remote machine
604 """
605 return self.run('/bin/uname -r').stdout.rstrip()
606
607
608 def get_cmdline(self):
609 """
610 Get the kernel command line of the remote machine
611 """
612 return self.run('cat /proc/cmdline').stdout.rstrip()
613
614
615 def ping(self):
616 """
617 Ping the remote system, and return whether it's available
618 """
619 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
620 rc = utils.system(fpingcmd, ignore_status = 1)
621 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000622
623
624 def ssh_ping(self):
625 self.run('ls', timeout=30)