blob: e2d96e680b7c3eea20e1bb2aa8a06d1fcdb35d0f [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 "
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
39 destination otherwise). The names must already be
40 pre-processed into the appropriate rsync/scp friendly
41 format (%s@%s:%s).
42 """
43
44 print '_copy_files: copying %s to %s' % (sources, dest)
45 try:
46 ssh = make_ssh_command(self.user, self.port)
mbligh89e258d2008-10-24 13:58:08 +000047 if delete_dest:
48 delete_flag = "--delete"
49 else:
50 delete_flag = ""
51 command = "rsync -L %s --rsh='%s' -az %s %s"
52 command %= (delete_flag, ssh, " ".join(sources), dest)
jadmanskica7da372008-10-21 16:26:52 +000053 utils.run(command)
54 except Exception, e:
55 print "warning: rsync failed with: %s" % e
56 print "attempting to copy with scp instead"
57 try:
mbligh89e258d2008-10-24 13:58:08 +000058 if delete_dest:
59 dest_path = dest.split(":", 1)[1]
60 is_dir = self.run("ls -d %s/" % dest_path,
61 ignore_status=True).exit_status == 0
62 if is_dir:
63 cmd = "rm -rf %s && mkdir %s"
64 cmd %= (dest_path, dest_path)
65 self.run(cmd)
jadmanskiaedfcbc2008-10-29 14:31:57 +000066 command = "scp -rpq -P %d %s '%s'"
jadmanskica7da372008-10-21 16:26:52 +000067 command %= (self.port, ' '.join(sources), dest)
jadmanski0f80bf52008-10-22 15:46:03 +000068 utils.run(command)
jadmanskica7da372008-10-21 16:26:52 +000069 except error.CmdError, cmderr:
70 raise error.AutoservRunError(cmderr.args[0], cmderr.args[1])
71
72
mbligh89e258d2008-10-24 13:58:08 +000073 def get_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +000074 """
75 Copy files from the remote host to a local path.
76
77 Directories will be copied recursively.
78 If a source component is a directory with a trailing slash,
79 the content of the directory will be copied, otherwise, the
80 directory itself and its content will be copied. This
81 behavior is similar to that of the program 'rsync'.
82
83 Args:
84 source: either
85 1) a single file or directory, as a string
86 2) a list of one or more (possibly mixed)
87 files or directories
88 dest: a file or a directory (if source contains a
89 directory or more than one element, you must
90 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +000091 delete_dest: if this is true, the command will also clear
92 out any old files at dest that are not in the
93 source
jadmanskica7da372008-10-21 16:26:52 +000094
95 Raises:
96 AutoservRunError: the scp command failed
97 """
98 if isinstance(source, basestring):
99 source = [source]
100
101 processed_source = []
102 for path in source:
103 if path.endswith('/'):
104 format_string = '%s@%s:"%s*"'
105 else:
106 format_string = '%s@%s:"%s"'
107 entry = format_string % (self.user, self.hostname,
108 utils.scp_remote_escape(path))
109 processed_source.append(entry)
110
111 processed_dest = utils.sh_escape(os.path.abspath(dest))
112 if os.path.isdir(dest):
113 processed_dest += "/"
114
mbligh89e258d2008-10-24 13:58:08 +0000115 self._copy_files(processed_source, processed_dest, delete_dest)
jadmanskica7da372008-10-21 16:26:52 +0000116
117
mbligh89e258d2008-10-24 13:58:08 +0000118 def send_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000119 """
120 Copy files from a local path to the remote host.
121
122 Directories will be copied recursively.
123 If a source component is a directory with a trailing slash,
124 the content of the directory will be copied, otherwise, the
125 directory itself and its content will be copied. This
126 behavior is similar to that of the program 'rsync'.
127
128 Args:
129 source: either
130 1) a single file or directory, as a string
131 2) a list of one or more (possibly mixed)
132 files or directories
133 dest: a file or a directory (if source contains a
134 directory or more than one element, you must
135 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000136 delete_dest: if this is true, the command will also clear
137 out any old files at dest that are not in the
138 source
jadmanskica7da372008-10-21 16:26:52 +0000139
140 Raises:
141 AutoservRunError: the scp command failed
142 """
143 if isinstance(source, basestring):
144 source = [source]
145
146 processed_source = []
147 for path in source:
148 if path.endswith('/'):
149 format_string = '"%s/"*'
150 else:
151 format_string = '"%s"'
152 entry = format_string % (utils.sh_escape(os.path.abspath(path)),)
153 processed_source.append(entry)
154
155 remote_dest = '%s@%s:"%s"' % (self.user, self.hostname,
156 utils.scp_remote_escape(dest))
157
mbligh89e258d2008-10-24 13:58:08 +0000158 self._copy_files(processed_source, remote_dest, delete_dest)
jadmanskica7da372008-10-21 16:26:52 +0000159 self.run('find "%s" -type d -print0 | xargs -0r chmod o+rx' % dest)
160 self.run('find "%s" -type f -print0 | xargs -0r chmod o+r' % dest)
161 if self.target_file_owner:
162 self.run('chown -R %s %s' % (self.target_file_owner, dest))
163
164
165 def ssh_ping(self, timeout=60):
166 try:
167 self.run("true", timeout=timeout, connect_timeout=timeout)
168 except error.AutoservSSHTimeout:
169 msg = "ssh ping timed out (timeout = %d)" % timeout
170 raise error.AutoservSSHTimeout(msg)
171 except error.AutoservRunError, e:
172 msg = "command true failed in ssh ping"
173 raise error.AutoservRunError(msg, e.result_obj)
174
175
176 def is_up(self):
177 """
178 Check if the remote host is up.
179
180 Returns:
181 True if the remote host is up, False otherwise
182 """
183 try:
184 self.ssh_ping()
185 except error.AutoservError:
186 return False
187 else:
188 return True
189
190
191 def wait_up(self, timeout=None):
192 """
193 Wait until the remote host is up or the timeout expires.
194
195 In fact, it will wait until an ssh connection to the remote
196 host can be established, and getty is running.
197
198 Args:
199 timeout: time limit in seconds before returning even
200 if the host is not up.
201
202 Returns:
203 True if the host was found to be up, False otherwise
204 """
205 if timeout:
206 end_time = time.time() + timeout
207
208 while not timeout or time.time() < end_time:
209 if self.is_up():
210 try:
211 if self.are_wait_up_processes_up():
212 return True
213 except error.AutoservError:
214 pass
215 time.sleep(1)
216
217 return False
218
219
220 def wait_down(self, timeout=None):
221 """
222 Wait until the remote host is down or the timeout expires.
223
224 In fact, it will wait until an ssh connection to the remote
225 host fails.
226
227 Args:
228 timeout: time limit in seconds before returning even
229 if the host is not up.
230
231 Returns:
232 True if the host was found to be down, False otherwise
233 """
234 if timeout:
235 end_time = time.time() + timeout
236
237 while not timeout or time.time() < end_time:
238 if not self.is_up():
239 return True
240 time.sleep(1)
241
242 return False
jadmanskif6562912008-10-21 17:59:01 +0000243
244 # tunable constants for the verify & repair code
245 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
246 HOURS_TO_WAIT_FOR_RECOVERY = 2.5
247
248 def verify(self):
249 super(AbstractSSHHost, self).verify()
250
251 print 'Pinging host ' + self.hostname
252 self.ssh_ping()
253
254 try:
255 autodir = autotest._get_autodir(self)
256 if autodir:
257 print 'Checking diskspace for %s on %s' % (self.hostname,
258 autodir)
259 self.check_diskspace(autodir,
260 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
261 except error.AutoservHostError:
262 raise # only want to raise if it's a space issue
263 except Exception:
264 pass # autotest dir may not exist, etc. ignore
265
266
267 def repair_filesystem_only(self):
268 super(AbstractSSHHost, self).repair_filesystem_only()
269 self.wait_up(int(self.HOURS_TO_WAIT_FOR_RECOVERY * 3600))
270 self.reboot()
271
272
273 def repair_full(self):
274 super(AbstractSSHHost, self).repair_full()
275 try:
276 self.repair_filesystem_only()
277 self.verify()
278 except Exception:
279 # the filesystem-only repair failed, try something more drastic
280 print "Filesystem-only repair failed"
281 traceback.print_exc()
282 try:
283 self.machine_install()
284 except NotImplementedError, e:
285 sys.stderr.write(str(e) + "\n\n")