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