blob: aa6a99760719b540e91c386a6fec18fc32f8fe70 [file] [log] [blame]
jadmanskica7da372008-10-21 16:26:52 +00001import os, time, types
2from autotest_lib.client.common_lib import error
3from autotest_lib.server import utils
4from 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 "
9 "-o ConnectTimeout=%d "
10 "-o ServerAliveInterval=300 "
11 "-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)
jadmanskica7da372008-10-21 16:26:52 +000027
28 self.user = user
29 self.port = port
30 self.password = password
31
32
33 def _copy_files(self, sources, dest):
34 """
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)
48 command = "rsync -L --rsh='%s' -az %s %s"
49 command %= (ssh, " ".join(sources), dest)
50 utils.run(command)
51 except Exception, e:
52 print "warning: rsync failed with: %s" % e
53 print "attempting to copy with scp instead"
54 try:
55 command = "scp -rpq -P %d %s '%s'"
56 command %= (self.port, ' '.join(sources), dest)
57 except error.CmdError, cmderr:
58 raise error.AutoservRunError(cmderr.args[0], cmderr.args[1])
59
60
61 def get_file(self, source, dest):
62 """
63 Copy files from the remote host to a local path.
64
65 Directories will be copied recursively.
66 If a source component is a directory with a trailing slash,
67 the content of the directory will be copied, otherwise, the
68 directory itself and its content will be copied. This
69 behavior is similar to that of the program 'rsync'.
70
71 Args:
72 source: either
73 1) a single file or directory, as a string
74 2) a list of one or more (possibly mixed)
75 files or directories
76 dest: a file or a directory (if source contains a
77 directory or more than one element, you must
78 supply a directory dest)
79
80 Raises:
81 AutoservRunError: the scp command failed
82 """
83 if isinstance(source, basestring):
84 source = [source]
85
86 processed_source = []
87 for path in source:
88 if path.endswith('/'):
89 format_string = '%s@%s:"%s*"'
90 else:
91 format_string = '%s@%s:"%s"'
92 entry = format_string % (self.user, self.hostname,
93 utils.scp_remote_escape(path))
94 processed_source.append(entry)
95
96 processed_dest = utils.sh_escape(os.path.abspath(dest))
97 if os.path.isdir(dest):
98 processed_dest += "/"
99
100 self._copy_files(processed_source, processed_dest)
101
102
103 def send_file(self, source, dest):
104 """
105 Copy files from a local path to the remote host.
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)
121
122 Raises:
123 AutoservRunError: the scp command failed
124 """
125 if isinstance(source, basestring):
126 source = [source]
127
128 processed_source = []
129 for path in source:
130 if path.endswith('/'):
131 format_string = '"%s/"*'
132 else:
133 format_string = '"%s"'
134 entry = format_string % (utils.sh_escape(os.path.abspath(path)),)
135 processed_source.append(entry)
136
137 remote_dest = '%s@%s:"%s"' % (self.user, self.hostname,
138 utils.scp_remote_escape(dest))
139
140 self._copy_files(processed_source, remote_dest)
141 self.run('find "%s" -type d -print0 | xargs -0r chmod o+rx' % dest)
142 self.run('find "%s" -type f -print0 | xargs -0r chmod o+r' % dest)
143 if self.target_file_owner:
144 self.run('chown -R %s %s' % (self.target_file_owner, dest))
145
146
147 def ssh_ping(self, timeout=60):
148 try:
149 self.run("true", timeout=timeout, connect_timeout=timeout)
150 except error.AutoservSSHTimeout:
151 msg = "ssh ping timed out (timeout = %d)" % timeout
152 raise error.AutoservSSHTimeout(msg)
153 except error.AutoservRunError, e:
154 msg = "command true failed in ssh ping"
155 raise error.AutoservRunError(msg, e.result_obj)
156
157
158 def is_up(self):
159 """
160 Check if the remote host is up.
161
162 Returns:
163 True if the remote host is up, False otherwise
164 """
165 try:
166 self.ssh_ping()
167 except error.AutoservError:
168 return False
169 else:
170 return True
171
172
173 def wait_up(self, timeout=None):
174 """
175 Wait until the remote host is up or the timeout expires.
176
177 In fact, it will wait until an ssh connection to the remote
178 host can be established, and getty is running.
179
180 Args:
181 timeout: time limit in seconds before returning even
182 if the host is not up.
183
184 Returns:
185 True if the host was found to be up, False otherwise
186 """
187 if timeout:
188 end_time = time.time() + timeout
189
190 while not timeout or time.time() < end_time:
191 if self.is_up():
192 try:
193 if self.are_wait_up_processes_up():
194 return True
195 except error.AutoservError:
196 pass
197 time.sleep(1)
198
199 return False
200
201
202 def wait_down(self, timeout=None):
203 """
204 Wait until the remote host is down or the timeout expires.
205
206 In fact, it will wait until an ssh connection to the remote
207 host fails.
208
209 Args:
210 timeout: time limit in seconds before returning even
211 if the host is not up.
212
213 Returns:
214 True if the host was found to be down, False otherwise
215 """
216 if timeout:
217 end_time = time.time() + timeout
218
219 while not timeout or time.time() < end_time:
220 if not self.is_up():
221 return True
222 time.sleep(1)
223
224 return False
jadmanskif6562912008-10-21 17:59:01 +0000225
226 # tunable constants for the verify & repair code
227 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
228 HOURS_TO_WAIT_FOR_RECOVERY = 2.5
229
230 def verify(self):
231 super(AbstractSSHHost, self).verify()
232
233 print 'Pinging host ' + self.hostname
234 self.ssh_ping()
235
236 try:
237 autodir = autotest._get_autodir(self)
238 if autodir:
239 print 'Checking diskspace for %s on %s' % (self.hostname,
240 autodir)
241 self.check_diskspace(autodir,
242 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
243 except error.AutoservHostError:
244 raise # only want to raise if it's a space issue
245 except Exception:
246 pass # autotest dir may not exist, etc. ignore
247
248
249 def repair_filesystem_only(self):
250 super(AbstractSSHHost, self).repair_filesystem_only()
251 self.wait_up(int(self.HOURS_TO_WAIT_FOR_RECOVERY * 3600))
252 self.reboot()
253
254
255 def repair_full(self):
256 super(AbstractSSHHost, self).repair_full()
257 try:
258 self.repair_filesystem_only()
259 self.verify()
260 except Exception:
261 # the filesystem-only repair failed, try something more drastic
262 print "Filesystem-only repair failed"
263 traceback.print_exc()
264 try:
265 self.machine_install()
266 except NotImplementedError, e:
267 sys.stderr.write(str(e) + "\n\n")