blob: 90b34ef7933c25eac17dededdf3856178abef857 [file] [log] [blame]
mblighe8819cd2008-02-15 16:48:40 +00001"""\
2Logic for control file generation.
3"""
4
5__author__ = 'showard@google.com (Steve Howard)'
6
showard9ca52702008-06-02 21:14:49 +00007import re, os
mbligh120351e2009-01-24 01:40:45 +00008
9import common
10from autotest_lib.frontend.afe import model_logic
mblighe8819cd2008-02-15 16:48:40 +000011import frontend.settings
12
13AUTOTEST_DIR = os.path.abspath(os.path.join(
14 os.path.dirname(frontend.settings.__file__), '..'))
15
mblighc86113b2009-04-28 18:32:51 +000016EMPTY_TEMPLATE = 'def step_init():\n'
mblighe8819cd2008-02-15 16:48:40 +000017
showard1d445e92008-03-12 21:33:31 +000018CLIENT_KERNEL_TEMPLATE = """\
mblighc86113b2009-04-28 18:32:51 +000019kernel_list = %(client_kernel_list)s
mbligh12eafff2008-11-05 23:42:42 +000020
mblighe8819cd2008-02-15 16:48:40 +000021def step_init():
mbligha3c58d22009-08-24 22:01:51 +000022 for kernel_info in kernel_list:
23 job.next_step(boot_kernel, kernel_info)
24 job.next_step(step_test, kernel_info['version'])
mblighe39c3c02008-11-18 15:00:06 +000025 if len(kernel_list) > 1:
mblighfc3da5b2010-01-06 18:37:22 +000026 job.use_sequence_number = True # include run numbers in directory names
mbligh12eafff2008-11-05 23:42:42 +000027
mbligha3c58d22009-08-24 22:01:51 +000028
29def boot_kernel(kernel_info):
mbligh3c0ea962009-11-06 03:02:38 +000030 # remove kernels (and associated data) not referenced by the bootloader
mbligh0d0f67d2009-11-06 03:15:03 +000031 for host in job.hosts:
32 host.cleanup_kernels()
mbligh3c0ea962009-11-06 03:02:38 +000033
mbligha3c58d22009-08-24 22:01:51 +000034 testkernel = job.kernel(kernel_info['version'])
35 if kernel_info['config_file']:
36 testkernel.config(kernel_info['config_file'])
mbligheaa75e52009-11-06 03:08:08 +000037 testkernel.build()
mblighf5fdfab2008-06-16 23:57:25 +000038 testkernel.install()
mbligha3c58d22009-08-24 22:01:51 +000039
40 cmdline = ' '.join((kernel_info.get('cmdline', ''), '%(kernel_args)s'))
41 testkernel.boot(args=cmdline)
42
mblighe8819cd2008-02-15 16:48:40 +000043
mbligh12eafff2008-11-05 23:42:42 +000044def step_test(kernel_version):
mbligh6d2a6f92008-11-13 16:47:52 +000045 global kernel
46 kernel = kernel_version # Set the global in case anyone is using it.
mblighfc3da5b2010-01-06 18:37:22 +000047 if len(kernel_list) > 1:
48 # this is local to a machine, safe to assume there's only one host
49 host, = job.hosts
50 job.automatic_test_tag = host.get_kernel_ver()
mblighe8819cd2008-02-15 16:48:40 +000051"""
52
showard1d445e92008-03-12 21:33:31 +000053SERVER_KERNEL_TEMPLATE = """\
mblighc86113b2009-04-28 18:32:51 +000054kernel_list = %%(server_kernel_list)s
showard1d445e92008-03-12 21:33:31 +000055kernel_install_control = \"""
mblighf5fdfab2008-06-16 23:57:25 +000056%s pass
showard1d445e92008-03-12 21:33:31 +000057\"""
58
showarda5512cd2009-06-30 01:59:22 +000059from autotest_lib.client.common_lib import error
60
showard1d445e92008-03-12 21:33:31 +000061at = autotest.Autotest()
showard232b7ae2009-11-10 00:46:48 +000062
63%%(upload_config_func)s
mbligha3c58d22009-08-24 22:01:51 +000064def install_kernel(machine, kernel_info):
jadmanski8d631c92008-08-18 21:12:40 +000065 host = hosts.create_host(machine)
mblighc86113b2009-04-28 18:32:51 +000066 at.install(host=host)
showard232b7ae2009-11-10 00:46:48 +000067 %%(call_upload_config)s
mblighc86113b2009-04-28 18:32:51 +000068 at.run(kernel_install_control %%%%
mbligha3c58d22009-08-24 22:01:51 +000069 {'client_kernel_list': repr([kernel_info])}, host=host)
70
showard1d445e92008-03-12 21:33:31 +000071
mbligh415dc212009-06-15 21:53:34 +000072num_machines_required = len(machines)
73if len(machines) > 4:
74 # Allow a large multi-host tests to proceed despite a couple of hosts
75 # failing to properly install the desired kernel (exclude those hosts).
76 # TODO(gps): Figure out how to get and use SYNC_COUNT here. It is defined
77 # within some control files and will end up inside of stepN functions below.
78 num_machines_required = len(machines) - 2
79
mbligha3c58d22009-08-24 22:01:51 +000080
mblighc86113b2009-04-28 18:32:51 +000081def step_init():
82 # a host object we use solely for the purpose of finding out the booted
83 # kernel version, we use machines[0] since we already check that the same
84 # kernel has been booted on all machines
85 if len(kernel_list) > 1:
86 kernel_host = hosts.create_host(machines[0])
87
mbligha3c58d22009-08-24 22:01:51 +000088 for kernel_info in kernel_list:
89 func = lambda machine: install_kernel(machine, kernel_info)
mbligh415dc212009-06-15 21:53:34 +000090 good_machines = job.parallel_on_machines(func, machines)
91 if len(good_machines) < num_machines_required:
92 raise error.TestError(
93 "kernel installed on only %%%%d of %%%%d machines."
94 %%%% (len(good_machines), num_machines_required))
95
96 # Replace the machines list that step_test() will use with the
97 # ones that successfully installed the kernel.
98 machines[:] = good_machines
mblighc86113b2009-04-28 18:32:51 +000099
100 # have server_job.run_test() automatically add the kernel version as
101 # a suffix to the test name otherwise we cannot run the same test on
102 # different kernel versions
103 if len(kernel_list) > 1:
mblighfc3da5b2010-01-06 18:37:22 +0000104 job.automatic_test_tag = kernel_host.get_kernel_ver()
mblighc86113b2009-04-28 18:32:51 +0000105 step_test()
106
mbligha3c58d22009-08-24 22:01:51 +0000107
mblighc86113b2009-04-28 18:32:51 +0000108def step_test():
showard1d445e92008-03-12 21:33:31 +0000109""" % CLIENT_KERNEL_TEMPLATE
110
mblighf5fdfab2008-06-16 23:57:25 +0000111CLIENT_STEP_TEMPLATE = " job.next_step('step%d')\n"
mblighc86113b2009-04-28 18:32:51 +0000112SERVER_STEP_TEMPLATE = ' step%d()\n'
showard9ca52702008-06-02 21:14:49 +0000113
showard232b7ae2009-11-10 00:46:48 +0000114UPLOAD_CONFIG_FUNC = """
115def upload_kernel_config(host, kernel_info):
116 \"""
117 If the kernel_info['config_file'] is a URL it will be downloaded
118 locally and then uploaded to the client and a copy of the original
119 dictionary with the new path to the config file will be returned.
120 If the config file is not a URL the function returns the original
121 dictionary.
122 \"""
123 import os
124 from autotest_lib.client.common_lib import autotemp, utils
125
126 config_orig = kernel_info.get('config_file')
127
128 # if the file is not an URL then we assume it's a local client path
129 if not config_orig or not utils.is_url(config_orig):
130 return kernel_info
131
132 # download it locally (on the server) and send it to the client
133 config_tmp = autotemp.tempfile('kernel_config_upload', dir=job.tmpdir)
134 try:
135 utils.urlretrieve(config_orig, config_tmp.name)
136 config_new = os.path.join(host.get_autodir(), 'tmp',
137 os.path.basename(config_orig))
138 host.send_file(config_tmp.name, config_new)
139 finally:
140 config_tmp.clean()
141
142 return dict(kernel_info, config_file=config_new)
143
144"""
145
146CALL_UPLOAD_CONFIG = 'kernel_info = upload_kernel_config(host, kernel_info)'
147
showard1d445e92008-03-12 21:33:31 +0000148
mbligha3c58d22009-08-24 22:01:51 +0000149def kernel_config_file(kernel, platform):
jadmanski0afbb632008-06-06 21:10:57 +0000150 if (not kernel.endswith('.rpm') and platform and
151 platform.kernel_config):
mbligha3c58d22009-08-24 22:01:51 +0000152 return platform.kernel_config
153 return None
mblighe8819cd2008-02-15 16:48:40 +0000154
155
156def read_control_file(test):
jadmanski0afbb632008-06-06 21:10:57 +0000157 control_file = open(os.path.join(AUTOTEST_DIR, test.path))
158 control_contents = control_file.read()
159 control_file.close()
160 return control_contents
mblighe8819cd2008-02-15 16:48:40 +0000161
162
mbligh12eafff2008-11-05 23:42:42 +0000163def get_kernel_stanza(kernel_list, platform=None, kernel_args='',
showard232b7ae2009-11-10 00:46:48 +0000164 is_server=False, upload_kernel_config=False):
showard1d445e92008-03-12 21:33:31 +0000165
mbligha3c58d22009-08-24 22:01:51 +0000166 template_args = {'kernel_args' : kernel_args}
167
168 # add 'config_file' keys to the kernel_info dictionaries
169 new_kernel_list = []
170 for kernel_info in kernel_list:
showard39c843b2009-11-10 00:46:14 +0000171 if kernel_info.get('config_file'):
172 # already got a config file from the user
173 new_kernel_info = kernel_info
174 else:
175 config_file = kernel_config_file(kernel_info['version'], platform)
176 new_kernel_info = dict(kernel_info, config_file=config_file)
177
mbligha3c58d22009-08-24 22:01:51 +0000178 new_kernel_list.append(new_kernel_info)
mblighc86113b2009-04-28 18:32:51 +0000179
180 if is_server:
181 template = SERVER_KERNEL_TEMPLATE
182 # leave client_kernel_list as a placeholder
183 template_args['client_kernel_list'] = '%(client_kernel_list)s'
mbligha3c58d22009-08-24 22:01:51 +0000184 template_args['server_kernel_list'] = repr(new_kernel_list)
showard232b7ae2009-11-10 00:46:48 +0000185
186 if upload_kernel_config:
187 template_args['call_upload_config'] = CALL_UPLOAD_CONFIG
188 template_args['upload_config_func'] = UPLOAD_CONFIG_FUNC
189 else:
190 template_args['call_upload_config'] = ''
191 template_args['upload_config_func'] = ''
mblighc86113b2009-04-28 18:32:51 +0000192 else:
193 template = CLIENT_KERNEL_TEMPLATE
mbligha3c58d22009-08-24 22:01:51 +0000194 template_args['client_kernel_list'] = repr(new_kernel_list)
mblighc86113b2009-04-28 18:32:51 +0000195
196 return template % template_args
mblighe8819cd2008-02-15 16:48:40 +0000197
198
showard9ca52702008-06-02 21:14:49 +0000199def add_boilerplate_to_nested_steps(lines):
jadmanski0afbb632008-06-06 21:10:57 +0000200 # Look for a line that begins with 'def step_init():' while
201 # being flexible on spacing. If it's found, this will be
202 # a nested set of steps, so add magic to make it work.
203 # See client/bin/job.py's step_engine for more info.
204 if re.search(r'^(.*\n)*def\s+step_init\s*\(\s*\)\s*:', lines):
205 lines += '\nreturn locals() '
206 lines += '# Boilerplate magic for nested sets of steps'
207 return lines
showard9ca52702008-06-02 21:14:49 +0000208
209
210def format_step(item, lines):
mblighf5fdfab2008-06-16 23:57:25 +0000211 lines = indent_text(lines, ' ')
jadmanski0afbb632008-06-06 21:10:57 +0000212 lines = 'def step%d():\n%s' % (item, lines)
213 return lines
showard9ca52702008-06-02 21:14:49 +0000214
jadmanski0afbb632008-06-06 21:10:57 +0000215
mbligh120351e2009-01-24 01:40:45 +0000216def get_tests_stanza(tests, is_server, prepend=None, append=None,
217 client_control_file=''):
218 """ Constructs the control file test step code from a list of tests.
mbligh12eafff2008-11-05 23:42:42 +0000219
mbligh120351e2009-01-24 01:40:45 +0000220 @param tests A sequence of test control files to run.
221 @param is_server bool, Is this a server side test?
222 @param prepend A list of steps to prepend to each client test.
223 Defaults to [].
224 @param append A list of steps to append to each client test.
225 Defaults to [].
226 @param client_control_file If specified, use this text as the body of a
227 final client control file to run after tests. is_server must be False.
228
229 @returns The control file test code to be run.
mbligh12eafff2008-11-05 23:42:42 +0000230 """
mbligh120351e2009-01-24 01:40:45 +0000231 assert not (client_control_file and is_server)
mbligh12eafff2008-11-05 23:42:42 +0000232 if not prepend:
233 prepend = []
234 if not append:
235 append = []
jadmanski0afbb632008-06-06 21:10:57 +0000236 raw_control_files = [read_control_file(test) for test in tests]
mbligh120351e2009-01-24 01:40:45 +0000237 return _get_tests_stanza(raw_control_files, is_server, prepend, append,
238 client_control_file=client_control_file)
mblighc5ddfd12008-08-04 17:15:00 +0000239
240
mbligh120351e2009-01-24 01:40:45 +0000241def _get_tests_stanza(raw_control_files, is_server, prepend, append,
242 client_control_file=''):
243 """
244 Implements the common parts of get_test_stanza.
245
246 A site_control_file that wants to implement its own get_tests_stanza
247 likely wants to call this in the end.
248
249 @param raw_control_files A list of raw control file data to be combined
250 into a single control file.
251 @param is_server bool, Is this a server side test?
252 @param prepend A list of steps to prepend to each client test.
253 @param append A list of steps to append to each client test.
254 @param client_control_file If specified, use this text as the body of a
255 final client control file to append to raw_control_files after fixups.
256
257 @returns The combined mega control file.
258 """
mbligh120351e2009-01-24 01:40:45 +0000259 if client_control_file:
260 # 'return locals()' is always appended incase the user forgot, it
261 # is necessary to allow for nested step engine execution to work.
262 raw_control_files.append(client_control_file + '\nreturn locals()')
jadmanski0afbb632008-06-06 21:10:57 +0000263 raw_steps = prepend + [add_boilerplate_to_nested_steps(step)
264 for step in raw_control_files] + append
265 steps = [format_step(index, step)
266 for index, step in enumerate(raw_steps)]
mblighc86113b2009-04-28 18:32:51 +0000267 if is_server:
268 step_template = SERVER_STEP_TEMPLATE
269 footer = '\n\nstep_init()\n'
270 else:
271 step_template = CLIENT_STEP_TEMPLATE
272 footer = ''
273
274 header = ''.join(step_template % i for i in xrange(len(steps)))
275 return header + '\n' + '\n\n'.join(steps) + footer
mblighe8819cd2008-02-15 16:48:40 +0000276
277
278def indent_text(text, indent):
showardd2624152009-04-29 21:29:01 +0000279 """Indent given lines of python code avoiding indenting multiline
280 quoted content (only for triple " and ' quoting for now)."""
281 regex = re.compile('(\\\\*)("""|\'\'\')')
282
283 res = []
284 in_quote = None
285 for line in text.splitlines():
286 # if not within a multinline quote indent the line contents
287 if in_quote:
288 res.append(line)
289 else:
290 res.append(indent + line)
291
292 while line:
293 match = regex.search(line)
294 if match:
295 # for an even number of backslashes before the triple quote
296 if len(match.group(1)) % 2 == 0:
297 if not in_quote:
298 in_quote = match.group(2)[0]
299 elif in_quote == match.group(2)[0]:
300 # if we found a matching end triple quote
301 in_quote = None
302 line = line[match.end():]
303 else:
304 break
305
306 return '\n'.join(res)
mblighe8819cd2008-02-15 16:48:40 +0000307
308
showard91f85102009-10-12 20:34:52 +0000309def _get_profiler_commands(profilers, is_server, profile_only):
showard2b9a88b2008-06-13 20:55:03 +0000310 prepend, append = [], []
showard91f85102009-10-12 20:34:52 +0000311 if profile_only is not None:
mblighfbf73ae2009-12-19 05:22:42 +0000312 prepend.append("job.default_profile_only = %r" % profile_only)
showard2b9a88b2008-06-13 20:55:03 +0000313 for profiler in profilers:
314 prepend.append("job.profilers.add('%s')" % profiler.name)
315 append.append("job.profilers.delete('%s')" % profiler.name)
316 return prepend, append
317
318
showard232b7ae2009-11-10 00:46:48 +0000319def _sanity_check_generate_control(is_server, client_control_file, kernels,
320 upload_kernel_config):
mbligh12eafff2008-11-05 23:42:42 +0000321 """
mbligh120351e2009-01-24 01:40:45 +0000322 Sanity check some of the parameters to generate_control().
323
324 This exists as its own function so that site_control_file may call it as
325 well from its own generate_control().
326
327 @raises ValidationError if any of the parameters do not make sense.
328 """
329 if is_server and client_control_file:
330 raise model_logic.ValidationError(
331 {'tests' : 'You cannot run server tests at the same time '
332 'as directly supplying a client-side control file.'})
333
mbligha3c58d22009-08-24 22:01:51 +0000334 if kernels:
335 # make sure that kernel is a list of dictionarions with at least
336 # the 'version' key in them
337 kernel_error = model_logic.ValidationError(
338 {'kernel': 'The kernel parameter must be a sequence of '
339 'dictionaries containing at least the "version" key '
340 '(got: %r)' % kernels})
341 try:
342 iter(kernels)
343 except TypeError:
344 raise kernel_error
345 for kernel_info in kernels:
346 if (not isinstance(kernel_info, dict) or
347 'version' not in kernel_info):
348 raise kernel_error
mbligh120351e2009-01-24 01:40:45 +0000349
showard232b7ae2009-11-10 00:46:48 +0000350 if upload_kernel_config and not is_server:
351 raise model_logic.ValidationError(
352 {'upload_kernel_config': 'Cannot use upload_kernel_config '
353 'with client side tests'})
354
mbligha3c58d22009-08-24 22:01:51 +0000355
356def generate_control(tests, kernels=None, platform=None, is_server=False,
showard232b7ae2009-11-10 00:46:48 +0000357 profilers=(), client_control_file='', profile_only=None,
358 upload_kernel_config=False):
mbligh120351e2009-01-24 01:40:45 +0000359 """
360 Generate a control file for a sequence of tests.
361
362 @param tests A sequence of test control files to run.
mbligha3c58d22009-08-24 22:01:51 +0000363 @param kernels A sequence of kernel info dictionaries configuring which
364 kernels to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000365 @param platform A platform object with a kernel_config attribute.
366 @param is_server bool, Is this a server control file rather than a client?
367 @param profilers A list of profiler objects to enable during the tests.
368 @param client_control_file Contents of a client control file to run as the
mbligha3c58d22009-08-24 22:01:51 +0000369 last test after everything in tests. Requires is_server=False.
showard91f85102009-10-12 20:34:52 +0000370 @param profile_only bool, should this control file run all tests in
371 profile_only mode by default
showard232b7ae2009-11-10 00:46:48 +0000372 @param upload_kernel_config: if enabled it will generate server control
373 file code that uploads the kernel config file to the client and
374 tells the client of the new (local) path when compiling the kernel;
375 the tests must be server side tests
mbligh120351e2009-01-24 01:40:45 +0000376
377 @returns The control file text as a string.
378 """
mbligha3c58d22009-08-24 22:01:51 +0000379 _sanity_check_generate_control(is_server=is_server, kernels=kernels,
showard232b7ae2009-11-10 00:46:48 +0000380 client_control_file=client_control_file,
381 upload_kernel_config=upload_kernel_config)
mbligh120351e2009-01-24 01:40:45 +0000382
jadmanski0afbb632008-06-06 21:10:57 +0000383 control_file_text = ''
mbligha3c58d22009-08-24 22:01:51 +0000384 if kernels:
showard232b7ae2009-11-10 00:46:48 +0000385 control_file_text = get_kernel_stanza(
386 kernels, platform, is_server=is_server,
387 upload_kernel_config=upload_kernel_config)
mblighc86113b2009-04-28 18:32:51 +0000388 else:
389 control_file_text = EMPTY_TEMPLATE
showard9ca52702008-06-02 21:14:49 +0000390
showard91f85102009-10-12 20:34:52 +0000391 prepend, append = _get_profiler_commands(profilers, is_server, profile_only)
showard2b9a88b2008-06-13 20:55:03 +0000392
mbligh120351e2009-01-24 01:40:45 +0000393 control_file_text += get_tests_stanza(tests, is_server, prepend, append,
394 client_control_file)
jadmanski0afbb632008-06-06 21:10:57 +0000395 return control_file_text