blob: b79f63ff1055bbe88e2a13919693322e36470d85 [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
mbligh31a49de2007-11-05 18:41:19 +0000195 self.__record("FAIL", None, "reboot")
mblighd567f722007-10-30 15:37:33 +0000196 raise errors.AutoservRebootError("Host would not shut down")
mbligh3409ee72007-10-16 23:58:33 +0000197 self.wait_up(timeout)
198 time.sleep(2) # this is needed for complete reliability
mbligh87c5d882007-10-29 17:07:24 +0000199 if not self.wait_up(timeout):
mbligh31a49de2007-11-05 18:41:19 +0000200 self.__record("FAIL", None, "reboot")
mbligh3409ee72007-10-16 23:58:33 +0000201 print "Reboot complete"
202
203
mbligh80d20772007-10-29 17:10:10 +0000204 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000205 """
206 Reach out and slap the box in the power switch
207 """
mblighba81c682007-10-25 15:35:59 +0000208 command_ran = self.__console_run(r"'~$hardreset'")
209 if not command_ran:
210 raise errors.AutoservUnsupportedError
211 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000212 self._wait_for_restart(timeout)
mbligh3409ee72007-10-16 23:58:33 +0000213
214
mblighe6c995f2007-10-26 19:43:01 +0000215 def __conmux_hostname(self):
216 if self.conmux_server:
217 return '%s/%s' % (self.conmux_server, self.hostname)
218 else:
219 return self.hostname
220
221
mbligh3409ee72007-10-16 23:58:33 +0000222 def __start_console_log(self, logfilename):
223 """
224 Log the output of the console session to a specified file
225 """
226 if logfilename == None:
227 return
228 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
229 return
mblighe6c995f2007-10-26 19:43:01 +0000230 cmd = [self.conmux_attach, self.__conmux_hostname(), 'cat - >> %s' % logfilename]
mbligh3409ee72007-10-16 23:58:33 +0000231 logger = subprocess.Popen(cmd,
232 stderr=open('/dev/null', 'w'),
233 preexec_fn=lambda: os.setpgid(0, 0))
234 self.logger_pid = logger.pid
235
236
mblighe6c995f2007-10-26 19:43:01 +0000237 def __start_warning_log(self, logfilename):
238 """
239 Log the output of the warning monitor to a specified file
240 """
241 if logfilename == None:
242 return
243 script_path = os.path.join(self.serverdir, 'warning_monitor')
mbligh7c5452d2007-11-05 18:35:31 +0000244 script_cmd = 'expect %s %s >> %s' % (script_path,
245 self.hostname,
246 logfilename)
mblighe6c995f2007-10-26 19:43:01 +0000247 if self.conmux_server:
248 to = '%s/%s'
249 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
250 logger = subprocess.Popen(cmd,
mbligh7c5452d2007-11-05 18:35:31 +0000251 stderr=open('debug/conmux.log', 'a', 0),
mblighe6c995f2007-10-26 19:43:01 +0000252 preexec_fn=lambda: os.setpgid(0, 0))
253 self.warning_pid = logger.pid
254
255
mbligh3409ee72007-10-16 23:58:33 +0000256 def __find_console_attach(self, conmux_attach):
257 if conmux_attach:
258 return conmux_attach
259 try:
260 res = utils.run('which conmux-attach')
261 if res.exit_status == 0:
262 return res.stdout.strip()
263 except errors.AutoservRunError, e:
264 pass
mbligh9708f732007-10-18 03:18:54 +0000265 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000266 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000267 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000268 '..', 'autotest',
269 'conmux', 'conmux-attach')
270 locations = [autotest_conmux,
271 autotest_conmux_alt,
272 '/usr/local/conmux/bin/conmux-attach',
273 '/usr/bin/conmux-attach']
274 for l in locations:
275 if os.path.exists(l):
276 return l
277
278 print "WARNING: conmux-attach not found on autoserv server"
279 return None
280
281
282 def __console_run(self, cmd):
283 """
284 Send a command to the conmux session
285 """
286 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
287 return False
mbligh3409ee72007-10-16 23:58:33 +0000288 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000289 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000290 cmd)
291 result = os.system(cmd)
292 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000293
294
mbligh31a49de2007-11-05 18:41:19 +0000295 def __record(self, status_code, subdir, operation, status = ''):
296 if self.job:
297 self.job.record(status_code, subdir, operation, status)
298 else:
299 if not subdir:
300 subdir = "----"
301 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
302 sys.stderr.write(msg + "\n")
303
304
mblighe6647d12007-10-17 00:00:01 +0000305 def ssh_command(self):
306 """Construct an ssh command with proper args for this host."""
mbligh0faf91f2007-10-18 03:10:48 +0000307 return r'%s -l %s -p %d %s' % (self.SSH_BASE_COMMAND,
308 self.user,
309 self.port,
310 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000311
312
mblighcf965b02007-07-25 16:49:45 +0000313 def run(self, command, timeout=None, ignore_status=False):
mbligh7d2bde82007-08-02 16:26:10 +0000314 """
315 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000316
317 Args:
318 command: the command line string
319 timeout: time limit in seconds before attempting to
320 kill the running process. The run() function
321 will take a few seconds longer than 'timeout'
322 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000323 ignore_status: do not raise an exception, no matter
324 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000325
326 Returns:
327 a hosts.base_classes.CmdResult object
328
329 Raises:
330 AutoservRunError: the exit code of the command
331 execution was not 0
332 """
333 #~ print "running %s" % (command,)
mblighe6647d12007-10-17 00:00:01 +0000334 result= utils.run(r'%s "%s"' % (self.ssh_command(),
335 utils.sh_escape(command)),
336 timeout, ignore_status)
mblighdcd57a82007-07-11 23:06:47 +0000337 return result
mbligh7d2bde82007-08-02 16:26:10 +0000338
339
mbligh80d20772007-10-29 17:10:10 +0000340 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
341 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000342 """
343 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000344
mbligha0452c82007-08-08 20:24:57 +0000345 Args:
346 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000347 """
mblighde384372007-10-17 04:25:37 +0000348 # forcibly include the "netconsole" kernel arg
349 if self.__netconsole_param:
350 if kernel_args is None:
351 kernel_args = self.__netconsole_param
352 else:
353 kernel_args += " " + self.__netconsole_param
354 # unload the (possibly loaded) module to avoid shutdown issues
355 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000356 if label or kernel_args:
357 self.bootloader.install_boottool()
358 if label:
359 self.bootloader.set_default(label)
360 if kernel_args:
361 if not label:
362 default = int(self.bootloader.get_default())
363 label = self.bootloader.get_titles()[default]
364 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000365 print "Reboot: initiating reboot"
mbligh31a49de2007-11-05 18:41:19 +0000366 self.__record("GOOD", None, "reboot")
mblighd2e46052007-11-05 18:31:00 +0000367 self.run('(sleep 5; reboot) >/dev/null 2>&1 &')
mbligha0452c82007-08-08 20:24:57 +0000368 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000369 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000370 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000371
mbligh7d2bde82007-08-02 16:26:10 +0000372
mblighdcd57a82007-07-11 23:06:47 +0000373 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000374 """
375 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000376
377 Directories will be copied recursively.
378 If a source component is a directory with a trailing slash,
379 the content of the directory will be copied, otherwise, the
380 directory itself and its content will be copied. This
381 behavior is similar to that of the program 'rsync'.
382
383 Args:
384 source: either
385 1) a single file or directory, as a string
386 2) a list of one or more (possibly mixed)
387 files or directories
388 dest: a file or a directory (if source contains a
389 directory or more than one element, you must
390 supply a directory dest)
391
392 Raises:
393 AutoservRunError: the scp command failed
394 """
395 if isinstance(source, types.StringTypes):
396 source= [source]
397
398 processed_source= []
399 for entry in source:
400 if entry.endswith('/'):
401 format_string= '%s@%s:"%s*"'
402 else:
403 format_string= '%s@%s:"%s"'
404 entry= format_string % (self.user, self.hostname,
405 utils.scp_remote_escape(entry))
406 processed_source.append(entry)
407
408 processed_dest= os.path.abspath(dest)
409 if os.path.isdir(dest):
410 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
411 else:
412 processed_dest= utils.sh_escape(processed_dest)
413
414 utils.run('scp -rpq %s "%s"' % (
415 " ".join(processed_source),
416 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000417
418
mblighdcd57a82007-07-11 23:06:47 +0000419 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000420 """
421 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000422
423 Directories will be copied recursively.
424 If a source component is a directory with a trailing slash,
425 the content of the directory will be copied, otherwise, the
426 directory itself and its content will be copied. This
427 behavior is similar to that of the program 'rsync'.
428
429 Args:
430 source: either
431 1) a single file or directory, as a string
432 2) a list of one or more (possibly mixed)
433 files or directories
434 dest: a file or a directory (if source contains a
435 directory or more than one element, you must
436 supply a directory dest)
437
438 Raises:
439 AutoservRunError: the scp command failed
440 """
441 if isinstance(source, types.StringTypes):
442 source= [source]
443
444 processed_source= []
445 for entry in source:
446 if entry.endswith('/'):
447 format_string= '"%s/"*'
448 else:
449 format_string= '"%s"'
450 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
451 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000452
mblighe6647d12007-10-17 00:00:01 +0000453 result = utils.run(r'%s rsync -h' % self.ssh_command(),
454 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000455
mbligh0faf91f2007-10-18 03:10:48 +0000456 remote_dest = '%s@%s:"%s"' % (
457 self.user, self.hostname,
458 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000459 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000460 utils.run('rsync --rsh="%s" -az %s %s' % (
461 self.SSH_BASE_COMMAND, " ".join(processed_source),
462 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000463 else:
mbligh0faf91f2007-10-18 03:10:48 +0000464 utils.run('scp -rpq %s %s' % (
465 " ".join(processed_source),
466 remote_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000467
mblighdcd57a82007-07-11 23:06:47 +0000468 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000469 """
470 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000471 for temporary file storage.
472
473 The directory and its content will be deleted automatically
474 on the destruction of the Host object that was used to obtain
475 it.
476 """
mbligha25b29e2007-08-26 13:58:04 +0000477 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000478 self.tmp_dirs.append(dir_name)
479 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000480
481
mblighdcd57a82007-07-11 23:06:47 +0000482 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000483 """
484 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000485
486 Returns:
487 True if the remote host is up, False otherwise
488 """
489 try:
490 result= self.run("true", timeout=10)
491 except errors.AutoservRunError:
492 return False
493 else:
494 if result.exit_status == 0:
495 return True
496 else:
mbligh7d2bde82007-08-02 16:26:10 +0000497
mblighdcd57a82007-07-11 23:06:47 +0000498 return False
mbligh7d2bde82007-08-02 16:26:10 +0000499
mblighdcd57a82007-07-11 23:06:47 +0000500 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000501 """
502 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000503
504 In fact, it will wait until an ssh connection to the remote
505 host can be established.
506
507 Args:
508 timeout: time limit in seconds before returning even
509 if the host is not up.
510
511 Returns:
512 True if the host was found to be up, False otherwise
513 """
514 if timeout:
515 end_time= time.time() + timeout
516
517 while not timeout or time.time() < end_time:
518 try:
mblighe9cf9d42007-08-31 08:56:00 +0000519 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000520 result= self.run("true", timeout=run_timeout)
521 except errors.AutoservRunError:
522 pass
523 else:
524 if result.exit_status == 0:
525 return True
526 time.sleep(1)
527
528 return False
mbligh7d2bde82007-08-02 16:26:10 +0000529
530
mblighdcd57a82007-07-11 23:06:47 +0000531 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000532 """
533 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000534
535 In fact, it will wait until an ssh connection to the remote
536 host fails.
537
538 Args:
539 timeout: time limit in seconds before returning even
540 if the host is not up.
541
542 Returns:
543 True if the host was found to be down, False otherwise
544 """
545 if timeout:
546 end_time= time.time() + timeout
547
548 while not timeout or time.time() < end_time:
549 try:
mbligh7e1e9642007-07-31 18:00:45 +0000550 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000551 result= self.run("true", timeout=run_timeout)
552 except errors.AutoservRunError:
553 return True
554 else:
555 if result.aborted:
556 return True
557 time.sleep(1)
558
559 return False
mbligh7d2bde82007-08-02 16:26:10 +0000560
561
mblighdbe4a382007-07-26 19:41:28 +0000562 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000563 """
564 Ensure the host is up if it is not then do not proceed;
565 this prevents cacading failures of tests
566 """
mbligha0452c82007-08-08 20:24:57 +0000567 print 'Ensuring that %s is up before continuing' % self.hostname
568 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000569 print "Performing a hardreset on %s" % self.hostname
570 self.hardreset()
mbligha9563b92007-10-25 14:45:56 +0000571 if not self.wait_up(60 * 30):
572 # 30 minutes should be more than enough
573 raise errors.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000574 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000575
576
mblighdcd57a82007-07-11 23:06:47 +0000577 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000578 """
579 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000580 /proc/cpuinfo.
581
582 Returns:
583 The number of CPUs
584 """
585
mbligh5f876ad2007-10-12 23:59:53 +0000586 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000587 cpus = 0
588 for line in proc_cpuinfo.splitlines():
589 if line.startswith('processor'):
590 cpus += 1
591 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000592
593
594 def check_uptime(self):
595 """
596 Check that uptime is available and monotonically increasing.
597 """
598 if not self.ping():
599 raise "Client is not pingable"
600 result = self.run("/bin/cat /proc/uptime", 30)
601 return result.stdout.strip().split()[0]
602
603
604 def get_arch(self):
605 """
606 Get the hardware architecture of the remote machine
607 """
608 arch = self.run('/bin/uname -m').stdout.rstrip()
609 if re.match(r'i\d86$', arch):
610 arch = 'i386'
611 return arch
612
613
614 def get_kernel_ver(self):
615 """
616 Get the kernel version of the remote machine
617 """
618 return self.run('/bin/uname -r').stdout.rstrip()
619
620
621 def get_cmdline(self):
622 """
623 Get the kernel command line of the remote machine
624 """
625 return self.run('cat /proc/cmdline').stdout.rstrip()
626
627
628 def ping(self):
629 """
630 Ping the remote system, and return whether it's available
631 """
632 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
633 rc = utils.system(fpingcmd, ignore_status = 1)
634 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000635
636
637 def ssh_ping(self):
638 self.run('ls', timeout=30)