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 44import types 45from 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('*'): 73 assert key.endswith('__in'), 'Key %s does not end with __in' % key 74 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 108 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 116 if len(results) >= len(filters[dbkey]): 117 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 """ 147 def execute(self): 148 handled = [] 149 150 if (self.op_action == 'delete' and not self.no_confirmation and 151 not self.prompt_confirmation()): 152 return 153 154 # 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) 160 handled.append(item) 161 except topic_common.CliError: 162 pass 163 return handled 164 165 166 def output(self, results): 167 if results: 168 results = ["'%s'" % r for r in results] 169 self.print_wrapped("%s %s" % (self.msg_done, self.msg_topic), 170 results) 171 172 173class atest_create(atest_create_or_delete): 174 usage_action = 'create' 175 op_action = 'add' 176 msg_done = 'Created' 177 178 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# 186# Adding or Removing things (users, hosts or labels) from a topic 187# (ACL, Label or AtomicGroup) 188# 189class atest_add_or_remove(topic_common.atest): 190 """atest <topic> [add|remove] 191 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. 199 """ 200 201 add_remove_things = {'users': 'user', 'hosts': 'host'} # Original behavior 202 203 204 def _add_remove_uh_to_topic(self, item, what): 205 """Adds the 'what' (such as users or hosts) to the 'item'""" 206 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) 211 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 224 225 226 def execute(self): 227 """Adds or removes things (users, hosts, etc.) from a topic, e.g.: 228 229 Add hosts to labels: 230 self.topic = 'label' 231 self.op_action = 'add' 232 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 """ 240 oks = {} 241 for item in self.get_items(): 242 # 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: 249 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 260 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 264 265 return results 266 267 268 def output(self, results): 269 for thing, single_thing in self.add_remove_things.iteritems(): 270 # Enclose each of the elements in a single quote. 271 things_ok = ["'%s'" % t for t in results[thing]] 272 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)) 278 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') 290