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