blob: 14a8d170f2afa1115fc2a9d3bcb2aaba2990ee84 [file] [log] [blame]
mbligha0a27592009-01-24 01:41:36 +00001import os, time, types, socket, shutil, glob
showard170873e2009-01-07 00:22:26 +00002from autotest_lib.client.common_lib import error, debug
jadmanski31c49b72008-10-27 20:44:48 +00003from autotest_lib.server import utils, autotest
mblighe8b93af2009-01-30 00:45:53 +00004from autotest_lib.server.hosts import remote
jadmanskica7da372008-10-21 16:26:52 +00005
6
jadmanskica7da372008-10-21 16:26:52 +00007def make_ssh_command(user="root", port=22, opts='', connect_timeout=30):
8 base_command = ("/usr/bin/ssh -a -x %s -o BatchMode=yes "
jadmanskid9365e52008-10-22 16:55:31 +00009 "-o ConnectTimeout=%d -o ServerAliveInterval=300 "
jadmanskica7da372008-10-21 16:26:52 +000010 "-l %s -p %d")
11 assert isinstance(connect_timeout, (int, long))
12 assert connect_timeout > 0 # can't disable the timeout
13 return base_command % (opts, connect_timeout, user, port)
14
15
mblighe8b93af2009-01-30 00:45:53 +000016# import site specific Host class
17SiteHost = utils.import_site_class(
18 __file__, "autotest_lib.server.hosts.site_host", "SiteHost",
19 remote.RemoteHost)
20
21
22class AbstractSSHHost(SiteHost):
jadmanskica7da372008-10-21 16:26:52 +000023 """ This class represents a generic implementation of most of the
24 framework necessary for controlling a host via ssh. It implements
25 almost all of the abstract Host methods, except for the core
26 Host.run method. """
27
jadmanskif6562912008-10-21 17:59:01 +000028 def _initialize(self, hostname, user="root", port=22, password="",
29 *args, **dargs):
30 super(AbstractSSHHost, self)._initialize(hostname=hostname,
31 *args, **dargs)
mbligh6369cf22008-10-24 17:21:57 +000032 self.ip = socket.getaddrinfo(self.hostname, None)[0][4][0]
jadmanskica7da372008-10-21 16:26:52 +000033 self.user = user
34 self.port = port
35 self.password = password
36
37
jadmanski2583a432009-02-10 23:59:11 +000038 def _encode_remote_paths(self, paths):
39 """ Given a list of file paths, encodes it as a single remote path, in
40 the style used by rsync and scp. """
41 escaped_paths = [utils.scp_remote_escape(path) for path in paths]
jadmanskid7b79ed2009-01-07 17:19:48 +000042 return '%s@%s:"%s"' % (self.user, self.hostname,
jadmanski2583a432009-02-10 23:59:11 +000043 " ".join(paths))
jadmanskica7da372008-10-21 16:26:52 +000044
jadmanskica7da372008-10-21 16:26:52 +000045
jadmanskid7b79ed2009-01-07 17:19:48 +000046 def _make_rsync_cmd(self, sources, dest, delete_dest):
47 """ Given a list of source paths and a destination path, produces the
48 appropriate rsync command for copying them. Remote paths must be
49 pre-encoded. """
50 ssh_cmd = make_ssh_command(self.user, self.port)
51 if delete_dest:
52 delete_flag = "--delete"
53 else:
54 delete_flag = ""
55 command = "rsync -L %s --rsh='%s' -az %s %s"
56 return command % (delete_flag, ssh_cmd, " ".join(sources), dest)
57
58
59 def _make_scp_cmd(self, sources, dest):
60 """ Given a list of source paths and a destination path, produces the
61 appropriate scp command for encoding it. Remote paths must be
62 pre-encoded. """
63 command = "scp -rpq -P %d %s '%s'"
64 return command % (self.port, " ".join(sources), dest)
65
66
67 def _make_rsync_compatible_globs(self, path, is_local):
68 """ Given an rsync-style path, returns a list of globbed paths
69 that will hopefully provide equivalent behaviour for scp. Does not
70 support the full range of rsync pattern matching behaviour, only that
71 exposed in the get/send_file interface (trailing slashes).
72
73 The is_local param is flag indicating if the paths should be
74 interpreted as local or remote paths. """
75
76 # non-trailing slash paths should just work
77 if len(path) == 0 or path[-1] != "/":
78 return [path]
79
80 # make a function to test if a pattern matches any files
81 if is_local:
82 def glob_matches_files(path):
83 return len(glob.glob(path)) > 0
84 else:
85 def glob_matches_files(path):
86 result = self.run("ls \"%s\"" % utils.sh_escape(path),
87 ignore_status=True)
88 return result.exit_status == 0
89
90 # take a set of globs that cover all files, and see which are needed
91 patterns = ["*", ".[!.]*"]
92 patterns = [p for p in patterns if glob_matches_files(path + p)]
93
94 # convert them into a set of paths suitable for the commandline
95 path = utils.sh_escape(path)
96 if is_local:
97 return ["\"%s\"%s" % (path, pattern) for pattern in patterns]
98 else:
99 return ["\"%s\"" % (path + pattern) for pattern in patterns]
100
101
102 def _make_rsync_compatible_source(self, source, is_local):
103 """ Applies the same logic as _make_rsync_compatible_globs, but
104 applies it to an entire list of sources, producing a new list of
105 sources, properly quoted. """
106 return sum((self._make_rsync_compatible_globs(path, is_local)
107 for path in source), [])
jadmanskica7da372008-10-21 16:26:52 +0000108
109
mbligh89e258d2008-10-24 13:58:08 +0000110 def get_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000111 """
112 Copy files from the remote host to a local path.
113
114 Directories will be copied recursively.
115 If a source component is a directory with a trailing slash,
116 the content of the directory will be copied, otherwise, the
117 directory itself and its content will be copied. This
118 behavior is similar to that of the program 'rsync'.
119
120 Args:
121 source: either
122 1) a single file or directory, as a string
123 2) a list of one or more (possibly mixed)
124 files or directories
125 dest: a file or a directory (if source contains a
126 directory or more than one element, you must
127 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000128 delete_dest: if this is true, the command will also clear
129 out any old files at dest that are not in the
130 source
jadmanskica7da372008-10-21 16:26:52 +0000131
132 Raises:
133 AutoservRunError: the scp command failed
134 """
135 if isinstance(source, basestring):
136 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000137 dest = os.path.abspath(dest)
jadmanskica7da372008-10-21 16:26:52 +0000138
jadmanskid7b79ed2009-01-07 17:19:48 +0000139 try:
jadmanski2583a432009-02-10 23:59:11 +0000140 remote_source = self._encode_remote_paths(source)
jadmanskid7b79ed2009-01-07 17:19:48 +0000141 local_dest = utils.sh_escape(dest)
jadmanski2583a432009-02-10 23:59:11 +0000142 rsync = self._make_rsync_cmd([remote_source], local_dest,
jadmanskid7b79ed2009-01-07 17:19:48 +0000143 delete_dest)
144 utils.run(rsync)
145 except error.CmdError, e:
146 print "warning: rsync failed with: %s" % e
147 print "attempting to copy with scp instead"
jadmanskica7da372008-10-21 16:26:52 +0000148
jadmanskid7b79ed2009-01-07 17:19:48 +0000149 # scp has no equivalent to --delete, just drop the entire dest dir
150 if delete_dest and os.path.isdir(dest):
151 shutil.rmtree(dest)
152 os.mkdir(dest)
jadmanskica7da372008-10-21 16:26:52 +0000153
jadmanskid7b79ed2009-01-07 17:19:48 +0000154 remote_source = self._make_rsync_compatible_source(source, False)
155 if remote_source:
jadmanski2583a432009-02-10 23:59:11 +0000156 remote_source = self._encode_remote_paths(remote_source)
jadmanskid7b79ed2009-01-07 17:19:48 +0000157 local_dest = utils.sh_escape(dest)
jadmanski2583a432009-02-10 23:59:11 +0000158 scp = self._make_scp_cmd([remote_source], local_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000159 try:
160 utils.run(scp)
161 except error.CmdError, e:
162 raise error.AutoservRunError(e.args[0], e.args[1])
jadmanskica7da372008-10-21 16:26:52 +0000163
164
mbligh89e258d2008-10-24 13:58:08 +0000165 def send_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000166 """
167 Copy files from a local path to the remote host.
168
169 Directories will be copied recursively.
170 If a source component is a directory with a trailing slash,
171 the content of the directory will be copied, otherwise, the
172 directory itself and its content will be copied. This
173 behavior is similar to that of the program 'rsync'.
174
175 Args:
176 source: either
177 1) a single file or directory, as a string
178 2) a list of one or more (possibly mixed)
179 files or directories
180 dest: a file or a directory (if source contains a
181 directory or more than one element, you must
182 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000183 delete_dest: if this is true, the command will also clear
184 out any old files at dest that are not in the
185 source
jadmanskica7da372008-10-21 16:26:52 +0000186
187 Raises:
188 AutoservRunError: the scp command failed
189 """
190 if isinstance(source, basestring):
191 source = [source]
jadmanski2583a432009-02-10 23:59:11 +0000192 remote_dest = self._encode_remote_paths([dest])
jadmanskica7da372008-10-21 16:26:52 +0000193
jadmanskid7b79ed2009-01-07 17:19:48 +0000194 try:
jadmanski2583a432009-02-10 23:59:11 +0000195 local_sources = [utils.sh_escape(path) for path in source]
196 rsync = self._make_rsync_cmd(local_sources, remote_dest,
jadmanskid7b79ed2009-01-07 17:19:48 +0000197 delete_dest)
198 utils.run(rsync)
199 except error.CmdError, e:
200 print "warning: rsync failed with: %s" % e
201 print "attempting to copy with scp instead"
jadmanskica7da372008-10-21 16:26:52 +0000202
jadmanskid7b79ed2009-01-07 17:19:48 +0000203 # scp has no equivalent to --delete, just drop the entire dest dir
204 if delete_dest:
205 is_dir = self.run("ls -d %s/" % remote_dest,
206 ignore_status=True).exit_status == 0
207 if is_dir:
208 cmd = "rm -rf %s && mkdir %s"
209 cmd %= (remote_dest, remote_dest)
210 self.run(cmd)
jadmanskica7da372008-10-21 16:26:52 +0000211
jadmanski2583a432009-02-10 23:59:11 +0000212 local_sources = self._make_rsync_compatible_source(source, True)
213 if local_sources:
214 scp = self._make_scp_cmd(local_sources, remote_dest)
jadmanskid7b79ed2009-01-07 17:19:48 +0000215 try:
216 utils.run(scp)
217 except error.CmdError, e:
218 raise error.AutoservRunError(e.args[0], e.args[1])
219
jadmanskica7da372008-10-21 16:26:52 +0000220 self.run('find "%s" -type d -print0 | xargs -0r chmod o+rx' % dest)
221 self.run('find "%s" -type f -print0 | xargs -0r chmod o+r' % dest)
222 if self.target_file_owner:
223 self.run('chown -R %s %s' % (self.target_file_owner, dest))
224
225
226 def ssh_ping(self, timeout=60):
227 try:
228 self.run("true", timeout=timeout, connect_timeout=timeout)
mbligh5f66ed42008-11-24 17:16:14 +0000229 print "ssh_ping of %s completed sucessfully" % self.hostname
jadmanskica7da372008-10-21 16:26:52 +0000230 except error.AutoservSSHTimeout:
231 msg = "ssh ping timed out (timeout = %d)" % timeout
232 raise error.AutoservSSHTimeout(msg)
233 except error.AutoservRunError, e:
234 msg = "command true failed in ssh ping"
235 raise error.AutoservRunError(msg, e.result_obj)
236
237
238 def is_up(self):
239 """
240 Check if the remote host is up.
241
242 Returns:
243 True if the remote host is up, False otherwise
244 """
245 try:
246 self.ssh_ping()
247 except error.AutoservError:
248 return False
249 else:
250 return True
251
252
253 def wait_up(self, timeout=None):
254 """
255 Wait until the remote host is up or the timeout expires.
256
257 In fact, it will wait until an ssh connection to the remote
258 host can be established, and getty is running.
259
260 Args:
261 timeout: time limit in seconds before returning even
262 if the host is not up.
263
264 Returns:
265 True if the host was found to be up, False otherwise
266 """
267 if timeout:
268 end_time = time.time() + timeout
269
270 while not timeout or time.time() < end_time:
271 if self.is_up():
272 try:
273 if self.are_wait_up_processes_up():
274 return True
275 except error.AutoservError:
276 pass
277 time.sleep(1)
278
279 return False
280
281
282 def wait_down(self, timeout=None):
283 """
284 Wait until the remote host is down or the timeout expires.
285
286 In fact, it will wait until an ssh connection to the remote
287 host fails.
288
289 Args:
290 timeout: time limit in seconds before returning even
291 if the host is not up.
292
293 Returns:
294 True if the host was found to be down, False otherwise
295 """
296 if timeout:
297 end_time = time.time() + timeout
298
299 while not timeout or time.time() < end_time:
300 if not self.is_up():
301 return True
302 time.sleep(1)
303
304 return False
jadmanskif6562912008-10-21 17:59:01 +0000305
mbligha0a27592009-01-24 01:41:36 +0000306
jadmanskif6562912008-10-21 17:59:01 +0000307 # tunable constants for the verify & repair code
308 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
mbligha0a27592009-01-24 01:41:36 +0000309
jadmanskif6562912008-10-21 17:59:01 +0000310
311 def verify(self):
312 super(AbstractSSHHost, self).verify()
313
314 print 'Pinging host ' + self.hostname
315 self.ssh_ping()
316
jadmanski80deb752009-01-21 17:14:16 +0000317 if self.is_shutting_down():
318 raise error.AutoservHostError("Host is shutting down")
319
jadmanskif6562912008-10-21 17:59:01 +0000320 try:
321 autodir = autotest._get_autodir(self)
322 if autodir:
jadmanskif6562912008-10-21 17:59:01 +0000323 self.check_diskspace(autodir,
324 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
325 except error.AutoservHostError:
326 raise # only want to raise if it's a space issue
327 except Exception:
328 pass # autotest dir may not exist, etc. ignore
329
330
showard170873e2009-01-07 00:22:26 +0000331class LoggerFile(object):
332 def write(self, data):
jadmanskifcc851b2009-01-07 17:31:12 +0000333 if data:
334 debug.get_logger().debug(data.rstrip("\n"))
showard170873e2009-01-07 00:22:26 +0000335
336
337 def flush(self):
338 pass