blob: f8e9d77988ea7dd505379c604af0cb96b8628546 [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
mbligh30270302007-11-05 20:33:52 +0000195 self.__record("ABORT", None, "reboot.verify")
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:
mbligh30270302007-11-05 20:33:52 +0000202 self.__record("ABORT", None, "reboot.verify")
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 """
mblighba81c682007-10-25 15:35:59 +0000211 command_ran = self.__console_run(r"'~$hardreset'")
212 if not command_ran:
213 raise errors.AutoservUnsupportedError
214 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000215 self._wait_for_restart(timeout)
mbligh3409ee72007-10-16 23:58:33 +0000216
217
mblighe6c995f2007-10-26 19:43:01 +0000218 def __conmux_hostname(self):
219 if self.conmux_server:
220 return '%s/%s' % (self.conmux_server, self.hostname)
221 else:
222 return self.hostname
223
224
mbligh3409ee72007-10-16 23:58:33 +0000225 def __start_console_log(self, logfilename):
226 """
227 Log the output of the console session to a specified file
228 """
229 if logfilename == None:
230 return
231 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
232 return
mblighe6c995f2007-10-26 19:43:01 +0000233 cmd = [self.conmux_attach, self.__conmux_hostname(), 'cat - >> %s' % logfilename]
mbligh3409ee72007-10-16 23:58:33 +0000234 logger = subprocess.Popen(cmd,
235 stderr=open('/dev/null', 'w'),
236 preexec_fn=lambda: os.setpgid(0, 0))
237 self.logger_pid = logger.pid
238
239
mblighe6c995f2007-10-26 19:43:01 +0000240 def __start_warning_log(self, logfilename):
241 """
242 Log the output of the warning monitor to a specified file
243 """
mbligh8b49a322007-11-05 20:36:54 +0000244 if logfilename == None or not os.path.isdir('debug'):
mblighe6c995f2007-10-26 19:43:01 +0000245 return
246 script_path = os.path.join(self.serverdir, 'warning_monitor')
mbligh7c5452d2007-11-05 18:35:31 +0000247 script_cmd = 'expect %s %s >> %s' % (script_path,
248 self.hostname,
249 logfilename)
mblighe6c995f2007-10-26 19:43:01 +0000250 if self.conmux_server:
251 to = '%s/%s'
252 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
253 logger = subprocess.Popen(cmd,
mbligh7c5452d2007-11-05 18:35:31 +0000254 stderr=open('debug/conmux.log', 'a', 0),
mblighe6c995f2007-10-26 19:43:01 +0000255 preexec_fn=lambda: os.setpgid(0, 0))
256 self.warning_pid = logger.pid
257
258
mbligh3409ee72007-10-16 23:58:33 +0000259 def __find_console_attach(self, conmux_attach):
260 if conmux_attach:
261 return conmux_attach
262 try:
263 res = utils.run('which conmux-attach')
264 if res.exit_status == 0:
265 return res.stdout.strip()
266 except errors.AutoservRunError, e:
267 pass
mbligh9708f732007-10-18 03:18:54 +0000268 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000269 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000270 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000271 '..', 'autotest',
272 'conmux', 'conmux-attach')
273 locations = [autotest_conmux,
274 autotest_conmux_alt,
275 '/usr/local/conmux/bin/conmux-attach',
276 '/usr/bin/conmux-attach']
277 for l in locations:
278 if os.path.exists(l):
279 return l
280
281 print "WARNING: conmux-attach not found on autoserv server"
282 return None
283
284
285 def __console_run(self, cmd):
286 """
287 Send a command to the conmux session
288 """
289 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
290 return False
mbligh3409ee72007-10-16 23:58:33 +0000291 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000292 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000293 cmd)
294 result = os.system(cmd)
295 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000296
297
mbligh31a49de2007-11-05 18:41:19 +0000298 def __record(self, status_code, subdir, operation, status = ''):
299 if self.job:
300 self.job.record(status_code, subdir, operation, status)
301 else:
302 if not subdir:
303 subdir = "----"
304 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
305 sys.stderr.write(msg + "\n")
306
307
mblighe6647d12007-10-17 00:00:01 +0000308 def ssh_command(self):
309 """Construct an ssh command with proper args for this host."""
mbligh0faf91f2007-10-18 03:10:48 +0000310 return r'%s -l %s -p %d %s' % (self.SSH_BASE_COMMAND,
311 self.user,
312 self.port,
313 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000314
315
mblighcf965b02007-07-25 16:49:45 +0000316 def run(self, command, timeout=None, ignore_status=False):
mbligh7d2bde82007-08-02 16:26:10 +0000317 """
318 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000319
320 Args:
321 command: the command line string
322 timeout: time limit in seconds before attempting to
323 kill the running process. The run() function
324 will take a few seconds longer than 'timeout'
325 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000326 ignore_status: do not raise an exception, no matter
327 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000328
329 Returns:
330 a hosts.base_classes.CmdResult object
331
332 Raises:
333 AutoservRunError: the exit code of the command
334 execution was not 0
335 """
336 #~ print "running %s" % (command,)
mblighe6647d12007-10-17 00:00:01 +0000337 result= utils.run(r'%s "%s"' % (self.ssh_command(),
338 utils.sh_escape(command)),
339 timeout, ignore_status)
mblighdcd57a82007-07-11 23:06:47 +0000340 return result
mbligh7d2bde82007-08-02 16:26:10 +0000341
342
mbligh80d20772007-10-29 17:10:10 +0000343 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
344 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000345 """
346 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000347
mbligha0452c82007-08-08 20:24:57 +0000348 Args:
349 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000350 """
mblighde384372007-10-17 04:25:37 +0000351 # forcibly include the "netconsole" kernel arg
352 if self.__netconsole_param:
353 if kernel_args is None:
354 kernel_args = self.__netconsole_param
355 else:
356 kernel_args += " " + self.__netconsole_param
357 # unload the (possibly loaded) module to avoid shutdown issues
358 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000359 if label or kernel_args:
360 self.bootloader.install_boottool()
361 if label:
362 self.bootloader.set_default(label)
363 if kernel_args:
364 if not label:
365 default = int(self.bootloader.get_default())
366 label = self.bootloader.get_titles()[default]
367 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000368 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000369 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000370 try:
371 self.run('(sleep 5; reboot) >/dev/null 2>&1 &')
372 except AutoservRunError:
mbligh30270302007-11-05 20:33:52 +0000373 self.__record("ABORT", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000374 raise
mbligha0452c82007-08-08 20:24:57 +0000375 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000376 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000377 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000378
mbligh7d2bde82007-08-02 16:26:10 +0000379
mblighdcd57a82007-07-11 23:06:47 +0000380 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000381 """
382 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000383
384 Directories will be copied recursively.
385 If a source component is a directory with a trailing slash,
386 the content of the directory will be copied, otherwise, the
387 directory itself and its content will be copied. This
388 behavior is similar to that of the program 'rsync'.
389
390 Args:
391 source: either
392 1) a single file or directory, as a string
393 2) a list of one or more (possibly mixed)
394 files or directories
395 dest: a file or a directory (if source contains a
396 directory or more than one element, you must
397 supply a directory dest)
398
399 Raises:
400 AutoservRunError: the scp command failed
401 """
402 if isinstance(source, types.StringTypes):
403 source= [source]
404
405 processed_source= []
406 for entry in source:
407 if entry.endswith('/'):
408 format_string= '%s@%s:"%s*"'
409 else:
410 format_string= '%s@%s:"%s"'
411 entry= format_string % (self.user, self.hostname,
412 utils.scp_remote_escape(entry))
413 processed_source.append(entry)
414
415 processed_dest= os.path.abspath(dest)
416 if os.path.isdir(dest):
417 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
418 else:
419 processed_dest= utils.sh_escape(processed_dest)
420
421 utils.run('scp -rpq %s "%s"' % (
422 " ".join(processed_source),
423 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000424
425
mblighdcd57a82007-07-11 23:06:47 +0000426 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000427 """
428 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000429
430 Directories will be copied recursively.
431 If a source component is a directory with a trailing slash,
432 the content of the directory will be copied, otherwise, the
433 directory itself and its content will be copied. This
434 behavior is similar to that of the program 'rsync'.
435
436 Args:
437 source: either
438 1) a single file or directory, as a string
439 2) a list of one or more (possibly mixed)
440 files or directories
441 dest: a file or a directory (if source contains a
442 directory or more than one element, you must
443 supply a directory dest)
444
445 Raises:
446 AutoservRunError: the scp command failed
447 """
448 if isinstance(source, types.StringTypes):
449 source= [source]
450
451 processed_source= []
452 for entry in source:
453 if entry.endswith('/'):
454 format_string= '"%s/"*'
455 else:
456 format_string= '"%s"'
457 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
458 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000459
mblighe6647d12007-10-17 00:00:01 +0000460 result = utils.run(r'%s rsync -h' % self.ssh_command(),
461 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000462
mbligh0faf91f2007-10-18 03:10:48 +0000463 remote_dest = '%s@%s:"%s"' % (
464 self.user, self.hostname,
465 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000466 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000467 utils.run('rsync --rsh="%s" -az %s %s' % (
468 self.SSH_BASE_COMMAND, " ".join(processed_source),
469 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000470 else:
mbligh0faf91f2007-10-18 03:10:48 +0000471 utils.run('scp -rpq %s %s' % (
472 " ".join(processed_source),
473 remote_dest))
mblighc42141f2007-11-05 20:25:46 +0000474 self.run('find "%s" -type d | xargs -r chmod o+rx' % dest)
475 self.run('find "%s" -type f | xargs -r chmod o+r' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000476
mblighdcd57a82007-07-11 23:06:47 +0000477 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000478 """
479 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000480 for temporary file storage.
481
482 The directory and its content will be deleted automatically
483 on the destruction of the Host object that was used to obtain
484 it.
485 """
mbligha25b29e2007-08-26 13:58:04 +0000486 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000487 self.tmp_dirs.append(dir_name)
488 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000489
490
mblighdcd57a82007-07-11 23:06:47 +0000491 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000492 """
493 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000494
495 Returns:
496 True if the remote host is up, False otherwise
497 """
498 try:
499 result= self.run("true", timeout=10)
500 except errors.AutoservRunError:
501 return False
502 else:
503 if result.exit_status == 0:
504 return True
505 else:
mbligh7d2bde82007-08-02 16:26:10 +0000506
mblighdcd57a82007-07-11 23:06:47 +0000507 return False
mbligh7d2bde82007-08-02 16:26:10 +0000508
mblighdcd57a82007-07-11 23:06:47 +0000509 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000510 """
511 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000512
513 In fact, it will wait until an ssh connection to the remote
514 host can be established.
515
516 Args:
517 timeout: time limit in seconds before returning even
518 if the host is not up.
519
520 Returns:
521 True if the host was found to be up, False otherwise
522 """
523 if timeout:
524 end_time= time.time() + timeout
525
526 while not timeout or time.time() < end_time:
527 try:
mblighe9cf9d42007-08-31 08:56:00 +0000528 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000529 result= self.run("true", timeout=run_timeout)
530 except errors.AutoservRunError:
531 pass
532 else:
533 if result.exit_status == 0:
534 return True
535 time.sleep(1)
536
537 return False
mbligh7d2bde82007-08-02 16:26:10 +0000538
539
mblighdcd57a82007-07-11 23:06:47 +0000540 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000541 """
542 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000543
544 In fact, it will wait until an ssh connection to the remote
545 host fails.
546
547 Args:
548 timeout: time limit in seconds before returning even
549 if the host is not up.
550
551 Returns:
552 True if the host was found to be down, False otherwise
553 """
554 if timeout:
555 end_time= time.time() + timeout
556
557 while not timeout or time.time() < end_time:
558 try:
mbligh7e1e9642007-07-31 18:00:45 +0000559 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000560 result= self.run("true", timeout=run_timeout)
561 except errors.AutoservRunError:
562 return True
563 else:
564 if result.aborted:
565 return True
566 time.sleep(1)
567
568 return False
mbligh7d2bde82007-08-02 16:26:10 +0000569
570
mblighdbe4a382007-07-26 19:41:28 +0000571 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000572 """
573 Ensure the host is up if it is not then do not proceed;
574 this prevents cacading failures of tests
575 """
mbligha0452c82007-08-08 20:24:57 +0000576 print 'Ensuring that %s is up before continuing' % self.hostname
577 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000578 print "Performing a hardreset on %s" % self.hostname
579 self.hardreset()
mbligha9563b92007-10-25 14:45:56 +0000580 if not self.wait_up(60 * 30):
581 # 30 minutes should be more than enough
582 raise errors.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000583 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000584
585
mblighdcd57a82007-07-11 23:06:47 +0000586 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000587 """
588 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000589 /proc/cpuinfo.
590
591 Returns:
592 The number of CPUs
593 """
594
mbligh5f876ad2007-10-12 23:59:53 +0000595 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000596 cpus = 0
597 for line in proc_cpuinfo.splitlines():
598 if line.startswith('processor'):
599 cpus += 1
600 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000601
602
603 def check_uptime(self):
604 """
605 Check that uptime is available and monotonically increasing.
606 """
607 if not self.ping():
608 raise "Client is not pingable"
609 result = self.run("/bin/cat /proc/uptime", 30)
610 return result.stdout.strip().split()[0]
611
612
613 def get_arch(self):
614 """
615 Get the hardware architecture of the remote machine
616 """
617 arch = self.run('/bin/uname -m').stdout.rstrip()
618 if re.match(r'i\d86$', arch):
619 arch = 'i386'
620 return arch
621
622
623 def get_kernel_ver(self):
624 """
625 Get the kernel version of the remote machine
626 """
627 return self.run('/bin/uname -r').stdout.rstrip()
628
629
630 def get_cmdline(self):
631 """
632 Get the kernel command line of the remote machine
633 """
634 return self.run('cat /proc/cmdline').stdout.rstrip()
635
636
637 def ping(self):
638 """
639 Ping the remote system, and return whether it's available
640 """
641 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
642 rc = utils.system(fpingcmd, ignore_status = 1)
643 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000644
645
646 def ssh_ping(self):
647 self.run('ls', timeout=30)