blob: bdb4b3e453bf83d0f9af850e73f53b0d78ae6fbe [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
jadmanskid7b79ed2009-01-07 17:19:48 +000038 def _encode_remote_path(self, path):
39 """ Given a file path, encodes it as a remote path, in the style used
40 by rsync and scp. """
41 return '%s@%s:"%s"' % (self.user, self.hostname,
42 utils.scp_remote_escape(path))
jadmanskica7da372008-10-21 16:26:52 +000043
jadmanskica7da372008-10-21 16:26:52 +000044
jadmanskid7b79ed2009-01-07 17:19:48 +000045 def _make_rsync_cmd(self, sources, dest, delete_dest):
46 """ Given a list of source paths and a destination path, produces the
47 appropriate rsync command for copying them. Remote paths must be
48 pre-encoded. """
49 ssh_cmd = make_ssh_command(self.user, self.port)
50 if delete_dest:
51 delete_flag = "--delete"
52 else:
53 delete_flag = ""
54 command = "rsync -L %s --rsh='%s' -az %s %s"
55 return command % (delete_flag, ssh_cmd, " ".join(sources), dest)
56
57
58 def _make_scp_cmd(self, sources, dest):
59 """ Given a list of source paths and a destination path, produces the
60 appropriate scp command for encoding it. Remote paths must be
61 pre-encoded. """
62 command = "scp -rpq -P %d %s '%s'"
63 return command % (self.port, " ".join(sources), dest)
64
65
66 def _make_rsync_compatible_globs(self, path, is_local):
67 """ Given an rsync-style path, returns a list of globbed paths
68 that will hopefully provide equivalent behaviour for scp. Does not
69 support the full range of rsync pattern matching behaviour, only that
70 exposed in the get/send_file interface (trailing slashes).
71
72 The is_local param is flag indicating if the paths should be
73 interpreted as local or remote paths. """
74
75 # non-trailing slash paths should just work
76 if len(path) == 0 or path[-1] != "/":
77 return [path]
78
79 # make a function to test if a pattern matches any files
80 if is_local:
81 def glob_matches_files(path):
82 return len(glob.glob(path)) > 0
83 else:
84 def glob_matches_files(path):
85 result = self.run("ls \"%s\"" % utils.sh_escape(path),
86 ignore_status=True)
87 return result.exit_status == 0
88
89 # take a set of globs that cover all files, and see which are needed
90 patterns = ["*", ".[!.]*"]
91 patterns = [p for p in patterns if glob_matches_files(path + p)]
92
93 # convert them into a set of paths suitable for the commandline
94 path = utils.sh_escape(path)
95 if is_local:
96 return ["\"%s\"%s" % (path, pattern) for pattern in patterns]
97 else:
98 return ["\"%s\"" % (path + pattern) for pattern in patterns]
99
100
101 def _make_rsync_compatible_source(self, source, is_local):
102 """ Applies the same logic as _make_rsync_compatible_globs, but
103 applies it to an entire list of sources, producing a new list of
104 sources, properly quoted. """
105 return sum((self._make_rsync_compatible_globs(path, is_local)
106 for path in source), [])
jadmanskica7da372008-10-21 16:26:52 +0000107
108
mbligh89e258d2008-10-24 13:58:08 +0000109 def get_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000110 """
111 Copy files from the remote host to a local path.
112
113 Directories will be copied recursively.
114 If a source component is a directory with a trailing slash,
115 the content of the directory will be copied, otherwise, the
116 directory itself and its content will be copied. This
117 behavior is similar to that of the program 'rsync'.
118
119 Args:
120 source: either
121 1) a single file or directory, as a string
122 2) a list of one or more (possibly mixed)
123 files or directories
124 dest: a file or a directory (if source contains a
125 directory or more than one element, you must
126 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000127 delete_dest: if this is true, the command will also clear
128 out any old files at dest that are not in the
129 source
jadmanskica7da372008-10-21 16:26:52 +0000130
131 Raises:
132 AutoservRunError: the scp command failed
133 """
134 if isinstance(source, basestring):
135 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000136 dest = os.path.abspath(dest)
jadmanskica7da372008-10-21 16:26:52 +0000137
jadmanskid7b79ed2009-01-07 17:19:48 +0000138 try:
139 remote_source = [self._encode_remote_path(p) for p in source]
140 local_dest = utils.sh_escape(dest)
141 rsync = self._make_rsync_cmd(remote_source, local_dest,
142 delete_dest)
143 utils.run(rsync)
144 except error.CmdError, e:
145 print "warning: rsync failed with: %s" % e
146 print "attempting to copy with scp instead"
jadmanskica7da372008-10-21 16:26:52 +0000147
jadmanskid7b79ed2009-01-07 17:19:48 +0000148 # scp has no equivalent to --delete, just drop the entire dest dir
149 if delete_dest and os.path.isdir(dest):
150 shutil.rmtree(dest)
151 os.mkdir(dest)
jadmanskica7da372008-10-21 16:26:52 +0000152
jadmanskid7b79ed2009-01-07 17:19:48 +0000153 remote_source = self._make_rsync_compatible_source(source, False)
154 if remote_source:
155 local_dest = utils.sh_escape(dest)
156 scp = self._make_scp_cmd(remote_source, local_dest)
157 try:
158 utils.run(scp)
159 except error.CmdError, e:
160 raise error.AutoservRunError(e.args[0], e.args[1])
jadmanskica7da372008-10-21 16:26:52 +0000161
162
mbligh89e258d2008-10-24 13:58:08 +0000163 def send_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000164 """
165 Copy files from a local path to the remote host.
166
167 Directories will be copied recursively.
168 If a source component is a directory with a trailing slash,
169 the content of the directory will be copied, otherwise, the
170 directory itself and its content will be copied. This
171 behavior is similar to that of the program 'rsync'.
172
173 Args:
174 source: either
175 1) a single file or directory, as a string
176 2) a list of one or more (possibly mixed)
177 files or directories
178 dest: a file or a directory (if source contains a
179 directory or more than one element, you must
180 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000181 delete_dest: if this is true, the command will also clear
182 out any old files at dest that are not in the
183 source
jadmanskica7da372008-10-21 16:26:52 +0000184
185 Raises:
186 AutoservRunError: the scp command failed
187 """
188 if isinstance(source, basestring):
189 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000190 remote_dest = self._encode_remote_path(dest)
jadmanskica7da372008-10-21 16:26:52 +0000191
jadmanskid7b79ed2009-01-07 17:19:48 +0000192 try:
193 local_source = [utils.sh_escape(path) for path in source]
194 rsync = self._make_rsync_cmd(local_source, remote_dest,
195 delete_dest)
196 utils.run(rsync)
197 except error.CmdError, e:
198 print "warning: rsync failed with: %s" % e
199 print "attempting to copy with scp instead"
jadmanskica7da372008-10-21 16:26:52 +0000200
jadmanskid7b79ed2009-01-07 17:19:48 +0000201 # scp has no equivalent to --delete, just drop the entire dest dir
202 if delete_dest:
203 is_dir = self.run("ls -d %s/" % remote_dest,
204 ignore_status=True).exit_status == 0
205 if is_dir:
206 cmd = "rm -rf %s && mkdir %s"
207 cmd %= (remote_dest, remote_dest)
208 self.run(cmd)
jadmanskica7da372008-10-21 16:26:52 +0000209
jadmanskid7b79ed2009-01-07 17:19:48 +0000210 local_source = self._make_rsync_compatible_source(source, True)
211 if local_source:
212 scp = self._make_scp_cmd(local_source, remote_dest)
213 try:
214 utils.run(scp)
215 except error.CmdError, e:
216 raise error.AutoservRunError(e.args[0], e.args[1])
217
jadmanskica7da372008-10-21 16:26:52 +0000218 self.run('find "%s" -type d -print0 | xargs -0r chmod o+rx' % dest)
219 self.run('find "%s" -type f -print0 | xargs -0r chmod o+r' % dest)
220 if self.target_file_owner:
221 self.run('chown -R %s %s' % (self.target_file_owner, dest))
222
223
224 def ssh_ping(self, timeout=60):
225 try:
226 self.run("true", timeout=timeout, connect_timeout=timeout)
mbligh5f66ed42008-11-24 17:16:14 +0000227 print "ssh_ping of %s completed sucessfully" % self.hostname
jadmanskica7da372008-10-21 16:26:52 +0000228 except error.AutoservSSHTimeout:
229 msg = "ssh ping timed out (timeout = %d)" % timeout
230 raise error.AutoservSSHTimeout(msg)
231 except error.AutoservRunError, e:
232 msg = "command true failed in ssh ping"
233 raise error.AutoservRunError(msg, e.result_obj)
234
235
236 def is_up(self):
237 """
238 Check if the remote host is up.
239
240 Returns:
241 True if the remote host is up, False otherwise
242 """
243 try:
244 self.ssh_ping()
245 except error.AutoservError:
246 return False
247 else:
248 return True
249
250
251 def wait_up(self, timeout=None):
252 """
253 Wait until the remote host is up or the timeout expires.
254
255 In fact, it will wait until an ssh connection to the remote
256 host can be established, and getty is running.
257
258 Args:
259 timeout: time limit in seconds before returning even
260 if the host is not up.
261
262 Returns:
263 True if the host was found to be up, False otherwise
264 """
265 if timeout:
266 end_time = time.time() + timeout
267
268 while not timeout or time.time() < end_time:
269 if self.is_up():
270 try:
271 if self.are_wait_up_processes_up():
272 return True
273 except error.AutoservError:
274 pass
275 time.sleep(1)
276
277 return False
278
279
280 def wait_down(self, timeout=None):
281 """
282 Wait until the remote host is down or the timeout expires.
283
284 In fact, it will wait until an ssh connection to the remote
285 host fails.
286
287 Args:
288 timeout: time limit in seconds before returning even
289 if the host is not up.
290
291 Returns:
292 True if the host was found to be down, False otherwise
293 """
294 if timeout:
295 end_time = time.time() + timeout
296
297 while not timeout or time.time() < end_time:
298 if not self.is_up():
299 return True
300 time.sleep(1)
301
302 return False
jadmanskif6562912008-10-21 17:59:01 +0000303
mbligha0a27592009-01-24 01:41:36 +0000304
jadmanskif6562912008-10-21 17:59:01 +0000305 # tunable constants for the verify & repair code
306 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
mbligha0a27592009-01-24 01:41:36 +0000307
jadmanskif6562912008-10-21 17:59:01 +0000308
309 def verify(self):
310 super(AbstractSSHHost, self).verify()
311
312 print 'Pinging host ' + self.hostname
313 self.ssh_ping()
314
jadmanski80deb752009-01-21 17:14:16 +0000315 if self.is_shutting_down():
316 raise error.AutoservHostError("Host is shutting down")
317
jadmanskif6562912008-10-21 17:59:01 +0000318 try:
319 autodir = autotest._get_autodir(self)
320 if autodir:
jadmanskif6562912008-10-21 17:59:01 +0000321 self.check_diskspace(autodir,
322 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
323 except error.AutoservHostError:
324 raise # only want to raise if it's a space issue
325 except Exception:
326 pass # autotest dir may not exist, etc. ignore
327
328
showard170873e2009-01-07 00:22:26 +0000329class LoggerFile(object):
330 def write(self, data):
jadmanskifcc851b2009-01-07 17:31:12 +0000331 if data:
332 debug.get_logger().debug(data.rstrip("\n"))
showard170873e2009-01-07 00:22:26 +0000333
334
335 def flush(self):
336 pass