| # 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') |