Dan Shi | 25e1fd4 | 2014-12-19 14:36:42 -0800 | [diff] [blame] | 1 | # pylint: disable-msg=C0111 |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 2 | # |
| 3 | # Copyright 2008 Google Inc. All Rights Reserved. |
| 4 | |
| 5 | """This module contains the common behavior of some actions |
| 6 | |
| 7 | Operations on ACLs or labels are very similar, so are creations and |
| 8 | deletions. The following classes provide the common handling. |
| 9 | |
| 10 | In 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 | |
| 24 | For '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 | |
Dan Shi | 25e1fd4 | 2014-12-19 14:36:42 -0800 | [diff] [blame] | 44 | import types |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 45 | from autotest_lib.cli import topic_common |
| 46 | |
| 47 | |
| 48 | # |
| 49 | # List action |
| 50 | # |
| 51 | class 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('*'): |
mbligh | f703fb4 | 2009-01-30 00:35:05 +0000 | [diff] [blame] | 73 | assert key.endswith('__in'), 'Key %s does not end with __in' % key |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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 | |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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 | |
mbligh | bd92948 | 2008-11-27 00:22:20 +0000 | [diff] [blame] | 116 | if len(results) >= len(filters[dbkey]): |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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 | # |
| 136 | class 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 | """ |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 147 | def execute(self): |
| 148 | handled = [] |
| 149 | |
Dan Shi | 25e1fd4 | 2014-12-19 14:36:42 -0800 | [diff] [blame] | 150 | if (self.op_action == 'delete' and not self.no_confirmation and |
| 151 | not self.prompt_confirmation()): |
| 152 | return |
| 153 | |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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) |
mbligh | ae64d3a | 2008-10-15 04:13:52 +0000 | [diff] [blame] | 160 | handled.append(item) |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 161 | except topic_common.CliError: |
| 162 | pass |
| 163 | return handled |
| 164 | |
| 165 | |
| 166 | def output(self, results): |
| 167 | if results: |
mbligh | 5dc6276 | 2009-09-03 20:22:38 +0000 | [diff] [blame] | 168 | results = ["'%s'" % r for r in results] |
| 169 | self.print_wrapped("%s %s" % (self.msg_done, self.msg_topic), |
| 170 | results) |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 171 | |
| 172 | |
| 173 | class atest_create(atest_create_or_delete): |
| 174 | usage_action = 'create' |
| 175 | op_action = 'add' |
| 176 | msg_done = 'Created' |
| 177 | |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 178 | |
| 179 | class atest_delete(atest_create_or_delete): |
| 180 | data_item_key = 'id' |
| 181 | usage_action = op_action = 'delete' |
| 182 | msg_done = 'Deleted' |
| 183 | |
| 184 | |
| 185 | # |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 186 | # Adding or Removing things (users, hosts or labels) from a topic |
Allen Li | 335f216 | 2017-02-01 14:47:01 -0800 | [diff] [blame] | 187 | # (ACL or Label) |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 188 | # |
| 189 | class atest_add_or_remove(topic_common.atest): |
| 190 | """atest <topic> [add|remove] |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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. |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 199 | """ |
| 200 | |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 201 | add_remove_things = {'users': 'user', 'hosts': 'host'} # Original behavior |
| 202 | |
| 203 | |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 204 | def _add_remove_uh_to_topic(self, item, what): |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 205 | """Adds the 'what' (such as users or hosts) to the 'item'""" |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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) |
mbligh | 7a3ebe3 | 2008-12-01 17:10:33 +0000 | [diff] [blame] | 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 |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 224 | |
| 225 | |
| 226 | def execute(self): |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 227 | """Adds or removes things (users, hosts, etc.) from a topic, e.g.: |
| 228 | |
| 229 | Add hosts to labels: |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 230 | self.topic = 'label' |
| 231 | self.op_action = 'add' |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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 | """ |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 240 | oks = {} |
| 241 | for item in self.get_items(): |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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: |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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 | |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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 |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 264 | |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 265 | return results |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 266 | |
| 267 | |
| 268 | def output(self, results): |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 269 | for thing, single_thing in self.add_remove_things.iteritems(): |
mbligh | 5dc6276 | 2009-09-03 20:22:38 +0000 | [diff] [blame] | 270 | # Enclose each of the elements in a single quote. |
mbligh | c613317 | 2009-09-18 19:34:50 +0000 | [diff] [blame] | 271 | things_ok = ["'%s'" % t for t in results[thing]] |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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)) |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 278 | |
| 279 | |
| 280 | class atest_add(atest_add_or_remove): |
| 281 | usage_action = op_action = 'add' |
| 282 | msg_done = 'Added to' |
| 283 | usage_words = ('Add', 'to') |
| 284 | |
| 285 | |
| 286 | class atest_remove(atest_add_or_remove): |
| 287 | usage_action = op_action = 'remove' |
| 288 | msg_done = 'Removed from' |
| 289 | usage_words = ('Remove', 'from') |