blob: 474e36cda7ba5df6ce05e08580095ad03214e034 [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
mbligh0faf91f2007-10-18 03:10:48 +000047 SSH_BASE_COMMAND = 'ssh -a'
48
mblighde384372007-10-17 04:25:37 +000049 def __init__(self, hostname, user="root", port=22, initialize=True,
mblighe6c995f2007-10-26 19:43:01 +000050 conmux_log="console.log", conmux_warnings="warning.log",
51 conmux_server=None, conmux_attach=None,
52 netconsole_log=None, netconsole_port=6666):
mbligh7d2bde82007-08-02 16:26:10 +000053 """
54 Construct a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000055
56 Args:
57 hostname: network hostname or address of remote machine
58 user: user to log in as on the remote machine
59 port: port the ssh daemon is listening on on the remote
60 machine
mbligh9708f732007-10-18 03:18:54 +000061 """
mblighdcd57a82007-07-11 23:06:47 +000062 self.hostname= hostname
63 self.user= user
64 self.port= port
65 self.tmp_dirs= []
mbligh137a05c2007-10-04 15:56:51 +000066 self.initialize = initialize
mbligh91334902007-09-28 01:47:59 +000067
mbligh9708f732007-10-18 03:18:54 +000068 super(SSHHost, self).__init__()
69
mbligh3409ee72007-10-16 23:58:33 +000070 self.conmux_server = conmux_server
71 self.conmux_attach = self.__find_console_attach(conmux_attach)
72 self.logger_pid = None
mblighde384372007-10-17 04:25:37 +000073 self.__start_console_log(conmux_log)
mblighe6c995f2007-10-26 19:43:01 +000074 self.warning_pid = None
75 self.__start_warning_log(conmux_warnings)
mbligh3409ee72007-10-16 23:58:33 +000076
mbligha0452c82007-08-08 20:24:57 +000077 self.bootloader = bootloader.Bootloader(self)
mbligh7d2bde82007-08-02 16:26:10 +000078
mblighde384372007-10-17 04:25:37 +000079 self.__init_netconsole_params(netconsole_port)
80 self.netlogger_pid = None
81 self.__start_netconsole_log(netconsole_log, netconsole_port)
82 self.__load_netconsole_module()
83
mbligh7d2bde82007-08-02 16:26:10 +000084
mblighdcd57a82007-07-11 23:06:47 +000085 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +000086 """
87 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000088 """
89 for dir in self.tmp_dirs:
90 try:
91 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
92 except errors.AutoservRunError:
93 pass
mblighde384372007-10-17 04:25:37 +000094 # kill the console logger
mbligh7364ae42007-10-18 03:20:34 +000095 if getattr(self, 'logger_pid', None):
mbligh3409ee72007-10-16 23:58:33 +000096 try:
97 pgid = os.getpgid(self.logger_pid)
98 os.killpg(pgid, signal.SIGTERM)
99 except OSError:
100 pass
mblighde384372007-10-17 04:25:37 +0000101 # kill the netconsole logger
mbligh7364ae42007-10-18 03:20:34 +0000102 if getattr(self, 'netlogger_pid', None):
mblighe6c995f2007-10-26 19:43:01 +0000103 self.__unload_netconsole_module()
mblighde384372007-10-17 04:25:37 +0000104 try:
105 os.kill(self.netlogger_pid, signal.SIGTERM)
106 except OSError:
107 pass
mblighe6c995f2007-10-26 19:43:01 +0000108 # kill the warning logger
109 if getattr(self, 'warning_pid', None):
110 try:
111 pgid = os.getpgid(self.warning_pid)
112 os.killpg(pgid, signal.SIGTERM)
113 except OSError:
114 pass
mblighde384372007-10-17 04:25:37 +0000115
116
117 def __init_netconsole_params(self, port):
118 """
119 Connect to the remote machine and determine the values to use for the
120 required netconsole parameters.
121 """
122 self.__netconsole_param = ""
123 # PROBLEM: on machines with multiple IPs this may not make any sense
124 # It also doesn't work with IPv6
125 remote_ip = socket.gethostbyname(self.hostname)
126 local_ip = socket.gethostbyname(socket.gethostname())
127 # Get the gateway of the remote machine
128 try:
129 traceroute = self.run('traceroute -n %s' % local_ip)
130 except errors.AutoservRunError:
131 return
132 first_node = traceroute.stdout.split("\n")[0]
133 match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
134 if match:
135 router_ip = match.group(1)
136 else:
137 return
138 # Look up the MAC address of the gateway
139 try:
140 self.run('ping -c 1 %s' % router_ip)
141 arp = self.run('arp -n -a %s' % router_ip)
142 except errors.AutoservRunError:
143 return
144 match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
145 if match:
146 gateway_mac = match.group(1)
147 else:
148 return
149 self.__netconsole_param = 'netconsole=@%s/,%s@%s/%s' % (remote_ip,
150 port,
151 local_ip,
152 gateway_mac)
153
154
155 def __start_netconsole_log(self, logfilename, port):
156 """
157 Log the output of netconsole to a specified file
158 """
159 if logfilename == None:
160 return
161 cmd = ['nc', '-u', '-l', '-p', str(port)]
mblighd2fc50f2007-10-23 22:38:00 +0000162 logger = subprocess.Popen(cmd, stdout=open(logfilename, "a", 0))
mblighde384372007-10-17 04:25:37 +0000163 self.netlogger_pid = logger.pid
164
165
166 def __load_netconsole_module(self):
167 """
168 Make a best effort to load the netconsole module.
169
170 Note that loading the module can fail even when the remote machine is
171 working correctly if netconsole is already compiled into the kernel
172 and started.
173 """
174 try:
175 self.run('modprobe netconsole %s' % self.__netconsole_param)
176 except errors.AutoservRunError:
177 # if it fails there isn't much we can do, just keep going
178 pass
179
180
181 def __unload_netconsole_module(self):
182 try:
183 self.run('modprobe -r netconsole')
184 except errors.AutoservRunError:
185 pass
mbligh3409ee72007-10-16 23:58:33 +0000186
187
mbligh6a0010f2007-10-25 15:45:21 +0000188 def _wait_for_restart(self, timeout):
mbligh3409ee72007-10-16 23:58:33 +0000189 self.wait_down(60) # Make sure he's dead, Jim
190 self.wait_up(timeout)
191 time.sleep(2) # this is needed for complete reliability
192 self.wait_up(timeout)
193 print "Reboot complete"
194
195
196 def hardreset(self, timeout=600, wait=True):
197 """
198 Reach out and slap the box in the power switch
199 """
mblighba81c682007-10-25 15:35:59 +0000200 command_ran = self.__console_run(r"'~$hardreset'")
201 if not command_ran:
202 raise errors.AutoservUnsupportedError
203 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000204 self._wait_for_restart(timeout)
mbligh3409ee72007-10-16 23:58:33 +0000205
206
mblighe6c995f2007-10-26 19:43:01 +0000207 def __conmux_hostname(self):
208 if self.conmux_server:
209 return '%s/%s' % (self.conmux_server, self.hostname)
210 else:
211 return self.hostname
212
213
mbligh3409ee72007-10-16 23:58:33 +0000214 def __start_console_log(self, logfilename):
215 """
216 Log the output of the console session to a specified file
217 """
218 if logfilename == None:
219 return
220 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
221 return
mblighe6c995f2007-10-26 19:43:01 +0000222 cmd = [self.conmux_attach, self.__conmux_hostname(), 'cat - >> %s' % logfilename]
mbligh3409ee72007-10-16 23:58:33 +0000223 logger = subprocess.Popen(cmd,
224 stderr=open('/dev/null', 'w'),
225 preexec_fn=lambda: os.setpgid(0, 0))
226 self.logger_pid = logger.pid
227
228
mblighe6c995f2007-10-26 19:43:01 +0000229 def __start_warning_log(self, logfilename):
230 """
231 Log the output of the warning monitor to a specified file
232 """
233 if logfilename == None:
234 return
235 script_path = os.path.join(self.serverdir, 'warning_monitor')
236 script_cmd = 'expect %s %s' % (script_path, self.hostname)
237 if self.conmux_server:
238 to = '%s/%s'
239 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
240 logger = subprocess.Popen(cmd,
241 stdout=open(logfilename, 'a', 0),
242 stderr=subprocess.STDOUT,
243 preexec_fn=lambda: os.setpgid(0, 0))
244 self.warning_pid = logger.pid
245
246
mbligh3409ee72007-10-16 23:58:33 +0000247 def __find_console_attach(self, conmux_attach):
248 if conmux_attach:
249 return conmux_attach
250 try:
251 res = utils.run('which conmux-attach')
252 if res.exit_status == 0:
253 return res.stdout.strip()
254 except errors.AutoservRunError, e:
255 pass
mbligh9708f732007-10-18 03:18:54 +0000256 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000257 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000258 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000259 '..', 'autotest',
260 'conmux', 'conmux-attach')
261 locations = [autotest_conmux,
262 autotest_conmux_alt,
263 '/usr/local/conmux/bin/conmux-attach',
264 '/usr/bin/conmux-attach']
265 for l in locations:
266 if os.path.exists(l):
267 return l
268
269 print "WARNING: conmux-attach not found on autoserv server"
270 return None
271
272
273 def __console_run(self, cmd):
274 """
275 Send a command to the conmux session
276 """
277 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
278 return False
mbligh3409ee72007-10-16 23:58:33 +0000279 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000280 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000281 cmd)
282 result = os.system(cmd)
283 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000284
285
mblighe6647d12007-10-17 00:00:01 +0000286 def ssh_command(self):
287 """Construct an ssh command with proper args for this host."""
mbligh0faf91f2007-10-18 03:10:48 +0000288 return r'%s -l %s -p %d %s' % (self.SSH_BASE_COMMAND,
289 self.user,
290 self.port,
291 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000292
293
mblighcf965b02007-07-25 16:49:45 +0000294 def run(self, command, timeout=None, ignore_status=False):
mbligh7d2bde82007-08-02 16:26:10 +0000295 """
296 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000297
298 Args:
299 command: the command line string
300 timeout: time limit in seconds before attempting to
301 kill the running process. The run() function
302 will take a few seconds longer than 'timeout'
303 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000304 ignore_status: do not raise an exception, no matter
305 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000306
307 Returns:
308 a hosts.base_classes.CmdResult object
309
310 Raises:
311 AutoservRunError: the exit code of the command
312 execution was not 0
313 """
314 #~ print "running %s" % (command,)
mblighe6647d12007-10-17 00:00:01 +0000315 result= utils.run(r'%s "%s"' % (self.ssh_command(),
316 utils.sh_escape(command)),
317 timeout, ignore_status)
mblighdcd57a82007-07-11 23:06:47 +0000318 return result
mbligh7d2bde82007-08-02 16:26:10 +0000319
320
mbligha0452c82007-08-08 20:24:57 +0000321 def reboot(self, timeout=600, label=None, kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000322 """
323 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000324
mbligha0452c82007-08-08 20:24:57 +0000325 Args:
326 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000327 """
mblighde384372007-10-17 04:25:37 +0000328 # forcibly include the "netconsole" kernel arg
329 if self.__netconsole_param:
330 if kernel_args is None:
331 kernel_args = self.__netconsole_param
332 else:
333 kernel_args += " " + self.__netconsole_param
334 # unload the (possibly loaded) module to avoid shutdown issues
335 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000336 if label or kernel_args:
337 self.bootloader.install_boottool()
338 if label:
339 self.bootloader.set_default(label)
340 if kernel_args:
341 if not label:
342 default = int(self.bootloader.get_default())
343 label = self.bootloader.get_titles()[default]
344 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000345 print "Reboot: initiating reboot"
mbligha0452c82007-08-08 20:24:57 +0000346 self.run('reboot')
347 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000348 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000349 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000350
mbligh7d2bde82007-08-02 16:26:10 +0000351
mblighdcd57a82007-07-11 23:06:47 +0000352 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000353 """
354 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000355
356 Directories will be copied recursively.
357 If a source component is a directory with a trailing slash,
358 the content of the directory will be copied, otherwise, the
359 directory itself and its content will be copied. This
360 behavior is similar to that of the program 'rsync'.
361
362 Args:
363 source: either
364 1) a single file or directory, as a string
365 2) a list of one or more (possibly mixed)
366 files or directories
367 dest: a file or a directory (if source contains a
368 directory or more than one element, you must
369 supply a directory dest)
370
371 Raises:
372 AutoservRunError: the scp command failed
373 """
374 if isinstance(source, types.StringTypes):
375 source= [source]
376
377 processed_source= []
378 for entry in source:
379 if entry.endswith('/'):
380 format_string= '%s@%s:"%s*"'
381 else:
382 format_string= '%s@%s:"%s"'
383 entry= format_string % (self.user, self.hostname,
384 utils.scp_remote_escape(entry))
385 processed_source.append(entry)
386
387 processed_dest= os.path.abspath(dest)
388 if os.path.isdir(dest):
389 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
390 else:
391 processed_dest= utils.sh_escape(processed_dest)
392
393 utils.run('scp -rpq %s "%s"' % (
394 " ".join(processed_source),
395 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000396
397
mblighdcd57a82007-07-11 23:06:47 +0000398 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000399 """
400 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000401
402 Directories will be copied recursively.
403 If a source component is a directory with a trailing slash,
404 the content of the directory will be copied, otherwise, the
405 directory itself and its content will be copied. This
406 behavior is similar to that of the program 'rsync'.
407
408 Args:
409 source: either
410 1) a single file or directory, as a string
411 2) a list of one or more (possibly mixed)
412 files or directories
413 dest: a file or a directory (if source contains a
414 directory or more than one element, you must
415 supply a directory dest)
416
417 Raises:
418 AutoservRunError: the scp command failed
419 """
420 if isinstance(source, types.StringTypes):
421 source= [source]
422
423 processed_source= []
424 for entry in source:
425 if entry.endswith('/'):
426 format_string= '"%s/"*'
427 else:
428 format_string= '"%s"'
429 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
430 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000431
mblighe6647d12007-10-17 00:00:01 +0000432 result = utils.run(r'%s rsync -h' % self.ssh_command(),
433 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000434
mbligh0faf91f2007-10-18 03:10:48 +0000435 remote_dest = '%s@%s:"%s"' % (
436 self.user, self.hostname,
437 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000438 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000439 utils.run('rsync --rsh="%s" -az %s %s' % (
440 self.SSH_BASE_COMMAND, " ".join(processed_source),
441 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000442 else:
mbligh0faf91f2007-10-18 03:10:48 +0000443 utils.run('scp -rpq %s %s' % (
444 " ".join(processed_source),
445 remote_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000446
mblighdcd57a82007-07-11 23:06:47 +0000447 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000448 """
449 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000450 for temporary file storage.
451
452 The directory and its content will be deleted automatically
453 on the destruction of the Host object that was used to obtain
454 it.
455 """
mbligha25b29e2007-08-26 13:58:04 +0000456 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000457 self.tmp_dirs.append(dir_name)
458 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000459
460
mblighdcd57a82007-07-11 23:06:47 +0000461 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000462 """
463 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000464
465 Returns:
466 True if the remote host is up, False otherwise
467 """
468 try:
469 result= self.run("true", timeout=10)
470 except errors.AutoservRunError:
471 return False
472 else:
473 if result.exit_status == 0:
474 return True
475 else:
mbligh7d2bde82007-08-02 16:26:10 +0000476
mblighdcd57a82007-07-11 23:06:47 +0000477 return False
mbligh7d2bde82007-08-02 16:26:10 +0000478
mblighdcd57a82007-07-11 23:06:47 +0000479 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000480 """
481 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000482
483 In fact, it will wait until an ssh connection to the remote
484 host can be established.
485
486 Args:
487 timeout: time limit in seconds before returning even
488 if the host is not up.
489
490 Returns:
491 True if the host was found to be up, False otherwise
492 """
493 if timeout:
494 end_time= time.time() + timeout
495
496 while not timeout or time.time() < end_time:
497 try:
mblighe9cf9d42007-08-31 08:56:00 +0000498 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000499 result= self.run("true", timeout=run_timeout)
500 except errors.AutoservRunError:
501 pass
502 else:
503 if result.exit_status == 0:
504 return True
505 time.sleep(1)
506
507 return False
mbligh7d2bde82007-08-02 16:26:10 +0000508
509
mblighdcd57a82007-07-11 23:06:47 +0000510 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000511 """
512 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000513
514 In fact, it will wait until an ssh connection to the remote
515 host fails.
516
517 Args:
518 timeout: time limit in seconds before returning even
519 if the host is not up.
520
521 Returns:
522 True if the host was found to be down, False otherwise
523 """
524 if timeout:
525 end_time= time.time() + timeout
526
527 while not timeout or time.time() < end_time:
528 try:
mbligh7e1e9642007-07-31 18:00:45 +0000529 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000530 result= self.run("true", timeout=run_timeout)
531 except errors.AutoservRunError:
532 return True
533 else:
534 if result.aborted:
535 return True
536 time.sleep(1)
537
538 return False
mbligh7d2bde82007-08-02 16:26:10 +0000539
540
mblighdbe4a382007-07-26 19:41:28 +0000541 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000542 """
543 Ensure the host is up if it is not then do not proceed;
544 this prevents cacading failures of tests
545 """
mbligha0452c82007-08-08 20:24:57 +0000546 print 'Ensuring that %s is up before continuing' % self.hostname
547 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000548 print "Performing a hardreset on %s" % self.hostname
549 self.hardreset()
mbligha9563b92007-10-25 14:45:56 +0000550 if not self.wait_up(60 * 30):
551 # 30 minutes should be more than enough
552 raise errors.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000553 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000554
555
mblighdcd57a82007-07-11 23:06:47 +0000556 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000557 """
558 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000559 /proc/cpuinfo.
560
561 Returns:
562 The number of CPUs
563 """
564
mbligh5f876ad2007-10-12 23:59:53 +0000565 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000566 cpus = 0
567 for line in proc_cpuinfo.splitlines():
568 if line.startswith('processor'):
569 cpus += 1
570 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000571
572
573 def check_uptime(self):
574 """
575 Check that uptime is available and monotonically increasing.
576 """
577 if not self.ping():
578 raise "Client is not pingable"
579 result = self.run("/bin/cat /proc/uptime", 30)
580 return result.stdout.strip().split()[0]
581
582
583 def get_arch(self):
584 """
585 Get the hardware architecture of the remote machine
586 """
587 arch = self.run('/bin/uname -m').stdout.rstrip()
588 if re.match(r'i\d86$', arch):
589 arch = 'i386'
590 return arch
591
592
593 def get_kernel_ver(self):
594 """
595 Get the kernel version of the remote machine
596 """
597 return self.run('/bin/uname -r').stdout.rstrip()
598
599
600 def get_cmdline(self):
601 """
602 Get the kernel command line of the remote machine
603 """
604 return self.run('cat /proc/cmdline').stdout.rstrip()
605
606
607 def ping(self):
608 """
609 Ping the remote system, and return whether it's available
610 """
611 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
612 rc = utils.system(fpingcmd, ignore_status = 1)
613 return (rc == 0)