blob: da7988b497315d556aa12eededf10e66a9e0a99a [file] [log] [blame]
lmrc6bd3a62009-06-10 19:38:06 +00001#!/usr/bin/python
lmra89bdac2009-08-10 14:01:26 +00002import logging, re, os, sys, StringIO, optparse
lmrc6bd3a62009-06-10 19:38:06 +00003import common
lmr6f669ce2009-05-31 19:02:42 +00004from autotest_lib.client.common_lib import error
lmra89bdac2009-08-10 14:01:26 +00005from autotest_lib.client.common_lib import logging_config, logging_manager
lmr6f669ce2009-05-31 19:02:42 +00006
7"""
8KVM configuration file utility functions.
9
10@copyright: Red Hat 2008-2009
11"""
12
lmra89bdac2009-08-10 14:01:26 +000013
14class KvmLoggingConfig(logging_config.LoggingConfig):
15 def configure_logging(self, results_dir=None, verbose=False):
16 super(KvmLoggingConfig, self).configure_logging(use_console=True,
17 verbose=verbose)
18
19
lmr6f669ce2009-05-31 19:02:42 +000020class config:
21 """
22 Parse an input file or string that follows the KVM Test Config File format
23 and generate a list of dicts that will be later used as configuration
24 parameters by the the KVM tests.
25
26 @see: http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File
27 """
28
29 def __init__(self, filename=None, debug=False):
30 """
31 Initialize the list and optionally parse filename.
32
lmra89bdac2009-08-10 14:01:26 +000033 @param filename: Path of the file that will be taken.
34 @param debug: Whether to turn debugging output.
lmr6f669ce2009-05-31 19:02:42 +000035 """
36 self.list = [{"name": "", "shortname": "", "depend": []}]
37 self.debug = debug
38 self.filename = filename
39 if filename:
40 self.parse_file(filename)
41
42
lmr6f669ce2009-05-31 19:02:42 +000043 def parse_file(self, filename):
44 """
45 Parse filename, return the resulting list and store it in .list. If
46 filename does not exist, raise an exception.
47
lmrfc0e1412009-06-10 20:03:26 +000048 @param filename: Path of the configuration file.
lmr6f669ce2009-05-31 19:02:42 +000049 """
50 if not os.path.exists(filename):
51 raise Exception, "File %s not found" % filename
52 self.filename = filename
53 file = open(filename, "r")
54 self.list = self.parse(file, self.list)
55 file.close()
56 return self.list
57
58
59 def parse_string(self, str):
60 """
61 Parse a string, return the resulting list and store it in .list.
62
lmrfc0e1412009-06-10 20:03:26 +000063 @param str: String that will be parsed.
lmr6f669ce2009-05-31 19:02:42 +000064 """
65 file = StringIO.StringIO(str)
66 self.list = self.parse(file, self.list)
67 file.close()
68 return self.list
69
70
71 def get_list(self):
72 """
73 Return the list of dictionaries. This should probably be called after
74 parsing something.
75 """
76 return self.list
77
78
79 def match(self, filter, dict):
80 """
81 Return True if dict matches filter.
82
lmrfc0e1412009-06-10 20:03:26 +000083 @param filter: A regular expression that defines the filter.
84 @param dict: Dictionary that will be inspected.
lmr6f669ce2009-05-31 19:02:42 +000085 """
lmr5e419062009-08-07 21:04:35 +000086 filter = re.compile(r"(\.|^)" + filter + r"(\.|$)")
87 return bool(filter.search(dict["name"]))
lmr6f669ce2009-05-31 19:02:42 +000088
89
90 def filter(self, filter, list=None):
91 """
92 Filter a list of dicts.
93
lmrfc0e1412009-06-10 20:03:26 +000094 @param filter: A regular expression that will be used as a filter.
95 @param list: A list of dictionaries that will be filtered.
lmr6f669ce2009-05-31 19:02:42 +000096 """
lmr5e419062009-08-07 21:04:35 +000097 if list is None:
lmr6f669ce2009-05-31 19:02:42 +000098 list = self.list
lmr5e419062009-08-07 21:04:35 +000099 return [dict for dict in list if self.match(filter, dict)]
lmr6f669ce2009-05-31 19:02:42 +0000100
101
102 # Currently unused, will be removed if it remains unused
103 def get_match_block_indices(self, filter, list=None):
104 """
105 Get the indexes of a list that match a given filter.
106
lmrfc0e1412009-06-10 20:03:26 +0000107 @param filter: A regular expression that will filter the list.
108 @param list: List which we want to know the indexes that match a filter.
lmr6f669ce2009-05-31 19:02:42 +0000109 """
lmr5e419062009-08-07 21:04:35 +0000110 if list is None:
lmr6f669ce2009-05-31 19:02:42 +0000111 list = self.list
112 block_list = []
113 prev_match = False
114 for index in range(len(list)):
115 dict = list[index]
116 if self.match(filter, dict):
117 if not prev_match:
118 block_list.append([index])
119 prev_match = True
120 else:
121 if prev_match:
122 block_list[-1].append(index)
123 prev_match = False
124 if prev_match:
125 block_list[-1].append(len(list))
126 return block_list
127
128
129 def split_and_strip(self, str, sep="="):
130 """
131 Split str and strip quotes from the resulting parts.
132
lmrfc0e1412009-06-10 20:03:26 +0000133 @param str: String that will be processed
134 @param sep: Separator that will be used to split the string
lmr6f669ce2009-05-31 19:02:42 +0000135 """
lmr0015c712009-06-08 13:51:14 +0000136 temp = str.split(sep, 1)
lmr6f669ce2009-05-31 19:02:42 +0000137 for i in range(len(temp)):
138 temp[i] = temp[i].strip()
lmr434252b2009-06-10 19:27:14 +0000139 if re.findall("^\".*\"$", temp[i]):
140 temp[i] = temp[i].strip("\"")
141 elif re.findall("^\'.*\'$", temp[i]):
142 temp[i] = temp[i].strip("\'")
lmr6f669ce2009-05-31 19:02:42 +0000143 return temp
144
145
146 def get_next_line(self, file):
147 """
148 Get the next non-empty, non-comment line in a file like object.
149
lmrfc0e1412009-06-10 20:03:26 +0000150 @param file: File like object
151 @return: If no line is available, return None.
lmr6f669ce2009-05-31 19:02:42 +0000152 """
153 while True:
154 line = file.readline()
155 if line == "": return None
156 stripped_line = line.strip()
157 if len(stripped_line) > 0 \
158 and not stripped_line.startswith('#') \
159 and not stripped_line.startswith('//'):
160 return line
161
162
163 def get_next_line_indent(self, file):
164 """
165 Return the indent level of the next non-empty, non-comment line in file.
166
lmrf402d232009-07-27 13:16:12 +0000167 @param file: File like object.
168 @return: If no line is available, return -1.
lmr6f669ce2009-05-31 19:02:42 +0000169 """
170 pos = file.tell()
171 line = self.get_next_line(file)
172 if not line:
173 file.seek(pos)
174 return -1
175 line = line.expandtabs()
176 indent = 0
177 while line[indent] == ' ':
178 indent += 1
179 file.seek(pos)
180 return indent
181
182
183 def add_name(self, str, name, append=False):
184 """
185 Add name to str with a separator dot and return the result.
186
lmrf402d232009-07-27 13:16:12 +0000187 @param str: String that will be processed
188 @param name: name that will be appended to the string.
189 @return: If append is True, append name to str.
190 Otherwise, pre-pend name to str.
lmr6f669ce2009-05-31 19:02:42 +0000191 """
192 if str == "":
193 return name
194 # Append?
195 elif append:
196 return str + "." + name
197 # Prepend?
198 else:
199 return name + "." + str
200
201
202 def parse_variants(self, file, list, subvariants=False, prev_indent=-1):
203 """
204 Read and parse lines from file like object until a line with an indent
205 level lower than or equal to prev_indent is encountered.
206
lmrf402d232009-07-27 13:16:12 +0000207 @brief: Parse a 'variants' or 'subvariants' block from a file-like
208 object.
209 @param file: File-like object that will be parsed
210 @param list: List of dicts to operate on
211 @param subvariants: If True, parse in 'subvariants' mode;
212 otherwise parse in 'variants' mode
213 @param prev_indent: The indent level of the "parent" block
214 @return: The resulting list of dicts.
lmr6f669ce2009-05-31 19:02:42 +0000215 """
216 new_list = []
217
218 while True:
219 indent = self.get_next_line_indent(file)
220 if indent <= prev_indent:
221 break
222 indented_line = self.get_next_line(file).rstrip()
223 line = indented_line.strip()
224
225 # Get name and dependencies
226 temp = line.strip("- ").split(":")
227 name = temp[0]
228 if len(temp) == 1:
229 dep_list = []
230 else:
231 dep_list = temp[1].split()
232
233 # See if name should be added to the 'shortname' field
234 add_to_shortname = True
235 if name.startswith("@"):
236 name = name.strip("@")
237 add_to_shortname = False
238
239 # Make a deep copy of list
240 temp_list = []
241 for dict in list:
242 new_dict = dict.copy()
243 new_dict["depend"] = dict["depend"][:]
244 temp_list.append(new_dict)
245
246 if subvariants:
lmr5e419062009-08-07 21:04:35 +0000247 # If we're parsing 'subvariants', first modify the list
248 self.__modify_list_subvariants(temp_list, name, dep_list,
249 add_to_shortname)
lmr6f669ce2009-05-31 19:02:42 +0000250 temp_list = self.parse(file, temp_list,
251 restricted=True, prev_indent=indent)
252 else:
lmr5e419062009-08-07 21:04:35 +0000253 # If we're parsing 'variants', parse before modifying the list
lmr6f669ce2009-05-31 19:02:42 +0000254 if self.debug:
lmr5e419062009-08-07 21:04:35 +0000255 self.__debug_print(indented_line,
256 "Entering variant '%s' "
257 "(variant inherits %d dicts)" %
258 (name, len(list)))
lmr6f669ce2009-05-31 19:02:42 +0000259 temp_list = self.parse(file, temp_list,
260 restricted=False, prev_indent=indent)
lmr5e419062009-08-07 21:04:35 +0000261 self.__modify_list_variants(temp_list, name, dep_list,
262 add_to_shortname)
lmr6f669ce2009-05-31 19:02:42 +0000263
264 new_list += temp_list
265
266 return new_list
267
268
269 def parse(self, file, list, restricted=False, prev_indent=-1):
270 """
271 Read and parse lines from file until a line with an indent level lower
272 than or equal to prev_indent is encountered.
273
lmrf402d232009-07-27 13:16:12 +0000274 @brief: Parse a file-like object.
275 @param file: A file-like object
276 @param list: A list of dicts to operate on (list is modified in
277 place and should not be used after the call)
278 @param restricted: if True, operate in restricted mode
279 (prohibit 'variants')
280 @param prev_indent: the indent level of the "parent" block
281 @return: Return the resulting list of dicts.
282 @note: List is destroyed and should not be used after the call.
283 Only the returned list should be used.
lmr6f669ce2009-05-31 19:02:42 +0000284 """
285 while True:
286 indent = self.get_next_line_indent(file)
287 if indent <= prev_indent:
288 break
289 indented_line = self.get_next_line(file).rstrip()
290 line = indented_line.strip()
291 words = line.split()
292
293 len_list = len(list)
294
295 # Look for a known operator in the line
296 operators = ["?+=", "?<=", "?=", "+=", "<=", "="]
297 op_found = None
lmr4f397fd2009-07-27 13:14:55 +0000298 op_pos = len(line)
lmr6f669ce2009-05-31 19:02:42 +0000299 for op in operators:
lmr4f397fd2009-07-27 13:14:55 +0000300 pos = line.find(op)
301 if pos >= 0 and pos < op_pos:
lmr6f669ce2009-05-31 19:02:42 +0000302 op_found = op
lmr4f397fd2009-07-27 13:14:55 +0000303 op_pos = pos
lmr6f669ce2009-05-31 19:02:42 +0000304
305 # Found an operator?
306 if op_found:
307 if self.debug and not restricted:
308 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000309 "Parsing operator (%d dicts in current "
310 "context)" % len_list)
lmr6f669ce2009-05-31 19:02:42 +0000311 (left, value) = self.split_and_strip(line, op_found)
312 filters_and_key = self.split_and_strip(left, ":")
313 filters = filters_and_key[:-1]
314 key = filters_and_key[-1]
315 filtered_list = list
316 for filter in filters:
317 filtered_list = self.filter(filter, filtered_list)
318 # Apply the operation to the filtered list
lmr9018a802009-08-17 20:47:01 +0000319 if op_found == "=":
320 for dict in filtered_list:
lmr6f669ce2009-05-31 19:02:42 +0000321 dict[key] = value
lmr9018a802009-08-17 20:47:01 +0000322 elif op_found == "+=":
323 for dict in filtered_list:
lmr6f669ce2009-05-31 19:02:42 +0000324 dict[key] = dict.get(key, "") + value
lmr9018a802009-08-17 20:47:01 +0000325 elif op_found == "<=":
326 for dict in filtered_list:
lmr6f669ce2009-05-31 19:02:42 +0000327 dict[key] = value + dict.get(key, "")
lmr9018a802009-08-17 20:47:01 +0000328 elif op_found.startswith("?"):
329 exp = re.compile("^(%s)$" % key)
330 if op_found == "?=":
331 for dict in filtered_list:
332 for key in dict.keys():
333 if exp.match(key):
334 dict[key] = value
335 elif op_found == "?+=":
336 for dict in filtered_list:
337 for key in dict.keys():
338 if exp.match(key):
339 dict[key] = dict.get(key, "") + value
340 elif op_found == "?<=":
341 for dict in filtered_list:
342 for key in dict.keys():
343 if exp.match(key):
344 dict[key] = value + dict.get(key, "")
lmr6f669ce2009-05-31 19:02:42 +0000345
346 # Parse 'no' and 'only' statements
347 elif words[0] == "no" or words[0] == "only":
348 if len(words) <= 1:
349 continue
350 filters = words[1:]
351 filtered_list = []
352 if words[0] == "no":
353 for dict in list:
354 for filter in filters:
355 if self.match(filter, dict):
356 break
357 else:
358 filtered_list.append(dict)
359 if words[0] == "only":
360 for dict in list:
361 for filter in filters:
362 if self.match(filter, dict):
363 filtered_list.append(dict)
364 break
365 list = filtered_list
366 if self.debug and not restricted:
367 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000368 "Parsing no/only (%d dicts in current "
369 "context, %d remain)" %
370 (len_list, len(list)))
lmr6f669ce2009-05-31 19:02:42 +0000371
372 # Parse 'variants'
373 elif line == "variants:":
374 # 'variants' not allowed in restricted mode
375 # (inside an exception or inside subvariants)
376 if restricted:
377 e_msg = "Using variants in this context is not allowed"
lmrc6bd3a62009-06-10 19:38:06 +0000378 raise error.AutotestError(e_msg)
lmr6f669ce2009-05-31 19:02:42 +0000379 if self.debug and not restricted:
380 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000381 "Entering variants block (%d dicts in "
382 "current context)" % len_list)
lmr6f669ce2009-05-31 19:02:42 +0000383 list = self.parse_variants(file, list, subvariants=False,
384 prev_indent=indent)
385
386 # Parse 'subvariants' (the block is parsed for each dict
387 # separately)
388 elif line == "subvariants:":
389 if self.debug and not restricted:
390 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000391 "Entering subvariants block (%d dicts in "
392 "current context)" % len_list)
lmr6f669ce2009-05-31 19:02:42 +0000393 new_list = []
394 # Remember current file position
395 pos = file.tell()
396 # Read the lines in any case
397 self.parse_variants(file, [], subvariants=True,
398 prev_indent=indent)
399 # Iterate over the list...
400 for index in range(len(list)):
401 # Revert to initial file position in this 'subvariants'
402 # block
403 file.seek(pos)
404 # Everything inside 'subvariants' should be parsed in
405 # restricted mode
406 new_list += self.parse_variants(file, list[index:index+1],
407 subvariants=True,
408 prev_indent=indent)
409 list = new_list
410
411 # Parse 'include' statements
412 elif words[0] == "include":
413 if len(words) <= 1:
414 continue
415 if self.debug and not restricted:
416 self.__debug_print(indented_line,
lmr5e419062009-08-07 21:04:35 +0000417 "Entering file %s" % words[1])
lmr6f669ce2009-05-31 19:02:42 +0000418 if self.filename:
419 filename = os.path.join(os.path.dirname(self.filename),
420 words[1])
lmr33177c92009-08-07 21:45:02 +0000421 if os.path.exists(filename):
422 new_file = open(filename, "r")
423 list = self.parse(new_file, list, restricted)
424 new_file.close()
425 if self.debug and not restricted:
426 self.__debug_print("", "Leaving file %s" % words[1])
427 else:
lmra89bdac2009-08-10 14:01:26 +0000428 logging.warning("Cannot include %s -- file not found",
429 filename)
lmr6f669ce2009-05-31 19:02:42 +0000430 else:
lmra89bdac2009-08-10 14:01:26 +0000431 logging.warning("Cannot include %s because no file is "
432 "currently open", words[1])
lmr6f669ce2009-05-31 19:02:42 +0000433
434 # Parse multi-line exceptions
435 # (the block is parsed for each dict separately)
436 elif line.endswith(":"):
437 if self.debug and not restricted:
438 self.__debug_print(indented_line,
lmrf402d232009-07-27 13:16:12 +0000439 "Entering multi-line exception block "
440 "(%d dicts in current context outside "
441 "exception)" % len_list)
lmr6f669ce2009-05-31 19:02:42 +0000442 line = line.strip(":")
443 new_list = []
444 # Remember current file position
445 pos = file.tell()
446 # Read the lines in any case
447 self.parse(file, [], restricted=True, prev_indent=indent)
448 # Iterate over the list...
449 for index in range(len(list)):
450 if self.match(line, list[index]):
451 # Revert to initial file position in this
452 # exception block
453 file.seek(pos)
454 # Everything inside an exception should be parsed in
455 # restricted mode
456 new_list += self.parse(file, list[index:index+1],
lmr5e419062009-08-07 21:04:35 +0000457 restricted=True,
458 prev_indent=indent)
lmr6f669ce2009-05-31 19:02:42 +0000459 else:
460 new_list += list[index:index+1]
461 list = new_list
462
463 return list
464
465
466 def __debug_print(self, str1, str2=""):
467 """
468 Nicely print two strings and an arrow.
469
lmrf402d232009-07-27 13:16:12 +0000470 @param str1: First string
471 @param str2: Second string
lmr6f669ce2009-05-31 19:02:42 +0000472 """
473 if str2:
474 str = "%-50s ---> %s" % (str1, str2)
475 else:
476 str = str1
lmra89bdac2009-08-10 14:01:26 +0000477 logging.debug(str)
lmr6f669ce2009-05-31 19:02:42 +0000478
479
480 def __modify_list_variants(self, list, name, dep_list, add_to_shortname):
481 """
482 Make some modifications to list, as part of parsing a 'variants' block.
483
lmrf402d232009-07-27 13:16:12 +0000484 @param list: List to be processed
485 @param name: Name to be prepended to the dictionary's 'name' key
486 @param dep_list: List of dependencies to be added to the dictionary's
487 'depend' key
488 @param add_to_shortname: Boolean indicating whether name should be
489 prepended to the dictionary's 'shortname' key as well
lmr6f669ce2009-05-31 19:02:42 +0000490 """
491 for dict in list:
492 # Prepend name to the dict's 'name' field
493 dict["name"] = self.add_name(dict["name"], name)
494 # Prepend name to the dict's 'shortname' field
495 if add_to_shortname:
496 dict["shortname"] = self.add_name(dict["shortname"], name)
497 # Prepend name to each of the dict's dependencies
498 for i in range(len(dict["depend"])):
499 dict["depend"][i] = self.add_name(dict["depend"][i], name)
500 # Add new dependencies
501 dict["depend"] += dep_list
502
503
504 def __modify_list_subvariants(self, list, name, dep_list, add_to_shortname):
505 """
lmrf402d232009-07-27 13:16:12 +0000506 Make some modifications to list, as part of parsing a 'subvariants'
507 block.
lmr6f669ce2009-05-31 19:02:42 +0000508
lmrf402d232009-07-27 13:16:12 +0000509 @param list: List to be processed
510 @param name: Name to be appended to the dictionary's 'name' key
511 @param dep_list: List of dependencies to be added to the dictionary's
512 'depend' key
513 @param add_to_shortname: Boolean indicating whether name should be
514 appended to the dictionary's 'shortname' as well
lmr6f669ce2009-05-31 19:02:42 +0000515 """
516 for dict in list:
517 # Add new dependencies
518 for dep in dep_list:
519 dep_name = self.add_name(dict["name"], dep, append=True)
520 dict["depend"].append(dep_name)
521 # Append name to the dict's 'name' field
522 dict["name"] = self.add_name(dict["name"], name, append=True)
523 # Append name to the dict's 'shortname' field
524 if add_to_shortname:
525 dict["shortname"] = self.add_name(dict["shortname"], name,
526 append=True)
527
528
529if __name__ == "__main__":
lmra89bdac2009-08-10 14:01:26 +0000530 parser = optparse.OptionParser()
531 parser.add_option('-f', '--file', dest="filename", action='store_true',
532 help='path to a config file that will be parsed. '
533 'If not specified, will parse kvm_tests.cfg '
534 'located inside the kvm test dir.')
535 parser.add_option('--verbose', dest="debug", action='store_true',
536 help='include debug messages in console output')
537
538 options, args = parser.parse_args()
539 filename = options.filename
540 debug = options.debug
541
542 if not filename:
lmr6f669ce2009-05-31 19:02:42 +0000543 filename = os.path.join(os.path.dirname(sys.argv[0]), "kvm_tests.cfg")
lmra89bdac2009-08-10 14:01:26 +0000544
545 # Here we configure the stand alone program to use the autotest
546 # logging system.
547 logging_manager.configure_logging(KvmLoggingConfig(), verbose=debug)
548 list = config(filename, debug=debug).get_list()
lmr6f669ce2009-05-31 19:02:42 +0000549 i = 0
550 for dict in list:
lmra89bdac2009-08-10 14:01:26 +0000551 logging.info("Dictionary #%d:", i)
lmr6f669ce2009-05-31 19:02:42 +0000552 keys = dict.keys()
553 keys.sort()
554 for key in keys:
lmra89bdac2009-08-10 14:01:26 +0000555 logging.info(" %s = %s", key, dict[key])
lmr6f669ce2009-05-31 19:02:42 +0000556 i += 1