blob: 0cd59da45f502e1be912e457e8fee01903b25f64 [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:
26 job.set_run_number(1) # Include run numbers in output directory names.
27 job.show_kernel_in_test_tag(True) # Include kernel in output dir name.
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.
mblighe8819cd2008-02-15 16:48:40 +000048"""
49
showard1d445e92008-03-12 21:33:31 +000050SERVER_KERNEL_TEMPLATE = """\
mblighc86113b2009-04-28 18:32:51 +000051kernel_list = %%(server_kernel_list)s
showard1d445e92008-03-12 21:33:31 +000052kernel_install_control = \"""
mblighf5fdfab2008-06-16 23:57:25 +000053%s pass
showard1d445e92008-03-12 21:33:31 +000054\"""
55
showarda5512cd2009-06-30 01:59:22 +000056from autotest_lib.client.common_lib import error
57
showard1d445e92008-03-12 21:33:31 +000058at = autotest.Autotest()
showard232b7ae2009-11-10 00:46:48 +000059
60%%(upload_config_func)s
mbligha3c58d22009-08-24 22:01:51 +000061def install_kernel(machine, kernel_info):
jadmanski8d631c92008-08-18 21:12:40 +000062 host = hosts.create_host(machine)
mblighc86113b2009-04-28 18:32:51 +000063 at.install(host=host)
showard232b7ae2009-11-10 00:46:48 +000064 %%(call_upload_config)s
mblighc86113b2009-04-28 18:32:51 +000065 at.run(kernel_install_control %%%%
mbligha3c58d22009-08-24 22:01:51 +000066 {'client_kernel_list': repr([kernel_info])}, host=host)
67
showard1d445e92008-03-12 21:33:31 +000068
mbligh415dc212009-06-15 21:53:34 +000069num_machines_required = len(machines)
70if len(machines) > 4:
71 # Allow a large multi-host tests to proceed despite a couple of hosts
72 # failing to properly install the desired kernel (exclude those hosts).
73 # TODO(gps): Figure out how to get and use SYNC_COUNT here. It is defined
74 # within some control files and will end up inside of stepN functions below.
75 num_machines_required = len(machines) - 2
76
mbligha3c58d22009-08-24 22:01:51 +000077
mblighc86113b2009-04-28 18:32:51 +000078def step_init():
79 # a host object we use solely for the purpose of finding out the booted
80 # kernel version, we use machines[0] since we already check that the same
81 # kernel has been booted on all machines
82 if len(kernel_list) > 1:
83 kernel_host = hosts.create_host(machines[0])
84
mbligha3c58d22009-08-24 22:01:51 +000085 for kernel_info in kernel_list:
86 func = lambda machine: install_kernel(machine, kernel_info)
mbligh415dc212009-06-15 21:53:34 +000087 good_machines = job.parallel_on_machines(func, machines)
88 if len(good_machines) < num_machines_required:
89 raise error.TestError(
90 "kernel installed on only %%%%d of %%%%d machines."
91 %%%% (len(good_machines), num_machines_required))
92
93 # Replace the machines list that step_test() will use with the
94 # ones that successfully installed the kernel.
95 machines[:] = good_machines
mblighc86113b2009-04-28 18:32:51 +000096
97 # have server_job.run_test() automatically add the kernel version as
98 # a suffix to the test name otherwise we cannot run the same test on
99 # different kernel versions
100 if len(kernel_list) > 1:
mbligh7eacbc22009-07-28 23:13:56 +0000101 job.set_test_tag_prefix(kernel_host.get_kernel_ver())
mblighc86113b2009-04-28 18:32:51 +0000102 step_test()
103
mbligha3c58d22009-08-24 22:01:51 +0000104
mblighc86113b2009-04-28 18:32:51 +0000105def step_test():
showard1d445e92008-03-12 21:33:31 +0000106""" % CLIENT_KERNEL_TEMPLATE
107
mblighf5fdfab2008-06-16 23:57:25 +0000108CLIENT_STEP_TEMPLATE = " job.next_step('step%d')\n"
mblighc86113b2009-04-28 18:32:51 +0000109SERVER_STEP_TEMPLATE = ' step%d()\n'
showard9ca52702008-06-02 21:14:49 +0000110
showard232b7ae2009-11-10 00:46:48 +0000111UPLOAD_CONFIG_FUNC = """
112def upload_kernel_config(host, kernel_info):
113 \"""
114 If the kernel_info['config_file'] is a URL it will be downloaded
115 locally and then uploaded to the client and a copy of the original
116 dictionary with the new path to the config file will be returned.
117 If the config file is not a URL the function returns the original
118 dictionary.
119 \"""
120 import os
121 from autotest_lib.client.common_lib import autotemp, utils
122
123 config_orig = kernel_info.get('config_file')
124
125 # if the file is not an URL then we assume it's a local client path
126 if not config_orig or not utils.is_url(config_orig):
127 return kernel_info
128
129 # download it locally (on the server) and send it to the client
130 config_tmp = autotemp.tempfile('kernel_config_upload', dir=job.tmpdir)
131 try:
132 utils.urlretrieve(config_orig, config_tmp.name)
133 config_new = os.path.join(host.get_autodir(), 'tmp',
134 os.path.basename(config_orig))
135 host.send_file(config_tmp.name, config_new)
136 finally:
137 config_tmp.clean()
138
139 return dict(kernel_info, config_file=config_new)
140
141"""
142
143CALL_UPLOAD_CONFIG = 'kernel_info = upload_kernel_config(host, kernel_info)'
144
showard1d445e92008-03-12 21:33:31 +0000145
mbligha3c58d22009-08-24 22:01:51 +0000146def kernel_config_file(kernel, platform):
jadmanski0afbb632008-06-06 21:10:57 +0000147 if (not kernel.endswith('.rpm') and platform and
148 platform.kernel_config):
mbligha3c58d22009-08-24 22:01:51 +0000149 return platform.kernel_config
150 return None
mblighe8819cd2008-02-15 16:48:40 +0000151
152
153def read_control_file(test):
jadmanski0afbb632008-06-06 21:10:57 +0000154 control_file = open(os.path.join(AUTOTEST_DIR, test.path))
155 control_contents = control_file.read()
156 control_file.close()
157 return control_contents
mblighe8819cd2008-02-15 16:48:40 +0000158
159
mbligh12eafff2008-11-05 23:42:42 +0000160def get_kernel_stanza(kernel_list, platform=None, kernel_args='',
showard232b7ae2009-11-10 00:46:48 +0000161 is_server=False, upload_kernel_config=False):
showard1d445e92008-03-12 21:33:31 +0000162
mbligha3c58d22009-08-24 22:01:51 +0000163 template_args = {'kernel_args' : kernel_args}
164
165 # add 'config_file' keys to the kernel_info dictionaries
166 new_kernel_list = []
167 for kernel_info in kernel_list:
showard39c843b2009-11-10 00:46:14 +0000168 if kernel_info.get('config_file'):
169 # already got a config file from the user
170 new_kernel_info = kernel_info
171 else:
172 config_file = kernel_config_file(kernel_info['version'], platform)
173 new_kernel_info = dict(kernel_info, config_file=config_file)
174
mbligha3c58d22009-08-24 22:01:51 +0000175 new_kernel_list.append(new_kernel_info)
mblighc86113b2009-04-28 18:32:51 +0000176
177 if is_server:
178 template = SERVER_KERNEL_TEMPLATE
179 # leave client_kernel_list as a placeholder
180 template_args['client_kernel_list'] = '%(client_kernel_list)s'
mbligha3c58d22009-08-24 22:01:51 +0000181 template_args['server_kernel_list'] = repr(new_kernel_list)
showard232b7ae2009-11-10 00:46:48 +0000182
183 if upload_kernel_config:
184 template_args['call_upload_config'] = CALL_UPLOAD_CONFIG
185 template_args['upload_config_func'] = UPLOAD_CONFIG_FUNC
186 else:
187 template_args['call_upload_config'] = ''
188 template_args['upload_config_func'] = ''
mblighc86113b2009-04-28 18:32:51 +0000189 else:
190 template = CLIENT_KERNEL_TEMPLATE
mbligha3c58d22009-08-24 22:01:51 +0000191 template_args['client_kernel_list'] = repr(new_kernel_list)
mblighc86113b2009-04-28 18:32:51 +0000192
193 return template % template_args
mblighe8819cd2008-02-15 16:48:40 +0000194
195
showard9ca52702008-06-02 21:14:49 +0000196def add_boilerplate_to_nested_steps(lines):
jadmanski0afbb632008-06-06 21:10:57 +0000197 # Look for a line that begins with 'def step_init():' while
198 # being flexible on spacing. If it's found, this will be
199 # a nested set of steps, so add magic to make it work.
200 # See client/bin/job.py's step_engine for more info.
201 if re.search(r'^(.*\n)*def\s+step_init\s*\(\s*\)\s*:', lines):
202 lines += '\nreturn locals() '
203 lines += '# Boilerplate magic for nested sets of steps'
204 return lines
showard9ca52702008-06-02 21:14:49 +0000205
206
207def format_step(item, lines):
mblighf5fdfab2008-06-16 23:57:25 +0000208 lines = indent_text(lines, ' ')
jadmanski0afbb632008-06-06 21:10:57 +0000209 lines = 'def step%d():\n%s' % (item, lines)
210 return lines
showard9ca52702008-06-02 21:14:49 +0000211
jadmanski0afbb632008-06-06 21:10:57 +0000212
mbligh120351e2009-01-24 01:40:45 +0000213def get_tests_stanza(tests, is_server, prepend=None, append=None,
214 client_control_file=''):
215 """ Constructs the control file test step code from a list of tests.
mbligh12eafff2008-11-05 23:42:42 +0000216
mbligh120351e2009-01-24 01:40:45 +0000217 @param tests A sequence of test control files to run.
218 @param is_server bool, Is this a server side test?
219 @param prepend A list of steps to prepend to each client test.
220 Defaults to [].
221 @param append A list of steps to append to each client test.
222 Defaults to [].
223 @param client_control_file If specified, use this text as the body of a
224 final client control file to run after tests. is_server must be False.
225
226 @returns The control file test code to be run.
mbligh12eafff2008-11-05 23:42:42 +0000227 """
mbligh120351e2009-01-24 01:40:45 +0000228 assert not (client_control_file and is_server)
mbligh12eafff2008-11-05 23:42:42 +0000229 if not prepend:
230 prepend = []
231 if not append:
232 append = []
jadmanski0afbb632008-06-06 21:10:57 +0000233 raw_control_files = [read_control_file(test) for test in tests]
mbligh120351e2009-01-24 01:40:45 +0000234 return _get_tests_stanza(raw_control_files, is_server, prepend, append,
235 client_control_file=client_control_file)
mblighc5ddfd12008-08-04 17:15:00 +0000236
237
mbligh120351e2009-01-24 01:40:45 +0000238def _get_tests_stanza(raw_control_files, is_server, prepend, append,
239 client_control_file=''):
240 """
241 Implements the common parts of get_test_stanza.
242
243 A site_control_file that wants to implement its own get_tests_stanza
244 likely wants to call this in the end.
245
246 @param raw_control_files A list of raw control file data to be combined
247 into a single control file.
248 @param is_server bool, Is this a server side test?
249 @param prepend A list of steps to prepend to each client test.
250 @param append A list of steps to append to each client test.
251 @param client_control_file If specified, use this text as the body of a
252 final client control file to append to raw_control_files after fixups.
253
254 @returns The combined mega control file.
255 """
mbligh120351e2009-01-24 01:40:45 +0000256 if client_control_file:
257 # 'return locals()' is always appended incase the user forgot, it
258 # is necessary to allow for nested step engine execution to work.
259 raw_control_files.append(client_control_file + '\nreturn locals()')
jadmanski0afbb632008-06-06 21:10:57 +0000260 raw_steps = prepend + [add_boilerplate_to_nested_steps(step)
261 for step in raw_control_files] + append
262 steps = [format_step(index, step)
263 for index, step in enumerate(raw_steps)]
mblighc86113b2009-04-28 18:32:51 +0000264 if is_server:
265 step_template = SERVER_STEP_TEMPLATE
266 footer = '\n\nstep_init()\n'
267 else:
268 step_template = CLIENT_STEP_TEMPLATE
269 footer = ''
270
271 header = ''.join(step_template % i for i in xrange(len(steps)))
272 return header + '\n' + '\n\n'.join(steps) + footer
mblighe8819cd2008-02-15 16:48:40 +0000273
274
275def indent_text(text, indent):
showardd2624152009-04-29 21:29:01 +0000276 """Indent given lines of python code avoiding indenting multiline
277 quoted content (only for triple " and ' quoting for now)."""
278 regex = re.compile('(\\\\*)("""|\'\'\')')
279
280 res = []
281 in_quote = None
282 for line in text.splitlines():
283 # if not within a multinline quote indent the line contents
284 if in_quote:
285 res.append(line)
286 else:
287 res.append(indent + line)
288
289 while line:
290 match = regex.search(line)
291 if match:
292 # for an even number of backslashes before the triple quote
293 if len(match.group(1)) % 2 == 0:
294 if not in_quote:
295 in_quote = match.group(2)[0]
296 elif in_quote == match.group(2)[0]:
297 # if we found a matching end triple quote
298 in_quote = None
299 line = line[match.end():]
300 else:
301 break
302
303 return '\n'.join(res)
mblighe8819cd2008-02-15 16:48:40 +0000304
305
showard91f85102009-10-12 20:34:52 +0000306def _get_profiler_commands(profilers, is_server, profile_only):
showard2b9a88b2008-06-13 20:55:03 +0000307 prepend, append = [], []
showard91f85102009-10-12 20:34:52 +0000308 if profile_only is not None:
mblighfbf73ae2009-12-19 05:22:42 +0000309 prepend.append("job.default_profile_only = %r" % profile_only)
showard2b9a88b2008-06-13 20:55:03 +0000310 for profiler in profilers:
311 prepend.append("job.profilers.add('%s')" % profiler.name)
312 append.append("job.profilers.delete('%s')" % profiler.name)
313 return prepend, append
314
315
showard232b7ae2009-11-10 00:46:48 +0000316def _sanity_check_generate_control(is_server, client_control_file, kernels,
317 upload_kernel_config):
mbligh12eafff2008-11-05 23:42:42 +0000318 """
mbligh120351e2009-01-24 01:40:45 +0000319 Sanity check some of the parameters to generate_control().
320
321 This exists as its own function so that site_control_file may call it as
322 well from its own generate_control().
323
324 @raises ValidationError if any of the parameters do not make sense.
325 """
326 if is_server and client_control_file:
327 raise model_logic.ValidationError(
328 {'tests' : 'You cannot run server tests at the same time '
329 'as directly supplying a client-side control file.'})
330
mbligha3c58d22009-08-24 22:01:51 +0000331 if kernels:
332 # make sure that kernel is a list of dictionarions with at least
333 # the 'version' key in them
334 kernel_error = model_logic.ValidationError(
335 {'kernel': 'The kernel parameter must be a sequence of '
336 'dictionaries containing at least the "version" key '
337 '(got: %r)' % kernels})
338 try:
339 iter(kernels)
340 except TypeError:
341 raise kernel_error
342 for kernel_info in kernels:
343 if (not isinstance(kernel_info, dict) or
344 'version' not in kernel_info):
345 raise kernel_error
mbligh120351e2009-01-24 01:40:45 +0000346
showard232b7ae2009-11-10 00:46:48 +0000347 if upload_kernel_config and not is_server:
348 raise model_logic.ValidationError(
349 {'upload_kernel_config': 'Cannot use upload_kernel_config '
350 'with client side tests'})
351
mbligha3c58d22009-08-24 22:01:51 +0000352
353def generate_control(tests, kernels=None, platform=None, is_server=False,
showard232b7ae2009-11-10 00:46:48 +0000354 profilers=(), client_control_file='', profile_only=None,
355 upload_kernel_config=False):
mbligh120351e2009-01-24 01:40:45 +0000356 """
357 Generate a control file for a sequence of tests.
358
359 @param tests A sequence of test control files to run.
mbligha3c58d22009-08-24 22:01:51 +0000360 @param kernels A sequence of kernel info dictionaries configuring which
361 kernels to boot for this job and other options for them
mbligh120351e2009-01-24 01:40:45 +0000362 @param platform A platform object with a kernel_config attribute.
363 @param is_server bool, Is this a server control file rather than a client?
364 @param profilers A list of profiler objects to enable during the tests.
365 @param client_control_file Contents of a client control file to run as the
mbligha3c58d22009-08-24 22:01:51 +0000366 last test after everything in tests. Requires is_server=False.
showard91f85102009-10-12 20:34:52 +0000367 @param profile_only bool, should this control file run all tests in
368 profile_only mode by default
showard232b7ae2009-11-10 00:46:48 +0000369 @param upload_kernel_config: if enabled it will generate server control
370 file code that uploads the kernel config file to the client and
371 tells the client of the new (local) path when compiling the kernel;
372 the tests must be server side tests
mbligh120351e2009-01-24 01:40:45 +0000373
374 @returns The control file text as a string.
375 """
mbligha3c58d22009-08-24 22:01:51 +0000376 _sanity_check_generate_control(is_server=is_server, kernels=kernels,
showard232b7ae2009-11-10 00:46:48 +0000377 client_control_file=client_control_file,
378 upload_kernel_config=upload_kernel_config)
mbligh120351e2009-01-24 01:40:45 +0000379
jadmanski0afbb632008-06-06 21:10:57 +0000380 control_file_text = ''
mbligha3c58d22009-08-24 22:01:51 +0000381 if kernels:
showard232b7ae2009-11-10 00:46:48 +0000382 control_file_text = get_kernel_stanza(
383 kernels, platform, is_server=is_server,
384 upload_kernel_config=upload_kernel_config)
mblighc86113b2009-04-28 18:32:51 +0000385 else:
386 control_file_text = EMPTY_TEMPLATE
showard9ca52702008-06-02 21:14:49 +0000387
showard91f85102009-10-12 20:34:52 +0000388 prepend, append = _get_profiler_commands(profilers, is_server, profile_only)
showard2b9a88b2008-06-13 20:55:03 +0000389
mbligh120351e2009-01-24 01:40:45 +0000390 control_file_text += get_tests_stanza(tests, is_server, prepend, append,
391 client_control_file)
jadmanski0afbb632008-06-06 21:10:57 +0000392 return control_file_text