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