action_common.py revision fb8f0ab0c168fb477b5eb756d041d06e3491bbde
1# 2# Copyright 2008 Google Inc. All Rights Reserved. 3 4"""This module contains the common behavior of some actions 5 6Operations on ACLs or labels are very similar, so are creations and 7deletions. The following classes provide the common handling. 8 9In these case, the class inheritance is, taking the command 10'atest label create' as an example: 11 12 atest 13 / \ 14 / \ 15 / \ 16 atest_create label 17 \ / 18 \ / 19 \ / 20 label_create 21 22 23For 'atest label add': 24 25 atest 26 / \ 27 / \ 28 / \ 29 | label 30 | | 31 | | 32 | | 33 atest_add label_add_or_remove 34 \ / 35 \ / 36 \ / 37 label_add 38 39 40 41""" 42 43import re, socket, types 44from autotest_lib.cli import topic_common 45 46 47# 48# List action 49# 50class atest_list(topic_common.atest): 51 """atest <topic> list""" 52 usage_action = 'list' 53 54 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] 61 62 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) 68 69 70 def _convert_in_wildcard(self, key, value, filters, check_results): 71 if value.endswith('*'): 72 assert(key.endswith('__in')) 73 new_key = key.replace('__in', '__startswith', 1) 74 self._convert_wildcard(key, new_key, value, filters, check_results) 75 76 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 85 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 91 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') 96 97 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) 106 107 socket.setdefaulttimeout(topic_common.LIST_SOCKET_TIMEOUT) 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 # Create or Delete the <topic> altogether 151 op = '%s_%s' % (self.op_action, self.topic) 152 for item in self.get_items(): 153 try: 154 self.data[self.data_item_key] = item 155 new_id = self.execute_rpc(op, item=item, **self.data) 156 handled.append(item) 157 except topic_common.CliError: 158 pass 159 return handled 160 161 162 def output(self, results): 163 if results: 164 self.print_wrapped ("%s %s" % (self.msg_done, self.msg_topic), 165 results) 166 167 168class atest_create(atest_create_or_delete): 169 usage_action = 'create' 170 op_action = 'add' 171 msg_done = 'Created' 172 173 def parse_hosts(self, args): 174 """ Parses the arguments to generate a list of hosts and meta_hosts 175 A host is a regular name, a meta_host is n*label or *label. 176 These can be mixed on the CLI, and separated by either commas or 177 spaces, e.g.: 5*Machine_Label host0 5*Machine_Label2,host2 """ 178 179 hosts = [] 180 meta_hosts = [] 181 182 for arg in args: 183 for host in arg.split(','): 184 if re.match('^[0-9]+[*]', host): 185 num, host = host.split('*', 1) 186 meta_hosts += int(num) * [host] 187 elif re.match('^[*](\w*)', host): 188 meta_hosts += [re.match('^[*](\w*)', host).group(1)] 189 elif host != '': 190 # Real hostname 191 hosts.append(host) 192 193 return (hosts, meta_hosts) 194 195 196class atest_delete(atest_create_or_delete): 197 data_item_key = 'id' 198 usage_action = op_action = 'delete' 199 msg_done = 'Deleted' 200 201 202# 203# Adding or Removing users or hosts from a topic (ACL or label) 204# 205class atest_add_or_remove(topic_common.atest): 206 """atest <topic> [add|remove] 207 To subclass this, you must define: 208 Example Comment 209 self.topic 'acl_group' 210 self.op_action 'remove' Action for adding users/hosts 211 """ 212 213 def _add_remove_uh_to_topic(self, item, what): 214 """Adds the 'what' (users or hosts) to the 'item'""" 215 uhs = getattr(self, what) 216 if len(uhs) == 0: 217 # To skip the try/else 218 raise AttributeError 219 op = '%s_%s_%s' % (self.topic, self.op_action, what) 220 self.execute_rpc(op=op, # The opcode 221 item='%s (%s)' %(item, ','.join(uhs)), # The error 222 **{'id': item, what: uhs}) # The data 223 224 225 def execute(self): 226 """Adds or removes users or hosts from a topic, e.g.: 227 add hosts to labels: 228 self.topic = 'label' 229 self.op_action = 'add' 230 self.get_items() = the labels that the hosts 231 should be added to""" 232 oks = {} 233 for item in self.get_items(): 234 for what in ['users', 'hosts']: 235 try: 236 self._add_remove_uh_to_topic(item, what) 237 except AttributeError: 238 pass 239 except topic_common.CliError, err: 240 # The error was already logged by 241 # self.failure() 242 pass 243 else: 244 oks.setdefault(item, []).append(what) 245 246 users_ok = [item for (item, what) in oks.items() if 'users' in what] 247 hosts_ok = [item for (item, what) in oks.items() if 'hosts' in what] 248 249 return (users_ok, hosts_ok) 250 251 252 def output(self, results): 253 (users_ok, hosts_ok) = results 254 if users_ok: 255 self.print_wrapped("%s %s %s user" % 256 (self.msg_done, 257 self.msg_topic, 258 ', '.join(users_ok)), 259 self.users) 260 261 if hosts_ok: 262 self.print_wrapped("%s %s %s host" % 263 (self.msg_done, 264 self.msg_topic, 265 ', '.join(hosts_ok)), 266 self.hosts) 267 268 269class atest_add(atest_add_or_remove): 270 usage_action = op_action = 'add' 271 msg_done = 'Added to' 272 usage_words = ('Add', 'to') 273 274 275class atest_remove(atest_add_or_remove): 276 usage_action = op_action = 'remove' 277 msg_done = 'Removed from' 278 usage_words = ('Remove', 'from') 279