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 | |
Gregory Nisbet | 8d60b1c | 2020-07-14 16:26:46 -0700 | [diff] [blame] | 44 | from __future__ import print_function |
| 45 | |
Dan Shi | 25e1fd4 | 2014-12-19 14:36:42 -0800 | [diff] [blame] | 46 | import types |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 47 | from autotest_lib.cli import topic_common |
| 48 | |
| 49 | |
| 50 | # |
| 51 | # List action |
| 52 | # |
| 53 | class 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('*'): |
mbligh | f703fb4 | 2009-01-30 00:35:05 +0000 | [diff] [blame] | 75 | assert key.endswith('__in'), 'Key %s does not end with __in' % key |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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 | |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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 | |
mbligh | bd92948 | 2008-11-27 00:22:20 +0000 | [diff] [blame] | 118 | if len(results) >= len(filters[dbkey]): |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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 | # |
| 138 | class 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 | """ |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 149 | def execute(self): |
| 150 | handled = [] |
| 151 | |
Dan Shi | 25e1fd4 | 2014-12-19 14:36:42 -0800 | [diff] [blame] | 152 | if (self.op_action == 'delete' and not self.no_confirmation and |
| 153 | not self.prompt_confirmation()): |
| 154 | return |
| 155 | |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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) |
mbligh | ae64d3a | 2008-10-15 04:13:52 +0000 | [diff] [blame] | 162 | handled.append(item) |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 163 | except topic_common.CliError: |
| 164 | pass |
| 165 | return handled |
| 166 | |
| 167 | |
| 168 | def output(self, results): |
| 169 | if results: |
mbligh | 5dc6276 | 2009-09-03 20:22:38 +0000 | [diff] [blame] | 170 | results = ["'%s'" % r for r in results] |
| 171 | self.print_wrapped("%s %s" % (self.msg_done, self.msg_topic), |
| 172 | results) |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 173 | |
| 174 | |
| 175 | class atest_create(atest_create_or_delete): |
| 176 | usage_action = 'create' |
| 177 | op_action = 'add' |
| 178 | msg_done = 'Created' |
| 179 | |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 180 | |
| 181 | class atest_delete(atest_create_or_delete): |
| 182 | data_item_key = 'id' |
| 183 | usage_action = op_action = 'delete' |
| 184 | msg_done = 'Deleted' |
| 185 | |
| 186 | |
| 187 | # |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 188 | # Adding or Removing things (users, hosts or labels) from a topic |
Allen Li | 335f216 | 2017-02-01 14:47:01 -0800 | [diff] [blame] | 189 | # (ACL or Label) |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 190 | # |
| 191 | class atest_add_or_remove(topic_common.atest): |
| 192 | """atest <topic> [add|remove] |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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. |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 201 | """ |
| 202 | |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 203 | add_remove_things = {'users': 'user', 'hosts': 'host'} # Original behavior |
| 204 | |
| 205 | |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 206 | def _add_remove_uh_to_topic(self, item, what): |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 207 | """Adds the 'what' (such as users or hosts) to the 'item'""" |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 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) |
mbligh | 7a3ebe3 | 2008-12-01 17:10:33 +0000 | [diff] [blame] | 213 | try: |
| 214 | self.execute_rpc(op=op, # The opcode |
| 215 | **{'id': item, what: uhs}) # The data |
| 216 | setattr(self, 'good_%s' % what, uhs) |
Gregory Nisbet | 8d60b1c | 2020-07-14 16:26:46 -0700 | [diff] [blame] | 217 | except topic_common.CliError as full_error: |
mbligh | 7a3ebe3 | 2008-12-01 17:10:33 +0000 | [diff] [blame] | 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 |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 226 | |
| 227 | |
| 228 | def execute(self): |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 229 | """Adds or removes things (users, hosts, etc.) from a topic, e.g.: |
| 230 | |
| 231 | Add hosts to labels: |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 232 | self.topic = 'label' |
| 233 | self.op_action = 'add' |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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 | """ |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 242 | oks = {} |
| 243 | for item in self.get_items(): |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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: |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 251 | try: |
| 252 | self._add_remove_uh_to_topic(item, what) |
| 253 | except AttributeError: |
| 254 | pass |
Gregory Nisbet | 8d60b1c | 2020-07-14 16:26:46 -0700 | [diff] [blame] | 255 | except topic_common.CliError as err: |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 256 | # The error was already logged by |
| 257 | # self.failure() |
| 258 | pass |
| 259 | else: |
| 260 | oks.setdefault(item, []).append(what) |
| 261 | |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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 |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 266 | |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 267 | return results |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 268 | |
| 269 | |
| 270 | def output(self, results): |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 271 | for thing, single_thing in self.add_remove_things.iteritems(): |
mbligh | 5dc6276 | 2009-09-03 20:22:38 +0000 | [diff] [blame] | 272 | # Enclose each of the elements in a single quote. |
mbligh | c613317 | 2009-09-18 19:34:50 +0000 | [diff] [blame] | 273 | things_ok = ["'%s'" % t for t in results[thing]] |
showard | fb64e6a | 2009-04-22 21:01:18 +0000 | [diff] [blame] | 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)) |
mbligh | be630eb | 2008-08-01 16:41:48 +0000 | [diff] [blame] | 280 | |
| 281 | |
| 282 | class atest_add(atest_add_or_remove): |
| 283 | usage_action = op_action = 'add' |
| 284 | msg_done = 'Added to' |
| 285 | usage_words = ('Add', 'to') |
| 286 | |
| 287 | |
| 288 | class atest_remove(atest_add_or_remove): |
| 289 | usage_action = op_action = 'remove' |
| 290 | msg_done = 'Removed from' |
| 291 | usage_words = ('Remove', 'from') |