blob: ee80d309a03d536dd6f94ea4616bf7e1f22197b2 [file] [log] [blame]
Dan Shid9be07a2017-08-18 09:51:45 -07001import os, time, socket, shutil, glob, logging, tempfile, re
xixuan6cf6d2f2016-01-29 15:29:00 -08002import shlex
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +08003import subprocess
4
Dan Shi4f8c0242017-07-07 15:34:49 -07005from autotest_lib.client.bin.result_tools import runner as result_tools_runner
Hidehiko Abe28422ed2017-06-21 10:50:44 +09006from autotest_lib.client.common_lib import error
Dan Shi9f92aa62017-07-27 17:07:05 -07007from autotest_lib.client.common_lib.cros.network import ping_runner
Hidehiko Abe28422ed2017-06-21 10:50:44 +09008from autotest_lib.client.common_lib.global_config import global_config
jadmanski31c49b72008-10-27 20:44:48 +00009from autotest_lib.server import utils, autotest
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080010from autotest_lib.server.hosts import host_info
mblighe8b93af2009-01-30 00:45:53 +000011from autotest_lib.server.hosts import remote
Roshan Pius58e5dd32015-10-16 15:16:42 -070012from autotest_lib.server.hosts import rpc_server_tracker
Hidehiko Abe28422ed2017-06-21 10:50:44 +090013from autotest_lib.server.hosts import ssh_multiplex
jadmanskica7da372008-10-21 16:26:52 +000014
Gwendal Grignou36b61702016-02-10 11:57:53 -080015# pylint: disable=C0111
jadmanskica7da372008-10-21 16:26:52 +000016
mblighb86bfa12010-02-12 20:22:21 +000017get_value = global_config.get_config_value
18enable_master_ssh = get_value('AUTOSERV', 'enable_master_ssh', type=bool,
19 default=False)
mblighefccc1b2010-01-11 19:08:42 +000020
Dan Shi9f92aa62017-07-27 17:07:05 -070021# Number of seconds to use the cached up status.
22_DEFAULT_UP_STATUS_EXPIRATION_SECONDS = 300
mblighefccc1b2010-01-11 19:08:42 +000023
Lutz Justen043e9c12017-10-27 12:40:47 +020024# Number of seconds to wait for the host to shut down in wait_down().
25_DEFAULT_WAIT_DOWN_TIME_SECONDS = 120
26
27# Timeout in seconds for a single call of get_boot_id() in wait_down().
28_DEFAULT_MAX_PING_TIMEOUT = 10
29
Fang Deng96667ca2013-08-01 17:46:18 -070030class AbstractSSHHost(remote.RemoteHost):
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 """
Simran Basi5ace6f22016-01-06 17:30:44 -080037 VERSION_PREFIX = ''
jadmanskica7da372008-10-21 16:26:52 +000038
Dean Liao4bd3e312017-11-13 17:32:17 +080039 def _initialize(self, hostname, user="root", port=22, password="",
40 is_client_install_supported=True, afe_host=None,
41 host_info_store=None, connection_pool=None,
Hidehiko Abe06893302017-06-24 07:32:38 +090042 *args, **dargs):
jadmanskif6562912008-10-21 17:59:01 +000043 super(AbstractSSHHost, self)._initialize(hostname=hostname,
44 *args, **dargs)
Kevin Cheng05ae2a42016-06-06 10:12:48 -070045 """
46 @param hostname: The hostname of the host.
47 @param user: The username to use when ssh'ing into the host.
48 @param password: The password to use when ssh'ing into the host.
49 @param port: The port to use for ssh.
50 @param is_client_install_supported: Boolean to indicate if we can
51 install autotest on the host.
52 @param afe_host: The host object attained from the AFE (get_hosts).
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080053 @param host_info_store: Optional host_info.CachingHostInfoStore object
54 to obtain / update host information.
Hidehiko Abe06893302017-06-24 07:32:38 +090055 @param connection_pool: ssh_multiplex.ConnectionPool instance to share
56 the master ssh connection across control scripts.
Kevin Cheng05ae2a42016-06-06 10:12:48 -070057 """
Dan Shic07b8932014-12-11 15:22:30 -080058 # IP address is retrieved only on demand. Otherwise the host
59 # initialization will fail for host is not online.
60 self._ip = None
jadmanskica7da372008-10-21 16:26:52 +000061 self.user = user
62 self.port = port
63 self.password = password
Roshan Piusa58163a2015-10-14 13:36:29 -070064 self._is_client_install_supported = is_client_install_supported
showard6eafb492010-01-15 20:29:06 +000065 self._use_rsync = None
Fang Deng3af66202013-08-16 15:19:25 -070066 self.known_hosts_file = tempfile.mkstemp()[1]
Roshan Pius58e5dd32015-10-16 15:16:42 -070067 self._rpc_server_tracker = rpc_server_tracker.RpcServerTracker(self);
jadmanskica7da372008-10-21 16:26:52 +000068
mblighefccc1b2010-01-11 19:08:42 +000069 """
70 Master SSH connection background job, socket temp directory and socket
71 control path option. If master-SSH is enabled, these fields will be
72 initialized by start_master_ssh when a new SSH connection is initiated.
73 """
Hidehiko Abe06893302017-06-24 07:32:38 +090074 self._connection_pool = connection_pool
75 if connection_pool:
76 self._master_ssh = connection_pool.get(hostname, user, port)
77 else:
78 self._master_ssh = ssh_multiplex.MasterSsh(hostname, user, port)
Simran Basi3b858a22015-03-17 16:23:24 -070079
Kevin Cheng05ae2a42016-06-06 10:12:48 -070080 self._afe_host = afe_host or utils.EmptyAFEHost()
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080081 self.host_info_store = (host_info_store or
82 host_info.InMemoryHostInfoStore())
showard6eafb492010-01-15 20:29:06 +000083
Dan Shi9f92aa62017-07-27 17:07:05 -070084 # The cached status of whether the DUT responded to ping.
85 self._cached_up_status = None
86 # The timestamp when the value of _cached_up_status is set.
87 self._cached_up_status_updated = None
88
89
Dan Shic07b8932014-12-11 15:22:30 -080090 @property
91 def ip(self):
92 """@return IP address of the host.
93 """
94 if not self._ip:
95 self._ip = socket.getaddrinfo(self.hostname, None)[0][4][0]
96 return self._ip
97
98
Roshan Piusa58163a2015-10-14 13:36:29 -070099 @property
100 def is_client_install_supported(self):
101 """"
102 Returns True if the host supports autotest client installs, False
103 otherwise.
104 """
105 return self._is_client_install_supported
106
107
Roshan Pius58e5dd32015-10-16 15:16:42 -0700108 @property
109 def rpc_server_tracker(self):
110 """"
111 @return The RPC server tracker associated with this host.
112 """
113 return self._rpc_server_tracker
114
115
Dean Liao4bd3e312017-11-13 17:32:17 +0800116 def make_ssh_command(self, user="root", port=22, opts='',
117 hosts_file='/dev/null',
118 connect_timeout=30, alive_interval=300):
119 base_command = ("/usr/bin/ssh -a -x %s -o StrictHostKeyChecking=no "
120 "-o UserKnownHostsFile=%s -o BatchMode=yes "
121 "-o ConnectTimeout=%d -o ServerAliveInterval=%d "
122 "-l %s -p %d")
Fang Deng96667ca2013-08-01 17:46:18 -0700123 assert isinstance(connect_timeout, (int, long))
124 assert connect_timeout > 0 # can't disable the timeout
Dean Liao4bd3e312017-11-13 17:32:17 +0800125 return base_command % (opts, hosts_file, connect_timeout,
126 alive_interval, user, port)
Fang Deng96667ca2013-08-01 17:46:18 -0700127
128
showard6eafb492010-01-15 20:29:06 +0000129 def use_rsync(self):
130 if self._use_rsync is not None:
131 return self._use_rsync
132
mblighc9892c02010-01-06 19:02:16 +0000133 # Check if rsync is available on the remote host. If it's not,
134 # don't try to use it for any future file transfers.
Gwendal Grignou03286f02017-03-24 10:50:59 -0700135 self._use_rsync = self.check_rsync()
showard6eafb492010-01-15 20:29:06 +0000136 if not self._use_rsync:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700137 logging.warning("rsync not available on remote host %s -- disabled",
Dean Liao4bd3e312017-11-13 17:32:17 +0800138 self.hostname)
Eric Lie0493a42010-11-15 13:05:43 -0800139 return self._use_rsync
mblighc9892c02010-01-06 19:02:16 +0000140
141
Gwendal Grignou03286f02017-03-24 10:50:59 -0700142 def check_rsync(self):
mblighc9892c02010-01-06 19:02:16 +0000143 """
144 Check if rsync is available on the remote host.
145 """
146 try:
Allen Liad719c12017-06-27 23:48:04 +0000147 self.run("rsync --version", stdout_tee=None, stderr_tee=None)
mblighc9892c02010-01-06 19:02:16 +0000148 except error.AutoservRunError:
149 return False
150 return True
151
jadmanskica7da372008-10-21 16:26:52 +0000152
Gwendal Grignou36b61702016-02-10 11:57:53 -0800153 def _encode_remote_paths(self, paths, escape=True, use_scp=False):
mblighbc9402b2009-12-29 01:15:34 +0000154 """
155 Given a list of file paths, encodes it as a single remote path, in
156 the style used by rsync and scp.
Gwendal Grignou36b61702016-02-10 11:57:53 -0800157 escape: add \\ to protect special characters.
158 use_scp: encode for scp if true, rsync if false.
mblighbc9402b2009-12-29 01:15:34 +0000159 """
showard56176ec2009-10-28 19:52:30 +0000160 if escape:
161 paths = [utils.scp_remote_escape(path) for path in paths]
Marc Herbert21eb6492015-11-13 15:48:53 -0800162
163 remote = self.hostname
164
165 # rsync and scp require IPv6 brackets, even when there isn't any
166 # trailing port number (ssh doesn't support IPv6 brackets).
167 # In the Python >= 3.3 future, 'import ipaddress' will parse addresses.
168 if re.search(r':.*:', remote):
169 remote = '[%s]' % remote
170
Gwendal Grignou36b61702016-02-10 11:57:53 -0800171 if use_scp:
172 return '%s@%s:"%s"' % (self.user, remote, " ".join(paths))
173 else:
174 return '%s@%s:%s' % (
175 self.user, remote,
176 " :".join('"%s"' % p for p in paths))
jadmanskica7da372008-10-21 16:26:52 +0000177
Gwendal Grignou36b61702016-02-10 11:57:53 -0800178 def _encode_local_paths(self, paths, escape=True):
179 """
180 Given a list of file paths, encodes it as a single local path.
181 escape: add \\ to protect special characters.
182 """
183 if escape:
184 paths = [utils.sh_escape(path) for path in paths]
185
186 return " ".join('"%s"' % p for p in paths)
jadmanskica7da372008-10-21 16:26:52 +0000187
Dean Liao4bd3e312017-11-13 17:32:17 +0800188 def _make_rsync_cmd(self, sources, dest, delete_dest,
189 preserve_symlinks, safe_symlinks, excludes=None):
190 """
191 Given a string of source paths and a destination path, produces the
192 appropriate rsync command for copying them. Remote paths must be
193 pre-encoded.
194 """
Fang Deng96667ca2013-08-01 17:46:18 -0700195 ssh_cmd = self.make_ssh_command(user=self.user, port=self.port,
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900196 opts=self._master_ssh.ssh_option,
Fang Deng96667ca2013-08-01 17:46:18 -0700197 hosts_file=self.known_hosts_file)
jadmanskid7b79ed2009-01-07 17:19:48 +0000198 if delete_dest:
199 delete_flag = "--delete"
200 else:
201 delete_flag = ""
Luigi Semenzato9b083072016-12-19 16:50:40 -0800202 if safe_symlinks:
203 symlink_flag = "-l --safe-links"
204 elif preserve_symlinks:
205 symlink_flag = "-l"
mbligh45561782009-05-11 21:14:34 +0000206 else:
207 symlink_flag = "-L"
Dan Shi92c34c92017-07-14 15:28:56 -0700208 exclude_args = ''
209 if excludes:
210 exclude_args = ' '.join(
211 ["--exclude '%s'" % exclude for exclude in excludes])
Dean Liao4bd3e312017-11-13 17:32:17 +0800212 command = ("rsync %s %s --timeout=1800 --rsh='%s' -az --no-o --no-g "
213 "%s %s \"%s\"")
214 return command % (symlink_flag, delete_flag, ssh_cmd, exclude_args,
215 sources, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000216
217
Eric Li861b2d52011-02-04 14:50:35 -0800218 def _make_ssh_cmd(self, cmd):
219 """
220 Create a base ssh command string for the host which can be used
221 to run commands directly on the machine
222 """
Fang Deng96667ca2013-08-01 17:46:18 -0700223 base_cmd = self.make_ssh_command(user=self.user, port=self.port,
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900224 opts=self._master_ssh.ssh_option,
Fang Deng96667ca2013-08-01 17:46:18 -0700225 hosts_file=self.known_hosts_file)
Eric Li861b2d52011-02-04 14:50:35 -0800226
227 return '%s %s "%s"' % (base_cmd, self.hostname, utils.sh_escape(cmd))
228
jadmanskid7b79ed2009-01-07 17:19:48 +0000229 def _make_scp_cmd(self, sources, dest):
mblighbc9402b2009-12-29 01:15:34 +0000230 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800231 Given a string of source paths and a destination path, produces the
jadmanskid7b79ed2009-01-07 17:19:48 +0000232 appropriate scp command for encoding it. Remote paths must be
mblighbc9402b2009-12-29 01:15:34 +0000233 pre-encoded.
234 """
mblighc0649d62010-01-15 18:15:58 +0000235 command = ("scp -rq %s -o StrictHostKeyChecking=no "
lmraf676f32010-02-04 03:36:26 +0000236 "-o UserKnownHostsFile=%s -P %d %s '%s'")
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900237 return command % (self._master_ssh.ssh_option, self.known_hosts_file,
Gwendal Grignou36b61702016-02-10 11:57:53 -0800238 self.port, sources, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000239
240
241 def _make_rsync_compatible_globs(self, path, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000242 """
243 Given an rsync-style path, returns a list of globbed paths
jadmanskid7b79ed2009-01-07 17:19:48 +0000244 that will hopefully provide equivalent behaviour for scp. Does not
245 support the full range of rsync pattern matching behaviour, only that
246 exposed in the get/send_file interface (trailing slashes).
247
248 The is_local param is flag indicating if the paths should be
mblighbc9402b2009-12-29 01:15:34 +0000249 interpreted as local or remote paths.
250 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000251
252 # non-trailing slash paths should just work
253 if len(path) == 0 or path[-1] != "/":
254 return [path]
255
256 # make a function to test if a pattern matches any files
257 if is_local:
showard56176ec2009-10-28 19:52:30 +0000258 def glob_matches_files(path, pattern):
259 return len(glob.glob(path + pattern)) > 0
jadmanskid7b79ed2009-01-07 17:19:48 +0000260 else:
showard56176ec2009-10-28 19:52:30 +0000261 def glob_matches_files(path, pattern):
262 result = self.run("ls \"%s\"%s" % (utils.sh_escape(path),
263 pattern),
264 stdout_tee=None, ignore_status=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000265 return result.exit_status == 0
266
267 # take a set of globs that cover all files, and see which are needed
268 patterns = ["*", ".[!.]*"]
showard56176ec2009-10-28 19:52:30 +0000269 patterns = [p for p in patterns if glob_matches_files(path, p)]
jadmanskid7b79ed2009-01-07 17:19:48 +0000270
271 # convert them into a set of paths suitable for the commandline
jadmanskid7b79ed2009-01-07 17:19:48 +0000272 if is_local:
showard56176ec2009-10-28 19:52:30 +0000273 return ["\"%s\"%s" % (utils.sh_escape(path), pattern)
274 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000275 else:
showard56176ec2009-10-28 19:52:30 +0000276 return [utils.scp_remote_escape(path) + pattern
277 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000278
279
280 def _make_rsync_compatible_source(self, source, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000281 """
282 Applies the same logic as _make_rsync_compatible_globs, but
jadmanskid7b79ed2009-01-07 17:19:48 +0000283 applies it to an entire list of sources, producing a new list of
mblighbc9402b2009-12-29 01:15:34 +0000284 sources, properly quoted.
285 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000286 return sum((self._make_rsync_compatible_globs(path, is_local)
287 for path in source), [])
jadmanskica7da372008-10-21 16:26:52 +0000288
289
mblighfeac0102009-04-28 18:31:12 +0000290 def _set_umask_perms(self, dest):
mblighbc9402b2009-12-29 01:15:34 +0000291 """
292 Given a destination file/dir (recursively) set the permissions on
293 all the files and directories to the max allowed by running umask.
294 """
mblighfeac0102009-04-28 18:31:12 +0000295
296 # now this looks strange but I haven't found a way in Python to _just_
297 # get the umask, apparently the only option is to try to set it
298 umask = os.umask(0)
299 os.umask(umask)
300
301 max_privs = 0777 & ~umask
302
303 def set_file_privs(filename):
Chris Masone567d0d92011-12-19 09:38:30 -0800304 """Sets mode of |filename|. Assumes |filename| exists."""
305 file_stat = os.stat(filename)
mblighfeac0102009-04-28 18:31:12 +0000306
307 file_privs = max_privs
308 # if the original file permissions do not have at least one
309 # executable bit then do not set it anywhere
310 if not file_stat.st_mode & 0111:
311 file_privs &= ~0111
312
313 os.chmod(filename, file_privs)
314
315 # try a bottom-up walk so changes on directory permissions won't cut
316 # our access to the files/directories inside it
317 for root, dirs, files in os.walk(dest, topdown=False):
318 # when setting the privileges we emulate the chmod "X" behaviour
319 # that sets to execute only if it is a directory or any of the
320 # owner/group/other already has execute right
321 for dirname in dirs:
322 os.chmod(os.path.join(root, dirname), max_privs)
323
Chris Masone567d0d92011-12-19 09:38:30 -0800324 # Filter out broken symlinks as we go.
325 for filename in filter(os.path.exists, files):
mblighfeac0102009-04-28 18:31:12 +0000326 set_file_privs(os.path.join(root, filename))
327
328
329 # now set privs for the dest itself
330 if os.path.isdir(dest):
331 os.chmod(dest, max_privs)
332 else:
333 set_file_privs(dest)
334
335
mbligh45561782009-05-11 21:14:34 +0000336 def get_file(self, source, dest, delete_dest=False, preserve_perm=True,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800337 preserve_symlinks=False, retry=True, safe_symlinks=False):
jadmanskica7da372008-10-21 16:26:52 +0000338 """
339 Copy files from the remote host to a local path.
340
341 Directories will be copied recursively.
342 If a source component is a directory with a trailing slash,
343 the content of the directory will be copied, otherwise, the
344 directory itself and its content will be copied. This
345 behavior is similar to that of the program 'rsync'.
346
347 Args:
348 source: either
349 1) a single file or directory, as a string
350 2) a list of one or more (possibly mixed)
351 files or directories
352 dest: a file or a directory (if source contains a
353 directory or more than one element, you must
354 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000355 delete_dest: if this is true, the command will also clear
356 out any old files at dest that are not in the
357 source
mblighfeac0102009-04-28 18:31:12 +0000358 preserve_perm: tells get_file() to try to preserve the sources
359 permissions on files and dirs
mbligh45561782009-05-11 21:14:34 +0000360 preserve_symlinks: try to preserve symlinks instead of
361 transforming them into files/dirs on copy
Luigi Semenzato9b083072016-12-19 16:50:40 -0800362 safe_symlinks: same as preserve_symlinks, but discard links
363 that may point outside the copied tree
jadmanskica7da372008-10-21 16:26:52 +0000364 Raises:
365 AutoservRunError: the scp command failed
366 """
Simran Basi882f15b2013-10-29 14:59:34 -0700367 logging.debug('get_file. source: %s, dest: %s, delete_dest: %s,'
368 'preserve_perm: %s, preserve_symlinks:%s', source, dest,
369 delete_dest, preserve_perm, preserve_symlinks)
Dan Shi4f8c0242017-07-07 15:34:49 -0700370
mblighefccc1b2010-01-11 19:08:42 +0000371 # Start a master SSH connection if necessary.
372 self.start_master_ssh()
373
jadmanskica7da372008-10-21 16:26:52 +0000374 if isinstance(source, basestring):
375 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000376 dest = os.path.abspath(dest)
jadmanskica7da372008-10-21 16:26:52 +0000377
mblighc9892c02010-01-06 19:02:16 +0000378 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000379 try_scp = True
380 if self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700381 logging.debug('Using Rsync.')
mblighc9892c02010-01-06 19:02:16 +0000382 try:
383 remote_source = self._encode_remote_paths(source)
384 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800385 rsync = self._make_rsync_cmd(remote_source, local_dest,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800386 delete_dest, preserve_symlinks,
387 safe_symlinks)
mblighc9892c02010-01-06 19:02:16 +0000388 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000389 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000390 except error.CmdError, e:
Luigi Semenzato7f9dff12016-11-21 14:01:20 -0800391 # retry on rsync exit values which may be caused by transient
392 # network problems:
393 #
394 # rc 10: Error in socket I/O
395 # rc 12: Error in rsync protocol data stream
396 # rc 23: Partial transfer due to error
397 # rc 255: Ssh error
398 #
399 # Note that rc 23 includes dangling symlinks. In this case
400 # retrying is useless, but not very damaging since rsync checks
401 # for those before starting the transfer (scp does not).
402 status = e.result_obj.exit_status
403 if status in [10, 12, 23, 255] and retry:
404 logging.warning('rsync status %d, retrying', status)
405 self.get_file(source, dest, delete_dest, preserve_perm,
406 preserve_symlinks, retry=False)
407 # The nested get_file() does all that's needed.
408 return
409 else:
410 logging.warning("trying scp, rsync failed: %s (%d)",
411 e, status)
mblighc9892c02010-01-06 19:02:16 +0000412
413 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700414 logging.debug('Trying scp.')
jadmanskid7b79ed2009-01-07 17:19:48 +0000415 # scp has no equivalent to --delete, just drop the entire dest dir
416 if delete_dest and os.path.isdir(dest):
417 shutil.rmtree(dest)
418 os.mkdir(dest)
jadmanskica7da372008-10-21 16:26:52 +0000419
jadmanskid7b79ed2009-01-07 17:19:48 +0000420 remote_source = self._make_rsync_compatible_source(source, False)
421 if remote_source:
showard56176ec2009-10-28 19:52:30 +0000422 # _make_rsync_compatible_source() already did the escaping
Gwendal Grignou36b61702016-02-10 11:57:53 -0800423 remote_source = self._encode_remote_paths(
424 remote_source, escape=False, use_scp=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000425 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800426 scp = self._make_scp_cmd(remote_source, local_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000427 try:
428 utils.run(scp)
429 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700430 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000431 raise error.AutoservRunError(e.args[0], e.args[1])
jadmanskica7da372008-10-21 16:26:52 +0000432
mblighfeac0102009-04-28 18:31:12 +0000433 if not preserve_perm:
434 # we have no way to tell scp to not try to preserve the
435 # permissions so set them after copy instead.
436 # for rsync we could use "--no-p --chmod=ugo=rwX" but those
437 # options are only in very recent rsync versions
438 self._set_umask_perms(dest)
439
jadmanskica7da372008-10-21 16:26:52 +0000440
mbligh45561782009-05-11 21:14:34 +0000441 def send_file(self, source, dest, delete_dest=False,
Dan Shi92c34c92017-07-14 15:28:56 -0700442 preserve_symlinks=False, excludes=None):
jadmanskica7da372008-10-21 16:26:52 +0000443 """
444 Copy files from a local path to the remote host.
445
446 Directories will be copied recursively.
447 If a source component is a directory with a trailing slash,
448 the content of the directory will be copied, otherwise, the
449 directory itself and its content will be copied. This
450 behavior is similar to that of the program 'rsync'.
451
452 Args:
453 source: either
454 1) a single file or directory, as a string
455 2) a list of one or more (possibly mixed)
456 files or directories
457 dest: a file or a directory (if source contains a
458 directory or more than one element, you must
459 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000460 delete_dest: if this is true, the command will also clear
461 out any old files at dest that are not in the
462 source
mbligh45561782009-05-11 21:14:34 +0000463 preserve_symlinks: controls if symlinks on the source will be
464 copied as such on the destination or transformed into the
465 referenced file/directory
Dan Shi92c34c92017-07-14 15:28:56 -0700466 excludes: A list of file pattern that matches files not to be
467 sent. `send_file` will fail if exclude is set, since
468 local copy does not support --exclude, e.g., when
469 using scp to copy file.
jadmanskica7da372008-10-21 16:26:52 +0000470
471 Raises:
472 AutoservRunError: the scp command failed
473 """
Simran Basi882f15b2013-10-29 14:59:34 -0700474 logging.debug('send_file. source: %s, dest: %s, delete_dest: %s,'
475 'preserve_symlinks:%s', source, dest,
476 delete_dest, preserve_symlinks)
mblighefccc1b2010-01-11 19:08:42 +0000477 # Start a master SSH connection if necessary.
478 self.start_master_ssh()
479
jadmanskica7da372008-10-21 16:26:52 +0000480 if isinstance(source, basestring):
481 source = [source]
482
Gwendal Grignou36b61702016-02-10 11:57:53 -0800483 local_sources = self._encode_local_paths(source)
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700484 if not local_sources:
Gwendal Grignou36b61702016-02-10 11:57:53 -0800485 raise error.TestError('source |%s| yielded an empty string' % (
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700486 source))
Gwendal Grignou36b61702016-02-10 11:57:53 -0800487 if local_sources.find('\x00') != -1:
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700488 raise error.TestError('one or more sources include NUL char')
489
mblighc9892c02010-01-06 19:02:16 +0000490 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000491 try_scp = True
492 if self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700493 logging.debug('Using Rsync.')
Gwendal Grignou36b61702016-02-10 11:57:53 -0800494 remote_dest = self._encode_remote_paths([dest])
mblighc9892c02010-01-06 19:02:16 +0000495 try:
mblighc9892c02010-01-06 19:02:16 +0000496 rsync = self._make_rsync_cmd(local_sources, remote_dest,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800497 delete_dest, preserve_symlinks,
Dan Shi92c34c92017-07-14 15:28:56 -0700498 False, excludes=excludes)
mblighc9892c02010-01-06 19:02:16 +0000499 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000500 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000501 except error.CmdError, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700502 logging.warning("trying scp, rsync failed: %s", e)
mblighc9892c02010-01-06 19:02:16 +0000503
504 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700505 logging.debug('Trying scp.')
Dan Shi92c34c92017-07-14 15:28:56 -0700506 if excludes:
507 raise error.AutotestHostRunError(
508 '--exclude is not supported in scp, try to use rsync. '
Dan Shid9be07a2017-08-18 09:51:45 -0700509 'excludes: %s' % ','.join(excludes))
jadmanskid7b79ed2009-01-07 17:19:48 +0000510 # scp has no equivalent to --delete, just drop the entire dest dir
511 if delete_dest:
showard27160152009-07-15 14:28:42 +0000512 is_dir = self.run("ls -d %s/" % dest,
jadmanskid7b79ed2009-01-07 17:19:48 +0000513 ignore_status=True).exit_status == 0
514 if is_dir:
515 cmd = "rm -rf %s && mkdir %s"
mbligh5a0ca532009-08-03 16:44:34 +0000516 cmd %= (dest, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000517 self.run(cmd)
jadmanskica7da372008-10-21 16:26:52 +0000518
Gwendal Grignou36b61702016-02-10 11:57:53 -0800519 remote_dest = self._encode_remote_paths([dest], use_scp=True)
jadmanski2583a432009-02-10 23:59:11 +0000520 local_sources = self._make_rsync_compatible_source(source, True)
521 if local_sources:
Cheng-Yi Chiang9b2812d2016-02-29 17:01:44 +0800522 sources = self._encode_local_paths(local_sources, escape=False)
523 scp = self._make_scp_cmd(sources, remote_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000524 try:
525 utils.run(scp)
526 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700527 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000528 raise error.AutoservRunError(e.args[0], e.args[1])
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700529 else:
530 logging.debug('skipping scp for empty source list')
jadmanskid7b79ed2009-01-07 17:19:48 +0000531
jadmanskica7da372008-10-21 16:26:52 +0000532
Simran Basi1621c632015-10-14 12:22:23 -0700533 def verify_ssh_user_access(self):
534 """Verify ssh access to this host.
535
536 @returns False if ssh_ping fails due to Permissions error, True
537 otherwise.
538 """
539 try:
540 self.ssh_ping()
541 except (error.AutoservSshPermissionDeniedError,
542 error.AutoservSshPingHostError):
543 return False
544 return True
545
546
Luigi Semenzato135574c2016-08-31 17:25:08 -0700547 def ssh_ping(self, timeout=60, connect_timeout=None, base_cmd='true'):
beepsadd66d32013-03-04 17:21:51 -0800548 """
549 Pings remote host via ssh.
550
551 @param timeout: Time in seconds before giving up.
552 Defaults to 60 seconds.
beeps46dadc92013-11-07 14:07:10 -0800553 @param base_cmd: The base command to run with the ssh ping.
554 Defaults to true.
beepsadd66d32013-03-04 17:21:51 -0800555 @raise AutoservSSHTimeout: If the ssh ping times out.
556 @raise AutoservSshPermissionDeniedError: If ssh ping fails due to
557 permissions.
558 @raise AutoservSshPingHostError: For other AutoservRunErrors.
559 """
Luigi Semenzato135574c2016-08-31 17:25:08 -0700560 ctimeout = min(timeout, connect_timeout or timeout)
jadmanskica7da372008-10-21 16:26:52 +0000561 try:
Allen Liad719c12017-06-27 23:48:04 +0000562 self.run(base_cmd, timeout=timeout, connect_timeout=ctimeout,
563 ssh_failure_retry_ok=True)
jadmanskica7da372008-10-21 16:26:52 +0000564 except error.AutoservSSHTimeout:
mblighd0e94982009-07-11 00:15:18 +0000565 msg = "Host (ssh) verify timed out (timeout = %d)" % timeout
jadmanskica7da372008-10-21 16:26:52 +0000566 raise error.AutoservSSHTimeout(msg)
mbligh9d738d62009-03-09 21:17:10 +0000567 except error.AutoservSshPermissionDeniedError:
Allen Liad719c12017-06-27 23:48:04 +0000568 #let AutoservSshPermissionDeniedError be visible to the callers
mbligh9d738d62009-03-09 21:17:10 +0000569 raise
jadmanskica7da372008-10-21 16:26:52 +0000570 except error.AutoservRunError, e:
mblighc971c5f2009-06-08 16:48:54 +0000571 # convert the generic AutoservRunError into something more
572 # specific for this context
573 raise error.AutoservSshPingHostError(e.description + '\n' +
574 repr(e.result_obj))
jadmanskica7da372008-10-21 16:26:52 +0000575
576
Luigi Semenzato135574c2016-08-31 17:25:08 -0700577 def is_up(self, timeout=60, connect_timeout=None, base_cmd='true'):
jadmanskica7da372008-10-21 16:26:52 +0000578 """
beeps46dadc92013-11-07 14:07:10 -0800579 Check if the remote host is up by ssh-ing and running a base command.
jadmanskica7da372008-10-21 16:26:52 +0000580
beepsadd66d32013-03-04 17:21:51 -0800581 @param timeout: timeout in seconds.
beeps46dadc92013-11-07 14:07:10 -0800582 @param base_cmd: a base command to run with ssh. The default is 'true'.
beepsadd66d32013-03-04 17:21:51 -0800583 @returns True if the remote host is up before the timeout expires,
584 False otherwise.
jadmanskica7da372008-10-21 16:26:52 +0000585 """
586 try:
Luigi Semenzato135574c2016-08-31 17:25:08 -0700587 self.ssh_ping(timeout=timeout,
588 connect_timeout=connect_timeout,
589 base_cmd=base_cmd)
jadmanskica7da372008-10-21 16:26:52 +0000590 except error.AutoservError:
591 return False
592 else:
593 return True
594
595
Dan Shi9f92aa62017-07-27 17:07:05 -0700596 def is_up_fast(self):
597 """Return True if the host can be pinged."""
598 ping_config = ping_runner.PingConfig(
599 self.hostname, count=3, ignore_result=True, ignore_status=True)
600 return ping_runner.PingRunner().ping(ping_config).received > 0
601
602
jadmanskica7da372008-10-21 16:26:52 +0000603 def wait_up(self, timeout=None):
604 """
605 Wait until the remote host is up or the timeout expires.
606
607 In fact, it will wait until an ssh connection to the remote
608 host can be established, and getty is running.
609
jadmanskic0354912010-01-12 15:57:29 +0000610 @param timeout time limit in seconds before returning even
611 if the host is not up.
jadmanskica7da372008-10-21 16:26:52 +0000612
beepsadd66d32013-03-04 17:21:51 -0800613 @returns True if the host was found to be up before the timeout expires,
614 False otherwise
jadmanskica7da372008-10-21 16:26:52 +0000615 """
616 if timeout:
beeps46dadc92013-11-07 14:07:10 -0800617 current_time = int(time.time())
618 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000619
Luigi Semenzato135574c2016-08-31 17:25:08 -0700620 autoserv_error_logged = False
beepsadd66d32013-03-04 17:21:51 -0800621 while not timeout or current_time < end_time:
Luigi Semenzato135574c2016-08-31 17:25:08 -0700622 if self.is_up(timeout=end_time - current_time,
623 connect_timeout=20):
jadmanskica7da372008-10-21 16:26:52 +0000624 try:
625 if self.are_wait_up_processes_up():
Dean Liao4bd3e312017-11-13 17:32:17 +0800626 logging.debug('Host %s is now up', self.hostname)
jadmanskica7da372008-10-21 16:26:52 +0000627 return True
Luigi Semenzato135574c2016-08-31 17:25:08 -0700628 except error.AutoservError as e:
629 if not autoserv_error_logged:
630 logging.debug('Ignoring failure to reach %s: %s %s',
Dean Liao4bd3e312017-11-13 17:32:17 +0800631 self.hostname, e,
Luigi Semenzato135574c2016-08-31 17:25:08 -0700632 '(and further similar failures)')
633 autoserv_error_logged = True
jadmanskica7da372008-10-21 16:26:52 +0000634 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800635 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000636
jadmanski7ebac3d2010-06-17 16:06:31 +0000637 logging.debug('Host %s is still down after waiting %d seconds',
Dean Liao4bd3e312017-11-13 17:32:17 +0800638 self.hostname, int(timeout + time.time() - end_time))
jadmanskica7da372008-10-21 16:26:52 +0000639 return False
640
641
Lutz Justen043e9c12017-10-27 12:40:47 +0200642 def wait_down(self, timeout=_DEFAULT_WAIT_DOWN_TIME_SECONDS,
643 warning_timer=None, old_boot_id=None,
644 max_ping_timeout=_DEFAULT_MAX_PING_TIMEOUT):
jadmanskica7da372008-10-21 16:26:52 +0000645 """
646 Wait until the remote host is down or the timeout expires.
647
Lutz Justen043e9c12017-10-27 12:40:47 +0200648 If old_boot_id is provided, waits until either the machine is
649 unpingable or self.get_boot_id() returns a value different from
jadmanskic0354912010-01-12 15:57:29 +0000650 old_boot_id. If the boot_id value has changed then the function
Lutz Justen043e9c12017-10-27 12:40:47 +0200651 returns True under the assumption that the machine has shut down
jadmanskic0354912010-01-12 15:57:29 +0000652 and has now already come back up.
jadmanskica7da372008-10-21 16:26:52 +0000653
jadmanskic0354912010-01-12 15:57:29 +0000654 If old_boot_id is None then until the machine becomes unreachable the
655 method assumes the machine has not yet shut down.
jadmanskica7da372008-10-21 16:26:52 +0000656
Lutz Justen043e9c12017-10-27 12:40:47 +0200657 @param timeout Time limit in seconds before returning even if the host
658 is still up.
659 @param warning_timer Time limit in seconds that will generate a warning
660 if the host is not down yet. Can be None for no warning.
jadmanskic0354912010-01-12 15:57:29 +0000661 @param old_boot_id A string containing the result of self.get_boot_id()
662 prior to the host being told to shut down. Can be None if this is
663 not available.
Lutz Justen043e9c12017-10-27 12:40:47 +0200664 @param max_ping_timeout Maximum timeout in seconds for each
665 self.get_boot_id() call. If this timeout is hit, it is assumed that
666 the host went down and became unreachable.
jadmanskic0354912010-01-12 15:57:29 +0000667
Lutz Justen043e9c12017-10-27 12:40:47 +0200668 @returns True if the host was found to be down (max_ping_timeout timeout
669 expired or boot_id changed if provided) and False if timeout
670 expired.
jadmanskica7da372008-10-21 16:26:52 +0000671 """
mblighe5e3cf22010-05-27 23:33:14 +0000672 #TODO: there is currently no way to distinguish between knowing
673 #TODO: boot_id was unsupported and not knowing the boot_id.
beeps46dadc92013-11-07 14:07:10 -0800674 current_time = int(time.time())
Lutz Justen043e9c12017-10-27 12:40:47 +0200675 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000676
mbligh2ed998f2009-04-08 21:03:47 +0000677 if warning_timer:
678 warn_time = current_time + warning_timer
679
jadmanskic0354912010-01-12 15:57:29 +0000680 if old_boot_id is not None:
681 logging.debug('Host %s pre-shutdown boot_id is %s',
Dean Liao4bd3e312017-11-13 17:32:17 +0800682 self.hostname, old_boot_id)
jadmanskic0354912010-01-12 15:57:29 +0000683
beepsadd66d32013-03-04 17:21:51 -0800684 # Impose semi real-time deadline constraints, since some clients
685 # (eg: watchdog timer tests) expect strict checking of time elapsed.
686 # Each iteration of this loop is treated as though it atomically
687 # completes within current_time, this is needed because if we used
688 # inline time.time() calls instead then the following could happen:
689 #
Lutz Justen043e9c12017-10-27 12:40:47 +0200690 # while time.time() < end_time: [23 < 30]
beepsadd66d32013-03-04 17:21:51 -0800691 # some code. [takes 10 secs]
692 # try:
693 # new_boot_id = self.get_boot_id(timeout=end_time - time.time())
694 # [30 - 33]
695 # The last step will lead to a return True, when in fact the machine
696 # went down at 32 seconds (>30). Hence we need to pass get_boot_id
697 # the same time that allowed us into that iteration of the loop.
Lutz Justen043e9c12017-10-27 12:40:47 +0200698 while current_time < end_time:
699 ping_timeout = min(end_time - current_time, max_ping_timeout)
jadmanskic0354912010-01-12 15:57:29 +0000700 try:
Lutz Justen043e9c12017-10-27 12:40:47 +0200701 new_boot_id = self.get_boot_id(timeout=ping_timeout)
mblighdbc7e4a2010-01-15 20:34:20 +0000702 except error.AutoservError:
jadmanskic0354912010-01-12 15:57:29 +0000703 logging.debug('Host %s is now unreachable over ssh, is down',
Dean Liao4bd3e312017-11-13 17:32:17 +0800704 self.hostname)
jadmanskica7da372008-10-21 16:26:52 +0000705 return True
jadmanskic0354912010-01-12 15:57:29 +0000706 else:
707 # if the machine is up but the boot_id value has changed from
708 # old boot id, then we can assume the machine has gone down
709 # and then already come back up
710 if old_boot_id is not None and old_boot_id != new_boot_id:
711 logging.debug('Host %s now has boot_id %s and so must '
Dean Liao4bd3e312017-11-13 17:32:17 +0800712 'have rebooted', self.hostname, new_boot_id)
jadmanskic0354912010-01-12 15:57:29 +0000713 return True
mbligh2ed998f2009-04-08 21:03:47 +0000714
715 if warning_timer and current_time > warn_time:
Scott Zawalskic86fdeb2013-10-23 10:24:04 -0400716 self.record("INFO", None, "shutdown",
mbligh2ed998f2009-04-08 21:03:47 +0000717 "Shutdown took longer than %ds" % warning_timer)
718 # Print the warning only once.
719 warning_timer = None
mbligha4464402009-04-17 20:13:41 +0000720 # If a machine is stuck switching runlevels
721 # This may cause the machine to reboot.
722 self.run('kill -HUP 1', ignore_status=True)
mbligh2ed998f2009-04-08 21:03:47 +0000723
jadmanskica7da372008-10-21 16:26:52 +0000724 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800725 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000726
727 return False
jadmanskif6562912008-10-21 17:59:01 +0000728
mbligha0a27592009-01-24 01:41:36 +0000729
jadmanskif6562912008-10-21 17:59:01 +0000730 # tunable constants for the verify & repair code
mblighb86bfa12010-02-12 20:22:21 +0000731 AUTOTEST_GB_DISKSPACE_REQUIRED = get_value("SERVER",
732 "gb_diskspace_required",
Fang Deng6b05f5b2013-03-20 13:42:11 -0700733 type=float,
734 default=20.0)
mbligha0a27592009-01-24 01:41:36 +0000735
jadmanskif6562912008-10-21 17:59:01 +0000736
showardca572982009-09-18 21:20:01 +0000737 def verify_connectivity(self):
738 super(AbstractSSHHost, self).verify_connectivity()
jadmanskif6562912008-10-21 17:59:01 +0000739
Dean Liao4bd3e312017-11-13 17:32:17 +0800740 logging.info('Pinging host ' + self.hostname)
jadmanskif6562912008-10-21 17:59:01 +0000741 self.ssh_ping()
Dean Liao4bd3e312017-11-13 17:32:17 +0800742 logging.info("Host (ssh) %s is alive", self.hostname)
jadmanskif6562912008-10-21 17:59:01 +0000743
jadmanski80deb752009-01-21 17:14:16 +0000744 if self.is_shutting_down():
mblighc971c5f2009-06-08 16:48:54 +0000745 raise error.AutoservHostIsShuttingDownError("Host is shutting down")
jadmanski80deb752009-01-21 17:14:16 +0000746
mblighb49b5232009-02-12 21:54:49 +0000747
showardca572982009-09-18 21:20:01 +0000748 def verify_software(self):
749 super(AbstractSSHHost, self).verify_software()
jadmanskif6562912008-10-21 17:59:01 +0000750 try:
showardad812bf2009-10-20 23:49:56 +0000751 self.check_diskspace(autotest.Autotest.get_install_dir(self),
752 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
Keith Haddow07f1d3e2017-08-03 17:40:41 -0700753 except error.AutoservDiskFullHostError:
754 # only want to raise if it's a space issue
755 raise
756 except (error.AutoservHostError, autotest.AutodirNotFoundError):
Lutz Justen043e9c12017-10-27 12:40:47 +0200757 logging.exception('autodir space check exception, this is probably '
Keith Haddow07f1d3e2017-08-03 17:40:41 -0700758 'safe to ignore\n')
mblighefccc1b2010-01-11 19:08:42 +0000759
760
761 def close(self):
762 super(AbstractSSHHost, self).close()
Godofredo Contreras773179e2016-05-24 10:17:48 -0700763 self.rpc_server_tracker.disconnect_all()
Hidehiko Abe06893302017-06-24 07:32:38 +0900764 if not self._connection_pool:
765 self._master_ssh.close()
xixuand6011f12016-12-08 15:01:58 -0800766 if os.path.exists(self.known_hosts_file):
767 os.remove(self.known_hosts_file)
mblighefccc1b2010-01-11 19:08:42 +0000768
769
Luigi Semenzato3b95ede2016-12-09 11:51:01 -0800770 def restart_master_ssh(self):
771 """
772 Stop and restart the ssh master connection. This is meant as a last
773 resort when ssh commands fail and we don't understand why.
774 """
775 logging.debug('Restarting master ssh connection')
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900776 self._master_ssh.close()
777 self._master_ssh.maybe_start(timeout=30)
Luigi Semenzato3b95ede2016-12-09 11:51:01 -0800778
779
mblighefccc1b2010-01-11 19:08:42 +0000780
Aviv Keshet0749a822013-10-17 09:53:26 -0700781 def start_master_ssh(self, timeout=5):
mblighefccc1b2010-01-11 19:08:42 +0000782 """
783 Called whenever a slave SSH connection needs to be initiated (e.g., by
784 run, rsync, scp). If master SSH support is enabled and a master SSH
785 connection is not active already, start a new one in the background.
786 Also, cleanup any zombie master SSH connections (e.g., dead due to
787 reboot).
Aviv Keshet0749a822013-10-17 09:53:26 -0700788
789 timeout: timeout in seconds (default 5) to wait for master ssh
790 connection to be established. If timeout is reached, a
791 warning message is logged, but no other action is taken.
mblighefccc1b2010-01-11 19:08:42 +0000792 """
793 if not enable_master_ssh:
794 return
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900795 self._master_ssh.maybe_start(timeout=timeout)
mbligh0a883702010-04-21 01:58:34 +0000796
797
798 def clear_known_hosts(self):
799 """Clears out the temporary ssh known_hosts file.
800
801 This is useful if the test SSHes to the machine, then reinstalls it,
802 then SSHes to it again. It can be called after the reinstall to
803 reduce the spam in the logs.
804 """
805 logging.info("Clearing known hosts for host '%s', file '%s'.",
Dean Liao4bd3e312017-11-13 17:32:17 +0800806 self.hostname, self.known_hosts_file)
mbligh0a883702010-04-21 01:58:34 +0000807 # Clear out the file by opening it for writing and then closing.
Fang Deng3af66202013-08-16 15:19:25 -0700808 fh = open(self.known_hosts_file, "w")
mbligh0a883702010-04-21 01:58:34 +0000809 fh.close()
Prashanth B98509c72014-04-04 16:01:34 -0700810
811
812 def collect_logs(self, remote_src_dir, local_dest_dir, ignore_errors=True):
813 """Copy log directories from a host to a local directory.
814
815 @param remote_src_dir: A destination directory on the host.
816 @param local_dest_dir: A path to a local destination directory.
817 If it doesn't exist it will be created.
818 @param ignore_errors: If True, ignore exceptions.
819
820 @raises OSError: If there were problems creating the local_dest_dir and
821 ignore_errors is False.
822 @raises AutoservRunError, AutotestRunError: If something goes wrong
823 while copying the directories and ignore_errors is False.
824 """
Dan Shi9f92aa62017-07-27 17:07:05 -0700825 if not self.check_cached_up_status():
826 logging.warning('Host %s did not answer to ping, skip collecting '
Dean Liao4bd3e312017-11-13 17:32:17 +0800827 'logs.', self.hostname)
Dan Shi9f92aa62017-07-27 17:07:05 -0700828 return
829
Prashanth B98509c72014-04-04 16:01:34 -0700830 locally_created_dest = False
831 if (not os.path.exists(local_dest_dir)
832 or not os.path.isdir(local_dest_dir)):
833 try:
834 os.makedirs(local_dest_dir)
835 locally_created_dest = True
836 except OSError as e:
837 logging.warning('Unable to collect logs from host '
Dean Liao4bd3e312017-11-13 17:32:17 +0800838 '%s: %s', self.hostname, e)
Prashanth B98509c72014-04-04 16:01:34 -0700839 if not ignore_errors:
840 raise
841 return
Dan Shi4f8c0242017-07-07 15:34:49 -0700842
843 # Build test result directory summary
844 try:
845 result_tools_runner.run_on_client(self, remote_src_dir)
846 except (error.AutotestRunError, error.AutoservRunError,
847 error.AutoservSSHTimeout) as e:
848 logging.exception(
849 'Non-critical failure: Failed to collect and throttle '
Dean Liao4bd3e312017-11-13 17:32:17 +0800850 'results at %s from host %s', remote_src_dir, self.hostname)
Dan Shi4f8c0242017-07-07 15:34:49 -0700851
Prashanth B98509c72014-04-04 16:01:34 -0700852 try:
Luigi Semenzato9b083072016-12-19 16:50:40 -0800853 self.get_file(remote_src_dir, local_dest_dir, safe_symlinks=True)
Prashanth B98509c72014-04-04 16:01:34 -0700854 except (error.AutotestRunError, error.AutoservRunError,
855 error.AutoservSSHTimeout) as e:
856 logging.warning('Collection of %s to local dir %s from host %s '
857 'failed: %s', remote_src_dir, local_dest_dir,
Dean Liao4bd3e312017-11-13 17:32:17 +0800858 self.hostname, e)
Prashanth B98509c72014-04-04 16:01:34 -0700859 if locally_created_dest:
860 shutil.rmtree(local_dest_dir, ignore_errors=ignore_errors)
861 if not ignore_errors:
862 raise
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800863
Dan Shi4f8c0242017-07-07 15:34:49 -0700864 # Clean up directory summary file on the client side.
865 try:
866 result_tools_runner.run_on_client(self, remote_src_dir,
867 cleanup_only=True)
868 except (error.AutotestRunError, error.AutoservRunError,
869 error.AutoservSSHTimeout) as e:
870 logging.exception(
871 'Non-critical failure: Failed to cleanup result summary '
Lutz Justen043e9c12017-10-27 12:40:47 +0200872 'files at %s in host %s', remote_src_dir, self.hostname)
Dan Shi4f8c0242017-07-07 15:34:49 -0700873
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800874
xixuan6cf6d2f2016-01-29 15:29:00 -0800875 def create_ssh_tunnel(self, port, local_port):
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800876 """Create an ssh tunnel from local_port to port.
877
xixuan6cf6d2f2016-01-29 15:29:00 -0800878 This is used to forward a port securely through a tunnel process from
879 the server to the DUT for RPC server connection.
880
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800881 @param port: remote port on the host.
882 @param local_port: local forwarding port.
883
884 @return: the tunnel process.
885 """
886 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
Prathmesh Prabhu817b3f12017-07-31 17:08:41 -0700887 ssh_cmd = self.make_ssh_command(opts=tunnel_options, port=self.port)
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800888 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
889 logging.debug('Full tunnel command: %s', tunnel_cmd)
xixuan6cf6d2f2016-01-29 15:29:00 -0800890 # Exec the ssh process directly here rather than using a shell.
891 # Using a shell leaves a dangling ssh process, because we deliver
892 # signals to the shell wrapping ssh, not the ssh process itself.
893 args = shlex.split(tunnel_cmd)
894 tunnel_proc = subprocess.Popen(args, close_fds=True)
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800895 logging.debug('Started ssh tunnel, local = %d'
896 ' remote = %d, pid = %d',
897 local_port, port, tunnel_proc.pid)
898 return tunnel_proc
Gilad Arnolda76bef02015-09-29 13:55:15 -0700899
900
xixuan6cf6d2f2016-01-29 15:29:00 -0800901 def disconnect_ssh_tunnel(self, tunnel_proc, port):
Roshan Pius58e5dd32015-10-16 15:16:42 -0700902 """
903 Disconnects a previously forwarded port from the server to the DUT for
904 RPC server connection.
905
xixuan6cf6d2f2016-01-29 15:29:00 -0800906 @param tunnel_proc: a tunnel process returned from |create_ssh_tunnel|.
907 @param port: remote port on the DUT, used in ADBHost.
Roshan Pius58e5dd32015-10-16 15:16:42 -0700908
909 """
910 if tunnel_proc.poll() is None:
911 tunnel_proc.terminate()
912 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
913 else:
914 logging.debug('Tunnel pid %d terminated early, status %d',
915 tunnel_proc.pid, tunnel_proc.returncode)
916
917
Gilad Arnolda76bef02015-09-29 13:55:15 -0700918 def get_os_type(self):
919 """Returns the host OS descriptor (to be implemented in subclasses).
920
921 @return A string describing the OS type.
922 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800923 raise NotImplementedError
Dan Shi9f92aa62017-07-27 17:07:05 -0700924
925
926 def check_cached_up_status(
927 self, expiration_seconds=_DEFAULT_UP_STATUS_EXPIRATION_SECONDS):
928 """Check if the DUT responded to ping in the past `expiration_seconds`.
929
930 @param expiration_seconds: The number of seconds to keep the cached
931 status of whether the DUT responded to ping.
932 @return: True if the DUT has responded to ping during the past
933 `expiration_seconds`.
934 """
935 # Refresh the up status if any of following conditions is true:
936 # * cached status is never set
937 # * cached status is False, so the method can check if the host is up
938 # again.
939 # * If the cached status is older than `expiration_seconds`
940 expire_time = time.time() - expiration_seconds
941 if (self._cached_up_status_updated is None or
942 not self._cached_up_status or
943 self._cached_up_status_updated < expire_time):
944 self._cached_up_status = self.is_up_fast()
945 self._cached_up_status_updated = time.time()
946 return self._cached_up_status