blob: ebd8a6f178ad6fabbefe39c482b71707bd3dda5f [file] [log] [blame]
jadmanski31c49b72008-10-27 20:44:48 +00001import os, sys, time, types, socket, traceback
jadmanskica7da372008-10-21 16:26:52 +00002from autotest_lib.client.common_lib import error
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 "
10 "-o GSSAPIAuthentication=no -o GSSAPIKeyExchange=no "
jadmanskica7da372008-10-21 16:26:52 +000011 "-l %s -p %d")
12 assert isinstance(connect_timeout, (int, long))
13 assert connect_timeout > 0 # can't disable the timeout
14 return base_command % (opts, connect_timeout, user, port)
15
16
17class AbstractSSHHost(site_host.SiteHost):
18 """ This class represents a generic implementation of most of the
19 framework necessary for controlling a host via ssh. It implements
20 almost all of the abstract Host methods, except for the core
21 Host.run method. """
22
jadmanskif6562912008-10-21 17:59:01 +000023 def _initialize(self, hostname, user="root", port=22, password="",
24 *args, **dargs):
25 super(AbstractSSHHost, self)._initialize(hostname=hostname,
26 *args, **dargs)
mbligh6369cf22008-10-24 17:21:57 +000027 self.ip = socket.getaddrinfo(self.hostname, None)[0][4][0]
jadmanskica7da372008-10-21 16:26:52 +000028 self.user = user
29 self.port = port
30 self.password = password
31
32
mbligh89e258d2008-10-24 13:58:08 +000033 def _copy_files(self, sources, dest, delete_dest):
jadmanskica7da372008-10-21 16:26:52 +000034 """
35 Copy files from one machine to another.
36
37 This is for internal use by other methods that intend to move
38 files between machines. It expects a list of source files and
39 a destination (a filename if the source is a single file, a
40 destination otherwise). The names must already be
41 pre-processed into the appropriate rsync/scp friendly
42 format (%s@%s:%s).
43 """
44
45 print '_copy_files: copying %s to %s' % (sources, dest)
46 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:
60 dest_path = dest.split(":", 1)[1]
61 is_dir = self.run("ls -d %s/" % dest_path,
62 ignore_status=True).exit_status == 0
63 if is_dir:
64 cmd = "rm -rf %s && mkdir %s"
65 cmd %= (dest_path, dest_path)
66 self.run(cmd)
jadmanski31c49b72008-10-27 20:44:48 +000067 command = ("scp -o GSSAPIAuthentication=no -o "
68 "GSSAPIKeyExchange=no -rpq -P %d %s '%s'")
jadmanskica7da372008-10-21 16:26:52 +000069 command %= (self.port, ' '.join(sources), dest)
jadmanski0f80bf52008-10-22 15:46:03 +000070 utils.run(command)
jadmanskica7da372008-10-21 16:26:52 +000071 except error.CmdError, cmderr:
72 raise error.AutoservRunError(cmderr.args[0], cmderr.args[1])
73
74
mbligh89e258d2008-10-24 13:58:08 +000075 def get_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +000076 """
77 Copy files from the remote host to a local path.
78
79 Directories will be copied recursively.
80 If a source component is a directory with a trailing slash,
81 the content of the directory will be copied, otherwise, the
82 directory itself and its content will be copied. This
83 behavior is similar to that of the program 'rsync'.
84
85 Args:
86 source: either
87 1) a single file or directory, as a string
88 2) a list of one or more (possibly mixed)
89 files or directories
90 dest: a file or a directory (if source contains a
91 directory or more than one element, you must
92 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +000093 delete_dest: if this is true, the command will also clear
94 out any old files at dest that are not in the
95 source
jadmanskica7da372008-10-21 16:26:52 +000096
97 Raises:
98 AutoservRunError: the scp command failed
99 """
100 if isinstance(source, basestring):
101 source = [source]
102
103 processed_source = []
104 for path in source:
105 if path.endswith('/'):
106 format_string = '%s@%s:"%s*"'
107 else:
108 format_string = '%s@%s:"%s"'
109 entry = format_string % (self.user, self.hostname,
110 utils.scp_remote_escape(path))
111 processed_source.append(entry)
112
113 processed_dest = utils.sh_escape(os.path.abspath(dest))
114 if os.path.isdir(dest):
115 processed_dest += "/"
116
mbligh89e258d2008-10-24 13:58:08 +0000117 self._copy_files(processed_source, processed_dest, delete_dest)
jadmanskica7da372008-10-21 16:26:52 +0000118
119
mbligh89e258d2008-10-24 13:58:08 +0000120 def send_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000121 """
122 Copy files from a local path to the remote host.
123
124 Directories will be copied recursively.
125 If a source component is a directory with a trailing slash,
126 the content of the directory will be copied, otherwise, the
127 directory itself and its content will be copied. This
128 behavior is similar to that of the program 'rsync'.
129
130 Args:
131 source: either
132 1) a single file or directory, as a string
133 2) a list of one or more (possibly mixed)
134 files or directories
135 dest: a file or a directory (if source contains a
136 directory or more than one element, you must
137 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000138 delete_dest: if this is true, the command will also clear
139 out any old files at dest that are not in the
140 source
jadmanskica7da372008-10-21 16:26:52 +0000141
142 Raises:
143 AutoservRunError: the scp command failed
144 """
145 if isinstance(source, basestring):
146 source = [source]
147
148 processed_source = []
149 for path in source:
150 if path.endswith('/'):
151 format_string = '"%s/"*'
152 else:
153 format_string = '"%s"'
154 entry = format_string % (utils.sh_escape(os.path.abspath(path)),)
155 processed_source.append(entry)
156
157 remote_dest = '%s@%s:"%s"' % (self.user, self.hostname,
158 utils.scp_remote_escape(dest))
159
mbligh89e258d2008-10-24 13:58:08 +0000160 self._copy_files(processed_source, remote_dest, delete_dest)
jadmanskica7da372008-10-21 16:26:52 +0000161 self.run('find "%s" -type d -print0 | xargs -0r chmod o+rx' % dest)
162 self.run('find "%s" -type f -print0 | xargs -0r chmod o+r' % dest)
163 if self.target_file_owner:
164 self.run('chown -R %s %s' % (self.target_file_owner, dest))
165
166
167 def ssh_ping(self, timeout=60):
168 try:
169 self.run("true", timeout=timeout, connect_timeout=timeout)
170 except error.AutoservSSHTimeout:
171 msg = "ssh ping timed out (timeout = %d)" % timeout
172 raise error.AutoservSSHTimeout(msg)
173 except error.AutoservRunError, e:
174 msg = "command true failed in ssh ping"
175 raise error.AutoservRunError(msg, e.result_obj)
176
177
178 def is_up(self):
179 """
180 Check if the remote host is up.
181
182 Returns:
183 True if the remote host is up, False otherwise
184 """
185 try:
186 self.ssh_ping()
187 except error.AutoservError:
188 return False
189 else:
190 return True
191
192
193 def wait_up(self, timeout=None):
194 """
195 Wait until the remote host is up or the timeout expires.
196
197 In fact, it will wait until an ssh connection to the remote
198 host can be established, and getty is running.
199
200 Args:
201 timeout: time limit in seconds before returning even
202 if the host is not up.
203
204 Returns:
205 True if the host was found to be up, False otherwise
206 """
207 if timeout:
208 end_time = time.time() + timeout
209
210 while not timeout or time.time() < end_time:
211 if self.is_up():
212 try:
213 if self.are_wait_up_processes_up():
214 return True
215 except error.AutoservError:
216 pass
217 time.sleep(1)
218
219 return False
220
221
222 def wait_down(self, timeout=None):
223 """
224 Wait until the remote host is down or the timeout expires.
225
226 In fact, it will wait until an ssh connection to the remote
227 host fails.
228
229 Args:
230 timeout: time limit in seconds before returning even
231 if the host is not up.
232
233 Returns:
234 True if the host was found to be down, False otherwise
235 """
236 if timeout:
237 end_time = time.time() + timeout
238
239 while not timeout or time.time() < end_time:
240 if not self.is_up():
241 return True
242 time.sleep(1)
243
244 return False
jadmanskif6562912008-10-21 17:59:01 +0000245
246 # tunable constants for the verify & repair code
247 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
248 HOURS_TO_WAIT_FOR_RECOVERY = 2.5
249
250 def verify(self):
251 super(AbstractSSHHost, self).verify()
252
253 print 'Pinging host ' + self.hostname
254 self.ssh_ping()
255
256 try:
257 autodir = autotest._get_autodir(self)
258 if autodir:
259 print 'Checking diskspace for %s on %s' % (self.hostname,
260 autodir)
261 self.check_diskspace(autodir,
262 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
263 except error.AutoservHostError:
264 raise # only want to raise if it's a space issue
265 except Exception:
266 pass # autotest dir may not exist, etc. ignore
267
268
269 def repair_filesystem_only(self):
270 super(AbstractSSHHost, self).repair_filesystem_only()
271 self.wait_up(int(self.HOURS_TO_WAIT_FOR_RECOVERY * 3600))
272 self.reboot()
273
274
275 def repair_full(self):
276 super(AbstractSSHHost, self).repair_full()
277 try:
278 self.repair_filesystem_only()
279 self.verify()
280 except Exception:
281 # the filesystem-only repair failed, try something more drastic
282 print "Filesystem-only repair failed"
283 traceback.print_exc()
284 try:
285 self.machine_install()
286 except NotImplementedError, e:
287 sys.stderr.write(str(e) + "\n\n")