blob: 26b7e77fab0756c2503b22ca06b5e286499a8067 [file] [log] [blame]
mblighdcd57a82007-07-11 23:06:47 +00001#!/usr/bin/python
2#
3# Copyright 2007 Google Inc. Released under the GPL v2
4
mbligh7d2bde82007-08-02 16:26:10 +00005"""
6This module defines the SSHHost class.
mblighdcd57a82007-07-11 23:06:47 +00007
8Implementation details:
9You should import the "hosts" package instead of importing each type of host.
10
11 SSHHost: a remote machine with a ssh access
12"""
13
mbligh7d2bde82007-08-02 16:26:10 +000014__author__ = """
15mbligh@google.com (Martin J. Bligh),
mblighdcd57a82007-07-11 23:06:47 +000016poirier@google.com (Benjamin Poirier),
mbligh7d2bde82007-08-02 16:26:10 +000017stutsman@google.com (Ryan Stutsman)
18"""
mblighdcd57a82007-07-11 23:06:47 +000019
20
mblighde384372007-10-17 04:25:37 +000021import types, os, sys, signal, subprocess, time, re, socket
mbligh5f876ad2007-10-12 23:59:53 +000022import base_classes, utils, errors, bootloader
mblighdcd57a82007-07-11 23:06:47 +000023
mbligh80d20772007-10-29 17:10:10 +000024DEFAULT_REBOOT_TIMEOUT = 1800
25
mblighdcd57a82007-07-11 23:06:47 +000026
27class SSHHost(base_classes.RemoteHost):
mbligh7d2bde82007-08-02 16:26:10 +000028 """
29 This class represents a remote machine controlled through an ssh
mblighdcd57a82007-07-11 23:06:47 +000030 session on which you can run programs.
mbligh7d2bde82007-08-02 16:26:10 +000031
mblighdcd57a82007-07-11 23:06:47 +000032 It is not the machine autoserv is running on. The machine must be
33 configured for password-less login, for example through public key
34 authentication.
mbligh7d2bde82007-08-02 16:26:10 +000035
mbligh3409ee72007-10-16 23:58:33 +000036 It includes support for controlling the machine through a serial
37 console on which you can run programs. If such a serial console is
38 set up on the machine then capabilities such as hard reset and
39 boot strap monitoring are available. If the machine does not have a
40 serial console available then ordinary SSH-based commands will
41 still be available, but attempts to use extensions such as
42 console logging or hard reset will fail silently.
43
mblighdcd57a82007-07-11 23:06:47 +000044 Implementation details:
45 This is a leaf class in an abstract class hierarchy, it must
46 implement the unimplemented methods in parent classes.
47 """
mbligh7d2bde82007-08-02 16:26:10 +000048
mbligh0faf91f2007-10-18 03:10:48 +000049 SSH_BASE_COMMAND = 'ssh -a'
50
mblighde384372007-10-17 04:25:37 +000051 def __init__(self, hostname, user="root", port=22, initialize=True,
mblighe6c995f2007-10-26 19:43:01 +000052 conmux_log="console.log", conmux_warnings="warning.log",
53 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):
mbligh3409ee72007-10-16 23:58:33 +0000191 self.wait_down(60) # Make sure he's dead, Jim
192 self.wait_up(timeout)
193 time.sleep(2) # this is needed for complete reliability
mbligh87c5d882007-10-29 17:07:24 +0000194 if not self.wait_up(timeout):
195 sys.stderr.write("REBOOT ERROR\n")
mbligh3409ee72007-10-16 23:58:33 +0000196 print "Reboot complete"
197
198
mbligh80d20772007-10-29 17:10:10 +0000199 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000200 """
201 Reach out and slap the box in the power switch
202 """
mblighba81c682007-10-25 15:35:59 +0000203 command_ran = self.__console_run(r"'~$hardreset'")
204 if not command_ran:
205 raise errors.AutoservUnsupportedError
206 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000207 self._wait_for_restart(timeout)
mbligh3409ee72007-10-16 23:58:33 +0000208
209
mblighe6c995f2007-10-26 19:43:01 +0000210 def __conmux_hostname(self):
211 if self.conmux_server:
212 return '%s/%s' % (self.conmux_server, self.hostname)
213 else:
214 return self.hostname
215
216
mbligh3409ee72007-10-16 23:58:33 +0000217 def __start_console_log(self, logfilename):
218 """
219 Log the output of the console session to a specified file
220 """
221 if logfilename == None:
222 return
223 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
224 return
mblighe6c995f2007-10-26 19:43:01 +0000225 cmd = [self.conmux_attach, self.__conmux_hostname(), 'cat - >> %s' % logfilename]
mbligh3409ee72007-10-16 23:58:33 +0000226 logger = subprocess.Popen(cmd,
227 stderr=open('/dev/null', 'w'),
228 preexec_fn=lambda: os.setpgid(0, 0))
229 self.logger_pid = logger.pid
230
231
mblighe6c995f2007-10-26 19:43:01 +0000232 def __start_warning_log(self, logfilename):
233 """
234 Log the output of the warning monitor to a specified file
235 """
236 if logfilename == None:
237 return
238 script_path = os.path.join(self.serverdir, 'warning_monitor')
239 script_cmd = 'expect %s %s' % (script_path, self.hostname)
240 if self.conmux_server:
241 to = '%s/%s'
242 cmd = [self.conmux_attach, self.__conmux_hostname(), script_cmd]
243 logger = subprocess.Popen(cmd,
244 stdout=open(logfilename, 'a', 0),
245 stderr=subprocess.STDOUT,
246 preexec_fn=lambda: os.setpgid(0, 0))
247 self.warning_pid = logger.pid
248
249
mbligh3409ee72007-10-16 23:58:33 +0000250 def __find_console_attach(self, conmux_attach):
251 if conmux_attach:
252 return conmux_attach
253 try:
254 res = utils.run('which conmux-attach')
255 if res.exit_status == 0:
256 return res.stdout.strip()
257 except errors.AutoservRunError, e:
258 pass
mbligh9708f732007-10-18 03:18:54 +0000259 autotest_conmux = os.path.join(self.serverdir, '..',
mbligh3409ee72007-10-16 23:58:33 +0000260 'conmux', 'conmux-attach')
mbligh9708f732007-10-18 03:18:54 +0000261 autotest_conmux_alt = os.path.join(self.serverdir,
mbligh3409ee72007-10-16 23:58:33 +0000262 '..', 'autotest',
263 'conmux', 'conmux-attach')
264 locations = [autotest_conmux,
265 autotest_conmux_alt,
266 '/usr/local/conmux/bin/conmux-attach',
267 '/usr/bin/conmux-attach']
268 for l in locations:
269 if os.path.exists(l):
270 return l
271
272 print "WARNING: conmux-attach not found on autoserv server"
273 return None
274
275
276 def __console_run(self, cmd):
277 """
278 Send a command to the conmux session
279 """
280 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
281 return False
mbligh3409ee72007-10-16 23:58:33 +0000282 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000283 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000284 cmd)
285 result = os.system(cmd)
286 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000287
288
mblighe6647d12007-10-17 00:00:01 +0000289 def ssh_command(self):
290 """Construct an ssh command with proper args for this host."""
mbligh0faf91f2007-10-18 03:10:48 +0000291 return r'%s -l %s -p %d %s' % (self.SSH_BASE_COMMAND,
292 self.user,
293 self.port,
294 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000295
296
mblighcf965b02007-07-25 16:49:45 +0000297 def run(self, command, timeout=None, ignore_status=False):
mbligh7d2bde82007-08-02 16:26:10 +0000298 """
299 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000300
301 Args:
302 command: the command line string
303 timeout: time limit in seconds before attempting to
304 kill the running process. The run() function
305 will take a few seconds longer than 'timeout'
306 to complete if it has to kill the process.
mbligh8b85dfb2007-08-28 09:50:31 +0000307 ignore_status: do not raise an exception, no matter
308 what the exit code of the command is.
mblighdcd57a82007-07-11 23:06:47 +0000309
310 Returns:
311 a hosts.base_classes.CmdResult object
312
313 Raises:
314 AutoservRunError: the exit code of the command
315 execution was not 0
316 """
317 #~ print "running %s" % (command,)
mblighe6647d12007-10-17 00:00:01 +0000318 result= utils.run(r'%s "%s"' % (self.ssh_command(),
319 utils.sh_escape(command)),
320 timeout, ignore_status)
mblighdcd57a82007-07-11 23:06:47 +0000321 return result
mbligh7d2bde82007-08-02 16:26:10 +0000322
323
mbligh80d20772007-10-29 17:10:10 +0000324 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
325 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000326 """
327 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000328
mbligha0452c82007-08-08 20:24:57 +0000329 Args:
330 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000331 """
mblighde384372007-10-17 04:25:37 +0000332 # forcibly include the "netconsole" kernel arg
333 if self.__netconsole_param:
334 if kernel_args is None:
335 kernel_args = self.__netconsole_param
336 else:
337 kernel_args += " " + self.__netconsole_param
338 # unload the (possibly loaded) module to avoid shutdown issues
339 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000340 if label or kernel_args:
341 self.bootloader.install_boottool()
342 if label:
343 self.bootloader.set_default(label)
344 if kernel_args:
345 if not label:
346 default = int(self.bootloader.get_default())
347 label = self.bootloader.get_titles()[default]
348 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000349 print "Reboot: initiating reboot"
mbligh87c5d882007-10-29 17:07:24 +0000350 sys.stderr.write("REBOOT\n")
mbligha0452c82007-08-08 20:24:57 +0000351 self.run('reboot')
352 if wait:
mbligh6a0010f2007-10-25 15:45:21 +0000353 self._wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000354 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000355
mbligh7d2bde82007-08-02 16:26:10 +0000356
mblighdcd57a82007-07-11 23:06:47 +0000357 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000358 """
359 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000360
361 Directories will be copied recursively.
362 If a source component is a directory with a trailing slash,
363 the content of the directory will be copied, otherwise, the
364 directory itself and its content will be copied. This
365 behavior is similar to that of the program 'rsync'.
366
367 Args:
368 source: either
369 1) a single file or directory, as a string
370 2) a list of one or more (possibly mixed)
371 files or directories
372 dest: a file or a directory (if source contains a
373 directory or more than one element, you must
374 supply a directory dest)
375
376 Raises:
377 AutoservRunError: the scp command failed
378 """
379 if isinstance(source, types.StringTypes):
380 source= [source]
381
382 processed_source= []
383 for entry in source:
384 if entry.endswith('/'):
385 format_string= '%s@%s:"%s*"'
386 else:
387 format_string= '%s@%s:"%s"'
388 entry= format_string % (self.user, self.hostname,
389 utils.scp_remote_escape(entry))
390 processed_source.append(entry)
391
392 processed_dest= os.path.abspath(dest)
393 if os.path.isdir(dest):
394 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
395 else:
396 processed_dest= utils.sh_escape(processed_dest)
397
398 utils.run('scp -rpq %s "%s"' % (
399 " ".join(processed_source),
400 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000401
402
mblighdcd57a82007-07-11 23:06:47 +0000403 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000404 """
405 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000406
407 Directories will be copied recursively.
408 If a source component is a directory with a trailing slash,
409 the content of the directory will be copied, otherwise, the
410 directory itself and its content will be copied. This
411 behavior is similar to that of the program 'rsync'.
412
413 Args:
414 source: either
415 1) a single file or directory, as a string
416 2) a list of one or more (possibly mixed)
417 files or directories
418 dest: a file or a directory (if source contains a
419 directory or more than one element, you must
420 supply a directory dest)
421
422 Raises:
423 AutoservRunError: the scp command failed
424 """
425 if isinstance(source, types.StringTypes):
426 source= [source]
427
428 processed_source= []
429 for entry in source:
430 if entry.endswith('/'):
431 format_string= '"%s/"*'
432 else:
433 format_string= '"%s"'
434 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
435 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000436
mblighe6647d12007-10-17 00:00:01 +0000437 result = utils.run(r'%s rsync -h' % self.ssh_command(),
438 ignore_status=True)
mblighd5669092007-08-27 19:01:05 +0000439
mbligh0faf91f2007-10-18 03:10:48 +0000440 remote_dest = '%s@%s:"%s"' % (
441 self.user, self.hostname,
442 utils.scp_remote_escape(dest))
mblighd5669092007-08-27 19:01:05 +0000443 if result.exit_status == 0:
mbligh0faf91f2007-10-18 03:10:48 +0000444 utils.run('rsync --rsh="%s" -az %s %s' % (
445 self.SSH_BASE_COMMAND, " ".join(processed_source),
446 remote_dest))
mblighd5669092007-08-27 19:01:05 +0000447 else:
mbligh0faf91f2007-10-18 03:10:48 +0000448 utils.run('scp -rpq %s %s' % (
449 " ".join(processed_source),
450 remote_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000451
mblighdcd57a82007-07-11 23:06:47 +0000452 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000453 """
454 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000455 for temporary file storage.
456
457 The directory and its content will be deleted automatically
458 on the destruction of the Host object that was used to obtain
459 it.
460 """
mbligha25b29e2007-08-26 13:58:04 +0000461 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000462 self.tmp_dirs.append(dir_name)
463 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000464
465
mblighdcd57a82007-07-11 23:06:47 +0000466 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000467 """
468 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000469
470 Returns:
471 True if the remote host is up, False otherwise
472 """
473 try:
474 result= self.run("true", timeout=10)
475 except errors.AutoservRunError:
476 return False
477 else:
478 if result.exit_status == 0:
479 return True
480 else:
mbligh7d2bde82007-08-02 16:26:10 +0000481
mblighdcd57a82007-07-11 23:06:47 +0000482 return False
mbligh7d2bde82007-08-02 16:26:10 +0000483
mblighdcd57a82007-07-11 23:06:47 +0000484 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000485 """
486 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000487
488 In fact, it will wait until an ssh connection to the remote
489 host can be established.
490
491 Args:
492 timeout: time limit in seconds before returning even
493 if the host is not up.
494
495 Returns:
496 True if the host was found to be up, False otherwise
497 """
498 if timeout:
499 end_time= time.time() + timeout
500
501 while not timeout or time.time() < end_time:
502 try:
mblighe9cf9d42007-08-31 08:56:00 +0000503 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000504 result= self.run("true", timeout=run_timeout)
505 except errors.AutoservRunError:
506 pass
507 else:
508 if result.exit_status == 0:
509 return True
510 time.sleep(1)
511
512 return False
mbligh7d2bde82007-08-02 16:26:10 +0000513
514
mblighdcd57a82007-07-11 23:06:47 +0000515 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000516 """
517 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000518
519 In fact, it will wait until an ssh connection to the remote
520 host fails.
521
522 Args:
523 timeout: time limit in seconds before returning even
524 if the host is not up.
525
526 Returns:
527 True if the host was found to be down, False otherwise
528 """
529 if timeout:
530 end_time= time.time() + timeout
531
532 while not timeout or time.time() < end_time:
533 try:
mbligh7e1e9642007-07-31 18:00:45 +0000534 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000535 result= self.run("true", timeout=run_timeout)
536 except errors.AutoservRunError:
537 return True
538 else:
539 if result.aborted:
540 return True
541 time.sleep(1)
542
543 return False
mbligh7d2bde82007-08-02 16:26:10 +0000544
545
mblighdbe4a382007-07-26 19:41:28 +0000546 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000547 """
548 Ensure the host is up if it is not then do not proceed;
549 this prevents cacading failures of tests
550 """
mbligha0452c82007-08-08 20:24:57 +0000551 print 'Ensuring that %s is up before continuing' % self.hostname
552 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000553 print "Performing a hardreset on %s" % self.hostname
554 self.hardreset()
mbligha9563b92007-10-25 14:45:56 +0000555 if not self.wait_up(60 * 30):
556 # 30 minutes should be more than enough
557 raise errors.AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000558 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000559
560
mblighdcd57a82007-07-11 23:06:47 +0000561 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000562 """
563 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000564 /proc/cpuinfo.
565
566 Returns:
567 The number of CPUs
568 """
569
mbligh5f876ad2007-10-12 23:59:53 +0000570 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000571 cpus = 0
572 for line in proc_cpuinfo.splitlines():
573 if line.startswith('processor'):
574 cpus += 1
575 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000576
577
578 def check_uptime(self):
579 """
580 Check that uptime is available and monotonically increasing.
581 """
582 if not self.ping():
583 raise "Client is not pingable"
584 result = self.run("/bin/cat /proc/uptime", 30)
585 return result.stdout.strip().split()[0]
586
587
588 def get_arch(self):
589 """
590 Get the hardware architecture of the remote machine
591 """
592 arch = self.run('/bin/uname -m').stdout.rstrip()
593 if re.match(r'i\d86$', arch):
594 arch = 'i386'
595 return arch
596
597
598 def get_kernel_ver(self):
599 """
600 Get the kernel version of the remote machine
601 """
602 return self.run('/bin/uname -r').stdout.rstrip()
603
604
605 def get_cmdline(self):
606 """
607 Get the kernel command line of the remote machine
608 """
609 return self.run('cat /proc/cmdline').stdout.rstrip()
610
611
612 def ping(self):
613 """
614 Ping the remote system, and return whether it's available
615 """
616 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
617 rc = utils.system(fpingcmd, ignore_status = 1)
618 return (rc == 0)