blob: 9a4b655147c5f0af9a2569d77411b11602ad06c1 [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
mblighea397bb2008-02-02 19:17:51 +000018
19
mblighbea56822007-08-31 08:53:40 +000020# A dictionary of pid and a list of tmpdirs for that pid
21__tmp_dirs = {}
mblighdcd57a82007-07-11 23:06:47 +000022
23
mblighc25b58f2008-05-29 21:05:25 +000024############# we need pass throughs for the methods in client/common_lib/utils
jadmanski0afbb632008-06-06 21:10:57 +000025def run(command, timeout=None, ignore_status=False,
jadmanskibc5a8b12008-09-12 19:37:12 +000026 stdout_tee=None, stderr_tee=None, verbose=True):
jadmanski0afbb632008-06-06 21:10:57 +000027 return utils.run(command, timeout, ignore_status,
jadmanskibc5a8b12008-09-12 19:37:12 +000028 stdout_tee, stderr_tee, verbose)
mblighc25b58f2008-05-29 21:05:25 +000029
30
mblighc25b58f2008-05-29 21:05:25 +000031def system(command, timeout=None, ignore_status=False):
jadmanski0afbb632008-06-06 21:10:57 +000032 return utils.system(command, timeout, ignore_status)
mblighc25b58f2008-05-29 21:05:25 +000033
34
35def system_output(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +000036 retain_output=False):
37 return utils.system_output(command, timeout, ignore_status,
38 retain_output)
mblighc25b58f2008-05-29 21:05:25 +000039
40
mbligh02ff2d52008-06-03 15:00:21 +000041def urlopen(url, data=None, proxies=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +000042 return utils.urlopen(url, data=data, proxies=proxies, timeout=timeout)
mbligh02ff2d52008-06-03 15:00:21 +000043
44
45def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +000046 return utils.urlretrieve(url, filename=filename, reporthook=reporthook,
47 data=data, timeout=timeout)
mbligh02ff2d52008-06-03 15:00:21 +000048
49
mblighc25b58f2008-05-29 21:05:25 +000050def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +000051 return utils.read_keyval(path)
mblighc25b58f2008-05-29 21:05:25 +000052
53
54def write_keyval(path, dictionary):
jadmanski0afbb632008-06-06 21:10:57 +000055 return utils.write_keyval(path, dictionary)
mblighc25b58f2008-05-29 21:05:25 +000056
57
58####################################################################
59
mblighdcd57a82007-07-11 23:06:47 +000060def sh_escape(command):
jadmanski0afbb632008-06-06 21:10:57 +000061 """
62 Escape special characters from a command so that it can be passed
63 as a double quoted (" ") string in a (ba)sh command.
mblighdc735a22007-08-02 16:54:37 +000064
jadmanski0afbb632008-06-06 21:10:57 +000065 Args:
66 command: the command string to escape.
mblighdc735a22007-08-02 16:54:37 +000067
jadmanski0afbb632008-06-06 21:10:57 +000068 Returns:
69 The escaped command string. The required englobing double
70 quotes are NOT added and so should be added at some point by
71 the caller.
mblighdc735a22007-08-02 16:54:37 +000072
jadmanski0afbb632008-06-06 21:10:57 +000073 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
74 """
75 command = command.replace("\\", "\\\\")
76 command = command.replace("$", r'\$')
77 command = command.replace('"', r'\"')
78 command = command.replace('`', r'\`')
79 return command
mblighdcd57a82007-07-11 23:06:47 +000080
81
82def scp_remote_escape(filename):
jadmanski0afbb632008-06-06 21:10:57 +000083 """
84 Escape special characters from a filename so that it can be passed
85 to scp (within double quotes) as a remote file.
mblighdc735a22007-08-02 16:54:37 +000086
jadmanski0afbb632008-06-06 21:10:57 +000087 Bis-quoting has to be used with scp for remote files, "bis-quoting"
88 as in quoting x 2
89 scp does not support a newline in the filename
mblighdc735a22007-08-02 16:54:37 +000090
jadmanski0afbb632008-06-06 21:10:57 +000091 Args:
92 filename: the filename string to escape.
mblighdc735a22007-08-02 16:54:37 +000093
jadmanski0afbb632008-06-06 21:10:57 +000094 Returns:
95 The escaped filename string. The required englobing double
96 quotes are NOT added and so should be added at some point by
97 the caller.
98 """
99 escape_chars= r' !"$&' "'" r'()*,:;<=>?[\]^`{|}'
mblighdc735a22007-08-02 16:54:37 +0000100
jadmanski0afbb632008-06-06 21:10:57 +0000101 new_name= []
102 for char in filename:
103 if char in escape_chars:
104 new_name.append("\\%s" % (char,))
105 else:
106 new_name.append(char)
mblighdc735a22007-08-02 16:54:37 +0000107
jadmanski0afbb632008-06-06 21:10:57 +0000108 return sh_escape("".join(new_name))
mblighdcd57a82007-07-11 23:06:47 +0000109
110
mbligh6e18dab2007-10-24 21:27:18 +0000111def get(location, local_copy = False):
jadmanski0afbb632008-06-06 21:10:57 +0000112 """Get a file or directory to a local temporary directory.
mblighdc735a22007-08-02 16:54:37 +0000113
jadmanski0afbb632008-06-06 21:10:57 +0000114 Args:
115 location: the source of the material to get. This source may
116 be one of:
117 * a local file or directory
118 * a URL (http or ftp)
119 * a python file-like object
mblighdc735a22007-08-02 16:54:37 +0000120
jadmanski0afbb632008-06-06 21:10:57 +0000121 Returns:
122 The location of the file or directory where the requested
123 content was saved. This will be contained in a temporary
124 directory on the local host. If the material to get was a
125 directory, the location will contain a trailing '/'
126 """
127 tmpdir = get_tmp_dir()
mblighdc735a22007-08-02 16:54:37 +0000128
jadmanski0afbb632008-06-06 21:10:57 +0000129 # location is a file-like object
130 if hasattr(location, "read"):
131 tmpfile = os.path.join(tmpdir, "file")
132 tmpfileobj = file(tmpfile, 'w')
133 shutil.copyfileobj(location, tmpfileobj)
134 tmpfileobj.close()
135 return tmpfile
mblighdc735a22007-08-02 16:54:37 +0000136
jadmanski0afbb632008-06-06 21:10:57 +0000137 if isinstance(location, types.StringTypes):
138 # location is a URL
139 if location.startswith('http') or location.startswith('ftp'):
140 tmpfile = os.path.join(tmpdir, os.path.basename(location))
141 utils.urlretrieve(location, tmpfile)
142 return tmpfile
143 # location is a local path
144 elif os.path.exists(os.path.abspath(location)):
145 if not local_copy:
146 if os.path.isdir(location):
147 return location.rstrip('/') + '/'
148 else:
149 return location
150 tmpfile = os.path.join(tmpdir, os.path.basename(location))
151 if os.path.isdir(location):
152 tmpfile += '/'
153 shutil.copytree(location, tmpfile, symlinks=True)
154 return tmpfile
155 shutil.copyfile(location, tmpfile)
156 return tmpfile
157 # location is just a string, dump it to a file
158 else:
159 tmpfd, tmpfile = tempfile.mkstemp(dir=tmpdir)
160 tmpfileobj = os.fdopen(tmpfd, 'w')
161 tmpfileobj.write(location)
162 tmpfileobj.close()
163 return tmpfile
mblighdcd57a82007-07-11 23:06:47 +0000164
mbligh5f876ad2007-10-12 23:59:53 +0000165
mblighdcd57a82007-07-11 23:06:47 +0000166def get_tmp_dir():
jadmanski0afbb632008-06-06 21:10:57 +0000167 """Return the pathname of a directory on the host suitable
168 for temporary file storage.
mblighdc735a22007-08-02 16:54:37 +0000169
jadmanski0afbb632008-06-06 21:10:57 +0000170 The directory and its content will be deleted automatically
171 at the end of the program execution if they are still present.
172 """
173 global __tmp_dirs
mblighdc735a22007-08-02 16:54:37 +0000174
jadmanski0afbb632008-06-06 21:10:57 +0000175 dir_name= tempfile.mkdtemp(prefix="autoserv-")
176 pid = os.getpid()
177 if not pid in __tmp_dirs:
178 __tmp_dirs[pid] = []
179 __tmp_dirs[pid].append(dir_name)
180 return dir_name
mblighdcd57a82007-07-11 23:06:47 +0000181
182
183@atexit.register
184def __clean_tmp_dirs():
jadmanski0afbb632008-06-06 21:10:57 +0000185 """Erase temporary directories that were created by the get_tmp_dir()
186 function and that are still present.
187 """
188 global __tmp_dirs
mblighdc735a22007-08-02 16:54:37 +0000189
jadmanski0afbb632008-06-06 21:10:57 +0000190 pid = os.getpid()
191 if pid not in __tmp_dirs:
192 return
193 for dir in __tmp_dirs[pid]:
194 try:
195 shutil.rmtree(dir)
196 except OSError, e:
197 if e.errno == 2:
198 pass
199 __tmp_dirs[pid] = []
mblighc8949b82007-07-23 16:33:58 +0000200
201
202def unarchive(host, source_material):
jadmanski0afbb632008-06-06 21:10:57 +0000203 """Uncompress and untar an archive on a host.
mblighdc735a22007-08-02 16:54:37 +0000204
jadmanski0afbb632008-06-06 21:10:57 +0000205 If the "source_material" is compresses (according to the file
206 extension) it will be uncompressed. Supported compression formats
207 are gzip and bzip2. Afterwards, if the source_material is a tar
208 archive, it will be untarred.
mblighdc735a22007-08-02 16:54:37 +0000209
jadmanski0afbb632008-06-06 21:10:57 +0000210 Args:
211 host: the host object on which the archive is located
212 source_material: the path of the archive on the host
mblighdc735a22007-08-02 16:54:37 +0000213
jadmanski0afbb632008-06-06 21:10:57 +0000214 Returns:
215 The file or directory name of the unarchived source material.
216 If the material is a tar archive, it will be extracted in the
217 directory where it is and the path returned will be the first
218 entry in the archive, assuming it is the topmost directory.
219 If the material is not an archive, nothing will be done so this
220 function is "harmless" when it is "useless".
221 """
222 # uncompress
223 if (source_material.endswith(".gz") or
224 source_material.endswith(".gzip")):
225 host.run('gunzip "%s"' % (sh_escape(source_material)))
226 source_material= ".".join(source_material.split(".")[:-1])
227 elif source_material.endswith("bz2"):
228 host.run('bunzip2 "%s"' % (sh_escape(source_material)))
229 source_material= ".".join(source_material.split(".")[:-1])
mblighdc735a22007-08-02 16:54:37 +0000230
jadmanski0afbb632008-06-06 21:10:57 +0000231 # untar
232 if source_material.endswith(".tar"):
233 retval= host.run('tar -C "%s" -xvf "%s"' % (
234 sh_escape(os.path.dirname(source_material)),
235 sh_escape(source_material),))
236 source_material= os.path.join(os.path.dirname(source_material),
237 retval.stdout.split()[0])
mblighdc735a22007-08-02 16:54:37 +0000238
jadmanski0afbb632008-06-06 21:10:57 +0000239 return source_material
mblighf1c52842007-10-16 15:21:38 +0000240
241
mbligh9708f732007-10-18 03:18:54 +0000242def get_server_dir():
jadmanski0afbb632008-06-06 21:10:57 +0000243 path = os.path.dirname(sys.modules['autotest_lib.server.utils'].__file__)
244 return os.path.abspath(path)
mbligh40f122a2007-11-03 23:08:46 +0000245
246
mbligh34a3fd72007-12-10 17:16:22 +0000247def find_pid(command):
jadmanski0afbb632008-06-06 21:10:57 +0000248 for line in utils.system_output('ps -eo pid,cmd').rstrip().split('\n'):
249 (pid, cmd) = line.split(None, 1)
250 if re.search(command, cmd):
251 return int(pid)
252 return None
mbligh34a3fd72007-12-10 17:16:22 +0000253
254
255def nohup(command, stdout='/dev/null', stderr='/dev/null', background=True,
jadmanski0afbb632008-06-06 21:10:57 +0000256 env = {}):
257 cmd = ' '.join(key+'='+val for key, val in env.iteritems())
258 cmd += ' nohup ' + command
259 cmd += ' > %s' % stdout
260 if stdout == stderr:
261 cmd += ' 2>&1'
262 else:
263 cmd += ' 2> %s' % stderr
264 if background:
265 cmd += ' &'
266 utils.system(cmd)
mbligh34a3fd72007-12-10 17:16:22 +0000267
268
mbligh0b4fe6e2008-05-06 20:41:37 +0000269def default_mappings(machines):
jadmanski0afbb632008-06-06 21:10:57 +0000270 """
271 Returns a simple mapping in which all machines are assigned to the
272 same key. Provides the default behavior for
273 form_ntuples_from_machines. """
274 mappings = {}
275 failures = []
276
277 mach = machines[0]
278 mappings['ident'] = [mach]
279 if len(machines) > 1:
280 machines = machines[1:]
281 for machine in machines:
282 mappings['ident'].append(machine)
283
284 return (mappings, failures)
mbligh0b4fe6e2008-05-06 20:41:37 +0000285
286
287def form_ntuples_from_machines(machines, n=2, mapping_func=default_mappings):
jadmanski0afbb632008-06-06 21:10:57 +0000288 """Returns a set of ntuples from machines where the machines in an
289 ntuple are in the same mapping, and a set of failures which are
290 (machine name, reason) tuples."""
291 ntuples = []
292 (mappings, failures) = mapping_func(machines)
mbligh0b4fe6e2008-05-06 20:41:37 +0000293
jadmanski0afbb632008-06-06 21:10:57 +0000294 # now run through the mappings and create n-tuples.
295 # throw out the odd guys out
296 for key in mappings:
297 key_machines = mappings[key]
298 total_machines = len(key_machines)
mbligh0b4fe6e2008-05-06 20:41:37 +0000299
jadmanski0afbb632008-06-06 21:10:57 +0000300 # form n-tuples
301 while len(key_machines) >= n:
302 ntuples.append(key_machines[0:n])
303 key_machines = key_machines[n:]
mbligh0b4fe6e2008-05-06 20:41:37 +0000304
jadmanski0afbb632008-06-06 21:10:57 +0000305 for mach in key_machines:
306 failures.append((mach, "machine can not be tupled"))
307
308 return (ntuples, failures)
mblighaa9e6742008-06-05 21:14:05 +0000309
jadmanskib0605d92008-06-06 16:12:34 +0000310
mblighaa9e6742008-06-05 21:14:05 +0000311def parse_machine(machine, user = 'root', port = 22, password = ''):
jadmanski0afbb632008-06-06 21:10:57 +0000312 """
313 Parse the machine string user:pass@host:port and return it separately,
314 if the machine string is not complete, use the default parameters
315 when appropriate.
316 """
mblighaa9e6742008-06-05 21:14:05 +0000317
jadmanski0afbb632008-06-06 21:10:57 +0000318 user = user
319 port = port
320 password = password
mblighaa9e6742008-06-05 21:14:05 +0000321
jadmanski0afbb632008-06-06 21:10:57 +0000322 if re.search('@', machine):
323 machine = machine.split('@')
mblighaa9e6742008-06-05 21:14:05 +0000324
jadmanski0afbb632008-06-06 21:10:57 +0000325 if re.search(':', machine[0]):
326 machine[0] = machine[0].split(':')
327 user = machine[0][0]
328 password = machine[0][1]
mblighaa9e6742008-06-05 21:14:05 +0000329
jadmanski0afbb632008-06-06 21:10:57 +0000330 else:
331 user = machine[0]
mblighaa9e6742008-06-05 21:14:05 +0000332
jadmanski0afbb632008-06-06 21:10:57 +0000333 if re.search(':', machine[1]):
334 machine[1] = machine[1].split(':')
335 hostname = machine[1][0]
336 port = int(machine[1][1])
mblighaa9e6742008-06-05 21:14:05 +0000337
jadmanski0afbb632008-06-06 21:10:57 +0000338 else:
339 hostname = machine[1]
mblighaa9e6742008-06-05 21:14:05 +0000340
jadmanski0afbb632008-06-06 21:10:57 +0000341 elif re.search(':', machine):
342 machine = machine.split(':')
343 hostname = machine[0]
344 port = int(machine[1])
mblighaa9e6742008-06-05 21:14:05 +0000345
jadmanski0afbb632008-06-06 21:10:57 +0000346 else:
347 hostname = machine
mblighaa9e6742008-06-05 21:14:05 +0000348
jadmanski0afbb632008-06-06 21:10:57 +0000349 return hostname, user, password, port
mblighaa9e6742008-06-05 21:14:05 +0000350
351
352def get_public_key():
jadmanski0afbb632008-06-06 21:10:57 +0000353 """
354 Return a valid string ssh public key for the user executing autoserv or
355 autotest. If there's no DSA or RSA public key, create a DSA keypair with
356 ssh-keygen and return it.
357 """
mblighaa9e6742008-06-05 21:14:05 +0000358
jadmanski0afbb632008-06-06 21:10:57 +0000359 ssh_conf_path = os.path.join(os.environ['HOME'], '.ssh')
mblighaa9e6742008-06-05 21:14:05 +0000360
jadmanski0afbb632008-06-06 21:10:57 +0000361 dsa_public_key_path = os.path.join(ssh_conf_path, 'id_dsa.pub')
362 dsa_private_key_path = os.path.join(ssh_conf_path, 'id_dsa')
mblighaa9e6742008-06-05 21:14:05 +0000363
jadmanski0afbb632008-06-06 21:10:57 +0000364 rsa_public_key_path = os.path.join(ssh_conf_path, 'id_rsa.pub')
365 rsa_private_key_path = os.path.join(ssh_conf_path, 'id_rsa')
mblighaa9e6742008-06-05 21:14:05 +0000366
jadmanski0afbb632008-06-06 21:10:57 +0000367 has_dsa_keypair = os.path.isfile(dsa_public_key_path) and \
368 os.path.isfile(dsa_private_key_path)
369 has_rsa_keypair = os.path.isfile(rsa_public_key_path) and \
370 os.path.isfile(rsa_private_key_path)
mblighaa9e6742008-06-05 21:14:05 +0000371
jadmanski0afbb632008-06-06 21:10:57 +0000372 if has_dsa_keypair:
373 print 'DSA keypair found, using it'
374 public_key_path = dsa_public_key_path
mblighaa9e6742008-06-05 21:14:05 +0000375
jadmanski0afbb632008-06-06 21:10:57 +0000376 elif has_rsa_keypair:
377 print 'RSA keypair found, using it'
378 public_key_path = rsa_public_key_path
mblighaa9e6742008-06-05 21:14:05 +0000379
jadmanski0afbb632008-06-06 21:10:57 +0000380 else:
381 print 'Neither RSA nor DSA keypair found, creating DSA ssh key pair'
382 system('ssh-keygen -t dsa -q -N "" -f %s' % dsa_private_key_path)
383 public_key_path = dsa_public_key_path
mblighaa9e6742008-06-05 21:14:05 +0000384
jadmanski0afbb632008-06-06 21:10:57 +0000385 public_key = open(public_key_path, 'r')
386 public_key_str = public_key.read()
387 public_key.close()
mblighaa9e6742008-06-05 21:14:05 +0000388
jadmanski0afbb632008-06-06 21:10:57 +0000389 return public_key_str