blob: 4ee54c3c96f83ace0fbdacef283a9f0cf4853234 [file] [log] [blame]
Marc Herbert21eb6492015-11-13 15:48:53 -08001import os, time, socket, shutil, glob, logging, traceback, tempfile, re
xixuan6cf6d2f2016-01-29 15:29:00 -08002import shlex
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +08003import subprocess
4
Simran Basi3b858a22015-03-17 16:23:24 -07005from multiprocessing import Lock
Aviv Keshet53a216a2013-08-27 13:58:46 -07006from autotest_lib.client.common_lib import autotemp, error
jadmanski31c49b72008-10-27 20:44:48 +00007from autotest_lib.server import utils, autotest
mblighe8b93af2009-01-30 00:45:53 +00008from autotest_lib.server.hosts import remote
Roshan Pius58e5dd32015-10-16 15:16:42 -07009from autotest_lib.server.hosts import rpc_server_tracker
mblighefccc1b2010-01-11 19:08:42 +000010from autotest_lib.client.common_lib.global_config import global_config
jadmanskica7da372008-10-21 16:26:52 +000011
Gwendal Grignou36b61702016-02-10 11:57:53 -080012# pylint: disable=C0111
jadmanskica7da372008-10-21 16:26:52 +000013
mblighb86bfa12010-02-12 20:22:21 +000014get_value = global_config.get_config_value
15enable_master_ssh = get_value('AUTOSERV', 'enable_master_ssh', type=bool,
16 default=False)
mblighefccc1b2010-01-11 19:08:42 +000017
18
Fang Deng96667ca2013-08-01 17:46:18 -070019class AbstractSSHHost(remote.RemoteHost):
mblighbc9402b2009-12-29 01:15:34 +000020 """
21 This class represents a generic implementation of most of the
jadmanskica7da372008-10-21 16:26:52 +000022 framework necessary for controlling a host via ssh. It implements
23 almost all of the abstract Host methods, except for the core
mblighbc9402b2009-12-29 01:15:34 +000024 Host.run method.
25 """
Simran Basi5ace6f22016-01-06 17:30:44 -080026 VERSION_PREFIX = ''
jadmanskica7da372008-10-21 16:26:52 +000027
jadmanskif6562912008-10-21 17:59:01 +000028 def _initialize(self, hostname, user="root", port=22, password="",
Simran Basi1bf60eb2015-12-01 16:39:29 -080029 is_client_install_supported=True, host_attributes={},
30 *args, **dargs):
jadmanskif6562912008-10-21 17:59:01 +000031 super(AbstractSSHHost, self)._initialize(hostname=hostname,
32 *args, **dargs)
Dan Shic07b8932014-12-11 15:22:30 -080033 # IP address is retrieved only on demand. Otherwise the host
34 # initialization will fail for host is not online.
35 self._ip = None
jadmanskica7da372008-10-21 16:26:52 +000036 self.user = user
37 self.port = port
38 self.password = password
Roshan Piusa58163a2015-10-14 13:36:29 -070039 self._is_client_install_supported = is_client_install_supported
showard6eafb492010-01-15 20:29:06 +000040 self._use_rsync = None
Fang Deng3af66202013-08-16 15:19:25 -070041 self.known_hosts_file = tempfile.mkstemp()[1]
Roshan Pius58e5dd32015-10-16 15:16:42 -070042 self._rpc_server_tracker = rpc_server_tracker.RpcServerTracker(self);
jadmanskica7da372008-10-21 16:26:52 +000043
mblighefccc1b2010-01-11 19:08:42 +000044 """
45 Master SSH connection background job, socket temp directory and socket
46 control path option. If master-SSH is enabled, these fields will be
47 initialized by start_master_ssh when a new SSH connection is initiated.
48 """
49 self.master_ssh_job = None
50 self.master_ssh_tempdir = None
51 self.master_ssh_option = ''
52
Simran Basi3b858a22015-03-17 16:23:24 -070053 # Create a Lock to protect against race conditions.
54 self._lock = Lock()
55
Simran Basi1bf60eb2015-12-01 16:39:29 -080056 self.host_attributes = host_attributes
57
showard6eafb492010-01-15 20:29:06 +000058
Dan Shic07b8932014-12-11 15:22:30 -080059 @property
60 def ip(self):
61 """@return IP address of the host.
62 """
63 if not self._ip:
64 self._ip = socket.getaddrinfo(self.hostname, None)[0][4][0]
65 return self._ip
66
67
Roshan Piusa58163a2015-10-14 13:36:29 -070068 @property
69 def is_client_install_supported(self):
70 """"
71 Returns True if the host supports autotest client installs, False
72 otherwise.
73 """
74 return self._is_client_install_supported
75
76
Roshan Pius58e5dd32015-10-16 15:16:42 -070077 @property
78 def rpc_server_tracker(self):
79 """"
80 @return The RPC server tracker associated with this host.
81 """
82 return self._rpc_server_tracker
83
84
Fang Deng96667ca2013-08-01 17:46:18 -070085 def make_ssh_command(self, user="root", port=22, opts='',
86 hosts_file='/dev/null',
87 connect_timeout=30, alive_interval=300):
88 base_command = ("/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no "
89 "-o UserKnownHostsFile=%s -o BatchMode=yes "
90 "-o ConnectTimeout=%d -o ServerAliveInterval=%d "
91 "-l %s -p %d")
92 assert isinstance(connect_timeout, (int, long))
93 assert connect_timeout > 0 # can't disable the timeout
94 return base_command % (opts, hosts_file, connect_timeout,
95 alive_interval, user, port)
96
97
showard6eafb492010-01-15 20:29:06 +000098 def use_rsync(self):
99 if self._use_rsync is not None:
100 return self._use_rsync
101
mblighc9892c02010-01-06 19:02:16 +0000102 # Check if rsync is available on the remote host. If it's not,
103 # don't try to use it for any future file transfers.
showard6eafb492010-01-15 20:29:06 +0000104 self._use_rsync = self._check_rsync()
105 if not self._use_rsync:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700106 logging.warning("rsync not available on remote host %s -- disabled",
mblighc9892c02010-01-06 19:02:16 +0000107 self.hostname)
Eric Lie0493a42010-11-15 13:05:43 -0800108 return self._use_rsync
mblighc9892c02010-01-06 19:02:16 +0000109
110
111 def _check_rsync(self):
112 """
113 Check if rsync is available on the remote host.
114 """
115 try:
116 self.run("rsync --version", stdout_tee=None, stderr_tee=None)
117 except error.AutoservRunError:
118 return False
119 return True
120
jadmanskica7da372008-10-21 16:26:52 +0000121
Gwendal Grignou36b61702016-02-10 11:57:53 -0800122 def _encode_remote_paths(self, paths, escape=True, use_scp=False):
mblighbc9402b2009-12-29 01:15:34 +0000123 """
124 Given a list of file paths, encodes it as a single remote path, in
125 the style used by rsync and scp.
Gwendal Grignou36b61702016-02-10 11:57:53 -0800126 escape: add \\ to protect special characters.
127 use_scp: encode for scp if true, rsync if false.
mblighbc9402b2009-12-29 01:15:34 +0000128 """
showard56176ec2009-10-28 19:52:30 +0000129 if escape:
130 paths = [utils.scp_remote_escape(path) for path in paths]
Marc Herbert21eb6492015-11-13 15:48:53 -0800131
132 remote = self.hostname
133
134 # rsync and scp require IPv6 brackets, even when there isn't any
135 # trailing port number (ssh doesn't support IPv6 brackets).
136 # In the Python >= 3.3 future, 'import ipaddress' will parse addresses.
137 if re.search(r':.*:', remote):
138 remote = '[%s]' % remote
139
Gwendal Grignou36b61702016-02-10 11:57:53 -0800140 if use_scp:
141 return '%s@%s:"%s"' % (self.user, remote, " ".join(paths))
142 else:
143 return '%s@%s:%s' % (
144 self.user, remote,
145 " :".join('"%s"' % p for p in paths))
jadmanskica7da372008-10-21 16:26:52 +0000146
Gwendal Grignou36b61702016-02-10 11:57:53 -0800147 def _encode_local_paths(self, paths, escape=True):
148 """
149 Given a list of file paths, encodes it as a single local path.
150 escape: add \\ to protect special characters.
151 """
152 if escape:
153 paths = [utils.sh_escape(path) for path in paths]
154
155 return " ".join('"%s"' % p for p in paths)
jadmanskica7da372008-10-21 16:26:52 +0000156
mbligh45561782009-05-11 21:14:34 +0000157 def _make_rsync_cmd(self, sources, dest, delete_dest, preserve_symlinks):
mblighbc9402b2009-12-29 01:15:34 +0000158 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800159 Given a string of source paths and a destination path, produces the
jadmanskid7b79ed2009-01-07 17:19:48 +0000160 appropriate rsync command for copying them. Remote paths must be
mblighbc9402b2009-12-29 01:15:34 +0000161 pre-encoded.
162 """
Fang Deng96667ca2013-08-01 17:46:18 -0700163 ssh_cmd = self.make_ssh_command(user=self.user, port=self.port,
164 opts=self.master_ssh_option,
165 hosts_file=self.known_hosts_file)
jadmanskid7b79ed2009-01-07 17:19:48 +0000166 if delete_dest:
167 delete_flag = "--delete"
168 else:
169 delete_flag = ""
mbligh45561782009-05-11 21:14:34 +0000170 if preserve_symlinks:
171 symlink_flag = ""
172 else:
173 symlink_flag = "-L"
Dan Shi06d7fbf2014-02-12 12:34:41 -0800174 command = ("rsync %s %s --timeout=1800 --rsh='%s' -az --no-o --no-g "
David Hendricksb8904182014-06-02 15:22:49 -0700175 "%s \"%s\"")
Gwendal Grignou36b61702016-02-10 11:57:53 -0800176 return command % (symlink_flag, delete_flag, ssh_cmd, sources, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000177
178
Eric Li861b2d52011-02-04 14:50:35 -0800179 def _make_ssh_cmd(self, cmd):
180 """
181 Create a base ssh command string for the host which can be used
182 to run commands directly on the machine
183 """
Fang Deng96667ca2013-08-01 17:46:18 -0700184 base_cmd = self.make_ssh_command(user=self.user, port=self.port,
185 opts=self.master_ssh_option,
186 hosts_file=self.known_hosts_file)
Eric Li861b2d52011-02-04 14:50:35 -0800187
188 return '%s %s "%s"' % (base_cmd, self.hostname, utils.sh_escape(cmd))
189
jadmanskid7b79ed2009-01-07 17:19:48 +0000190 def _make_scp_cmd(self, sources, dest):
mblighbc9402b2009-12-29 01:15:34 +0000191 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800192 Given a string of source paths and a destination path, produces the
jadmanskid7b79ed2009-01-07 17:19:48 +0000193 appropriate scp command for encoding it. Remote paths must be
mblighbc9402b2009-12-29 01:15:34 +0000194 pre-encoded.
195 """
mblighc0649d62010-01-15 18:15:58 +0000196 command = ("scp -rq %s -o StrictHostKeyChecking=no "
lmraf676f32010-02-04 03:36:26 +0000197 "-o UserKnownHostsFile=%s -P %d %s '%s'")
Fang Deng3af66202013-08-16 15:19:25 -0700198 return command % (self.master_ssh_option, self.known_hosts_file,
Gwendal Grignou36b61702016-02-10 11:57:53 -0800199 self.port, sources, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000200
201
202 def _make_rsync_compatible_globs(self, path, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000203 """
204 Given an rsync-style path, returns a list of globbed paths
jadmanskid7b79ed2009-01-07 17:19:48 +0000205 that will hopefully provide equivalent behaviour for scp. Does not
206 support the full range of rsync pattern matching behaviour, only that
207 exposed in the get/send_file interface (trailing slashes).
208
209 The is_local param is flag indicating if the paths should be
mblighbc9402b2009-12-29 01:15:34 +0000210 interpreted as local or remote paths.
211 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000212
213 # non-trailing slash paths should just work
214 if len(path) == 0 or path[-1] != "/":
215 return [path]
216
217 # make a function to test if a pattern matches any files
218 if is_local:
showard56176ec2009-10-28 19:52:30 +0000219 def glob_matches_files(path, pattern):
220 return len(glob.glob(path + pattern)) > 0
jadmanskid7b79ed2009-01-07 17:19:48 +0000221 else:
showard56176ec2009-10-28 19:52:30 +0000222 def glob_matches_files(path, pattern):
223 result = self.run("ls \"%s\"%s" % (utils.sh_escape(path),
224 pattern),
225 stdout_tee=None, ignore_status=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000226 return result.exit_status == 0
227
228 # take a set of globs that cover all files, and see which are needed
229 patterns = ["*", ".[!.]*"]
showard56176ec2009-10-28 19:52:30 +0000230 patterns = [p for p in patterns if glob_matches_files(path, p)]
jadmanskid7b79ed2009-01-07 17:19:48 +0000231
232 # convert them into a set of paths suitable for the commandline
jadmanskid7b79ed2009-01-07 17:19:48 +0000233 if is_local:
showard56176ec2009-10-28 19:52:30 +0000234 return ["\"%s\"%s" % (utils.sh_escape(path), pattern)
235 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000236 else:
showard56176ec2009-10-28 19:52:30 +0000237 return [utils.scp_remote_escape(path) + pattern
238 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000239
240
241 def _make_rsync_compatible_source(self, source, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000242 """
243 Applies the same logic as _make_rsync_compatible_globs, but
jadmanskid7b79ed2009-01-07 17:19:48 +0000244 applies it to an entire list of sources, producing a new list of
mblighbc9402b2009-12-29 01:15:34 +0000245 sources, properly quoted.
246 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000247 return sum((self._make_rsync_compatible_globs(path, is_local)
248 for path in source), [])
jadmanskica7da372008-10-21 16:26:52 +0000249
250
mblighfeac0102009-04-28 18:31:12 +0000251 def _set_umask_perms(self, dest):
mblighbc9402b2009-12-29 01:15:34 +0000252 """
253 Given a destination file/dir (recursively) set the permissions on
254 all the files and directories to the max allowed by running umask.
255 """
mblighfeac0102009-04-28 18:31:12 +0000256
257 # now this looks strange but I haven't found a way in Python to _just_
258 # get the umask, apparently the only option is to try to set it
259 umask = os.umask(0)
260 os.umask(umask)
261
262 max_privs = 0777 & ~umask
263
264 def set_file_privs(filename):
Chris Masone567d0d92011-12-19 09:38:30 -0800265 """Sets mode of |filename|. Assumes |filename| exists."""
266 file_stat = os.stat(filename)
mblighfeac0102009-04-28 18:31:12 +0000267
268 file_privs = max_privs
269 # if the original file permissions do not have at least one
270 # executable bit then do not set it anywhere
271 if not file_stat.st_mode & 0111:
272 file_privs &= ~0111
273
274 os.chmod(filename, file_privs)
275
276 # try a bottom-up walk so changes on directory permissions won't cut
277 # our access to the files/directories inside it
278 for root, dirs, files in os.walk(dest, topdown=False):
279 # when setting the privileges we emulate the chmod "X" behaviour
280 # that sets to execute only if it is a directory or any of the
281 # owner/group/other already has execute right
282 for dirname in dirs:
283 os.chmod(os.path.join(root, dirname), max_privs)
284
Chris Masone567d0d92011-12-19 09:38:30 -0800285 # Filter out broken symlinks as we go.
286 for filename in filter(os.path.exists, files):
mblighfeac0102009-04-28 18:31:12 +0000287 set_file_privs(os.path.join(root, filename))
288
289
290 # now set privs for the dest itself
291 if os.path.isdir(dest):
292 os.chmod(dest, max_privs)
293 else:
294 set_file_privs(dest)
295
296
mbligh45561782009-05-11 21:14:34 +0000297 def get_file(self, source, dest, delete_dest=False, preserve_perm=True,
298 preserve_symlinks=False):
jadmanskica7da372008-10-21 16:26:52 +0000299 """
300 Copy files from the remote host to a local path.
301
302 Directories will be copied recursively.
303 If a source component is a directory with a trailing slash,
304 the content of the directory will be copied, otherwise, the
305 directory itself and its content will be copied. This
306 behavior is similar to that of the program 'rsync'.
307
308 Args:
309 source: either
310 1) a single file or directory, as a string
311 2) a list of one or more (possibly mixed)
312 files or directories
313 dest: a file or a directory (if source contains a
314 directory or more than one element, you must
315 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000316 delete_dest: if this is true, the command will also clear
317 out any old files at dest that are not in the
318 source
mblighfeac0102009-04-28 18:31:12 +0000319 preserve_perm: tells get_file() to try to preserve the sources
320 permissions on files and dirs
mbligh45561782009-05-11 21:14:34 +0000321 preserve_symlinks: try to preserve symlinks instead of
322 transforming them into files/dirs on copy
jadmanskica7da372008-10-21 16:26:52 +0000323
324 Raises:
325 AutoservRunError: the scp command failed
326 """
Simran Basi882f15b2013-10-29 14:59:34 -0700327 logging.debug('get_file. source: %s, dest: %s, delete_dest: %s,'
328 'preserve_perm: %s, preserve_symlinks:%s', source, dest,
329 delete_dest, preserve_perm, preserve_symlinks)
mblighefccc1b2010-01-11 19:08:42 +0000330 # Start a master SSH connection if necessary.
331 self.start_master_ssh()
332
jadmanskica7da372008-10-21 16:26:52 +0000333 if isinstance(source, basestring):
334 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000335 dest = os.path.abspath(dest)
jadmanskica7da372008-10-21 16:26:52 +0000336
mblighc9892c02010-01-06 19:02:16 +0000337 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000338 try_scp = True
339 if self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700340 logging.debug('Using Rsync.')
mblighc9892c02010-01-06 19:02:16 +0000341 try:
342 remote_source = self._encode_remote_paths(source)
343 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800344 rsync = self._make_rsync_cmd(remote_source, local_dest,
mblighc9892c02010-01-06 19:02:16 +0000345 delete_dest, preserve_symlinks)
346 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000347 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000348 except error.CmdError, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700349 logging.warning("trying scp, rsync failed: %s", e)
mblighc9892c02010-01-06 19:02:16 +0000350
351 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700352 logging.debug('Trying scp.')
jadmanskid7b79ed2009-01-07 17:19:48 +0000353 # scp has no equivalent to --delete, just drop the entire dest dir
354 if delete_dest and os.path.isdir(dest):
355 shutil.rmtree(dest)
356 os.mkdir(dest)
jadmanskica7da372008-10-21 16:26:52 +0000357
jadmanskid7b79ed2009-01-07 17:19:48 +0000358 remote_source = self._make_rsync_compatible_source(source, False)
359 if remote_source:
showard56176ec2009-10-28 19:52:30 +0000360 # _make_rsync_compatible_source() already did the escaping
Gwendal Grignou36b61702016-02-10 11:57:53 -0800361 remote_source = self._encode_remote_paths(
362 remote_source, escape=False, use_scp=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000363 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800364 scp = self._make_scp_cmd(remote_source, local_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000365 try:
366 utils.run(scp)
367 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700368 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000369 raise error.AutoservRunError(e.args[0], e.args[1])
jadmanskica7da372008-10-21 16:26:52 +0000370
mblighfeac0102009-04-28 18:31:12 +0000371 if not preserve_perm:
372 # we have no way to tell scp to not try to preserve the
373 # permissions so set them after copy instead.
374 # for rsync we could use "--no-p --chmod=ugo=rwX" but those
375 # options are only in very recent rsync versions
376 self._set_umask_perms(dest)
377
jadmanskica7da372008-10-21 16:26:52 +0000378
mbligh45561782009-05-11 21:14:34 +0000379 def send_file(self, source, dest, delete_dest=False,
380 preserve_symlinks=False):
jadmanskica7da372008-10-21 16:26:52 +0000381 """
382 Copy files from a local path to the remote host.
383
384 Directories will be copied recursively.
385 If a source component is a directory with a trailing slash,
386 the content of the directory will be copied, otherwise, the
387 directory itself and its content will be copied. This
388 behavior is similar to that of the program 'rsync'.
389
390 Args:
391 source: either
392 1) a single file or directory, as a string
393 2) a list of one or more (possibly mixed)
394 files or directories
395 dest: a file or a directory (if source contains a
396 directory or more than one element, you must
397 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000398 delete_dest: if this is true, the command will also clear
399 out any old files at dest that are not in the
400 source
mbligh45561782009-05-11 21:14:34 +0000401 preserve_symlinks: controls if symlinks on the source will be
402 copied as such on the destination or transformed into the
403 referenced file/directory
jadmanskica7da372008-10-21 16:26:52 +0000404
405 Raises:
406 AutoservRunError: the scp command failed
407 """
Simran Basi882f15b2013-10-29 14:59:34 -0700408 logging.debug('send_file. source: %s, dest: %s, delete_dest: %s,'
409 'preserve_symlinks:%s', source, dest,
410 delete_dest, preserve_symlinks)
mblighefccc1b2010-01-11 19:08:42 +0000411 # Start a master SSH connection if necessary.
412 self.start_master_ssh()
413
jadmanskica7da372008-10-21 16:26:52 +0000414 if isinstance(source, basestring):
415 source = [source]
416
Gwendal Grignou36b61702016-02-10 11:57:53 -0800417 local_sources = self._encode_local_paths(source)
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700418 if not local_sources:
Gwendal Grignou36b61702016-02-10 11:57:53 -0800419 raise error.TestError('source |%s| yielded an empty string' % (
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700420 source))
Gwendal Grignou36b61702016-02-10 11:57:53 -0800421 if local_sources.find('\x00') != -1:
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700422 raise error.TestError('one or more sources include NUL char')
423
mblighc9892c02010-01-06 19:02:16 +0000424 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000425 try_scp = True
426 if self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700427 logging.debug('Using Rsync.')
Gwendal Grignou36b61702016-02-10 11:57:53 -0800428 remote_dest = self._encode_remote_paths([dest])
mblighc9892c02010-01-06 19:02:16 +0000429 try:
mblighc9892c02010-01-06 19:02:16 +0000430 rsync = self._make_rsync_cmd(local_sources, remote_dest,
431 delete_dest, preserve_symlinks)
432 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000433 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000434 except error.CmdError, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700435 logging.warning("trying scp, rsync failed: %s", e)
mblighc9892c02010-01-06 19:02:16 +0000436
437 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700438 logging.debug('Trying scp.')
jadmanskid7b79ed2009-01-07 17:19:48 +0000439 # scp has no equivalent to --delete, just drop the entire dest dir
440 if delete_dest:
showard27160152009-07-15 14:28:42 +0000441 is_dir = self.run("ls -d %s/" % dest,
jadmanskid7b79ed2009-01-07 17:19:48 +0000442 ignore_status=True).exit_status == 0
443 if is_dir:
444 cmd = "rm -rf %s && mkdir %s"
mbligh5a0ca532009-08-03 16:44:34 +0000445 cmd %= (dest, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000446 self.run(cmd)
jadmanskica7da372008-10-21 16:26:52 +0000447
Gwendal Grignou36b61702016-02-10 11:57:53 -0800448 remote_dest = self._encode_remote_paths([dest], use_scp=True)
jadmanski2583a432009-02-10 23:59:11 +0000449 local_sources = self._make_rsync_compatible_source(source, True)
450 if local_sources:
Cheng-Yi Chiang9b2812d2016-02-29 17:01:44 +0800451 sources = self._encode_local_paths(local_sources, escape=False)
452 scp = self._make_scp_cmd(sources, remote_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000453 try:
454 utils.run(scp)
455 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700456 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000457 raise error.AutoservRunError(e.args[0], e.args[1])
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700458 else:
459 logging.debug('skipping scp for empty source list')
jadmanskid7b79ed2009-01-07 17:19:48 +0000460
jadmanskica7da372008-10-21 16:26:52 +0000461
Simran Basi1621c632015-10-14 12:22:23 -0700462 def verify_ssh_user_access(self):
463 """Verify ssh access to this host.
464
465 @returns False if ssh_ping fails due to Permissions error, True
466 otherwise.
467 """
468 try:
469 self.ssh_ping()
470 except (error.AutoservSshPermissionDeniedError,
471 error.AutoservSshPingHostError):
472 return False
473 return True
474
475
beeps46dadc92013-11-07 14:07:10 -0800476 def ssh_ping(self, timeout=60, base_cmd='true'):
beepsadd66d32013-03-04 17:21:51 -0800477 """
478 Pings remote host via ssh.
479
480 @param timeout: Time in seconds before giving up.
481 Defaults to 60 seconds.
beeps46dadc92013-11-07 14:07:10 -0800482 @param base_cmd: The base command to run with the ssh ping.
483 Defaults to true.
beepsadd66d32013-03-04 17:21:51 -0800484 @raise AutoservSSHTimeout: If the ssh ping times out.
485 @raise AutoservSshPermissionDeniedError: If ssh ping fails due to
486 permissions.
487 @raise AutoservSshPingHostError: For other AutoservRunErrors.
488 """
jadmanskica7da372008-10-21 16:26:52 +0000489 try:
beeps46dadc92013-11-07 14:07:10 -0800490 self.run(base_cmd, timeout=timeout, connect_timeout=timeout)
jadmanskica7da372008-10-21 16:26:52 +0000491 except error.AutoservSSHTimeout:
mblighd0e94982009-07-11 00:15:18 +0000492 msg = "Host (ssh) verify timed out (timeout = %d)" % timeout
jadmanskica7da372008-10-21 16:26:52 +0000493 raise error.AutoservSSHTimeout(msg)
mbligh9d738d62009-03-09 21:17:10 +0000494 except error.AutoservSshPermissionDeniedError:
495 #let AutoservSshPermissionDeniedError be visible to the callers
496 raise
jadmanskica7da372008-10-21 16:26:52 +0000497 except error.AutoservRunError, e:
mblighc971c5f2009-06-08 16:48:54 +0000498 # convert the generic AutoservRunError into something more
499 # specific for this context
500 raise error.AutoservSshPingHostError(e.description + '\n' +
501 repr(e.result_obj))
jadmanskica7da372008-10-21 16:26:52 +0000502
503
beeps46dadc92013-11-07 14:07:10 -0800504 def is_up(self, timeout=60, base_cmd='true'):
jadmanskica7da372008-10-21 16:26:52 +0000505 """
beeps46dadc92013-11-07 14:07:10 -0800506 Check if the remote host is up by ssh-ing and running a base command.
jadmanskica7da372008-10-21 16:26:52 +0000507
beepsadd66d32013-03-04 17:21:51 -0800508 @param timeout: timeout in seconds.
beeps46dadc92013-11-07 14:07:10 -0800509 @param base_cmd: a base command to run with ssh. The default is 'true'.
beepsadd66d32013-03-04 17:21:51 -0800510 @returns True if the remote host is up before the timeout expires,
511 False otherwise.
jadmanskica7da372008-10-21 16:26:52 +0000512 """
513 try:
beeps46dadc92013-11-07 14:07:10 -0800514 self.ssh_ping(timeout=timeout, base_cmd=base_cmd)
jadmanskica7da372008-10-21 16:26:52 +0000515 except error.AutoservError:
516 return False
517 else:
518 return True
519
520
521 def wait_up(self, timeout=None):
522 """
523 Wait until the remote host is up or the timeout expires.
524
525 In fact, it will wait until an ssh connection to the remote
526 host can be established, and getty is running.
527
jadmanskic0354912010-01-12 15:57:29 +0000528 @param timeout time limit in seconds before returning even
529 if the host is not up.
jadmanskica7da372008-10-21 16:26:52 +0000530
beepsadd66d32013-03-04 17:21:51 -0800531 @returns True if the host was found to be up before the timeout expires,
532 False otherwise
jadmanskica7da372008-10-21 16:26:52 +0000533 """
534 if timeout:
beeps46dadc92013-11-07 14:07:10 -0800535 current_time = int(time.time())
536 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000537
beepsadd66d32013-03-04 17:21:51 -0800538 while not timeout or current_time < end_time:
539 if self.is_up(timeout=end_time - current_time):
jadmanskica7da372008-10-21 16:26:52 +0000540 try:
541 if self.are_wait_up_processes_up():
jadmanski7ebac3d2010-06-17 16:06:31 +0000542 logging.debug('Host %s is now up', self.hostname)
jadmanskica7da372008-10-21 16:26:52 +0000543 return True
544 except error.AutoservError:
545 pass
546 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800547 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000548
jadmanski7ebac3d2010-06-17 16:06:31 +0000549 logging.debug('Host %s is still down after waiting %d seconds',
550 self.hostname, int(timeout + time.time() - end_time))
jadmanskica7da372008-10-21 16:26:52 +0000551 return False
552
553
jadmanskic0354912010-01-12 15:57:29 +0000554 def wait_down(self, timeout=None, warning_timer=None, old_boot_id=None):
jadmanskica7da372008-10-21 16:26:52 +0000555 """
556 Wait until the remote host is down or the timeout expires.
557
jadmanskic0354912010-01-12 15:57:29 +0000558 If old_boot_id is provided, this will wait until either the machine
559 is unpingable or self.get_boot_id() returns a value different from
560 old_boot_id. If the boot_id value has changed then the function
561 returns true under the assumption that the machine has shut down
562 and has now already come back up.
jadmanskica7da372008-10-21 16:26:52 +0000563
jadmanskic0354912010-01-12 15:57:29 +0000564 If old_boot_id is None then until the machine becomes unreachable the
565 method assumes the machine has not yet shut down.
jadmanskica7da372008-10-21 16:26:52 +0000566
beepsadd66d32013-03-04 17:21:51 -0800567 Based on this definition, the 4 possible permutations of timeout
568 and old_boot_id are:
569 1. timeout and old_boot_id: wait timeout seconds for either the
570 host to become unpingable, or the boot id
571 to change. In the latter case we've rebooted
572 and in the former case we've only shutdown,
573 but both cases return True.
574 2. only timeout: wait timeout seconds for the host to become unpingable.
575 If the host remains pingable throughout timeout seconds
576 we return False.
577 3. only old_boot_id: wait forever until either the host becomes
578 unpingable or the boot_id changes. Return true
579 when either of those conditions are met.
580 4. not timeout, not old_boot_id: wait forever till the host becomes
581 unpingable.
582
jadmanskic0354912010-01-12 15:57:29 +0000583 @param timeout Time limit in seconds before returning even
584 if the host is still up.
585 @param warning_timer Time limit in seconds that will generate
586 a warning if the host is not down yet.
587 @param old_boot_id A string containing the result of self.get_boot_id()
588 prior to the host being told to shut down. Can be None if this is
589 not available.
590
591 @returns True if the host was found to be down, False otherwise
jadmanskica7da372008-10-21 16:26:52 +0000592 """
mblighe5e3cf22010-05-27 23:33:14 +0000593 #TODO: there is currently no way to distinguish between knowing
594 #TODO: boot_id was unsupported and not knowing the boot_id.
beeps46dadc92013-11-07 14:07:10 -0800595 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000596 if timeout:
mbligh2ed998f2009-04-08 21:03:47 +0000597 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000598
mbligh2ed998f2009-04-08 21:03:47 +0000599 if warning_timer:
600 warn_time = current_time + warning_timer
601
jadmanskic0354912010-01-12 15:57:29 +0000602 if old_boot_id is not None:
603 logging.debug('Host %s pre-shutdown boot_id is %s',
604 self.hostname, old_boot_id)
605
beepsadd66d32013-03-04 17:21:51 -0800606 # Impose semi real-time deadline constraints, since some clients
607 # (eg: watchdog timer tests) expect strict checking of time elapsed.
608 # Each iteration of this loop is treated as though it atomically
609 # completes within current_time, this is needed because if we used
610 # inline time.time() calls instead then the following could happen:
611 #
612 # while not timeout or time.time() < end_time: [23 < 30]
613 # some code. [takes 10 secs]
614 # try:
615 # new_boot_id = self.get_boot_id(timeout=end_time - time.time())
616 # [30 - 33]
617 # The last step will lead to a return True, when in fact the machine
618 # went down at 32 seconds (>30). Hence we need to pass get_boot_id
619 # the same time that allowed us into that iteration of the loop.
mbligh2ed998f2009-04-08 21:03:47 +0000620 while not timeout or current_time < end_time:
jadmanskic0354912010-01-12 15:57:29 +0000621 try:
beeps46dadc92013-11-07 14:07:10 -0800622 new_boot_id = self.get_boot_id(timeout=end_time-current_time)
mblighdbc7e4a2010-01-15 20:34:20 +0000623 except error.AutoservError:
jadmanskic0354912010-01-12 15:57:29 +0000624 logging.debug('Host %s is now unreachable over ssh, is down',
625 self.hostname)
jadmanskica7da372008-10-21 16:26:52 +0000626 return True
jadmanskic0354912010-01-12 15:57:29 +0000627 else:
628 # if the machine is up but the boot_id value has changed from
629 # old boot id, then we can assume the machine has gone down
630 # and then already come back up
631 if old_boot_id is not None and old_boot_id != new_boot_id:
632 logging.debug('Host %s now has boot_id %s and so must '
633 'have rebooted', self.hostname, new_boot_id)
634 return True
mbligh2ed998f2009-04-08 21:03:47 +0000635
636 if warning_timer and current_time > warn_time:
Scott Zawalskic86fdeb2013-10-23 10:24:04 -0400637 self.record("INFO", None, "shutdown",
mbligh2ed998f2009-04-08 21:03:47 +0000638 "Shutdown took longer than %ds" % warning_timer)
639 # Print the warning only once.
640 warning_timer = None
mbligha4464402009-04-17 20:13:41 +0000641 # If a machine is stuck switching runlevels
642 # This may cause the machine to reboot.
643 self.run('kill -HUP 1', ignore_status=True)
mbligh2ed998f2009-04-08 21:03:47 +0000644
jadmanskica7da372008-10-21 16:26:52 +0000645 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800646 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000647
648 return False
jadmanskif6562912008-10-21 17:59:01 +0000649
mbligha0a27592009-01-24 01:41:36 +0000650
jadmanskif6562912008-10-21 17:59:01 +0000651 # tunable constants for the verify & repair code
mblighb86bfa12010-02-12 20:22:21 +0000652 AUTOTEST_GB_DISKSPACE_REQUIRED = get_value("SERVER",
653 "gb_diskspace_required",
Fang Deng6b05f5b2013-03-20 13:42:11 -0700654 type=float,
655 default=20.0)
mbligha0a27592009-01-24 01:41:36 +0000656
jadmanskif6562912008-10-21 17:59:01 +0000657
showardca572982009-09-18 21:20:01 +0000658 def verify_connectivity(self):
659 super(AbstractSSHHost, self).verify_connectivity()
jadmanskif6562912008-10-21 17:59:01 +0000660
showardb18134f2009-03-20 20:52:18 +0000661 logging.info('Pinging host ' + self.hostname)
jadmanskif6562912008-10-21 17:59:01 +0000662 self.ssh_ping()
mbligh2ba7ab02009-08-24 22:09:26 +0000663 logging.info("Host (ssh) %s is alive", self.hostname)
jadmanskif6562912008-10-21 17:59:01 +0000664
jadmanski80deb752009-01-21 17:14:16 +0000665 if self.is_shutting_down():
mblighc971c5f2009-06-08 16:48:54 +0000666 raise error.AutoservHostIsShuttingDownError("Host is shutting down")
jadmanski80deb752009-01-21 17:14:16 +0000667
mblighb49b5232009-02-12 21:54:49 +0000668
showardca572982009-09-18 21:20:01 +0000669 def verify_software(self):
670 super(AbstractSSHHost, self).verify_software()
jadmanskif6562912008-10-21 17:59:01 +0000671 try:
showardad812bf2009-10-20 23:49:56 +0000672 self.check_diskspace(autotest.Autotest.get_install_dir(self),
673 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
jadmanskif6562912008-10-21 17:59:01 +0000674 except error.AutoservHostError:
675 raise # only want to raise if it's a space issue
showardad812bf2009-10-20 23:49:56 +0000676 except autotest.AutodirNotFoundError:
showardca572982009-09-18 21:20:01 +0000677 # autotest dir may not exist, etc. ignore
678 logging.debug('autodir space check exception, this is probably '
679 'safe to ignore\n' + traceback.format_exc())
mblighefccc1b2010-01-11 19:08:42 +0000680
681
682 def close(self):
683 super(AbstractSSHHost, self).close()
Godofredo Contreras773179e2016-05-24 10:17:48 -0700684 self.rpc_server_tracker.disconnect_all()
mblighefccc1b2010-01-11 19:08:42 +0000685 self._cleanup_master_ssh()
Fang Deng3af66202013-08-16 15:19:25 -0700686 os.remove(self.known_hosts_file)
mblighefccc1b2010-01-11 19:08:42 +0000687
688
689 def _cleanup_master_ssh(self):
690 """
691 Release all resources (process, temporary directory) used by an active
692 master SSH connection.
693 """
694 # If a master SSH connection is running, kill it.
695 if self.master_ssh_job is not None:
Aviv Keshet46250752013-08-27 15:52:06 -0700696 logging.debug('Nuking master_ssh_job.')
mblighefccc1b2010-01-11 19:08:42 +0000697 utils.nuke_subprocess(self.master_ssh_job.sp)
698 self.master_ssh_job = None
699
700 # Remove the temporary directory for the master SSH socket.
701 if self.master_ssh_tempdir is not None:
Aviv Keshet46250752013-08-27 15:52:06 -0700702 logging.debug('Cleaning master_ssh_tempdir.')
mblighefccc1b2010-01-11 19:08:42 +0000703 self.master_ssh_tempdir.clean()
704 self.master_ssh_tempdir = None
705 self.master_ssh_option = ''
706
707
Aviv Keshet0749a822013-10-17 09:53:26 -0700708 def start_master_ssh(self, timeout=5):
mblighefccc1b2010-01-11 19:08:42 +0000709 """
710 Called whenever a slave SSH connection needs to be initiated (e.g., by
711 run, rsync, scp). If master SSH support is enabled and a master SSH
712 connection is not active already, start a new one in the background.
713 Also, cleanup any zombie master SSH connections (e.g., dead due to
714 reboot).
Aviv Keshet0749a822013-10-17 09:53:26 -0700715
716 timeout: timeout in seconds (default 5) to wait for master ssh
717 connection to be established. If timeout is reached, a
718 warning message is logged, but no other action is taken.
mblighefccc1b2010-01-11 19:08:42 +0000719 """
720 if not enable_master_ssh:
721 return
722
Simran Basi3b858a22015-03-17 16:23:24 -0700723 # Multiple processes might try in parallel to clean up the old master
724 # ssh connection and create a new one, therefore use a lock to protect
725 # against race conditions.
726 with self._lock:
727 # If a previously started master SSH connection is not running
728 # anymore, it needs to be cleaned up and then restarted.
729 if self.master_ssh_job is not None:
730 socket_path = os.path.join(self.master_ssh_tempdir.name,
731 'socket')
732 if (not os.path.exists(socket_path) or
733 self.master_ssh_job.sp.poll() is not None):
734 logging.info("Master ssh connection to %s is down.",
735 self.hostname)
736 self._cleanup_master_ssh()
mblighefccc1b2010-01-11 19:08:42 +0000737
Simran Basi3b858a22015-03-17 16:23:24 -0700738 # Start a new master SSH connection.
739 if self.master_ssh_job is None:
740 # Create a shared socket in a temp location.
741 self.master_ssh_tempdir = autotemp.tempdir(
742 unique_id='ssh-master')
743 self.master_ssh_option = ("-o ControlPath=%s/socket" %
744 self.master_ssh_tempdir.name)
mblighefccc1b2010-01-11 19:08:42 +0000745
Simran Basi3b858a22015-03-17 16:23:24 -0700746 # Start the master SSH connection in the background.
747 master_cmd = self.ssh_command(
748 options="-N -o ControlMaster=yes")
749 logging.info("Starting master ssh connection '%s'", master_cmd)
750 self.master_ssh_job = utils.BgJob(master_cmd,
751 nickname='master-ssh',
752 no_pipes=True)
753 # To prevent a race between the the master ssh connection
754 # startup and its first attempted use, wait for socket file to
755 # exist before returning.
756 end_time = time.time() + timeout
757 socket_file_path = os.path.join(self.master_ssh_tempdir.name,
758 'socket')
759 while time.time() < end_time:
760 if os.path.exists(socket_file_path):
761 break
762 time.sleep(.2)
763 else:
764 logging.info('Timed out waiting for master-ssh connection '
765 'to be established.')
mbligh0a883702010-04-21 01:58:34 +0000766
767
768 def clear_known_hosts(self):
769 """Clears out the temporary ssh known_hosts file.
770
771 This is useful if the test SSHes to the machine, then reinstalls it,
772 then SSHes to it again. It can be called after the reinstall to
773 reduce the spam in the logs.
774 """
775 logging.info("Clearing known hosts for host '%s', file '%s'.",
Fang Deng3af66202013-08-16 15:19:25 -0700776 self.hostname, self.known_hosts_file)
mbligh0a883702010-04-21 01:58:34 +0000777 # Clear out the file by opening it for writing and then closing.
Fang Deng3af66202013-08-16 15:19:25 -0700778 fh = open(self.known_hosts_file, "w")
mbligh0a883702010-04-21 01:58:34 +0000779 fh.close()
Prashanth B98509c72014-04-04 16:01:34 -0700780
781
782 def collect_logs(self, remote_src_dir, local_dest_dir, ignore_errors=True):
783 """Copy log directories from a host to a local directory.
784
785 @param remote_src_dir: A destination directory on the host.
786 @param local_dest_dir: A path to a local destination directory.
787 If it doesn't exist it will be created.
788 @param ignore_errors: If True, ignore exceptions.
789
790 @raises OSError: If there were problems creating the local_dest_dir and
791 ignore_errors is False.
792 @raises AutoservRunError, AutotestRunError: If something goes wrong
793 while copying the directories and ignore_errors is False.
794 """
795 locally_created_dest = False
796 if (not os.path.exists(local_dest_dir)
797 or not os.path.isdir(local_dest_dir)):
798 try:
799 os.makedirs(local_dest_dir)
800 locally_created_dest = True
801 except OSError as e:
802 logging.warning('Unable to collect logs from host '
803 '%s: %s', self.hostname, e)
804 if not ignore_errors:
805 raise
806 return
807 try:
808 self.get_file(
809 remote_src_dir, local_dest_dir, preserve_symlinks=True)
810 except (error.AutotestRunError, error.AutoservRunError,
811 error.AutoservSSHTimeout) as e:
812 logging.warning('Collection of %s to local dir %s from host %s '
813 'failed: %s', remote_src_dir, local_dest_dir,
814 self.hostname, e)
815 if locally_created_dest:
816 shutil.rmtree(local_dest_dir, ignore_errors=ignore_errors)
817 if not ignore_errors:
818 raise
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800819
820
xixuan6cf6d2f2016-01-29 15:29:00 -0800821 def create_ssh_tunnel(self, port, local_port):
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800822 """Create an ssh tunnel from local_port to port.
823
xixuan6cf6d2f2016-01-29 15:29:00 -0800824 This is used to forward a port securely through a tunnel process from
825 the server to the DUT for RPC server connection.
826
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800827 @param port: remote port on the host.
828 @param local_port: local forwarding port.
829
830 @return: the tunnel process.
831 """
832 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
833 ssh_cmd = self.make_ssh_command(opts=tunnel_options)
834 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
835 logging.debug('Full tunnel command: %s', tunnel_cmd)
xixuan6cf6d2f2016-01-29 15:29:00 -0800836 # Exec the ssh process directly here rather than using a shell.
837 # Using a shell leaves a dangling ssh process, because we deliver
838 # signals to the shell wrapping ssh, not the ssh process itself.
839 args = shlex.split(tunnel_cmd)
840 tunnel_proc = subprocess.Popen(args, close_fds=True)
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800841 logging.debug('Started ssh tunnel, local = %d'
842 ' remote = %d, pid = %d',
843 local_port, port, tunnel_proc.pid)
844 return tunnel_proc
Gilad Arnolda76bef02015-09-29 13:55:15 -0700845
846
xixuan6cf6d2f2016-01-29 15:29:00 -0800847 def disconnect_ssh_tunnel(self, tunnel_proc, port):
Roshan Pius58e5dd32015-10-16 15:16:42 -0700848 """
849 Disconnects a previously forwarded port from the server to the DUT for
850 RPC server connection.
851
xixuan6cf6d2f2016-01-29 15:29:00 -0800852 @param tunnel_proc: a tunnel process returned from |create_ssh_tunnel|.
853 @param port: remote port on the DUT, used in ADBHost.
Roshan Pius58e5dd32015-10-16 15:16:42 -0700854
855 """
856 if tunnel_proc.poll() is None:
857 tunnel_proc.terminate()
858 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
859 else:
860 logging.debug('Tunnel pid %d terminated early, status %d',
861 tunnel_proc.pid, tunnel_proc.returncode)
862
863
Gilad Arnolda76bef02015-09-29 13:55:15 -0700864 def get_os_type(self):
865 """Returns the host OS descriptor (to be implemented in subclasses).
866
867 @return A string describing the OS type.
868 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800869 raise NotImplementedError