blob: fe91d0d346164f7bf10fbbacccfa89c3a597cf94 [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
jadmanskica7da372008-10-21 16:26:52 +00004from autotest_lib.server.hosts import site_host
5
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
16class AbstractSSHHost(site_host.SiteHost):
17 """ This class represents a generic implementation of most of the
18 framework necessary for controlling a host via ssh. It implements
19 almost all of the abstract Host methods, except for the core
20 Host.run method. """
21
jadmanskif6562912008-10-21 17:59:01 +000022 def _initialize(self, hostname, user="root", port=22, password="",
23 *args, **dargs):
24 super(AbstractSSHHost, self)._initialize(hostname=hostname,
25 *args, **dargs)
mbligh6369cf22008-10-24 17:21:57 +000026 self.ip = socket.getaddrinfo(self.hostname, None)[0][4][0]
jadmanskica7da372008-10-21 16:26:52 +000027 self.user = user
28 self.port = port
29 self.password = password
30
31
jadmanskid7b79ed2009-01-07 17:19:48 +000032 def _encode_remote_path(self, path):
33 """ Given a file path, encodes it as a remote path, in the style used
34 by rsync and scp. """
35 return '%s@%s:"%s"' % (self.user, self.hostname,
36 utils.scp_remote_escape(path))
jadmanskica7da372008-10-21 16:26:52 +000037
jadmanskica7da372008-10-21 16:26:52 +000038
jadmanskid7b79ed2009-01-07 17:19:48 +000039 def _make_rsync_cmd(self, sources, dest, delete_dest):
40 """ Given a list of source paths and a destination path, produces the
41 appropriate rsync command for copying them. Remote paths must be
42 pre-encoded. """
43 ssh_cmd = make_ssh_command(self.user, self.port)
44 if delete_dest:
45 delete_flag = "--delete"
46 else:
47 delete_flag = ""
48 command = "rsync -L %s --rsh='%s' -az %s %s"
49 return command % (delete_flag, ssh_cmd, " ".join(sources), dest)
50
51
52 def _make_scp_cmd(self, sources, dest):
53 """ Given a list of source paths and a destination path, produces the
54 appropriate scp command for encoding it. Remote paths must be
55 pre-encoded. """
56 command = "scp -rpq -P %d %s '%s'"
57 return command % (self.port, " ".join(sources), dest)
58
59
60 def _make_rsync_compatible_globs(self, path, is_local):
61 """ Given an rsync-style path, returns a list of globbed paths
62 that will hopefully provide equivalent behaviour for scp. Does not
63 support the full range of rsync pattern matching behaviour, only that
64 exposed in the get/send_file interface (trailing slashes).
65
66 The is_local param is flag indicating if the paths should be
67 interpreted as local or remote paths. """
68
69 # non-trailing slash paths should just work
70 if len(path) == 0 or path[-1] != "/":
71 return [path]
72
73 # make a function to test if a pattern matches any files
74 if is_local:
75 def glob_matches_files(path):
76 return len(glob.glob(path)) > 0
77 else:
78 def glob_matches_files(path):
79 result = self.run("ls \"%s\"" % utils.sh_escape(path),
80 ignore_status=True)
81 return result.exit_status == 0
82
83 # take a set of globs that cover all files, and see which are needed
84 patterns = ["*", ".[!.]*"]
85 patterns = [p for p in patterns if glob_matches_files(path + p)]
86
87 # convert them into a set of paths suitable for the commandline
88 path = utils.sh_escape(path)
89 if is_local:
90 return ["\"%s\"%s" % (path, pattern) for pattern in patterns]
91 else:
92 return ["\"%s\"" % (path + pattern) for pattern in patterns]
93
94
95 def _make_rsync_compatible_source(self, source, is_local):
96 """ Applies the same logic as _make_rsync_compatible_globs, but
97 applies it to an entire list of sources, producing a new list of
98 sources, properly quoted. """
99 return sum((self._make_rsync_compatible_globs(path, is_local)
100 for path in source), [])
jadmanskica7da372008-10-21 16:26:52 +0000101
102
mbligh89e258d2008-10-24 13:58:08 +0000103 def get_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000104 """
105 Copy files from the remote host to a local path.
106
107 Directories will be copied recursively.
108 If a source component is a directory with a trailing slash,
109 the content of the directory will be copied, otherwise, the
110 directory itself and its content will be copied. This
111 behavior is similar to that of the program 'rsync'.
112
113 Args:
114 source: either
115 1) a single file or directory, as a string
116 2) a list of one or more (possibly mixed)
117 files or directories
118 dest: a file or a directory (if source contains a
119 directory or more than one element, you must
120 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000121 delete_dest: if this is true, the command will also clear
122 out any old files at dest that are not in the
123 source
jadmanskica7da372008-10-21 16:26:52 +0000124
125 Raises:
126 AutoservRunError: the scp command failed
127 """
128 if isinstance(source, basestring):
129 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000130 dest = os.path.abspath(dest)
jadmanskica7da372008-10-21 16:26:52 +0000131
jadmanskid7b79ed2009-01-07 17:19:48 +0000132 try:
133 remote_source = [self._encode_remote_path(p) for p in source]
134 local_dest = utils.sh_escape(dest)
135 rsync = self._make_rsync_cmd(remote_source, local_dest,
136 delete_dest)
137 utils.run(rsync)
138 except error.CmdError, e:
139 print "warning: rsync failed with: %s" % e
140 print "attempting to copy with scp instead"
jadmanskica7da372008-10-21 16:26:52 +0000141
jadmanskid7b79ed2009-01-07 17:19:48 +0000142 # scp has no equivalent to --delete, just drop the entire dest dir
143 if delete_dest and os.path.isdir(dest):
144 shutil.rmtree(dest)
145 os.mkdir(dest)
jadmanskica7da372008-10-21 16:26:52 +0000146
jadmanskid7b79ed2009-01-07 17:19:48 +0000147 remote_source = self._make_rsync_compatible_source(source, False)
148 if remote_source:
149 local_dest = utils.sh_escape(dest)
150 scp = self._make_scp_cmd(remote_source, local_dest)
151 try:
152 utils.run(scp)
153 except error.CmdError, e:
154 raise error.AutoservRunError(e.args[0], e.args[1])
jadmanskica7da372008-10-21 16:26:52 +0000155
156
mbligh89e258d2008-10-24 13:58:08 +0000157 def send_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000158 """
159 Copy files from a local path to the remote host.
160
161 Directories will be copied recursively.
162 If a source component is a directory with a trailing slash,
163 the content of the directory will be copied, otherwise, the
164 directory itself and its content will be copied. This
165 behavior is similar to that of the program 'rsync'.
166
167 Args:
168 source: either
169 1) a single file or directory, as a string
170 2) a list of one or more (possibly mixed)
171 files or directories
172 dest: a file or a directory (if source contains a
173 directory or more than one element, you must
174 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000175 delete_dest: if this is true, the command will also clear
176 out any old files at dest that are not in the
177 source
jadmanskica7da372008-10-21 16:26:52 +0000178
179 Raises:
180 AutoservRunError: the scp command failed
181 """
182 if isinstance(source, basestring):
183 source = [source]
jadmanskid7b79ed2009-01-07 17:19:48 +0000184 remote_dest = self._encode_remote_path(dest)
jadmanskica7da372008-10-21 16:26:52 +0000185
jadmanskid7b79ed2009-01-07 17:19:48 +0000186 try:
187 local_source = [utils.sh_escape(path) for path in source]
188 rsync = self._make_rsync_cmd(local_source, remote_dest,
189 delete_dest)
190 utils.run(rsync)
191 except error.CmdError, e:
192 print "warning: rsync failed with: %s" % e
193 print "attempting to copy with scp instead"
jadmanskica7da372008-10-21 16:26:52 +0000194
jadmanskid7b79ed2009-01-07 17:19:48 +0000195 # scp has no equivalent to --delete, just drop the entire dest dir
196 if delete_dest:
197 is_dir = self.run("ls -d %s/" % remote_dest,
198 ignore_status=True).exit_status == 0
199 if is_dir:
200 cmd = "rm -rf %s && mkdir %s"
201 cmd %= (remote_dest, remote_dest)
202 self.run(cmd)
jadmanskica7da372008-10-21 16:26:52 +0000203
jadmanskid7b79ed2009-01-07 17:19:48 +0000204 local_source = self._make_rsync_compatible_source(source, True)
205 if local_source:
206 scp = self._make_scp_cmd(local_source, remote_dest)
207 try:
208 utils.run(scp)
209 except error.CmdError, e:
210 raise error.AutoservRunError(e.args[0], e.args[1])
211
jadmanskica7da372008-10-21 16:26:52 +0000212 self.run('find "%s" -type d -print0 | xargs -0r chmod o+rx' % dest)
213 self.run('find "%s" -type f -print0 | xargs -0r chmod o+r' % dest)
214 if self.target_file_owner:
215 self.run('chown -R %s %s' % (self.target_file_owner, dest))
216
217
218 def ssh_ping(self, timeout=60):
219 try:
220 self.run("true", timeout=timeout, connect_timeout=timeout)
mbligh5f66ed42008-11-24 17:16:14 +0000221 print "ssh_ping of %s completed sucessfully" % self.hostname
jadmanskica7da372008-10-21 16:26:52 +0000222 except error.AutoservSSHTimeout:
223 msg = "ssh ping timed out (timeout = %d)" % timeout
224 raise error.AutoservSSHTimeout(msg)
225 except error.AutoservRunError, e:
226 msg = "command true failed in ssh ping"
227 raise error.AutoservRunError(msg, e.result_obj)
228
229
230 def is_up(self):
231 """
232 Check if the remote host is up.
233
234 Returns:
235 True if the remote host is up, False otherwise
236 """
237 try:
238 self.ssh_ping()
239 except error.AutoservError:
240 return False
241 else:
242 return True
243
244
245 def wait_up(self, timeout=None):
246 """
247 Wait until the remote host is up or the timeout expires.
248
249 In fact, it will wait until an ssh connection to the remote
250 host can be established, and getty is running.
251
252 Args:
253 timeout: time limit in seconds before returning even
254 if the host is not up.
255
256 Returns:
257 True if the host was found to be up, False otherwise
258 """
259 if timeout:
260 end_time = time.time() + timeout
261
262 while not timeout or time.time() < end_time:
263 if self.is_up():
264 try:
265 if self.are_wait_up_processes_up():
266 return True
267 except error.AutoservError:
268 pass
269 time.sleep(1)
270
271 return False
272
273
274 def wait_down(self, timeout=None):
275 """
276 Wait until the remote host is down or the timeout expires.
277
278 In fact, it will wait until an ssh connection to the remote
279 host fails.
280
281 Args:
282 timeout: time limit in seconds before returning even
283 if the host is not up.
284
285 Returns:
286 True if the host was found to be down, False otherwise
287 """
288 if timeout:
289 end_time = time.time() + timeout
290
291 while not timeout or time.time() < end_time:
292 if not self.is_up():
293 return True
294 time.sleep(1)
295
296 return False
jadmanskif6562912008-10-21 17:59:01 +0000297
mbligha0a27592009-01-24 01:41:36 +0000298
jadmanskif6562912008-10-21 17:59:01 +0000299 # tunable constants for the verify & repair code
300 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
mbligha0a27592009-01-24 01:41:36 +0000301
jadmanskif6562912008-10-21 17:59:01 +0000302
303 def verify(self):
304 super(AbstractSSHHost, self).verify()
305
306 print 'Pinging host ' + self.hostname
307 self.ssh_ping()
308
jadmanski80deb752009-01-21 17:14:16 +0000309 if self.is_shutting_down():
310 raise error.AutoservHostError("Host is shutting down")
311
jadmanskif6562912008-10-21 17:59:01 +0000312 try:
313 autodir = autotest._get_autodir(self)
314 if autodir:
jadmanskif6562912008-10-21 17:59:01 +0000315 self.check_diskspace(autodir,
316 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
317 except error.AutoservHostError:
318 raise # only want to raise if it's a space issue
319 except Exception:
320 pass # autotest dir may not exist, etc. ignore
321
322
showard170873e2009-01-07 00:22:26 +0000323class LoggerFile(object):
324 def write(self, data):
jadmanskifcc851b2009-01-07 17:31:12 +0000325 if data:
326 debug.get_logger().debug(data.rstrip("\n"))
showard170873e2009-01-07 00:22:26 +0000327
328
329 def flush(self):
330 pass