193 """ |
193 """ |
194 Convert criteria list into dictionary. This function is intended to be |
194 Convert criteria list into dictionary. This function is intended to be |
195 called by a main function, or the options parser, so it can potentially |
195 called by a main function, or the options parser, so it can potentially |
196 raise the SystemExit exception. |
196 raise the SystemExit exception. |
197 Args: criteria in list format: [ criteria=value, criteria=value, ... ] |
197 Args: criteria in list format: [ criteria=value, criteria=value, ... ] |
198 where value can be a: single value |
198 where value can be a: single string value |
|
199 space-separated string value (list of values) |
199 range (<lower>-<upper>) |
200 range (<lower>-<upper>) |
200 Returns: dictionary of criteria { criteria: value, criteria: value, ... } |
201 Returns: dictionary of criteria { criteria: value, criteria: value, ... } |
201 with all keys and values in lower case |
202 with all keys in lower case, values are case-sensitive. |
202 Raises: ValueError on malformed name=value strings in input list. |
203 Raises: ValueError on malformed name=value strings in input list. |
203 """ |
204 """ |
204 cri_dict = {} |
205 cri_dict = {} |
205 for entry in criteria: |
206 for entry in criteria: |
206 entries = entry.lower().partition("=") |
207 entries = entry.partition("=") |
207 |
208 |
208 if entries[1]: |
209 if entries[1]: |
209 if not entries[0]: |
210 if not entries[0]: |
210 raise ValueError(_("Missing criteria name in " |
211 raise ValueError(_("Missing criteria name in " |
211 "'%s'\n") % entry) |
212 "'%s'\n") % entry) |
212 elif entries[0] in cri_dict: |
213 elif entries[0].lower() in cri_dict: |
213 raise ValueError(_("Duplicate criteria: '%s'\n") % |
214 raise ValueError(_("Duplicate criteria: '%s'\n") % |
214 entries[0]) |
215 entries[0]) |
215 elif not entries[2]: |
216 elif not entries[2]: |
216 raise ValueError(_("Missing value for criteria " |
217 raise ValueError(_("Missing value for criteria " |
217 "'%s'\n") % entries[0]) |
218 "'%s'\n") % entries[0]) |
218 cri_dict[entries[0]] = entries[2] |
219 cri_dict[entries[0].lower()] = entries[2] |
219 else: |
220 else: |
220 raise ValueError(_("Criteria must be of the form " |
221 raise ValueError(_("Criteria must be of the form " |
221 "<criteria>=<value>\n")) |
222 "<criteria>=<value>\n")) |
222 |
223 |
223 return cri_dict |
224 return cri_dict |
264 # collisions with database entries |
265 # collisions with database entries |
265 for crit in criteria: |
266 for crit in criteria: |
266 # gather this criteria's values from the manifest |
267 # gather this criteria's values from the manifest |
267 man_criterion = criteria[crit] |
268 man_criterion = criteria[crit] |
268 |
269 |
269 # check "value" criteria here (check the criteria exists in DB, and |
270 # Determine if this crit is a range criteria or not. |
270 # then find collisions) |
271 is_range_crit = AIdb.isRangeCriteria(db.getQueue(), crit, |
271 if isinstance(man_criterion, basestring): |
272 AIdb.MANIFESTS_TABLE) |
|
273 |
|
274 # Process "value" criteria here (check if the criteria exists in |
|
275 # DB, and then find collisions) |
|
276 if not is_range_crit: |
272 # only check criteria in use in the DB |
277 # only check criteria in use in the DB |
273 if crit not in AIdb.getCriteria(db.getQueue(), |
278 if crit not in AIdb.getCriteria(db.getQueue(), |
274 onlyUsed=False, strip=False): |
279 onlyUsed=False, strip=False): |
275 raise SystemExit(_("Error:\tCriteria %s is not a " + |
280 raise SystemExit(_("Error:\tCriteria %s is not a " + |
276 "valid criteria!") % crit) |
281 "valid criteria!") % crit) |
283 excludeManifests=exclude_manifests) |
288 excludeManifests=exclude_manifests) |
284 |
289 |
285 # will iterate over a list of the form [manName, manInst, crit, |
290 # will iterate over a list of the form [manName, manInst, crit, |
286 # None] |
291 # None] |
287 for row in db_criteria: |
292 for row in db_criteria: |
288 # check if the database and manifest values differ |
293 # check if a value in the list of values to be added is equal |
289 if(str(row[Fields.CRIT]).lower() == |
294 # to a value in the list of values for this criteria for this |
290 str(man_criterion).lower()): |
295 # row |
291 # record manifest name, instance and criteria name |
296 for value in man_criterion: |
292 try: |
297 if AIdb.is_in_list(crit, value, str(row[Fields.CRIT]), |
293 collisions[row[Fields.MANNAME], |
298 None): |
294 row[Fields.MANINST]] += crit + "," |
299 # record manifest name, instance and criteria name |
295 except KeyError: |
300 try: |
296 collisions[row[Fields.MANNAME], |
301 collisions[row[Fields.MANNAME], |
297 row[Fields.MANINST]] = crit + "," |
302 row[Fields.MANINST]] += crit + "," |
|
303 except KeyError: |
|
304 collisions[row[Fields.MANNAME], |
|
305 row[Fields.MANINST]] = crit + "," |
298 |
306 |
299 # This is a range criteria. (Check that ranges are valid, that |
307 # This is a range criteria. (Check that ranges are valid, that |
300 # "unbounded" gets set to 0/+inf, ensure the criteria exists |
308 # "unbounded" gets set to 0/+inf, ensure the criteria exists |
301 # in the DB, then look for collisions.) |
309 # in the DB, then look for collisions.) |
302 else: |
310 else: |
482 "\n\tin criteria: %s!") % |
490 "\n\tin criteria: %s!") % |
483 (man_inst[0], man_inst[1], |
491 (man_inst[0], man_inst[1], |
484 crit.replace('MIN', '', 1). |
492 crit.replace('MIN', '', 1). |
485 replace('MAX', '', 1))) |
493 replace('MAX', '', 1))) |
486 |
494 |
487 # the range did not collide or this is a single value (if we |
495 # Either the range did not collide or this is not a range |
488 # differ we can break out knowing we diverge for this |
496 # criteria. (If the value of this criteria in the db does |
|
497 # not equal the value of this criteria for the set of criteria |
|
498 # to check, we can break out knowing we diverge for this |
489 # manifest/instance) |
499 # manifest/instance) |
490 elif str(db_criterion).lower() != str(man_criterion).lower(): |
500 elif not db_criterion and not man_criterion: |
491 # manifests diverge (they don't collide) |
501 # Neither the value for this criteria in the db nor |
|
502 # the value for for this criteria in the given set of |
|
503 # criteria to check are populated. Loop around to |
|
504 # check the next criteria. |
|
505 continue |
|
506 elif not db_criterion or not man_criterion: |
|
507 # One of the two are not populated, we can break knowing |
|
508 # they're different. |
492 break |
509 break |
493 |
510 else: |
494 # end of for loop and we never broke out (diverged) |
511 # Both are populated. If none of values in the list for |
|
512 # this criteria to be added are equal to any of the values |
|
513 # in the list for this criteria from the db, there will be |
|
514 # no collision. We can break out. |
|
515 if not [value for value in man_criterion if \ |
|
516 AIdb.is_in_list(crit, value, str(db_criterion), None)]: |
|
517 break |
|
518 |
|
519 # end of for loop and we never broke out (collision) |
495 else: |
520 else: |
496 raise SystemExit(_("Error:\tManifest has same criteria as " + |
521 raise SystemExit(_("Error:\tManifest has same criteria as " + |
497 "manifest: %s/%i!") % |
522 "manifest: %s/%i!") % |
498 (man_inst[0], man_inst[1])) |
523 (man_inst[0], man_inst[1])) |
499 |
524 |
543 query += "NULL,NULL," |
568 query += "NULL,NULL," |
544 # this is a single value |
569 # this is a single value |
545 else: |
570 else: |
546 query += "NULL," |
571 query += "NULL," |
547 |
572 |
548 # this is a single criteria (not a range) |
573 # Else if this is a value criteria (not a range), insert the value |
549 elif isinstance(values, basestring): |
574 # as a space-separated list of values which will account for the case |
550 # translate "unbounded" to a database NULL |
575 # where a list of values have been given. |
551 if values == "unbounded": |
576 elif not crit.startswith('MAX'): |
552 query += "NULL," |
577 # Join the values of the list with a space separator. |
553 else: |
578 query += "'" + AIdb.sanitizeSQL(" ".join(values)) + "'," |
554 # use lower case for text strings |
|
555 query += "'" + AIdb.sanitizeSQL(str(values).lower()) + "'," |
|
556 |
|
557 # else values is a range |
579 # else values is a range |
558 else: |
580 else: |
559 for value in values: |
581 for value in values: |
560 # translate "unbounded" to a database NULL |
582 # translate "unbounded" to a database NULL |
561 if value == "unbounded": |
583 if value == "unbounded": |
693 root, errors = (verifyXML.verifyRelaxNGManifest(schema, |
715 root, errors = (verifyXML.verifyRelaxNGManifest(schema, |
694 StringIO.StringIO(lxml.etree.tostring(crit.getroot())))) |
716 StringIO.StringIO(lxml.etree.tostring(crit.getroot())))) |
695 logging.debug('criteria file passed RNG validation') |
717 logging.debug('criteria file passed RNG validation') |
696 |
718 |
697 if errors: |
719 if errors: |
698 raise ValueError(_("Error:\tFile %s failed validation:\n\t%s") % |
720 raise ValueError(_("Error:\tFile %s failed validation:\n" |
699 (criteria_path, root.message)) |
721 "\tline %s: %s") % (criteria_path, errors.line, |
|
722 errors.message)) |
700 try: |
723 try: |
701 verifyXML.prepValuesAndRanges(root, db, table) |
724 verifyXML.prepValuesAndRanges(root, db, table) |
702 except ValueError, err: |
725 except ValueError, err: |
703 raise ValueError(_("Error:\tCriteria manifest error: %s") % err) |
726 raise ValueError(_("Error:\tCriteria manifest error: %s") % err) |
704 |
727 |
815 raise AssertionError(_("Criteria contains no values")) |
838 raise AssertionError(_("Criteria contains no values")) |
816 |
839 |
817 def get_criterion(self, criterion): |
840 def get_criterion(self, criterion): |
818 """ |
841 """ |
819 Return criterion out of the criteria DOM |
842 Return criterion out of the criteria DOM |
820 Returns: A list for range criterion with a min and max entry |
843 Returns: A two-item list for range criterion with a min and max entry |
821 A string for value criterion |
844 A list of one or more values for value criterion |
822 """ |
845 """ |
823 |
846 |
824 if self._criteria_root is None: |
847 if self._criteria_root is None: |
825 return None |
848 return None |
826 |
849 |
828 for tag in source.getiterator('ai_criteria'): |
851 for tag in source.getiterator('ai_criteria'): |
829 crit = tag.get('name') |
852 crit = tag.get('name') |
830 # compare criteria name case-insensitive |
853 # compare criteria name case-insensitive |
831 if crit.lower() == criterion.lower(): |
854 if crit.lower() == criterion.lower(): |
832 for child in tag.getchildren(): |
855 for child in tag.getchildren(): |
833 if child.tag == "range": |
856 if child.text is not None: |
834 # this is a range response (split on white space) |
857 # split on white space for both values and ranges |
835 return child.text.split() |
858 return child.text.split() |
836 elif child.tag == "value": |
|
837 # this is a value response (strip white space) |
|
838 return child.text.strip() |
|
839 # should not happen according to schema |
859 # should not happen according to schema |
840 elif child.text is None: |
860 else: |
841 raise AssertionError(_("Criteria contains no values")) |
861 raise AssertionError(_("Criteria contains no values")) |
842 return None |
862 return None |
843 |
863 |
844 """ |
864 """ |
845 Look up a requested criteria (akin to dictionary access) but for an |
865 Look up a requested criteria (akin to dictionary access) but for an |