blob: 9253871a235c8a8f5d5ce3eb47f97e2167aa28b7 [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
mbligh03f4fc72007-11-29 20:56:14 +000022import base_classes, utils, bootloader
23
24from common.error import *
mblighdcd57a82007-07-11 23:06:47 +000025
26
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
mbligh31a49de2007-11-05 18:41:19 +000049 DEFAULT_REBOOT_TIMEOUT = 1800
50 job = None
mbligh0faf91f2007-10-18 03:10:48 +000051
mblighde384372007-10-17 04:25:37 +000052 def __init__(self, hostname, user="root", port=22, initialize=True,
mblighf4e04152008-02-21 16:05:53 +000053 conmux_log="console.log",
mblighe6c995f2007-10-26 19:43:01 +000054 conmux_server=None, conmux_attach=None,
mblighda13d542008-01-03 16:28:34 +000055 netconsole_log=None, netconsole_port=6666, autodir=None):
mbligh7d2bde82007-08-02 16:26:10 +000056 """
57 Construct a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000058
59 Args:
60 hostname: network hostname or address of remote machine
61 user: user to log in as on the remote machine
62 port: port the ssh daemon is listening on on the remote
63 machine
mbligh9708f732007-10-18 03:18:54 +000064 """
mblighdcd57a82007-07-11 23:06:47 +000065 self.hostname= hostname
66 self.user= user
67 self.port= port
68 self.tmp_dirs= []
mbligh137a05c2007-10-04 15:56:51 +000069 self.initialize = initialize
mblighda13d542008-01-03 16:28:34 +000070 self.autodir = autodir
mbligh91334902007-09-28 01:47:59 +000071
mbligh9708f732007-10-18 03:18:54 +000072 super(SSHHost, self).__init__()
73
mbligh3409ee72007-10-16 23:58:33 +000074 self.conmux_server = conmux_server
mbligh70cf0ec2008-01-18 17:57:14 +000075 if conmux_attach:
76 self.conmux_attach = conmux_attach
77 else:
78 self.conmux_attach = os.path.abspath(os.path.join(
79 self.serverdir, '..',
80 'conmux', 'conmux-attach'))
mblighfbb03542008-02-11 16:27:29 +000081 self.logger_popen = None
mblighf4e04152008-02-21 16:05:53 +000082 self.warning_stream = None
mblighde384372007-10-17 04:25:37 +000083 self.__start_console_log(conmux_log)
mbligh3409ee72007-10-16 23:58:33 +000084
mbligha0452c82007-08-08 20:24:57 +000085 self.bootloader = bootloader.Bootloader(self)
mbligh7d2bde82007-08-02 16:26:10 +000086
mblighc0e92392007-11-05 19:10:10 +000087 self.__netconsole_param = ""
mblighfbb03542008-02-11 16:27:29 +000088 self.netlogger_popen = None
mblighc0e92392007-11-05 19:10:10 +000089 if netconsole_log:
90 self.__init_netconsole_params(netconsole_port)
91 self.__start_netconsole_log(netconsole_log, netconsole_port)
92 self.__load_netconsole_module()
mblighde384372007-10-17 04:25:37 +000093
mbligh7d2bde82007-08-02 16:26:10 +000094
mblighfbb03542008-02-11 16:27:29 +000095 @staticmethod
mblighf4e04152008-02-21 16:05:53 +000096 def __kill(popen):
mblighfbb03542008-02-11 16:27:29 +000097 return_code = popen.poll()
mblighf4e04152008-02-21 16:05:53 +000098 if return_code is None:
mblighfbb03542008-02-11 16:27:29 +000099 try:
mblighf4e04152008-02-21 16:05:53 +0000100 os.kill(popen.pid, signal.SIGTERM)
mblighfbb03542008-02-11 16:27:29 +0000101 except OSError:
102 pass
103
104
mblighdcd57a82007-07-11 23:06:47 +0000105 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +0000106 """
107 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +0000108 """
109 for dir in self.tmp_dirs:
110 try:
111 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
mbligh03f4fc72007-11-29 20:56:14 +0000112 except AutoservRunError:
mblighdcd57a82007-07-11 23:06:47 +0000113 pass
mblighde384372007-10-17 04:25:37 +0000114 # kill the console logger
mblighfbb03542008-02-11 16:27:29 +0000115 if getattr(self, 'logger_popen', None):
mblighf4e04152008-02-21 16:05:53 +0000116 self.__kill(self.logger_popen)
117 self.warning_stream.close()
mblighde384372007-10-17 04:25:37 +0000118 # kill the netconsole logger
mblighfbb03542008-02-11 16:27:29 +0000119 if getattr(self, 'netlogger_popen', None):
mblighe6c995f2007-10-26 19:43:01 +0000120 self.__unload_netconsole_module()
mblighf4e04152008-02-21 16:05:53 +0000121 self.__kill(self.netlogger_popen)
mblighde384372007-10-17 04:25:37 +0000122
123
124 def __init_netconsole_params(self, port):
125 """
126 Connect to the remote machine and determine the values to use for the
127 required netconsole parameters.
128 """
mblighde384372007-10-17 04:25:37 +0000129 # PROBLEM: on machines with multiple IPs this may not make any sense
130 # It also doesn't work with IPv6
131 remote_ip = socket.gethostbyname(self.hostname)
132 local_ip = socket.gethostbyname(socket.gethostname())
133 # Get the gateway of the remote machine
134 try:
135 traceroute = self.run('traceroute -n %s' % local_ip)
mbligh03f4fc72007-11-29 20:56:14 +0000136 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000137 return
138 first_node = traceroute.stdout.split("\n")[0]
139 match = re.search(r'\s+((\d+\.){3}\d+)\s+', first_node)
140 if match:
141 router_ip = match.group(1)
142 else:
143 return
144 # Look up the MAC address of the gateway
145 try:
146 self.run('ping -c 1 %s' % router_ip)
147 arp = self.run('arp -n -a %s' % router_ip)
mbligh03f4fc72007-11-29 20:56:14 +0000148 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000149 return
150 match = re.search(r'\s+(([0-9A-F]{2}:){5}[0-9A-F]{2})\s+', arp.stdout)
151 if match:
152 gateway_mac = match.group(1)
153 else:
154 return
155 self.__netconsole_param = 'netconsole=@%s/,%s@%s/%s' % (remote_ip,
156 port,
157 local_ip,
158 gateway_mac)
159
160
161 def __start_netconsole_log(self, logfilename, port):
162 """
163 Log the output of netconsole to a specified file
164 """
165 if logfilename == None:
166 return
167 cmd = ['nc', '-u', '-l', '-p', str(port)]
mblighfbb03542008-02-11 16:27:29 +0000168 logfile = open(logfilename, 'a', 0)
169 self.netlogger_popen = subprocess.Popen(cmd, stdout=logfile)
mblighde384372007-10-17 04:25:37 +0000170
171
172 def __load_netconsole_module(self):
173 """
174 Make a best effort to load the netconsole module.
175
176 Note that loading the module can fail even when the remote machine is
177 working correctly if netconsole is already compiled into the kernel
178 and started.
179 """
mblighc0e92392007-11-05 19:10:10 +0000180 if not self.__netconsole_param:
181 return
mblighde384372007-10-17 04:25:37 +0000182 try:
183 self.run('modprobe netconsole %s' % self.__netconsole_param)
mbligh03f4fc72007-11-29 20:56:14 +0000184 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000185 # if it fails there isn't much we can do, just keep going
186 pass
187
188
189 def __unload_netconsole_module(self):
190 try:
191 self.run('modprobe -r netconsole')
mbligh03f4fc72007-11-29 20:56:14 +0000192 except AutoservRunError:
mblighde384372007-10-17 04:25:37 +0000193 pass
mbligh3409ee72007-10-16 23:58:33 +0000194
195
mbligh5deff3d2008-01-04 21:21:28 +0000196 def wait_for_restart(self, timeout=DEFAULT_REBOOT_TIMEOUT):
mblighd567f722007-10-30 15:37:33 +0000197 if not self.wait_down(300): # Make sure he's dead, Jim
mblighf3b78932007-11-07 16:52:47 +0000198 self.__record("ABORT", None, "reboot.verify", "shutdown failed")
mbligh03f4fc72007-11-29 20:56:14 +0000199 raise AutoservRebootError("Host did not shut down")
mbligh3409ee72007-10-16 23:58:33 +0000200 self.wait_up(timeout)
201 time.sleep(2) # this is needed for complete reliability
mblighcf3d83a2007-11-05 19:21:39 +0000202 if self.wait_up(timeout):
mbligh30270302007-11-05 20:33:52 +0000203 self.__record("GOOD", None, "reboot.verify")
mblighcf3d83a2007-11-05 19:21:39 +0000204 else:
mblighf3b78932007-11-07 16:52:47 +0000205 self.__record("ABORT", None, "reboot.verify", "bringup failed")
mbligh03f4fc72007-11-29 20:56:14 +0000206 raise AutoservRebootError("Host did not return from reboot")
mbligh3409ee72007-10-16 23:58:33 +0000207 print "Reboot complete"
208
209
mbligh80d20772007-10-29 17:10:10 +0000210 def hardreset(self, timeout=DEFAULT_REBOOT_TIMEOUT, wait=True):
mbligh3409ee72007-10-16 23:58:33 +0000211 """
212 Reach out and slap the box in the power switch
213 """
mblighf3b78932007-11-07 16:52:47 +0000214 if not self.__console_run(r"'~$hardreset'"):
215 self.__record("ABORT", None, "reboot.start", "hard reset unavailable")
mbligh4d6feff2008-01-14 16:48:56 +0000216 raise AutoservUnsupportedError('Hard reset unavailable')
mbligh37d53c32008-01-14 16:16:00 +0000217
218 if wait:
219 self.wait_for_restart(timeout)
mbligha4d4f372008-01-22 15:49:50 +0000220 self.__record("GOOD", None, "reboot.start", "hard reset")
mbligh3409ee72007-10-16 23:58:33 +0000221
222
mblighe6c995f2007-10-26 19:43:01 +0000223 def __conmux_hostname(self):
224 if self.conmux_server:
225 return '%s/%s' % (self.conmux_server, self.hostname)
226 else:
227 return self.hostname
228
229
mbligh3409ee72007-10-16 23:58:33 +0000230 def __start_console_log(self, logfilename):
231 """
232 Log the output of the console session to a specified file
233 """
234 if logfilename == None:
235 return
236 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
237 return
mblighf4e04152008-02-21 16:05:53 +0000238
239 r, w = os.pipe()
240 script_path = os.path.join(self.serverdir,
241 'warning_monitor.py')
mblighfbb03542008-02-11 16:27:29 +0000242 cmd = [self.conmux_attach, self.__conmux_hostname(),
mblighf4e04152008-02-21 16:05:53 +0000243 '%s %s %s %d' % (sys.executable, script_path,
244 logfilename, w)]
mblighfbb03542008-02-11 16:27:29 +0000245 dev_null = open('/dev/null', 'w')
mbligh3409ee72007-10-16 23:58:33 +0000246
mblighf4e04152008-02-21 16:05:53 +0000247 self.warning_stream = os.fdopen(r, 'r', 0)
248 self.job.warning_loggers.add(self.warning_stream)
249 self.logger_popen = subprocess.Popen(cmd, stderr=dev_null)
250 os.close(w)
mblighe6c995f2007-10-26 19:43:01 +0000251
252
mbligh3409ee72007-10-16 23:58:33 +0000253 def __console_run(self, cmd):
254 """
255 Send a command to the conmux session
256 """
257 if not self.conmux_attach or not os.path.exists(self.conmux_attach):
258 return False
mbligh3409ee72007-10-16 23:58:33 +0000259 cmd = '%s %s echo %s 2> /dev/null' % (self.conmux_attach,
mblighe6c995f2007-10-26 19:43:01 +0000260 self.__conmux_hostname(),
mbligh3409ee72007-10-16 23:58:33 +0000261 cmd)
mbligh0f5ad642008-01-22 16:37:40 +0000262 result = utils.system(cmd, ignore_status=True)
mbligh3409ee72007-10-16 23:58:33 +0000263 return result == 0
mbligh7d2bde82007-08-02 16:26:10 +0000264
265
mbligh31a49de2007-11-05 18:41:19 +0000266 def __record(self, status_code, subdir, operation, status = ''):
267 if self.job:
268 self.job.record(status_code, subdir, operation, status)
269 else:
270 if not subdir:
271 subdir = "----"
272 msg = "%s\t%s\t%s\t%s" % (status_code, subdir, operation, status)
273 sys.stderr.write(msg + "\n")
274
275
mblighfa971602008-01-03 01:57:20 +0000276 def ssh_base_command(self, connect_timeout=30):
277 SSH_BASE_COMMAND = '/usr/bin/ssh -a -x -o ' + \
278 'BatchMode=yes -o ConnectTimeout=%d'
279 assert isinstance(connect_timeout, (int, long))
280 assert connect_timeout > 0 # can't disable the timeout
281 return SSH_BASE_COMMAND % connect_timeout
282
283
284 def ssh_command(self, connect_timeout=30):
mblighe6647d12007-10-17 00:00:01 +0000285 """Construct an ssh command with proper args for this host."""
mblighfa971602008-01-03 01:57:20 +0000286 ssh = self.ssh_base_command(connect_timeout)
287 return r'%s -l %s -p %d %s' % (ssh,
mbligh0faf91f2007-10-18 03:10:48 +0000288 self.user,
289 self.port,
290 self.hostname)
mblighe6647d12007-10-17 00:00:01 +0000291
292
mbligh07a923f2008-01-16 17:49:04 +0000293 def run(self, command, timeout=3600, ignore_status=False,
mblighfa971602008-01-03 01:57:20 +0000294 stdout_tee=None, stderr_tee=None, connect_timeout=30):
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 """
mblighadf2aab2007-11-29 18:16:43 +0000314 stdout = stdout_tee or sys.stdout
315 stderr = stderr_tee or sys.stderr
mbligh7995cc62007-11-30 15:53:23 +0000316 print "ssh: %s" % (command,)
mblighadf2aab2007-11-29 18:16:43 +0000317 env = " ".join("=".join(pair) for pair in self.env.iteritems())
mbligh34faa282008-01-16 17:44:49 +0000318 full_cmd = '%s "%s %s"' % (self.ssh_command(connect_timeout),
319 env, utils.sh_escape(command))
320 result = utils.run(full_cmd, timeout, True, stdout, stderr)
321 if result.exit_status == 255: # ssh's exit status for timeout
322 if re.match(r'^ssh: connect to host .* port .*: ' +
323 r'Connection timed out\r$', result.stderr):
324 raise AutoservSSHTimeout("ssh timed out",
325 result)
326 if not ignore_status and result.exit_status > 0:
327 raise AutoservRunError("command execution error",
328 result)
mblighdcd57a82007-07-11 23:06:47 +0000329 return result
mbligh7d2bde82007-08-02 16:26:10 +0000330
331
mbligh78669ff2008-01-10 16:33:07 +0000332 def run_grep(self, command, timeout=30, ignore_status=False,
333 stdout_ok_regexp=None, stdout_err_regexp=None,
334 stderr_ok_regexp=None, stderr_err_regexp=None,
335 connect_timeout=30):
336 """
337 Run a command on the remote host and look for regexp
338 in stdout or stderr to determine if the command was
339 successul or not.
mbligh6a2a2df2008-01-16 17:41:55 +0000340
mbligh78669ff2008-01-10 16:33:07 +0000341 Args:
342 command: the command line string
mbligh6a2a2df2008-01-16 17:41:55 +0000343 timeout: time limit in seconds before attempting to
mbligh78669ff2008-01-10 16:33:07 +0000344 kill the running process. The run() function
345 will take a few seconds longer than 'timeout'
346 to complete if it has to kill the process.
mbligh6a2a2df2008-01-16 17:41:55 +0000347 ignore_status: do not raise an exception, no matter
mbligh78669ff2008-01-10 16:33:07 +0000348 what the exit code of the command is.
349 stdout_ok_regexp: regexp that should be in stdout
350 if the command was successul.
351 stdout_err_regexp: regexp that should be in stdout
352 if the command failed.
353 stderr_ok_regexp: regexp that should be in stderr
354 if the command was successul.
355 stderr_err_regexp: regexp that should be in stderr
356 if the command failed.
mbligh6a2a2df2008-01-16 17:41:55 +0000357
mbligh78669ff2008-01-10 16:33:07 +0000358 Returns:
359 if the command was successul, raises an exception
360 otherwise.
mbligh6a2a2df2008-01-16 17:41:55 +0000361
mbligh78669ff2008-01-10 16:33:07 +0000362 Raises:
363 AutoservRunError:
364 - the exit code of the command execution was not 0.
mbligh6a2a2df2008-01-16 17:41:55 +0000365 - If stderr_err_regexp is found in stderr,
366 - If stdout_err_regexp is found in stdout,
mbligh78669ff2008-01-10 16:33:07 +0000367 - If stderr_ok_regexp is not found in stderr.
368 - If stdout_ok_regexp is not found in stdout,
369 """
370
371 # We ignore the status, because we will handle it at the end.
372 result = self.run(command, timeout, ignore_status=True,
mbligh6a2a2df2008-01-16 17:41:55 +0000373 connect_timeout=connect_timeout)
mbligh78669ff2008-01-10 16:33:07 +0000374
375 # Look for the patterns, in order
376 for (regexp, stream) in ((stderr_err_regexp, result.stderr),
mbligh6a2a2df2008-01-16 17:41:55 +0000377 (stdout_err_regexp, result.stdout)):
mbligh78669ff2008-01-10 16:33:07 +0000378 if regexp and stream:
379 err_re = re.compile (regexp)
380 if err_re.search(stream):
mbligh6a2a2df2008-01-16 17:41:55 +0000381 raise AutoservRunError(
382 '%s failed, found error pattern: '
383 '"%s"' % (command, regexp), result)
mbligh78669ff2008-01-10 16:33:07 +0000384
385 for (regexp, stream) in ((stderr_ok_regexp, result.stderr),
mbligh6a2a2df2008-01-16 17:41:55 +0000386 (stdout_ok_regexp, result.stdout)):
mbligh78669ff2008-01-10 16:33:07 +0000387 if regexp and stream:
388 ok_re = re.compile (regexp)
389 if ok_re.search(stream):
390 if ok_re.search(stream):
391 return
392
393 if not ignore_status and result.exit_status > 0:
mbligh6a2a2df2008-01-16 17:41:55 +0000394 raise AutoservRunError("command execution error",
395 result)
mbligh78669ff2008-01-10 16:33:07 +0000396
397
mbligh80d20772007-10-29 17:10:10 +0000398 def reboot(self, timeout=DEFAULT_REBOOT_TIMEOUT, label=None,
399 kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000400 """
401 Reboot the remote host.
mbligh8b85dfb2007-08-28 09:50:31 +0000402
mbligha0452c82007-08-08 20:24:57 +0000403 Args:
404 timeout
mbligh8b85dfb2007-08-28 09:50:31 +0000405 """
mbligh33ae0902007-11-24 19:27:08 +0000406 self.reboot_setup()
407
mblighde384372007-10-17 04:25:37 +0000408 # forcibly include the "netconsole" kernel arg
409 if self.__netconsole_param:
410 if kernel_args is None:
411 kernel_args = self.__netconsole_param
412 else:
413 kernel_args += " " + self.__netconsole_param
414 # unload the (possibly loaded) module to avoid shutdown issues
415 self.__unload_netconsole_module()
mbligha0452c82007-08-08 20:24:57 +0000416 if label or kernel_args:
417 self.bootloader.install_boottool()
418 if label:
419 self.bootloader.set_default(label)
420 if kernel_args:
421 if not label:
422 default = int(self.bootloader.get_default())
423 label = self.bootloader.get_titles()[default]
424 self.bootloader.add_args(label, kernel_args)
mblighd742a222007-09-30 01:27:06 +0000425 print "Reboot: initiating reboot"
mbligh30270302007-11-05 20:33:52 +0000426 self.__record("GOOD", None, "reboot.start")
mblighcf3d83a2007-11-05 19:21:39 +0000427 try:
mblighf3b78932007-11-07 16:52:47 +0000428 self.run('(sleep 5; reboot) </dev/null >/dev/null 2>&1 &')
mbligh03f4fc72007-11-29 20:56:14 +0000429 except AutoservRunError:
mblighf3b78932007-11-07 16:52:47 +0000430 self.__record("ABORT", None, "reboot.start",
431 "reboot command failed")
mblighcf3d83a2007-11-05 19:21:39 +0000432 raise
mbligha0452c82007-08-08 20:24:57 +0000433 if wait:
mbligh5deff3d2008-01-04 21:21:28 +0000434 self.wait_for_restart(timeout)
mblighde384372007-10-17 04:25:37 +0000435 self.__load_netconsole_module() # if the builtin fails
mbligha0452c82007-08-08 20:24:57 +0000436
mbligh7d2bde82007-08-02 16:26:10 +0000437
mblighcfc7ab32008-01-25 16:35:28 +0000438 def __copy_files(self, sources, dest):
439 """
440 Copy files from one machine to another.
441
442 This is for internal use by other methods that intend to move
443 files between machines. It expects a list of source files and
444 a destination (a filename if the source is a single file, a
445 destination otherwise). The names must already be
446 pre-processed into the appropriate rsync/scp friendly
447 format (%s@%s:%s).
448 """
449 # wait until there are only a small number of copies running
450 # before starting this one
451 MAXIMUM_SIMULTANEOUS_COPIES = 4
452 while True:
453 copy_count = 0
454 procs = utils.system_output('ps -ef')
455 for line in procs:
456 if 'rsync ' in line or 'scp ' in line:
457 copy_count += 1
458 if copy_count < MAXIMUM_SIMULTANEOUS_COPIES:
459 break
460 time.sleep(60)
461
462 try:
463 utils.run('rsync --rsh="%s" -az %s %s' % (
464 self.ssh_base_command(), ' '.join(sources), dest))
465 except Exception:
466 utils.run('scp -rpq %s "%s"' % (
467 ' '.join(sources), dest))
468
469
mblighdcd57a82007-07-11 23:06:47 +0000470 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000471 """
472 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000473
474 Directories will be copied recursively.
475 If a source component is a directory with a trailing slash,
476 the content of the directory will be copied, otherwise, the
477 directory itself and its content will be copied. This
478 behavior is similar to that of the program 'rsync'.
479
480 Args:
481 source: either
482 1) a single file or directory, as a string
483 2) a list of one or more (possibly mixed)
484 files or directories
485 dest: a file or a directory (if source contains a
486 directory or more than one element, you must
487 supply a directory dest)
488
489 Raises:
490 AutoservRunError: the scp command failed
491 """
492 if isinstance(source, types.StringTypes):
493 source= [source]
494
495 processed_source= []
496 for entry in source:
497 if entry.endswith('/'):
498 format_string= '%s@%s:"%s*"'
499 else:
500 format_string= '%s@%s:"%s"'
501 entry= format_string % (self.user, self.hostname,
502 utils.scp_remote_escape(entry))
503 processed_source.append(entry)
504
505 processed_dest= os.path.abspath(dest)
506 if os.path.isdir(dest):
507 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
508 else:
509 processed_dest= utils.sh_escape(processed_dest)
mblighcfc7ab32008-01-25 16:35:28 +0000510
511 self.__copy_files(processed_source, processed_dest)
mbligh7d2bde82007-08-02 16:26:10 +0000512
513
mblighdcd57a82007-07-11 23:06:47 +0000514 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000515 """
516 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000517
518 Directories will be copied recursively.
519 If a source component is a directory with a trailing slash,
520 the content of the directory will be copied, otherwise, the
521 directory itself and its content will be copied. This
522 behavior is similar to that of the program 'rsync'.
523
524 Args:
525 source: either
526 1) a single file or directory, as a string
527 2) a list of one or more (possibly mixed)
528 files or directories
529 dest: a file or a directory (if source contains a
530 directory or more than one element, you must
531 supply a directory dest)
532
533 Raises:
534 AutoservRunError: the scp command failed
535 """
536 if isinstance(source, types.StringTypes):
537 source= [source]
538
539 processed_source= []
540 for entry in source:
541 if entry.endswith('/'):
542 format_string= '"%s/"*'
543 else:
544 format_string= '"%s"'
545 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
546 processed_source.append(entry)
mbligh7d2bde82007-08-02 16:26:10 +0000547
mbligh0faf91f2007-10-18 03:10:48 +0000548 remote_dest = '%s@%s:"%s"' % (
549 self.user, self.hostname,
550 utils.scp_remote_escape(dest))
mblighcfc7ab32008-01-25 16:35:28 +0000551
552 self.__copy_files(processed_source, remote_dest)
mblighc42141f2007-11-05 20:25:46 +0000553 self.run('find "%s" -type d | xargs -r chmod o+rx' % dest)
554 self.run('find "%s" -type f | xargs -r chmod o+r' % dest)
mbligh7d2bde82007-08-02 16:26:10 +0000555
mblighdcd57a82007-07-11 23:06:47 +0000556 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000557 """
558 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000559 for temporary file storage.
560
561 The directory and its content will be deleted automatically
562 on the destruction of the Host object that was used to obtain
563 it.
564 """
mbligha25b29e2007-08-26 13:58:04 +0000565 dir_name= self.run("mktemp -d /tmp/autoserv-XXXXXX").stdout.rstrip(" \n")
mblighdcd57a82007-07-11 23:06:47 +0000566 self.tmp_dirs.append(dir_name)
567 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000568
569
mblighdcd57a82007-07-11 23:06:47 +0000570 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000571 """
572 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000573
574 Returns:
575 True if the remote host is up, False otherwise
576 """
577 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000578 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000579 except:
mblighdcd57a82007-07-11 23:06:47 +0000580 return False
mbligheadfbb12007-11-26 23:03:12 +0000581 return True
mbligh7d2bde82007-08-02 16:26:10 +0000582
mbligh7d2bde82007-08-02 16:26:10 +0000583
mblighdcd57a82007-07-11 23:06:47 +0000584 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000585 """
586 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000587
588 In fact, it will wait until an ssh connection to the remote
589 host can be established.
590
591 Args:
592 timeout: time limit in seconds before returning even
593 if the host is not up.
594
595 Returns:
596 True if the host was found to be up, False otherwise
597 """
598 if timeout:
599 end_time= time.time() + timeout
600
601 while not timeout or time.time() < end_time:
602 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000603 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000604 except:
mblighdcd57a82007-07-11 23:06:47 +0000605 pass
606 else:
mbligheadfbb12007-11-26 23:03:12 +0000607 return True
mblighdcd57a82007-07-11 23:06:47 +0000608 time.sleep(1)
609
610 return False
mbligh7d2bde82007-08-02 16:26:10 +0000611
612
mblighdcd57a82007-07-11 23:06:47 +0000613 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000614 """
615 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000616
617 In fact, it will wait until an ssh connection to the remote
618 host fails.
619
620 Args:
621 timeout: time limit in seconds before returning even
622 if the host is not up.
623
624 Returns:
625 True if the host was found to be down, False otherwise
626 """
627 if timeout:
628 end_time= time.time() + timeout
629
630 while not timeout or time.time() < end_time:
631 try:
mbligh4cfa76a2007-11-26 20:45:16 +0000632 self.ssh_ping()
mbligheadfbb12007-11-26 23:03:12 +0000633 except:
mblighdcd57a82007-07-11 23:06:47 +0000634 return True
mblighdcd57a82007-07-11 23:06:47 +0000635 time.sleep(1)
636
637 return False
mbligh7d2bde82007-08-02 16:26:10 +0000638
639
mblighdbe4a382007-07-26 19:41:28 +0000640 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000641 """
642 Ensure the host is up if it is not then do not proceed;
643 this prevents cacading failures of tests
644 """
mbligha0452c82007-08-08 20:24:57 +0000645 print 'Ensuring that %s is up before continuing' % self.hostname
646 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000647 print "Performing a hardreset on %s" % self.hostname
mbligh4ba0b462007-11-05 23:05:40 +0000648 try:
649 self.hardreset()
mbligh03f4fc72007-11-29 20:56:14 +0000650 except AutoservUnsupportedError:
mbligh4ba0b462007-11-05 23:05:40 +0000651 print "Hardreset is unsupported on %s" % self.hostname
mbligha9563b92007-10-25 14:45:56 +0000652 if not self.wait_up(60 * 30):
653 # 30 minutes should be more than enough
mbligh03f4fc72007-11-29 20:56:14 +0000654 raise AutoservHostError
mbligha0452c82007-08-08 20:24:57 +0000655 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000656
657
mblighdcd57a82007-07-11 23:06:47 +0000658 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000659 """
660 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000661 /proc/cpuinfo.
662
663 Returns:
664 The number of CPUs
665 """
666
mbligh5f876ad2007-10-12 23:59:53 +0000667 proc_cpuinfo = self.run("cat /proc/cpuinfo").stdout
mblighdcd57a82007-07-11 23:06:47 +0000668 cpus = 0
669 for line in proc_cpuinfo.splitlines():
670 if line.startswith('processor'):
671 cpus += 1
672 return cpus
mbligh5f876ad2007-10-12 23:59:53 +0000673
674
675 def check_uptime(self):
676 """
677 Check that uptime is available and monotonically increasing.
678 """
679 if not self.ping():
mbligh4d6feff2008-01-14 16:48:56 +0000680 raise AutoservHostError('Client is not pingable')
mbligh5f876ad2007-10-12 23:59:53 +0000681 result = self.run("/bin/cat /proc/uptime", 30)
682 return result.stdout.strip().split()[0]
683
684
685 def get_arch(self):
686 """
687 Get the hardware architecture of the remote machine
688 """
689 arch = self.run('/bin/uname -m').stdout.rstrip()
690 if re.match(r'i\d86$', arch):
691 arch = 'i386'
692 return arch
693
694
695 def get_kernel_ver(self):
696 """
697 Get the kernel version of the remote machine
698 """
699 return self.run('/bin/uname -r').stdout.rstrip()
700
701
702 def get_cmdline(self):
703 """
704 Get the kernel command line of the remote machine
705 """
706 return self.run('cat /proc/cmdline').stdout.rstrip()
707
708
709 def ping(self):
710 """
711 Ping the remote system, and return whether it's available
712 """
713 fpingcmd = "%s -q %s" % ('/usr/bin/fping', self.hostname)
714 rc = utils.system(fpingcmd, ignore_status = 1)
715 return (rc == 0)
mblighd2e46052007-11-05 18:31:00 +0000716
mblighf014ff42007-11-26 21:33:11 +0000717
mbligh4cfa76a2007-11-26 20:45:16 +0000718 def ssh_ping(self, timeout = 60):
mbligh4ff46b02008-02-01 17:33:37 +0000719 self.run('true', timeout = timeout, connect_timeout = timeout)
mblighda13d542008-01-03 16:28:34 +0000720
721
722 def get_autodir(self):
723 return self.autodir