blob: 5b3e008d0a9c244e295173538dd35fc88ac22b7d [file] [log] [blame]
mblighdcd57a82007-07-11 23:06:47 +00001#!/usr/bin/python
2#
3# Copyright 2007 Google Inc. Released under the GPL v2
4
mbligh7d2bde82007-08-02 16:26:10 +00005"""
6This module defines the SSHHost class.
mblighdcd57a82007-07-11 23:06:47 +00007
8Implementation details:
9You should import the "hosts" package instead of importing each type of host.
10
11 SSHHost: a remote machine with a ssh access
12"""
13
mbligh7d2bde82007-08-02 16:26:10 +000014__author__ = """
15mbligh@google.com (Martin J. Bligh),
mblighdcd57a82007-07-11 23:06:47 +000016poirier@google.com (Benjamin Poirier),
mbligh7d2bde82007-08-02 16:26:10 +000017stutsman@google.com (Ryan Stutsman)
18"""
mblighdcd57a82007-07-11 23:06:47 +000019
20
21import types
22import os
23import time
24
25import base_classes
26import utils
27import errors
mbligha0452c82007-08-08 20:24:57 +000028import bootloader
mblighdcd57a82007-07-11 23:06:47 +000029
30
31class SSHHost(base_classes.RemoteHost):
mbligh7d2bde82007-08-02 16:26:10 +000032 """
33 This class represents a remote machine controlled through an ssh
mblighdcd57a82007-07-11 23:06:47 +000034 session on which you can run programs.
mbligh7d2bde82007-08-02 16:26:10 +000035
mblighdcd57a82007-07-11 23:06:47 +000036 It is not the machine autoserv is running on. The machine must be
37 configured for password-less login, for example through public key
38 authentication.
mbligh7d2bde82007-08-02 16:26:10 +000039
mblighdcd57a82007-07-11 23:06:47 +000040 Implementation details:
41 This is a leaf class in an abstract class hierarchy, it must
42 implement the unimplemented methods in parent classes.
43 """
mbligh7d2bde82007-08-02 16:26:10 +000044
mblighdcd57a82007-07-11 23:06:47 +000045 def __init__(self, hostname, user="root", port=22):
mbligh7d2bde82007-08-02 16:26:10 +000046 """
47 Construct a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000048
49 Args:
50 hostname: network hostname or address of remote machine
51 user: user to log in as on the remote machine
52 port: port the ssh daemon is listening on on the remote
53 machine
54 """
55 super(SSHHost, self).__init__()
56
57 self.hostname= hostname
58 self.user= user
59 self.port= port
60 self.tmp_dirs= []
mbligha0452c82007-08-08 20:24:57 +000061 self.bootloader = bootloader.Bootloader(self)
mbligh7d2bde82007-08-02 16:26:10 +000062
63
mblighdcd57a82007-07-11 23:06:47 +000064 def __del__(self):
mbligh7d2bde82007-08-02 16:26:10 +000065 """
66 Destroy a SSHHost object
mblighdcd57a82007-07-11 23:06:47 +000067 """
68 for dir in self.tmp_dirs:
69 try:
70 self.run('rm -rf "%s"' % (utils.sh_escape(dir)))
71 except errors.AutoservRunError:
72 pass
mbligh7d2bde82007-08-02 16:26:10 +000073
74
mblighcf965b02007-07-25 16:49:45 +000075 def run(self, command, timeout=None, ignore_status=False):
mbligh7d2bde82007-08-02 16:26:10 +000076 """
77 Run a command on the remote host.
mblighdcd57a82007-07-11 23:06:47 +000078
79 Args:
80 command: the command line string
81 timeout: time limit in seconds before attempting to
82 kill the running process. The run() function
83 will take a few seconds longer than 'timeout'
84 to complete if it has to kill the process.
85
86 Returns:
87 a hosts.base_classes.CmdResult object
88
89 Raises:
90 AutoservRunError: the exit code of the command
91 execution was not 0
92 """
93 #~ print "running %s" % (command,)
94 result= utils.run(r'ssh -l %s -p %d %s "%s"' % (self.user,
95 self.port, self.hostname, utils.sh_escape(command)),
mblighcf965b02007-07-25 16:49:45 +000096 timeout, ignore_status)
mblighdcd57a82007-07-11 23:06:47 +000097 return result
mbligh7d2bde82007-08-02 16:26:10 +000098
99
mbligha0452c82007-08-08 20:24:57 +0000100 def reboot(self, timeout=600, label=None, kernel_args=None, wait=True):
mbligh7d2bde82007-08-02 16:26:10 +0000101 """
102 Reboot the remote host.
mbligha0452c82007-08-08 20:24:57 +0000103
104 Args:
105 timeout
106 """
107 if label or kernel_args:
108 self.bootloader.install_boottool()
109 if label:
110 self.bootloader.set_default(label)
111 if kernel_args:
112 if not label:
113 default = int(self.bootloader.get_default())
114 label = self.bootloader.get_titles()[default]
115 self.bootloader.add_args(label, kernel_args)
116 self.run('reboot')
117 if wait:
118 self.wait_down(60) # Make sure he's dead, Jim
119 print "Reboot: machine has gone down"
120 self.wait_up(timeout)
121 time.sleep(2) # this is needed for complete reliability
122 self.wait_up(timeout)
123 print "Reboot complete"
124
mbligh7d2bde82007-08-02 16:26:10 +0000125
mblighdcd57a82007-07-11 23:06:47 +0000126 def get_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000127 """
128 Copy files from the remote host to a local path.
mblighdcd57a82007-07-11 23:06:47 +0000129
130 Directories will be copied recursively.
131 If a source component is a directory with a trailing slash,
132 the content of the directory will be copied, otherwise, the
133 directory itself and its content will be copied. This
134 behavior is similar to that of the program 'rsync'.
135
136 Args:
137 source: either
138 1) a single file or directory, as a string
139 2) a list of one or more (possibly mixed)
140 files or directories
141 dest: a file or a directory (if source contains a
142 directory or more than one element, you must
143 supply a directory dest)
144
145 Raises:
146 AutoservRunError: the scp command failed
147 """
148 if isinstance(source, types.StringTypes):
149 source= [source]
150
151 processed_source= []
152 for entry in source:
153 if entry.endswith('/'):
154 format_string= '%s@%s:"%s*"'
155 else:
156 format_string= '%s@%s:"%s"'
157 entry= format_string % (self.user, self.hostname,
158 utils.scp_remote_escape(entry))
159 processed_source.append(entry)
160
161 processed_dest= os.path.abspath(dest)
162 if os.path.isdir(dest):
163 processed_dest= "%s/" % (utils.sh_escape(processed_dest),)
164 else:
165 processed_dest= utils.sh_escape(processed_dest)
166
167 utils.run('scp -rpq %s "%s"' % (
168 " ".join(processed_source),
169 processed_dest))
mbligh7d2bde82007-08-02 16:26:10 +0000170
171
mblighdcd57a82007-07-11 23:06:47 +0000172 def send_file(self, source, dest):
mbligh7d2bde82007-08-02 16:26:10 +0000173 """
174 Copy files from a local path to the remote host.
mblighdcd57a82007-07-11 23:06:47 +0000175
176 Directories will be copied recursively.
177 If a source component is a directory with a trailing slash,
178 the content of the directory will be copied, otherwise, the
179 directory itself and its content will be copied. This
180 behavior is similar to that of the program 'rsync'.
181
182 Args:
183 source: either
184 1) a single file or directory, as a string
185 2) a list of one or more (possibly mixed)
186 files or directories
187 dest: a file or a directory (if source contains a
188 directory or more than one element, you must
189 supply a directory dest)
190
191 Raises:
192 AutoservRunError: the scp command failed
193 """
194 if isinstance(source, types.StringTypes):
195 source= [source]
196
197 processed_source= []
198 for entry in source:
199 if entry.endswith('/'):
200 format_string= '"%s/"*'
201 else:
202 format_string= '"%s"'
203 entry= format_string % (utils.sh_escape(os.path.abspath(entry)),)
204 processed_source.append(entry)
205
206 utils.run('scp -rpq %s %s@%s:"%s"' % (
207 " ".join(processed_source), self.user, self.hostname,
208 utils.scp_remote_escape(dest)))
mbligh7d2bde82007-08-02 16:26:10 +0000209
210
mblighdcd57a82007-07-11 23:06:47 +0000211 def get_tmp_dir(self):
mbligh7d2bde82007-08-02 16:26:10 +0000212 """
213 Return the pathname of a directory on the host suitable
mblighdcd57a82007-07-11 23:06:47 +0000214 for temporary file storage.
215
216 The directory and its content will be deleted automatically
217 on the destruction of the Host object that was used to obtain
218 it.
219 """
220 dir_name= self.run("mktemp -dt autoserv-XXXXXX").stdout.rstrip(" \n")
221 self.tmp_dirs.append(dir_name)
222 return dir_name
mbligh7d2bde82007-08-02 16:26:10 +0000223
224
mblighdcd57a82007-07-11 23:06:47 +0000225 def is_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000226 """
227 Check if the remote host is up.
mblighdcd57a82007-07-11 23:06:47 +0000228
229 Returns:
230 True if the remote host is up, False otherwise
231 """
232 try:
233 result= self.run("true", timeout=10)
234 except errors.AutoservRunError:
235 return False
236 else:
237 if result.exit_status == 0:
238 return True
239 else:
mbligh7d2bde82007-08-02 16:26:10 +0000240
mblighdcd57a82007-07-11 23:06:47 +0000241 return False
mbligh7d2bde82007-08-02 16:26:10 +0000242
mblighdcd57a82007-07-11 23:06:47 +0000243 def wait_up(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000244 """
245 Wait until the remote host is up or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000246
247 In fact, it will wait until an ssh connection to the remote
248 host can be established.
249
250 Args:
251 timeout: time limit in seconds before returning even
252 if the host is not up.
253
254 Returns:
255 True if the host was found to be up, False otherwise
256 """
257 if timeout:
258 end_time= time.time() + timeout
259
260 while not timeout or time.time() < end_time:
261 try:
262 if timeout:
263 run_timeout= end_time - time.time()
264 else:
265 run_timeout= 10
266 result= self.run("true", timeout=run_timeout)
267 except errors.AutoservRunError:
268 pass
269 else:
270 if result.exit_status == 0:
271 return True
272 time.sleep(1)
273
274 return False
mbligh7d2bde82007-08-02 16:26:10 +0000275
276
mblighdcd57a82007-07-11 23:06:47 +0000277 def wait_down(self, timeout=None):
mbligh7d2bde82007-08-02 16:26:10 +0000278 """
279 Wait until the remote host is down or the timeout expires.
mblighdcd57a82007-07-11 23:06:47 +0000280
281 In fact, it will wait until an ssh connection to the remote
282 host fails.
283
284 Args:
285 timeout: time limit in seconds before returning even
286 if the host is not up.
287
288 Returns:
289 True if the host was found to be down, False otherwise
290 """
291 if timeout:
292 end_time= time.time() + timeout
293
294 while not timeout or time.time() < end_time:
295 try:
mbligh7e1e9642007-07-31 18:00:45 +0000296 run_timeout= 10
mblighdcd57a82007-07-11 23:06:47 +0000297 result= self.run("true", timeout=run_timeout)
298 except errors.AutoservRunError:
299 return True
300 else:
301 if result.aborted:
302 return True
303 time.sleep(1)
304
305 return False
mbligh7d2bde82007-08-02 16:26:10 +0000306
307
mblighdbe4a382007-07-26 19:41:28 +0000308 def ensure_up(self):
mbligh7d2bde82007-08-02 16:26:10 +0000309 """
310 Ensure the host is up if it is not then do not proceed;
311 this prevents cacading failures of tests
312 """
mbligha0452c82007-08-08 20:24:57 +0000313 print 'Ensuring that %s is up before continuing' % self.hostname
314 if hasattr(self, 'hardreset') and not self.wait_up(300):
mblighdbe4a382007-07-26 19:41:28 +0000315 print "Performing a hardreset on %s" % self.hostname
316 self.hardreset()
317 self.wait_up()
mbligha0452c82007-08-08 20:24:57 +0000318 print 'Host up, continuing'
mbligh7d2bde82007-08-02 16:26:10 +0000319
320
mblighdcd57a82007-07-11 23:06:47 +0000321 def get_num_cpu(self):
mbligh7d2bde82007-08-02 16:26:10 +0000322 """
323 Get the number of CPUs in the host according to
mblighdcd57a82007-07-11 23:06:47 +0000324 /proc/cpuinfo.
325
326 Returns:
327 The number of CPUs
328 """
329
330 proc_cpuinfo= self.run("cat /proc/cpuinfo").stdout
331 cpus = 0
332 for line in proc_cpuinfo.splitlines():
333 if line.startswith('processor'):
334 cpus += 1
335 return cpus