blob: c6707d18bbc3508eb91f500a2cac37e62635fb2e [file] [log] [blame]
jadmanski7d223812008-12-08 21:27:19 +00001import os, sys, time, types, socket, traceback, shutil
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
mbligh89e258d2008-10-24 13:58:08 +000032 def _copy_files(self, sources, dest, delete_dest):
jadmanskica7da372008-10-21 16:26:52 +000033 """
34 Copy files from one machine to another.
35
36 This is for internal use by other methods that intend to move
37 files between machines. It expects a list of source files and
38 a destination (a filename if the source is a single file, a
jadmanski7d223812008-12-08 21:27:19 +000039 destination otherwise). The remote paths must already be
jadmanskica7da372008-10-21 16:26:52 +000040 pre-processed into the appropriate rsync/scp friendly
41 format (%s@%s:%s).
42 """
43
showard170873e2009-01-07 00:22:26 +000044 debug.get_logger().debug('_copy_files: copying %s to %s' %
45 (sources, dest))
jadmanskica7da372008-10-21 16:26:52 +000046 try:
47 ssh = make_ssh_command(self.user, self.port)
mbligh89e258d2008-10-24 13:58:08 +000048 if delete_dest:
49 delete_flag = "--delete"
50 else:
51 delete_flag = ""
52 command = "rsync -L %s --rsh='%s' -az %s %s"
53 command %= (delete_flag, ssh, " ".join(sources), dest)
jadmanskica7da372008-10-21 16:26:52 +000054 utils.run(command)
55 except Exception, e:
56 print "warning: rsync failed with: %s" % e
57 print "attempting to copy with scp instead"
58 try:
mbligh89e258d2008-10-24 13:58:08 +000059 if delete_dest:
jadmanski7d223812008-12-08 21:27:19 +000060 if ":" in dest:
61 # dest is remote
62 dest_path = dest.split(":", 1)[1]
63 is_dir = self.run("ls -d %s/" % dest_path,
64 ignore_status=True).exit_status == 0
65 if is_dir:
66 cmd = "rm -rf %s && mkdir %s"
67 cmd %= (dest_path, dest_path)
68 self.run(cmd)
69 else:
70 # dest is local
71 if os.path.isdir(dest):
72 shutil.rmtree(dest)
73 os.mkdir(dest)
jadmanskiaedfcbc2008-10-29 14:31:57 +000074 command = "scp -rpq -P %d %s '%s'"
jadmanskica7da372008-10-21 16:26:52 +000075 command %= (self.port, ' '.join(sources), dest)
jadmanski0f80bf52008-10-22 15:46:03 +000076 utils.run(command)
jadmanskica7da372008-10-21 16:26:52 +000077 except error.CmdError, cmderr:
78 raise error.AutoservRunError(cmderr.args[0], cmderr.args[1])
79
80
mbligh89e258d2008-10-24 13:58:08 +000081 def get_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +000082 """
83 Copy files from the remote host to a local path.
84
85 Directories will be copied recursively.
86 If a source component is a directory with a trailing slash,
87 the content of the directory will be copied, otherwise, the
88 directory itself and its content will be copied. This
89 behavior is similar to that of the program 'rsync'.
90
91 Args:
92 source: either
93 1) a single file or directory, as a string
94 2) a list of one or more (possibly mixed)
95 files or directories
96 dest: a file or a directory (if source contains a
97 directory or more than one element, you must
98 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +000099 delete_dest: if this is true, the command will also clear
100 out any old files at dest that are not in the
101 source
jadmanskica7da372008-10-21 16:26:52 +0000102
103 Raises:
104 AutoservRunError: the scp command failed
105 """
106 if isinstance(source, basestring):
107 source = [source]
108
109 processed_source = []
110 for path in source:
111 if path.endswith('/'):
112 format_string = '%s@%s:"%s*"'
113 else:
114 format_string = '%s@%s:"%s"'
115 entry = format_string % (self.user, self.hostname,
116 utils.scp_remote_escape(path))
117 processed_source.append(entry)
118
119 processed_dest = utils.sh_escape(os.path.abspath(dest))
120 if os.path.isdir(dest):
121 processed_dest += "/"
122
mbligh89e258d2008-10-24 13:58:08 +0000123 self._copy_files(processed_source, processed_dest, delete_dest)
jadmanskica7da372008-10-21 16:26:52 +0000124
125
mbligh89e258d2008-10-24 13:58:08 +0000126 def send_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000127 """
128 Copy files from a local path to the remote host.
129
130 Directories will be copied recursively.
131 If a source component is a directory with a trailing slash,
132 the content of the directory will be copied, otherwise, the
133 directory itself and its content will be copied. This
134 behavior is similar to that of the program 'rsync'.
135
136 Args:
137 source: either
138 1) a single file or directory, as a string
139 2) a list of one or more (possibly mixed)
140 files or directories
141 dest: a file or a directory (if source contains a
142 directory or more than one element, you must
143 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000144 delete_dest: if this is true, the command will also clear
145 out any old files at dest that are not in the
146 source
jadmanskica7da372008-10-21 16:26:52 +0000147
148 Raises:
149 AutoservRunError: the scp command failed
150 """
151 if isinstance(source, basestring):
152 source = [source]
153
154 processed_source = []
155 for path in source:
156 if path.endswith('/'):
157 format_string = '"%s/"*'
158 else:
159 format_string = '"%s"'
160 entry = format_string % (utils.sh_escape(os.path.abspath(path)),)
161 processed_source.append(entry)
162
163 remote_dest = '%s@%s:"%s"' % (self.user, self.hostname,
164 utils.scp_remote_escape(dest))
165
mbligh89e258d2008-10-24 13:58:08 +0000166 self._copy_files(processed_source, remote_dest, delete_dest)
jadmanskica7da372008-10-21 16:26:52 +0000167 self.run('find "%s" -type d -print0 | xargs -0r chmod o+rx' % dest)
168 self.run('find "%s" -type f -print0 | xargs -0r chmod o+r' % dest)
169 if self.target_file_owner:
170 self.run('chown -R %s %s' % (self.target_file_owner, dest))
171
172
173 def ssh_ping(self, timeout=60):
174 try:
175 self.run("true", timeout=timeout, connect_timeout=timeout)
mbligh5f66ed42008-11-24 17:16:14 +0000176 print "ssh_ping of %s completed sucessfully" % self.hostname
jadmanskica7da372008-10-21 16:26:52 +0000177 except error.AutoservSSHTimeout:
178 msg = "ssh ping timed out (timeout = %d)" % timeout
179 raise error.AutoservSSHTimeout(msg)
180 except error.AutoservRunError, e:
181 msg = "command true failed in ssh ping"
182 raise error.AutoservRunError(msg, e.result_obj)
183
184
185 def is_up(self):
186 """
187 Check if the remote host is up.
188
189 Returns:
190 True if the remote host is up, False otherwise
191 """
192 try:
193 self.ssh_ping()
194 except error.AutoservError:
195 return False
196 else:
197 return True
198
199
200 def wait_up(self, timeout=None):
201 """
202 Wait until the remote host is up or the timeout expires.
203
204 In fact, it will wait until an ssh connection to the remote
205 host can be established, and getty is running.
206
207 Args:
208 timeout: time limit in seconds before returning even
209 if the host is not up.
210
211 Returns:
212 True if the host was found to be up, False otherwise
213 """
214 if timeout:
215 end_time = time.time() + timeout
216
217 while not timeout or time.time() < end_time:
218 if self.is_up():
219 try:
220 if self.are_wait_up_processes_up():
221 return True
222 except error.AutoservError:
223 pass
224 time.sleep(1)
225
226 return False
227
228
229 def wait_down(self, timeout=None):
230 """
231 Wait until the remote host is down or the timeout expires.
232
233 In fact, it will wait until an ssh connection to the remote
234 host fails.
235
236 Args:
237 timeout: time limit in seconds before returning even
238 if the host is not up.
239
240 Returns:
241 True if the host was found to be down, False otherwise
242 """
243 if timeout:
244 end_time = time.time() + timeout
245
246 while not timeout or time.time() < end_time:
247 if not self.is_up():
248 return True
249 time.sleep(1)
250
251 return False
jadmanskif6562912008-10-21 17:59:01 +0000252
253 # tunable constants for the verify & repair code
254 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
255 HOURS_TO_WAIT_FOR_RECOVERY = 2.5
256
257 def verify(self):
258 super(AbstractSSHHost, self).verify()
259
260 print 'Pinging host ' + self.hostname
261 self.ssh_ping()
262
263 try:
264 autodir = autotest._get_autodir(self)
265 if autodir:
jadmanskif6562912008-10-21 17:59:01 +0000266 self.check_diskspace(autodir,
267 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
268 except error.AutoservHostError:
269 raise # only want to raise if it's a space issue
270 except Exception:
271 pass # autotest dir may not exist, etc. ignore
272
273
274 def repair_filesystem_only(self):
275 super(AbstractSSHHost, self).repair_filesystem_only()
276 self.wait_up(int(self.HOURS_TO_WAIT_FOR_RECOVERY * 3600))
277 self.reboot()
278
279
280 def repair_full(self):
281 super(AbstractSSHHost, self).repair_full()
282 try:
283 self.repair_filesystem_only()
284 self.verify()
285 except Exception:
286 # the filesystem-only repair failed, try something more drastic
287 print "Filesystem-only repair failed"
288 traceback.print_exc()
289 try:
290 self.machine_install()
291 except NotImplementedError, e:
292 sys.stderr.write(str(e) + "\n\n")
showard170873e2009-01-07 00:22:26 +0000293
294
295class LoggerFile(object):
296 def write(self, data):
297 debug.get_logger().debug(data)
298
299
300 def flush(self):
301 pass