blob: a88a8b483d429a45a1c2125717856e3165809e9b [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
Michael Tang84a2ecf2016-06-07 15:10:53 -070011from autotest_lib.frontend.afe import site_rpc_interface
mblighe8819cd2008-02-15 16:48:40 +000012import frontend.settings
13
14AUTOTEST_DIR = os.path.abspath(os.path.join(
15 os.path.dirname(frontend.settings.__file__), '..'))
16
mblighc86113b2009-04-28 18:32:51 +000017EMPTY_TEMPLATE = 'def step_init():\n'
mblighe8819cd2008-02-15 16:48:40 +000018
showard1d445e92008-03-12 21:33:31 +000019CLIENT_KERNEL_TEMPLATE = """\
mblighc86113b2009-04-28 18:32:51 +000020kernel_list = %(client_kernel_list)s
mbligh12eafff2008-11-05 23:42:42 +000021
mblighe8819cd2008-02-15 16:48:40 +000022def step_init():
mbligha3c58d22009-08-24 22:01:51 +000023 for kernel_info in kernel_list:
24 job.next_step(boot_kernel, kernel_info)
25 job.next_step(step_test, kernel_info['version'])
mblighe39c3c02008-11-18 15:00:06 +000026 if len(kernel_list) > 1:
mblighfc3da5b2010-01-06 18:37:22 +000027 job.use_sequence_number = True # include run numbers in directory names
mbligh12eafff2008-11-05 23:42:42 +000028
mbligha3c58d22009-08-24 22:01:51 +000029
30def boot_kernel(kernel_info):
mbligh3c0ea962009-11-06 03:02:38 +000031 # remove kernels (and associated data) not referenced by the bootloader
mbligh0d0f67d2009-11-06 03:15:03 +000032 for host in job.hosts:
33 host.cleanup_kernels()
mbligh3c0ea962009-11-06 03:02:38 +000034
mbligha3c58d22009-08-24 22:01:51 +000035 testkernel = job.kernel(kernel_info['version'])
36 if kernel_info['config_file']:
37 testkernel.config(kernel_info['config_file'])
mbligheaa75e52009-11-06 03:08:08 +000038 testkernel.build()
mblighf5fdfab2008-06-16 23:57:25 +000039 testkernel.install()
mbligha3c58d22009-08-24 22:01:51 +000040
41 cmdline = ' '.join((kernel_info.get('cmdline', ''), '%(kernel_args)s'))
42 testkernel.boot(args=cmdline)
43
mblighe8819cd2008-02-15 16:48:40 +000044
mbligh12eafff2008-11-05 23:42:42 +000045def step_test(kernel_version):
mbligh6d2a6f92008-11-13 16:47:52 +000046 global kernel
47 kernel = kernel_version # Set the global in case anyone is using it.
mblighfc3da5b2010-01-06 18:37:22 +000048 if len(kernel_list) > 1:
49 # this is local to a machine, safe to assume there's only one host
50 host, = job.hosts
51 job.automatic_test_tag = host.get_kernel_ver()
mblighe8819cd2008-02-15 16:48:40 +000052"""
53
showard1d445e92008-03-12 21:33:31 +000054SERVER_KERNEL_TEMPLATE = """\
mblighc86113b2009-04-28 18:32:51 +000055kernel_list = %%(server_kernel_list)s
showard1d445e92008-03-12 21:33:31 +000056kernel_install_control = \"""
mblighf5fdfab2008-06-16 23:57:25 +000057%s pass
showard1d445e92008-03-12 21:33:31 +000058\"""
59
showarda5512cd2009-06-30 01:59:22 +000060from autotest_lib.client.common_lib import error
61
showard1d445e92008-03-12 21:33:31 +000062at = autotest.Autotest()
showard232b7ae2009-11-10 00:46:48 +000063
64%%(upload_config_func)s
mbligha3c58d22009-08-24 22:01:51 +000065def install_kernel(machine, kernel_info):
jadmanski8d631c92008-08-18 21:12:40 +000066 host = hosts.create_host(machine)
mblighc86113b2009-04-28 18:32:51 +000067 at.install(host=host)
showard232b7ae2009-11-10 00:46:48 +000068 %%(call_upload_config)s
mblighc86113b2009-04-28 18:32:51 +000069 at.run(kernel_install_control %%%%
mbligha3c58d22009-08-24 22:01:51 +000070 {'client_kernel_list': repr([kernel_info])}, host=host)
71
showard1d445e92008-03-12 21:33:31 +000072
mbligh415dc212009-06-15 21:53:34 +000073num_machines_required = len(machines)
74if len(machines) > 4:
75 # Allow a large multi-host tests to proceed despite a couple of hosts
76 # failing to properly install the desired kernel (exclude those hosts).
77 # TODO(gps): Figure out how to get and use SYNC_COUNT here. It is defined
78 # within some control files and will end up inside of stepN functions below.
79 num_machines_required = len(machines) - 2
80
mbligha3c58d22009-08-24 22:01:51 +000081
mblighc86113b2009-04-28 18:32:51 +000082def step_init():
83 # a host object we use solely for the purpose of finding out the booted
84 # kernel version, we use machines[0] since we already check that the same
85 # kernel has been booted on all machines
86 if len(kernel_list) > 1:
87 kernel_host = hosts.create_host(machines[0])
88
mbligha3c58d22009-08-24 22:01:51 +000089 for kernel_info in kernel_list:
90 func = lambda machine: install_kernel(machine, kernel_info)
mbligh415dc212009-06-15 21:53:34 +000091 good_machines = job.parallel_on_machines(func, machines)
92 if len(good_machines) < num_machines_required:
93 raise error.TestError(
94 "kernel installed on only %%%%d of %%%%d machines."
95 %%%% (len(good_machines), num_machines_required))
96
97 # Replace the machines list that step_test() will use with the
98 # ones that successfully installed the kernel.
99 machines[:] = good_machines
mblighc86113b2009-04-28 18:32:51 +0000100
101 # have server_job.run_test() automatically add the kernel version as
102 # a suffix to the test name otherwise we cannot run the same test on
103 # different kernel versions
104 if len(kernel_list) > 1:
mblighfc3da5b2010-01-06 18:37:22 +0000105 job.automatic_test_tag = kernel_host.get_kernel_ver()
mblighc86113b2009-04-28 18:32:51 +0000106 step_test()
107
mbligha3c58d22009-08-24 22:01:51 +0000108
mblighc86113b2009-04-28 18:32:51 +0000109def step_test():
showard1d445e92008-03-12 21:33:31 +0000110""" % CLIENT_KERNEL_TEMPLATE
111
mblighf5fdfab2008-06-16 23:57:25 +0000112CLIENT_STEP_TEMPLATE = " job.next_step('step%d')\n"
mblighc86113b2009-04-28 18:32:51 +0000113SERVER_STEP_TEMPLATE = ' step%d()\n'
showard9ca52702008-06-02 21:14:49 +0000114
showard232b7ae2009-11-10 00:46:48 +0000115UPLOAD_CONFIG_FUNC = """
116def upload_kernel_config(host, kernel_info):
117 \"""
118 If the kernel_info['config_file'] is a URL it will be downloaded
119 locally and then uploaded to the client and a copy of the original
120 dictionary with the new path to the config file will be returned.
121 If the config file is not a URL the function returns the original
122 dictionary.
123 \"""
124 import os
125 from autotest_lib.client.common_lib import autotemp, utils
126
127 config_orig = kernel_info.get('config_file')
128
129 # if the file is not an URL then we assume it's a local client path
130 if not config_orig or not utils.is_url(config_orig):
131 return kernel_info
132
133 # download it locally (on the server) and send it to the client
134 config_tmp = autotemp.tempfile('kernel_config_upload', dir=job.tmpdir)
135 try:
136 utils.urlretrieve(config_orig, config_tmp.name)
137 config_new = os.path.join(host.get_autodir(), 'tmp',
138 os.path.basename(config_orig))
139 host.send_file(config_tmp.name, config_new)
140 finally:
141 config_tmp.clean()
142
143 return dict(kernel_info, config_file=config_new)
144
145"""
146
147CALL_UPLOAD_CONFIG = 'kernel_info = upload_kernel_config(host, kernel_info)'
148
showard1d445e92008-03-12 21:33:31 +0000149
mbligha3c58d22009-08-24 22:01:51 +0000150def kernel_config_file(kernel, platform):
Michael Tang84a2ecf2016-06-07 15:10:53 -0700151 """Gets the kernel config.
152
153 @param kernel The kernel rpm .
154 @param platform The platform object.
155
156 @return The kernel config string or None.
157 """
jadmanski0afbb632008-06-06 21:10:57 +0000158 if (not kernel.endswith('.rpm') and platform and
159 platform.kernel_config):
mbligha3c58d22009-08-24 22:01:51 +0000160 return platform.kernel_config
161 return None
mblighe8819cd2008-02-15 16:48:40 +0000162
163
164def read_control_file(test):
Michael Tang84a2ecf2016-06-07 15:10:53 -0700165 """Reads the test control file from local disk.
166
167 @param test The test name.
168
169 @return The test control file string.
170 """
jadmanski0afbb632008-06-06 21:10:57 +0000171 control_file = open(os.path.join(AUTOTEST_DIR, test.path))
172 control_contents = control_file.read()
173 control_file.close()
174 return control_contents
mblighe8819cd2008-02-15 16:48:40 +0000175
176
mbligh12eafff2008-11-05 23:42:42 +0000177def get_kernel_stanza(kernel_list, platform=None, kernel_args='',
showard232b7ae2009-11-10 00:46:48 +0000178 is_server=False, upload_kernel_config=False):
showard1d445e92008-03-12 21:33:31 +0000179
mbligha3c58d22009-08-24 22:01:51 +0000180 template_args = {'kernel_args' : kernel_args}
181
182 # add 'config_file' keys to the kernel_info dictionaries
183 new_kernel_list = []
184 for kernel_info in kernel_list:
showard39c843b2009-11-10 00:46:14 +0000185 if kernel_info.get('config_file'):
186 # already got a config file from the user
187 new_kernel_info = kernel_info
188 else:
189 config_file = kernel_config_file(kernel_info['version'], platform)
190 new_kernel_info = dict(kernel_info, config_file=config_file)
191
mbligha3c58d22009-08-24 22:01:51 +0000192 new_kernel_list.append(new_kernel_info)
mblighc86113b2009-04-28 18:32:51 +0000193
194 if is_server:
195 template = SERVER_KERNEL_TEMPLATE
196 # leave client_kernel_list as a placeholder
197 template_args['client_kernel_list'] = '%(client_kernel_list)s'
mbligha3c58d22009-08-24 22:01:51 +0000198 template_args['server_kernel_list'] = repr(new_kernel_list)
showard232b7ae2009-11-10 00:46:48 +0000199
200 if upload_kernel_config:
201 template_args['call_upload_config'] = CALL_UPLOAD_CONFIG
202 template_args['upload_config_func'] = UPLOAD_CONFIG_FUNC
203 else:
204 template_args['call_upload_config'] = ''
205 template_args['upload_config_func'] = ''
mblighc86113b2009-04-28 18:32:51 +0000206 else:
207 template = CLIENT_KERNEL_TEMPLATE
mbligha3c58d22009-08-24 22:01:51 +0000208 template_args['client_kernel_list'] = repr(new_kernel_list)
mblighc86113b2009-04-28 18:32:51 +0000209
210 return template % template_args
mblighe8819cd2008-02-15 16:48:40 +0000211
212
showard9ca52702008-06-02 21:14:49 +0000213def add_boilerplate_to_nested_steps(lines):
Michael Tang84a2ecf2016-06-07 15:10:53 -0700214 """Adds boilerplate magic.
215
216 @param lines The string of lines.
217
218 @returns The string lines.
219 """
jadmanski0afbb632008-06-06 21:10:57 +0000220 # Look for a line that begins with 'def step_init():' while
221 # being flexible on spacing. If it's found, this will be
222 # a nested set of steps, so add magic to make it work.
223 # See client/bin/job.py's step_engine for more info.
224 if re.search(r'^(.*\n)*def\s+step_init\s*\(\s*\)\s*:', lines):
225 lines += '\nreturn locals() '
226 lines += '# Boilerplate magic for nested sets of steps'
227 return lines
showard9ca52702008-06-02 21:14:49 +0000228
229
230def format_step(item, lines):
Michael Tang84a2ecf2016-06-07 15:10:53 -0700231 """Format a line item.
232 @param item The item number.
233 @param lines The string of lines.
234
235 @returns The string lines.
236 """
mblighf5fdfab2008-06-16 23:57:25 +0000237 lines = indent_text(lines, ' ')
jadmanski0afbb632008-06-06 21:10:57 +0000238 lines = 'def step%d():\n%s' % (item, lines)
239 return lines
showard9ca52702008-06-02 21:14:49 +0000240
jadmanski0afbb632008-06-06 21:10:57 +0000241
mbligh120351e2009-01-24 01:40:45 +0000242def get_tests_stanza(tests, is_server, prepend=None, append=None,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700243 client_control_file='', test_source_build=None):
mbligh120351e2009-01-24 01:40:45 +0000244 """ Constructs the control file test step code from a list of tests.
mbligh12eafff2008-11-05 23:42:42 +0000245
mbligh120351e2009-01-24 01:40:45 +0000246 @param tests A sequence of test control files to run.
247 @param is_server bool, Is this a server side test?
248 @param prepend A list of steps to prepend to each client test.
249 Defaults to [].
250 @param append A list of steps to append to each client test.
251 Defaults to [].
252 @param client_control_file If specified, use this text as the body of a
253 final client control file to run after tests. is_server must be False.
Michael Tang84a2ecf2016-06-07 15:10:53 -0700254 @param test_source_build: Build to be used to retrieve test code. Default
255 to None.
mbligh120351e2009-01-24 01:40:45 +0000256
257 @returns The control file test code to be run.
mbligh12eafff2008-11-05 23:42:42 +0000258 """
mbligh120351e2009-01-24 01:40:45 +0000259 assert not (client_control_file and is_server)
mbligh12eafff2008-11-05 23:42:42 +0000260 if not prepend:
261 prepend = []
262 if not append:
263 append = []
Michael Tang84a2ecf2016-06-07 15:10:53 -0700264 if test_source_build:
265 raw_control_files = site_rpc_interface.get_test_control_files_by_build(
266 tests, test_source_build)
267 else:
268 raw_control_files = [read_control_file(test) for test in tests]
mbligh120351e2009-01-24 01:40:45 +0000269 return _get_tests_stanza(raw_control_files, is_server, prepend, append,
270 client_control_file=client_control_file)
mblighc5ddfd12008-08-04 17:15:00 +0000271
272
mbligh120351e2009-01-24 01:40:45 +0000273def _get_tests_stanza(raw_control_files, is_server, prepend, append,
274 client_control_file=''):
275 """
276 Implements the common parts of get_test_stanza.
277
278 A site_control_file that wants to implement its own get_tests_stanza
279 likely wants to call this in the end.
280
281 @param raw_control_files A list of raw control file data to be combined
282 into a single control file.
283 @param is_server bool, Is this a server side test?
284 @param prepend A list of steps to prepend to each client test.
285 @param append A list of steps to append to each client test.
286 @param client_control_file If specified, use this text as the body of a
287 final client control file to append to raw_control_files after fixups.
288
289 @returns The combined mega control file.
290 """
mbligh120351e2009-01-24 01:40:45 +0000291 if client_control_file:
292 # 'return locals()' is always appended incase the user forgot, it
293 # is necessary to allow for nested step engine execution to work.
294 raw_control_files.append(client_control_file + '\nreturn locals()')
jadmanski0afbb632008-06-06 21:10:57 +0000295 raw_steps = prepend + [add_boilerplate_to_nested_steps(step)
296 for step in raw_control_files] + append
297 steps = [format_step(index, step)
298 for index, step in enumerate(raw_steps)]
mblighc86113b2009-04-28 18:32:51 +0000299 if is_server:
300 step_template = SERVER_STEP_TEMPLATE
301 footer = '\n\nstep_init()\n'
302 else:
303 step_template = CLIENT_STEP_TEMPLATE
304 footer = ''
305
306 header = ''.join(step_template % i for i in xrange(len(steps)))
307 return header + '\n' + '\n\n'.join(steps) + footer
mblighe8819cd2008-02-15 16:48:40 +0000308
309
310def indent_text(text, indent):
showardd2624152009-04-29 21:29:01 +0000311 """Indent given lines of python code avoiding indenting multiline
Michael Tang84a2ecf2016-06-07 15:10:53 -0700312 quoted content (only for triple " and ' quoting for now).
313
314 @param text The string of lines.
315 @param indent The indent string.
316
317 @return The indented string.
318 """
showardd2624152009-04-29 21:29:01 +0000319 regex = re.compile('(\\\\*)("""|\'\'\')')
320
321 res = []
322 in_quote = None
323 for line in text.splitlines():
324 # if not within a multinline quote indent the line contents
325 if in_quote:
326 res.append(line)
327 else:
328 res.append(indent + line)
329
330 while line:
331 match = regex.search(line)
332 if match:
333 # for an even number of backslashes before the triple quote
334 if len(match.group(1)) % 2 == 0:
335 if not in_quote:
336 in_quote = match.group(2)[0]
337 elif in_quote == match.group(2)[0]:
338 # if we found a matching end triple quote
339 in_quote = None
340 line = line[match.end():]
341 else:
342 break
343
344 return '\n'.join(res)
mblighe8819cd2008-02-15 16:48:40 +0000345
346
showard91f85102009-10-12 20:34:52 +0000347def _get_profiler_commands(profilers, is_server, profile_only):
showard2b9a88b2008-06-13 20:55:03 +0000348 prepend, append = [], []
showard91f85102009-10-12 20:34:52 +0000349 if profile_only is not None:
mblighfbf73ae2009-12-19 05:22:42 +0000350 prepend.append("job.default_profile_only = %r" % profile_only)
showard2b9a88b2008-06-13 20:55:03 +0000351 for profiler in profilers:
352 prepend.append("job.profilers.add('%s')" % profiler.name)
353 append.append("job.profilers.delete('%s')" % profiler.name)
354 return prepend, append
355
356
showard232b7ae2009-11-10 00:46:48 +0000357def _sanity_check_generate_control(is_server, client_control_file, kernels,
358 upload_kernel_config):
mbligh12eafff2008-11-05 23:42:42 +0000359 """
mbligh120351e2009-01-24 01:40:45 +0000360 Sanity check some of the parameters to generate_control().
361
362 This exists as its own function so that site_control_file may call it as
363 well from its own generate_control().
364
365 @raises ValidationError if any of the parameters do not make sense.
366 """
367 if is_server and client_control_file:
368 raise model_logic.ValidationError(
369 {'tests' : 'You cannot run server tests at the same time '
370 'as directly supplying a client-side control file.'})
371
mbligha3c58d22009-08-24 22:01:51 +0000372 if kernels:
373 # make sure that kernel is a list of dictionarions with at least
374 # the 'version' key in them
375 kernel_error = model_logic.ValidationError(
376 {'kernel': 'The kernel parameter must be a sequence of '
377 'dictionaries containing at least the "version" key '
378 '(got: %r)' % kernels})
379 try:
380 iter(kernels)
381 except TypeError:
382 raise kernel_error
383 for kernel_info in kernels:
384 if (not isinstance(kernel_info, dict) or
385 'version' not in kernel_info):
386 raise kernel_error
mbligh120351e2009-01-24 01:40:45 +0000387
showard232b7ae2009-11-10 00:46:48 +0000388 if upload_kernel_config and not is_server:
389 raise model_logic.ValidationError(
390 {'upload_kernel_config': 'Cannot use upload_kernel_config '
391 'with client side tests'})
392
mbligha3c58d22009-08-24 22:01:51 +0000393
394def generate_control(tests, kernels=None, platform=None, is_server=False,
showard232b7ae2009-11-10 00:46:48 +0000395 profilers=(), client_control_file='', profile_only=None,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700396 upload_kernel_config=False, test_source_build=None):
mbligh120351e2009-01-24 01:40:45 +0000397 """
398 Generate a control file for a sequence of tests.
399
400 @param tests A sequence of test control files to run.
mbligha3c58d22009-08-24 22:01:51 +0000401 @param kernels A sequence of kernel info dictionaries configuring which
402 kernels to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000403 @param platform A platform object with a kernel_config attribute.
404 @param is_server bool, Is this a server control file rather than a client?
405 @param profilers A list of profiler objects to enable during the tests.
406 @param client_control_file Contents of a client control file to run as the
mbligha3c58d22009-08-24 22:01:51 +0000407 last test after everything in tests. Requires is_server=False.
showard91f85102009-10-12 20:34:52 +0000408 @param profile_only bool, should this control file run all tests in
409 profile_only mode by default
showard232b7ae2009-11-10 00:46:48 +0000410 @param upload_kernel_config: if enabled it will generate server control
411 file code that uploads the kernel config file to the client and
412 tells the client of the new (local) path when compiling the kernel;
413 the tests must be server side tests
Michael Tang84a2ecf2016-06-07 15:10:53 -0700414 @param test_source_build: Build to be used to retrieve test code. Default
415 to None.
mbligh120351e2009-01-24 01:40:45 +0000416
417 @returns The control file text as a string.
418 """
mbligha3c58d22009-08-24 22:01:51 +0000419 _sanity_check_generate_control(is_server=is_server, kernels=kernels,
showard232b7ae2009-11-10 00:46:48 +0000420 client_control_file=client_control_file,
421 upload_kernel_config=upload_kernel_config)
mbligh120351e2009-01-24 01:40:45 +0000422
jadmanski0afbb632008-06-06 21:10:57 +0000423 control_file_text = ''
mbligha3c58d22009-08-24 22:01:51 +0000424 if kernels:
showard232b7ae2009-11-10 00:46:48 +0000425 control_file_text = get_kernel_stanza(
426 kernels, platform, is_server=is_server,
427 upload_kernel_config=upload_kernel_config)
mblighc86113b2009-04-28 18:32:51 +0000428 else:
429 control_file_text = EMPTY_TEMPLATE
showard9ca52702008-06-02 21:14:49 +0000430
showard91f85102009-10-12 20:34:52 +0000431 prepend, append = _get_profiler_commands(profilers, is_server, profile_only)
showard2b9a88b2008-06-13 20:55:03 +0000432
mbligh120351e2009-01-24 01:40:45 +0000433 control_file_text += get_tests_stanza(tests, is_server, prepend, append,
Michael Tang84a2ecf2016-06-07 15:10:53 -0700434 client_control_file,
435 test_source_build)
jadmanski0afbb632008-06-06 21:10:57 +0000436 return control_file_text