| # pylint: disable-msg=C0111 |
| # |
| # Copyright 2008 Google Inc. All Rights Reserved. |
| |
| """This module contains the common behavior of some actions |
| |
| Operations on ACLs or labels are very similar, so are creations and |
| deletions. The following classes provide the common handling. |
| |
| In these case, the class inheritance is, taking the command |
| 'atest label create' as an example: |
| |
| atest |
| / \ |
| / \ |
| / \ |
| atest_create label |
| \ / |
| \ / |
| \ / |
| label_create |
| |
| |
| For 'atest label add': |
| |
| atest |
| / \ |
| / \ |
| / \ |
| | label |
| | | |
| | | |
| | | |
| atest_add label_add_or_remove |
| \ / |
| \ / |
| \ / |
| label_add |
| |
| |
| |
| """ |
| |
| import types |
| from autotest_lib.cli import topic_common |
| |
| |
| # |
| # List action |
| # |
| class atest_list(topic_common.atest): |
| """atest <topic> list""" |
| usage_action = 'list' |
| |
| |
| def _convert_wildcard(self, old_key, new_key, |
| value, filters, check_results): |
| filters[new_key] = value.rstrip('*') |
| check_results[new_key] = None |
| del filters[old_key] |
| del check_results[old_key] |
| |
| |
| def _convert_name_wildcard(self, key, value, filters, check_results): |
| if value.endswith('*'): |
| # Could be __name, __login, __hostname |
| new_key = key + '__startswith' |
| self._convert_wildcard(key, new_key, value, filters, check_results) |
| |
| |
| def _convert_in_wildcard(self, key, value, filters, check_results): |
| if value.endswith('*'): |
| assert key.endswith('__in'), 'Key %s does not end with __in' % key |
| new_key = key.replace('__in', '__startswith', 1) |
| self._convert_wildcard(key, new_key, value, filters, check_results) |
| |
| |
| def check_for_wildcard(self, filters, check_results): |
| """Check if there is a wilcard (only * for the moment) |
| and replace the request appropriately""" |
| for (key, values) in filters.iteritems(): |
| if isinstance(values, types.StringTypes): |
| self._convert_name_wildcard(key, values, |
| filters, check_results) |
| continue |
| |
| if isinstance(values, types.ListType): |
| if len(values) == 1: |
| self._convert_in_wildcard(key, values[0], |
| filters, check_results) |
| continue |
| |
| for value in values: |
| if value.endswith('*'): |
| # Can only be a wildcard if it is by itelf |
| self.invalid_syntax('Cannot mix wilcards and items') |
| |
| |
| def execute(self, op, filters={}, check_results={}): |
| """Generic list execute: |
| If no filters where specified, list all the items. If |
| some specific items where asked for, filter on those: |
| check_results has the same keys than filters. If only |
| one filter is set, we use the key from check_result to |
| print the error""" |
| self.check_for_wildcard(filters, check_results) |
| |
| results = self.execute_rpc(op, **filters) |
| |
| for dbkey in filters.keys(): |
| if not check_results.get(dbkey, None): |
| # Don't want to check the results |
| # for this key |
| continue |
| |
| if len(results) >= len(filters[dbkey]): |
| continue |
| |
| # Some bad items |
| field = check_results[dbkey] |
| # The filtering for the job is on the ID which is an int. |
| # Convert it as the jobids from the CLI args are strings. |
| good = set(str(result[field]) for result in results) |
| self.invalid_arg('Unknown %s(s): \n' % self.msg_topic, |
| ', '.join(set(filters[dbkey]) - good)) |
| return results |
| |
| |
| def output(self, results, keys, sublist_keys=[]): |
| self.print_table(results, keys, sublist_keys) |
| |
| |
| # |
| # Creation & Deletion of a topic (ACL, label, user) |
| # |
| class atest_create_or_delete(topic_common.atest): |
| """atest <topic> [create|delete] |
| To subclass this, you must define: |
| Example Comment |
| self.topic 'acl_group' |
| self.op_action 'delete' Action to remove a 'topic' |
| self.data {} Additional args for the topic |
| creation/deletion |
| self.msg_topic: 'ACL' The printable version of the topic. |
| self.msg_done: 'Deleted' The printable version of the action. |
| """ |
| def execute(self): |
| handled = [] |
| |
| if (self.op_action == 'delete' and not self.no_confirmation and |
| not self.prompt_confirmation()): |
| return |
| |
| # Create or Delete the <topic> altogether |
| op = '%s_%s' % (self.op_action, self.topic) |
| for item in self.get_items(): |
| try: |
| self.data[self.data_item_key] = item |
| new_id = self.execute_rpc(op, item=item, **self.data) |
| handled.append(item) |
| except topic_common.CliError: |
| pass |
| return handled |
| |
| |
| def output(self, results): |
| if results: |
| results = ["'%s'" % r for r in results] |
| self.print_wrapped("%s %s" % (self.msg_done, self.msg_topic), |
| results) |
| |
| |
| class atest_create(atest_create_or_delete): |
| usage_action = 'create' |
| op_action = 'add' |
| msg_done = 'Created' |
| |
| |
| class atest_delete(atest_create_or_delete): |
| data_item_key = 'id' |
| usage_action = op_action = 'delete' |
| msg_done = 'Deleted' |
| |
| |
| # |
| # Adding or Removing things (users, hosts or labels) from a topic |
| # (ACL or Label) |
| # |
| class atest_add_or_remove(topic_common.atest): |
| """atest <topic> [add|remove] |
| To subclass this, you must define these attributes: |
| Example Comment |
| topic 'acl_group' |
| op_action 'remove' Action for adding users/hosts |
| add_remove_things {'users': 'user'} Dict of things to try add/removing. |
| Keys are the attribute names. Values |
| are the word to print for an |
| individual item of such a value. |
| """ |
| |
| add_remove_things = {'users': 'user', 'hosts': 'host'} # Original behavior |
| |
| |
| def _add_remove_uh_to_topic(self, item, what): |
| """Adds the 'what' (such as users or hosts) to the 'item'""" |
| uhs = getattr(self, what) |
| if len(uhs) == 0: |
| # To skip the try/else |
| raise AttributeError |
| op = '%s_%s_%s' % (self.topic, self.op_action, what) |
| try: |
| self.execute_rpc(op=op, # The opcode |
| **{'id': item, what: uhs}) # The data |
| setattr(self, 'good_%s' % what, uhs) |
| except topic_common.CliError, full_error: |
| bad_uhs = self.parse_json_exception(full_error) |
| good_uhs = list(set(uhs) - set(bad_uhs)) |
| if bad_uhs and good_uhs: |
| self.execute_rpc(op=op, |
| **{'id': item, what: good_uhs}) |
| setattr(self, 'good_%s' % what, good_uhs) |
| else: |
| raise |
| |
| |
| def execute(self): |
| """Adds or removes things (users, hosts, etc.) from a topic, e.g.: |
| |
| Add hosts to labels: |
| self.topic = 'label' |
| self.op_action = 'add' |
| self.add_remove_things = {'users': 'user', 'hosts': 'host'} |
| self.get_items() = The labels/ACLs that the hosts |
| should be added to. |
| |
| Returns: |
| A dictionary of lists of things added successfully using the same |
| keys as self.add_remove_things. |
| """ |
| oks = {} |
| for item in self.get_items(): |
| # FIXME(gps): |
| # This reverse sorting is only here to avoid breaking many |
| # existing extremely fragile unittests which depend on the |
| # exact order of the calls made below. 'users' must be run |
| # before 'hosts'. |
| plurals = reversed(sorted(self.add_remove_things.keys())) |
| for what in plurals: |
| try: |
| self._add_remove_uh_to_topic(item, what) |
| except AttributeError: |
| pass |
| except topic_common.CliError, err: |
| # The error was already logged by |
| # self.failure() |
| pass |
| else: |
| oks.setdefault(item, []).append(what) |
| |
| results = {} |
| for thing in self.add_remove_things: |
| things_ok = [item for item, what in oks.items() if thing in what] |
| results[thing] = things_ok |
| |
| return results |
| |
| |
| def output(self, results): |
| for thing, single_thing in self.add_remove_things.iteritems(): |
| # Enclose each of the elements in a single quote. |
| things_ok = ["'%s'" % t for t in results[thing]] |
| if things_ok: |
| self.print_wrapped("%s %s %s %s" % (self.msg_done, |
| self.msg_topic, |
| ', '.join(things_ok), |
| single_thing), |
| getattr(self, 'good_%s' % thing)) |
| |
| |
| class atest_add(atest_add_or_remove): |
| usage_action = op_action = 'add' |
| msg_done = 'Added to' |
| usage_words = ('Add', 'to') |
| |
| |
| class atest_remove(atest_add_or_remove): |
| usage_action = op_action = 'remove' |
| msg_done = 'Removed from' |
| usage_words = ('Remove', 'from') |