blob: ce020077e37d651d4eece00f7f438bbb319f96ed [file] [log] [blame]
Dan Shi25e1fd42014-12-19 14:36:42 -08001# pylint: disable-msg=C0111
mblighbe630eb2008-08-01 16:41:48 +00002#
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
Dan Shi25e1fd42014-12-19 14:36:42 -080044import types
mblighbe630eb2008-08-01 16:41:48 +000045from autotest_lib.cli import topic_common
46
47
48#
49# List action
50#
51class 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('*'):
mblighf703fb42009-01-30 00:35:05 +000073 assert key.endswith('__in'), 'Key %s does not end with __in' % key
mblighbe630eb2008-08-01 16:41:48 +000074 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
mblighbe630eb2008-08-01 16:41:48 +0000108 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
mblighbd929482008-11-27 00:22:20 +0000116 if len(results) >= len(filters[dbkey]):
mblighbe630eb2008-08-01 16:41:48 +0000117 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#
136class 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 """
mblighbe630eb2008-08-01 16:41:48 +0000147 def execute(self):
148 handled = []
149
Dan Shi25e1fd42014-12-19 14:36:42 -0800150 if (self.op_action == 'delete' and not self.no_confirmation and
151 not self.prompt_confirmation()):
152 return
153
mblighbe630eb2008-08-01 16:41:48 +0000154 # 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)
mblighae64d3a2008-10-15 04:13:52 +0000160 handled.append(item)
mblighbe630eb2008-08-01 16:41:48 +0000161 except topic_common.CliError:
162 pass
163 return handled
164
165
166 def output(self, results):
167 if results:
mbligh5dc62762009-09-03 20:22:38 +0000168 results = ["'%s'" % r for r in results]
169 self.print_wrapped("%s %s" % (self.msg_done, self.msg_topic),
170 results)
mblighbe630eb2008-08-01 16:41:48 +0000171
172
173class atest_create(atest_create_or_delete):
174 usage_action = 'create'
175 op_action = 'add'
176 msg_done = 'Created'
177
mblighbe630eb2008-08-01 16:41:48 +0000178
179class atest_delete(atest_create_or_delete):
180 data_item_key = 'id'
181 usage_action = op_action = 'delete'
182 msg_done = 'Deleted'
183
184
185#
showardfb64e6a2009-04-22 21:01:18 +0000186# Adding or Removing things (users, hosts or labels) from a topic
Allen Li335f2162017-02-01 14:47:01 -0800187# (ACL or Label)
mblighbe630eb2008-08-01 16:41:48 +0000188#
189class atest_add_or_remove(topic_common.atest):
190 """atest <topic> [add|remove]
showardfb64e6a2009-04-22 21:01:18 +0000191 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.
mblighbe630eb2008-08-01 16:41:48 +0000199 """
200
showardfb64e6a2009-04-22 21:01:18 +0000201 add_remove_things = {'users': 'user', 'hosts': 'host'} # Original behavior
202
203
mblighbe630eb2008-08-01 16:41:48 +0000204 def _add_remove_uh_to_topic(self, item, what):
showardfb64e6a2009-04-22 21:01:18 +0000205 """Adds the 'what' (such as users or hosts) to the 'item'"""
mblighbe630eb2008-08-01 16:41:48 +0000206 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)
mbligh7a3ebe32008-12-01 17:10:33 +0000211 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
mblighbe630eb2008-08-01 16:41:48 +0000224
225
226 def execute(self):
showardfb64e6a2009-04-22 21:01:18 +0000227 """Adds or removes things (users, hosts, etc.) from a topic, e.g.:
228
229 Add hosts to labels:
mblighbe630eb2008-08-01 16:41:48 +0000230 self.topic = 'label'
231 self.op_action = 'add'
showardfb64e6a2009-04-22 21:01:18 +0000232 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 """
mblighbe630eb2008-08-01 16:41:48 +0000240 oks = {}
241 for item in self.get_items():
showardfb64e6a2009-04-22 21:01:18 +0000242 # 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:
mblighbe630eb2008-08-01 16:41:48 +0000249 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
showardfb64e6a2009-04-22 21:01:18 +0000260 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
mblighbe630eb2008-08-01 16:41:48 +0000264
showardfb64e6a2009-04-22 21:01:18 +0000265 return results
mblighbe630eb2008-08-01 16:41:48 +0000266
267
268 def output(self, results):
showardfb64e6a2009-04-22 21:01:18 +0000269 for thing, single_thing in self.add_remove_things.iteritems():
mbligh5dc62762009-09-03 20:22:38 +0000270 # Enclose each of the elements in a single quote.
mblighc6133172009-09-18 19:34:50 +0000271 things_ok = ["'%s'" % t for t in results[thing]]
showardfb64e6a2009-04-22 21:01:18 +0000272 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))
mblighbe630eb2008-08-01 16:41:48 +0000278
279
280class atest_add(atest_add_or_remove):
281 usage_action = op_action = 'add'
282 msg_done = 'Added to'
283 usage_words = ('Add', 'to')
284
285
286class atest_remove(atest_add_or_remove):
287 usage_action = op_action = 'remove'
288 msg_done = 'Removed from'
289 usage_words = ('Remove', 'from')