blob: 4b209c78c7570a1521fcfa7cf46571d4b6d9f333 [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,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800387 preserve_symlinks=False, retry=True, safe_symlinks=False):
jadmanskica7da372008-10-21 16:26:52 +0000388 """
389 Copy files from the remote host to a local path.
390
391 Directories will be copied recursively.
392 If a source component is a directory with a trailing slash,
393 the content of the directory will be copied, otherwise, the
394 directory itself and its content will be copied. This
395 behavior is similar to that of the program 'rsync'.
396
397 Args:
398 source: either
399 1) a single file or directory, as a string
400 2) a list of one or more (possibly mixed)
401 files or directories
402 dest: a file or a directory (if source contains a
403 directory or more than one element, you must
404 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000405 delete_dest: if this is true, the command will also clear
406 out any old files at dest that are not in the
407 source
mblighfeac0102009-04-28 18:31:12 +0000408 preserve_perm: tells get_file() to try to preserve the sources
409 permissions on files and dirs
mbligh45561782009-05-11 21:14:34 +0000410 preserve_symlinks: try to preserve symlinks instead of
411 transforming them into files/dirs on copy
Luigi Semenzato9b083072016-12-19 16:50:40 -0800412 safe_symlinks: same as preserve_symlinks, but discard links
413 that may point outside the copied tree
jadmanskica7da372008-10-21 16:26:52 +0000414 Raises:
415 AutoservRunError: the scp command failed
416 """
Simran Basi882f15b2013-10-29 14:59:34 -0700417 logging.debug('get_file. source: %s, dest: %s, delete_dest: %s,'
418 'preserve_perm: %s, preserve_symlinks:%s', source, dest,
419 delete_dest, preserve_perm, preserve_symlinks)
Dan Shi4f8c0242017-07-07 15:34:49 -0700420
mblighefccc1b2010-01-11 19:08:42 +0000421 # Start a master SSH connection if necessary.
422 self.start_master_ssh()
423
jadmanskica7da372008-10-21 16:26:52 +0000424 if isinstance(source, basestring):
425 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000426 dest = os.path.abspath(dest)
jadmanskica7da372008-10-21 16:26:52 +0000427
mblighc9892c02010-01-06 19:02:16 +0000428 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000429 try_scp = True
430 if self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700431 logging.debug('Using Rsync.')
mblighc9892c02010-01-06 19:02:16 +0000432 try:
433 remote_source = self._encode_remote_paths(source)
434 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800435 rsync = self._make_rsync_cmd(remote_source, local_dest,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800436 delete_dest, preserve_symlinks,
437 safe_symlinks)
mblighc9892c02010-01-06 19:02:16 +0000438 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000439 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000440 except error.CmdError, e:
Luigi Semenzato7f9dff12016-11-21 14:01:20 -0800441 # retry on rsync exit values which may be caused by transient
442 # network problems:
443 #
444 # rc 10: Error in socket I/O
445 # rc 12: Error in rsync protocol data stream
446 # rc 23: Partial transfer due to error
447 # rc 255: Ssh error
448 #
449 # Note that rc 23 includes dangling symlinks. In this case
450 # retrying is useless, but not very damaging since rsync checks
451 # for those before starting the transfer (scp does not).
452 status = e.result_obj.exit_status
453 if status in [10, 12, 23, 255] and retry:
454 logging.warning('rsync status %d, retrying', status)
455 self.get_file(source, dest, delete_dest, preserve_perm,
456 preserve_symlinks, retry=False)
457 # The nested get_file() does all that's needed.
458 return
459 else:
460 logging.warning("trying scp, rsync failed: %s (%d)",
461 e, status)
mblighc9892c02010-01-06 19:02:16 +0000462
463 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700464 logging.debug('Trying scp.')
jadmanskid7b79ed2009-01-07 17:19:48 +0000465 # scp has no equivalent to --delete, just drop the entire dest dir
466 if delete_dest and os.path.isdir(dest):
467 shutil.rmtree(dest)
468 os.mkdir(dest)
jadmanskica7da372008-10-21 16:26:52 +0000469
jadmanskid7b79ed2009-01-07 17:19:48 +0000470 remote_source = self._make_rsync_compatible_source(source, False)
471 if remote_source:
showard56176ec2009-10-28 19:52:30 +0000472 # _make_rsync_compatible_source() already did the escaping
Gwendal Grignou36b61702016-02-10 11:57:53 -0800473 remote_source = self._encode_remote_paths(
474 remote_source, escape=False, use_scp=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000475 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800476 scp = self._make_scp_cmd(remote_source, local_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000477 try:
478 utils.run(scp)
479 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700480 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000481 raise error.AutoservRunError(e.args[0], e.args[1])
jadmanskica7da372008-10-21 16:26:52 +0000482
mblighfeac0102009-04-28 18:31:12 +0000483 if not preserve_perm:
484 # we have no way to tell scp to not try to preserve the
485 # permissions so set them after copy instead.
486 # for rsync we could use "--no-p --chmod=ugo=rwX" but those
487 # options are only in very recent rsync versions
488 self._set_umask_perms(dest)
489
jadmanskica7da372008-10-21 16:26:52 +0000490
mbligh45561782009-05-11 21:14:34 +0000491 def send_file(self, source, dest, delete_dest=False,
Dan Shi92c34c92017-07-14 15:28:56 -0700492 preserve_symlinks=False, excludes=None):
jadmanskica7da372008-10-21 16:26:52 +0000493 """
494 Copy files from a local path to the remote host.
495
496 Directories will be copied recursively.
497 If a source component is a directory with a trailing slash,
498 the content of the directory will be copied, otherwise, the
499 directory itself and its content will be copied. This
500 behavior is similar to that of the program 'rsync'.
501
502 Args:
503 source: either
504 1) a single file or directory, as a string
505 2) a list of one or more (possibly mixed)
506 files or directories
507 dest: a file or a directory (if source contains a
508 directory or more than one element, you must
509 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000510 delete_dest: if this is true, the command will also clear
511 out any old files at dest that are not in the
512 source
mbligh45561782009-05-11 21:14:34 +0000513 preserve_symlinks: controls if symlinks on the source will be
514 copied as such on the destination or transformed into the
515 referenced file/directory
Dan Shi92c34c92017-07-14 15:28:56 -0700516 excludes: A list of file pattern that matches files not to be
517 sent. `send_file` will fail if exclude is set, since
518 local copy does not support --exclude, e.g., when
519 using scp to copy file.
jadmanskica7da372008-10-21 16:26:52 +0000520
521 Raises:
522 AutoservRunError: the scp command failed
523 """
Simran Basi882f15b2013-10-29 14:59:34 -0700524 logging.debug('send_file. source: %s, dest: %s, delete_dest: %s,'
525 'preserve_symlinks:%s', source, dest,
526 delete_dest, preserve_symlinks)
mblighefccc1b2010-01-11 19:08:42 +0000527 # Start a master SSH connection if necessary.
528 self.start_master_ssh()
529
jadmanskica7da372008-10-21 16:26:52 +0000530 if isinstance(source, basestring):
531 source = [source]
532
Gwendal Grignou36b61702016-02-10 11:57:53 -0800533 local_sources = self._encode_local_paths(source)
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700534 if not local_sources:
Gwendal Grignou36b61702016-02-10 11:57:53 -0800535 raise error.TestError('source |%s| yielded an empty string' % (
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700536 source))
Gwendal Grignou36b61702016-02-10 11:57:53 -0800537 if local_sources.find('\x00') != -1:
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700538 raise error.TestError('one or more sources include NUL char')
539
mblighc9892c02010-01-06 19:02:16 +0000540 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000541 try_scp = True
542 if self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700543 logging.debug('Using Rsync.')
Gwendal Grignou36b61702016-02-10 11:57:53 -0800544 remote_dest = self._encode_remote_paths([dest])
mblighc9892c02010-01-06 19:02:16 +0000545 try:
mblighc9892c02010-01-06 19:02:16 +0000546 rsync = self._make_rsync_cmd(local_sources, remote_dest,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800547 delete_dest, preserve_symlinks,
Dan Shi92c34c92017-07-14 15:28:56 -0700548 False, excludes=excludes)
mblighc9892c02010-01-06 19:02:16 +0000549 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000550 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000551 except error.CmdError, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700552 logging.warning("trying scp, rsync failed: %s", e)
mblighc9892c02010-01-06 19:02:16 +0000553
554 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700555 logging.debug('Trying scp.')
Dan Shi92c34c92017-07-14 15:28:56 -0700556 if excludes:
557 raise error.AutotestHostRunError(
558 '--exclude is not supported in scp, try to use rsync. '
Brian Norrisd7650482018-02-21 18:38:18 -0800559 'excludes: %s' % ','.join(excludes), None)
jadmanskid7b79ed2009-01-07 17:19:48 +0000560 # scp has no equivalent to --delete, just drop the entire dest dir
561 if delete_dest:
showard27160152009-07-15 14:28:42 +0000562 is_dir = self.run("ls -d %s/" % dest,
jadmanskid7b79ed2009-01-07 17:19:48 +0000563 ignore_status=True).exit_status == 0
564 if is_dir:
565 cmd = "rm -rf %s && mkdir %s"
mbligh5a0ca532009-08-03 16:44:34 +0000566 cmd %= (dest, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000567 self.run(cmd)
jadmanskica7da372008-10-21 16:26:52 +0000568
Gwendal Grignou36b61702016-02-10 11:57:53 -0800569 remote_dest = self._encode_remote_paths([dest], use_scp=True)
jadmanski2583a432009-02-10 23:59:11 +0000570 local_sources = self._make_rsync_compatible_source(source, True)
571 if local_sources:
Cheng-Yi Chiang9b2812d2016-02-29 17:01:44 +0800572 sources = self._encode_local_paths(local_sources, escape=False)
573 scp = self._make_scp_cmd(sources, remote_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000574 try:
575 utils.run(scp)
576 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700577 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000578 raise error.AutoservRunError(e.args[0], e.args[1])
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700579 else:
580 logging.debug('skipping scp for empty source list')
jadmanskid7b79ed2009-01-07 17:19:48 +0000581
jadmanskica7da372008-10-21 16:26:52 +0000582
Simran Basi1621c632015-10-14 12:22:23 -0700583 def verify_ssh_user_access(self):
584 """Verify ssh access to this host.
585
586 @returns False if ssh_ping fails due to Permissions error, True
587 otherwise.
588 """
589 try:
590 self.ssh_ping()
591 except (error.AutoservSshPermissionDeniedError,
592 error.AutoservSshPingHostError):
593 return False
594 return True
595
596
Luigi Semenzato135574c2016-08-31 17:25:08 -0700597 def ssh_ping(self, timeout=60, connect_timeout=None, base_cmd='true'):
beepsadd66d32013-03-04 17:21:51 -0800598 """
599 Pings remote host via ssh.
600
Philip Chen7ce1e392018-12-09 23:53:32 -0800601 @param timeout: Command execution timeout in seconds.
beepsadd66d32013-03-04 17:21:51 -0800602 Defaults to 60 seconds.
Philip Chen7ce1e392018-12-09 23:53:32 -0800603 @param connect_timeout: ssh connection timeout in seconds.
beeps46dadc92013-11-07 14:07:10 -0800604 @param base_cmd: The base command to run with the ssh ping.
605 Defaults to true.
beepsadd66d32013-03-04 17:21:51 -0800606 @raise AutoservSSHTimeout: If the ssh ping times out.
607 @raise AutoservSshPermissionDeniedError: If ssh ping fails due to
608 permissions.
609 @raise AutoservSshPingHostError: For other AutoservRunErrors.
610 """
Luigi Semenzato135574c2016-08-31 17:25:08 -0700611 ctimeout = min(timeout, connect_timeout or timeout)
jadmanskica7da372008-10-21 16:26:52 +0000612 try:
Allen Liad719c12017-06-27 23:48:04 +0000613 self.run(base_cmd, timeout=timeout, connect_timeout=ctimeout,
614 ssh_failure_retry_ok=True)
jadmanskica7da372008-10-21 16:26:52 +0000615 except error.AutoservSSHTimeout:
mblighd0e94982009-07-11 00:15:18 +0000616 msg = "Host (ssh) verify timed out (timeout = %d)" % timeout
jadmanskica7da372008-10-21 16:26:52 +0000617 raise error.AutoservSSHTimeout(msg)
mbligh9d738d62009-03-09 21:17:10 +0000618 except error.AutoservSshPermissionDeniedError:
Allen Liad719c12017-06-27 23:48:04 +0000619 #let AutoservSshPermissionDeniedError be visible to the callers
mbligh9d738d62009-03-09 21:17:10 +0000620 raise
jadmanskica7da372008-10-21 16:26:52 +0000621 except error.AutoservRunError, e:
mblighc971c5f2009-06-08 16:48:54 +0000622 # convert the generic AutoservRunError into something more
623 # specific for this context
624 raise error.AutoservSshPingHostError(e.description + '\n' +
625 repr(e.result_obj))
jadmanskica7da372008-10-21 16:26:52 +0000626
627
Luigi Semenzato135574c2016-08-31 17:25:08 -0700628 def is_up(self, timeout=60, connect_timeout=None, base_cmd='true'):
jadmanskica7da372008-10-21 16:26:52 +0000629 """
beeps46dadc92013-11-07 14:07:10 -0800630 Check if the remote host is up by ssh-ing and running a base command.
jadmanskica7da372008-10-21 16:26:52 +0000631
Philip Chen7ce1e392018-12-09 23:53:32 -0800632 @param timeout: command execution timeout in seconds.
633 @param connect_timeout: ssh connection timeout in seconds.
beeps46dadc92013-11-07 14:07:10 -0800634 @param base_cmd: a base command to run with ssh. The default is 'true'.
beepsadd66d32013-03-04 17:21:51 -0800635 @returns True if the remote host is up before the timeout expires,
636 False otherwise.
jadmanskica7da372008-10-21 16:26:52 +0000637 """
638 try:
Luigi Semenzato135574c2016-08-31 17:25:08 -0700639 self.ssh_ping(timeout=timeout,
640 connect_timeout=connect_timeout,
641 base_cmd=base_cmd)
jadmanskica7da372008-10-21 16:26:52 +0000642 except error.AutoservError:
643 return False
644 else:
645 return True
646
647
Dan Shi9f92aa62017-07-27 17:07:05 -0700648 def is_up_fast(self):
649 """Return True if the host can be pinged."""
650 ping_config = ping_runner.PingConfig(
651 self.hostname, count=3, ignore_result=True, ignore_status=True)
652 return ping_runner.PingRunner().ping(ping_config).received > 0
653
654
Philip Chen7ce1e392018-12-09 23:53:32 -0800655 def wait_up(self, timeout=_DEFAULT_WAIT_UP_TIME_SECONDS):
jadmanskica7da372008-10-21 16:26:52 +0000656 """
657 Wait until the remote host is up or the timeout expires.
658
659 In fact, it will wait until an ssh connection to the remote
660 host can be established, and getty is running.
661
jadmanskic0354912010-01-12 15:57:29 +0000662 @param timeout time limit in seconds before returning even
663 if the host is not up.
jadmanskica7da372008-10-21 16:26:52 +0000664
beepsadd66d32013-03-04 17:21:51 -0800665 @returns True if the host was found to be up before the timeout expires,
666 False otherwise
jadmanskica7da372008-10-21 16:26:52 +0000667 """
Philip Chen7ce1e392018-12-09 23:53:32 -0800668 current_time = int(time.time())
669 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000670
Luigi Semenzato135574c2016-08-31 17:25:08 -0700671 autoserv_error_logged = False
Philip Chen7ce1e392018-12-09 23:53:32 -0800672 while current_time < end_time:
673 ping_timeout = min(_DEFAULT_MAX_PING_TIMEOUT,
674 end_time - current_time)
675 if self.is_up(timeout=ping_timeout, connect_timeout=ping_timeout):
jadmanskica7da372008-10-21 16:26:52 +0000676 try:
677 if self.are_wait_up_processes_up():
Dean Liaoe3e75f62017-11-14 10:36:43 +0800678 logging.debug('Host %s is now up', self.host_port)
jadmanskica7da372008-10-21 16:26:52 +0000679 return True
Luigi Semenzato135574c2016-08-31 17:25:08 -0700680 except error.AutoservError as e:
681 if not autoserv_error_logged:
682 logging.debug('Ignoring failure to reach %s: %s %s',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800683 self.host_port, e,
Luigi Semenzato135574c2016-08-31 17:25:08 -0700684 '(and further similar failures)')
685 autoserv_error_logged = True
jadmanskica7da372008-10-21 16:26:52 +0000686 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800687 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000688
jadmanski7ebac3d2010-06-17 16:06:31 +0000689 logging.debug('Host %s is still down after waiting %d seconds',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800690 self.host_port, int(timeout + time.time() - end_time))
jadmanskica7da372008-10-21 16:26:52 +0000691 return False
692
693
Lutz Justen043e9c12017-10-27 12:40:47 +0200694 def wait_down(self, timeout=_DEFAULT_WAIT_DOWN_TIME_SECONDS,
695 warning_timer=None, old_boot_id=None,
696 max_ping_timeout=_DEFAULT_MAX_PING_TIMEOUT):
jadmanskica7da372008-10-21 16:26:52 +0000697 """
698 Wait until the remote host is down or the timeout expires.
699
Lutz Justen043e9c12017-10-27 12:40:47 +0200700 If old_boot_id is provided, waits until either the machine is
701 unpingable or self.get_boot_id() returns a value different from
jadmanskic0354912010-01-12 15:57:29 +0000702 old_boot_id. If the boot_id value has changed then the function
Lutz Justen043e9c12017-10-27 12:40:47 +0200703 returns True under the assumption that the machine has shut down
jadmanskic0354912010-01-12 15:57:29 +0000704 and has now already come back up.
jadmanskica7da372008-10-21 16:26:52 +0000705
jadmanskic0354912010-01-12 15:57:29 +0000706 If old_boot_id is None then until the machine becomes unreachable the
707 method assumes the machine has not yet shut down.
jadmanskica7da372008-10-21 16:26:52 +0000708
Lutz Justen043e9c12017-10-27 12:40:47 +0200709 @param timeout Time limit in seconds before returning even if the host
710 is still up.
711 @param warning_timer Time limit in seconds that will generate a warning
712 if the host is not down yet. Can be None for no warning.
jadmanskic0354912010-01-12 15:57:29 +0000713 @param old_boot_id A string containing the result of self.get_boot_id()
714 prior to the host being told to shut down. Can be None if this is
715 not available.
Lutz Justen043e9c12017-10-27 12:40:47 +0200716 @param max_ping_timeout Maximum timeout in seconds for each
717 self.get_boot_id() call. If this timeout is hit, it is assumed that
718 the host went down and became unreachable.
jadmanskic0354912010-01-12 15:57:29 +0000719
Lutz Justen043e9c12017-10-27 12:40:47 +0200720 @returns True if the host was found to be down (max_ping_timeout timeout
721 expired or boot_id changed if provided) and False if timeout
722 expired.
jadmanskica7da372008-10-21 16:26:52 +0000723 """
mblighe5e3cf22010-05-27 23:33:14 +0000724 #TODO: there is currently no way to distinguish between knowing
725 #TODO: boot_id was unsupported and not knowing the boot_id.
beeps46dadc92013-11-07 14:07:10 -0800726 current_time = int(time.time())
Lutz Justen043e9c12017-10-27 12:40:47 +0200727 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000728
mbligh2ed998f2009-04-08 21:03:47 +0000729 if warning_timer:
730 warn_time = current_time + warning_timer
731
jadmanskic0354912010-01-12 15:57:29 +0000732 if old_boot_id is not None:
733 logging.debug('Host %s pre-shutdown boot_id is %s',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800734 self.host_port, old_boot_id)
jadmanskic0354912010-01-12 15:57:29 +0000735
beepsadd66d32013-03-04 17:21:51 -0800736 # Impose semi real-time deadline constraints, since some clients
737 # (eg: watchdog timer tests) expect strict checking of time elapsed.
738 # Each iteration of this loop is treated as though it atomically
739 # completes within current_time, this is needed because if we used
740 # inline time.time() calls instead then the following could happen:
741 #
Lutz Justen043e9c12017-10-27 12:40:47 +0200742 # while time.time() < end_time: [23 < 30]
beepsadd66d32013-03-04 17:21:51 -0800743 # some code. [takes 10 secs]
744 # try:
745 # new_boot_id = self.get_boot_id(timeout=end_time - time.time())
746 # [30 - 33]
747 # The last step will lead to a return True, when in fact the machine
748 # went down at 32 seconds (>30). Hence we need to pass get_boot_id
749 # the same time that allowed us into that iteration of the loop.
Lutz Justen043e9c12017-10-27 12:40:47 +0200750 while current_time < end_time:
751 ping_timeout = min(end_time - current_time, max_ping_timeout)
jadmanskic0354912010-01-12 15:57:29 +0000752 try:
Lutz Justen043e9c12017-10-27 12:40:47 +0200753 new_boot_id = self.get_boot_id(timeout=ping_timeout)
mblighdbc7e4a2010-01-15 20:34:20 +0000754 except error.AutoservError:
jadmanskic0354912010-01-12 15:57:29 +0000755 logging.debug('Host %s is now unreachable over ssh, is down',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800756 self.host_port)
jadmanskica7da372008-10-21 16:26:52 +0000757 return True
jadmanskic0354912010-01-12 15:57:29 +0000758 else:
759 # if the machine is up but the boot_id value has changed from
760 # old boot id, then we can assume the machine has gone down
761 # and then already come back up
762 if old_boot_id is not None and old_boot_id != new_boot_id:
763 logging.debug('Host %s now has boot_id %s and so must '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800764 'have rebooted', self.host_port, new_boot_id)
jadmanskic0354912010-01-12 15:57:29 +0000765 return True
mbligh2ed998f2009-04-08 21:03:47 +0000766
767 if warning_timer and current_time > warn_time:
Scott Zawalskic86fdeb2013-10-23 10:24:04 -0400768 self.record("INFO", None, "shutdown",
mbligh2ed998f2009-04-08 21:03:47 +0000769 "Shutdown took longer than %ds" % warning_timer)
770 # Print the warning only once.
771 warning_timer = None
mbligha4464402009-04-17 20:13:41 +0000772 # If a machine is stuck switching runlevels
773 # This may cause the machine to reboot.
774 self.run('kill -HUP 1', ignore_status=True)
mbligh2ed998f2009-04-08 21:03:47 +0000775
jadmanskica7da372008-10-21 16:26:52 +0000776 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800777 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000778
779 return False
jadmanskif6562912008-10-21 17:59:01 +0000780
mbligha0a27592009-01-24 01:41:36 +0000781
jadmanskif6562912008-10-21 17:59:01 +0000782 # tunable constants for the verify & repair code
mblighb86bfa12010-02-12 20:22:21 +0000783 AUTOTEST_GB_DISKSPACE_REQUIRED = get_value("SERVER",
784 "gb_diskspace_required",
Fang Deng6b05f5b2013-03-20 13:42:11 -0700785 type=float,
786 default=20.0)
mbligha0a27592009-01-24 01:41:36 +0000787
jadmanskif6562912008-10-21 17:59:01 +0000788
showardca572982009-09-18 21:20:01 +0000789 def verify_connectivity(self):
790 super(AbstractSSHHost, self).verify_connectivity()
jadmanskif6562912008-10-21 17:59:01 +0000791
Dean Liaoe3e75f62017-11-14 10:36:43 +0800792 logging.info('Pinging host ' + self.host_port)
jadmanskif6562912008-10-21 17:59:01 +0000793 self.ssh_ping()
Dean Liaoe3e75f62017-11-14 10:36:43 +0800794 logging.info("Host (ssh) %s is alive", self.host_port)
jadmanskif6562912008-10-21 17:59:01 +0000795
jadmanski80deb752009-01-21 17:14:16 +0000796 if self.is_shutting_down():
mblighc971c5f2009-06-08 16:48:54 +0000797 raise error.AutoservHostIsShuttingDownError("Host is shutting down")
jadmanski80deb752009-01-21 17:14:16 +0000798
mblighb49b5232009-02-12 21:54:49 +0000799
showardca572982009-09-18 21:20:01 +0000800 def verify_software(self):
801 super(AbstractSSHHost, self).verify_software()
jadmanskif6562912008-10-21 17:59:01 +0000802 try:
showardad812bf2009-10-20 23:49:56 +0000803 self.check_diskspace(autotest.Autotest.get_install_dir(self),
804 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
Keith Haddow07f1d3e2017-08-03 17:40:41 -0700805 except error.AutoservDiskFullHostError:
806 # only want to raise if it's a space issue
807 raise
808 except (error.AutoservHostError, autotest.AutodirNotFoundError):
Lutz Justen043e9c12017-10-27 12:40:47 +0200809 logging.exception('autodir space check exception, this is probably '
Keith Haddow07f1d3e2017-08-03 17:40:41 -0700810 'safe to ignore\n')
mblighefccc1b2010-01-11 19:08:42 +0000811
812
813 def close(self):
814 super(AbstractSSHHost, self).close()
Godofredo Contreras773179e2016-05-24 10:17:48 -0700815 self.rpc_server_tracker.disconnect_all()
Hidehiko Abe06893302017-06-24 07:32:38 +0900816 if not self._connection_pool:
817 self._master_ssh.close()
xixuand6011f12016-12-08 15:01:58 -0800818 if os.path.exists(self.known_hosts_file):
819 os.remove(self.known_hosts_file)
mblighefccc1b2010-01-11 19:08:42 +0000820
821
Luigi Semenzato3b95ede2016-12-09 11:51:01 -0800822 def restart_master_ssh(self):
823 """
824 Stop and restart the ssh master connection. This is meant as a last
825 resort when ssh commands fail and we don't understand why.
826 """
827 logging.debug('Restarting master ssh connection')
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900828 self._master_ssh.close()
829 self._master_ssh.maybe_start(timeout=30)
Luigi Semenzato3b95ede2016-12-09 11:51:01 -0800830
831
mblighefccc1b2010-01-11 19:08:42 +0000832
Prathmesh Prabhuf0507422018-08-28 15:51:45 -0700833 def start_master_ssh(self, timeout=DEFAULT_START_MASTER_SSH_TIMEOUT_S):
mblighefccc1b2010-01-11 19:08:42 +0000834 """
835 Called whenever a slave SSH connection needs to be initiated (e.g., by
836 run, rsync, scp). If master SSH support is enabled and a master SSH
837 connection is not active already, start a new one in the background.
838 Also, cleanup any zombie master SSH connections (e.g., dead due to
839 reboot).
Aviv Keshet0749a822013-10-17 09:53:26 -0700840
841 timeout: timeout in seconds (default 5) to wait for master ssh
842 connection to be established. If timeout is reached, a
843 warning message is logged, but no other action is taken.
mblighefccc1b2010-01-11 19:08:42 +0000844 """
845 if not enable_master_ssh:
846 return
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900847 self._master_ssh.maybe_start(timeout=timeout)
mbligh0a883702010-04-21 01:58:34 +0000848
849
850 def clear_known_hosts(self):
851 """Clears out the temporary ssh known_hosts file.
852
853 This is useful if the test SSHes to the machine, then reinstalls it,
854 then SSHes to it again. It can be called after the reinstall to
855 reduce the spam in the logs.
856 """
857 logging.info("Clearing known hosts for host '%s', file '%s'.",
Dean Liaoe3e75f62017-11-14 10:36:43 +0800858 self.host_port, self.known_hosts_file)
mbligh0a883702010-04-21 01:58:34 +0000859 # Clear out the file by opening it for writing and then closing.
Fang Deng3af66202013-08-16 15:19:25 -0700860 fh = open(self.known_hosts_file, "w")
mbligh0a883702010-04-21 01:58:34 +0000861 fh.close()
Prashanth B98509c72014-04-04 16:01:34 -0700862
863
864 def collect_logs(self, remote_src_dir, local_dest_dir, ignore_errors=True):
865 """Copy log directories from a host to a local directory.
866
867 @param remote_src_dir: A destination directory on the host.
868 @param local_dest_dir: A path to a local destination directory.
869 If it doesn't exist it will be created.
870 @param ignore_errors: If True, ignore exceptions.
871
872 @raises OSError: If there were problems creating the local_dest_dir and
873 ignore_errors is False.
874 @raises AutoservRunError, AutotestRunError: If something goes wrong
875 while copying the directories and ignore_errors is False.
876 """
Dan Shi9f92aa62017-07-27 17:07:05 -0700877 if not self.check_cached_up_status():
878 logging.warning('Host %s did not answer to ping, skip collecting '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800879 'logs.', self.host_port)
Dan Shi9f92aa62017-07-27 17:07:05 -0700880 return
881
Prashanth B98509c72014-04-04 16:01:34 -0700882 locally_created_dest = False
883 if (not os.path.exists(local_dest_dir)
884 or not os.path.isdir(local_dest_dir)):
885 try:
886 os.makedirs(local_dest_dir)
887 locally_created_dest = True
888 except OSError as e:
889 logging.warning('Unable to collect logs from host '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800890 '%s: %s', self.host_port, e)
Prashanth B98509c72014-04-04 16:01:34 -0700891 if not ignore_errors:
892 raise
893 return
Dan Shi4f8c0242017-07-07 15:34:49 -0700894
895 # Build test result directory summary
896 try:
897 result_tools_runner.run_on_client(self, remote_src_dir)
898 except (error.AutotestRunError, error.AutoservRunError,
899 error.AutoservSSHTimeout) as e:
900 logging.exception(
901 'Non-critical failure: Failed to collect and throttle '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800902 'results at %s from host %s', remote_src_dir,
903 self.host_port)
Dan Shi4f8c0242017-07-07 15:34:49 -0700904
Prashanth B98509c72014-04-04 16:01:34 -0700905 try:
Luigi Semenzato9b083072016-12-19 16:50:40 -0800906 self.get_file(remote_src_dir, local_dest_dir, safe_symlinks=True)
Prashanth B98509c72014-04-04 16:01:34 -0700907 except (error.AutotestRunError, error.AutoservRunError,
908 error.AutoservSSHTimeout) as e:
909 logging.warning('Collection of %s to local dir %s from host %s '
910 'failed: %s', remote_src_dir, local_dest_dir,
Dean Liaoe3e75f62017-11-14 10:36:43 +0800911 self.host_port, e)
Prashanth B98509c72014-04-04 16:01:34 -0700912 if locally_created_dest:
913 shutil.rmtree(local_dest_dir, ignore_errors=ignore_errors)
914 if not ignore_errors:
915 raise
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800916
Dan Shi4f8c0242017-07-07 15:34:49 -0700917 # Clean up directory summary file on the client side.
918 try:
919 result_tools_runner.run_on_client(self, remote_src_dir,
920 cleanup_only=True)
921 except (error.AutotestRunError, error.AutoservRunError,
922 error.AutoservSSHTimeout) as e:
923 logging.exception(
924 'Non-critical failure: Failed to cleanup result summary '
Lutz Justen043e9c12017-10-27 12:40:47 +0200925 'files at %s in host %s', remote_src_dir, self.hostname)
Dan Shi4f8c0242017-07-07 15:34:49 -0700926
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800927
xixuan6cf6d2f2016-01-29 15:29:00 -0800928 def create_ssh_tunnel(self, port, local_port):
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800929 """Create an ssh tunnel from local_port to port.
930
xixuan6cf6d2f2016-01-29 15:29:00 -0800931 This is used to forward a port securely through a tunnel process from
932 the server to the DUT for RPC server connection.
933
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800934 @param port: remote port on the host.
935 @param local_port: local forwarding port.
936
937 @return: the tunnel process.
938 """
939 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
Prathmesh Prabhu817b3f12017-07-31 17:08:41 -0700940 ssh_cmd = self.make_ssh_command(opts=tunnel_options, port=self.port)
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800941 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
942 logging.debug('Full tunnel command: %s', tunnel_cmd)
xixuan6cf6d2f2016-01-29 15:29:00 -0800943 # Exec the ssh process directly here rather than using a shell.
944 # Using a shell leaves a dangling ssh process, because we deliver
945 # signals to the shell wrapping ssh, not the ssh process itself.
946 args = shlex.split(tunnel_cmd)
Kuang-che Wu0ea03232019-08-31 10:52:31 +0800947 with open('/dev/null', 'w') as devnull:
948 tunnel_proc = subprocess.Popen(args, stdout=devnull, stderr=devnull,
949 close_fds=True)
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800950 logging.debug('Started ssh tunnel, local = %d'
951 ' remote = %d, pid = %d',
952 local_port, port, tunnel_proc.pid)
953 return tunnel_proc
Gilad Arnolda76bef02015-09-29 13:55:15 -0700954
955
xixuan6cf6d2f2016-01-29 15:29:00 -0800956 def disconnect_ssh_tunnel(self, tunnel_proc, port):
Roshan Pius58e5dd32015-10-16 15:16:42 -0700957 """
958 Disconnects a previously forwarded port from the server to the DUT for
959 RPC server connection.
960
xixuan6cf6d2f2016-01-29 15:29:00 -0800961 @param tunnel_proc: a tunnel process returned from |create_ssh_tunnel|.
962 @param port: remote port on the DUT, used in ADBHost.
Roshan Pius58e5dd32015-10-16 15:16:42 -0700963
964 """
965 if tunnel_proc.poll() is None:
966 tunnel_proc.terminate()
967 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
968 else:
969 logging.debug('Tunnel pid %d terminated early, status %d',
970 tunnel_proc.pid, tunnel_proc.returncode)
971
972
Gilad Arnolda76bef02015-09-29 13:55:15 -0700973 def get_os_type(self):
974 """Returns the host OS descriptor (to be implemented in subclasses).
975
976 @return A string describing the OS type.
977 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800978 raise NotImplementedError
Dan Shi9f92aa62017-07-27 17:07:05 -0700979
980
981 def check_cached_up_status(
982 self, expiration_seconds=_DEFAULT_UP_STATUS_EXPIRATION_SECONDS):
983 """Check if the DUT responded to ping in the past `expiration_seconds`.
984
985 @param expiration_seconds: The number of seconds to keep the cached
986 status of whether the DUT responded to ping.
987 @return: True if the DUT has responded to ping during the past
988 `expiration_seconds`.
989 """
990 # Refresh the up status if any of following conditions is true:
991 # * cached status is never set
992 # * cached status is False, so the method can check if the host is up
993 # again.
994 # * If the cached status is older than `expiration_seconds`
995 expire_time = time.time() - expiration_seconds
996 if (self._cached_up_status_updated is None or
997 not self._cached_up_status or
998 self._cached_up_status_updated < expire_time):
999 self._cached_up_status = self.is_up_fast()
1000 self._cached_up_status_updated = time.time()
1001 return self._cached_up_status