blob: 9a5b38feb3d4a41d0c464d05f8d44640d38bd034 [file] [log] [blame]
jadmanski7d223812008-12-08 21:27:19 +00001import os, sys, time, types, socket, traceback, shutil
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
jadmanski7d223812008-12-08 21:27:19 +000039 destination otherwise). The remote paths must already be
jadmanskica7da372008-10-21 16:26:52 +000040 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:
jadmanski7d223812008-12-08 21:27:19 +000059 if ":" in dest:
60 # dest is remote
61 dest_path = dest.split(":", 1)[1]
62 is_dir = self.run("ls -d %s/" % dest_path,
63 ignore_status=True).exit_status == 0
64 if is_dir:
65 cmd = "rm -rf %s && mkdir %s"
66 cmd %= (dest_path, dest_path)
67 self.run(cmd)
68 else:
69 # dest is local
70 if os.path.isdir(dest):
71 shutil.rmtree(dest)
72 os.mkdir(dest)
jadmanskiaedfcbc2008-10-29 14:31:57 +000073 command = "scp -rpq -P %d %s '%s'"
jadmanskica7da372008-10-21 16:26:52 +000074 command %= (self.port, ' '.join(sources), dest)
jadmanski0f80bf52008-10-22 15:46:03 +000075 utils.run(command)
jadmanskica7da372008-10-21 16:26:52 +000076 except error.CmdError, cmderr:
77 raise error.AutoservRunError(cmderr.args[0], cmderr.args[1])
78
79
mbligh89e258d2008-10-24 13:58:08 +000080 def get_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +000081 """
82 Copy files from the remote host to a local path.
83
84 Directories will be copied recursively.
85 If a source component is a directory with a trailing slash,
86 the content of the directory will be copied, otherwise, the
87 directory itself and its content will be copied. This
88 behavior is similar to that of the program 'rsync'.
89
90 Args:
91 source: either
92 1) a single file or directory, as a string
93 2) a list of one or more (possibly mixed)
94 files or directories
95 dest: a file or a directory (if source contains a
96 directory or more than one element, you must
97 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +000098 delete_dest: if this is true, the command will also clear
99 out any old files at dest that are not in the
100 source
jadmanskica7da372008-10-21 16:26:52 +0000101
102 Raises:
103 AutoservRunError: the scp command failed
104 """
105 if isinstance(source, basestring):
106 source = [source]
107
108 processed_source = []
109 for path in source:
110 if path.endswith('/'):
111 format_string = '%s@%s:"%s*"'
112 else:
113 format_string = '%s@%s:"%s"'
114 entry = format_string % (self.user, self.hostname,
115 utils.scp_remote_escape(path))
116 processed_source.append(entry)
117
118 processed_dest = utils.sh_escape(os.path.abspath(dest))
119 if os.path.isdir(dest):
120 processed_dest += "/"
121
mbligh89e258d2008-10-24 13:58:08 +0000122 self._copy_files(processed_source, processed_dest, delete_dest)
jadmanskica7da372008-10-21 16:26:52 +0000123
124
mbligh89e258d2008-10-24 13:58:08 +0000125 def send_file(self, source, dest, delete_dest=False):
jadmanskica7da372008-10-21 16:26:52 +0000126 """
127 Copy files from a local path to the remote host.
128
129 Directories will be copied recursively.
130 If a source component is a directory with a trailing slash,
131 the content of the directory will be copied, otherwise, the
132 directory itself and its content will be copied. This
133 behavior is similar to that of the program 'rsync'.
134
135 Args:
136 source: either
137 1) a single file or directory, as a string
138 2) a list of one or more (possibly mixed)
139 files or directories
140 dest: a file or a directory (if source contains a
141 directory or more than one element, you must
142 supply a directory dest)
mbligh89e258d2008-10-24 13:58:08 +0000143 delete_dest: if this is true, the command will also clear
144 out any old files at dest that are not in the
145 source
jadmanskica7da372008-10-21 16:26:52 +0000146
147 Raises:
148 AutoservRunError: the scp command failed
149 """
150 if isinstance(source, basestring):
151 source = [source]
152
153 processed_source = []
154 for path in source:
155 if path.endswith('/'):
156 format_string = '"%s/"*'
157 else:
158 format_string = '"%s"'
159 entry = format_string % (utils.sh_escape(os.path.abspath(path)),)
160 processed_source.append(entry)
161
162 remote_dest = '%s@%s:"%s"' % (self.user, self.hostname,
163 utils.scp_remote_escape(dest))
164
mbligh89e258d2008-10-24 13:58:08 +0000165 self._copy_files(processed_source, remote_dest, delete_dest)
jadmanskica7da372008-10-21 16:26:52 +0000166 self.run('find "%s" -type d -print0 | xargs -0r chmod o+rx' % dest)
167 self.run('find "%s" -type f -print0 | xargs -0r chmod o+r' % dest)
168 if self.target_file_owner:
169 self.run('chown -R %s %s' % (self.target_file_owner, dest))
170
171
172 def ssh_ping(self, timeout=60):
173 try:
174 self.run("true", timeout=timeout, connect_timeout=timeout)
mbligh5f66ed42008-11-24 17:16:14 +0000175 print "ssh_ping of %s completed sucessfully" % self.hostname
jadmanskica7da372008-10-21 16:26:52 +0000176 except error.AutoservSSHTimeout:
177 msg = "ssh ping timed out (timeout = %d)" % timeout
178 raise error.AutoservSSHTimeout(msg)
179 except error.AutoservRunError, e:
180 msg = "command true failed in ssh ping"
181 raise error.AutoservRunError(msg, e.result_obj)
182
183
184 def is_up(self):
185 """
186 Check if the remote host is up.
187
188 Returns:
189 True if the remote host is up, False otherwise
190 """
191 try:
192 self.ssh_ping()
193 except error.AutoservError:
194 return False
195 else:
196 return True
197
198
199 def wait_up(self, timeout=None):
200 """
201 Wait until the remote host is up or the timeout expires.
202
203 In fact, it will wait until an ssh connection to the remote
204 host can be established, and getty is running.
205
206 Args:
207 timeout: time limit in seconds before returning even
208 if the host is not up.
209
210 Returns:
211 True if the host was found to be up, False otherwise
212 """
213 if timeout:
214 end_time = time.time() + timeout
215
216 while not timeout or time.time() < end_time:
217 if self.is_up():
218 try:
219 if self.are_wait_up_processes_up():
220 return True
221 except error.AutoservError:
222 pass
223 time.sleep(1)
224
225 return False
226
227
228 def wait_down(self, timeout=None):
229 """
230 Wait until the remote host is down or the timeout expires.
231
232 In fact, it will wait until an ssh connection to the remote
233 host fails.
234
235 Args:
236 timeout: time limit in seconds before returning even
237 if the host is not up.
238
239 Returns:
240 True if the host was found to be down, False otherwise
241 """
242 if timeout:
243 end_time = time.time() + timeout
244
245 while not timeout or time.time() < end_time:
246 if not self.is_up():
247 return True
248 time.sleep(1)
249
250 return False
jadmanskif6562912008-10-21 17:59:01 +0000251
252 # tunable constants for the verify & repair code
253 AUTOTEST_GB_DISKSPACE_REQUIRED = 20
254 HOURS_TO_WAIT_FOR_RECOVERY = 2.5
255
256 def verify(self):
257 super(AbstractSSHHost, self).verify()
258
259 print 'Pinging host ' + self.hostname
260 self.ssh_ping()
261
262 try:
263 autodir = autotest._get_autodir(self)
264 if autodir:
jadmanskif6562912008-10-21 17:59:01 +0000265 self.check_diskspace(autodir,
266 self.AUTOTEST_GB_DISKSPACE_REQUIRED)
267 except error.AutoservHostError:
268 raise # only want to raise if it's a space issue
269 except Exception:
270 pass # autotest dir may not exist, etc. ignore
271
272
273 def repair_filesystem_only(self):
274 super(AbstractSSHHost, self).repair_filesystem_only()
275 self.wait_up(int(self.HOURS_TO_WAIT_FOR_RECOVERY * 3600))
276 self.reboot()
277
278
279 def repair_full(self):
280 super(AbstractSSHHost, self).repair_full()
281 try:
282 self.repair_filesystem_only()
283 self.verify()
284 except Exception:
285 # the filesystem-only repair failed, try something more drastic
286 print "Filesystem-only repair failed"
287 traceback.print_exc()
288 try:
289 self.machine_install()
290 except NotImplementedError, e:
291 sys.stderr.write(str(e) + "\n\n")