blob: 9e3fe62030f316fca4e4ad6f4184869947edb56d [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
28# Timeout in seconds for a single call of get_boot_id() in wait_down().
29_DEFAULT_MAX_PING_TIMEOUT = 10
30
Fang Deng96667ca2013-08-01 17:46:18 -070031class AbstractSSHHost(remote.RemoteHost):
mblighbc9402b2009-12-29 01:15:34 +000032 """
33 This class represents a generic implementation of most of the
jadmanskica7da372008-10-21 16:26:52 +000034 framework necessary for controlling a host via ssh. It implements
35 almost all of the abstract Host methods, except for the core
mblighbc9402b2009-12-29 01:15:34 +000036 Host.run method.
37 """
Simran Basi5ace6f22016-01-06 17:30:44 -080038 VERSION_PREFIX = ''
jadmanskica7da372008-10-21 16:26:52 +000039
Dean Liaoe3e75f62017-11-14 10:36:43 +080040 def _initialize(self, hostname, user="root", port=_DEFAULT_SSH_PORT,
41 password="", is_client_install_supported=True,
42 afe_host=None, host_info_store=None, connection_pool=None,
Hidehiko Abe06893302017-06-24 07:32:38 +090043 *args, **dargs):
jadmanskif6562912008-10-21 17:59:01 +000044 super(AbstractSSHHost, self)._initialize(hostname=hostname,
45 *args, **dargs)
Kevin Cheng05ae2a42016-06-06 10:12:48 -070046 """
47 @param hostname: The hostname of the host.
48 @param user: The username to use when ssh'ing into the host.
49 @param password: The password to use when ssh'ing into the host.
50 @param port: The port to use for ssh.
51 @param is_client_install_supported: Boolean to indicate if we can
52 install autotest on the host.
53 @param afe_host: The host object attained from the AFE (get_hosts).
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080054 @param host_info_store: Optional host_info.CachingHostInfoStore object
55 to obtain / update host information.
Hidehiko Abe06893302017-06-24 07:32:38 +090056 @param connection_pool: ssh_multiplex.ConnectionPool instance to share
57 the master ssh connection across control scripts.
Kevin Cheng05ae2a42016-06-06 10:12:48 -070058 """
Dan Shic07b8932014-12-11 15:22:30 -080059 # IP address is retrieved only on demand. Otherwise the host
60 # initialization will fail for host is not online.
61 self._ip = None
jadmanskica7da372008-10-21 16:26:52 +000062 self.user = user
63 self.port = port
64 self.password = password
Roshan Piusa58163a2015-10-14 13:36:29 -070065 self._is_client_install_supported = is_client_install_supported
showard6eafb492010-01-15 20:29:06 +000066 self._use_rsync = None
Fang Deng3af66202013-08-16 15:19:25 -070067 self.known_hosts_file = tempfile.mkstemp()[1]
Roshan Pius58e5dd32015-10-16 15:16:42 -070068 self._rpc_server_tracker = rpc_server_tracker.RpcServerTracker(self);
jadmanskica7da372008-10-21 16:26:52 +000069
mblighefccc1b2010-01-11 19:08:42 +000070 """
71 Master SSH connection background job, socket temp directory and socket
72 control path option. If master-SSH is enabled, these fields will be
73 initialized by start_master_ssh when a new SSH connection is initiated.
74 """
Hidehiko Abe06893302017-06-24 07:32:38 +090075 self._connection_pool = connection_pool
76 if connection_pool:
77 self._master_ssh = connection_pool.get(hostname, user, port)
78 else:
79 self._master_ssh = ssh_multiplex.MasterSsh(hostname, user, port)
Simran Basi3b858a22015-03-17 16:23:24 -070080
Kevin Cheng05ae2a42016-06-06 10:12:48 -070081 self._afe_host = afe_host or utils.EmptyAFEHost()
Prathmesh Prabhu8b5065d2017-01-10 17:13:01 -080082 self.host_info_store = (host_info_store or
83 host_info.InMemoryHostInfoStore())
showard6eafb492010-01-15 20:29:06 +000084
Dan Shi9f92aa62017-07-27 17:07:05 -070085 # The cached status of whether the DUT responded to ping.
86 self._cached_up_status = None
87 # The timestamp when the value of _cached_up_status is set.
88 self._cached_up_status_updated = None
89
90
Dan Shic07b8932014-12-11 15:22:30 -080091 @property
92 def ip(self):
93 """@return IP address of the host.
94 """
95 if not self._ip:
96 self._ip = socket.getaddrinfo(self.hostname, None)[0][4][0]
97 return self._ip
98
99
Roshan Piusa58163a2015-10-14 13:36:29 -0700100 @property
101 def is_client_install_supported(self):
102 """"
103 Returns True if the host supports autotest client installs, False
104 otherwise.
105 """
106 return self._is_client_install_supported
107
108
Roshan Pius58e5dd32015-10-16 15:16:42 -0700109 @property
110 def rpc_server_tracker(self):
111 """"
112 @return The RPC server tracker associated with this host.
113 """
114 return self._rpc_server_tracker
115
116
Dean Liaoe3e75f62017-11-14 10:36:43 +0800117 @property
118 def is_default_port(self):
119 """Returns True if its port is default SSH port."""
120 return self.port == _DEFAULT_SSH_PORT
121
122 @property
123 def host_port(self):
124 """Returns hostname if port is default. Otherwise, hostname:port.
125 """
126 if self.is_default_port:
127 return self.hostname
128 else:
129 return '%s:%d' % (self.hostname, self.port)
130
131
132 # Though it doesn't use self here, it is not declared as staticmethod
133 # because its subclass may use self to access member variables.
134 def make_ssh_command(self, user="root", port=_DEFAULT_SSH_PORT, opts='',
135 hosts_file='/dev/null', connect_timeout=30,
136 alive_interval=300, alive_count_max=3,
137 connection_attempts=1):
138 ssh_options = " ".join([
139 opts,
140 self.make_ssh_options(
141 hosts_file=hosts_file, connect_timeout=connect_timeout,
142 alive_interval=alive_interval, alive_count_max=alive_count_max,
143 connection_attempts=connection_attempts)])
144 return "/usr/bin/ssh -a -x %s -l %s -p %d" % (ssh_options, user, port)
145
146
147 @staticmethod
148 def make_ssh_options(hosts_file='/dev/null', connect_timeout=30,
149 alive_interval=300, alive_count_max=3,
150 connection_attempts=1):
151 """Composes SSH -o options."""
Fang Deng96667ca2013-08-01 17:46:18 -0700152 assert isinstance(connect_timeout, (int, long))
153 assert connect_timeout > 0 # can't disable the timeout
Dean Liaoe3e75f62017-11-14 10:36:43 +0800154
155 options = [("StrictHostKeyChecking", "no"),
156 ("UserKnownHostsFile", hosts_file),
157 ("BatchMode", "yes"),
158 ("ConnectTimeout", str(connect_timeout)),
159 ("ServerAliveInterval", str(alive_interval)),
160 ("ServerAliveCountMax", str(alive_count_max)),
161 ("ConnectionAttempts", str(connection_attempts))]
162 return " ".join("-o %s=%s" % kv for kv in options)
Fang Deng96667ca2013-08-01 17:46:18 -0700163
164
showard6eafb492010-01-15 20:29:06 +0000165 def use_rsync(self):
166 if self._use_rsync is not None:
167 return self._use_rsync
168
mblighc9892c02010-01-06 19:02:16 +0000169 # Check if rsync is available on the remote host. If it's not,
170 # don't try to use it for any future file transfers.
Gwendal Grignou03286f02017-03-24 10:50:59 -0700171 self._use_rsync = self.check_rsync()
showard6eafb492010-01-15 20:29:06 +0000172 if not self._use_rsync:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700173 logging.warning("rsync not available on remote host %s -- disabled",
Dean Liaoe3e75f62017-11-14 10:36:43 +0800174 self.host_port)
Eric Lie0493a42010-11-15 13:05:43 -0800175 return self._use_rsync
mblighc9892c02010-01-06 19:02:16 +0000176
177
Gwendal Grignou03286f02017-03-24 10:50:59 -0700178 def check_rsync(self):
mblighc9892c02010-01-06 19:02:16 +0000179 """
180 Check if rsync is available on the remote host.
181 """
182 try:
Allen Liad719c12017-06-27 23:48:04 +0000183 self.run("rsync --version", stdout_tee=None, stderr_tee=None)
mblighc9892c02010-01-06 19:02:16 +0000184 except error.AutoservRunError:
185 return False
186 return True
187
jadmanskica7da372008-10-21 16:26:52 +0000188
Gwendal Grignou36b61702016-02-10 11:57:53 -0800189 def _encode_remote_paths(self, paths, escape=True, use_scp=False):
mblighbc9402b2009-12-29 01:15:34 +0000190 """
191 Given a list of file paths, encodes it as a single remote path, in
192 the style used by rsync and scp.
Gwendal Grignou36b61702016-02-10 11:57:53 -0800193 escape: add \\ to protect special characters.
194 use_scp: encode for scp if true, rsync if false.
mblighbc9402b2009-12-29 01:15:34 +0000195 """
showard56176ec2009-10-28 19:52:30 +0000196 if escape:
197 paths = [utils.scp_remote_escape(path) for path in paths]
Marc Herbert21eb6492015-11-13 15:48:53 -0800198
199 remote = self.hostname
200
201 # rsync and scp require IPv6 brackets, even when there isn't any
202 # trailing port number (ssh doesn't support IPv6 brackets).
203 # In the Python >= 3.3 future, 'import ipaddress' will parse addresses.
204 if re.search(r':.*:', remote):
205 remote = '[%s]' % remote
206
Gwendal Grignou36b61702016-02-10 11:57:53 -0800207 if use_scp:
208 return '%s@%s:"%s"' % (self.user, remote, " ".join(paths))
209 else:
210 return '%s@%s:%s' % (
211 self.user, remote,
212 " :".join('"%s"' % p for p in paths))
jadmanskica7da372008-10-21 16:26:52 +0000213
Gwendal Grignou36b61702016-02-10 11:57:53 -0800214 def _encode_local_paths(self, paths, escape=True):
215 """
216 Given a list of file paths, encodes it as a single local path.
217 escape: add \\ to protect special characters.
218 """
219 if escape:
220 paths = [utils.sh_escape(path) for path in paths]
221
222 return " ".join('"%s"' % p for p in paths)
jadmanskica7da372008-10-21 16:26:52 +0000223
Dean Liaoe3e75f62017-11-14 10:36:43 +0800224
225 def rsync_options(self, delete_dest=False, preserve_symlinks=False,
226 safe_symlinks=False, excludes=None):
227 """Obtains rsync options for the remote."""
Fang Deng96667ca2013-08-01 17:46:18 -0700228 ssh_cmd = self.make_ssh_command(user=self.user, port=self.port,
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900229 opts=self._master_ssh.ssh_option,
Fang Deng96667ca2013-08-01 17:46:18 -0700230 hosts_file=self.known_hosts_file)
jadmanskid7b79ed2009-01-07 17:19:48 +0000231 if delete_dest:
232 delete_flag = "--delete"
233 else:
234 delete_flag = ""
Luigi Semenzato9b083072016-12-19 16:50:40 -0800235 if safe_symlinks:
236 symlink_flag = "-l --safe-links"
237 elif preserve_symlinks:
238 symlink_flag = "-l"
mbligh45561782009-05-11 21:14:34 +0000239 else:
240 symlink_flag = "-L"
Dan Shi92c34c92017-07-14 15:28:56 -0700241 exclude_args = ''
242 if excludes:
243 exclude_args = ' '.join(
244 ["--exclude '%s'" % exclude for exclude in excludes])
Dean Liaoe3e75f62017-11-14 10:36:43 +0800245 return "%s %s --timeout=1800 --rsh='%s' -az --no-o --no-g %s" % (
246 symlink_flag, delete_flag, ssh_cmd, exclude_args)
247
248
249 def _make_rsync_cmd(self, sources, dest, delete_dest,
250 preserve_symlinks, safe_symlinks, excludes=None):
251 """
252 Given a string of source paths and a destination path, produces the
253 appropriate rsync command for copying them. Remote paths must be
254 pre-encoded.
255 """
256 rsync_options = self.rsync_options(
257 delete_dest=delete_dest, preserve_symlinks=preserve_symlinks,
258 safe_symlinks=safe_symlinks, excludes=excludes)
259 return 'rsync %s %s "%s"' % (rsync_options, sources, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000260
261
Eric Li861b2d52011-02-04 14:50:35 -0800262 def _make_ssh_cmd(self, cmd):
263 """
264 Create a base ssh command string for the host which can be used
265 to run commands directly on the machine
266 """
Fang Deng96667ca2013-08-01 17:46:18 -0700267 base_cmd = self.make_ssh_command(user=self.user, port=self.port,
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900268 opts=self._master_ssh.ssh_option,
Fang Deng96667ca2013-08-01 17:46:18 -0700269 hosts_file=self.known_hosts_file)
Eric Li861b2d52011-02-04 14:50:35 -0800270
271 return '%s %s "%s"' % (base_cmd, self.hostname, utils.sh_escape(cmd))
272
jadmanskid7b79ed2009-01-07 17:19:48 +0000273 def _make_scp_cmd(self, sources, dest):
mblighbc9402b2009-12-29 01:15:34 +0000274 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800275 Given a string of source paths and a destination path, produces the
jadmanskid7b79ed2009-01-07 17:19:48 +0000276 appropriate scp command for encoding it. Remote paths must be
mblighbc9402b2009-12-29 01:15:34 +0000277 pre-encoded.
278 """
mblighc0649d62010-01-15 18:15:58 +0000279 command = ("scp -rq %s -o StrictHostKeyChecking=no "
lmraf676f32010-02-04 03:36:26 +0000280 "-o UserKnownHostsFile=%s -P %d %s '%s'")
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900281 return command % (self._master_ssh.ssh_option, self.known_hosts_file,
Gwendal Grignou36b61702016-02-10 11:57:53 -0800282 self.port, sources, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000283
284
285 def _make_rsync_compatible_globs(self, path, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000286 """
287 Given an rsync-style path, returns a list of globbed paths
jadmanskid7b79ed2009-01-07 17:19:48 +0000288 that will hopefully provide equivalent behaviour for scp. Does not
289 support the full range of rsync pattern matching behaviour, only that
290 exposed in the get/send_file interface (trailing slashes).
291
292 The is_local param is flag indicating if the paths should be
mblighbc9402b2009-12-29 01:15:34 +0000293 interpreted as local or remote paths.
294 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000295
296 # non-trailing slash paths should just work
297 if len(path) == 0 or path[-1] != "/":
298 return [path]
299
300 # make a function to test if a pattern matches any files
301 if is_local:
showard56176ec2009-10-28 19:52:30 +0000302 def glob_matches_files(path, pattern):
303 return len(glob.glob(path + pattern)) > 0
jadmanskid7b79ed2009-01-07 17:19:48 +0000304 else:
showard56176ec2009-10-28 19:52:30 +0000305 def glob_matches_files(path, pattern):
306 result = self.run("ls \"%s\"%s" % (utils.sh_escape(path),
307 pattern),
308 stdout_tee=None, ignore_status=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000309 return result.exit_status == 0
310
311 # take a set of globs that cover all files, and see which are needed
312 patterns = ["*", ".[!.]*"]
showard56176ec2009-10-28 19:52:30 +0000313 patterns = [p for p in patterns if glob_matches_files(path, p)]
jadmanskid7b79ed2009-01-07 17:19:48 +0000314
315 # convert them into a set of paths suitable for the commandline
jadmanskid7b79ed2009-01-07 17:19:48 +0000316 if is_local:
showard56176ec2009-10-28 19:52:30 +0000317 return ["\"%s\"%s" % (utils.sh_escape(path), pattern)
318 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000319 else:
showard56176ec2009-10-28 19:52:30 +0000320 return [utils.scp_remote_escape(path) + pattern
321 for pattern in patterns]
jadmanskid7b79ed2009-01-07 17:19:48 +0000322
323
324 def _make_rsync_compatible_source(self, source, is_local):
mblighbc9402b2009-12-29 01:15:34 +0000325 """
326 Applies the same logic as _make_rsync_compatible_globs, but
jadmanskid7b79ed2009-01-07 17:19:48 +0000327 applies it to an entire list of sources, producing a new list of
mblighbc9402b2009-12-29 01:15:34 +0000328 sources, properly quoted.
329 """
jadmanskid7b79ed2009-01-07 17:19:48 +0000330 return sum((self._make_rsync_compatible_globs(path, is_local)
331 for path in source), [])
jadmanskica7da372008-10-21 16:26:52 +0000332
333
mblighfeac0102009-04-28 18:31:12 +0000334 def _set_umask_perms(self, dest):
mblighbc9402b2009-12-29 01:15:34 +0000335 """
336 Given a destination file/dir (recursively) set the permissions on
337 all the files and directories to the max allowed by running umask.
338 """
mblighfeac0102009-04-28 18:31:12 +0000339
340 # now this looks strange but I haven't found a way in Python to _just_
341 # get the umask, apparently the only option is to try to set it
342 umask = os.umask(0)
343 os.umask(umask)
344
345 max_privs = 0777 & ~umask
346
347 def set_file_privs(filename):
Chris Masone567d0d92011-12-19 09:38:30 -0800348 """Sets mode of |filename|. Assumes |filename| exists."""
349 file_stat = os.stat(filename)
mblighfeac0102009-04-28 18:31:12 +0000350
351 file_privs = max_privs
352 # if the original file permissions do not have at least one
353 # executable bit then do not set it anywhere
354 if not file_stat.st_mode & 0111:
355 file_privs &= ~0111
356
357 os.chmod(filename, file_privs)
358
359 # try a bottom-up walk so changes on directory permissions won't cut
360 # our access to the files/directories inside it
361 for root, dirs, files in os.walk(dest, topdown=False):
362 # when setting the privileges we emulate the chmod "X" behaviour
363 # that sets to execute only if it is a directory or any of the
364 # owner/group/other already has execute right
365 for dirname in dirs:
366 os.chmod(os.path.join(root, dirname), max_privs)
367
Chris Masone567d0d92011-12-19 09:38:30 -0800368 # Filter out broken symlinks as we go.
369 for filename in filter(os.path.exists, files):
mblighfeac0102009-04-28 18:31:12 +0000370 set_file_privs(os.path.join(root, filename))
371
372
373 # now set privs for the dest itself
374 if os.path.isdir(dest):
375 os.chmod(dest, max_privs)
376 else:
377 set_file_privs(dest)
378
379
mbligh45561782009-05-11 21:14:34 +0000380 def get_file(self, source, dest, delete_dest=False, preserve_perm=True,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800381 preserve_symlinks=False, retry=True, safe_symlinks=False):
jadmanskica7da372008-10-21 16:26:52 +0000382 """
383 Copy files from the remote host to a local path.
384
385 Directories will be copied recursively.
386 If a source component is a directory with a trailing slash,
387 the content of the directory will be copied, otherwise, the
388 directory itself and its content will be copied. This
389 behavior is similar to that of the program 'rsync'.
390
391 Args:
392 source: either
393 1) a single file or directory, as a string
394 2) a list of one or more (possibly mixed)
395 files or directories
396 dest: a file or a directory (if source contains a
397 directory or more than one element, you must
398 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000399 delete_dest: if this is true, the command will also clear
400 out any old files at dest that are not in the
401 source
mblighfeac0102009-04-28 18:31:12 +0000402 preserve_perm: tells get_file() to try to preserve the sources
403 permissions on files and dirs
mbligh45561782009-05-11 21:14:34 +0000404 preserve_symlinks: try to preserve symlinks instead of
405 transforming them into files/dirs on copy
Luigi Semenzato9b083072016-12-19 16:50:40 -0800406 safe_symlinks: same as preserve_symlinks, but discard links
407 that may point outside the copied tree
jadmanskica7da372008-10-21 16:26:52 +0000408 Raises:
409 AutoservRunError: the scp command failed
410 """
Simran Basi882f15b2013-10-29 14:59:34 -0700411 logging.debug('get_file. source: %s, dest: %s, delete_dest: %s,'
412 'preserve_perm: %s, preserve_symlinks:%s', source, dest,
413 delete_dest, preserve_perm, preserve_symlinks)
Dan Shi4f8c0242017-07-07 15:34:49 -0700414
mblighefccc1b2010-01-11 19:08:42 +0000415 # Start a master SSH connection if necessary.
416 self.start_master_ssh()
417
jadmanskica7da372008-10-21 16:26:52 +0000418 if isinstance(source, basestring):
419 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000420 dest = os.path.abspath(dest)
jadmanskica7da372008-10-21 16:26:52 +0000421
mblighc9892c02010-01-06 19:02:16 +0000422 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000423 try_scp = True
424 if self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700425 logging.debug('Using Rsync.')
mblighc9892c02010-01-06 19:02:16 +0000426 try:
427 remote_source = self._encode_remote_paths(source)
428 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800429 rsync = self._make_rsync_cmd(remote_source, local_dest,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800430 delete_dest, preserve_symlinks,
431 safe_symlinks)
mblighc9892c02010-01-06 19:02:16 +0000432 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000433 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000434 except error.CmdError, e:
Luigi Semenzato7f9dff12016-11-21 14:01:20 -0800435 # retry on rsync exit values which may be caused by transient
436 # network problems:
437 #
438 # rc 10: Error in socket I/O
439 # rc 12: Error in rsync protocol data stream
440 # rc 23: Partial transfer due to error
441 # rc 255: Ssh error
442 #
443 # Note that rc 23 includes dangling symlinks. In this case
444 # retrying is useless, but not very damaging since rsync checks
445 # for those before starting the transfer (scp does not).
446 status = e.result_obj.exit_status
447 if status in [10, 12, 23, 255] and retry:
448 logging.warning('rsync status %d, retrying', status)
449 self.get_file(source, dest, delete_dest, preserve_perm,
450 preserve_symlinks, retry=False)
451 # The nested get_file() does all that's needed.
452 return
453 else:
454 logging.warning("trying scp, rsync failed: %s (%d)",
455 e, status)
mblighc9892c02010-01-06 19:02:16 +0000456
457 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700458 logging.debug('Trying scp.')
jadmanskid7b79ed2009-01-07 17:19:48 +0000459 # scp has no equivalent to --delete, just drop the entire dest dir
460 if delete_dest and os.path.isdir(dest):
461 shutil.rmtree(dest)
462 os.mkdir(dest)
jadmanskica7da372008-10-21 16:26:52 +0000463
jadmanskid7b79ed2009-01-07 17:19:48 +0000464 remote_source = self._make_rsync_compatible_source(source, False)
465 if remote_source:
showard56176ec2009-10-28 19:52:30 +0000466 # _make_rsync_compatible_source() already did the escaping
Gwendal Grignou36b61702016-02-10 11:57:53 -0800467 remote_source = self._encode_remote_paths(
468 remote_source, escape=False, use_scp=True)
jadmanskid7b79ed2009-01-07 17:19:48 +0000469 local_dest = utils.sh_escape(dest)
Gwendal Grignou36b61702016-02-10 11:57:53 -0800470 scp = self._make_scp_cmd(remote_source, local_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000471 try:
472 utils.run(scp)
473 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700474 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000475 raise error.AutoservRunError(e.args[0], e.args[1])
jadmanskica7da372008-10-21 16:26:52 +0000476
mblighfeac0102009-04-28 18:31:12 +0000477 if not preserve_perm:
478 # we have no way to tell scp to not try to preserve the
479 # permissions so set them after copy instead.
480 # for rsync we could use "--no-p --chmod=ugo=rwX" but those
481 # options are only in very recent rsync versions
482 self._set_umask_perms(dest)
483
jadmanskica7da372008-10-21 16:26:52 +0000484
mbligh45561782009-05-11 21:14:34 +0000485 def send_file(self, source, dest, delete_dest=False,
Dan Shi92c34c92017-07-14 15:28:56 -0700486 preserve_symlinks=False, excludes=None):
jadmanskica7da372008-10-21 16:26:52 +0000487 """
488 Copy files from a local path to the remote host.
489
490 Directories will be copied recursively.
491 If a source component is a directory with a trailing slash,
492 the content of the directory will be copied, otherwise, the
493 directory itself and its content will be copied. This
494 behavior is similar to that of the program 'rsync'.
495
496 Args:
497 source: either
498 1) a single file or directory, as a string
499 2) a list of one or more (possibly mixed)
500 files or directories
501 dest: a file or a directory (if source contains a
502 directory or more than one element, you must
503 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000504 delete_dest: if this is true, the command will also clear
505 out any old files at dest that are not in the
506 source
mbligh45561782009-05-11 21:14:34 +0000507 preserve_symlinks: controls if symlinks on the source will be
508 copied as such on the destination or transformed into the
509 referenced file/directory
Dan Shi92c34c92017-07-14 15:28:56 -0700510 excludes: A list of file pattern that matches files not to be
511 sent. `send_file` will fail if exclude is set, since
512 local copy does not support --exclude, e.g., when
513 using scp to copy file.
jadmanskica7da372008-10-21 16:26:52 +0000514
515 Raises:
516 AutoservRunError: the scp command failed
517 """
Simran Basi882f15b2013-10-29 14:59:34 -0700518 logging.debug('send_file. source: %s, dest: %s, delete_dest: %s,'
519 'preserve_symlinks:%s', source, dest,
520 delete_dest, preserve_symlinks)
mblighefccc1b2010-01-11 19:08:42 +0000521 # Start a master SSH connection if necessary.
522 self.start_master_ssh()
523
jadmanskica7da372008-10-21 16:26:52 +0000524 if isinstance(source, basestring):
525 source = [source]
526
Gwendal Grignou36b61702016-02-10 11:57:53 -0800527 local_sources = self._encode_local_paths(source)
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700528 if not local_sources:
Gwendal Grignou36b61702016-02-10 11:57:53 -0800529 raise error.TestError('source |%s| yielded an empty string' % (
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700530 source))
Gwendal Grignou36b61702016-02-10 11:57:53 -0800531 if local_sources.find('\x00') != -1:
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700532 raise error.TestError('one or more sources include NUL char')
533
mblighc9892c02010-01-06 19:02:16 +0000534 # If rsync is disabled or fails, try scp.
showard6eafb492010-01-15 20:29:06 +0000535 try_scp = True
536 if self.use_rsync():
Simran Basi882f15b2013-10-29 14:59:34 -0700537 logging.debug('Using Rsync.')
Gwendal Grignou36b61702016-02-10 11:57:53 -0800538 remote_dest = self._encode_remote_paths([dest])
mblighc9892c02010-01-06 19:02:16 +0000539 try:
mblighc9892c02010-01-06 19:02:16 +0000540 rsync = self._make_rsync_cmd(local_sources, remote_dest,
Luigi Semenzato9b083072016-12-19 16:50:40 -0800541 delete_dest, preserve_symlinks,
Dan Shi92c34c92017-07-14 15:28:56 -0700542 False, excludes=excludes)
mblighc9892c02010-01-06 19:02:16 +0000543 utils.run(rsync)
showard6eafb492010-01-15 20:29:06 +0000544 try_scp = False
mblighc9892c02010-01-06 19:02:16 +0000545 except error.CmdError, e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700546 logging.warning("trying scp, rsync failed: %s", e)
mblighc9892c02010-01-06 19:02:16 +0000547
548 if try_scp:
Simran Basi882f15b2013-10-29 14:59:34 -0700549 logging.debug('Trying scp.')
Dan Shi92c34c92017-07-14 15:28:56 -0700550 if excludes:
551 raise error.AutotestHostRunError(
552 '--exclude is not supported in scp, try to use rsync. '
Dan Shid9be07a2017-08-18 09:51:45 -0700553 'excludes: %s' % ','.join(excludes))
jadmanskid7b79ed2009-01-07 17:19:48 +0000554 # scp has no equivalent to --delete, just drop the entire dest dir
555 if delete_dest:
showard27160152009-07-15 14:28:42 +0000556 is_dir = self.run("ls -d %s/" % dest,
jadmanskid7b79ed2009-01-07 17:19:48 +0000557 ignore_status=True).exit_status == 0
558 if is_dir:
559 cmd = "rm -rf %s && mkdir %s"
mbligh5a0ca532009-08-03 16:44:34 +0000560 cmd %= (dest, dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000561 self.run(cmd)
jadmanskica7da372008-10-21 16:26:52 +0000562
Gwendal Grignou36b61702016-02-10 11:57:53 -0800563 remote_dest = self._encode_remote_paths([dest], use_scp=True)
jadmanski2583a432009-02-10 23:59:11 +0000564 local_sources = self._make_rsync_compatible_source(source, True)
565 if local_sources:
Cheng-Yi Chiang9b2812d2016-02-29 17:01:44 +0800566 sources = self._encode_local_paths(local_sources, escape=False)
567 scp = self._make_scp_cmd(sources, remote_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000568 try:
569 utils.run(scp)
570 except error.CmdError, e:
Simran Basi882f15b2013-10-29 14:59:34 -0700571 logging.debug('scp failed: %s', e)
jadmanskid7b79ed2009-01-07 17:19:48 +0000572 raise error.AutoservRunError(e.args[0], e.args[1])
mukesh agrawal0d3616c2015-07-17 15:47:36 -0700573 else:
574 logging.debug('skipping scp for empty source list')
jadmanskid7b79ed2009-01-07 17:19:48 +0000575
jadmanskica7da372008-10-21 16:26:52 +0000576
Simran Basi1621c632015-10-14 12:22:23 -0700577 def verify_ssh_user_access(self):
578 """Verify ssh access to this host.
579
580 @returns False if ssh_ping fails due to Permissions error, True
581 otherwise.
582 """
583 try:
584 self.ssh_ping()
585 except (error.AutoservSshPermissionDeniedError,
586 error.AutoservSshPingHostError):
587 return False
588 return True
589
590
Luigi Semenzato135574c2016-08-31 17:25:08 -0700591 def ssh_ping(self, timeout=60, connect_timeout=None, base_cmd='true'):
beepsadd66d32013-03-04 17:21:51 -0800592 """
593 Pings remote host via ssh.
594
595 @param timeout: Time in seconds before giving up.
596 Defaults to 60 seconds.
beeps46dadc92013-11-07 14:07:10 -0800597 @param base_cmd: The base command to run with the ssh ping.
598 Defaults to true.
beepsadd66d32013-03-04 17:21:51 -0800599 @raise AutoservSSHTimeout: If the ssh ping times out.
600 @raise AutoservSshPermissionDeniedError: If ssh ping fails due to
601 permissions.
602 @raise AutoservSshPingHostError: For other AutoservRunErrors.
603 """
Luigi Semenzato135574c2016-08-31 17:25:08 -0700604 ctimeout = min(timeout, connect_timeout or timeout)
jadmanskica7da372008-10-21 16:26:52 +0000605 try:
Allen Liad719c12017-06-27 23:48:04 +0000606 self.run(base_cmd, timeout=timeout, connect_timeout=ctimeout,
607 ssh_failure_retry_ok=True)
jadmanskica7da372008-10-21 16:26:52 +0000608 except error.AutoservSSHTimeout:
mblighd0e94982009-07-11 00:15:18 +0000609 msg = "Host (ssh) verify timed out (timeout = %d)" % timeout
jadmanskica7da372008-10-21 16:26:52 +0000610 raise error.AutoservSSHTimeout(msg)
mbligh9d738d62009-03-09 21:17:10 +0000611 except error.AutoservSshPermissionDeniedError:
Allen Liad719c12017-06-27 23:48:04 +0000612 #let AutoservSshPermissionDeniedError be visible to the callers
mbligh9d738d62009-03-09 21:17:10 +0000613 raise
jadmanskica7da372008-10-21 16:26:52 +0000614 except error.AutoservRunError, e:
mblighc971c5f2009-06-08 16:48:54 +0000615 # convert the generic AutoservRunError into something more
616 # specific for this context
617 raise error.AutoservSshPingHostError(e.description + '\n' +
618 repr(e.result_obj))
jadmanskica7da372008-10-21 16:26:52 +0000619
620
Luigi Semenzato135574c2016-08-31 17:25:08 -0700621 def is_up(self, timeout=60, connect_timeout=None, base_cmd='true'):
jadmanskica7da372008-10-21 16:26:52 +0000622 """
beeps46dadc92013-11-07 14:07:10 -0800623 Check if the remote host is up by ssh-ing and running a base command.
jadmanskica7da372008-10-21 16:26:52 +0000624
beepsadd66d32013-03-04 17:21:51 -0800625 @param timeout: timeout in seconds.
beeps46dadc92013-11-07 14:07:10 -0800626 @param base_cmd: a base command to run with ssh. The default is 'true'.
beepsadd66d32013-03-04 17:21:51 -0800627 @returns True if the remote host is up before the timeout expires,
628 False otherwise.
jadmanskica7da372008-10-21 16:26:52 +0000629 """
630 try:
Luigi Semenzato135574c2016-08-31 17:25:08 -0700631 self.ssh_ping(timeout=timeout,
632 connect_timeout=connect_timeout,
633 base_cmd=base_cmd)
jadmanskica7da372008-10-21 16:26:52 +0000634 except error.AutoservError:
635 return False
636 else:
637 return True
638
639
Dan Shi9f92aa62017-07-27 17:07:05 -0700640 def is_up_fast(self):
641 """Return True if the host can be pinged."""
642 ping_config = ping_runner.PingConfig(
643 self.hostname, count=3, ignore_result=True, ignore_status=True)
644 return ping_runner.PingRunner().ping(ping_config).received > 0
645
646
jadmanskica7da372008-10-21 16:26:52 +0000647 def wait_up(self, timeout=None):
648 """
649 Wait until the remote host is up or the timeout expires.
650
651 In fact, it will wait until an ssh connection to the remote
652 host can be established, and getty is running.
653
jadmanskic0354912010-01-12 15:57:29 +0000654 @param timeout time limit in seconds before returning even
655 if the host is not up.
jadmanskica7da372008-10-21 16:26:52 +0000656
beepsadd66d32013-03-04 17:21:51 -0800657 @returns True if the host was found to be up before the timeout expires,
658 False otherwise
jadmanskica7da372008-10-21 16:26:52 +0000659 """
660 if timeout:
beeps46dadc92013-11-07 14:07:10 -0800661 current_time = int(time.time())
662 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000663
Luigi Semenzato135574c2016-08-31 17:25:08 -0700664 autoserv_error_logged = False
beepsadd66d32013-03-04 17:21:51 -0800665 while not timeout or current_time < end_time:
Luigi Semenzato135574c2016-08-31 17:25:08 -0700666 if self.is_up(timeout=end_time - current_time,
667 connect_timeout=20):
jadmanskica7da372008-10-21 16:26:52 +0000668 try:
669 if self.are_wait_up_processes_up():
Dean Liaoe3e75f62017-11-14 10:36:43 +0800670 logging.debug('Host %s is now up', self.host_port)
jadmanskica7da372008-10-21 16:26:52 +0000671 return True
Luigi Semenzato135574c2016-08-31 17:25:08 -0700672 except error.AutoservError as e:
673 if not autoserv_error_logged:
674 logging.debug('Ignoring failure to reach %s: %s %s',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800675 self.host_port, e,
Luigi Semenzato135574c2016-08-31 17:25:08 -0700676 '(and further similar failures)')
677 autoserv_error_logged = True
jadmanskica7da372008-10-21 16:26:52 +0000678 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800679 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000680
jadmanski7ebac3d2010-06-17 16:06:31 +0000681 logging.debug('Host %s is still down after waiting %d seconds',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800682 self.host_port, int(timeout + time.time() - end_time))
jadmanskica7da372008-10-21 16:26:52 +0000683 return False
684
685
Lutz Justen043e9c12017-10-27 12:40:47 +0200686 def wait_down(self, timeout=_DEFAULT_WAIT_DOWN_TIME_SECONDS,
687 warning_timer=None, old_boot_id=None,
688 max_ping_timeout=_DEFAULT_MAX_PING_TIMEOUT):
jadmanskica7da372008-10-21 16:26:52 +0000689 """
690 Wait until the remote host is down or the timeout expires.
691
Lutz Justen043e9c12017-10-27 12:40:47 +0200692 If old_boot_id is provided, waits until either the machine is
693 unpingable or self.get_boot_id() returns a value different from
jadmanskic0354912010-01-12 15:57:29 +0000694 old_boot_id. If the boot_id value has changed then the function
Lutz Justen043e9c12017-10-27 12:40:47 +0200695 returns True under the assumption that the machine has shut down
jadmanskic0354912010-01-12 15:57:29 +0000696 and has now already come back up.
jadmanskica7da372008-10-21 16:26:52 +0000697
jadmanskic0354912010-01-12 15:57:29 +0000698 If old_boot_id is None then until the machine becomes unreachable the
699 method assumes the machine has not yet shut down.
jadmanskica7da372008-10-21 16:26:52 +0000700
Lutz Justen043e9c12017-10-27 12:40:47 +0200701 @param timeout Time limit in seconds before returning even if the host
702 is still up.
703 @param warning_timer Time limit in seconds that will generate a warning
704 if the host is not down yet. Can be None for no warning.
jadmanskic0354912010-01-12 15:57:29 +0000705 @param old_boot_id A string containing the result of self.get_boot_id()
706 prior to the host being told to shut down. Can be None if this is
707 not available.
Lutz Justen043e9c12017-10-27 12:40:47 +0200708 @param max_ping_timeout Maximum timeout in seconds for each
709 self.get_boot_id() call. If this timeout is hit, it is assumed that
710 the host went down and became unreachable.
jadmanskic0354912010-01-12 15:57:29 +0000711
Lutz Justen043e9c12017-10-27 12:40:47 +0200712 @returns True if the host was found to be down (max_ping_timeout timeout
713 expired or boot_id changed if provided) and False if timeout
714 expired.
jadmanskica7da372008-10-21 16:26:52 +0000715 """
mblighe5e3cf22010-05-27 23:33:14 +0000716 #TODO: there is currently no way to distinguish between knowing
717 #TODO: boot_id was unsupported and not knowing the boot_id.
beeps46dadc92013-11-07 14:07:10 -0800718 current_time = int(time.time())
Lutz Justen043e9c12017-10-27 12:40:47 +0200719 end_time = current_time + timeout
jadmanskica7da372008-10-21 16:26:52 +0000720
mbligh2ed998f2009-04-08 21:03:47 +0000721 if warning_timer:
722 warn_time = current_time + warning_timer
723
jadmanskic0354912010-01-12 15:57:29 +0000724 if old_boot_id is not None:
725 logging.debug('Host %s pre-shutdown boot_id is %s',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800726 self.host_port, old_boot_id)
jadmanskic0354912010-01-12 15:57:29 +0000727
beepsadd66d32013-03-04 17:21:51 -0800728 # Impose semi real-time deadline constraints, since some clients
729 # (eg: watchdog timer tests) expect strict checking of time elapsed.
730 # Each iteration of this loop is treated as though it atomically
731 # completes within current_time, this is needed because if we used
732 # inline time.time() calls instead then the following could happen:
733 #
Lutz Justen043e9c12017-10-27 12:40:47 +0200734 # while time.time() < end_time: [23 < 30]
beepsadd66d32013-03-04 17:21:51 -0800735 # some code. [takes 10 secs]
736 # try:
737 # new_boot_id = self.get_boot_id(timeout=end_time - time.time())
738 # [30 - 33]
739 # The last step will lead to a return True, when in fact the machine
740 # went down at 32 seconds (>30). Hence we need to pass get_boot_id
741 # the same time that allowed us into that iteration of the loop.
Lutz Justen043e9c12017-10-27 12:40:47 +0200742 while current_time < end_time:
743 ping_timeout = min(end_time - current_time, max_ping_timeout)
jadmanskic0354912010-01-12 15:57:29 +0000744 try:
Lutz Justen043e9c12017-10-27 12:40:47 +0200745 new_boot_id = self.get_boot_id(timeout=ping_timeout)
mblighdbc7e4a2010-01-15 20:34:20 +0000746 except error.AutoservError:
jadmanskic0354912010-01-12 15:57:29 +0000747 logging.debug('Host %s is now unreachable over ssh, is down',
Dean Liaoe3e75f62017-11-14 10:36:43 +0800748 self.host_port)
jadmanskica7da372008-10-21 16:26:52 +0000749 return True
jadmanskic0354912010-01-12 15:57:29 +0000750 else:
751 # if the machine is up but the boot_id value has changed from
752 # old boot id, then we can assume the machine has gone down
753 # and then already come back up
754 if old_boot_id is not None and old_boot_id != new_boot_id:
755 logging.debug('Host %s now has boot_id %s and so must '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800756 'have rebooted', self.host_port, new_boot_id)
jadmanskic0354912010-01-12 15:57:29 +0000757 return True
mbligh2ed998f2009-04-08 21:03:47 +0000758
759 if warning_timer and current_time > warn_time:
Scott Zawalskic86fdeb2013-10-23 10:24:04 -0400760 self.record("INFO", None, "shutdown",
mbligh2ed998f2009-04-08 21:03:47 +0000761 "Shutdown took longer than %ds" % warning_timer)
762 # Print the warning only once.
763 warning_timer = None
mbligha4464402009-04-17 20:13:41 +0000764 # If a machine is stuck switching runlevels
765 # This may cause the machine to reboot.
766 self.run('kill -HUP 1', ignore_status=True)
mbligh2ed998f2009-04-08 21:03:47 +0000767
jadmanskica7da372008-10-21 16:26:52 +0000768 time.sleep(1)
beeps46dadc92013-11-07 14:07:10 -0800769 current_time = int(time.time())
jadmanskica7da372008-10-21 16:26:52 +0000770
771 return False
jadmanskif6562912008-10-21 17:59:01 +0000772
mbligha0a27592009-01-24 01:41:36 +0000773
jadmanskif6562912008-10-21 17:59:01 +0000774 # tunable constants for the verify & repair code
mblighb86bfa12010-02-12 20:22:21 +0000775 AUTOTEST_GB_DISKSPACE_REQUIRED = get_value("SERVER",
776 "gb_diskspace_required",
Fang Deng6b05f5b2013-03-20 13:42:11 -0700777 type=float,
778 default=20.0)
mbligha0a27592009-01-24 01:41:36 +0000779
jadmanskif6562912008-10-21 17:59:01 +0000780
showardca572982009-09-18 21:20:01 +0000781 def verify_connectivity(self):
782 super(AbstractSSHHost, self).verify_connectivity()
jadmanskif6562912008-10-21 17:59:01 +0000783
Dean Liaoe3e75f62017-11-14 10:36:43 +0800784 logging.info('Pinging host ' + self.host_port)
jadmanskif6562912008-10-21 17:59:01 +0000785 self.ssh_ping()
Dean Liaoe3e75f62017-11-14 10:36:43 +0800786 logging.info("Host (ssh) %s is alive", self.host_port)
jadmanskif6562912008-10-21 17:59:01 +0000787
jadmanski80deb752009-01-21 17:14:16 +0000788 if self.is_shutting_down():
mblighc971c5f2009-06-08 16:48:54 +0000789 raise error.AutoservHostIsShuttingDownError("Host is shutting down")
jadmanski80deb752009-01-21 17:14:16 +0000790
mblighb49b5232009-02-12 21:54:49 +0000791
showardca572982009-09-18 21:20:01 +0000792 def verify_software(self):
793 super(AbstractSSHHost, self).verify_software()
jadmanskif6562912008-10-21 17:59:01 +0000794 try:
showardad812bf2009-10-20 23:49:56 +0000795 self.check_diskspace(autotest.Autotest.get_install_dir(self),
796 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
Keith Haddow07f1d3e2017-08-03 17:40:41 -0700797 except error.AutoservDiskFullHostError:
798 # only want to raise if it's a space issue
799 raise
800 except (error.AutoservHostError, autotest.AutodirNotFoundError):
Lutz Justen043e9c12017-10-27 12:40:47 +0200801 logging.exception('autodir space check exception, this is probably '
Keith Haddow07f1d3e2017-08-03 17:40:41 -0700802 'safe to ignore\n')
mblighefccc1b2010-01-11 19:08:42 +0000803
804
805 def close(self):
806 super(AbstractSSHHost, self).close()
Godofredo Contreras773179e2016-05-24 10:17:48 -0700807 self.rpc_server_tracker.disconnect_all()
Hidehiko Abe06893302017-06-24 07:32:38 +0900808 if not self._connection_pool:
809 self._master_ssh.close()
xixuand6011f12016-12-08 15:01:58 -0800810 if os.path.exists(self.known_hosts_file):
811 os.remove(self.known_hosts_file)
mblighefccc1b2010-01-11 19:08:42 +0000812
813
Luigi Semenzato3b95ede2016-12-09 11:51:01 -0800814 def restart_master_ssh(self):
815 """
816 Stop and restart the ssh master connection. This is meant as a last
817 resort when ssh commands fail and we don't understand why.
818 """
819 logging.debug('Restarting master ssh connection')
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900820 self._master_ssh.close()
821 self._master_ssh.maybe_start(timeout=30)
Luigi Semenzato3b95ede2016-12-09 11:51:01 -0800822
823
mblighefccc1b2010-01-11 19:08:42 +0000824
Aviv Keshet0749a822013-10-17 09:53:26 -0700825 def start_master_ssh(self, timeout=5):
mblighefccc1b2010-01-11 19:08:42 +0000826 """
827 Called whenever a slave SSH connection needs to be initiated (e.g., by
828 run, rsync, scp). If master SSH support is enabled and a master SSH
829 connection is not active already, start a new one in the background.
830 Also, cleanup any zombie master SSH connections (e.g., dead due to
831 reboot).
Aviv Keshet0749a822013-10-17 09:53:26 -0700832
833 timeout: timeout in seconds (default 5) to wait for master ssh
834 connection to be established. If timeout is reached, a
835 warning message is logged, but no other action is taken.
mblighefccc1b2010-01-11 19:08:42 +0000836 """
837 if not enable_master_ssh:
838 return
Hidehiko Abe28422ed2017-06-21 10:50:44 +0900839 self._master_ssh.maybe_start(timeout=timeout)
mbligh0a883702010-04-21 01:58:34 +0000840
841
842 def clear_known_hosts(self):
843 """Clears out the temporary ssh known_hosts file.
844
845 This is useful if the test SSHes to the machine, then reinstalls it,
846 then SSHes to it again. It can be called after the reinstall to
847 reduce the spam in the logs.
848 """
849 logging.info("Clearing known hosts for host '%s', file '%s'.",
Dean Liaoe3e75f62017-11-14 10:36:43 +0800850 self.host_port, self.known_hosts_file)
mbligh0a883702010-04-21 01:58:34 +0000851 # Clear out the file by opening it for writing and then closing.
Fang Deng3af66202013-08-16 15:19:25 -0700852 fh = open(self.known_hosts_file, "w")
mbligh0a883702010-04-21 01:58:34 +0000853 fh.close()
Prashanth B98509c72014-04-04 16:01:34 -0700854
855
856 def collect_logs(self, remote_src_dir, local_dest_dir, ignore_errors=True):
857 """Copy log directories from a host to a local directory.
858
859 @param remote_src_dir: A destination directory on the host.
860 @param local_dest_dir: A path to a local destination directory.
861 If it doesn't exist it will be created.
862 @param ignore_errors: If True, ignore exceptions.
863
864 @raises OSError: If there were problems creating the local_dest_dir and
865 ignore_errors is False.
866 @raises AutoservRunError, AutotestRunError: If something goes wrong
867 while copying the directories and ignore_errors is False.
868 """
Dan Shi9f92aa62017-07-27 17:07:05 -0700869 if not self.check_cached_up_status():
870 logging.warning('Host %s did not answer to ping, skip collecting '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800871 'logs.', self.host_port)
Dan Shi9f92aa62017-07-27 17:07:05 -0700872 return
873
Prashanth B98509c72014-04-04 16:01:34 -0700874 locally_created_dest = False
875 if (not os.path.exists(local_dest_dir)
876 or not os.path.isdir(local_dest_dir)):
877 try:
878 os.makedirs(local_dest_dir)
879 locally_created_dest = True
880 except OSError as e:
881 logging.warning('Unable to collect logs from host '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800882 '%s: %s', self.host_port, e)
Prashanth B98509c72014-04-04 16:01:34 -0700883 if not ignore_errors:
884 raise
885 return
Dan Shi4f8c0242017-07-07 15:34:49 -0700886
887 # Build test result directory summary
888 try:
889 result_tools_runner.run_on_client(self, remote_src_dir)
890 except (error.AutotestRunError, error.AutoservRunError,
891 error.AutoservSSHTimeout) as e:
892 logging.exception(
893 'Non-critical failure: Failed to collect and throttle '
Dean Liaoe3e75f62017-11-14 10:36:43 +0800894 'results at %s from host %s', remote_src_dir,
895 self.host_port)
Dan Shi4f8c0242017-07-07 15:34:49 -0700896
Prashanth B98509c72014-04-04 16:01:34 -0700897 try:
Luigi Semenzato9b083072016-12-19 16:50:40 -0800898 self.get_file(remote_src_dir, local_dest_dir, safe_symlinks=True)
Prashanth B98509c72014-04-04 16:01:34 -0700899 except (error.AutotestRunError, error.AutoservRunError,
900 error.AutoservSSHTimeout) as e:
901 logging.warning('Collection of %s to local dir %s from host %s '
902 'failed: %s', remote_src_dir, local_dest_dir,
Dean Liaoe3e75f62017-11-14 10:36:43 +0800903 self.host_port, e)
Prashanth B98509c72014-04-04 16:01:34 -0700904 if locally_created_dest:
905 shutil.rmtree(local_dest_dir, ignore_errors=ignore_errors)
906 if not ignore_errors:
907 raise
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800908
Dan Shi4f8c0242017-07-07 15:34:49 -0700909 # Clean up directory summary file on the client side.
910 try:
911 result_tools_runner.run_on_client(self, remote_src_dir,
912 cleanup_only=True)
913 except (error.AutotestRunError, error.AutoservRunError,
914 error.AutoservSSHTimeout) as e:
915 logging.exception(
916 'Non-critical failure: Failed to cleanup result summary '
Lutz Justen043e9c12017-10-27 12:40:47 +0200917 'files at %s in host %s', remote_src_dir, self.hostname)
Dan Shi4f8c0242017-07-07 15:34:49 -0700918
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800919
xixuan6cf6d2f2016-01-29 15:29:00 -0800920 def create_ssh_tunnel(self, port, local_port):
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800921 """Create an ssh tunnel from local_port to port.
922
xixuan6cf6d2f2016-01-29 15:29:00 -0800923 This is used to forward a port securely through a tunnel process from
924 the server to the DUT for RPC server connection.
925
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800926 @param port: remote port on the host.
927 @param local_port: local forwarding port.
928
929 @return: the tunnel process.
930 """
931 tunnel_options = '-n -N -q -L %d:localhost:%d' % (local_port, port)
Prathmesh Prabhu817b3f12017-07-31 17:08:41 -0700932 ssh_cmd = self.make_ssh_command(opts=tunnel_options, port=self.port)
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800933 tunnel_cmd = '%s %s' % (ssh_cmd, self.hostname)
934 logging.debug('Full tunnel command: %s', tunnel_cmd)
xixuan6cf6d2f2016-01-29 15:29:00 -0800935 # Exec the ssh process directly here rather than using a shell.
936 # Using a shell leaves a dangling ssh process, because we deliver
937 # signals to the shell wrapping ssh, not the ssh process itself.
938 args = shlex.split(tunnel_cmd)
939 tunnel_proc = subprocess.Popen(args, close_fds=True)
Cheng-Yi Chianga155e7e2015-08-20 20:42:04 +0800940 logging.debug('Started ssh tunnel, local = %d'
941 ' remote = %d, pid = %d',
942 local_port, port, tunnel_proc.pid)
943 return tunnel_proc
Gilad Arnolda76bef02015-09-29 13:55:15 -0700944
945
xixuan6cf6d2f2016-01-29 15:29:00 -0800946 def disconnect_ssh_tunnel(self, tunnel_proc, port):
Roshan Pius58e5dd32015-10-16 15:16:42 -0700947 """
948 Disconnects a previously forwarded port from the server to the DUT for
949 RPC server connection.
950
xixuan6cf6d2f2016-01-29 15:29:00 -0800951 @param tunnel_proc: a tunnel process returned from |create_ssh_tunnel|.
952 @param port: remote port on the DUT, used in ADBHost.
Roshan Pius58e5dd32015-10-16 15:16:42 -0700953
954 """
955 if tunnel_proc.poll() is None:
956 tunnel_proc.terminate()
957 logging.debug('Terminated tunnel, pid %d', tunnel_proc.pid)
958 else:
959 logging.debug('Tunnel pid %d terminated early, status %d',
960 tunnel_proc.pid, tunnel_proc.returncode)
961
962
Gilad Arnolda76bef02015-09-29 13:55:15 -0700963 def get_os_type(self):
964 """Returns the host OS descriptor (to be implemented in subclasses).
965
966 @return A string describing the OS type.
967 """
Gwendal Grignou36b61702016-02-10 11:57:53 -0800968 raise NotImplementedError
Dan Shi9f92aa62017-07-27 17:07:05 -0700969
970
971 def check_cached_up_status(
972 self, expiration_seconds=_DEFAULT_UP_STATUS_EXPIRATION_SECONDS):
973 """Check if the DUT responded to ping in the past `expiration_seconds`.
974
975 @param expiration_seconds: The number of seconds to keep the cached
976 status of whether the DUT responded to ping.
977 @return: True if the DUT has responded to ping during the past
978 `expiration_seconds`.
979 """
980 # Refresh the up status if any of following conditions is true:
981 # * cached status is never set
982 # * cached status is False, so the method can check if the host is up
983 # again.
984 # * If the cached status is older than `expiration_seconds`
985 expire_time = time.time() - expiration_seconds
986 if (self._cached_up_status_updated is None or
987 not self._cached_up_status or
988 self._cached_up_status_updated < expire_time):
989 self._cached_up_status = self.is_up_fast()
990 self._cached_up_status_updated = time.time()
991 return self._cached_up_status