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