blob: e4a5a8e8ae152bf0388bd5d8ffb4dad9b843b1aa [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
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
mbligh31a49de2007-11-05 18:41:19 +0000192 self.__record("FAIL", None, "reboot")
mblighd567f722007-10-30 15:37:33 +0000193 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):
mbligh31a49de2007-11-05 18:41:19 +0000197 self.__record("FAIL", None, "reboot")
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
mbligh31a49de2007-11-05 18:41:19 +0000292 def __record(self, status_code, subdir, operation, status = ''):
293 if self.job:
294 self.job.record(status_code, subdir, operation, status)
295 else:
296 if not subdir:
297 subdir = "----"
298 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
299 sys.stderr.write(msg + "\n")
300
301
mblighe6647d12007-10-17 00:00:01 +0000302 def ssh_command(self):
303 """Construct an ssh command with proper args for this host."""
mbligh0faf91f2007-10-18 03:10:48 +0000304 return r'%s -l %s -p %d %s' % (self.SSH_BASE_COMMAND,
305 self.user,
306 self.port,
307 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000308
309
mblighcf965b02007-07-25 16:49:45 +0000310 def run(self, command, timeout=None, ignore_status=False):
mbligh7d2bde82007-08-02 16:26:10 +0000311 """
312 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000313
314 Args:
315 command: the command line string
316 timeout: time limit in seconds before attempting to
317 kill the running process. The run() function
318 will take a few seconds longer than 'timeout'
319 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000320 ignore_status: do not raise an exception, no matter
321 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000322
323 Returns:
324 a hosts.base_classes.CmdResult object
325
326 Raises:
327 AutoservRunError: the exit code of the command
328 execution was not 0
329 """
330 #~ print "running %s" % (command,)
mblighe6647d12007-10-17 00:00:01 +0000331 result= utils.run(r'%s "%s"' % (self.ssh_command(),
332 utils.sh_escape(command)),
333 timeout, ignore_status)
mblighdcd57a82007-07-11 23:06:47 +0000334 return result
mbligh7d2bde82007-08-02 16:26:10 +0000335
336
mbligh80d20772007-10-29 17:10:10 +0000337 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
338 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000339 """
340 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000341
mbligha0452c82007-08-08 20:24:57 +0000342 Args:
343 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000344 """
mblighde384372007-10-17 04:25:37 +0000345 # forcibly include the "netconsole" kernel arg
346 if self.__netconsole_param:
347 if kernel_args is None:
348 kernel_args = self.__netconsole_param
349 else:
350 kernel_args += " " + self.__netconsole_param
351 # unload the (possibly loaded) module to avoid shutdown issues
352 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000353 if label or kernel_args:
354 self.bootloader.install_boottool()
355 if label:
356 self.bootloader.set_default(label)
357 if kernel_args:
358 if not label:
359 default = int(self.bootloader.get_default())
360 label = self.bootloader.get_titles()[default]
361 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000362 print "Reboot: initiating reboot"
mbligh31a49de2007-11-05 18:41:19 +0000363 self.__record("GOOD", None, "reboot")
mblighd2e46052007-11-05 18:31:00 +0000364 self.run('(sleep 5; reboot) >/dev/null 2>&1 &')
mbligha0452c82007-08-08 20:24:57 +0000365 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000366 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000367 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000368
mbligh7d2bde82007-08-02 16:26:10 +0000369
mblighdcd57a82007-07-11 23:06:47 +0000370 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000371 """
372 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000373
374 Directories will be copied recursively.
375 If a source component is a directory with a trailing slash,
376 the content of the directory will be copied, otherwise, the
377 directory itself and its content will be copied. This
378 behavior is similar to that of the program 'rsync'.
379
380 Args:
381 source: either
382 1) a single file or directory, as a string
383 2) a list of one or more (possibly mixed)
384 files or directories
385 dest: a file or a directory (if source contains a
386 directory or more than one element, you must
387 supply a directory dest)
388
389 Raises:
390 AutoservRunError: the scp command failed
391 """
392 if isinstance(source, types.StringTypes):
393 source= [source]
394
395 processed_source= []
396 for entry in source:
397 if entry.endswith('/'):
398 format_string= '%s@%s:"%s*"'
399 else:
400 format_string= '%s@%s:"%s"'
401 entry= format_string % (self.user, self.hostname,
402 utils.scp_remote_escape(entry))
403 processed_source.append(entry)
404
405 processed_dest= os.path.abspath(dest)
406 if os.path.isdir(dest):
407 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
408 else:
409 processed_dest= utils.sh_escape(processed_dest)
410
411 utils.run('scp -rpq %s "%s"' % (
412 " ".join(processed_source),
413 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000414
415
mblighdcd57a82007-07-11 23:06:47 +0000416 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000417 """
418 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000419
420 Directories will be copied recursively.
421 If a source component is a directory with a trailing slash,
422 the content of the directory will be copied, otherwise, the
423 directory itself and its content will be copied. This
424 behavior is similar to that of the program 'rsync'.
425
426 Args:
427 source: either
428 1) a single file or directory, as a string
429 2) a list of one or more (possibly mixed)
430 files or directories
431 dest: a file or a directory (if source contains a
432 directory or more than one element, you must
433 supply a directory dest)
434
435 Raises:
436 AutoservRunError: the scp command failed
437 """
438 if isinstance(source, types.StringTypes):
439 source= [source]
440
441 processed_source= []
442 for entry in source:
443 if entry.endswith('/'):
444 format_string= '"%s/"*'
445 else:
446 format_string= '"%s"'
447 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
448 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000449
mblighe6647d12007-10-17 00:00:01 +0000450 result = utils.run(r'%s rsync -h' % self.ssh_command(),
451 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000452
mbligh0faf91f2007-10-18 03:10:48 +0000453 remote_dest = '%s@%s:"%s"' % (
454 self.user, self.hostname,
455 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000456 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000457 utils.run('rsync --rsh="%s" -az %s %s' % (
458 self.SSH_BASE_COMMAND, " ".join(processed_source),
459 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000460 else:
mbligh0faf91f2007-10-18 03:10:48 +0000461 utils.run('scp -rpq %s %s' % (
462 " ".join(processed_source),
463 remote_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000464
mblighdcd57a82007-07-11 23:06:47 +0000465 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000466 """
467 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000468 for temporary file storage.
469
470 The directory and its content will be deleted automatically
471 on the destruction of the Host object that was used to obtain
472 it.
473 """
mbligha25b29e2007-08-26 13:58:04 +0000474 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000475 self.tmp_dirs.append(dir_name)
476 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000477
478
mblighdcd57a82007-07-11 23:06:47 +0000479 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000480 """
481 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000482
483 Returns:
484 True if the remote host is up, False otherwise
485 """
486 try:
487 result= self.run("true", timeout=10)
488 except errors.AutoservRunError:
489 return False
490 else:
491 if result.exit_status == 0:
492 return True
493 else:
mbligh7d2bde82007-08-02 16:26:10 +0000494
mblighdcd57a82007-07-11 23:06:47 +0000495 return False
mbligh7d2bde82007-08-02 16:26:10 +0000496
mblighdcd57a82007-07-11 23:06:47 +0000497 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000498 """
499 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000500
501 In fact, it will wait until an ssh connection to the remote
502 host can be established.
503
504 Args:
505 timeout: time limit in seconds before returning even
506 if the host is not up.
507
508 Returns:
509 True if the host was found to be up, False otherwise
510 """
511 if timeout:
512 end_time= time.time() + timeout
513
514 while not timeout or time.time() < end_time:
515 try:
mblighe9cf9d42007-08-31 08:56:00 +0000516 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000517 result= self.run("true", timeout=run_timeout)
518 except errors.AutoservRunError:
519 pass
520 else:
521 if result.exit_status == 0:
522 return True
523 time.sleep(1)
524
525 return False
mbligh7d2bde82007-08-02 16:26:10 +0000526
527
mblighdcd57a82007-07-11 23:06:47 +0000528 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000529 """
530 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000531
532 In fact, it will wait until an ssh connection to the remote
533 host fails.
534
535 Args:
536 timeout: time limit in seconds before returning even
537 if the host is not up.
538
539 Returns:
540 True if the host was found to be down, False otherwise
541 """
542 if timeout:
543 end_time= time.time() + timeout
544
545 while not timeout or time.time() < end_time:
546 try:
mbligh7e1e9642007-07-31 18:00:45 +0000547 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000548 result= self.run("true", timeout=run_timeout)
549 except errors.AutoservRunError:
550 return True
551 else:
552 if result.aborted:
553 return True
554 time.sleep(1)
555
556 return False
mbligh7d2bde82007-08-02 16:26:10 +0000557
558
mblighdbe4a382007-07-26 19:41:28 +0000559 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000560 """
561 Ensure the host is up if it is not then do not proceed;
562 this prevents cacading failures of tests
563 """
mbligha0452c82007-08-08 20:24:57 +0000564 print 'Ensuring that %s is up before continuing' % self.hostname
565 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000566 print "Performing a hardreset on %s" % self.hostname
567 self.hardreset()
mbligha9563b92007-10-25 14:45:56 +0000568 if not self.wait_up(60 * 30):
569 # 30 minutes should be more than enough
570 raise errors.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000571 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000572
573
mblighdcd57a82007-07-11 23:06:47 +0000574 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000575 """
576 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000577 /proc/cpuinfo.
578
579 Returns:
580 The number of CPUs
581 """
582
mbligh5f876ad2007-10-12 23:59:53 +0000583 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000584 cpus = 0
585 for line in proc_cpuinfo.splitlines():
586 if line.startswith('processor'):
587 cpus += 1
588 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000589
590
591 def check_uptime(self):
592 """
593 Check that uptime is available and monotonically increasing.
594 """
595 if not self.ping():
596 raise "Client is not pingable"
597 result = self.run("/bin/cat /proc/uptime", 30)
598 return result.stdout.strip().split()[0]
599
600
601 def get_arch(self):
602 """
603 Get the hardware architecture of the remote machine
604 """
605 arch = self.run('/bin/uname -m').stdout.rstrip()
606 if re.match(r'i\d86$', arch):
607 arch = 'i386'
608 return arch
609
610
611 def get_kernel_ver(self):
612 """
613 Get the kernel version of the remote machine
614 """
615 return self.run('/bin/uname -r').stdout.rstrip()
616
617
618 def get_cmdline(self):
619 """
620 Get the kernel command line of the remote machine
621 """
622 return self.run('cat /proc/cmdline').stdout.rstrip()
623
624
625 def ping(self):
626 """
627 Ping the remote system, and return whether it's available
628 """
629 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
630 rc = utils.system(fpingcmd, ignore_status = 1)
631 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000632
633
634 def ssh_ping(self):
635 self.run('ls', timeout=30)