blob: cea8c63b9c8209282a4b8dc1402431c020438358 [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
Dean Liaoe3e75f62017-11-14 10:36:43 +080023_DEFAULT_SSH_PORT = 22
mblighefccc1b2010-01-11 19:08:42 +000024
Lutz Justen043e9c12017-10-27 12:40:47 +020025# Number of seconds to wait for the host to shut down in wait_down().
26_DEFAULT_WAIT_DOWN_TIME_SECONDS = 120
27
Philip Chen7ce1e392018-12-09 23:53:32 -080028# Number of seconds to wait for the host to boot up in wait_up().
29_DEFAULT_WAIT_UP_TIME_SECONDS = 120
30
31# Timeout in seconds for a single call of get_boot_id() in wait_down()
32# and a single ssh ping in wait_up().
Lutz Justen043e9c12017-10-27 12:40:47 +020033_DEFAULT_MAX_PING_TIMEOUT = 10
34
Fang Deng96667ca2013-08-01 17:46:18 -070035class AbstractSSHHost(remote.RemoteHost):
mblighbc9402b2009-12-29 01:15:34 +000036 """
37 This class represents a generic implementation of most of the
jadmanskica7da372008-10-21 16:26:52 +000038 framework necessary for controlling a host via ssh. It implements
39 almost all of the abstract Host methods, except for the core
mblighbc9402b2009-12-29 01:15:34 +000040 Host.run method.
41 """
Simran Basi5ace6f22016-01-06 17:30:44 -080042 VERSION_PREFIX = ''
Prathmesh Prabhuf0507422018-08-28 15:51:45 -070043 # Timeout for master ssh connection setup, in seconds.
44 DEFAULT_START_MASTER_SSH_TIMEOUT_S = 5
jadmanskica7da372008-10-21 16:26:52 +000045
Dean Liaoe3e75f62017-11-14 10:36:43 +080046 def _initialize(self, hostname, user="root", port=_DEFAULT_SSH_PORT,
47 password="", is_client_install_supported=True,
48 afe_host=None, host_info_store=None, connection_pool=None,
Hidehiko Abe06893302017-06-24 07:32:38 +090049 *args, **dargs):
jadmanskif6562912008-10-21 17:59:01 +000050 super(AbstractSSHHost, self)._initialize(hostname=hostname,
51 *args, **dargs)
Kevin Cheng05ae2a42016-06-06 10:12:48 -070052 """
53 @param hostname: The hostname of the host.
54 @param user: The username to use when ssh'ing into the host.
55 @param password: The password to use when ssh'ing into the host.
56 @param port: The port to use for ssh.
57 @param is_client_install_supported: Boolean to indicate if we can
58 install autotest on the host.
59 @param afe_host: The host object attained from the AFE (get_hosts).
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080060 @param host_info_store: Optional host_info.CachingHostInfoStore object
61 to obtain / update host information.
Hidehiko Abe06893302017-06-24 07:32:38 +090062 @param connection_pool: ssh_multiplex.ConnectionPool instance to share
63 the master ssh connection across control scripts.
Kevin Cheng05ae2a42016-06-06 10:12:48 -070064 """
Dan Shic07b8932014-12-11 15:22:30 -080065 # IP address is retrieved only on demand. Otherwise the host
66 # initialization will fail for host is not online.
67 self._ip = None
jadmanskica7da372008-10-21 16:26:52 +000068 self.user = user
69 self.port = port
70 self.password = password
Roshan Piusa58163a2015-10-14 13:36:29 -070071 self._is_client_install_supported = is_client_install_supported
showard6eafb492010-01-15 20:29:06 +000072 self._use_rsync = None
Fang Deng3af66202013-08-16 15:19:25 -070073 self.known_hosts_file = tempfile.mkstemp()[1]
Roshan Pius58e5dd32015-10-16 15:16:42 -070074 self._rpc_server_tracker = rpc_server_tracker.RpcServerTracker(self);
jadmanskica7da372008-10-21 16:26:52 +000075
mblighefccc1b2010-01-11 19:08:42 +000076 """
77 Master SSH connection background job, socket temp directory and socket
78 control path option. If master-SSH is enabled, these fields will be
79 initialized by start_master_ssh when a new SSH connection is initiated.
80 """
Hidehiko Abe06893302017-06-24 07:32:38 +090081 self._connection_pool = connection_pool
82 if connection_pool:
83 self._master_ssh = connection_pool.get(hostname, user, port)
84 else:
85 self._master_ssh = ssh_multiplex.MasterSsh(hostname, user, port)
Simran Basi3b858a22015-03-17 16:23:24 -070086
Kevin Cheng05ae2a42016-06-06 10:12:48 -070087 self._afe_host = afe_host or utils.EmptyAFEHost()
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080088 self.host_info_store = (host_info_store or
89 host_info.InMemoryHostInfoStore())
showard6eafb492010-01-15 20:29:06 +000090
Dan Shi9f92aa62017-07-27 17:07:05 -070091 # The cached status of whether the DUT responded to ping.
92 self._cached_up_status = None
93 # The timestamp when the value of _cached_up_status is set.
94 self._cached_up_status_updated = None
95
96
Dan Shic07b8932014-12-11 15:22:30 -080097 @property
98 def ip(self):
99 """@return IP address of the host.
100 """
101 if not self._ip:
102 self._ip = socket.getaddrinfo(self.hostname, None)[0][4][0]
103 return self._ip
104
105
Roshan Piusa58163a2015-10-14 13:36:29 -0700106 @property
107 def is_client_install_supported(self):
108 """"
109 Returns True if the host supports autotest client installs, False
110 otherwise.
111 """
112 return self._is_client_install_supported
113
114
Roshan Pius58e5dd32015-10-16 15:16:42 -0700115 @property
116 def rpc_server_tracker(self):
117 """"
118 @return The RPC server tracker associated with this host.
119 """
120 return self._rpc_server_tracker
121
122
Dean Liaoe3e75f62017-11-14 10:36:43 +0800123 @property
124 def is_default_port(self):
125 """Returns True if its port is default SSH port."""
126 return self.port == _DEFAULT_SSH_PORT
127
128 @property
129 def host_port(self):
130 """Returns hostname if port is default. Otherwise, hostname:port.
131 """
132 if self.is_default_port:
133 return self.hostname
134 else:
135 return '%s:%d' % (self.hostname, self.port)
136
137
138 # Though it doesn't use self here, it is not declared as staticmethod
139 # because its subclass may use self to access member variables.
140 def make_ssh_command(self, user="root", port=_DEFAULT_SSH_PORT, opts='',
141 hosts_file='/dev/null', connect_timeout=30,
142 alive_interval=300, alive_count_max=3,
143 connection_attempts=1):
144 ssh_options = " ".join([
145 opts,
146 self.make_ssh_options(
147 hosts_file=hosts_file, connect_timeout=connect_timeout,
148 alive_interval=alive_interval, alive_count_max=alive_count_max,
149 connection_attempts=connection_attempts)])
150 return "/usr/bin/ssh -a -x %s -l %s -p %d" % (ssh_options, user, port)
151
152
153 @staticmethod
154 def make_ssh_options(hosts_file='/dev/null', connect_timeout=30,
155 alive_interval=300, alive_count_max=3,
156 connection_attempts=1):
157 """Composes SSH -o options."""
Fang Deng96667ca2013-08-01 17:46:18 -0700158 assert isinstance(connect_timeout, (int, long))
159 assert connect_timeout > 0 # can't disable the timeout
Dean Liaoe3e75f62017-11-14 10:36:43 +0800160
161 options = [("StrictHostKeyChecking", "no"),
162 ("UserKnownHostsFile", hosts_file),
163 ("BatchMode", "yes"),
164 ("ConnectTimeout", str(connect_timeout)),
165 ("ServerAliveInterval", str(alive_interval)),
166 ("ServerAliveCountMax", str(alive_count_max)),
167 ("ConnectionAttempts", str(connection_attempts))]
168 return " ".join("-o %s=%s" % kv for kv in options)
Fang Deng96667ca2013-08-01 17:46:18 -0700169
170
showard6eafb492010-01-15 20:29:06 +0000171 def use_rsync(self):
172 if self._use_rsync is not None:
173 return self._use_rsync
174
mblighc9892c02010-01-06 19:02:16 +0000175 # Check if rsync is available on the remote host. If it's not,
176 # don't try to use it for any future file transfers.
Gwendal Grignou03286f02017-03-24 10:50:59 -0700177 self._use_rsync = self.check_rsync()
showard6eafb492010-01-15 20:29:06 +0000178 if not self._use_rsync:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700179 logging.warning("rsync not available on remote host %s -- disabled",
Dean Liaoe3e75f62017-11-14 10:36:43 +0800180 self.host_port)
Eric Lie0493a42010-11-15 13:05:43 -0800181 return self._use_rsync
mblighc9892c02010-01-06 19:02:16 +0000182
183
Gwendal Grignou03286f02017-03-24 10:50:59 -0700184 def check_rsync(self):
mblighc9892c02010-01-06 19:02:16 +0000185 """
186 Check if rsync is available on the remote host.
187 """
188 try:
Allen Liad719c12017-06-27 23:48:04 +0000189 self.run("rsync --version", stdout_tee=None, stderr_tee=None)
mblighc9892c02010-01-06 19:02:16 +0000190 except error.AutoservRunError:
191 return False
192 return True
193
jadmanskica7da372008-10-21 16:26:52 +0000194
Gwendal Grignou36b61702016-02-10 11:57:53 -0800195 def _encode_remote_paths(self, paths, escape=True, use_scp=False):
mblighbc9402b2009-12-29 01:15:34 +0000196 """
197 Given a list of file paths, encodes it as a single remote path, in
198 the style used by rsync and scp.
Gwendal Grignou36b61702016-02-10 11:57:53 -0800199 escape: add \\ to protect special characters.
200 use_scp: encode for scp if true, rsync if false.
mblighbc9402b2009-12-29 01:15:34 +0000201 """
showard56176ec2009-10-28 19:52:30 +0000202 if escape:
203 paths = [utils.scp_remote_escape(path) for path in paths]
Marc Herbert21eb6492015-11-13 15:48:53 -0800204
205 remote = self.hostname
206
207 # rsync and scp require IPv6 brackets, even when there isn't any
208 # trailing port number (ssh doesn't support IPv6 brackets).
209 # In the Python >= 3.3 future, 'import ipaddress' will parse addresses.
210 if re.search(r':.*:', remote):
211 remote = '[%s]' % remote
212
Gwendal Grignou36b61702016-02-10 11:57:53 -0800213 if use_scp:
214 return '%s@%s:"%s"' % (self.user, remote, " ".join(paths))
215 else:
216 return '%s@%s:%s' % (
217 self.user, remote,
218 " :".join('"%s"' % p for p in paths))
jadmanskica7da372008-10-21 16:26:52 +0000219
Gwendal Grignou36b61702016-02-10 11:57:53 -0800220 def _encode_local_paths(self, paths, escape=True):
221 """
222 Given a list of file paths, encodes it as a single local path.
223 escape: add \\ to protect special characters.
224 """
225 if escape:
226 paths = [utils.sh_escape(path) for path in paths]
227
228 return " ".join('"%s"' % p for p in paths)
jadmanskica7da372008-10-21 16:26:52 +0000229
Dean Liaoe3e75f62017-11-14 10:36:43 +0800230
231 def rsync_options(self, delete_dest=False, preserve_symlinks=False,
232 safe_symlinks=False, excludes=None):
233 """Obtains rsync options for the remote."""
Fang Deng96667ca2013-08-01 17:46:18 -0700234 ssh_cmd = self.make_ssh_command(user=self.user, port=self.port,
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900235 opts=self._master_ssh.ssh_option,
Fang Deng96667ca2013-08-01 17:46:18 -0700236 hosts_file=self.known_hosts_file)
jadmanskid7b79ed2009-01-07 17:19:48 +0000237 if delete_dest:
238 delete_flag = "--delete"
239 else:
240 delete_flag = ""
Luigi Semenzato9b083072016-12-19 16:50:40 -0800241 if safe_symlinks:
242 symlink_flag = "-l --safe-links"
243 elif preserve_symlinks:
244 symlink_flag = "-l"
mbligh45561782009-05-11 21:14:34 +0000245 else:
246 symlink_flag = "-L"
Dan Shi92c34c92017-07-14 15:28:56 -0700247 exclude_args = ''
248 if excludes:
249 exclude_args = ' '.join(
250 ["--exclude '%s'" % exclude for exclude in excludes])
Dean Liaoe3e75f62017-11-14 10:36:43 +0800251 return "%s %s --timeout=1800 --rsh='%s' -az --no-o --no-g %s" % (
252 symlink_flag, delete_flag, ssh_cmd, exclude_args)
253
254
255 def _make_rsync_cmd(self, sources, dest, delete_dest,
256 preserve_symlinks, safe_symlinks, excludes=None):
257 """
258 Given a string of source paths and a destination path, produces the
259 appropriate rsync command for copying them. Remote paths must be
260 pre-encoded.
261 """
262 rsync_options = self.rsync_options(
263 delete_dest=delete_dest, preserve_symlinks=preserve_symlinks,
264 safe_symlinks=safe_symlinks, excludes=excludes)
265 return 'rsync %s %s "%s"' % (rsync_options, sources, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000266
267
Eric Li861b2d52011-02-04 14:50:35 -0800268 def _make_ssh_cmd(self, cmd):
269 """
270 Create a base ssh command string for the host which can be used
271 to run commands directly on the machine
272 """
Fang Deng96667ca2013-08-01 17:46:18 -0700273 base_cmd = self.make_ssh_command(user=self.user, port=self.port,
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900274 opts=self._master_ssh.ssh_option,
Fang Deng96667ca2013-08-01 17:46:18 -0700275 hosts_file=self.known_hosts_file)
Eric Li861b2d52011-02-04 14:50:35 -0800276
277 return '%s %s "%s"' % (base_cmd, self.hostname, utils.sh_escape(cmd))
278
jadmanskid7b79ed2009-01-07 17:19:48 +0000279 def _make_scp_cmd(self, sources, dest):
mblighbc9402b2009-12-29 01:15:34 +0000280 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800281 Given a string of source paths and a destination path, produces the
jadmanskid7b79ed2009-01-07 17:19:48 +0000282 appropriate scp command for encoding it. Remote paths must be
mblighbc9402b2009-12-29 01:15:34 +0000283 pre-encoded.
284 """
mblighc0649d62010-01-15 18:15:58 +0000285 command = ("scp -rq %s -o StrictHostKeyChecking=no "
lmraf676f32010-02-04 03:36:26 +0000286 "-o UserKnownHostsFile=%s -P %d %s '%s'")
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900287 return command % (self._master_ssh.ssh_option, self.known_hosts_file,
Gwendal Grignou36b61702016-02-10 11:57:53 -0800288 self.port, sources, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000289
290
291 def _make_rsync_compatible_globs(self, path, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000292 """
293 Given an rsync-style path, returns a list of globbed paths
jadmanskid7b79ed2009-01-07 17:19:48 +0000294 that will hopefully provide equivalent behaviour for scp. Does not
295 support the full range of rsync pattern matching behaviour, only that
296 exposed in the get/send_file interface (trailing slashes).
297
298 The is_local param is flag indicating if the paths should be
mblighbc9402b2009-12-29 01:15:34 +0000299 interpreted as local or remote paths.
300 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000301
302 # non-trailing slash paths should just work
303 if len(path) == 0 or path[-1] != "/":
304 return [path]
305
306 # make a function to test if a pattern matches any files
307 if is_local:
showard56176ec2009-10-28 19:52:30 +0000308 def glob_matches_files(path, pattern):
309 return len(glob.glob(path + pattern)) > 0
jadmanskid7b79ed2009-01-07 17:19:48 +0000310 else:
showard56176ec2009-10-28 19:52:30 +0000311 def glob_matches_files(path, pattern):
312 result = self.run("ls \"%s\"%s" % (utils.sh_escape(path),
313 pattern),
314 stdout_tee=None, ignore_status=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000315 return result.exit_status == 0
316
317 # take a set of globs that cover all files, and see which are needed
318 patterns = ["*", ".[!.]*"]
showard56176ec2009-10-28 19:52:30 +0000319 patterns = [p for p in patterns if glob_matches_files(path, p)]
jadmanskid7b79ed2009-01-07 17:19:48 +0000320
321 # convert them into a set of paths suitable for the commandline
jadmanskid7b79ed2009-01-07 17:19:48 +0000322 if is_local:
showard56176ec2009-10-28 19:52:30 +0000323 return ["\"%s\"%s" % (utils.sh_escape(path), pattern)
324 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000325 else:
showard56176ec2009-10-28 19:52:30 +0000326 return [utils.scp_remote_escape(path) + pattern
327 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000328
329
330 def _make_rsync_compatible_source(self, source, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000331 """
332 Applies the same logic as _make_rsync_compatible_globs, but
jadmanskid7b79ed2009-01-07 17:19:48 +0000333 applies it to an entire list of sources, producing a new list of
mblighbc9402b2009-12-29 01:15:34 +0000334 sources, properly quoted.
335 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000336 return sum((self._make_rsync_compatible_globs(path, is_local)
337 for path in source), [])
jadmanskica7da372008-10-21 16:26:52 +0000338
339
mblighfeac0102009-04-28 18:31:12 +0000340 def _set_umask_perms(self, dest):
mblighbc9402b2009-12-29 01:15:34 +0000341 """
342 Given a destination file/dir (recursively) set the permissions on
343 all the files and directories to the max allowed by running umask.
344 """
mblighfeac0102009-04-28 18:31:12 +0000345
346 # now this looks strange but I haven't found a way in Python to _just_
347 # get the umask, apparently the only option is to try to set it
348 umask = os.umask(0)
349 os.umask(umask)
350
351 max_privs = 0777 & ~umask
352
353 def set_file_privs(filename):
Chris Masone567d0d92011-12-19 09:38:30 -0800354 """Sets mode of |filename|. Assumes |filename| exists."""
355 file_stat = os.stat(filename)
mblighfeac0102009-04-28 18:31:12 +0000356
357 file_privs = max_privs
358 # if the original file permissions do not have at least one
359 # executable bit then do not set it anywhere
360 if not file_stat.st_mode & 0111:
361 file_privs &= ~0111
362
363 os.chmod(filename, file_privs)
364
365 # try a bottom-up walk so changes on directory permissions won't cut
366 # our access to the files/directories inside it
367 for root, dirs, files in os.walk(dest, topdown=False):
368 # when setting the privileges we emulate the chmod "X" behaviour
369 # that sets to execute only if it is a directory or any of the
370 # owner/group/other already has execute right
371 for dirname in dirs:
372 os.chmod(os.path.join(root, dirname), max_privs)
373
Chris Masone567d0d92011-12-19 09:38:30 -0800374 # Filter out broken symlinks as we go.
375 for filename in filter(os.path.exists, files):
mblighfeac0102009-04-28 18:31:12 +0000376 set_file_privs(os.path.join(root, filename))
377
378
379 # now set privs for the dest itself
380 if os.path.isdir(dest):
381 os.chmod(dest, max_privs)
382 else:
383 set_file_privs(dest)
384
385
mbligh45561782009-05-11 21:14:34 +0000386 def get_file(self, source, dest, delete_dest=False, preserve_perm=True,
Dana Goyette4d864e12019-09-19 11:05:44 -0700387 preserve_symlinks=False, retry=True, safe_symlinks=False,
388 try_rsync=True):
jadmanskica7da372008-10-21 16:26:52 +0000389 """
390 Copy files from the remote host to a local path.
391
392 Directories will be copied recursively.
393 If a source component is a directory with a trailing slash,
394 the content of the directory will be copied, otherwise, the
395 directory itself and its content will be copied. This
396 behavior is similar to that of the program 'rsync'.
397
398 Args:
399 source: either
400 1) a single file or directory, as a string
401 2) a list of one or more (possibly mixed)
402 files or directories
403 dest: a file or a directory (if source contains a
404 directory or more than one element, you must
405 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000406 delete_dest: if this is true, the command will also clear
407 out any old files at dest that are not in the
408 source
mblighfeac0102009-04-28 18:31:12 +0000409 preserve_perm: tells get_file() to try to preserve the sources
410 permissions on files and dirs
mbligh45561782009-05-11 21:14:34 +0000411 preserve_symlinks: try to preserve symlinks instead of
412 transforming them into files/dirs on copy
Luigi Semenzato9b083072016-12-19 16:50:40 -0800413 safe_symlinks: same as preserve_symlinks, but discard links
414 that may point outside the copied tree
Dana Goyette4d864e12019-09-19 11:05:44 -0700415 try_rsync: set to False to skip directly to using scp
jadmanskica7da372008-10-21 16:26:52 +0000416 Raises:
417 AutoservRunError: the scp command failed
418 """
Simran Basi882f15b2013-10-29 14:59:34 -0700419 logging.debug('get_file. source: %s, dest: %s, delete_dest: %s,'
420 'preserve_perm: %s, preserve_symlinks:%s', source, dest,
421 delete_dest, preserve_perm, preserve_symlinks)
Dan Shi4f8c0242017-07-07 15:34:49 -0700422
mblighefccc1b2010-01-11 19:08:42 +0000423 # Start a master SSH connection if necessary.
424 self.start_master_ssh()
425
jadmanskica7da372008-10-21 16:26:52 +0000426 if isinstance(source, basestring):
427 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000428 dest = os.path.abspath(dest)
jadmanskica7da372008-10-21 16:26:52 +0000429
mblighc9892c02010-01-06 19:02:16 +0000430 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000431 try_scp = True
Dana Goyette4d864e12019-09-19 11:05:44 -0700432 if try_rsync and self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700433 logging.debug('Using Rsync.')
mblighc9892c02010-01-06 19:02:16 +0000434 try:
435 remote_source = self._encode_remote_paths(source)
436 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800437 rsync = self._make_rsync_cmd(remote_source, local_dest,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800438 delete_dest, preserve_symlinks,
439 safe_symlinks)
mblighc9892c02010-01-06 19:02:16 +0000440 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000441 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000442 except error.CmdError, e:
Luigi Semenzato7f9dff12016-11-21 14:01:20 -0800443 # retry on rsync exit values which may be caused by transient
444 # network problems:
445 #
446 # rc 10: Error in socket I/O
447 # rc 12: Error in rsync protocol data stream
448 # rc 23: Partial transfer due to error
449 # rc 255: Ssh error
450 #
451 # Note that rc 23 includes dangling symlinks. In this case
452 # retrying is useless, but not very damaging since rsync checks
453 # for those before starting the transfer (scp does not).
454 status = e.result_obj.exit_status
455 if status in [10, 12, 23, 255] and retry:
456 logging.warning('rsync status %d, retrying', status)
457 self.get_file(source, dest, delete_dest, preserve_perm,
458 preserve_symlinks, retry=False)
459 # The nested get_file() does all that's needed.
460 return
461 else:
462 logging.warning("trying scp, rsync failed: %s (%d)",
463 e, status)
mblighc9892c02010-01-06 19:02:16 +0000464
465 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700466 logging.debug('Trying scp.')
jadmanskid7b79ed2009-01-07 17:19:48 +0000467 # scp has no equivalent to --delete, just drop the entire dest dir
468 if delete_dest and os.path.isdir(dest):
469 shutil.rmtree(dest)
470 os.mkdir(dest)
jadmanskica7da372008-10-21 16:26:52 +0000471
jadmanskid7b79ed2009-01-07 17:19:48 +0000472 remote_source = self._make_rsync_compatible_source(source, False)
473 if remote_source:
showard56176ec2009-10-28 19:52:30 +0000474 # _make_rsync_compatible_source() already did the escaping
Gwendal Grignou36b61702016-02-10 11:57:53 -0800475 remote_source = self._encode_remote_paths(
476 remote_source, escape=False, use_scp=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000477 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800478 scp = self._make_scp_cmd(remote_source, local_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000479 try:
480 utils.run(scp)
481 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700482 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000483 raise error.AutoservRunError(e.args[0], e.args[1])
jadmanskica7da372008-10-21 16:26:52 +0000484
mblighfeac0102009-04-28 18:31:12 +0000485 if not preserve_perm:
486 # we have no way to tell scp to not try to preserve the
487 # permissions so set them after copy instead.
488 # for rsync we could use "--no-p --chmod=ugo=rwX" but those
489 # options are only in very recent rsync versions
490 self._set_umask_perms(dest)
491
jadmanskica7da372008-10-21 16:26:52 +0000492
mbligh45561782009-05-11 21:14:34 +0000493 def send_file(self, source, dest, delete_dest=False,
Dan Shi92c34c92017-07-14 15:28:56 -0700494 preserve_symlinks=False, excludes=None):
jadmanskica7da372008-10-21 16:26:52 +0000495 """
496 Copy files from a local path to the remote host.
497
498 Directories will be copied recursively.
499 If a source component is a directory with a trailing slash,
500 the content of the directory will be copied, otherwise, the
501 directory itself and its content will be copied. This
502 behavior is similar to that of the program 'rsync'.
503
504 Args:
505 source: either
506 1) a single file or directory, as a string
507 2) a list of one or more (possibly mixed)
508 files or directories
509 dest: a file or a directory (if source contains a
510 directory or more than one element, you must
511 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000512 delete_dest: if this is true, the command will also clear
513 out any old files at dest that are not in the
514 source
mbligh45561782009-05-11 21:14:34 +0000515 preserve_symlinks: controls if symlinks on the source will be
516 copied as such on the destination or transformed into the
517 referenced file/directory
Dan Shi92c34c92017-07-14 15:28:56 -0700518 excludes: A list of file pattern that matches files not to be
519 sent. `send_file` will fail if exclude is set, since
520 local copy does not support --exclude, e.g., when
521 using scp to copy file.
jadmanskica7da372008-10-21 16:26:52 +0000522
523 Raises:
524 AutoservRunError: the scp command failed
525 """
Simran Basi882f15b2013-10-29 14:59:34 -0700526 logging.debug('send_file. source: %s, dest: %s, delete_dest: %s,'
527 'preserve_symlinks:%s', source, dest,
528 delete_dest, preserve_symlinks)
mblighefccc1b2010-01-11 19:08:42 +0000529 # Start a master SSH connection if necessary.
530 self.start_master_ssh()
531
jadmanskica7da372008-10-21 16:26:52 +0000532 if isinstance(source, basestring):
533 source = [source]
534
Gwendal Grignou36b61702016-02-10 11:57:53 -0800535 local_sources = self._encode_local_paths(source)
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700536 if not local_sources:
Gwendal Grignou36b61702016-02-10 11:57:53 -0800537 raise error.TestError('source |%s| yielded an empty string' % (
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700538 source))
Gwendal Grignou36b61702016-02-10 11:57:53 -0800539 if local_sources.find('\x00') != -1:
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700540 raise error.TestError('one or more sources include NUL char')
541
mblighc9892c02010-01-06 19:02:16 +0000542 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000543 try_scp = True
544 if self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700545 logging.debug('Using Rsync.')
Gwendal Grignou36b61702016-02-10 11:57:53 -0800546 remote_dest = self._encode_remote_paths([dest])
mblighc9892c02010-01-06 19:02:16 +0000547 try:
mblighc9892c02010-01-06 19:02:16 +0000548 rsync = self._make_rsync_cmd(local_sources, remote_dest,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800549 delete_dest, preserve_symlinks,
Dan Shi92c34c92017-07-14 15:28:56 -0700550 False, excludes=excludes)
mblighc9892c02010-01-06 19:02:16 +0000551 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000552 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000553 except error.CmdError, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700554 logging.warning("trying scp, rsync failed: %s", e)
mblighc9892c02010-01-06 19:02:16 +0000555
556 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700557 logging.debug('Trying scp.')
Dan Shi92c34c92017-07-14 15:28:56 -0700558 if excludes:
559 raise error.AutotestHostRunError(
560 '--exclude is not supported in scp, try to use rsync. '
Brian Norrisd7650482018-02-21 18:38:18 -0800561 'excludes: %s' % ','.join(excludes), None)
jadmanskid7b79ed2009-01-07 17:19:48 +0000562 # scp has no equivalent to --delete, just drop the entire dest dir
563 if delete_dest:
showard27160152009-07-15 14:28:42 +0000564 is_dir = self.run("ls -d %s/" % dest,
jadmanskid7b79ed2009-01-07 17:19:48 +0000565 ignore_status=True).exit_status == 0
566 if is_dir:
567 cmd = "rm -rf %s && mkdir %s"
mbligh5a0ca532009-08-03 16:44:34 +0000568 cmd %= (dest, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000569 self.run(cmd)
jadmanskica7da372008-10-21 16:26:52 +0000570
Gwendal Grignou36b61702016-02-10 11:57:53 -0800571 remote_dest = self._encode_remote_paths([dest], use_scp=True)
jadmanski2583a432009-02-10 23:59:11 +0000572 local_sources = self._make_rsync_compatible_source(source, True)
573 if local_sources:
Cheng-Yi Chiang9b2812d2016-02-29 17:01:44 +0800574 sources = self._encode_local_paths(local_sources, escape=False)
575 scp = self._make_scp_cmd(sources, remote_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000576 try:
577 utils.run(scp)
578 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700579 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000580 raise error.AutoservRunError(e.args[0], e.args[1])
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700581 else:
582 logging.debug('skipping scp for empty source list')
jadmanskid7b79ed2009-01-07 17:19:48 +0000583
jadmanskica7da372008-10-21 16:26:52 +0000584
Simran Basi1621c632015-10-14 12:22:23 -0700585 def verify_ssh_user_access(self):
586 """Verify ssh access to this host.
587
588 @returns False if ssh_ping fails due to Permissions error, True
589 otherwise.
590 """
591 try:
592 self.ssh_ping()
593 except (error.AutoservSshPermissionDeniedError,
594 error.AutoservSshPingHostError):
595 return False
596 return True
597
598
Luigi Semenzato135574c2016-08-31 17:25:08 -0700599 def ssh_ping(self, timeout=60, connect_timeout=None, base_cmd='true'):
beepsadd66d32013-03-04 17:21:51 -0800600 """
601 Pings remote host via ssh.
602
Philip Chen7ce1e392018-12-09 23:53:32 -0800603 @param timeout: Command execution timeout in seconds.
beepsadd66d32013-03-04 17:21:51 -0800604 Defaults to 60 seconds.
Philip Chen7ce1e392018-12-09 23:53:32 -0800605 @param connect_timeout: ssh connection timeout in seconds.
beeps46dadc92013-11-07 14:07:10 -0800606 @param base_cmd: The base command to run with the ssh ping.
607 Defaults to true.
beepsadd66d32013-03-04 17:21:51 -0800608 @raise AutoservSSHTimeout: If the ssh ping times out.
609 @raise AutoservSshPermissionDeniedError: If ssh ping fails due to
610 permissions.
611 @raise AutoservSshPingHostError: For other AutoservRunErrors.
612 """
Luigi Semenzato135574c2016-08-31 17:25:08 -0700613 ctimeout = min(timeout, connect_timeout or timeout)
jadmanskica7da372008-10-21 16:26:52 +0000614 try:
Allen Liad719c12017-06-27 23:48:04 +0000615 self.run(base_cmd, timeout=timeout, connect_timeout=ctimeout,
616 ssh_failure_retry_ok=True)
jadmanskica7da372008-10-21 16:26:52 +0000617 except error.AutoservSSHTimeout:
mblighd0e94982009-07-11 00:15:18 +0000618 msg = "Host (ssh) verify timed out (timeout = %d)" % timeout
jadmanskica7da372008-10-21 16:26:52 +0000619 raise error.AutoservSSHTimeout(msg)
mbligh9d738d62009-03-09 21:17:10 +0000620 except error.AutoservSshPermissionDeniedError:
Allen Liad719c12017-06-27 23:48:04 +0000621 #let AutoservSshPermissionDeniedError be visible to the callers
mbligh9d738d62009-03-09 21:17:10 +0000622 raise
jadmanskica7da372008-10-21 16:26:52 +0000623 except error.AutoservRunError, e:
mblighc971c5f2009-06-08 16:48:54 +0000624 # convert the generic AutoservRunError into something more
625 # specific for this context
626 raise error.AutoservSshPingHostError(e.description + '\n' +
627 repr(e.result_obj))
jadmanskica7da372008-10-21 16:26:52 +0000628
629
Luigi Semenzato135574c2016-08-31 17:25:08 -0700630 def is_up(self, timeout=60, connect_timeout=None, base_cmd='true'):
jadmanskica7da372008-10-21 16:26:52 +0000631 """
beeps46dadc92013-11-07 14:07:10 -0800632 Check if the remote host is up by ssh-ing and running a base command.
jadmanskica7da372008-10-21 16:26:52 +0000633
Philip Chen7ce1e392018-12-09 23:53:32 -0800634 @param timeout: command execution timeout in seconds.
635 @param connect_timeout: ssh connection timeout in seconds.
beeps46dadc92013-11-07 14:07:10 -0800636 @param base_cmd: a base command to run with ssh. The default is 'true'.
beepsadd66d32013-03-04 17:21:51 -0800637 @returns True if the remote host is up before the timeout expires,
638 False otherwise.
jadmanskica7da372008-10-21 16:26:52 +0000639 """
640 try:
Luigi Semenzato135574c2016-08-31 17:25:08 -0700641 self.ssh_ping(timeout=timeout,
642 connect_timeout=connect_timeout,
643 base_cmd=base_cmd)
jadmanskica7da372008-10-21 16:26:52 +0000644 except error.AutoservError:
645 return False
646 else:
647 return True
648
649
Dan Shi9f92aa62017-07-27 17:07:05 -0700650 def is_up_fast(self):
651 """Return True if the host can be pinged."""
652 ping_config = ping_runner.PingConfig(
653 self.hostname, count=3, ignore_result=True, ignore_status=True)
654 return ping_runner.PingRunner().ping(ping_config).received > 0
655
656
Philip Chen7ce1e392018-12-09 23:53:32 -0800657 def wait_up(self, timeout=_DEFAULT_WAIT_UP_TIME_SECONDS):
jadmanskica7da372008-10-21 16:26:52 +0000658 """
659 Wait until the remote host is up or the timeout expires.
660
661 In fact, it will wait until an ssh connection to the remote
662 host can be established, and getty is running.
663
jadmanskic0354912010-01-12 15:57:29 +0000664 @param timeout time limit in seconds before returning even
665 if the host is not up.
jadmanskica7da372008-10-21 16:26:52 +0000666
beepsadd66d32013-03-04 17:21:51 -0800667 @returns True if the host was found to be up before the timeout expires,
668 False otherwise
jadmanskica7da372008-10-21 16:26:52 +0000669 """
Philip Chen7ce1e392018-12-09 23:53:32 -0800670 current_time = int(time.time())
671 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000672
Luigi Semenzato135574c2016-08-31 17:25:08 -0700673 autoserv_error_logged = False
Philip Chen7ce1e392018-12-09 23:53:32 -0800674 while current_time < end_time:
675 ping_timeout = min(_DEFAULT_MAX_PING_TIMEOUT,
676 end_time - current_time)
677 if self.is_up(timeout=ping_timeout, connect_timeout=ping_timeout):
jadmanskica7da372008-10-21 16:26:52 +0000678 try:
679 if self.are_wait_up_processes_up():
Dean Liaoe3e75f62017-11-14 10:36:43 +0800680 logging.debug('Host %s is now up', self.host_port)
jadmanskica7da372008-10-21 16:26:52 +0000681 return True
Luigi Semenzato135574c2016-08-31 17:25:08 -0700682 except error.AutoservError as e:
683 if not autoserv_error_logged:
684 logging.debug('Ignoring failure to reach %s: %s %s',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800685 self.host_port, e,
Luigi Semenzato135574c2016-08-31 17:25:08 -0700686 '(and further similar failures)')
687 autoserv_error_logged = True
jadmanskica7da372008-10-21 16:26:52 +0000688 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800689 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000690
jadmanski7ebac3d2010-06-17 16:06:31 +0000691 logging.debug('Host %s is still down after waiting %d seconds',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800692 self.host_port, int(timeout + time.time() - end_time))
jadmanskica7da372008-10-21 16:26:52 +0000693 return False
694
695
Lutz Justen043e9c12017-10-27 12:40:47 +0200696 def wait_down(self, timeout=_DEFAULT_WAIT_DOWN_TIME_SECONDS,
697 warning_timer=None, old_boot_id=None,
698 max_ping_timeout=_DEFAULT_MAX_PING_TIMEOUT):
jadmanskica7da372008-10-21 16:26:52 +0000699 """
700 Wait until the remote host is down or the timeout expires.
701
Lutz Justen043e9c12017-10-27 12:40:47 +0200702 If old_boot_id is provided, waits until either the machine is
703 unpingable or self.get_boot_id() returns a value different from
jadmanskic0354912010-01-12 15:57:29 +0000704 old_boot_id. If the boot_id value has changed then the function
Lutz Justen043e9c12017-10-27 12:40:47 +0200705 returns True under the assumption that the machine has shut down
jadmanskic0354912010-01-12 15:57:29 +0000706 and has now already come back up.
jadmanskica7da372008-10-21 16:26:52 +0000707
jadmanskic0354912010-01-12 15:57:29 +0000708 If old_boot_id is None then until the machine becomes unreachable the
709 method assumes the machine has not yet shut down.
jadmanskica7da372008-10-21 16:26:52 +0000710
Lutz Justen043e9c12017-10-27 12:40:47 +0200711 @param timeout Time limit in seconds before returning even if the host
712 is still up.
713 @param warning_timer Time limit in seconds that will generate a warning
714 if the host is not down yet. Can be None for no warning.
jadmanskic0354912010-01-12 15:57:29 +0000715 @param old_boot_id A string containing the result of self.get_boot_id()
716 prior to the host being told to shut down. Can be None if this is
717 not available.
Lutz Justen043e9c12017-10-27 12:40:47 +0200718 @param max_ping_timeout Maximum timeout in seconds for each
719 self.get_boot_id() call. If this timeout is hit, it is assumed that
720 the host went down and became unreachable.
jadmanskic0354912010-01-12 15:57:29 +0000721
Lutz Justen043e9c12017-10-27 12:40:47 +0200722 @returns True if the host was found to be down (max_ping_timeout timeout
723 expired or boot_id changed if provided) and False if timeout
724 expired.
jadmanskica7da372008-10-21 16:26:52 +0000725 """
mblighe5e3cf22010-05-27 23:33:14 +0000726 #TODO: there is currently no way to distinguish between knowing
727 #TODO: boot_id was unsupported and not knowing the boot_id.
beeps46dadc92013-11-07 14:07:10 -0800728 current_time = int(time.time())
Lutz Justen043e9c12017-10-27 12:40:47 +0200729 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000730
mbligh2ed998f2009-04-08 21:03:47 +0000731 if warning_timer:
732 warn_time = current_time + warning_timer
733
jadmanskic0354912010-01-12 15:57:29 +0000734 if old_boot_id is not None:
735 logging.debug('Host %s pre-shutdown boot_id is %s',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800736 self.host_port, old_boot_id)
jadmanskic0354912010-01-12 15:57:29 +0000737
beepsadd66d32013-03-04 17:21:51 -0800738 # Impose semi real-time deadline constraints, since some clients
739 # (eg: watchdog timer tests) expect strict checking of time elapsed.
740 # Each iteration of this loop is treated as though it atomically
741 # completes within current_time, this is needed because if we used
742 # inline time.time() calls instead then the following could happen:
743 #
Lutz Justen043e9c12017-10-27 12:40:47 +0200744 # while time.time() < end_time: [23 < 30]
beepsadd66d32013-03-04 17:21:51 -0800745 # some code. [takes 10 secs]
746 # try:
747 # new_boot_id = self.get_boot_id(timeout=end_time - time.time())
748 # [30 - 33]
749 # The last step will lead to a return True, when in fact the machine
750 # went down at 32 seconds (>30). Hence we need to pass get_boot_id
751 # the same time that allowed us into that iteration of the loop.
Lutz Justen043e9c12017-10-27 12:40:47 +0200752 while current_time < end_time:
753 ping_timeout = min(end_time - current_time, max_ping_timeout)
jadmanskic0354912010-01-12 15:57:29 +0000754 try:
Lutz Justen043e9c12017-10-27 12:40:47 +0200755 new_boot_id = self.get_boot_id(timeout=ping_timeout)
mblighdbc7e4a2010-01-15 20:34:20 +0000756 except error.AutoservError:
jadmanskic0354912010-01-12 15:57:29 +0000757 logging.debug('Host %s is now unreachable over ssh, is down',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800758 self.host_port)
jadmanskica7da372008-10-21 16:26:52 +0000759 return True
jadmanskic0354912010-01-12 15:57:29 +0000760 else:
761 # if the machine is up but the boot_id value has changed from
762 # old boot id, then we can assume the machine has gone down
763 # and then already come back up
764 if old_boot_id is not None and old_boot_id != new_boot_id:
765 logging.debug('Host %s now has boot_id %s and so must '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800766 'have rebooted', self.host_port, new_boot_id)
jadmanskic0354912010-01-12 15:57:29 +0000767 return True
mbligh2ed998f2009-04-08 21:03:47 +0000768
769 if warning_timer and current_time > warn_time:
Scott Zawalskic86fdeb2013-10-23 10:24:04 -0400770 self.record("INFO", None, "shutdown",
mbligh2ed998f2009-04-08 21:03:47 +0000771 "Shutdown took longer than %ds" % warning_timer)
772 # Print the warning only once.
773 warning_timer = None
mbligha4464402009-04-17 20:13:41 +0000774 # If a machine is stuck switching runlevels
775 # This may cause the machine to reboot.
776 self.run('kill -HUP 1', ignore_status=True)
mbligh2ed998f2009-04-08 21:03:47 +0000777
jadmanskica7da372008-10-21 16:26:52 +0000778 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800779 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000780
781 return False
jadmanskif6562912008-10-21 17:59:01 +0000782
mbligha0a27592009-01-24 01:41:36 +0000783
jadmanskif6562912008-10-21 17:59:01 +0000784 # tunable constants for the verify & repair code
mblighb86bfa12010-02-12 20:22:21 +0000785 AUTOTEST_GB_DISKSPACE_REQUIRED = get_value("SERVER",
786 "gb_diskspace_required",
Fang Deng6b05f5b2013-03-20 13:42:11 -0700787 type=float,
788 default=20.0)
mbligha0a27592009-01-24 01:41:36 +0000789
jadmanskif6562912008-10-21 17:59:01 +0000790
showardca572982009-09-18 21:20:01 +0000791 def verify_connectivity(self):
792 super(AbstractSSHHost, self).verify_connectivity()
jadmanskif6562912008-10-21 17:59:01 +0000793
Dean Liaoe3e75f62017-11-14 10:36:43 +0800794 logging.info('Pinging host ' + self.host_port)
jadmanskif6562912008-10-21 17:59:01 +0000795 self.ssh_ping()
Dean Liaoe3e75f62017-11-14 10:36:43 +0800796 logging.info("Host (ssh) %s is alive", self.host_port)
jadmanskif6562912008-10-21 17:59:01 +0000797
jadmanski80deb752009-01-21 17:14:16 +0000798 if self.is_shutting_down():
mblighc971c5f2009-06-08 16:48:54 +0000799 raise error.AutoservHostIsShuttingDownError("Host is shutting down")
jadmanski80deb752009-01-21 17:14:16 +0000800
mblighb49b5232009-02-12 21:54:49 +0000801
showardca572982009-09-18 21:20:01 +0000802 def verify_software(self):
803 super(AbstractSSHHost, self).verify_software()
jadmanskif6562912008-10-21 17:59:01 +0000804 try:
showardad812bf2009-10-20 23:49:56 +0000805 self.check_diskspace(autotest.Autotest.get_install_dir(self),
806 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
Keith Haddow07f1d3e2017-08-03 17:40:41 -0700807 except error.AutoservDiskFullHostError:
808 # only want to raise if it's a space issue
809 raise
810 except (error.AutoservHostError, autotest.AutodirNotFoundError):
Lutz Justen043e9c12017-10-27 12:40:47 +0200811 logging.exception('autodir space check exception, this is probably '
Keith Haddow07f1d3e2017-08-03 17:40:41 -0700812 'safe to ignore\n')
mblighefccc1b2010-01-11 19:08:42 +0000813
814
815 def close(self):
816 super(AbstractSSHHost, self).close()
Godofredo Contreras773179e2016-05-24 10:17:48 -0700817 self.rpc_server_tracker.disconnect_all()
Hidehiko Abe06893302017-06-24 07:32:38 +0900818 if not self._connection_pool:
819 self._master_ssh.close()
xixuand6011f12016-12-08 15:01:58 -0800820 if os.path.exists(self.known_hosts_file):
821 os.remove(self.known_hosts_file)
mblighefccc1b2010-01-11 19:08:42 +0000822
823
Luigi Semenzato3b95ede2016-12-09 11:51:01 -0800824 def restart_master_ssh(self):
825 """
826 Stop and restart the ssh master connection. This is meant as a last
827 resort when ssh commands fail and we don't understand why.
828 """
829 logging.debug('Restarting master ssh connection')
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900830 self._master_ssh.close()
831 self._master_ssh.maybe_start(timeout=30)
Luigi Semenzato3b95ede2016-12-09 11:51:01 -0800832
833
mblighefccc1b2010-01-11 19:08:42 +0000834
Prathmesh Prabhuf0507422018-08-28 15:51:45 -0700835 def start_master_ssh(self, timeout=DEFAULT_START_MASTER_SSH_TIMEOUT_S):
mblighefccc1b2010-01-11 19:08:42 +0000836 """
837 Called whenever a slave SSH connection needs to be initiated (e.g., by
838 run, rsync, scp). If master SSH support is enabled and a master SSH
839 connection is not active already, start a new one in the background.
840 Also, cleanup any zombie master SSH connections (e.g., dead due to
841 reboot).
Aviv Keshet0749a822013-10-17 09:53:26 -0700842
843 timeout: timeout in seconds (default 5) to wait for master ssh
844 connection to be established. If timeout is reached, a
845 warning message is logged, but no other action is taken.
mblighefccc1b2010-01-11 19:08:42 +0000846 """
847 if not enable_master_ssh:
848 return
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900849 self._master_ssh.maybe_start(timeout=timeout)
mbligh0a883702010-04-21 01:58:34 +0000850
851
852 def clear_known_hosts(self):
853 """Clears out the temporary ssh known_hosts file.
854
855 This is useful if the test SSHes to the machine, then reinstalls it,
856 then SSHes to it again. It can be called after the reinstall to
857 reduce the spam in the logs.
858 """
859 logging.info("Clearing known hosts for host '%s', file '%s'.",
Dean Liaoe3e75f62017-11-14 10:36:43 +0800860 self.host_port, self.known_hosts_file)
mbligh0a883702010-04-21 01:58:34 +0000861 # Clear out the file by opening it for writing and then closing.
Fang Deng3af66202013-08-16 15:19:25 -0700862 fh = open(self.known_hosts_file, "w")
mbligh0a883702010-04-21 01:58:34 +0000863 fh.close()
Prashanth B98509c72014-04-04 16:01:34 -0700864
865
866 def collect_logs(self, remote_src_dir, local_dest_dir, ignore_errors=True):
867 """Copy log directories from a host to a local directory.
868
869 @param remote_src_dir: A destination directory on the host.
870 @param local_dest_dir: A path to a local destination directory.
871 If it doesn't exist it will be created.
872 @param ignore_errors: If True, ignore exceptions.
873
874 @raises OSError: If there were problems creating the local_dest_dir and
875 ignore_errors is False.
876 @raises AutoservRunError, AutotestRunError: If something goes wrong
877 while copying the directories and ignore_errors is False.
878 """
Dan Shi9f92aa62017-07-27 17:07:05 -0700879 if not self.check_cached_up_status():
880 logging.warning('Host %s did not answer to ping, skip collecting '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800881 'logs.', self.host_port)
Dan Shi9f92aa62017-07-27 17:07:05 -0700882 return
883
Prashanth B98509c72014-04-04 16:01:34 -0700884 locally_created_dest = False
885 if (not os.path.exists(local_dest_dir)
886 or not os.path.isdir(local_dest_dir)):
887 try:
888 os.makedirs(local_dest_dir)
889 locally_created_dest = True
890 except OSError as e:
891 logging.warning('Unable to collect logs from host '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800892 '%s: %s', self.host_port, e)
Prashanth B98509c72014-04-04 16:01:34 -0700893 if not ignore_errors:
894 raise
895 return
Dan Shi4f8c0242017-07-07 15:34:49 -0700896
897 # Build test result directory summary
898 try:
899 result_tools_runner.run_on_client(self, remote_src_dir)
900 except (error.AutotestRunError, error.AutoservRunError,
901 error.AutoservSSHTimeout) as e:
902 logging.exception(
903 'Non-critical failure: Failed to collect and throttle '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800904 'results at %s from host %s', remote_src_dir,
905 self.host_port)
Dan Shi4f8c0242017-07-07 15:34:49 -0700906
Prashanth B98509c72014-04-04 16:01:34 -0700907 try:
Luigi Semenzato9b083072016-12-19 16:50:40 -0800908 self.get_file(remote_src_dir, local_dest_dir, safe_symlinks=True)
Prashanth B98509c72014-04-04 16:01:34 -0700909 except (error.AutotestRunError, error.AutoservRunError,
910 error.AutoservSSHTimeout) as e:
911 logging.warning('Collection of %s to local dir %s from host %s '
912 'failed: %s', remote_src_dir, local_dest_dir,
Dean Liaoe3e75f62017-11-14 10:36:43 +0800913 self.host_port, e)
Prashanth B98509c72014-04-04 16:01:34 -0700914 if locally_created_dest:
915 shutil.rmtree(local_dest_dir, ignore_errors=ignore_errors)
916 if not ignore_errors:
917 raise
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800918
Dan Shi4f8c0242017-07-07 15:34:49 -0700919 # Clean up directory summary file on the client side.
920 try:
921 result_tools_runner.run_on_client(self, remote_src_dir,
922 cleanup_only=True)
923 except (error.AutotestRunError, error.AutoservRunError,
924 error.AutoservSSHTimeout) as e:
925 logging.exception(
926 'Non-critical failure: Failed to cleanup result summary '
Lutz Justen043e9c12017-10-27 12:40:47 +0200927 'files at %s in host %s', remote_src_dir, self.hostname)
Dan Shi4f8c0242017-07-07 15:34:49 -0700928
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800929
xixuan6cf6d2f2016-01-29 15:29:00 -0800930 def create_ssh_tunnel(self, port, local_port):
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800931 """Create an ssh tunnel from local_port to port.
932
xixuan6cf6d2f2016-01-29 15:29:00 -0800933 This is used to forward a port securely through a tunnel process from
934 the server to the DUT for RPC server connection.
935
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800936 @param port: remote port on the host.
937 @param local_port: local forwarding port.
938
939 @return: the tunnel process.
940 """
941 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
Prathmesh Prabhu817b3f12017-07-31 17:08:41 -0700942 ssh_cmd = self.make_ssh_command(opts=tunnel_options, port=self.port)
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800943 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
944 logging.debug('Full tunnel command: %s', tunnel_cmd)
xixuan6cf6d2f2016-01-29 15:29:00 -0800945 # Exec the ssh process directly here rather than using a shell.
946 # Using a shell leaves a dangling ssh process, because we deliver
947 # signals to the shell wrapping ssh, not the ssh process itself.
948 args = shlex.split(tunnel_cmd)
Kuang-che Wu0ea03232019-08-31 10:52:31 +0800949 with open('/dev/null', 'w') as devnull:
950 tunnel_proc = subprocess.Popen(args, stdout=devnull, stderr=devnull,
951 close_fds=True)
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800952 logging.debug('Started ssh tunnel, local = %d'
953 ' remote = %d, pid = %d',
954 local_port, port, tunnel_proc.pid)
955 return tunnel_proc
Gilad Arnolda76bef02015-09-29 13:55:15 -0700956
957
xixuan6cf6d2f2016-01-29 15:29:00 -0800958 def disconnect_ssh_tunnel(self, tunnel_proc, port):
Roshan Pius58e5dd32015-10-16 15:16:42 -0700959 """
960 Disconnects a previously forwarded port from the server to the DUT for
961 RPC server connection.
962
xixuan6cf6d2f2016-01-29 15:29:00 -0800963 @param tunnel_proc: a tunnel process returned from |create_ssh_tunnel|.
964 @param port: remote port on the DUT, used in ADBHost.
Roshan Pius58e5dd32015-10-16 15:16:42 -0700965
966 """
967 if tunnel_proc.poll() is None:
968 tunnel_proc.terminate()
969 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
970 else:
971 logging.debug('Tunnel pid %d terminated early, status %d',
972 tunnel_proc.pid, tunnel_proc.returncode)
973
974
Gilad Arnolda76bef02015-09-29 13:55:15 -0700975 def get_os_type(self):
976 """Returns the host OS descriptor (to be implemented in subclasses).
977
978 @return A string describing the OS type.
979 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800980 raise NotImplementedError
Dan Shi9f92aa62017-07-27 17:07:05 -0700981
982
983 def check_cached_up_status(
984 self, expiration_seconds=_DEFAULT_UP_STATUS_EXPIRATION_SECONDS):
985 """Check if the DUT responded to ping in the past `expiration_seconds`.
986
987 @param expiration_seconds: The number of seconds to keep the cached
988 status of whether the DUT responded to ping.
989 @return: True if the DUT has responded to ping during the past
990 `expiration_seconds`.
991 """
992 # Refresh the up status if any of following conditions is true:
993 # * cached status is never set
994 # * cached status is False, so the method can check if the host is up
995 # again.
996 # * If the cached status is older than `expiration_seconds`
997 expire_time = time.time() - expiration_seconds
998 if (self._cached_up_status_updated is None or
999 not self._cached_up_status or
1000 self._cached_up_status_updated < expire_time):
1001 self._cached_up_status = self.is_up_fast()
1002 self._cached_up_status_updated = time.time()
1003 return self._cached_up_status