blob: d5991f50c5cd7a4a918509d26bd59aff75789c4b [file] [log] [blame]
mblighdcd57a82007-07-11 23:06:47 +00001#!/usr/bin/python
2#
mbligh63073c92008-03-31 16:49:32 +00003# Copyright 2008 Google Inc. Released under the GPL v2
mblighdcd57a82007-07-11 23:06:47 +00004
mblighdc735a22007-08-02 16:54:37 +00005"""
6Miscellaneous small functions.
mblighdcd57a82007-07-11 23:06:47 +00007"""
8
mblighdc735a22007-08-02 16:54:37 +00009__author__ = """
10mbligh@google.com (Martin J. Bligh),
mblighdcd57a82007-07-11 23:06:47 +000011poirier@google.com (Benjamin Poirier),
mblighdc735a22007-08-02 16:54:37 +000012stutsman@google.com (Ryan Stutsman)
13"""
mblighdcd57a82007-07-11 23:06:47 +000014
mbligh02ff2d52008-06-03 15:00:21 +000015import atexit, os, re, shutil, textwrap, sys, tempfile, types
mblighccb9e182008-04-17 15:42:10 +000016
mblighc25b58f2008-05-29 21:05:25 +000017from autotest_lib.client.common_lib import utils
jadmanski6d2ada52008-12-15 17:22:57 +000018from autotest_lib.server import subcommand
mblighea397bb2008-02-02 19:17:51 +000019
20
mblighbea56822007-08-31 08:53:40 +000021# A dictionary of pid and a list of tmpdirs for that pid
22__tmp_dirs = {}
mblighdcd57a82007-07-11 23:06:47 +000023
24
mblighc25b58f2008-05-29 21:05:25 +000025############# we need pass throughs for the methods in client/common_lib/utils
jadmanski0afbb632008-06-06 21:10:57 +000026def run(command, timeout=None, ignore_status=False,
showard170873e2009-01-07 00:22:26 +000027 stdout_tee=None, stderr_tee=None, verbose=True, stdin=None):
jadmanski0afbb632008-06-06 21:10:57 +000028 return utils.run(command, timeout, ignore_status,
showard170873e2009-01-07 00:22:26 +000029 stdout_tee, stderr_tee, verbose, stdin=stdin)
mblighc25b58f2008-05-29 21:05:25 +000030
31
mblighc25b58f2008-05-29 21:05:25 +000032def system(command, timeout=None, ignore_status=False):
jadmanski0afbb632008-06-06 21:10:57 +000033 return utils.system(command, timeout, ignore_status)
mblighc25b58f2008-05-29 21:05:25 +000034
35
36def system_output(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +000037 retain_output=False):
38 return utils.system_output(command, timeout, ignore_status,
39 retain_output)
mblighc25b58f2008-05-29 21:05:25 +000040
41
mbligh02ff2d52008-06-03 15:00:21 +000042def urlopen(url, data=None, proxies=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +000043 return utils.urlopen(url, data=data, proxies=proxies, timeout=timeout)
mbligh02ff2d52008-06-03 15:00:21 +000044
45
46def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +000047 return utils.urlretrieve(url, filename=filename, reporthook=reporthook,
48 data=data, timeout=timeout)
mbligh02ff2d52008-06-03 15:00:21 +000049
50
mblighc25b58f2008-05-29 21:05:25 +000051def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +000052 return utils.read_keyval(path)
mblighc25b58f2008-05-29 21:05:25 +000053
54
55def write_keyval(path, dictionary):
jadmanski0afbb632008-06-06 21:10:57 +000056 return utils.write_keyval(path, dictionary)
mblighc25b58f2008-05-29 21:05:25 +000057
58
59####################################################################
60
mblighdcd57a82007-07-11 23:06:47 +000061def sh_escape(command):
jadmanski0afbb632008-06-06 21:10:57 +000062 """
63 Escape special characters from a command so that it can be passed
64 as a double quoted (" ") string in a (ba)sh command.
mblighdc735a22007-08-02 16:54:37 +000065
jadmanski0afbb632008-06-06 21:10:57 +000066 Args:
67 command: the command string to escape.
mblighdc735a22007-08-02 16:54:37 +000068
jadmanski0afbb632008-06-06 21:10:57 +000069 Returns:
70 The escaped command string. The required englobing double
71 quotes are NOT added and so should be added at some point by
72 the caller.
mblighdc735a22007-08-02 16:54:37 +000073
jadmanski0afbb632008-06-06 21:10:57 +000074 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
75 """
76 command = command.replace("\\", "\\\\")
77 command = command.replace("$", r'\$')
78 command = command.replace('"', r'\"')
79 command = command.replace('`', r'\`')
80 return command
mblighdcd57a82007-07-11 23:06:47 +000081
82
83def scp_remote_escape(filename):
jadmanski0afbb632008-06-06 21:10:57 +000084 """
85 Escape special characters from a filename so that it can be passed
86 to scp (within double quotes) as a remote file.
mblighdc735a22007-08-02 16:54:37 +000087
jadmanski0afbb632008-06-06 21:10:57 +000088 Bis-quoting has to be used with scp for remote files, "bis-quoting"
89 as in quoting x 2
90 scp does not support a newline in the filename
mblighdc735a22007-08-02 16:54:37 +000091
jadmanski0afbb632008-06-06 21:10:57 +000092 Args:
93 filename: the filename string to escape.
mblighdc735a22007-08-02 16:54:37 +000094
jadmanski0afbb632008-06-06 21:10:57 +000095 Returns:
96 The escaped filename string. The required englobing double
97 quotes are NOT added and so should be added at some point by
98 the caller.
99 """
100 escape_chars= r' !"$&' "'" r'()*,:;<=>?[\]^`{|}'
mblighdc735a22007-08-02 16:54:37 +0000101
jadmanski0afbb632008-06-06 21:10:57 +0000102 new_name= []
103 for char in filename:
104 if char in escape_chars:
105 new_name.append("\\%s" % (char,))
106 else:
107 new_name.append(char)
mblighdc735a22007-08-02 16:54:37 +0000108
jadmanski0afbb632008-06-06 21:10:57 +0000109 return sh_escape("".join(new_name))
mblighdcd57a82007-07-11 23:06:47 +0000110
111
mbligh6e18dab2007-10-24 21:27:18 +0000112def get(location, local_copy = False):
jadmanski0afbb632008-06-06 21:10:57 +0000113 """Get a file or directory to a local temporary directory.
mblighdc735a22007-08-02 16:54:37 +0000114
jadmanski0afbb632008-06-06 21:10:57 +0000115 Args:
116 location: the source of the material to get. This source may
117 be one of:
118 * a local file or directory
119 * a URL (http or ftp)
120 * a python file-like object
mblighdc735a22007-08-02 16:54:37 +0000121
jadmanski0afbb632008-06-06 21:10:57 +0000122 Returns:
123 The location of the file or directory where the requested
124 content was saved. This will be contained in a temporary
125 directory on the local host. If the material to get was a
126 directory, the location will contain a trailing '/'
127 """
128 tmpdir = get_tmp_dir()
mblighdc735a22007-08-02 16:54:37 +0000129
jadmanski0afbb632008-06-06 21:10:57 +0000130 # location is a file-like object
131 if hasattr(location, "read"):
132 tmpfile = os.path.join(tmpdir, "file")
133 tmpfileobj = file(tmpfile, 'w')
134 shutil.copyfileobj(location, tmpfileobj)
135 tmpfileobj.close()
136 return tmpfile
mblighdc735a22007-08-02 16:54:37 +0000137
jadmanski0afbb632008-06-06 21:10:57 +0000138 if isinstance(location, types.StringTypes):
139 # location is a URL
140 if location.startswith('http') or location.startswith('ftp'):
141 tmpfile = os.path.join(tmpdir, os.path.basename(location))
142 utils.urlretrieve(location, tmpfile)
143 return tmpfile
144 # location is a local path
145 elif os.path.exists(os.path.abspath(location)):
146 if not local_copy:
147 if os.path.isdir(location):
148 return location.rstrip('/') + '/'
149 else:
150 return location
151 tmpfile = os.path.join(tmpdir, os.path.basename(location))
152 if os.path.isdir(location):
153 tmpfile += '/'
154 shutil.copytree(location, tmpfile, symlinks=True)
155 return tmpfile
156 shutil.copyfile(location, tmpfile)
157 return tmpfile
158 # location is just a string, dump it to a file
159 else:
160 tmpfd, tmpfile = tempfile.mkstemp(dir=tmpdir)
161 tmpfileobj = os.fdopen(tmpfd, 'w')
162 tmpfileobj.write(location)
163 tmpfileobj.close()
164 return tmpfile
mblighdcd57a82007-07-11 23:06:47 +0000165
mbligh5f876ad2007-10-12 23:59:53 +0000166
mblighdcd57a82007-07-11 23:06:47 +0000167def get_tmp_dir():
jadmanski0afbb632008-06-06 21:10:57 +0000168 """Return the pathname of a directory on the host suitable
169 for temporary file storage.
mblighdc735a22007-08-02 16:54:37 +0000170
jadmanski0afbb632008-06-06 21:10:57 +0000171 The directory and its content will be deleted automatically
172 at the end of the program execution if they are still present.
173 """
jadmanski6d2ada52008-12-15 17:22:57 +0000174 dir_name = tempfile.mkdtemp(prefix="autoserv-")
jadmanski0afbb632008-06-06 21:10:57 +0000175 pid = os.getpid()
176 if not pid in __tmp_dirs:
177 __tmp_dirs[pid] = []
178 __tmp_dirs[pid].append(dir_name)
179 return dir_name
mblighdcd57a82007-07-11 23:06:47 +0000180
181
mblighdcd57a82007-07-11 23:06:47 +0000182def __clean_tmp_dirs():
jadmanski0afbb632008-06-06 21:10:57 +0000183 """Erase temporary directories that were created by the get_tmp_dir()
184 function and that are still present.
185 """
jadmanski0afbb632008-06-06 21:10:57 +0000186 pid = os.getpid()
187 if pid not in __tmp_dirs:
188 return
189 for dir in __tmp_dirs[pid]:
190 try:
191 shutil.rmtree(dir)
192 except OSError, e:
193 if e.errno == 2:
194 pass
195 __tmp_dirs[pid] = []
jadmanski6d2ada52008-12-15 17:22:57 +0000196atexit.register(__clean_tmp_dirs)
197subcommand.subcommand.register_join_hook(lambda _: __clean_tmp_dirs())
mblighc8949b82007-07-23 16:33:58 +0000198
199
200def unarchive(host, source_material):
jadmanski0afbb632008-06-06 21:10:57 +0000201 """Uncompress and untar an archive on a host.
mblighdc735a22007-08-02 16:54:37 +0000202
jadmanski0afbb632008-06-06 21:10:57 +0000203 If the "source_material" is compresses (according to the file
204 extension) it will be uncompressed. Supported compression formats
205 are gzip and bzip2. Afterwards, if the source_material is a tar
206 archive, it will be untarred.
mblighdc735a22007-08-02 16:54:37 +0000207
jadmanski0afbb632008-06-06 21:10:57 +0000208 Args:
209 host: the host object on which the archive is located
210 source_material: the path of the archive on the host
mblighdc735a22007-08-02 16:54:37 +0000211
jadmanski0afbb632008-06-06 21:10:57 +0000212 Returns:
213 The file or directory name of the unarchived source material.
214 If the material is a tar archive, it will be extracted in the
215 directory where it is and the path returned will be the first
216 entry in the archive, assuming it is the topmost directory.
217 If the material is not an archive, nothing will be done so this
218 function is "harmless" when it is "useless".
219 """
220 # uncompress
221 if (source_material.endswith(".gz") or
222 source_material.endswith(".gzip")):
223 host.run('gunzip "%s"' % (sh_escape(source_material)))
224 source_material= ".".join(source_material.split(".")[:-1])
225 elif source_material.endswith("bz2"):
226 host.run('bunzip2 "%s"' % (sh_escape(source_material)))
227 source_material= ".".join(source_material.split(".")[:-1])
mblighdc735a22007-08-02 16:54:37 +0000228
jadmanski0afbb632008-06-06 21:10:57 +0000229 # untar
230 if source_material.endswith(".tar"):
231 retval= host.run('tar -C "%s" -xvf "%s"' % (
232 sh_escape(os.path.dirname(source_material)),
233 sh_escape(source_material),))
234 source_material= os.path.join(os.path.dirname(source_material),
235 retval.stdout.split()[0])
mblighdc735a22007-08-02 16:54:37 +0000236
jadmanski0afbb632008-06-06 21:10:57 +0000237 return source_material
mblighf1c52842007-10-16 15:21:38 +0000238
239
mbligh9708f732007-10-18 03:18:54 +0000240def get_server_dir():
jadmanski0afbb632008-06-06 21:10:57 +0000241 path = os.path.dirname(sys.modules['autotest_lib.server.utils'].__file__)
242 return os.path.abspath(path)
mbligh40f122a2007-11-03 23:08:46 +0000243
244
mbligh34a3fd72007-12-10 17:16:22 +0000245def find_pid(command):
jadmanski0afbb632008-06-06 21:10:57 +0000246 for line in utils.system_output('ps -eo pid,cmd').rstrip().split('\n'):
247 (pid, cmd) = line.split(None, 1)
248 if re.search(command, cmd):
249 return int(pid)
250 return None
mbligh34a3fd72007-12-10 17:16:22 +0000251
252
253def nohup(command, stdout='/dev/null', stderr='/dev/null', background=True,
jadmanski0afbb632008-06-06 21:10:57 +0000254 env = {}):
255 cmd = ' '.join(key+'='+val for key, val in env.iteritems())
256 cmd += ' nohup ' + command
257 cmd += ' > %s' % stdout
258 if stdout == stderr:
259 cmd += ' 2>&1'
260 else:
261 cmd += ' 2> %s' % stderr
262 if background:
263 cmd += ' &'
264 utils.system(cmd)
mbligh34a3fd72007-12-10 17:16:22 +0000265
266
mbligh0b4fe6e2008-05-06 20:41:37 +0000267def default_mappings(machines):
jadmanski0afbb632008-06-06 21:10:57 +0000268 """
269 Returns a simple mapping in which all machines are assigned to the
270 same key. Provides the default behavior for
271 form_ntuples_from_machines. """
272 mappings = {}
273 failures = []
274
275 mach = machines[0]
276 mappings['ident'] = [mach]
277 if len(machines) > 1:
278 machines = machines[1:]
279 for machine in machines:
280 mappings['ident'].append(machine)
281
282 return (mappings, failures)
mbligh0b4fe6e2008-05-06 20:41:37 +0000283
284
285def form_ntuples_from_machines(machines, n=2, mapping_func=default_mappings):
jadmanski0afbb632008-06-06 21:10:57 +0000286 """Returns a set of ntuples from machines where the machines in an
287 ntuple are in the same mapping, and a set of failures which are
288 (machine name, reason) tuples."""
289 ntuples = []
290 (mappings, failures) = mapping_func(machines)
mbligh0b4fe6e2008-05-06 20:41:37 +0000291
jadmanski0afbb632008-06-06 21:10:57 +0000292 # now run through the mappings and create n-tuples.
293 # throw out the odd guys out
294 for key in mappings:
295 key_machines = mappings[key]
296 total_machines = len(key_machines)
mbligh0b4fe6e2008-05-06 20:41:37 +0000297
jadmanski0afbb632008-06-06 21:10:57 +0000298 # form n-tuples
299 while len(key_machines) >= n:
300 ntuples.append(key_machines[0:n])
301 key_machines = key_machines[n:]
mbligh0b4fe6e2008-05-06 20:41:37 +0000302
jadmanski0afbb632008-06-06 21:10:57 +0000303 for mach in key_machines:
304 failures.append((mach, "machine can not be tupled"))
305
306 return (ntuples, failures)
mblighaa9e6742008-06-05 21:14:05 +0000307
jadmanskib0605d92008-06-06 16:12:34 +0000308
mblighaa9e6742008-06-05 21:14:05 +0000309def parse_machine(machine, user = 'root', port = 22, password = ''):
jadmanski0afbb632008-06-06 21:10:57 +0000310 """
311 Parse the machine string user:pass@host:port and return it separately,
312 if the machine string is not complete, use the default parameters
313 when appropriate.
314 """
mblighaa9e6742008-06-05 21:14:05 +0000315
jadmanski0afbb632008-06-06 21:10:57 +0000316 user = user
317 port = port
318 password = password
mblighaa9e6742008-06-05 21:14:05 +0000319
jadmanski0afbb632008-06-06 21:10:57 +0000320 if re.search('@', machine):
321 machine = machine.split('@')
mblighaa9e6742008-06-05 21:14:05 +0000322
jadmanski0afbb632008-06-06 21:10:57 +0000323 if re.search(':', machine[0]):
324 machine[0] = machine[0].split(':')
325 user = machine[0][0]
326 password = machine[0][1]
mblighaa9e6742008-06-05 21:14:05 +0000327
jadmanski0afbb632008-06-06 21:10:57 +0000328 else:
329 user = machine[0]
mblighaa9e6742008-06-05 21:14:05 +0000330
jadmanski0afbb632008-06-06 21:10:57 +0000331 if re.search(':', machine[1]):
332 machine[1] = machine[1].split(':')
333 hostname = machine[1][0]
334 port = int(machine[1][1])
mblighaa9e6742008-06-05 21:14:05 +0000335
jadmanski0afbb632008-06-06 21:10:57 +0000336 else:
337 hostname = machine[1]
mblighaa9e6742008-06-05 21:14:05 +0000338
jadmanski0afbb632008-06-06 21:10:57 +0000339 elif re.search(':', machine):
340 machine = machine.split(':')
341 hostname = machine[0]
342 port = int(machine[1])
mblighaa9e6742008-06-05 21:14:05 +0000343
jadmanski0afbb632008-06-06 21:10:57 +0000344 else:
345 hostname = machine
mblighaa9e6742008-06-05 21:14:05 +0000346
jadmanski0afbb632008-06-06 21:10:57 +0000347 return hostname, user, password, port
mblighaa9e6742008-06-05 21:14:05 +0000348
349
350def get_public_key():
jadmanski0afbb632008-06-06 21:10:57 +0000351 """
352 Return a valid string ssh public key for the user executing autoserv or
353 autotest. If there's no DSA or RSA public key, create a DSA keypair with
354 ssh-keygen and return it.
355 """
mblighaa9e6742008-06-05 21:14:05 +0000356
jadmanski0afbb632008-06-06 21:10:57 +0000357 ssh_conf_path = os.path.join(os.environ['HOME'], '.ssh')
mblighaa9e6742008-06-05 21:14:05 +0000358
jadmanski0afbb632008-06-06 21:10:57 +0000359 dsa_public_key_path = os.path.join(ssh_conf_path, 'id_dsa.pub')
360 dsa_private_key_path = os.path.join(ssh_conf_path, 'id_dsa')
mblighaa9e6742008-06-05 21:14:05 +0000361
jadmanski0afbb632008-06-06 21:10:57 +0000362 rsa_public_key_path = os.path.join(ssh_conf_path, 'id_rsa.pub')
363 rsa_private_key_path = os.path.join(ssh_conf_path, 'id_rsa')
mblighaa9e6742008-06-05 21:14:05 +0000364
jadmanski0afbb632008-06-06 21:10:57 +0000365 has_dsa_keypair = os.path.isfile(dsa_public_key_path) and \
366 os.path.isfile(dsa_private_key_path)
367 has_rsa_keypair = os.path.isfile(rsa_public_key_path) and \
368 os.path.isfile(rsa_private_key_path)
mblighaa9e6742008-06-05 21:14:05 +0000369
jadmanski0afbb632008-06-06 21:10:57 +0000370 if has_dsa_keypair:
371 print 'DSA keypair found, using it'
372 public_key_path = dsa_public_key_path
mblighaa9e6742008-06-05 21:14:05 +0000373
jadmanski0afbb632008-06-06 21:10:57 +0000374 elif has_rsa_keypair:
375 print 'RSA keypair found, using it'
376 public_key_path = rsa_public_key_path
mblighaa9e6742008-06-05 21:14:05 +0000377
jadmanski0afbb632008-06-06 21:10:57 +0000378 else:
379 print 'Neither RSA nor DSA keypair found, creating DSA ssh key pair'
380 system('ssh-keygen -t dsa -q -N "" -f %s' % dsa_private_key_path)
381 public_key_path = dsa_public_key_path
mblighaa9e6742008-06-05 21:14:05 +0000382
jadmanski0afbb632008-06-06 21:10:57 +0000383 public_key = open(public_key_path, 'r')
384 public_key_str = public_key.read()
385 public_key.close()
mblighaa9e6742008-06-05 21:14:05 +0000386
jadmanski0afbb632008-06-06 21:10:57 +0000387 return public_key_str