blob: 441e0cce7ef462689a3579b40b97782bb093f500 [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)
mbligh5f66ed42008-11-24 17:16:14 +0000168 print "ssh_ping of %s completed sucessfully" % self.hostname
jadmanskica7da372008-10-21 16:26:52 +0000169 except error.AutoservSSHTimeout:
170 msg = "ssh ping timed out (timeout = %d)" % timeout
171 raise error.AutoservSSHTimeout(msg)
172 except error.AutoservRunError, e:
173 msg = "command true failed in ssh ping"
174 raise error.AutoservRunError(msg, e.result_obj)
175
176
177 def is_up(self):
178 """
179 Check if the remote host is up.
180
181 Returns:
182 True if the remote host is up, False otherwise
183 """
184 try:
185 self.ssh_ping()
186 except error.AutoservError:
187 return False
188 else:
189 return True
190
191
192 def wait_up(self, timeout=None):
193 """
194 Wait until the remote host is up or the timeout expires.
195
196 In fact, it will wait until an ssh connection to the remote
197 host can be established, and getty is running.
198
199 Args:
200 timeout: time limit in seconds before returning even
201 if the host is not up.
202
203 Returns:
204 True if the host was found to be up, False otherwise
205 """
206 if timeout:
207 end_time = time.time() + timeout
208
209 while not timeout or time.time() < end_time:
210 if self.is_up():
211 try:
212 if self.are_wait_up_processes_up():
213 return True
214 except error.AutoservError:
215 pass
216 time.sleep(1)
217
218 return False
219
220
221 def wait_down(self, timeout=None):
222 """
223 Wait until the remote host is down or the timeout expires.
224
225 In fact, it will wait until an ssh connection to the remote
226 host fails.
227
228 Args:
229 timeout: time limit in seconds before returning even
230 if the host is not up.
231
232 Returns:
233 True if the host was found to be down, False otherwise
234 """
235 if timeout:
236 end_time = time.time() + timeout
237
238 while not timeout or time.time() < end_time:
239 if not self.is_up():
240 return True
241 time.sleep(1)
242
243 return False
jadmanskif6562912008-10-21 17:59:01 +0000244
245 # tunable constants for the verify & repair code
246 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
247 HOURS_TO_WAIT_FOR_RECOVERY = 2.5
248
249 def verify(self):
250 super(AbstractSSHHost, self).verify()
251
252 print 'Pinging host ' + self.hostname
253 self.ssh_ping()
254
255 try:
256 autodir = autotest._get_autodir(self)
257 if autodir:
jadmanskif6562912008-10-21 17:59:01 +0000258 self.check_diskspace(autodir,
259 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
260 except error.AutoservHostError:
261 raise # only want to raise if it's a space issue
262 except Exception:
263 pass # autotest dir may not exist, etc. ignore
264
265
266 def repair_filesystem_only(self):
267 super(AbstractSSHHost, self).repair_filesystem_only()
268 self.wait_up(int(self.HOURS_TO_WAIT_FOR_RECOVERY * 3600))
269 self.reboot()
270
271
272 def repair_full(self):
273 super(AbstractSSHHost, self).repair_full()
274 try:
275 self.repair_filesystem_only()
276 self.verify()
277 except Exception:
278 # the filesystem-only repair failed, try something more drastic
279 print "Filesystem-only repair failed"
280 traceback.print_exc()
281 try:
282 self.machine_install()
283 except NotImplementedError, e:
284 sys.stderr.write(str(e) + "\n\n")