blob: 0559c1229e8e35ff0bcebd1e71509c4cdb2b617f [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
mblighc25b58f2008-05-29 21:05:25 +000026
mbligha64835c2009-01-07 16:45:09 +000027run = utils.run
28system = utils.system
29system_output = utils.system_output
30urlopen = utils.urlopen
31urlretrieve = utils.urlretrieve
32read_keyval = utils.read_keyval
33write_keyval = utils.write_keyval
mblighc25b58f2008-05-29 21:05:25 +000034
35####################################################################
36
mblighdcd57a82007-07-11 23:06:47 +000037def sh_escape(command):
jadmanski0afbb632008-06-06 21:10:57 +000038 """
39 Escape special characters from a command so that it can be passed
40 as a double quoted (" ") string in a (ba)sh command.
mblighdc735a22007-08-02 16:54:37 +000041
jadmanski0afbb632008-06-06 21:10:57 +000042 Args:
43 command: the command string to escape.
mblighdc735a22007-08-02 16:54:37 +000044
jadmanski0afbb632008-06-06 21:10:57 +000045 Returns:
46 The escaped command string. The required englobing double
47 quotes are NOT added and so should be added at some point by
48 the caller.
mblighdc735a22007-08-02 16:54:37 +000049
jadmanski0afbb632008-06-06 21:10:57 +000050 See also: http://www.tldp.org/LDP/abs/html/escapingsection.html
51 """
52 command = command.replace("\\", "\\\\")
53 command = command.replace("$", r'\$')
54 command = command.replace('"', r'\"')
55 command = command.replace('`', r'\`')
56 return command
mblighdcd57a82007-07-11 23:06:47 +000057
58
59def scp_remote_escape(filename):
jadmanski0afbb632008-06-06 21:10:57 +000060 """
61 Escape special characters from a filename so that it can be passed
62 to scp (within double quotes) as a remote file.
mblighdc735a22007-08-02 16:54:37 +000063
jadmanski0afbb632008-06-06 21:10:57 +000064 Bis-quoting has to be used with scp for remote files, "bis-quoting"
65 as in quoting x 2
66 scp does not support a newline in the filename
mblighdc735a22007-08-02 16:54:37 +000067
jadmanski0afbb632008-06-06 21:10:57 +000068 Args:
69 filename: the filename string to escape.
mblighdc735a22007-08-02 16:54:37 +000070
jadmanski0afbb632008-06-06 21:10:57 +000071 Returns:
72 The escaped filename string. The required englobing double
73 quotes are NOT added and so should be added at some point by
74 the caller.
75 """
76 escape_chars= r' !"$&' "'" r'()*,:;<=>?[\]^`{|}'
mblighdc735a22007-08-02 16:54:37 +000077
jadmanski0afbb632008-06-06 21:10:57 +000078 new_name= []
79 for char in filename:
80 if char in escape_chars:
81 new_name.append("\\%s" % (char,))
82 else:
83 new_name.append(char)
mblighdc735a22007-08-02 16:54:37 +000084
jadmanski0afbb632008-06-06 21:10:57 +000085 return sh_escape("".join(new_name))
mblighdcd57a82007-07-11 23:06:47 +000086
87
mbligh6e18dab2007-10-24 21:27:18 +000088def get(location, local_copy = False):
jadmanski0afbb632008-06-06 21:10:57 +000089 """Get a file or directory to a local temporary directory.
mblighdc735a22007-08-02 16:54:37 +000090
jadmanski0afbb632008-06-06 21:10:57 +000091 Args:
92 location: the source of the material to get. This source may
93 be one of:
94 * a local file or directory
95 * a URL (http or ftp)
96 * a python file-like object
mblighdc735a22007-08-02 16:54:37 +000097
jadmanski0afbb632008-06-06 21:10:57 +000098 Returns:
99 The location of the file or directory where the requested
100 content was saved. This will be contained in a temporary
101 directory on the local host. If the material to get was a
102 directory, the location will contain a trailing '/'
103 """
104 tmpdir = get_tmp_dir()
mblighdc735a22007-08-02 16:54:37 +0000105
jadmanski0afbb632008-06-06 21:10:57 +0000106 # location is a file-like object
107 if hasattr(location, "read"):
108 tmpfile = os.path.join(tmpdir, "file")
109 tmpfileobj = file(tmpfile, 'w')
110 shutil.copyfileobj(location, tmpfileobj)
111 tmpfileobj.close()
112 return tmpfile
mblighdc735a22007-08-02 16:54:37 +0000113
jadmanski0afbb632008-06-06 21:10:57 +0000114 if isinstance(location, types.StringTypes):
115 # location is a URL
116 if location.startswith('http') or location.startswith('ftp'):
117 tmpfile = os.path.join(tmpdir, os.path.basename(location))
118 utils.urlretrieve(location, tmpfile)
119 return tmpfile
120 # location is a local path
121 elif os.path.exists(os.path.abspath(location)):
122 if not local_copy:
123 if os.path.isdir(location):
124 return location.rstrip('/') + '/'
125 else:
126 return location
127 tmpfile = os.path.join(tmpdir, os.path.basename(location))
128 if os.path.isdir(location):
129 tmpfile += '/'
130 shutil.copytree(location, tmpfile, symlinks=True)
131 return tmpfile
132 shutil.copyfile(location, tmpfile)
133 return tmpfile
134 # location is just a string, dump it to a file
135 else:
136 tmpfd, tmpfile = tempfile.mkstemp(dir=tmpdir)
137 tmpfileobj = os.fdopen(tmpfd, 'w')
138 tmpfileobj.write(location)
139 tmpfileobj.close()
140 return tmpfile
mblighdcd57a82007-07-11 23:06:47 +0000141
mbligh5f876ad2007-10-12 23:59:53 +0000142
mblighdcd57a82007-07-11 23:06:47 +0000143def get_tmp_dir():
jadmanski0afbb632008-06-06 21:10:57 +0000144 """Return the pathname of a directory on the host suitable
145 for temporary file storage.
mblighdc735a22007-08-02 16:54:37 +0000146
jadmanski0afbb632008-06-06 21:10:57 +0000147 The directory and its content will be deleted automatically
148 at the end of the program execution if they are still present.
149 """
jadmanski6d2ada52008-12-15 17:22:57 +0000150 dir_name = tempfile.mkdtemp(prefix="autoserv-")
jadmanski0afbb632008-06-06 21:10:57 +0000151 pid = os.getpid()
152 if not pid in __tmp_dirs:
153 __tmp_dirs[pid] = []
154 __tmp_dirs[pid].append(dir_name)
155 return dir_name
mblighdcd57a82007-07-11 23:06:47 +0000156
157
mblighdcd57a82007-07-11 23:06:47 +0000158def __clean_tmp_dirs():
jadmanski0afbb632008-06-06 21:10:57 +0000159 """Erase temporary directories that were created by the get_tmp_dir()
160 function and that are still present.
161 """
jadmanski0afbb632008-06-06 21:10:57 +0000162 pid = os.getpid()
163 if pid not in __tmp_dirs:
164 return
165 for dir in __tmp_dirs[pid]:
166 try:
167 shutil.rmtree(dir)
168 except OSError, e:
169 if e.errno == 2:
170 pass
171 __tmp_dirs[pid] = []
jadmanski6d2ada52008-12-15 17:22:57 +0000172atexit.register(__clean_tmp_dirs)
173subcommand.subcommand.register_join_hook(lambda _: __clean_tmp_dirs())
mblighc8949b82007-07-23 16:33:58 +0000174
175
176def unarchive(host, source_material):
jadmanski0afbb632008-06-06 21:10:57 +0000177 """Uncompress and untar an archive on a host.
mblighdc735a22007-08-02 16:54:37 +0000178
jadmanski0afbb632008-06-06 21:10:57 +0000179 If the "source_material" is compresses (according to the file
180 extension) it will be uncompressed. Supported compression formats
181 are gzip and bzip2. Afterwards, if the source_material is a tar
182 archive, it will be untarred.
mblighdc735a22007-08-02 16:54:37 +0000183
jadmanski0afbb632008-06-06 21:10:57 +0000184 Args:
185 host: the host object on which the archive is located
186 source_material: the path of the archive on the host
mblighdc735a22007-08-02 16:54:37 +0000187
jadmanski0afbb632008-06-06 21:10:57 +0000188 Returns:
189 The file or directory name of the unarchived source material.
190 If the material is a tar archive, it will be extracted in the
191 directory where it is and the path returned will be the first
192 entry in the archive, assuming it is the topmost directory.
193 If the material is not an archive, nothing will be done so this
194 function is "harmless" when it is "useless".
195 """
196 # uncompress
197 if (source_material.endswith(".gz") or
198 source_material.endswith(".gzip")):
199 host.run('gunzip "%s"' % (sh_escape(source_material)))
200 source_material= ".".join(source_material.split(".")[:-1])
201 elif source_material.endswith("bz2"):
202 host.run('bunzip2 "%s"' % (sh_escape(source_material)))
203 source_material= ".".join(source_material.split(".")[:-1])
mblighdc735a22007-08-02 16:54:37 +0000204
jadmanski0afbb632008-06-06 21:10:57 +0000205 # untar
206 if source_material.endswith(".tar"):
207 retval= host.run('tar -C "%s" -xvf "%s"' % (
208 sh_escape(os.path.dirname(source_material)),
209 sh_escape(source_material),))
210 source_material= os.path.join(os.path.dirname(source_material),
211 retval.stdout.split()[0])
mblighdc735a22007-08-02 16:54:37 +0000212
jadmanski0afbb632008-06-06 21:10:57 +0000213 return source_material
mblighf1c52842007-10-16 15:21:38 +0000214
215
mbligh9708f732007-10-18 03:18:54 +0000216def get_server_dir():
jadmanski0afbb632008-06-06 21:10:57 +0000217 path = os.path.dirname(sys.modules['autotest_lib.server.utils'].__file__)
218 return os.path.abspath(path)
mbligh40f122a2007-11-03 23:08:46 +0000219
220
mbligh34a3fd72007-12-10 17:16:22 +0000221def find_pid(command):
jadmanski0afbb632008-06-06 21:10:57 +0000222 for line in utils.system_output('ps -eo pid,cmd').rstrip().split('\n'):
223 (pid, cmd) = line.split(None, 1)
224 if re.search(command, cmd):
225 return int(pid)
226 return None
mbligh34a3fd72007-12-10 17:16:22 +0000227
228
229def nohup(command, stdout='/dev/null', stderr='/dev/null', background=True,
jadmanski0afbb632008-06-06 21:10:57 +0000230 env = {}):
231 cmd = ' '.join(key+'='+val for key, val in env.iteritems())
232 cmd += ' nohup ' + command
233 cmd += ' > %s' % stdout
234 if stdout == stderr:
235 cmd += ' 2>&1'
236 else:
237 cmd += ' 2> %s' % stderr
238 if background:
239 cmd += ' &'
240 utils.system(cmd)
mbligh34a3fd72007-12-10 17:16:22 +0000241
242
mbligh0b4fe6e2008-05-06 20:41:37 +0000243def default_mappings(machines):
jadmanski0afbb632008-06-06 21:10:57 +0000244 """
245 Returns a simple mapping in which all machines are assigned to the
246 same key. Provides the default behavior for
247 form_ntuples_from_machines. """
248 mappings = {}
249 failures = []
250
251 mach = machines[0]
252 mappings['ident'] = [mach]
253 if len(machines) > 1:
254 machines = machines[1:]
255 for machine in machines:
256 mappings['ident'].append(machine)
257
258 return (mappings, failures)
mbligh0b4fe6e2008-05-06 20:41:37 +0000259
260
261def form_ntuples_from_machines(machines, n=2, mapping_func=default_mappings):
jadmanski0afbb632008-06-06 21:10:57 +0000262 """Returns a set of ntuples from machines where the machines in an
263 ntuple are in the same mapping, and a set of failures which are
264 (machine name, reason) tuples."""
265 ntuples = []
266 (mappings, failures) = mapping_func(machines)
mbligh0b4fe6e2008-05-06 20:41:37 +0000267
jadmanski0afbb632008-06-06 21:10:57 +0000268 # now run through the mappings and create n-tuples.
269 # throw out the odd guys out
270 for key in mappings:
271 key_machines = mappings[key]
272 total_machines = len(key_machines)
mbligh0b4fe6e2008-05-06 20:41:37 +0000273
jadmanski0afbb632008-06-06 21:10:57 +0000274 # form n-tuples
275 while len(key_machines) >= n:
276 ntuples.append(key_machines[0:n])
277 key_machines = key_machines[n:]
mbligh0b4fe6e2008-05-06 20:41:37 +0000278
jadmanski0afbb632008-06-06 21:10:57 +0000279 for mach in key_machines:
280 failures.append((mach, "machine can not be tupled"))
281
282 return (ntuples, failures)
mblighaa9e6742008-06-05 21:14:05 +0000283
jadmanskib0605d92008-06-06 16:12:34 +0000284
mblighaa9e6742008-06-05 21:14:05 +0000285def parse_machine(machine, user = 'root', port = 22, password = ''):
jadmanski0afbb632008-06-06 21:10:57 +0000286 """
287 Parse the machine string user:pass@host:port and return it separately,
288 if the machine string is not complete, use the default parameters
289 when appropriate.
290 """
mblighaa9e6742008-06-05 21:14:05 +0000291
jadmanski0afbb632008-06-06 21:10:57 +0000292 user = user
293 port = port
294 password = password
mblighaa9e6742008-06-05 21:14:05 +0000295
jadmanski0afbb632008-06-06 21:10:57 +0000296 if re.search('@', machine):
297 machine = machine.split('@')
mblighaa9e6742008-06-05 21:14:05 +0000298
jadmanski0afbb632008-06-06 21:10:57 +0000299 if re.search(':', machine[0]):
300 machine[0] = machine[0].split(':')
301 user = machine[0][0]
302 password = machine[0][1]
mblighaa9e6742008-06-05 21:14:05 +0000303
jadmanski0afbb632008-06-06 21:10:57 +0000304 else:
305 user = machine[0]
mblighaa9e6742008-06-05 21:14:05 +0000306
jadmanski0afbb632008-06-06 21:10:57 +0000307 if re.search(':', machine[1]):
308 machine[1] = machine[1].split(':')
309 hostname = machine[1][0]
310 port = int(machine[1][1])
mblighaa9e6742008-06-05 21:14:05 +0000311
jadmanski0afbb632008-06-06 21:10:57 +0000312 else:
313 hostname = machine[1]
mblighaa9e6742008-06-05 21:14:05 +0000314
jadmanski0afbb632008-06-06 21:10:57 +0000315 elif re.search(':', machine):
316 machine = machine.split(':')
317 hostname = machine[0]
318 port = int(machine[1])
mblighaa9e6742008-06-05 21:14:05 +0000319
jadmanski0afbb632008-06-06 21:10:57 +0000320 else:
321 hostname = machine
mblighaa9e6742008-06-05 21:14:05 +0000322
jadmanski0afbb632008-06-06 21:10:57 +0000323 return hostname, user, password, port
mblighaa9e6742008-06-05 21:14:05 +0000324
325
326def get_public_key():
jadmanski0afbb632008-06-06 21:10:57 +0000327 """
328 Return a valid string ssh public key for the user executing autoserv or
329 autotest. If there's no DSA or RSA public key, create a DSA keypair with
330 ssh-keygen and return it.
331 """
mblighaa9e6742008-06-05 21:14:05 +0000332
jadmanski0afbb632008-06-06 21:10:57 +0000333 ssh_conf_path = os.path.join(os.environ['HOME'], '.ssh')
mblighaa9e6742008-06-05 21:14:05 +0000334
jadmanski0afbb632008-06-06 21:10:57 +0000335 dsa_public_key_path = os.path.join(ssh_conf_path, 'id_dsa.pub')
336 dsa_private_key_path = os.path.join(ssh_conf_path, 'id_dsa')
mblighaa9e6742008-06-05 21:14:05 +0000337
jadmanski0afbb632008-06-06 21:10:57 +0000338 rsa_public_key_path = os.path.join(ssh_conf_path, 'id_rsa.pub')
339 rsa_private_key_path = os.path.join(ssh_conf_path, 'id_rsa')
mblighaa9e6742008-06-05 21:14:05 +0000340
jadmanski0afbb632008-06-06 21:10:57 +0000341 has_dsa_keypair = os.path.isfile(dsa_public_key_path) and \
342 os.path.isfile(dsa_private_key_path)
343 has_rsa_keypair = os.path.isfile(rsa_public_key_path) and \
344 os.path.isfile(rsa_private_key_path)
mblighaa9e6742008-06-05 21:14:05 +0000345
jadmanski0afbb632008-06-06 21:10:57 +0000346 if has_dsa_keypair:
347 print 'DSA keypair found, using it'
348 public_key_path = dsa_public_key_path
mblighaa9e6742008-06-05 21:14:05 +0000349
jadmanski0afbb632008-06-06 21:10:57 +0000350 elif has_rsa_keypair:
351 print 'RSA keypair found, using it'
352 public_key_path = rsa_public_key_path
mblighaa9e6742008-06-05 21:14:05 +0000353
jadmanski0afbb632008-06-06 21:10:57 +0000354 else:
355 print 'Neither RSA nor DSA keypair found, creating DSA ssh key pair'
356 system('ssh-keygen -t dsa -q -N "" -f %s' % dsa_private_key_path)
357 public_key_path = dsa_public_key_path
mblighaa9e6742008-06-05 21:14:05 +0000358
jadmanski0afbb632008-06-06 21:10:57 +0000359 public_key = open(public_key_path, 'r')
360 public_key_str = public_key.read()
361 public_key.close()
mblighaa9e6742008-06-05 21:14:05 +0000362
jadmanski0afbb632008-06-06 21:10:57 +0000363 return public_key_str