You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
292 lines
9.9 KiB
292 lines
9.9 KiB
# pylint: disable-msg=C0111
|
|
#
|
|
# Copyright 2008 Google Inc. All Rights Reserved.
|
|
|
|
"""This module contains the common behavior of some actions
|
|
|
|
Operations on ACLs or labels are very similar, so are creations and
|
|
deletions. The following classes provide the common handling.
|
|
|
|
In these case, the class inheritance is, taking the command
|
|
'atest label create' as an example:
|
|
|
|
atest
|
|
/ \
|
|
/ \
|
|
/ \
|
|
atest_create label
|
|
\ /
|
|
\ /
|
|
\ /
|
|
label_create
|
|
|
|
|
|
For 'atest label add':
|
|
|
|
atest
|
|
/ \
|
|
/ \
|
|
/ \
|
|
| label
|
|
| |
|
|
| |
|
|
| |
|
|
atest_add label_add_or_remove
|
|
\ /
|
|
\ /
|
|
\ /
|
|
label_add
|
|
|
|
|
|
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import types
|
|
from autotest_lib.cli import topic_common
|
|
|
|
|
|
#
|
|
# List action
|
|
#
|
|
class atest_list(topic_common.atest):
|
|
"""atest <topic> list"""
|
|
usage_action = 'list'
|
|
|
|
|
|
def _convert_wildcard(self, old_key, new_key,
|
|
value, filters, check_results):
|
|
filters[new_key] = value.rstrip('*')
|
|
check_results[new_key] = None
|
|
del filters[old_key]
|
|
del check_results[old_key]
|
|
|
|
|
|
def _convert_name_wildcard(self, key, value, filters, check_results):
|
|
if value.endswith('*'):
|
|
# Could be __name, __login, __hostname
|
|
new_key = key + '__startswith'
|
|
self._convert_wildcard(key, new_key, value, filters, check_results)
|
|
|
|
|
|
def _convert_in_wildcard(self, key, value, filters, check_results):
|
|
if value.endswith('*'):
|
|
assert key.endswith('__in'), 'Key %s does not end with __in' % key
|
|
new_key = key.replace('__in', '__startswith', 1)
|
|
self._convert_wildcard(key, new_key, value, filters, check_results)
|
|
|
|
|
|
def check_for_wildcard(self, filters, check_results):
|
|
"""Check if there is a wilcard (only * for the moment)
|
|
and replace the request appropriately"""
|
|
for (key, values) in filters.iteritems():
|
|
if isinstance(values, types.StringTypes):
|
|
self._convert_name_wildcard(key, values,
|
|
filters, check_results)
|
|
continue
|
|
|
|
if isinstance(values, types.ListType):
|
|
if len(values) == 1:
|
|
self._convert_in_wildcard(key, values[0],
|
|
filters, check_results)
|
|
continue
|
|
|
|
for value in values:
|
|
if value.endswith('*'):
|
|
# Can only be a wildcard if it is by itelf
|
|
self.invalid_syntax('Cannot mix wilcards and items')
|
|
|
|
|
|
def execute(self, op, filters={}, check_results={}):
|
|
"""Generic list execute:
|
|
If no filters where specified, list all the items. If
|
|
some specific items where asked for, filter on those:
|
|
check_results has the same keys than filters. If only
|
|
one filter is set, we use the key from check_result to
|
|
print the error"""
|
|
self.check_for_wildcard(filters, check_results)
|
|
|
|
results = self.execute_rpc(op, **filters)
|
|
|
|
for dbkey in filters.keys():
|
|
if not check_results.get(dbkey, None):
|
|
# Don't want to check the results
|
|
# for this key
|
|
continue
|
|
|
|
if len(results) >= len(filters[dbkey]):
|
|
continue
|
|
|
|
# Some bad items
|
|
field = check_results[dbkey]
|
|
# The filtering for the job is on the ID which is an int.
|
|
# Convert it as the jobids from the CLI args are strings.
|
|
good = set(str(result[field]) for result in results)
|
|
self.invalid_arg('Unknown %s(s): \n' % self.msg_topic,
|
|
', '.join(set(filters[dbkey]) - good))
|
|
return results
|
|
|
|
|
|
def output(self, results, keys, sublist_keys=[]):
|
|
self.print_table(results, keys, sublist_keys)
|
|
|
|
|
|
#
|
|
# Creation & Deletion of a topic (ACL, label, user)
|
|
#
|
|
class atest_create_or_delete(topic_common.atest):
|
|
"""atest <topic> [create|delete]
|
|
To subclass this, you must define:
|
|
Example Comment
|
|
self.topic 'acl_group'
|
|
self.op_action 'delete' Action to remove a 'topic'
|
|
self.data {} Additional args for the topic
|
|
creation/deletion
|
|
self.msg_topic: 'ACL' The printable version of the topic.
|
|
self.msg_done: 'Deleted' The printable version of the action.
|
|
"""
|
|
def execute(self):
|
|
handled = []
|
|
|
|
if (self.op_action == 'delete' and not self.no_confirmation and
|
|
not self.prompt_confirmation()):
|
|
return
|
|
|
|
# Create or Delete the <topic> altogether
|
|
op = '%s_%s' % (self.op_action, self.topic)
|
|
for item in self.get_items():
|
|
try:
|
|
self.data[self.data_item_key] = item
|
|
new_id = self.execute_rpc(op, item=item, **self.data)
|
|
handled.append(item)
|
|
except topic_common.CliError:
|
|
pass
|
|
return handled
|
|
|
|
|
|
def output(self, results):
|
|
if results:
|
|
results = ["'%s'" % r for r in results]
|
|
self.print_wrapped("%s %s" % (self.msg_done, self.msg_topic),
|
|
results)
|
|
|
|
|
|
class atest_create(atest_create_or_delete):
|
|
usage_action = 'create'
|
|
op_action = 'add'
|
|
msg_done = 'Created'
|
|
|
|
|
|
class atest_delete(atest_create_or_delete):
|
|
data_item_key = 'id'
|
|
usage_action = op_action = 'delete'
|
|
msg_done = 'Deleted'
|
|
|
|
|
|
#
|
|
# Adding or Removing things (users, hosts or labels) from a topic
|
|
# (ACL or Label)
|
|
#
|
|
class atest_add_or_remove(topic_common.atest):
|
|
"""atest <topic> [add|remove]
|
|
To subclass this, you must define these attributes:
|
|
Example Comment
|
|
topic 'acl_group'
|
|
op_action 'remove' Action for adding users/hosts
|
|
add_remove_things {'users': 'user'} Dict of things to try add/removing.
|
|
Keys are the attribute names. Values
|
|
are the word to print for an
|
|
individual item of such a value.
|
|
"""
|
|
|
|
add_remove_things = {'users': 'user', 'hosts': 'host'} # Original behavior
|
|
|
|
|
|
def _add_remove_uh_to_topic(self, item, what):
|
|
"""Adds the 'what' (such as users or hosts) to the 'item'"""
|
|
uhs = getattr(self, what)
|
|
if len(uhs) == 0:
|
|
# To skip the try/else
|
|
raise AttributeError
|
|
op = '%s_%s_%s' % (self.topic, self.op_action, what)
|
|
try:
|
|
self.execute_rpc(op=op, # The opcode
|
|
**{'id': item, what: uhs}) # The data
|
|
setattr(self, 'good_%s' % what, uhs)
|
|
except topic_common.CliError as full_error:
|
|
bad_uhs = self.parse_json_exception(full_error)
|
|
good_uhs = list(set(uhs) - set(bad_uhs))
|
|
if bad_uhs and good_uhs:
|
|
self.execute_rpc(op=op,
|
|
**{'id': item, what: good_uhs})
|
|
setattr(self, 'good_%s' % what, good_uhs)
|
|
else:
|
|
raise
|
|
|
|
|
|
def execute(self):
|
|
"""Adds or removes things (users, hosts, etc.) from a topic, e.g.:
|
|
|
|
Add hosts to labels:
|
|
self.topic = 'label'
|
|
self.op_action = 'add'
|
|
self.add_remove_things = {'users': 'user', 'hosts': 'host'}
|
|
self.get_items() = The labels/ACLs that the hosts
|
|
should be added to.
|
|
|
|
Returns:
|
|
A dictionary of lists of things added successfully using the same
|
|
keys as self.add_remove_things.
|
|
"""
|
|
oks = {}
|
|
for item in self.get_items():
|
|
# FIXME(gps):
|
|
# This reverse sorting is only here to avoid breaking many
|
|
# existing extremely fragile unittests which depend on the
|
|
# exact order of the calls made below. 'users' must be run
|
|
# before 'hosts'.
|
|
plurals = reversed(sorted(self.add_remove_things.keys()))
|
|
for what in plurals:
|
|
try:
|
|
self._add_remove_uh_to_topic(item, what)
|
|
except AttributeError:
|
|
pass
|
|
except topic_common.CliError as err:
|
|
# The error was already logged by
|
|
# self.failure()
|
|
pass
|
|
else:
|
|
oks.setdefault(item, []).append(what)
|
|
|
|
results = {}
|
|
for thing in self.add_remove_things:
|
|
things_ok = [item for item, what in oks.items() if thing in what]
|
|
results[thing] = things_ok
|
|
|
|
return results
|
|
|
|
|
|
def output(self, results):
|
|
for thing, single_thing in self.add_remove_things.iteritems():
|
|
# Enclose each of the elements in a single quote.
|
|
things_ok = ["'%s'" % t for t in results[thing]]
|
|
if things_ok:
|
|
self.print_wrapped("%s %s %s %s" % (self.msg_done,
|
|
self.msg_topic,
|
|
', '.join(things_ok),
|
|
single_thing),
|
|
getattr(self, 'good_%s' % thing))
|
|
|
|
|
|
class atest_add(atest_add_or_remove):
|
|
usage_action = op_action = 'add'
|
|
msg_done = 'Added to'
|
|
usage_words = ('Add', 'to')
|
|
|
|
|
|
class atest_remove(atest_add_or_remove):
|
|
usage_action = op_action = 'remove'
|
|
msg_done = 'Removed from'
|
|
usage_words = ('Remove', 'from')
|