Home | History | Annotate | Download | only in cli
      1 # pylint: disable-msg=C0111
      2 #
      3 # Copyright 2008 Google Inc. All Rights Reserved.
      4 
      5 """This module contains the common behavior of some actions
      6 
      7 Operations on ACLs or labels are very similar, so are creations and
      8 deletions. The following classes provide the common handling.
      9 
     10 In these case, the class inheritance is, taking the command
     11 'atest label create' as an example:
     12 
     13                   atest
     14                  /     \
     15                 /       \
     16                /         \
     17          atest_create   label
     18                \         /
     19                 \       /
     20                  \     /
     21                label_create
     22 
     23 
     24 For 'atest label add':
     25 
     26                   atest
     27                  /     \
     28                 /       \
     29                /         \
     30                |       label
     31                |         |
     32                |         |
     33                |         |
     34          atest_add   label_add_or_remove
     35                \         /
     36                 \       /
     37                  \     /
     38                label_add
     39 
     40 
     41 
     42 """
     43 
     44 import types
     45 from autotest_lib.cli import topic_common
     46 
     47 
     48 #
     49 # List action
     50 #
     51 class atest_list(topic_common.atest):
     52     """atest <topic> list"""
     53     usage_action = 'list'
     54 
     55 
     56     def _convert_wildcard(self, old_key, new_key,
     57                           value, filters, check_results):
     58         filters[new_key] = value.rstrip('*')
     59         check_results[new_key] = None
     60         del filters[old_key]
     61         del check_results[old_key]
     62 
     63 
     64     def _convert_name_wildcard(self, key, value, filters, check_results):
     65         if value.endswith('*'):
     66             # Could be __name, __login, __hostname
     67             new_key = key + '__startswith'
     68             self._convert_wildcard(key, new_key, value, filters, check_results)
     69 
     70 
     71     def _convert_in_wildcard(self, key, value, filters, check_results):
     72         if value.endswith('*'):
     73             assert key.endswith('__in'), 'Key %s does not end with __in' % key
     74             new_key = key.replace('__in', '__startswith', 1)
     75             self._convert_wildcard(key, new_key, value, filters, check_results)
     76 
     77 
     78     def check_for_wildcard(self, filters, check_results):
     79         """Check if there is a wilcard (only * for the moment)
     80         and replace the request appropriately"""
     81         for (key, values) in filters.iteritems():
     82             if isinstance(values, types.StringTypes):
     83                 self._convert_name_wildcard(key, values,
     84                                             filters, check_results)
     85                 continue
     86 
     87             if isinstance(values, types.ListType):
     88                 if len(values) == 1:
     89                     self._convert_in_wildcard(key, values[0],
     90                                               filters, check_results)
     91                     continue
     92 
     93                 for value in values:
     94                     if value.endswith('*'):
     95                         # Can only be a wildcard if it is by itelf
     96                         self.invalid_syntax('Cannot mix wilcards and items')
     97 
     98 
     99     def execute(self, op, filters={}, check_results={}):
    100         """Generic list execute:
    101         If no filters where specified, list all the items.  If
    102         some specific items where asked for, filter on those:
    103         check_results has the same keys than filters.  If only
    104         one filter is set, we use the key from check_result to
    105         print the error"""
    106         self.check_for_wildcard(filters, check_results)
    107 
    108         results = self.execute_rpc(op, **filters)
    109 
    110         for dbkey in filters.keys():
    111             if not check_results.get(dbkey, None):
    112                 # Don't want to check the results
    113                 # for this key
    114                 continue
    115 
    116             if len(results) >= len(filters[dbkey]):
    117                 continue
    118 
    119             # Some bad items
    120             field = check_results[dbkey]
    121             # The filtering for the job is on the ID which is an int.
    122             # Convert it as the jobids from the CLI args are strings.
    123             good = set(str(result[field]) for result in results)
    124             self.invalid_arg('Unknown %s(s): \n' % self.msg_topic,
    125                              ', '.join(set(filters[dbkey]) - good))
    126         return results
    127 
    128 
    129     def output(self, results, keys, sublist_keys=[]):
    130         self.print_table(results, keys, sublist_keys)
    131 
    132 
    133 #
    134 # Creation & Deletion of a topic (ACL, label, user)
    135 #
    136 class atest_create_or_delete(topic_common.atest):
    137     """atest <topic> [create|delete]
    138     To subclass this, you must define:
    139                          Example          Comment
    140     self.topic           'acl_group'
    141     self.op_action       'delete'        Action to remove a 'topic'
    142     self.data            {}              Additional args for the topic
    143                                          creation/deletion
    144     self.msg_topic:      'ACL'           The printable version of the topic.
    145     self.msg_done:       'Deleted'       The printable version of the action.
    146     """
    147     def execute(self):
    148         handled = []
    149 
    150         if (self.op_action == 'delete' and not self.no_confirmation and
    151             not self.prompt_confirmation()):
    152             return
    153 
    154         # Create or Delete the <topic> altogether
    155         op = '%s_%s' % (self.op_action, self.topic)
    156         for item in self.get_items():
    157             try:
    158                 self.data[self.data_item_key] = item
    159                 new_id = self.execute_rpc(op, item=item, **self.data)
    160                 handled.append(item)
    161             except topic_common.CliError:
    162                 pass
    163         return handled
    164 
    165 
    166     def output(self, results):
    167         if results:
    168             results = ["'%s'" % r for r in results]
    169             self.print_wrapped("%s %s" % (self.msg_done, self.msg_topic),
    170                                results)
    171 
    172 
    173 class atest_create(atest_create_or_delete):
    174     usage_action = 'create'
    175     op_action = 'add'
    176     msg_done = 'Created'
    177 
    178 
    179 class atest_delete(atest_create_or_delete):
    180     data_item_key = 'id'
    181     usage_action = op_action = 'delete'
    182     msg_done = 'Deleted'
    183 
    184 
    185 #
    186 # Adding or Removing things (users, hosts or labels) from a topic
    187 # (ACL or Label)
    188 #
    189 class atest_add_or_remove(topic_common.atest):
    190     """atest <topic> [add|remove]
    191     To subclass this, you must define these attributes:
    192                        Example             Comment
    193     topic              'acl_group'
    194     op_action          'remove'            Action for adding users/hosts
    195     add_remove_things  {'users': 'user'}   Dict of things to try add/removing.
    196                                            Keys are the attribute names.  Values
    197                                            are the word to print for an
    198                                            individual item of such a value.
    199     """
    200 
    201     add_remove_things = {'users': 'user', 'hosts': 'host'}  # Original behavior
    202 
    203 
    204     def _add_remove_uh_to_topic(self, item, what):
    205         """Adds the 'what' (such as users or hosts) to the 'item'"""
    206         uhs = getattr(self, what)
    207         if len(uhs) == 0:
    208             # To skip the try/else
    209             raise AttributeError
    210         op = '%s_%s_%s' % (self.topic, self.op_action, what)
    211         try:
    212             self.execute_rpc(op=op,                       # The opcode
    213                              **{'id': item, what: uhs})   # The data
    214             setattr(self, 'good_%s' % what, uhs)
    215         except topic_common.CliError, full_error:
    216             bad_uhs = self.parse_json_exception(full_error)
    217             good_uhs = list(set(uhs) - set(bad_uhs))
    218             if bad_uhs and good_uhs:
    219                 self.execute_rpc(op=op,
    220                                  **{'id': item, what: good_uhs})
    221                 setattr(self, 'good_%s' % what, good_uhs)
    222             else:
    223                 raise
    224 
    225 
    226     def execute(self):
    227         """Adds or removes things (users, hosts, etc.) from a topic, e.g.:
    228 
    229         Add hosts to labels:
    230           self.topic = 'label'
    231           self.op_action = 'add'
    232           self.add_remove_things = {'users': 'user', 'hosts': 'host'}
    233           self.get_items() = The labels/ACLs that the hosts
    234                              should be added to.
    235 
    236         Returns:
    237           A dictionary of lists of things added successfully using the same
    238           keys as self.add_remove_things.
    239         """
    240         oks = {}
    241         for item in self.get_items():
    242             # FIXME(gps):
    243             # This reverse sorting is only here to avoid breaking many
    244             # existing extremely fragile unittests which depend on the
    245             # exact order of the calls made below.  'users' must be run
    246             # before 'hosts'.
    247             plurals = reversed(sorted(self.add_remove_things.keys()))
    248             for what in plurals:
    249                 try:
    250                     self._add_remove_uh_to_topic(item, what)
    251                 except AttributeError:
    252                     pass
    253                 except topic_common.CliError, err:
    254                     # The error was already logged by
    255                     # self.failure()
    256                     pass
    257                 else:
    258                     oks.setdefault(item, []).append(what)
    259 
    260         results = {}
    261         for thing in self.add_remove_things:
    262             things_ok = [item for item, what in oks.items() if thing in what]
    263             results[thing] = things_ok
    264 
    265         return results
    266 
    267 
    268     def output(self, results):
    269         for thing, single_thing in self.add_remove_things.iteritems():
    270             # Enclose each of the elements in a single quote.
    271             things_ok = ["'%s'" % t for t in results[thing]]
    272             if things_ok:
    273                 self.print_wrapped("%s %s %s %s" % (self.msg_done,
    274                                                     self.msg_topic,
    275                                                     ', '.join(things_ok),
    276                                                     single_thing),
    277                                    getattr(self, 'good_%s' % thing))
    278 
    279 
    280 class atest_add(atest_add_or_remove):
    281     usage_action = op_action = 'add'
    282     msg_done = 'Added to'
    283     usage_words = ('Add', 'to')
    284 
    285 
    286 class atest_remove(atest_add_or_remove):
    287     usage_action = op_action = 'remove'
    288     msg_done = 'Removed from'
    289     usage_words = ('Remove', 'from')
    290