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