blob: 49797bcccc8f4d569b6f27e4d2d578cc5e52d7ba [file] [log] [blame]
showardca572982009-09-18 21:20:01 +00001import os, time, types, socket, shutil, glob, logging, traceback
mblighefccc1b2010-01-11 19:08:42 +00002from autotest_lib.client.common_lib import autotemp, error, logging_manager
jadmanski31c49b72008-10-27 20:44:48 +00003from autotest_lib.server import utils, autotest
mblighe8b93af2009-01-30 00:45:53 +00004from autotest_lib.server.hosts import remote
mblighefccc1b2010-01-11 19:08:42 +00005from autotest_lib.client.common_lib.global_config import global_config
jadmanskica7da372008-10-21 16:26:52 +00006
7
mblighc0649d62010-01-15 18:15:58 +00008enable_master_ssh = global_config.get_config_value('AUTOSERV',
9 'enable_master_ssh',
10 type=bool, default=False)
mblighefccc1b2010-01-11 19:08:42 +000011
12
13def make_ssh_command(user="root", port=22, opts='', connect_timeout=30,
14 alive_interval=300):
mblighc0649d62010-01-15 18:15:58 +000015 base_command = ("/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no "
16 "-o UserKnownHostsFile=/dev/null -o BatchMode=yes "
mblighefccc1b2010-01-11 19:08:42 +000017 "-o ConnectTimeout=%d -o ServerAliveInterval=%d "
jadmanskica7da372008-10-21 16:26:52 +000018 "-l %s -p %d")
19 assert isinstance(connect_timeout, (int, long))
20 assert connect_timeout > 0 # can't disable the timeout
mblighefccc1b2010-01-11 19:08:42 +000021 return base_command % (opts, connect_timeout, alive_interval, user, port)
jadmanskica7da372008-10-21 16:26:52 +000022
23
mblighe8b93af2009-01-30 00:45:53 +000024# import site specific Host class
25SiteHost = utils.import_site_class(
26 __file__, "autotest_lib.server.hosts.site_host", "SiteHost",
27 remote.RemoteHost)
28
29
30class AbstractSSHHost(SiteHost):
mblighbc9402b2009-12-29 01:15:34 +000031 """
32 This class represents a generic implementation of most of the
jadmanskica7da372008-10-21 16:26:52 +000033 framework necessary for controlling a host via ssh. It implements
34 almost all of the abstract Host methods, except for the core
mblighbc9402b2009-12-29 01:15:34 +000035 Host.run method.
36 """
jadmanskica7da372008-10-21 16:26:52 +000037
jadmanskif6562912008-10-21 17:59:01 +000038 def _initialize(self, hostname, user="root", port=22, password="",
39 *args, **dargs):
40 super(AbstractSSHHost, self)._initialize(hostname=hostname,
41 *args, **dargs)
mbligh6369cf22008-10-24 17:21:57 +000042 self.ip = socket.getaddrinfo(self.hostname, None)[0][4][0]
jadmanskica7da372008-10-21 16:26:52 +000043 self.user = user
44 self.port = port
45 self.password = password
46
mblighefccc1b2010-01-11 19:08:42 +000047 """
48 Master SSH connection background job, socket temp directory and socket
49 control path option. If master-SSH is enabled, these fields will be
50 initialized by start_master_ssh when a new SSH connection is initiated.
51 """
52 self.master_ssh_job = None
53 self.master_ssh_tempdir = None
54 self.master_ssh_option = ''
55
mblighc9892c02010-01-06 19:02:16 +000056 # Check if rsync is available on the remote host. If it's not,
57 # don't try to use it for any future file transfers.
58 self.use_rsync = self._check_rsync()
59 if not self.use_rsync:
60 logging.warn("rsync not available on remote host %s -- disabled",
61 self.hostname)
62
63
64 def _check_rsync(self):
65 """
66 Check if rsync is available on the remote host.
67 """
68 try:
69 self.run("rsync --version", stdout_tee=None, stderr_tee=None)
70 except error.AutoservRunError:
71 return False
72 return True
73
jadmanskica7da372008-10-21 16:26:52 +000074
showard56176ec2009-10-28 19:52:30 +000075 def _encode_remote_paths(self, paths, escape=True):
mblighbc9402b2009-12-29 01:15:34 +000076 """
77 Given a list of file paths, encodes it as a single remote path, in
78 the style used by rsync and scp.
79 """
showard56176ec2009-10-28 19:52:30 +000080 if escape:
81 paths = [utils.scp_remote_escape(path) for path in paths]
82 return '%s@%s:"%s"' % (self.user, self.hostname, " ".join(paths))
jadmanskica7da372008-10-21 16:26:52 +000083
jadmanskica7da372008-10-21 16:26:52 +000084
mbligh45561782009-05-11 21:14:34 +000085 def _make_rsync_cmd(self, sources, dest, delete_dest, preserve_symlinks):
mblighbc9402b2009-12-29 01:15:34 +000086 """
87 Given a list of source paths and a destination path, produces the
jadmanskid7b79ed2009-01-07 17:19:48 +000088 appropriate rsync command for copying them. Remote paths must be
mblighbc9402b2009-12-29 01:15:34 +000089 pre-encoded.
90 """
mblighefccc1b2010-01-11 19:08:42 +000091 ssh_cmd = make_ssh_command(self.user, self.port,
92 self.master_ssh_option)
jadmanskid7b79ed2009-01-07 17:19:48 +000093 if delete_dest:
94 delete_flag = "--delete"
95 else:
96 delete_flag = ""
mbligh45561782009-05-11 21:14:34 +000097 if preserve_symlinks:
98 symlink_flag = ""
99 else:
100 symlink_flag = "-L"
101 command = "rsync %s %s --timeout=1800 --rsh='%s' -az %s %s"
102 return command % (symlink_flag, delete_flag, ssh_cmd,
103 " ".join(sources), dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000104
105
106 def _make_scp_cmd(self, sources, dest):
mblighbc9402b2009-12-29 01:15:34 +0000107 """
108 Given a list of source paths and a destination path, produces the
jadmanskid7b79ed2009-01-07 17:19:48 +0000109 appropriate scp command for encoding it. Remote paths must be
mblighbc9402b2009-12-29 01:15:34 +0000110 pre-encoded.
111 """
mblighc0649d62010-01-15 18:15:58 +0000112 command = ("scp -rq %s -o StrictHostKeyChecking=no "
113 "-o UserKnownHostsFile=/dev/null -P %d %s '%s'")
mblighefccc1b2010-01-11 19:08:42 +0000114 return command % (self.master_ssh_option,
115 self.port, " ".join(sources), dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000116
117
118 def _make_rsync_compatible_globs(self, path, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000119 """
120 Given an rsync-style path, returns a list of globbed paths
jadmanskid7b79ed2009-01-07 17:19:48 +0000121 that will hopefully provide equivalent behaviour for scp. Does not
122 support the full range of rsync pattern matching behaviour, only that
123 exposed in the get/send_file interface (trailing slashes).
124
125 The is_local param is flag indicating if the paths should be
mblighbc9402b2009-12-29 01:15:34 +0000126 interpreted as local or remote paths.
127 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000128
129 # non-trailing slash paths should just work
130 if len(path) == 0 or path[-1] != "/":
131 return [path]
132
133 # make a function to test if a pattern matches any files
134 if is_local:
showard56176ec2009-10-28 19:52:30 +0000135 def glob_matches_files(path, pattern):
136 return len(glob.glob(path + pattern)) > 0
jadmanskid7b79ed2009-01-07 17:19:48 +0000137 else:
showard56176ec2009-10-28 19:52:30 +0000138 def glob_matches_files(path, pattern):
139 result = self.run("ls \"%s\"%s" % (utils.sh_escape(path),
140 pattern),
141 stdout_tee=None, ignore_status=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000142 return result.exit_status == 0
143
144 # take a set of globs that cover all files, and see which are needed
145 patterns = ["*", ".[!.]*"]
showard56176ec2009-10-28 19:52:30 +0000146 patterns = [p for p in patterns if glob_matches_files(path, p)]
jadmanskid7b79ed2009-01-07 17:19:48 +0000147
148 # convert them into a set of paths suitable for the commandline
jadmanskid7b79ed2009-01-07 17:19:48 +0000149 if is_local:
showard56176ec2009-10-28 19:52:30 +0000150 return ["\"%s\"%s" % (utils.sh_escape(path), pattern)
151 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000152 else:
showard56176ec2009-10-28 19:52:30 +0000153 return [utils.scp_remote_escape(path) + pattern
154 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000155
156
157 def _make_rsync_compatible_source(self, source, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000158 """
159 Applies the same logic as _make_rsync_compatible_globs, but
jadmanskid7b79ed2009-01-07 17:19:48 +0000160 applies it to an entire list of sources, producing a new list of
mblighbc9402b2009-12-29 01:15:34 +0000161 sources, properly quoted.
162 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000163 return sum((self._make_rsync_compatible_globs(path, is_local)
164 for path in source), [])
jadmanskica7da372008-10-21 16:26:52 +0000165
166
mblighfeac0102009-04-28 18:31:12 +0000167 def _set_umask_perms(self, dest):
mblighbc9402b2009-12-29 01:15:34 +0000168 """
169 Given a destination file/dir (recursively) set the permissions on
170 all the files and directories to the max allowed by running umask.
171 """
mblighfeac0102009-04-28 18:31:12 +0000172
173 # now this looks strange but I haven't found a way in Python to _just_
174 # get the umask, apparently the only option is to try to set it
175 umask = os.umask(0)
176 os.umask(umask)
177
178 max_privs = 0777 & ~umask
179
180 def set_file_privs(filename):
181 file_stat = os.stat(filename)
182
183 file_privs = max_privs
184 # if the original file permissions do not have at least one
185 # executable bit then do not set it anywhere
186 if not file_stat.st_mode & 0111:
187 file_privs &= ~0111
188
189 os.chmod(filename, file_privs)
190
191 # try a bottom-up walk so changes on directory permissions won't cut
192 # our access to the files/directories inside it
193 for root, dirs, files in os.walk(dest, topdown=False):
194 # when setting the privileges we emulate the chmod "X" behaviour
195 # that sets to execute only if it is a directory or any of the
196 # owner/group/other already has execute right
197 for dirname in dirs:
198 os.chmod(os.path.join(root, dirname), max_privs)
199
200 for filename in files:
201 set_file_privs(os.path.join(root, filename))
202
203
204 # now set privs for the dest itself
205 if os.path.isdir(dest):
206 os.chmod(dest, max_privs)
207 else:
208 set_file_privs(dest)
209
210
mbligh45561782009-05-11 21:14:34 +0000211 def get_file(self, source, dest, delete_dest=False, preserve_perm=True,
212 preserve_symlinks=False):
jadmanskica7da372008-10-21 16:26:52 +0000213 """
214 Copy files from the remote host to a local path.
215
216 Directories will be copied recursively.
217 If a source component is a directory with a trailing slash,
218 the content of the directory will be copied, otherwise, the
219 directory itself and its content will be copied. This
220 behavior is similar to that of the program 'rsync'.
221
222 Args:
223 source: either
224 1) a single file or directory, as a string
225 2) a list of one or more (possibly mixed)
226 files or directories
227 dest: a file or a directory (if source contains a
228 directory or more than one element, you must
229 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000230 delete_dest: if this is true, the command will also clear
231 out any old files at dest that are not in the
232 source
mblighfeac0102009-04-28 18:31:12 +0000233 preserve_perm: tells get_file() to try to preserve the sources
234 permissions on files and dirs
mbligh45561782009-05-11 21:14:34 +0000235 preserve_symlinks: try to preserve symlinks instead of
236 transforming them into files/dirs on copy
jadmanskica7da372008-10-21 16:26:52 +0000237
238 Raises:
239 AutoservRunError: the scp command failed
240 """
mblighefccc1b2010-01-11 19:08:42 +0000241
242 # Start a master SSH connection if necessary.
243 self.start_master_ssh()
244
jadmanskica7da372008-10-21 16:26:52 +0000245 if isinstance(source, basestring):
246 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000247 dest = os.path.abspath(dest)
jadmanskica7da372008-10-21 16:26:52 +0000248
mblighc9892c02010-01-06 19:02:16 +0000249 # If rsync is disabled or fails, try scp.
250 try_scp = not self.use_rsync
251 if self.use_rsync:
252 try:
253 remote_source = self._encode_remote_paths(source)
254 local_dest = utils.sh_escape(dest)
255 rsync = self._make_rsync_cmd([remote_source], local_dest,
256 delete_dest, preserve_symlinks)
257 utils.run(rsync)
258 except error.CmdError, e:
259 logging.warn("trying scp, rsync failed: %s" % e)
260 try_scp = True
261
262 if try_scp:
jadmanskid7b79ed2009-01-07 17:19:48 +0000263 # scp has no equivalent to --delete, just drop the entire dest dir
264 if delete_dest and os.path.isdir(dest):
265 shutil.rmtree(dest)
266 os.mkdir(dest)
jadmanskica7da372008-10-21 16:26:52 +0000267
jadmanskid7b79ed2009-01-07 17:19:48 +0000268 remote_source = self._make_rsync_compatible_source(source, False)
269 if remote_source:
showard56176ec2009-10-28 19:52:30 +0000270 # _make_rsync_compatible_source() already did the escaping
271 remote_source = self._encode_remote_paths(remote_source,
272 escape=False)
jadmanskid7b79ed2009-01-07 17:19:48 +0000273 local_dest = utils.sh_escape(dest)
jadmanski2583a432009-02-10 23:59:11 +0000274 scp = self._make_scp_cmd([remote_source], local_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000275 try:
276 utils.run(scp)
277 except error.CmdError, e:
278 raise error.AutoservRunError(e.args[0], e.args[1])
jadmanskica7da372008-10-21 16:26:52 +0000279
mblighfeac0102009-04-28 18:31:12 +0000280 if not preserve_perm:
281 # we have no way to tell scp to not try to preserve the
282 # permissions so set them after copy instead.
283 # for rsync we could use "--no-p --chmod=ugo=rwX" but those
284 # options are only in very recent rsync versions
285 self._set_umask_perms(dest)
286
jadmanskica7da372008-10-21 16:26:52 +0000287
mbligh45561782009-05-11 21:14:34 +0000288 def send_file(self, source, dest, delete_dest=False,
289 preserve_symlinks=False):
jadmanskica7da372008-10-21 16:26:52 +0000290 """
291 Copy files from a local path to the remote host.
292
293 Directories will be copied recursively.
294 If a source component is a directory with a trailing slash,
295 the content of the directory will be copied, otherwise, the
296 directory itself and its content will be copied. This
297 behavior is similar to that of the program 'rsync'.
298
299 Args:
300 source: either
301 1) a single file or directory, as a string
302 2) a list of one or more (possibly mixed)
303 files or directories
304 dest: a file or a directory (if source contains a
305 directory or more than one element, you must
306 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000307 delete_dest: if this is true, the command will also clear
308 out any old files at dest that are not in the
309 source
mbligh45561782009-05-11 21:14:34 +0000310 preserve_symlinks: controls if symlinks on the source will be
311 copied as such on the destination or transformed into the
312 referenced file/directory
jadmanskica7da372008-10-21 16:26:52 +0000313
314 Raises:
315 AutoservRunError: the scp command failed
316 """
mblighefccc1b2010-01-11 19:08:42 +0000317
318 # Start a master SSH connection if necessary.
319 self.start_master_ssh()
320
jadmanskica7da372008-10-21 16:26:52 +0000321 if isinstance(source, basestring):
322 source = [source]
jadmanski2583a432009-02-10 23:59:11 +0000323 remote_dest = self._encode_remote_paths([dest])
jadmanskica7da372008-10-21 16:26:52 +0000324
mblighc9892c02010-01-06 19:02:16 +0000325 # If rsync is disabled or fails, try scp.
326 try_scp = not self.use_rsync
327 if self.use_rsync:
328 try:
329 local_sources = [utils.sh_escape(path) for path in source]
330 rsync = self._make_rsync_cmd(local_sources, remote_dest,
331 delete_dest, preserve_symlinks)
332 utils.run(rsync)
333 except error.CmdError, e:
334 logging.warn("trying scp, rsync failed: %s" % e)
335 try_scp = True
336
337 if try_scp:
jadmanskid7b79ed2009-01-07 17:19:48 +0000338 # scp has no equivalent to --delete, just drop the entire dest dir
339 if delete_dest:
showard27160152009-07-15 14:28:42 +0000340 is_dir = self.run("ls -d %s/" % dest,
jadmanskid7b79ed2009-01-07 17:19:48 +0000341 ignore_status=True).exit_status == 0
342 if is_dir:
343 cmd = "rm -rf %s && mkdir %s"
mbligh5a0ca532009-08-03 16:44:34 +0000344 cmd %= (dest, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000345 self.run(cmd)
jadmanskica7da372008-10-21 16:26:52 +0000346
jadmanski2583a432009-02-10 23:59:11 +0000347 local_sources = self._make_rsync_compatible_source(source, True)
348 if local_sources:
349 scp = self._make_scp_cmd(local_sources, remote_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000350 try:
351 utils.run(scp)
352 except error.CmdError, e:
353 raise error.AutoservRunError(e.args[0], e.args[1])
354
jadmanskica7da372008-10-21 16:26:52 +0000355
356 def ssh_ping(self, timeout=60):
357 try:
358 self.run("true", timeout=timeout, connect_timeout=timeout)
359 except error.AutoservSSHTimeout:
mblighd0e94982009-07-11 00:15:18 +0000360 msg = "Host (ssh) verify timed out (timeout = %d)" % timeout
jadmanskica7da372008-10-21 16:26:52 +0000361 raise error.AutoservSSHTimeout(msg)
mbligh9d738d62009-03-09 21:17:10 +0000362 except error.AutoservSshPermissionDeniedError:
363 #let AutoservSshPermissionDeniedError be visible to the callers
364 raise
jadmanskica7da372008-10-21 16:26:52 +0000365 except error.AutoservRunError, e:
mblighc971c5f2009-06-08 16:48:54 +0000366 # convert the generic AutoservRunError into something more
367 # specific for this context
368 raise error.AutoservSshPingHostError(e.description + '\n' +
369 repr(e.result_obj))
jadmanskica7da372008-10-21 16:26:52 +0000370
371
372 def is_up(self):
373 """
374 Check if the remote host is up.
375
jadmanskic0354912010-01-12 15:57:29 +0000376 @returns True if the remote host is up, False otherwise
jadmanskica7da372008-10-21 16:26:52 +0000377 """
378 try:
379 self.ssh_ping()
380 except error.AutoservError:
381 return False
382 else:
383 return True
384
385
386 def wait_up(self, timeout=None):
387 """
388 Wait until the remote host is up or the timeout expires.
389
390 In fact, it will wait until an ssh connection to the remote
391 host can be established, and getty is running.
392
jadmanskic0354912010-01-12 15:57:29 +0000393 @param timeout time limit in seconds before returning even
394 if the host is not up.
jadmanskica7da372008-10-21 16:26:52 +0000395
jadmanskic0354912010-01-12 15:57:29 +0000396 @returns True if the host was found to be up, False otherwise
jadmanskica7da372008-10-21 16:26:52 +0000397 """
398 if timeout:
399 end_time = time.time() + timeout
400
401 while not timeout or time.time() < end_time:
402 if self.is_up():
403 try:
404 if self.are_wait_up_processes_up():
405 return True
406 except error.AutoservError:
407 pass
408 time.sleep(1)
409
410 return False
411
412
jadmanskic0354912010-01-12 15:57:29 +0000413 def wait_down(self, timeout=None, warning_timer=None, old_boot_id=None):
jadmanskica7da372008-10-21 16:26:52 +0000414 """
415 Wait until the remote host is down or the timeout expires.
416
jadmanskic0354912010-01-12 15:57:29 +0000417 If old_boot_id is provided, this will wait until either the machine
418 is unpingable or self.get_boot_id() returns a value different from
419 old_boot_id. If the boot_id value has changed then the function
420 returns true under the assumption that the machine has shut down
421 and has now already come back up.
jadmanskica7da372008-10-21 16:26:52 +0000422
jadmanskic0354912010-01-12 15:57:29 +0000423 If old_boot_id is None then until the machine becomes unreachable the
424 method assumes the machine has not yet shut down.
jadmanskica7da372008-10-21 16:26:52 +0000425
jadmanskic0354912010-01-12 15:57:29 +0000426 @param timeout Time limit in seconds before returning even
427 if the host is still up.
428 @param warning_timer Time limit in seconds that will generate
429 a warning if the host is not down yet.
430 @param old_boot_id A string containing the result of self.get_boot_id()
431 prior to the host being told to shut down. Can be None if this is
432 not available.
433
434 @returns True if the host was found to be down, False otherwise
jadmanskica7da372008-10-21 16:26:52 +0000435 """
mbligh2ed998f2009-04-08 21:03:47 +0000436 current_time = time.time()
jadmanskica7da372008-10-21 16:26:52 +0000437 if timeout:
mbligh2ed998f2009-04-08 21:03:47 +0000438 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000439
mbligh2ed998f2009-04-08 21:03:47 +0000440 if warning_timer:
441 warn_time = current_time + warning_timer
442
jadmanskic0354912010-01-12 15:57:29 +0000443 if old_boot_id is not None:
444 logging.debug('Host %s pre-shutdown boot_id is %s',
445 self.hostname, old_boot_id)
446
mbligh2ed998f2009-04-08 21:03:47 +0000447 while not timeout or current_time < end_time:
jadmanskic0354912010-01-12 15:57:29 +0000448 try:
449 new_boot_id = self.get_boot_id()
450 except error.AutoservSSHTimeout:
451 logging.debug('Host %s is now unreachable over ssh, is down',
452 self.hostname)
jadmanskica7da372008-10-21 16:26:52 +0000453 return True
jadmanskic0354912010-01-12 15:57:29 +0000454 else:
455 # if the machine is up but the boot_id value has changed from
456 # old boot id, then we can assume the machine has gone down
457 # and then already come back up
458 if old_boot_id is not None and old_boot_id != new_boot_id:
459 logging.debug('Host %s now has boot_id %s and so must '
460 'have rebooted', self.hostname, new_boot_id)
461 return True
mbligh2ed998f2009-04-08 21:03:47 +0000462
463 if warning_timer and current_time > warn_time:
464 self.record("WARN", None, "shutdown",
465 "Shutdown took longer than %ds" % warning_timer)
466 # Print the warning only once.
467 warning_timer = None
mbligha4464402009-04-17 20:13:41 +0000468 # If a machine is stuck switching runlevels
469 # This may cause the machine to reboot.
470 self.run('kill -HUP 1', ignore_status=True)
mbligh2ed998f2009-04-08 21:03:47 +0000471
jadmanskica7da372008-10-21 16:26:52 +0000472 time.sleep(1)
mbligh2ed998f2009-04-08 21:03:47 +0000473 current_time = time.time()
jadmanskica7da372008-10-21 16:26:52 +0000474
475 return False
jadmanskif6562912008-10-21 17:59:01 +0000476
mbligha0a27592009-01-24 01:41:36 +0000477
jadmanskif6562912008-10-21 17:59:01 +0000478 # tunable constants for the verify & repair code
479 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
mbligha0a27592009-01-24 01:41:36 +0000480
jadmanskif6562912008-10-21 17:59:01 +0000481
showardca572982009-09-18 21:20:01 +0000482 def verify_connectivity(self):
483 super(AbstractSSHHost, self).verify_connectivity()
jadmanskif6562912008-10-21 17:59:01 +0000484
showardb18134f2009-03-20 20:52:18 +0000485 logging.info('Pinging host ' + self.hostname)
jadmanskif6562912008-10-21 17:59:01 +0000486 self.ssh_ping()
mbligh2ba7ab02009-08-24 22:09:26 +0000487 logging.info("Host (ssh) %s is alive", self.hostname)
jadmanskif6562912008-10-21 17:59:01 +0000488
jadmanski80deb752009-01-21 17:14:16 +0000489 if self.is_shutting_down():
mblighc971c5f2009-06-08 16:48:54 +0000490 raise error.AutoservHostIsShuttingDownError("Host is shutting down")
jadmanski80deb752009-01-21 17:14:16 +0000491
mblighb49b5232009-02-12 21:54:49 +0000492
showardca572982009-09-18 21:20:01 +0000493 def verify_software(self):
494 super(AbstractSSHHost, self).verify_software()
jadmanskif6562912008-10-21 17:59:01 +0000495 try:
showardad812bf2009-10-20 23:49:56 +0000496 self.check_diskspace(autotest.Autotest.get_install_dir(self),
497 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
jadmanskif6562912008-10-21 17:59:01 +0000498 except error.AutoservHostError:
499 raise # only want to raise if it's a space issue
showardad812bf2009-10-20 23:49:56 +0000500 except autotest.AutodirNotFoundError:
showardca572982009-09-18 21:20:01 +0000501 # autotest dir may not exist, etc. ignore
502 logging.debug('autodir space check exception, this is probably '
503 'safe to ignore\n' + traceback.format_exc())
mblighefccc1b2010-01-11 19:08:42 +0000504
505
506 def close(self):
507 super(AbstractSSHHost, self).close()
508 self._cleanup_master_ssh()
509
510
511 def _cleanup_master_ssh(self):
512 """
513 Release all resources (process, temporary directory) used by an active
514 master SSH connection.
515 """
516 # If a master SSH connection is running, kill it.
517 if self.master_ssh_job is not None:
518 utils.nuke_subprocess(self.master_ssh_job.sp)
519 self.master_ssh_job = None
520
521 # Remove the temporary directory for the master SSH socket.
522 if self.master_ssh_tempdir is not None:
523 self.master_ssh_tempdir.clean()
524 self.master_ssh_tempdir = None
525 self.master_ssh_option = ''
526
527
528 def start_master_ssh(self):
529 """
530 Called whenever a slave SSH connection needs to be initiated (e.g., by
531 run, rsync, scp). If master SSH support is enabled and a master SSH
532 connection is not active already, start a new one in the background.
533 Also, cleanup any zombie master SSH connections (e.g., dead due to
534 reboot).
535 """
536 if not enable_master_ssh:
537 return
538
539 # If a previously started master SSH connection is not running
540 # anymore, it needs to be cleaned up and then restarted.
541 if self.master_ssh_job is not None:
542 if self.master_ssh_job.sp.poll() is not None:
543 logging.info("Master ssh connection to %s is down.",
544 self.hostname)
545 self._cleanup_master_ssh()
546
547 # Start a new master SSH connection.
548 if self.master_ssh_job is None:
549 # Create a shared socket in a temp location.
550 self.master_ssh_tempdir = autotemp.tempdir(unique_id='ssh-master')
551 self.master_ssh_option = ("-o ControlPath=%s/socket" %
552 self.master_ssh_tempdir.name)
553
554 # Start the master SSH connection in the background.
555 master_cmd = self.ssh_command(options="-N -o ControlMaster=yes",
556 alive_interval=5)
557 logging.info("Starting master ssh connection '%s'" % master_cmd)
558 self.master_ssh_job = utils.BgJob(master_cmd)