blob: 6cb7599e36f7ba920a6c244f982eec8ba3745747 [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
Gregory Nisbet8d60b1c2020-07-14 16:26:46 -070044from __future__ import print_function
45
Dan Shi25e1fd42014-12-19 14:36:42 -080046import types
mblighbe630eb2008-08-01 16:41:48 +000047from 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('*'):
mblighf703fb42009-01-30 00:35:05 +000075 assert key.endswith('__in'), 'Key %s does not end with __in' % key
mblighbe630eb2008-08-01 16:41:48 +000076 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
mblighbe630eb2008-08-01 16:41:48 +0000110 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
mblighbd929482008-11-27 00:22:20 +0000118 if len(results) >= len(filters[dbkey]):
mblighbe630eb2008-08-01 16:41:48 +0000119 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 """
mblighbe630eb2008-08-01 16:41:48 +0000149 def execute(self):
150 handled = []
151
Dan Shi25e1fd42014-12-19 14:36:42 -0800152 if (self.op_action == 'delete' and not self.no_confirmation and
153 not self.prompt_confirmation()):
154 return
155
mblighbe630eb2008-08-01 16:41:48 +0000156 # 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)
mblighae64d3a2008-10-15 04:13:52 +0000162 handled.append(item)
mblighbe630eb2008-08-01 16:41:48 +0000163 except topic_common.CliError:
164 pass
165 return handled
166
167
168 def output(self, results):
169 if results:
mbligh5dc62762009-09-03 20:22:38 +0000170 results = ["'%s'" % r for r in results]
171 self.print_wrapped("%s %s" % (self.msg_done, self.msg_topic),
172 results)
mblighbe630eb2008-08-01 16:41:48 +0000173
174
175class atest_create(atest_create_or_delete):
176 usage_action = 'create'
177 op_action = 'add'
178 msg_done = 'Created'
179
mblighbe630eb2008-08-01 16:41:48 +0000180
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#
showardfb64e6a2009-04-22 21:01:18 +0000188# Adding or Removing things (users, hosts or labels) from a topic
Allen Li335f2162017-02-01 14:47:01 -0800189# (ACL or Label)
mblighbe630eb2008-08-01 16:41:48 +0000190#
191class atest_add_or_remove(topic_common.atest):
192 """atest <topic> [add|remove]
showardfb64e6a2009-04-22 21:01:18 +0000193 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.
mblighbe630eb2008-08-01 16:41:48 +0000201 """
202
showardfb64e6a2009-04-22 21:01:18 +0000203 add_remove_things = {'users': 'user', 'hosts': 'host'} # Original behavior
204
205
mblighbe630eb2008-08-01 16:41:48 +0000206 def _add_remove_uh_to_topic(self, item, what):
showardfb64e6a2009-04-22 21:01:18 +0000207 """Adds the 'what' (such as users or hosts) to the 'item'"""
mblighbe630eb2008-08-01 16:41:48 +0000208 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)
mbligh7a3ebe32008-12-01 17:10:33 +0000213 try:
214 self.execute_rpc(op=op, # The opcode
215 **{'id': item, what: uhs}) # The data
216 setattr(self, 'good_%s' % what, uhs)
Gregory Nisbet8d60b1c2020-07-14 16:26:46 -0700217 except topic_common.CliError as full_error:
mbligh7a3ebe32008-12-01 17:10:33 +0000218 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
mblighbe630eb2008-08-01 16:41:48 +0000226
227
228 def execute(self):
showardfb64e6a2009-04-22 21:01:18 +0000229 """Adds or removes things (users, hosts, etc.) from a topic, e.g.:
230
231 Add hosts to labels:
mblighbe630eb2008-08-01 16:41:48 +0000232 self.topic = 'label'
233 self.op_action = 'add'
showardfb64e6a2009-04-22 21:01:18 +0000234 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 """
mblighbe630eb2008-08-01 16:41:48 +0000242 oks = {}
243 for item in self.get_items():
showardfb64e6a2009-04-22 21:01:18 +0000244 # 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:
mblighbe630eb2008-08-01 16:41:48 +0000251 try:
252 self._add_remove_uh_to_topic(item, what)
253 except AttributeError:
254 pass
Gregory Nisbet8d60b1c2020-07-14 16:26:46 -0700255 except topic_common.CliError as err:
mblighbe630eb2008-08-01 16:41:48 +0000256 # The error was already logged by
257 # self.failure()
258 pass
259 else:
260 oks.setdefault(item, []).append(what)
261
showardfb64e6a2009-04-22 21:01:18 +0000262 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
mblighbe630eb2008-08-01 16:41:48 +0000266
showardfb64e6a2009-04-22 21:01:18 +0000267 return results
mblighbe630eb2008-08-01 16:41:48 +0000268
269
270 def output(self, results):
showardfb64e6a2009-04-22 21:01:18 +0000271 for thing, single_thing in self.add_remove_things.iteritems():
mbligh5dc62762009-09-03 20:22:38 +0000272 # Enclose each of the elements in a single quote.
mblighc6133172009-09-18 19:34:50 +0000273 things_ok = ["'%s'" % t for t in results[thing]]
showardfb64e6a2009-04-22 21:01:18 +0000274 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))
mblighbe630eb2008-08-01 16:41:48 +0000280
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')