From 05c5a2b4a9b0eb9c0bf88cd81390c7bc906bd289 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Wed, 11 Apr 2012 02:20:57 -0700 Subject: [PATCH 001/135] Remove server-specific functionality. * Moved server-specific functionality found in quantum.common to the quantum repo. * Renamed primary package from quantum -> quantumclient. * Addresses bug 977711 and bug 921933 Change-Id: If34553924c8dfcc6b148c1d91f173a4b81eeb95a --- .gitignore | 1 - MANIFEST.in | 4 +- quantum/__init__.py | 19 - quantum/client/batch_config.py | 121 ------ quantum/common/config.py | 345 ------------------ quantum/common/flags.py | 249 ------------- quantum/common/test_lib.py | 291 --------------- quantum/common/utils.py | 279 -------------- {quantum/client => quantumclient}/__init__.py | 13 +- {quantum/client => quantumclient}/cli.py | 22 +- {quantum/client => quantumclient}/cli_lib.py | 4 +- {quantum => quantumclient}/common/__init__.py | 0 .../common/exceptions.py | 92 +---- .../common/serializer.py | 5 +- quantumclient/common/utils.py | 70 ++++ .../tests/__init__.py | 0 .../tests/unit/__init__.py | 0 quantumclient/tests/unit/stubs.py | 65 ++++ .../tests/unit/test_cli.py | 9 +- .../tests/unit/test_clientlib.py | 8 +- setup.py | 7 +- tools/pip-requires | 16 - tools/test-requires | 7 + tox.ini | 18 +- version.py | 2 +- 25 files changed, 190 insertions(+), 1457 deletions(-) delete mode 100644 quantum/__init__.py delete mode 100755 quantum/client/batch_config.py delete mode 100644 quantum/common/config.py delete mode 100644 quantum/common/flags.py delete mode 100644 quantum/common/test_lib.py delete mode 100644 quantum/common/utils.py rename {quantum/client => quantumclient}/__init__.py (98%) rename {quantum/client => quantumclient}/cli.py (95%) rename {quantum/client => quantumclient}/cli_lib.py (99%) rename {quantum => quantumclient}/common/__init__.py (100%) rename {quantum => quantumclient}/common/exceptions.py (53%) rename {quantum => quantumclient}/common/serializer.py (97%) create mode 100644 quantumclient/common/utils.py rename {quantum/client => quantumclient}/tests/__init__.py (100%) rename {quantum/client => quantumclient}/tests/unit/__init__.py (100%) create mode 100644 quantumclient/tests/unit/stubs.py rename {quantum/client => quantumclient}/tests/unit/test_cli.py (99%) rename {quantum/client => quantumclient}/tests/unit/test_clientlib.py (99%) delete mode 100644 tools/pip-requires create mode 100644 tools/test-requires diff --git a/.gitignore b/.gitignore index 1fe7c256e..2fc28f418 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,5 @@ python_quantumclient.egg-info/* quantum/vcsversion.py run_tests.err.log run_tests.log -tests/ .venv/ .tox/ diff --git a/MANIFEST.in b/MANIFEST.in index 8aa75aee5..b38b63714 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,5 +2,5 @@ include tox.ini include LICENSE README include version.py include tools/* -include quantum/client/tests/* -include quantum/client/tests/unit/* +include quantumclient/tests/* +include quantumclient/tests/unit/* diff --git a/quantum/__init__.py b/quantum/__init__.py deleted file mode 100644 index d5dcad659..000000000 --- a/quantum/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from pkgutil import extend_path -__path__ = extend_path(__path__, __name__) diff --git a/quantum/client/batch_config.py b/quantum/client/batch_config.py deleted file mode 100755 index edcb75825..000000000 --- a/quantum/client/batch_config.py +++ /dev/null @@ -1,121 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Nicira Networks, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# @author: Dan Wendlandt, Nicira Networks, Inc. - -import logging as LOG -from optparse import OptionParser -import os -import sys - -possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), - os.pardir, - os.pardir, - os.pardir)) - -if os.path.exists(os.path.join(possible_topdir, 'quantum', '__init__.py')): - sys.path.insert(0, possible_topdir) - -from quantum.client import Client - -FORMAT = "json" -CONTENT_TYPE = "application/" + FORMAT - - -def delete_all_nets(client): - res = client.list_networks() - for n in res["networks"]: - nid = n["id"] - pres = client.list_ports(nid) - for port in pres["ports"]: - pid = port['id'] - client.detach_resource(nid, pid) - client.delete_port(nid, pid) - print "Deleted Virtual Port:%s " \ - "on Virtual Network:%s" % (pid, nid) - client.delete_network(nid) - print "Deleted Virtual Network with ID:%s" % nid - - -def create_net_with_attachments(client, net_name, iface_ids): - data = {'network': {'name': '%s' % net_name}} - res = client.create_network(data) - nid = res["network"]["id"] - print "Created a new Virtual Network %s with ID:%s" % (net_name, nid) - - for iface_id in iface_ids: - res = client.create_port(nid, {'port': {'state': 'ACTIVE'}}) - new_port_id = res["port"]["id"] - print "Created Virtual Port:%s " \ - "on Virtual Network:%s" % (new_port_id, nid) - data = {'attachment': {'id': iface_id}} - client.attach_resource(nid, new_port_id, data) - print "Plugged interface \"%s\" to port:%s on network:%s" % \ - (iface_id, new_port_id, nid) - -if __name__ == "__main__": - usagestr = "Usage: %prog [OPTIONS] [args]\n" \ - "Example config-string: net1=instance-1,instance-2"\ - ":net2=instance-3,instance-4\n" \ - "This string would create two networks: \n" \ - "'net1' would have two ports, with iface-ids "\ - "instance-1 and instance-2 attached\n" \ - "'net2' would have two ports, with iface-ids"\ - " instance-3 and instance-4 attached\n" - parser = OptionParser(usage=usagestr) - parser.add_option("-H", "--host", dest="host", - type="string", default="127.0.0.1", help="ip address of api host") - parser.add_option("-p", "--port", dest="port", - type="int", default=9696, help="api poort") - parser.add_option("-s", "--ssl", dest="ssl", - action="store_true", default=False, help="use ssl") - parser.add_option("-v", "--verbose", dest="verbose", - action="store_true", default=False, help="turn on verbose logging") - parser.add_option("-d", "--delete", dest="delete", - action="store_true", default=False, \ - help="delete existing tenants networks") - - options, args = parser.parse_args() - - if options.verbose: - LOG.basicConfig(level=LOG.DEBUG) - else: - LOG.basicConfig(level=LOG.WARN) - - if len(args) < 1: - parser.print_help() - sys.exit(1) - - nets = {} - tenant_id = args[0] - if len(args) > 1: - config_str = args[1] - for net_str in config_str.split(":"): - arr = net_str.split("=") - net_name = arr[0] - nets[net_name] = arr[1].split(",") - - print "nets: %s" % str(nets) - - client = Client(options.host, options.port, options.ssl, - format='json', tenant=tenant_id) - - if options.delete: - delete_all_nets(client) - - for net_name, iface_ids in nets.items(): - create_net_with_attachments(client, net_name, iface_ids) - - sys.exit(0) diff --git a/quantum/common/config.py b/quantum/common/config.py deleted file mode 100644 index 67c831d35..000000000 --- a/quantum/common/config.py +++ /dev/null @@ -1,345 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Nicira Networks, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Routines for configuring Quantum -""" - -import ConfigParser -import logging -import logging.config -import logging.handlers -import optparse -import os -import re -import sys -import socket - -from paste import deploy - -from quantum.common import flags -from quantum.common import exceptions as exception - -DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s" -DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S" - -FLAGS = flags.FLAGS -LOG = logging.getLogger('quantum.wsgi') - - -def parse_options(parser, cli_args=None): - """ - Returns the parsed CLI options, command to run and its arguments, merged - with any same-named options found in a configuration file. - - The function returns a tuple of (options, args), where options is a - mapping of option key/str(value) pairs, and args is the set of arguments - (not options) supplied on the command-line. - - The reason that the option values are returned as strings only is that - ConfigParser and paste.deploy only accept string values... - - :param parser: The option parser - :param cli_args: (Optional) Set of arguments to process. If not present, - sys.argv[1:] is used. - :retval tuple of (options, args) - """ - - (options, args) = parser.parse_args(cli_args) - - return (vars(options), args) - - -def add_common_options(parser): - """ - Given a supplied optparse.OptionParser, adds an OptionGroup that - represents all common configuration options. - - :param parser: optparse.OptionParser - """ - help_text = "The following configuration options are common to "\ - "all quantum programs." - - group = optparse.OptionGroup(parser, "Common Options", help_text) - group.add_option('-v', '--verbose', default=False, dest="verbose", - action="store_true", - help="Print more verbose output") - group.add_option('-d', '--debug', default=False, dest="debug", - action="store_true", - help="Print debugging output") - group.add_option('--config-file', default=None, metavar="PATH", - help="Path to the config file to use. When not specified " - "(the default), we generally look at the first " - "argument specified to be a config file, and if " - "that is also missing, we search standard " - "directories for a config file.") - parser.add_option_group(group) - - -def add_log_options(parser): - """ - Given a supplied optparse.OptionParser, adds an OptionGroup that - represents all the configuration options around logging. - - :param parser: optparse.OptionParser - """ - help_text = "The following configuration options are specific to logging "\ - "functionality for this program." - - group = optparse.OptionGroup(parser, "Logging Options", help_text) - group.add_option('--log-config', default=None, metavar="PATH", - help="If this option is specified, the logging " - "configuration file specified is used and overrides " - "any other logging options specified. Please see " - "the Python logging module documentation for " - "details on logging configuration files.") - group.add_option('--log-date-format', metavar="FORMAT", - default=DEFAULT_LOG_DATE_FORMAT, - help="Format string for %(asctime)s in log records. " - "Default: %default") - group.add_option('--use-syslog', default=False, - action="store_true", - help="Output logs to syslog.") - group.add_option('--log-file', default=None, metavar="PATH", - help="(Optional) Name of log file to output to. " - "If not set, logging will go to stdout.") - group.add_option("--log-dir", default=None, - help="(Optional) The directory to keep log files in " - "(will be prepended to --logfile)") - parser.add_option_group(group) - - -def setup_logging(options, conf): - """ - Sets up the logging options for a log with supplied name - - :param options: Mapping of typed option key/values - :param conf: Mapping of untyped key/values from config file - """ - - if options.get('log_config', None): - # Use a logging configuration file for all settings... - if os.path.exists(options['log_config']): - logging.config.fileConfig(options['log_config']) - return - else: - raise RuntimeError("Unable to locate specified logging " - "config file: %s" % options['log_config']) - - # If either the CLI option or the conf value - # is True, we set to True - debug = options.get('debug') or \ - get_option(conf, 'debug', type='bool', default=False) - verbose = options.get('verbose') or \ - get_option(conf, 'verbose', type='bool', default=False) - root_logger = logging.root - if debug: - root_logger.setLevel(logging.DEBUG) - elif verbose: - root_logger.setLevel(logging.INFO) - else: - root_logger.setLevel(logging.WARNING) - - # Set log configuration from options... - # Note that we use a hard-coded log format in the options - # because of Paste.Deploy bug #379 - # http://trac.pythonpaste.org/pythonpaste/ticket/379 - log_format = options.get('log_format', DEFAULT_LOG_FORMAT) - log_date_format = options.get('log_date_format', DEFAULT_LOG_DATE_FORMAT) - formatter = logging.Formatter(log_format, log_date_format) - - syslog = options.get('use_syslog') - if not syslog: - syslog = conf.get('use_syslog') - - if syslog: - SysLogHandler = logging.handlers.SysLogHandler - try: - handler = SysLogHandler(address='/dev/log', - facility=SysLogHandler.LOG_SYSLOG) - except socket.error: - handler = SysLogHandler(address='/var/run/syslog', - facility=SysLogHandler.LOG_SYSLOG) - handler.setFormatter(formatter) - root_logger.addHandler(handler) - - logfile = options.get('log_file') - if not logfile: - logfile = conf.get('log_file') - - if logfile: - logdir = options.get('log_dir') - if not logdir: - logdir = conf.get('log_dir') - if logdir: - logfile = os.path.join(logdir, logfile) - logfile = logging.FileHandler(logfile) - logfile.setFormatter(formatter) - logfile.setFormatter(formatter) - root_logger.addHandler(logfile) - else: - handler = logging.StreamHandler(sys.stdout) - handler.setFormatter(formatter) - root_logger.addHandler(handler) - - -def find_config_file(options, args, config_file='quantum.conf'): - """ - Return the first config file found. - - We search for the paste config file in the following order: - * If --config-file option is used, use that - * If args[0] is a file, use that - * Search for the configuration file in standard directories: - * . - * ~.quantum/ - * ~ - * $FLAGS.state_path/etc/quantum - * $FLAGS.state_path/etc - - :retval Full path to config file, or None if no config file found - """ - - fix_path = lambda p: os.path.abspath(os.path.expanduser(p)) - if options.get('config_file'): - if os.path.exists(options['config_file']): - return fix_path(options['config_file']) - elif args: - if os.path.exists(args[0]): - return fix_path(args[0]) - - dir_to_common = os.path.dirname(os.path.abspath(__file__)) - root = os.path.join(dir_to_common, '..', '..', '..', '..') - # Handle standard directory search for the config file - config_file_dirs = [fix_path(os.path.join(os.getcwd(), 'etc')), - fix_path(os.path.join('~', '.quantum-venv', 'etc', - 'quantum')), - fix_path('~'), - os.path.join(FLAGS.state_path, 'etc'), - os.path.join(FLAGS.state_path, 'etc', 'quantum'), - fix_path(os.path.join('~', '.local', - 'etc', 'quantum')), - '/usr/etc/quantum', - '/usr/local/etc/quantum', - '/etc/quantum/', - '/etc'] - - if 'plugin' in options: - config_file_dirs = [os.path.join(x, 'quantum', 'plugins', - options['plugin']) - for x in config_file_dirs] - - if os.path.exists(os.path.join(root, 'plugins')): - plugins = [fix_path(os.path.join(root, 'plugins', p, 'etc')) - for p in os.listdir(os.path.join(root, 'plugins'))] - plugins = [p for p in plugins if os.path.isdir(p)] - config_file_dirs.extend(plugins) - - for cfg_dir in config_file_dirs: - cfg_file = os.path.join(cfg_dir, config_file) - if os.path.exists(cfg_file): - return cfg_file - - -def load_paste_config(app_name, options, args): - """ - Looks for a config file to use for an app and returns the - config file path and a configuration mapping from a paste config file. - - We search for the paste config file in the following order: - * If --config-file option is used, use that - * If args[0] is a file, use that - * Search for quantum.conf in standard directories: - * . - * ~.quantum/ - * ~ - * /etc/quantum - * /etc - - :param app_name: Name of the application to load config for, or None. - None signifies to only load the [DEFAULT] section of - the config file. - :param options: Set of typed options returned from parse_options() - :param args: Command line arguments from argv[1:] - :retval Tuple of (conf_file, conf) - - :raises RuntimeError when config file cannot be located or there was a - problem loading the configuration file. - """ - conf_file = find_config_file(options, args) - if not conf_file: - raise RuntimeError("Unable to locate any configuration file. " - "Cannot load application %s" % app_name) - try: - conf = deploy.appconfig("config:%s" % conf_file, name=app_name) - return conf_file, conf - except Exception, e: - raise RuntimeError("Error trying to load config %s: %s" - % (conf_file, e)) - - -def load_paste_app(app_name, options, args): - """ - Builds and returns a WSGI app from a paste config file. - - We search for the paste config file in the following order: - * If --config-file option is used, use that - * If args[0] is a file, use that - * Search for quantum.conf in standard directories: - * . - * ~.quantum/ - * ~ - * /etc/quantum - * /etc - - :param app_name: Name of the application to load - :param options: Set of typed options returned from parse_options() - :param args: Command line arguments from argv[1:] - - :raises RuntimeError when config file cannot be located or application - cannot be loaded from config file - """ - conf_file, conf = load_paste_config(app_name, options, args) - - try: - app = deploy.loadapp("config:%s" % conf_file, name=app_name) - except (LookupError, ImportError), e: - raise RuntimeError("Unable to load %(app_name)s from " - "configuration file %(conf_file)s." - "\nGot: %(e)r" % locals()) - return conf, app - - -def get_option(options, option, **kwargs): - if option in options: - value = options[option] - type_ = kwargs.get('type', 'str') - if type_ == 'bool': - if hasattr(value, 'lower'): - return value.lower() == 'true' - else: - return value - elif type_ == 'int': - return int(value) - elif type_ == 'float': - return float(value) - else: - return value - elif 'default' in kwargs: - return kwargs['default'] - else: - raise KeyError("option '%s' not found" % option) diff --git a/quantum/common/flags.py b/quantum/common/flags.py deleted file mode 100644 index 16badd332..000000000 --- a/quantum/common/flags.py +++ /dev/null @@ -1,249 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Citrix Systems, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Command-line flag library. - -Wraps gflags. -Global flags should be defined here, the rest are defined where they're used. - -""" -import getopt -import gflags -import os -import string -import sys - - -class FlagValues(gflags.FlagValues): - """Extension of gflags.FlagValues that allows undefined and runtime flags. - - Unknown flags will be ignored when parsing the command line, but the - command line will be kept so that it can be replayed if new flags are - defined after the initial parsing. - - """ - - def __init__(self, extra_context=None): - gflags.FlagValues.__init__(self) - self.__dict__['__dirty'] = [] - self.__dict__['__was_already_parsed'] = False - self.__dict__['__stored_argv'] = [] - self.__dict__['__extra_context'] = extra_context - - def __call__(self, argv): - # We're doing some hacky stuff here so that we don't have to copy - # out all the code of the original verbatim and then tweak a few lines. - # We're hijacking the output of getopt so we can still return the - # leftover args at the end - sneaky_unparsed_args = {"value": None} - original_argv = list(argv) - - if self.IsGnuGetOpt(): - orig_getopt = getattr(getopt, 'gnu_getopt') - orig_name = 'gnu_getopt' - else: - orig_getopt = getattr(getopt, 'getopt') - orig_name = 'getopt' - - def _sneaky(*args, **kw): - optlist, unparsed_args = orig_getopt(*args, **kw) - sneaky_unparsed_args['value'] = unparsed_args - return optlist, unparsed_args - - try: - setattr(getopt, orig_name, _sneaky) - args = gflags.FlagValues.__call__(self, argv) - except gflags.UnrecognizedFlagError: - # Undefined args were found, for now we don't care so just - # act like everything went well - # (these three lines are copied pretty much verbatim from the end - # of the __call__ function we are wrapping) - unparsed_args = sneaky_unparsed_args['value'] - if unparsed_args: - if self.IsGnuGetOpt(): - args = argv[:1] + unparsed_args - else: - args = argv[:1] + original_argv[-len(unparsed_args):] - else: - args = argv[:1] - finally: - setattr(getopt, orig_name, orig_getopt) - - # Store the arguments for later, we'll need them for new flags - # added at runtime - self.__dict__['__stored_argv'] = original_argv - self.__dict__['__was_already_parsed'] = True - self.ClearDirty() - return args - - def Reset(self): - gflags.FlagValues.Reset(self) - self.__dict__['__dirty'] = [] - self.__dict__['__was_already_parsed'] = False - self.__dict__['__stored_argv'] = [] - - def SetDirty(self, name): - """Mark a flag as dirty so that accessing it will case a reparse.""" - self.__dict__['__dirty'].append(name) - - def IsDirty(self, name): - return name in self.__dict__['__dirty'] - - def ClearDirty(self): - self.__dict__['__is_dirty'] = [] - - def WasAlreadyParsed(self): - return self.__dict__['__was_already_parsed'] - - def ParseNewFlags(self): - if '__stored_argv' not in self.__dict__: - return - new_flags = FlagValues(self) - for k in self.__dict__['__dirty']: - new_flags[k] = gflags.FlagValues.__getitem__(self, k) - - new_flags(self.__dict__['__stored_argv']) - for k in self.__dict__['__dirty']: - setattr(self, k, getattr(new_flags, k)) - self.ClearDirty() - - def __setitem__(self, name, flag): - gflags.FlagValues.__setitem__(self, name, flag) - if self.WasAlreadyParsed(): - self.SetDirty(name) - - def __getitem__(self, name): - if self.IsDirty(name): - self.ParseNewFlags() - return gflags.FlagValues.__getitem__(self, name) - - def __getattr__(self, name): - if self.IsDirty(name): - self.ParseNewFlags() - val = gflags.FlagValues.__getattr__(self, name) - if type(val) is str: - tmpl = string.Template(val) - context = [self, self.__dict__['__extra_context']] - return tmpl.substitute(StrWrapper(context)) - return val - - -class StrWrapper(object): - """Wrapper around FlagValues objects. - - Wraps FlagValues objects for string.Template so that we're - sure to return strings. - - """ - def __init__(self, context_objs): - self.context_objs = context_objs - - def __getitem__(self, name): - for context in self.context_objs: - val = getattr(context, name, False) - if val: - return str(val) - raise KeyError(name) - - -# Copied from gflags with small mods to get the naming correct. -# Originally gflags checks for the first module that is not gflags that is -# in the call chain, we want to check for the first module that is not gflags -# and not this module. -def _GetCallingModule(): - """Returns the name of the module that's calling into this module. - - We generally use this function to get the name of the module calling a - DEFINE_foo... function. - - """ - # Walk down the stack to find the first globals dict that's not ours. - for depth in range(1, sys.getrecursionlimit()): - if not sys._getframe(depth).f_globals is globals(): - module_name = __GetModuleName(sys._getframe(depth).f_globals) - if module_name == 'gflags': - continue - if module_name is not None: - return module_name - raise AssertionError("No module was found") - - -# Copied from gflags because it is a private function -def __GetModuleName(globals_dict): - """Given a globals dict, returns the name of the module that defines it. - - Args: - globals_dict: A dictionary that should correspond to an environment - providing the values of the globals. - - Returns: - A string (the name of the module) or None (if the module could not - be identified. - - """ - for name, module in sys.modules.iteritems(): - if getattr(module, '__dict__', None) is globals_dict: - if name == '__main__': - return sys.argv[0] - return name - return None - - -def _wrapper(func): - def _wrapped(*args, **kw): - kw.setdefault('flag_values', FLAGS) - func(*args, **kw) - _wrapped.func_name = func.func_name - return _wrapped - - -FLAGS = FlagValues() -gflags.FLAGS = FLAGS -gflags._GetCallingModule = _GetCallingModule - - -DEFINE = _wrapper(gflags.DEFINE) -DEFINE_string = _wrapper(gflags.DEFINE_string) -DEFINE_integer = _wrapper(gflags.DEFINE_integer) -DEFINE_bool = _wrapper(gflags.DEFINE_bool) -DEFINE_boolean = _wrapper(gflags.DEFINE_boolean) -DEFINE_float = _wrapper(gflags.DEFINE_float) -DEFINE_enum = _wrapper(gflags.DEFINE_enum) -DEFINE_list = _wrapper(gflags.DEFINE_list) -DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist) -DEFINE_multistring = _wrapper(gflags.DEFINE_multistring) -DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int) -DEFINE_flag = _wrapper(gflags.DEFINE_flag) -HelpFlag = gflags.HelpFlag -HelpshortFlag = gflags.HelpshortFlag -HelpXMLFlag = gflags.HelpXMLFlag - - -def DECLARE(name, module_string, flag_values=FLAGS): - if module_string not in sys.modules: - __import__(module_string, globals(), locals()) - if name not in flag_values: - raise gflags.UnrecognizedFlag( - "%s not defined by %s" % (name, module_string)) - - -# __GLOBAL FLAGS ONLY__ -# Define any app-specific flags in their own files, docs at: -# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#a9 - -DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../../'), - "Top-level directory for maintaining quantum's state") diff --git a/quantum/common/test_lib.py b/quantum/common/test_lib.py deleted file mode 100644 index 03578817d..000000000 --- a/quantum/common/test_lib.py +++ /dev/null @@ -1,291 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010 OpenStack, LLC -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Colorizer Code is borrowed from Twisted: -# Copyright (c) 2001-2010 Twisted Matrix Laboratories. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -import gettext -import os -import unittest -import sys -import logging - -from nose import result -from nose import core -from nose import config - - -class _AnsiColorizer(object): - """ - A colorizer is an object that loosely wraps around a stream, allowing - callers to write text to the stream in a particular color. - - Colorizer classes must implement C{supported()} and C{write(text, color)}. - """ - _colors = dict(black=30, red=31, green=32, yellow=33, - blue=34, magenta=35, cyan=36, white=37) - - def __init__(self, stream): - self.stream = stream - - def supported(cls, stream=sys.stdout): - """ - A class method that returns True if the current platform supports - coloring terminal output using this method. Returns False otherwise. - """ - if not stream.isatty(): - return False # auto color only on TTYs - try: - import curses - except ImportError: - return False - else: - try: - try: - return curses.tigetnum("colors") > 2 - except curses.error: - curses.setupterm() - return curses.tigetnum("colors") > 2 - except: - raise - # guess false in case of error - return False - supported = classmethod(supported) - - def write(self, text, color): - """ - Write the given text to the stream in the given color. - - @param text: Text to be written to the stream. - - @param color: A string label for a color. e.g. 'red', 'white'. - """ - color = self._colors[color] - self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text)) - - -class _Win32Colorizer(object): - """ - See _AnsiColorizer docstring. - """ - def __init__(self, stream): - from win32console import GetStdHandle, STD_OUT_HANDLE, \ - FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \ - FOREGROUND_INTENSITY - red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN, - FOREGROUND_BLUE, FOREGROUND_INTENSITY) - self.stream = stream - self.screenBuffer = GetStdHandle(STD_OUT_HANDLE) - self._colors = { - 'normal': red | green | blue, - 'red': red | bold, - 'green': green | bold, - 'blue': blue | bold, - 'yellow': red | green | bold, - 'magenta': red | blue | bold, - 'cyan': green | blue | bold, - 'white': red | green | blue | bold} - - def supported(cls, stream=sys.stdout): - try: - import win32console - screenBuffer = win32console.GetStdHandle( - win32console.STD_OUT_HANDLE) - except ImportError: - return False - import pywintypes - try: - screenBuffer.SetConsoleTextAttribute( - win32console.FOREGROUND_RED | - win32console.FOREGROUND_GREEN | - win32console.FOREGROUND_BLUE) - except pywintypes.error: - return False - else: - return True - supported = classmethod(supported) - - def write(self, text, color): - color = self._colors[color] - self.screenBuffer.SetConsoleTextAttribute(color) - self.stream.write(text) - self.screenBuffer.SetConsoleTextAttribute(self._colors['normal']) - - -class _NullColorizer(object): - """ - See _AnsiColorizer docstring. - """ - def __init__(self, stream): - self.stream = stream - - def supported(cls, stream=sys.stdout): - return True - supported = classmethod(supported) - - def write(self, text, color): - self.stream.write(text) - - -class QuantumTestResult(result.TextTestResult): - def __init__(self, *args, **kw): - result.TextTestResult.__init__(self, *args, **kw) - self._last_case = None - self.colorizer = None - # NOTE(vish, tfukushima): reset stdout for the terminal check - stdout = sys.__stdout__ - sys.stdout = sys.__stdout__ - for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]: - if colorizer.supported(): - self.colorizer = colorizer(self.stream) - break - sys.stdout = stdout - - def getDescription(self, test): - return str(test) - - # NOTE(vish, tfukushima): copied from unittest with edit to add color - def addSuccess(self, test): - unittest.TestResult.addSuccess(self, test) - if self.showAll: - self.colorizer.write("OK", 'green') - self.stream.writeln() - elif self.dots: - self.stream.write('.') - self.stream.flush() - - # NOTE(vish, tfukushima): copied from unittest with edit to add color - def addFailure(self, test, err): - unittest.TestResult.addFailure(self, test, err) - if self.showAll: - self.colorizer.write("FAIL", 'red') - self.stream.writeln() - elif self.dots: - self.stream.write('F') - self.stream.flush() - - # NOTE(vish, tfukushima): copied from unittest with edit to add color - def addError(self, test, err): - """Overrides normal addError to add support for errorClasses. - If the exception is a registered class, the error will be added - to the list for that class, not errors. - """ - stream = getattr(self, 'stream', None) - ec, ev, tb = err - try: - exc_info = self._exc_info_to_string(err, test) - except TypeError: - # This is for compatibility with Python 2.3. - exc_info = self._exc_info_to_string(err) - for cls, (storage, label, isfail) in self.errorClasses.items(): - if result.isclass(ec) and issubclass(ec, cls): - if isfail: - test.passwd = False - storage.append((test, exc_info)) - # Might get patched into a streamless result - if stream is not None: - if self.showAll: - message = [label] - detail = result._exception_details(err[1]) - if detail: - message.append(detail) - stream.writeln(": ".join(message)) - elif self.dots: - stream.write(label[:1]) - return - self.errors.append((test, exc_info)) - test.passed = False - if stream is not None: - if self.showAll: - self.colorizer.write("ERROR", 'red') - self.stream.writeln() - elif self.dots: - stream.write('E') - - def startTest(self, test): - unittest.TestResult.startTest(self, test) - current_case = test.test.__class__.__name__ - - if self.showAll: - if current_case != self._last_case: - self.stream.writeln(current_case) - self._last_case = current_case - #NOTE(salvatore-orlando): - #slightly changed in order to print test case class - #together with unit test name - self.stream.write( - ' %s' % str(test.test).ljust(60)) - self.stream.flush() - - -class QuantumTestRunner(core.TextTestRunner): - def _makeResult(self): - return QuantumTestResult(self.stream, - self.descriptions, - self.verbosity, - self.config) - - -def run_tests(c=None): - logger = logging.getLogger() - hdlr = logging.StreamHandler() - formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') - hdlr.setFormatter(formatter) - logger.addHandler(hdlr) - logger.setLevel(logging.DEBUG) - - # NOTE(bgh): I'm not entirely sure why but nose gets confused here when - # calling run_tests from a plugin directory run_tests.py (instead of the - # main run_tests.py). It will call run_tests with no arguments and the - # testing of run_tests will fail (though the plugin tests will pass). For - # now we just return True to let the run_tests test pass. - if not c: - return True - - runner = QuantumTestRunner(stream=c.stream, - verbosity=c.verbosity, - config=c) - return not core.run(config=c, testRunner=runner) - -# describes parameters used by different unit/functional tests -# a plugin-specific testing mechanism should import this dictionary -# and override the values in it if needed (e.g., run_tests.py in -# quantum/plugins/openvswitch/ ) -test_config = { - "plugin_name": "quantum.plugins.sample.SamplePlugin.FakePlugin", - "default_net_op_status": "UP", - "default_port_op_status": "UP", -} diff --git a/quantum/common/utils.py b/quantum/common/utils.py deleted file mode 100644 index acd72b737..000000000 --- a/quantum/common/utils.py +++ /dev/null @@ -1,279 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011, Nicira Networks, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# Borrowed from nova code base, more utilities will be added/borrowed as and -# when needed. -# @author: Somik Behera, Nicira Networks, Inc. - -"""Utilities and helper functions.""" - -import ConfigParser -import datetime -import inspect -import logging -import os -import random -import subprocess -import socket -import sys -import base64 -import functools -import json -import re -import string -import struct -import time -import types - -from quantum.common import flags -from quantum.common import exceptions as exception -from quantum.common.exceptions import ProcessExecutionError - - -def env(*vars, **kwargs): - """ - returns the first environment variable set - if none are non-empty, defaults to '' or keyword arg default - """ - for v in vars: - value = os.environ.get(v) - if value: - return value - return kwargs.get('default', '') - - -def import_class(import_str): - """Returns a class from a string including module and class.""" - mod_str, _sep, class_str = import_str.rpartition('.') - try: - __import__(mod_str) - return getattr(sys.modules[mod_str], class_str) - except (ImportError, ValueError, AttributeError), exc: - print(('Inner Exception: %s'), exc) - raise exception.ClassNotFound(class_name=class_str) - - -def import_object(import_str): - """Returns an object including a module or module and class.""" - try: - __import__(import_str) - return sys.modules[import_str] - except ImportError: - cls = import_class(import_str) - return cls() - - -def to_primitive(value): - if type(value) is type([]) or type(value) is type((None,)): - o = [] - for v in value: - o.append(to_primitive(v)) - return o - elif type(value) is type({}): - o = {} - for k, v in value.iteritems(): - o[k] = to_primitive(v) - return o - elif isinstance(value, datetime.datetime): - return str(value) - elif hasattr(value, 'iteritems'): - return to_primitive(dict(value.iteritems())) - elif hasattr(value, '__iter__'): - return to_primitive(list(value)) - else: - return value - - -def dumps(value): - try: - return json.dumps(value) - except TypeError: - pass - return json.dumps(to_primitive(value)) - - -def loads(s): - return json.loads(s) - -TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" -FLAGS = flags.FLAGS - - -def int_from_bool_as_string(subject): - """ - Interpret a string as a boolean and return either 1 or 0. - - Any string value in: - ('True', 'true', 'On', 'on', '1') - is interpreted as a boolean True. - - Useful for JSON-decoded stuff and config file parsing - """ - return bool_from_string(subject) and 1 or 0 - - -def bool_from_string(subject): - """ - Interpret a string as a boolean. - - Any string value in: - ('True', 'true', 'On', 'on', '1') - is interpreted as a boolean True. - - Useful for JSON-decoded stuff and config file parsing - """ - if type(subject) == type(bool): - return subject - if hasattr(subject, 'startswith'): # str or unicode... - if subject.strip().lower() in ('true', 'on', '1'): - return True - return False - - -def fetchfile(url, target): - logging.debug("Fetching %s" % url) -# c = pycurl.Curl() -# fp = open(target, "wb") -# c.setopt(c.URL, url) -# c.setopt(c.WRITEDATA, fp) -# c.perform() -# c.close() -# fp.close() - execute("curl --fail %s -o %s" % (url, target)) - - -def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): - logging.debug("Running cmd: %s", cmd) - env = os.environ.copy() - if addl_env: - env.update(addl_env) - obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) - result = None - if process_input != None: - result = obj.communicate(process_input) - else: - result = obj.communicate() - obj.stdin.close() - if obj.returncode: - logging.debug("Result was %s" % (obj.returncode)) - if check_exit_code and obj.returncode != 0: - (stdout, stderr) = result - raise ProcessExecutionError(exit_code=obj.returncode, - stdout=stdout, - stderr=stderr, - cmd=cmd) - return result - - -def abspath(s): - return os.path.join(os.path.dirname(__file__), s) - - -# TODO(sirp): when/if utils is extracted to common library, we should remove -# the argument's default. -#def default_flagfile(filename='nova.conf'): -def default_flagfile(filename='quantum.conf'): - for arg in sys.argv: - if arg.find('flagfile') != -1: - break - else: - if not os.path.isabs(filename): - # turn relative filename into an absolute path - script_dir = os.path.dirname(inspect.stack()[-1][1]) - filename = os.path.abspath(os.path.join(script_dir, filename)) - if os.path.exists(filename): - sys.argv = \ - sys.argv[:1] + ['--flagfile=%s' % filename] + sys.argv[1:] - - -def debug(arg): - logging.debug('debug in callback: %s', arg) - return arg - - -def runthis(prompt, cmd, check_exit_code=True): - logging.debug("Running %s" % (cmd)) - exit_code = subprocess.call(cmd.split(" ")) - logging.debug(prompt % (exit_code)) - if check_exit_code and exit_code != 0: - raise ProcessExecutionError(exit_code=exit_code, - stdout=None, - stderr=None, - cmd=cmd) - - -def generate_uid(topic, size=8): - return '%s-%s' % (topic, ''.join( - [random.choice('01234567890abcdefghijklmnopqrstuvwxyz') - for x in xrange(size)])) - - -def generate_mac(): - mac = [0x02, 0x16, 0x3e, random.randint(0x00, 0x7f), - random.randint(0x00, 0xff), random.randint(0x00, 0xff)] - return ':'.join(map(lambda x: "%02x" % x, mac)) - - -def last_octet(address): - return int(address.split(".")[-1]) - - -def isotime(at=None): - if not at: - at = datetime.datetime.utcnow() - return at.strftime(TIME_FORMAT) - - -def parse_isotime(timestr): - return datetime.datetime.strptime(timestr, TIME_FORMAT) - - -def get_plugin_from_config(file="config.ini"): - Config = ConfigParser.ConfigParser() - Config.read(file) - return Config.get("PLUGIN", "provider") - - -class LazyPluggable(object): - """A pluggable backend loaded lazily based on some value.""" - - def __init__(self, pivot, **backends): - self.__backends = backends - self.__pivot = pivot - self.__backend = None - - def __get_backend(self): - if not self.__backend: - backend_name = self.__pivot.value - if backend_name not in self.__backends: - raise exception.Error('Invalid backend: %s' % backend_name) - - backend = self.__backends[backend_name] - if type(backend) == type(tuple()): - name = backend[0] - fromlist = backend[1] - else: - name = backend - fromlist = backend - - self.__backend = __import__(name, None, None, fromlist) - logging.info('backend %s', self.__backend) - return self.__backend - - def __getattr__(self, key): - backend = self.__get_backend() - return getattr(backend, key) diff --git a/quantum/client/__init__.py b/quantumclient/__init__.py similarity index 98% rename from quantum/client/__init__.py rename to quantumclient/__init__.py index d14eac66b..b7e0f504c 100644 --- a/quantum/client/__init__.py +++ b/quantumclient/__init__.py @@ -17,15 +17,22 @@ # @author: Tyler Smith, Cisco Systems import logging +import gettext import httplib import socket import time import urllib -from quantum.common import exceptions -from quantum.common.serializer import Serializer -LOG = logging.getLogger('quantum.client') +# gettext must be initialized before any quantumclient imports +gettext.install('quantumclient', unicode=1) + + +from quantumclient.common import exceptions +from quantumclient.common.serializer import Serializer + + +LOG = logging.getLogger('quantumclient') AUTH_TOKEN_HEADER = "X-Auth-Token" diff --git a/quantum/client/cli.py b/quantumclient/cli.py similarity index 95% rename from quantum/client/cli.py rename to quantumclient/cli.py index 18e0a6a2e..99ce0f102 100755 --- a/quantum/client/cli.py +++ b/quantumclient/cli.py @@ -18,28 +18,18 @@ # @author: Brad Hall, Nicira Networks, Inc. # @author: Salvatore Orlando, Citrix -import gettext import logging import logging.handlers +from optparse import OptionParser import os import sys -from optparse import OptionParser - -from quantum.common import exceptions - -possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), - os.pardir, - os.pardir)) -if os.path.exists(os.path.join(possible_topdir, 'quantum', '__init__.py')): - sys.path.insert(0, possible_topdir) - -gettext.install('quantum', unicode=1) +from quantumclient import cli_lib +from quantumclient import Client +from quantumclient import ClientV11 +from quantumclient.common import exceptions +from quantumclient.common import utils -from quantum.client import cli_lib -from quantum.client import Client -from quantum.client import ClientV11 -from quantum.common import utils #Configure logger for client - cli logger is a child of it #NOTE(salvatore-orlando): logger name does not map to package diff --git a/quantum/client/cli_lib.py b/quantumclient/cli_lib.py similarity index 99% rename from quantum/client/cli_lib.py rename to quantumclient/cli_lib.py index 449e0add3..6ff8a1f79 100755 --- a/quantum/client/cli_lib.py +++ b/quantumclient/cli_lib.py @@ -21,12 +21,10 @@ """ Functions providing implementation for CLI commands. """ import logging -import os -import sys import traceback FORMAT = "json" -LOG = logging.getLogger('quantum.client.cli_lib') +LOG = logging.getLogger('quantumclient.cli_lib') class OutputTemplate(object): diff --git a/quantum/common/__init__.py b/quantumclient/common/__init__.py similarity index 100% rename from quantum/common/__init__.py rename to quantumclient/common/__init__.py diff --git a/quantum/common/exceptions.py b/quantumclient/common/exceptions.py similarity index 53% rename from quantum/common/exceptions.py rename to quantumclient/common/exceptions.py index 69b7a3f6f..33526c2d0 100644 --- a/quantum/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -16,15 +16,9 @@ # under the License. """ -Quantum base exception handling, including decorator for re-raising -Quantum-type exceptions. SHOULD include dedicated exception logging. +Quantum base exception handling. """ -import logging -import gettext - -gettext.install('quantum', unicode=1) - class QuantumException(Exception): """Base Quantum Exception @@ -53,40 +47,6 @@ class NotFound(QuantumException): pass -class ClassNotFound(NotFound): - message = _("Class %(class_name)s could not be found") - - -class NetworkNotFound(NotFound): - message = _("Network %(net_id)s could not be found") - - -class PortNotFound(NotFound): - message = _("Port %(port_id)s could not be found " \ - "on network %(net_id)s") - - -class StateInvalid(QuantumException): - message = _("Unsupported port state: %(port_state)s") - - -class NetworkInUse(QuantumException): - message = _("Unable to complete operation on network %(net_id)s. " \ - "There is one or more attachments plugged into its ports.") - - -class PortInUse(QuantumException): - message = _("Unable to complete operation on port %(port_id)s " \ - "for network %(net_id)s. The attachment '%(att_id)s" \ - "is plugged into the logical port.") - - -class AlreadyAttached(QuantumException): - message = _("Unable to plug the attachment %(att_id)s into port " \ - "%(port_id)s for network %(net_id)s. The attachment is " \ - "already plugged into port %(att_port_id)s") - - class QuantumClientException(QuantumException): def __init__(self, **kwargs): @@ -145,68 +105,18 @@ class BadInputError(Exception): pass -class ProcessExecutionError(IOError): - def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, - description=None): - if description is None: - description = "Unexpected error while running command." - if exit_code is None: - exit_code = '-' - message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % ( - description, cmd, exit_code, stdout, stderr) - IOError.__init__(self, message) - - class Error(Exception): def __init__(self, message=None): super(Error, self).__init__(message) -class ApiError(Error): - def __init__(self, message='Unknown', code='Unknown'): - self.message = message - self.code = code - super(ApiError, self).__init__('%s: %s' % (code, message)) - - class MalformedRequestBody(QuantumException): message = _("Malformed request body: %(reason)s") -class Duplicate(Error): - pass - - -class NotEmpty(Error): - pass - - class Invalid(Error): pass class InvalidContentType(Invalid): message = _("Invalid content type %(content_type)s.") - - -class MissingArgumentError(Error): - pass - - -class NotImplementedError(Error): - pass - - -def wrap_exception(f): - def _wrap(*args, **kw): - try: - return f(*args, **kw) - except Exception, e: - if not isinstance(e, Error): - #exc_type, exc_value, exc_traceback = sys.exc_info() - logging.exception('Uncaught exception') - #logging.error(traceback.extract_stack(exc_traceback)) - raise Error(str(e)) - raise - _wrap.func_name = f.func_name - return _wrap diff --git a/quantum/common/serializer.py b/quantumclient/common/serializer.py similarity index 97% rename from quantum/common/serializer.py rename to quantumclient/common/serializer.py index da50c6444..7fefd2dc1 100644 --- a/quantum/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -1,9 +1,10 @@ from xml.dom import minidom -from quantum.common import exceptions as exception -from quantum.common import utils +from quantumclient.common import exceptions as exception +from quantumclient.common import utils +# NOTE(maru): this class is duplicated from quantum.wsgi class Serializer(object): """Serializes and deserializes dictionaries to certain MIME types.""" diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py new file mode 100644 index 000000000..afb554e26 --- /dev/null +++ b/quantumclient/common/utils.py @@ -0,0 +1,70 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011, Nicira Networks, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# Borrowed from nova code base, more utilities will be added/borrowed as and +# when needed. +# @author: Somik Behera, Nicira Networks, Inc. + +"""Utilities and helper functions.""" + +import datetime +import json +import os + + +def env(*vars, **kwargs): + """ + returns the first environment variable set + if none are non-empty, defaults to '' or keyword arg default + """ + for v in vars: + value = os.environ.get(v) + if value: + return value + return kwargs.get('default', '') + + +def to_primitive(value): + if type(value) is type([]) or type(value) is type((None,)): + o = [] + for v in value: + o.append(to_primitive(v)) + return o + elif type(value) is type({}): + o = {} + for k, v in value.iteritems(): + o[k] = to_primitive(v) + return o + elif isinstance(value, datetime.datetime): + return str(value) + elif hasattr(value, 'iteritems'): + return to_primitive(dict(value.iteritems())) + elif hasattr(value, '__iter__'): + return to_primitive(list(value)) + else: + return value + + +def dumps(value): + try: + return json.dumps(value) + except TypeError: + pass + return json.dumps(to_primitive(value)) + + +def loads(s): + return json.loads(s) diff --git a/quantum/client/tests/__init__.py b/quantumclient/tests/__init__.py similarity index 100% rename from quantum/client/tests/__init__.py rename to quantumclient/tests/__init__.py diff --git a/quantum/client/tests/unit/__init__.py b/quantumclient/tests/unit/__init__.py similarity index 100% rename from quantum/client/tests/unit/__init__.py rename to quantumclient/tests/unit/__init__.py diff --git a/quantumclient/tests/unit/stubs.py b/quantumclient/tests/unit/stubs.py new file mode 100644 index 000000000..3295753fb --- /dev/null +++ b/quantumclient/tests/unit/stubs.py @@ -0,0 +1,65 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +""" Stubs for client tools unit tests """ + + +from quantum import api as server +from quantum.tests.unit import testlib_api + + +class FakeStdout: + + def __init__(self): + self.content = [] + + def write(self, text): + self.content.append(text) + + def make_string(self): + result = '' + for line in self.content: + result = result + line + return result + + +class FakeHTTPConnection: + """ stub HTTP connection class for CLI testing """ + def __init__(self, _1, _2): + # Ignore host and port parameters + self._req = None + plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin' + options = dict(plugin_provider=plugin) + self._api = server.APIRouterV11(options) + + def request(self, method, action, body, headers): + # TODO: remove version prefix from action! + parts = action.split('/', 2) + path = '/' + parts[2] + self._req = testlib_api.create_request(path, body, "application/json", + method) + + def getresponse(self): + res = self._req.get_response(self._api) + + def _fake_read(): + """ Trick for making a webob.Response look like a + httplib.Response + + """ + return res.body + + setattr(res, 'read', _fake_read) + return res diff --git a/quantum/client/tests/unit/test_cli.py b/quantumclient/tests/unit/test_cli.py similarity index 99% rename from quantum/client/tests/unit/test_cli.py rename to quantumclient/tests/unit/test_cli.py index 571012a28..c68eb6a54 100644 --- a/quantum/client/tests/unit/test_cli.py +++ b/quantumclient/tests/unit/test_cli.py @@ -27,12 +27,13 @@ import unittest from quantum import api as server -from quantum.client import cli_lib as cli -from quantum.client import Client from quantum.db import api as db -from quantum.tests.unit.client_tools import stubs as client_stubs +from quantumclient import cli_lib as cli +from quantumclient import Client +from quantumclient.tests.unit import stubs as client_stubs -LOG = logging.getLogger('quantum.tests.test_cli') + +LOG = logging.getLogger('quantumclient.tests.test_cli') API_VERSION = "1.1" FORMAT = 'json' diff --git a/quantum/client/tests/unit/test_clientlib.py b/quantumclient/tests/unit/test_clientlib.py similarity index 99% rename from quantum/client/tests/unit/test_clientlib.py rename to quantumclient/tests/unit/test_clientlib.py index 33f094526..94df09242 100644 --- a/quantum/client/tests/unit/test_clientlib.py +++ b/quantumclient/tests/unit/test_clientlib.py @@ -20,11 +20,11 @@ import unittest import re -from quantum.common import exceptions -from quantum.common.serializer import Serializer -from quantum.client import Client +from quantumclient.common import exceptions +from quantumclient.common.serializer import Serializer +from quantumclient import Client -LOG = logging.getLogger('quantum.tests.test_api') +LOG = logging.getLogger('quantumclient.tests.test_api') # Set a couple tenants to use for testing TENANT_1 = 'totore' diff --git a/setup.py b/setup.py index 6b3dc8061..087e1546b 100644 --- a/setup.py +++ b/setup.py @@ -36,9 +36,6 @@ Description = Summary requires = [ - 'Paste', - 'PasteDeploy', - 'python-gflags', ] EagerResources = [ @@ -63,12 +60,12 @@ scripts=ProjectScripts, install_requires=requires, include_package_data=False, - packages=["quantum", "quantum.client", "quantum.common"], + packages=["quantumclient", "quantumclient.common"], package_data=PackageData, eager_resources=EagerResources, entry_points={ 'console_scripts': [ - 'quantum = quantum.client.cli:main' + 'quantum = quantumclient.cli:main' ] }, ) diff --git a/tools/pip-requires b/tools/pip-requires deleted file mode 100644 index 4b447507a..000000000 --- a/tools/pip-requires +++ /dev/null @@ -1,16 +0,0 @@ -# NOTE(danwent): these paste dependencies are only here because of the weird -# split of "common" quantum code between quantum-server and quantum-client. -# Should be removed from here and setup.py once we fix that. -Paste -PasteDeploy==1.5.0 -python-gflags==1.3 - -distribute>=0.6.24 - -coverage -nose -nosexcover -pep8==0.6.1 - --e git+https://review.openstack.org/p/openstack-dev/openstack-nose.git#egg=openstack.nose_plugin --e git+https://review.openstack.org/p/openstack/quantum#egg=quantum-dev diff --git a/tools/test-requires b/tools/test-requires new file mode 100644 index 000000000..4ddb40b25 --- /dev/null +++ b/tools/test-requires @@ -0,0 +1,7 @@ +coverage +nose +nosexcover +pep8==0.6.1 + +-e git+https://review.openstack.org/p/openstack-dev/openstack-nose.git#egg=openstack.nose_plugin +-e git+https://review.openstack.org/p/openstack/quantum#egg=quantum-dev diff --git a/tox.ini b/tox.ini index 23565b9dd..0ed1adbb2 100644 --- a/tox.ini +++ b/tox.ini @@ -2,14 +2,19 @@ envlist = py26,py27,pep8 [testenv] -deps = -r{toxinidir}/tools/pip-requires +deps = + -r{toxinidir}/tools/test-requires commands = nosetests [testenv:pep8] -commands = pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantum setup.py version.py +commands = + pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantumclient \ + setup.py version.py [testenv:cover] -commands = nosetests --with-coverage --cover-html --cover-erase --cover-package=quantum +commands = + nosetests --with-coverage --cover-html --cover-erase \ + --cover-package=quantumclient [testenv:hudson] downloadcache = ~/cache/pip @@ -24,8 +29,11 @@ deps = file://{toxinidir}/.cache.bundle [testenv:jenkinspep8] deps = file://{toxinidir}/.cache.bundle -commands = pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantum setup.py version.py +commands = + pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantumclient \ + setup.py version.py [testenv:jenkinscover] deps = file://{toxinidir}/.cache.bundle -commands = nosetests --cover-erase --cover-package=quantum --with-xcoverage +commands = + nosetests --cover-erase --cover-package=quantumclient --with-xcoverage diff --git a/version.py b/version.py index fb80c73eb..fae32acd3 100644 --- a/version.py +++ b/version.py @@ -15,7 +15,7 @@ # under the License. try: - from quantum.vcsversion import version_info + from quantumclient.vcsversion import version_info except ImportError: version_info = {'branch_nick': u'LOCALBRANCH', 'revision_id': 'LOCALREVISION', From fa43d08dcb30183b7b0c3a70dbd833893d0e60ce Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Fri, 13 Apr 2012 17:03:05 -0700 Subject: [PATCH 002/135] Clean up codebase in accordance with HACKING/PEP8. * Adds HACKING.rst * Addresses 981208 Change-Id: I3d701ca9a748a0c4ceada7d76a31dc6bb2d5969b --- HACKING.rst | 187 +++++++++++++++++ quantumclient/__init__.py | 40 ++-- quantumclient/cli.py | 211 +++++++++++-------- quantumclient/cli_lib.py | 233 +++++++++++++-------- quantumclient/common/serializer.py | 2 +- quantumclient/tests/unit/test_cli.py | 29 ++- quantumclient/tests/unit/test_clientlib.py | 88 ++++---- 7 files changed, 539 insertions(+), 251 deletions(-) create mode 100644 HACKING.rst diff --git a/HACKING.rst b/HACKING.rst new file mode 100644 index 000000000..d99a07f9a --- /dev/null +++ b/HACKING.rst @@ -0,0 +1,187 @@ +QuantumClient Style Commandments +================================ + +- Step 1: Read http://www.python.org/dev/peps/pep-0008/ +- Step 2: Read http://www.python.org/dev/peps/pep-0008/ again +- Step 3: Read on + + +General +------- +- Put two newlines between top-level code (funcs, classes, etc) +- Put one newline between methods in classes and anywhere else +- Do not write "except:", use "except Exception:" at the very least +- Include your name with TODOs as in "#TODO(termie)" +- Do not shadow a built-in or reserved word. Example:: + + def list(): + return [1, 2, 3] + + mylist = list() # BAD, shadows `list` built-in + + class Foo(object): + def list(self): + return [1, 2, 3] + + mylist = Foo().list() # OKAY, does not shadow built-in + + +Imports +------- +- Do not make relative imports +- Order your imports by the full module path +- Organize your imports according to the following template + +Example:: + + # vim: tabstop=4 shiftwidth=4 softtabstop=4 + {{stdlib imports in human alphabetical order}} + \n + {{third-party lib imports in human alphabetical order}} + \n + {{quantum imports in human alphabetical order}} + \n + \n + {{begin your code}} + + +Human Alphabetical Order Examples +--------------------------------- +Example:: + + import httplib + import logging + import random + import StringIO + import time + import unittest + + import eventlet + import webob.exc + + import quantum.api.networks + from quantum.api import ports + from quantum.db import models + from quantum.extensions import multiport + import quantum.manager + from quantum import service + + +Docstrings +---------- +Example:: + + """A one line docstring looks like this and ends in a period.""" + + + """A multiline docstring has a one-line summary, less than 80 characters. + + Then a new paragraph after a newline that explains in more detail any + general information about the function, class or method. Example usages + are also great to have here if it is a complex class for function. + + When writing the docstring for a class, an extra line should be placed + after the closing quotations. For more in-depth explanations for these + decisions see http://www.python.org/dev/peps/pep-0257/ + + If you are going to describe parameters and return values, use Sphinx, the + appropriate syntax is as follows. + + :param foo: the foo parameter + :param bar: the bar parameter + :returns: return_type -- description of the return value + :returns: description of the return value + :raises: AttributeError, KeyError + """ + + +Dictionaries/Lists +------------------ +If a dictionary (dict) or list object is longer than 80 characters, its items +should be split with newlines. Embedded iterables should have their items +indented. Additionally, the last item in the dictionary should have a trailing +comma. This increases readability and simplifies future diffs. + +Example:: + + my_dictionary = { + "image": { + "name": "Just a Snapshot", + "size": 2749573, + "properties": { + "user_id": 12, + "arch": "x86_64", + }, + "things": [ + "thing_one", + "thing_two", + ], + "status": "ACTIVE", + }, + } + + +Calling Methods +--------------- +Calls to methods 80 characters or longer should format each argument with +newlines. This is not a requirement, but a guideline:: + + unnecessarily_long_function_name('string one', + 'string two', + kwarg1=constants.ACTIVE, + kwarg2=['a', 'b', 'c']) + + +Rather than constructing parameters inline, it is better to break things up:: + + list_of_strings = [ + 'what_a_long_string', + 'not as long', + ] + + dict_of_numbers = { + 'one': 1, + 'two': 2, + 'twenty four': 24, + } + + object_one.call_a_method('string three', + 'string four', + kwarg1=list_of_strings, + kwarg2=dict_of_numbers) + + +Internationalization (i18n) Strings +----------------------------------- +In order to support multiple languages, we have a mechanism to support +automatic translations of exception and log strings. + +Example:: + + msg = _("An error occurred") + raise HTTPBadRequest(explanation=msg) + +If you have a variable to place within the string, first internationalize the +template string then do the replacement. + +Example:: + + msg = _("Missing parameter: %s") % ("flavor",) + LOG.error(msg) + +If you have multiple variables to place in the string, use keyword parameters. +This helps our translators reorder parameters when needed. + +Example:: + + msg = _("The server with id %(s_id)s has no key %(m_key)s") + LOG.error(msg % {"s_id": "1234", "m_key": "imageId"}) + + +Creating Unit Tests +------------------- +For every new feature, unit tests should be created that both test and +(implicitly) document the usage of said feature. If submitting a patch for a +bug that had no unit test, a new passing unit test should be added. If a +submitted bug fix does have a unit test, be sure to add a new one that fails +without the patch and passes with the patch. diff --git a/quantumclient/__init__.py b/quantumclient/__init__.py index b7e0f504c..056e0dccc 100644 --- a/quantumclient/__init__.py +++ b/quantumclient/__init__.py @@ -16,9 +16,9 @@ # under the License. # @author: Tyler Smith, Cisco Systems -import logging import gettext import httplib +import logging import socket import time import urllib @@ -33,6 +33,8 @@ LOG = logging.getLogger('quantumclient') + + AUTH_TOKEN_HEADER = "X-Auth-Token" @@ -53,7 +55,7 @@ def exception_handler_v10(status_code, error_content): 430: 'portNotFound', 431: 'requestedStateInvalid', 432: 'portInUse', - 440: 'alreadyAttached' + 440: 'alreadyAttached', } quantum_errors = { @@ -66,7 +68,7 @@ def exception_handler_v10(status_code, error_content): 431: exceptions.StateInvalidClient, 432: exceptions.PortInUseClient, 440: exceptions.AlreadyAttachedClient, - 501: NotImplementedError + 501: NotImplementedError, } # Find real error type @@ -75,8 +77,7 @@ def exception_handler_v10(status_code, error_content): error_type = quantum_error_types.get(status_code) if error_type: error_dict = error_content[error_type] - error_message = error_dict['message'] + "\n" +\ - error_dict['detail'] + error_message = error_dict['message'] + "\n" + error_dict['detail'] else: error_message = error_content # raise the appropriate error! @@ -128,7 +129,7 @@ def exception_handler_v11(status_code, error_content): EXCEPTION_HANDLERS = { '1.0': exception_handler_v10, - '1.1': exception_handler_v11 + '1.1': exception_handler_v11, } @@ -166,9 +167,12 @@ class Client(object): "network": ["id", "name"], "port": ["id", "state"], "attachment": ["id"]}, - "plurals": {"networks": "network", - "ports": "port"}}, - } + "plurals": { + "networks": "network", + "ports": "port", + }, + }, + } # Action query strings networks_path = "/networks" @@ -249,8 +253,8 @@ def _send_request(self, conn, method, action, body, headers): # Salvatore: Isolating this piece of code in its own method to # facilitate stubout for testing if self.logger: - self.logger.debug("Quantum Client Request:\n" \ - + method + " " + action + "\n") + self.logger.debug("Quantum Client Request:\n" + + method + " " + action + "\n") if body: self.logger.debug(body) conn.request(method, action, body, headers) @@ -286,7 +290,7 @@ def do_request(self, method, action, body=None, try: connection_type = self.get_connection_type() headers = headers or {"Content-Type": - "application/%s" % self.format} + "application/%s" % self.format} # if available, add authentication token if self.auth_token: headers[AUTH_TOKEN_HEADER] = self.auth_token @@ -301,8 +305,8 @@ def do_request(self, method, action, body=None, status_code = self.get_status_code(res) data = res.read() if self.logger: - self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \ - % (str(status_code), data)) + self.logger.debug("Quantum Client Reply (code = %s) :\n %s" % + (str(status_code), data)) if status_code in (httplib.OK, httplib.CREATED, httplib.ACCEPTED, @@ -335,8 +339,8 @@ def serialize(self, data): elif type(data) is dict: return Serializer().serialize(data, self.content_type()) else: - raise Exception("unable to serialize object of type = '%s'" \ - % type(data)) + raise Exception("unable to serialize object of type = '%s'" % + type(data)) def deserialize(self, data, status_code): """ @@ -344,8 +348,8 @@ def deserialize(self, data, status_code): """ if status_code == 204: return data - return Serializer(self._serialization_metadata).\ - deserialize(data, self.content_type()) + return Serializer(self._serialization_metadata).deserialize( + data, self.content_type()) def content_type(self, format=None): """ diff --git a/quantumclient/cli.py b/quantumclient/cli.py index 99ce0f102..a1a5b1da5 100755 --- a/quantumclient/cli.py +++ b/quantumclient/cli.py @@ -34,90 +34,114 @@ #Configure logger for client - cli logger is a child of it #NOTE(salvatore-orlando): logger name does not map to package #this is deliberate. Simplifies logger configuration -LOG = logging.getLogger('quantum') +LOG = logging.getLogger('quantumclient') + + DEFAULT_QUANTUM_VERSION = '1.1' FORMAT = 'json' commands_v10 = { - "list_nets": { - "func": cli_lib.list_nets, - "args": ["tenant-id"]}, - "list_nets_detail": { - "func": cli_lib.list_nets_detail, - "args": ["tenant-id"]}, - "create_net": { - "func": cli_lib.create_net, - "args": ["tenant-id", "net-name"]}, - "delete_net": { - "func": cli_lib.delete_net, - "args": ["tenant-id", "net-id"]}, - "show_net": { - "func": cli_lib.show_net, - "args": ["tenant-id", "net-id"]}, - "show_net_detail": { - "func": cli_lib.show_net_detail, - "args": ["tenant-id", "net-id"]}, - "update_net": { - "func": cli_lib.update_net, - "args": ["tenant-id", "net-id", "new-name"]}, - "list_ports": { - "func": cli_lib.list_ports, - "args": ["tenant-id", "net-id"]}, - "list_ports_detail": { - "func": cli_lib.list_ports_detail, - "args": ["tenant-id", "net-id"]}, - "create_port": { - "func": cli_lib.create_port, - "args": ["tenant-id", "net-id"]}, - "delete_port": { - "func": cli_lib.delete_port, - "args": ["tenant-id", "net-id", "port-id"]}, - "update_port": { - "func": cli_lib.update_port, - "args": ["tenant-id", "net-id", "port-id", "params"]}, - "show_port": { - "func": cli_lib.show_port, - "args": ["tenant-id", "net-id", "port-id"]}, - "show_port_detail": { - "func": cli_lib.show_port_detail, - "args": ["tenant-id", "net-id", "port-id"]}, - "plug_iface": { - "func": cli_lib.plug_iface, - "args": ["tenant-id", "net-id", "port-id", "iface-id"]}, - "unplug_iface": { - "func": cli_lib.unplug_iface, - "args": ["tenant-id", "net-id", "port-id"]}, - "show_iface": { - "func": cli_lib.show_iface, - "args": ["tenant-id", "net-id", "port-id"]}, } + "list_nets": { + "func": cli_lib.list_nets, + "args": ["tenant-id"], + }, + "list_nets_detail": { + "func": cli_lib.list_nets_detail, + "args": ["tenant-id"], + }, + "create_net": { + "func": cli_lib.create_net, + "args": ["tenant-id", "net-name"], + }, + "delete_net": { + "func": cli_lib.delete_net, + "args": ["tenant-id", "net-id"], + }, + "show_net": { + "func": cli_lib.show_net, + "args": ["tenant-id", "net-id"], + }, + "show_net_detail": { + "func": cli_lib.show_net_detail, + "args": ["tenant-id", "net-id"], + }, + "update_net": { + "func": cli_lib.update_net, + "args": ["tenant-id", "net-id", "new-name"], + }, + "list_ports": { + "func": cli_lib.list_ports, + "args": ["tenant-id", "net-id"], + }, + "list_ports_detail": { + "func": cli_lib.list_ports_detail, + "args": ["tenant-id", "net-id"], + }, + "create_port": { + "func": cli_lib.create_port, + "args": ["tenant-id", "net-id"], + }, + "delete_port": { + "func": cli_lib.delete_port, + "args": ["tenant-id", "net-id", "port-id"], + }, + "update_port": { + "func": cli_lib.update_port, + "args": ["tenant-id", "net-id", "port-id", "params"], + }, + "show_port": { + "func": cli_lib.show_port, + "args": ["tenant-id", "net-id", "port-id"], + }, + "show_port_detail": { + "func": cli_lib.show_port_detail, + "args": ["tenant-id", "net-id", "port-id"], + }, + "plug_iface": { + "func": cli_lib.plug_iface, + "args": ["tenant-id", "net-id", "port-id", "iface-id"], + }, + "unplug_iface": { + "func": cli_lib.unplug_iface, + "args": ["tenant-id", "net-id", "port-id"], + }, + "show_iface": { + "func": cli_lib.show_iface, + "args": ["tenant-id", "net-id", "port-id"], + }, + } commands_v11 = commands_v10.copy() commands_v11.update({ - "list_nets": { - "func": cli_lib.list_nets_v11, - "args": ["tenant-id"], - "filters": ["name", "op-status", "port-op-status", "port-state", - "has-attachment", "attachment", "port"]}, - "list_nets_detail": { - "func": cli_lib.list_nets_detail_v11, - "args": ["tenant-id"], - "filters": ["name", "op-status", "port-op-status", "port-state", - "has-attachment", "attachment", "port"]}, - "list_ports": { - "func": cli_lib.list_ports_v11, - "args": ["tenant-id", "net-id"], - "filters": ["name", "op-status", "has-attachment", "attachment"]}, - "list_ports_detail": { - "func": cli_lib.list_ports_detail_v11, - "args": ["tenant-id", "net-id"], - "filters": ["name", "op-status", "has-attachment", "attachment"]}, - }) + "list_nets": { + "func": cli_lib.list_nets_v11, + "args": ["tenant-id"], + "filters": ["name", "op-status", "port-op-status", "port-state", + "has-attachment", "attachment", "port"], + }, + "list_nets_detail": { + "func": cli_lib.list_nets_detail_v11, + "args": ["tenant-id"], + "filters": ["name", "op-status", "port-op-status", "port-state", + "has-attachment", "attachment", "port"], + }, + "list_ports": { + "func": cli_lib.list_ports_v11, + "args": ["tenant-id", "net-id"], + "filters": ["name", "op-status", "has-attachment", "attachment"], + }, + "list_ports_detail": { + "func": cli_lib.list_ports_detail_v11, + "args": ["tenant-id", "net-id"], + "filters": ["name", "op-status", "has-attachment", "attachment"], + }, + }) commands = { '1.0': commands_v10, - '1.1': commands_v11 + '1.1': commands_v11, } clients = { '1.0': Client, - '1.1': ClientV11 + '1.1': ClientV11, } @@ -125,23 +149,24 @@ def help(version): print "\nCommands:" cmds = commands[version] for k in cmds.keys(): - print " %s %s %s" % (k, - " ".join(["<%s>" % y for y in cmds[k]["args"]]), - 'filters' in cmds[k] and "[filterspec ...]" or "") + print " %s %s %s" % ( + k, + " ".join(["<%s>" % y for y in cmds[k]["args"]]), + 'filters' in cmds[k] and "[filterspec ...]" or "") def print_usage(cmd, version): cmds = commands[version] - print "Usage:\n %s %s" % (cmd, - " ".join(["<%s>" % y for y in cmds[cmd]["args"]])) + print "Usage:\n %s %s" % ( + cmd, " ".join(["<%s>" % y for y in cmds[cmd]["args"]])) def build_args(cmd, cmdargs, arglist): arglist_len = len(arglist) cmdargs_len = len(cmdargs) if arglist_len < cmdargs_len: - message = "Not enough arguments for \"%s\" (expected: %d, got: %d)"\ - % (cmd, len(cmdargs), arglist_len) + message = ("Not enough arguments for \"%s\" (expected: %d, got: %d)" % + (cmd, len(cmdargs), arglist_len)) raise exceptions.QuantumCLIError(message=message) args = arglist[:cmdargs_len] return args @@ -192,12 +217,13 @@ def build_cmd(cmd, cmd_args, cmd_filters, arglist, version): return None, None filter_len = (filters is not None) and len(filters) or 0 if len(arglist) - len(args) - filter_len > 0: - message = "Too many arguments for \"%s\" (expected: %d, got: %d)"\ - % (cmd, len(cmd_args), arglist_len) + message = ("Too many arguments for \"%s\" (expected: %d, got: %d)" % + (cmd, len(cmd_args), arglist_len)) LOG.error(message) print "Error in command line: %s " % message - print "Usage:\n %s %s" % (cmd, - " ".join(["<%s>" % y for y in commands[version][cmd]["args"]])) + print "Usage:\n %s %s" % ( + cmd, + " ".join(["<%s>" % y for y in commands[version][cmd]["args"]])) return None, None # Append version to arguments for cli functions args.append(version) @@ -219,18 +245,21 @@ def main(): usagestr = "Usage: %prog [OPTIONS] [args]" parser = OptionParser(usage=usagestr) parser.add_option("-H", "--host", dest="host", - type="string", default="127.0.0.1", help="ip address of api host") + type="string", default="127.0.0.1", + help="ip address of api host") parser.add_option("-p", "--port", dest="port", - type="int", default=9696, help="api poort") + type="int", default=9696, help="api poort") parser.add_option("-s", "--ssl", dest="ssl", - action="store_true", default=False, help="use ssl") + action="store_true", default=False, help="use ssl") parser.add_option("-v", "--verbose", dest="verbose", - action="store_true", default=False, help="turn on verbose logging") + action="store_true", default=False, + help="turn on verbose logging") parser.add_option("-f", "--logfile", dest="logfile", - type="string", default="syslog", help="log file path") + type="string", default="syslog", help="log file path") parser.add_option("-t", "--token", dest="token", - type="string", default=None, help="authentication token") - parser.add_option('--version', + type="string", default=None, help="authentication token") + parser.add_option( + '--version', default=utils.env('QUANTUM_VERSION', default=DEFAULT_QUANTUM_VERSION), help='Accepts 1.1 and 1.0, defaults to env[QUANTUM_VERSION].') options, args = parser.parse_args() diff --git a/quantumclient/cli_lib.py b/quantumclient/cli_lib.py index 6ff8a1f79..6111bcb3d 100755 --- a/quantumclient/cli_lib.py +++ b/quantumclient/cli_lib.py @@ -23,10 +23,13 @@ import logging import traceback -FORMAT = "json" + LOG = logging.getLogger('quantumclient.cli_lib') +FORMAT = "json" + + class OutputTemplate(object): """ A class for generating simple templated output. Based on Python templating mechanism. @@ -95,90 +98,146 @@ class CmdOutputTemplate(OutputTemplate): Extends OutputTemplate loading a different template for each command. """ - _templates_v10 = { - "list_nets": "Virtual Networks for Tenant %(tenant_id)s\n" + - "%(networks|\tNetwork ID: %(id)s)s", - "list_nets_detail": "Virtual Networks for Tenant %(tenant_id)s\n" + - "%(networks|\tNetwork %(name)s with ID: %(id)s)s", - "show_net": "Network ID: %(network.id)s\n" + - "Network Name: %(network.name)s", - "show_net_detail": "Network ID: %(network.id)s\n" + - "Network Name: %(network.name)s\n" + - "Ports: %(network.ports|\tID: %(id)s\n" + - "\t\tadministrative state: %(state)s\n" + - "\t\tinterface: %(attachment.id)s)s", - "create_net": "Created a new Virtual Network with ID: " + - "%(network_id)s\n" + - "for Tenant: %(tenant_id)s", - "update_net": "Updated Virtual Network with ID: " + - "%(network.id)s\n" + - "for Tenant: %(tenant_id)s\n", - "delete_net": "Deleted Virtual Network with ID: " + - "%(network_id)s\n" + - "for Tenant %(tenant_id)s", - "list_ports": "Ports on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s\n" + - "%(ports|\tLogical Port: %(id)s)s", - "list_ports_detail": "Ports on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s\n" + - "%(ports|\tLogical Port: %(id)s\n" + - "\t\tadministrative State: %(state)s)s", - "create_port": "Created new Logical Port with ID: " + - "%(port_id)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s", - "show_port": "Logical Port ID: %(port.id)s\n" + - "administrative State: %(port.state)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s", - "show_port_detail": "Logical Port ID: %(port.id)s\n" + - "administrative State: %(port.state)s\n" + - "interface: %(port.attachment.id)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s", - "update_port": "Updated Logical Port " + - "with ID: %(port.id)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for tenant: %(tenant_id)s", - "delete_port": "Deleted Logical Port with ID: %(port_id)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s", - "plug_iface": "Plugged interface %(attachment)s\n" + - "into Logical Port: %(port_id)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s", - "unplug_iface": "Unplugged interface from Logical Port:" + - "%(port_id)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s", - "show_iface": "interface: %(iface.id)s\n" + - "plugged in Logical Port ID: %(port_id)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s"} + _templates_v10 = dict( + list_nets=""" +Virtual Networks for Tenant %(tenant_id)s +%(networks|\tNetwork ID: %(id)s)s +""".strip(), + + list_nets_detail=""" +Virtual Networks for Tenant %(tenant_id)s +%(networks|\tNetwork %(name)s with ID: %(id)s)s +""".strip(), + + show_net=""" +Network ID: %(network.id)s +Network Name: %(network.name)s +""".strip(), + + show_net_detail=""" +Network ID: %(network.id)s +Network Name: %(network.name)s +Ports: %(network.ports|\tID: %(id)s +\t\tadministrative state: %(state)s +\t\tinterface: %(attachment.id)s)s +""".strip(), + + create_net=""" +Created a new Virtual Network with ID: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + + update_net=""" +Updated Virtual Network with ID: %(network.id)s +for Tenant: %(tenant_id)s +""".strip(), + + delete_net=""" +Deleted Virtual Network with ID: %(network_id)s +for Tenant %(tenant_id)s +""".strip(), + + list_ports=""" +Ports on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +%(ports|\tLogical Port: %(id)s)s +""".strip(), + + list_ports_detail=""" +Ports on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +%(ports|\tLogical Port: %(id)s +\t\tadministrative State: %(state)s)s +""".strip(), + + create_port=""" +Created new Logical Port with ID: %(port_id)s +on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + + show_port=""" +Logical Port ID: %(port.id)s +administrative State: %(port.state)s +on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + + show_port_detail=""" +Logical Port ID: %(port.id)s +administrative State: %(port.state)s +interface: %(port.attachment.id)s +on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + + update_port=""" +Updated Logical Port with ID: %(port.id)s +on Virtual Network: %(network_id)s +for tenant: %(tenant_id)s +""".strip(), + + delete_port=""" +Deleted Logical Port with ID: %(port_id)s +on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + + plug_iface=""" +Plugged interface %(attachment)s +into Logical Port: %(port_id)s +on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + + unplug_iface=""" +Unplugged interface from Logical Port: %(port_id)s +on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + + show_iface=""" +interface: %(iface.id)s +plugged in Logical Port ID: %(port_id)s +on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + ) _templates_v11 = _templates_v10.copy() - _templates_v11.update({ - "show_net": "Network ID: %(network.id)s\n" + - "network Name: %(network.name)s\n" + - "operational Status: %(network.op-status)s", - "show_net_detail": "Network ID: %(network.id)s\n" + - "Network Name: %(network.name)s\n" + - "operational Status: %(network.op-status)s\n" + - "Ports: %(network.ports|\tID: %(id)s\n" + - "\t\tadministrative state: %(state)s\n" + - "\t\tinterface: %(attachment.id)s)s", - "show_port": "Logical Port ID: %(port.id)s\n" + - "administrative state: %(port.state)s\n" + - "operational status: %(port.op-status)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s", - "show_port_detail": "Logical Port ID: %(port.id)s\n" + - "administrative State: %(port.state)s\n" + - "operational status: %(port.op-status)s\n" + - "interface: %(port.attachment.id)s\n" + - "on Virtual Network: %(network_id)s\n" + - "for Tenant: %(tenant_id)s", - }) + _templates_v11.update(dict( + show_net=""" +Network ID: %(network.id)s +network Name: %(network.name)s +operational Status: %(network.op-status)s +""".strip(), + + show_net_detail=""" +Network ID: %(network.id)s +Network Name: %(network.name)s +operational Status: %(network.op-status)s +Ports: %(network.ports|\tID: %(id)s +\t\tadministrative state: %(state)s +\t\tinterface: %(attachment.id)s)s +""".strip(), + + show_port=""" +Logical Port ID: %(port.id)s +administrative state: %(port.state)s +operational status: %(port.op-status)s +on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + + show_port_detail=""" +Logical Port ID: %(port.id)s +administrative State: %(port.state)s +operational status: %(port.op-status)s +interface: %(port.attachment.id)s +on Virtual Network: %(network_id)s +for Tenant: %(tenant_id)s +""".strip(), + )) _templates = { '1.0': _templates_v10, @@ -197,8 +256,8 @@ def _handle_exception(ex): if ex.args and isinstance(ex.args[-1][0], dict): status_code = ex.args[-1][0].get('status_code', None) message = ex.args[-1][0].get('message', None) - msg_1 = "Command failed with error code: %s" \ - % (status_code or '') + msg_1 = ("Command failed with error code: %s" % + (status_code or '')) msg_2 = "Error message:%s" % (message or '') print msg_1 print msg_2 @@ -207,8 +266,8 @@ def _handle_exception(ex): def prepare_output(cmd, tenant_id, response, version): - LOG.debug("Preparing output for response:%s, version:%s" - % (response, version)) + LOG.debug("Preparing output for response:%s, version:%s" % + (response, version)) response['tenant_id'] = tenant_id output = str(CmdOutputTemplate(cmd, response, version)) LOG.debug("Finished preparing output for command:%s", cmd) diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py index 7fefd2dc1..6e914673a 100644 --- a/quantumclient/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -43,7 +43,7 @@ def deserialize(self, datastring, content_type): return self.get_deserialize_handler(content_type)(datastring) except Exception: raise exception.MalformedResponseBody( - reason="Unable to deserialize response body") + reason="Unable to deserialize response body") def get_deserialize_handler(self, content_type): handlers = { diff --git a/quantumclient/tests/unit/test_cli.py b/quantumclient/tests/unit/test_cli.py index c68eb6a54..6cf618c47 100644 --- a/quantumclient/tests/unit/test_cli.py +++ b/quantumclient/tests/unit/test_cli.py @@ -21,7 +21,6 @@ """ - import logging import sys import unittest @@ -43,8 +42,8 @@ class CLITest(unittest.TestCase): def setUp(self): """Prepare the test environment""" options = {} - options['plugin_provider'] = \ - 'quantum.plugins.sample.SamplePlugin.FakePlugin' + options['plugin_provider'] = ( + 'quantum.plugins.sample.SamplePlugin.FakePlugin') #TODO: make the version of the API router configurable self.api = server.APIRouterV11(options) @@ -160,17 +159,25 @@ def _verify_show_network_details(self): 'op-status': nw.op_status} port_list = db.port_list(nw.uuid) if not port_list: - network['ports'] = [{'id': '', - 'state': '', - 'attachment': {'id': ''}}] + network['ports'] = [ + { + 'id': '', + 'state': '', + 'attachment': { + 'id': '', + }, + }, + ] else: network['ports'] = [] for port in port_list: - network['ports'].append({'id': port.uuid, - 'state': port.state, - 'attachment': {'id': - port.interface_id - or ''}}) + network['ports'].append({ + 'id': port.uuid, + 'state': port.state, + 'attachment': { + 'id': port.interface_id or '', + }, + }) # Fill CLI template output = cli.prepare_output('show_net_detail', diff --git a/quantumclient/tests/unit/test_clientlib.py b/quantumclient/tests/unit/test_clientlib.py index 94df09242..3b88f1521 100644 --- a/quantumclient/tests/unit/test_clientlib.py +++ b/quantumclient/tests/unit/test_clientlib.py @@ -17,15 +17,17 @@ # @author: Tyler Smith, Cisco Systems import logging -import unittest import re +import unittest from quantumclient.common import exceptions from quantumclient.common.serializer import Serializer from quantumclient import Client + LOG = logging.getLogger('quantumclient.tests.test_api') + # Set a couple tenants to use for testing TENANT_1 = 'totore' TENANT_2 = 'totore2' @@ -119,7 +121,7 @@ def _assert_sanity(self, call, status, method, path, data=[], params={}): return data def _test_list_networks(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_networks - tenant:%s "\ + LOG.debug("_test_list_networks - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.list_networks, @@ -129,13 +131,13 @@ def _test_list_networks(self, tenant=TENANT_1, format='json', status=200): data=[], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_list_networks - tenant:%s "\ - "- format:%s - END", format, tenant) + LOG.debug("_test_list_networks - tenant:%s - format:%s - END", + format, tenant) def _test_list_networks_details(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_networks_details - tenant:%s "\ + LOG.debug("_test_list_networks_details - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.list_networks_details, @@ -145,12 +147,12 @@ def _test_list_networks_details(self, data=[], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_list_networks_details - tenant:%s "\ + LOG.debug("_test_list_networks_details - tenant:%s " "- format:%s - END", format, tenant) def _test_show_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_network - tenant:%s "\ + LOG.debug("_test_show_network - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.show_network, @@ -160,12 +162,12 @@ def _test_show_network(self, data=["001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_show_network - tenant:%s "\ + LOG.debug("_test_show_network - tenant:%s " "- format:%s - END", format, tenant) def _test_show_network_details(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_network_details - tenant:%s "\ + LOG.debug("_test_show_network_details - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.show_network_details, @@ -175,11 +177,11 @@ def _test_show_network_details(self, data=["001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_show_network_details - tenant:%s "\ + LOG.debug("_test_show_network_details - tenant:%s " "- format:%s - END", format, tenant) def _test_create_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_create_network - tenant:%s "\ + LOG.debug("_test_create_network - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.create_network, @@ -189,11 +191,11 @@ def _test_create_network(self, tenant=TENANT_1, format='json', status=200): data=[{'network': {'net-name': 'testNetwork'}}], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_create_network - tenant:%s "\ + LOG.debug("_test_create_network - tenant:%s " "- format:%s - END", format, tenant) def _test_update_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_update_network - tenant:%s "\ + LOG.debug("_test_update_network - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.update_network, @@ -204,11 +206,11 @@ def _test_update_network(self, tenant=TENANT_1, format='json', status=200): {'network': {'net-name': 'newName'}}], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_update_network - tenant:%s "\ + LOG.debug("_test_update_network - tenant:%s " "- format:%s - END", format, tenant) def _test_delete_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_delete_network - tenant:%s "\ + LOG.debug("_test_delete_network - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.delete_network, @@ -218,11 +220,11 @@ def _test_delete_network(self, tenant=TENANT_1, format='json', status=200): data=["001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_delete_network - tenant:%s "\ + LOG.debug("_test_delete_network - tenant:%s " "- format:%s - END", format, tenant) def _test_list_ports(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_ports - tenant:%s "\ + LOG.debug("_test_list_ports - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.list_ports, @@ -232,12 +234,12 @@ def _test_list_ports(self, tenant=TENANT_1, format='json', status=200): data=["001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_list_ports - tenant:%s "\ + LOG.debug("_test_list_ports - tenant:%s " "- format:%s - END", format, tenant) def _test_list_ports_details(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_ports_details - tenant:%s "\ + LOG.debug("_test_list_ports_details - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.list_ports_details, @@ -247,11 +249,11 @@ def _test_list_ports_details(self, data=["001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_list_ports_details - tenant:%s "\ + LOG.debug("_test_list_ports_details - tenant:%s " "- format:%s - END", format, tenant) def _test_show_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_port - tenant:%s "\ + LOG.debug("_test_show_port - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.show_port, @@ -261,12 +263,12 @@ def _test_show_port(self, tenant=TENANT_1, format='json', status=200): data=["001", "001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_show_port - tenant:%s "\ + LOG.debug("_test_show_port - tenant:%s " "- format:%s - END", format, tenant) def _test_show_port_details(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_port_details - tenant:%s "\ + LOG.debug("_test_show_port_details - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.show_port_details, @@ -276,11 +278,11 @@ def _test_show_port_details(self, data=["001", "001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_show_port_details - tenant:%s "\ + LOG.debug("_test_show_port_details - tenant:%s " "- format:%s - END", format, tenant) def _test_create_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_create_port - tenant:%s "\ + LOG.debug("_test_create_port - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.create_port, @@ -290,11 +292,11 @@ def _test_create_port(self, tenant=TENANT_1, format='json', status=200): data=["001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_create_port - tenant:%s "\ + LOG.debug("_test_create_port - tenant:%s " "- format:%s - END", format, tenant) def _test_delete_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_delete_port - tenant:%s "\ + LOG.debug("_test_delete_port - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.delete_port, @@ -304,11 +306,11 @@ def _test_delete_port(self, tenant=TENANT_1, format='json', status=200): data=["001", "001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_delete_port - tenant:%s "\ + LOG.debug("_test_delete_port - tenant:%s " "- format:%s - END", format, tenant) def _test_update_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_update_port - tenant:%s "\ + LOG.debug("_test_update_port - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.update_port, @@ -319,12 +321,12 @@ def _test_update_port(self, tenant=TENANT_1, format='json', status=200): {'port': {'state': 'ACTIVE'}}], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_update_port - tenant:%s "\ + LOG.debug("_test_update_port - tenant:%s " "- format:%s - END", format, tenant) def _test_show_port_attachment(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_port_attachment - tenant:%s "\ + LOG.debug("_test_show_port_attachment - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.show_port_attachment, @@ -334,12 +336,12 @@ def _test_show_port_attachment(self, data=["001", "001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_show_port_attachment - tenant:%s "\ + LOG.debug("_test_show_port_attachment - tenant:%s " "- format:%s - END", format, tenant) def _test_attach_resource(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_attach_resource - tenant:%s "\ + LOG.debug("_test_attach_resource - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.attach_resource, @@ -350,12 +352,12 @@ def _test_attach_resource(self, tenant=TENANT_1, {'resource': {'id': '1234'}}], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_attach_resource - tenant:%s "\ + LOG.debug("_test_attach_resource - tenant:%s " "- format:%s - END", format, tenant) def _test_detach_resource(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_detach_resource - tenant:%s "\ + LOG.debug("_test_detach_resource - tenant:%s " "- format:%s - START", format, tenant) self._assert_sanity(self.client.detach_resource, @@ -365,12 +367,12 @@ def _test_detach_resource(self, tenant=TENANT_1, data=["001", "001"], params={'tenant': tenant, 'format': format}) - LOG.debug("_test_detach_resource - tenant:%s "\ + LOG.debug("_test_detach_resource - tenant:%s " "- format:%s - END", format, tenant) def _test_ssl_certificates(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_ssl_certificates - tenant:%s "\ + LOG.debug("_test_ssl_certificates - tenant:%s " "- format:%s - START", format, tenant) # Set SSL, and our cert file @@ -379,16 +381,16 @@ def _test_ssl_certificates(self, tenant=TENANT_1, self.client.key_file = self.client.cert_file = cert_file data = self._assert_sanity(self.client.list_networks, - status, - "GET", - "networks", - data=[], - params={'tenant': tenant, 'format': format}) + status, + "GET", + "networks", + data=[], + params={'tenant': tenant, 'format': format}) self.assertEquals(data["key_file"], cert_file) self.assertEquals(data["cert_file"], cert_file) - LOG.debug("_test_ssl_certificates - tenant:%s "\ + LOG.debug("_test_ssl_certificates - tenant:%s " "- format:%s - END", format, tenant) def test_list_networks_json(self): From 878c939e2c6dcbbf4e0792b04a070af90c72fba6 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Wed, 16 May 2012 11:42:30 -0400 Subject: [PATCH 003/135] Align tox with standards. Moved openstack nose invocation to tox.ini from setup.cfg, as there is no way to turn it off if it's in setup.cfg, and when we're running in jenkins, we want to run via xunit and not via openstack color output. Change-Id: I0a7b6232834b5cdfc97be9c2f93f726d6b47e0ac --- setup.cfg | 5 ----- tools/test-requires | 8 ++++++-- tox.ini | 20 +++++++++++++++++++- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/setup.cfg b/setup.cfg index f72c0a437..48000a777 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,8 +5,3 @@ # openstack-nose https://github.com/jkoelker/openstack-nose verbosity=2 detailed-errors=1 -with-openstack=1 -openstack-red=0.05 -openstack-yellow=0.025 -openstack-show-elapsed=1 - diff --git a/tools/test-requires b/tools/test-requires index 4ddb40b25..a048ab095 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,7 +1,11 @@ -coverage +distribute>=0.6.24 + +mox nose +nose-exclude nosexcover +openstack.nose_plugin pep8==0.6.1 +sphinx>=1.1.2 --e git+https://review.openstack.org/p/openstack-dev/openstack-nose.git#egg=openstack.nose_plugin -e git+https://review.openstack.org/p/openstack/quantum#egg=quantum-dev diff --git a/tox.ini b/tox.ini index 0ed1adbb2..a5feeffaa 100644 --- a/tox.ini +++ b/tox.ini @@ -2,6 +2,13 @@ envlist = py26,py27,pep8 [testenv] +setenv = + VIRTUAL_ENV={envdir} + NOSE_WITH_OPENSTACK=1 + NOSE_OPENSTACK_COLOR=1 + NOSE_OPENSTACK_RED=0.05 + NOSE_OPENSTACK_YELLOW=0.025 + NOSE_OPENSTACK_SHOW_ELAPSED=1 deps = -r{toxinidir}/tools/test-requires commands = nosetests @@ -11,20 +18,25 @@ commands = pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantumclient \ setup.py version.py +[testenv:venv] +commands = {posargs} + [testenv:cover] commands = nosetests --with-coverage --cover-html --cover-erase \ --cover-package=quantumclient -[testenv:hudson] +[tox:jenkins] downloadcache = ~/cache/pip [testenv:jenkins26] basepython = python2.6 +setenv = NOSE_WITH_XUNIT=1 deps = file://{toxinidir}/.cache.bundle [testenv:jenkins27] basepython = python2.7 +setenv = NOSE_WITH_XUNIT=1 deps = file://{toxinidir}/.cache.bundle [testenv:jenkinspep8] @@ -35,5 +47,11 @@ commands = [testenv:jenkinscover] deps = file://{toxinidir}/.cache.bundle +setenv = NOSE_WITH_XUNIT=1 commands = nosetests --cover-erase --cover-package=quantumclient --with-xcoverage + +[testenv:jenkinsvenv] +deps = file://{toxinidir}/.cache.bundle +setenv = NOSE_WITH_XUNIT=1 +commands = {posargs} From 1fb4540bfa8abb8fbba710c5037ec31a35884d9f Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Tue, 22 May 2012 11:54:39 +0200 Subject: [PATCH 004/135] Add HACKING.rst to generated tarballs Add HACKING.rst to MANIFEST.in so that it shows in tarballs. Fixes bug 1002776. Change-Id: I937d0673d23d922137e684217a8c4c75a5424800 --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index b38b63714..94058d438 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include tox.ini -include LICENSE README +include LICENSE README HACKING.rst include version.py include tools/* include quantumclient/tests/* From bc2d6a66f0fbcb23b2e9782352b9477a85950ef2 Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Fri, 11 May 2012 10:18:01 +0800 Subject: [PATCH 005/135] Use --debug to enable printing HTTP message(s) between client and server, besides logging verbosely. fix Bug #993859 change --verbose into --debug, which is a practice by other clients (nova and glance). note: httplib prints message by print. Patch 2: fix pep8, modify according to some comments Change-Id: Ibc959cca7d89c18b6d5c902f2e98bb856102c51c --- quantumclient/__init__.py | 7 +++++++ quantumclient/cli.py | 13 +++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/quantumclient/__init__.py b/quantumclient/__init__.py index 056e0dccc..461f9b853 100644 --- a/quantumclient/__init__.py +++ b/quantumclient/__init__.py @@ -216,6 +216,8 @@ def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, self.key_file = key_file self.cert_file = cert_file self.logger = logger + if not self.logger: + self.logger = LOG self.auth_token = auth_token self.version = version self.action_prefix = "/v%s%s" % (version, uri_prefix) @@ -301,6 +303,11 @@ def do_request(self, method, action, body=None, conn = connection_type(self.host, self.port, **certs) else: conn = connection_type(self.host, self.port) + # besides HTTP(s)Connection, we still have testConnection + if (LOG.isEnabledFor(logging.DEBUG) and + isinstance(conn, httplib.HTTPConnection)): + conn.set_debuglevel(1) + res = self._send_request(conn, method, action, body, headers) status_code = self.get_status_code(res) data = res.read() diff --git a/quantumclient/cli.py b/quantumclient/cli.py index a1a5b1da5..163663901 100755 --- a/quantumclient/cli.py +++ b/quantumclient/cli.py @@ -31,9 +31,10 @@ from quantumclient.common import utils -#Configure logger for client - cli logger is a child of it -#NOTE(salvatore-orlando): logger name does not map to package -#this is deliberate. Simplifies logger configuration +# Configure logger for client - cli logger is a child of it +# NOTE(salvatore-orlando): logger name does not map to package +# this is deliberate. Simplifies logger configuration +logging.basicConfig() LOG = logging.getLogger('quantumclient') @@ -251,9 +252,9 @@ def main(): type="int", default=9696, help="api poort") parser.add_option("-s", "--ssl", dest="ssl", action="store_true", default=False, help="use ssl") - parser.add_option("-v", "--verbose", dest="verbose", + parser.add_option("--debug", dest="debug", action="store_true", default=False, - help="turn on verbose logging") + help="print debugging output") parser.add_option("-f", "--logfile", dest="logfile", type="string", default="syslog", help="log file path") parser.add_option("-t", "--token", dest="token", @@ -264,7 +265,7 @@ def main(): help='Accepts 1.1 and 1.0, defaults to env[QUANTUM_VERSION].') options, args = parser.parse_args() - if options.verbose: + if options.debug: LOG.setLevel(logging.DEBUG) else: LOG.setLevel(logging.WARN) From f5035b341009505bf4f24c52a3aae548a6b238ea Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Thu, 24 May 2012 14:59:32 +0800 Subject: [PATCH 006/135] quit and print usage when unsupported version specified. bug 974835 Change-Id: Iddbf72281be0d4dd819b6f51cc7950d90af0dbbc --- quantumclient/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quantumclient/cli.py b/quantumclient/cli.py index 163663901..6c14f632e 100755 --- a/quantumclient/cli.py +++ b/quantumclient/cli.py @@ -280,7 +280,8 @@ def main(): version = options.version if not version in commands: LOG.error("Unknown API version specified:%s", version) - print "Unknown API version: %s" % version + parser.print_help() + sys.exit(1) if len(args) < 1: parser.print_help() From f7086ed40a9cf7b978a906179fd9d883c00e5eff Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Wed, 13 Jun 2012 16:10:57 -0400 Subject: [PATCH 007/135] Add initial docs. Change-Id: I846eec12e800c15a545946604fe77a0a6b83fb46 --- .gitignore | 3 + doc/source/conf.py | 58 +++++ doc/source/index.rst | 17 ++ openstack-common.conf | 7 + quantumclient/openstack/__init__.py | 0 quantumclient/openstack/common/__init__.py | 0 quantumclient/openstack/common/setup.py | 252 +++++++++++++++++++++ setup.cfg | 5 + setup.py | 23 +- tools/test-requires | 2 +- tox.ini | 60 ++--- 11 files changed, 369 insertions(+), 58 deletions(-) create mode 100644 doc/source/conf.py create mode 100644 doc/source/index.rst create mode 100644 openstack-common.conf create mode 100644 quantumclient/openstack/__init__.py create mode 100644 quantumclient/openstack/common/__init__.py create mode 100644 quantumclient/openstack/common/setup.py diff --git a/.gitignore b/.gitignore index 2fc28f418..14c363976 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,6 @@ run_tests.err.log run_tests.log .venv/ .tox/ +.autogenerated +doc/build/ +doc/source/api/ diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 000000000..fba29db68 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +# + +import sys +import os + +project = 'python-quantumclient' + +# -- General configuration --------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +copyright = u'OpenStack LLC' + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# -- Options for HTML output --------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'nature' + +# Output file base name for HTML help builder. +htmlhelp_basename = '%sdoc' % project + + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, +# documentclass [howto/manual]). +latex_documents = [ + ('index', + '%s.tex' % project, + u'%s Documentation' % project, + u'OpenStack LLC', 'manual'), +] + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 000000000..8032d6914 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,17 @@ +Python bindings to the OpenStack Network API +============================================ + +This is a client for OpenStack Network API. Contents: + +.. toctree:: + :maxdepth: 1 + + api/autoindex + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/openstack-common.conf b/openstack-common.conf new file mode 100644 index 000000000..35d48d06e --- /dev/null +++ b/openstack-common.conf @@ -0,0 +1,7 @@ +[DEFAULT] + +# The list of modules to copy from openstack-common +modules=setup + +# The base module to hold the copy of openstack.common +base=quantumclient diff --git a/quantumclient/openstack/__init__.py b/quantumclient/openstack/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/quantumclient/openstack/common/__init__.py b/quantumclient/openstack/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/quantumclient/openstack/common/setup.py b/quantumclient/openstack/common/setup.py new file mode 100644 index 000000000..429ba35c5 --- /dev/null +++ b/quantumclient/openstack/common/setup.py @@ -0,0 +1,252 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Utilities with minimum-depends for use in setup.py +""" + +import os +import re +import subprocess + +from setuptools.command import sdist + + +def parse_mailmap(mailmap='.mailmap'): + mapping = {} + if os.path.exists(mailmap): + fp = open(mailmap, 'r') + for l in fp: + l = l.strip() + if not l.startswith('#') and ' ' in l: + canonical_email, alias = [x for x in l.split(' ') + if x.startswith('<')] + mapping[alias] = canonical_email + return mapping + + +def canonicalize_emails(changelog, mapping): + """Takes in a string and an email alias mapping and replaces all + instances of the aliases in the string with their real email. + """ + for alias, email in mapping.iteritems(): + changelog = changelog.replace(alias, email) + return changelog + + +# Get requirements from the first file that exists +def get_reqs_from_files(requirements_files): + reqs_in = [] + for requirements_file in requirements_files: + if os.path.exists(requirements_file): + return open(requirements_file, 'r').read().split('\n') + return [] + + +def parse_requirements(requirements_files=['requirements.txt', + 'tools/pip-requires']): + requirements = [] + for line in get_reqs_from_files(requirements_files): + # For the requirements list, we need to inject only the portion + # after egg= so that distutils knows the package it's looking for + # such as: + # -e git://github.com/openstack/nova/master#egg=nova + if re.match(r'\s*-e\s+', line): + requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1', + line)) + # such as: + # http://github.com/openstack/nova/zipball/master#egg=nova + elif re.match(r'\s*https?:', line): + requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1', + line)) + # -f lines are for index locations, and don't get used here + elif re.match(r'\s*-f\s+', line): + pass + else: + requirements.append(line) + + return requirements + + +def parse_dependency_links(requirements_files=['requirements.txt', + 'tools/pip-requires']): + dependency_links = [] + # dependency_links inject alternate locations to find packages listed + # in requirements + for line in get_reqs_from_files(requirements_files): + # skip comments and blank lines + if re.match(r'(\s*#)|(\s*$)', line): + continue + # lines with -e or -f need the whole line, minus the flag + if re.match(r'\s*-[ef]\s+', line): + dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line)) + # lines that are only urls can go in unmolested + elif re.match(r'\s*https?:', line): + dependency_links.append(line) + return dependency_links + + +def write_requirements(): + venv = os.environ.get('VIRTUAL_ENV', None) + if venv is not None: + with open("requirements.txt", "w") as req_file: + output = subprocess.Popen(["pip", "-E", venv, "freeze", "-l"], + stdout=subprocess.PIPE) + requirements = output.communicate()[0].strip() + req_file.write(requirements) + + +def _run_shell_command(cmd): + output = subprocess.Popen(["/bin/sh", "-c", cmd], + stdout=subprocess.PIPE) + return output.communicate()[0].strip() + + +def write_vcsversion(location): + """Produce a vcsversion dict that mimics the old one produced by bzr. + """ + if os.path.isdir('.git'): + branch_nick_cmd = 'git branch | grep -Ei "\* (.*)" | cut -f2 -d" "' + branch_nick = _run_shell_command(branch_nick_cmd) + revid_cmd = "git rev-parse HEAD" + revid = _run_shell_command(revid_cmd).split()[0] + revno_cmd = "git log --oneline | wc -l" + revno = _run_shell_command(revno_cmd) + with open(location, 'w') as version_file: + version_file.write(""" +# This file is automatically generated by setup.py, So don't edit it. :) +version_info = { + 'branch_nick': '%s', + 'revision_id': '%s', + 'revno': %s +} +""" % (branch_nick, revid, revno)) + + +def write_git_changelog(): + """Write a changelog based on the git changelog.""" + if os.path.isdir('.git'): + git_log_cmd = 'git log --stat' + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open("ChangeLog", "w") as changelog_file: + changelog_file.write(canonicalize_emails(changelog, mailmap)) + + +def generate_authors(): + """Create AUTHORS file using git commits.""" + jenkins_email = 'jenkins@review.openstack.org' + old_authors = 'AUTHORS.in' + new_authors = 'AUTHORS' + if os.path.isdir('.git'): + # don't include jenkins email address in AUTHORS file + git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " + "grep -v " + jenkins_email) + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_authors, 'w') as new_authors_fh: + new_authors_fh.write(canonicalize_emails(changelog, mailmap)) + if os.path.exists(old_authors): + with open(old_authors, "r") as old_authors_fh: + new_authors_fh.write('\n' + old_authors_fh.read()) + +_rst_template = """%(heading)s +%(underline)s + +.. automodule:: %(module)s + :members: + :undoc-members: + :show-inheritance: +""" + + +def get_cmdclass(): + """Return dict of commands to run from setup.py.""" + + cmdclass = dict() + + def _find_modules(arg, dirname, files): + for filename in files: + if filename.endswith('.py') and filename != '__init__.py': + arg["%s.%s" % (dirname.replace('/', '.'), + filename[:-3])] = True + + class LocalSDist(sdist.sdist): + """Builds the ChangeLog and Authors files from VC first.""" + + def run(self): + write_git_changelog() + generate_authors() + # sdist.sdist is an old style class, can't use super() + sdist.sdist.run(self) + + cmdclass['sdist'] = LocalSDist + + # If Sphinx is installed on the box running setup.py, + # enable setup.py to build the documentation, otherwise, + # just ignore it + try: + from sphinx.setup_command import BuildDoc + + class LocalBuildDoc(BuildDoc): + def generate_autoindex(self): + print "**Autodocumenting from %s" % os.path.abspath(os.curdir) + modules = {} + option_dict = self.distribution.get_option_dict('build_sphinx') + source_dir = os.path.join(option_dict['source_dir'][1], 'api') + if not os.path.exists(source_dir): + os.makedirs(source_dir) + for pkg in self.distribution.packages: + if '.' not in pkg: + os.path.walk(pkg, _find_modules, modules) + module_list = modules.keys() + module_list.sort() + autoindex_filename = os.path.join(source_dir, 'autoindex.rst') + with open(autoindex_filename, 'w') as autoindex: + autoindex.write(""".. toctree:: + :maxdepth: 1 + +""") + for module in module_list: + output_filename = os.path.join(source_dir, + "%s.rst" % module) + heading = "The :mod:`%s` Module" % module + underline = "=" * len(heading) + values = dict(module=module, heading=heading, + underline=underline) + + print "Generating %s" % output_filename + with open(output_filename, 'w') as output_file: + output_file.write(_rst_template % values) + autoindex.write(" %s.rst\n" % module) + + def run(self): + if not os.getenv('SPHINX_DEBUG'): + self.generate_autoindex() + + for builder in ['html', 'man']: + self.builder = builder + self.finalize_options() + self.project = self.distribution.get_name() + self.version = self.distribution.get_version() + self.release = self.distribution.get_version() + BuildDoc.run(self) + cmdclass['build_sphinx'] = LocalBuildDoc + except ImportError: + pass + + return cmdclass diff --git a/setup.cfg b/setup.cfg index 48000a777..394b128fe 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,8 @@ +[build_sphinx] +all_files = 1 +build-dir = doc/build +source-dir = doc/source + [nosetests] # NOTE(jkoelker) To run the test suite under nose install the following # coverage http://pypi.python.org/pypi/coverage diff --git a/setup.py b/setup.py index 087e1546b..e57b15eba 100644 --- a/setup.py +++ b/setup.py @@ -15,12 +15,11 @@ # License for the specific language governing permissions and limitations # under the License. -try: - from setuptools import setup, find_packages -except ImportError: - from ez_setup import use_setuptools - use_setuptools() - from setuptools import setup, find_packages +import os +import sys +import setuptools + +from quantumclient.openstack.common import setup import version @@ -35,8 +34,8 @@ ShortDescription = Summary Description = Summary -requires = [ -] +dependency_links = setup.parse_dependency_links() +tests_require = setup.parse_requirements(['tools/test-requires']) EagerResources = [ ] @@ -48,7 +47,7 @@ } -setup( +setuptools.setup( name=Name, version=Version, url=Url, @@ -58,9 +57,11 @@ long_description=Description, license=License, scripts=ProjectScripts, - install_requires=requires, + dependency_links=dependency_links, + tests_require=tests_require, + cmdclass=setup.get_cmdclass(), include_package_data=False, - packages=["quantumclient", "quantumclient.common"], + packages=setuptools.find_packages(exclude=['tests', 'tests.*']), package_data=PackageData, eager_resources=EagerResources, entry_points={ diff --git a/tools/test-requires b/tools/test-requires index a048ab095..427a1b233 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -8,4 +8,4 @@ openstack.nose_plugin pep8==0.6.1 sphinx>=1.1.2 --e git+https://review.openstack.org/p/openstack/quantum#egg=quantum-dev +https://github.com/openstack/quantum/zipball/master#egg=quantum diff --git a/tox.ini b/tox.ini index a5feeffaa..fce1185cf 100644 --- a/tox.ini +++ b/tox.ini @@ -2,56 +2,24 @@ envlist = py26,py27,pep8 [testenv] -setenv = - VIRTUAL_ENV={envdir} - NOSE_WITH_OPENSTACK=1 - NOSE_OPENSTACK_COLOR=1 - NOSE_OPENSTACK_RED=0.05 - NOSE_OPENSTACK_YELLOW=0.025 - NOSE_OPENSTACK_SHOW_ELAPSED=1 -deps = - -r{toxinidir}/tools/test-requires -commands = nosetests - -[testenv:pep8] -commands = - pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantumclient \ - setup.py version.py - -[testenv:venv] -commands = {posargs} - -[testenv:cover] -commands = - nosetests --with-coverage --cover-html --cover-erase \ - --cover-package=quantumclient +setenv = VIRTUAL_ENV={envdir} + NOSE_WITH_OPENSTACK=1 + NOSE_OPENSTACK_COLOR=1 + NOSE_OPENSTACK_RED=0.05 + NOSE_OPENSTACK_YELLOW=0.025 + NOSE_OPENSTACK_SHOW_ELAPSED=1 + NOSE_OPENSTACK_STDOUT=1 +deps = -r{toxinidir}/tools/test-requires +commands = nosetests {posargs} [tox:jenkins] downloadcache = ~/cache/pip -[testenv:jenkins26] -basepython = python2.6 -setenv = NOSE_WITH_XUNIT=1 -deps = file://{toxinidir}/.cache.bundle - -[testenv:jenkins27] -basepython = python2.7 -setenv = NOSE_WITH_XUNIT=1 -deps = file://{toxinidir}/.cache.bundle - -[testenv:jenkinspep8] -deps = file://{toxinidir}/.cache.bundle -commands = - pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantumclient \ - setup.py version.py +[testenv:pep8] +commands = pep8 --repeat --show-source --exclude=.venv,.tox,dist,doc . -[testenv:jenkinscover] -deps = file://{toxinidir}/.cache.bundle -setenv = NOSE_WITH_XUNIT=1 -commands = - nosetests --cover-erase --cover-package=quantumclient --with-xcoverage +[testenv:cover] +setenv = NOSE_WITH_COVERAGE=1 -[testenv:jenkinsvenv] -deps = file://{toxinidir}/.cache.bundle -setenv = NOSE_WITH_XUNIT=1 +[testenv:venv] commands = {posargs} From dd803f8e26f4c3a3b8f1b9e7526d0e1980b2491c Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Fri, 18 May 2012 09:02:29 +0800 Subject: [PATCH 008/135] add keystone support, new command interface, API v2.0 blueprint new-cli Bug #1001053 Implement new commands interface, ready for v2.0. adopt cliff arch. new client binary is quantumv2. After it is stable, we will remove quantum binary. Httplibs2 is used. usage: https://docs.google.com/document/d/1e_4UtnhFfgtnsB8EVB31BZKldaVzl_BlsGnGBrKmcDk/edit Patch 2: add license header Patch 3: add v1.0 support, fix show net details Patch 4: quantumclient network api v2.0 Patch 5: subnet and port commands for api v2.0, add fields selector Patch 6: add test cases Patch 7: fix interactive mode, modify according to comments and https://review.openstack.org/#/c/8366/, add two tasks to BP: noauth and openstack common Patch 8: fix log problem Patch 9: modify according to the comments by dan on patch 5 Patch 10: just trigger jenkins Patch 11: pep 1.3 fix Patch 12: cliff and prettytable to more than 0.6.0 Patch 13: change setup.py to include more packages Patch 14: pep check on jenkins Patch 15: add license text to empty __init__.py files Patch 16: fix v1.1 test cases after server changes Change-Id: Ibbbdd834371c6a023b31e4797718fc0fe9786d89 --- quantumclient/__init__.py | 102 ++++- quantumclient/cli.py | 76 ++-- quantumclient/cli_lib.py | 9 +- quantumclient/client.py | 200 +++++++++ quantumclient/common/clientmanager.py | 79 ++++ quantumclient/common/command.py | 35 ++ quantumclient/common/exceptions.py | 35 +- quantumclient/common/utils.py | 93 +++- quantumclient/quantum/__init__.py | 14 + quantumclient/quantum/client.py | 68 +++ quantumclient/quantum/v1_1/__init__.py | 62 +++ quantumclient/quantum/v1_1/interface.py | 97 +++++ quantumclient/quantum/v1_1/network.py | 268 ++++++++++++ quantumclient/quantum/v1_1/port.py | 222 ++++++++++ quantumclient/quantum/v2_0/__init__.py | 339 +++++++++++++++ quantumclient/quantum/v2_0/network.py | 99 +++++ quantumclient/quantum/v2_0/port.py | 110 +++++ quantumclient/quantum/v2_0/subnet.py | 101 +++++ quantumclient/shell.py | 383 +++++++++++++++++ quantumclient/tests/unit/stubs.py | 6 +- quantumclient/tests/unit/test_casual_args.py | 59 +++ quantumclient/tests/unit/test_cli.py | 74 +++- quantumclient/tests/unit/test_cli20.py | 290 +++++++++++++ .../tests/unit/test_cli20_network.py | 140 ++++++ quantumclient/tests/unit/test_cli20_port.py | 139 ++++++ quantumclient/tests/unit/test_cli20_subnet.py | 130 ++++++ quantumclient/tests/unit/test_clientlib.py | 2 +- quantumclient/v2_0/__init__.py | 14 + quantumclient/v2_0/client.py | 398 ++++++++++++++++++ setup.py | 5 +- tools/pip-requires | 7 + tools/test-requires | 8 +- 32 files changed, 3550 insertions(+), 114 deletions(-) create mode 100644 quantumclient/client.py create mode 100644 quantumclient/common/clientmanager.py create mode 100644 quantumclient/common/command.py create mode 100644 quantumclient/quantum/__init__.py create mode 100644 quantumclient/quantum/client.py create mode 100644 quantumclient/quantum/v1_1/__init__.py create mode 100644 quantumclient/quantum/v1_1/interface.py create mode 100644 quantumclient/quantum/v1_1/network.py create mode 100644 quantumclient/quantum/v1_1/port.py create mode 100644 quantumclient/quantum/v2_0/__init__.py create mode 100644 quantumclient/quantum/v2_0/network.py create mode 100644 quantumclient/quantum/v2_0/port.py create mode 100644 quantumclient/quantum/v2_0/subnet.py create mode 100644 quantumclient/shell.py create mode 100644 quantumclient/tests/unit/test_casual_args.py create mode 100644 quantumclient/tests/unit/test_cli20.py create mode 100644 quantumclient/tests/unit/test_cli20_network.py create mode 100644 quantumclient/tests/unit/test_cli20_port.py create mode 100644 quantumclient/tests/unit/test_cli20_subnet.py create mode 100644 quantumclient/v2_0/__init__.py create mode 100644 quantumclient/v2_0/client.py create mode 100644 tools/pip-requires diff --git a/quantumclient/__init__.py b/quantumclient/__init__.py index 461f9b853..e9cfde4de 100644 --- a/quantumclient/__init__.py +++ b/quantumclient/__init__.py @@ -30,11 +30,46 @@ from quantumclient.common import exceptions from quantumclient.common.serializer import Serializer - +from quantumclient.common import utils + +net_filters_v11_opt = [] +net_filters_v11_opt.append({'--name': + {'help': _('name filter'), }, }) +net_filters_v11_opt.append({'--op-status': + {'help': _('op-status filter'), }, }) +net_filters_v11_opt.append({'--port-op-status': + {'help': _('port-op-status filter'), }, }) +net_filters_v11_opt.append({'--port-state': + {'help': _('port-state filter'), }, }) +net_filters_v11_opt.append({'--has-attachment': + {'help': _('has-attachment filter'), }, }) +net_filters_v11_opt.append({'--attachment': + {'help': _('attachment filter'), }, }) +net_filters_v11_opt.append({'--port': + {'help': _('port filter'), }, }) + +port_filters_v11_opt = [] +port_filters_v11_opt.append({'--name': + {'help': _('name filter'), }, }) +port_filters_v11_opt.append({'--op-status': + {'help': _('op-status filter'), }, }) +port_filters_v11_opt.append({'--has-attachment': + {'help': _('has-attachment filter'), }, }) +port_filters_v11_opt.append({'--attachement': + {'help': _('attachment filter'), }, }) + +net_filters_v11 = [] +for arg in net_filters_v11_opt: + for key in arg.iterkeys(): + net_filters_v11.append(key.split('--', 2)[1]) + +port_filters_v11 = [] +for arg in port_filters_v11_opt: + for key in arg.iterkeys(): + port_filters_v11.append(key.split('--', 2)[1]) LOG = logging.getLogger('quantumclient') - AUTH_TOKEN_HEADER = "X-Auth-Token" @@ -55,12 +90,11 @@ def exception_handler_v10(status_code, error_content): 430: 'portNotFound', 431: 'requestedStateInvalid', 432: 'portInUse', - 440: 'alreadyAttached', - } + 440: 'alreadyAttached', } quantum_errors = { 400: exceptions.BadInputError, - 401: exceptions.NotAuthorized, + 401: exceptions.Unauthorized, 404: exceptions.NotFound, 420: exceptions.NetworkNotFoundClient, 421: exceptions.NetworkInUseClient, @@ -68,8 +102,7 @@ def exception_handler_v10(status_code, error_content): 431: exceptions.StateInvalidClient, 432: exceptions.PortInUseClient, 440: exceptions.AlreadyAttachedClient, - 501: NotImplementedError, - } + 501: NotImplementedError, } # Find real error type error_type = None @@ -105,7 +138,7 @@ def exception_handler_v11(status_code, error_content): 'RequestedStateInvalid': exceptions.StateInvalidClient, 'PortInUse': exceptions.PortInUseClient, 'AlreadyAttached': exceptions.AlreadyAttachedClient, - } + 'QuantumServiceFault': exceptions.QuantumClientException, } error_dict = None if isinstance(error_content, dict): @@ -156,6 +189,33 @@ def with_params(*args, **kwargs): return with_params +class APIFilterCall(object): + + def __init__(self, filters): + self.filters = filters + + def __call__(self, f): + def wrapped_f(*args, **kwargs): + """ + Temporarily sets the format and tenant for this request + """ + instance = args[0] + (format, tenant) = (instance.format, instance.tenant) + + if 'format' in kwargs: + instance.format = kwargs['format'] + if 'format' not in self.filters: + del kwargs['format'] + if 'tenant' in kwargs: + instance.tenant = kwargs['tenant'] + if 'tenant' not in self.filters: + del kwargs['tenant'] + ret = f(*args, **kwargs) + (instance.format, instance.tenant) = (format, tenant) + return ret + return wrapped_f + + class Client(object): """A base client class - derived from Glance.BaseClient""" @@ -166,13 +226,10 @@ class Client(object): "attributes": { "network": ["id", "name"], "port": ["id", "state"], - "attachment": ["id"]}, + "attachment": ["id"], }, "plurals": { "networks": "network", - "ports": "port", - }, - }, - } + "ports": "port", }, }, } # Action query strings networks_path = "/networks" @@ -298,19 +355,18 @@ def do_request(self, method, action, body=None, headers[AUTH_TOKEN_HEADER] = self.auth_token # Open connection and send request, handling SSL certs certs = {'key_file': self.key_file, 'cert_file': self.cert_file} - certs = dict((x, certs[x]) for x in certs if certs[x] != None) + certs = dict((x, certs[x]) for x in certs if certs[x] is not None) if self.use_ssl and len(certs): conn = connection_type(self.host, self.port, **certs) else: conn = connection_type(self.host, self.port) - # besides HTTP(s)Connection, we still have testConnection - if (LOG.isEnabledFor(logging.DEBUG) and - isinstance(conn, httplib.HTTPConnection)): - conn.set_debuglevel(1) - res = self._send_request(conn, method, action, body, headers) status_code = self.get_status_code(res) data = res.read() + utils.http_log(LOG, [method, action], + {'body': body, + 'headers': headers, + }, res, data) if self.logger: self.logger.debug("Quantum Client Reply (code = %s) :\n %s" % (str(status_code), data)) @@ -531,7 +587,7 @@ class ClientV11(Client): features specific to API v1.1 such as filters """ - @ApiCall + @APIFilterCall(net_filters_v11) def list_networks(self, **filters): """ Fetches a list of all networks for a tenant @@ -539,14 +595,14 @@ def list_networks(self, **filters): # Pass filters in "params" argument to do_request return self.get(self.networks_path, params=filters) - @ApiCall + @APIFilterCall(net_filters_v11) def list_networks_details(self, **filters): """ Fetches a detailed list of all networks for a tenant """ return self.get(self.networks_path + self.detail_path, params=filters) - @ApiCall + @APIFilterCall(port_filters_v11) def list_ports(self, network, **filters): """ Fetches a list of ports on a given network @@ -554,7 +610,7 @@ def list_ports(self, network, **filters): # Pass filters in "params" argument to do_request return self.get(self.ports_path % (network), params=filters) - @ApiCall + @APIFilterCall(port_filters_v11) def list_ports_details(self, network, **filters): """ Fetches a detailed list of ports on a given network diff --git a/quantumclient/cli.py b/quantumclient/cli.py index 6c14f632e..5956f7db0 100755 --- a/quantumclient/cli.py +++ b/quantumclient/cli.py @@ -29,7 +29,8 @@ from quantumclient import ClientV11 from quantumclient.common import exceptions from quantumclient.common import utils - +from quantumclient import net_filters_v11 +from quantumclient import port_filters_v11 # Configure logger for client - cli logger is a child of it # NOTE(salvatore-orlando): logger name does not map to package @@ -43,107 +44,80 @@ commands_v10 = { "list_nets": { "func": cli_lib.list_nets, - "args": ["tenant-id"], - }, + "args": ["tenant-id"], }, "list_nets_detail": { "func": cli_lib.list_nets_detail, - "args": ["tenant-id"], - }, + "args": ["tenant-id"], }, "create_net": { "func": cli_lib.create_net, - "args": ["tenant-id", "net-name"], - }, + "args": ["tenant-id", "net-name"], }, "delete_net": { "func": cli_lib.delete_net, - "args": ["tenant-id", "net-id"], - }, + "args": ["tenant-id", "net-id"], }, "show_net": { "func": cli_lib.show_net, - "args": ["tenant-id", "net-id"], - }, + "args": ["tenant-id", "net-id"], }, "show_net_detail": { "func": cli_lib.show_net_detail, - "args": ["tenant-id", "net-id"], - }, + "args": ["tenant-id", "net-id"], }, "update_net": { "func": cli_lib.update_net, - "args": ["tenant-id", "net-id", "new-name"], - }, + "args": ["tenant-id", "net-id", "new-name"], }, "list_ports": { "func": cli_lib.list_ports, - "args": ["tenant-id", "net-id"], - }, + "args": ["tenant-id", "net-id"], }, "list_ports_detail": { "func": cli_lib.list_ports_detail, - "args": ["tenant-id", "net-id"], - }, + "args": ["tenant-id", "net-id"], }, "create_port": { "func": cli_lib.create_port, - "args": ["tenant-id", "net-id"], - }, + "args": ["tenant-id", "net-id"], }, "delete_port": { "func": cli_lib.delete_port, - "args": ["tenant-id", "net-id", "port-id"], - }, + "args": ["tenant-id", "net-id", "port-id"], }, "update_port": { "func": cli_lib.update_port, - "args": ["tenant-id", "net-id", "port-id", "params"], - }, + "args": ["tenant-id", "net-id", "port-id", "params"], }, "show_port": { "func": cli_lib.show_port, - "args": ["tenant-id", "net-id", "port-id"], - }, + "args": ["tenant-id", "net-id", "port-id"], }, "show_port_detail": { "func": cli_lib.show_port_detail, - "args": ["tenant-id", "net-id", "port-id"], - }, + "args": ["tenant-id", "net-id", "port-id"], }, "plug_iface": { "func": cli_lib.plug_iface, - "args": ["tenant-id", "net-id", "port-id", "iface-id"], - }, + "args": ["tenant-id", "net-id", "port-id", "iface-id"], }, "unplug_iface": { "func": cli_lib.unplug_iface, - "args": ["tenant-id", "net-id", "port-id"], - }, + "args": ["tenant-id", "net-id", "port-id"], }, "show_iface": { "func": cli_lib.show_iface, - "args": ["tenant-id", "net-id", "port-id"], - }, - } + "args": ["tenant-id", "net-id", "port-id"], }, } commands_v11 = commands_v10.copy() commands_v11.update({ "list_nets": { "func": cli_lib.list_nets_v11, "args": ["tenant-id"], - "filters": ["name", "op-status", "port-op-status", "port-state", - "has-attachment", "attachment", "port"], - }, + "filters": net_filters_v11, }, "list_nets_detail": { "func": cli_lib.list_nets_detail_v11, "args": ["tenant-id"], - "filters": ["name", "op-status", "port-op-status", "port-state", - "has-attachment", "attachment", "port"], - }, + "filters": net_filters_v11, }, "list_ports": { "func": cli_lib.list_ports_v11, "args": ["tenant-id", "net-id"], - "filters": ["name", "op-status", "has-attachment", "attachment"], - }, + "filters": port_filters_v11, }, "list_ports_detail": { "func": cli_lib.list_ports_detail_v11, "args": ["tenant-id", "net-id"], - "filters": ["name", "op-status", "has-attachment", "attachment"], - }, - }) + "filters": port_filters_v11, }, }) commands = { '1.0': commands_v10, - '1.1': commands_v11, - } + '1.1': commands_v11, } clients = { '1.0': Client, - '1.1': ClientV11, - } + '1.1': ClientV11, } def help(version): diff --git a/quantumclient/cli_lib.py b/quantumclient/cli_lib.py index 6111bcb3d..081414bf6 100755 --- a/quantumclient/cli_lib.py +++ b/quantumclient/cli_lib.py @@ -201,8 +201,7 @@ class CmdOutputTemplate(OutputTemplate): plugged in Logical Port ID: %(port_id)s on Virtual Network: %(network_id)s for Tenant: %(tenant_id)s -""".strip(), - ) +""".strip(), ) _templates_v11 = _templates_v10.copy() _templates_v11.update(dict( @@ -236,13 +235,11 @@ class CmdOutputTemplate(OutputTemplate): interface: %(port.attachment.id)s on Virtual Network: %(network_id)s for Tenant: %(tenant_id)s -""".strip(), - )) +""".strip(), )) _templates = { '1.0': _templates_v10, - '1.1': _templates_v11 - } + '1.1': _templates_v11, } def __init__(self, cmd, data, version): super(CmdOutputTemplate, self).__init__( diff --git a/quantumclient/client.py b/quantumclient/client.py new file mode 100644 index 000000000..b892d303e --- /dev/null +++ b/quantumclient/client.py @@ -0,0 +1,200 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +try: + import json +except ImportError: + import simplejson as json +import logging +import urlparse +# Python 2.5 compat fix +if not hasattr(urlparse, 'parse_qsl'): + import cgi + urlparse.parse_qsl = cgi.parse_qsl + +import httplib2 + +from quantumclient.common import exceptions +from quantumclient.common import utils + +_logger = logging.getLogger(__name__) + + +class ServiceCatalog(object): + """Helper methods for dealing with a Keystone Service Catalog.""" + + def __init__(self, resource_dict): + self.catalog = resource_dict + + def get_token(self): + """Fetch token details fron service catalog""" + token = {'id': self.catalog['access']['token']['id'], + 'expires': self.catalog['access']['token']['expires'], } + try: + token['user_id'] = self.catalog['access']['user']['id'] + token['tenant_id'] = ( + self.catalog['access']['token']['tenant']['id']) + except: + # just leave the tenant and user out if it doesn't exist + pass + return token + + def url_for(self, attr=None, filter_value=None, + service_type='network', endpoint_type='adminURL'): + """Fetch the admin URL from the Quantum service for + a particular endpoint attribute. If none given, return + the first. See tests for sample service catalog.""" + + catalog = self.catalog['access'].get('serviceCatalog', []) + matching_endpoints = [] + for service in catalog: + if service['type'] != service_type: + continue + + endpoints = service['endpoints'] + for endpoint in endpoints: + if not filter_value or endpoint.get(attr) == filter_value: + matching_endpoints.append(endpoint) + + if not matching_endpoints: + raise exceptions.EndpointNotFound() + elif len(matching_endpoints) > 1: + raise exceptions.AmbiguousEndpoints(message=matching_endpoints) + else: + return matching_endpoints[0][endpoint_type] + + +class HTTPClient(httplib2.Http): + """Handles the REST calls and responses, include authn""" + + USER_AGENT = 'python-quantumclient' + + def __init__(self, username=None, tenant_name=None, + password=None, auth_url=None, + token=None, region_name=None, timeout=None, + endpoint_url=None, insecure=False, **kwargs): + super(HTTPClient, self).__init__(timeout=timeout) + self.username = username + self.tenant_name = tenant_name + self.password = password + self.auth_url = auth_url.rstrip('/') if auth_url else None + self.region_name = region_name + self.auth_token = token + self.content_type = 'application/json' + self.endpoint_url = endpoint_url + # httplib2 overrides + self.force_exception_to_status_code = True + self.disable_ssl_certificate_validation = insecure + + def _cs_request(self, *args, **kwargs): + kargs = {} + kargs.setdefault('headers', kwargs.get('headers', {})) + kargs['headers']['User-Agent'] = self.USER_AGENT + + if 'content_type' in kwargs: + kargs['headers']['Content-Type'] = kwargs['content_type'] + kargs['headers']['Accept'] = kwargs['content_type'] + else: + kargs['headers']['Content-Type'] = self.content_type + kargs['headers']['Accept'] = self.content_type + + if 'body' in kwargs: + kargs['body'] = kwargs['body'] + + resp, body = self.request(*args, **kargs) + + utils.http_log(_logger, args, kargs, resp, body) + status_code = self.get_status_code(resp) + if status_code == 401: + raise exceptions.Unauthorized(message=body) + elif status_code == 403: + raise exceptions.Forbidden(message=body) + return resp, body + + def do_request(self, url, method, **kwargs): + if not self.endpoint_url or not self.auth_token: + self.authenticate() + + # Perform the request once. If we get a 401 back then it + # might be because the auth token expired, so try to + # re-authenticate and try again. If it still fails, bail. + try: + kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token + resp, body = self._cs_request(self.endpoint_url + url, method, + **kwargs) + return resp, body + except exceptions.Unauthorized as ex: + if not self.endpoint_url or not self.auth_token: + self.authenticate() + resp, body = self._cs_request( + self.management_url + url, method, **kwargs) + return resp, body + else: + raise ex + + def _extract_service_catalog(self, body): + """ Set the client's service catalog from the response data. """ + self.service_catalog = ServiceCatalog(body) + try: + sc = self.service_catalog.get_token() + self.auth_token = sc['id'] + self.auth_tenant_id = sc.get('tenant_id') + self.auth_user_id = sc.get('user_id') + except KeyError: + raise exceptions.Unauthorized() + self.endpoint_url = self.service_catalog.url_for( + attr='region', filter_value=self.region_name, + endpoint_type='adminURL') + + def authenticate(self): + body = {'auth': {'passwordCredentials': + {'username': self.username, + 'password': self.password, }, + 'tenantName': self.tenant_name, }, } + + token_url = self.auth_url + "/tokens" + + # Make sure we follow redirects when trying to reach Keystone + tmp_follow_all_redirects = self.follow_all_redirects + self.follow_all_redirects = True + try: + resp, body = self._cs_request(token_url, "POST", + body=json.dumps(body), + content_type="application/json") + finally: + self.follow_all_redirects = tmp_follow_all_redirects + status_code = self.get_status_code(resp) + if status_code != 200: + raise exceptions.Unauthorized(message=body) + if body: + try: + body = json.loads(body) + except ValueError: + pass + else: + body = None + self._extract_service_catalog(body) + + def get_status_code(self, response): + """ + Returns the integer status code from the response, which + can be either a Webob.Response (used in testing) or httplib.Response + """ + if hasattr(response, 'status_int'): + return response.status_int + else: + return response.status diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py new file mode 100644 index 000000000..ee2fcf3d2 --- /dev/null +++ b/quantumclient/common/clientmanager.py @@ -0,0 +1,79 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +"""Manage access to the clients, including authenticating when needed. +""" + +import logging + +from quantumclient.common import exceptions as exc + +from quantumclient.quantum import client as quantum_client +from quantumclient.client import HTTPClient +LOG = logging.getLogger(__name__) + + +class ClientCache(object): + """Descriptor class for caching created client handles. + """ + + def __init__(self, factory): + self.factory = factory + self._handle = None + + def __get__(self, instance, owner): + # Tell the ClientManager to login to keystone + if self._handle is None: + self._handle = self.factory(instance) + return self._handle + + +class ClientManager(object): + """Manages access to API clients, including authentication. + """ + quantum = ClientCache(quantum_client.make_client) + + def __init__(self, token=None, url=None, + auth_url=None, + tenant_name=None, tenant_id=None, + username=None, password=None, + region_name=None, + api_version=None, + ): + self._token = token + self._url = url + self._auth_url = auth_url + self._tenant_name = tenant_name + self._tenant_id = tenant_id + self._username = username + self._password = password + self._region_name = region_name + self._api_version = api_version + self._service_catalog = None + return + + def initialize(self): + if not self._url: + httpclient = HTTPClient(username=self._username, + tenant_name=self._tenant_name, + password=self._password, + region_name=self._region_name, + auth_url=self._auth_url) + httpclient.authenticate() + # Populate other password flow attributes + self._token = httpclient.auth_token + self._url = httpclient.endpoint_url diff --git a/quantumclient/common/command.py b/quantumclient/common/command.py new file mode 100644 index 000000000..405f65556 --- /dev/null +++ b/quantumclient/common/command.py @@ -0,0 +1,35 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +""" +OpenStack base command +""" + +from cliff.command import Command + + +class OpenStackCommand(Command): + """Base class for OpenStack commands + """ + + api = None + + def run(self, parsed_args): + if not self.api: + return + else: + return super(OpenStackCommand, self).run(parsed_args) diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py index 33526c2d0..80f968052 100644 --- a/quantumclient/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -87,10 +87,33 @@ class AlreadyAttachedClient(QuantumClientException): pass -class NotAuthorized(QuantumClientException): +class Unauthorized(QuantumClientException): + """ + HTTP 401 - Unauthorized: bad credentials. + """ + pass + + +class Forbidden(QuantumClientException): + """ + HTTP 403 - Forbidden: your credentials don't give you access to this + resource. + """ + pass + + +class EndpointNotFound(QuantumClientException): + """Could not find Service or Region in Service Catalog.""" pass +class AmbiguousEndpoints(QuantumClientException): + """Found more than one matching endpoint in Service Catalog.""" + + def __str__(self): + return "AmbiguousEndpoints: %s" % repr(self.message) + + class QuantumCLIError(QuantumClientException): """ Exception raised when command line parsing fails """ pass @@ -120,3 +143,13 @@ class Invalid(Error): class InvalidContentType(Invalid): message = _("Invalid content type %(content_type)s.") + + +class UnsupportedVersion(Exception): + """Indicates that the user is trying to use an unsupported + version of the API""" + pass + + +class CommandError(Exception): + pass diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py index afb554e26..4044ae8af 100644 --- a/quantumclient/common/utils.py +++ b/quantumclient/common/utils.py @@ -22,7 +22,11 @@ import datetime import json +import logging import os +import sys + +from quantumclient.common import exceptions def env(*vars, **kwargs): @@ -38,12 +42,12 @@ def env(*vars, **kwargs): def to_primitive(value): - if type(value) is type([]) or type(value) is type((None,)): + if isinstance(value, list) or isinstance(value, tuple): o = [] for v in value: o.append(to_primitive(v)) return o - elif type(value) is type({}): + elif isinstance(value, dict): o = {} for k, v in value.iteritems(): o[k] = to_primitive(v) @@ -68,3 +72,88 @@ def dumps(value): def loads(s): return json.loads(s) + + +def import_class(import_str): + """Returns a class from a string including module and class + + :param import_str: a string representation of the class name + :rtype: the requested class + """ + mod_str, _sep, class_str = import_str.rpartition('.') + __import__(mod_str) + return getattr(sys.modules[mod_str], class_str) + + +def get_client_class(api_name, version, version_map): + """Returns the client class for the requested API version + + :param api_name: the name of the API, e.g. 'compute', 'image', etc + :param version: the requested API version + :param version_map: a dict of client classes keyed by version + :rtype: a client class for the requested API version + """ + try: + client_path = version_map[str(version)] + except (KeyError, ValueError): + msg = "Invalid %s client version '%s'. must be one of: %s" % ( + (api_name, version, ', '.join(version_map.keys()))) + raise exceptions.UnsupportedVersion(msg) + + return import_class(client_path) + + +def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): + """Return a tuple containing the item properties. + + :param item: a single item resource (e.g. Server, Tenant, etc) + :param fields: tuple of strings with the desired field names + :param mixed_case_fields: tuple of field names to preserve case + :param formatters: dictionary mapping field names to callables + to format the values + """ + row = [] + + for field in fields: + if field in formatters: + row.append(formatters[field](item)) + else: + if field in mixed_case_fields: + field_name = field.replace(' ', '_') + else: + field_name = field.lower().replace(' ', '_') + if not hasattr(item, field_name) and isinstance(item, dict): + data = item[field_name] + else: + data = getattr(item, field_name, '') + row.append(data) + return tuple(row) + + +def __str2bool(strbool): + if strbool is None: + return None + else: + return strbool.lower() == 'true' + + +def http_log(_logger, args, kwargs, resp, body): + if not _logger.isEnabledFor(logging.DEBUG): + return + + string_parts = ['curl -i'] + for element in args: + if element in ('GET', 'POST'): + string_parts.append(' -X %s' % element) + else: + string_parts.append(' %s' % element) + + for element in kwargs['headers']: + header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) + string_parts.append(header) + + _logger.debug("REQ: %s\n" % "".join(string_parts)) + if 'body' in kwargs and kwargs['body']: + _logger.debug("REQ BODY: %s\n" % (kwargs['body'])) + _logger.debug("RESP:%s\n", resp) + _logger.debug("RESP BODY:%s\n", body) diff --git a/quantumclient/quantum/__init__.py b/quantumclient/quantum/__init__.py new file mode 100644 index 000000000..63c3905e2 --- /dev/null +++ b/quantumclient/quantum/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/quantumclient/quantum/client.py b/quantumclient/quantum/client.py new file mode 100644 index 000000000..e2c059fc0 --- /dev/null +++ b/quantumclient/quantum/client.py @@ -0,0 +1,68 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging +import urlparse + + +from quantumclient.common import utils + +LOG = logging.getLogger(__name__) + +API_NAME = 'network' +API_VERSIONS = { + '1.0': 'quantumclient.Client', + '1.1': 'quantumclient.ClientV11', + '2.0': 'quantumclient.v2_0.client.Client', +} + + +def make_client(instance): + """Returns an identity service client. + """ + quantum_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS, + ) + instance.initialize() + url = instance._url + url = url.rstrip("/") + client_full_name = (quantum_client.__module__ + "." + + quantum_client.__name__) + LOG.debug("we are using client: %s", client_full_name) + v1x = (client_full_name == API_VERSIONS['1.1'] or + client_full_name == API_VERSIONS['1.0']) + if v1x: + magic_tuple = urlparse.urlsplit(url) + scheme, netloc, path, query, frag = magic_tuple + host = magic_tuple.hostname + port = magic_tuple.port + use_ssl = scheme.lower().startswith('https') + client = quantum_client(host=host, port=port, use_ssl=use_ssl) + client.auth_token = instance._token + client.logger = LOG + return client + else: + client = quantum_client(username=instance._username, + tenant_name=instance._tenant_name, + password=instance._password, + region_name=instance._region_name, + auth_url=instance._auth_url, + endpoint_url=url, + token=instance._token) + return client diff --git a/quantumclient/quantum/v1_1/__init__.py b/quantumclient/quantum/v1_1/__init__.py new file mode 100644 index 000000000..e8836d8ac --- /dev/null +++ b/quantumclient/quantum/v1_1/__init__.py @@ -0,0 +1,62 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.common import command +from quantumclient.common import utils + + +class QuantumCommand(command.OpenStackCommand): + api = 'network' + log = logging.getLogger(__name__ + '.QuantumCommand') + + def get_parser(self, prog_name): + parser = super(QuantumCommand, self).get_parser(prog_name) + parser.add_argument( + '--request-format', + help=_('the xml or json request format'), + default='json', + choices=['json', 'xml', ], ) + parser.add_argument( + 'tenant_id', metavar='tenant-id', + help=_('the owner tenant ID'), ) + return parser + + +class QuantumPortCommand(QuantumCommand): + api = 'network' + log = logging.getLogger(__name__ + '.QuantumPortCommand') + + def get_parser(self, prog_name): + parser = super(QuantumPortCommand, self).get_parser(prog_name) + parser.add_argument( + 'net_id', metavar='net-id', + help=_('the owner network ID'), ) + return parser + + +class QuantumInterfaceCommand(QuantumPortCommand): + api = 'network' + log = logging.getLogger(__name__ + '.QuantumInterfaceCommand') + + def get_parser(self, prog_name): + parser = super(QuantumInterfaceCommand, self).get_parser(prog_name) + parser.add_argument( + 'port_id', metavar='port-id', + help=_('the owner Port ID'), ) + return parser diff --git a/quantumclient/quantum/v1_1/interface.py b/quantumclient/quantum/v1_1/interface.py new file mode 100644 index 000000000..955e459b2 --- /dev/null +++ b/quantumclient/quantum/v1_1/interface.py @@ -0,0 +1,97 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from cliff import show + +from quantumclient.quantum.v1_1 import QuantumInterfaceCommand + + +class PlugInterface(QuantumInterfaceCommand): + """Plug interface to a given port""" + + api = 'network' + log = logging.getLogger(__name__ + '.PlugInterface') + + def get_parser(self, prog_name): + parser = super(PlugInterface, self).get_parser(prog_name) + parser.add_argument( + 'iface_id', metavar='iface-id', + help='_(ID of the interface to plug)', ) + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + + data = {'attachment': {'id': '%s' % parsed_args.iface_id, }, } + quantum_client.attach_resource(parsed_args.net_id, + parsed_args.port_id, + data) + print >>self.app.stdout, (_('Plugged interface %(interfaceid)s' + ' into Logical Port %(portid)s') + % {'interfaceid': parsed_args.iface_id, + 'portid': parsed_args.port_id, }) + + return + + +class UnPlugInterface(QuantumInterfaceCommand): + """Unplug interface from a given port""" + + api = 'network' + log = logging.getLogger(__name__ + '.UnPlugInterface') + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + + quantum_client.detach_resource(parsed_args.net_id, parsed_args.port_id) + print >>self.app.stdout, ( + _('Unplugged interface on Logical Port %(portid)s') + % {'portid': parsed_args.port_id, }) + + return + + +class ShowInterface(QuantumInterfaceCommand, show.ShowOne): + """Show interface on a given port""" + + api = 'network' + log = logging.getLogger(__name__ + '.ShowInterface') + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + + iface = quantum_client.show_port_attachment( + parsed_args.net_id, + parsed_args.port_id)['attachment'] + + if iface: + if 'id' not in iface: + iface['id'] = '' + else: + iface = {'': ''} + return zip(*sorted(iface.iteritems())) diff --git a/quantumclient/quantum/v1_1/network.py b/quantumclient/quantum/v1_1/network.py new file mode 100644 index 000000000..162976221 --- /dev/null +++ b/quantumclient/quantum/v1_1/network.py @@ -0,0 +1,268 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging +import itertools + +from cliff import lister +from cliff import show + +from quantumclient.common import exceptions +from quantumclient.common import utils +from quantumclient import net_filters_v11_opt +from quantumclient.quantum.v1_1 import QuantumCommand + + +class ListNetwork(QuantumCommand, lister.Lister): + """List networks that belong to a given tenant""" + + api = 'network' + log = logging.getLogger(__name__ + '.ListNetwork') + + def get_parser(self, prog_name): + parser = super(ListNetwork, self).get_parser(prog_name) + parser.add_argument( + '--show-details', + help='show detailed info', + action='store_true', + default=False, ) + for net_filter in net_filters_v11_opt: + option_key = net_filter.keys()[0] + option_defs = net_filter.get(option_key) + parser.add_argument(option_key, **option_defs) + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + search_opts = { + 'tenant': parsed_args.tenant_id, } + for net_filter in net_filters_v11_opt: + option_key = net_filter.keys()[0] + arg = option_key[2:] + arg = arg.replace('-', '_') + arg_value = getattr(parsed_args, arg, None) + if arg_value is not None: + search_opts.update({option_key[2:]: arg_value, }) + + self.log.debug('search options: %s', search_opts) + quantum_client.format = parsed_args.request_format + columns = ('ID', ) + data = None + if parsed_args.show_details: + data = quantum_client.list_networks_details(**search_opts) + # dict: {u'networks': [{u'op-status': u'UP', + # u'id': u'7a068b68-c736-42ab-9e43-c9d83c57627e', + # u'name': u'private'}]} + columns = ('ID', 'op-status', 'name', ) + else: + data = quantum_client.list_networks(**search_opts) + # {u'networks': [{u'id': + # u'7a068b68-c736-42ab-9e43-c9d83c57627e'}]} + networks = [] + if 'networks' in data: + networks = data['networks'] + + return (columns, + (utils.get_item_properties( + s, columns, formatters={}, ) for s in networks), ) + + +def _format_attachment(port): + # attachment {u'id': u'gw-7a068b68-c7'} + try: + return ('attachment' in port and port['attachment'] and + 'id' in port['attachment'] and + port['attachment']['id'] or '') + except Exception: + return '' + + +class ShowNetwork(QuantumCommand, show.ShowOne): + """Show information of a given network""" + + api = 'network' + log = logging.getLogger(__name__ + '.ShowNetwork') + + def get_parser(self, prog_name): + parser = super(ShowNetwork, self).get_parser(prog_name) + parser.add_argument( + 'net_id', metavar='net-id', + help='ID of network to display') + + parser.add_argument( + '--show-details', + help='show detailed info of networks', + action='store_true', + default=False, ) + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + data = None + if parsed_args.show_details: + data = quantum_client.show_network_details(parsed_args.net_id) + else: + data = quantum_client.show_network(parsed_args.net_id) + # {u'network': {u'op-status': u'UP', 'xmlns': + # u'http://openstack.org/quantum/api/v1.1', u'id': + # u'7a068b68-c736-42ab-9e43-c9d83c57627e', u'name': u'private'}} + network = {} + ports = None + network = 'network' in data and data['network'] or None + if network: + ports = network.pop('ports', None) + column_names, data = zip(*sorted(network.iteritems())) + if not parsed_args.columns: + columns_to_include = column_names + else: + columns_to_include = [c for c in column_names + if c in parsed_args.columns] + # Set up argument to compress() + selector = [(c in columns_to_include) + for c in column_names] + data = list(itertools.compress(data, selector)) + formatter = self.formatters[parsed_args.formatter] + formatter.emit_one(columns_to_include, data, + self.app.stdout, parsed_args) + if ports: + print >>self.app.stdout, _('Network Ports:') + columns = ('op-status', 'state', 'id', 'attachment', ) + column_names, data = (columns, (utils.get_item_properties( + s, columns, formatters={'attachment': _format_attachment}, ) + for s in ports), ) + if not parsed_args.columns: + columns_to_include = column_names + data_gen = data + else: + columns_to_include = [c for c in column_names + if c in parsed_args.columns] + if not columns_to_include: + raise ValueError( + 'No recognized column names in %s' % + str(parsed_args.columns)) + # Set up argument to compress() + selector = [(c in columns_to_include) + for c in column_names] + # Generator expression to only return the parts of a row + # of data that the user has expressed interest in + # seeing. We have to convert the compress() output to a + # list so the table formatter can ask for its length. + data_gen = (list(itertools.compress(row, selector)) + for row in data) + formatter = self.formatters[parsed_args.formatter] + formatter.emit_list(columns_to_include, + data_gen, self.app.stdout, parsed_args) + + return ('', []) + + +class CreateNetwork(QuantumCommand, show.ShowOne): + """Create a network for a given tenant""" + + api = 'network' + log = logging.getLogger(__name__ + '.CreateNetwork') + + def get_parser(self, prog_name): + parser = super(CreateNetwork, self).get_parser(prog_name) + parser.add_argument( + 'net_name', metavar='net-name', + help='Name of network to create') + + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + body = {'network': {'name': parsed_args.net_name, }, } + network = quantum_client.create_network(body) + # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} + info = 'network' in network and network['network'] or None + if info: + print >>self.app.stdout, _('Created a new Virtual Network:') + else: + info = {'': ''} + return zip(*sorted(info.iteritems())) + + +class DeleteNetwork(QuantumCommand): + """Delete a given network""" + + api = 'network' + log = logging.getLogger(__name__ + '.DeleteNetwork') + + def get_parser(self, prog_name): + parser = super(DeleteNetwork, self).get_parser(prog_name) + parser.add_argument( + 'net_id', metavar='net-id', + help='ID of network to delete') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + quantum_client.delete_network(parsed_args.net_id) + print >>self.app.stdout, (_('Deleted Network: %(networkid)s') + % {'networkid': parsed_args.net_id}) + return + + +class UpdateNetwork(QuantumCommand): + """Update network's information""" + + api = 'network' + log = logging.getLogger(__name__ + '.UpdateNetwork') + + def get_parser(self, prog_name): + parser = super(UpdateNetwork, self).get_parser(prog_name) + parser.add_argument( + 'net_id', metavar='net-id', + help='ID of network to update') + + parser.add_argument( + 'newvalues', metavar='field=newvalue[,field2=newvalue2]', + help='new values for the network') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + field_values = parsed_args.newvalues + data = {'network': {}} + for kv in field_values.split(","): + try: + k, v = kv.split("=") + data['network'][k] = v + except ValueError: + raise exceptions.CommandError( + "malformed new values (field=newvalue): %s" % kv) + + data['network']['id'] = parsed_args.net_id + quantum_client.update_network(parsed_args.net_id, data) + print >>self.app.stdout, ( + _('Updated Network: %(networkid)s') % + {'networkid': parsed_args.net_id}) + return diff --git a/quantumclient/quantum/v1_1/port.py b/quantumclient/quantum/v1_1/port.py new file mode 100644 index 000000000..e8d1a48be --- /dev/null +++ b/quantumclient/quantum/v1_1/port.py @@ -0,0 +1,222 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from cliff import lister +from cliff import show + +from quantumclient.common import exceptions +from quantumclient.common import utils +from quantumclient import port_filters_v11_opt +from quantumclient.quantum.v1_1 import QuantumPortCommand + + +class ListPort(QuantumPortCommand, lister.Lister): + """List ports that belong to a given tenant's network""" + + api = 'network' + log = logging.getLogger(__name__ + '.ListPort') + + def get_parser(self, prog_name): + parser = super(ListPort, self).get_parser(prog_name) + + parser.add_argument( + '--show-details', + help='show detailed info of networks', + action='store_true', + default=False, ) + for item in port_filters_v11_opt: + option_key = item.keys()[0] + option_defs = item.get(option_key) + parser.add_argument(option_key, **option_defs) + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + search_opts = { + 'tenant': parsed_args.tenant_id, } + for item in port_filters_v11_opt: + option_key = item.keys()[0] + arg = option_key[2:] + arg = arg.replace('-', '_') + arg_value = getattr(parsed_args, arg, None) + if arg_value is not None: + search_opts.update({option_key[2:]: arg_value, }) + + self.log.debug('search options: %s', search_opts) + + columns = ('ID', ) + data = None + if parsed_args.show_details: + data = quantum_client.list_ports_details( + parsed_args.net_id, **search_opts) + # dict:dict: {u'ports': [{ + # u'op-status': u'DOWN', + # u'state': u'ACTIVE', + # u'id': u'479ba2b7-042f-44b9-aefb-b1550e114454'}, ]} + columns = ('ID', 'op-status', 'state') + else: + data = quantum_client.list_ports(parsed_args.net_id, **search_opts) + # {u'ports': [{u'id': u'7a068b68-c736-42ab-9e43-c9d83c57627e'}]} + ports = [] + if 'ports' in data: + ports = data['ports'] + + return (columns, + (utils.get_item_properties( + s, columns, formatters={}, ) for s in ports), ) + + +class ShowPort(QuantumPortCommand, show.ShowOne): + """Show information of a given port""" + + api = 'network' + log = logging.getLogger(__name__ + '.ShowPort') + + def get_parser(self, prog_name): + parser = super(ShowPort, self).get_parser(prog_name) + parser.add_argument( + 'port_id', metavar='port-id', + help='ID of the port to show', ) + parser.add_argument( + '--show-details', + help='show detailed info', + action='store_true', + default=False, ) + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + data = None + if parsed_args.show_details: + data = quantum_client.show_port_details( + parsed_args.net_id, parsed_args.port_id) + # {u'port': {u'op-status': u'DOWN', u'state': u'ACTIVE', + # u'id': u'479ba2b7-042f-44b9-aefb- + # b1550e114454', u'attachment': {u'id': u'gw-7a068b68-c7'}}} + else: + data = quantum_client.show_port( + parsed_args.net_id, parsed_args.port_id) + # {u'port': {u'op-status': u'DOWN', u'state': u'ACTIVE', + # u'id': u'479ba2b7-042f-44b9-aefb-b1550e114454'}} + + port = 'port' in data and data['port'] or None + if port: + attachment = 'attachment' in port and port['attachment'] or None + if attachment: + interface = attachment['id'] + port.update({'attachment': interface}) + return zip(*sorted(port.iteritems())) + return ('', []) + + +class CreatePort(QuantumPortCommand, show.ShowOne): + """Create port for a given network""" + + api = 'network' + log = logging.getLogger(__name__ + '.CreatePort') + + def get_parser(self, prog_name): + parser = super(CreatePort, self).get_parser(prog_name) + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + data = quantum_client.create_port(parsed_args.net_id) + # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} + info = 'port' in data and data['port'] or None + if info: + print >>self.app.stdout, _('Created a new Logical Port:') + else: + info = {'': ''} + return zip(*sorted(info.iteritems())) + + +class DeletePort(QuantumPortCommand): + """Delete a given port""" + + api = 'network' + log = logging.getLogger(__name__ + '.DeletePort') + + def get_parser(self, prog_name): + parser = super(DeletePort, self).get_parser(prog_name) + parser.add_argument( + 'port_id', metavar='port-id', + help='ID of the port to delete', ) + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + quantum_client.delete_port(parsed_args.net_id, parsed_args.port_id) + print >>self.app.stdout, (_('Deleted Logical Port: %(portid)s') % + {'portid': parsed_args.port_id}) + return + + +class UpdatePort(QuantumPortCommand): + """Update information of a given port""" + + api = 'network' + log = logging.getLogger(__name__ + '.UpdatePort') + + def get_parser(self, prog_name): + parser = super(UpdatePort, self).get_parser(prog_name) + parser.add_argument( + 'port_id', metavar='port-id', + help='ID of the port to update', ) + + parser.add_argument( + 'newvalues', metavar='field=newvalue[,field2=newvalue2]', + help='new values for the Port') + + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.app.client_manager.quantum + quantum_client.tenant = parsed_args.tenant_id + quantum_client.format = parsed_args.request_format + field_values = parsed_args.newvalues + data = {'port': {}} + for kv in field_values.split(","): + try: + k, v = kv.split("=") + data['port'][k] = v + except ValueError: + raise exceptions.CommandError( + "malformed new values (field=newvalue): %s" % kv) + data['network_id'] = parsed_args.net_id + data['port']['id'] = parsed_args.port_id + + quantum_client.update_port( + parsed_args.net_id, parsed_args.port_id, data) + print >>self.app.stdout, (_('Updated Logical Port: %(portid)s') % + {'portid': parsed_args.port_id}) + return diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py new file mode 100644 index 000000000..89c1c7a63 --- /dev/null +++ b/quantumclient/quantum/v2_0/__init__.py @@ -0,0 +1,339 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import argparse +import logging + +from cliff import lister +from cliff import show + +from quantumclient.common import command +from quantumclient.common import exceptions +from quantumclient.common import utils + + +def add_show_list_common_argument(parser): + parser.add_argument( + '-D', '--show-details', + help='show detailed info', + action='store_true', + default=False, ) + parser.add_argument( + '-F', '--fields', + help='specify the field(s) to be returned by server,' + ' can be repeated', + action='append', + default=[], ) + + +def add_extra_argument(parser, name, _help): + parser.add_argument( + name, + nargs=argparse.REMAINDER, + help=_help + ': --key1 [type=int|bool|...] value ' + '[--key2 [type=int|bool|...] value ...]') + + +def parse_args_to_dict(values_specs): + '''It is used to analyze the extra command options to command. + + Besides known options and arguments, our commands also support user to + put more options to the end of command line. For example, + list_nets -- --tag x y --key1 value1, where '-- --tag x y --key1 value1' + is extra options to our list_nets. This feature can support V2.0 API's + fields selection and filters. For example, to list networks which has name + 'test4', we can have list_nets -- --name=test4. + + value spec is: --key type=int|bool|... value. Type is one of Python + built-in types. By default, type is string. The key without value is + a bool option. Key with two values will be a list option. + + ''' + # -- is a pseudo argument + if values_specs and values_specs[0] == '--': + del values_specs[0] + _options = {} + current_arg = None + _values_specs = [] + _value_number = 0 + current_item = None + for _item in values_specs: + if _item.startswith('--'): + if current_arg is not None: + if _value_number > 1: + current_arg.update({'nargs': '+'}) + elif _value_number == 0: + current_arg.update({'action': 'store_true'}) + _temp = _item + if "=" in _item: + _item = _item.split('=')[0] + if _item in _options: + raise exceptions.CommandError( + "duplicated options %s" % ' '.join(values_specs)) + else: + _options.update({_item: {}}) + current_arg = _options[_item] + _item = _temp + elif _item.startswith('type='): + if current_arg is not None: + _type_str = _item.split('=', 2)[1] + current_arg.update({'type': eval(_type_str)}) + if _type_str == 'bool': + current_arg.update({'type': utils.__str2bool}) + continue + else: + raise exceptions.CommandError( + "invalid values_specs %s" % ' '.join(values_specs)) + + if not _item.startswith('--'): + if not current_item or '=' in current_item: + raise exceptions.CommandError( + "Invalid values_specs %s" % ' '.join(values_specs)) + _value_number += 1 + elif _item.startswith('--'): + current_item = _item + if '=' in current_item: + _value_number = 1 + else: + _value_number = 0 + _values_specs.append(_item) + if current_arg is not None: + if _value_number > 1: + current_arg.update({'nargs': '+'}) + elif _value_number == 0: + current_arg.update({'action': 'store_true'}) + _parser = argparse.ArgumentParser(add_help=False) + for opt, optspec in _options.iteritems(): + _parser.add_argument(opt, **optspec) + _args = _parser.parse_args(_values_specs) + result_dict = {} + for opt in _options.iterkeys(): + _opt = opt.split('--', 2)[1] + _value = getattr(_args, _opt.replace('-', '_')) + if _value is not None: + result_dict.update({_opt: _value}) + return result_dict + + +class QuantumCommand(command.OpenStackCommand): + api = 'network' + log = logging.getLogger(__name__ + '.QuantumCommand') + + def get_client(self): + return self.app.client_manager.quantum + + def get_parser(self, prog_name): + parser = super(QuantumCommand, self).get_parser(prog_name) + parser.add_argument( + '--request-format', + help=_('the xml or json request format'), + default='json', + choices=['json', 'xml', ], ) + + return parser + + +class CreateCommand(QuantumCommand, show.ShowOne): + """Create a resource for a given tenant + + """ + + api = 'network' + resource = None + log = None + + def get_parser(self, prog_name): + parser = super(CreateCommand, self).get_parser(prog_name) + parser.add_argument( + '--tenant-id', metavar='tenant-id', + help=_('the owner tenant ID'), ) + self.add_known_arguments(parser) + add_extra_argument(parser, 'value_specs', + 'new values for the %s' % self.resource) + return parser + + def add_known_arguments(self, parser): + pass + + def args2body(self, parsed_args): + return {} + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + body = self.args2body(parsed_args) + _extra_values = parse_args_to_dict(parsed_args.value_specs) + body[self.resource].update(_extra_values) + obj_creator = getattr(quantum_client, + "create_%s" % self.resource) + data = obj_creator(body) + # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} + info = self.resource in data and data[self.resource] or None + if info: + print >>self.app.stdout, _('Created a new %s:' % self.resource) + else: + info = {'': ''} + return zip(*sorted(info.iteritems())) + + +class UpdateCommand(QuantumCommand): + """Update resource's information + """ + + api = 'network' + resource = None + log = None + + def get_parser(self, prog_name): + parser = super(UpdateCommand, self).get_parser(prog_name) + parser.add_argument( + 'id', metavar='%s-id' % self.resource, + help='ID of %s to update' % self.resource) + add_extra_argument(parser, 'value_specs', + 'new values for the %s' % self.resource) + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + value_specs = parsed_args.value_specs + if not value_specs: + raise exceptions.CommandError( + "Must specify new values to update %s" % self.resource) + data = {self.resource: parse_args_to_dict(value_specs)} + obj_updator = getattr(quantum_client, + "update_%s" % self.resource) + obj_updator(parsed_args.id, data) + print >>self.app.stdout, ( + _('Updated %(resource)s: %(id)s') % + {'id': parsed_args.id, 'resource': self.resource}) + return + + +class DeleteCommand(QuantumCommand): + """Delete a given resource + + """ + + api = 'network' + resource = None + log = None + + def get_parser(self, prog_name): + parser = super(DeleteCommand, self).get_parser(prog_name) + parser.add_argument( + 'id', metavar='%s-id' % self.resource, + help='ID of %s to delete' % self.resource) + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + obj_deleter = getattr(quantum_client, + "delete_%s" % self.resource) + obj_deleter(parsed_args.id) + print >>self.app.stdout, (_('Deleted %(resource)s: %(id)s') + % {'id': parsed_args.id, + 'resource': self.resource}) + return + + +class ListCommand(QuantumCommand, lister.Lister): + """List resourcs that belong to a given tenant + + """ + + api = 'network' + resource = None + log = None + _formatters = None + + def get_parser(self, prog_name): + parser = super(ListCommand, self).get_parser(prog_name) + add_show_list_common_argument(parser) + add_extra_argument(parser, 'filter_specs', 'filters options') + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.get_client() + search_opts = parse_args_to_dict(parsed_args.filter_specs) + + self.log.debug('search options: %s', search_opts) + quantum_client.format = parsed_args.request_format + fields = parsed_args.fields + extra_fields = search_opts.get('fields', []) + if extra_fields: + if isinstance(extra_fields, list): + fields.extend(extra_fields) + else: + fields.append(extra_fields) + if fields: + search_opts.update({'fields': fields}) + if parsed_args.show_details: + search_opts.update({'verbose': 'True'}) + obj_lister = getattr(quantum_client, + "list_%ss" % self.resource) + + data = obj_lister(**search_opts) + info = [] + collection = self.resource + "s" + if collection in data: + info = data[collection] + _columns = len(info) > 0 and sorted(info[0].keys()) or [] + return (_columns, (utils.get_item_properties( + s, _columns, formatters=self._formatters, ) + for s in info), ) + + +class ShowCommand(QuantumCommand, show.ShowOne): + """Show information of a given resource + + """ + + api = 'network' + resource = None + log = None + + def get_parser(self, prog_name): + parser = super(ShowCommand, self).get_parser(prog_name) + add_show_list_common_argument(parser) + parser.add_argument( + 'id', metavar='%s-id' % self.resource, + help='ID of %s to look up' % self.resource) + + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + params = {} + if parsed_args.show_details: + params = {'verbose': 'True'} + if parsed_args.fields: + params = {'fields': parsed_args.fields} + obj_showor = getattr(quantum_client, + "show_%s" % self.resource) + data = obj_showor(parsed_args.id, **params) + if self.resource in data: + return zip(*sorted(data[self.resource].iteritems())) + else: + return None diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py new file mode 100644 index 000000000..646612375 --- /dev/null +++ b/quantumclient/quantum/v2_0/network.py @@ -0,0 +1,99 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum.v2_0 import CreateCommand +from quantumclient.quantum.v2_0 import DeleteCommand +from quantumclient.quantum.v2_0 import ListCommand +from quantumclient.quantum.v2_0 import UpdateCommand +from quantumclient.quantum.v2_0 import ShowCommand + + +def _format_subnets(network): + try: + return '\n'.join(network['subnets']) + except Exception: + return '' + + +class ListNetwork(ListCommand): + """List networks that belong to a given tenant + + Sample: list_nets -D -- --name=test4 --tag a b + """ + + resource = 'network' + log = logging.getLogger(__name__ + '.ListNetwork') + _formatters = {'subnets': _format_subnets, } + + +class ShowNetwork(ShowCommand): + """Show information of a given network + + Sample: show_net -D + """ + + resource = 'network' + log = logging.getLogger(__name__ + '.ShowNetwork') + + +class CreateNetwork(CreateCommand): + """Create a network for a given tenant + + Sample create_net --tenant-id xxx --admin-state-down --tag x y + """ + + resource = 'network' + log = logging.getLogger(__name__ + '.CreateNetwork') + + def add_known_arguments(self, parser): + parser.add_argument( + '--admin-state-down', + default=True, action='store_false', + help='Set Admin State Up to false') + parser.add_argument( + 'name', metavar='name', + help='Name of network to create') + + def args2body(self, parsed_args): + body = {'network': { + 'name': parsed_args.name, + 'admin_state_up': parsed_args.admin_state_down, }, } + if parsed_args.tenant_id: + body['network'].update({'tenant_id': parsed_args.tenant_id}) + return body + + +class DeleteNetwork(DeleteCommand): + """Delete a given network + + Sample: delete_net + """ + + log = logging.getLogger(__name__ + '.DeleteNetwork') + resource = 'network' + + +class UpdateNetwork(UpdateCommand): + """Update network's information + + Sample: update_net --name=test --admin_state_up type=bool True + """ + + log = logging.getLogger(__name__ + '.UpdateNetwork') + resource = 'network' diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py new file mode 100644 index 000000000..9b8af129e --- /dev/null +++ b/quantumclient/quantum/v2_0/port.py @@ -0,0 +1,110 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum.v2_0 import CreateCommand +from quantumclient.quantum.v2_0 import DeleteCommand +from quantumclient.quantum.v2_0 import ListCommand +from quantumclient.quantum.v2_0 import ShowCommand +from quantumclient.quantum.v2_0 import UpdateCommand + + +def _format_fixed_ips(port): + try: + return '\n'.join(port['fixed_ips']) + except Exception: + return '' + + +class ListPort(ListCommand): + """List networks that belong to a given tenant + + Sample: list_ports -D -- --name=test4 --tag a b + """ + + resource = 'port' + log = logging.getLogger(__name__ + '.ListPort') + _formatters = {'fixed_ips': _format_fixed_ips, } + + +class ShowPort(ShowCommand): + """Show information of a given port + + Sample: show_port -D + """ + + resource = 'port' + log = logging.getLogger(__name__ + '.ShowPort') + + +class CreatePort(CreateCommand): + """Create a port for a given tenant + + Sample create_port --tenant-id xxx --admin-state-down \ + --mac_address mac --device_id deviceid + + """ + + resource = 'port' + log = logging.getLogger(__name__ + '.CreatePort') + + def add_known_arguments(self, parser): + parser.add_argument( + '--admin-state-down', + default=True, action='store_false', + help='set admin state up to false') + parser.add_argument( + '--mac-address', + help='mac address of port') + parser.add_argument( + '--device-id', + help='device id of this port') + parser.add_argument( + 'network_id', + help='Network id of this port belongs to') + + def args2body(self, parsed_args): + body = {'port': {'admin_state_up': parsed_args.admin_state_down, + 'network_id': parsed_args.network_id, }, } + if parsed_args.mac_address: + body['port'].update({'mac_address': parsed_args.mac_address}) + if parsed_args.device_id: + body['port'].update({'device_id': parsed_args.device_id}) + if parsed_args.tenant_id: + body['port'].update({'tenant_id': parsed_args.tenant_id}) + return body + + +class DeletePort(DeleteCommand): + """Delete a given port + + Sample: delete_port + """ + + resource = 'port' + log = logging.getLogger(__name__ + '.DeletePort') + + +class UpdatePort(UpdateCommand): + """Update port's information + + Sample: update_port --name=test --admin_state_up type=bool True + """ + + resource = 'port' + log = logging.getLogger(__name__ + '.UpdatePort') diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py new file mode 100644 index 000000000..07b09acc8 --- /dev/null +++ b/quantumclient/quantum/v2_0/subnet.py @@ -0,0 +1,101 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum.v2_0 import CreateCommand +from quantumclient.quantum.v2_0 import DeleteCommand +from quantumclient.quantum.v2_0 import ListCommand +from quantumclient.quantum.v2_0 import ShowCommand +from quantumclient.quantum.v2_0 import UpdateCommand + + +class ListSubnet(ListCommand): + """List networks that belong to a given tenant + + Sample: list_subnets -D -- --name=test4 --tag a b + """ + + resource = 'subnet' + log = logging.getLogger(__name__ + '.ListSubnet') + _formatters = {} + + +class ShowSubnet(ShowCommand): + """Show information of a given subnet + + Sample: show_subnet -D + """ + + resource = 'subnet' + log = logging.getLogger(__name__ + '.ShowSubnet') + + +class CreateSubnet(CreateCommand): + """Create a subnet for a given tenant + + Sample create_subnet --tenant-id xxx --ip-version 4\ + --tag x y --otherfield value + """ + + resource = 'subnet' + log = logging.getLogger(__name__ + '.CreateSubnet') + + def add_known_arguments(self, parser): + parser.add_argument('--ip-version', type=int, + default=4, choices=[4, 6], + help='IP version with default 4') + parser.add_argument( + '--gateway', metavar='gateway', + help='gateway ip of this subnet') + parser.add_argument( + 'network_id', + help='Network id of this subnet belongs to') + parser.add_argument( + 'cidr', metavar='cidr', + help='cidr of subnet to create') + + def args2body(self, parsed_args): + body = {'subnet': {'cidr': parsed_args.cidr, + 'network_id': parsed_args.network_id, + 'ip_version': parsed_args.ip_version, }, } + if parsed_args.gateway: + body['subnet'].update({'gateway_ip': parsed_args.gateway}) + if parsed_args.tenant_id: + body['subnet'].update({'tenant_id': parsed_args.tenant_id}) + return body + + +class DeleteSubnet(DeleteCommand): + """Delete a given subnet + + Sample: delete_subnet + """ + + resource = 'subnet' + log = logging.getLogger(__name__ + '.DeleteSubnet') + + +class UpdateSubnet(UpdateCommand): + """Update subnet's information + + Sample: + update_subnet --name=test --admin_state_up type=bool True + """ + + resource = 'subnet' + log = logging.getLogger(__name__ + '.UpdateSubnet') diff --git a/quantumclient/shell.py b/quantumclient/shell.py new file mode 100644 index 000000000..975a1459f --- /dev/null +++ b/quantumclient/shell.py @@ -0,0 +1,383 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +""" +Command-line interface to the Quantum APIs +""" +import argparse +import gettext +import itertools +import logging +import os +import sys + +from cliff.app import App +from cliff.commandmanager import CommandManager + +from quantumclient.common import clientmanager +from quantumclient.common import exceptions as exc +from quantumclient.common import utils + + +gettext.install('quantum', unicode=1) +VERSION = '2.0' + + +def env(*vars, **kwargs): + """Search for the first defined of possibly many env vars + + Returns the first environment variable defined in vars, or + returns the default defined in kwargs. + + """ + for v in vars: + value = os.environ.get(v, None) + if value: + return value + return kwargs.get('default', '') + +COMMAND_V1 = { + 'list_nets': utils.import_class( + 'quantumclient.quantum.v1_1.network.ListNetwork'), + 'show_net': utils.import_class( + 'quantumclient.quantum.v1_1.network.ShowNetwork'), + 'create_net': utils.import_class( + 'quantumclient.quantum.v1_1.network.CreateNetwork'), + 'delete_net': utils.import_class( + 'quantumclient.quantum.v1_1.network.DeleteNetwork'), + 'update_net': utils.import_class( + 'quantumclient.quantum.v1_1.network.UpdateNetwork'), + + 'list_ports': utils.import_class( + 'quantumclient.quantum.v1_1.port.ListPort'), + 'show_port': utils.import_class( + 'quantumclient.quantum.v1_1.port.ShowPort'), + 'create_port': utils.import_class( + 'quantumclient.quantum.v1_1.port.CreatePort'), + 'delete_port': utils.import_class( + 'quantumclient.quantum.v1_1.port.DeletePort'), + 'update_port': utils.import_class( + 'quantumclient.quantum.v1_1.port.UpdatePort'), + + 'plug_iface': utils.import_class( + 'quantumclient.quantum.v1_1.interface.PlugInterface'), + 'unplug_iface': utils.import_class( + 'quantumclient.quantum.v1_1.interface.UnPlugInterface'), + 'show_iface': utils.import_class( + 'quantumclient.quantum.v1_1.interface.ShowInterface'), } +COMMAND_V2 = { + 'list_nets': utils.import_class( + 'quantumclient.quantum.v2_0.network.ListNetwork'), + 'show_net': utils.import_class( + 'quantumclient.quantum.v2_0.network.ShowNetwork'), + 'create_net': utils.import_class( + 'quantumclient.quantum.v2_0.network.CreateNetwork'), + 'delete_net': utils.import_class( + 'quantumclient.quantum.v2_0.network.DeleteNetwork'), + 'update_net': utils.import_class( + 'quantumclient.quantum.v2_0.network.UpdateNetwork'), + 'list_subnets': utils.import_class( + 'quantumclient.quantum.v2_0.subnet.ListSubnet'), + 'show_subnet': utils.import_class( + 'quantumclient.quantum.v2_0.subnet.ShowSubnet'), + 'create_subnet': utils.import_class( + 'quantumclient.quantum.v2_0.subnet.CreateSubnet'), + 'delete_subnet': utils.import_class( + 'quantumclient.quantum.v2_0.subnet.DeleteSubnet'), + 'update_subnet': utils.import_class( + 'quantumclient.quantum.v2_0.subnet.UpdateSubnet'), + 'list_ports': utils.import_class( + 'quantumclient.quantum.v2_0.port.ListPort'), + 'show_port': utils.import_class( + 'quantumclient.quantum.v2_0.port.ShowPort'), + 'create_port': utils.import_class( + 'quantumclient.quantum.v2_0.port.CreatePort'), + 'delete_port': utils.import_class( + 'quantumclient.quantum.v2_0.port.DeletePort'), + 'update_port': utils.import_class( + 'quantumclient.quantum.v2_0.port.UpdatePort'), } + +COMMANDS = {'1.0': COMMAND_V1, + '1.1': COMMAND_V1, + '2.0': COMMAND_V2, } + + +class HelpAction(argparse.Action): + """Provide a custom action so the -h and --help options + to the main app will print a list of the commands. + + The commands are determined by checking the CommandManager + instance, passed in as the "default" value for the action. + """ + def __call__(self, parser, namespace, values, option_string=None): + app = self.default + parser.print_help(app.stdout) + app.stdout.write('\nCommands for API v%s:\n' % app.api_version) + command_manager = app.command_manager + for name, ep in sorted(command_manager): + factory = ep.load() + cmd = factory(self, None) + one_liner = cmd.get_description().split('\n')[0] + app.stdout.write(' %-13s %s\n' % (name, one_liner)) + sys.exit(0) + + +class QuantumShell(App): + + CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' + + log = logging.getLogger(__name__) + + def __init__(self, apiversion): + super(QuantumShell, self).__init__( + description=__doc__.strip(), + version=VERSION, + command_manager=CommandManager('quantum.cli'), ) + for k, v in COMMANDS[apiversion].items(): + self.command_manager.add_command(k, v) + + # This is instantiated in initialize_app() only when using + # password flow auth + self.auth_client = None + self.api_version = apiversion + + def build_option_parser(self, description, version): + """Return an argparse option parser for this application. + + Subclasses may override this method to extend + the parser with more global options. + + :param description: full description of the application + :paramtype description: str + :param version: version number for the application + :paramtype version: str + """ + parser = argparse.ArgumentParser( + description=description, + add_help=False, ) + parser.add_argument( + '--version', + action='version', + version='%(prog)s {0}'.format(version), ) + parser.add_argument( + '-v', '--verbose', + action='count', + dest='verbose_level', + default=self.DEFAULT_VERBOSE_LEVEL, + help='Increase verbosity of output. Can be repeated.', ) + parser.add_argument( + '-q', '--quiet', + action='store_const', + dest='verbose_level', + const=0, + help='suppress output except warnings and errors', ) + parser.add_argument( + '-H', '--Help', + action=HelpAction, + nargs=0, + default=self, # tricky + help="show this help message and exit", ) + parser.add_argument( + '--debug', + default=False, + action='store_true', + help='show tracebacks on errors', ) + # Global arguments + parser.add_argument( + '--os-auth-url', metavar='', + default=env('OS_AUTH_URL'), + help='Authentication URL (Env: OS_AUTH_URL)') + + parser.add_argument( + '--os-tenant-name', metavar='', + default=env('OS_TENANT_NAME'), + help='Authentication tenant name (Env: OS_TENANT_NAME)') + + parser.add_argument( + '--os-username', metavar='', + default=utils.env('OS_USERNAME'), + help='Authentication username (Env: OS_USERNAME)') + + parser.add_argument( + '--os-password', metavar='', + default=utils.env('OS_PASSWORD'), + help='Authentication password (Env: OS_PASSWORD)') + + parser.add_argument( + '--os-region-name', metavar='', + default=env('OS_REGION_NAME'), + help='Authentication region name (Env: OS_REGION_NAME)') + + parser.add_argument( + '--os-quantum-api-version', + metavar='', + default=env('OS_QUANTUM_API_VERSION', default='2.0'), + help='QAUNTUM API version, default=2.0 ' + '(Env: OS_QUANTUM_API_VERSION)') + + parser.add_argument( + '--os-token', metavar='', + default=env('OS_TOKEN'), + help='Defaults to env[OS_TOKEN]') + + parser.add_argument( + '--os-url', metavar='', + default=env('OS_URL'), + help='Defaults to env[OS_URL]') + + return parser + + def run(self, argv): + """Equivalent to the main program for the application. + + :param argv: input arguments and options + :paramtype argv: list of str + """ + try: + self.options, remainder = self.parser.parse_known_args(argv) + self.configure_logging() + self.interactive_mode = not remainder + self.initialize_app(remainder) + except Exception as err: + if self.options.debug: + self.log.exception(err) + raise + else: + self.log.error(err) + return 1 + result = 1 + if self.interactive_mode: + _argv = [sys.argv[0]] + sys.argv = _argv + result = self.interact() + else: + result = self.run_subcommand(remainder) + return result + + def authenticate_user(self): + """Make sure the user has provided all of the authentication + info we need. + """ + + if self.options.os_token or self.options.os_url: + # Token flow auth takes priority + if not self.options.os_token: + raise exc.CommandError( + "You must provide a token via" + " either --os-token or env[OS_TOKEN]") + + if not self.options.os_url: + raise exc.CommandError( + "You must provide a service URL via" + " either --os-url or env[OS_URL]") + + else: + # Validate password flow auth + if not self.options.os_username: + raise exc.CommandError( + "You must provide a username via" + " either --os-username or env[OS_USERNAME]") + + if not self.options.os_password: + raise exc.CommandError( + "You must provide a password via" + " either --os-password or env[OS_PASSWORD]") + + if not (self.options.os_tenant_name): + raise exc.CommandError( + "You must provide a tenant_name via" + " either --os-tenant-name or via env[OS_TENANT_NAME]") + + if not self.options.os_auth_url: + raise exc.CommandError( + "You must provide an auth url via" + " either --os-auth-url or via env[OS_AUTH_URL]") + + self.client_manager = clientmanager.ClientManager( + token=self.options.os_token, + url=self.options.os_url, + auth_url=self.options.os_auth_url, + tenant_name=self.options.os_tenant_name, + username=self.options.os_username, + password=self.options.os_password, + region_name=self.options.os_region_name, + api_version=self.api_version, ) + return + + def initialize_app(self, argv): + """Global app init bits: + + * set up API versions + * validate authentication info + """ + + super(QuantumShell, self).initialize_app(argv) + + self.api_version = { + 'network': self.options.os_quantum_api_version, } + + # If the user is not asking for help, make sure they + # have given us auth. + cmd_name = None + if argv: + cmd_info = self.command_manager.find_command(argv) + cmd_factory, cmd_name, sub_argv = cmd_info + if self.interactive_mode or cmd_name != 'help': + self.authenticate_user() + + def clean_up(self, cmd, result, err): + self.log.debug('clean_up %s', cmd.__class__.__name__) + if err: + self.log.debug('got an error: %s', err) + + +def itertools_compressdef(data, selectors): + # patch 2.6 compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F + return (d for d, s in itertools.izip(data, selectors) if s) + + +def main(argv=sys.argv[1:]): + apiVersion = None + versionFlag = False + for argitem in argv: + if argitem.startswith('--os-quantum-api-version='): + apiVersion = argitem.split('=', 2)[1] + break + elif '--os-quantum-api-version' == argitem: + versionFlag = True + elif versionFlag: + apiVersion = argitem + break + if apiVersion and apiVersion not in COMMANDS.keys(): + print ("Invalid API version or API version '%s' is not supported" % + apiVersion) + sys.exit(1) + if not apiVersion: + apiVersion = env('OS_QUANTUM_API_VERSION', default='2.0') + try: + if not getattr(itertools, 'compress', None): + setattr(itertools, 'compress', itertools_compressdef) + return QuantumShell(apiVersion).run(argv) + except exc.QuantumClientException: + return 1 + except Exception as e: + print e + return 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/quantumclient/tests/unit/stubs.py b/quantumclient/tests/unit/stubs.py index 3295753fb..9bf82024d 100644 --- a/quantumclient/tests/unit/stubs.py +++ b/quantumclient/tests/unit/stubs.py @@ -17,6 +17,7 @@ from quantum import api as server +from quantum.openstack.common import cfg from quantum.tests.unit import testlib_api @@ -40,9 +41,8 @@ class FakeHTTPConnection: def __init__(self, _1, _2): # Ignore host and port parameters self._req = None - plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin' - options = dict(plugin_provider=plugin) - self._api = server.APIRouterV11(options) + cfg.CONF.core_plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin' + self._api = server.APIRouterV11() def request(self, method, action, body, headers): # TODO: remove version prefix from action! diff --git a/quantumclient/tests/unit/test_casual_args.py b/quantumclient/tests/unit/test_casual_args.py new file mode 100644 index 000000000..695aa0aff --- /dev/null +++ b/quantumclient/tests/unit/test_casual_args.py @@ -0,0 +1,59 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import unittest + +from quantumclient.common import exceptions +from quantumclient.quantum import v2_0 as quantumV20 + + +class CLITestArgs(unittest.TestCase): + + def test_empty(self): + _mydict = quantumV20.parse_args_to_dict([]) + self.assertEqual({}, _mydict) + + def test_default_bool(self): + _specs = ['--my_bool', '--arg1', 'value1'] + _mydict = quantumV20.parse_args_to_dict(_specs) + self.assertTrue(_mydict['my_bool']) + + def test_bool_true(self): + _specs = ['--my-bool', 'type=bool', 'true', '--arg1', 'value1'] + _mydict = quantumV20.parse_args_to_dict(_specs) + self.assertTrue(_mydict['my-bool']) + + def test_bool_false(self): + _specs = ['--my_bool', 'type=bool', 'false', '--arg1', 'value1'] + _mydict = quantumV20.parse_args_to_dict(_specs) + self.assertFalse(_mydict['my_bool']) + + def test_nargs(self): + _specs = ['--tag', 'x', 'y', '--arg1', 'value1'] + _mydict = quantumV20.parse_args_to_dict(_specs) + self.assertTrue('x' in _mydict['tag']) + self.assertTrue('y' in _mydict['tag']) + + def test_badarg(self): + _specs = ['--tag=t', 'x', 'y', '--arg1', 'value1'] + self.assertRaises(exceptions.CommandError, + quantumV20.parse_args_to_dict, _specs) + + def test_arg(self): + _specs = ['--tag=t', '--arg1', 'value1'] + self.assertEqual('value1', + quantumV20.parse_args_to_dict(_specs)['arg1']) diff --git a/quantumclient/tests/unit/test_cli.py b/quantumclient/tests/unit/test_cli.py index 6cf618c47..8876dbc9b 100644 --- a/quantumclient/tests/unit/test_cli.py +++ b/quantumclient/tests/unit/test_cli.py @@ -26,12 +26,12 @@ import unittest from quantum import api as server -from quantum.db import api as db +from quantumclient import ClientV11 from quantumclient import cli_lib as cli -from quantumclient import Client +from quantum.db import api as db +from quantum.openstack.common import cfg from quantumclient.tests.unit import stubs as client_stubs - LOG = logging.getLogger('quantumclient.tests.test_cli') API_VERSION = "1.1" FORMAT = 'json' @@ -41,21 +41,19 @@ class CLITest(unittest.TestCase): def setUp(self): """Prepare the test environment""" - options = {} - options['plugin_provider'] = ( - 'quantum.plugins.sample.SamplePlugin.FakePlugin') #TODO: make the version of the API router configurable - self.api = server.APIRouterV11(options) + cfg.CONF.core_plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin' + self.api = server.APIRouterV11() self.tenant_id = "test_tenant" self.network_name_1 = "test_network_1" self.network_name_2 = "test_network_2" self.version = API_VERSION # Prepare client and plugin manager - self.client = Client(tenant=self.tenant_id, - format=FORMAT, - testingStub=client_stubs.FakeHTTPConnection, - version=self.version) + self.client = ClientV11(tenant=self.tenant_id, + format=FORMAT, + testingStub=client_stubs.FakeHTTPConnection, + version=self.version) # Redirect stdout self.fake_stdout = client_stubs.FakeStdout() sys.stdout = self.fake_stdout @@ -69,7 +67,7 @@ def _verify_list_networks(self): # Verification - get raw result from db nw_list = db.network_list(self.tenant_id) networks = [{'id': nw.uuid, 'name': nw.name} - for nw in nw_list] + for nw in nw_list] # Fill CLI template output = cli.prepare_output('list_nets', self.tenant_id, @@ -92,6 +90,23 @@ def _verify_list_networks_details(self): # Must add newline at the end to match effect of print call self.assertEquals(self.fake_stdout.make_string(), output + '\n') + def _verify_list_networks_details_name_filter(self, name): + # Verification - get raw result from db + nw_list = db.network_list(self.tenant_id) + nw_filtered = [] + for nw in nw_list: + if nw.name == name: + nw_filtered.append(nw) + networks = [dict(id=nw.uuid, name=nw.name) for nw in nw_filtered] + # Fill CLI template + output = cli.prepare_output('list_nets_detail', + self.tenant_id, + dict(networks=networks), + self.version) + # Verify! + # Must add newline at the end to match effect of print call + self.assertEquals(self.fake_stdout.make_string(), output + '\n') + def _verify_create_network(self): # Verification - get raw result from db nw_list = db.network_list(self.tenant_id) @@ -160,14 +175,10 @@ def _verify_show_network_details(self): port_list = db.port_list(nw.uuid) if not port_list: network['ports'] = [ - { - 'id': '', - 'state': '', - 'attachment': { - 'id': '', - }, - }, - ] + {'id': '', + 'state': '', + 'attachment': { + 'id': '', }, }, ] else: network['ports'] = [] for port in port_list: @@ -175,9 +186,7 @@ def _verify_show_network_details(self): 'id': port.uuid, 'state': port.state, 'attachment': { - 'id': port.interface_id or '', - }, - }) + 'id': port.interface_id or '', }, }) # Fill CLI template output = cli.prepare_output('show_net_detail', @@ -416,6 +425,25 @@ def test_list_networks_details_v11(self): LOG.debug(self.fake_stdout.content) self._verify_list_networks_details() + def test_list_networks_details_v11_name_filter(self): + try: + # Pre-populate data for testing using db api + db.network_create(self.tenant_id, self.network_name_1) + db.network_create(self.tenant_id, self.network_name_2) + #TODO: test filters + cli.list_nets_detail_v11(self.client, + self.tenant_id, + self.version, + {'name': self.network_name_1, }) + except: + LOG.exception("Exception caught: %s", sys.exc_info()) + self.fail("test_list_networks_details_v11 failed due to " + + "an exception") + + LOG.debug("Operation completed. Verifying result") + LOG.debug(self.fake_stdout.content) + self._verify_list_networks_details_name_filter(self.network_name_1) + def test_create_network(self): try: cli.create_net(self.client, diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py new file mode 100644 index 000000000..32601f9de --- /dev/null +++ b/quantumclient/tests/unit/test_cli20.py @@ -0,0 +1,290 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys +import unittest + +import mox +from mox import ContainsKeyValue +from mox import Comparator + +from quantumclient.v2_0.client import Client + +API_VERSION = "2.0" +FORMAT = 'json' +TOKEN = 'testtoken' +ENDURL = 'localurl' + + +class FakeStdout: + + def __init__(self): + self.content = [] + + def write(self, text): + self.content.append(text) + + def make_string(self): + result = '' + for line in self.content: + result = result + line + return result + + +class MyResp(object): + def __init__(self, status): + self.status = status + + +class MyApp(object): + def __init__(self, _stdout): + self.stdout = _stdout + + +class MyComparator(Comparator): + def __init__(self, lhs, client): + self.lhs = lhs + self.client = client + + def _com_dict(self, lhs, rhs): + if len(lhs) != len(rhs): + return False + for key, value in lhs.iteritems(): + if key not in rhs: + return False + rhs_value = rhs[key] + if not self._com(value, rhs_value): + return False + return True + + def _com_list(self, lhs, rhs): + if len(lhs) != len(rhs): + return False + for lhs_value in lhs: + if lhs_value not in rhs: + return False + return True + + def _com(self, lhs, rhs): + if lhs is None: + return rhs is None + if isinstance(lhs, dict): + if not isinstance(rhs, dict): + return False + return self._com_dict(lhs, rhs) + if isinstance(lhs, list): + if not isinstance(rhs, list): + return False + return self._com_list(lhs, rhs) + if isinstance(lhs, tuple): + if not isinstance(rhs, tuple): + return False + return self._com_list(lhs, rhs) + return lhs == rhs + + def equals(self, rhs): + if self.client: + rhs = self.client.deserialize(rhs, 200) + return self._com(self.lhs, rhs) + + def __repr__(self): + return str(self.lhs) + + +class CLITestV20Base(unittest.TestCase): + + def _url(self, path, query=None): + _url_str = self.endurl + "/v" + API_VERSION + path + "." + FORMAT + return query and _url_str + "?" + query or _url_str + + def setUp(self): + """Prepare the test environment""" + self.mox = mox.Mox() + self.endurl = ENDURL + self.client = Client(token=TOKEN, endpoint_url=self.endurl) + self.fake_stdout = FakeStdout() + sys.stdout = self.fake_stdout + + def tearDown(self): + """Clear the test environment""" + sys.stdout = sys.__stdout__ + + def _test_create_resource(self, resource, cmd, + name, myid, args, + position_names, position_values, tenant_id=None, + tags=None, admin_state_up=True): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + if resource == 'subnet': + body = {resource: {}, } + else: + body = {resource: {'admin_state_up': admin_state_up, }, } + if tenant_id: + body[resource].update({'tenant_id': tenant_id}) + if tags: + body[resource].update({'tags': tags}) + for i in xrange(len(position_names)): + body[resource].update({position_names[i]: position_values[i]}) + ress = {resource: + {'id': myid, + 'name': name, }, } + resstr = self.client.serialize(ress) + # url method body + path = getattr(self.client, resource + "s_path") + self.client.httpclient.request( + self._url(path), 'POST', + body=MyComparator(body, self.client), + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(200), + resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser('create_' + resource) + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertTrue(myid, _str) + self.assertTrue(name, _str) + + def _test_list_resources(self, resources, cmd, detail=False, tags=[], + fields_1=[], fields_2=[]): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + reses = {resources: [{'id': 'myid1', }, + {'id': 'myid2', }, ], } + resstr = self.client.serialize(reses) + # url method body + query = "" + args = detail and ['-D', ] or [] + if fields_1: + for field in fields_1: + args.append('--fields') + args.append(field) + + if tags: + args.append('--') + args.append("--tag") + for tag in tags: + if query: + query += "&tag=" + tag + else: + query = "tag=" + tag + args.append(tag) + if (not tags) and fields_2: + args.append('--') + if fields_2: + args.append("--fields") + for field in fields_2: + args.append(field) + if detail: + query = query and query + '&verbose=True' or 'verbose=True' + fields_1.extend(fields_2) + for field in fields_1: + if query: + query += "&fields=" + field + else: + query = "fields=" + field + path = getattr(self.client, resources + "_path") + self.client.httpclient.request( + self._url(path, query), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(200), resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("list_" + resources) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertTrue('myid1' in _str) + + def _test_update_resource(self, resource, cmd, myid, args, extrafields): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + body = {resource: extrafields} + path = getattr(self.client, resource + "_path") + self.client.httpclient.request( + self._url(path % myid), 'PUT', + body=MyComparator(body, self.client), + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(204), None)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("update_" + resource) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertTrue(myid in _str) + + def _test_show_resource(self, resource, cmd, myid, args, fields=[]): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + query = None + for field in fields: + if query: + query += "&fields=" + field + else: + query = "fields=" + field + resnetworks = {resource: + {'id': myid, + 'name': 'myname', }, } + resstr = self.client.serialize(resnetworks) + path = getattr(self.client, resource + "_path") + self.client.httpclient.request( + self._url(path % myid, query), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(200), resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("show_" + resource) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertTrue(myid in _str) + self.assertTrue('myname' in _str) + + def _test_delete_resource(self, resource, cmd, myid, args): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + path = getattr(self.client, resource + "_path") + self.client.httpclient.request( + self._url(path % myid), 'DELETE', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(204), None)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("delete_" + resource) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertTrue(myid in _str) diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py new file mode 100644 index 000000000..809419aeb --- /dev/null +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -0,0 +1,140 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from quantumclient.common import exceptions +from quantumclient.tests.unit.test_cli20 import CLITestV20Base +from quantumclient.tests.unit.test_cli20 import MyApp +from quantumclient.quantum.v2_0.network import CreateNetwork +from quantumclient.quantum.v2_0.network import ListNetwork +from quantumclient.quantum.v2_0.network import UpdateNetwork +from quantumclient.quantum.v2_0.network import ShowNetwork +from quantumclient.quantum.v2_0.network import DeleteNetwork + + +class CLITestV20Network(CLITestV20Base): + def test_create_network(self): + """ create_net myname""" + resource = 'network' + cmd = CreateNetwork(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + args = [name, ] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_network_tenant(self): + """create_net --tenant-id tenantid myname""" + resource = 'network' + cmd = CreateNetwork(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + args = ['--tenant-id', 'tenantid', name] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_network_tags(self): + """ create_net myname --tags a b""" + resource = 'network' + cmd = CreateNetwork(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + args = [name, '--tags', 'a', 'b'] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tags=['a', 'b']) + + def test_create_network_state(self): + """ create_net --admin-state-down myname""" + resource = 'network' + cmd = CreateNetwork(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + args = ['--admin-state-down', name, ] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + admin_state_up=False) + + def test_list_nets_detail(self): + """list_nets -D""" + resources = "networks" + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_list_nets_tags(self): + """list_nets -- --tags a b""" + resources = "networks" + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, tags=['a', 'b']) + + def test_list_nets_detail_tags(self): + """list_nets -D -- --tags a b""" + resources = "networks" + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) + + def test_list_nets_fields(self): + """list_nets --fields a --fields b -- --fields c d""" + resources = "networks" + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + fields_1=['a', 'b'], fields_2=['c', 'd']) + + def test_update_network_exception(self): + """ update_net myid""" + resource = 'network' + cmd = UpdateNetwork(MyApp(sys.stdout), None) + self.assertRaises(exceptions.CommandError, self._test_update_resource, + resource, cmd, 'myid', ['myid'], {}) + + def test_update_network(self): + """ update_net myid --name myname --tags a b""" + resource = 'network' + cmd = UpdateNetwork(MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'myname', + '--tags', 'a', 'b'], + {'name': 'myname', 'tags': ['a', 'b'], } + ) + + def test_show_network(self): + """ show_net --fields id --fields name myid """ + resource = 'network' + cmd = ShowNetwork(MyApp(sys.stdout), None) + myid = 'myid' + args = ['--fields', 'id', '--fields', 'name', myid] + self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) + + def test_delete_network(self): + """ + delete_net myid + """ + resource = 'network' + cmd = DeleteNetwork(MyApp(sys.stdout), None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py new file mode 100644 index 000000000..febb69e37 --- /dev/null +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -0,0 +1,139 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from quantumclient.tests.unit.test_cli20 import CLITestV20Base +from quantumclient.tests.unit.test_cli20 import MyApp +from quantumclient.quantum.v2_0.port import CreatePort +from quantumclient.quantum.v2_0.port import ListPort +from quantumclient.quantum.v2_0.port import UpdatePort +from quantumclient.quantum.v2_0.port import ShowPort +from quantumclient.quantum.v2_0.port import DeletePort + + +class CLITestV20Port(CLITestV20Base): + + def test_create_port(self): + """ create_port netid""" + resource = 'port' + cmd = CreatePort(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + args = [netid] + position_names = ['network_id'] + position_values = [] + position_values.extend([netid]) + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_port_full(self): + """ create_port --mac-address mac --device-id deviceid netid""" + resource = 'port' + cmd = CreatePort(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + args = ['--mac-address', 'mac', '--device-id', 'deviceid', netid] + position_names = ['network_id', 'mac_address', 'device_id'] + position_values = [netid, 'mac', 'deviceid'] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_port_tenant(self): + """create_port --tenant-id tenantid netid""" + resource = 'port' + cmd = CreatePort(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + args = ['--tenant-id', 'tenantid', netid, ] + position_names = ['network_id'] + position_values = [] + position_values.extend([netid]) + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_port_tags(self): + """ create_port netid mac_address device_id --tags a b""" + resource = 'port' + cmd = CreatePort(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + args = [netid, '--tags', 'a', 'b'] + position_names = ['network_id'] + position_values = [] + position_values.extend([netid]) + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tags=['a', 'b']) + + def test_list_ports_detail(self): + """list_ports -D""" + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_list_ports_tags(self): + """list_ports -- --tags a b""" + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, tags=['a', 'b']) + + def test_list_ports_detail_tags(self): + """list_ports -D -- --tags a b""" + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) + + def test_list_ports_fields(self): + """list_ports --fields a --fields b -- --fields c d""" + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + fields_1=['a', 'b'], fields_2=['c', 'd']) + + def test_update_port(self): + """ update_port myid --name myname --tags a b""" + resource = 'port' + cmd = UpdatePort(MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'myname', + '--tags', 'a', 'b'], + {'name': 'myname', 'tags': ['a', 'b'], } + ) + + def test_show_port(self): + """ show_port --fields id --fields name myid """ + resource = 'port' + cmd = ShowPort(MyApp(sys.stdout), None) + myid = 'myid' + args = ['--fields', 'id', '--fields', 'name', myid] + self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) + + def test_delete_port(self): + """ + delete_port myid + """ + resource = 'port' + cmd = DeletePort(MyApp(sys.stdout), None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py new file mode 100644 index 000000000..08385eed9 --- /dev/null +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -0,0 +1,130 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from quantumclient.tests.unit.test_cli20 import CLITestV20Base +from quantumclient.tests.unit.test_cli20 import MyApp +from quantumclient.quantum.v2_0.subnet import CreateSubnet +from quantumclient.quantum.v2_0.subnet import ListSubnet +from quantumclient.quantum.v2_0.subnet import UpdateSubnet +from quantumclient.quantum.v2_0.subnet import ShowSubnet +from quantumclient.quantum.v2_0.subnet import DeleteSubnet + + +class CLITestV20Subnet(CLITestV20Base): + + def test_create_subnet(self): + """ create_subnet --gateway gateway netid cidr""" + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'cidrvalue' + gateway = 'gatewayvalue' + args = ['--gateway', gateway, netid, cidr] + position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] + position_values = [4, ] + position_values.extend([netid, cidr, gateway]) + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_subnet_tenant(self): + """create_subnet --tenant-id tenantid netid cidr""" + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant-id', 'tenantid', netid, cidr] + position_names = ['ip_version', 'network_id', 'cidr'] + position_values = [4, ] + position_values.extend([netid, cidr]) + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_subnet_tags(self): + """ create_subnet netid cidr --tags a b""" + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = [netid, cidr, '--tags', 'a', 'b'] + position_names = ['ip_version', 'network_id', 'cidr'] + position_values = [4, ] + position_values.extend([netid, cidr]) + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tags=['a', 'b']) + + def test_list_subnets_detail(self): + """list_subnets -D""" + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_list_subnets_tags(self): + """list_subnets -- --tags a b""" + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, tags=['a', 'b']) + + def test_list_subnets_detail_tags(self): + """list_subnets -D -- --tags a b""" + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) + + def test_list_subnets_fields(self): + """list_subnets --fields a --fields b -- --fields c d""" + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + fields_1=['a', 'b'], fields_2=['c', 'd']) + + def test_update_subnet(self): + """ update_subnet myid --name myname --tags a b""" + resource = 'subnet' + cmd = UpdateSubnet(MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'myname', + '--tags', 'a', 'b'], + {'name': 'myname', 'tags': ['a', 'b'], } + ) + + def test_show_subnet(self): + """ show_subnet --fields id --fields name myid """ + resource = 'subnet' + cmd = ShowSubnet(MyApp(sys.stdout), None) + myid = 'myid' + args = ['--fields', 'id', '--fields', 'name', myid] + self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) + + def test_delete_subnet(self): + """ + delete_subnet myid + """ + resource = 'subnet' + cmd = DeleteSubnet(MyApp(sys.stdout), None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) diff --git a/quantumclient/tests/unit/test_clientlib.py b/quantumclient/tests/unit/test_clientlib.py index 3b88f1521..28f78d041 100644 --- a/quantumclient/tests/unit/test_clientlib.py +++ b/quantumclient/tests/unit/test_clientlib.py @@ -349,7 +349,7 @@ def _test_attach_resource(self, tenant=TENANT_1, "PUT", "networks/001/ports/001/attachment", data=["001", "001", - {'resource': {'id': '1234'}}], + {'resource': {'id': '1234'}}], params={'tenant': tenant, 'format': format}) LOG.debug("_test_attach_resource - tenant:%s " diff --git a/quantumclient/v2_0/__init__.py b/quantumclient/v2_0/__init__.py new file mode 100644 index 000000000..63c3905e2 --- /dev/null +++ b/quantumclient/v2_0/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py new file mode 100644 index 000000000..4273d8259 --- /dev/null +++ b/quantumclient/v2_0/client.py @@ -0,0 +1,398 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import httplib +import logging +import time +import urllib + +from quantumclient.client import HTTPClient +from quantumclient.common import exceptions +from quantumclient.common.serializer import Serializer + +_logger = logging.getLogger(__name__) + + +def exception_handler_v20(status_code, error_content): + """ Exception handler for API v2.0 client + + This routine generates the appropriate + Quantum exception according to the contents of the + response body + + :param status_code: HTTP error status code + :param error_content: deserialized body of error response + """ + + quantum_errors = { + 'NetworkNotFound': exceptions.NetworkNotFoundClient, + 'NetworkInUse': exceptions.NetworkInUseClient, + 'PortNotFound': exceptions.PortNotFoundClient, + 'RequestedStateInvalid': exceptions.StateInvalidClient, + 'PortInUse': exceptions.PortInUseClient, + 'AlreadyAttached': exceptions.AlreadyAttachedClient, } + + error_dict = None + if isinstance(error_content, dict): + error_dict = error_content.get('QuantumError') + # Find real error type + bad_quantum_error_flag = False + if error_dict: + # If QuantumError key is found, it will definitely contain + # a 'message' and 'type' keys? + try: + error_type = error_dict['type'] + error_message = (error_dict['message'] + "\n" + + error_dict['detail']) + except Exception: + bad_quantum_error_flag = True + if not bad_quantum_error_flag: + ex = None + try: + # raise the appropriate error! + ex = quantum_errors[error_type](message=error_message) + ex.args = ([dict(status_code=status_code, + message=error_message)], ) + except Exception: + pass + if ex: + raise ex + else: + raise exceptions.QuantumClientException(message=error_dict) + else: + message = None + if isinstance(error_content, dict): + message = error_content.get('message', None) + if message: + raise exceptions.QuantumClientException(message=message) + + # If we end up here the exception was not a quantum error + msg = "%s-%s" % (status_code, error_content) + raise exceptions.QuantumClientException(message=msg) + + +class APIParamsCall(object): + """A Decorator to add support for format and tenant overriding + and filters + """ + def __init__(self, function): + self.function = function + + def __get__(self, instance, owner): + def with_params(*args, **kwargs): + _format = instance.format + if 'format' in kwargs: + instance.format = kwargs['format'] + ret = self.function(instance, *args, **kwargs) + instance.forma = _format + return ret + return with_params + + +class Client(object): + """Client for the OpenStack Quantum v2.0 API. + + :param string username: Username for authentication. (optional) + :param string password: Password for authentication. (optional) + :param string token: Token for authentication. (optional) + :param string tenant_name: Tenant name. (optional) + :param string auth_url: Keystone service endpoint for authorization. + :param string region_name: Name of a region to select when choosing an + endpoint from the service catalog. + :param string endpoint_url: A user-supplied endpoint URL for the quantum + service. Lazy-authentication is possible for API + service calls if endpoint is set at + instantiation.(optional) + :param integer timeout: Allows customization of the timeout for client + http requests. (optional) + :param insecure: ssl certificate validation. (optional) + + Example:: + + >>> from quantumclient.v2_0 import client + >>> quantum = client.Client(username=USER, + password=PASS, + tenant_name=TENANT_NAME, + auth_url=KEYSTONE_URL) + + >>> nets = quantum.list_nets() + ... + + """ + + #Metadata for deserializing xml + _serialization_metadata = { + "application/xml": { + "attributes": { + "network": ["id", "name"], + "port": ["id", "mac_address"], + "subnet": ["id", "prefix"]}, + "plurals": { + "networks": "network", + "ports": "port", + "subnets": "subnet", }, }, } + + networks_path = "/networks" + network_path = "/networks/%s" + ports_path = "/ports" + port_path = "/ports/%s" + subnets_path = "/subnets" + subnet_path = "/subnets/%s" + + @APIParamsCall + def list_ports(self, **_params): + """ + Fetches a list of all networks for a tenant + """ + # Pass filters in "params" argument to do_request + return self.get(self.ports_path, params=_params) + + @APIParamsCall + def show_port(self, port, **_params): + """ + Fetches information of a certain network + """ + return self.get(self.port_path % (port), params=_params) + + @APIParamsCall + def create_port(self, body=None): + """ + Creates a new port + """ + return self.post(self.ports_path, body=body) + + @APIParamsCall + def update_port(self, port, body=None): + """ + Updates a port + """ + return self.put(self.port_path % (port), body=body) + + @APIParamsCall + def delete_port(self, port): + """ + Deletes the specified port + """ + return self.delete(self.port_path % (port)) + + @APIParamsCall + def list_networks(self, **_params): + """ + Fetches a list of all networks for a tenant + """ + # Pass filters in "params" argument to do_request + return self.get(self.networks_path, params=_params) + + @APIParamsCall + def show_network(self, network, **_params): + """ + Fetches information of a certain network + """ + return self.get(self.network_path % (network), params=_params) + + @APIParamsCall + def create_network(self, body=None): + """ + Creates a new network + """ + return self.post(self.networks_path, body=body) + + @APIParamsCall + def update_network(self, network, body=None): + """ + Updates a network + """ + return self.put(self.network_path % (network), body=body) + + @APIParamsCall + def delete_network(self, network): + """ + Deletes the specified network + """ + return self.delete(self.network_path % (network)) + + @APIParamsCall + def list_subnets(self, **_params): + """ + Fetches a list of all networks for a tenant + """ + return self.get(self.subnets_path, params=_params) + + @APIParamsCall + def show_subnet(self, subnet, **_params): + """ + Fetches information of a certain subnet + """ + return self.get(self.subnet_path % (subnet), params=_params) + + @APIParamsCall + def create_subnet(self, body=None): + """ + Creates a new subnet + """ + return self.post(self.subnets_path, body=body) + + @APIParamsCall + def update_subnet(self, subnet, body=None): + """ + Updates a subnet + """ + return self.put(self.subnet_path % (subnet), body=body) + + @APIParamsCall + def delete_subnet(self, subnet): + """ + Deletes the specified subnet + """ + return self.delete(self.subnet_path % (subnet)) + + def __init__(self, **kwargs): + """ Initialize a new client for the Quantum v2.0 API. """ + super(Client, self).__init__() + self.httpclient = HTTPClient(**kwargs) + self.version = '2.0' + self.format = 'json' + self.action_prefix = "/v%s" % (self.version) + self.retries = 0 + self.retry_interval = 1 + + def _handle_fault_response(self, status_code, response_body): + # Create exception with HTTP status code and message + error_message = response_body + _logger.debug("Error message: %s", error_message) + # Add deserialized error message to exception arguments + try: + des_error_body = Serializer().deserialize(error_message, + self.content_type()) + except: + # If unable to deserialized body it is probably not a + # Quantum error + des_error_body = {'message': error_message} + # Raise the appropriate exception + exception_handler_v20(status_code, des_error_body) + + def do_request(self, method, action, body=None, headers=None, params=None): + # Add format and tenant_id + action += ".%s" % self.format + action = self.action_prefix + action + if type(params) is dict: + action += '?' + urllib.urlencode(params, doseq=1) + if body: + body = self.serialize(body) + self.httpclient.content_type = self.content_type() + resp, replybody = self.httpclient.do_request(action, method, body=body) + status_code = self.get_status_code(resp) + if status_code in (httplib.OK, + httplib.CREATED, + httplib.ACCEPTED, + httplib.NO_CONTENT): + return self.deserialize(replybody, status_code) + else: + self._handle_fault_response(status_code, replybody) + + def get_status_code(self, response): + """ + Returns the integer status code from the response, which + can be either a Webob.Response (used in testing) or httplib.Response + """ + if hasattr(response, 'status_int'): + return response.status_int + else: + return response.status + + def serialize(self, data): + """ + Serializes a dictionary with a single key (which can contain any + structure) into either xml or json + """ + if data is None: + return None + elif type(data) is dict: + return Serializer().serialize(data, self.content_type()) + else: + raise Exception("unable to serialize object of type = '%s'" % + type(data)) + + def deserialize(self, data, status_code): + """ + Deserializes a an xml or json string into a dictionary + """ + if status_code == 204: + return data + return Serializer(self._serialization_metadata).deserialize( + data, self.content_type()) + + def content_type(self, format=None): + """ + Returns the mime-type for either 'xml' or 'json'. Defaults to the + currently set format + """ + if not format: + format = self.format + return "application/%s" % (format) + + def retry_request(self, method, action, body=None, + headers=None, params=None): + """ + Call do_request with the default retry configuration. Only + idempotent requests should retry failed connection attempts. + + :raises: ConnectionFailed if the maximum # of retries is exceeded + """ + max_attempts = self.retries + 1 + for i in xrange(max_attempts): + try: + return self.do_request(method, action, body=body, + headers=headers, params=params) + except exceptions.ConnectionFailed: + # Exception has already been logged by do_request() + if i < self.retries: + _logger.debug(_('Retrying connection to quantum service')) + time.sleep(self.retry_interval) + + raise exceptions.ConnectionFailed(reason=_("Maximum attempts reached")) + + def delete(self, action, body=None, headers=None, params=None): + return self.retry_request("DELETE", action, body=body, + headers=headers, params=params) + + def get(self, action, body=None, headers=None, params=None): + return self.retry_request("GET", action, body=body, + headers=headers, params=params) + + def post(self, action, body=None, headers=None, params=None): + # Do not retry POST requests to avoid the orphan objects problem. + return self.do_request("POST", action, body=body, + headers=headers, params=params) + + def put(self, action, body=None, headers=None, params=None): + return self.retry_request("PUT", action, body=body, + headers=headers, params=params) + +#if __name__ == '__main__': +# +# client20 = Client(username='admin', +# password='password', +# auth_url='http://localhost:5000/v2.0', +# tenant_name='admin') +# client20 = Client(token='ec796583fcad4aa690b723bc0b25270e', +# endpoint_url='http://localhost:9696') +# +# client20.tenant = 'default' +# client20.format = 'json' +# nets = client20.list_networks() +# print nets diff --git a/setup.py b/setup.py index e57b15eba..893874861 100644 --- a/setup.py +++ b/setup.py @@ -61,12 +61,13 @@ tests_require=tests_require, cmdclass=setup.get_cmdclass(), include_package_data=False, - packages=setuptools.find_packages(exclude=['tests', 'tests.*']), + packages=setuptools.find_packages('.'), package_data=PackageData, eager_resources=EagerResources, entry_points={ 'console_scripts': [ - 'quantum = quantumclient.cli:main' + 'quantum = quantumclient.cli:main', + 'quantumv2 = quantumclient.shell:main', ] }, ) diff --git a/tools/pip-requires b/tools/pip-requires new file mode 100644 index 000000000..2a0be2812 --- /dev/null +++ b/tools/pip-requires @@ -0,0 +1,7 @@ +cliff>=0.6.0 +argparse +httplib2 +prettytable>=0.6.0 +simplejson + + diff --git a/tools/test-requires b/tools/test-requires index 427a1b233..c6e20802f 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,11 +1,15 @@ distribute>=0.6.24 - +cliff>=0.6.0 +argparse +httplib2 +prettytable>=0.6.0 +simplejson mox nose nose-exclude nosexcover openstack.nose_plugin -pep8==0.6.1 +pep8 sphinx>=1.1.2 https://github.com/openstack/quantum/zipball/master#egg=quantum From 50c46f61d1a76b5429c20ad61203afedc7c508bb Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Sun, 24 Jun 2012 16:11:11 +0800 Subject: [PATCH 009/135] add --fixed-ip argument to create port add --fixed-ip argument to create port and add list and dict type for unknow option now we can use known option feature: quantumv2 create_port --fixed-ip subnet_id=,ip_address= --fixed-ip subnet_id=, ip_address= network_id or unknown option feature: one ip: quantumv2 create_port network_id --fixed_ips type=dict list=true subnet_id=,ip_address= two ips: quantumv2 create_port network_id --fixed_ips type=dict subnet_id=,ip_address= subnet_id=,ip_address= to create port Please download: https://review.openstack.org/#/c/8794/4 and set core_plugin = quantum.db.db_base_plugin_v2.QuantumDbPluginV2 on quantum server side Patch 2: support cliff 1.0 Patch 3: support specify auth strategy, for now, any other auth strategy than keystone will disable auth, format port output Patch 4: format None as '' when outputing, deal with list of dict, add QUANTUMCLIENT_DEBUG env to enable http req/resp print, which is helpful for testing nova integration Patch 5: fix interactive mode, and initialize_app problem Change-Id: I693848c75055d1947862d55f4b538c1dfb1e86db --- quantumclient/client.py | 20 ++++-- quantumclient/common/clientmanager.py | 2 + quantumclient/common/command.py | 6 ++ quantumclient/common/utils.py | 17 ++++- quantumclient/quantum/client.py | 3 +- quantumclient/quantum/v2_0/__init__.py | 40 +++++++++-- quantumclient/quantum/v2_0/port.py | 15 +++- quantumclient/shell.py | 73 ++++++++++++-------- quantumclient/tests/unit/test_casual_args.py | 13 ++++ 9 files changed, 146 insertions(+), 43 deletions(-) diff --git a/quantumclient/client.py b/quantumclient/client.py index b892d303e..03c590a21 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -20,6 +20,7 @@ except ImportError: import simplejson as json import logging +import os import urlparse # Python 2.5 compat fix if not hasattr(urlparse, 'parse_qsl'): @@ -33,6 +34,11 @@ _logger = logging.getLogger(__name__) +if 'QUANTUMCLIENT_DEBUG' in os.environ and os.environ['QUANTUMCLIENT_DEBUG']: + ch = logging.StreamHandler() + _logger.setLevel(logging.DEBUG) + _logger.addHandler(ch) + class ServiceCatalog(object): """Helper methods for dealing with a Keystone Service Catalog.""" @@ -86,7 +92,8 @@ class HTTPClient(httplib2.Http): def __init__(self, username=None, tenant_name=None, password=None, auth_url=None, token=None, region_name=None, timeout=None, - endpoint_url=None, insecure=False, **kwargs): + endpoint_url=None, insecure=False, + auth_strategy='keystone', **kwargs): super(HTTPClient, self).__init__(timeout=timeout) self.username = username self.tenant_name = tenant_name @@ -96,6 +103,7 @@ def __init__(self, username=None, tenant_name=None, self.auth_token = token self.content_type = 'application/json' self.endpoint_url = endpoint_url + self.auth_strategy = auth_strategy # httplib2 overrides self.force_exception_to_status_code = True self.disable_ssl_certificate_validation = insecure @@ -126,19 +134,21 @@ def _cs_request(self, *args, **kwargs): return resp, body def do_request(self, url, method, **kwargs): - if not self.endpoint_url or not self.auth_token: + if not self.endpoint_url: self.authenticate() # Perform the request once. If we get a 401 back then it # might be because the auth token expired, so try to # re-authenticate and try again. If it still fails, bail. try: - kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token + if self.auth_token: + kwargs.setdefault('headers', {}) + kwargs['headers']['X-Auth-Token'] = self.auth_token resp, body = self._cs_request(self.endpoint_url + url, method, **kwargs) return resp, body except exceptions.Unauthorized as ex: - if not self.endpoint_url or not self.auth_token: + if not self.endpoint_url: self.authenticate() resp, body = self._cs_request( self.management_url + url, method, **kwargs) @@ -161,6 +171,8 @@ def _extract_service_catalog(self, body): endpoint_type='adminURL') def authenticate(self): + if self.auth_strategy != 'keystone': + raise exceptions.Unauthorized(message='unknown auth strategy') body = {'auth': {'passwordCredentials': {'username': self.username, 'password': self.password, }, diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py index ee2fcf3d2..1c1f1acd0 100644 --- a/quantumclient/common/clientmanager.py +++ b/quantumclient/common/clientmanager.py @@ -53,6 +53,7 @@ def __init__(self, token=None, url=None, username=None, password=None, region_name=None, api_version=None, + auth_strategy=None ): self._token = token self._url = url @@ -64,6 +65,7 @@ def __init__(self, token=None, url=None, self._region_name = region_name self._api_version = api_version self._service_catalog = None + self._auth_strategy = auth_strategy return def initialize(self): diff --git a/quantumclient/common/command.py b/quantumclient/common/command.py index 405f65556..f2706d019 100644 --- a/quantumclient/common/command.py +++ b/quantumclient/common/command.py @@ -33,3 +33,9 @@ def run(self, parsed_args): return else: return super(OpenStackCommand, self).run(parsed_args) + + def get_data(self, parsed_args): + pass + + def take_action(self, parsed_args): + return self.get_data(parsed_args) diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py index 4044ae8af..bed02e010 100644 --- a/quantumclient/common/utils.py +++ b/quantumclient/common/utils.py @@ -62,9 +62,9 @@ def to_primitive(value): return value -def dumps(value): +def dumps(value, indent=None): try: - return json.dumps(value) + return json.dumps(value, indent=indent) except TypeError: pass return json.dumps(to_primitive(value)) @@ -126,17 +126,28 @@ def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): data = item[field_name] else: data = getattr(item, field_name, '') + if data is None: + data = '' row.append(data) return tuple(row) -def __str2bool(strbool): +def str2bool(strbool): if strbool is None: return None else: return strbool.lower() == 'true' +def str2dict(strdict): + '''@param strdict: key1=value1,key2=value2''' + _info = {} + for kv_str in strdict.split(","): + k, v = kv_str.split("=", 1) + _info.update({k: v}) + return _info + + def http_log(_logger, args, kwargs, resp, body): if not _logger.isEnabledFor(logging.DEBUG): return diff --git a/quantumclient/quantum/client.py b/quantumclient/quantum/client.py index e2c059fc0..7aa1f1589 100644 --- a/quantumclient/quantum/client.py +++ b/quantumclient/quantum/client.py @@ -64,5 +64,6 @@ def make_client(instance): region_name=instance._region_name, auth_url=instance._auth_url, endpoint_url=url, - token=instance._token) + token=instance._token, + auth_strategy=instance._auth_strategy) return client diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 89c1c7a63..e1402af85 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -70,11 +70,12 @@ def parse_args_to_dict(values_specs): current_arg = None _values_specs = [] _value_number = 0 + _list_flag = False current_item = None for _item in values_specs: if _item.startswith('--'): if current_arg is not None: - if _value_number > 1: + if _value_number > 1 or _list_flag: current_arg.update({'nargs': '+'}) elif _value_number == 0: current_arg.update({'action': 'store_true'}) @@ -93,12 +94,16 @@ def parse_args_to_dict(values_specs): _type_str = _item.split('=', 2)[1] current_arg.update({'type': eval(_type_str)}) if _type_str == 'bool': - current_arg.update({'type': utils.__str2bool}) + current_arg.update({'type': utils.str2bool}) + elif _type_str == 'dict': + current_arg.update({'type': utils.str2dict}) continue else: raise exceptions.CommandError( "invalid values_specs %s" % ' '.join(values_specs)) - + elif _item == 'list=true': + _list_flag = True + continue if not _item.startswith('--'): if not current_item or '=' in current_item: raise exceptions.CommandError( @@ -110,9 +115,10 @@ def parse_args_to_dict(values_specs): _value_number = 1 else: _value_number = 0 + _list_flag = False _values_specs.append(_item) if current_arg is not None: - if _value_number > 1: + if _value_number > 1 or _list_flag: current_arg.update({'nargs': '+'}) elif _value_number == 0: current_arg.update({'action': 'store_true'}) @@ -188,6 +194,19 @@ def get_data(self, parsed_args): print >>self.app.stdout, _('Created a new %s:' % self.resource) else: info = {'': ''} + for k, v in info.iteritems(): + if isinstance(v, list): + value = "" + for _item in v: + if value: + value += "\n" + if isinstance(_item, dict): + value += utils.dumps(_item) + else: + value += str(_item) + info[k] = value + elif v is None: + info[k] = '' return zip(*sorted(info.iteritems())) @@ -334,6 +353,19 @@ def get_data(self, parsed_args): "show_%s" % self.resource) data = obj_showor(parsed_args.id, **params) if self.resource in data: + for k, v in data[self.resource].iteritems(): + if isinstance(v, list): + value = "" + for _item in v: + if value: + value += "\n" + if isinstance(_item, dict): + value += utils.dumps(_item) + else: + value += str(_item) + data[self.resource][k] = value + elif v is None: + data[self.resource][k] = '' return zip(*sorted(data[self.resource].iteritems())) else: return None diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 9b8af129e..bd213f8ae 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -17,6 +17,7 @@ import logging +from quantumclient import utils from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand @@ -26,7 +27,7 @@ def _format_fixed_ips(port): try: - return '\n'.join(port['fixed_ips']) + return '\n'.join([utils.dumps(ip) for ip in port['fixed_ips']]) except Exception: return '' @@ -74,6 +75,12 @@ def add_known_arguments(self, parser): parser.add_argument( '--device-id', help='device id of this port') + parser.add_argument( + '--fixed-ip', + action='append', + help='desired Ip for this port: ' + 'subnet_id=,ip_address=, ' + 'can be repeated') parser.add_argument( 'network_id', help='Network id of this port belongs to') @@ -87,6 +94,12 @@ def args2body(self, parsed_args): body['port'].update({'device_id': parsed_args.device_id}) if parsed_args.tenant_id: body['port'].update({'tenant_id': parsed_args.tenant_id}) + ips = [] + if parsed_args.fixed_ip: + for ip_spec in parsed_args.fixed_ip: + ips.append(utils.str2dict(ip_spec)) + if ips: + body['port'].update({'fixed_ips': ips}) return body diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 975a1459f..a737b4b66 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -197,6 +197,13 @@ def build_option_parser(self, description, version): action='store_true', help='show tracebacks on errors', ) # Global arguments + parser.add_argument( + '--os-auth-strategy', metavar='', + default=env('OS_AUTH_STRATEGY', default='keystone'), + help='Authentication strategy (Env: OS_AUTH_STRATEGY' + ', default keystone). For now, any other value will' + ' disable the authentication') + parser.add_argument( '--os-auth-url', metavar='', default=env('OS_AUTH_URL'), @@ -272,41 +279,46 @@ def authenticate_user(self): """Make sure the user has provided all of the authentication info we need. """ + if self.options.os_auth_strategy == 'keystone': + if self.options.os_token or self.options.os_url: + # Token flow auth takes priority + if not self.options.os_token: + raise exc.CommandError( + "You must provide a token via" + " either --os-token or env[OS_TOKEN]") + + if not self.options.os_url: + raise exc.CommandError( + "You must provide a service URL via" + " either --os-url or env[OS_URL]") - if self.options.os_token or self.options.os_url: - # Token flow auth takes priority - if not self.options.os_token: - raise exc.CommandError( - "You must provide a token via" - " either --os-token or env[OS_TOKEN]") - + else: + # Validate password flow auth + if not self.options.os_username: + raise exc.CommandError( + "You must provide a username via" + " either --os-username or env[OS_USERNAME]") + + if not self.options.os_password: + raise exc.CommandError( + "You must provide a password via" + " either --os-password or env[OS_PASSWORD]") + + if not (self.options.os_tenant_name): + raise exc.CommandError( + "You must provide a tenant_name via" + " either --os-tenant-name or via env[OS_TENANT_NAME]") + + if not self.options.os_auth_url: + raise exc.CommandError( + "You must provide an auth url via" + " either --os-auth-url or via env[OS_AUTH_URL]") + else: # not keystone if not self.options.os_url: raise exc.CommandError( "You must provide a service URL via" " either --os-url or env[OS_URL]") - else: - # Validate password flow auth - if not self.options.os_username: - raise exc.CommandError( - "You must provide a username via" - " either --os-username or env[OS_USERNAME]") - - if not self.options.os_password: - raise exc.CommandError( - "You must provide a password via" - " either --os-password or env[OS_PASSWORD]") - - if not (self.options.os_tenant_name): - raise exc.CommandError( - "You must provide a tenant_name via" - " either --os-tenant-name or via env[OS_TENANT_NAME]") - - if not self.options.os_auth_url: - raise exc.CommandError( - "You must provide an auth url via" - " either --os-auth-url or via env[OS_AUTH_URL]") - self.client_manager = clientmanager.ClientManager( token=self.options.os_token, url=self.options.os_url, @@ -315,7 +327,8 @@ def authenticate_user(self): username=self.options.os_username, password=self.options.os_password, region_name=self.options.os_region_name, - api_version=self.api_version, ) + api_version=self.api_version, + auth_strategy=self.options.os_auth_strategy, ) return def initialize_app(self, argv): diff --git a/quantumclient/tests/unit/test_casual_args.py b/quantumclient/tests/unit/test_casual_args.py index 695aa0aff..4192ff81b 100644 --- a/quantumclient/tests/unit/test_casual_args.py +++ b/quantumclient/tests/unit/test_casual_args.py @@ -57,3 +57,16 @@ def test_arg(self): _specs = ['--tag=t', '--arg1', 'value1'] self.assertEqual('value1', quantumV20.parse_args_to_dict(_specs)['arg1']) + + def test_dict_arg(self): + _specs = ['--tag=t', '--arg1', 'type=dict', 'key1=value1,key2=value2'] + arg1 = quantumV20.parse_args_to_dict(_specs)['arg1'] + self.assertEqual('value1', arg1['key1']) + self.assertEqual('value2', arg1['key2']) + + def test_list_of_dict_arg(self): + _specs = ['--tag=t', '--arg1', 'type=dict', + 'list=true', 'key1=value1,key2=value2'] + arg1 = quantumV20.parse_args_to_dict(_specs)['arg1'] + self.assertEqual('value1', arg1[0]['key1']) + self.assertEqual('value2', arg1[0]['key2']) From d955740671000b2b915c8c6717d12a5e52f3b89e Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Mon, 25 Jun 2012 11:35:01 -0500 Subject: [PATCH 010/135] Add post-tag versioning. Change-Id: I0667c7b72c36a20827730dd829a12430954b666e --- .gitignore | 1 + MANIFEST.in | 1 + quantumclient/openstack/common/setup.py | 138 +++++++++++++++++++----- setup.py | 4 +- version.py | 46 -------- 5 files changed, 117 insertions(+), 73 deletions(-) delete mode 100644 version.py diff --git a/.gitignore b/.gitignore index 14c363976..15eae7e22 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ run_tests.log .autogenerated doc/build/ doc/source/api/ +quantumclient/versioninfo diff --git a/MANIFEST.in b/MANIFEST.in index 94058d438..648953813 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,3 +4,4 @@ include version.py include tools/* include quantumclient/tests/* include quantumclient/tests/unit/* +include quantumclient/versioninfo diff --git a/quantumclient/openstack/common/setup.py b/quantumclient/openstack/common/setup.py index 429ba35c5..caf06fa5b 100644 --- a/quantumclient/openstack/common/setup.py +++ b/quantumclient/openstack/common/setup.py @@ -19,9 +19,11 @@ Utilities with minimum-depends for use in setup.py """ +import datetime import os import re import subprocess +import sys from setuptools.command import sdist @@ -33,8 +35,7 @@ def parse_mailmap(mailmap='.mailmap'): for l in fp: l = l.strip() if not l.startswith('#') and ' ' in l: - canonical_email, alias = [x for x in l.split(' ') - if x.startswith('<')] + canonical_email, alias = l.split(' ') mapping[alias] = canonical_email return mapping @@ -76,6 +77,10 @@ def parse_requirements(requirements_files=['requirements.txt', # -f lines are for index locations, and don't get used here elif re.match(r'\s*-f\s+', line): pass + # argparse is part of the standard library starting with 2.7 + # adding it to the requirements list screws distro installs + elif line == 'argparse' and sys.version_info >= (2, 7): + pass else: requirements.append(line) @@ -113,28 +118,54 @@ def write_requirements(): def _run_shell_command(cmd): output = subprocess.Popen(["/bin/sh", "-c", cmd], stdout=subprocess.PIPE) - return output.communicate()[0].strip() - - -def write_vcsversion(location): - """Produce a vcsversion dict that mimics the old one produced by bzr. - """ - if os.path.isdir('.git'): - branch_nick_cmd = 'git branch | grep -Ei "\* (.*)" | cut -f2 -d" "' - branch_nick = _run_shell_command(branch_nick_cmd) - revid_cmd = "git rev-parse HEAD" - revid = _run_shell_command(revid_cmd).split()[0] - revno_cmd = "git log --oneline | wc -l" - revno = _run_shell_command(revno_cmd) - with open(location, 'w') as version_file: - version_file.write(""" -# This file is automatically generated by setup.py, So don't edit it. :) -version_info = { - 'branch_nick': '%s', - 'revision_id': '%s', - 'revno': %s -} -""" % (branch_nick, revid, revno)) + out = output.communicate() + if len(out) == 0: + return None + if len(out[0].strip()) == 0: + return None + return out[0].strip() + + +def _get_git_next_version_suffix(branch_name): + datestamp = datetime.datetime.now().strftime('%Y%m%d') + if branch_name == 'milestone-proposed': + revno_prefix = "r" + else: + revno_prefix = "" + _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*") + milestone_cmd = "git show meta/openstack/release:%s" % branch_name + milestonever = _run_shell_command(milestone_cmd) + if not milestonever: + milestonever = "" + post_version = _get_git_post_version() + revno = post_version.split(".")[-1] + return "%s~%s.%s%s" % (milestonever, datestamp, revno_prefix, revno) + + +def _get_git_current_tag(): + return _run_shell_command("git tag --contains HEAD") + + +def _get_git_tag_info(): + return _run_shell_command("git describe --tags") + + +def _get_git_post_version(): + current_tag = _get_git_current_tag() + if current_tag is not None: + return current_tag + else: + tag_info = _get_git_tag_info() + if tag_info is None: + base_version = "0.0" + cmd = "git --no-pager log --oneline" + out = _run_shell_command(cmd) + revno = len(out.split("\n")) + else: + tag_infos = tag_info.split("-") + base_version = "-".join(tag_infos[:-2]) + revno = tag_infos[-2] + return "%s.%s" % (base_version, revno) def write_git_changelog(): @@ -174,6 +205,25 @@ def generate_authors(): """ +def read_versioninfo(project): + """Read the versioninfo file. If it doesn't exist, we're in a github + zipball, and there's really know way to know what version we really + are, but that should be ok, because the utility of that should be + just about nil if this code path is in use in the first place.""" + versioninfo_path = os.path.join(project, 'versioninfo') + if os.path.exists(versioninfo_path): + with open(versioninfo_path, 'r') as vinfo: + version = vinfo.read().strip() + else: + version = "0.0.0" + return version + + +def write_versioninfo(project, version): + """Write a simple file containing the version of the package.""" + open(os.path.join(project, 'versioninfo'), 'w').write("%s\n" % version) + + def get_cmdclass(): """Return dict of commands to run from setup.py.""" @@ -250,3 +300,43 @@ def run(self): pass return cmdclass + + +def get_git_branchname(): + for branch in _run_shell_command("git branch --color=never").split("\n"): + if branch.startswith('*'): + _branch_name = branch.split()[1].strip() + if _branch_name == "(no": + _branch_name = "no-branch" + return _branch_name + + +def get_pre_version(projectname, base_version): + """Return a version which is based""" + if os.path.isdir('.git'): + current_tag = _get_git_current_tag() + if current_tag is not None: + version = current_tag + else: + branch_name = os.getenv('BRANCHNAME', + os.getenv('GERRIT_REFNAME', + get_git_branchname())) + version_suffix = _get_git_next_version_suffix(branch_name) + version = "%s~%s" % (base_version, version_suffix) + write_versioninfo(projectname, version) + return version.split('~')[0] + else: + version = read_versioninfo(projectname) + return version.split('~')[0] + + +def get_post_version(projectname): + """Return a version which is equal to the tag that's on the current + revision if there is one, or tag plus number of additional revisions + if the current revision has no tag.""" + + if os.path.isdir('.git'): + version = _get_git_post_version() + write_versioninfo(projectname, version) + return version + return read_versioninfo(projectname) diff --git a/setup.py b/setup.py index 893874861..18ca05cdc 100644 --- a/setup.py +++ b/setup.py @@ -21,11 +21,9 @@ from quantumclient.openstack.common import setup -import version - Name = 'python-quantumclient' Url = "https://launchpad.net/quantum" -Version = version.canonical_version_string() +Version = setup.get_post_version('quantumclient') License = 'Apache License 2.0' Author = 'Netstack' AuthorEmail = 'netstack@lists.launchpad.net' diff --git a/version.py b/version.py deleted file mode 100644 index fae32acd3..000000000 --- a/version.py +++ /dev/null @@ -1,46 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -try: - from quantumclient.vcsversion import version_info -except ImportError: - version_info = {'branch_nick': u'LOCALBRANCH', - 'revision_id': 'LOCALREVISION', - 'revno': 0} - -QUANTUM_VERSION = ['2012', '2', None] -YEAR, COUNT, REVSISION = QUANTUM_VERSION - -FINAL = False # This becomes true at Release Candidate time - - -def canonical_version_string(): - return '.'.join(filter(None, QUANTUM_VERSION)) - - -def version_string(): - if FINAL: - return canonical_version_string() - else: - return '%s-dev' % (canonical_version_string(),) - - -def vcs_version_string(): - return "%s:%s" % (version_info['branch_nick'], version_info['revision_id']) - - -def version_string_with_vcs(): - return "%s-%s" % (canonical_version_string(), vcs_version_string()) From 1586c923d9dd0a717df07cc1d729fc45d55d3b06 Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Thu, 5 Jul 2012 15:01:39 +0800 Subject: [PATCH 011/135] Make quantum cli consistent with other cli's practice. Bug 1011759 We use dash in command names, underscore in options or arguments, adopt noun-verb format command names. Change-Id: Ibeb2b4a31929dbb7008cec3b04bd77e75d9ace1a --- quantumclient/quantum/v2_0/__init__.py | 12 +-- quantumclient/quantum/v2_0/network.py | 27 ++---- quantumclient/quantum/v2_0/port.py | 35 ++----- quantumclient/quantum/v2_0/subnet.py | 29 ++---- quantumclient/shell.py | 94 +++++++++---------- .../tests/unit/test_cli20_network.py | 30 +++--- quantumclient/tests/unit/test_cli20_port.py | 30 +++--- quantumclient/tests/unit/test_cli20_subnet.py | 24 +++-- 8 files changed, 113 insertions(+), 168 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index e1402af85..c761530c0 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -28,7 +28,7 @@ def add_show_list_common_argument(parser): parser.add_argument( - '-D', '--show-details', + '-D', '--show_details', help='show detailed info', action='store_true', default=False, ) @@ -145,7 +145,7 @@ def get_client(self): def get_parser(self, prog_name): parser = super(QuantumCommand, self).get_parser(prog_name) parser.add_argument( - '--request-format', + '--request_format', help=_('the xml or json request format'), default='json', choices=['json', 'xml', ], ) @@ -165,7 +165,7 @@ class CreateCommand(QuantumCommand, show.ShowOne): def get_parser(self, prog_name): parser = super(CreateCommand, self).get_parser(prog_name) parser.add_argument( - '--tenant-id', metavar='tenant-id', + '--tenant_id', metavar='tenant_id', help=_('the owner tenant ID'), ) self.add_known_arguments(parser) add_extra_argument(parser, 'value_specs', @@ -221,7 +221,7 @@ class UpdateCommand(QuantumCommand): def get_parser(self, prog_name): parser = super(UpdateCommand, self).get_parser(prog_name) parser.add_argument( - 'id', metavar='%s-id' % self.resource, + 'id', metavar='%s_id' % self.resource, help='ID of %s to update' % self.resource) add_extra_argument(parser, 'value_specs', 'new values for the %s' % self.resource) @@ -257,7 +257,7 @@ class DeleteCommand(QuantumCommand): def get_parser(self, prog_name): parser = super(DeleteCommand, self).get_parser(prog_name) parser.add_argument( - 'id', metavar='%s-id' % self.resource, + 'id', metavar='%s_id' % self.resource, help='ID of %s to delete' % self.resource) return parser @@ -335,7 +335,7 @@ def get_parser(self, prog_name): parser = super(ShowCommand, self).get_parser(prog_name) add_show_list_common_argument(parser) parser.add_argument( - 'id', metavar='%s-id' % self.resource, + 'id', metavar='%s_id' % self.resource, help='ID of %s to look up' % self.resource) return parser diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 646612375..61d004bbe 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -32,10 +32,7 @@ def _format_subnets(network): class ListNetwork(ListCommand): - """List networks that belong to a given tenant - - Sample: list_nets -D -- --name=test4 --tag a b - """ + """List networks that belong to a given tenant.""" resource = 'network' log = logging.getLogger(__name__ + '.ListNetwork') @@ -43,27 +40,21 @@ class ListNetwork(ListCommand): class ShowNetwork(ShowCommand): - """Show information of a given network - - Sample: show_net -D - """ + """Show information of a given network.""" resource = 'network' log = logging.getLogger(__name__ + '.ShowNetwork') class CreateNetwork(CreateCommand): - """Create a network for a given tenant - - Sample create_net --tenant-id xxx --admin-state-down --tag x y - """ + """Create a network for a given tenant.""" resource = 'network' log = logging.getLogger(__name__ + '.CreateNetwork') def add_known_arguments(self, parser): parser.add_argument( - '--admin-state-down', + '--admin_state_down', default=True, action='store_false', help='Set Admin State Up to false') parser.add_argument( @@ -80,20 +71,14 @@ def args2body(self, parsed_args): class DeleteNetwork(DeleteCommand): - """Delete a given network - - Sample: delete_net - """ + """Delete a given network.""" log = logging.getLogger(__name__ + '.DeleteNetwork') resource = 'network' class UpdateNetwork(UpdateCommand): - """Update network's information - - Sample: update_net --name=test --admin_state_up type=bool True - """ + """Update network's information.""" log = logging.getLogger(__name__ + '.UpdateNetwork') resource = 'network' diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index bd213f8ae..d4c4fb550 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -33,10 +33,7 @@ def _format_fixed_ips(port): class ListPort(ListCommand): - """List networks that belong to a given tenant - - Sample: list_ports -D -- --name=test4 --tag a b - """ + """List networks that belong to a given tenant.""" resource = 'port' log = logging.getLogger(__name__ + '.ListPort') @@ -44,39 +41,31 @@ class ListPort(ListCommand): class ShowPort(ShowCommand): - """Show information of a given port - - Sample: show_port -D - """ + """Show information of a given port.""" resource = 'port' log = logging.getLogger(__name__ + '.ShowPort') class CreatePort(CreateCommand): - """Create a port for a given tenant - - Sample create_port --tenant-id xxx --admin-state-down \ - --mac_address mac --device_id deviceid - - """ + """Create a port for a given tenant.""" resource = 'port' log = logging.getLogger(__name__ + '.CreatePort') def add_known_arguments(self, parser): parser.add_argument( - '--admin-state-down', + '--admin_state_down', default=True, action='store_false', help='set admin state up to false') parser.add_argument( - '--mac-address', + '--mac_address', help='mac address of port') parser.add_argument( - '--device-id', + '--device_id', help='device id of this port') parser.add_argument( - '--fixed-ip', + '--fixed_ip', action='append', help='desired Ip for this port: ' 'subnet_id=,ip_address=, ' @@ -104,20 +93,14 @@ def args2body(self, parsed_args): class DeletePort(DeleteCommand): - """Delete a given port - - Sample: delete_port - """ + """Delete a given port.""" resource = 'port' log = logging.getLogger(__name__ + '.DeletePort') class UpdatePort(UpdateCommand): - """Update port's information - - Sample: update_port --name=test --admin_state_up type=bool True - """ + """Update port's information.""" resource = 'port' log = logging.getLogger(__name__ + '.UpdatePort') diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 07b09acc8..8dcf004c7 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -25,10 +25,7 @@ class ListSubnet(ListCommand): - """List networks that belong to a given tenant - - Sample: list_subnets -D -- --name=test4 --tag a b - """ + """List networks that belong to a given tenant.""" resource = 'subnet' log = logging.getLogger(__name__ + '.ListSubnet') @@ -36,27 +33,20 @@ class ListSubnet(ListCommand): class ShowSubnet(ShowCommand): - """Show information of a given subnet - - Sample: show_subnet -D - """ + """Show information of a given subnet.""" resource = 'subnet' log = logging.getLogger(__name__ + '.ShowSubnet') class CreateSubnet(CreateCommand): - """Create a subnet for a given tenant - - Sample create_subnet --tenant-id xxx --ip-version 4\ - --tag x y --otherfield value - """ + """Create a subnet for a given tenant.""" resource = 'subnet' log = logging.getLogger(__name__ + '.CreateSubnet') def add_known_arguments(self, parser): - parser.add_argument('--ip-version', type=int, + parser.add_argument('--ip_version', type=int, default=4, choices=[4, 6], help='IP version with default 4') parser.add_argument( @@ -81,21 +71,14 @@ def args2body(self, parsed_args): class DeleteSubnet(DeleteCommand): - """Delete a given subnet - - Sample: delete_subnet - """ + """Delete a given subnet.""" resource = 'subnet' log = logging.getLogger(__name__ + '.DeleteSubnet') class UpdateSubnet(UpdateCommand): - """Update subnet's information - - Sample: - update_subnet --name=test --admin_state_up type=bool True - """ + """Update subnet's information.""" resource = 'subnet' log = logging.getLogger(__name__ + '.UpdateSubnet') diff --git a/quantumclient/shell.py b/quantumclient/shell.py index a737b4b66..f16e18692 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -51,64 +51,64 @@ def env(*vars, **kwargs): return kwargs.get('default', '') COMMAND_V1 = { - 'list_nets': utils.import_class( + 'net-list': utils.import_class( 'quantumclient.quantum.v1_1.network.ListNetwork'), - 'show_net': utils.import_class( + 'net-show': utils.import_class( 'quantumclient.quantum.v1_1.network.ShowNetwork'), - 'create_net': utils.import_class( + 'net-create': utils.import_class( 'quantumclient.quantum.v1_1.network.CreateNetwork'), - 'delete_net': utils.import_class( + 'net-delete': utils.import_class( 'quantumclient.quantum.v1_1.network.DeleteNetwork'), - 'update_net': utils.import_class( + 'net-update': utils.import_class( 'quantumclient.quantum.v1_1.network.UpdateNetwork'), - 'list_ports': utils.import_class( + 'port-list': utils.import_class( 'quantumclient.quantum.v1_1.port.ListPort'), - 'show_port': utils.import_class( + 'port-show': utils.import_class( 'quantumclient.quantum.v1_1.port.ShowPort'), - 'create_port': utils.import_class( + 'port-create': utils.import_class( 'quantumclient.quantum.v1_1.port.CreatePort'), - 'delete_port': utils.import_class( + 'port-delete': utils.import_class( 'quantumclient.quantum.v1_1.port.DeletePort'), - 'update_port': utils.import_class( + 'port-update': utils.import_class( 'quantumclient.quantum.v1_1.port.UpdatePort'), - 'plug_iface': utils.import_class( + 'iface-plugin': utils.import_class( 'quantumclient.quantum.v1_1.interface.PlugInterface'), - 'unplug_iface': utils.import_class( + 'iface-unplug': utils.import_class( 'quantumclient.quantum.v1_1.interface.UnPlugInterface'), - 'show_iface': utils.import_class( + 'iface-show': utils.import_class( 'quantumclient.quantum.v1_1.interface.ShowInterface'), } COMMAND_V2 = { - 'list_nets': utils.import_class( + 'net-list': utils.import_class( 'quantumclient.quantum.v2_0.network.ListNetwork'), - 'show_net': utils.import_class( + 'net-show': utils.import_class( 'quantumclient.quantum.v2_0.network.ShowNetwork'), - 'create_net': utils.import_class( + 'net-create': utils.import_class( 'quantumclient.quantum.v2_0.network.CreateNetwork'), - 'delete_net': utils.import_class( + 'net-delete': utils.import_class( 'quantumclient.quantum.v2_0.network.DeleteNetwork'), - 'update_net': utils.import_class( + 'net-update': utils.import_class( 'quantumclient.quantum.v2_0.network.UpdateNetwork'), - 'list_subnets': utils.import_class( + 'subnet-list': utils.import_class( 'quantumclient.quantum.v2_0.subnet.ListSubnet'), - 'show_subnet': utils.import_class( + 'subnet-show': utils.import_class( 'quantumclient.quantum.v2_0.subnet.ShowSubnet'), - 'create_subnet': utils.import_class( + 'subnet-create': utils.import_class( 'quantumclient.quantum.v2_0.subnet.CreateSubnet'), - 'delete_subnet': utils.import_class( + 'subnet-delete': utils.import_class( 'quantumclient.quantum.v2_0.subnet.DeleteSubnet'), - 'update_subnet': utils.import_class( + 'subnet-update': utils.import_class( 'quantumclient.quantum.v2_0.subnet.UpdateSubnet'), - 'list_ports': utils.import_class( + 'port-list': utils.import_class( 'quantumclient.quantum.v2_0.port.ListPort'), - 'show_port': utils.import_class( + 'port-show': utils.import_class( 'quantumclient.quantum.v2_0.port.ShowPort'), - 'create_port': utils.import_class( + 'port-create': utils.import_class( 'quantumclient.quantum.v2_0.port.CreatePort'), - 'delete_port': utils.import_class( + 'port-delete': utils.import_class( 'quantumclient.quantum.v2_0.port.DeletePort'), - 'update_port': utils.import_class( + 'port-update': utils.import_class( 'quantumclient.quantum.v2_0.port.UpdatePort'), } COMMANDS = {'1.0': COMMAND_V1, @@ -198,51 +198,51 @@ def build_option_parser(self, description, version): help='show tracebacks on errors', ) # Global arguments parser.add_argument( - '--os-auth-strategy', metavar='', + '--os_auth_strategy', metavar='', default=env('OS_AUTH_STRATEGY', default='keystone'), help='Authentication strategy (Env: OS_AUTH_STRATEGY' ', default keystone). For now, any other value will' ' disable the authentication') parser.add_argument( - '--os-auth-url', metavar='', + '--os_auth_url', metavar='', default=env('OS_AUTH_URL'), help='Authentication URL (Env: OS_AUTH_URL)') parser.add_argument( - '--os-tenant-name', metavar='', + '--os_tenant_name', metavar='', default=env('OS_TENANT_NAME'), help='Authentication tenant name (Env: OS_TENANT_NAME)') parser.add_argument( - '--os-username', metavar='', + '--os_username', metavar='', default=utils.env('OS_USERNAME'), help='Authentication username (Env: OS_USERNAME)') parser.add_argument( - '--os-password', metavar='', + '--os_password', metavar='', default=utils.env('OS_PASSWORD'), help='Authentication password (Env: OS_PASSWORD)') parser.add_argument( - '--os-region-name', metavar='', + '--os_region_name', metavar='', default=env('OS_REGION_NAME'), help='Authentication region name (Env: OS_REGION_NAME)') parser.add_argument( - '--os-quantum-api-version', - metavar='', + '--os_quantum_api_version', + metavar='', default=env('OS_QUANTUM_API_VERSION', default='2.0'), help='QAUNTUM API version, default=2.0 ' '(Env: OS_QUANTUM_API_VERSION)') parser.add_argument( - '--os-token', metavar='', + '--os_token', metavar='', default=env('OS_TOKEN'), help='Defaults to env[OS_TOKEN]') parser.add_argument( - '--os-url', metavar='', + '--os_url', metavar='', default=env('OS_URL'), help='Defaults to env[OS_URL]') @@ -285,39 +285,39 @@ def authenticate_user(self): if not self.options.os_token: raise exc.CommandError( "You must provide a token via" - " either --os-token or env[OS_TOKEN]") + " either --os_token or env[OS_TOKEN]") if not self.options.os_url: raise exc.CommandError( "You must provide a service URL via" - " either --os-url or env[OS_URL]") + " either --os_url or env[OS_URL]") else: # Validate password flow auth if not self.options.os_username: raise exc.CommandError( "You must provide a username via" - " either --os-username or env[OS_USERNAME]") + " either --os_username or env[OS_USERNAME]") if not self.options.os_password: raise exc.CommandError( "You must provide a password via" - " either --os-password or env[OS_PASSWORD]") + " either --os_password or env[OS_PASSWORD]") if not (self.options.os_tenant_name): raise exc.CommandError( "You must provide a tenant_name via" - " either --os-tenant-name or via env[OS_TENANT_NAME]") + " either --os_tenant_name or via env[OS_TENANT_NAME]") if not self.options.os_auth_url: raise exc.CommandError( "You must provide an auth url via" - " either --os-auth-url or via env[OS_AUTH_URL]") + " either --os_auth_url or via env[OS_AUTH_URL]") else: # not keystone if not self.options.os_url: raise exc.CommandError( "You must provide a service URL via" - " either --os-url or env[OS_URL]") + " either --os_url or env[OS_URL]") self.client_manager = clientmanager.ClientManager( token=self.options.os_token, @@ -367,10 +367,10 @@ def main(argv=sys.argv[1:]): apiVersion = None versionFlag = False for argitem in argv: - if argitem.startswith('--os-quantum-api-version='): + if argitem.startswith('--os_quantum_api_version='): apiVersion = argitem.split('=', 2)[1] break - elif '--os-quantum-api-version' == argitem: + elif '--os_quantum_api_version' == argitem: versionFlag = True elif versionFlag: apiVersion = argitem diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index 809419aeb..69fcd1d7f 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -29,7 +29,7 @@ class CLITestV20Network(CLITestV20Base): def test_create_network(self): - """ create_net myname""" + """Create net: myname.""" resource = 'network' cmd = CreateNetwork(MyApp(sys.stdout), None) name = 'myname' @@ -41,12 +41,12 @@ def test_create_network(self): position_names, position_values) def test_create_network_tenant(self): - """create_net --tenant-id tenantid myname""" + """Create net: --tenant_id tenantid myname.""" resource = 'network' cmd = CreateNetwork(MyApp(sys.stdout), None) name = 'myname' myid = 'myid' - args = ['--tenant-id', 'tenantid', name] + args = ['--tenant_id', 'tenantid', name] position_names = ['name', ] position_values = [name, ] _str = self._test_create_resource(resource, cmd, name, myid, args, @@ -54,7 +54,7 @@ def test_create_network_tenant(self): tenant_id='tenantid') def test_create_network_tags(self): - """ create_net myname --tags a b""" + """Create net: myname --tags a b.""" resource = 'network' cmd = CreateNetwork(MyApp(sys.stdout), None) name = 'myname' @@ -67,12 +67,12 @@ def test_create_network_tags(self): tags=['a', 'b']) def test_create_network_state(self): - """ create_net --admin-state-down myname""" + """Create net: --admin_state_down myname.""" resource = 'network' cmd = CreateNetwork(MyApp(sys.stdout), None) name = 'myname' myid = 'myid' - args = ['--admin-state-down', name, ] + args = ['--admin_state_down', name, ] position_names = ['name', ] position_values = [name, ] _str = self._test_create_resource(resource, cmd, name, myid, args, @@ -80,39 +80,39 @@ def test_create_network_state(self): admin_state_up=False) def test_list_nets_detail(self): - """list_nets -D""" + """list nets: -D.""" resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_nets_tags(self): - """list_nets -- --tags a b""" + """List nets: -- --tags a b.""" resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, tags=['a', 'b']) def test_list_nets_detail_tags(self): - """list_nets -D -- --tags a b""" + """List nets: -D -- --tags a b.""" resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) def test_list_nets_fields(self): - """list_nets --fields a --fields b -- --fields c d""" + """List nets: --fields a --fields b -- --fields c d.""" resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) def test_update_network_exception(self): - """ update_net myid""" + """Update net: myid.""" resource = 'network' cmd = UpdateNetwork(MyApp(sys.stdout), None) self.assertRaises(exceptions.CommandError, self._test_update_resource, resource, cmd, 'myid', ['myid'], {}) def test_update_network(self): - """ update_net myid --name myname --tags a b""" + """Update net: myid --name myname --tags a b.""" resource = 'network' cmd = UpdateNetwork(MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', @@ -122,7 +122,7 @@ def test_update_network(self): ) def test_show_network(self): - """ show_net --fields id --fields name myid """ + """Show net: --fields id --fields name myid.""" resource = 'network' cmd = ShowNetwork(MyApp(sys.stdout), None) myid = 'myid' @@ -130,9 +130,7 @@ def test_show_network(self): self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) def test_delete_network(self): - """ - delete_net myid - """ + """Delete net: myid.""" resource = 'network' cmd = DeleteNetwork(MyApp(sys.stdout), None) myid = 'myid' diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index febb69e37..427d473b4 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -29,7 +29,7 @@ class CLITestV20Port(CLITestV20Base): def test_create_port(self): - """ create_port netid""" + """Create port: netid.""" resource = 'port' cmd = CreatePort(MyApp(sys.stdout), None) name = 'myname' @@ -43,26 +43,26 @@ def test_create_port(self): position_names, position_values) def test_create_port_full(self): - """ create_port --mac-address mac --device-id deviceid netid""" + """Create port: --mac_address mac --device_id deviceid netid.""" resource = 'port' cmd = CreatePort(MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' - args = ['--mac-address', 'mac', '--device-id', 'deviceid', netid] + args = ['--mac_address', 'mac', '--device_id', 'deviceid', netid] position_names = ['network_id', 'mac_address', 'device_id'] position_values = [netid, 'mac', 'deviceid'] _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) def test_create_port_tenant(self): - """create_port --tenant-id tenantid netid""" + """Create port: --tenant_id tenantid netid.""" resource = 'port' cmd = CreatePort(MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' - args = ['--tenant-id', 'tenantid', netid, ] + args = ['--tenant_id', 'tenantid', netid, ] position_names = ['network_id'] position_values = [] position_values.extend([netid]) @@ -71,7 +71,7 @@ def test_create_port_tenant(self): tenant_id='tenantid') def test_create_port_tags(self): - """ create_port netid mac_address device_id --tags a b""" + """Create port: netid mac_address device_id --tags a b.""" resource = 'port' cmd = CreatePort(MyApp(sys.stdout), None) name = 'myname' @@ -85,33 +85,33 @@ def test_create_port_tags(self): position_names, position_values, tags=['a', 'b']) - def test_list_ports_detail(self): - """list_ports -D""" + def test_list_ports(self): + """List ports: -D.""" resources = "ports" cmd = ListPort(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_ports_tags(self): - """list_ports -- --tags a b""" + """List ports: -- --tags a b.""" resources = "ports" cmd = ListPort(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, tags=['a', 'b']) def test_list_ports_detail_tags(self): - """list_ports -D -- --tags a b""" + """List ports: -D -- --tags a b.""" resources = "ports" cmd = ListPort(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) def test_list_ports_fields(self): - """list_ports --fields a --fields b -- --fields c d""" + """List ports: --fields a --fields b -- --fields c d.""" resources = "ports" cmd = ListPort(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) def test_update_port(self): - """ update_port myid --name myname --tags a b""" + """Update port: myid --name myname --tags a b.""" resource = 'port' cmd = UpdatePort(MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', @@ -121,7 +121,7 @@ def test_update_port(self): ) def test_show_port(self): - """ show_port --fields id --fields name myid """ + """Show port: --fields id --fields name myid.""" resource = 'port' cmd = ShowPort(MyApp(sys.stdout), None) myid = 'myid' @@ -129,9 +129,7 @@ def test_show_port(self): self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) def test_delete_port(self): - """ - delete_port myid - """ + """Delete port: myid.""" resource = 'port' cmd = DeletePort(MyApp(sys.stdout), None) myid = 'myid' diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index 08385eed9..1c5d7aebf 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -29,7 +29,7 @@ class CLITestV20Subnet(CLITestV20Base): def test_create_subnet(self): - """ create_subnet --gateway gateway netid cidr""" + """Create sbunet: --gateway gateway netid cidr.""" resource = 'subnet' cmd = CreateSubnet(MyApp(sys.stdout), None) name = 'myname' @@ -45,14 +45,14 @@ def test_create_subnet(self): position_names, position_values) def test_create_subnet_tenant(self): - """create_subnet --tenant-id tenantid netid cidr""" + """Create subnet: --tenant_id tenantid netid cidr.""" resource = 'subnet' cmd = CreateSubnet(MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' cidr = 'prefixvalue' - args = ['--tenant-id', 'tenantid', netid, cidr] + args = ['--tenant_id', 'tenantid', netid, cidr] position_names = ['ip_version', 'network_id', 'cidr'] position_values = [4, ] position_values.extend([netid, cidr]) @@ -61,7 +61,7 @@ def test_create_subnet_tenant(self): tenant_id='tenantid') def test_create_subnet_tags(self): - """ create_subnet netid cidr --tags a b""" + """Create subnet: netid cidr --tags a b.""" resource = 'subnet' cmd = CreateSubnet(MyApp(sys.stdout), None) name = 'myname' @@ -77,32 +77,32 @@ def test_create_subnet_tags(self): tags=['a', 'b']) def test_list_subnets_detail(self): - """list_subnets -D""" + """List subnets: -D.""" resources = "subnets" cmd = ListSubnet(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_subnets_tags(self): - """list_subnets -- --tags a b""" + """List subnets: -- --tags a b.""" resources = "subnets" cmd = ListSubnet(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, tags=['a', 'b']) def test_list_subnets_detail_tags(self): - """list_subnets -D -- --tags a b""" + """List subnets: -D -- --tags a b.""" resources = "subnets" cmd = ListSubnet(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) def test_list_subnets_fields(self): - """list_subnets --fields a --fields b -- --fields c d""" + """List subnets: --fields a --fields b -- --fields c d.""" resources = "subnets" cmd = ListSubnet(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) def test_update_subnet(self): - """ update_subnet myid --name myname --tags a b""" + """Update subnet: myid --name myname --tags a b.""" resource = 'subnet' cmd = UpdateSubnet(MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', @@ -112,7 +112,7 @@ def test_update_subnet(self): ) def test_show_subnet(self): - """ show_subnet --fields id --fields name myid """ + """Show subnet: --fields id --fields name myid.""" resource = 'subnet' cmd = ShowSubnet(MyApp(sys.stdout), None) myid = 'myid' @@ -120,9 +120,7 @@ def test_show_subnet(self): self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) def test_delete_subnet(self): - """ - delete_subnet myid - """ + """Delete subnet: subnetid.""" resource = 'subnet' cmd = DeleteSubnet(MyApp(sys.stdout), None) myid = 'myid' From 42404616f79ff920d40775d6c4d44a4ce4c101c7 Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Sun, 8 Jul 2012 09:20:04 -0400 Subject: [PATCH 012/135] Support allocation pools for subnet Fixes bug 1022227 Usage examples: create_subnet --tenant_id --allocation_pool start=1.1.1.1,end=1.1.1.10 More than one allocation pool can be added by doing the following: --allocation_pool start=1.1.1.1,end=1.1.1.10 --allocation_pool start=1.1.1.123,end=1.1.1.125 ... Change-Id: I313625b6a8c1da6eaca8b1943da52738978ed72b --- quantumclient/quantum/v2_0/subnet.py | 24 ++++++++- quantumclient/tests/unit/test_cli20.py | 4 +- quantumclient/tests/unit/test_cli20_subnet.py | 54 ++++++++++++++++--- 3 files changed, 73 insertions(+), 9 deletions(-) diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 8dcf004c7..b24f723a9 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -17,6 +17,7 @@ import logging +from quantumclient import utils from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand @@ -24,12 +25,20 @@ from quantumclient.quantum.v2_0 import UpdateCommand +def _format_allocation_pools(subnet): + try: + return '\n'.join([utils.dumps(pool) for pool in + subnet['allocation_pools']]) + except Exception: + return '' + + class ListSubnet(ListCommand): """List networks that belong to a given tenant.""" resource = 'subnet' log = logging.getLogger(__name__ + '.ListSubnet') - _formatters = {} + _formatters = {'allocation_pools': _format_allocation_pools, } class ShowSubnet(ShowCommand): @@ -52,6 +61,12 @@ def add_known_arguments(self, parser): parser.add_argument( '--gateway', metavar='gateway', help='gateway ip of this subnet') + parser.add_argument( + '--allocation_pool', + action='append', + help='Allocation pool IP addresses for this subnet: ' + 'start=,end= ' + 'can be repeated') parser.add_argument( 'network_id', help='Network id of this subnet belongs to') @@ -67,6 +82,13 @@ def args2body(self, parsed_args): body['subnet'].update({'gateway_ip': parsed_args.gateway}) if parsed_args.tenant_id: body['subnet'].update({'tenant_id': parsed_args.tenant_id}) + ips = [] + if parsed_args.allocation_pool: + for ip_spec in parsed_args.allocation_pool: + ips.append(utils.str2dict(ip_spec)) + if ips: + body['subnet'].update({'allocation_pools': ips}) + return body diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 32601f9de..14a153e8f 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -159,8 +159,8 @@ def _test_create_resource(self, resource, cmd, self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() - self.assertTrue(myid, _str) - self.assertTrue(name, _str) + self.assertTrue(myid in _str) + self.assertTrue(name in _str) def _test_list_resources(self, resources, cmd, detail=False, tags=[], fields_1=[], fields_2=[]): diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index 1c5d7aebf..b649b744c 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -39,8 +39,7 @@ def test_create_subnet(self): gateway = 'gatewayvalue' args = ['--gateway', gateway, netid, cidr] position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] - position_values = [4, ] - position_values.extend([netid, cidr, gateway]) + position_values = [4, netid, cidr, gateway] _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) @@ -54,8 +53,7 @@ def test_create_subnet_tenant(self): cidr = 'prefixvalue' args = ['--tenant_id', 'tenantid', netid, cidr] position_names = ['ip_version', 'network_id', 'cidr'] - position_values = [4, ] - position_values.extend([netid, cidr]) + position_values = [4, netid, cidr] _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values, tenant_id='tenantid') @@ -70,12 +68,56 @@ def test_create_subnet_tags(self): cidr = 'prefixvalue' args = [netid, cidr, '--tags', 'a', 'b'] position_names = ['ip_version', 'network_id', 'cidr'] - position_values = [4, ] - position_values.extend([netid, cidr]) + position_values = [4, netid, cidr] _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values, tags=['a', 'b']) + def test_create_subnet_allocation_pool(self): + """Create subnet: --tenant_id tenantid netid cidr. + The is --allocation_pool start=1.1.1.10,end=1.1.1.20 + """ + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + '--allocation_pool', 'start=1.1.1.10,end=1.1.1.20', + netid, cidr] + position_names = ['ip_version', 'allocation_pools', 'network_id', + 'cidr'] + pool = [{'start': '1.1.1.10', 'end': '1.1.1.20'}] + position_values = [4, pool, netid, cidr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_subnet_allocation_pools(self): + """Create subnet: --tenant-id tenantid netid cidr. + The are --allocation_pool start=1.1.1.10,end=1.1.1.20 and + --allocation_pool start=1.1.1.30,end=1.1.1.40 + """ + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + '--allocation_pool', 'start=1.1.1.10,end=1.1.1.20', + '--allocation_pool', 'start=1.1.1.30,end=1.1.1.40', + netid, cidr] + position_names = ['ip_version', 'allocation_pools', 'network_id', + 'cidr'] + pools = [{'start': '1.1.1.10', 'end': '1.1.1.20'}, + {'start': '1.1.1.30', 'end': '1.1.1.40'}] + position_values = [4, pools, netid, cidr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + def test_list_subnets_detail(self): """List subnets: -D.""" resources = "subnets" From 463121305619a24001ba80be4ed899de148ccb75 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Wed, 11 Jul 2012 14:44:31 +0900 Subject: [PATCH 013/135] Use -h, --help to show help messages. Fixes bug 1023260 "quantumv2" command used '-H' and '--Help' to show help messages, but it is inconsistent to the convention of option names used in other OpenStack client lib commands. This commit fixes it. Change-Id: I85c1e79c2cd08bcc0112ed2f10ca8210e9384687 --- quantumclient/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantumclient/shell.py b/quantumclient/shell.py index f16e18692..4672f88bb 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -186,7 +186,7 @@ def build_option_parser(self, description, version): const=0, help='suppress output except warnings and errors', ) parser.add_argument( - '-H', '--Help', + '-h', '--help', action=HelpAction, nargs=0, default=self, # tricky From 7f3e6eeb7a56d26d786275461293ef48a4727ca8 Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Fri, 6 Jul 2012 12:22:54 +0800 Subject: [PATCH 014/135] Remove quantum client codes for API v1.0 Bug #1021546 Change-Id: Ic628db01034bc6dfb5fad3d6a2905e243900229a --- quantumclient/__init__.py | 596 -------------- quantumclient/cli.py | 299 -------- quantumclient/cli_lib.py | 591 -------------- quantumclient/quantum/client.py | 28 +- quantumclient/quantum/v1_1/__init__.py | 62 -- quantumclient/quantum/v1_1/interface.py | 97 --- quantumclient/quantum/v1_1/network.py | 268 ------- quantumclient/quantum/v1_1/port.py | 222 ------ quantumclient/quantum/v2_0/port.py | 2 +- quantumclient/quantum/v2_0/subnet.py | 2 +- quantumclient/shell.py | 66 +- quantumclient/tests/unit/stubs.py | 65 -- quantumclient/tests/unit/test_cli.py | 853 --------------------- quantumclient/tests/unit/test_clientlib.py | 770 ------------------- setup.py | 3 +- tools/test-requires | 1 - 16 files changed, 14 insertions(+), 3911 deletions(-) delete mode 100755 quantumclient/cli.py delete mode 100755 quantumclient/cli_lib.py delete mode 100644 quantumclient/quantum/v1_1/__init__.py delete mode 100644 quantumclient/quantum/v1_1/interface.py delete mode 100644 quantumclient/quantum/v1_1/network.py delete mode 100644 quantumclient/quantum/v1_1/port.py delete mode 100644 quantumclient/tests/unit/stubs.py delete mode 100644 quantumclient/tests/unit/test_cli.py delete mode 100644 quantumclient/tests/unit/test_clientlib.py diff --git a/quantumclient/__init__.py b/quantumclient/__init__.py index e9cfde4de..5558fdb97 100644 --- a/quantumclient/__init__.py +++ b/quantumclient/__init__.py @@ -17,603 +17,7 @@ # @author: Tyler Smith, Cisco Systems import gettext -import httplib -import logging -import socket -import time -import urllib # gettext must be initialized before any quantumclient imports gettext.install('quantumclient', unicode=1) - - -from quantumclient.common import exceptions -from quantumclient.common.serializer import Serializer -from quantumclient.common import utils - -net_filters_v11_opt = [] -net_filters_v11_opt.append({'--name': - {'help': _('name filter'), }, }) -net_filters_v11_opt.append({'--op-status': - {'help': _('op-status filter'), }, }) -net_filters_v11_opt.append({'--port-op-status': - {'help': _('port-op-status filter'), }, }) -net_filters_v11_opt.append({'--port-state': - {'help': _('port-state filter'), }, }) -net_filters_v11_opt.append({'--has-attachment': - {'help': _('has-attachment filter'), }, }) -net_filters_v11_opt.append({'--attachment': - {'help': _('attachment filter'), }, }) -net_filters_v11_opt.append({'--port': - {'help': _('port filter'), }, }) - -port_filters_v11_opt = [] -port_filters_v11_opt.append({'--name': - {'help': _('name filter'), }, }) -port_filters_v11_opt.append({'--op-status': - {'help': _('op-status filter'), }, }) -port_filters_v11_opt.append({'--has-attachment': - {'help': _('has-attachment filter'), }, }) -port_filters_v11_opt.append({'--attachement': - {'help': _('attachment filter'), }, }) - -net_filters_v11 = [] -for arg in net_filters_v11_opt: - for key in arg.iterkeys(): - net_filters_v11.append(key.split('--', 2)[1]) - -port_filters_v11 = [] -for arg in port_filters_v11_opt: - for key in arg.iterkeys(): - port_filters_v11.append(key.split('--', 2)[1]) - -LOG = logging.getLogger('quantumclient') - -AUTH_TOKEN_HEADER = "X-Auth-Token" - - -def exception_handler_v10(status_code, error_content): - """ Exception handler for API v1.0 client - - This routine generates the appropriate - Quantum exception according to the contents of the - response body - - :param status_code: HTTP error status code - :param error_content: deserialized body of error response - """ - - quantum_error_types = { - 420: 'networkNotFound', - 421: 'networkInUse', - 430: 'portNotFound', - 431: 'requestedStateInvalid', - 432: 'portInUse', - 440: 'alreadyAttached', } - - quantum_errors = { - 400: exceptions.BadInputError, - 401: exceptions.Unauthorized, - 404: exceptions.NotFound, - 420: exceptions.NetworkNotFoundClient, - 421: exceptions.NetworkInUseClient, - 430: exceptions.PortNotFoundClient, - 431: exceptions.StateInvalidClient, - 432: exceptions.PortInUseClient, - 440: exceptions.AlreadyAttachedClient, - 501: NotImplementedError, } - - # Find real error type - error_type = None - if isinstance(error_content, dict): - error_type = quantum_error_types.get(status_code) - if error_type: - error_dict = error_content[error_type] - error_message = error_dict['message'] + "\n" + error_dict['detail'] - else: - error_message = error_content - # raise the appropriate error! - ex = quantum_errors[status_code](message=error_message) - ex.args = ([dict(status_code=status_code, - message=error_message)],) - raise ex - - -def exception_handler_v11(status_code, error_content): - """ Exception handler for API v1.1 client - - This routine generates the appropriate - Quantum exception according to the contents of the - response body - - :param status_code: HTTP error status code - :param error_content: deserialized body of error response - """ - - quantum_errors = { - 'NetworkNotFound': exceptions.NetworkNotFoundClient, - 'NetworkInUse': exceptions.NetworkInUseClient, - 'PortNotFound': exceptions.PortNotFoundClient, - 'RequestedStateInvalid': exceptions.StateInvalidClient, - 'PortInUse': exceptions.PortInUseClient, - 'AlreadyAttached': exceptions.AlreadyAttachedClient, - 'QuantumServiceFault': exceptions.QuantumClientException, } - - error_dict = None - if isinstance(error_content, dict): - error_dict = error_content.get('QuantumError') - # Find real error type - if error_dict: - # If QuantumError key is found, it will definitely contain - # a 'message' and 'type' keys - error_type = error_dict['type'] - error_message = (error_dict['message'] + "\n" + - error_dict['detail']) - # raise the appropriate error! - ex = quantum_errors[error_type](message=error_message) - ex.args = ([dict(status_code=status_code, - message=error_message)],) - raise ex - # If we end up here the exception was not a quantum error - msg = "%s-%s" % (status_code, error_content) - raise exceptions.QuantumClientException(message=msg) - - -EXCEPTION_HANDLERS = { - '1.0': exception_handler_v10, - '1.1': exception_handler_v11, -} - - -class ApiCall(object): - """A Decorator to add support for format and tenant overriding""" - def __init__(self, function): - self.function = function - - def __get__(self, instance, owner): - def with_params(*args, **kwargs): - """ - Temporarily sets the format and tenant for this request - """ - (format, tenant) = (instance.format, instance.tenant) - - if 'format' in kwargs: - instance.format = kwargs['format'] - if 'tenant' in kwargs: - instance.tenant = kwargs['tenant'] - - ret = self.function(instance, *args) - (instance.format, instance.tenant) = (format, tenant) - return ret - return with_params - - -class APIFilterCall(object): - - def __init__(self, filters): - self.filters = filters - - def __call__(self, f): - def wrapped_f(*args, **kwargs): - """ - Temporarily sets the format and tenant for this request - """ - instance = args[0] - (format, tenant) = (instance.format, instance.tenant) - - if 'format' in kwargs: - instance.format = kwargs['format'] - if 'format' not in self.filters: - del kwargs['format'] - if 'tenant' in kwargs: - instance.tenant = kwargs['tenant'] - if 'tenant' not in self.filters: - del kwargs['tenant'] - ret = f(*args, **kwargs) - (instance.format, instance.tenant) = (format, tenant) - return ret - return wrapped_f - - -class Client(object): - - """A base client class - derived from Glance.BaseClient""" - - #Metadata for deserializing xml - _serialization_metadata = { - "application/xml": { - "attributes": { - "network": ["id", "name"], - "port": ["id", "state"], - "attachment": ["id"], }, - "plurals": { - "networks": "network", - "ports": "port", }, }, } - - # Action query strings - networks_path = "/networks" - network_path = "/networks/%s" - ports_path = "/networks/%s/ports" - port_path = "/networks/%s/ports/%s" - attachment_path = "/networks/%s/ports/%s/attachment" - detail_path = "/detail" - - def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None, - format="xml", testingStub=None, key_file=None, cert_file=None, - auth_token=None, logger=None, - version="1.1", - uri_prefix="/tenants/{tenant_id}", - retries=0, - retry_interval=1): - """ - Creates a new client to some service. - - :param host: The host where service resides - :param port: The port where service resides - :param use_ssl: True to use SSL, False to use HTTP - :param tenant: The tenant ID to make requests with - :param format: The format to query the server with - :param testingStub: A class that stubs basic server methods for tests - :param key_file: The SSL key file to use if use_ssl is true - :param cert_file: The SSL cert file to use if use_ssl is true - :param auth_token: authentication token to be passed to server - :param logger: Logger object for the client library - :param action_prefix: prefix for request URIs - :param retries: How many times to retry a failed connection attempt - :param retry_interval: The # of seconds between connection attempts - """ - self.host = host - self.port = port - self.use_ssl = use_ssl - self.tenant = tenant - self.format = format - self.connection = None - self.testingStub = testingStub - self.key_file = key_file - self.cert_file = cert_file - self.logger = logger - if not self.logger: - self.logger = LOG - self.auth_token = auth_token - self.version = version - self.action_prefix = "/v%s%s" % (version, uri_prefix) - self.retries = retries - self.retry_interval = retry_interval - - def _handle_fault_response(self, status_code, response_body): - # Create exception with HTTP status code and message - error_message = response_body - LOG.debug("Server returned error: %s", status_code) - LOG.debug("Error message: %s", error_message) - # Add deserialized error message to exception arguments - try: - des_error_body = Serializer().deserialize(error_message, - self.content_type()) - except: - # If unable to deserialized body it is probably not a - # Quantum error - des_error_body = {'message': error_message} - # Raise the appropriate exception - EXCEPTION_HANDLERS[self.version](status_code, des_error_body) - - def get_connection_type(self): - """ - Returns the proper connection type - """ - if self.testingStub: - return self.testingStub - if self.use_ssl: - return httplib.HTTPSConnection - else: - return httplib.HTTPConnection - - def _send_request(self, conn, method, action, body, headers): - # Salvatore: Isolating this piece of code in its own method to - # facilitate stubout for testing - if self.logger: - self.logger.debug("Quantum Client Request:\n" - + method + " " + action + "\n") - if body: - self.logger.debug(body) - conn.request(method, action, body, headers) - return conn.getresponse() - - def do_request(self, method, action, body=None, - headers=None, params=None): - """ - Connects to the server and issues a request. - Returns the result data, or raises an appropriate exception if - HTTP status code is not 2xx - - :param method: HTTP method ("GET", "POST", "PUT", etc...) - :param body: string of data to send, or None (default) - :param headers: mapping of key/value pairs to add as headers - :param params: dictionary of key/value pairs to add to append - to action - :raises: ConnectionFailed - """ - LOG.debug("Client issuing request: %s", action) - # Ensure we have a tenant id - if not self.tenant: - raise Exception("Tenant ID not set") - # Add format and tenant_id - action += ".%s" % self.format - action = self.action_prefix + action - action = action.replace('{tenant_id}', self.tenant) - - if type(params) is dict: - action += '?' + urllib.urlencode(params) - if body: - body = self.serialize(body) - try: - connection_type = self.get_connection_type() - headers = headers or {"Content-Type": - "application/%s" % self.format} - # if available, add authentication token - if self.auth_token: - headers[AUTH_TOKEN_HEADER] = self.auth_token - # Open connection and send request, handling SSL certs - certs = {'key_file': self.key_file, 'cert_file': self.cert_file} - certs = dict((x, certs[x]) for x in certs if certs[x] is not None) - if self.use_ssl and len(certs): - conn = connection_type(self.host, self.port, **certs) - else: - conn = connection_type(self.host, self.port) - res = self._send_request(conn, method, action, body, headers) - status_code = self.get_status_code(res) - data = res.read() - utils.http_log(LOG, [method, action], - {'body': body, - 'headers': headers, - }, res, data) - if self.logger: - self.logger.debug("Quantum Client Reply (code = %s) :\n %s" % - (str(status_code), data)) - if status_code in (httplib.OK, - httplib.CREATED, - httplib.ACCEPTED, - httplib.NO_CONTENT): - return self.deserialize(data, status_code) - else: - self._handle_fault_response(status_code, data) - except (socket.error, IOError), e: - exc = exceptions.ConnectionFailed(reason=str(e)) - LOG.exception(exc.message) - raise exc - - def get_status_code(self, response): - """ - Returns the integer status code from the response, which - can be either a Webob.Response (used in testing) or httplib.Response - """ - if hasattr(response, 'status_int'): - return response.status_int - else: - return response.status - - def serialize(self, data): - """ - Serializes a dictionary with a single key (which can contain any - structure) into either xml or json - """ - if data is None: - return None - elif type(data) is dict: - return Serializer().serialize(data, self.content_type()) - else: - raise Exception("unable to serialize object of type = '%s'" % - type(data)) - - def deserialize(self, data, status_code): - """ - Deserializes a an xml or json string into a dictionary - """ - if status_code == 204: - return data - return Serializer(self._serialization_metadata).deserialize( - data, self.content_type()) - - def content_type(self, format=None): - """ - Returns the mime-type for either 'xml' or 'json'. Defaults to the - currently set format - """ - if not format: - format = self.format - return "application/%s" % (format) - - def retry_request(self, method, action, body=None, - headers=None, params=None): - """ - Call do_request with the default retry configuration. Only - idempotent requests should retry failed connection attempts. - - :raises: ConnectionFailed if the maximum # of retries is exceeded - """ - max_attempts = self.retries + 1 - for i in xrange(max_attempts): - try: - return self.do_request(method, action, body=body, - headers=headers, params=params) - except exceptions.ConnectionFailed: - # Exception has already been logged by do_request() - if i < self.retries: - LOG.debug(_('Retrying connection to quantum service')) - time.sleep(self.retry_interval) - - raise exceptions.ConnectionFailed(reason=_("Maximum attempts reached")) - - def delete(self, action, body=None, headers=None, params=None): - return self.retry_request("DELETE", action, body=body, - headers=headers, params=params) - - def get(self, action, body=None, headers=None, params=None): - return self.retry_request("GET", action, body=body, - headers=headers, params=params) - - def post(self, action, body=None, headers=None, params=None): - # Do not retry POST requests to avoid the orphan objects problem. - return self.do_request("POST", action, body=body, - headers=headers, params=params) - - def put(self, action, body=None, headers=None, params=None): - return self.retry_request("PUT", action, body=body, - headers=headers, params=params) - - @ApiCall - def list_networks(self): - """ - Fetches a list of all networks for a tenant - """ - return self.get(self.networks_path) - - @ApiCall - def list_networks_details(self): - """ - Fetches a detailed list of all networks for a tenant - """ - return self.get(self.networks_path + self.detail_path) - - @ApiCall - def show_network(self, network): - """ - Fetches information of a certain network - """ - return self.get(self.network_path % (network)) - - @ApiCall - def show_network_details(self, network): - """ - Fetches the details of a certain network - """ - return self.get((self.network_path + self.detail_path) % (network)) - - @ApiCall - def create_network(self, body=None): - """ - Creates a new network - """ - return self.post(self.networks_path, body=body) - - @ApiCall - def update_network(self, network, body=None): - """ - Updates a network - """ - return self.put(self.network_path % (network), body=body) - - @ApiCall - def delete_network(self, network): - """ - Deletes the specified network - """ - return self.delete(self.network_path % (network)) - - @ApiCall - def list_ports(self, network): - """ - Fetches a list of ports on a given network - """ - return self.get(self.ports_path % (network)) - - @ApiCall - def list_ports_details(self, network): - """ - Fetches a detailed list of ports on a given network - """ - return self.get((self.ports_path + self.detail_path) % (network)) - - @ApiCall - def show_port(self, network, port): - """ - Fetches the information of a certain port - """ - return self.get(self.port_path % (network, port)) - - @ApiCall - def show_port_details(self, network, port): - """ - Fetches the details of a certain port - """ - return self.get((self.port_path + self.detail_path) % (network, port)) - - @ApiCall - def create_port(self, network, body=None): - """ - Creates a new port on a given network - """ - return self.post(self.ports_path % (network), body=body) - - @ApiCall - def delete_port(self, network, port): - """ - Deletes the specified port from a network - """ - return self.delete(self.port_path % (network, port)) - - @ApiCall - def update_port(self, network, port, body=None): - """ - Sets the attributes of the specified port - """ - return self.put(self.port_path % (network, port), body=body) - - @ApiCall - def show_port_attachment(self, network, port): - """ - Fetches the attachment-id associated with the specified port - """ - return self.get(self.attachment_path % (network, port)) - - @ApiCall - def attach_resource(self, network, port, body=None): - """ - Sets the attachment-id of the specified port - """ - return self.put(self.attachment_path % (network, port), body=body) - - @ApiCall - def detach_resource(self, network, port): - """ - Removes the attachment-id of the specified port - """ - return self.delete(self.attachment_path % (network, port)) - - -class ClientV11(Client): - """ - This class overiddes some methods of the Client class in order to deal with - features specific to API v1.1 such as filters - """ - - @APIFilterCall(net_filters_v11) - def list_networks(self, **filters): - """ - Fetches a list of all networks for a tenant - """ - # Pass filters in "params" argument to do_request - return self.get(self.networks_path, params=filters) - - @APIFilterCall(net_filters_v11) - def list_networks_details(self, **filters): - """ - Fetches a detailed list of all networks for a tenant - """ - return self.get(self.networks_path + self.detail_path, params=filters) - - @APIFilterCall(port_filters_v11) - def list_ports(self, network, **filters): - """ - Fetches a list of ports on a given network - """ - # Pass filters in "params" argument to do_request - return self.get(self.ports_path % (network), params=filters) - - @APIFilterCall(port_filters_v11) - def list_ports_details(self, network, **filters): - """ - Fetches a detailed list of ports on a given network - """ - return self.get((self.ports_path + self.detail_path) % (network), - params=filters) diff --git a/quantumclient/cli.py b/quantumclient/cli.py deleted file mode 100755 index 5956f7db0..000000000 --- a/quantumclient/cli.py +++ /dev/null @@ -1,299 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2012 Nicira Networks, Inc. -# Copyright 2012 Citrix Systems -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# @author: Somik Behera, Nicira Networks, Inc. -# @author: Brad Hall, Nicira Networks, Inc. -# @author: Salvatore Orlando, Citrix - -import logging -import logging.handlers -from optparse import OptionParser -import os -import sys - -from quantumclient import cli_lib -from quantumclient import Client -from quantumclient import ClientV11 -from quantumclient.common import exceptions -from quantumclient.common import utils -from quantumclient import net_filters_v11 -from quantumclient import port_filters_v11 - -# Configure logger for client - cli logger is a child of it -# NOTE(salvatore-orlando): logger name does not map to package -# this is deliberate. Simplifies logger configuration -logging.basicConfig() -LOG = logging.getLogger('quantumclient') - - -DEFAULT_QUANTUM_VERSION = '1.1' -FORMAT = 'json' -commands_v10 = { - "list_nets": { - "func": cli_lib.list_nets, - "args": ["tenant-id"], }, - "list_nets_detail": { - "func": cli_lib.list_nets_detail, - "args": ["tenant-id"], }, - "create_net": { - "func": cli_lib.create_net, - "args": ["tenant-id", "net-name"], }, - "delete_net": { - "func": cli_lib.delete_net, - "args": ["tenant-id", "net-id"], }, - "show_net": { - "func": cli_lib.show_net, - "args": ["tenant-id", "net-id"], }, - "show_net_detail": { - "func": cli_lib.show_net_detail, - "args": ["tenant-id", "net-id"], }, - "update_net": { - "func": cli_lib.update_net, - "args": ["tenant-id", "net-id", "new-name"], }, - "list_ports": { - "func": cli_lib.list_ports, - "args": ["tenant-id", "net-id"], }, - "list_ports_detail": { - "func": cli_lib.list_ports_detail, - "args": ["tenant-id", "net-id"], }, - "create_port": { - "func": cli_lib.create_port, - "args": ["tenant-id", "net-id"], }, - "delete_port": { - "func": cli_lib.delete_port, - "args": ["tenant-id", "net-id", "port-id"], }, - "update_port": { - "func": cli_lib.update_port, - "args": ["tenant-id", "net-id", "port-id", "params"], }, - "show_port": { - "func": cli_lib.show_port, - "args": ["tenant-id", "net-id", "port-id"], }, - "show_port_detail": { - "func": cli_lib.show_port_detail, - "args": ["tenant-id", "net-id", "port-id"], }, - "plug_iface": { - "func": cli_lib.plug_iface, - "args": ["tenant-id", "net-id", "port-id", "iface-id"], }, - "unplug_iface": { - "func": cli_lib.unplug_iface, - "args": ["tenant-id", "net-id", "port-id"], }, - "show_iface": { - "func": cli_lib.show_iface, - "args": ["tenant-id", "net-id", "port-id"], }, } - -commands_v11 = commands_v10.copy() -commands_v11.update({ - "list_nets": { - "func": cli_lib.list_nets_v11, - "args": ["tenant-id"], - "filters": net_filters_v11, }, - "list_nets_detail": { - "func": cli_lib.list_nets_detail_v11, - "args": ["tenant-id"], - "filters": net_filters_v11, }, - "list_ports": { - "func": cli_lib.list_ports_v11, - "args": ["tenant-id", "net-id"], - "filters": port_filters_v11, }, - "list_ports_detail": { - "func": cli_lib.list_ports_detail_v11, - "args": ["tenant-id", "net-id"], - "filters": port_filters_v11, }, }) -commands = { - '1.0': commands_v10, - '1.1': commands_v11, } -clients = { - '1.0': Client, - '1.1': ClientV11, } - - -def help(version): - print "\nCommands:" - cmds = commands[version] - for k in cmds.keys(): - print " %s %s %s" % ( - k, - " ".join(["<%s>" % y for y in cmds[k]["args"]]), - 'filters' in cmds[k] and "[filterspec ...]" or "") - - -def print_usage(cmd, version): - cmds = commands[version] - print "Usage:\n %s %s" % ( - cmd, " ".join(["<%s>" % y for y in cmds[cmd]["args"]])) - - -def build_args(cmd, cmdargs, arglist): - arglist_len = len(arglist) - cmdargs_len = len(cmdargs) - if arglist_len < cmdargs_len: - message = ("Not enough arguments for \"%s\" (expected: %d, got: %d)" % - (cmd, len(cmdargs), arglist_len)) - raise exceptions.QuantumCLIError(message=message) - args = arglist[:cmdargs_len] - return args - - -def build_filters(cmd, cmd_filters, filter_list, version): - filters = {} - # Each filter is expected to be in the = format - for flt in filter_list: - split_filter = flt.split("=") - if len(split_filter) != 2: - message = "Invalid filter argument detected (%s)" % flt - raise exceptions.QuantumCLIError(message=message) - filter_key, filter_value = split_filter - # Ensure the filter is allowed - if not filter_key in cmd_filters: - message = "Invalid filter key (%s)" % filter_key - raise exceptions.QuantumCLIError(message=message) - filters[filter_key] = filter_value - return filters - - -def build_cmd(cmd, cmd_args, cmd_filters, arglist, version): - """ - Builds arguments and filters to be passed to the cli library routines - - :param cmd: Command to be executed - :param cmd_args: List of arguments required by the command - :param cmd_filters: List of filters allowed by the command - :param arglist: Command line arguments (includes both arguments and - filter specifications) - :param version: API version - """ - arglist_len = len(arglist) - try: - # Parse arguments - args = build_args(cmd, cmd_args, arglist) - # Parse filters - filters = None - if cmd_filters: - # Pop consumed arguments - arglist = arglist[len(args):] - filters = build_filters(cmd, cmd_filters, arglist, version) - except exceptions.QuantumCLIError as cli_ex: - LOG.error(cli_ex.message) - print " Error in command line:%s" % cli_ex.message - print_usage(cmd, version) - return None, None - filter_len = (filters is not None) and len(filters) or 0 - if len(arglist) - len(args) - filter_len > 0: - message = ("Too many arguments for \"%s\" (expected: %d, got: %d)" % - (cmd, len(cmd_args), arglist_len)) - LOG.error(message) - print "Error in command line: %s " % message - print "Usage:\n %s %s" % ( - cmd, - " ".join(["<%s>" % y for y in commands[version][cmd]["args"]])) - return None, None - # Append version to arguments for cli functions - args.append(version) - return args, filters - - -def instantiate_client(host, port, ssl, tenant, token, version): - client = clients[version](host, - port, - ssl, - tenant, - FORMAT, - auth_token=token, - version=version) - return client - - -def main(): - usagestr = "Usage: %prog [OPTIONS] [args]" - parser = OptionParser(usage=usagestr) - parser.add_option("-H", "--host", dest="host", - type="string", default="127.0.0.1", - help="ip address of api host") - parser.add_option("-p", "--port", dest="port", - type="int", default=9696, help="api poort") - parser.add_option("-s", "--ssl", dest="ssl", - action="store_true", default=False, help="use ssl") - parser.add_option("--debug", dest="debug", - action="store_true", default=False, - help="print debugging output") - parser.add_option("-f", "--logfile", dest="logfile", - type="string", default="syslog", help="log file path") - parser.add_option("-t", "--token", dest="token", - type="string", default=None, help="authentication token") - parser.add_option( - '--version', - default=utils.env('QUANTUM_VERSION', default=DEFAULT_QUANTUM_VERSION), - help='Accepts 1.1 and 1.0, defaults to env[QUANTUM_VERSION].') - options, args = parser.parse_args() - - if options.debug: - LOG.setLevel(logging.DEBUG) - else: - LOG.setLevel(logging.WARN) - - if options.logfile == "syslog": - LOG.addHandler(logging.handlers.SysLogHandler(address='/dev/log')) - else: - LOG.addHandler(logging.handlers.WatchedFileHandler(options.logfile)) - # Set permissions on log file - os.chmod(options.logfile, 0644) - - version = options.version - if not version in commands: - LOG.error("Unknown API version specified:%s", version) - parser.print_help() - sys.exit(1) - - if len(args) < 1: - parser.print_help() - help(version) - sys.exit(1) - - cmd = args[0] - if cmd not in commands[version].keys(): - LOG.error("Unknown command: %s" % cmd) - help(version) - sys.exit(1) - - # Build argument list for CLI command - # The argument list will include the version number as well - args, filters = build_cmd(cmd, - commands[version][cmd]["args"], - commands[version][cmd].get("filters", None), - args[1:], - options.version) - if not args: - sys.exit(1) - LOG.info("Executing command \"%s\" with args: %s" % (cmd, args)) - - client = instantiate_client(options.host, - options.port, - options.ssl, - args[0], - options.token, - options.version) - # append filters to arguments - # this will allow for using the same prototype for v10 and v11 - # TODO: Use **kwargs instead of *args (keyword is better than positional) - if filters: - args.append(filters) - commands[version][cmd]["func"](client, *args) - - LOG.info("Command execution completed") - sys.exit(0) - -if __name__ == '__main__': - main() diff --git a/quantumclient/cli_lib.py b/quantumclient/cli_lib.py deleted file mode 100755 index 081414bf6..000000000 --- a/quantumclient/cli_lib.py +++ /dev/null @@ -1,591 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Nicira Networks, Inc. -# Copyright 2011 Citrix Systems -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# @author: Somik Behera, Nicira Networks, Inc. -# @author: Brad Hall, Nicira Networks, Inc. -# @author: Salvatore Orlando, Citrix - -""" Functions providing implementation for CLI commands. """ - -import logging -import traceback - - -LOG = logging.getLogger('quantumclient.cli_lib') - - -FORMAT = "json" - - -class OutputTemplate(object): - """ A class for generating simple templated output. - Based on Python templating mechanism. - Templates can also express attributes on objects, such as network.id; - templates can also be nested, thus allowing for iteration on inner - templates. - - Examples: - 1) template with class attributes - Name: %(person.name)s \n - Surname: %(person.surname)s \n - 2) template with iteration - Telephone numbers: \n - %(phone_numbers|Telephone number:%(number)s) - 3) template with iteration and class attributes - Addresses: \n - %(Addresses|Street:%(address.street)s\nNumber%(address.number)) - - Instances of this class are initialized with a template string and - the dictionary for performing substitution. The class implements the - __str__ method, so it can be directly printed. - """ - - def __init__(self, template, data): - self._template = template - self.data = data - - def __str__(self): - return self._template % self - - def __getitem__(self, key): - items = key.split("|") - # Note(madhav-puri): for now the logic supports only 0 or 1 list - # indicators (|) in a template. - if len(items) > 2: - raise Exception("Found more than one list indicator (|).") - if len(items) == 1: - return self._make_attribute(key) - else: - # Note(salvatore-orlando): items[0] must be subscriptable - return self._make_list(self._make_attribute(items[0]), items[1]) - - def _make_attribute(self, item): - """ Renders an entity attribute key in the template. - e.g.: entity.attribute - """ - items = item.split('.') - attr = self.data[items[0]] - for _item in items[1:]: - attr = attr[_item] - return attr - - def _make_list(self, items, inner_template): - """ Renders a list key in the template. - e.g.: %(list|item data:%(item)) - """ - #make sure list is subscriptable - if not hasattr(items, '__getitem__'): - raise Exception("Element is not iterable") - return "\n".join([str(OutputTemplate(inner_template, item)) - for item in items]) - - -class CmdOutputTemplate(OutputTemplate): - """ This class provides templated output for CLI commands. - Extends OutputTemplate loading a different template for each command. - """ - - _templates_v10 = dict( - list_nets=""" -Virtual Networks for Tenant %(tenant_id)s -%(networks|\tNetwork ID: %(id)s)s -""".strip(), - - list_nets_detail=""" -Virtual Networks for Tenant %(tenant_id)s -%(networks|\tNetwork %(name)s with ID: %(id)s)s -""".strip(), - - show_net=""" -Network ID: %(network.id)s -Network Name: %(network.name)s -""".strip(), - - show_net_detail=""" -Network ID: %(network.id)s -Network Name: %(network.name)s -Ports: %(network.ports|\tID: %(id)s -\t\tadministrative state: %(state)s -\t\tinterface: %(attachment.id)s)s -""".strip(), - - create_net=""" -Created a new Virtual Network with ID: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), - - update_net=""" -Updated Virtual Network with ID: %(network.id)s -for Tenant: %(tenant_id)s -""".strip(), - - delete_net=""" -Deleted Virtual Network with ID: %(network_id)s -for Tenant %(tenant_id)s -""".strip(), - - list_ports=""" -Ports on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -%(ports|\tLogical Port: %(id)s)s -""".strip(), - - list_ports_detail=""" -Ports on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -%(ports|\tLogical Port: %(id)s -\t\tadministrative State: %(state)s)s -""".strip(), - - create_port=""" -Created new Logical Port with ID: %(port_id)s -on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), - - show_port=""" -Logical Port ID: %(port.id)s -administrative State: %(port.state)s -on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), - - show_port_detail=""" -Logical Port ID: %(port.id)s -administrative State: %(port.state)s -interface: %(port.attachment.id)s -on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), - - update_port=""" -Updated Logical Port with ID: %(port.id)s -on Virtual Network: %(network_id)s -for tenant: %(tenant_id)s -""".strip(), - - delete_port=""" -Deleted Logical Port with ID: %(port_id)s -on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), - - plug_iface=""" -Plugged interface %(attachment)s -into Logical Port: %(port_id)s -on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), - - unplug_iface=""" -Unplugged interface from Logical Port: %(port_id)s -on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), - - show_iface=""" -interface: %(iface.id)s -plugged in Logical Port ID: %(port_id)s -on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), ) - - _templates_v11 = _templates_v10.copy() - _templates_v11.update(dict( - show_net=""" -Network ID: %(network.id)s -network Name: %(network.name)s -operational Status: %(network.op-status)s -""".strip(), - - show_net_detail=""" -Network ID: %(network.id)s -Network Name: %(network.name)s -operational Status: %(network.op-status)s -Ports: %(network.ports|\tID: %(id)s -\t\tadministrative state: %(state)s -\t\tinterface: %(attachment.id)s)s -""".strip(), - - show_port=""" -Logical Port ID: %(port.id)s -administrative state: %(port.state)s -operational status: %(port.op-status)s -on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), - - show_port_detail=""" -Logical Port ID: %(port.id)s -administrative State: %(port.state)s -operational status: %(port.op-status)s -interface: %(port.attachment.id)s -on Virtual Network: %(network_id)s -for Tenant: %(tenant_id)s -""".strip(), )) - - _templates = { - '1.0': _templates_v10, - '1.1': _templates_v11, } - - def __init__(self, cmd, data, version): - super(CmdOutputTemplate, self).__init__( - self._templates[version][cmd], data) - - -def _handle_exception(ex): - status_code = None - message = None - # Retrieve dict at 1st element of tuple at last argument - if ex.args and isinstance(ex.args[-1][0], dict): - status_code = ex.args[-1][0].get('status_code', None) - message = ex.args[-1][0].get('message', None) - msg_1 = ("Command failed with error code: %s" % - (status_code or '')) - msg_2 = "Error message:%s" % (message or '') - print msg_1 - print msg_2 - else: - traceback.print_exc() - - -def prepare_output(cmd, tenant_id, response, version): - LOG.debug("Preparing output for response:%s, version:%s" % - (response, version)) - response['tenant_id'] = tenant_id - output = str(CmdOutputTemplate(cmd, response, version)) - LOG.debug("Finished preparing output for command:%s", cmd) - return output - - -def list_nets(client, *args): - tenant_id, version = args - try: - res = client.list_networks() - LOG.debug("Operation 'list_networks' executed.") - output = prepare_output("list_nets", tenant_id, res, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def list_nets_v11(client, *args): - filters = {} - tenant_id, version = args[:2] - if len(args) > 2: - filters = args[2] - try: - res = client.list_networks(**filters) - LOG.debug("Operation 'list_networks' executed.") - output = prepare_output("list_nets", tenant_id, res, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def list_nets_detail(client, *args): - tenant_id, version = args - try: - res = client.list_networks_details() - LOG.debug("Operation 'list_networks_details' executed.") - output = prepare_output("list_nets_detail", tenant_id, res, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def list_nets_detail_v11(client, *args): - filters = {} - tenant_id, version = args[:2] - if len(args) > 2: - filters = args[2] - try: - res = client.list_networks_details(**filters) - LOG.debug("Operation 'list_networks_details' executed.") - output = prepare_output("list_nets_detail", tenant_id, res, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def create_net(client, *args): - tenant_id, name, version = args - data = {'network': {'name': name}} - new_net_id = None - try: - res = client.create_network(data) - new_net_id = res["network"]["id"] - LOG.debug("Operation 'create_network' executed.") - output = prepare_output("create_net", - tenant_id, - dict(network_id=new_net_id), - version) - print output - except Exception as ex: - _handle_exception(ex) - - -def delete_net(client, *args): - tenant_id, network_id, version = args - try: - client.delete_network(network_id) - LOG.debug("Operation 'delete_network' executed.") - output = prepare_output("delete_net", - tenant_id, - dict(network_id=network_id), - version) - print output - except Exception as ex: - _handle_exception(ex) - - -def show_net(client, *args): - tenant_id, network_id, version = args - try: - res = client.show_network(network_id)["network"] - LOG.debug("Operation 'show_network' executed.") - output = prepare_output("show_net", tenant_id, - dict(network=res), - version) - print output - except Exception as ex: - _handle_exception(ex) - - -def show_net_detail(client, *args): - tenant_id, network_id, version = args - try: - res = client.show_network_details(network_id)["network"] - LOG.debug("Operation 'show_network_details' executed.") - if not len(res["ports"]): - res["ports"] = [{"id": "", - "state": "", - "attachment": {"id": ""}}] - else: - for port in res["ports"]: - if "attachment" not in port: - port["attachment"] = {"id": ""} - - output = prepare_output("show_net_detail", - tenant_id, - dict(network=res), - version) - print output - except Exception as ex: - _handle_exception(ex) - - -def update_net(client, *args): - tenant_id, network_id, param_data, version = args - data = {'network': {}} - for kv in param_data.split(","): - k, v = kv.split("=") - data['network'][k] = v - data['network']['id'] = network_id - try: - client.update_network(network_id, data) - LOG.debug("Operation 'update_network' executed.") - # Response has no body. Use data for populating output - output = prepare_output("update_net", tenant_id, data, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def list_ports(client, *args): - tenant_id, network_id, version = args - try: - ports = client.list_ports(network_id) - LOG.debug("Operation 'list_ports' executed.") - data = ports - data['network_id'] = network_id - output = prepare_output("list_ports", tenant_id, data, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def list_ports_v11(client, *args): - filters = {} - tenant_id, network_id, version = args[:3] - if len(args) > 3: - filters = args[3] - try: - ports = client.list_ports(network_id, **filters) - LOG.debug("Operation 'list_ports' executed.") - data = ports - data['network_id'] = network_id - output = prepare_output("list_ports", tenant_id, data, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def list_ports_detail(client, *args): - tenant_id, network_id, version = args - try: - ports = client.list_ports_details(network_id) - LOG.debug("Operation 'list_ports_details' executed.") - data = ports - data['network_id'] = network_id - output = prepare_output("list_ports_detail", tenant_id, data, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def list_ports_detail_v11(client, *args): - filters = {} - tenant_id, network_id, version = args[:3] - if len(args) > 3: - filters = args[3] - try: - ports = client.list_ports_details(network_id, **filters) - LOG.debug("Operation 'list_ports' executed.") - data = ports - data['network_id'] = network_id - output = prepare_output("list_ports_detail", tenant_id, data, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def create_port(client, *args): - tenant_id, network_id, version = args - try: - res = client.create_port(network_id) - LOG.debug("Operation 'create_port' executed.") - new_port_id = res["port"]["id"] - output = prepare_output("create_port", - tenant_id, - dict(network_id=network_id, - port_id=new_port_id), - version) - print output - except Exception as ex: - _handle_exception(ex) - - -def delete_port(client, *args): - tenant_id, network_id, port_id, version = args - try: - client.delete_port(network_id, port_id) - LOG.debug("Operation 'delete_port' executed.") - output = prepare_output("delete_port", - tenant_id, - dict(network_id=network_id, - port_id=port_id), - version) - print output - except Exception as ex: - _handle_exception(ex) - return - - -def show_port(client, *args): - tenant_id, network_id, port_id, version = args - try: - port = client.show_port(network_id, port_id)["port"] - LOG.debug("Operation 'show_port' executed.") - output = prepare_output("show_port", tenant_id, - dict(network_id=network_id, - port=port), - version) - print output - except Exception as ex: - _handle_exception(ex) - - -def show_port_detail(client, *args): - tenant_id, network_id, port_id, version = args - try: - port = client.show_port_details(network_id, port_id)["port"] - LOG.debug("Operation 'show_port_details' executed.") - if 'attachment' not in port: - port['attachment'] = {'id': ''} - output = prepare_output("show_port_detail", tenant_id, - dict(network_id=network_id, - port=port), - version) - print output - except Exception as ex: - _handle_exception(ex) - - -def update_port(client, *args): - tenant_id, network_id, port_id, param_data, version = args - data = {'port': {}} - for kv in param_data.split(","): - k, v = kv.split("=") - data['port'][k] = v - data['network_id'] = network_id - data['port']['id'] = port_id - try: - client.update_port(network_id, port_id, data) - LOG.debug("Operation 'udpate_port' executed.") - # Response has no body. Use data for populating output - output = prepare_output("update_port", tenant_id, data, version) - print output - except Exception as ex: - _handle_exception(ex) - - -def plug_iface(client, *args): - tenant_id, network_id, port_id, attachment, version = args - try: - data = {'attachment': {'id': '%s' % attachment}} - client.attach_resource(network_id, port_id, data) - LOG.debug("Operation 'attach_resource' executed.") - output = prepare_output("plug_iface", tenant_id, - dict(network_id=network_id, - port_id=port_id, - attachment=attachment), - version) - print output - except Exception as ex: - _handle_exception(ex) - - -def unplug_iface(client, *args): - tenant_id, network_id, port_id, version = args - try: - client.detach_resource(network_id, port_id) - LOG.debug("Operation 'detach_resource' executed.") - output = prepare_output("unplug_iface", - tenant_id, - dict(network_id=network_id, - port_id=port_id), - version) - print output - except Exception as ex: - _handle_exception(ex) - - -def show_iface(client, *args): - tenant_id, network_id, port_id, version = args - try: - iface = client.show_port_attachment(network_id, port_id)['attachment'] - if 'id' not in iface: - iface['id'] = '' - LOG.debug("Operation 'show_port_attachment' executed.") - output = prepare_output("show_iface", tenant_id, - dict(network_id=network_id, - port_id=port_id, - iface=iface), - version) - print output - except Exception as ex: - _handle_exception(ex) diff --git a/quantumclient/quantum/client.py b/quantumclient/quantum/client.py index 7aa1f1589..c7d7948d1 100644 --- a/quantumclient/quantum/client.py +++ b/quantumclient/quantum/client.py @@ -15,18 +15,12 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 -import logging -import urlparse - - +from quantumclient.common import exceptions from quantumclient.common import utils -LOG = logging.getLogger(__name__) API_NAME = 'network' API_VERSIONS = { - '1.0': 'quantumclient.Client', - '1.1': 'quantumclient.ClientV11', '2.0': 'quantumclient.v2_0.client.Client', } @@ -42,22 +36,7 @@ def make_client(instance): instance.initialize() url = instance._url url = url.rstrip("/") - client_full_name = (quantum_client.__module__ + "." + - quantum_client.__name__) - LOG.debug("we are using client: %s", client_full_name) - v1x = (client_full_name == API_VERSIONS['1.1'] or - client_full_name == API_VERSIONS['1.0']) - if v1x: - magic_tuple = urlparse.urlsplit(url) - scheme, netloc, path, query, frag = magic_tuple - host = magic_tuple.hostname - port = magic_tuple.port - use_ssl = scheme.lower().startswith('https') - client = quantum_client(host=host, port=port, use_ssl=use_ssl) - client.auth_token = instance._token - client.logger = LOG - return client - else: + if '2.0' == instance._api_version[API_NAME]: client = quantum_client(username=instance._username, tenant_name=instance._tenant_name, password=instance._password, @@ -67,3 +46,6 @@ def make_client(instance): token=instance._token, auth_strategy=instance._auth_strategy) return client + else: + raise exceptions.UnsupportedVersion("API version %s is not supported" % + instance._api_version[API_NAME]) diff --git a/quantumclient/quantum/v1_1/__init__.py b/quantumclient/quantum/v1_1/__init__.py deleted file mode 100644 index e8836d8ac..000000000 --- a/quantumclient/quantum/v1_1/__init__.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -import logging - -from quantumclient.common import command -from quantumclient.common import utils - - -class QuantumCommand(command.OpenStackCommand): - api = 'network' - log = logging.getLogger(__name__ + '.QuantumCommand') - - def get_parser(self, prog_name): - parser = super(QuantumCommand, self).get_parser(prog_name) - parser.add_argument( - '--request-format', - help=_('the xml or json request format'), - default='json', - choices=['json', 'xml', ], ) - parser.add_argument( - 'tenant_id', metavar='tenant-id', - help=_('the owner tenant ID'), ) - return parser - - -class QuantumPortCommand(QuantumCommand): - api = 'network' - log = logging.getLogger(__name__ + '.QuantumPortCommand') - - def get_parser(self, prog_name): - parser = super(QuantumPortCommand, self).get_parser(prog_name) - parser.add_argument( - 'net_id', metavar='net-id', - help=_('the owner network ID'), ) - return parser - - -class QuantumInterfaceCommand(QuantumPortCommand): - api = 'network' - log = logging.getLogger(__name__ + '.QuantumInterfaceCommand') - - def get_parser(self, prog_name): - parser = super(QuantumInterfaceCommand, self).get_parser(prog_name) - parser.add_argument( - 'port_id', metavar='port-id', - help=_('the owner Port ID'), ) - return parser diff --git a/quantumclient/quantum/v1_1/interface.py b/quantumclient/quantum/v1_1/interface.py deleted file mode 100644 index 955e459b2..000000000 --- a/quantumclient/quantum/v1_1/interface.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -import logging - -from cliff import show - -from quantumclient.quantum.v1_1 import QuantumInterfaceCommand - - -class PlugInterface(QuantumInterfaceCommand): - """Plug interface to a given port""" - - api = 'network' - log = logging.getLogger(__name__ + '.PlugInterface') - - def get_parser(self, prog_name): - parser = super(PlugInterface, self).get_parser(prog_name) - parser.add_argument( - 'iface_id', metavar='iface-id', - help='_(ID of the interface to plug)', ) - return parser - - def run(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - - data = {'attachment': {'id': '%s' % parsed_args.iface_id, }, } - quantum_client.attach_resource(parsed_args.net_id, - parsed_args.port_id, - data) - print >>self.app.stdout, (_('Plugged interface %(interfaceid)s' - ' into Logical Port %(portid)s') - % {'interfaceid': parsed_args.iface_id, - 'portid': parsed_args.port_id, }) - - return - - -class UnPlugInterface(QuantumInterfaceCommand): - """Unplug interface from a given port""" - - api = 'network' - log = logging.getLogger(__name__ + '.UnPlugInterface') - - def run(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - - quantum_client.detach_resource(parsed_args.net_id, parsed_args.port_id) - print >>self.app.stdout, ( - _('Unplugged interface on Logical Port %(portid)s') - % {'portid': parsed_args.port_id, }) - - return - - -class ShowInterface(QuantumInterfaceCommand, show.ShowOne): - """Show interface on a given port""" - - api = 'network' - log = logging.getLogger(__name__ + '.ShowInterface') - - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - - iface = quantum_client.show_port_attachment( - parsed_args.net_id, - parsed_args.port_id)['attachment'] - - if iface: - if 'id' not in iface: - iface['id'] = '' - else: - iface = {'': ''} - return zip(*sorted(iface.iteritems())) diff --git a/quantumclient/quantum/v1_1/network.py b/quantumclient/quantum/v1_1/network.py deleted file mode 100644 index 162976221..000000000 --- a/quantumclient/quantum/v1_1/network.py +++ /dev/null @@ -1,268 +0,0 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -import logging -import itertools - -from cliff import lister -from cliff import show - -from quantumclient.common import exceptions -from quantumclient.common import utils -from quantumclient import net_filters_v11_opt -from quantumclient.quantum.v1_1 import QuantumCommand - - -class ListNetwork(QuantumCommand, lister.Lister): - """List networks that belong to a given tenant""" - - api = 'network' - log = logging.getLogger(__name__ + '.ListNetwork') - - def get_parser(self, prog_name): - parser = super(ListNetwork, self).get_parser(prog_name) - parser.add_argument( - '--show-details', - help='show detailed info', - action='store_true', - default=False, ) - for net_filter in net_filters_v11_opt: - option_key = net_filter.keys()[0] - option_defs = net_filter.get(option_key) - parser.add_argument(option_key, **option_defs) - return parser - - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - search_opts = { - 'tenant': parsed_args.tenant_id, } - for net_filter in net_filters_v11_opt: - option_key = net_filter.keys()[0] - arg = option_key[2:] - arg = arg.replace('-', '_') - arg_value = getattr(parsed_args, arg, None) - if arg_value is not None: - search_opts.update({option_key[2:]: arg_value, }) - - self.log.debug('search options: %s', search_opts) - quantum_client.format = parsed_args.request_format - columns = ('ID', ) - data = None - if parsed_args.show_details: - data = quantum_client.list_networks_details(**search_opts) - # dict: {u'networks': [{u'op-status': u'UP', - # u'id': u'7a068b68-c736-42ab-9e43-c9d83c57627e', - # u'name': u'private'}]} - columns = ('ID', 'op-status', 'name', ) - else: - data = quantum_client.list_networks(**search_opts) - # {u'networks': [{u'id': - # u'7a068b68-c736-42ab-9e43-c9d83c57627e'}]} - networks = [] - if 'networks' in data: - networks = data['networks'] - - return (columns, - (utils.get_item_properties( - s, columns, formatters={}, ) for s in networks), ) - - -def _format_attachment(port): - # attachment {u'id': u'gw-7a068b68-c7'} - try: - return ('attachment' in port and port['attachment'] and - 'id' in port['attachment'] and - port['attachment']['id'] or '') - except Exception: - return '' - - -class ShowNetwork(QuantumCommand, show.ShowOne): - """Show information of a given network""" - - api = 'network' - log = logging.getLogger(__name__ + '.ShowNetwork') - - def get_parser(self, prog_name): - parser = super(ShowNetwork, self).get_parser(prog_name) - parser.add_argument( - 'net_id', metavar='net-id', - help='ID of network to display') - - parser.add_argument( - '--show-details', - help='show detailed info of networks', - action='store_true', - default=False, ) - return parser - - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - data = None - if parsed_args.show_details: - data = quantum_client.show_network_details(parsed_args.net_id) - else: - data = quantum_client.show_network(parsed_args.net_id) - # {u'network': {u'op-status': u'UP', 'xmlns': - # u'http://openstack.org/quantum/api/v1.1', u'id': - # u'7a068b68-c736-42ab-9e43-c9d83c57627e', u'name': u'private'}} - network = {} - ports = None - network = 'network' in data and data['network'] or None - if network: - ports = network.pop('ports', None) - column_names, data = zip(*sorted(network.iteritems())) - if not parsed_args.columns: - columns_to_include = column_names - else: - columns_to_include = [c for c in column_names - if c in parsed_args.columns] - # Set up argument to compress() - selector = [(c in columns_to_include) - for c in column_names] - data = list(itertools.compress(data, selector)) - formatter = self.formatters[parsed_args.formatter] - formatter.emit_one(columns_to_include, data, - self.app.stdout, parsed_args) - if ports: - print >>self.app.stdout, _('Network Ports:') - columns = ('op-status', 'state', 'id', 'attachment', ) - column_names, data = (columns, (utils.get_item_properties( - s, columns, formatters={'attachment': _format_attachment}, ) - for s in ports), ) - if not parsed_args.columns: - columns_to_include = column_names - data_gen = data - else: - columns_to_include = [c for c in column_names - if c in parsed_args.columns] - if not columns_to_include: - raise ValueError( - 'No recognized column names in %s' % - str(parsed_args.columns)) - # Set up argument to compress() - selector = [(c in columns_to_include) - for c in column_names] - # Generator expression to only return the parts of a row - # of data that the user has expressed interest in - # seeing. We have to convert the compress() output to a - # list so the table formatter can ask for its length. - data_gen = (list(itertools.compress(row, selector)) - for row in data) - formatter = self.formatters[parsed_args.formatter] - formatter.emit_list(columns_to_include, - data_gen, self.app.stdout, parsed_args) - - return ('', []) - - -class CreateNetwork(QuantumCommand, show.ShowOne): - """Create a network for a given tenant""" - - api = 'network' - log = logging.getLogger(__name__ + '.CreateNetwork') - - def get_parser(self, prog_name): - parser = super(CreateNetwork, self).get_parser(prog_name) - parser.add_argument( - 'net_name', metavar='net-name', - help='Name of network to create') - - return parser - - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - body = {'network': {'name': parsed_args.net_name, }, } - network = quantum_client.create_network(body) - # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} - info = 'network' in network and network['network'] or None - if info: - print >>self.app.stdout, _('Created a new Virtual Network:') - else: - info = {'': ''} - return zip(*sorted(info.iteritems())) - - -class DeleteNetwork(QuantumCommand): - """Delete a given network""" - - api = 'network' - log = logging.getLogger(__name__ + '.DeleteNetwork') - - def get_parser(self, prog_name): - parser = super(DeleteNetwork, self).get_parser(prog_name) - parser.add_argument( - 'net_id', metavar='net-id', - help='ID of network to delete') - return parser - - def run(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - quantum_client.delete_network(parsed_args.net_id) - print >>self.app.stdout, (_('Deleted Network: %(networkid)s') - % {'networkid': parsed_args.net_id}) - return - - -class UpdateNetwork(QuantumCommand): - """Update network's information""" - - api = 'network' - log = logging.getLogger(__name__ + '.UpdateNetwork') - - def get_parser(self, prog_name): - parser = super(UpdateNetwork, self).get_parser(prog_name) - parser.add_argument( - 'net_id', metavar='net-id', - help='ID of network to update') - - parser.add_argument( - 'newvalues', metavar='field=newvalue[,field2=newvalue2]', - help='new values for the network') - return parser - - def run(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - field_values = parsed_args.newvalues - data = {'network': {}} - for kv in field_values.split(","): - try: - k, v = kv.split("=") - data['network'][k] = v - except ValueError: - raise exceptions.CommandError( - "malformed new values (field=newvalue): %s" % kv) - - data['network']['id'] = parsed_args.net_id - quantum_client.update_network(parsed_args.net_id, data) - print >>self.app.stdout, ( - _('Updated Network: %(networkid)s') % - {'networkid': parsed_args.net_id}) - return diff --git a/quantumclient/quantum/v1_1/port.py b/quantumclient/quantum/v1_1/port.py deleted file mode 100644 index e8d1a48be..000000000 --- a/quantumclient/quantum/v1_1/port.py +++ /dev/null @@ -1,222 +0,0 @@ -# Copyright 2012 OpenStack LLC. -# All Rights Reserved -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -import logging - -from cliff import lister -from cliff import show - -from quantumclient.common import exceptions -from quantumclient.common import utils -from quantumclient import port_filters_v11_opt -from quantumclient.quantum.v1_1 import QuantumPortCommand - - -class ListPort(QuantumPortCommand, lister.Lister): - """List ports that belong to a given tenant's network""" - - api = 'network' - log = logging.getLogger(__name__ + '.ListPort') - - def get_parser(self, prog_name): - parser = super(ListPort, self).get_parser(prog_name) - - parser.add_argument( - '--show-details', - help='show detailed info of networks', - action='store_true', - default=False, ) - for item in port_filters_v11_opt: - option_key = item.keys()[0] - option_defs = item.get(option_key) - parser.add_argument(option_key, **option_defs) - return parser - - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - search_opts = { - 'tenant': parsed_args.tenant_id, } - for item in port_filters_v11_opt: - option_key = item.keys()[0] - arg = option_key[2:] - arg = arg.replace('-', '_') - arg_value = getattr(parsed_args, arg, None) - if arg_value is not None: - search_opts.update({option_key[2:]: arg_value, }) - - self.log.debug('search options: %s', search_opts) - - columns = ('ID', ) - data = None - if parsed_args.show_details: - data = quantum_client.list_ports_details( - parsed_args.net_id, **search_opts) - # dict:dict: {u'ports': [{ - # u'op-status': u'DOWN', - # u'state': u'ACTIVE', - # u'id': u'479ba2b7-042f-44b9-aefb-b1550e114454'}, ]} - columns = ('ID', 'op-status', 'state') - else: - data = quantum_client.list_ports(parsed_args.net_id, **search_opts) - # {u'ports': [{u'id': u'7a068b68-c736-42ab-9e43-c9d83c57627e'}]} - ports = [] - if 'ports' in data: - ports = data['ports'] - - return (columns, - (utils.get_item_properties( - s, columns, formatters={}, ) for s in ports), ) - - -class ShowPort(QuantumPortCommand, show.ShowOne): - """Show information of a given port""" - - api = 'network' - log = logging.getLogger(__name__ + '.ShowPort') - - def get_parser(self, prog_name): - parser = super(ShowPort, self).get_parser(prog_name) - parser.add_argument( - 'port_id', metavar='port-id', - help='ID of the port to show', ) - parser.add_argument( - '--show-details', - help='show detailed info', - action='store_true', - default=False, ) - return parser - - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - data = None - if parsed_args.show_details: - data = quantum_client.show_port_details( - parsed_args.net_id, parsed_args.port_id) - # {u'port': {u'op-status': u'DOWN', u'state': u'ACTIVE', - # u'id': u'479ba2b7-042f-44b9-aefb- - # b1550e114454', u'attachment': {u'id': u'gw-7a068b68-c7'}}} - else: - data = quantum_client.show_port( - parsed_args.net_id, parsed_args.port_id) - # {u'port': {u'op-status': u'DOWN', u'state': u'ACTIVE', - # u'id': u'479ba2b7-042f-44b9-aefb-b1550e114454'}} - - port = 'port' in data and data['port'] or None - if port: - attachment = 'attachment' in port and port['attachment'] or None - if attachment: - interface = attachment['id'] - port.update({'attachment': interface}) - return zip(*sorted(port.iteritems())) - return ('', []) - - -class CreatePort(QuantumPortCommand, show.ShowOne): - """Create port for a given network""" - - api = 'network' - log = logging.getLogger(__name__ + '.CreatePort') - - def get_parser(self, prog_name): - parser = super(CreatePort, self).get_parser(prog_name) - return parser - - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - data = quantum_client.create_port(parsed_args.net_id) - # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} - info = 'port' in data and data['port'] or None - if info: - print >>self.app.stdout, _('Created a new Logical Port:') - else: - info = {'': ''} - return zip(*sorted(info.iteritems())) - - -class DeletePort(QuantumPortCommand): - """Delete a given port""" - - api = 'network' - log = logging.getLogger(__name__ + '.DeletePort') - - def get_parser(self, prog_name): - parser = super(DeletePort, self).get_parser(prog_name) - parser.add_argument( - 'port_id', metavar='port-id', - help='ID of the port to delete', ) - return parser - - def run(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - quantum_client.delete_port(parsed_args.net_id, parsed_args.port_id) - print >>self.app.stdout, (_('Deleted Logical Port: %(portid)s') % - {'portid': parsed_args.port_id}) - return - - -class UpdatePort(QuantumPortCommand): - """Update information of a given port""" - - api = 'network' - log = logging.getLogger(__name__ + '.UpdatePort') - - def get_parser(self, prog_name): - parser = super(UpdatePort, self).get_parser(prog_name) - parser.add_argument( - 'port_id', metavar='port-id', - help='ID of the port to update', ) - - parser.add_argument( - 'newvalues', metavar='field=newvalue[,field2=newvalue2]', - help='new values for the Port') - - return parser - - def run(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) - quantum_client = self.app.client_manager.quantum - quantum_client.tenant = parsed_args.tenant_id - quantum_client.format = parsed_args.request_format - field_values = parsed_args.newvalues - data = {'port': {}} - for kv in field_values.split(","): - try: - k, v = kv.split("=") - data['port'][k] = v - except ValueError: - raise exceptions.CommandError( - "malformed new values (field=newvalue): %s" % kv) - data['network_id'] = parsed_args.net_id - data['port']['id'] = parsed_args.port_id - - quantum_client.update_port( - parsed_args.net_id, parsed_args.port_id, data) - print >>self.app.stdout, (_('Updated Logical Port: %(portid)s') % - {'portid': parsed_args.port_id}) - return diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index d4c4fb550..54d831a33 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -17,7 +17,7 @@ import logging -from quantumclient import utils +from quantumclient.common import utils from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index b24f723a9..280d918f7 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -17,7 +17,7 @@ import logging -from quantumclient import utils +from quantumclient.common import utils from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand diff --git a/quantumclient/shell.py b/quantumclient/shell.py index f16e18692..9143bf121 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -35,50 +35,23 @@ gettext.install('quantum', unicode=1) VERSION = '2.0' +QUANTUM_API_VERSION = '2.0' -def env(*vars, **kwargs): +def env(*_vars, **kwargs): """Search for the first defined of possibly many env vars Returns the first environment variable defined in vars, or returns the default defined in kwargs. """ - for v in vars: + for v in _vars: value = os.environ.get(v, None) if value: return value return kwargs.get('default', '') -COMMAND_V1 = { - 'net-list': utils.import_class( - 'quantumclient.quantum.v1_1.network.ListNetwork'), - 'net-show': utils.import_class( - 'quantumclient.quantum.v1_1.network.ShowNetwork'), - 'net-create': utils.import_class( - 'quantumclient.quantum.v1_1.network.CreateNetwork'), - 'net-delete': utils.import_class( - 'quantumclient.quantum.v1_1.network.DeleteNetwork'), - 'net-update': utils.import_class( - 'quantumclient.quantum.v1_1.network.UpdateNetwork'), - 'port-list': utils.import_class( - 'quantumclient.quantum.v1_1.port.ListPort'), - 'port-show': utils.import_class( - 'quantumclient.quantum.v1_1.port.ShowPort'), - 'port-create': utils.import_class( - 'quantumclient.quantum.v1_1.port.CreatePort'), - 'port-delete': utils.import_class( - 'quantumclient.quantum.v1_1.port.DeletePort'), - 'port-update': utils.import_class( - 'quantumclient.quantum.v1_1.port.UpdatePort'), - - 'iface-plugin': utils.import_class( - 'quantumclient.quantum.v1_1.interface.PlugInterface'), - 'iface-unplug': utils.import_class( - 'quantumclient.quantum.v1_1.interface.UnPlugInterface'), - 'iface-show': utils.import_class( - 'quantumclient.quantum.v1_1.interface.ShowInterface'), } COMMAND_V2 = { 'net-list': utils.import_class( 'quantumclient.quantum.v2_0.network.ListNetwork'), @@ -111,9 +84,7 @@ def env(*vars, **kwargs): 'port-update': utils.import_class( 'quantumclient.quantum.v2_0.port.UpdatePort'), } -COMMANDS = {'1.0': COMMAND_V1, - '1.1': COMMAND_V1, - '2.0': COMMAND_V2, } +COMMANDS = {'2.0': COMMAND_V2} class HelpAction(argparse.Action): @@ -229,13 +200,6 @@ def build_option_parser(self, description, version): default=env('OS_REGION_NAME'), help='Authentication region name (Env: OS_REGION_NAME)') - parser.add_argument( - '--os_quantum_api_version', - metavar='', - default=env('OS_QUANTUM_API_VERSION', default='2.0'), - help='QAUNTUM API version, default=2.0 ' - '(Env: OS_QUANTUM_API_VERSION)') - parser.add_argument( '--os_token', metavar='', default=env('OS_TOKEN'), @@ -340,8 +304,7 @@ def initialize_app(self, argv): super(QuantumShell, self).initialize_app(argv) - self.api_version = { - 'network': self.options.os_quantum_api_version, } + self.api_version = {'network': self.api_version} # If the user is not asking for help, make sure they # have given us auth. @@ -364,27 +327,10 @@ def itertools_compressdef(data, selectors): def main(argv=sys.argv[1:]): - apiVersion = None - versionFlag = False - for argitem in argv: - if argitem.startswith('--os_quantum_api_version='): - apiVersion = argitem.split('=', 2)[1] - break - elif '--os_quantum_api_version' == argitem: - versionFlag = True - elif versionFlag: - apiVersion = argitem - break - if apiVersion and apiVersion not in COMMANDS.keys(): - print ("Invalid API version or API version '%s' is not supported" % - apiVersion) - sys.exit(1) - if not apiVersion: - apiVersion = env('OS_QUANTUM_API_VERSION', default='2.0') try: if not getattr(itertools, 'compress', None): setattr(itertools, 'compress', itertools_compressdef) - return QuantumShell(apiVersion).run(argv) + return QuantumShell(QUANTUM_API_VERSION).run(argv) except exc.QuantumClientException: return 1 except Exception as e: diff --git a/quantumclient/tests/unit/stubs.py b/quantumclient/tests/unit/stubs.py deleted file mode 100644 index 9bf82024d..000000000 --- a/quantumclient/tests/unit/stubs.py +++ /dev/null @@ -1,65 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -""" Stubs for client tools unit tests """ - - -from quantum import api as server -from quantum.openstack.common import cfg -from quantum.tests.unit import testlib_api - - -class FakeStdout: - - def __init__(self): - self.content = [] - - def write(self, text): - self.content.append(text) - - def make_string(self): - result = '' - for line in self.content: - result = result + line - return result - - -class FakeHTTPConnection: - """ stub HTTP connection class for CLI testing """ - def __init__(self, _1, _2): - # Ignore host and port parameters - self._req = None - cfg.CONF.core_plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin' - self._api = server.APIRouterV11() - - def request(self, method, action, body, headers): - # TODO: remove version prefix from action! - parts = action.split('/', 2) - path = '/' + parts[2] - self._req = testlib_api.create_request(path, body, "application/json", - method) - - def getresponse(self): - res = self._req.get_response(self._api) - - def _fake_read(): - """ Trick for making a webob.Response look like a - httplib.Response - - """ - return res.body - - setattr(res, 'read', _fake_read) - return res diff --git a/quantumclient/tests/unit/test_cli.py b/quantumclient/tests/unit/test_cli.py deleted file mode 100644 index 8876dbc9b..000000000 --- a/quantumclient/tests/unit/test_cli.py +++ /dev/null @@ -1,853 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2010-2011 ???? -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# @author: Salvatore Orlando, Citrix Systems - -""" Module containing unit tests for Quantum - command line interface - -""" - -import logging -import sys -import unittest - -from quantum import api as server -from quantumclient import ClientV11 -from quantumclient import cli_lib as cli -from quantum.db import api as db -from quantum.openstack.common import cfg -from quantumclient.tests.unit import stubs as client_stubs - -LOG = logging.getLogger('quantumclient.tests.test_cli') -API_VERSION = "1.1" -FORMAT = 'json' - - -class CLITest(unittest.TestCase): - - def setUp(self): - """Prepare the test environment""" - #TODO: make the version of the API router configurable - cfg.CONF.core_plugin = 'quantum.plugins.sample.SamplePlugin.FakePlugin' - self.api = server.APIRouterV11() - - self.tenant_id = "test_tenant" - self.network_name_1 = "test_network_1" - self.network_name_2 = "test_network_2" - self.version = API_VERSION - # Prepare client and plugin manager - self.client = ClientV11(tenant=self.tenant_id, - format=FORMAT, - testingStub=client_stubs.FakeHTTPConnection, - version=self.version) - # Redirect stdout - self.fake_stdout = client_stubs.FakeStdout() - sys.stdout = self.fake_stdout - - def tearDown(self): - """Clear the test environment""" - db.clear_db() - sys.stdout = sys.__stdout__ - - def _verify_list_networks(self): - # Verification - get raw result from db - nw_list = db.network_list(self.tenant_id) - networks = [{'id': nw.uuid, 'name': nw.name} - for nw in nw_list] - # Fill CLI template - output = cli.prepare_output('list_nets', - self.tenant_id, - dict(networks=networks), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_list_networks_details(self): - # Verification - get raw result from db - nw_list = db.network_list(self.tenant_id) - networks = [dict(id=nw.uuid, name=nw.name) for nw in nw_list] - # Fill CLI template - output = cli.prepare_output('list_nets_detail', - self.tenant_id, - dict(networks=networks), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_list_networks_details_name_filter(self, name): - # Verification - get raw result from db - nw_list = db.network_list(self.tenant_id) - nw_filtered = [] - for nw in nw_list: - if nw.name == name: - nw_filtered.append(nw) - networks = [dict(id=nw.uuid, name=nw.name) for nw in nw_filtered] - # Fill CLI template - output = cli.prepare_output('list_nets_detail', - self.tenant_id, - dict(networks=networks), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_create_network(self): - # Verification - get raw result from db - nw_list = db.network_list(self.tenant_id) - if len(nw_list) != 1: - self.fail("No network created") - network_id = nw_list[0].uuid - # Fill CLI template - output = cli.prepare_output('create_net', - self.tenant_id, - dict(network_id=network_id), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_delete_network(self, network_id): - # Verification - get raw result from db - nw_list = db.network_list(self.tenant_id) - if len(nw_list) != 0: - self.fail("DB should not contain any network") - # Fill CLI template - output = cli.prepare_output('delete_net', - self.tenant_id, - dict(network_id=network_id), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_update_network(self): - # Verification - get raw result from db - nw_list = db.network_list(self.tenant_id) - network_data = {'id': nw_list[0].uuid, - 'name': nw_list[0].name, - 'op-status': nw_list[0].op_status} - # Fill CLI template - output = cli.prepare_output('update_net', - self.tenant_id, - dict(network=network_data), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_show_network(self): - # Verification - get raw result from db - nw = db.network_list(self.tenant_id)[0] - network = {'id': nw.uuid, - 'name': nw.name, - 'op-status': nw.op_status} - # Fill CLI template - output = cli.prepare_output('show_net', - self.tenant_id, - dict(network=network), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_show_network_details(self): - # Verification - get raw result from db - nw = db.network_list(self.tenant_id)[0] - network = {'id': nw.uuid, - 'name': nw.name, - 'op-status': nw.op_status} - port_list = db.port_list(nw.uuid) - if not port_list: - network['ports'] = [ - {'id': '', - 'state': '', - 'attachment': { - 'id': '', }, }, ] - else: - network['ports'] = [] - for port in port_list: - network['ports'].append({ - 'id': port.uuid, - 'state': port.state, - 'attachment': { - 'id': port.interface_id or '', }, }) - - # Fill CLI template - output = cli.prepare_output('show_net_detail', - self.tenant_id, - dict(network=network), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_list_ports(self, network_id): - # Verification - get raw result from db - port_list = db.port_list(network_id) - ports = [dict(id=port.uuid, state=port.state) - for port in port_list] - # Fill CLI template - output = cli.prepare_output('list_ports', - self.tenant_id, - dict(network_id=network_id, - ports=ports), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_list_ports_details(self, network_id): - # Verification - get raw result from db - port_list = db.port_list(network_id) - ports = [dict(id=port.uuid, state=port.state) - for port in port_list] - # Fill CLI template - output = cli.prepare_output('list_ports_detail', - self.tenant_id, - dict(network_id=network_id, - ports=ports), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_create_port(self, network_id): - # Verification - get raw result from db - port_list = db.port_list(network_id) - if len(port_list) != 1: - self.fail("No port created") - port_id = port_list[0].uuid - # Fill CLI template - output = cli.prepare_output('create_port', - self.tenant_id, - dict(network_id=network_id, - port_id=port_id), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_delete_port(self, network_id, port_id): - # Verification - get raw result from db - port_list = db.port_list(network_id) - if len(port_list) != 0: - self.fail("DB should not contain any port") - # Fill CLI template - output = cli.prepare_output('delete_port', - self.tenant_id, - dict(network_id=network_id, - port_id=port_id), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_update_port(self, network_id, port_id): - # Verification - get raw result from db - port = db.port_get(port_id, network_id) - port_data = {'id': port.uuid, - 'state': port.state, - 'op-status': port.op_status} - # Fill CLI template - output = cli.prepare_output('update_port', - self.tenant_id, - dict(network_id=network_id, - port=port_data), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_show_port(self, network_id, port_id): - # Verification - get raw result from db - port = db.port_get(port_id, network_id) - port_data = {'id': port.uuid, - 'state': port.state, - 'attachment': {'id': port.interface_id or ''}, - 'op-status': port.op_status} - - # Fill CLI template - output = cli.prepare_output('show_port', - self.tenant_id, - dict(network_id=network_id, - port=port_data), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_show_port_details(self, network_id, port_id): - # Verification - get raw result from db - # TODO(salvatore-orlando): Must resolve this issue with - # attachment in separate bug fix. - port = db.port_get(port_id, network_id) - port_data = {'id': port.uuid, - 'state': port.state, - 'attachment': {'id': port.interface_id or ''}, - 'op-status': port.op_status} - - # Fill CLI template - output = cli.prepare_output('show_port_detail', - self.tenant_id, - dict(network_id=network_id, - port=port_data), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_plug_iface(self, network_id, port_id): - # Verification - get raw result from db - port = db.port_get(port_id, network_id) - # Fill CLI template - output = cli.prepare_output("plug_iface", - self.tenant_id, - dict(network_id=network_id, - port_id=port['uuid'], - attachment=port['interface_id']), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_unplug_iface(self, network_id, port_id): - # Verification - get raw result from db - port = db.port_get(port_id, network_id) - # Fill CLI template - output = cli.prepare_output("unplug_iface", - self.tenant_id, - dict(network_id=network_id, - port_id=port['uuid']), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def _verify_show_iface(self, network_id, port_id): - # Verification - get raw result from db - port = db.port_get(port_id, network_id) - iface = {'id': port.interface_id or ''} - - # Fill CLI template - output = cli.prepare_output('show_iface', - self.tenant_id, - dict(network_id=network_id, - port_id=port.uuid, - iface=iface), - self.version) - # Verify! - # Must add newline at the end to match effect of print call - self.assertEquals(self.fake_stdout.make_string(), output + '\n') - - def test_list_networks_v10(self): - try: - # Pre-populate data for testing using db api - db.network_create(self.tenant_id, self.network_name_1) - db.network_create(self.tenant_id, self.network_name_2) - - cli.list_nets(self.client, - self.tenant_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_list_networks_v10 failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_list_networks() - - def test_list_networks_details_v10(self): - try: - # Pre-populate data for testing using db api - db.network_create(self.tenant_id, self.network_name_1) - db.network_create(self.tenant_id, self.network_name_2) - - cli.list_nets_detail(self.client, - self.tenant_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_list_networks_details_v10 failed due to" + - " an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_list_networks_details() - - def test_list_networks_v11(self): - try: - # Pre-populate data for testing using db api - db.network_create(self.tenant_id, self.network_name_1) - db.network_create(self.tenant_id, self.network_name_2) - #TODO: test filters - cli.list_nets_v11(self.client, - self.tenant_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_list_networks_v11 failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_list_networks() - - def test_list_networks_details_v11(self): - try: - # Pre-populate data for testing using db api - db.network_create(self.tenant_id, self.network_name_1) - db.network_create(self.tenant_id, self.network_name_2) - #TODO: test filters - cli.list_nets_detail_v11(self.client, - self.tenant_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_list_networks_details_v11 failed due to " + - "an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_list_networks_details() - - def test_list_networks_details_v11_name_filter(self): - try: - # Pre-populate data for testing using db api - db.network_create(self.tenant_id, self.network_name_1) - db.network_create(self.tenant_id, self.network_name_2) - #TODO: test filters - cli.list_nets_detail_v11(self.client, - self.tenant_id, - self.version, - {'name': self.network_name_1, }) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_list_networks_details_v11 failed due to " + - "an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_list_networks_details_name_filter(self.network_name_1) - - def test_create_network(self): - try: - cli.create_net(self.client, - self.tenant_id, - "test", - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_create_network failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_create_network() - - def test_delete_network(self): - try: - db.network_create(self.tenant_id, self.network_name_1) - network_id = db.network_list(self.tenant_id)[0]['uuid'] - cli.delete_net(self.client, - self.tenant_id, - network_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_delete_network failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_delete_network(network_id) - - def test_show_network(self): - try: - # Load some data into the datbase - net = db.network_create(self.tenant_id, self.network_name_1) - cli.show_net(self.client, - self.tenant_id, - net['uuid'], - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_detail_network failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_show_network() - - def test_show_network_details_no_ports(self): - try: - # Load some data into the datbase - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - cli.show_net_detail(self.client, - self.tenant_id, - network_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_show_network_details_no_ports failed due to" + - " an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_show_network_details() - - def test_show_network_details(self): - iface_id = "flavor crystals" - try: - # Load some data into the datbase - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(network_id) - port_id = port['uuid'] - db.port_set_attachment(port_id, network_id, iface_id) - port = db.port_create(network_id) - cli.show_net_detail(self.client, - self.tenant_id, - network_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_show_network_details failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_show_network_details() - - def test_update_network(self): - try: - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - cli.update_net(self.client, - self.tenant_id, - network_id, - 'name=%s' % self.network_name_2, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_update_network failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_update_network() - - def test_list_ports_v10(self): - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - db.port_create(network_id) - db.port_create(network_id) - cli.list_ports(self.client, - self.tenant_id, - network_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_list_ports failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_list_ports(network_id) - - def test_list_ports_details_v10(self): - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - db.port_create(network_id) - db.port_create(network_id) - cli.list_ports_detail(self.client, - self.tenant_id, - network_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_list_ports_details_v10 failed due to" + - " an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_list_ports_details(network_id) - - def test_list_ports_v11(self): - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - db.port_create(network_id) - db.port_create(network_id) - #TODO: test filters - cli.list_ports_v11(self.client, - self.tenant_id, - network_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_list_ports_v11 failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_list_ports(network_id) - - def test_list_ports_details_v11(self): - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - db.port_create(network_id) - db.port_create(network_id) - #TODO: test filters - cli.list_ports_detail_v11(self.client, - self.tenant_id, - network_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_list_ports_details_v11 failed due to " + - "an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_list_ports_details(network_id) - - def test_create_port(self): - network_id = None - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - cli.create_port(self.client, - self.tenant_id, - network_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_create_port failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_create_port(network_id) - - def test_delete_port(self): - network_id = None - port_id = None - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(network_id) - port_id = port['uuid'] - cli.delete_port(self.client, - self.tenant_id, - network_id, - port_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_delete_port failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_delete_port(network_id, port_id) - - def test_update_port(self): - try: - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(network_id) - port_id = port['uuid'] - # Default state is DOWN - change to ACTIVE. - cli.update_port(self.client, - self.tenant_id, - network_id, - port_id, - 'state=ACTIVE', - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_update_port failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_update_port(network_id, port_id) - - def test_show_port(self): - network_id = None - port_id = None - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(network_id) - port_id = port['uuid'] - cli.show_port(self.client, - self.tenant_id, - network_id, - port_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_show_port failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_show_port(network_id, port_id) - - def test_show_port_details_no_attach(self): - network_id = None - port_id = None - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(network_id) - port_id = port['uuid'] - cli.show_port_detail(self.client, - self.tenant_id, - network_id, - port_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_show_port_details_no_attach failed due to" + - " an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_show_port_details(network_id, port_id) - - def test_show_port_details_with_attach(self): - network_id = None - port_id = None - iface_id = "flavor crystals" - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(network_id) - port_id = port['uuid'] - db.port_set_attachment(port_id, network_id, iface_id) - cli.show_port_detail(self.client, - self.tenant_id, - network_id, - port_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_show_port_details_with_attach failed due" + - " to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_show_port_details(network_id, port_id) - - def test_plug_iface(self): - network_id = None - port_id = None - try: - # Load some data into the datbase - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(net['uuid']) - port_id = port['uuid'] - cli.plug_iface(self.client, - self.tenant_id, - network_id, - port_id, - "test_iface_id", - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_plug_iface failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_plug_iface(network_id, port_id) - - def test_unplug_iface(self): - network_id = None - port_id = None - try: - # Load some data into the datbase - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(net['uuid']) - port_id = port['uuid'] - db.port_set_attachment(port_id, network_id, "test_iface_id") - cli.unplug_iface(self.client, - self.tenant_id, - network_id, - port_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_plug_iface failed due to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_unplug_iface(network_id, port_id) - - def test_show_iface_no_attach(self): - network_id = None - port_id = None - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(network_id) - port_id = port['uuid'] - cli.show_iface(self.client, - self.tenant_id, - network_id, - port_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_show_iface_no_attach failed due to" + - " an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_show_iface(network_id, port_id) - - def test_show_iface_with_attach(self): - network_id = None - port_id = None - iface_id = "flavor crystals" - try: - # Pre-populate data for testing using db api - net = db.network_create(self.tenant_id, self.network_name_1) - network_id = net['uuid'] - port = db.port_create(network_id) - port_id = port['uuid'] - db.port_set_attachment(port_id, network_id, iface_id) - cli.show_iface(self.client, - self.tenant_id, - network_id, - port_id, - self.version) - except: - LOG.exception("Exception caught: %s", sys.exc_info()) - self.fail("test_show_iface_with_attach failed due" + - " to an exception") - - LOG.debug("Operation completed. Verifying result") - LOG.debug(self.fake_stdout.content) - self._verify_show_iface(network_id, port_id) diff --git a/quantumclient/tests/unit/test_clientlib.py b/quantumclient/tests/unit/test_clientlib.py deleted file mode 100644 index 28f78d041..000000000 --- a/quantumclient/tests/unit/test_clientlib.py +++ /dev/null @@ -1,770 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Cisco Systems -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# @author: Tyler Smith, Cisco Systems - -import logging -import re -import unittest - -from quantumclient.common import exceptions -from quantumclient.common.serializer import Serializer -from quantumclient import Client - - -LOG = logging.getLogger('quantumclient.tests.test_api') - - -# Set a couple tenants to use for testing -TENANT_1 = 'totore' -TENANT_2 = 'totore2' - - -class ServerStub(): - """This class stubs a basic server for the API client to talk to""" - - class Response(object): - """This class stubs a basic response to send the API client""" - def __init__(self, content=None, status=None): - self.content = content - self.status = status - - def read(self): - return self.content - - def status(self): - return self.status - - # To test error codes, set the host to 10.0.0.1, and the port to the code - def __init__(self, host, port=9696, key_file="", cert_file=""): - self.host = host - self.port = port - self.key_file = key_file - self.cert_file = cert_file - - def request(self, method, action, body, headers): - self.method = method - self.action = action - self.body = body - - def status(self, status=None): - return status or 200 - - def getresponse(self): - res = self.Response(status=self.status()) - - # If the host is 10.0.0.1, return the port as an error code - if self.host == "10.0.0.1": - res.status = self.port - return res - - # Extract important information from the action string to assure sanity - match = re.search('tenants/(.+?)/(.+)\.(json|xml)$', self.action) - - tenant = match.group(1) - path = match.group(2) - format = match.group(3) - - data = {'data': {'method': self.method, 'action': self.action, - 'body': self.body, 'tenant': tenant, 'path': path, - 'format': format, 'key_file': self.key_file, - 'cert_file': self.cert_file}} - - # Serialize it to the proper format so the API client can handle it - if data['data']['format'] == 'json': - res.content = Serializer().serialize(data, "application/json") - else: - res.content = Serializer().serialize(data, "application/xml") - return res - - -class APITest(unittest.TestCase): - - def setUp(self): - """ Setups a test environment for the API client """ - HOST = '127.0.0.1' - PORT = 9696 - USE_SSL = False - - self.client = Client(HOST, PORT, USE_SSL, TENANT_1, 'json', ServerStub) - - def _assert_sanity(self, call, status, method, path, data=[], params={}): - """ Perform common assertions to test the sanity of client requests """ - - # Handle an error case first - if status != 200: - (self.client.host, self.client.port) = ("10.0.0.1", status) - self.assertRaises(Exception, call, *data, **params) - return - - # Make the call, then get the data from the root node and assert it - data = call(*data, **params)['data'] - - self.assertEqual(data['method'], method) - self.assertEqual(data['format'], params['format']) - self.assertEqual(data['tenant'], params['tenant']) - self.assertEqual(data['path'], path) - - return data - - def _test_list_networks(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_networks - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.list_networks, - status, - "GET", - "networks", - data=[], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_list_networks - tenant:%s - format:%s - END", - format, tenant) - - def _test_list_networks_details(self, - tenant=TENANT_1, format='json', - status=200): - LOG.debug("_test_list_networks_details - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.list_networks_details, - status, - "GET", - "networks/detail", - data=[], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_list_networks_details - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_show_network(self, - tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_network - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.show_network, - status, - "GET", - "networks/001", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_show_network - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_show_network_details(self, - tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_network_details - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.show_network_details, - status, - "GET", - "networks/001/detail", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_show_network_details - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_create_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_create_network - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.create_network, - status, - "POST", - "networks", - data=[{'network': {'net-name': 'testNetwork'}}], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_create_network - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_update_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_update_network - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.update_network, - status, - "PUT", - "networks/001", - data=["001", - {'network': {'net-name': 'newName'}}], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_update_network - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_delete_network(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_delete_network - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.delete_network, - status, - "DELETE", - "networks/001", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_delete_network - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_list_ports(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_ports - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.list_ports, - status, - "GET", - "networks/001/ports", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_list_ports - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_list_ports_details(self, - tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_list_ports_details - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.list_ports_details, - status, - "GET", - "networks/001/ports/detail", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_list_ports_details - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_show_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_port - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.show_port, - status, - "GET", - "networks/001/ports/001", - data=["001", "001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_show_port - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_show_port_details(self, - tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_port_details - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.show_port_details, - status, - "GET", - "networks/001/ports/001/detail", - data=["001", "001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_show_port_details - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_create_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_create_port - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.create_port, - status, - "POST", - "networks/001/ports", - data=["001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_create_port - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_delete_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_delete_port - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.delete_port, - status, - "DELETE", - "networks/001/ports/001", - data=["001", "001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_delete_port - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_update_port(self, tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_update_port - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.update_port, - status, - "PUT", - "networks/001/ports/001", - data=["001", "001", - {'port': {'state': 'ACTIVE'}}], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_update_port - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_show_port_attachment(self, - tenant=TENANT_1, format='json', status=200): - LOG.debug("_test_show_port_attachment - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.show_port_attachment, - status, - "GET", - "networks/001/ports/001/attachment", - data=["001", "001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_show_port_attachment - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_attach_resource(self, tenant=TENANT_1, - format='json', status=200): - LOG.debug("_test_attach_resource - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.attach_resource, - status, - "PUT", - "networks/001/ports/001/attachment", - data=["001", "001", - {'resource': {'id': '1234'}}], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_attach_resource - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_detach_resource(self, tenant=TENANT_1, - format='json', status=200): - LOG.debug("_test_detach_resource - tenant:%s " - "- format:%s - START", format, tenant) - - self._assert_sanity(self.client.detach_resource, - status, - "DELETE", - "networks/001/ports/001/attachment", - data=["001", "001"], - params={'tenant': tenant, 'format': format}) - - LOG.debug("_test_detach_resource - tenant:%s " - "- format:%s - END", format, tenant) - - def _test_ssl_certificates(self, tenant=TENANT_1, - format='json', status=200): - LOG.debug("_test_ssl_certificates - tenant:%s " - "- format:%s - START", format, tenant) - - # Set SSL, and our cert file - self.client.use_ssl = True - cert_file = "/fake.cert" - self.client.key_file = self.client.cert_file = cert_file - - data = self._assert_sanity(self.client.list_networks, - status, - "GET", - "networks", - data=[], - params={'tenant': tenant, 'format': format}) - - self.assertEquals(data["key_file"], cert_file) - self.assertEquals(data["cert_file"], cert_file) - - LOG.debug("_test_ssl_certificates - tenant:%s " - "- format:%s - END", format, tenant) - - def test_list_networks_json(self): - self._test_list_networks(format='json') - - def test_list_networks_xml(self): - self._test_list_networks(format='xml') - - def test_list_networks_alt_tenant(self): - self._test_list_networks(tenant=TENANT_2) - - def test_list_networks_error_470(self): - self._test_list_networks(status=470) - - def test_list_networks_error_401(self): - self._test_list_networks(status=401) - - def test_list_networks_details_json(self): - self._test_list_networks_details(format='json') - - def test_list_networks_details_xml(self): - self._test_list_networks_details(format='xml') - - def test_list_networks_details_alt_tenant(self): - self._test_list_networks_details(tenant=TENANT_2) - - def test_list_networks_details_error_470(self): - self._test_list_networks_details(status=470) - - def test_list_networks_details_error_401(self): - self._test_list_networks_details(status=401) - - def test_show_network_json(self): - self._test_show_network(format='json') - - def test_show_network__xml(self): - self._test_show_network(format='xml') - - def test_show_network_alt_tenant(self): - self._test_show_network(tenant=TENANT_2) - - def test_show_network_error_470(self): - self._test_show_network(status=470) - - def test_show_network_error_401(self): - self._test_show_network(status=401) - - def test_show_network_error_420(self): - self._test_show_network(status=420) - - def test_show_network_details_json(self): - self._test_show_network_details(format='json') - - def test_show_network_details_xml(self): - self._test_show_network_details(format='xml') - - def test_show_network_details_alt_tenant(self): - self._test_show_network_details(tenant=TENANT_2) - - def test_show_network_details_error_470(self): - self._test_show_network_details(status=470) - - def test_show_network_details_error_401(self): - self._test_show_network_details(status=401) - - def test_show_network_details_error_420(self): - self._test_show_network_details(status=420) - - def test_create_network_json(self): - self._test_create_network(format='json') - - def test_create_network_xml(self): - self._test_create_network(format='xml') - - def test_create_network_alt_tenant(self): - self._test_create_network(tenant=TENANT_2) - - def test_create_network_error_470(self): - self._test_create_network(status=470) - - def test_create_network_error_401(self): - self._test_create_network(status=401) - - def test_create_network_error_400(self): - self._test_create_network(status=400) - - def test_create_network_error_422(self): - self._test_create_network(status=422) - - def test_update_network_json(self): - self._test_update_network(format='json') - - def test_update_network_xml(self): - self._test_update_network(format='xml') - - def test_update_network_alt_tenant(self): - self._test_update_network(tenant=TENANT_2) - - def test_update_network_error_470(self): - self._test_update_network(status=470) - - def test_update_network_error_401(self): - self._test_update_network(status=401) - - def test_update_network_error_400(self): - self._test_update_network(status=400) - - def test_update_network_error_420(self): - self._test_update_network(status=420) - - def test_update_network_error_422(self): - self._test_update_network(status=422) - - def test_delete_network_json(self): - self._test_delete_network(format='json') - - def test_delete_network_xml(self): - self._test_delete_network(format='xml') - - def test_delete_network_alt_tenant(self): - self._test_delete_network(tenant=TENANT_2) - - def test_delete_network_error_470(self): - self._test_delete_network(status=470) - - def test_delete_network_error_401(self): - self._test_delete_network(status=401) - - def test_delete_network_error_420(self): - self._test_delete_network(status=420) - - def test_delete_network_error_421(self): - self._test_delete_network(status=421) - - def test_list_ports_json(self): - self._test_list_ports(format='json') - - def test_list_ports_xml(self): - self._test_list_ports(format='xml') - - def test_list_ports_alt_tenant(self): - self._test_list_ports(tenant=TENANT_2) - - def test_list_ports_error_470(self): - self._test_list_ports(status=470) - - def test_list_ports_error_401(self): - self._test_list_ports(status=401) - - def test_list_ports_error_420(self): - self._test_list_ports(status=420) - - def test_list_ports_details_json(self): - self._test_list_ports_details(format='json') - - def test_list_ports_details_xml(self): - self._test_list_ports_details(format='xml') - - def test_list_ports_details_alt_tenant(self): - self._test_list_ports_details(tenant=TENANT_2) - - def test_list_ports_details_error_470(self): - self._test_list_ports_details(status=470) - - def test_list_ports_details_error_401(self): - self._test_list_ports_details(status=401) - - def test_list_ports_details_error_420(self): - self._test_list_ports_details(status=420) - - def test_show_port_json(self): - self._test_show_port(format='json') - - def test_show_port_xml(self): - self._test_show_port(format='xml') - - def test_show_port_alt_tenant(self): - self._test_show_port(tenant=TENANT_2) - - def test_show_port_error_470(self): - self._test_show_port(status=470) - - def test_show_port_error_401(self): - self._test_show_port(status=401) - - def test_show_port_error_420(self): - self._test_show_port(status=420) - - def test_show_port_error_430(self): - self._test_show_port(status=430) - - def test_show_port_details_json(self): - self._test_show_port_details(format='json') - - def test_show_port_details_xml(self): - self._test_show_port_details(format='xml') - - def test_show_port_details_alt_tenant(self): - self._test_show_port_details(tenant=TENANT_2) - - def test_show_port_details_error_470(self): - self._test_show_port_details(status=470) - - def test_show_port_details_error_401(self): - self._test_show_port_details(status=401) - - def test_show_port_details_error_420(self): - self._test_show_port_details(status=420) - - def test_show_port_details_error_430(self): - self._test_show_port_details(status=430) - - def test_create_port_json(self): - self._test_create_port(format='json') - - def test_create_port_xml(self): - self._test_create_port(format='xml') - - def test_create_port_alt_tenant(self): - self._test_create_port(tenant=TENANT_2) - - def test_create_port_error_470(self): - self._test_create_port(status=470) - - def test_create_port_error_401(self): - self._test_create_port(status=401) - - def test_create_port_error_400(self): - self._test_create_port(status=400) - - def test_create_port_error_420(self): - self._test_create_port(status=420) - - def test_create_port_error_430(self): - self._test_create_port(status=430) - - def test_create_port_error_431(self): - self._test_create_port(status=431) - - def test_delete_port_json(self): - self._test_delete_port(format='json') - - def test_delete_port_xml(self): - self._test_delete_port(format='xml') - - def test_delete_port_alt_tenant(self): - self._test_delete_port(tenant=TENANT_2) - - def test_delete_port_error_470(self): - self._test_delete_port(status=470) - - def test_delete_port_error_401(self): - self._test_delete_port(status=401) - - def test_delete_port_error_420(self): - self._test_delete_port(status=420) - - def test_delete_port_error_430(self): - self._test_delete_port(status=430) - - def test_delete_port_error_432(self): - self._test_delete_port(status=432) - - def test_update_port_json(self): - self._test_update_port(format='json') - - def test_update_port_xml(self): - self._test_update_port(format='xml') - - def test_update_port_alt_tenant(self): - self._test_update_port(tenant=TENANT_2) - - def test_update_port_error_470(self): - self._test_update_port(status=470) - - def test_update_port_error_401(self): - self._test_update_port(status=401) - - def test_update_port_error_400(self): - self._test_update_port(status=400) - - def test_update_port_error_420(self): - self._test_update_port(status=420) - - def test_update_port_error_430(self): - self._test_update_port(status=430) - - def test_update_port_error_431(self): - self._test_update_port(status=431) - - def test_show_port_attachment_json(self): - self._test_show_port_attachment(format='json') - - def test_show_port_attachment_xml(self): - self._test_show_port_attachment(format='xml') - - def test_show_port_attachment_alt_tenant(self): - self._test_show_port_attachment(tenant=TENANT_2) - - def test_show_port_attachment_error_470(self): - self._test_show_port_attachment(status=470) - - def test_show_port_attachment_error_401(self): - self._test_show_port_attachment(status=401) - - def test_show_port_attachment_error_400(self): - self._test_show_port_attachment(status=400) - - def test_show_port_attachment_error_420(self): - self._test_show_port_attachment(status=420) - - def test_show_port_attachment_error_430(self): - self._test_show_port_attachment(status=430) - - def test_attach_resource_json(self): - self._test_attach_resource(format='json') - - def test_attach_resource_xml(self): - self._test_attach_resource(format='xml') - - def test_attach_resource_alt_tenant(self): - self._test_attach_resource(tenant=TENANT_2) - - def test_attach_resource_error_470(self): - self._test_attach_resource(status=470) - - def test_attach_resource_error_401(self): - self._test_attach_resource(status=401) - - def test_attach_resource_error_400(self): - self._test_attach_resource(status=400) - - def test_attach_resource_error_420(self): - self._test_attach_resource(status=420) - - def test_attach_resource_error_430(self): - self._test_attach_resource(status=430) - - def test_attach_resource_error_432(self): - self._test_attach_resource(status=432) - - def test_attach_resource_error_440(self): - self._test_attach_resource(status=440) - - def test_detach_resource_json(self): - self._test_detach_resource(format='json') - - def test_detach_resource_xml(self): - self._test_detach_resource(format='xml') - - def test_detach_resource_alt_tenant(self): - self._test_detach_resource(tenant=TENANT_2) - - def test_detach_resource_error_470(self): - self._test_detach_resource(status=470) - - def test_detach_resource_error_401(self): - self._test_detach_resource(status=401) - - def test_detach_resource_error_420(self): - self._test_detach_resource(status=420) - - def test_detach_resource_error_430(self): - self._test_detach_resource(status=430) - - def test_ssl_certificates(self): - self._test_ssl_certificates() - - def test_connection_retry_failure(self): - self.client = Client(port=55555, tenant=TENANT_1, retries=1, - retry_interval=0) - try: - self.client.list_networks() - except exceptions.ConnectionFailed as exc: - self.assertTrue('Maximum attempts reached' in str(exc)) - else: - self.fail('ConnectionFailed not raised') diff --git a/setup.py b/setup.py index 18ca05cdc..aa6a0c0e6 100644 --- a/setup.py +++ b/setup.py @@ -64,8 +64,7 @@ eager_resources=EagerResources, entry_points={ 'console_scripts': [ - 'quantum = quantumclient.cli:main', - 'quantumv2 = quantumclient.shell:main', + 'quantum = quantumclient.shell:main', ] }, ) diff --git a/tools/test-requires b/tools/test-requires index c6e20802f..3c03917a6 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -12,4 +12,3 @@ openstack.nose_plugin pep8 sphinx>=1.1.2 -https://github.com/openstack/quantum/zipball/master#egg=quantum From d16e00a056bbe7ba576c4b7195180bfc383bbfad Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 24 Jul 2012 23:45:10 -0700 Subject: [PATCH 015/135] Allow to retrieve objects by name Fixes bug 979527 xxx-show commands now can accept either an id or a name of the resource to retrieve, similarly to the "nova get" command. This has been preferred to using mutually exclusive keyword argument, in order to avoid confusion with other CLI tools. NOTE: the current patch allow search by name only for networks. The restriction will be lifted once name attributes for port and subnets are added. Change-Id: Id186139a01c9f2cfc36ca3405b4024bd7780622e --- quantumclient/common/exceptions.py | 1 + quantumclient/quantum/v2_0/__init__.py | 64 +++++++++++++++++-- quantumclient/quantum/v2_0/port.py | 2 +- quantumclient/quantum/v2_0/subnet.py | 2 +- quantumclient/tests/unit/test_cli20.py | 42 +++++++++--- .../tests/unit/test_cli20_network.py | 15 ++++- quantumclient/tests/unit/test_cli20_port.py | 15 ++++- quantumclient/tests/unit/test_cli20_subnet.py | 15 ++++- quantumclient/v2_0/client.py | 6 +- 9 files changed, 134 insertions(+), 28 deletions(-) diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py index 80f968052..734a49894 100644 --- a/quantumclient/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -51,6 +51,7 @@ class QuantumClientException(QuantumException): def __init__(self, **kwargs): message = kwargs.get('message') + self.status_code = kwargs.get('status_code', 0) if message: self.message = message super(QuantumClientException, self).__init__(**kwargs) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index c761530c0..92dc4963c 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -17,6 +17,7 @@ import argparse import logging +import re from cliff import lister from cliff import show @@ -326,7 +327,10 @@ class ShowCommand(QuantumCommand, show.ShowOne): """Show information of a given resource """ - + HEX_ELEM = '[0-9A-Fa-f]' + UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', + HEX_ELEM + '{4}', HEX_ELEM + '{4}', + HEX_ELEM + '{12}']) api = 'network' resource = None log = None @@ -336,22 +340,70 @@ def get_parser(self, prog_name): add_show_list_common_argument(parser) parser.add_argument( 'id', metavar='%s_id' % self.resource, - help='ID of %s to look up' % self.resource) - + help='ID or name of %s to look up' % self.resource) return parser def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) quantum_client = self.get_client() quantum_client.format = parsed_args.request_format + params = {} if parsed_args.show_details: params = {'verbose': 'True'} if parsed_args.fields: params = {'fields': parsed_args.fields} - obj_showor = getattr(quantum_client, - "show_%s" % self.resource) - data = obj_showor(parsed_args.id, **params) + + data = None + # Error message to be used in case both search by id and name are + # unsuccessful (if list by name fails it does not return an error) + not_found_message = "Unable to find resource:%s" % parsed_args.id + + # perform search by id only if we are passing a valid UUID + match = re.match(self.UUID_PATTERN, parsed_args.id) + if match: + try: + obj_shower = getattr(quantum_client, + "show_%s" % self.resource) + data = obj_shower(parsed_args.id, **params) + except exceptions.QuantumClientException as ex: + logging.debug("Show operation failed with code:%s", + ex.status_code) + not_found_message = ex.message + if ex.status_code != 404: + logging.exception("Unable to perform show operation") + raise + + # If data is empty, then we got a 404. Try to interpret Id as a name + if not data: + logging.debug("Trying to interpret %s as a %s name", + parsed_args.id, + self.resource) + # build search_opts for the name + search_opts = parse_args_to_dict(["--name=%s" % parsed_args.id]) + search_opts.update(params) + obj_lister = getattr(quantum_client, + "list_%ss" % self.resource) + data = obj_lister(**search_opts) + info = [] + collection = self.resource + "s" + if collection in data: + info = data[collection] + if len(info) > 1: + logging.info("Multiple occurrences found for: %s", + parsed_args.id) + _columns = ['id'] + # put all ids in a single string as formatter for show + # command will print on record only + id_string = "\n".join(utils.get_item_properties( + s, _columns)[0] for s in info) + return (_columns, (id_string, ), ) + elif len(info) == 0: + #Nothing was found + raise exceptions.QuantumClientException( + message=not_found_message) + else: + data = {self.resource: info[0]} if self.resource in data: for k, v in data[self.resource].iteritems(): if isinstance(v, list): diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 54d831a33..9d6a91a5b 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -72,7 +72,7 @@ def add_known_arguments(self, parser): 'can be repeated') parser.add_argument( 'network_id', - help='Network id of this port belongs to') + help='Network id this port belongs to') def args2body(self, parsed_args): body = {'port': {'admin_state_up': parsed_args.admin_state_down, diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 280d918f7..a43ec2744 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -69,7 +69,7 @@ def add_known_arguments(self, parser): 'can be repeated') parser.add_argument( 'network_id', - help='Network id of this subnet belongs to') + help='Network id this subnet belongs to') parser.add_argument( 'cidr', metavar='cidr', help='cidr of subnet to create') diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 14a153e8f..b58d65cac 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -107,6 +107,8 @@ def __repr__(self): class CLITestV20Base(unittest.TestCase): + test_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' + def _url(self, path, query=None): _url_str = self.endurl + "/v" + API_VERSION + path + "." + FORMAT return query and _url_str + "?" + query or _url_str @@ -242,16 +244,11 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) - query = None - for field in fields: - if query: - query += "&fields=" + field - else: - query = "fields=" + field - resnetworks = {resource: - {'id': myid, + query = "&".join(["fields=%s" % field for field in fields]) + expected_res = {resource: + {'id': myid, 'name': 'myname', }, } - resstr = self.client.serialize(resnetworks) + resstr = self.client.serialize(expected_res) path = getattr(self.client, resource + "_path") self.client.httpclient.request( self._url(path % myid, query), 'GET', @@ -269,6 +266,33 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]): self.assertTrue(myid in _str) self.assertTrue('myname' in _str) + def _test_show_resource_by_name(self, resource, cmd, name, + args, fields=[]): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + query = "&".join(["fields=%s" % field for field in fields]) + expected_res = {"%ss" % resource: + [{'id': 'some_id', + 'name': name, }], } + resstr = self.client.serialize(expected_res) + list_path = getattr(self.client, resource + "s_path") + self.client.httpclient.request( + self._url(list_path, "%s&name=%s" % (query, name)), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(200), resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("show_" + resource) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertTrue(name in _str) + self.assertTrue('some_id' in _str) + def _test_delete_resource(self, resource, cmd, myid, args): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index 69fcd1d7f..ad85dc652 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -125,9 +125,18 @@ def test_show_network(self): """Show net: --fields id --fields name myid.""" resource = 'network' cmd = ShowNetwork(MyApp(sys.stdout), None) - myid = 'myid' - args = ['--fields', 'id', '--fields', 'name', myid] - self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, + ['id', 'name']) + + def test_show_network_by_name(self): + """Show net: --fields id --fields name myname.""" + resource = 'network' + cmd = ShowNetwork(MyApp(sys.stdout), None) + myname = 'myname' + args = ['--fields', 'id', '--fields', 'name', myname] + self._test_show_resource_by_name(resource, cmd, myname, + args, ['id', 'name']) def test_delete_network(self): """Delete net: myid.""" diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index 427d473b4..676aaea8d 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -124,9 +124,18 @@ def test_show_port(self): """Show port: --fields id --fields name myid.""" resource = 'port' cmd = ShowPort(MyApp(sys.stdout), None) - myid = 'myid' - args = ['--fields', 'id', '--fields', 'name', myid] - self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id', 'name']) + + def test_show_port_by_name(self): + """Show port: --fields id --fields name myname.""" + resource = 'port' + cmd = ShowPort(MyApp(sys.stdout), None) + myname = 'myname' + args = ['--fields', 'id', '--fields', 'name', myname] + self._test_show_resource_by_name(resource, cmd, myname, + args, ['id', 'name']) def test_delete_port(self): """Delete port: myid.""" diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index b649b744c..8783ed0ad 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -157,9 +157,18 @@ def test_show_subnet(self): """Show subnet: --fields id --fields name myid.""" resource = 'subnet' cmd = ShowSubnet(MyApp(sys.stdout), None) - myid = 'myid' - args = ['--fields', 'id', '--fields', 'name', myid] - self._test_show_resource(resource, cmd, myid, args, ['id', 'name']) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id', 'name']) + + def test_show_subnet_by_name(self): + """Show subnet: --fields id --fields name myname.""" + resource = 'subnet' + cmd = ShowSubnet(MyApp(sys.stdout), None) + myname = 'myname' + args = ['--fields', 'id', '--fields', 'name', myname] + self._test_show_resource_by_name(resource, cmd, myname, + args, ['id', 'name']) def test_delete_subnet(self): """Delete subnet: subnetid.""" diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 4273d8259..b906237b0 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -78,11 +78,13 @@ def exception_handler_v20(status_code, error_content): if isinstance(error_content, dict): message = error_content.get('message', None) if message: - raise exceptions.QuantumClientException(message=message) + raise exceptions.QuantumClientException(status_code=status_code, + message=message) # If we end up here the exception was not a quantum error msg = "%s-%s" % (status_code, error_content) - raise exceptions.QuantumClientException(message=msg) + raise exceptions.QuantumClientException(status_code=status_code, + message=msg) class APIParamsCall(object): From defb5481b0e7e2469405de98e6dd49ed239ef986 Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Sun, 29 Jul 2012 02:56:43 +0800 Subject: [PATCH 016/135] Add name or id to quantum cli commands. Bug #1030180 We first lookup the resource id via id if it looks like an id. If we cannot find it via id, we will look it up via name. All of the update/show/delete support reference resource via name or id. To create port/subnet, we can refercen network via network's name. Also in port creation, we support reference subnet in fixed_ip via its name. quantum_test.sh is added to test the command lines after running 'python setup.py install' Change-Id: I54b6912e2c4044ba70aaf604cd79520022de262f --- quantum_test.sh | 59 ++++++++ quantumclient/quantum/v2_0/__init__.py | 118 +++++++-------- quantumclient/quantum/v2_0/port.py | 28 +++- quantumclient/quantum/v2_0/subnet.py | 14 +- quantumclient/tests/unit/test_cli20.py | 51 ++----- .../tests/unit/test_cli20_network.py | 9 -- quantumclient/tests/unit/test_cli20_port.py | 9 -- quantumclient/tests/unit/test_cli20_subnet.py | 9 -- quantumclient/tests/unit/test_name_or_id.py | 140 ++++++++++++++++++ 9 files changed, 303 insertions(+), 134 deletions(-) create mode 100755 quantum_test.sh create mode 100644 quantumclient/tests/unit/test_name_or_id.py diff --git a/quantum_test.sh b/quantum_test.sh new file mode 100755 index 000000000..590d22690 --- /dev/null +++ b/quantum_test.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -x +function die() { + local exitcode=$? + set +o xtrace + echo $@ + exit $exitcode +} + + +# test the CRUD of network +network=mynet1 +quantum net-create $network || die "fail to create network $network" +temp=`quantum net-list -- --name $network --fields id | wc -l` +echo $temp +if [ $temp -ne 5 ]; then + die "networks with name $network is not unique or found" +fi +network_id=`quantum net-list -- --name $network --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` +echo "ID of network with name $network is $network_id" + +quantum net-show $network || die "fail to show network $network" +quantum net-show $network_id || die "fail to show network $network_id" + +quantum net-update $network --admin_state_up False || die "fail to update network $network" +quantum net-update $network_id --admin_state_up True || die "fail to update network $network_id" + +# test the CRUD of subnet +subnet=mysubnet1 +cidr=10.0.1.3/24 +quantum subnet-create $network $cidr --name $subnet || die "fail to create subnet $subnet" +tempsubnet=`quantum subnet-list -- --name $subnet --fields id | wc -l` +echo $tempsubnet +if [ $tempsubnet -ne 5 ]; then + die "subnets with name $subnet is not unique or found" +fi +subnet_id=`quantum subnet-list -- --name $subnet --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` +echo "ID of subnet with name $subnet is $subnet_id" +quantum subnet-show $subnet || die "fail to show subnet $subnet" +quantum subnet-show $subnet_id || die "fail to show subnet $subnet_id" + +quantum subnet-update $subnet --dns_namesevers host1 || die "fail to update subnet $subnet" +quantum subnet-update $subnet_id --dns_namesevers host2 || die "fail to update subnet $subnet_id" + +# test the crud of ports +port=myport1 +quantum port-create $network --name $port || die "fail to create port $port" +tempport=`quantum port-list -- --name $port --fields id | wc -l` +echo $tempport +if [ $tempport -ne 5 ]; then + die "ports with name $port is not unique or found" +fi +port_id=`quantum port-list -- --name $port --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` +echo "ID of port with name $port is $port_id" +quantum port-show $port || die "fail to show port $port" +quantum port-show $port_id || die "fail to show port $port_id" + +quantum port-update $port --device_id deviceid1 || die "fail to update port $port" +quantum port-update $port_id --device_id deviceid2 || die "fail to update port $port_id" diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 92dc4963c..5de594bf2 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -26,6 +26,44 @@ from quantumclient.common import exceptions from quantumclient.common import utils +HEX_ELEM = '[0-9A-Fa-f]' +UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', + HEX_ELEM + '{4}', HEX_ELEM + '{4}', + HEX_ELEM + '{12}']) + + +def find_resourceid_by_name_or_id(client, resource, name_or_id): + obj_lister = getattr(client, "list_%ss" % resource) + # perform search by id only if we are passing a valid UUID + match = re.match(UUID_PATTERN, name_or_id) + collection = resource + "s" + if match: + data = obj_lister(id=name_or_id, fields='id') + if data and data[collection]: + return data[collection][0]['id'] + return _find_resourceid_by_name(client, resource, name_or_id) + + +def _find_resourceid_by_name(client, resource, name): + obj_lister = getattr(client, "list_%ss" % resource) + data = obj_lister(name=name, fields='id') + collection = resource + "s" + info = data[collection] + if len(info) > 1: + msg = (_("Multiple %(resource)s matches found for '%(name)s'," + " use an ID to be more specific.") % + {'resource': resource, 'name': name}) + raise exceptions.QuantumClientException( + message=msg) + elif len(info) == 0: + not_found_message = (_("Unable to find %(resource)s with '%(name)s'") % + {'resource': resource, 'name': name}) + # 404 is used to simulate server side behavior + raise exceptions.QuantumClientException( + message=not_found_message, status_code=404) + else: + return info[0]['id'] + def add_show_list_common_argument(parser): parser.add_argument( @@ -222,8 +260,8 @@ class UpdateCommand(QuantumCommand): def get_parser(self, prog_name): parser = super(UpdateCommand, self).get_parser(prog_name) parser.add_argument( - 'id', metavar='%s_id' % self.resource, - help='ID of %s to update' % self.resource) + 'id', metavar=self.resource, + help='ID or name of %s to update' % self.resource) add_extra_argument(parser, 'value_specs', 'new values for the %s' % self.resource) return parser @@ -237,9 +275,12 @@ def run(self, parsed_args): raise exceptions.CommandError( "Must specify new values to update %s" % self.resource) data = {self.resource: parse_args_to_dict(value_specs)} + _id = find_resourceid_by_name_or_id(quantum_client, + self.resource, + parsed_args.id) obj_updator = getattr(quantum_client, "update_%s" % self.resource) - obj_updator(parsed_args.id, data) + obj_updator(_id, data) print >>self.app.stdout, ( _('Updated %(resource)s: %(id)s') % {'id': parsed_args.id, 'resource': self.resource}) @@ -258,8 +299,8 @@ class DeleteCommand(QuantumCommand): def get_parser(self, prog_name): parser = super(DeleteCommand, self).get_parser(prog_name) parser.add_argument( - 'id', metavar='%s_id' % self.resource, - help='ID of %s to delete' % self.resource) + 'id', metavar=self.resource, + help='ID or name of %s to delete' % self.resource) return parser def run(self, parsed_args): @@ -268,7 +309,10 @@ def run(self, parsed_args): quantum_client.format = parsed_args.request_format obj_deleter = getattr(quantum_client, "delete_%s" % self.resource) - obj_deleter(parsed_args.id) + _id = find_resourceid_by_name_or_id(quantum_client, + self.resource, + parsed_args.id) + obj_deleter(_id) print >>self.app.stdout, (_('Deleted %(resource)s: %(id)s') % {'id': parsed_args.id, 'resource': self.resource}) @@ -327,10 +371,7 @@ class ShowCommand(QuantumCommand, show.ShowOne): """Show information of a given resource """ - HEX_ELEM = '[0-9A-Fa-f]' - UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', - HEX_ELEM + '{4}', HEX_ELEM + '{4}', - HEX_ELEM + '{12}']) + api = 'network' resource = None log = None @@ -339,7 +380,7 @@ def get_parser(self, prog_name): parser = super(ShowCommand, self).get_parser(prog_name) add_show_list_common_argument(parser) parser.add_argument( - 'id', metavar='%s_id' % self.resource, + 'id', metavar=self.resource, help='ID or name of %s to look up' % self.resource) return parser @@ -353,57 +394,10 @@ def get_data(self, parsed_args): params = {'verbose': 'True'} if parsed_args.fields: params = {'fields': parsed_args.fields} - - data = None - # Error message to be used in case both search by id and name are - # unsuccessful (if list by name fails it does not return an error) - not_found_message = "Unable to find resource:%s" % parsed_args.id - - # perform search by id only if we are passing a valid UUID - match = re.match(self.UUID_PATTERN, parsed_args.id) - if match: - try: - obj_shower = getattr(quantum_client, - "show_%s" % self.resource) - data = obj_shower(parsed_args.id, **params) - except exceptions.QuantumClientException as ex: - logging.debug("Show operation failed with code:%s", - ex.status_code) - not_found_message = ex.message - if ex.status_code != 404: - logging.exception("Unable to perform show operation") - raise - - # If data is empty, then we got a 404. Try to interpret Id as a name - if not data: - logging.debug("Trying to interpret %s as a %s name", - parsed_args.id, - self.resource) - # build search_opts for the name - search_opts = parse_args_to_dict(["--name=%s" % parsed_args.id]) - search_opts.update(params) - obj_lister = getattr(quantum_client, - "list_%ss" % self.resource) - data = obj_lister(**search_opts) - info = [] - collection = self.resource + "s" - if collection in data: - info = data[collection] - if len(info) > 1: - logging.info("Multiple occurrences found for: %s", - parsed_args.id) - _columns = ['id'] - # put all ids in a single string as formatter for show - # command will print on record only - id_string = "\n".join(utils.get_item_properties( - s, _columns)[0] for s in info) - return (_columns, (id_string, ), ) - elif len(info) == 0: - #Nothing was found - raise exceptions.QuantumClientException( - message=not_found_message) - else: - data = {self.resource: info[0]} + _id = find_resourceid_by_name_or_id(quantum_client, self.resource, + parsed_args.id) + obj_shower = getattr(quantum_client, "show_%s" % self.resource) + data = obj_shower(_id, **params) if self.resource in data: for k, v in data[self.resource].iteritems(): if isinstance(v, list): diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 9d6a91a5b..c0674e0be 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -18,6 +18,7 @@ import logging from quantumclient.common import utils +from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand @@ -54,39 +55,52 @@ class CreatePort(CreateCommand): log = logging.getLogger(__name__ + '.CreatePort') def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help='name of this port') parser.add_argument( '--admin_state_down', default=True, action='store_false', help='set admin state up to false') parser.add_argument( '--mac_address', - help='mac address of port') + help='mac address of this port') parser.add_argument( '--device_id', help='device id of this port') parser.add_argument( '--fixed_ip', action='append', - help='desired Ip for this port: ' - 'subnet_id=,ip_address=, ' + help='desired IP for this port: ' + 'subnet_id=,ip_address=, ' 'can be repeated') parser.add_argument( - 'network_id', - help='Network id this port belongs to') + 'network_id', metavar='network', + help='Network id or name this port belongs to') def args2body(self, parsed_args): + _network_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'network', parsed_args.network_id) body = {'port': {'admin_state_up': parsed_args.admin_state_down, - 'network_id': parsed_args.network_id, }, } + 'network_id': _network_id, }, } if parsed_args.mac_address: body['port'].update({'mac_address': parsed_args.mac_address}) if parsed_args.device_id: body['port'].update({'device_id': parsed_args.device_id}) if parsed_args.tenant_id: body['port'].update({'tenant_id': parsed_args.tenant_id}) + if parsed_args.name: + body['port'].update({'name': parsed_args.name}) ips = [] if parsed_args.fixed_ip: for ip_spec in parsed_args.fixed_ip: - ips.append(utils.str2dict(ip_spec)) + ip_dict = utils.str2dict(ip_spec) + if 'subnet_id' in ip_dict: + subnet_name_id = ip_dict['subnet_id'] + _subnet_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'subnet', subnet_name_id) + ip_dict['subnet_id'] = _subnet_id + ips.append(ip_dict) if ips: body['port'].update({'fixed_ips': ips}) return body diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index a43ec2744..62c8ad33c 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -18,6 +18,7 @@ import logging from quantumclient.common import utils +from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand @@ -55,6 +56,9 @@ class CreateSubnet(CreateCommand): log = logging.getLogger(__name__ + '.CreateSubnet') def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help='name of this subnet') parser.add_argument('--ip_version', type=int, default=4, choices=[4, 6], help='IP version with default 4') @@ -68,20 +72,24 @@ def add_known_arguments(self, parser): 'start=,end= ' 'can be repeated') parser.add_argument( - 'network_id', - help='Network id this subnet belongs to') + 'network_id', metavar='network', + help='Network id or name this subnet belongs to') parser.add_argument( 'cidr', metavar='cidr', help='cidr of subnet to create') def args2body(self, parsed_args): + _network_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'network', parsed_args.network_id) body = {'subnet': {'cidr': parsed_args.cidr, - 'network_id': parsed_args.network_id, + 'network_id': _network_id, 'ip_version': parsed_args.ip_version, }, } if parsed_args.gateway: body['subnet'].update({'gateway_ip': parsed_args.gateway}) if parsed_args.tenant_id: body['subnet'].update({'tenant_id': parsed_args.tenant_id}) + if parsed_args.name: + body['subnet'].update({'name': parsed_args.name}) ips = [] if parsed_args.allocation_pool: for ip_spec in parsed_args.allocation_pool: diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index b58d65cac..373706103 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -22,6 +22,7 @@ from mox import ContainsKeyValue from mox import Comparator +from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.v2_0.client import Client API_VERSION = "2.0" @@ -55,6 +56,11 @@ def __init__(self, _stdout): self.stdout = _stdout +def end_url(path, query=None): + _url_str = ENDURL + "/v" + API_VERSION + path + "." + FORMAT + return query and _url_str + "?" + query or _url_str + + class MyComparator(Comparator): def __init__(self, lhs, client): self.lhs = lhs @@ -109,9 +115,8 @@ class CLITestV20Base(unittest.TestCase): test_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' - def _url(self, path, query=None): - _url_str = self.endurl + "/v" + API_VERSION + path + "." + FORMAT - return query and _url_str + "?" + query or _url_str + def _find_resourceid(self, client, resource, name_or_id): + return name_or_id def setUp(self): """Prepare the test environment""" @@ -120,10 +125,13 @@ def setUp(self): self.client = Client(token=TOKEN, endpoint_url=self.endurl) self.fake_stdout = FakeStdout() sys.stdout = self.fake_stdout + self.old_find_resourceid = quantumv20.find_resourceid_by_name_or_id + quantumv20.find_resourceid_by_name_or_id = self._find_resourceid def tearDown(self): """Clear the test environment""" sys.stdout = sys.__stdout__ + quantumv20.find_resourceid_by_name_or_id = self.old_find_resourceid def _test_create_resource(self, resource, cmd, name, myid, args, @@ -149,7 +157,7 @@ def _test_create_resource(self, resource, cmd, # url method body path = getattr(self.client, resource + "s_path") self.client.httpclient.request( - self._url(path), 'POST', + end_url(path), 'POST', body=MyComparator(body, self.client), headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), @@ -205,7 +213,7 @@ def _test_list_resources(self, resources, cmd, detail=False, tags=[], query = "fields=" + field path = getattr(self.client, resources + "_path") self.client.httpclient.request( - self._url(path, query), 'GET', + end_url(path, query), 'GET', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) @@ -226,7 +234,7 @@ def _test_update_resource(self, resource, cmd, myid, args, extrafields): body = {resource: extrafields} path = getattr(self.client, resource + "_path") self.client.httpclient.request( - self._url(path % myid), 'PUT', + end_url(path % myid), 'PUT', body=MyComparator(body, self.client), headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) @@ -251,7 +259,7 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]): resstr = self.client.serialize(expected_res) path = getattr(self.client, resource + "_path") self.client.httpclient.request( - self._url(path % myid, query), 'GET', + end_url(path % myid, query), 'GET', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) @@ -266,40 +274,13 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]): self.assertTrue(myid in _str) self.assertTrue('myname' in _str) - def _test_show_resource_by_name(self, resource, cmd, name, - args, fields=[]): - self.mox.StubOutWithMock(cmd, "get_client") - self.mox.StubOutWithMock(self.client.httpclient, "request") - cmd.get_client().MultipleTimes().AndReturn(self.client) - query = "&".join(["fields=%s" % field for field in fields]) - expected_res = {"%ss" % resource: - [{'id': 'some_id', - 'name': name, }], } - resstr = self.client.serialize(expected_res) - list_path = getattr(self.client, resource + "s_path") - self.client.httpclient.request( - self._url(list_path, "%s&name=%s" % (query, name)), 'GET', - body=None, - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(200), resstr)) - self.mox.ReplayAll() - cmd_parser = cmd.get_parser("show_" + resource) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) - self.mox.VerifyAll() - self.mox.UnsetStubs() - _str = self.fake_stdout.make_string() - self.assertTrue(name in _str) - self.assertTrue('some_id' in _str) - def _test_delete_resource(self, resource, cmd, myid, args): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) path = getattr(self.client, resource + "_path") self.client.httpclient.request( - self._url(path % myid), 'DELETE', + end_url(path % myid), 'DELETE', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index ad85dc652..16837b349 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -129,15 +129,6 @@ def test_show_network(self): self._test_show_resource(resource, cmd, self.test_id, args, ['id', 'name']) - def test_show_network_by_name(self): - """Show net: --fields id --fields name myname.""" - resource = 'network' - cmd = ShowNetwork(MyApp(sys.stdout), None) - myname = 'myname' - args = ['--fields', 'id', '--fields', 'name', myname] - self._test_show_resource_by_name(resource, cmd, myname, - args, ['id', 'name']) - def test_delete_network(self): """Delete net: myid.""" resource = 'network' diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index 676aaea8d..14ae209c2 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -128,15 +128,6 @@ def test_show_port(self): self._test_show_resource(resource, cmd, self.test_id, args, ['id', 'name']) - def test_show_port_by_name(self): - """Show port: --fields id --fields name myname.""" - resource = 'port' - cmd = ShowPort(MyApp(sys.stdout), None) - myname = 'myname' - args = ['--fields', 'id', '--fields', 'name', myname] - self._test_show_resource_by_name(resource, cmd, myname, - args, ['id', 'name']) - def test_delete_port(self): """Delete port: myid.""" resource = 'port' diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index 8783ed0ad..ad9ee2f77 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -161,15 +161,6 @@ def test_show_subnet(self): self._test_show_resource(resource, cmd, self.test_id, args, ['id', 'name']) - def test_show_subnet_by_name(self): - """Show subnet: --fields id --fields name myname.""" - resource = 'subnet' - cmd = ShowSubnet(MyApp(sys.stdout), None) - myname = 'myname' - args = ['--fields', 'id', '--fields', 'name', myname] - self._test_show_resource_by_name(resource, cmd, myname, - args, ['id', 'name']) - def test_delete_subnet(self): """Delete subnet: subnetid.""" resource = 'subnet' diff --git a/quantumclient/tests/unit/test_name_or_id.py b/quantumclient/tests/unit/test_name_or_id.py new file mode 100644 index 000000000..d5947d809 --- /dev/null +++ b/quantumclient/tests/unit/test_name_or_id.py @@ -0,0 +1,140 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import uuid + +import mox +from mox import ContainsKeyValue +import unittest + +from quantumclient.common import exceptions +from quantumclient.quantum import v2_0 as quantumv20 +from quantumclient.tests.unit import test_cli20 +from quantumclient.v2_0.client import Client + + +class CLITestNameorID(unittest.TestCase): + + def setUp(self): + """Prepare the test environment""" + self.mox = mox.Mox() + self.endurl = test_cli20.ENDURL + self.client = Client(token=test_cli20.TOKEN, endpoint_url=self.endurl) + + def tearDown(self): + """Clear the test environment""" + self.mox.VerifyAll() + self.mox.UnsetStubs() + + def test_get_id_from_id(self): + _id = str(uuid.uuid4()) + reses = {'networks': [{'id': _id, }, ], } + resstr = self.client.serialize(reses) + self.mox.StubOutWithMock(self.client.httpclient, "request") + path = getattr(self.client, "networks_path") + self.client.httpclient.request( + test_cli20.end_url(path, "fields=id&id=" + _id), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + returned_id = quantumv20.find_resourceid_by_name_or_id( + self.client, 'network', _id) + self.assertEqual(_id, returned_id) + + def test_get_id_from_id_then_name_empty(self): + _id = str(uuid.uuid4()) + reses = {'networks': [{'id': _id, }, ], } + resstr = self.client.serialize(reses) + resstr1 = self.client.serialize({'networks': []}) + self.mox.StubOutWithMock(self.client.httpclient, "request") + path = getattr(self.client, "networks_path") + self.client.httpclient.request( + test_cli20.end_url(path, "fields=id&id=" + _id), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr1)) + self.client.httpclient.request( + test_cli20.end_url(path, "fields=id&name=" + _id), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + returned_id = quantumv20.find_resourceid_by_name_or_id( + self.client, 'network', _id) + self.assertEqual(_id, returned_id) + + def test_get_id_from_name(self): + name = 'myname' + _id = str(uuid.uuid4()) + reses = {'networks': [{'id': _id, }, ], } + resstr = self.client.serialize(reses) + self.mox.StubOutWithMock(self.client.httpclient, "request") + path = getattr(self.client, "networks_path") + self.client.httpclient.request( + test_cli20.end_url(path, "fields=id&name=" + name), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + returned_id = quantumv20.find_resourceid_by_name_or_id( + self.client, 'network', name) + self.assertEqual(_id, returned_id) + + def test_get_id_from_name_multiple(self): + name = 'myname' + reses = {'networks': [{'id': str(uuid.uuid4())}, + {'id': str(uuid.uuid4())}]} + resstr = self.client.serialize(reses) + self.mox.StubOutWithMock(self.client.httpclient, "request") + path = getattr(self.client, "networks_path") + self.client.httpclient.request( + test_cli20.end_url(path, "fields=id&name=" + name), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + try: + quantumv20.find_resourceid_by_name_or_id( + self.client, 'network', name) + except exceptions.QuantumClientException as ex: + self.assertTrue('Multiple' in ex.message) + + def test_get_id_from_name_notfound(self): + name = 'myname' + reses = {'networks': []} + resstr = self.client.serialize(reses) + self.mox.StubOutWithMock(self.client.httpclient, "request") + path = getattr(self.client, "networks_path") + self.client.httpclient.request( + test_cli20.end_url(path, "fields=id&name=" + name), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + try: + quantumv20.find_resourceid_by_name_or_id( + self.client, 'network', name) + except exceptions.QuantumClientException as ex: + self.assertTrue('Unable to find' in ex.message) + self.assertEqual(404, ex.status_code) From 22012a2f34c26a35e3d0fa06874ffa06e55599f3 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Thu, 9 Aug 2012 10:02:46 -0700 Subject: [PATCH 017/135] update mailing list, etc in setup.py Change-Id: I0f50dd8d2d76b66e39b09d2fe65be66d6d405761 --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index aa6a0c0e6..6c1e5e688 100644 --- a/setup.py +++ b/setup.py @@ -25,10 +25,10 @@ Url = "https://launchpad.net/quantum" Version = setup.get_post_version('quantumclient') License = 'Apache License 2.0' -Author = 'Netstack' -AuthorEmail = 'netstack@lists.launchpad.net' +Author = 'OpenStack Quantum Project' +AuthorEmail = 'openstack-dev@lists.launchpad.net' Maintainer = '' -Summary = 'Client functionalities for Quantum' +Summary = 'CLI and python client library for OpenStack Quantum' ShortDescription = Summary Description = Summary From 4b12a7222628bbbf6a073909d5541dc42df746eb Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Sat, 11 Aug 2012 20:19:41 +0900 Subject: [PATCH 018/135] Refreshes keystone token if a token is expired. Fixes bug 1034212 Change-Id: I523d289807caff74068328f612451cee74b94cbb --- quantumclient/client.py | 9 +- quantumclient/tests/unit/test_auth.py | 130 ++++++++++++++++++++++++++ 2 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 quantumclient/tests/unit/test_auth.py diff --git a/quantumclient/client.py b/quantumclient/client.py index 03c590a21..18bb3de2a 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -101,6 +101,7 @@ def __init__(self, username=None, tenant_name=None, self.auth_url = auth_url.rstrip('/') if auth_url else None self.region_name = region_name self.auth_token = token + self.token_retrieved = False self.content_type = 'application/json' self.endpoint_url = endpoint_url self.auth_strategy = auth_strategy @@ -148,10 +149,13 @@ def do_request(self, url, method, **kwargs): **kwargs) return resp, body except exceptions.Unauthorized as ex: - if not self.endpoint_url: + if not self.endpoint_url or self.token_retrieved: self.authenticate() + if self.auth_token: + kwargs.setdefault('headers', {}) + kwargs['headers']['X-Auth-Token'] = self.auth_token resp, body = self._cs_request( - self.management_url + url, method, **kwargs) + self.endpoint_url + url, method, **kwargs) return resp, body else: raise ex @@ -164,6 +168,7 @@ def _extract_service_catalog(self, body): self.auth_token = sc['id'] self.auth_tenant_id = sc.get('tenant_id') self.auth_user_id = sc.get('user_id') + self.token_retrieved = True except KeyError: raise exceptions.Unauthorized() self.endpoint_url = self.service_catalog.url_for( diff --git a/quantumclient/tests/unit/test_auth.py b/quantumclient/tests/unit/test_auth.py new file mode 100644 index 000000000..9f270b5b6 --- /dev/null +++ b/quantumclient/tests/unit/test_auth.py @@ -0,0 +1,130 @@ +# Copyright 2012 NEC Corporation +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import mox +from mox import ContainsKeyValue, IsA, StrContains +import unittest + +import httplib2 +import json +import uuid + +from quantumclient.common import exceptions +from quantumclient.client import HTTPClient + + +USERNAME = 'testuser' +TENANT_NAME = 'testtenant' +PASSWORD = 'password' +AUTH_URL = 'authurl' +ENDPOINT_URL = 'localurl' +TOKEN = 'tokentoken' +REGION = 'RegionTest' + +KS_TOKEN_RESULT = { + 'access': { + 'token': {'id': TOKEN, + 'expires': '2012-08-11T07:49:01Z', + 'tenant': {'id': str(uuid.uuid1())}}, + 'user': {'id': str(uuid.uuid1())}, + 'serviceCatalog': [ + {'endpoints_links': [], + 'endpoints': [{'adminURL': ENDPOINT_URL, + 'internalURL': ENDPOINT_URL, + 'publicURL': ENDPOINT_URL, + 'region': REGION}], + 'type': 'network', + 'name': 'Quantum Service'} + ] + } +} + + +class CLITestAuthKeystone(unittest.TestCase): + + def setUp(self): + """Prepare the test environment""" + self.mox = mox.Mox() + self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, + password=PASSWORD, auth_url=AUTH_URL, + region_name=REGION) + + def tearDown(self): + """Clear the test environment""" + self.mox.VerifyAll() + self.mox.UnsetStubs() + + def test_get_token(self): + self.mox.StubOutWithMock(self.client, "request") + + res200 = self.mox.CreateMock(httplib2.Response) + res200.status = 200 + + self.client.request(AUTH_URL + '/tokens', 'POST', + body=IsA(str), headers=IsA(dict)).\ + AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) + self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ + AndReturn((res200, '')) + self.mox.ReplayAll() + + self.client.do_request('/resource', 'GET') + self.assertEqual(self.client.endpoint_url, ENDPOINT_URL) + self.assertEqual(self.client.auth_token, TOKEN) + self.assertEqual(self.client.token_retrieved, True) + + def test_already_token_retrieved(self): + self.mox.StubOutWithMock(self.client, "request") + + self.client.auth_token = TOKEN + self.client.endpoint_url = ENDPOINT_URL + self.client.token_retrieved = True + + res200 = self.mox.CreateMock(httplib2.Response) + res200.status = 200 + + self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ + AndReturn((res200, '')) + self.mox.ReplayAll() + + self.client.do_request('/resource', 'GET') + + def test_refresh_token(self): + self.mox.StubOutWithMock(self.client, "request") + + self.client.auth_token = TOKEN + self.client.endpoint_url = ENDPOINT_URL + self.client.token_retrieved = True + + res200 = self.mox.CreateMock(httplib2.Response) + res200.status = 200 + res401 = self.mox.CreateMock(httplib2.Response) + res401.status = 401 + + # If a token is expired, quantum server retruns 401 + self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ + AndReturn((res401, '')) + self.client.request(AUTH_URL + '/tokens', 'POST', + body=IsA(str), headers=IsA(dict)).\ + AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) + self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ + AndReturn((res200, '')) + self.mox.ReplayAll() + self.client.do_request('/resource', 'GET') From 8f1ce248b33fdf8d693ea1cea9e00524b6c54495 Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Sun, 29 Jul 2012 20:30:32 +0800 Subject: [PATCH 019/135] Add quota commands to change quota of a tenant. blueprint quantum-api-quotas quantum quota-show --tenant_id: if tenant_id is not specified, the server will get tenant_id from context quantum quota-update --network --port --subnet --tenant_id : if tenant_id is not specified, the server will get tenant_id from context quantum quota-list: list all tenants' quota values after the updation. quantum quota-delete --tenant_id : delete the given tenant's customized quota values. Change-Id: Ib0efb159bea96837bf4e35eaefa5e172c1c9f34a --- quantum_test.sh | 72 +++++++++- quantumclient/quantum/v2_0/quota.py | 205 ++++++++++++++++++++++++++++ quantumclient/shell.py | 11 +- quantumclient/v2_0/client.py | 30 +++- 4 files changed, 313 insertions(+), 5 deletions(-) create mode 100644 quantumclient/quantum/v2_0/quota.py diff --git a/quantum_test.sh b/quantum_test.sh index 590d22690..19fe390e8 100755 --- a/quantum_test.sh +++ b/quantum_test.sh @@ -7,10 +7,17 @@ function die() { exit $exitcode } +noauth_tenant_id=me +if [ $1 == 'noauth' ]; then + NOAUTH="--tenant_id $noauth_tenant_id" +else + NOAUTH= +fi + # test the CRUD of network network=mynet1 -quantum net-create $network || die "fail to create network $network" +quantum net-create $NOAUTH $network || die "fail to create network $network" temp=`quantum net-list -- --name $network --fields id | wc -l` echo $temp if [ $temp -ne 5 ]; then @@ -28,7 +35,7 @@ quantum net-update $network_id --admin_state_up True || die "fail to update n # test the CRUD of subnet subnet=mysubnet1 cidr=10.0.1.3/24 -quantum subnet-create $network $cidr --name $subnet || die "fail to create subnet $subnet" +quantum subnet-create $NOAUTH $network $cidr --name $subnet || die "fail to create subnet $subnet" tempsubnet=`quantum subnet-list -- --name $subnet --fields id | wc -l` echo $tempsubnet if [ $tempsubnet -ne 5 ]; then @@ -44,7 +51,7 @@ quantum subnet-update $subnet_id --dns_namesevers host2 || die "fail to updat # test the crud of ports port=myport1 -quantum port-create $network --name $port || die "fail to create port $port" +quantum port-create $NOAUTH $network --name $port || die "fail to create port $port" tempport=`quantum port-list -- --name $port --fields id | wc -l` echo $tempport if [ $tempport -ne 5 ]; then @@ -57,3 +64,62 @@ quantum port-show $port_id || die "fail to show port $port_id" quantum port-update $port --device_id deviceid1 || die "fail to update port $port" quantum port-update $port_id --device_id deviceid2 || die "fail to update port $port_id" + +# test quota commands RUD +DEFAULT_NETWORKS=10 +DEFAULT_PORTS=50 +tenant_id=tenant_a +tenant_id_b=tenant_b +quantum quota-update --tenant_id $tenant_id --network 30 || die "fail to update quota for tenant $tenant_id" +quantum quota-update --tenant_id $tenant_id_b --network 20 || die "fail to update quota for tenant $tenant_id" +networks=`quantum quota-list | grep $tenant_id | awk '{print $2}'` +if [ $networks -ne 30 ]; then + die "networks quota should be 30" +fi +networks=`quantum quota-list | grep $tenant_id_b | awk '{print $2}'` +if [ $networks -ne 20 ]; then + die "networks quota should be 20" +fi +networks=`quantum quota-show --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` +if [ $networks -ne 30 ]; then + die "networks quota should be 30" +fi +quantum quota-delete --tenant_id $tenant_id || die "fail to delete quota for tenant $tenant_id" +networks=`quantum quota-show --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` +if [ $networks -ne $DEFAULT_NETWORKS ]; then + die "networks quota should be $DEFAULT_NETWORKS" +fi +# update self +if [ "t$NOAUTH" = "t" ]; then + # with auth + quantum quota-update --port 99 || die "fail to update quota for self" + ports=`quantum quota-show | grep port | awk -F'|' '{print $3}'` + if [ $ports -ne 99 ]; then + die "ports quota should be 99" + fi + + ports=`quantum quota-list | grep 99 | awk '{print $4}'` + if [ $ports -ne 99 ]; then + die "ports quota should be 99" + fi + quantum quota-delete || die "fail to delete quota for tenant self" + ports=`quantum quota-show | grep port | awk -F'|' '{print $3}'` + if [ $ports -ne $DEFAULT_PORTS ]; then + die "ports quota should be $DEFAULT_PORTS" + fi +else + # without auth + quantum quota-update --port 100 + if [ $? -eq 0 ]; then + die "without valid context on server, quota update command should fail." + fi + quantum quota-show + if [ $? -eq 0 ]; then + die "without valid context on server, quota show command should fail." + fi + quantum quota-delete + if [ $? -eq 0 ]; then + die "without valid context on server, quota delete command should fail." + fi + quantum quota-list || die "fail to update quota for self" +fi \ No newline at end of file diff --git a/quantumclient/quantum/v2_0/quota.py b/quantumclient/quantum/v2_0/quota.py new file mode 100644 index 000000000..d8dbefe2d --- /dev/null +++ b/quantumclient/quantum/v2_0/quota.py @@ -0,0 +1,205 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from cliff import lister +from cliff import show + +from quantumclient.common import exceptions +from quantumclient.common import utils +from quantumclient.quantum import v2_0 as quantumv20 +from quantumclient.quantum.v2_0 import QuantumCommand + + +def get_tenant_id(tenant_id, client): + return (tenant_id if tenant_id else + client.get_quotas_tenant()['tenant']['tenant_id']) + + +class DeleteQuota(QuantumCommand): + """Delete a given tenant's quotas.""" + + api = 'network' + resource = 'quota' + log = logging.getLogger(__name__ + '.DeleteQuota') + + def get_parser(self, prog_name): + parser = super(DeleteQuota, self).get_parser(prog_name) + parser.add_argument( + '--tenant_id', metavar='tenant_id', + help='the owner tenant ID') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + tenant_id = get_tenant_id(parsed_args.tenant_id, + quantum_client) + obj_deleter = getattr(quantum_client, + "delete_%s" % self.resource) + obj_deleter(tenant_id) + print >>self.app.stdout, (_('Deleted %(resource)s: %(tenant_id)s') + % {'tenant_id': tenant_id, + 'resource': self.resource}) + return + + +class ListQuota(QuantumCommand, lister.Lister): + """List all tenants' quotas.""" + + api = 'network' + resource = 'quota' + log = logging.getLogger(__name__ + '.ListQuota') + _formatters = None + + def get_parser(self, prog_name): + parser = super(ListQuota, self).get_parser(prog_name) + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.get_client() + search_opts = {} + self.log.debug('search options: %s', search_opts) + quantum_client.format = parsed_args.request_format + obj_lister = getattr(quantum_client, + "list_%ss" % self.resource) + data = obj_lister(**search_opts) + info = [] + collection = self.resource + "s" + if collection in data: + info = data[collection] + _columns = len(info) > 0 and sorted(info[0].keys()) or [] + return (_columns, (utils.get_item_properties(s, _columns) + for s in info)) + + +class ShowQuota(QuantumCommand, show.ShowOne): + """Show information of a given resource + + """ + api = 'network' + resource = "quota" + log = logging.getLogger(__name__ + '.ShowQuota') + + def get_parser(self, prog_name): + parser = super(ShowQuota, self).get_parser(prog_name) + parser.add_argument( + '--tenant_id', metavar='tenant_id', + help='the owner tenant ID') + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + tenant_id = get_tenant_id(parsed_args.tenant_id, + quantum_client) + params = {} + obj_shower = getattr(quantum_client, + "show_%s" % self.resource) + data = obj_shower(tenant_id, **params) + if self.resource in data: + for k, v in data[self.resource].iteritems(): + if isinstance(v, list): + value = "" + for _item in v: + if value: + value += "\n" + if isinstance(_item, dict): + value += utils.dumps(_item) + else: + value += str(_item) + data[self.resource][k] = value + elif v is None: + data[self.resource][k] = '' + return zip(*sorted(data[self.resource].iteritems())) + else: + return None + + +class UpdateQuota(QuantumCommand, show.ShowOne): + """Update port's information.""" + + resource = 'quota' + log = logging.getLogger(__name__ + '.UpdateQuota') + + def get_parser(self, prog_name): + parser = super(UpdateQuota, self).get_parser(prog_name) + parser.add_argument( + '--tenant_id', metavar='tenant_id', + help='the owner tenant ID') + parser.add_argument( + '--network', metavar='networks', + help='the limit of network quota') + parser.add_argument( + '--subnet', metavar='subnets', + help='the limit of subnet quota') + parser.add_argument( + '--port', metavar='ports', + help='the limit of port quota') + quantumv20.add_extra_argument( + parser, 'value_specs', + 'new values for the %s' % self.resource) + return parser + + def _validate_int(self, name, value): + try: + return_value = int(value) + except Exception: + message = (_('quota limit for %(name)s must be an integer') % + {'name': name}) + raise exceptions.QuantumClientException(message=message) + return return_value + + def get_data(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + quota = {} + for resource in ('network', 'subnet', 'port'): + if getattr(parsed_args, resource): + quota[resource] = self._validate_int( + resource, + getattr(parsed_args, resource)) + value_specs = parsed_args.value_specs + if value_specs: + quota.update(quantumv20.parse_args_to_dict(value_specs)) + obj_updator = getattr(quantum_client, + "update_%s" % self.resource) + tenant_id = get_tenant_id(parsed_args.tenant_id, + quantum_client) + data = obj_updator(tenant_id, {self.resource: quota}) + if self.resource in data: + for k, v in data[self.resource].iteritems(): + if isinstance(v, list): + value = "" + for _item in v: + if value: + value += "\n" + if isinstance(_item, dict): + value += utils.dumps(_item) + else: + value += str(_item) + data[self.resource][k] = value + elif v is None: + data[self.resource][k] = '' + return zip(*sorted(data[self.resource].iteritems())) + else: + return None diff --git a/quantumclient/shell.py b/quantumclient/shell.py index a4097580f..a80784014 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -82,7 +82,16 @@ def env(*_vars, **kwargs): 'port-delete': utils.import_class( 'quantumclient.quantum.v2_0.port.DeletePort'), 'port-update': utils.import_class( - 'quantumclient.quantum.v2_0.port.UpdatePort'), } + 'quantumclient.quantum.v2_0.port.UpdatePort'), + 'quota-list': utils.import_class( + 'quantumclient.quantum.v2_0.quota.ListQuota'), + 'quota-show': utils.import_class( + 'quantumclient.quantum.v2_0.quota.ShowQuota'), + 'quota-delete': utils.import_class( + 'quantumclient.quantum.v2_0.quota.DeleteQuota'), + 'quota-update': utils.import_class( + 'quantumclient.quantum.v2_0.quota.UpdateQuota'), +} COMMANDS = {'2.0': COMMAND_V2} diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index b906237b0..8cc78d211 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -100,7 +100,7 @@ def with_params(*args, **kwargs): if 'format' in kwargs: instance.format = kwargs['format'] ret = self.function(instance, *args, **kwargs) - instance.forma = _format + instance.format = _format return ret return with_params @@ -154,6 +154,34 @@ class Client(object): port_path = "/ports/%s" subnets_path = "/subnets" subnet_path = "/subnets/%s" + quotas_path = "/quotas" + quota_path = "/quotas/%s" + + @APIParamsCall + def get_quotas_tenant(self, **_params): + """Fetch tenant info in server's context for + following quota operation.""" + return self.get(self.quota_path % 'tenant', params=_params) + + @APIParamsCall + def list_quotas(self, **_params): + """Fetch all tenants' quotas.""" + return self.get(self.quotas_path, params=_params) + + @APIParamsCall + def show_quota(self, tenant_id, **_params): + """Fetch information of a certain tenant's quotas.""" + return self.get(self.quota_path % (tenant_id), params=_params) + + @APIParamsCall + def update_quota(self, tenant_id, body=None): + """Update a tenant's quotas.""" + return self.put(self.quota_path % (tenant_id), body=body) + + @APIParamsCall + def delete_quota(self, tenant_id): + """Delete the specified tenant's quota values.""" + return self.delete(self.quota_path % (tenant_id)) @APIParamsCall def list_ports(self, **_params): From 060057c5840949abcb5eea3b2b424bf4e5c8df5a Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Sun, 12 Aug 2012 09:33:40 +0800 Subject: [PATCH 020/135] deal with -c option when the list result is empty. bug #1033123 Change-Id: Idd10e9ae8fd57e6173ef7f92411176834efebff5 --- quantum_test.sh | 2 ++ quantumclient/quantum/v2_0/__init__.py | 2 ++ .../tests/unit/test_cli20_network.py | 30 +++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/quantum_test.sh b/quantum_test.sh index 590d22690..1a4cef974 100755 --- a/quantum_test.sh +++ b/quantum_test.sh @@ -25,6 +25,8 @@ quantum net-show $network_id || die "fail to show network $network_id" quantum net-update $network --admin_state_up False || die "fail to update network $network" quantum net-update $network_id --admin_state_up True || die "fail to update network $network_id" +quantum net-list -c id -- --id fakeid || die "fail to list networks with column selection on empty list" + # test the CRUD of subnet subnet=mysubnet1 cidr=10.0.1.3/24 diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 5de594bf2..78c53395d 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -362,6 +362,8 @@ def get_data(self, parsed_args): if collection in data: info = data[collection] _columns = len(info) > 0 and sorted(info[0].keys()) or [] + if not _columns: + parsed_args.columns = [] return (_columns, (utils.get_item_properties( s, _columns, formatters=self._formatters, ) for s in info), ) diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index 16837b349..de627ded2 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -18,6 +18,7 @@ import sys from quantumclient.common import exceptions +from quantumclient.tests.unit import test_cli20 from quantumclient.tests.unit.test_cli20 import CLITestV20Base from quantumclient.tests.unit.test_cli20 import MyApp from quantumclient.quantum.v2_0.network import CreateNetwork @@ -79,6 +80,35 @@ def test_create_network_state(self): position_names, position_values, admin_state_up=False) + def test_lsit_nets_empty_with_column(self): + resources = "networks" + cmd = ListNetwork(MyApp(sys.stdout), None) + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + reses = {resources: []} + resstr = self.client.serialize(reses) + # url method body + query = "id=myfakeid" + args = ['-c', 'id', '--', '--id', 'myfakeid'] + path = getattr(self.client, resources + "_path") + self.client.httpclient.request( + test_cli20.end_url(path, query), 'GET', + body=None, + headers=test_cli20.ContainsKeyValue( + 'X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("list_" + resources) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertEquals('\n', _str) + def test_list_nets_detail(self): """list nets: -D.""" resources = "networks" From 80e89c929bb5aeddc8a31288ad5f7b749e24e4f5 Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Sun, 12 Aug 2012 11:16:47 +0800 Subject: [PATCH 021/135] enable -h | --help after command to show the command usage. Bug #1023260 we caculate the position of -h|--help and command, if -h|--help is after command, we replace the command with 'help' command. Change-Id: Ieb5fc9d37daafd704edb71e35b74dbf83cb69a4c --- quantumclient/shell.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/quantumclient/shell.py b/quantumclient/shell.py index a4097580f..b751545a7 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -219,6 +219,19 @@ def run(self, argv): :paramtype argv: list of str """ try: + index = 0 + command_pos = -1 + help_pos = -1 + for arg in argv: + if arg in COMMANDS[self.api_version]: + if command_pos == -1: + command_pos = index + elif arg in ('-h', '--help'): + if help_pos == -1: + help_pos = index + index = index + 1 + if command_pos > -1 and help_pos > command_pos: + argv = ['help', argv[command_pos]] self.options, remainder = self.parser.parse_known_args(argv) self.configure_logging() self.interactive_mode = not remainder From a3694571078752b74be9aa95a205d53550dbd9cc Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Sun, 12 Aug 2012 11:38:58 +0800 Subject: [PATCH 022/135] remove cli.app in quantum client error message. blueprint f-3-cli-usability-improvments Change-Id: I3f73f5f0e915ae62e5056abb3f04d7f7f2e68ceb --- quantumclient/shell.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/quantumclient/shell.py b/quantumclient/shell.py index a4097580f..86c85e58e 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -109,8 +109,8 @@ def __call__(self, parser, namespace, values, option_string=None): class QuantumShell(App): - CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' - + CONSOLE_MESSAGE_FORMAT = '%(message)s' + DEBUG_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' log = logging.getLogger(__name__) def __init__(self, apiversion): @@ -320,6 +320,29 @@ def clean_up(self, cmd, result, err): if err: self.log.debug('got an error: %s', err) + def configure_logging(self): + """Create logging handlers for any log output. + """ + root_logger = logging.getLogger('') + + # Set up logging to a file + root_logger.setLevel(logging.DEBUG) + + # Send higher-level messages to the console via stderr + console = logging.StreamHandler(self.stderr) + console_level = {0: logging.WARNING, + 1: logging.INFO, + 2: logging.DEBUG, + }.get(self.options.verbose_level, logging.DEBUG) + console.setLevel(console_level) + if logging.DEBUG == console_level: + formatter = logging.Formatter(self.DEBUG_MESSAGE_FORMAT) + else: + formatter = logging.Formatter(self.CONSOLE_MESSAGE_FORMAT) + console.setFormatter(formatter) + root_logger.addHandler(console) + return + def itertools_compressdef(data, selectors): # patch 2.6 compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F From fdc12bd65027ecf28a78734b3046f20e5f90893f Mon Sep 17 00:00:00 2001 From: Yong Sheng Gong Date: Sun, 12 Aug 2012 07:33:10 +0800 Subject: [PATCH 023/135] add ext list and show commands. Change-Id: I3bdf1a3b066ee12572468b8d7abee96eb07f9257 --- quantumclient/quantum/v2_0/extension.py | 95 +++++++++++++++++++++++++ quantumclient/shell.py | 4 ++ quantumclient/v2_0/client.py | 12 ++++ 3 files changed, 111 insertions(+) create mode 100644 quantumclient/quantum/v2_0/extension.py diff --git a/quantumclient/quantum/v2_0/extension.py b/quantumclient/quantum/v2_0/extension.py new file mode 100644 index 000000000..00fa1fba7 --- /dev/null +++ b/quantumclient/quantum/v2_0/extension.py @@ -0,0 +1,95 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from cliff import lister +from cliff import show + +from quantumclient.common import utils +from quantumclient.quantum.v2_0 import QuantumCommand + + +class ListExt(QuantumCommand, lister.Lister): + """List all exts.""" + + api = 'network' + resource = 'extension' + log = logging.getLogger(__name__ + '.ListExt') + _formatters = None + + def get_parser(self, prog_name): + parser = super(ListExt, self).get_parser(prog_name) + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.get_client() + search_opts = {} + quantum_client.format = parsed_args.request_format + obj_lister = getattr(quantum_client, + "list_%ss" % self.resource) + data = obj_lister(**search_opts) + info = [] + collection = self.resource + "s" + if collection in data: + info = data[collection] + _columns = len(info) > 0 and sorted(info[0].keys()) or [] + return (_columns, (utils.get_item_properties(s, _columns) + for s in info)) + + +class ShowExt(QuantumCommand, show.ShowOne): + """Show information of a given resource + + """ + api = 'network' + resource = "extension" + log = logging.getLogger(__name__ + '.ShowExt') + + def get_parser(self, prog_name): + parser = super(ShowExt, self).get_parser(prog_name) + parser.add_argument( + 'ext_alias', metavar='ext_alias', + help='the extension alias') + return parser + + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + params = {} + obj_shower = getattr(quantum_client, + "show_%s" % self.resource) + data = obj_shower(parsed_args.ext_alias, **params) + if self.resource in data: + for k, v in data[self.resource].iteritems(): + if isinstance(v, list): + value = "" + for _item in v: + if value: + value += "\n" + if isinstance(_item, dict): + value += utils.dumps(_item) + else: + value += str(_item) + data[self.resource][k] = value + elif v is None: + data[self.resource][k] = '' + return zip(*sorted(data[self.resource].iteritems())) + else: + return None diff --git a/quantumclient/shell.py b/quantumclient/shell.py index a80784014..1790e81c2 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -91,6 +91,10 @@ def env(*_vars, **kwargs): 'quantumclient.quantum.v2_0.quota.DeleteQuota'), 'quota-update': utils.import_class( 'quantumclient.quantum.v2_0.quota.UpdateQuota'), + 'ext-list': utils.import_class( + 'quantumclient.quantum.v2_0.extension.ListExt'), + 'ext-show': utils.import_class( + 'quantumclient.quantum.v2_0.extension.ShowExt'), } COMMANDS = {'2.0': COMMAND_V2} diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 8cc78d211..23766daf2 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -156,6 +156,8 @@ class Client(object): subnet_path = "/subnets/%s" quotas_path = "/quotas" quota_path = "/quotas/%s" + exts_path = "/extensions" + ext_path = "/extensions/%s" @APIParamsCall def get_quotas_tenant(self, **_params): @@ -183,6 +185,16 @@ def delete_quota(self, tenant_id): """Delete the specified tenant's quota values.""" return self.delete(self.quota_path % (tenant_id)) + @APIParamsCall + def list_extensions(self, **_params): + """Fetch a list of all exts on server side.""" + return self.get(self.exts_path, params=_params) + + @APIParamsCall + def show_extension(self, ext_alias, **_params): + """Fetch a list of all exts on server side.""" + return self.get(self.ext_path % ext_alias, params=_params) + @APIParamsCall def list_ports(self, **_params): """ From 466e704bfecb3b0797c2c0554b68d671f35a35e1 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Sun, 12 Aug 2012 13:39:22 -0700 Subject: [PATCH 024/135] add pyparsing to pip-requires bug 1035953 Change-Id: Ie18fa78f242920e1acfdf1af15232591908c8de8 --- tools/pip-requires | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/pip-requires b/tools/pip-requires index 2a0be2812..5f2cc0b69 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -3,5 +3,4 @@ argparse httplib2 prettytable>=0.6.0 simplejson - - +pyparsing From f182c58e448c9ef9fcd645a4e36407c050e84556 Mon Sep 17 00:00:00 2001 From: Nachi Ueno Date: Tue, 14 Aug 2012 02:58:41 +0000 Subject: [PATCH 025/135] Support --no-gateway option if --no-gateway is specified, null is used for gateway value. Fixs bug 1035987 Change-Id: I6b9e00563cc2993f40de204e2c399218f00b88c4 --- quantumclient/quantum/v2_0/subnet.py | 11 ++++++ quantumclient/tests/unit/test_cli20_subnet.py | 35 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 62c8ad33c..51e11176b 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -65,6 +65,10 @@ def add_known_arguments(self, parser): parser.add_argument( '--gateway', metavar='gateway', help='gateway ip of this subnet') + parser.add_argument( + '--no-gateway', + default=False, action='store_true', + help='No distribution of gateway') parser.add_argument( '--allocation_pool', action='append', @@ -84,6 +88,13 @@ def args2body(self, parsed_args): body = {'subnet': {'cidr': parsed_args.cidr, 'network_id': _network_id, 'ip_version': parsed_args.ip_version, }, } + + if parsed_args.gateway and parsed_args.no_gateway: + raise exceptions.CommandError("--gateway option and " + "--no-gateway option can " + "not be used same time") + if parsed_args.no_gateway: + body['subnet'].update({'gateway_ip': None}) if parsed_args.gateway: body['subnet'].update({'gateway_ip': parsed_args.gateway}) if parsed_args.tenant_id: diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index ad9ee2f77..77bf0674e 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -29,7 +29,7 @@ class CLITestV20Subnet(CLITestV20Base): def test_create_subnet(self): - """Create sbunet: --gateway gateway netid cidr.""" + """Create subnet: --gateway gateway netid cidr.""" resource = 'subnet' cmd = CreateSubnet(MyApp(sys.stdout), None) name = 'myname' @@ -43,6 +43,39 @@ def test_create_subnet(self): _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + def test_create_subnet_with_no_gateway(self): + """Create subnet: --no-gateway netid cidr""" + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'cidrvalue' + args = ['--no-gateway', netid, cidr] + position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] + position_values = [4, netid, cidr, None] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_subnet_with_bad_gateway_option(self): + """Create sbunet: --no-gateway netid cidr""" + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'cidrvalue' + gateway = 'gatewayvalue' + args = ['--gateway', gateway, '--no-gateway', netid, cidr] + position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] + position_values = [4, netid, cidr, None] + try: + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + except: + return + self.fail('No exception for bad gateway option') + def test_create_subnet_tenant(self): """Create subnet: --tenant_id tenantid netid cidr.""" resource = 'subnet' From 9d4f8fe13c768f94a886713a2d65ca5b7b276e53 Mon Sep 17 00:00:00 2001 From: Bhuvan Arumugam Date: Fri, 17 Aug 2012 22:07:45 -0700 Subject: [PATCH 026/135] Fix warning when creating the sdist package. Bug: 1037334 * MANIFEST.in version.py not exists. Change-Id: Id5eb6a5f5f48827bf053f7d18694eeca38f20a32 --- MANIFEST.in | 1 - 1 file changed, 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 648953813..62ce0fa62 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,5 @@ include tox.ini include LICENSE README HACKING.rst -include version.py include tools/* include quantumclient/tests/* include quantumclient/tests/unit/* From f574f77731ed3ee2123c2b8f8572c50e30810c43 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Sun, 19 Aug 2012 16:50:48 +0900 Subject: [PATCH 027/135] Add install_requires in setuptools.setup(). Fixes bug 1038587 Change-Id: I173cfd3ac8e53f6fb9ce58f30cc40afc9936165d --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 6c1e5e688..7681059b0 100644 --- a/setup.py +++ b/setup.py @@ -56,6 +56,7 @@ license=License, scripts=ProjectScripts, dependency_links=dependency_links, + install_requires=setup.parse_requirements(), tests_require=tests_require, cmdclass=setup.get_cmdclass(), include_package_data=False, From 7a57be1d2763eeb0ec9dd86e2930e7f05559f43e Mon Sep 17 00:00:00 2001 From: Clark Boylan Date: Tue, 21 Aug 2012 14:38:53 -0700 Subject: [PATCH 028/135] Add nosehtmloutput as a test dependency. Adding nosehtmloutput as a test dependency allows nose to output its results to an html file. This will be used by Jenkins to save logs on a different server. Change-Id: I378954997a5d893ac1156c2ae254532c617f78c7 --- tools/test-requires | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/test-requires b/tools/test-requires index 3c03917a6..5367da9a4 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -9,6 +9,7 @@ nose nose-exclude nosexcover openstack.nose_plugin +nosehtmloutput pep8 sphinx>=1.1.2 From 38abece8a6a82c722b00d8ea46997aacecb463a5 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Tue, 21 Aug 2012 01:54:17 -0700 Subject: [PATCH 029/135] initial client + CLI support for routers + floating ips bp quantum-client-l3-floating-ip The task also does the following: 1. Fixes alignment of the --help output 2. Ensures that a show command prints a dictionary correctly Change-Id: Ib61b3e8748a7bd476ec008ab6ce20ab852e92f58 --- quantumclient/quantum/v2_0/__init__.py | 3 + quantumclient/quantum/v2_0/floatingip.py | 133 +++++++++++++ quantumclient/quantum/v2_0/router.py | 186 ++++++++++++++++++ quantumclient/shell.py | 32 ++- quantumclient/tests/unit/test_cli20.py | 24 ++- .../tests/unit/test_cli20_floatingips.py | 96 +++++++++ quantumclient/tests/unit/test_cli20_router.py | 151 ++++++++++++++ quantumclient/v2_0/client.py | 108 ++++++++++ 8 files changed, 731 insertions(+), 2 deletions(-) create mode 100644 quantumclient/quantum/v2_0/floatingip.py create mode 100644 quantumclient/quantum/v2_0/router.py create mode 100644 quantumclient/tests/unit/test_cli20_floatingips.py create mode 100644 quantumclient/tests/unit/test_cli20_router.py diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 78c53395d..f2a458c3d 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -412,6 +412,9 @@ def get_data(self, parsed_args): else: value += str(_item) data[self.resource][k] = value + elif isinstance(v, dict): + value = utils.dumps(v) + data[self.resource][k] = value elif v is None: data[self.resource][k] = '' return zip(*sorted(data[self.resource].iteritems())) diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py new file mode 100644 index 000000000..cd1437e3f --- /dev/null +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -0,0 +1,133 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum.v2_0 import CreateCommand +from quantumclient.quantum.v2_0 import DeleteCommand +from quantumclient.quantum.v2_0 import ListCommand +from quantumclient.quantum.v2_0 import QuantumCommand +from quantumclient.quantum.v2_0 import ShowCommand + + +class ListFloatingIP(ListCommand): + """List floating ips that belong to a given tenant.""" + + resource = 'floatingip' + log = logging.getLogger(__name__ + '.ListFloatingIP') + _formatters = {} + + +class ShowFloatingIP(ShowCommand): + """Show information of a given floating ip.""" + + resource = 'floatingip' + log = logging.getLogger(__name__ + '.ShowFloatingIP') + + +class CreateFloatingIP(CreateCommand): + """Create a floating ip for a given tenant.""" + + resource = 'floatingip' + log = logging.getLogger(__name__ + '.CreateFloatingIP') + + def add_known_arguments(self, parser): + parser.add_argument( + 'floating_network_id', + help='Network to allocate floating IP from') + parser.add_argument( + '--port_id', + help='ID of the port to be associated with the floatingip') + parser.add_argument( + '--fixed_ip_address', + help=('IP address on the port (only required if port has multiple' + 'IPs)')) + + def args2body(self, parsed_args): + body = {'floatingip': { + 'floating_network_id': parsed_args.floating_network_id}} + if parsed_args.tenant_id: + body['floatingip'].update({'tenant_id': parsed_args.tenant_id}) + return body + + +class DeleteFloatingIP(DeleteCommand): + """Delete a given floating ip.""" + + log = logging.getLogger(__name__ + '.DeleteFloatingIP') + resource = 'floatingip' + + +class AssociateFloatingIP(QuantumCommand): + """Create a mapping between a floating ip and a fixed ip.""" + + api = 'network' + log = logging.getLogger(__name__ + '.AssociateFloatingIP') + resource = 'floatingip' + + def get_parser(self, prog_name): + parser = super(AssociateFloatingIP, self).get_parser(prog_name) + parser.add_argument( + 'floatingip_id', metavar='floatingip_id', + help='IP address of the floating IP to associate') + parser.add_argument( + 'port_id', + help='ID of the port to be associated with the floatingip') + parser.add_argument( + '--fixed_ip_address', + help=('IP address on the port (only required if port has multiple' + 'IPs)')) + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + update_dict = {} + if parsed_args.port_id: + update_dict['port_id'] = parsed_args.port_id + if parsed_args.fixed_ip_address: + update_dict['fixed_ip_address'] = parsed_args.fixed_ip_address + quantum_client.update_floatingip(parsed_args.floatingip_id, + {'floatingip': update_dict}) + print >>self.app.stdout, ( + _('Associated floatingip %s') % parsed_args.floatingip_id) + + +class DisassociateFloatingIP(QuantumCommand): + """Remove a mapping from a floating ip to a fixed ip. + """ + + api = 'network' + log = logging.getLogger(__name__ + '.DisassociateFloatingIP') + resource = 'floatingip' + + def get_parser(self, prog_name): + parser = super(DisassociateFloatingIP, self).get_parser(prog_name) + parser.add_argument( + 'floatingip_id', metavar='floatingip_id', + help='IP address of the floating IP to associate') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + quantum_client.update_floatingip(parsed_args.floatingip_id, + {'floatingip': {'port_id': None}}) + print >>self.app.stdout, ( + _('Disassociated floatingip %s') % parsed_args.floatingip_id) diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py new file mode 100644 index 000000000..06afa34d1 --- /dev/null +++ b/quantumclient/quantum/v2_0/router.py @@ -0,0 +1,186 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.common import utils +from quantumclient.quantum.v2_0 import CreateCommand +from quantumclient.quantum.v2_0 import DeleteCommand +from quantumclient.quantum.v2_0 import ListCommand +from quantumclient.quantum.v2_0 import QuantumCommand +from quantumclient.quantum.v2_0 import ShowCommand +from quantumclient.quantum.v2_0 import UpdateCommand + + +def _format_external_gateway_info(router): + try: + return utils.dumps(router['external_gateway_info']) + except Exception: + return '' + + +class ListRouter(ListCommand): + """List routers that belong to a given tenant.""" + + resource = 'router' + log = logging.getLogger(__name__ + '.ListRouter') + _formatters = {'external_gateway_info': _format_external_gateway_info, } + + +class ShowRouter(ShowCommand): + """Show information of a given router.""" + + resource = 'router' + log = logging.getLogger(__name__ + '.ShowRouter') + + +class CreateRouter(CreateCommand): + """Create a router for a given tenant.""" + + resource = 'router' + log = logging.getLogger(__name__ + '.CreateRouter') + + def add_known_arguments(self, parser): + parser.add_argument( + '--admin_state_down', + default=True, action='store_false', + help='Set Admin State Up to false') + parser.add_argument( + 'name', metavar='name', + help='Name of router to create') + + def args2body(self, parsed_args): + body = {'router': { + 'name': parsed_args.name, + 'admin_state_up': parsed_args.admin_state_down, }, } + if parsed_args.tenant_id: + body['router'].update({'tenant_id': parsed_args.tenant_id}) + return body + + +class DeleteRouter(DeleteCommand): + """Delete a given router.""" + + log = logging.getLogger(__name__ + '.DeleteRouter') + resource = 'router' + + +class UpdateRouter(UpdateCommand): + """Update router's information.""" + + log = logging.getLogger(__name__ + '.UpdateRouter') + resource = 'router' + + +class RouterInterfaceCommand(QuantumCommand): + """Based class to Add/Remove router interface.""" + + api = 'network' + log = logging.getLogger(__name__ + '.AddInterfaceRouter') + resource = 'router' + + def get_parser(self, prog_name): + parser = super(RouterInterfaceCommand, self).get_parser(prog_name) + parser.add_argument( + 'router_id', metavar='router_id', + help='ID of the router') + parser.add_argument( + 'subnet_id', metavar='subnet_id', + help='ID of the internal subnet for the interface') + return parser + + +class AddInterfaceRouter(RouterInterfaceCommand): + """Add an internal network interface to a router.""" + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + #TODO(danwent): handle passing in port-id + quantum_client.add_interface_router(parsed_args.router_id, + {'subnet_id': + parsed_args.subnet_id}) + #TODO(danwent): print port ID that is added + print >>self.app.stdout, ( + _('Added interface to router %s') % parsed_args.router_id) + + +class RemoveInterfaceRouter(RouterInterfaceCommand): + """Remove an internal network interface from a router.""" + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + #TODO(danwent): handle passing in port-id + quantum_client.remove_interface_router(parsed_args.router_id, + {'subnet_id': + parsed_args.subnet_id}) + print >>self.app.stdout, ( + _('Removed interface from router %s') % parsed_args.router_id) + + +class SetGatewayRouter(QuantumCommand): + """Set the external network gateway for a router.""" + + log = logging.getLogger(__name__ + '.SetGatewayRouter') + api = 'network' + resource = 'router' + + def get_parser(self, prog_name): + parser = super(SetGatewayRouter, self).get_parser(prog_name) + parser.add_argument( + 'router_id', metavar='router_id', + help='ID of the router') + parser.add_argument( + 'external_network_id', metavar='external_network_id', + help='ID of the external network for the gateway') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + quantum_client.add_gateway_router(parsed_args.router_id, + {'network_id': + parsed_args.external_network_id}) + print >>self.app.stdout, ( + _('Set gateway for router %s') % parsed_args.router_id) + + +class RemoveGatewayRouter(QuantumCommand): + """Remove an external network gateway from a router.""" + + log = logging.getLogger(__name__ + '.RemoveGatewayRouter') + api = 'network' + resource = 'router' + + def get_parser(self, prog_name): + parser = super(RemoveGatewayRouter, self).get_parser(prog_name) + parser.add_argument( + 'router_id', metavar='router_id', + help='ID of the router') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + quantum_client.remove_gateway_router(parsed_args.router_id) + print >>self.app.stdout, ( + _('Removed gateway from router %s') % parsed_args.router_id) diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 952f56f9e..5ccc1c77f 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -95,6 +95,36 @@ def env(*_vars, **kwargs): 'quantumclient.quantum.v2_0.extension.ListExt'), 'ext-show': utils.import_class( 'quantumclient.quantum.v2_0.extension.ShowExt'), + 'router-list': utils.import_class( + 'quantumclient.quantum.v2_0.router.ListRouter'), + 'router-show': utils.import_class( + 'quantumclient.quantum.v2_0.router.ShowRouter'), + 'router-create': utils.import_class( + 'quantumclient.quantum.v2_0.router.CreateRouter'), + 'router-delete': utils.import_class( + 'quantumclient.quantum.v2_0.router.DeleteRouter'), + 'router-update': utils.import_class( + 'quantumclient.quantum.v2_0.router.UpdateRouter'), + 'router-interface-add': utils.import_class( + 'quantumclient.quantum.v2_0.router.AddInterfaceRouter'), + 'router-interface-delete': utils.import_class( + 'quantumclient.quantum.v2_0.router.RemoveInterfaceRouter'), + 'router-gateway-set': utils.import_class( + 'quantumclient.quantum.v2_0.router.SetGatewayRouter'), + 'router-gateway-clear': utils.import_class( + 'quantumclient.quantum.v2_0.router.RemoveGatewayRouter'), + 'floatingip-list': utils.import_class( + 'quantumclient.quantum.v2_0.floatingip.ListFloatingIP'), + 'floatingip-show': utils.import_class( + 'quantumclient.quantum.v2_0.floatingip.ShowFloatingIP'), + 'floatingip-create': utils.import_class( + 'quantumclient.quantum.v2_0.floatingip.CreateFloatingIP'), + 'floatingip-delete': utils.import_class( + 'quantumclient.quantum.v2_0.floatingip.DeleteFloatingIP'), + 'floatingip-associate': utils.import_class( + 'quantumclient.quantum.v2_0.floatingip.AssociateFloatingIP'), + 'floatingip-disassociate': utils.import_class( + 'quantumclient.quantum.v2_0.floatingip.DisassociateFloatingIP'), } COMMANDS = {'2.0': COMMAND_V2} @@ -116,7 +146,7 @@ def __call__(self, parser, namespace, values, option_string=None): factory = ep.load() cmd = factory(self, None) one_liner = cmd.get_description().split('\n')[0] - app.stdout.write(' %-13s %s\n' % (name, one_liner)) + app.stdout.write(' %-25s %s\n' % (name, one_liner)) sys.exit(0) diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 373706103..6ed0afc54 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -140,7 +140,7 @@ def _test_create_resource(self, resource, cmd, self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) - if resource == 'subnet': + if (resource == 'subnet' or resource == 'floatingip'): body = {resource: {}, } else: body = {resource: {'admin_state_up': admin_state_up, }, } @@ -293,3 +293,25 @@ def _test_delete_resource(self, resource, cmd, myid, args): self.mox.UnsetStubs() _str = self.fake_stdout.make_string() self.assertTrue(myid in _str) + + def _test_update_resource_action(self, resource, cmd, myid, action, args, + body): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + path = getattr(self.client, resource + "_path") + path_action = '%s/%s' % (myid, action) + self.client.httpclient.request( + end_url(path % path_action), 'PUT', + body=MyComparator(body, self.client), + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(204), None)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("update_" + resource) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertTrue(myid in _str) diff --git a/quantumclient/tests/unit/test_cli20_floatingips.py b/quantumclient/tests/unit/test_cli20_floatingips.py new file mode 100644 index 000000000..f9b63df6f --- /dev/null +++ b/quantumclient/tests/unit/test_cli20_floatingips.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + +from quantumclient.common import exceptions +from quantumclient.quantum.v2_0.floatingip import AssociateFloatingIP +from quantumclient.quantum.v2_0.floatingip import CreateFloatingIP +from quantumclient.quantum.v2_0.floatingip import DeleteFloatingIP +from quantumclient.quantum.v2_0.floatingip import DisassociateFloatingIP +from quantumclient.quantum.v2_0.floatingip import ListFloatingIP +from quantumclient.quantum.v2_0.floatingip import ShowFloatingIP +from quantumclient.tests.unit.test_cli20 import CLITestV20Base +from quantumclient.tests.unit.test_cli20 import MyApp + + +class CLITestV20FloatingIps(CLITestV20Base): + def test_create_floatingip(self): + """Create floatingip: fip1.""" + resource = 'floatingip' + cmd = CreateFloatingIP(MyApp(sys.stdout), None) + name = 'fip1' + myid = 'myid' + args = [name] + position_names = ['floating_network_id'] + position_values = [name] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_floatingip_and_port(self): + """Create floatingip: fip1.""" + resource = 'floatingip' + cmd = CreateFloatingIP(MyApp(sys.stdout), None) + name = 'fip1' + myid = 'myid' + pid = 'mypid' + args = [name, '--port_id', pid] + position_names = ['floating_network_id', 'port_id'] + position_values = [name, pid] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_list_floatingips(self): + """list floatingips: -D.""" + resources = 'floatingips' + cmd = ListFloatingIP(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_delete_floatingip(self): + """Delete floatingip: fip1""" + resource = 'floatingip' + cmd = DeleteFloatingIP(MyApp(sys.stdout), None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) + + def test_show_floatingip(self): + """Show floatingip: --fields id.""" + resource = 'floatingip' + cmd = ShowFloatingIP(MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id']) + + def test_disassociate_ip(self): + """Disassociate floating IP: myid""" + resource = 'floatingip' + cmd = DisassociateFloatingIP(MyApp(sys.stdout), None) + args = ['myid'] + self._test_update_resource(resource, cmd, 'myid', + args, {"port_id": None} + ) + + def test_associate_ip(self): + """Associate floating IP: myid portid""" + resource = 'floatingip' + cmd = AssociateFloatingIP(MyApp(sys.stdout), None) + args = ['myid', 'portid'] + self._test_update_resource(resource, cmd, 'myid', + args, {"port_id": "portid"} + ) diff --git a/quantumclient/tests/unit/test_cli20_router.py b/quantumclient/tests/unit/test_cli20_router.py new file mode 100644 index 000000000..502365db3 --- /dev/null +++ b/quantumclient/tests/unit/test_cli20_router.py @@ -0,0 +1,151 @@ +# Copyright 2012 Nicira, Inc +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from quantumclient.common import exceptions +from quantumclient.quantum.v2_0.router import AddInterfaceRouter +from quantumclient.quantum.v2_0.router import CreateRouter +from quantumclient.quantum.v2_0.router import DeleteRouter +from quantumclient.quantum.v2_0.router import ListRouter +from quantumclient.quantum.v2_0.router import RemoveGatewayRouter +from quantumclient.quantum.v2_0.router import RemoveInterfaceRouter +from quantumclient.quantum.v2_0.router import SetGatewayRouter +from quantumclient.quantum.v2_0.router import ShowRouter +from quantumclient.quantum.v2_0.router import UpdateRouter +from quantumclient.tests.unit.test_cli20 import CLITestV20Base +from quantumclient.tests.unit.test_cli20 import MyApp + + +class CLITestV20Router(CLITestV20Base): + def test_create_router(self): + """Create router: router1.""" + resource = 'router' + cmd = CreateRouter(MyApp(sys.stdout), None) + name = 'router1' + myid = 'myid' + args = [name, ] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_router_tenant(self): + """Create router: --tenant_id tenantid myname.""" + resource = 'router' + cmd = CreateRouter(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + args = ['--tenant_id', 'tenantid', name] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_router_admin_state(self): + """Create router: --admin_state_down myname.""" + resource = 'router' + cmd = CreateRouter(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + args = ['--admin_state_down', name, ] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + admin_state_up=False) + + def test_list_routers_detail(self): + """list routers: -D.""" + resources = "routers" + cmd = ListRouter(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_update_router_exception(self): + """Update router: myid.""" + resource = 'router' + cmd = UpdateRouter(MyApp(sys.stdout), None) + self.assertRaises(exceptions.CommandError, self._test_update_resource, + resource, cmd, 'myid', ['myid'], {}) + + def test_update_router(self): + """Update router: myid --name myname --tags a b.""" + resource = 'router' + cmd = UpdateRouter(MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'myname'], + {'name': 'myname'} + ) + + def test_delete_router(self): + """Delete router: myid.""" + resource = 'router' + cmd = DeleteRouter(MyApp(sys.stdout), None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) + + def test_show_router(self): + """Show router: myid.""" + resource = 'router' + cmd = ShowRouter(MyApp(sys.stdout), None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, + ['id', 'name']) + + def test_add_interface(self): + """Add interface to router: myid subnetid""" + resource = 'router' + cmd = AddInterfaceRouter(MyApp(sys.stdout), None) + args = ['myid', 'subnetid'] + self._test_update_resource_action(resource, cmd, 'myid', + 'add_router_interface', + args, + {'subnet_id': 'subnetid'} + ) + + def test_del_interface(self): + """Delete interface from router: myid subnetid""" + resource = 'router' + cmd = RemoveInterfaceRouter(MyApp(sys.stdout), None) + args = ['myid', 'subnetid'] + self._test_update_resource_action(resource, cmd, 'myid', + 'remove_router_interface', + args, + {'subnet_id': 'subnetid'} + ) + + def test_set_gateway(self): + """Set external gateway for router: myid externalid""" + resource = 'router' + cmd = SetGatewayRouter(MyApp(sys.stdout), None) + args = ['myid', 'externalid'] + self._test_update_resource(resource, cmd, 'myid', + args, + {"external_gateway_info": + {"network_id": "externalid"}} + ) + + def test_remove_gateway(self): + """Remove external gateway from router: externalid""" + resource = 'router' + cmd = RemoveGatewayRouter(MyApp(sys.stdout), None) + args = ['externalid'] + self._test_update_resource(resource, cmd, 'externalid', + args, {"external_gateway_info": {}} + ) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 23766daf2..09712c9f6 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -158,6 +158,10 @@ class Client(object): quota_path = "/quotas/%s" exts_path = "/extensions" ext_path = "/extensions/%s" + routers_path = "/routers" + router_path = "/routers/%s" + floatingips_path = "/floatingips" + floatingip_path = "/floatingips/%s" @APIParamsCall def get_quotas_tenant(self, **_params): @@ -302,6 +306,110 @@ def delete_subnet(self, subnet): """ return self.delete(self.subnet_path % (subnet)) + @APIParamsCall + def list_routers(self, **_params): + """ + Fetches a list of all routers for a tenant + """ + # Pass filters in "params" argument to do_request + return self.get(self.routers_path, params=_params) + + @APIParamsCall + def show_router(self, router, **_params): + """ + Fetches information of a certain router + """ + return self.get(self.router_path % (router), params=_params) + + @APIParamsCall + def create_router(self, body=None): + """ + Creates a new router + """ + return self.post(self.routers_path, body=body) + + @APIParamsCall + def update_router(self, router, body=None): + """ + Updates a router + """ + return self.put(self.router_path % (router), body=body) + + @APIParamsCall + def delete_router(self, router): + """ + Deletes the specified router + """ + return self.delete(self.router_path % (router)) + + @APIParamsCall + def add_interface_router(self, router, body=None): + """ + Adds an internal network interface to the specified router + """ + return self.put((self.router_path % router) + "/add_router_interface", + body=body) + + @APIParamsCall + def remove_interface_router(self, router, body=None): + """ + Removes an internal network interface from the specified router + """ + return self.put((self.router_path % router) + + "/remove_router_interface", body=body) + + @APIParamsCall + def add_gateway_router(self, router, body=None): + """ + Adds an external network gateway to the specified router + """ + return self.put((self.router_path % router), + body={'router': {'external_gateway_info': body}}) + + @APIParamsCall + def remove_gateway_router(self, router): + """ + Removes an external network gateway from the specified router + """ + return self.put((self.router_path % router), + body={'router': {'external_gateway_info': {}}}) + + @APIParamsCall + def list_floatingips(self, **_params): + """ + Fetches a list of all floatingips for a tenant + """ + # Pass filters in "params" argument to do_request + return self.get(self.floatingips_path, params=_params) + + @APIParamsCall + def show_floatingip(self, floatingip, **_params): + """ + Fetches information of a certain floatingip + """ + return self.get(self.floatingip_path % (floatingip), params=_params) + + @APIParamsCall + def create_floatingip(self, body=None): + """ + Creates a new floatingip + """ + return self.post(self.floatingips_path, body=body) + + @APIParamsCall + def update_floatingip(self, floatingip, body=None): + """ + Updates a floatingip + """ + return self.put(self.floatingip_path % (floatingip), body=body) + + @APIParamsCall + def delete_floatingip(self, floatingip): + """ + Deletes the specified floatingip + """ + return self.delete(self.floatingip_path % (floatingip)) + def __init__(self, **kwargs): """ Initialize a new client for the Quantum v2.0 API. """ super(Client, self).__init__() From 5f211956f129e54a3209ae7bc71e700db04ab108 Mon Sep 17 00:00:00 2001 From: Dean Troyer Date: Thu, 23 Aug 2012 13:09:54 -0500 Subject: [PATCH 030/135] Change '_' to '-' in options This changes every command-line option with a '_' in its name and changes them to '-'. The old option names are maintained for backward compatibility but are no longer in the help text. BP command-options Change-Id: I94daea544ab613321c0a1c4de45092be6dc8471d --- quantumclient/quantum/v2_0/__init__.py | 17 ++++-- quantumclient/quantum/v2_0/extension.py | 2 +- quantumclient/quantum/v2_0/floatingip.py | 20 +++++-- quantumclient/quantum/v2_0/network.py | 7 ++- quantumclient/quantum/v2_0/port.py | 23 ++++++-- quantumclient/quantum/v2_0/quota.py | 16 ++++-- quantumclient/quantum/v2_0/router.py | 17 +++--- quantumclient/quantum/v2_0/subnet.py | 20 +++++-- quantumclient/shell.py | 54 +++++++++++++------ .../tests/unit/test_cli20_floatingips.py | 6 +++ .../tests/unit/test_cli20_network.py | 12 +++++ quantumclient/tests/unit/test_cli20_port.py | 12 +++++ 12 files changed, 164 insertions(+), 42 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index f2a458c3d..62b8c708a 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -67,10 +67,14 @@ def _find_resourceid_by_name(client, resource, name): def add_show_list_common_argument(parser): parser.add_argument( - '-D', '--show_details', + '-D', '--show-details', help='show detailed info', action='store_true', default=False, ) + parser.add_argument( + '--show_details', + action='store_true', + help=argparse.SUPPRESS) parser.add_argument( '-F', '--fields', help='specify the field(s) to be returned by server,' @@ -184,10 +188,14 @@ def get_client(self): def get_parser(self, prog_name): parser = super(QuantumCommand, self).get_parser(prog_name) parser.add_argument( - '--request_format', + '--request-format', help=_('the xml or json request format'), default='json', choices=['json', 'xml', ], ) + parser.add_argument( + '--request_format', + choices=['json', 'xml', ], + help=argparse.SUPPRESS) return parser @@ -204,8 +212,11 @@ class CreateCommand(QuantumCommand, show.ShowOne): def get_parser(self, prog_name): parser = super(CreateCommand, self).get_parser(prog_name) parser.add_argument( - '--tenant_id', metavar='tenant_id', + '--tenant-id', metavar='tenant-id', help=_('the owner tenant ID'), ) + parser.add_argument( + '--tenant_id', + help=argparse.SUPPRESS) self.add_known_arguments(parser) add_extra_argument(parser, 'value_specs', 'new values for the %s' % self.resource) diff --git a/quantumclient/quantum/v2_0/extension.py b/quantumclient/quantum/v2_0/extension.py index 00fa1fba7..df4a88bbf 100644 --- a/quantumclient/quantum/v2_0/extension.py +++ b/quantumclient/quantum/v2_0/extension.py @@ -64,7 +64,7 @@ class ShowExt(QuantumCommand, show.ShowOne): def get_parser(self, prog_name): parser = super(ShowExt, self).get_parser(prog_name) parser.add_argument( - 'ext_alias', metavar='ext_alias', + 'ext_alias', metavar='ext-alias', help='the extension alias') return parser diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index cd1437e3f..ae56ef745 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -15,6 +15,7 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 +import argparse import logging from quantumclient.quantum.v2_0 import CreateCommand @@ -50,12 +51,18 @@ def add_known_arguments(self, parser): 'floating_network_id', help='Network to allocate floating IP from') parser.add_argument( - '--port_id', + '--port-id', help='ID of the port to be associated with the floatingip') parser.add_argument( - '--fixed_ip_address', + '--port_id', + help=argparse.SUPPRESS) + parser.add_argument( + '--fixed-ip-address', help=('IP address on the port (only required if port has multiple' 'IPs)')) + parser.add_argument( + '--fixed_ip_address', + help=argparse.SUPPRESS) def args2body(self, parsed_args): body = {'floatingip': { @@ -82,15 +89,18 @@ class AssociateFloatingIP(QuantumCommand): def get_parser(self, prog_name): parser = super(AssociateFloatingIP, self).get_parser(prog_name) parser.add_argument( - 'floatingip_id', metavar='floatingip_id', + 'floatingip_id', metavar='floatingip-id', help='IP address of the floating IP to associate') parser.add_argument( 'port_id', help='ID of the port to be associated with the floatingip') parser.add_argument( - '--fixed_ip_address', + '--fixed-ip-address', help=('IP address on the port (only required if port has multiple' 'IPs)')) + parser.add_argument( + '--fixed_ip_address', + help=argparse.SUPPRESS) return parser def run(self, parsed_args): @@ -119,7 +129,7 @@ class DisassociateFloatingIP(QuantumCommand): def get_parser(self, prog_name): parser = super(DisassociateFloatingIP, self).get_parser(prog_name) parser.add_argument( - 'floatingip_id', metavar='floatingip_id', + 'floatingip_id', metavar='floatingip-id', help='IP address of the floating IP to associate') return parser diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 61d004bbe..9132343ad 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -15,6 +15,7 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 +import argparse import logging from quantumclient.quantum.v2_0 import CreateCommand @@ -54,9 +55,13 @@ class CreateNetwork(CreateCommand): def add_known_arguments(self, parser): parser.add_argument( - '--admin_state_down', + '--admin-state-down', default=True, action='store_false', help='Set Admin State Up to false') + parser.add_argument( + '--admin_state_down', + action='store_false', + help=argparse.SUPPRESS) parser.add_argument( 'name', metavar='name', help='Name of network to create') diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index c0674e0be..04e6e9845 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -15,6 +15,7 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 +import argparse import logging from quantumclient.common import utils @@ -59,21 +60,35 @@ def add_known_arguments(self, parser): '--name', help='name of this port') parser.add_argument( - '--admin_state_down', + '--admin-state-down', default=True, action='store_false', help='set admin state up to false') parser.add_argument( - '--mac_address', + '--admin_state_down', + action='store_false', + help=argparse.SUPPRESS) + parser.add_argument( + '--mac-address', help='mac address of this port') parser.add_argument( - '--device_id', + '--mac_address', + help=argparse.SUPPRESS) + parser.add_argument( + '--device-id', help='device id of this port') parser.add_argument( - '--fixed_ip', + '--device_id', + help=argparse.SUPPRESS) + parser.add_argument( + '--fixed-ip', action='append', help='desired IP for this port: ' 'subnet_id=,ip_address=, ' 'can be repeated') + parser.add_argument( + '--fixed_ip', + action='append', + help=argparse.SUPPRESS) parser.add_argument( 'network_id', metavar='network', help='Network id or name this port belongs to') diff --git a/quantumclient/quantum/v2_0/quota.py b/quantumclient/quantum/v2_0/quota.py index d8dbefe2d..4028bc720 100644 --- a/quantumclient/quantum/v2_0/quota.py +++ b/quantumclient/quantum/v2_0/quota.py @@ -15,6 +15,7 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 +import argparse import logging from cliff import lister @@ -41,8 +42,11 @@ class DeleteQuota(QuantumCommand): def get_parser(self, prog_name): parser = super(DeleteQuota, self).get_parser(prog_name) parser.add_argument( - '--tenant_id', metavar='tenant_id', + '--tenant-id', metavar='tenant-id', help='the owner tenant ID') + parser.add_argument( + '--tenant_id', + help=argparse.SUPPRESS) return parser def run(self, parsed_args): @@ -101,8 +105,11 @@ class ShowQuota(QuantumCommand, show.ShowOne): def get_parser(self, prog_name): parser = super(ShowQuota, self).get_parser(prog_name) parser.add_argument( - '--tenant_id', metavar='tenant_id', + '--tenant-id', metavar='tenant-id', help='the owner tenant ID') + parser.add_argument( + '--tenant_id', + help=argparse.SUPPRESS) return parser def get_data(self, parsed_args): @@ -143,8 +150,11 @@ class UpdateQuota(QuantumCommand, show.ShowOne): def get_parser(self, prog_name): parser = super(UpdateQuota, self).get_parser(prog_name) parser.add_argument( - '--tenant_id', metavar='tenant_id', + '--tenant-id', metavar='tenant-id', help='the owner tenant ID') + parser.add_argument( + '--tenant_id', + help=argparse.SUPPRESS) parser.add_argument( '--network', metavar='networks', help='the limit of network quota') diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index 06afa34d1..f52ff5f07 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -15,6 +15,7 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 +import argparse import logging from quantumclient.common import utils @@ -56,9 +57,13 @@ class CreateRouter(CreateCommand): def add_known_arguments(self, parser): parser.add_argument( - '--admin_state_down', + '--admin-state-down', default=True, action='store_false', help='Set Admin State Up to false') + parser.add_argument( + '--admin_state_down', + action='store_false', + help=argparse.SUPPRESS) parser.add_argument( 'name', metavar='name', help='Name of router to create') @@ -96,10 +101,10 @@ class RouterInterfaceCommand(QuantumCommand): def get_parser(self, prog_name): parser = super(RouterInterfaceCommand, self).get_parser(prog_name) parser.add_argument( - 'router_id', metavar='router_id', + 'router_id', metavar='router-id', help='ID of the router') parser.add_argument( - 'subnet_id', metavar='subnet_id', + 'subnet_id', metavar='subnet-id', help='ID of the internal subnet for the interface') return parser @@ -145,10 +150,10 @@ class SetGatewayRouter(QuantumCommand): def get_parser(self, prog_name): parser = super(SetGatewayRouter, self).get_parser(prog_name) parser.add_argument( - 'router_id', metavar='router_id', + 'router_id', metavar='router-id', help='ID of the router') parser.add_argument( - 'external_network_id', metavar='external_network_id', + 'external_network_id', metavar='external-network-id', help='ID of the external network for the gateway') return parser @@ -173,7 +178,7 @@ class RemoveGatewayRouter(QuantumCommand): def get_parser(self, prog_name): parser = super(RemoveGatewayRouter, self).get_parser(prog_name) parser.add_argument( - 'router_id', metavar='router_id', + 'router_id', metavar='router-id', help='ID of the router') return parser diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 51e11176b..a7561f33d 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -15,6 +15,7 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 +import argparse import logging from quantumclient.common import utils @@ -59,9 +60,16 @@ def add_known_arguments(self, parser): parser.add_argument( '--name', help='name of this subnet') - parser.add_argument('--ip_version', type=int, - default=4, choices=[4, 6], - help='IP version with default 4') + parser.add_argument( + '--ip-version', + type=int, + default=4, choices=[4, 6], + help='IP version with default 4') + parser.add_argument( + '--ip_version', + type=int, + choices=[4, 6], + help=argparse.SUPPRESS) parser.add_argument( '--gateway', metavar='gateway', help='gateway ip of this subnet') @@ -70,11 +78,15 @@ def add_known_arguments(self, parser): default=False, action='store_true', help='No distribution of gateway') parser.add_argument( - '--allocation_pool', + '--allocation-pool', action='append', help='Allocation pool IP addresses for this subnet: ' 'start=,end= ' 'can be repeated') + parser.add_argument( + '--allocation_pool', + action='append', + help=argparse.SUPPRESS) parser.add_argument( 'network_id', metavar='network', help='Network id or name this subnet belongs to') diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 5ccc1c77f..ed75534a4 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -212,46 +212,70 @@ def build_option_parser(self, description, version): help='show tracebacks on errors', ) # Global arguments parser.add_argument( - '--os_auth_strategy', metavar='', + '--os-auth-strategy', metavar='', default=env('OS_AUTH_STRATEGY', default='keystone'), help='Authentication strategy (Env: OS_AUTH_STRATEGY' ', default keystone). For now, any other value will' ' disable the authentication') + parser.add_argument( + '--os_auth_strategy', + help=argparse.SUPPRESS) parser.add_argument( - '--os_auth_url', metavar='', + '--os-auth-url', metavar='', default=env('OS_AUTH_URL'), help='Authentication URL (Env: OS_AUTH_URL)') + parser.add_argument( + '--os_auth_url', + help=argparse.SUPPRESS) parser.add_argument( - '--os_tenant_name', metavar='', + '--os-tenant-name', metavar='', default=env('OS_TENANT_NAME'), help='Authentication tenant name (Env: OS_TENANT_NAME)') + parser.add_argument( + '--os_tenant_name', + help=argparse.SUPPRESS) parser.add_argument( - '--os_username', metavar='', + '--os-username', metavar='', default=utils.env('OS_USERNAME'), help='Authentication username (Env: OS_USERNAME)') + parser.add_argument( + '--os_username', + help=argparse.SUPPRESS) parser.add_argument( - '--os_password', metavar='', + '--os-password', metavar='', default=utils.env('OS_PASSWORD'), help='Authentication password (Env: OS_PASSWORD)') + parser.add_argument( + '--os_password', + help=argparse.SUPPRESS) parser.add_argument( - '--os_region_name', metavar='', + '--os-region-name', metavar='', default=env('OS_REGION_NAME'), help='Authentication region name (Env: OS_REGION_NAME)') + parser.add_argument( + '--os_region_name', + help=argparse.SUPPRESS) parser.add_argument( - '--os_token', metavar='', + '--os-token', metavar='', default=env('OS_TOKEN'), help='Defaults to env[OS_TOKEN]') + parser.add_argument( + '--os_token', + help=argparse.SUPPRESS) parser.add_argument( - '--os_url', metavar='', + '--os-url', metavar='', default=env('OS_URL'), help='Defaults to env[OS_URL]') + parser.add_argument( + '--os_url', + help=argparse.SUPPRESS) return parser @@ -305,39 +329,39 @@ def authenticate_user(self): if not self.options.os_token: raise exc.CommandError( "You must provide a token via" - " either --os_token or env[OS_TOKEN]") + " either --os-token or env[OS_TOKEN]") if not self.options.os_url: raise exc.CommandError( "You must provide a service URL via" - " either --os_url or env[OS_URL]") + " either --os-url or env[OS_URL]") else: # Validate password flow auth if not self.options.os_username: raise exc.CommandError( "You must provide a username via" - " either --os_username or env[OS_USERNAME]") + " either --os-username or env[OS_USERNAME]") if not self.options.os_password: raise exc.CommandError( "You must provide a password via" - " either --os_password or env[OS_PASSWORD]") + " either --os-password or env[OS_PASSWORD]") if not (self.options.os_tenant_name): raise exc.CommandError( "You must provide a tenant_name via" - " either --os_tenant_name or via env[OS_TENANT_NAME]") + " either --os-tenant-name or via env[OS_TENANT_NAME]") if not self.options.os_auth_url: raise exc.CommandError( "You must provide an auth url via" - " either --os_auth_url or via env[OS_AUTH_URL]") + " either --os-auth-url or via env[OS_AUTH_URL]") else: # not keystone if not self.options.os_url: raise exc.CommandError( "You must provide a service URL via" - " either --os_url or env[OS_URL]") + " either --os-url or env[OS_URL]") self.client_manager = clientmanager.ClientManager( token=self.options.os_token, diff --git a/quantumclient/tests/unit/test_cli20_floatingips.py b/quantumclient/tests/unit/test_cli20_floatingips.py index f9b63df6f..9aeb1e2b1 100644 --- a/quantumclient/tests/unit/test_cli20_floatingips.py +++ b/quantumclient/tests/unit/test_cli20_floatingips.py @@ -55,6 +55,12 @@ def test_create_floatingip_and_port(self): _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + # Test dashed options + args = [name, '--port-id', pid] + position_names = ['floating_network_id', 'port-id'] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + def test_list_floatingips(self): """list floatingips: -D.""" resources = 'floatingips' diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index de627ded2..c82d92c7f 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -54,6 +54,12 @@ def test_create_network_tenant(self): position_names, position_values, tenant_id='tenantid') + # Test dashed options + args = ['--tenant-id', 'tenantid', name] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + def test_create_network_tags(self): """Create net: myname --tags a b.""" resource = 'network' @@ -80,6 +86,12 @@ def test_create_network_state(self): position_names, position_values, admin_state_up=False) + # Test dashed options + args = ['--admin-state-down', name, ] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + admin_state_up=False) + def test_lsit_nets_empty_with_column(self): resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index 14ae209c2..c57aa899d 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -55,6 +55,12 @@ def test_create_port_full(self): _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + # Test dashed options + args = ['--mac-address', 'mac', '--device-id', 'deviceid', netid] + position_names = ['network_id', 'mac_address', 'device_id'] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + def test_create_port_tenant(self): """Create port: --tenant_id tenantid netid.""" resource = 'port' @@ -70,6 +76,12 @@ def test_create_port_tenant(self): position_names, position_values, tenant_id='tenantid') + # Test dashed options + args = ['--tenant-id', 'tenantid', netid, ] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + def test_create_port_tags(self): """Create port: netid mac_address device_id --tags a b.""" resource = 'port' From 5e2f6fd375ddfab0898d62ba88ce7a7951d6f17e Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Wed, 29 Aug 2012 03:56:12 -0400 Subject: [PATCH 031/135] Fix printout of dns name servers and host routes Fixes bug 1043105 Change-Id: I20e9310088767a57f078554025728ae2eb1953e2 --- quantumclient/quantum/v2_0/subnet.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index a7561f33d..9bbacddf6 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -35,12 +35,30 @@ def _format_allocation_pools(subnet): return '' +def _format_dns_nameservers(subnet): + try: + return '\n'.join([utils.dumps(server) for server in + subnet['dns_nameservers']]) + except Exception: + return '' + + +def _format_host_routes(subnet): + try: + return '\n'.join([utils.dumps(route) for route in + subnet['host_routes']]) + except Exception: + return '' + + class ListSubnet(ListCommand): """List networks that belong to a given tenant.""" resource = 'subnet' log = logging.getLogger(__name__ + '.ListSubnet') - _formatters = {'allocation_pools': _format_allocation_pools, } + _formatters = {'allocation_pools': _format_allocation_pools, + 'dns_nameservers': _format_dns_nameservers, + 'host_routes': _format_host_routes, } class ShowSubnet(ShowCommand): From 62f508939e367733552570cc3bac15b82436b981 Mon Sep 17 00:00:00 2001 From: yong sheng gong Date: Sat, 1 Sep 2012 11:38:45 +0800 Subject: [PATCH 032/135] *-list command shows only limited fields normally. Bug #1036051 We add list_columns in list commands to limit the output columns. The behaviour is overriden if we use -c in command. Change-Id: I0fa6c73cd7270d86aff01d3790d59c8d4e8a235a --- quantum_test.sh | 6 ++-- quantumclient/quantum/v2_0/__init__.py | 7 ++++ quantumclient/quantum/v2_0/network.py | 1 + quantumclient/quantum/v2_0/port.py | 1 + quantumclient/quantum/v2_0/subnet.py | 2 ++ quantumclient/shell.py | 8 ----- quantumclient/tests/unit/test_cli20.py | 22 ++++++++++++ .../tests/unit/test_cli20_network.py | 35 ++++++++++++++++++- quantumclient/v2_0/client.py | 2 +- tools/pip-requires | 2 +- tools/test-requires | 6 +--- 11 files changed, 73 insertions(+), 19 deletions(-) diff --git a/quantum_test.sh b/quantum_test.sh index b2e409359..e19600bec 100755 --- a/quantum_test.sh +++ b/quantum_test.sh @@ -74,11 +74,11 @@ tenant_id=tenant_a tenant_id_b=tenant_b quantum quota-update --tenant_id $tenant_id --network 30 || die "fail to update quota for tenant $tenant_id" quantum quota-update --tenant_id $tenant_id_b --network 20 || die "fail to update quota for tenant $tenant_id" -networks=`quantum quota-list | grep $tenant_id | awk '{print $2}'` +networks=`quantum quota-list -c network -c tenant_id | grep $tenant_id | awk '{print $2}'` if [ $networks -ne 30 ]; then die "networks quota should be 30" fi -networks=`quantum quota-list | grep $tenant_id_b | awk '{print $2}'` +networks=`quantum quota-list -c network -c tenant_id | grep $tenant_id_b | awk '{print $2}'` if [ $networks -ne 20 ]; then die "networks quota should be 20" fi @@ -100,7 +100,7 @@ if [ "t$NOAUTH" = "t" ]; then die "ports quota should be 99" fi - ports=`quantum quota-list | grep 99 | awk '{print $4}'` + ports=`quantum quota-list -c port | grep 99 | awk '{print $2}'` if [ $ports -ne 99 ]; then die "ports quota should be 99" fi diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 62b8c708a..d78042ae2 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -339,6 +339,7 @@ class ListCommand(QuantumCommand, lister.Lister): resource = None log = None _formatters = None + list_columns = [] def get_parser(self, prog_name): parser = super(ListCommand, self).get_parser(prog_name) @@ -374,7 +375,13 @@ def get_data(self, parsed_args): info = data[collection] _columns = len(info) > 0 and sorted(info[0].keys()) or [] if not _columns: + # clean the parsed_args.columns so that cliff will not break parsed_args.columns = [] + elif not parsed_args.columns and self.list_columns: + # if no -c(s) by user and list_columns, we use columns in + # both list_columns and returned resource. + # Also Keep their order the same as in list_columns + _columns = [x for x in self.list_columns if x in _columns] return (_columns, (utils.get_item_properties( s, _columns, formatters=self._formatters, ) for s in info), ) diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 9132343ad..07ebf3bfe 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -38,6 +38,7 @@ class ListNetwork(ListCommand): resource = 'network' log = logging.getLogger(__name__ + '.ListNetwork') _formatters = {'subnets': _format_subnets, } + list_columns = ['id', 'name', 'tenant_id', 'subnets'] class ShowNetwork(ShowCommand): diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 04e6e9845..6984f5de5 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -40,6 +40,7 @@ class ListPort(ListCommand): resource = 'port' log = logging.getLogger(__name__ + '.ListPort') _formatters = {'fixed_ips': _format_fixed_ips, } + list_columns = ['id', 'name', 'mac_address', 'fixed_ips'] class ShowPort(ShowCommand): diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 9bbacddf6..0e67cb948 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -18,6 +18,7 @@ import argparse import logging +from quantumclient.common import exceptions from quantumclient.common import utils from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.quantum.v2_0 import CreateCommand @@ -59,6 +60,7 @@ class ListSubnet(ListCommand): _formatters = {'allocation_pools': _format_allocation_pools, 'dns_nameservers': _format_dns_nameservers, 'host_routes': _format_host_routes, } + list_columns = ['id', 'name', 'cidr', 'allocation_pools'] class ShowSubnet(ShowCommand): diff --git a/quantumclient/shell.py b/quantumclient/shell.py index ed75534a4..5f5eccdc8 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -20,7 +20,6 @@ """ import argparse import gettext -import itertools import logging import os import sys @@ -424,15 +423,8 @@ def configure_logging(self): return -def itertools_compressdef(data, selectors): - # patch 2.6 compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F - return (d for d, s in itertools.izip(data, selectors) if s) - - def main(argv=sys.argv[1:]): try: - if not getattr(itertools, 'compress', None): - setattr(itertools, 'compress', itertools_compressdef) return QuantumShell(QUANTUM_API_VERSION).run(argv) except exc.QuantumClientException: return 1 diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 6ed0afc54..d953f88b8 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -25,6 +25,7 @@ from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.v2_0.client import Client + API_VERSION = "2.0" FORMAT = 'json' TOKEN = 'testtoken' @@ -172,6 +173,27 @@ def _test_create_resource(self, resource, cmd, self.assertTrue(myid in _str) self.assertTrue(name in _str) + def _test_list_columns(self, cmd, resources_collection, + resources_out, args=['-f', 'json']): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + resstr = self.client.serialize(resources_out) + + path = getattr(self.client, resources_collection + "_path") + self.client.httpclient.request( + end_url(path), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(200), resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("list_" + resources_collection) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + def _test_list_resources(self, resources, cmd, detail=False, tags=[], fields_1=[], fields_2=[]): self.mox.StubOutWithMock(cmd, "get_client") diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index c82d92c7f..ff4dec865 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -18,6 +18,7 @@ import sys from quantumclient.common import exceptions +from quantumclient.common import utils from quantumclient.tests.unit import test_cli20 from quantumclient.tests.unit.test_cli20 import CLITestV20Base from quantumclient.tests.unit.test_cli20 import MyApp @@ -92,7 +93,7 @@ def test_create_network_state(self): position_names, position_values, admin_state_up=False) - def test_lsit_nets_empty_with_column(self): + def test_list_nets_empty_with_column(self): resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, "get_client") @@ -146,6 +147,38 @@ def test_list_nets_fields(self): self._test_list_resources(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) + def test_list_nets_defined_column(self): + resources = 'networks' + cmd = ListNetwork(MyApp(sys.stdout), None) + returned_body = {"networks": [{"name": "buildname3", + "id": "id3", + "tenant_id": "tenant_3", + "subnets": []}]} + self._test_list_columns(cmd, resources, returned_body, + args=['-f', 'json', '-c', 'id']) + _str = self.fake_stdout.make_string() + returned_networks = utils.loads(_str) + self.assertEquals(1, len(returned_networks)) + network = returned_networks[0] + self.assertEquals(1, len(network)) + self.assertEquals("id", network.keys()[0]) + + def test_list_nets_with_default_column(self): + resources = 'networks' + cmd = ListNetwork(MyApp(sys.stdout), None) + returned_body = {"networks": [{"name": "buildname3", + "id": "id3", + "tenant_id": "tenant_3", + "subnets": []}]} + self._test_list_columns(cmd, resources, returned_body) + _str = self.fake_stdout.make_string() + returned_networks = utils.loads(_str) + self.assertEquals(1, len(returned_networks)) + network = returned_networks[0] + self.assertEquals(4, len(network)) + self.assertEquals(0, len(set(network) ^ + set(cmd.list_columns))) + def test_update_network_exception(self): """Update net: myid.""" resource = 'network' diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 09712c9f6..3dae82344 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -439,7 +439,7 @@ def do_request(self, method, action, body=None, headers=None, params=None): # Add format and tenant_id action += ".%s" % self.format action = self.action_prefix + action - if type(params) is dict: + if type(params) is dict and params: action += '?' + urllib.urlencode(params, doseq=1) if body: body = self.serialize(body) diff --git a/tools/pip-requires b/tools/pip-requires index 5f2cc0b69..6183e4e1b 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,4 +1,4 @@ -cliff>=0.6.0 +cliff>=1.2.1 argparse httplib2 prettytable>=0.6.0 diff --git a/tools/test-requires b/tools/test-requires index 5367da9a4..8b02c3b54 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,9 +1,5 @@ distribute>=0.6.24 -cliff>=0.6.0 -argparse -httplib2 -prettytable>=0.6.0 -simplejson +cliff-tablib>=1.0 mox nose nose-exclude From 010e71512a155fe60760a32c63786de8139a6c5e Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Tue, 4 Sep 2012 21:15:34 -0700 Subject: [PATCH 033/135] update error message to make clear that we were search for a name. Change-Id: I0abce71fbb8964d7dee74a904167db76c9f65e67 --- quantumclient/quantum/v2_0/__init__.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index d78042ae2..7f7b13bf9 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -50,13 +50,14 @@ def _find_resourceid_by_name(client, resource, name): collection = resource + "s" info = data[collection] if len(info) > 1: - msg = (_("Multiple %(resource)s matches found for '%(name)s'," + msg = (_("Multiple %(resource)s matches found for name '%(name)s'," " use an ID to be more specific.") % {'resource': resource, 'name': name}) raise exceptions.QuantumClientException( message=msg) elif len(info) == 0: - not_found_message = (_("Unable to find %(resource)s with '%(name)s'") % + not_found_message = (_("Unable to find %(resource)s with name " + "'%(name)s'") % {'resource': resource, 'name': name}) # 404 is used to simulate server side behavior raise exceptions.QuantumClientException( From 99d7d7000da385aec82a900ed180014402f783d6 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Thu, 6 Sep 2012 23:20:19 +0900 Subject: [PATCH 034/135] router/floating commands support resource reference by name Fixes bug 1046855. Change-Id: I7cc578246019a895c020e822e2d636625363fdde --- quantumclient/quantum/v2_0/floatingip.py | 5 +++- quantumclient/quantum/v2_0/router.py | 32 ++++++++++++++++-------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index ae56ef745..0f4e89b9a 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -18,6 +18,7 @@ import argparse import logging +from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand @@ -65,8 +66,10 @@ def add_known_arguments(self, parser): help=argparse.SUPPRESS) def args2body(self, parsed_args): + _network_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'network', parsed_args.floating_network_id) body = {'floatingip': { - 'floating_network_id': parsed_args.floating_network_id}} + 'floating_network_id': _network_id}} if parsed_args.tenant_id: body['floatingip'].update({'tenant_id': parsed_args.tenant_id}) return body diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index f52ff5f07..e832a7e46 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -19,6 +19,7 @@ import logging from quantumclient.common import utils +from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand @@ -117,9 +118,12 @@ def run(self, parsed_args): quantum_client = self.get_client() quantum_client.format = parsed_args.request_format #TODO(danwent): handle passing in port-id - quantum_client.add_interface_router(parsed_args.router_id, - {'subnet_id': - parsed_args.subnet_id}) + _router_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, self.resource, parsed_args.router_id) + _subnet_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, 'subnet', parsed_args.subnet_id) + quantum_client.add_interface_router(_router_id, + {'subnet_id': _subnet_id}) #TODO(danwent): print port ID that is added print >>self.app.stdout, ( _('Added interface to router %s') % parsed_args.router_id) @@ -133,9 +137,12 @@ def run(self, parsed_args): quantum_client = self.get_client() quantum_client.format = parsed_args.request_format #TODO(danwent): handle passing in port-id - quantum_client.remove_interface_router(parsed_args.router_id, - {'subnet_id': - parsed_args.subnet_id}) + _router_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, self.resource, parsed_args.router_id) + _subnet_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, 'subnet', parsed_args.subnet_id) + quantum_client.remove_interface_router(_router_id, + {'subnet_id': _subnet_id}) print >>self.app.stdout, ( _('Removed interface from router %s') % parsed_args.router_id) @@ -161,9 +168,12 @@ def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) quantum_client = self.get_client() quantum_client.format = parsed_args.request_format - quantum_client.add_gateway_router(parsed_args.router_id, - {'network_id': - parsed_args.external_network_id}) + _router_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, self.resource, parsed_args.router_id) + _ext_net_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, 'network', parsed_args.external_network_id) + quantum_client.add_gateway_router(_router_id, + {'network_id': _ext_net_id}) print >>self.app.stdout, ( _('Set gateway for router %s') % parsed_args.router_id) @@ -186,6 +196,8 @@ def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) quantum_client = self.get_client() quantum_client.format = parsed_args.request_format - quantum_client.remove_gateway_router(parsed_args.router_id) + _router_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, self.resource, parsed_args.router_id) + quantum_client.remove_gateway_router(_router_id) print >>self.app.stdout, ( _('Removed gateway from router %s') % parsed_args.router_id) From 2dd97991dff0831689fdad29d2ff2b942bf75ae9 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Sat, 8 Sep 2012 18:11:14 -0700 Subject: [PATCH 035/135] Send all options with CreateFloatingIP Fix Bug 1048081 Change-Id: I87a66e80a42304508e3e9bc3afbe275b41918404 --- quantumclient/quantum/v2_0/floatingip.py | 10 +++++++--- .../tests/unit/test_cli20_floatingips.py | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index 0f4e89b9a..636552b90 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -68,10 +68,14 @@ def add_known_arguments(self, parser): def args2body(self, parsed_args): _network_id = quantumv20.find_resourceid_by_name_or_id( self.get_client(), 'network', parsed_args.floating_network_id) - body = {'floatingip': { - 'floating_network_id': _network_id}} + body = {self.resource: {'floating_network_id': _network_id}} + if parsed_args.port_id: + body[self.resource].update({'port_id': parsed_args.port_id}) if parsed_args.tenant_id: - body['floatingip'].update({'tenant_id': parsed_args.tenant_id}) + body[self.resource].update({'tenant_id': parsed_args.tenant_id}) + if parsed_args.fixed_ip_address: + body[self.resource].update({'fixed_ip_address': + parsed_args.fixed_ip_address}) return body diff --git a/quantumclient/tests/unit/test_cli20_floatingips.py b/quantumclient/tests/unit/test_cli20_floatingips.py index 9aeb1e2b1..e48d29e86 100644 --- a/quantumclient/tests/unit/test_cli20_floatingips.py +++ b/quantumclient/tests/unit/test_cli20_floatingips.py @@ -61,6 +61,25 @@ def test_create_floatingip_and_port(self): _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + def test_create_floatingip_and_port_and_address(self): + """Create floatingip: fip1 with a given port and address""" + resource = 'floatingip' + cmd = CreateFloatingIP(MyApp(sys.stdout), None) + name = 'fip1' + myid = 'myid' + pid = 'mypid' + addr = '10.0.0.99' + args = [name, '--port_id', pid, '--fixed_ip_address', addr] + position_names = ['floating_network_id', 'port_id', 'fixed_ip_address'] + position_values = [name, pid, addr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + # Test dashed options + args = [name, '--port-id', pid, '--fixed-ip-address', addr] + position_names = ['floating_network_id', 'port-id', 'fixed-ip-address'] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + def test_list_floatingips(self): """list floatingips: -D.""" resources = 'floatingips' From 75fcafbbc6872be61dab3cbdd3c9f63dec3cf09c Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Sun, 9 Sep 2012 09:14:33 -0700 Subject: [PATCH 036/135] prevent floatingip-show and floatingip-delete from querying by "name" bug 1048088 floating IPs do not have names, but the show and delete commands inherited logic that implied that they did, leading to very odd results that would result is "floating-ip show X" return a result even if X was not a valid UUID for any floating IP. Change-Id: I5939a2a28ae4abf94d44fadb75a657fe706f1295 --- quantumclient/quantum/v2_0/__init__.py | 30 ++++++++++++++++++------ quantumclient/quantum/v2_0/floatingip.py | 2 ++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 7f7b13bf9..c719d15ba 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -307,12 +307,17 @@ class DeleteCommand(QuantumCommand): api = 'network' resource = None log = None + allow_names = True def get_parser(self, prog_name): parser = super(DeleteCommand, self).get_parser(prog_name) + if self.allow_names: + help_str = 'ID or name of %s to delete' + else: + help_str = 'ID of %s to delete' parser.add_argument( 'id', metavar=self.resource, - help='ID or name of %s to delete' % self.resource) + help=help_str % self.resource) return parser def run(self, parsed_args): @@ -321,9 +326,11 @@ def run(self, parsed_args): quantum_client.format = parsed_args.request_format obj_deleter = getattr(quantum_client, "delete_%s" % self.resource) - _id = find_resourceid_by_name_or_id(quantum_client, - self.resource, - parsed_args.id) + if self.allow_names: + _id = find_resourceid_by_name_or_id(quantum_client, self.resource, + parsed_args.id) + else: + _id = parsed_args.id obj_deleter(_id) print >>self.app.stdout, (_('Deleted %(resource)s: %(id)s') % {'id': parsed_args.id, @@ -396,13 +403,18 @@ class ShowCommand(QuantumCommand, show.ShowOne): api = 'network' resource = None log = None + allow_names = True def get_parser(self, prog_name): parser = super(ShowCommand, self).get_parser(prog_name) add_show_list_common_argument(parser) + if self.allow_names: + help_str = 'ID or name of %s to look up' + else: + help_str = 'ID of %s to look up' parser.add_argument( 'id', metavar=self.resource, - help='ID or name of %s to look up' % self.resource) + help=help_str % self.resource) return parser def get_data(self, parsed_args): @@ -415,8 +427,12 @@ def get_data(self, parsed_args): params = {'verbose': 'True'} if parsed_args.fields: params = {'fields': parsed_args.fields} - _id = find_resourceid_by_name_or_id(quantum_client, self.resource, - parsed_args.id) + if self.allow_names: + _id = find_resourceid_by_name_or_id(quantum_client, self.resource, + parsed_args.id) + else: + _id = parsed_args.id + obj_shower = getattr(quantum_client, "show_%s" % self.resource) data = obj_shower(_id, **params) if self.resource in data: diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index 0f4e89b9a..d5d21814a 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -39,6 +39,7 @@ class ShowFloatingIP(ShowCommand): resource = 'floatingip' log = logging.getLogger(__name__ + '.ShowFloatingIP') + allow_names = False class CreateFloatingIP(CreateCommand): @@ -80,6 +81,7 @@ class DeleteFloatingIP(DeleteCommand): log = logging.getLogger(__name__ + '.DeleteFloatingIP') resource = 'floatingip' + allow_names = False class AssociateFloatingIP(QuantumCommand): From 95c4fee38cf9eb33252a170c1f54c8272b96e82e Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Sat, 8 Sep 2012 03:57:01 -0400 Subject: [PATCH 037/135] Limit list command for router and floating ip Fixes bug 1047751 Also removes tenant id from network list output Change-Id: I5302b97ed784580508d2ca2170ab3c240053c1a1 --- quantumclient/quantum/v2_0/floatingip.py | 2 ++ quantumclient/quantum/v2_0/network.py | 2 +- quantumclient/quantum/v2_0/router.py | 1 + quantumclient/tests/unit/test_cli20_network.py | 2 +- 4 files changed, 5 insertions(+), 2 deletions(-) diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index 002384a85..5613af866 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -32,6 +32,8 @@ class ListFloatingIP(ListCommand): resource = 'floatingip' log = logging.getLogger(__name__ + '.ListFloatingIP') _formatters = {} + list_columns = ['id', 'fixed_ip_address', 'floating_ip_address', + 'port_id'] class ShowFloatingIP(ShowCommand): diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 07ebf3bfe..499d11d11 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -38,7 +38,7 @@ class ListNetwork(ListCommand): resource = 'network' log = logging.getLogger(__name__ + '.ListNetwork') _formatters = {'subnets': _format_subnets, } - list_columns = ['id', 'name', 'tenant_id', 'subnets'] + list_columns = ['id', 'name', 'subnets'] class ShowNetwork(ShowCommand): diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index e832a7e46..9d6787d6a 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -41,6 +41,7 @@ class ListRouter(ListCommand): resource = 'router' log = logging.getLogger(__name__ + '.ListRouter') _formatters = {'external_gateway_info': _format_external_gateway_info, } + list_columns = ['id', 'name', 'external_gateway_info'] class ShowRouter(ShowCommand): diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index ff4dec865..729e98274 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -175,7 +175,7 @@ def test_list_nets_with_default_column(self): returned_networks = utils.loads(_str) self.assertEquals(1, len(returned_networks)) network = returned_networks[0] - self.assertEquals(4, len(network)) + self.assertEquals(3, len(network)) self.assertEquals(0, len(set(network) ^ set(cmd.list_columns))) From f0a9235586897740e829154dfeac743eae001843 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 11 Sep 2012 15:17:44 -0700 Subject: [PATCH 038/135] Support shared option in CLI Fix bug 1049386 Add the 'shared' attribute to known parameters Change-Id: Ica3b00c6b4108a328748025f94ceebf8c935e662 --- quantumclient/quantum/v2_0/network.py | 9 ++++++++- quantumclient/tests/unit/test_cli20.py | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 499d11d11..21efd2d84 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -63,6 +63,11 @@ def add_known_arguments(self, parser): '--admin_state_down', action='store_false', help=argparse.SUPPRESS) + parser.add_argument( + '--shared', + action='store_true', + default=argparse.SUPPRESS, + help='Set the network as shared') parser.add_argument( 'name', metavar='name', help='Name of network to create') @@ -70,9 +75,11 @@ def add_known_arguments(self, parser): def args2body(self, parsed_args): body = {'network': { 'name': parsed_args.name, - 'admin_state_up': parsed_args.admin_state_down, }, } + 'admin_state_up': parsed_args.admin_state_down}, } if parsed_args.tenant_id: body['network'].update({'tenant_id': parsed_args.tenant_id}) + if hasattr(parsed_args, 'shared'): + body['network'].update({'shared': parsed_args.shared}) return body diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index d953f88b8..15ca12270 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -137,7 +137,7 @@ def tearDown(self): def _test_create_resource(self, resource, cmd, name, myid, args, position_names, position_values, tenant_id=None, - tags=None, admin_state_up=True): + tags=None, admin_state_up=True, shared=False): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) @@ -149,6 +149,9 @@ def _test_create_resource(self, resource, cmd, body[resource].update({'tenant_id': tenant_id}) if tags: body[resource].update({'tags': tags}) + if shared: + body[resource].update({'shared': shared}) + for i in xrange(len(position_names)): body[resource].update({position_names[i]: position_values[i]}) ress = {resource: From 2a45b707bd0f8b44149ba12f4473f0e5df7db364 Mon Sep 17 00:00:00 2001 From: gongysh Date: Wed, 12 Sep 2012 10:33:38 +0800 Subject: [PATCH 039/135] Add document for using quantum client by python or cli invocation. After this patch, we should see output at http://docs.openstack.org/developer/python-quantumclient/. Change-Id: I908c0d0e7f80a6eb73e97ee30eaab1bfbfec1e61 --- doc/source/index.rst | 41 +++++++++++++++++++++++++-------- quantumclient/quantum/client.py | 14 ++++++++++- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index 8032d6914..85ca4009f 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,17 +1,40 @@ Python bindings to the OpenStack Network API ============================================ -This is a client for OpenStack Network API. Contents: +In order to use the python quantum client directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so:: -.. toctree:: - :maxdepth: 1 + >>> from quantumclient.quantum import client + >>> quantum = client.Client('2.0', endpoint_url=OS_URL, token=OS_TOKEN) + >>> network = {'name': 'mynetwork', 'admin_state_up': True} + >>> quantum.create_network({'network':network}) + >>> networks = quantum.list_networks(name='mynetwork') + >>> print networks + >>> quantum.delete_network(name='mynetwork') - api/autoindex -Indices and tables -================== +Command-line Tool +================= +In order to use the CLI, you must provide your OpenStack username, password, tenant, and auth endpoint. Use the corresponding configuration options (``--os-username``, ``--os-password``, ``--os-tenant-name``, and ``--os-auth-url``) or set them in environment variables:: -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + export OS_USERNAME=user + export OS_PASSWORD=pass + export OS_TENANT_NAME=tenant + export OS_AUTH_URL=http://auth.example.com:5000/v2.0 +The command line tool will attempt to reauthenticate using your provided credentials for every request. You can override this behavior by manually supplying an auth token using ``--os-url`` and ``--os-auth-token``. You can alternatively set these environment variables:: + + export OS_URL=http://quantum.example.org:9696/ + export OS_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 + +If quantum server does not require authentication, besides these two arguments or environment variables (We can use any value as token.), we need manually supply ``--os-auth-strategy`` or set the environment variable:: + + export OS_AUTH_STRATEGY=noauth + +Once you've configured your authentication parameters, you can run ``quantum -h`` to see a complete listing of available commands. + +Release Notes +============= + +2.0 +----- +* support Quantum API 2.0 diff --git a/quantumclient/quantum/client.py b/quantumclient/quantum/client.py index c7d7948d1..c89408b9f 100644 --- a/quantumclient/quantum/client.py +++ b/quantumclient/quantum/client.py @@ -26,7 +26,7 @@ def make_client(instance): - """Returns an identity service client. + """Returns an quantum client. """ quantum_client = utils.get_client_class( API_NAME, @@ -49,3 +49,15 @@ def make_client(instance): else: raise exceptions.UnsupportedVersion("API version %s is not supported" % instance._api_version[API_NAME]) + + +def Client(api_version, *args, **kwargs): + """Return an quantum client. + @param api_version: only 2.0 is supported now + """ + quantum_client = utils.get_client_class( + API_NAME, + api_version, + API_VERSIONS, + ) + return quantum_client(*args, **kwargs) From d2214940872d7a4096e3eafed60da96405273303 Mon Sep 17 00:00:00 2001 From: Jiajun Liu Date: Thu, 13 Sep 2012 17:52:15 +0800 Subject: [PATCH 040/135] fix a minor comment error Change-Id: I8df6f16acf20d54290dc9687005571fb06aa7c00 --- quantumclient/v2_0/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 3dae82344..6ec752def 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -479,7 +479,7 @@ def serialize(self, data): def deserialize(self, data, status_code): """ - Deserializes a an xml or json string into a dictionary + Deserializes an xml or json string into a dictionary """ if status_code == 204: return data From 3e19fc0a563e78ff81c63788e6f3d163054ff34f Mon Sep 17 00:00:00 2001 From: gongysh Date: Thu, 20 Sep 2012 06:46:32 +0800 Subject: [PATCH 041/135] clean the descriptions for quota cli commands. Bug #1053099 By default, the quotas for all tenants will use defaults from quantum.conf. Admin can use the cli commands to define different quotas for tenants. Change-Id: Ifb0ef3ce2680245ccb2c2872bed1cbef3bb9629b --- quantumclient/quantum/v2_0/quota.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quantumclient/quantum/v2_0/quota.py b/quantumclient/quantum/v2_0/quota.py index 4028bc720..2a6979c33 100644 --- a/quantumclient/quantum/v2_0/quota.py +++ b/quantumclient/quantum/v2_0/quota.py @@ -33,7 +33,7 @@ def get_tenant_id(tenant_id, client): class DeleteQuota(QuantumCommand): - """Delete a given tenant's quotas.""" + """Delete defined quotas of a given tenant.""" api = 'network' resource = 'quota' @@ -65,7 +65,7 @@ def run(self, parsed_args): class ListQuota(QuantumCommand, lister.Lister): - """List all tenants' quotas.""" + """List defined quotas of all tenants.""" api = 'network' resource = 'quota' @@ -95,7 +95,7 @@ def get_data(self, parsed_args): class ShowQuota(QuantumCommand, show.ShowOne): - """Show information of a given resource + """Show quotas of a given tenant """ api = 'network' @@ -142,7 +142,7 @@ def get_data(self, parsed_args): class UpdateQuota(QuantumCommand, show.ShowOne): - """Update port's information.""" + """Define tenant's quotas not to use defaults.""" resource = 'quota' log = logging.getLogger(__name__ + '.UpdateQuota') From c8e7ed26befb9a413e5b6dee8a5e9e4db28a17c5 Mon Sep 17 00:00:00 2001 From: gongysh Date: Mon, 8 Oct 2012 18:48:26 +0800 Subject: [PATCH 042/135] Generate bash_completion string so that we can use bash completion. Bug #1063500 To install, copy tools/quantum.bash_completion to /etc/bash_completion.d/quantum Change-Id: I0afff3967c63111854455226fc90092f5bc7845a --- quantumclient/shell.py | 21 +++++++++++++++++++++ tools/quantum.bash_completion | 27 +++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 tools/quantum.bash_completion diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 5f5eccdc8..c4195ba5c 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -278,6 +278,24 @@ def build_option_parser(self, description, version): return parser + def _bash_completion(self): + """ + Prints all of the commands and options to stdout so that the + quantum's bash-completion script doesn't have to hard code them. + """ + commands = set() + options = set() + for option, _action in self.parser._option_string_actions.items(): + options.add(option) + for command_name, command in self.command_manager: + commands.add(command_name) + cmd_factory = command.load() + cmd = cmd_factory(self, None) + cmd_parser = cmd.get_parser('') + for option, _action in cmd_parser._option_string_actions.items(): + options.add(option) + print ' '.join(commands | options) + def run(self, argv): """Equivalent to the main program for the application. @@ -289,6 +307,9 @@ def run(self, argv): command_pos = -1 help_pos = -1 for arg in argv: + if arg == 'bash-completion': + self._bash_completion() + return 0 if arg in COMMANDS[self.api_version]: if command_pos == -1: command_pos = index diff --git a/tools/quantum.bash_completion b/tools/quantum.bash_completion new file mode 100644 index 000000000..45db025af --- /dev/null +++ b/tools/quantum.bash_completion @@ -0,0 +1,27 @@ +_quantum_opts="" # lazy init +_quantum_flags="" # lazy init +_quantum_opts_exp="" # lazy init +_quantum() +{ + local cur prev nbc cflags + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [ "x$_quantum_opts" == "x" ] ; then + nbc="`quantum bash-completion | sed -e "s/\s-h\s/\s/"`" + _quantum_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" + _quantum_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" + _quantum_opts_exp="`echo "$_quantum_opts" | sed -e "s/\s/|/g"`" + fi + + if [[ " ${COMP_WORDS[@]} " =~ " "($_quantum_opts_exp)" " && "$prev" != "help" ]] ; then + COMPLETION_CACHE=~/.quantumclient/*/*-cache + cflags="$_quantum_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ') + COMPREPLY=($(compgen -W "${cflags}" -- ${cur})) + else + COMPREPLY=($(compgen -W "${_quantum_opts}" -- ${cur})) + fi + return 0 +} +complete -F _quantum quantum From 855c4a1209d03c9aeeb5fa04310cb78b576ff404 Mon Sep 17 00:00:00 2001 From: Doug Hellmann Date: Mon, 22 Oct 2012 18:50:30 -0400 Subject: [PATCH 043/135] Add OpenStack trove classifier for PyPI Add trove classifier to have the client listed among the other OpenStack-related projets on PyPI. Change-Id: Ia37256fb8eb10e3e0e3943f1ccae4344a6ed97de Signed-off-by: Doug Hellmann --- setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/setup.py b/setup.py index 7681059b0..c3d851cbd 100644 --- a/setup.py +++ b/setup.py @@ -54,6 +54,16 @@ description=ShortDescription, long_description=Description, license=License, + classifiers=[ + 'Environment :: OpenStack', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + ], scripts=ProjectScripts, dependency_links=dependency_links, install_requires=setup.parse_requirements(), From 4ca036541c8f9f132754db5d62071828b6038628 Mon Sep 17 00:00:00 2001 From: Aaron Rosen Date: Mon, 8 Oct 2012 23:14:22 -0700 Subject: [PATCH 044/135] Adds securitygroup implementation Implements blueprint quantum-client-security-groups API Change-Id: I9b6ad8525909688915fadefc75075406b8380327 --- quantumclient/quantum/v2_0/securitygroup.py | 158 ++++++++++++++++++ quantumclient/shell.py | 16 ++ quantumclient/tests/unit/test_cli20.py | 12 +- .../tests/unit/test_cli20_securitygroup.py | 148 ++++++++++++++++ quantumclient/v2_0/client.py | 63 +++++++ 5 files changed, 393 insertions(+), 4 deletions(-) create mode 100644 quantumclient/quantum/v2_0/securitygroup.py create mode 100644 quantumclient/tests/unit/test_cli20_securitygroup.py diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py new file mode 100644 index 000000000..b56d84d89 --- /dev/null +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -0,0 +1,158 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum import v2_0 as quantumv20 + + +class ListSecurityGroup(quantumv20.ListCommand): + """List security groups that belong to a given tenant.""" + + resource = 'security_group' + log = logging.getLogger(__name__ + '.ListSecurityGroup') + _formatters = {} + list_columns = ['id', 'name', 'description'] + + +class ShowSecurityGroup(quantumv20.ShowCommand): + """Show information of a given security group.""" + + resource = 'security_group' + log = logging.getLogger(__name__ + '.ShowSecurityGroup') + allow_names = True + + +class CreateSecurityGroup(quantumv20.CreateCommand): + """Create a security group.""" + + resource = 'security_group' + log = logging.getLogger(__name__ + '.CreateSecurityGroup') + + def add_known_arguments(self, parser): + parser.add_argument( + 'name', metavar='name', + help='Name of security group') + parser.add_argument( + '--description', + help='description of security group') + + def args2body(self, parsed_args): + body = {'security_group': { + 'name': parsed_args.name}} + if parsed_args.description: + body['security_group'].update( + {'description': parsed_args.description}) + if parsed_args.tenant_id: + body['security_group'].update({'tenant_id': parsed_args.tenant_id}) + return body + + +class DeleteSecurityGroup(quantumv20.DeleteCommand): + """Delete a given security group.""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroup') + resource = 'security_group' + allow_names = True + + +class ListSecurityGroupRule(quantumv20.ListCommand): + """List security group rules that belong to a given tenant.""" + + resource = 'security_group_rule' + log = logging.getLogger(__name__ + '.ListSecurityGroupRule') + _formatters = {} + list_columns = ['id', 'security_group_id', 'direction', 'protocol', + 'source_ip_prefix', 'source_group_id'] + + +class ShowSecurityGroupRule(quantumv20.ShowCommand): + """Show information of a given security group rule.""" + + resource = 'security_group_rule' + log = logging.getLogger(__name__ + '.ShowSecurityGroupRule') + allow_names = False + + +class CreateSecurityGroupRule(quantumv20.CreateCommand): + """Create a security group rule.""" + + resource = 'security_group_rule' + log = logging.getLogger(__name__ + '.CreateSecurityGroupRule') + + def add_known_arguments(self, parser): + parser.add_argument( + 'security_group_id', metavar='security_group_id', + help='Security group to add rule.') + parser.add_argument( + '--direction', + default='ingress', + help='direction of traffic: ingress/egress') + parser.add_argument( + '--ethertype', + default='IPv4', + help='IPv4/IPv6') + parser.add_argument( + '--protocol', + help='protocol of packet') + parser.add_argument( + '--port_range_min', + help='starting port range') + parser.add_argument( + '--port_range_max', + help='ending port range') + parser.add_argument( + '--source_ip_prefix', + help='cidr to match on') + parser.add_argument( + '--source_group_id', + help='source security group to apply rule') + + def args2body(self, parsed_args): + _security_group_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'security_group', parsed_args.security_group_id) + body = {'security_group_rule': { + 'security_group_id': _security_group_id, + 'direction': parsed_args.direction, + 'ethertype': parsed_args.ethertype}} + if parsed_args.protocol: + body['security_group_rule'].update( + {'protocol': parsed_args.protocol}) + if parsed_args.port_range_min: + body['security_group_rule'].update( + {'port_range_min': parsed_args.port_range_min}) + if parsed_args.port_range_max: + body['security_group_rule'].update( + {'port_range_max': parsed_args.port_range_max}) + if parsed_args.source_ip_prefix: + body['security_group_rule'].update( + {'source_ip_prefix': parsed_args.source_ip_prefix}) + if parsed_args.source_group_id: + body['security_group_rule'].update( + {'source_group_id': parsed_args.source_group_id}) + if parsed_args.tenant_id: + body['security_group_rule'].update( + {'tenant_id': parsed_args.tenant_id}) + return body + + +class DeleteSecurityGroupRule(quantumv20.DeleteCommand): + """Delete a given security group rule.""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') + resource = 'security_group_rule' + allow_names = False diff --git a/quantumclient/shell.py b/quantumclient/shell.py index c4195ba5c..0fb7952a2 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -124,6 +124,22 @@ def env(*_vars, **kwargs): 'quantumclient.quantum.v2_0.floatingip.AssociateFloatingIP'), 'floatingip-disassociate': utils.import_class( 'quantumclient.quantum.v2_0.floatingip.DisassociateFloatingIP'), + 'security-group-list': utils.import_class( + 'quantumclient.quantum.v2_0.securitygroup.ListSecurityGroup'), + 'security-group-show': utils.import_class( + 'quantumclient.quantum.v2_0.securitygroup.ShowSecurityGroup'), + 'security-group-create': utils.import_class( + 'quantumclient.quantum.v2_0.securitygroup.CreateSecurityGroup'), + 'security-group-delete': utils.import_class( + 'quantumclient.quantum.v2_0.securitygroup.DeleteSecurityGroup'), + 'security-group-rule-list': utils.import_class( + 'quantumclient.quantum.v2_0.securitygroup.ListSecurityGroupRule'), + 'security-group-rule-show': utils.import_class( + 'quantumclient.quantum.v2_0.securitygroup.ShowSecurityGroupRule'), + 'security-group-rule-create': utils.import_class( + 'quantumclient.quantum.v2_0.securitygroup.CreateSecurityGroupRule'), + 'security-group-rule-delete': utils.import_class( + 'quantumclient.quantum.v2_0.securitygroup.DeleteSecurityGroupRule'), } COMMANDS = {'2.0': COMMAND_V2} diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 15ca12270..788460046 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -141,7 +141,9 @@ def _test_create_resource(self, resource, cmd, self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) - if (resource == 'subnet' or resource == 'floatingip'): + non_admin_status_resources = ['subnet', 'floatingip', 'security_group', + 'security_group_rule'] + if (resource in non_admin_status_resources): body = {resource: {}, } else: body = {resource: {'admin_state_up': admin_state_up, }, } @@ -155,8 +157,9 @@ def _test_create_resource(self, resource, cmd, for i in xrange(len(position_names)): body[resource].update({position_names[i]: position_values[i]}) ress = {resource: - {'id': myid, - 'name': name, }, } + {'id': myid}, } + if name: + ress[resource].update({'name': name}) resstr = self.client.serialize(ress) # url method body path = getattr(self.client, resource + "s_path") @@ -174,7 +177,8 @@ def _test_create_resource(self, resource, cmd, self.mox.UnsetStubs() _str = self.fake_stdout.make_string() self.assertTrue(myid in _str) - self.assertTrue(name in _str) + if name: + self.assertTrue(name in _str) def _test_list_columns(self, cmd, resources_collection, resources_out, args=['-f', 'json']): diff --git a/quantumclient/tests/unit/test_cli20_securitygroup.py b/quantumclient/tests/unit/test_cli20_securitygroup.py new file mode 100644 index 000000000..c5fcf5fc1 --- /dev/null +++ b/quantumclient/tests/unit/test_cli20_securitygroup.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + +from quantumclient.quantum.v2_0 import securitygroup +from quantumclient.tests.unit import test_cli20 + + +class CLITestV20SecurityGroups(test_cli20.CLITestV20Base): + def test_create_security_group(self): + """Create security group: webservers.""" + resource = 'security_group' + cmd = securitygroup.CreateSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + name = 'webservers' + myid = 'myid' + args = [name, ] + position_names = ['name'] + position_values = [name] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_security_group_tenant(self): + """Create security group: webservers.""" + resource = 'security_group' + cmd = securitygroup.CreateSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + name = 'webservers' + description = 'my webservers' + myid = 'myid' + args = ['--tenant_id', 'tenant_id', '--description', description, name] + position_names = ['name', 'description'] + position_values = [name, description] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenant_id') + + def test_create_security_group_with_description(self): + """Create security group: webservers.""" + resource = 'security_group' + cmd = securitygroup.CreateSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + name = 'webservers' + description = 'my webservers' + myid = 'myid' + args = [name, '--description', description] + position_names = ['name', 'description'] + position_values = [name, description] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_list_security_groups(self): + resources = "security_groups" + cmd = securitygroup.ListSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_show_security_group_id(self): + resource = 'security_group' + cmd = securitygroup.ShowSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id']) + + def test_show_security_group_id_name(self): + resource = 'security_group' + cmd = securitygroup.ShowSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id', 'name']) + + def test_delete_security_group(self): + """Delete security group: myid.""" + resource = 'security_group' + cmd = securitygroup.DeleteSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) + + def test_create_security_group_rule_full(self): + """Create security group rule""" + resource = 'security_group_rule' + cmd = securitygroup.CreateSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + myid = 'myid' + direction = 'ingress' + ethertype = 'IPv4' + protocol = 'tcp' + port_range_min = '22' + port_range_max = '22' + source_ip_prefix = '10.0.0.0/24' + security_group_id = '1' + source_group_id = '1' + args = ['--source_ip_prefix', source_ip_prefix, '--direction', + direction, '--ethertype', ethertype, '--protocol', protocol, + '--port_range_min', port_range_min, '--port_range_max', + port_range_max, '--source_group_id', source_group_id, + security_group_id] + position_names = ['source_ip_prefix', 'direction', 'ethertype', + 'protocol', 'port_range_min', 'port_range_max', + 'source_group_id', 'security_group_id'] + position_values = [source_ip_prefix, direction, ethertype, protocol, + port_range_min, port_range_max, source_group_id, + security_group_id] + self._test_create_resource(resource, cmd, None, myid, args, + position_names, position_values) + + def test_delete_security_group_rule(self): + """Delete security group rule: myid.""" + resource = 'security_group_rule' + cmd = securitygroup.DeleteSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) + + def test_list_security_group_rules(self): + resources = "security_group_rules" + cmd = securitygroup.ListSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_show_security_group_rule(self): + resource = 'security_group_rule' + cmd = securitygroup.ShowSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id']) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 6ec752def..3f195527a 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -162,6 +162,10 @@ class Client(object): router_path = "/routers/%s" floatingips_path = "/floatingips" floatingip_path = "/floatingips/%s" + security_groups_path = "/security-groups" + security_group_path = "/security-groups/%s" + security_group_rules_path = "/security-group-rules" + security_group_rule_path = "/security-group-rules/%s" @APIParamsCall def get_quotas_tenant(self, **_params): @@ -410,6 +414,65 @@ def delete_floatingip(self, floatingip): """ return self.delete(self.floatingip_path % (floatingip)) + @APIParamsCall + def create_security_group(self, body=None): + """ + Creates a new security group + """ + return self.post(self.security_groups_path, body=body) + + @APIParamsCall + def list_security_groups(self, **_params): + """ + Fetches a list of all security groups for a tenant + """ + return self.get(self.security_groups_path, params=_params) + + @APIParamsCall + def show_security_group(self, security_group, **_params): + """ + Fetches information of a certain security group + """ + return self.get(self.security_group_path % (security_group), + params=_params) + + @APIParamsCall + def delete_security_group(self, security_group): + """ + Deletes the specified security group + """ + return self.delete(self.security_group_path % (security_group)) + + @APIParamsCall + def create_security_group_rule(self, body=None): + """ + Creates a new security group rule + """ + return self.post(self.security_group_rules_path, body=body) + + @APIParamsCall + def delete_security_group_rule(self, security_group_rule): + """ + Deletes the specified security group rule + """ + return self.delete(self.security_group_rule_path % + (security_group_rule)) + + @APIParamsCall + def list_security_group_rules(self, **_params): + """ + Fetches a list of all security group rules for a tenant + """ + return self.get(self.security_group_rules_path, params=_params) + + @APIParamsCall + def show_security_group_rule(self, security_group_rule, **_params): + """ + Fetches information of a certain security group rule + """ + return self.get(self.security_group_rule_path % (security_group_rule), + params=_params) + def __init__(self, **kwargs): """ Initialize a new client for the Quantum v2.0 API. """ super(Client, self).__init__() From 6857c2ad69595d74fb5de90e1b00e9dabed50896 Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Sat, 3 Nov 2012 21:40:50 +0200 Subject: [PATCH 045/135] Fixes setup compatibility issue on Windows Fixes Bug #1052161 "python setup.py build" fails on Windows due to a hardcoded shell path: /bin/sh setup.py updated using openstack-common/update.py Change-Id: I3943725a1f4b067a06f8f850e993c6867399a967 --- quantumclient/openstack/common/setup.py | 102 +++++++++++++++--------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/quantumclient/openstack/common/setup.py b/quantumclient/openstack/common/setup.py index caf06fa5b..e6f72f034 100644 --- a/quantumclient/openstack/common/setup.py +++ b/quantumclient/openstack/common/setup.py @@ -31,12 +31,13 @@ def parse_mailmap(mailmap='.mailmap'): mapping = {} if os.path.exists(mailmap): - fp = open(mailmap, 'r') - for l in fp: - l = l.strip() - if not l.startswith('#') and ' ' in l: - canonical_email, alias = l.split(' ') - mapping[alias] = canonical_email + with open(mailmap, 'r') as fp: + for l in fp: + l = l.strip() + if not l.startswith('#') and ' ' in l: + canonical_email, alias = [x for x in l.split(' ') + if x.startswith('<')] + mapping[alias] = canonical_email return mapping @@ -51,10 +52,10 @@ def canonicalize_emails(changelog, mapping): # Get requirements from the first file that exists def get_reqs_from_files(requirements_files): - reqs_in = [] for requirements_file in requirements_files: if os.path.exists(requirements_file): - return open(requirements_file, 'r').read().split('\n') + with open(requirements_file, 'r') as fil: + return fil.read().split('\n') return [] @@ -116,8 +117,12 @@ def write_requirements(): def _run_shell_command(cmd): - output = subprocess.Popen(["/bin/sh", "-c", cmd], - stdout=subprocess.PIPE) + if os.name == 'nt': + output = subprocess.Popen(["cmd.exe", "/C", cmd], + stdout=subprocess.PIPE) + else: + output = subprocess.Popen(["/bin/sh", "-c", cmd], + stdout=subprocess.PIPE) out = output.communicate() if len(out) == 0: return None @@ -135,11 +140,19 @@ def _get_git_next_version_suffix(branch_name): _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*") milestone_cmd = "git show meta/openstack/release:%s" % branch_name milestonever = _run_shell_command(milestone_cmd) - if not milestonever: - milestonever = "" + if milestonever: + first_half = "%s~%s" % (milestonever, datestamp) + else: + first_half = datestamp + post_version = _get_git_post_version() - revno = post_version.split(".")[-1] - return "%s~%s.%s%s" % (milestonever, datestamp, revno_prefix, revno) + # post version should look like: + # 0.1.1.4.gcc9e28a + # where the bit after the last . is the short sha, and the bit between + # the last and second to last is the revno count + (revno, sha) = post_version.split(".")[-2:] + second_half = "%s%s.%s" % (revno_prefix, revno, sha) + return ".".join((first_half, second_half)) def _get_git_current_tag(): @@ -161,39 +174,48 @@ def _get_git_post_version(): cmd = "git --no-pager log --oneline" out = _run_shell_command(cmd) revno = len(out.split("\n")) + sha = _run_shell_command("git describe --always") else: tag_infos = tag_info.split("-") base_version = "-".join(tag_infos[:-2]) - revno = tag_infos[-2] - return "%s.%s" % (base_version, revno) + (revno, sha) = tag_infos[-2:] + return "%s.%s.%s" % (base_version, revno, sha) def write_git_changelog(): """Write a changelog based on the git changelog.""" - if os.path.isdir('.git'): - git_log_cmd = 'git log --stat' - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open("ChangeLog", "w") as changelog_file: - changelog_file.write(canonicalize_emails(changelog, mailmap)) + new_changelog = 'ChangeLog' + if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'): + if os.path.isdir('.git'): + git_log_cmd = 'git log --stat' + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_changelog, "w") as changelog_file: + changelog_file.write(canonicalize_emails(changelog, mailmap)) + else: + open(new_changelog, 'w').close() def generate_authors(): """Create AUTHORS file using git commits.""" - jenkins_email = 'jenkins@review.openstack.org' + jenkins_email = 'jenkins@review.(openstack|stackforge).org' old_authors = 'AUTHORS.in' new_authors = 'AUTHORS' - if os.path.isdir('.git'): - # don't include jenkins email address in AUTHORS file - git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " - "grep -v " + jenkins_email) - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open(new_authors, 'w') as new_authors_fh: - new_authors_fh.write(canonicalize_emails(changelog, mailmap)) - if os.path.exists(old_authors): - with open(old_authors, "r") as old_authors_fh: - new_authors_fh.write('\n' + old_authors_fh.read()) + if not os.getenv('SKIP_GENERATE_AUTHORS'): + if os.path.isdir('.git'): + # don't include jenkins email address in AUTHORS file + git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " + "egrep -v '" + jenkins_email + "'") + changelog = _run_shell_command(git_log_cmd) + mailmap = parse_mailmap() + with open(new_authors, 'w') as new_authors_fh: + new_authors_fh.write(canonicalize_emails(changelog, mailmap)) + if os.path.exists(old_authors): + with open(old_authors, "r") as old_authors_fh: + new_authors_fh.write('\n' + old_authors_fh.read()) + else: + open(new_authors, 'w').close() + _rst_template = """%(heading)s %(underline)s @@ -207,7 +229,7 @@ def generate_authors(): def read_versioninfo(project): """Read the versioninfo file. If it doesn't exist, we're in a github - zipball, and there's really know way to know what version we really + zipball, and there's really no way to know what version we really are, but that should be ok, because the utility of that should be just about nil if this code path is in use in the first place.""" versioninfo_path = os.path.join(project, 'versioninfo') @@ -221,7 +243,8 @@ def read_versioninfo(project): def write_versioninfo(project, version): """Write a simple file containing the version of the package.""" - open(os.path.join(project, 'versioninfo'), 'w').write("%s\n" % version) + with open(os.path.join(project, 'versioninfo'), 'w') as fil: + fil.write("%s\n" % version) def get_cmdclass(): @@ -312,7 +335,8 @@ def get_git_branchname(): def get_pre_version(projectname, base_version): - """Return a version which is based""" + """Return a version which is leading up to a version that will + be released in the future.""" if os.path.isdir('.git'): current_tag = _get_git_current_tag() if current_tag is not None: @@ -324,10 +348,10 @@ def get_pre_version(projectname, base_version): version_suffix = _get_git_next_version_suffix(branch_name) version = "%s~%s" % (base_version, version_suffix) write_versioninfo(projectname, version) - return version.split('~')[0] + return version else: version = read_versioninfo(projectname) - return version.split('~')[0] + return version def get_post_version(projectname): From 57f0e918360ce7913ef6390eba1bde99c66aa10f Mon Sep 17 00:00:00 2001 From: gongysh Date: Fri, 9 Nov 2012 19:36:30 +0800 Subject: [PATCH 046/135] Fix curl command for PUT and DELETE. Bug #1076968 We print request curl command before HTTP request, and response information after HTTP request. Change-Id: I19840e8bd606a30389cd029c0add066c945fcdf6 --- quantumclient/client.py | 5 ++--- quantumclient/common/utils.py | 40 +++++++++++++++++++---------------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/quantumclient/client.py b/quantumclient/client.py index 18bb3de2a..39e770e79 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -123,10 +123,9 @@ def _cs_request(self, *args, **kwargs): if 'body' in kwargs: kargs['body'] = kwargs['body'] - + utils.http_log_req(_logger, args, kargs) resp, body = self.request(*args, **kargs) - - utils.http_log(_logger, args, kargs, resp, body) + utils.http_log_resp(_logger, resp, body) status_code = self.get_status_code(resp) if status_code == 401: raise exceptions.Unauthorized(message=body) diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py index bed02e010..45c64a998 100644 --- a/quantumclient/common/utils.py +++ b/quantumclient/common/utils.py @@ -148,23 +148,27 @@ def str2dict(strdict): return _info -def http_log(_logger, args, kwargs, resp, body): - if not _logger.isEnabledFor(logging.DEBUG): - return - - string_parts = ['curl -i'] - for element in args: - if element in ('GET', 'POST'): - string_parts.append(' -X %s' % element) - else: - string_parts.append(' %s' % element) +def http_log_req(_logger, args, kwargs): + if not _logger.isEnabledFor(logging.DEBUG): + return + + string_parts = ['curl -i'] + for element in args: + if element in ('GET', 'POST', 'DELETE', 'PUT'): + string_parts.append(' -X %s' % element) + else: + string_parts.append(' %s' % element) + + for element in kwargs['headers']: + header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) + string_parts.append(header) + + if 'body' in kwargs and kwargs['body']: + string_parts.append(" -d '%s'" % (kwargs['body'])) + _logger.debug("\nREQ: %s\n" % "".join(string_parts)) - for element in kwargs['headers']: - header = ' -H "%s: %s"' % (element, kwargs['headers'][element]) - string_parts.append(header) - _logger.debug("REQ: %s\n" % "".join(string_parts)) - if 'body' in kwargs and kwargs['body']: - _logger.debug("REQ BODY: %s\n" % (kwargs['body'])) - _logger.debug("RESP:%s\n", resp) - _logger.debug("RESP BODY:%s\n", body) +def http_log_resp(_logger, resp, body): + if not _logger.isEnabledFor(logging.DEBUG): + return + _logger.debug("RESP:%s %s\n", resp, body) From eb7a7443a63ebbe00bbfa13daa75a355466eb63e Mon Sep 17 00:00:00 2001 From: ivan-zhu Date: Wed, 10 Oct 2012 17:24:35 +0800 Subject: [PATCH 047/135] Convenience cmds for l3 Bug #1049551 Add two CLI and unit tests: quantum net-external-list (runs net-list with router:external=True filter) quantum router-port-list (runs port-list, filtering with device_id equal to specified router) Change-Id: I9a9668836ac24d4cbc6a3867ec031611b64ded14 --- quantumclient/quantum/v2_0/network.py | 15 ++ quantumclient/quantum/v2_0/port.py | 29 +++- quantumclient/shell.py | 4 + .../tests/unit/test_cli20_network.py | 160 ++++++++++++++++-- quantumclient/tests/unit/test_cli20_port.py | 127 ++++++++++++-- 5 files changed, 302 insertions(+), 33 deletions(-) diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 21efd2d84..070de54c4 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -41,6 +41,21 @@ class ListNetwork(ListCommand): list_columns = ['id', 'name', 'subnets'] +class ListExternalNetwork(ListCommand): + """List external networks that belong to a given tenant""" + + resource = 'network' + log = logging.getLogger(__name__ + '.ListExternalNetwork') + _formatters = {'subnets': _format_subnets, } + list_colums = ['id', 'name', 'subnets'] + + def get_data(self, parsed_args): + if '--' not in parsed_args.filter_specs: + parsed_args.filter_specs.append('--') + parsed_args.filter_specs.append('--router:external=True') + return super(ListExternalNetwork, self).get_data(parsed_args) + + class ShowNetwork(ShowCommand): """Show information of a given network.""" diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 6984f5de5..7f393654a 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -35,7 +35,7 @@ def _format_fixed_ips(port): class ListPort(ListCommand): - """List networks that belong to a given tenant.""" + """List ports that belong to a given tenant.""" resource = 'port' log = logging.getLogger(__name__ + '.ListPort') @@ -43,6 +43,33 @@ class ListPort(ListCommand): list_columns = ['id', 'name', 'mac_address', 'fixed_ips'] +class ListRouterPort(ListCommand): + """List ports that belong to a given tenant, with specified router""" + + resource = 'port' + log = logging.getLogger(__name__ + '.ListRouterPort') + _formatters = {'fixed_ips': _format_fixed_ips, } + list_columns = ['id', 'name', 'mac_address', 'fixed_ips'] + + def get_parser(self, prog_name): + parser = super(ListCommand, self).get_parser(prog_name) + quantumv20.add_show_list_common_argument(parser) + parser.add_argument( + 'id', metavar='router', + help='ID or name of router to look up') + quantumv20.add_extra_argument(parser, 'filter_specs', + 'filters options') + return parser + + def get_data(self, parsed_args): + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + _id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, 'router', parsed_args.id) + parsed_args.filter_specs.append('--device_id=%s' % _id) + return super(ListRouterPort, self).get_data(parsed_args) + + class ShowPort(ShowCommand): """Show information of a given port.""" diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 5f5eccdc8..b7acc596e 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -54,6 +54,8 @@ def env(*_vars, **kwargs): COMMAND_V2 = { 'net-list': utils.import_class( 'quantumclient.quantum.v2_0.network.ListNetwork'), + 'net-external-list': utils.import_class( + 'quantumclient.quantum.v2_0.network.ListExternalNetwork'), 'net-show': utils.import_class( 'quantumclient.quantum.v2_0.network.ShowNetwork'), 'net-create': utils.import_class( @@ -96,6 +98,8 @@ def env(*_vars, **kwargs): 'quantumclient.quantum.v2_0.extension.ShowExt'), 'router-list': utils.import_class( 'quantumclient.quantum.v2_0.router.ListRouter'), + 'router-port-list': utils.import_class( + 'quantumclient.quantum.v2_0.port.ListRouterPort'), 'router-show': utils.import_class( 'quantumclient.quantum.v2_0.router.ShowRouter'), 'router-create': utils.import_class( diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index 729e98274..5360c08cc 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -16,6 +16,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 import sys +from mox import ContainsKeyValue from quantumclient.common import exceptions from quantumclient.common import utils @@ -27,6 +28,7 @@ from quantumclient.quantum.v2_0.network import UpdateNetwork from quantumclient.quantum.v2_0.network import ShowNetwork from quantumclient.quantum.v2_0.network import DeleteNetwork +from quantumclient.quantum.v2_0.network import ListExternalNetwork class CLITestV20Network(CLITestV20Base): @@ -39,8 +41,8 @@ def test_create_network(self): args = [name, ] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) def test_create_network_tenant(self): """Create net: --tenant_id tenantid myname.""" @@ -51,15 +53,15 @@ def test_create_network_tenant(self): args = ['--tenant_id', 'tenantid', name] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') # Test dashed options args = ['--tenant-id', 'tenantid', name] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_network_tags(self): """Create net: myname --tags a b.""" @@ -70,9 +72,9 @@ def test_create_network_tags(self): args = [name, '--tags', 'a', 'b'] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tags=['a', 'b']) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tags=['a', 'b']) def test_create_network_state(self): """Create net: --admin_state_down myname.""" @@ -83,15 +85,15 @@ def test_create_network_state(self): args = ['--admin_state_down', name, ] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - admin_state_up=False) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + admin_state_up=False) # Test dashed options args = ['--admin-state-down', name, ] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - admin_state_up=False) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + admin_state_up=False) def test_list_nets_empty_with_column(self): resources = "networks" @@ -179,6 +181,130 @@ def test_list_nets_with_default_column(self): self.assertEquals(0, len(set(network) ^ set(cmd.list_columns))) + def test_list_external_nets_empty_with_column(self): + resources = "networks" + cmd = ListExternalNetwork(MyApp(sys.stdout), None) + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + reses = {resources: []} + resstr = self.client.serialize(reses) + # url method body + query = "router%3Aexternal=True&id=myfakeid" + args = ['-c', 'id', '--', '--id', 'myfakeid'] + path = getattr(self.client, resources + "_path") + self.client.httpclient.request( + test_cli20.end_url(path, query), 'GET', + body=None, + headers=test_cli20.ContainsKeyValue( + 'X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("list_" + resources) + + parsed_args = cmd_parser.parse_args(args) + + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertEquals('\n', _str) + + def _test_list_external_nets(self, resources, cmd, + detail=False, tags=[], + fields_1=[], fields_2=[]): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + reses = {resources: [{'id': 'myid1', }, + {'id': 'myid2', }, ], } + + resstr = self.client.serialize(reses) + + # url method body + query = "" + args = detail and ['-D', ] or [] + if fields_1: + for field in fields_1: + args.append('--fields') + args.append(field) + if tags: + args.append('--') + args.append("--tag") + for tag in tags: + args.append(tag) + if (not tags) and fields_2: + args.append('--') + if fields_2: + args.append("--fields") + for field in fields_2: + args.append(field) + fields_1.extend(fields_2) + for field in fields_1: + if query: + query += "&fields=" + field + else: + query = "fields=" + field + if query: + query += '&router%3Aexternal=True' + else: + query += 'router%3Aexternal=True' + for tag in tags: + if query: + query += "&tag=" + tag + else: + query = "tag=" + tag + if detail: + query = query and query + '&verbose=True' or 'verbose=True' + path = getattr(self.client, resources + "_path") + + self.client.httpclient.request( + test_cli20.end_url(path, query), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("list_" + resources) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + + self.assertTrue('myid1' in _str) + + def test_list_external_nets_detail(self): + """list external nets: -D.""" + resources = "networks" + cmd = ListExternalNetwork(MyApp(sys.stdout), None) + self._test_list_external_nets(resources, cmd, True) + + def test_list_external_nets_tags(self): + """List external nets: -- --tags a b.""" + resources = "networks" + cmd = ListExternalNetwork(MyApp(sys.stdout), None) + self._test_list_external_nets(resources, + cmd, tags=['a', 'b']) + + def test_list_external_nets_detail_tags(self): + """List external nets: -D -- --tags a b.""" + resources = "networks" + cmd = ListExternalNetwork(MyApp(sys.stdout), None) + self._test_list_external_nets(resources, cmd, + detail=True, tags=['a', 'b']) + + def test_list_externel_nets_fields(self): + """List external nets: --fields a --fields b -- --fields c d.""" + resources = "networks" + cmd = ListExternalNetwork(MyApp(sys.stdout), None) + self._test_list_external_nets(resources, cmd, + fields_1=['a', 'b'], + fields_2=['c', 'd']) + def test_update_network_exception(self): """Update net: myid.""" resource = 'network' diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index c57aa899d..6ddbf319c 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -16,14 +16,17 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 import sys +from mox import ContainsKeyValue from quantumclient.tests.unit.test_cli20 import CLITestV20Base from quantumclient.tests.unit.test_cli20 import MyApp +from quantumclient.tests.unit import test_cli20 from quantumclient.quantum.v2_0.port import CreatePort from quantumclient.quantum.v2_0.port import ListPort from quantumclient.quantum.v2_0.port import UpdatePort from quantumclient.quantum.v2_0.port import ShowPort from quantumclient.quantum.v2_0.port import DeletePort +from quantumclient.quantum.v2_0.port import ListRouterPort class CLITestV20Port(CLITestV20Base): @@ -39,8 +42,8 @@ def test_create_port(self): position_names = ['network_id'] position_values = [] position_values.extend([netid]) - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) def test_create_port_full(self): """Create port: --mac_address mac --device_id deviceid netid.""" @@ -52,14 +55,14 @@ def test_create_port_full(self): args = ['--mac_address', 'mac', '--device_id', 'deviceid', netid] position_names = ['network_id', 'mac_address', 'device_id'] position_values = [netid, 'mac', 'deviceid'] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) # Test dashed options args = ['--mac-address', 'mac', '--device-id', 'deviceid', netid] position_names = ['network_id', 'mac_address', 'device_id'] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) def test_create_port_tenant(self): """Create port: --tenant_id tenantid netid.""" @@ -72,15 +75,15 @@ def test_create_port_tenant(self): position_names = ['network_id'] position_values = [] position_values.extend([netid]) - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') # Test dashed options args = ['--tenant-id', 'tenantid', netid, ] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_port_tags(self): """Create port: netid mac_address device_id --tags a b.""" @@ -93,9 +96,9 @@ def test_create_port_tags(self): position_names = ['network_id'] position_values = [] position_values.extend([netid]) - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tags=['a', 'b']) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tags=['a', 'b']) def test_list_ports(self): """List ports: -D.""" @@ -122,6 +125,100 @@ def test_list_ports_fields(self): self._test_list_resources(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) + def _test_list_router_port(self, resources, cmd, + myid, detail=False, tags=[], + fields_1=[], fields_2=[]): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + reses = {resources: [{'id': 'myid1', }, + {'id': 'myid2', }, ], } + + resstr = self.client.serialize(reses) + + # url method body + query = "" + args = detail and ['-D', ] or [] + + if fields_1: + for field in fields_1: + args.append('--fields') + args.append(field) + args.append(myid) + if tags: + args.append('--') + args.append("--tag") + for tag in tags: + args.append(tag) + if (not tags) and fields_2: + args.append('--') + if fields_2: + args.append("--fields") + for field in fields_2: + args.append(field) + fields_1.extend(fields_2) + for field in fields_1: + if query: + query += "&fields=" + field + else: + query = "fields=" + field + + for tag in tags: + if query: + query += "&tag=" + tag + else: + query = "tag=" + tag + if detail: + query = query and query + '&verbose=True' or 'verbose=True' + query = query and query + '&device_id=%s' or 'device_id=%s' + path = getattr(self.client, resources + "_path") + self.client.httpclient.request( + test_cli20.end_url(path, query % myid), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn( + (test_cli20.MyResp(200), resstr)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("list_" + resources) + + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + + self.assertTrue('myid1' in _str) + + def test_list_router_ports(self): + """List router ports: -D.""" + resources = "ports" + cmd = ListRouterPort(MyApp(sys.stdout), None) + self._test_list_router_port(resources, cmd, + self.test_id, True) + + def test_list_router_ports_tags(self): + """List router ports: -- --tags a b.""" + resources = "ports" + cmd = ListRouterPort(MyApp(sys.stdout), None) + self._test_list_router_port(resources, cmd, + self.test_id, tags=['a', 'b']) + + def test_list_router_ports_detail_tags(self): + """List router ports: -D -- --tags a b.""" + resources = "ports" + cmd = ListRouterPort(MyApp(sys.stdout), None) + self._test_list_router_port(resources, cmd, self.test_id, + detail=True, tags=['a', 'b']) + + def test_list_router_ports_fields(self): + """List ports: --fields a --fields b -- --fields c d.""" + resources = "ports" + cmd = ListRouterPort(MyApp(sys.stdout), None) + self._test_list_router_port(resources, cmd, self.test_id, + fields_1=['a', 'b'], + fields_2=['c', 'd']) + def test_update_port(self): """Update port: myid --name myname --tags a b.""" resource = 'port' From f6dcfd855bd6928c37fb511bb22a288e910fba82 Mon Sep 17 00:00:00 2001 From: Kyle Mestery Date: Thu, 1 Nov 2012 15:53:14 +0000 Subject: [PATCH 048/135] Correct the verbose output formatting when creating routers. fixes bug 1070460 Change-Id: Id9157e7226f906ec608aa95766eeed1e62b5cc50 --- quantumclient/quantum/v2_0/__init__.py | 48 ++++++++++---------------- quantumclient/quantum/v2_0/router.py | 1 + 2 files changed, 19 insertions(+), 30 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index c719d15ba..b067d32d4 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -200,6 +200,20 @@ def get_parser(self, prog_name): return parser + def format_output_data(self, data): + # Modify data to make it more readable + if self.resource in data: + for k, v in data[self.resource].iteritems(): + if isinstance(v, list): + value = '\n'.join(utils.dumps(i) if isinstance(i, dict) + else str(i) for i in v) + data[self.resource][k] = value + elif isinstance(v, dict): + value = utils.dumps(v) + data[self.resource][k] = value + elif v is None: + data[self.resource][k] = '' + class CreateCommand(QuantumCommand, show.ShowOne): """Create a resource for a given tenant @@ -239,25 +253,13 @@ def get_data(self, parsed_args): obj_creator = getattr(quantum_client, "create_%s" % self.resource) data = obj_creator(body) + self.format_output_data(data) # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} info = self.resource in data and data[self.resource] or None if info: print >>self.app.stdout, _('Created a new %s:' % self.resource) else: info = {'': ''} - for k, v in info.iteritems(): - if isinstance(v, list): - value = "" - for _item in v: - if value: - value += "\n" - if isinstance(_item, dict): - value += utils.dumps(_item) - else: - value += str(_item) - info[k] = value - elif v is None: - info[k] = '' return zip(*sorted(info.iteritems())) @@ -435,23 +437,9 @@ def get_data(self, parsed_args): obj_shower = getattr(quantum_client, "show_%s" % self.resource) data = obj_shower(_id, **params) + self.format_output_data(data) + resource = data[self.resource] if self.resource in data: - for k, v in data[self.resource].iteritems(): - if isinstance(v, list): - value = "" - for _item in v: - if value: - value += "\n" - if isinstance(_item, dict): - value += utils.dumps(_item) - else: - value += str(_item) - data[self.resource][k] = value - elif isinstance(v, dict): - value = utils.dumps(v) - data[self.resource][k] = value - elif v is None: - data[self.resource][k] = '' - return zip(*sorted(data[self.resource].iteritems())) + return zip(*sorted(resource.iteritems())) else: return None diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index 9d6787d6a..db85b8057 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -56,6 +56,7 @@ class CreateRouter(CreateCommand): resource = 'router' log = logging.getLogger(__name__ + '.CreateRouter') + _formatters = {'external_gateway_info': _format_external_gateway_info, } def add_known_arguments(self, parser): parser.add_argument( From 45c471899ba30b7db970b27976f749a16a2fdb92 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Mon, 3 Dec 2012 20:10:38 +0900 Subject: [PATCH 049/135] Add --router and --floatingip to quota-update options. Fixes bug 1085820 Change-Id: I333b21414ec159cbf8474d47bd8ba3c6bd3cf8ad --- quantumclient/quantum/v2_0/quota.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/quantumclient/quantum/v2_0/quota.py b/quantumclient/quantum/v2_0/quota.py index 2a6979c33..0e749491e 100644 --- a/quantumclient/quantum/v2_0/quota.py +++ b/quantumclient/quantum/v2_0/quota.py @@ -164,6 +164,12 @@ def get_parser(self, prog_name): parser.add_argument( '--port', metavar='ports', help='the limit of port quota') + parser.add_argument( + '--router', metavar='routers', + help='the limit of router quota') + parser.add_argument( + '--floatingip', metavar='floatingips', + help='the limit of floating IP quota') quantumv20.add_extra_argument( parser, 'value_specs', 'new values for the %s' % self.resource) @@ -183,7 +189,7 @@ def get_data(self, parsed_args): quantum_client = self.get_client() quantum_client.format = parsed_args.request_format quota = {} - for resource in ('network', 'subnet', 'port'): + for resource in ('network', 'subnet', 'port', 'router', 'floatingip'): if getattr(parsed_args, resource): quota[resource] = self._validate_int( resource, From b8ef8ac2ee537b3e861040e2720d6086fa006637 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Mon, 3 Dec 2012 13:42:00 +0900 Subject: [PATCH 050/135] Display columns in the order of -c options Fixes bug 1085785 Change-Id: Ifd8ddfbb7fa5e32be96febe981a6baf57b5e58c7 --- quantumclient/quantum/v2_0/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index b067d32d4..c73795b1c 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -387,7 +387,9 @@ def get_data(self, parsed_args): if not _columns: # clean the parsed_args.columns so that cliff will not break parsed_args.columns = [] - elif not parsed_args.columns and self.list_columns: + elif parsed_args.columns: + _columns = [x for x in parsed_args.columns if x in _columns] + elif self.list_columns: # if no -c(s) by user and list_columns, we use columns in # both list_columns and returned resource. # Also Keep their order the same as in list_columns From d48d35aa9bb1ef38714d7b8eb7859fc59a5df0ec Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Wed, 5 Dec 2012 12:17:00 +0000 Subject: [PATCH 051/135] Ensures that help alignment is not hard coded Fixes bug 1086770 Change-Id: I6fa3edea83783e274d78e0c0195bca69d63b6e04 --- quantumclient/shell.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 6e0ebc118..88504ebd8 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -157,6 +157,8 @@ class HelpAction(argparse.Action): instance, passed in as the "default" value for the action. """ def __call__(self, parser, namespace, values, option_string=None): + outputs = [] + max_len = 0 app = self.default parser.print_help(app.stdout) app.stdout.write('\nCommands for API v%s:\n' % app.api_version) @@ -165,7 +167,10 @@ def __call__(self, parser, namespace, values, option_string=None): factory = ep.load() cmd = factory(self, None) one_liner = cmd.get_description().split('\n')[0] - app.stdout.write(' %-25s %s\n' % (name, one_liner)) + outputs.append((name, one_liner)) + max_len = max(len(name), max_len) + for (name, one_liner) in outputs: + app.stdout.write(' %s %s\n' % (name.ljust(max_len), one_liner)) sys.exit(0) From 2ba519d142abcf2e3907b031e569618150992ca1 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Sat, 8 Dec 2012 03:28:45 +0900 Subject: [PATCH 052/135] Fix a wrong substition for '-h' in bash completion Fixes bug #1087806 Change-Id: Id39cac821922ca6adea296335f2395fabb011754 --- tools/quantum.bash_completion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/quantum.bash_completion b/tools/quantum.bash_completion index 45db025af..311f53309 100644 --- a/tools/quantum.bash_completion +++ b/tools/quantum.bash_completion @@ -9,7 +9,7 @@ _quantum() prev="${COMP_WORDS[COMP_CWORD-1]}" if [ "x$_quantum_opts" == "x" ] ; then - nbc="`quantum bash-completion | sed -e "s/\s-h\s/\s/"`" + nbc="`quantum bash-completion`" _quantum_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" _quantum_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" _quantum_opts_exp="`echo "$_quantum_opts" | sed -e "s/\s/|/g"`" From 4f337d9bb7e0e3112a425d6cdfe87fb728c87cff Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Mon, 3 Dec 2012 20:41:55 +0900 Subject: [PATCH 053/135] Add --dns-nameserver, --host-route, --disable-dhcp to subnet-create Fixes bug 1042982 Change-Id: I71f7e54a0982ac5fd188986d902667bd3fd6b219 --- quantumclient/quantum/v2_0/subnet.py | 38 +++++--- quantumclient/tests/unit/test_cli20_subnet.py | 88 +++++++++++++++++++ 2 files changed, 114 insertions(+), 12 deletions(-) diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 0e67cb948..390f7bf82 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -98,15 +98,27 @@ def add_known_arguments(self, parser): default=False, action='store_true', help='No distribution of gateway') parser.add_argument( - '--allocation-pool', - action='append', - help='Allocation pool IP addresses for this subnet: ' - 'start=,end= ' - 'can be repeated') + '--allocation-pool', metavar='start=IP_ADDR,end=IP_ADDR', + action='append', dest='allocation_pools', type=utils.str2dict, + help='Allocation pool IP addresses for this subnet ' + '(This option can be repeated)') parser.add_argument( '--allocation_pool', - action='append', + action='append', dest='allocation_pools', type=utils.str2dict, help=argparse.SUPPRESS) + parser.add_argument( + '--host-route', metavar='destination=CIDR,nexthop=IP_ADDR', + action='append', dest='host_routes', type=utils.str2dict, + help='Additional route (This option can be repeated)') + parser.add_argument( + '--dns-nameserver', metavar='DNS_NAMESERVER', + action='append', dest='dns_nameservers', + help='DNS name server for this subnet ' + '(This option can be repeated)') + parser.add_argument( + '--disable-dhcp', + action='store_true', + help='Disable DHCP for this subnet') parser.add_argument( 'network_id', metavar='network', help='Network id or name this subnet belongs to') @@ -133,12 +145,14 @@ def args2body(self, parsed_args): body['subnet'].update({'tenant_id': parsed_args.tenant_id}) if parsed_args.name: body['subnet'].update({'name': parsed_args.name}) - ips = [] - if parsed_args.allocation_pool: - for ip_spec in parsed_args.allocation_pool: - ips.append(utils.str2dict(ip_spec)) - if ips: - body['subnet'].update({'allocation_pools': ips}) + if parsed_args.disable_dhcp: + body['subnet'].update({'enable_dhcp': False}) + if parsed_args.allocation_pools: + body['subnet']['allocation_pools'] = parsed_args.allocation_pools + if parsed_args.host_routes: + body['subnet']['host_routes'] = parsed_args.host_routes + if parsed_args.dns_nameservers: + body['subnet']['dns_nameservers'] = parsed_args.dns_nameservers return body diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index 77bf0674e..6bcabe865 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -151,6 +151,94 @@ def test_create_subnet_allocation_pools(self): position_names, position_values, tenant_id='tenantid') + def test_create_subnet_host_route(self): + """Create subnet: --tenant_id tenantid netid cidr. + The is + --host-route destination=172.16.1.0/24,nexthop=1.1.1.20 + """ + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + '--host-route', 'destination=172.16.1.0/24,nexthop=1.1.1.20', + netid, cidr] + position_names = ['ip_version', 'host_routes', 'network_id', + 'cidr'] + route = [{'destination': '172.16.1.0/24', 'nexthop': '1.1.1.20'}] + position_values = [4, route, netid, cidr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_subnet_host_routes(self): + """Create subnet: --tenant-id tenantid netid cidr. + The are + --host-route destination=172.16.1.0/24,nexthop=1.1.1.20 and + --host-route destination=172.17.7.0/24,nexthop=1.1.1.40 + """ + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + '--host-route', 'destination=172.16.1.0/24,nexthop=1.1.1.20', + '--host-route', 'destination=172.17.7.0/24,nexthop=1.1.1.40', + netid, cidr] + position_names = ['ip_version', 'host_routes', 'network_id', + 'cidr'] + routes = [{'destination': '172.16.1.0/24', 'nexthop': '1.1.1.20'}, + {'destination': '172.17.7.0/24', 'nexthop': '1.1.1.40'}] + position_values = [4, routes, netid, cidr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_subnet_dns_nameservers(self): + """Create subnet: --tenant-id tenantid netid cidr. + The are + --dns-nameserver 1.1.1.20 and --dns-nameserver 1.1.1.40 + """ + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + '--dns-nameserver', '1.1.1.20', + '--dns-nameserver', '1.1.1.40', + netid, cidr] + position_names = ['ip_version', 'dns_nameservers', 'network_id', + 'cidr'] + nameservers = ['1.1.1.20', '1.1.1.40'] + position_values = [4, nameservers, netid, cidr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_subnet_with_disable_dhcp(self): + """Create subnet: --tenant-id tenantid --disable-dhcp netid cidr.""" + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + '--disable-dhcp', + netid, cidr] + position_names = ['ip_version', 'enable_dhcp', 'network_id', + 'cidr'] + position_values = [4, False, netid, cidr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + def test_list_subnets_detail(self): """List subnets: -D.""" resources = "subnets" From 5b239e03c4323b447b539564e06db31978f10d36 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Fri, 14 Dec 2012 15:07:10 +0900 Subject: [PATCH 054/135] Support dash-style options for security-group commands. Fixes bug 1090233 underscore-stype options are still supported for backward compatibility. Also add secgroup name support for --source-group-id. Change-Id: I5947ae510e074db612dae1431487fe9ca195a467 --- quantumclient/quantum/v2_0/securitygroup.py | 28 ++++++++++++++++----- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py index b56d84d89..ae78a0168 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -15,6 +15,7 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 +import argparse import logging from quantumclient.quantum import v2_0 as quantumv20 @@ -100,7 +101,7 @@ def add_known_arguments(self, parser): help='Security group to add rule.') parser.add_argument( '--direction', - default='ingress', + default='ingress', choices=['ingress', 'egress'], help='direction of traffic: ingress/egress') parser.add_argument( '--ethertype', @@ -110,17 +111,29 @@ def add_known_arguments(self, parser): '--protocol', help='protocol of packet') parser.add_argument( - '--port_range_min', + '--port-range-min', help='starting port range') parser.add_argument( - '--port_range_max', + '--port_range_min', + help=argparse.SUPPRESS) + parser.add_argument( + '--port-range-max', help='ending port range') parser.add_argument( - '--source_ip_prefix', + '--port_range_max', + help=argparse.SUPPRESS) + parser.add_argument( + '--source-ip-prefix', help='cidr to match on') parser.add_argument( - '--source_group_id', + '--source_ip_prefix', + help=argparse.SUPPRESS) + parser.add_argument( + '--source-group-id', help='source security group to apply rule') + parser.add_argument( + '--source_group_id', + help=argparse.SUPPRESS) def args2body(self, parsed_args): _security_group_id = quantumv20.find_resourceid_by_name_or_id( @@ -142,8 +155,11 @@ def args2body(self, parsed_args): body['security_group_rule'].update( {'source_ip_prefix': parsed_args.source_ip_prefix}) if parsed_args.source_group_id: + _source_group_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'security_group', + parsed_args.source_group_id) body['security_group_rule'].update( - {'source_group_id': parsed_args.source_group_id}) + {'source_group_id': _source_group_id}) if parsed_args.tenant_id: body['security_group_rule'].update( {'tenant_id': parsed_args.tenant_id}) From 5d0ee641f2bb4116e88f4f3788c1aa5b7bc4aacb Mon Sep 17 00:00:00 2001 From: Ben Andrews Date: Sun, 16 Dec 2012 20:39:18 -0500 Subject: [PATCH 055/135] bug 1091028 sets version that pip can use for pyparser to one that is for python 2.X. 2.0.0 is only for python 3 Change-Id: I8e23e576032290a71ba929af5f5c729ae3dffca6 Fixes: bug #1091028 --- tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index 6183e4e1b..2deb910ce 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -3,4 +3,4 @@ argparse httplib2 prettytable>=0.6.0 simplejson -pyparsing +pyparsing>=1.5.6,<2.0 From cd9899450521795ea7b0431da5c719b707cd0f61 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Fri, 14 Dec 2012 15:00:21 +0900 Subject: [PATCH 056/135] Add --security-group option to port-create [Usage] quantum port-create --security-group sg1 --security-group sg2 net1 A security group can be specified by name. Part of blueprint quantum-client-security-groups Change-Id: I82126f4839c4c0b265912ac6f5e111f5fbd2f72b --- quantumclient/quantum/v2_0/port.py | 13 +++++++++ quantumclient/tests/unit/test_cli20_port.py | 32 +++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 7f393654a..5a3b43472 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -117,6 +117,11 @@ def add_known_arguments(self, parser): '--fixed_ip', action='append', help=argparse.SUPPRESS) + parser.add_argument( + '--security-group', metavar='SECURITY_GROUP', + default=[], action='append', dest='security_groups', + help='security group associated with the port ' + '(This option can be repeated)') parser.add_argument( 'network_id', metavar='network', help='Network id or name this port belongs to') @@ -146,6 +151,14 @@ def args2body(self, parsed_args): ips.append(ip_dict) if ips: body['port'].update({'fixed_ips': ips}) + + _sgids = [] + for sg in parsed_args.security_groups: + _sgids.append(quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'security_group', sg)) + if _sgids: + body['port']['security_groups'] = _sgids + return body diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index 6ddbf319c..62645113b 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -100,6 +100,38 @@ def test_create_port_tags(self): position_names, position_values, tags=['a', 'b']) + def test_create_port_secgroup(self): + """Create port: --security-group sg1_id netid""" + resource = 'port' + cmd = CreatePort(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + args = ['--security-group', 'sg1_id', netid] + position_names = ['network_id', 'security_groups'] + position_values = [netid, ['sg1_id']] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_port_secgroups(self): + """Create port: netid + + The are + --security-group sg1_id --security-group sg2_id + """ + resource = 'port' + cmd = CreatePort(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + args = ['--security-group', 'sg1_id', + '--security-group', 'sg2_id', + netid] + position_names = ['network_id', 'security_groups'] + position_values = [netid, ['sg1_id', 'sg2_id']] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + def test_list_ports(self): """List ports: -D.""" resources = "ports" From 76066de98abdfad55dd685941dbe9683f7abbbc3 Mon Sep 17 00:00:00 2001 From: Sascha Peilicke Date: Wed, 19 Dec 2012 15:10:17 +0100 Subject: [PATCH 057/135] Add file 'ChangeLog' to MANIFEST.in The file is missing from tarballs released at tarballs.openstack.org. Change-Id: Id782305495033948b05ee66da3d0ee2ff1933c20 --- .gitignore | 1 + MANIFEST.in | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 15eae7e22..1a5761972 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ run_tests.log doc/build/ doc/source/api/ quantumclient/versioninfo +ChangeLog diff --git a/MANIFEST.in b/MANIFEST.in index 62ce0fa62..41d72419d 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include tox.ini include LICENSE README HACKING.rst +include ChangeLog include tools/* include quantumclient/tests/* include quantumclient/tests/unit/* From 093bac1bc6749326857241ca226a4219917891e7 Mon Sep 17 00:00:00 2001 From: Zhongyue Luo Date: Mon, 31 Dec 2012 22:25:00 +0800 Subject: [PATCH 058/135] Fix import order nits Change-Id: I7c95fa2db1d719f6ed34468ad12b7a9e4c9e794d --- quantumclient/common/clientmanager.py | 5 +++-- quantumclient/quantum/v2_0/network.py | 2 +- quantumclient/shell.py | 1 + quantumclient/tests/unit/test_auth.py | 10 +++++----- quantumclient/tests/unit/test_cli20.py | 2 +- quantumclient/tests/unit/test_cli20_network.py | 13 +++++++------ quantumclient/tests/unit/test_cli20_port.py | 13 +++++++------ quantumclient/tests/unit/test_cli20_subnet.py | 8 ++++---- quantumclient/v2_0/client.py | 15 +-------------- 9 files changed, 30 insertions(+), 39 deletions(-) diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py index 1c1f1acd0..7346aa541 100644 --- a/quantumclient/common/clientmanager.py +++ b/quantumclient/common/clientmanager.py @@ -20,10 +20,11 @@ import logging +from quantumclient.client import HTTPClient from quantumclient.common import exceptions as exc - from quantumclient.quantum import client as quantum_client -from quantumclient.client import HTTPClient + + LOG = logging.getLogger(__name__) diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 070de54c4..19b9978f2 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -21,8 +21,8 @@ from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand -from quantumclient.quantum.v2_0 import UpdateCommand from quantumclient.quantum.v2_0 import ShowCommand +from quantumclient.quantum.v2_0 import UpdateCommand def _format_subnets(network): diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 88504ebd8..038ce20f2 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -18,6 +18,7 @@ """ Command-line interface to the Quantum APIs """ + import argparse import gettext import logging diff --git a/quantumclient/tests/unit/test_auth.py b/quantumclient/tests/unit/test_auth.py index 9f270b5b6..477ba904f 100644 --- a/quantumclient/tests/unit/test_auth.py +++ b/quantumclient/tests/unit/test_auth.py @@ -15,16 +15,16 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 -import mox -from mox import ContainsKeyValue, IsA, StrContains -import unittest - import httplib2 import json +import unittest import uuid -from quantumclient.common import exceptions +import mox +from mox import ContainsKeyValue, IsA, StrContains + from quantumclient.client import HTTPClient +from quantumclient.common import exceptions USERNAME = 'testuser' diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 788460046..520653e9b 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -19,8 +19,8 @@ import unittest import mox -from mox import ContainsKeyValue from mox import Comparator +from mox import ContainsKeyValue from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.v2_0.client import Client diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index 5360c08cc..1ef02150a 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -16,19 +16,20 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 import sys + from mox import ContainsKeyValue from quantumclient.common import exceptions from quantumclient.common import utils -from quantumclient.tests.unit import test_cli20 -from quantumclient.tests.unit.test_cli20 import CLITestV20Base -from quantumclient.tests.unit.test_cli20 import MyApp from quantumclient.quantum.v2_0.network import CreateNetwork -from quantumclient.quantum.v2_0.network import ListNetwork -from quantumclient.quantum.v2_0.network import UpdateNetwork -from quantumclient.quantum.v2_0.network import ShowNetwork from quantumclient.quantum.v2_0.network import DeleteNetwork from quantumclient.quantum.v2_0.network import ListExternalNetwork +from quantumclient.quantum.v2_0.network import ListNetwork +from quantumclient.quantum.v2_0.network import ShowNetwork +from quantumclient.quantum.v2_0.network import UpdateNetwork +from quantumclient.tests.unit import test_cli20 +from quantumclient.tests.unit.test_cli20 import CLITestV20Base +from quantumclient.tests.unit.test_cli20 import MyApp class CLITestV20Network(CLITestV20Base): diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index 62645113b..5580dde3f 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -16,17 +16,18 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 import sys + from mox import ContainsKeyValue -from quantumclient.tests.unit.test_cli20 import CLITestV20Base -from quantumclient.tests.unit.test_cli20 import MyApp -from quantumclient.tests.unit import test_cli20 from quantumclient.quantum.v2_0.port import CreatePort -from quantumclient.quantum.v2_0.port import ListPort -from quantumclient.quantum.v2_0.port import UpdatePort -from quantumclient.quantum.v2_0.port import ShowPort from quantumclient.quantum.v2_0.port import DeletePort +from quantumclient.quantum.v2_0.port import ListPort from quantumclient.quantum.v2_0.port import ListRouterPort +from quantumclient.quantum.v2_0.port import ShowPort +from quantumclient.quantum.v2_0.port import UpdatePort +from quantumclient.tests.unit import test_cli20 +from quantumclient.tests.unit.test_cli20 import CLITestV20Base +from quantumclient.tests.unit.test_cli20 import MyApp class CLITestV20Port(CLITestV20Base): diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index 6bcabe865..e07f6c712 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -17,13 +17,13 @@ import sys -from quantumclient.tests.unit.test_cli20 import CLITestV20Base -from quantumclient.tests.unit.test_cli20 import MyApp from quantumclient.quantum.v2_0.subnet import CreateSubnet +from quantumclient.quantum.v2_0.subnet import DeleteSubnet from quantumclient.quantum.v2_0.subnet import ListSubnet -from quantumclient.quantum.v2_0.subnet import UpdateSubnet from quantumclient.quantum.v2_0.subnet import ShowSubnet -from quantumclient.quantum.v2_0.subnet import DeleteSubnet +from quantumclient.quantum.v2_0.subnet import UpdateSubnet +from quantumclient.tests.unit.test_cli20 import CLITestV20Base +from quantumclient.tests.unit.test_cli20 import MyApp class CLITestV20Subnet(CLITestV20Base): diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 3f195527a..c7937fa16 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -24,6 +24,7 @@ from quantumclient.common import exceptions from quantumclient.common.serializer import Serializer + _logger = logging.getLogger(__name__) @@ -595,17 +596,3 @@ def post(self, action, body=None, headers=None, params=None): def put(self, action, body=None, headers=None, params=None): return self.retry_request("PUT", action, body=body, headers=headers, params=params) - -#if __name__ == '__main__': -# -# client20 = Client(username='admin', -# password='password', -# auth_url='http://localhost:5000/v2.0', -# tenant_name='admin') -# client20 = Client(token='ec796583fcad4aa690b723bc0b25270e', -# endpoint_url='http://localhost:9696') -# -# client20.tenant = 'default' -# client20.format = 'json' -# nets = client20.list_networks() -# print nets From a9d5479ecfca3bac939a62b1037adc1497b622be Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Mon, 31 Dec 2012 02:47:53 +0900 Subject: [PATCH 059/135] Make "quantum help" to show a list of subcommands. Fix bug 1023260 All other OpenStack clients (nova, keystone, glance, ....) accepts just "help" which displays a list of subcommands. This commit makes quantum command consist with other OpenStack projects. After this change help behavior of quantum command becomes as follows: Show general help message: quantum --help quantum help Show help message of subcommand quantum help quantum --help Change-Id: I34ca0df809da04f9b9b9275c697e6aafca312cfe --- quantumclient/shell.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 038ce20f2..ce742137f 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -332,6 +332,7 @@ def run(self, argv): index = 0 command_pos = -1 help_pos = -1 + help_command_pos = -1 for arg in argv: if arg == 'bash-completion': self._bash_completion() @@ -342,9 +343,14 @@ def run(self, argv): elif arg in ('-h', '--help'): if help_pos == -1: help_pos = index + elif arg == 'help': + if help_command_pos == -1: + help_command_pos = index index = index + 1 if command_pos > -1 and help_pos > command_pos: argv = ['help', argv[command_pos]] + if help_command_pos > -1 and command_pos == -1: + argv[help_command_pos] = '--help' self.options, remainder = self.parser.parse_known_args(argv) self.configure_logging() self.interactive_mode = not remainder From ab5399e64daf742a5b857d7532dc554a6102c7c7 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Thu, 10 Jan 2013 03:22:14 +0900 Subject: [PATCH 060/135] Display subnet cidr information in net-list Fixes bug 1074415 This commit introduces extend_list() in ListCommand class. This method can be used to update a retrieved list (e.g., add additional information or convert some field to more human-readable value). Change-Id: Icf5ab616ab4d9add16c921e1944ba37b376b2ab2 --- quantumclient/quantum/v2_0/__init__.py | 30 ++++-- quantumclient/quantum/v2_0/network.py | 24 +++-- .../tests/unit/test_cli20_network.py | 101 +++++++++++++++--- 3 files changed, 127 insertions(+), 28 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index c73795b1c..d4becd72e 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -341,7 +341,7 @@ def run(self, parsed_args): class ListCommand(QuantumCommand, lister.Lister): - """List resourcs that belong to a given tenant + """List resources that belong to a given tenant """ @@ -357,11 +357,10 @@ def get_parser(self, prog_name): add_extra_argument(parser, 'filter_specs', 'filters options') return parser - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) + def retrieve_list(self, parsed_args): + """Retrieve a list of resources from Quantum server""" quantum_client = self.get_client() search_opts = parse_args_to_dict(parsed_args.filter_specs) - self.log.debug('search options: %s', search_opts) quantum_client.format = parsed_args.request_format fields = parsed_args.fields @@ -377,12 +376,21 @@ def get_data(self, parsed_args): search_opts.update({'verbose': 'True'}) obj_lister = getattr(quantum_client, "list_%ss" % self.resource) - data = obj_lister(**search_opts) - info = [] + collection = self.resource + "s" - if collection in data: - info = data[collection] + return data.get(collection, []) + + def extend_list(self, data, parsed_args): + """Update a retrieved list. + + This method provides a way to modify a original list returned from + the quantum server. For example, you can add subnet cidr information + to a list network. + """ + pass + + def setup_columns(self, info, parsed_args): _columns = len(info) > 0 and sorted(info[0].keys()) or [] if not _columns: # clean the parsed_args.columns so that cliff will not break @@ -398,6 +406,12 @@ def get_data(self, parsed_args): s, _columns, formatters=self._formatters, ) for s in info), ) + def get_data(self, parsed_args): + self.log.debug('get_data(%s)' % parsed_args) + data = self.retrieve_list(parsed_args) + self.extend_list(data, parsed_args) + return self.setup_columns(data, parsed_args) + class ShowCommand(QuantumCommand, show.ShowOne): """Show information of a given resource diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 19b9978f2..d0c35dbae 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -18,6 +18,7 @@ import argparse import logging +from quantumclient.common import utils from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand @@ -27,7 +28,8 @@ def _format_subnets(network): try: - return '\n'.join(network['subnets']) + return '\n'.join([' '.join([s['id'], s.get('cidr', '')]) + for s in network['subnets']]) except Exception: return '' @@ -40,20 +42,28 @@ class ListNetwork(ListCommand): _formatters = {'subnets': _format_subnets, } list_columns = ['id', 'name', 'subnets'] + def extend_list(self, data, parsed_args): + """Add subnet information to a network list""" + quantum_client = self.get_client() + search_opts = {'fields': ['id', 'cidr']} + subnets = quantum_client.list_subnets(**search_opts).get('subnets', []) + subnet_dict = dict([(s['id'], s) for s in subnets]) + for n in data: + if 'subnets' in n: + n['subnets'] = [(subnet_dict.get(s) or {"id": s}) + for s in n['subnets']] -class ListExternalNetwork(ListCommand): + +class ListExternalNetwork(ListNetwork): """List external networks that belong to a given tenant""" - resource = 'network' log = logging.getLogger(__name__ + '.ListExternalNetwork') - _formatters = {'subnets': _format_subnets, } - list_colums = ['id', 'name', 'subnets'] - def get_data(self, parsed_args): + def retrieve_list(self, parsed_args): if '--' not in parsed_args.filter_specs: parsed_args.filter_specs.append('--') parsed_args.filter_specs.append('--router:external=True') - return super(ListExternalNetwork, self).get_data(parsed_args) + return super(ListExternalNetwork, self).retrieve_list(parsed_args) class ShowNetwork(ShowCommand): diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index 1ef02150a..8eebcfe6c 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -17,7 +17,8 @@ import sys -from mox import ContainsKeyValue +import mox +from mox import (ContainsKeyValue, IgnoreArg, IsA) from quantumclient.common import exceptions from quantumclient.common import utils @@ -101,6 +102,8 @@ def test_list_nets_empty_with_column(self): cmd = ListNetwork(MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") + self.mox.StubOutWithMock(ListNetwork, "extend_list") + ListNetwork.extend_list(IsA(list), IgnoreArg()) cmd.get_client().MultipleTimes().AndReturn(self.client) reses = {resources: []} resstr = self.client.serialize(reses) @@ -125,40 +128,109 @@ def test_list_nets_empty_with_column(self): _str = self.fake_stdout.make_string() self.assertEquals('\n', _str) + def _test_list_networks(self, cmd, detail=False, tags=[], + fields_1=[], fields_2=[]): + resources = "networks" + self.mox.StubOutWithMock(ListNetwork, "extend_list") + ListNetwork.extend_list(IsA(list), IgnoreArg()) + self._test_list_resources(resources, cmd, detail, tags, + fields_1, fields_2) + def test_list_nets_detail(self): """list nets: -D.""" - resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) - self._test_list_resources(resources, cmd, True) + self._test_list_networks(cmd, True) def test_list_nets_tags(self): """List nets: -- --tags a b.""" - resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) - self._test_list_resources(resources, cmd, tags=['a', 'b']) + self._test_list_networks(cmd, tags=['a', 'b']) def test_list_nets_detail_tags(self): """List nets: -D -- --tags a b.""" resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) - self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) + self._test_list_networks(cmd, detail=True, tags=['a', 'b']) + + def _test_list_nets_extend_subnets(self, data, expected): + def setup_list_stub(resources, data, query): + reses = {resources: data} + resstr = self.client.serialize(reses) + resp = (test_cli20.MyResp(200), resstr) + path = getattr(self.client, resources + '_path') + self.client.httpclient.request( + test_cli20.end_url(path, query), 'GET', + body=None, + headers=ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(resp) + + resources = "networks" + cmd = ListNetwork(test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(cmd, 'get_client') + self.mox.StubOutWithMock(self.client.httpclient, 'request') + cmd.get_client().AndReturn(self.client) + setup_list_stub('networks', data, '') + cmd.get_client().AndReturn(self.client) + setup_list_stub('subnets', + [{'id': 'mysubid1', 'cidr': '192.168.1.0/24'}, + {'id': 'mysubid2', 'cidr': '172.16.0.0/24'}, + {'id': 'mysubid3', 'cidr': '10.1.1.0/24'}], + query='fields=id&fields=cidr') + self.mox.ReplayAll() + + args = [] + cmd_parser = cmd.get_parser('list_networks') + parsed_args = cmd_parser.parse_args(args) + result = cmd.get_data(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + _result = [x for x in result[1]] + self.assertEqual(len(_result), len(expected)) + for res, exp in zip(_result, expected): + self.assertEqual(len(res), len(exp)) + for a, b in zip(res, exp): + self.assertEqual(a, b) + + def test_list_nets_extend_subnets(self): + data = [{'id': 'netid1', 'name': 'net1', 'subnets': ['mysubid1']}, + {'id': 'netid2', 'name': 'net2', 'subnets': ['mysubid2', + 'mysubid3']}] + # id, name, subnets + expected = [('netid1', 'net1', 'mysubid1 192.168.1.0/24'), + ('netid2', 'net2', + 'mysubid2 172.16.0.0/24\nmysubid3 10.1.1.0/24')] + self._test_list_nets_extend_subnets(data, expected) + + def test_list_nets_extend_subnets_no_subnet(self): + data = [{'id': 'netid1', 'name': 'net1', 'subnets': ['mysubid1']}, + {'id': 'netid2', 'name': 'net2', 'subnets': ['mysubid4']}] + # id, name, subnets + expected = [('netid1', 'net1', 'mysubid1 192.168.1.0/24'), + ('netid2', 'net2', 'mysubid4 ')] + self._test_list_nets_extend_subnets(data, expected) def test_list_nets_fields(self): """List nets: --fields a --fields b -- --fields c d.""" resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) - self._test_list_resources(resources, cmd, - fields_1=['a', 'b'], fields_2=['c', 'd']) + self._test_list_networks(cmd, + fields_1=['a', 'b'], fields_2=['c', 'd']) - def test_list_nets_defined_column(self): + def _test_list_nets_columns(self, cmd, returned_body, + args=['-f', 'json']): resources = 'networks' + self.mox.StubOutWithMock(ListNetwork, "extend_list") + ListNetwork.extend_list(IsA(list), IgnoreArg()) + self._test_list_columns(cmd, resources, returned_body, args=args) + + def test_list_nets_defined_column(self): cmd = ListNetwork(MyApp(sys.stdout), None) returned_body = {"networks": [{"name": "buildname3", "id": "id3", "tenant_id": "tenant_3", "subnets": []}]} - self._test_list_columns(cmd, resources, returned_body, - args=['-f', 'json', '-c', 'id']) + self._test_list_nets_columns(cmd, returned_body, + args=['-f', 'json', '-c', 'id']) _str = self.fake_stdout.make_string() returned_networks = utils.loads(_str) self.assertEquals(1, len(returned_networks)) @@ -167,13 +239,12 @@ def test_list_nets_defined_column(self): self.assertEquals("id", network.keys()[0]) def test_list_nets_with_default_column(self): - resources = 'networks' cmd = ListNetwork(MyApp(sys.stdout), None) returned_body = {"networks": [{"name": "buildname3", "id": "id3", "tenant_id": "tenant_3", "subnets": []}]} - self._test_list_columns(cmd, resources, returned_body) + self._test_list_nets_columns(cmd, returned_body) _str = self.fake_stdout.make_string() returned_networks = utils.loads(_str) self.assertEquals(1, len(returned_networks)) @@ -187,6 +258,8 @@ def test_list_external_nets_empty_with_column(self): cmd = ListExternalNetwork(MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") + self.mox.StubOutWithMock(ListNetwork, "extend_list") + ListNetwork.extend_list(IsA(list), IgnoreArg()) cmd.get_client().MultipleTimes().AndReturn(self.client) reses = {resources: []} resstr = self.client.serialize(reses) @@ -217,6 +290,8 @@ def _test_list_external_nets(self, resources, cmd, fields_1=[], fields_2=[]): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") + self.mox.StubOutWithMock(ListNetwork, "extend_list") + ListNetwork.extend_list(IsA(list), IgnoreArg()) cmd.get_client().MultipleTimes().AndReturn(self.client) reses = {resources: [{'id': 'myid1', }, {'id': 'myid2', }, ], } From ee3ab2d7afb57abf574a31bbb72e4c94dde099df Mon Sep 17 00:00:00 2001 From: gongysh Date: Tue, 11 Dec 2012 23:40:16 +0800 Subject: [PATCH 061/135] Allow known options defined after position arguments. We run the argument parser to split known options and unknown options. Make '-' work and have the same effect as '_' in both known and unknown option parts. Make metavar Uppercase. blueprint options-location Change-Id: Ic27b278484133c8b83e3b031a0810a76b050219f --- quantumclient/quantum/v2_0/__init__.py | 70 ++++++++++++++----- quantumclient/quantum/v2_0/floatingip.py | 4 +- quantumclient/quantum/v2_0/network.py | 2 +- quantumclient/quantum/v2_0/port.py | 4 +- quantumclient/quantum/v2_0/router.py | 2 +- quantumclient/quantum/v2_0/securitygroup.py | 10 +-- quantumclient/quantum/v2_0/subnet.py | 8 +-- quantumclient/shell.py | 40 +++++++++++ quantumclient/tests/unit/test_casual_args.py | 2 +- quantumclient/tests/unit/test_cli20.py | 5 +- .../tests/unit/test_cli20_floatingips.py | 4 +- quantumclient/tests/unit/test_cli20_subnet.py | 61 ++++++++++++++++ 12 files changed, 174 insertions(+), 38 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index c73795b1c..5eae661a9 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -77,11 +77,17 @@ def add_show_list_common_argument(parser): action='store_true', help=argparse.SUPPRESS) parser.add_argument( - '-F', '--fields', + '--fields', + help=argparse.SUPPRESS, + action='append', + default=[]) + parser.add_argument( + '-F', '--field', + dest='fields', metavar='FIELD', help='specify the field(s) to be returned by server,' ' can be repeated', action='append', - default=[], ) + default=[]) def add_extra_argument(parser, name, _help): @@ -108,15 +114,16 @@ def parse_args_to_dict(values_specs): ''' # -- is a pseudo argument - if values_specs and values_specs[0] == '--': - del values_specs[0] + values_specs_copy = values_specs[:] + if values_specs_copy and values_specs_copy[0] == '--': + del values_specs_copy[0] _options = {} current_arg = None _values_specs = [] _value_number = 0 _list_flag = False current_item = None - for _item in values_specs: + for _item in values_specs_copy: if _item.startswith('--'): if current_arg is not None: if _value_number > 1 or _list_flag: @@ -166,22 +173,49 @@ def parse_args_to_dict(values_specs): current_arg.update({'nargs': '+'}) elif _value_number == 0: current_arg.update({'action': 'store_true'}) - _parser = argparse.ArgumentParser(add_help=False) - for opt, optspec in _options.iteritems(): - _parser.add_argument(opt, **optspec) - _args = _parser.parse_args(_values_specs) + _args = None + if _values_specs: + _parser = argparse.ArgumentParser(add_help=False) + for opt, optspec in _options.iteritems(): + _parser.add_argument(opt, **optspec) + _args = _parser.parse_args(_values_specs) result_dict = {} - for opt in _options.iterkeys(): - _opt = opt.split('--', 2)[1] - _value = getattr(_args, _opt.replace('-', '_')) - if _value is not None: - result_dict.update({_opt: _value}) + if _args: + for opt in _options.iterkeys(): + _opt = opt.split('--', 2)[1] + _opt = _opt.replace('-', '_') + _value = getattr(_args, _opt) + if _value is not None: + result_dict.update({_opt: _value}) return result_dict +def _merge_args(qCmd, parsed_args, _extra_values, value_specs): + """Merge arguments from _extra_values into parsed_args. + + If an argument value are provided in both and it is a list, + the values in _extra_values will be merged into parsed_args. + + @param parsed_args: the parsed args from known options + @param _extra_values: the other parsed arguments in unknown parts + @param values_specs: the unparsed unknown parts + """ + temp_values = _extra_values.copy() + for key, value in temp_values.iteritems(): + if hasattr(parsed_args, key): + arg_value = getattr(parsed_args, key) + if arg_value is not None and value is not None: + if isinstance(arg_value, list): + if value and isinstance(value, list): + if type(arg_value[0]) == type(value[0]): + arg_value.extend(value) + _extra_values.pop(key) + + class QuantumCommand(command.OpenStackCommand): api = 'network' log = logging.getLogger(__name__ + '.QuantumCommand') + values_specs = [] def get_client(self): return self.app.client_manager.quantum @@ -227,14 +261,12 @@ class CreateCommand(QuantumCommand, show.ShowOne): def get_parser(self, prog_name): parser = super(CreateCommand, self).get_parser(prog_name) parser.add_argument( - '--tenant-id', metavar='tenant-id', + '--tenant-id', metavar='TENANT_ID', help=_('the owner tenant ID'), ) parser.add_argument( '--tenant_id', help=argparse.SUPPRESS) self.add_known_arguments(parser) - add_extra_argument(parser, 'value_specs', - 'new values for the %s' % self.resource) return parser def add_known_arguments(self, parser): @@ -247,8 +279,10 @@ def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) quantum_client = self.get_client() quantum_client.format = parsed_args.request_format + _extra_values = parse_args_to_dict(self.values_specs) + _merge_args(self, parsed_args, _extra_values, + self.values_specs) body = self.args2body(parsed_args) - _extra_values = parse_args_to_dict(parsed_args.value_specs) body[self.resource].update(_extra_values) obj_creator = getattr(quantum_client, "create_%s" % self.resource) diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index 5613af866..3198dfeb7 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -52,8 +52,8 @@ class CreateFloatingIP(CreateCommand): def add_known_arguments(self, parser): parser.add_argument( - 'floating_network_id', - help='Network to allocate floating IP from') + 'floating_network_id', metavar='FLOATING_NETWORK', + help='Network name or id to allocate floating IP from') parser.add_argument( '--port-id', help='ID of the port to be associated with the floatingip') diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 19b9978f2..4d88ab3d3 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -84,7 +84,7 @@ def add_known_arguments(self, parser): default=argparse.SUPPRESS, help='Set the network as shared') parser.add_argument( - 'name', metavar='name', + 'name', metavar='NAME', help='Name of network to create') def args2body(self, parsed_args): diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 5a3b43472..27d499803 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -112,7 +112,7 @@ def add_known_arguments(self, parser): action='append', help='desired IP for this port: ' 'subnet_id=,ip_address=, ' - 'can be repeated') + '(This option can be repeated.)') parser.add_argument( '--fixed_ip', action='append', @@ -123,7 +123,7 @@ def add_known_arguments(self, parser): help='security group associated with the port ' '(This option can be repeated)') parser.add_argument( - 'network_id', metavar='network', + 'network_id', metavar='NETWORK', help='Network id or name this port belongs to') def args2body(self, parsed_args): diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index db85b8057..a0de0c835 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -68,7 +68,7 @@ def add_known_arguments(self, parser): action='store_false', help=argparse.SUPPRESS) parser.add_argument( - 'name', metavar='name', + 'name', metavar='NAME', help='Name of router to create') def args2body(self, parsed_args): diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py index ae78a0168..ffbd2ae05 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -46,7 +46,7 @@ class CreateSecurityGroup(quantumv20.CreateCommand): def add_known_arguments(self, parser): parser.add_argument( - 'name', metavar='name', + 'name', metavar='NAME', help='Name of security group') parser.add_argument( '--description', @@ -97,8 +97,8 @@ class CreateSecurityGroupRule(quantumv20.CreateCommand): def add_known_arguments(self, parser): parser.add_argument( - 'security_group_id', metavar='security_group_id', - help='Security group to add rule.') + 'security_group_id', metavar='SECURITY_GROUP', + help='Security group name or id to add rule.') parser.add_argument( '--direction', default='ingress', choices=['ingress', 'egress'], @@ -129,8 +129,8 @@ def add_known_arguments(self, parser): '--source_ip_prefix', help=argparse.SUPPRESS) parser.add_argument( - '--source-group-id', - help='source security group to apply rule') + '--source-group-id', metavar='SOURCE_GROUP', + help='source security group name or id to apply rule') parser.add_argument( '--source_group_id', help=argparse.SUPPRESS) diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 390f7bf82..9339ff58b 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -91,7 +91,7 @@ def add_known_arguments(self, parser): choices=[4, 6], help=argparse.SUPPRESS) parser.add_argument( - '--gateway', metavar='gateway', + '--gateway', metavar='GATEWAY_IP', help='gateway ip of this subnet') parser.add_argument( '--no-gateway', @@ -120,10 +120,10 @@ def add_known_arguments(self, parser): action='store_true', help='Disable DHCP for this subnet') parser.add_argument( - 'network_id', metavar='network', - help='Network id or name this subnet belongs to') + 'network_id', metavar='NETWORK', + help='network id or name this subnet belongs to') parser.add_argument( - 'cidr', metavar='cidr', + 'cidr', metavar='CIDR', help='cidr of subnet to create') def args2body(self, parsed_args): diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 038ce20f2..da36bd09f 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -365,6 +365,46 @@ def run(self, argv): result = self.run_subcommand(remainder) return result + def run_subcommand(self, argv): + subcommand = self.command_manager.find_command(argv) + cmd_factory, cmd_name, sub_argv = subcommand + cmd = cmd_factory(self, self.options) + err = None + result = 1 + try: + self.prepare_to_run_command(cmd) + full_name = (cmd_name + if self.interactive_mode + else ' '.join([self.NAME, cmd_name]) + ) + cmd_parser = cmd.get_parser(full_name) + known_args, values_specs = cmd_parser.parse_known_args(sub_argv) + cmd.values_specs = values_specs + result = cmd.run(known_args) + except Exception as err: + if self.options.debug: + self.log.exception(err) + else: + self.log.error(err) + try: + self.clean_up(cmd, result, err) + except Exception as err2: + if self.options.debug: + self.log.exception(err2) + else: + self.log.error('Could not clean up: %s', err2) + if self.options.debug: + raise + else: + try: + self.clean_up(cmd, result, None) + except Exception as err3: + if self.options.debug: + self.log.exception(err3) + else: + self.log.error('Could not clean up: %s', err3) + return result + def authenticate_user(self): """Make sure the user has provided all of the authentication info we need. diff --git a/quantumclient/tests/unit/test_casual_args.py b/quantumclient/tests/unit/test_casual_args.py index 4192ff81b..4eb2b6622 100644 --- a/quantumclient/tests/unit/test_casual_args.py +++ b/quantumclient/tests/unit/test_casual_args.py @@ -35,7 +35,7 @@ def test_default_bool(self): def test_bool_true(self): _specs = ['--my-bool', 'type=bool', 'true', '--arg1', 'value1'] _mydict = quantumV20.parse_args_to_dict(_specs) - self.assertTrue(_mydict['my-bool']) + self.assertTrue(_mydict['my_bool']) def test_bool_false(self): _specs = ['--my_bool', 'type=bool', 'false', '--arg1', 'value1'] diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 520653e9b..874c7773e 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -171,8 +171,9 @@ def _test_create_resource(self, resource, cmd, resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser('create_' + resource) - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) + known_args, values_specs = cmd_parser.parse_known_args(args) + cmd.values_specs = values_specs + cmd.run(known_args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() diff --git a/quantumclient/tests/unit/test_cli20_floatingips.py b/quantumclient/tests/unit/test_cli20_floatingips.py index e48d29e86..7d0da0424 100644 --- a/quantumclient/tests/unit/test_cli20_floatingips.py +++ b/quantumclient/tests/unit/test_cli20_floatingips.py @@ -57,7 +57,7 @@ def test_create_floatingip_and_port(self): # Test dashed options args = [name, '--port-id', pid] - position_names = ['floating_network_id', 'port-id'] + position_names = ['floating_network_id', 'port_id'] _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) @@ -76,7 +76,7 @@ def test_create_floatingip_and_port_and_address(self): position_names, position_values) # Test dashed options args = [name, '--port-id', pid, '--fixed-ip-address', addr] - position_names = ['floating_network_id', 'port-id', 'fixed-ip-address'] + position_names = ['floating_network_id', 'port_id', 'fixed_ip_address'] _str = self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index e07f6c712..ec4396fd5 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -239,6 +239,67 @@ def test_create_subnet_with_disable_dhcp(self): position_names, position_values, tenant_id='tenantid') + def test_create_subnet_merge_single_plurar(self): + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + '--allocation-pool', 'start=1.1.1.10,end=1.1.1.20', + netid, cidr, + '--allocation-pools', 'list=true', 'type=dict', + 'start=1.1.1.30,end=1.1.1.40'] + position_names = ['ip_version', 'allocation_pools', 'network_id', + 'cidr'] + pools = [{'start': '1.1.1.10', 'end': '1.1.1.20'}, + {'start': '1.1.1.30', 'end': '1.1.1.40'}] + position_values = [4, pools, netid, cidr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_subnet_merge_plurar(self): + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + netid, cidr, + '--allocation-pools', 'list=true', 'type=dict', + 'start=1.1.1.30,end=1.1.1.40'] + position_names = ['ip_version', 'allocation_pools', 'network_id', + 'cidr'] + pools = [{'start': '1.1.1.30', 'end': '1.1.1.40'}] + position_values = [4, pools, netid, cidr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_subnet_merge_single_single(self): + resource = 'subnet' + cmd = CreateSubnet(MyApp(sys.stdout), None) + name = 'myname' + myid = 'myid' + netid = 'netid' + cidr = 'prefixvalue' + args = ['--tenant_id', 'tenantid', + '--allocation-pool', 'start=1.1.1.10,end=1.1.1.20', + netid, cidr, + '--allocation-pool', + 'start=1.1.1.30,end=1.1.1.40'] + position_names = ['ip_version', 'allocation_pools', 'network_id', + 'cidr'] + pools = [{'start': '1.1.1.10', 'end': '1.1.1.20'}, + {'start': '1.1.1.30', 'end': '1.1.1.40'}] + position_values = [4, pools, netid, cidr] + _str = self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + def test_list_subnets_detail(self): """List subnets: -D.""" resources = "subnets" From 80fe2540e73e59ccefbbf1cbd11619e4f83b6d7b Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 8 Jan 2013 06:18:31 +0000 Subject: [PATCH 062/135] Migrate from unittest to testtools Part of blueprint grizzly-testtools Change-Id: If92ba40209cb45905220df8c72501f33cdc4e1fc --- quantumclient/tests/unit/test_auth.py | 11 +++++------ quantumclient/tests/unit/test_casual_args.py | 4 ++-- quantumclient/tests/unit/test_cli20.py | 18 ++++++++---------- quantumclient/tests/unit/test_name_or_id.py | 12 +++++------- tools/test-requires | 3 ++- 5 files changed, 22 insertions(+), 26 deletions(-) diff --git a/quantumclient/tests/unit/test_auth.py b/quantumclient/tests/unit/test_auth.py index 477ba904f..ba1aa0b6a 100644 --- a/quantumclient/tests/unit/test_auth.py +++ b/quantumclient/tests/unit/test_auth.py @@ -22,6 +22,7 @@ import mox from mox import ContainsKeyValue, IsA, StrContains +import testtools from quantumclient.client import HTTPClient from quantumclient.common import exceptions @@ -54,19 +55,17 @@ } -class CLITestAuthKeystone(unittest.TestCase): +class CLITestAuthKeystone(testtools.TestCase): def setUp(self): """Prepare the test environment""" + super(CLITestAuthKeystone, self).setUp() self.mox = mox.Mox() self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, auth_url=AUTH_URL, region_name=REGION) - - def tearDown(self): - """Clear the test environment""" - self.mox.VerifyAll() - self.mox.UnsetStubs() + self.addCleanup(self.mox.VerifyAll) + self.addCleanup(self.mox.UnsetStubs) def test_get_token(self): self.mox.StubOutWithMock(self.client, "request") diff --git a/quantumclient/tests/unit/test_casual_args.py b/quantumclient/tests/unit/test_casual_args.py index 4eb2b6622..e086186a9 100644 --- a/quantumclient/tests/unit/test_casual_args.py +++ b/quantumclient/tests/unit/test_casual_args.py @@ -15,13 +15,13 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 -import unittest +import testtools from quantumclient.common import exceptions from quantumclient.quantum import v2_0 as quantumV20 -class CLITestArgs(unittest.TestCase): +class CLITestArgs(testtools.TestCase): def test_empty(self): _mydict = quantumV20.parse_args_to_dict([]) diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 874c7773e..0c9e6a0a5 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -16,11 +16,12 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 import sys -import unittest +import fixtures import mox from mox import Comparator from mox import ContainsKeyValue +import testtools from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.v2_0.client import Client @@ -112,7 +113,7 @@ def __repr__(self): return str(self.lhs) -class CLITestV20Base(unittest.TestCase): +class CLITestV20Base(testtools.TestCase): test_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' @@ -121,18 +122,15 @@ def _find_resourceid(self, client, resource, name_or_id): def setUp(self): """Prepare the test environment""" + super(CLITestV20Base, self).setUp() self.mox = mox.Mox() self.endurl = ENDURL self.client = Client(token=TOKEN, endpoint_url=self.endurl) self.fake_stdout = FakeStdout() - sys.stdout = self.fake_stdout - self.old_find_resourceid = quantumv20.find_resourceid_by_name_or_id - quantumv20.find_resourceid_by_name_or_id = self._find_resourceid - - def tearDown(self): - """Clear the test environment""" - sys.stdout = sys.__stdout__ - quantumv20.find_resourceid_by_name_or_id = self.old_find_resourceid + self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.fake_stdout)) + self.useFixture(fixtures.MonkeyPatch( + 'quantumclient.quantum.v2_0.find_resourceid_by_name_or_id', + self._find_resourceid)) def _test_create_resource(self, resource, cmd, name, myid, args, diff --git a/quantumclient/tests/unit/test_name_or_id.py b/quantumclient/tests/unit/test_name_or_id.py index d5947d809..3b01a1c9b 100644 --- a/quantumclient/tests/unit/test_name_or_id.py +++ b/quantumclient/tests/unit/test_name_or_id.py @@ -19,7 +19,7 @@ import mox from mox import ContainsKeyValue -import unittest +import testtools from quantumclient.common import exceptions from quantumclient.quantum import v2_0 as quantumv20 @@ -27,18 +27,16 @@ from quantumclient.v2_0.client import Client -class CLITestNameorID(unittest.TestCase): +class CLITestNameorID(testtools.TestCase): def setUp(self): """Prepare the test environment""" + super(CLITestNameorID, self).setUp() self.mox = mox.Mox() self.endurl = test_cli20.ENDURL self.client = Client(token=test_cli20.TOKEN, endpoint_url=self.endurl) - - def tearDown(self): - """Clear the test environment""" - self.mox.VerifyAll() - self.mox.UnsetStubs() + self.addCleanup(self.mox.VerifyAll) + self.addCleanup(self.mox.UnsetStubs) def test_get_id_from_id(self): _id = str(uuid.uuid4()) diff --git a/tools/test-requires b/tools/test-requires index 8b02c3b54..12622f2e2 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,5 +1,6 @@ distribute>=0.6.24 cliff-tablib>=1.0 +fixtures>=0.3.12 mox nose nose-exclude @@ -8,4 +9,4 @@ openstack.nose_plugin nosehtmloutput pep8 sphinx>=1.1.2 - +testtools From 4d7c6b6ec7c9e3afbc238f70f09dda4274941217 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Fri, 14 Dec 2012 21:20:45 +0900 Subject: [PATCH 063/135] Display security group name in security-group-rule-list It is useful to display security group name rather than its id for security_group_id and source_group_id. Also add '--no-nameconv' option to disable conversion from security group id to its name. When security-group-rule-list is executed by admin user it is likely more than one groups have same name. This option is useful for such cases. Change-Id: I7fd0f1fb26fce8ed24e0f710666866607e681bc8 --- quantumclient/quantum/v2_0/securitygroup.py | 49 +++++++++ .../tests/unit/test_cli20_securitygroup.py | 100 ++++++++++++++++++ 2 files changed, 149 insertions(+) diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py index ffbd2ae05..9442cccda 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -79,6 +79,55 @@ class ListSecurityGroupRule(quantumv20.ListCommand): _formatters = {} list_columns = ['id', 'security_group_id', 'direction', 'protocol', 'source_ip_prefix', 'source_group_id'] + replace_rules = {'security_group_id': 'security_group', + 'source_group_id': 'source_group'} + + def get_parser(self, prog_name): + parser = super(ListSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + '--no-nameconv', action='store_true', + help='Do not convert security group ID to its name') + return parser + + @staticmethod + def replace_columns(cols, rules, reverse=False): + if reverse: + rules = dict((rules[k], k) for k in rules.keys()) + return [rules.get(col, col) for col in cols] + + def retrieve_list(self, parsed_args): + parsed_args.fields = self.replace_columns(parsed_args.fields, + self.replace_rules, + reverse=True) + return super(ListSecurityGroupRule, self).retrieve_list(parsed_args) + + def extend_list(self, data, parsed_args): + if parsed_args.no_nameconv: + return + quantum_client = self.get_client() + search_opts = {'fields': ['id', 'name']} + secgroups = quantum_client.list_security_groups(**search_opts) + secgroups = secgroups.get('security_groups', []) + sg_dict = dict([(sg['id'], sg['name']) + for sg in secgroups if sg['name']]) + for rule in data: + for key in self.replace_rules: + rule[key] = sg_dict.get(rule[key], rule[key]) + + def setup_columns(self, info, parsed_args): + parsed_args.columns = self.replace_columns(parsed_args.columns, + self.replace_rules, + reverse=True) + # NOTE(amotoki): 2nd element of the tuple returned by setup_columns() + # is a generator, so if you need to create a look using the generator + # object, you need to recreate a generator to show a list expectedly. + info = super(ListSecurityGroupRule, self).setup_columns(info, + parsed_args) + cols = info[0] + if not parsed_args.no_nameconv: + cols = self.replace_columns(info[0], self.replace_rules) + parsed_args.columns = cols + return (cols, info[1]) class ShowSecurityGroupRule(quantumv20.ShowCommand): diff --git a/quantumclient/tests/unit/test_cli20_securitygroup.py b/quantumclient/tests/unit/test_cli20_securitygroup.py index c5fcf5fc1..d1dd08547 100644 --- a/quantumclient/tests/unit/test_cli20_securitygroup.py +++ b/quantumclient/tests/unit/test_cli20_securitygroup.py @@ -18,6 +18,8 @@ import sys +import mox + from quantumclient.quantum.v2_0 import securitygroup from quantumclient.tests.unit import test_cli20 @@ -137,6 +139,10 @@ def test_list_security_group_rules(self): resources = "security_group_rules" cmd = securitygroup.ListSecurityGroupRule( test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(securitygroup.ListSecurityGroupRule, + "extend_list") + securitygroup.ListSecurityGroupRule.extend_list(mox.IsA(list), + mox.IgnoreArg()) self._test_list_resources(resources, cmd, True) def test_show_security_group_rule(self): @@ -146,3 +152,97 @@ def test_show_security_group_rule(self): args = ['--fields', 'id', self.test_id] self._test_show_resource(resource, cmd, self.test_id, args, ['id']) + + def _test_list_security_group_rules_extend(self, data=None, expected=None, + args=[], conv=True, + query_field=False): + def setup_list_stub(resources, data, query): + reses = {resources: data} + resstr = self.client.serialize(reses) + resp = (test_cli20.MyResp(200), resstr) + path = getattr(self.client, resources + '_path') + self.client.httpclient.request( + test_cli20.end_url(path, query), 'GET', + body=None, + headers=mox.ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(resp) + + # Setup the default data + _data = {'cols': ['id', 'security_group_id', 'source_group_id'], + 'data': [('ruleid1', 'myid1', 'myid1'), + ('ruleid2', 'myid2', 'myid3'), + ('ruleid3', 'myid2', 'myid2')]} + _expected = {'cols': ['id', 'security_group', 'source_group'], + 'data': [('ruleid1', 'group1', 'group1'), + ('ruleid2', 'group2', 'group3'), + ('ruleid3', 'group2', 'group2')]} + if data is None: + data = _data + list_data = [dict(zip(data['cols'], d)) for d in data['data']] + if expected is None: + expected = {} + expected['cols'] = expected.get('cols', _expected['cols']) + expected['data'] = expected.get('data', _expected['data']) + + resources = "security_group_rules" + cmd = securitygroup.ListSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(cmd, 'get_client') + self.mox.StubOutWithMock(self.client.httpclient, 'request') + cmd.get_client().AndReturn(self.client) + query = '' + if query_field: + query = '&'.join(['fields=' + f for f in data['cols']]) + setup_list_stub('security_group_rules', list_data, query) + if conv: + cmd.get_client().AndReturn(self.client) + setup_list_stub('security_groups', + [{'id': 'myid1', 'name': 'group1'}, + {'id': 'myid2', 'name': 'group2'}, + {'id': 'myid3', 'name': 'group3'}], + query='fields=id&fields=name') + self.mox.ReplayAll() + + cmd_parser = cmd.get_parser('list_security_group_rules') + parsed_args = cmd_parser.parse_args(args) + result = cmd.get_data(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + # Check columns + self.assertEqual(result[0], expected['cols']) + # Check data + _result = [x for x in result[1]] + self.assertEqual(len(_result), len(expected['data'])) + for res, exp in zip(_result, expected['data']): + self.assertEqual(len(res), len(exp)) + self.assertEqual(res, exp) + + def test_list_security_group_rules_extend_source_id(self): + self._test_list_security_group_rules_extend() + + def test_list_security_group_rules_extend_no_nameconv(self): + expected = {'cols': ['id', 'security_group_id', 'source_group_id'], + 'data': [('ruleid1', 'myid1', 'myid1'), + ('ruleid2', 'myid2', 'myid3'), + ('ruleid3', 'myid2', 'myid2')]} + args = ['--no-nameconv'] + self._test_list_security_group_rules_extend(expected=expected, + args=args, conv=False) + + def test_list_security_group_rules_extend_with_columns(self): + args = '-c id -c security_group_id -c source_group_id'.split() + self._test_list_security_group_rules_extend(args=args) + + def test_list_security_group_rules_extend_with_columns_no_id(self): + args = '-c id -c security_group -c source_group'.split() + self._test_list_security_group_rules_extend(args=args) + + def test_list_security_group_rules_extend_with_fields(self): + args = '-F id -F security_group_id -F source_group_id'.split() + self._test_list_security_group_rules_extend(args=args, + query_field=True) + + def test_list_security_group_rules_extend_with_fields_no_id(self): + args = '-F id -F security_group -F source_group'.split() + self._test_list_security_group_rules_extend(args=args, + query_field=True) From 04992fbf8ce404b5b0cdbb3247fb19ec77df33ca Mon Sep 17 00:00:00 2001 From: Aaron Rosen Date: Tue, 22 Jan 2013 21:27:08 -0800 Subject: [PATCH 064/135] Exception should raise with status code Fixes bug 1103330 Change-Id: Ia706a76429027a4627ea7d12eeee3badae8c16b5 --- quantumclient/v2_0/client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index c7937fa16..322ee48a7 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -73,7 +73,8 @@ def exception_handler_v20(status_code, error_content): if ex: raise ex else: - raise exceptions.QuantumClientException(message=error_dict) + raise exceptions.QuantumClientException(status_code=status_code, + message=error_dict) else: message = None if isinstance(error_content, dict): From ff5fb29498709d8510eedb0f787d916eeb9fccba Mon Sep 17 00:00:00 2001 From: sthakkar Date: Mon, 21 Jan 2013 17:32:28 -0800 Subject: [PATCH 065/135] Fix quantum client example Example references an incorrect method. list_net() should be list_networks() Change-Id: Ia1f4e13898964da63cb9c9da75270236aa6587b6 --- quantumclient/v2_0/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index c7937fa16..b3871adbe 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -132,7 +132,7 @@ class Client(object): tenant_name=TENANT_NAME, auth_url=KEYSTONE_URL) - >>> nets = quantum.list_nets() + >>> nets = quantum.list_networks() ... """ From 0e103024f2842fde40517d4bd167652fd422155f Mon Sep 17 00:00:00 2001 From: gongysh Date: Sat, 26 Jan 2013 09:00:52 +0800 Subject: [PATCH 066/135] Remove multiple white spaces Change-Id: I3715160a18cc2facdb8978ec97bf0f7ece1c3f05 --- quantumclient/tests/unit/test_casual_args.py | 4 ++-- quantumclient/tests/unit/test_cli20_floatingips.py | 4 ++-- quantumclient/tests/unit/test_cli20_securitygroup.py | 2 +- quantumclient/tests/unit/test_cli20_subnet.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/quantumclient/tests/unit/test_casual_args.py b/quantumclient/tests/unit/test_casual_args.py index e086186a9..c827e0e41 100644 --- a/quantumclient/tests/unit/test_casual_args.py +++ b/quantumclient/tests/unit/test_casual_args.py @@ -33,12 +33,12 @@ def test_default_bool(self): self.assertTrue(_mydict['my_bool']) def test_bool_true(self): - _specs = ['--my-bool', 'type=bool', 'true', '--arg1', 'value1'] + _specs = ['--my-bool', 'type=bool', 'true', '--arg1', 'value1'] _mydict = quantumV20.parse_args_to_dict(_specs) self.assertTrue(_mydict['my_bool']) def test_bool_false(self): - _specs = ['--my_bool', 'type=bool', 'false', '--arg1', 'value1'] + _specs = ['--my_bool', 'type=bool', 'false', '--arg1', 'value1'] _mydict = quantumV20.parse_args_to_dict(_specs) self.assertFalse(_mydict['my_bool']) diff --git a/quantumclient/tests/unit/test_cli20_floatingips.py b/quantumclient/tests/unit/test_cli20_floatingips.py index 7d0da0424..7b5912c00 100644 --- a/quantumclient/tests/unit/test_cli20_floatingips.py +++ b/quantumclient/tests/unit/test_cli20_floatingips.py @@ -108,7 +108,7 @@ def test_disassociate_ip(self): cmd = DisassociateFloatingIP(MyApp(sys.stdout), None) args = ['myid'] self._test_update_resource(resource, cmd, 'myid', - args, {"port_id": None} + args, {"port_id": None} ) def test_associate_ip(self): @@ -117,5 +117,5 @@ def test_associate_ip(self): cmd = AssociateFloatingIP(MyApp(sys.stdout), None) args = ['myid', 'portid'] self._test_update_resource(resource, cmd, 'myid', - args, {"port_id": "portid"} + args, {"port_id": "portid"} ) diff --git a/quantumclient/tests/unit/test_cli20_securitygroup.py b/quantumclient/tests/unit/test_cli20_securitygroup.py index d1dd08547..7a2e941b8 100644 --- a/quantumclient/tests/unit/test_cli20_securitygroup.py +++ b/quantumclient/tests/unit/test_cli20_securitygroup.py @@ -61,7 +61,7 @@ def test_create_security_group_with_description(self): name = 'webservers' description = 'my webservers' myid = 'myid' - args = [name, '--description', description] + args = [name, '--description', description] position_names = ['name', 'description'] position_values = [name, description] self._test_create_resource(resource, cmd, name, myid, args, diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index ec4396fd5..997718bb2 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -51,7 +51,7 @@ def test_create_subnet_with_no_gateway(self): myid = 'myid' netid = 'netid' cidr = 'cidrvalue' - args = ['--no-gateway', netid, cidr] + args = ['--no-gateway', netid, cidr] position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] position_values = [4, netid, cidr, None] _str = self._test_create_resource(resource, cmd, name, myid, args, @@ -66,7 +66,7 @@ def test_create_subnet_with_bad_gateway_option(self): netid = 'netid' cidr = 'cidrvalue' gateway = 'gatewayvalue' - args = ['--gateway', gateway, '--no-gateway', netid, cidr] + args = ['--gateway', gateway, '--no-gateway', netid, cidr] position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] position_values = [4, netid, cidr, None] try: From 0dc8d7c8ba3bc6d061ebc44472b1d1e73cb137e8 Mon Sep 17 00:00:00 2001 From: gongysh Date: Sun, 27 Jan 2013 22:51:17 +0800 Subject: [PATCH 067/135] Delete network with id in sample code using API. Bug #1106936 Change-Id: I6a7ee694a916af3f0268884ce28ca12e5efdc025 --- doc/source/index.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/source/index.rst b/doc/source/index.rst index 85ca4009f..1dd7caefc 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -3,13 +3,17 @@ Python bindings to the OpenStack Network API In order to use the python quantum client directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so:: + >>> import logging >>> from quantumclient.quantum import client + >>> logging.basicConfig(level=logging.DEBUG) >>> quantum = client.Client('2.0', endpoint_url=OS_URL, token=OS_TOKEN) + >>> quantum.format = 'json' >>> network = {'name': 'mynetwork', 'admin_state_up': True} >>> quantum.create_network({'network':network}) >>> networks = quantum.list_networks(name='mynetwork') >>> print networks - >>> quantum.delete_network(name='mynetwork') + >>> network_id = networks['networks'][0]['id'] + >>> quantum.delete_network(network_id) Command-line Tool From 2bca8ee4407aee03a83592395d3191f097c459a5 Mon Sep 17 00:00:00 2001 From: Ilya Shakhat Date: Wed, 19 Dec 2012 17:40:00 +0400 Subject: [PATCH 068/135] The change implements LBaaS CLI commands. Implements: blueprint lbaas-cli New commands: * Vip: lb-vip-create, lb-vip-list, lb-vip-show, lb-vip-update, lb-vip-delete * Pool: lb-pool-create, lb-pool-list, lb-pool-show, lb-pool-update, lb-pool-delete, lb-pool-stats * Member: lb-member-create, lb-member-list, lb-member-show, lb-member-update, lb-member-delete * Health Monitor: lb-healthmonitor-create, lb-healthmonitor-list, lb-healthmonitor-show, lb-healthmonitor-update, lb-healthmonitor-delete, lb-healthmonitor-associate, lb-healthmonitor-disassociate Change-Id: Idaa569024c24955886a836e3dfedd009fed87007 --- quantumclient/quantum/v2_0/__init__.py | 14 +- quantumclient/quantum/v2_0/lb/__init__.py | 16 ++ .../quantum/v2_0/lb/healthmonitor.py | 172 ++++++++++++++++ quantumclient/quantum/v2_0/lb/member.py | 93 +++++++++ quantumclient/quantum/v2_0/lb/pool.py | 120 +++++++++++ quantumclient/quantum/v2_0/lb/vip.py | 111 ++++++++++ quantumclient/shell.py | 47 +++++ quantumclient/tests/unit/lb/__init__.py | 16 ++ .../tests/unit/lb/test_cli20_healthmonitor.py | 189 ++++++++++++++++++ .../tests/unit/lb/test_cli20_member.py | 104 ++++++++++ .../tests/unit/lb/test_cli20_pool.py | 145 ++++++++++++++ quantumclient/tests/unit/lb/test_cli20_vip.py | 185 +++++++++++++++++ quantumclient/tests/unit/test_casual_args.py | 16 ++ quantumclient/tests/unit/test_cli20.py | 5 +- quantumclient/v2_0/client.py | 181 +++++++++++++++++ 15 files changed, 1409 insertions(+), 5 deletions(-) create mode 100644 quantumclient/quantum/v2_0/lb/__init__.py create mode 100644 quantumclient/quantum/v2_0/lb/healthmonitor.py create mode 100644 quantumclient/quantum/v2_0/lb/member.py create mode 100644 quantumclient/quantum/v2_0/lb/pool.py create mode 100644 quantumclient/quantum/v2_0/lb/vip.py create mode 100644 quantumclient/tests/unit/lb/__init__.py create mode 100644 quantumclient/tests/unit/lb/test_cli20_healthmonitor.py create mode 100644 quantumclient/tests/unit/lb/test_cli20_member.py create mode 100644 quantumclient/tests/unit/lb/test_cli20_pool.py create mode 100644 quantumclient/tests/unit/lb/test_cli20_vip.py diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 530b07a84..fbc64ed60 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -141,7 +141,10 @@ def parse_args_to_dict(values_specs): current_arg = _options[_item] _item = _temp elif _item.startswith('type='): - if current_arg is not None: + if current_arg is None: + raise exceptions.CommandError( + "invalid values_specs %s" % ' '.join(values_specs)) + if 'type' not in current_arg: _type_str = _item.split('=', 2)[1] current_arg.update({'type': eval(_type_str)}) if _type_str == 'bool': @@ -149,9 +152,6 @@ def parse_args_to_dict(values_specs): elif _type_str == 'dict': current_arg.update({'type': utils.str2dict}) continue - else: - raise exceptions.CommandError( - "invalid values_specs %s" % ' '.join(values_specs)) elif _item == 'list=true': _list_flag = True continue @@ -212,6 +212,12 @@ def _merge_args(qCmd, parsed_args, _extra_values, value_specs): _extra_values.pop(key) +def update_dict(obj, dict, attributes): + for attribute in attributes: + if hasattr(obj, attribute) and getattr(obj, attribute): + dict[attribute] = getattr(obj, attribute) + + class QuantumCommand(command.OpenStackCommand): api = 'network' log = logging.getLogger(__name__ + '.QuantumCommand') diff --git a/quantumclient/quantum/v2_0/lb/__init__.py b/quantumclient/quantum/v2_0/lb/__init__.py new file mode 100644 index 000000000..1668497e7 --- /dev/null +++ b/quantumclient/quantum/v2_0/lb/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2013 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 diff --git a/quantumclient/quantum/v2_0/lb/healthmonitor.py b/quantumclient/quantum/v2_0/lb/healthmonitor.py new file mode 100644 index 000000000..9aa287401 --- /dev/null +++ b/quantumclient/quantum/v2_0/lb/healthmonitor.py @@ -0,0 +1,172 @@ +# Copyright 2013 Mirantis Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ilya Shakhat, Mirantis Inc. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum import v2_0 as quantumv20 + + +class ListHealthMonitor(quantumv20.ListCommand): + """List healthmonitors that belong to a given tenant.""" + + resource = 'health_monitor' + log = logging.getLogger(__name__ + '.ListHealthMonitor') + list_columns = ['id', 'type', 'admin_state_up', 'status'] + _formatters = {} + + +class ShowHealthMonitor(quantumv20.ShowCommand): + """Show information of a given healthmonitor.""" + + resource = 'health_monitor' + log = logging.getLogger(__name__ + '.ShowHealthMonitor') + + +class CreateHealthMonitor(quantumv20.CreateCommand): + """Create a healthmonitor""" + + resource = 'health_monitor' + log = logging.getLogger(__name__ + '.CreateHealthMonitor') + + def add_known_arguments(self, parser): + parser.add_argument( + '--admin-state-down', + default=True, action='store_false', + help='set admin state up to false') + parser.add_argument( + '--delay', + required=True, + help='the minimum time in seconds between regular connections ' + 'of the member.') + parser.add_argument( + '--expected-codes', + help='the list of HTTP status codes expected in ' + 'response from the member to declare it healthy. This ' + 'attribute can contain one value, ' + 'or a list of values separated by comma, ' + 'or a range of values (e.g. "200-299"). If this attribute ' + 'is not specified, it defaults to "200". ') + parser.add_argument( + '--http-method', + help='the HTTP method used for requests by the monitor of type ' + 'HTTP.') + parser.add_argument( + '--max-retries', + required=True, + help='number of permissible connection failures before changing ' + 'the member status to INACTIVE.') + parser.add_argument( + '--timeout', + required=True, + help='maximum number of seconds for a monitor to wait for a ' + 'connection to be established before it times out. The ' + 'value must be less than the delay value.') + parser.add_argument( + '--type', + required=True, + help='one of predefined health monitor types, e.g. RoundRobin') + parser.add_argument( + '--url-path', + help='the HTTP path used in the HTTP request used by the monitor' + ' to test a member health. This must be a string ' + 'beginning with a / (forward slash)') + + def args2body(self, parsed_args): + body = { + self.resource: { + 'admin_state_up': parsed_args.admin_state_down, + 'delay': parsed_args.delay, + 'max_retries': parsed_args.max_retries, + 'timeout': parsed_args.timeout, + 'type': parsed_args.type, + }, + } + quantumv20.update_dict(parsed_args, body[self.resource], + ['expected_codes', 'http_method', 'url_path', + 'tenant_id']) + return body + + +class UpdateHealthMonitor(quantumv20.UpdateCommand): + """Update a given healthmonitor.""" + + resource = 'health_monitor' + log = logging.getLogger(__name__ + '.UpdateHealthMonitor') + + +class DeleteHealthMonitor(quantumv20.DeleteCommand): + """Delete a given healthmonitor.""" + + resource = 'health_monitor' + log = logging.getLogger(__name__ + '.DeleteHealthMonitor') + + +class AssociateHealthMonitor(quantumv20.QuantumCommand): + """Create a mapping between a health monitor and a pool.""" + + log = logging.getLogger(__name__ + '.AssociateHealthMonitor') + resource = 'health_monitor' + + def get_parser(self, prog_name): + parser = super(AssociateHealthMonitor, self).get_parser(prog_name) + parser.add_argument( + 'health_monitor_id', + help='Health monitor to associate') + parser.add_argument( + 'pool_id', + help='ID of the pool to be associated with the health monitor') + return parser + + def run(self, parsed_args): + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + body = {'health_monitor': {'id': parsed_args.health_monitor_id}} + pool_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, 'pool', parsed_args.pool_id) + quantum_client.associate_health_monitor(pool_id, body) + print >>self.app.stdout, (_('Associated health monitor ' + '%s') % parsed_args.health_monitor_id) + + +class DisassociateHealthMonitor(quantumv20.QuantumCommand): + """Remove a mapping from a health monitor to a pool.""" + + log = logging.getLogger(__name__ + '.DisassociateHealthMonitor') + resource = 'health_monitor' + + def get_parser(self, prog_name): + parser = super(DisassociateHealthMonitor, self).get_parser(prog_name) + parser.add_argument( + 'health_monitor_id', + help='Health monitor to associate') + parser.add_argument( + 'pool_id', + help='ID of the pool to be associated with the health monitor') + return parser + + def run(self, parsed_args): + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + pool_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, 'pool', parsed_args.pool_id) + quantum_client.disassociate_health_monitor(pool_id, + parsed_args + .health_monitor_id) + print >>self.app.stdout, (_('Disassociated health monitor ' + '%s') % parsed_args.health_monitor_id) diff --git a/quantumclient/quantum/v2_0/lb/member.py b/quantumclient/quantum/v2_0/lb/member.py new file mode 100644 index 000000000..25707175a --- /dev/null +++ b/quantumclient/quantum/v2_0/lb/member.py @@ -0,0 +1,93 @@ +# Copyright 2013 Mirantis Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ilya Shakhat, Mirantis Inc. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum import v2_0 as quantumv20 + + +class ListMember(quantumv20.ListCommand): + """List members that belong to a given tenant.""" + + resource = 'member' + log = logging.getLogger(__name__ + '.ListMember') + list_columns = ['id', 'address', 'port', 'admin_state_up', 'status'] + _formatters = {} + + +class ShowMember(quantumv20.ShowCommand): + """Show information of a given member.""" + + resource = 'member' + log = logging.getLogger(__name__ + '.ShowMember') + + +class CreateMember(quantumv20.CreateCommand): + """Create a member""" + + resource = 'member' + log = logging.getLogger(__name__ + '.CreateMember') + + def add_known_arguments(self, parser): + parser.add_argument( + 'pool_id', metavar='pool', + help='Pool id or name this vip belongs to') + parser.add_argument( + '--address', + required=True, + help='IP address of the pool member on the pool network. ') + parser.add_argument( + '--admin-state-down', + default=True, action='store_false', + help='set admin state up to false') + parser.add_argument( + '--port', + required=True, + help='port on which the pool member listens for requests or ' + 'connections. ') + parser.add_argument( + '--weight', + help='weight of pool member in the pool') + + def args2body(self, parsed_args): + _pool_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'pool', parsed_args.pool_id) + body = { + self.resource: { + 'pool_id': _pool_id, + 'admin_state_up': parsed_args.admin_state_down, + }, + } + quantumv20.update_dict(parsed_args, body[self.resource], + ['address', 'port', 'weight', 'tenant_id']) + return body + + +class UpdateMember(quantumv20.UpdateCommand): + """Update a given member.""" + + resource = 'member' + log = logging.getLogger(__name__ + '.UpdateMember') + + +class DeleteMember(quantumv20.DeleteCommand): + """Delete a given member.""" + + resource = 'member' + log = logging.getLogger(__name__ + '.DeleteMember') diff --git a/quantumclient/quantum/v2_0/lb/pool.py b/quantumclient/quantum/v2_0/lb/pool.py new file mode 100644 index 000000000..b39c68459 --- /dev/null +++ b/quantumclient/quantum/v2_0/lb/pool.py @@ -0,0 +1,120 @@ +# Copyright 2013 Mirantis Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ilya Shakhat, Mirantis Inc. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum import v2_0 as quantumv20 + + +class ListPool(quantumv20.ListCommand): + """List pools that belong to a given tenant.""" + + resource = 'pool' + log = logging.getLogger(__name__ + '.ListPool') + list_columns = ['id', 'name', 'lb_method', 'protocol', + 'admin_state_up', 'status'] + _formatters = {} + + +class ShowPool(quantumv20.ShowCommand): + """Show information of a given pool.""" + + resource = 'pool' + log = logging.getLogger(__name__ + '.ShowPool') + + +class CreatePool(quantumv20.CreateCommand): + """Create a pool""" + + resource = 'pool' + log = logging.getLogger(__name__ + '.CreatePool') + + def add_known_arguments(self, parser): + parser.add_argument( + '--admin-state-down', + default=True, action='store_false', + help='set admin state up to false') + parser.add_argument( + '--description', + help='description of the pool') + parser.add_argument( + '--lb-method', + required=True, + help='the algorithm used to distribute load between the members ' + 'of the pool') + parser.add_argument( + '--name', + required=True, + help='the name of the pool') + parser.add_argument( + '--protocol', + required=True, + help='protocol for balancing') + parser.add_argument( + '--subnet-id', + required=True, + help='the subnet on which the members of the pool will be located') + + def args2body(self, parsed_args): + body = { + self.resource: { + 'admin_state_up': parsed_args.admin_state_down, + }, + } + quantumv20.update_dict(parsed_args, body[self.resource], + ['description', 'lb_method', 'name', + 'subnet_id', 'protocol', 'tenant_id']) + return body + + +class UpdatePool(quantumv20.UpdateCommand): + """Update a given pool.""" + + resource = 'pool' + log = logging.getLogger(__name__ + '.UpdatePool') + + +class DeletePool(quantumv20.DeleteCommand): + """Delete a given pool.""" + + resource = 'pool' + log = logging.getLogger(__name__ + '.DeletePool') + + +class RetrievePoolStats(quantumv20.ShowCommand): + """Retrieve stats for a given pool.""" + + resource = 'pool' + log = logging.getLogger(__name__ + '.RetrievePoolStats') + + def get_data(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + params = {} + if parsed_args.fields: + params = {'fields': parsed_args.fields} + + data = quantum_client.retrieve_pool_stats(parsed_args.id, **params) + self.format_output_data(data) + stats = data['stats'] + if 'stats' in data: + return zip(*sorted(stats.iteritems())) + else: + return None diff --git a/quantumclient/quantum/v2_0/lb/vip.py b/quantumclient/quantum/v2_0/lb/vip.py new file mode 100644 index 000000000..741b14d67 --- /dev/null +++ b/quantumclient/quantum/v2_0/lb/vip.py @@ -0,0 +1,111 @@ +# Copyright 2013 Mirantis Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ilya Shakhat, Mirantis Inc. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum import v2_0 as quantumv20 + + +class ListVip(quantumv20.ListCommand): + """List vips that belong to a given tenant.""" + + resource = 'vip' + log = logging.getLogger(__name__ + '.ListVip') + list_columns = ['id', 'name', 'algorithm', 'protocol', + 'admin_state_up', 'status'] + _formatters = {} + + +class ShowVip(quantumv20.ShowCommand): + """Show information of a given vip.""" + + resource = 'vip' + log = logging.getLogger(__name__ + '.ShowVip') + + +class CreateVip(quantumv20.CreateCommand): + """Create a vip""" + + resource = 'vip' + log = logging.getLogger(__name__ + '.CreateVip') + + def add_known_arguments(self, parser): + parser.add_argument( + 'pool_id', metavar='pool', + help='Pool id or name this vip belongs to') + parser.add_argument( + '--address', + help='IP address of the vip') + parser.add_argument( + '--admin-state-down', + default=True, action='store_false', + help='set admin state up to false') + parser.add_argument( + '--connection-limit', + help='the maximum number of connections per second allowed for ' + 'the vip') + parser.add_argument( + '--description', + help='description of the vip') + parser.add_argument( + '--name', + required=True, + help='name of the vip') + parser.add_argument( + '--port', + required=True, + help='TCP port on which to listen for client traffic that is ' + 'associated with the vip address') + parser.add_argument( + '--protocol', + required=True, + help='protocol for balancing') + parser.add_argument( + '--subnet-id', + required=True, + help='the subnet on which to allocate the vip address') + + def args2body(self, parsed_args): + _pool_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'pool', parsed_args.pool_id) + body = { + self.resource: { + 'pool_id': _pool_id, + 'admin_state_up': parsed_args.admin_state_down, + }, + } + quantumv20.update_dict(parsed_args, body[self.resource], + ['address', 'connection_limit', 'description', + 'name', 'port', 'protocol', 'subnet_id', + 'tenant_id']) + return body + + +class UpdateVip(quantumv20.UpdateCommand): + """Update a given vip.""" + + resource = 'vip' + log = logging.getLogger(__name__ + '.UpdateVip') + + +class DeleteVip(quantumv20.DeleteCommand): + """Delete a given vip.""" + + resource = 'vip' + log = logging.getLogger(__name__ + '.DeleteVip') diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 8008978ac..3b8ae5c84 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -145,6 +145,53 @@ def env(*_vars, **kwargs): 'quantumclient.quantum.v2_0.securitygroup.CreateSecurityGroupRule'), 'security-group-rule-delete': utils.import_class( 'quantumclient.quantum.v2_0.securitygroup.DeleteSecurityGroupRule'), + 'lb-vip-list': utils.import_class( + 'quantumclient.quantum.v2_0.lb.vip.ListVip'), + 'lb-vip-show': utils.import_class( + 'quantumclient.quantum.v2_0.lb.vip.ShowVip'), + 'lb-vip-create': utils.import_class( + 'quantumclient.quantum.v2_0.lb.vip.CreateVip'), + 'lb-vip-update': utils.import_class( + 'quantumclient.quantum.v2_0.lb.vip.UpdateVip'), + 'lb-vip-delete': utils.import_class( + 'quantumclient.quantum.v2_0.lb.vip.DeleteVip'), + 'lb-pool-list': utils.import_class( + 'quantumclient.quantum.v2_0.lb.pool.ListPool'), + 'lb-pool-show': utils.import_class( + 'quantumclient.quantum.v2_0.lb.pool.ShowPool'), + 'lb-pool-create': utils.import_class( + 'quantumclient.quantum.v2_0.lb.pool.CreatePool'), + 'lb-pool-update': utils.import_class( + 'quantumclient.quantum.v2_0.lb.pool.UpdatePool'), + 'lb-pool-delete': utils.import_class( + 'quantumclient.quantum.v2_0.lb.pool.DeletePool'), + 'lb-pool-stats': utils.import_class( + 'quantumclient.quantum.v2_0.lb.pool.RetrievePoolStats'), + 'lb-member-list': utils.import_class( + 'quantumclient.quantum.v2_0.lb.member.ListMember'), + 'lb-member-show': utils.import_class( + 'quantumclient.quantum.v2_0.lb.member.ShowMember'), + 'lb-member-create': utils.import_class( + 'quantumclient.quantum.v2_0.lb.member.CreateMember'), + 'lb-member-update': utils.import_class( + 'quantumclient.quantum.v2_0.lb.member.UpdateMember'), + 'lb-member-delete': utils.import_class( + 'quantumclient.quantum.v2_0.lb.member.DeleteMember'), + 'lb-healthmonitor-list': utils.import_class( + 'quantumclient.quantum.v2_0.lb.healthmonitor.ListHealthMonitor'), + 'lb-healthmonitor-show': utils.import_class( + 'quantumclient.quantum.v2_0.lb.healthmonitor.ShowHealthMonitor'), + 'lb-healthmonitor-create': utils.import_class( + 'quantumclient.quantum.v2_0.lb.healthmonitor.CreateHealthMonitor'), + 'lb-healthmonitor-update': utils.import_class( + 'quantumclient.quantum.v2_0.lb.healthmonitor.UpdateHealthMonitor'), + 'lb-healthmonitor-delete': utils.import_class( + 'quantumclient.quantum.v2_0.lb.healthmonitor.DeleteHealthMonitor'), + 'lb-healthmonitor-associate': utils.import_class( + 'quantumclient.quantum.v2_0.lb.healthmonitor.AssociateHealthMonitor'), + 'lb-healthmonitor-disassociate': utils.import_class( + 'quantumclient.quantum.v2_0.lb.healthmonitor' + '.DisassociateHealthMonitor'), } COMMANDS = {'2.0': COMMAND_V2} diff --git a/quantumclient/tests/unit/lb/__init__.py b/quantumclient/tests/unit/lb/__init__.py new file mode 100644 index 000000000..1668497e7 --- /dev/null +++ b/quantumclient/tests/unit/lb/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2013 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 diff --git a/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py b/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py new file mode 100644 index 000000000..657b24e73 --- /dev/null +++ b/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py @@ -0,0 +1,189 @@ +# Copyright 2013 Mirantis Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ilya Shakhat, Mirantis Inc. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from mox import ContainsKeyValue + +from quantumclient.quantum.v2_0.lb import healthmonitor +from quantumclient.tests.unit import test_cli20 + + +class CLITestV20LbHealthmonitor(test_cli20.CLITestV20Base): + def test_create_healthmonitor_with_all_params(self): + """lb-healthmonitor-create with mandatory params only""" + resource = 'health_monitor' + cmd = healthmonitor.CreateHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + delay = '60' + max_retries = '2' + timeout = '10' + type = 'tcp' + tenant_id = 'my-tenant' + my_id = 'my-id' + args = ['--admin-state-down', + '--delay', delay, + '--max-retries', max_retries, + '--timeout', timeout, + '--type', type, + '--tenant-id', tenant_id] + position_names = ['admin_state_up', 'delay', 'max_retries', 'timeout', + 'type', 'tenant_id'] + position_values = [True, delay, max_retries, timeout, type, + tenant_id] + self._test_create_resource(resource, cmd, '', my_id, args, + position_names, position_values) + + def test_create_healthmonitor_with_all_params(self): + """lb-healthmonitor-create with all params set""" + resource = 'health_monitor' + cmd = healthmonitor.CreateHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + admin_state_up = False + delay = '60' + expected_codes = '200-202,204' + http_method = 'HEAD' + max_retries = '2' + timeout = '10' + type = 'tcp' + tenant_id = 'my-tenant' + url_path = '/health' + my_id = 'my-id' + args = ['--admin-state-down', + '--delay', delay, + '--expected-codes', expected_codes, + '--http-method', http_method, + '--max-retries', max_retries, + '--timeout', timeout, + '--type', type, + '--tenant-id', tenant_id, + '--url-path', url_path] + position_names = ['admin_state_up', 'delay', + 'expected_codes', 'http_method', + 'max_retries', 'timeout', + 'type', 'tenant_id', 'url_path'] + position_values = [admin_state_up, delay, + expected_codes, http_method, + max_retries, timeout, + type, tenant_id, url_path] + self._test_create_resource(resource, cmd, '', my_id, args, + position_names, position_values) + + def test_list_healthmonitors(self): + """lb-healthmonitor-list""" + resources = "health_monitors" + cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, True) + + def test_show_healthmonitor_id(self): + """lb-healthmonitor-show test_id""" + resource = 'health_monitor' + cmd = healthmonitor.ShowHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, ['id']) + + def test_show_healthmonitor_id_name(self): + """lb-healthmonitor-show""" + resource = 'health_monitor' + cmd = healthmonitor.ShowHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id', 'name']) + + def test_update_health_monitor(self): + """lb-healthmonitor-update myid --name myname --tags a b.""" + resource = 'health_monitor' + cmd = healthmonitor.UpdateHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--timeout', '5'], + {'timeout': '5', }) + + def test_delete_healthmonitor(self): + """lb-healthmonitor-delete my-id""" + resource = 'health_monitor' + cmd = healthmonitor.DeleteHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + my_id = 'my-id' + args = [my_id] + self._test_delete_resource(resource, cmd, my_id, args) + + def test_associate_healthmonitor(self): + cmd = healthmonitor.AssociateHealthMonitor( + test_cli20.MyApp(sys.stdout), + None) + resource = 'health_monitor' + health_monitor_id = 'hm-id' + pool_id = 'p_id' + args = [health_monitor_id, pool_id] + + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + + body = {resource: {'id': health_monitor_id}} + result = {resource: {'id': health_monitor_id}, } + result_str = self.client.serialize(result) + + path = getattr(self.client, + "associate_pool_health_monitors_path") % pool_id + return_tup = (test_cli20.MyResp(200), result_str) + self.client.httpclient.request( + test_cli20.end_url(path), 'POST', + body=test_cli20.MyComparator(body, self.client), + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn(return_tup) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser('test_' + resource) + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + + def test_disassociate_healthmonitor(self): + cmd = healthmonitor.DisassociateHealthMonitor( + test_cli20.MyApp(sys.stdout), + None) + resource = 'health_monitor' + health_monitor_id = 'hm-id' + pool_id = 'p_id' + args = [health_monitor_id, pool_id] + + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + + path = getattr(self.client, + "disassociate_pool_health_monitors_path") % \ + {'pool': pool_id, 'health_monitor': health_monitor_id} + return_tup = (test_cli20.MyResp(204), None) + self.client.httpclient.request( + test_cli20.end_url(path), 'DELETE', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn(return_tup) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser('test_' + resource) + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() diff --git a/quantumclient/tests/unit/lb/test_cli20_member.py b/quantumclient/tests/unit/lb/test_cli20_member.py new file mode 100644 index 000000000..7155073a5 --- /dev/null +++ b/quantumclient/tests/unit/lb/test_cli20_member.py @@ -0,0 +1,104 @@ +# Copyright 2013 Mirantis Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ilya Shakhat, Mirantis Inc. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from quantumclient.quantum.v2_0.lb import member +from quantumclient.tests.unit import test_cli20 + + +class CLITestV20LbMember(test_cli20.CLITestV20Base): + def test_create_member(self): + """lb-member-create with mandatory params only""" + resource = 'member' + cmd = member.CreateMember(test_cli20.MyApp(sys.stdout), None) + address = '10.0.0.1' + port = '8080' + tenant_id = 'my-tenant' + my_id = 'my-id' + pool_id = 'pool-id' + args = ['--address', address, '--port', port, + '--tenant-id', tenant_id, pool_id] + position_names = ['address', 'port', 'tenant_id', 'pool_id', + 'admin_state_up'] + position_values = [address, port, tenant_id, pool_id, + True] + self._test_create_resource(resource, cmd, None, my_id, args, + position_names, position_values, + admin_state_up=None) + + def test_create_member_all_params(self): + """lb-member-create with all available params""" + resource = 'member' + cmd = member.CreateMember(test_cli20.MyApp(sys.stdout), None) + address = '10.0.0.1' + admin_state_up = False + port = '8080' + weight = '1' + tenant_id = 'my-tenant' + my_id = 'my-id' + pool_id = 'pool-id' + args = ['--address', address, '--admin-state-down', + '--port', port, '--weight', weight, + '--tenant-id', tenant_id, pool_id] + position_names = ['address', 'admin_state_up', 'port', 'weight', + 'tenant_id', 'pool_id'] + position_values = [address, admin_state_up, port, weight, + tenant_id, pool_id] + self._test_create_resource(resource, cmd, None, my_id, args, + position_names, position_values, + admin_state_up=None) + + def test_list_members(self): + """lb-member-list""" + resources = "members" + cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_show_member_id(self): + """lb-member-show test_id""" + resource = 'member' + cmd = member.ShowMember(test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, ['id']) + + def test_show_member_id_name(self): + """lb-member-show""" + resource = 'member' + cmd = member.ShowMember(test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id', 'name']) + + def test_update_member(self): + """lb-member-update myid --name myname --tags a b.""" + resource = 'member' + cmd = member.UpdateMember(test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'myname', + '--tags', 'a', 'b'], + {'name': 'myname', 'tags': ['a', 'b'], }) + + def test_delete_member(self): + """lb-member-delete my-id""" + resource = 'member' + cmd = member.DeleteMember(test_cli20.MyApp(sys.stdout), None) + my_id = 'my-id' + args = [my_id] + self._test_delete_resource(resource, cmd, my_id, args) diff --git a/quantumclient/tests/unit/lb/test_cli20_pool.py b/quantumclient/tests/unit/lb/test_cli20_pool.py new file mode 100644 index 000000000..ee2f4e3eb --- /dev/null +++ b/quantumclient/tests/unit/lb/test_cli20_pool.py @@ -0,0 +1,145 @@ +# Copyright 2013 Mirantis Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ilya Shakhat, Mirantis Inc. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from mox import ContainsKeyValue + +from quantumclient.quantum.v2_0.lb import pool +from quantumclient.tests.unit import test_cli20 + + +class CLITestV20LbPool(test_cli20.CLITestV20Base): + + def test_create_pool_with_mandatory_params(self): + """lb-pool-create with mandatory params only""" + resource = 'pool' + cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None) + name = 'my-name' + lb_method = 'round-robin' + protocol = 'http' + subnet_id = 'subnet-id' + tenant_id = 'my-tenant' + my_id = 'my-id' + args = ['--lb-method', lb_method, + '--name', name, + '--protocol', protocol, + '--subnet-id', subnet_id, + '--tenant-id', tenant_id] + position_names = ['admin_state_up', 'lb_method', 'name', + 'protocol', 'subnet_id', 'tenant_id'] + position_values = [True, lb_method, name, + protocol, subnet_id, tenant_id] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_pool_with_all_params(self): + """lb-pool-create with all params set""" + resource = 'pool' + cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None) + name = 'my-name' + description = 'my-desc' + lb_method = 'round-robin' + protocol = 'http' + subnet_id = 'subnet-id' + tenant_id = 'my-tenant' + my_id = 'my-id' + args = ['--admin-state-down', + '--description', description, + '--lb-method', lb_method, + '--name', name, + '--protocol', protocol, + '--subnet-id', subnet_id, + '--tenant-id', tenant_id] + position_names = ['admin_state_up', 'description', 'lb_method', 'name', + 'protocol', 'subnet_id', 'tenant_id'] + position_values = [False, description, lb_method, name, + protocol, subnet_id, tenant_id] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_list_pools(self): + """lb-pool-list""" + resources = "pools" + cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_show_pool_id(self): + """lb-pool-show test_id""" + resource = 'pool' + cmd = pool.ShowPool(test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, ['id']) + + def test_show_pool_id_name(self): + """lb-pool-show""" + resource = 'pool' + cmd = pool.ShowPool(test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id', 'name']) + + def test_update_pool(self): + """lb-pool-update myid --name newname --tags a b.""" + resource = 'pool' + cmd = pool.UpdatePool(test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'newname'], + {'name': 'newname', }) + + def test_delete_pool(self): + """lb-pool-delete my-id""" + resource = 'pool' + cmd = pool.DeletePool(test_cli20.MyApp(sys.stdout), None) + my_id = 'my-id' + args = [my_id] + self._test_delete_resource(resource, cmd, my_id, args) + + def test_retrieve_pool_stats(self): + """lb-pool-stats test_id""" + resource = 'pool' + cmd = pool.RetrievePoolStats(test_cli20.MyApp(sys.stdout), None) + my_id = self.test_id + fields = ['bytes_in', 'bytes_out'] + args = ['--fields', 'bytes_in', '--fields', 'bytes_out', my_id] + + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + query = "&".join(["fields=%s" % field for field in fields]) + expected_res = {'stats': {'bytes_in': '1234', 'bytes_out': '4321'}} + resstr = self.client.serialize(expected_res) + path = getattr(self.client, "pool_path_stats") + return_tup = (test_cli20.MyResp(200), resstr) + self.client.httpclient.request( + test_cli20.end_url(path % my_id, query), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + test_cli20.TOKEN)).AndReturn(return_tup) + self.mox.ReplayAll() + + cmd_parser = cmd.get_parser("test_" + resource) + parsed_args = cmd_parser.parse_args(args) + cmd.run(parsed_args) + + self.mox.VerifyAll() + self.mox.UnsetStubs() + _str = self.fake_stdout.make_string() + self.assertTrue('bytes_in' in _str) + self.assertTrue('bytes_out' in _str) diff --git a/quantumclient/tests/unit/lb/test_cli20_vip.py b/quantumclient/tests/unit/lb/test_cli20_vip.py new file mode 100644 index 000000000..8b7d17621 --- /dev/null +++ b/quantumclient/tests/unit/lb/test_cli20_vip.py @@ -0,0 +1,185 @@ +# Copyright 2013 Mirantis Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Ilya Shakhat, Mirantis Inc. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from quantumclient.quantum.v2_0.lb import vip +from quantumclient.tests.unit import test_cli20 + + +class CLITestV20LbVip(test_cli20.CLITestV20Base): + def test_create_vip_with_mandatory_params(self): + """lb-vip-create with all mandatory params""" + resource = 'vip' + cmd = vip.CreateVip(test_cli20.MyApp(sys.stdout), None) + pool_id = 'my-pool-id' + name = 'my-name' + subnet_id = 'subnet-id' + port = '1000' + protocol = 'tcp' + tenant_id = 'my-tenant' + my_id = 'my-id' + args = ['--name', name, + '--port', port, + '--protocol', protocol, + '--subnet-id', subnet_id, + '--tenant-id', tenant_id, + pool_id] + position_names = ['pool_id', 'name', 'port', 'protocol', + 'subnet_id', 'tenant_id'] + position_values = [pool_id, name, port, protocol, + subnet_id, tenant_id] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values, + admin_state_up=True) + + def test_create_vip_with_all_params(self): + """lb-vip-create with all params""" + resource = 'vip' + cmd = vip.CreateVip(test_cli20.MyApp(sys.stdout), None) + pool_id = 'my-pool-id' + name = 'my-name' + description = 'my-desc' + address = '10.0.0.2' + admin_state = False + connection_limit = '1000' + subnet_id = 'subnet-id' + port = '80' + protocol = 'tcp' + tenant_id = 'my-tenant' + my_id = 'my-id' + args = ['--name', name, + '--description', description, + '--address', address, + '--admin-state-down', + '--connection-limit', connection_limit, + '--port', port, + '--protocol', protocol, + '--subnet-id', subnet_id, + '--tenant-id', tenant_id, + pool_id] + position_names = ['pool_id', 'name', 'description', 'address', + 'admin_state_up', 'connection_limit', 'port', + 'protocol', 'subnet_id', + 'tenant_id'] + position_values = [pool_id, name, description, address, + admin_state, connection_limit, port, + protocol, subnet_id, + tenant_id] + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values) + + def test_create_vip_with_session_persistence_params(self): + """lb-vip-create with mandatory and session-persistence params""" + resource = 'vip' + cmd = vip.CreateVip(test_cli20.MyApp(sys.stdout), None) + pool_id = 'my-pool-id' + name = 'my-name' + subnet_id = 'subnet-id' + port = '1000' + protocol = 'tcp' + tenant_id = 'my-tenant' + my_id = 'my-id' + args = ['--name', name, + '--port', port, + '--protocol', protocol, + '--subnet-id', subnet_id, + '--tenant-id', tenant_id, + pool_id, + '--session-persistence', 'type=dict', + 'type=cookie,cookie_name=pie', + '--optional-param', 'any'] + position_names = ['pool_id', 'name', 'port', 'protocol', + 'subnet_id', 'tenant_id', 'optional_param'] + position_values = [pool_id, name, port, protocol, + subnet_id, tenant_id, 'any'] + extra_body = { + 'session_persistence': { + 'type': 'cookie', + 'cookie_name': 'pie', + }, + } + self._test_create_resource(resource, cmd, name, my_id, args, + position_names, position_values, + admin_state_up=True, extra_body=extra_body) + + def test_list_vips(self): + """lb-vip-list""" + resources = "vips" + cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_show_vip_id(self): + """lb-vip-show test_id""" + resource = 'vip' + cmd = vip.ShowVip(test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args, ['id']) + + def test_show_vip_id_name(self): + """lb-vip-show""" + resource = 'vip' + cmd = vip.ShowVip(test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id', 'name']) + + def test_update_vip(self): + """lb-vip-update myid --name myname --tags a b.""" + resource = 'vip' + cmd = vip.UpdateVip(test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'myname', + '--tags', 'a', 'b'], + {'name': 'myname', 'tags': ['a', 'b'], }) + + def test_update_vip_with_session_persistence(self): + resource = 'vip' + cmd = vip.UpdateVip(test_cli20.MyApp(sys.stdout), None) + body = { + 'session_persistence': { + 'type': 'source', + }, + } + args = ['myid', '--session-persistence', 'type=dict', + 'type=source'] + self._test_update_resource(resource, cmd, 'myid', args, body) + + def test_update_vip_with_session_persistence_and_name(self): + resource = 'vip' + cmd = vip.UpdateVip(test_cli20.MyApp(sys.stdout), None) + body = { + 'name': 'newname', + 'session_persistence': { + 'type': 'cookie', + 'cookie_name': 'pie', + }, + } + args = ['myid', '--name', 'newname', + '--session-persistence', 'type=dict', + 'type=cookie,cookie_name=pie'] + self._test_update_resource(resource, cmd, 'myid', args, body) + + def test_delete_vip(self): + """lb-vip-delete my-id""" + resource = 'vip' + cmd = vip.DeleteVip(test_cli20.MyApp(sys.stdout), None) + my_id = 'my-id' + args = [my_id] + self._test_delete_resource(resource, cmd, my_id, args) diff --git a/quantumclient/tests/unit/test_casual_args.py b/quantumclient/tests/unit/test_casual_args.py index c827e0e41..150edec2d 100644 --- a/quantumclient/tests/unit/test_casual_args.py +++ b/quantumclient/tests/unit/test_casual_args.py @@ -53,6 +53,16 @@ def test_badarg(self): self.assertRaises(exceptions.CommandError, quantumV20.parse_args_to_dict, _specs) + def test_badarg_duplicate(self): + _specs = ['--tag=t', '--arg1', 'value1', '--arg1', 'value1'] + self.assertRaises(exceptions.CommandError, + quantumV20.parse_args_to_dict, _specs) + + def test_badarg_early_type_specification(self): + _specs = ['type=dict', 'key=value'] + self.assertRaises(exceptions.CommandError, + quantumV20.parse_args_to_dict, _specs) + def test_arg(self): _specs = ['--tag=t', '--arg1', 'value1'] self.assertEqual('value1', @@ -64,6 +74,12 @@ def test_dict_arg(self): self.assertEqual('value1', arg1['key1']) self.assertEqual('value2', arg1['key2']) + def test_dict_arg_with_attribute_named_type(self): + _specs = ['--tag=t', '--arg1', 'type=dict', 'type=value1,key2=value2'] + arg1 = quantumV20.parse_args_to_dict(_specs)['arg1'] + self.assertEqual('value1', arg1['type']) + self.assertEqual('value2', arg1['key2']) + def test_list_of_dict_arg(self): _specs = ['--tag=t', '--arg1', 'type=dict', 'list=true', 'key1=value1,key2=value2'] diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 0c9e6a0a5..1a97eb1b4 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -135,7 +135,8 @@ def setUp(self): def _test_create_resource(self, resource, cmd, name, myid, args, position_names, position_values, tenant_id=None, - tags=None, admin_state_up=True, shared=False): + tags=None, admin_state_up=True, shared=False, + extra_body=None): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) @@ -151,6 +152,8 @@ def _test_create_resource(self, resource, cmd, body[resource].update({'tags': tags}) if shared: body[resource].update({'shared': shared}) + if extra_body: + body[resource].update(extra_body) for i in xrange(len(position_names)): body[resource].update({position_names[i]: position_values[i]}) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 542f83ff0..305ea1e5f 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -168,6 +168,18 @@ class Client(object): security_group_path = "/security-groups/%s" security_group_rules_path = "/security-group-rules" security_group_rule_path = "/security-group-rules/%s" + vips_path = "/lb/vips" + vip_path = "/lb/vips/%s" + pools_path = "/lb/pools" + pool_path = "/lb/pools/%s" + pool_path_stats = "/lb/pools/%s/stats" + members_path = "/lb/members" + member_path = "/lb/members/%s" + health_monitors_path = "/lb/health_monitors" + health_monitor_path = "/lb/health_monitors/%s" + associate_pool_health_monitors_path = "/lb/pools/%s/health_monitors" + disassociate_pool_health_monitors_path = ( + "/lb/pools/%(pool)s/health_monitors/%(health_monitor)s") @APIParamsCall def get_quotas_tenant(self, **_params): @@ -475,6 +487,175 @@ def show_security_group_rule(self, security_group_rule, **_params): return self.get(self.security_group_rule_path % (security_group_rule), params=_params) + @APIParamsCall + def list_vips(self, **_params): + """ + Fetches a list of all load balancer vips for a tenant + """ + # Pass filters in "params" argument to do_request + return self.get(self.vips_path, params=_params) + + @APIParamsCall + def show_vip(self, vip, **_params): + """ + Fetches information of a certain load balancer vip + """ + return self.get(self.vip_path % (vip), params=_params) + + @APIParamsCall + def create_vip(self, body=None): + """ + Creates a new load balancer vip + """ + return self.post(self.vips_path, body=body) + + @APIParamsCall + def update_vip(self, vip, body=None): + """ + Updates a load balancer vip + """ + return self.put(self.vip_path % (vip), body=body) + + @APIParamsCall + def delete_vip(self, vip): + """ + Deletes the specified load balancer vip + """ + return self.delete(self.vip_path % (vip)) + + @APIParamsCall + def list_pools(self, **_params): + """ + Fetches a list of all load balancer pools for a tenant + """ + # Pass filters in "params" argument to do_request + return self.get(self.pools_path, params=_params) + + @APIParamsCall + def show_pool(self, pool, **_params): + """ + Fetches information of a certain load balancer pool + """ + return self.get(self.pool_path % (pool), params=_params) + + @APIParamsCall + def create_pool(self, body=None): + """ + Creates a new load balancer pool + """ + return self.post(self.pools_path, body=body) + + @APIParamsCall + def update_pool(self, pool, body=None): + """ + Updates a load balancer pool + """ + return self.put(self.pool_path % (pool), body=body) + + @APIParamsCall + def delete_pool(self, pool): + """ + Deletes the specified load balancer pool + """ + return self.delete(self.pool_path % (pool)) + + @APIParamsCall + def retrieve_pool_stats(self, pool, **_params): + """ + Retrieves stats for a certain load balancer pool + """ + return self.get(self.pool_path_stats % (pool), params=_params) + + @APIParamsCall + def list_members(self, **_params): + """ + Fetches a list of all load balancer members for a tenant + """ + # Pass filters in "params" argument to do_request + return self.get(self.members_path, params=_params) + + @APIParamsCall + def show_member(self, member, **_params): + """ + Fetches information of a certain load balancer member + """ + return self.get(self.member_path % (member), params=_params) + + @APIParamsCall + def create_member(self, body=None): + """ + Creates a new load balancer member + """ + return self.post(self.members_path, body=body) + + @APIParamsCall + def update_member(self, member, body=None): + """ + Updates a load balancer member + """ + return self.put(self.member_path % (member), body=body) + + @APIParamsCall + def delete_member(self, member): + """ + Deletes the specified load balancer member + """ + return self.delete(self.member_path % (member)) + + @APIParamsCall + def list_health_monitors(self, **_params): + """ + Fetches a list of all load balancer health monitors for a tenant + """ + # Pass filters in "params" argument to do_request + return self.get(self.health_monitors_path, params=_params) + + @APIParamsCall + def show_health_monitor(self, health_monitor, **_params): + """ + Fetches information of a certain load balancer health monitor + """ + return self.get(self.health_monitor_path % (health_monitor), + params=_params) + + @APIParamsCall + def create_health_monitor(self, body=None): + """ + Creates a new load balancer health monitor + """ + return self.post(self.health_monitors_path, body=body) + + @APIParamsCall + def update_health_monitor(self, health_monitor, body=None): + """ + Updates a load balancer health monitor + """ + return self.put(self.health_monitor_path % (health_monitor), body=body) + + @APIParamsCall + def delete_health_monitor(self, health_monitor): + """ + Deletes the specified load balancer health monitor + """ + return self.delete(self.health_monitor_path % (health_monitor)) + + @APIParamsCall + def associate_health_monitor(self, pool, body): + """ + Associate specified load balancer health monitor and pool + """ + return self.post(self.associate_pool_health_monitors_path % (pool), + body=body) + + @APIParamsCall + def disassociate_health_monitor(self, pool, health_monitor): + """ + Disassociate specified load balancer health monitor and pool + """ + path = (self.disassociate_pool_health_monitors_path % + {'pool': pool, 'health_monitor': health_monitor}) + return self.delete(path) + def __init__(self, **kwargs): """ Initialize a new client for the Quantum v2.0 API. """ super(Client, self).__init__() From fe4b3498b9cb76c2f6255ce52ebe9b657f5cefda Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 8 Jan 2013 06:26:00 +0000 Subject: [PATCH 069/135] Migrate from nose to testr Part of blueprint grizzly-testtools Change-Id: Ia53b0987b1e890a96b190f4b1a47dde4bf84fb6f --- .testr.conf | 4 ++++ HACKING.rst | 19 +++++++++++++++++++ setup.cfg | 8 -------- tools/test-requires | 13 ++++++------- tox.ini | 24 +++++++++++------------- 5 files changed, 40 insertions(+), 28 deletions(-) create mode 100644 .testr.conf diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 000000000..d356fcf1e --- /dev/null +++ b/.testr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./quantumclient/tests $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/HACKING.rst b/HACKING.rst index d99a07f9a..c7643238e 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -185,3 +185,22 @@ For every new feature, unit tests should be created that both test and bug that had no unit test, a new passing unit test should be added. If a submitted bug fix does have a unit test, be sure to add a new one that fails without the patch and passes with the patch. + +Running Tests +------------- +The testing system is based on a combination of tox and testr. The canonical +approach to running tests is to simply run the command `tox`. This will +create virtual environments, populate them with depenedencies and run all of +the tests that OpenStack CI systems run. Behind the scenes, tox is running +`testr run --parallel`, but is set up such that you can supply any additional +testr arguments that are needed to tox. For example, you can run: +`tox -- --analyze-isolation` to cause tox to tell testr to add +--analyze-isolation to its argument list. + +It is also possible to run the tests inside of a virtual environment +you have created, or it is possible that you have all of the dependencies +installed locally already. In this case, you can interact with the testr +command directly. Running `testr run` will run the entire test suite. `testr +run --parallel` will run it in parallel (this is the default incantation tox +uses.) More information about testr can be found at: +http://wiki.openstack.org/testr diff --git a/setup.cfg b/setup.cfg index 394b128fe..11d2c4422 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,11 +2,3 @@ all_files = 1 build-dir = doc/build source-dir = doc/source - -[nosetests] -# NOTE(jkoelker) To run the test suite under nose install the following -# coverage http://pypi.python.org/pypi/coverage -# tissue http://pypi.python.org/pypi/tissue (pep8 checker) -# openstack-nose https://github.com/jkoelker/openstack-nose -verbosity=2 -detailed-errors=1 diff --git a/tools/test-requires b/tools/test-requires index 12622f2e2..b82e805ed 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,12 +1,11 @@ -distribute>=0.6.24 cliff-tablib>=1.0 +coverage +discover +distribute>=0.6.24 fixtures>=0.3.12 mox -nose -nose-exclude -nosexcover -openstack.nose_plugin -nosehtmloutput pep8 +python-subunit sphinx>=1.1.2 -testtools +testrepository>=0.0.13 +testtools>=0.9.22 diff --git a/tox.ini b/tox.ini index fce1185cf..c9d960819 100644 --- a/tox.ini +++ b/tox.ini @@ -3,23 +3,21 @@ envlist = py26,py27,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} - NOSE_WITH_OPENSTACK=1 - NOSE_OPENSTACK_COLOR=1 - NOSE_OPENSTACK_RED=0.05 - NOSE_OPENSTACK_YELLOW=0.025 - NOSE_OPENSTACK_SHOW_ELAPSED=1 - NOSE_OPENSTACK_STDOUT=1 -deps = -r{toxinidir}/tools/test-requires -commands = nosetests {posargs} + LANG=en_US.UTF-8 + LANGUAGE=en_US:en + LC_ALL=C -[tox:jenkins] -downloadcache = ~/cache/pip +deps = -r{toxinidir}/tools/test-requires +commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] commands = pep8 --repeat --show-source --exclude=.venv,.tox,dist,doc . -[testenv:cover] -setenv = NOSE_WITH_COVERAGE=1 - [testenv:venv] commands = {posargs} + +[testenv:cover] +commands = python setup.py testr --coverage --testr-args='{posargs}' + +[tox:jenkins] +downloadcache = ~/cache/pip From 6cc6875b383b7f94e0f2cd6ebafeac3159a112d4 Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Sat, 19 Jan 2013 21:39:40 +0800 Subject: [PATCH 070/135] Remove gettext.install from quantumclient.__init__ fix bug 1097628 Change-Id: Ic6f2dfb4593fa507fb788786616bff6af3cbb896 --- quantumclient/__init__.py | 6 ------ quantumclient/common/__init__.py | 8 ++++++++ quantumclient/common/exceptions.py | 2 ++ quantumclient/shell.py | 2 +- quantumclient/tests/unit/__init__.py | 22 ++++++++++++++++++++++ quantumclient/v2_0/client.py | 1 + 6 files changed, 34 insertions(+), 7 deletions(-) diff --git a/quantumclient/__init__.py b/quantumclient/__init__.py index 5558fdb97..034d66eaf 100644 --- a/quantumclient/__init__.py +++ b/quantumclient/__init__.py @@ -15,9 +15,3 @@ # License for the specific language governing permissions and limitations # under the License. # @author: Tyler Smith, Cisco Systems - -import gettext - - -# gettext must be initialized before any quantumclient imports -gettext.install('quantumclient', unicode=1) diff --git a/quantumclient/common/__init__.py b/quantumclient/common/__init__.py index 7e695ff08..1415c50a3 100644 --- a/quantumclient/common/__init__.py +++ b/quantumclient/common/__init__.py @@ -14,3 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. # @author: Somik Behera, Nicira Networks, Inc. + +import gettext + +t = gettext.translation('quantumclient', fallback=True) + + +def _(msg): + return t.ugettext(msg) diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py index 734a49894..22ac67a49 100644 --- a/quantumclient/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +from quantumclient.common import _ + """ Quantum base exception handling. """ diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 3b8ae5c84..21f64cf8f 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -33,7 +33,6 @@ from quantumclient.common import utils -gettext.install('quantum', unicode=1) VERSION = '2.0' QUANTUM_API_VERSION = '2.0' @@ -564,6 +563,7 @@ def configure_logging(self): def main(argv=sys.argv[1:]): + gettext.install('quantumclient', unicode=1) try: return QuantumShell(QUANTUM_API_VERSION).run(argv) except exc.QuantumClientException: diff --git a/quantumclient/tests/unit/__init__.py b/quantumclient/tests/unit/__init__.py index e69de29bb..353303fb4 100644 --- a/quantumclient/tests/unit/__init__.py +++ b/quantumclient/tests/unit/__init__.py @@ -0,0 +1,22 @@ +# Copyright 2013 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import gettext + +# Because we installed '_' for quantum cli in shell.py, this help unittest +# have definition of '_' +gettext.install('quantumclient', unicode=1) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 305ea1e5f..9c045fc92 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -21,6 +21,7 @@ import urllib from quantumclient.client import HTTPClient +from quantumclient.common import _ from quantumclient.common import exceptions from quantumclient.common.serializer import Serializer From 4f3b47d596b9e777828575fe2012ffe2bcbfa7c9 Mon Sep 17 00:00:00 2001 From: Jason Zhang Date: Tue, 5 Feb 2013 17:51:20 -0800 Subject: [PATCH 071/135] Stored the quantum commands list to the variable. By storing the quantum commands list to the variable, the subclass can depend the quantum shell to parse the command. Fixes: bug 1116837 Change-Id: I0a6f3226d326cf015e262e4ddf364d6f9a91d041 --- quantumclient/shell.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 21f64cf8f..e9bbb24ca 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -232,7 +232,8 @@ def __init__(self, apiversion): description=__doc__.strip(), version=VERSION, command_manager=CommandManager('quantum.cli'), ) - for k, v in COMMANDS[apiversion].items(): + self.commands = COMMANDS + for k, v in self.commands[apiversion].items(): self.command_manager.add_command(k, v) # This is instantiated in initialize_app() only when using @@ -383,7 +384,7 @@ def run(self, argv): if arg == 'bash-completion': self._bash_completion() return 0 - if arg in COMMANDS[self.api_version]: + if arg in self.commands[self.api_version]: if command_pos == -1: command_pos = index elif arg in ('-h', '--help'): From dad11b3e757d459f9afed5b4b65b74a86362dbd5 Mon Sep 17 00:00:00 2001 From: Aaron Rosen Date: Thu, 31 Jan 2013 19:54:11 -0800 Subject: [PATCH 072/135] Allow ability to remove security groups from ports This commit adds the option --no-security-groups to port-update in order to remove security groups from a port. Fixes bug 1112089 Change-Id: I43a5cc2c8b443f2d34fffe66b442925a5ae312ac --- quantumclient/quantum/v2_0/__init__.py | 22 ++++++++++++--------- quantumclient/quantum/v2_0/port.py | 12 +++++++++++ quantumclient/tests/unit/test_cli20_port.py | 8 ++++++++ 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index fbc64ed60..7387e073b 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -254,6 +254,12 @@ def format_output_data(self, data): elif v is None: data[self.resource][k] = '' + def add_known_arguments(self, parser): + pass + + def args2body(self, parsed_args): + return {} + class CreateCommand(QuantumCommand, show.ShowOne): """Create a resource for a given tenant @@ -275,12 +281,6 @@ def get_parser(self, prog_name): self.add_known_arguments(parser) return parser - def add_known_arguments(self, parser): - pass - - def args2body(self, parsed_args): - return {} - def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) quantum_client = self.get_client() @@ -318,6 +318,7 @@ def get_parser(self, prog_name): help='ID or name of %s to update' % self.resource) add_extra_argument(parser, 'value_specs', 'new values for the %s' % self.resource) + self.add_known_arguments(parser) return parser def run(self, parsed_args): @@ -325,16 +326,19 @@ def run(self, parsed_args): quantum_client = self.get_client() quantum_client.format = parsed_args.request_format value_specs = parsed_args.value_specs - if not value_specs: + dict_args = self.args2body(parsed_args).get(self.resource, {}) + dict_specs = parse_args_to_dict(value_specs) + body = {self.resource: dict(dict_args.items() + + dict_specs.items())} + if not body[self.resource]: raise exceptions.CommandError( "Must specify new values to update %s" % self.resource) - data = {self.resource: parse_args_to_dict(value_specs)} _id = find_resourceid_by_name_or_id(quantum_client, self.resource, parsed_args.id) obj_updator = getattr(quantum_client, "update_%s" % self.resource) - obj_updator(_id, data) + obj_updator(_id, body) print >>self.app.stdout, ( _('Updated %(resource)s: %(id)s') % {'id': parsed_args.id, 'resource': self.resource}) diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 27d499803..76468b77b 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -174,3 +174,15 @@ class UpdatePort(UpdateCommand): resource = 'port' log = logging.getLogger(__name__ + '.UpdatePort') + + def add_known_arguments(self, parser): + parser.add_argument( + '--no-security-groups', + default=False, action='store_true', + help='remove security groups from port') + + def args2body(self, parsed_args): + body = {'port': {}} + if parsed_args.no_security_groups: + body['port'].update({'security_groups': None}) + return body diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index 5580dde3f..c4d9dadd9 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -262,6 +262,14 @@ def test_update_port(self): {'name': 'myname', 'tags': ['a', 'b'], } ) + def test_update_port_security_group_off(self): + """Update port: --no-security-groups myid.""" + resource = 'port' + cmd = UpdatePort(MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['--no-security-groups', 'myid'], + {'security_groups': None}) + def test_show_port(self): """Show port: --fields id --fields name myid.""" resource = 'port' From 5117731a6d55651adcd2277fb65b977a1ec8e970 Mon Sep 17 00:00:00 2001 From: gongysh Date: Fri, 18 Jan 2013 23:37:01 +0800 Subject: [PATCH 073/135] Support XML request format blueprint quantum-client-xml Change-Id: I9db8ea7c395909def00d6f25c9c1a98c07fdde68 --- openstack-common.conf | 2 +- quantum_test.sh | 75 ++-- quantumclient/common/constants.py | 40 ++ quantumclient/common/serializer.py | 465 +++++++++++++++----- quantumclient/openstack/common/jsonutils.py | 148 +++++++ quantumclient/openstack/common/timeutils.py | 164 +++++++ quantumclient/v2_0/client.py | 64 +-- tools/pip-requires | 5 +- tox.ini | 2 +- 9 files changed, 789 insertions(+), 176 deletions(-) create mode 100644 quantumclient/common/constants.py create mode 100644 quantumclient/openstack/common/jsonutils.py create mode 100644 quantumclient/openstack/common/timeutils.py diff --git a/openstack-common.conf b/openstack-common.conf index 35d48d06e..d52611484 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=setup +modules=jsonutils,setup,timeutils # The base module to hold the copy of openstack.common base=quantumclient diff --git a/quantum_test.sh b/quantum_test.sh index e19600bec..b42299beb 100755 --- a/quantum_test.sh +++ b/quantum_test.sh @@ -14,11 +14,12 @@ else NOAUTH= fi +FORMAT=" --request-format xml" # test the CRUD of network network=mynet1 -quantum net-create $NOAUTH $network || die "fail to create network $network" -temp=`quantum net-list -- --name $network --fields id | wc -l` +quantum net-create $FORMAT $NOAUTH $network || die "fail to create network $network" +temp=`quantum net-list $FORMAT -- --name $network --fields id | wc -l` echo $temp if [ $temp -ne 5 ]; then die "networks with name $network is not unique or found" @@ -26,102 +27,102 @@ fi network_id=`quantum net-list -- --name $network --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` echo "ID of network with name $network is $network_id" -quantum net-show $network || die "fail to show network $network" -quantum net-show $network_id || die "fail to show network $network_id" +quantum net-show $FORMAT $network || die "fail to show network $network" +quantum net-show $FORMAT $network_id || die "fail to show network $network_id" -quantum net-update $network --admin_state_up False || die "fail to update network $network" -quantum net-update $network_id --admin_state_up True || die "fail to update network $network_id" +quantum net-update $FORMAT $network --admin_state_up False || die "fail to update network $network" +quantum net-update $FORMAT $network_id --admin_state_up True || die "fail to update network $network_id" -quantum net-list -c id -- --id fakeid || die "fail to list networks with column selection on empty list" +quantum net-list $FORMAT -c id -- --id fakeid || die "fail to list networks with column selection on empty list" # test the CRUD of subnet subnet=mysubnet1 cidr=10.0.1.3/24 -quantum subnet-create $NOAUTH $network $cidr --name $subnet || die "fail to create subnet $subnet" -tempsubnet=`quantum subnet-list -- --name $subnet --fields id | wc -l` +quantum subnet-create $FORMAT $NOAUTH $network $cidr --name $subnet || die "fail to create subnet $subnet" +tempsubnet=`quantum subnet-list $FORMAT -- --name $subnet --fields id | wc -l` echo $tempsubnet if [ $tempsubnet -ne 5 ]; then die "subnets with name $subnet is not unique or found" fi -subnet_id=`quantum subnet-list -- --name $subnet --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` +subnet_id=`quantum subnet-list $FORMAT -- --name $subnet --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` echo "ID of subnet with name $subnet is $subnet_id" -quantum subnet-show $subnet || die "fail to show subnet $subnet" -quantum subnet-show $subnet_id || die "fail to show subnet $subnet_id" +quantum subnet-show $FORMAT $subnet || die "fail to show subnet $subnet" +quantum subnet-show $FORMAT $subnet_id || die "fail to show subnet $subnet_id" -quantum subnet-update $subnet --dns_namesevers host1 || die "fail to update subnet $subnet" -quantum subnet-update $subnet_id --dns_namesevers host2 || die "fail to update subnet $subnet_id" +quantum subnet-update $FORMAT $subnet --dns_namesevers host1 || die "fail to update subnet $subnet" +quantum subnet-update $FORMAT $subnet_id --dns_namesevers host2 || die "fail to update subnet $subnet_id" # test the crud of ports port=myport1 -quantum port-create $NOAUTH $network --name $port || die "fail to create port $port" -tempport=`quantum port-list -- --name $port --fields id | wc -l` +quantum port-create $FORMAT $NOAUTH $network --name $port || die "fail to create port $port" +tempport=`quantum port-list $FORMAT -- --name $port --fields id | wc -l` echo $tempport if [ $tempport -ne 5 ]; then die "ports with name $port is not unique or found" fi -port_id=`quantum port-list -- --name $port --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` +port_id=`quantum port-list $FORMAT -- --name $port --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` echo "ID of port with name $port is $port_id" -quantum port-show $port || die "fail to show port $port" -quantum port-show $port_id || die "fail to show port $port_id" +quantum port-show $FORMAT $port || die "fail to show port $port" +quantum port-show $FORMAT $port_id || die "fail to show port $port_id" -quantum port-update $port --device_id deviceid1 || die "fail to update port $port" -quantum port-update $port_id --device_id deviceid2 || die "fail to update port $port_id" +quantum port-update $FORMAT $port --device_id deviceid1 || die "fail to update port $port" +quantum port-update $FORMAT $port_id --device_id deviceid2 || die "fail to update port $port_id" # test quota commands RUD DEFAULT_NETWORKS=10 DEFAULT_PORTS=50 tenant_id=tenant_a tenant_id_b=tenant_b -quantum quota-update --tenant_id $tenant_id --network 30 || die "fail to update quota for tenant $tenant_id" -quantum quota-update --tenant_id $tenant_id_b --network 20 || die "fail to update quota for tenant $tenant_id" -networks=`quantum quota-list -c network -c tenant_id | grep $tenant_id | awk '{print $2}'` +quantum quota-update $FORMAT --tenant_id $tenant_id --network 30 || die "fail to update quota for tenant $tenant_id" +quantum quota-update $FORMAT --tenant_id $tenant_id_b --network 20 || die "fail to update quota for tenant $tenant_id" +networks=`quantum quota-list $FORMAT -c network -c tenant_id | grep $tenant_id | awk '{print $2}'` if [ $networks -ne 30 ]; then die "networks quota should be 30" fi -networks=`quantum quota-list -c network -c tenant_id | grep $tenant_id_b | awk '{print $2}'` +networks=`quantum quota-list $FORMAT -c network -c tenant_id | grep $tenant_id_b | awk '{print $2}'` if [ $networks -ne 20 ]; then die "networks quota should be 20" fi -networks=`quantum quota-show --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` +networks=`quantum quota-show $FORMAT --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` if [ $networks -ne 30 ]; then die "networks quota should be 30" fi -quantum quota-delete --tenant_id $tenant_id || die "fail to delete quota for tenant $tenant_id" -networks=`quantum quota-show --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` +quantum quota-delete $FORMAT --tenant_id $tenant_id || die "fail to delete quota for tenant $tenant_id" +networks=`quantum quota-show $FORMAT --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` if [ $networks -ne $DEFAULT_NETWORKS ]; then die "networks quota should be $DEFAULT_NETWORKS" fi # update self if [ "t$NOAUTH" = "t" ]; then # with auth - quantum quota-update --port 99 || die "fail to update quota for self" - ports=`quantum quota-show | grep port | awk -F'|' '{print $3}'` + quantum quota-update $FORMAT --port 99 || die "fail to update quota for self" + ports=`quantum quota-show $FORMAT | grep port | awk -F'|' '{print $3}'` if [ $ports -ne 99 ]; then die "ports quota should be 99" fi - ports=`quantum quota-list -c port | grep 99 | awk '{print $2}'` + ports=`quantum quota-list $FORMAT -c port | grep 99 | awk '{print $2}'` if [ $ports -ne 99 ]; then die "ports quota should be 99" fi - quantum quota-delete || die "fail to delete quota for tenant self" - ports=`quantum quota-show | grep port | awk -F'|' '{print $3}'` + quantum quota-delete $FORMAT || die "fail to delete quota for tenant self" + ports=`quantum quota-show $FORMAT | grep port | awk -F'|' '{print $3}'` if [ $ports -ne $DEFAULT_PORTS ]; then die "ports quota should be $DEFAULT_PORTS" fi else # without auth - quantum quota-update --port 100 + quantum quota-update $FORMAT --port 100 if [ $? -eq 0 ]; then die "without valid context on server, quota update command should fail." fi - quantum quota-show + quantum quota-show $FORMAT if [ $? -eq 0 ]; then die "without valid context on server, quota show command should fail." fi - quantum quota-delete + quantum quota-delete $FORMAT if [ $? -eq 0 ]; then die "without valid context on server, quota delete command should fail." fi - quantum quota-list || die "fail to update quota for self" + quantum quota-list $FORMAT || die "fail to update quota for self" fi diff --git a/quantumclient/common/constants.py b/quantumclient/common/constants.py new file mode 100644 index 000000000..572baa941 --- /dev/null +++ b/quantumclient/common/constants.py @@ -0,0 +1,40 @@ +# Copyright (c) 2012 OpenStack, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +EXT_NS = '_extension_ns' +XML_NS_V20 = 'http://openstack.org/quantum/api/v2.0' +XSI_NAMESPACE = "http://www.w3.org/2001/XMLSchema-instance" +XSI_ATTR = "xsi:nil" +XSI_NIL_ATTR = "xmlns:xsi" +TYPE_XMLNS = "xmlns:quantum" +TYPE_ATTR = "quantum:type" +VIRTUAL_ROOT_KEY = "_v_root" + +TYPE_BOOL = "bool" +TYPE_INT = "int" +TYPE_LONG = "long" +TYPE_FLOAT = "float" +TYPE_LIST = "list" +TYPE_DICT = "dict" + +PLURALS = {'networks': 'network', + 'ports': 'port', + 'subnets': 'subnet', + 'dns_nameservers': 'dns_nameserver', + 'host_routes': 'host_route', + 'allocation_pools': 'allocation_pool', + 'fixed_ips': 'fixed_ip', + 'extensions': 'extension'} diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py index 6e914673a..7eba93069 100644 --- a/quantumclient/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -1,7 +1,353 @@ -from xml.dom import minidom +# Copyright 2013 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +### +### Codes from quantum wsgi +### + +import logging + +from xml.etree import ElementTree as etree +from xml.parsers import expat + +from quantumclient.common import constants from quantumclient.common import exceptions as exception -from quantumclient.common import utils +from quantumclient.openstack.common import jsonutils + +LOG = logging.getLogger(__name__) + + +class ActionDispatcher(object): + """Maps method name to local methods through action name.""" + + def dispatch(self, *args, **kwargs): + """Find and call local method.""" + action = kwargs.pop('action', 'default') + action_method = getattr(self, str(action), self.default) + return action_method(*args, **kwargs) + + def default(self, data): + raise NotImplementedError() + + +class DictSerializer(ActionDispatcher): + """Default request body serialization""" + + def serialize(self, data, action='default'): + return self.dispatch(data, action=action) + + def default(self, data): + return "" + + +class JSONDictSerializer(DictSerializer): + """Default JSON request body serialization""" + + def default(self, data): + return jsonutils.dumps(data) + + +class XMLDictSerializer(DictSerializer): + + def __init__(self, metadata=None, xmlns=None): + """ + :param metadata: information needed to deserialize xml into + a dictionary. + :param xmlns: XML namespace to include with serialized xml + """ + super(XMLDictSerializer, self).__init__() + self.metadata = metadata or {} + if not xmlns: + xmlns = self.metadata.get('xmlns') + if not xmlns: + xmlns = constants.XML_NS_V20 + self.xmlns = xmlns + + def default(self, data): + # We expect data to contain a single key which is the XML root or + # non root + try: + key_len = data and len(data.keys()) or 0 + if (key_len == 1): + root_key = data.keys()[0] + root_value = data[root_key] + else: + root_key = constants.VIRTUAL_ROOT_KEY + root_value = data + doc = etree.Element("_temp_root") + used_prefixes = [] + self._to_xml_node(doc, self.metadata, root_key, + root_value, used_prefixes) + return self.to_xml_string(list(doc)[0], used_prefixes) + except AttributeError as e: + LOG.exception(str(e)) + return '' + + def __call__(self, data): + # Provides a migration path to a cleaner WSGI layer, this + # "default" stuff and extreme extensibility isn't being used + # like originally intended + return self.default(data) + + def to_xml_string(self, node, used_prefixes, has_atom=False): + self._add_xmlns(node, used_prefixes, has_atom) + return etree.tostring(node, encoding='UTF-8') + + #NOTE (ameade): the has_atom should be removed after all of the + # xml serializers and view builders have been updated to the current + # spec that required all responses include the xmlns:atom, the has_atom + # flag is to prevent current tests from breaking + def _add_xmlns(self, node, used_prefixes, has_atom=False): + node.set('xmlns', self.xmlns) + node.set(constants.TYPE_XMLNS, self.xmlns) + if has_atom: + node.set('xmlns:atom', "http://www.w3.org/2005/Atom") + node.set(constants.XSI_NIL_ATTR, constants.XSI_NAMESPACE) + ext_ns = self.metadata.get(constants.EXT_NS, {}) + for prefix in used_prefixes: + if prefix in ext_ns: + node.set('xmlns:' + prefix, ext_ns[prefix]) + + def _to_xml_node(self, parent, metadata, nodename, data, used_prefixes): + """Recursive method to convert data members to XML nodes.""" + result = etree.SubElement(parent, nodename) + if ":" in nodename: + used_prefixes.append(nodename.split(":", 1)[0]) + #TODO(bcwaldon): accomplish this without a type-check + if isinstance(data, list): + if not data: + result.set( + constants.TYPE_ATTR, + constants.TYPE_LIST) + return result + singular = metadata.get('plurals', {}).get(nodename, None) + if singular is None: + if nodename.endswith('s'): + singular = nodename[:-1] + else: + singular = 'item' + for item in data: + self._to_xml_node(result, metadata, singular, item, + used_prefixes) + #TODO(bcwaldon): accomplish this without a type-check + elif isinstance(data, dict): + if not data: + result.set( + constants.TYPE_ATTR, + constants.TYPE_DICT) + return result + attrs = metadata.get('attributes', {}).get(nodename, {}) + for k, v in data.items(): + if k in attrs: + result.set(k, str(v)) + else: + self._to_xml_node(result, metadata, k, v, + used_prefixes) + elif data is None: + result.set(constants.XSI_ATTR, 'true') + else: + if isinstance(data, bool): + result.set( + constants.TYPE_ATTR, + constants.TYPE_BOOL) + elif isinstance(data, int): + result.set( + constants.TYPE_ATTR, + constants.TYPE_INT) + elif isinstance(data, long): + result.set( + constants.TYPE_ATTR, + constants.TYPE_LONG) + elif isinstance(data, float): + result.set( + constants.TYPE_ATTR, + constants.TYPE_FLOAT) + LOG.debug(_("Data %(data)s type is %(type)s"), + {'data': data, + 'type': type(data)}) + result.text = str(data) + return result + + def _create_link_nodes(self, xml_doc, links): + link_nodes = [] + for link in links: + link_node = xml_doc.createElement('atom:link') + link_node.set('rel', link['rel']) + link_node.set('href', link['href']) + if 'type' in link: + link_node.set('type', link['type']) + link_nodes.append(link_node) + return link_nodes + + +class TextDeserializer(ActionDispatcher): + """Default request body deserialization""" + + def deserialize(self, datastring, action='default'): + return self.dispatch(datastring, action=action) + + def default(self, datastring): + return {} + + +class JSONDeserializer(TextDeserializer): + + def _from_json(self, datastring): + try: + return jsonutils.loads(datastring) + except ValueError: + msg = _("Cannot understand JSON") + raise exception.MalformedRequestBody(reason=msg) + + def default(self, datastring): + return {'body': self._from_json(datastring)} + + +class XMLDeserializer(TextDeserializer): + + def __init__(self, metadata=None): + """ + :param metadata: information needed to deserialize xml into + a dictionary. + """ + super(XMLDeserializer, self).__init__() + self.metadata = metadata or {} + xmlns = self.metadata.get('xmlns') + if not xmlns: + xmlns = constants.XML_NS_V20 + self.xmlns = xmlns + + def _get_key(self, tag): + tags = tag.split("}", 1) + if len(tags) == 2: + ns = tags[0][1:] + bare_tag = tags[1] + ext_ns = self.metadata.get(constants.EXT_NS, {}) + if ns == self.xmlns: + return bare_tag + for prefix, _ns in ext_ns.items(): + if ns == _ns: + return prefix + ":" + bare_tag + else: + return tag + + def _from_xml(self, datastring): + if datastring is None: + return None + plurals = set(self.metadata.get('plurals', {})) + try: + node = etree.fromstring(datastring) + result = self._from_xml_node(node, plurals) + root_tag = self._get_key(node.tag) + if root_tag == constants.VIRTUAL_ROOT_KEY: + return result + else: + return {root_tag: result} + except Exception as e: + parseError = False + # Python2.7 + if (hasattr(etree, 'ParseError') and + isinstance(e, getattr(etree, 'ParseError'))): + parseError = True + # Python2.6 + elif isinstance(e, expat.ExpatError): + parseError = True + if parseError: + msg = _("Cannot understand XML") + raise exception.MalformedRequestBody(reason=msg) + else: + raise + + def _from_xml_node(self, node, listnames): + """Convert a minidom node to a simple Python type. + + :param listnames: list of XML node names whose subnodes should + be considered list items. + + """ + attrNil = node.get(str(etree.QName(constants.XSI_NAMESPACE, "nil"))) + attrType = node.get(str(etree.QName( + self.metadata.get('xmlns'), "type"))) + if (attrNil and attrNil.lower() == 'true'): + return None + elif not len(node) and not node.text: + if (attrType and attrType == constants.TYPE_DICT): + return {} + elif (attrType and attrType == constants.TYPE_LIST): + return [] + else: + return '' + elif (len(node) == 0 and node.text): + converters = {constants.TYPE_BOOL: + lambda x: x.lower() == 'true', + constants.TYPE_INT: + lambda x: int(x), + constants.TYPE_LONG: + lambda x: long(x), + constants.TYPE_FLOAT: + lambda x: float(x)} + if attrType and attrType in converters: + return converters[attrType](node.text) + else: + return node.text + elif self._get_key(node.tag) in listnames: + return [self._from_xml_node(n, listnames) for n in node] + else: + result = dict() + for attr in node.keys(): + if (attr == 'xmlns' or + attr.startswith('xmlns:') or + attr == constants.XSI_ATTR or + attr == constants.TYPE_ATTR): + continue + result[self._get_key(attr)] = node.get[attr] + children = list(node) + for child in children: + result[self._get_key(child.tag)] = self._from_xml_node( + child, listnames) + return result + + def find_first_child_named(self, parent, name): + """Search a nodes children for the first child with a given name""" + for node in parent.childNodes: + if node.nodeName == name: + return node + return None + + def find_children_named(self, parent, name): + """Return all of a nodes children who have the given name""" + for node in parent.childNodes: + if node.nodeName == name: + yield node + + def extract_text(self, node): + """Get the text field contained by the given node""" + if len(node.childNodes) == 1: + child = node.childNodes[0] + if child.nodeType == child.TEXT_NODE: + return child.nodeValue + return "" + + def default(self, datastring): + return {'body': self._from_xml(datastring)} + + def __call__(self, datastring): + # Adding a migration path to allow us to remove unncessary classes + return self.default(datastring) # NOTE(maru): this class is duplicated from quantum.wsgi @@ -20,8 +366,8 @@ def __init__(self, metadata=None, default_xmlns=None): def _get_serialize_handler(self, content_type): handlers = { - 'application/json': self._to_json, - 'application/xml': self._to_xml, + 'application/json': JSONDictSerializer(), + 'application/xml': XMLDictSerializer(self.metadata), } try: @@ -31,7 +377,7 @@ def _get_serialize_handler(self, content_type): def serialize(self, data, content_type): """Serialize a dictionary into the specified content type.""" - return self._get_serialize_handler(content_type)(data) + return self._get_serialize_handler(content_type).serialize(data) def deserialize(self, datastring, content_type): """Deserialize a string to a dictionary. @@ -39,117 +385,16 @@ def deserialize(self, datastring, content_type): The string must be in the format of a supported MIME type. """ - try: - return self.get_deserialize_handler(content_type)(datastring) - except Exception: - raise exception.MalformedResponseBody( - reason="Unable to deserialize response body") + return self.get_deserialize_handler(content_type).deserialize( + datastring) def get_deserialize_handler(self, content_type): handlers = { - 'application/json': self._from_json, - 'application/xml': self._from_xml, + 'application/json': JSONDeserializer(), + 'application/xml': XMLDeserializer(self.metadata), } try: return handlers[content_type] except Exception: raise exception.InvalidContentType(content_type=content_type) - - def _from_json(self, datastring): - return utils.loads(datastring) - - def _from_xml(self, datastring): - xmldata = self.metadata.get('application/xml', {}) - plurals = set(xmldata.get('plurals', {})) - node = minidom.parseString(datastring).childNodes[0] - return {node.nodeName: self._from_xml_node(node, plurals)} - - def _from_xml_node(self, node, listnames): - """Convert a minidom node to a simple Python type. - - listnames is a collection of names of XML nodes whose subnodes should - be considered list items. - - """ - if len(node.childNodes) == 1 and node.childNodes[0].nodeType == 3: - return node.childNodes[0].nodeValue - elif node.nodeName in listnames: - return [self._from_xml_node(n, listnames) - for n in node.childNodes if n.nodeType != node.TEXT_NODE] - else: - result = dict() - for attr in node.attributes.keys(): - result[attr] = node.attributes[attr].nodeValue - for child in node.childNodes: - if child.nodeType != node.TEXT_NODE: - result[child.nodeName] = self._from_xml_node(child, - listnames) - return result - - def _to_json(self, data): - return utils.dumps(data) - - def _to_xml(self, data): - metadata = self.metadata.get('application/xml', {}) - # We expect data to contain a single key which is the XML root. - root_key = data.keys()[0] - doc = minidom.Document() - node = self._to_xml_node(doc, metadata, root_key, data[root_key]) - - xmlns = node.getAttribute('xmlns') - if not xmlns and self.default_xmlns: - node.setAttribute('xmlns', self.default_xmlns) - - return node.toprettyxml(indent='', newl='') - - def _to_xml_node(self, doc, metadata, nodename, data): - """Recursive method to convert data members to XML nodes.""" - result = doc.createElement(nodename) - - # Set the xml namespace if one is specified - # TODO(justinsb): We could also use prefixes on the keys - xmlns = metadata.get('xmlns', None) - if xmlns: - result.setAttribute('xmlns', xmlns) - if type(data) is list: - collections = metadata.get('list_collections', {}) - if nodename in collections: - metadata = collections[nodename] - for item in data: - node = doc.createElement(metadata['item_name']) - node.setAttribute(metadata['item_key'], str(item)) - result.appendChild(node) - return result - singular = metadata.get('plurals', {}).get(nodename, None) - if singular is None: - if nodename.endswith('s'): - singular = nodename[:-1] - else: - singular = 'item' - for item in data: - node = self._to_xml_node(doc, metadata, singular, item) - result.appendChild(node) - elif type(data) is dict: - collections = metadata.get('dict_collections', {}) - if nodename in collections: - metadata = collections[nodename] - for k, v in data.items(): - node = doc.createElement(metadata['item_name']) - node.setAttribute(metadata['item_key'], str(k)) - text = doc.createTextNode(str(v)) - node.appendChild(text) - result.appendChild(node) - return result - attrs = metadata.get('attributes', {}).get(nodename, {}) - for k, v in data.items(): - if k in attrs: - result.setAttribute(k, str(v)) - else: - node = self._to_xml_node(doc, metadata, k, v) - result.appendChild(node) - else: - # Type is atom. - node = doc.createTextNode(str(data)) - result.appendChild(node) - return result diff --git a/quantumclient/openstack/common/jsonutils.py b/quantumclient/openstack/common/jsonutils.py new file mode 100644 index 000000000..ad76e0655 --- /dev/null +++ b/quantumclient/openstack/common/jsonutils.py @@ -0,0 +1,148 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# Copyright 2011 Justin Santa Barbara +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +''' +JSON related utilities. + +This module provides a few things: + + 1) A handy function for getting an object down to something that can be + JSON serialized. See to_primitive(). + + 2) Wrappers around loads() and dumps(). The dumps() wrapper will + automatically use to_primitive() for you if needed. + + 3) This sets up anyjson to use the loads() and dumps() wrappers if anyjson + is available. +''' + + +import datetime +import inspect +import itertools +import json +import xmlrpclib + +from quantumclient.openstack.common import timeutils + + +def to_primitive(value, convert_instances=False, level=0): + """Convert a complex object into primitives. + + Handy for JSON serialization. We can optionally handle instances, + but since this is a recursive function, we could have cyclical + data structures. + + To handle cyclical data structures we could track the actual objects + visited in a set, but not all objects are hashable. Instead we just + track the depth of the object inspections and don't go too deep. + + Therefore, convert_instances=True is lossy ... be aware. + + """ + nasty = [inspect.ismodule, inspect.isclass, inspect.ismethod, + inspect.isfunction, inspect.isgeneratorfunction, + inspect.isgenerator, inspect.istraceback, inspect.isframe, + inspect.iscode, inspect.isbuiltin, inspect.isroutine, + inspect.isabstract] + for test in nasty: + if test(value): + return unicode(value) + + # value of itertools.count doesn't get caught by inspects + # above and results in infinite loop when list(value) is called. + if type(value) == itertools.count: + return unicode(value) + + # FIXME(vish): Workaround for LP bug 852095. Without this workaround, + # tests that raise an exception in a mocked method that + # has a @wrap_exception with a notifier will fail. If + # we up the dependency to 0.5.4 (when it is released) we + # can remove this workaround. + if getattr(value, '__module__', None) == 'mox': + return 'mock' + + if level > 3: + return '?' + + # The try block may not be necessary after the class check above, + # but just in case ... + try: + # It's not clear why xmlrpclib created their own DateTime type, but + # for our purposes, make it a datetime type which is explicitly + # handled + if isinstance(value, xmlrpclib.DateTime): + value = datetime.datetime(*tuple(value.timetuple())[:6]) + + if isinstance(value, (list, tuple)): + o = [] + for v in value: + o.append(to_primitive(v, convert_instances=convert_instances, + level=level)) + return o + elif isinstance(value, dict): + o = {} + for k, v in value.iteritems(): + o[k] = to_primitive(v, convert_instances=convert_instances, + level=level) + return o + elif isinstance(value, datetime.datetime): + return timeutils.strtime(value) + elif hasattr(value, 'iteritems'): + return to_primitive(dict(value.iteritems()), + convert_instances=convert_instances, + level=level + 1) + elif hasattr(value, '__iter__'): + return to_primitive(list(value), + convert_instances=convert_instances, + level=level) + elif convert_instances and hasattr(value, '__dict__'): + # Likely an instance of something. Watch for cycles. + # Ignore class member vars. + return to_primitive(value.__dict__, + convert_instances=convert_instances, + level=level + 1) + else: + return value + except TypeError: + # Class objects are tricky since they may define something like + # __iter__ defined but it isn't callable as list(). + return unicode(value) + + +def dumps(value, default=to_primitive, **kwargs): + return json.dumps(value, default=default, **kwargs) + + +def loads(s): + return json.loads(s) + + +def load(s): + return json.load(s) + + +try: + import anyjson +except ImportError: + pass +else: + anyjson._modules.append((__name__, 'dumps', TypeError, + 'loads', ValueError, 'load')) + anyjson.force_implementation(__name__) diff --git a/quantumclient/openstack/common/timeutils.py b/quantumclient/openstack/common/timeutils.py new file mode 100644 index 000000000..0f346087f --- /dev/null +++ b/quantumclient/openstack/common/timeutils.py @@ -0,0 +1,164 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Time related utilities and helper functions. +""" + +import calendar +import datetime + +import iso8601 + + +TIME_FORMAT = "%Y-%m-%dT%H:%M:%S" +PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" + + +def isotime(at=None): + """Stringify time in ISO 8601 format""" + if not at: + at = utcnow() + str = at.strftime(TIME_FORMAT) + tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' + str += ('Z' if tz == 'UTC' else tz) + return str + + +def parse_isotime(timestr): + """Parse time from ISO 8601 format""" + try: + return iso8601.parse_date(timestr) + except iso8601.ParseError as e: + raise ValueError(e.message) + except TypeError as e: + raise ValueError(e.message) + + +def strtime(at=None, fmt=PERFECT_TIME_FORMAT): + """Returns formatted utcnow.""" + if not at: + at = utcnow() + return at.strftime(fmt) + + +def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT): + """Turn a formatted time back into a datetime.""" + return datetime.datetime.strptime(timestr, fmt) + + +def normalize_time(timestamp): + """Normalize time in arbitrary timezone to UTC naive object""" + offset = timestamp.utcoffset() + if offset is None: + return timestamp + return timestamp.replace(tzinfo=None) - offset + + +def is_older_than(before, seconds): + """Return True if before is older than seconds.""" + if isinstance(before, basestring): + before = parse_strtime(before).replace(tzinfo=None) + return utcnow() - before > datetime.timedelta(seconds=seconds) + + +def is_newer_than(after, seconds): + """Return True if after is newer than seconds.""" + if isinstance(after, basestring): + after = parse_strtime(after).replace(tzinfo=None) + return after - utcnow() > datetime.timedelta(seconds=seconds) + + +def utcnow_ts(): + """Timestamp version of our utcnow function.""" + return calendar.timegm(utcnow().timetuple()) + + +def utcnow(): + """Overridable version of utils.utcnow.""" + if utcnow.override_time: + try: + return utcnow.override_time.pop(0) + except AttributeError: + return utcnow.override_time + return datetime.datetime.utcnow() + + +utcnow.override_time = None + + +def set_time_override(override_time=datetime.datetime.utcnow()): + """ + Override utils.utcnow to return a constant time or a list thereof, + one at a time. + """ + utcnow.override_time = override_time + + +def advance_time_delta(timedelta): + """Advance overridden time using a datetime.timedelta.""" + assert(not utcnow.override_time is None) + try: + for dt in utcnow.override_time: + dt += timedelta + except TypeError: + utcnow.override_time += timedelta + + +def advance_time_seconds(seconds): + """Advance overridden time by seconds.""" + advance_time_delta(datetime.timedelta(0, seconds)) + + +def clear_time_override(): + """Remove the overridden time.""" + utcnow.override_time = None + + +def marshall_now(now=None): + """Make an rpc-safe datetime with microseconds. + + Note: tzinfo is stripped, but not required for relative times.""" + if not now: + now = utcnow() + return dict(day=now.day, month=now.month, year=now.year, hour=now.hour, + minute=now.minute, second=now.second, + microsecond=now.microsecond) + + +def unmarshall_time(tyme): + """Unmarshall a datetime dict.""" + return datetime.datetime(day=tyme['day'], + month=tyme['month'], + year=tyme['year'], + hour=tyme['hour'], + minute=tyme['minute'], + second=tyme['second'], + microsecond=tyme['microsecond']) + + +def delta_seconds(before, after): + """ + Compute the difference in seconds between two date, time, or + datetime objects (as a float, to microsecond resolution). + """ + delta = after - before + try: + return delta.total_seconds() + except AttributeError: + return ((delta.days * 24 * 3600) + delta.seconds + + float(delta.microseconds) / (10 ** 6)) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 9c045fc92..202e1a7f2 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -22,8 +22,9 @@ from quantumclient.client import HTTPClient from quantumclient.common import _ +from quantumclient.common import constants from quantumclient.common import exceptions -from quantumclient.common.serializer import Serializer +from quantumclient.common import serializer _logger = logging.getLogger(__name__) @@ -139,18 +140,6 @@ class Client(object): """ - #Metadata for deserializing xml - _serialization_metadata = { - "application/xml": { - "attributes": { - "network": ["id", "name"], - "port": ["id", "mac_address"], - "subnet": ["id", "prefix"]}, - "plurals": { - "networks": "network", - "ports": "port", - "subnets": "subnet", }, }, } - networks_path = "/networks" network_path = "/networks/%s" ports_path = "/ports" @@ -182,6 +171,33 @@ class Client(object): disassociate_pool_health_monitors_path = ( "/lb/pools/%(pool)s/health_monitors/%(health_monitor)s") + # API has no way to report plurals, so we have to hard code them + EXTED_PLURALS = {'routers': 'router', + 'floatingips': 'floatingip', + 'service_types': 'service_type', + 'service_definitions': 'service_definition', + 'security_groups': 'security_group', + 'security_group_rules': 'security_group_rule', + 'vips': 'vip', + 'pools': 'pool', + 'members': 'member', + 'health_monitors': 'health_monitor', + 'quotas': 'quota', + } + + def get_attr_metadata(self): + if self.format == 'json': + return {} + old_request_format = self.format + self.format = 'json' + exts = self.list_extensions()['extensions'] + self.format = old_request_format + ns = dict([(ext['alias'], ext['namespace']) for ext in exts]) + self.EXTED_PLURALS.update(constants.PLURALS) + return {'plurals': self.EXTED_PLURALS, + 'xmlns': constants.XML_NS_V20, + constants.EXT_NS: ns} + @APIParamsCall def get_quotas_tenant(self, **_params): """Fetch tenant info in server's context for @@ -669,16 +685,14 @@ def __init__(self, **kwargs): def _handle_fault_response(self, status_code, response_body): # Create exception with HTTP status code and message - error_message = response_body - _logger.debug("Error message: %s", error_message) + _logger.debug("Error message: %s", response_body) # Add deserialized error message to exception arguments try: - des_error_body = Serializer().deserialize(error_message, - self.content_type()) + des_error_body = self.deserialize(response_body, status_code) except: # If unable to deserialized body it is probably not a # Quantum error - des_error_body = {'message': error_message} + des_error_body = {'message': response_body} # Raise the appropriate exception exception_handler_v20(status_code, des_error_body) @@ -719,7 +733,8 @@ def serialize(self, data): if data is None: return None elif type(data) is dict: - return Serializer().serialize(data, self.content_type()) + return serializer.Serializer( + self.get_attr_metadata()).serialize(data, self.content_type()) else: raise Exception("unable to serialize object of type = '%s'" % type(data)) @@ -730,17 +745,16 @@ def deserialize(self, data, status_code): """ if status_code == 204: return data - return Serializer(self._serialization_metadata).deserialize( - data, self.content_type()) + return serializer.Serializer(self.get_attr_metadata()).deserialize( + data, self.content_type())['body'] - def content_type(self, format=None): + def content_type(self, _format=None): """ Returns the mime-type for either 'xml' or 'json'. Defaults to the currently set format """ - if not format: - format = self.format - return "application/%s" % (format) + _format = _format or self.format + return "application/%s" % (_format) def retry_request(self, method, action, body=None, headers=None, params=None): diff --git a/tools/pip-requires b/tools/pip-requires index 2deb910ce..59ee8fa34 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,6 +1,7 @@ -cliff>=1.2.1 argparse +cliff>=1.2.1 httplib2 +iso8601 prettytable>=0.6.0 -simplejson pyparsing>=1.5.6,<2.0 +simplejson diff --git a/tox.ini b/tox.ini index c9d960819..dbb367d4d 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ deps = -r{toxinidir}/tools/test-requires commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] -commands = pep8 --repeat --show-source --exclude=.venv,.tox,dist,doc . +commands = pep8 --repeat --show-source --ignore=E125 --exclude=.venv,.tox,dist,doc . [testenv:venv] commands = {posargs} From 82b9697b04b8c4e7751d0dc757b4446a2bed9af6 Mon Sep 17 00:00:00 2001 From: Alessio Ababilov Date: Sun, 10 Feb 2013 21:41:22 +0200 Subject: [PATCH 074/135] Add .coveragerc Set up proper source and omit options. Change-Id: Icbda7894072f3589502ba2c4b5dcdf7f68b053af Implements: blueprint update-coveragerc --- .coveragerc | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..cd84ef96b --- /dev/null +++ b/.coveragerc @@ -0,0 +1,7 @@ +[run] +branch = True +source = quantumclient +omit = quantumclient/openstack/* + +[report] +ignore-errors = True From 2711af7ae39e0aa1f2da9aa1cc6c65eef0117fab Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Thu, 14 Feb 2013 12:04:26 +0000 Subject: [PATCH 075/135] Add exceptions messages for authentication Fixes bug 1125137 Change-Id: Id79d11fb515a7af3b4e5cbe87bc717880fdb268b --- quantumclient/common/exceptions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py index 22ac67a49..1df133d7b 100644 --- a/quantumclient/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -94,7 +94,7 @@ class Unauthorized(QuantumClientException): """ HTTP 401 - Unauthorized: bad credentials. """ - pass + message = _("Unauthorized: bad credentials.") class Forbidden(QuantumClientException): @@ -102,12 +102,13 @@ class Forbidden(QuantumClientException): HTTP 403 - Forbidden: your credentials don't give you access to this resource. """ - pass + message = _("Forbidden: your credentials don't give you access to this " + "resource.") class EndpointNotFound(QuantumClientException): """Could not find Service or Region in Service Catalog.""" - pass + message = _("Could not find Service or Region in Service Catalog.") class AmbiguousEndpoints(QuantumClientException): From aa2c9628dbd94871936fa6e10107e46691c038e4 Mon Sep 17 00:00:00 2001 From: Aaron Rosen Date: Sat, 3 Nov 2012 18:41:53 -0700 Subject: [PATCH 076/135] Add nvp queue support to client This patch adds the nvp_qos_queue commands to the client Implements blueprint nvp-qos-extension-client Change-Id: Ic6d2a13ecb82e7e68b52b3143befb2f34b5e759f --- quantumclient/quantum/v2_0/nvp_qos_queue.py | 90 +++++++++++++++++++ quantumclient/shell.py | 8 ++ quantumclient/tests/unit/test_cli20.py | 2 +- .../tests/unit/test_cli20_nvp_queue.py | 80 +++++++++++++++++ quantumclient/v2_0/client.py | 31 +++++++ 5 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 quantumclient/quantum/v2_0/nvp_qos_queue.py create mode 100644 quantumclient/tests/unit/test_cli20_nvp_queue.py diff --git a/quantumclient/quantum/v2_0/nvp_qos_queue.py b/quantumclient/quantum/v2_0/nvp_qos_queue.py new file mode 100644 index 000000000..040ce161c --- /dev/null +++ b/quantumclient/quantum/v2_0/nvp_qos_queue.py @@ -0,0 +1,90 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Nicira Inc. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import logging + +from quantumclient.quantum import v2_0 as quantumv20 + + +class ListQoSQueue(quantumv20.ListCommand): + """List queues that belong to a given tenant.""" + + resource = 'qos_queue' + log = logging.getLogger(__name__ + '.ListQoSQueue') + _formatters = {} + list_columns = ['id', 'name', 'min', 'max', + 'qos_marking', 'dscp', 'default'] + + +class ShowQoSQueue(quantumv20.ShowCommand): + """Show information of a given queue.""" + + resource = 'qos_queue' + log = logging.getLogger(__name__ + '.ShowQoSQueue') + allow_names = True + + +class CreateQoSQueue(quantumv20.CreateCommand): + """Create a queue.""" + + resource = 'qos_queue' + log = logging.getLogger(__name__ + '.CreateQoSQueue') + + def add_known_arguments(self, parser): + parser.add_argument( + 'name', metavar='NAME', + help='Name of queue') + parser.add_argument( + '--min', + help='min-rate'), + parser.add_argument( + '--max', + help='max-rate'), + parser.add_argument( + '--qos-marking', + help='qos marking untrusted/trusted'), + parser.add_argument( + '--default', + default=False, + help=('If true all ports created with be the size of this queue' + ' if queue is not specified')), + parser.add_argument( + '--dscp', + help='Differentiated Services Code Point'), + + def args2body(self, parsed_args): + params = {'name': parsed_args.name, + 'default': parsed_args.default} + if parsed_args.min: + params['min'] = parsed_args.min + if parsed_args.max: + params['max'] = parsed_args.max + if parsed_args.qos_marking: + params['qos_marking'] = parsed_args.qos_marking + if parsed_args.dscp: + params['dscp'] = parsed_args.dscp + if parsed_args.tenant_id: + params['tenant_id'] = parsed_args.tenant_id + return {'qos_queue': params} + + +class DeleteQoSQueue(quantumv20.DeleteCommand): + """Delete a given queue.""" + + log = logging.getLogger(__name__ + '.DeleteQoSQueue') + resource = 'qos_queue' + allow_names = True diff --git a/quantumclient/shell.py b/quantumclient/shell.py index e9bbb24ca..8b8ba69d1 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -191,6 +191,14 @@ def env(*_vars, **kwargs): 'lb-healthmonitor-disassociate': utils.import_class( 'quantumclient.quantum.v2_0.lb.healthmonitor' '.DisassociateHealthMonitor'), + 'queue-create': utils.import_class( + 'quantumclient.quantum.v2_0.nvp_qos_queue.CreateQoSQueue'), + 'queue-delete': utils.import_class( + 'quantumclient.quantum.v2_0.nvp_qos_queue.DeleteQoSQueue'), + 'queue-show': utils.import_class( + 'quantumclient.quantum.v2_0.nvp_qos_queue.ShowQoSQueue'), + 'queue-list': utils.import_class( + 'quantumclient.quantum.v2_0.nvp_qos_queue.ListQoSQueue'), } COMMANDS = {'2.0': COMMAND_V2} diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 1a97eb1b4..1c1dfcad7 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -141,7 +141,7 @@ def _test_create_resource(self, resource, cmd, self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) non_admin_status_resources = ['subnet', 'floatingip', 'security_group', - 'security_group_rule'] + 'security_group_rule', 'qos_queue'] if (resource in non_admin_status_resources): body = {resource: {}, } else: diff --git a/quantumclient/tests/unit/test_cli20_nvp_queue.py b/quantumclient/tests/unit/test_cli20_nvp_queue.py new file mode 100644 index 000000000..96ed11a0d --- /dev/null +++ b/quantumclient/tests/unit/test_cli20_nvp_queue.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 Nicira Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + +from quantumclient.quantum.v2_0 import nvp_qos_queue as qos +from quantumclient.tests.unit import test_cli20 + + +class CLITestV20NvpQosQueue(test_cli20.CLITestV20Base): + def test_create_qos_queue(self): + """Create a qos queue.""" + resource = 'qos_queue' + cmd = qos.CreateQoSQueue( + test_cli20.MyApp(sys.stdout), None) + myid = 'myid' + name = 'my_queue' + default = False + args = ['--default', default, name] + position_names = ['name', 'default'] + position_values = [name, default] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_qos_queue_all_values(self): + """Create a qos queue.""" + resource = 'qos_queue' + cmd = qos.CreateQoSQueue( + test_cli20.MyApp(sys.stdout), None) + myid = 'myid' + name = 'my_queue' + default = False + min = '10' + max = '40' + qos_marking = 'untrusted' + dscp = '0' + args = ['--default', default, '--min', min, '--max', max, + '--qos-marking', qos_marking, '--dscp', dscp, name] + position_names = ['name', 'default', 'min', 'max', 'qos_marking', + 'dscp'] + position_values = [name, default, min, max, qos_marking, dscp] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + + def test_list_qos_queue(self): + resources = "qos_queues" + cmd = qos.ListQoSQueue( + test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_show_qos_queue_id(self): + resource = 'qos_queue' + cmd = qos.ShowQoSQueue( + test_cli20.MyApp(sys.stdout), None) + args = ['--fields', 'id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, + args, ['id']) + + def test_delete_qos_queue(self): + resource = 'qos_queue' + cmd = qos.DeleteQoSQueue( + test_cli20.MyApp(sys.stdout), None) + myid = 'myid' + args = [myid] + self._test_delete_resource(resource, cmd, myid, args) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 202e1a7f2..f6d09be83 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -170,6 +170,8 @@ class Client(object): associate_pool_health_monitors_path = "/lb/pools/%s/health_monitors" disassociate_pool_health_monitors_path = ( "/lb/pools/%(pool)s/health_monitors/%(health_monitor)s") + qos_queues_path = "/qos-queues" + qos_queue_path = "/qos-queues/%s" # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', @@ -673,6 +675,35 @@ def disassociate_health_monitor(self, pool, health_monitor): {'pool': pool, 'health_monitor': health_monitor}) return self.delete(path) + @APIParamsCall + def create_qos_queue(self, body=None): + """ + Creates a new queue + """ + return self.post(self.qos_queues_path, body=body) + + @APIParamsCall + def list_qos_queues(self, **_params): + """ + Fetches a list of all queues for a tenant + """ + return self.get(self.qos_queues_path, params=_params) + + @APIParamsCall + def show_qos_queue(self, queue, **_params): + """ + Fetches information of a certain queue + """ + return self.get(self.qos_queue_path % (queue), + params=_params) + + @APIParamsCall + def delete_qos_queue(self, queue): + """ + Deletes the specified queue + """ + return self.delete(self.qos_queue_path % (queue)) + def __init__(self, **kwargs): """ Initialize a new client for the Quantum v2.0 API. """ super(Client, self).__init__() From 9e3ba2a16143c428bd2b72945b17ed3ee290d21a Mon Sep 17 00:00:00 2001 From: gongysh Date: Sun, 3 Feb 2013 10:00:00 +0800 Subject: [PATCH 077/135] Client for agent extension blueprint quantum-scheduler Change-Id: Ic5a2198017cacfb0ff5b5da1461c06b3a0f87ea1 --- quantumclient/quantum/v2_0/__init__.py | 8 ++-- quantumclient/quantum/v2_0/agent.py | 64 ++++++++++++++++++++++++++ quantumclient/shell.py | 8 ++++ quantumclient/v2_0/client.py | 31 +++++++++++++ 4 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 quantumclient/quantum/v2_0/agent.py diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 7387e073b..dde2a69ab 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -222,6 +222,7 @@ class QuantumCommand(command.OpenStackCommand): api = 'network' log = logging.getLogger(__name__ + '.QuantumCommand') values_specs = [] + json_indent = None def get_client(self): return self.app.client_manager.quantum @@ -245,11 +246,12 @@ def format_output_data(self, data): if self.resource in data: for k, v in data[self.resource].iteritems(): if isinstance(v, list): - value = '\n'.join(utils.dumps(i) if isinstance(i, dict) - else str(i) for i in v) + value = '\n'.join(utils.dumps( + i, indent=self.json_indent) if isinstance(i, dict) + else str(i) for i in v) data[self.resource][k] = value elif isinstance(v, dict): - value = utils.dumps(v) + value = utils.dumps(v, indent=self.json_indent) data[self.resource][k] = value elif v is None: data[self.resource][k] = '' diff --git a/quantumclient/quantum/v2_0/agent.py b/quantumclient/quantum/v2_0/agent.py new file mode 100644 index 000000000..1597465be --- /dev/null +++ b/quantumclient/quantum/v2_0/agent.py @@ -0,0 +1,64 @@ +# Copyright 2013 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum import v2_0 as quantumV20 + + +def _format_timestamp(component): + try: + return component['heartbeat_timestamp'].split(".", 2)[0] + except Exception: + return '' + + +class ListAgent(quantumV20.ListCommand): + """List agents.""" + + resource = 'agent' + log = logging.getLogger(__name__ + '.ListAgent') + list_columns = ['id', 'agent_type', 'host', 'alive', 'admin_state_up'] + _formatters = {'heartbeat_timestamp': _format_timestamp} + + def extend_list(self, data, parsed_args): + for agent in data: + agent['alive'] = ":-)" if agent['alive'] else 'xxx' + + +class ShowAgent(quantumV20.ShowCommand): + """Show information of a given agent.""" + + resource = 'agent' + log = logging.getLogger(__name__ + '.ShowAgent') + allow_names = False + json_indent = 5 + + +class DeleteAgent(quantumV20.DeleteCommand): + """Delete a given agent.""" + + log = logging.getLogger(__name__ + '.DeleteAgent') + resource = 'agent' + + +class UpdateAgent(quantumV20.UpdateCommand): + """Update a given agent.""" + + log = logging.getLogger(__name__ + '.UpdateAgent') + resource = 'agent' + allow_names = False diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 8b8ba69d1..534cf6392 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -199,6 +199,14 @@ def env(*_vars, **kwargs): 'quantumclient.quantum.v2_0.nvp_qos_queue.ShowQoSQueue'), 'queue-list': utils.import_class( 'quantumclient.quantum.v2_0.nvp_qos_queue.ListQoSQueue'), + 'agent-list': utils.import_class( + 'quantumclient.quantum.v2_0.agent.ListAgent'), + 'agent-show': utils.import_class( + 'quantumclient.quantum.v2_0.agent.ShowAgent'), + 'agent-delete': utils.import_class( + 'quantumclient.quantum.v2_0.agent.DeleteAgent'), + 'agent-update': utils.import_class( + 'quantumclient.quantum.v2_0.agent.UpdateAgent'), } COMMANDS = {'2.0': COMMAND_V2} diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index f6d09be83..883b208be 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -172,6 +172,8 @@ class Client(object): "/lb/pools/%(pool)s/health_monitors/%(health_monitor)s") qos_queues_path = "/qos-queues" qos_queue_path = "/qos-queues/%s" + agents_path = "/agents" + agent_path = "/agents/%s" # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', @@ -704,6 +706,35 @@ def delete_qos_queue(self, queue): """ return self.delete(self.qos_queue_path % (queue)) + @APIParamsCall + def list_agents(self, **_params): + """ + Fetches agents + """ + # Pass filters in "params" argument to do_request + return self.get(self.agents_path, params=_params) + + @APIParamsCall + def show_agent(self, agent, **_params): + """ + Fetches information of a certain agent + """ + return self.get(self.agent_path % (agent), params=_params) + + @APIParamsCall + def update_agent(self, agent, body=None): + """ + Updates an agent + """ + return self.put(self.agent_path % (agent), body=body) + + @APIParamsCall + def delete_agent(self, agent): + """ + Deletes the specified agent + """ + return self.delete(self.agent_path % (agent)) + def __init__(self, **kwargs): """ Initialize a new client for the Quantum v2.0 API. """ super(Client, self).__init__() From 448c8ff6b3cf54e3ebe3cfebc76514f4d03b80e9 Mon Sep 17 00:00:00 2001 From: Brian Waldon Date: Sun, 17 Feb 2013 16:08:53 -0800 Subject: [PATCH 078/135] Match other python-*client prettytable dependency The other openstack clients use prettytable>=0.6,<0.7. For several reasons (primarily a lack of support in pip), we will match that here. Change-Id: Id4fb08ae48a65666014c96a22baefe46a771b002 --- tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index 59ee8fa34..cfa62bb55 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -2,6 +2,6 @@ argparse cliff>=1.2.1 httplib2 iso8601 -prettytable>=0.6.0 +prettytable>=0.6,<0.7 pyparsing>=1.5.6,<2.0 simplejson From 73b93725fee0383c2b0ac73a309dc95063f12d48 Mon Sep 17 00:00:00 2001 From: gongysh Date: Mon, 28 Jan 2013 12:19:22 +0800 Subject: [PATCH 079/135] Allow known options after unknown ones in list and update command blueprint known-options-location-in-list-update-commands Change-Id: Icad4fbc0d9f1751bd36573b37ac7fe32987fada9 --- quantumclient/quantum/v2_0/__init__.py | 59 ++++++++++------- quantumclient/quantum/v2_0/network.py | 6 +- quantumclient/quantum/v2_0/port.py | 7 +-- quantumclient/shell.py | 17 ++++- quantumclient/tests/unit/test_casual_args.py | 11 ++++ quantumclient/tests/unit/test_cli20.py | 63 +++++++++++-------- .../tests/unit/test_cli20_network.py | 15 ++--- quantumclient/tests/unit/test_cli20_port.py | 6 +- quantumclient/tests/unit/test_cli20_subnet.py | 28 +++++++++ 9 files changed, 136 insertions(+), 76 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 7387e073b..10a50d215 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -98,6 +98,18 @@ def add_extra_argument(parser, name, _help): '[--key2 [type=int|bool|...] value ...]') +def is_number(s): + try: + float(s) # for int, long and float + except ValueError: + try: + complex(s) # for complex + except ValueError: + return False + + return True + + def parse_args_to_dict(values_specs): '''It is used to analyze the extra command options to command. @@ -156,7 +168,8 @@ def parse_args_to_dict(values_specs): _list_flag = True continue if not _item.startswith('--'): - if not current_item or '=' in current_item: + if (not current_item or '=' in current_item or + _item.startswith('-') and not is_number(_item)): raise exceptions.CommandError( "Invalid values_specs %s" % ' '.join(values_specs)) _value_number += 1 @@ -316,8 +329,6 @@ def get_parser(self, prog_name): parser.add_argument( 'id', metavar=self.resource, help='ID or name of %s to update' % self.resource) - add_extra_argument(parser, 'value_specs', - 'new values for the %s' % self.resource) self.add_known_arguments(parser) return parser @@ -325,11 +336,14 @@ def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) quantum_client = self.get_client() quantum_client.format = parsed_args.request_format - value_specs = parsed_args.value_specs - dict_args = self.args2body(parsed_args).get(self.resource, {}) - dict_specs = parse_args_to_dict(value_specs) - body = {self.resource: dict(dict_args.items() + - dict_specs.items())} + _extra_values = parse_args_to_dict(self.values_specs) + _merge_args(self, parsed_args, _extra_values, + self.values_specs) + body = self.args2body(parsed_args) + if self.resource in body: + body[self.resource].update(_extra_values) + else: + body[self.resource] = _extra_values if not body[self.resource]: raise exceptions.CommandError( "Must specify new values to update %s" % self.resource) @@ -398,30 +412,29 @@ class ListCommand(QuantumCommand, lister.Lister): def get_parser(self, prog_name): parser = super(ListCommand, self).get_parser(prog_name) add_show_list_common_argument(parser) - add_extra_argument(parser, 'filter_specs', 'filters options') return parser - def retrieve_list(self, parsed_args): - """Retrieve a list of resources from Quantum server""" - quantum_client = self.get_client() - search_opts = parse_args_to_dict(parsed_args.filter_specs) - self.log.debug('search options: %s', search_opts) - quantum_client.format = parsed_args.request_format + def args2search_opts(self, parsed_args): + search_opts = {} fields = parsed_args.fields - extra_fields = search_opts.get('fields', []) - if extra_fields: - if isinstance(extra_fields, list): - fields.extend(extra_fields) - else: - fields.append(extra_fields) - if fields: + if parsed_args.fields: search_opts.update({'fields': fields}) if parsed_args.show_details: search_opts.update({'verbose': 'True'}) + return search_opts + + def retrieve_list(self, parsed_args): + """Retrieve a list of resources from Quantum server""" + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + _extra_values = parse_args_to_dict(self.values_specs) + _merge_args(self, parsed_args, _extra_values, + self.values_specs) + search_opts = self.args2search_opts(parsed_args) + search_opts.update(_extra_values) obj_lister = getattr(quantum_client, "list_%ss" % self.resource) data = obj_lister(**search_opts) - collection = self.resource + "s" return data.get(collection, []) diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index b714ae59e..dfc0171b6 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -60,9 +60,9 @@ class ListExternalNetwork(ListNetwork): log = logging.getLogger(__name__ + '.ListExternalNetwork') def retrieve_list(self, parsed_args): - if '--' not in parsed_args.filter_specs: - parsed_args.filter_specs.append('--') - parsed_args.filter_specs.append('--router:external=True') + external = '--router:external=True' + if external not in self.values_specs: + self.values_specs.append('--router:external=True') return super(ListExternalNetwork, self).retrieve_list(parsed_args) diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 76468b77b..753df0ec3 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -52,13 +52,10 @@ class ListRouterPort(ListCommand): list_columns = ['id', 'name', 'mac_address', 'fixed_ips'] def get_parser(self, prog_name): - parser = super(ListCommand, self).get_parser(prog_name) - quantumv20.add_show_list_common_argument(parser) + parser = super(ListRouterPort, self).get_parser(prog_name) parser.add_argument( 'id', metavar='router', help='ID or name of router to look up') - quantumv20.add_extra_argument(parser, 'filter_specs', - 'filters options') return parser def get_data(self, parsed_args): @@ -66,7 +63,7 @@ def get_data(self, parsed_args): quantum_client.format = parsed_args.request_format _id = quantumv20.find_resourceid_by_name_or_id( quantum_client, 'router', parsed_args.id) - parsed_args.filter_specs.append('--device_id=%s' % _id) + self.values_specs.append('--device_id=%s' % _id) return super(ListRouterPort, self).get_data(parsed_args) diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 8b8ba69d1..f77405a18 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -37,6 +37,19 @@ QUANTUM_API_VERSION = '2.0' +def run_command(cmd, cmd_parser, sub_argv): + _argv = sub_argv + index = -1 + values_specs = [] + if '--' in sub_argv: + index = sub_argv.index('--') + _argv = sub_argv[:index] + values_specs = sub_argv[index:] + known_args, _values_specs = cmd_parser.parse_known_args(_argv) + cmd.values_specs = (index == -1 and _values_specs or values_specs) + return cmd.run(known_args) + + def env(*_vars, **kwargs): """Search for the first defined of possibly many env vars @@ -439,9 +452,7 @@ def run_subcommand(self, argv): else ' '.join([self.NAME, cmd_name]) ) cmd_parser = cmd.get_parser(full_name) - known_args, values_specs = cmd_parser.parse_known_args(sub_argv) - cmd.values_specs = values_specs - result = cmd.run(known_args) + return run_command(cmd, cmd_parser, sub_argv) except Exception as err: if self.options.debug: self.log.exception(err) diff --git a/quantumclient/tests/unit/test_casual_args.py b/quantumclient/tests/unit/test_casual_args.py index 150edec2d..fcfff9331 100644 --- a/quantumclient/tests/unit/test_casual_args.py +++ b/quantumclient/tests/unit/test_casual_args.py @@ -53,6 +53,17 @@ def test_badarg(self): self.assertRaises(exceptions.CommandError, quantumV20.parse_args_to_dict, _specs) + def test_badarg_with_minus(self): + _specs = ['--arg1', 'value1', '-D'] + self.assertRaises(exceptions.CommandError, + quantumV20.parse_args_to_dict, _specs) + + def test_goodarg_with_minus_number(self): + _specs = ['--arg1', 'value1', '-1', '-1.0'] + _mydict = quantumV20.parse_args_to_dict(_specs) + self.assertEqual(['value1', '-1', '-1.0'], + _mydict['arg1']) + def test_badarg_duplicate(self): _specs = ['--tag=t', '--arg1', 'value1', '--arg1', 'value1'] self.assertRaises(exceptions.CommandError, diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 1c1dfcad7..d2abaf94e 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -15,15 +15,13 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 -import sys - import fixtures import mox from mox import Comparator from mox import ContainsKeyValue import testtools -from quantumclient.quantum import v2_0 as quantumv20 +from quantumclient import shell from quantumclient.v2_0.client import Client @@ -63,6 +61,31 @@ def end_url(path, query=None): return query and _url_str + "?" + query or _url_str +class MyUrlComparator(Comparator): + def __init__(self, lhs, client): + self.lhs = lhs + self.client = client + + def equals(self, rhs): + return str(self) == rhs + + def __str__(self): + if self.client and self.client.format != FORMAT: + lhs_parts = self.lhs.split("?", 1) + if len(lhs_parts) == 2: + lhs = ("%s%s?%s" % (lhs_parts[0][:-4], + self.client.format, + lhs_parts[1])) + else: + lhs = ("%s%s" % (lhs_parts[0][:-4], + self.client.format)) + return lhs + return self.lhs + + def __repr__(self): + return str(self) + + class MyComparator(Comparator): def __init__(self, lhs, client): self.lhs = lhs @@ -172,9 +195,7 @@ def _test_create_resource(self, resource, cmd, resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser('create_' + resource) - known_args, values_specs = cmd_parser.parse_known_args(args) - cmd.values_specs = values_specs - cmd.run(known_args) + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() @@ -197,9 +218,7 @@ def _test_list_columns(self, cmd, resources_collection, TOKEN)).AndReturn((MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources_collection) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() @@ -244,15 +263,13 @@ def _test_list_resources(self, resources, cmd, detail=False, tags=[], query = "fields=" + field path = getattr(self.client, resources + "_path") self.client.httpclient.request( - end_url(path, query), 'GET', + MyUrlComparator(end_url(path, query), self.client), 'GET', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() @@ -265,15 +282,13 @@ def _test_update_resource(self, resource, cmd, myid, args, extrafields): body = {resource: extrafields} path = getattr(self.client, resource + "_path") self.client.httpclient.request( - end_url(path % myid), 'PUT', + MyUrlComparator(end_url(path % myid), self.client), 'PUT', body=MyComparator(body, self.client), headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("update_" + resource) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() @@ -296,9 +311,7 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]): TOKEN)).AndReturn((MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("show_" + resource) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() @@ -317,9 +330,7 @@ def _test_delete_resource(self, resource, cmd, myid, args): TOKEN)).AndReturn((MyResp(204), None)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("delete_" + resource) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() @@ -338,10 +349,8 @@ def _test_update_resource_action(self, resource, cmd, myid, action, args, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) self.mox.ReplayAll() - cmd_parser = cmd.get_parser("update_" + resource) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) + cmd_parser = cmd.get_parser("delete_" + resource) + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index 8eebcfe6c..6bc9ebd4c 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -28,6 +28,7 @@ from quantumclient.quantum.v2_0.network import ListNetwork from quantumclient.quantum.v2_0.network import ShowNetwork from quantumclient.quantum.v2_0.network import UpdateNetwork +from quantumclient import shell from quantumclient.tests.unit import test_cli20 from quantumclient.tests.unit.test_cli20 import CLITestV20Base from quantumclient.tests.unit.test_cli20 import MyApp @@ -120,9 +121,7 @@ def test_list_nets_empty_with_column(self): (test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() @@ -276,10 +275,7 @@ def test_list_external_nets_empty_with_column(self): (test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) - - parsed_args = cmd_parser.parse_args(args) - - cmd.run(parsed_args) + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() @@ -343,10 +339,7 @@ def _test_list_external_nets(self, resources, cmd, (test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) - + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index c4d9dadd9..faec572ea 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -19,6 +19,7 @@ from mox import ContainsKeyValue +from quantumclient import shell from quantumclient.quantum.v2_0.port import CreatePort from quantumclient.quantum.v2_0.port import DeletePort from quantumclient.quantum.v2_0.port import ListPort @@ -213,10 +214,7 @@ def _test_list_router_port(self, resources, cmd, (test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) - - parsed_args = cmd_parser.parse_args(args) - cmd.run(parsed_args) - + shell.run_command(cmd, cmd_parser, args) self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index 997718bb2..9b9650e16 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -312,6 +312,12 @@ def test_list_subnets_tags(self): cmd = ListSubnet(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, tags=['a', 'b']) + def test_list_subnets_known_option_after_unknown(self): + """List subnets: -- --tags a b --request-format xml.""" + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, tags=['a', 'b']) + def test_list_subnets_detail_tags(self): """List subnets: -D -- --tags a b.""" resources = "subnets" @@ -335,6 +341,28 @@ def test_update_subnet(self): {'name': 'myname', 'tags': ['a', 'b'], } ) + def test_update_subnet_known_option_before_id(self): + """Update subnet: --request-format json myid --name myname.""" + # --request-format xml is known option + resource = 'subnet' + cmd = UpdateSubnet(MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['--request-format', 'json', + 'myid', '--name', 'myname'], + {'name': 'myname', } + ) + + def test_update_subnet_known_option_after_id(self): + """Update subnet: myid --name myname --request-format json.""" + # --request-format xml is known option + resource = 'subnet' + cmd = UpdateSubnet(MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'myname', + '--request-format', 'json'], + {'name': 'myname', } + ) + def test_show_subnet(self): """Show subnet: --fields id --fields name myid.""" resource = 'subnet' From d77f86218e4c0c2f5371accce64605e7cfff41c5 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 29 Nov 2012 06:51:25 -0800 Subject: [PATCH 080/135] CLI support for network gateway feature Blueprint nvp-nwgw-extension-client Adds commands for gateway management, and for connecting networks to gateways. These commands use the nicira-specific extension 'nvp-network-gateway' Change-Id: Iefcba201bc9fd8dce35762514af0f56b29430ccd --- .../quantum/v2_0/nvpnetworkgateway.py | 160 ++++++++++++++++++ quantumclient/shell.py | 15 ++ quantumclient/tests/unit/test_cli20.py | 6 +- .../unit/test_cli20_nvpnetworkgateway.py | 108 ++++++++++++ quantumclient/v2_0/client.py | 53 ++++++ 5 files changed, 340 insertions(+), 2 deletions(-) create mode 100644 quantumclient/quantum/v2_0/nvpnetworkgateway.py create mode 100644 quantumclient/tests/unit/test_cli20_nvpnetworkgateway.py diff --git a/quantumclient/quantum/v2_0/nvpnetworkgateway.py b/quantumclient/quantum/v2_0/nvpnetworkgateway.py new file mode 100644 index 000000000..39573041c --- /dev/null +++ b/quantumclient/quantum/v2_0/nvpnetworkgateway.py @@ -0,0 +1,160 @@ +# Copyright 2013 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import argparse +import logging + +from quantumclient.common import utils +from quantumclient.quantum import v2_0 as quantumv20 + +RESOURCE = 'network_gateway' + + +class ListNetworkGateway(quantumv20.ListCommand): + """List network gateways for a given tenant.""" + + resource = RESOURCE + _formatters = {} + log = logging.getLogger(__name__ + '.ListNetworkGateway') + list_columns = ['id', 'name'] + + +class ShowNetworkGateway(quantumv20.ShowCommand): + """Show information of a given network gateway.""" + + resource = RESOURCE + log = logging.getLogger(__name__ + '.ShowNetworkGateway') + + +class CreateNetworkGateway(quantumv20.CreateCommand): + """Create a network gateway.""" + + resource = RESOURCE + log = logging.getLogger(__name__ + '.CreateNetworkGateway') + + def add_known_arguments(self, parser): + parser.add_argument( + 'name', metavar='NAME', + help='Name of network gateway to create') + parser.add_argument( + '--device', + action='append', + help='device info for this gateway ' + 'device_id=,' + 'interface_name= ' + 'It can be repeated for multiple devices for HA gateways') + + def args2body(self, parsed_args): + body = {self.resource: { + 'name': parsed_args.name}} + devices = [] + if parsed_args.device: + for device in parsed_args.device: + devices.append(utils.str2dict(device)) + if devices: + body[self.resource].update({'devices': devices}) + if parsed_args.tenant_id: + body[self.resource].update({'tenant_id': parsed_args.tenant_id}) + return body + + +class DeleteNetworkGateway(quantumv20.DeleteCommand): + """Delete a given network gateway.""" + + resource = RESOURCE + log = logging.getLogger(__name__ + '.DeleteNetworkGateway') + + +class UpdateNetworkGateway(quantumv20.UpdateCommand): + """Update the name for a network gateway.""" + + resource = RESOURCE + log = logging.getLogger(__name__ + '.UpdateNetworkGateway') + + +class NetworkGatewayInterfaceCommand(quantumv20.QuantumCommand): + """Base class for connecting/disconnecting networks to/from a gateway.""" + + resource = RESOURCE + + def get_parser(self, prog_name): + parser = super(NetworkGatewayInterfaceCommand, + self).get_parser(prog_name) + parser.add_argument( + 'net_gateway_id', metavar='NET-GATEWAY-ID', + help='ID of the network gateway') + parser.add_argument( + 'network_id', metavar='NETWORK-ID', + help='ID of the internal network to connect on the gateway') + parser.add_argument( + '--segmentation-type', + help=('L2 segmentation strategy on the external side of ' + 'the gateway (e.g.: VLAN, FLAT)')) + parser.add_argument( + '--segmentation-id', + help=('Identifier for the L2 segment on the external side ' + 'of the gateway')) + return parser + + def retrieve_ids(self, client, args): + gateway_id = quantumv20.find_resourceid_by_name_or_id( + client, self.resource, args.net_gateway_id) + network_id = quantumv20.find_resourceid_by_name_or_id( + client, 'network', args.network_id) + return (gateway_id, network_id) + + +class ConnectNetworkGateway(NetworkGatewayInterfaceCommand): + """Add an internal network interface to a router.""" + + log = logging.getLogger(__name__ + '.ConnectNetworkGateway') + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + (gateway_id, network_id) = self.retrieve_ids(quantum_client, + parsed_args) + quantum_client.connect_network_gateway( + gateway_id, {'network_id': network_id, + 'segmentation_type': parsed_args.segmentation_type, + 'segmentation_id': parsed_args.segmentation_id}) + # TODO(Salvatore-Orlando): Do output formatting as + # any other command + print >>self.app.stdout, ( + _('Connected network to gateway %s') % gateway_id) + + +class DisconnectNetworkGateway(NetworkGatewayInterfaceCommand): + """Remove a network from a network gateway.""" + + log = logging.getLogger(__name__ + '.DisconnectNetworkGateway') + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + (gateway_id, network_id) = self.retrieve_ids(quantum_client, + parsed_args) + quantum_client.disconnect_network_gateway( + gateway_id, {'network_id': network_id, + 'segmentation_type': parsed_args.segmentation_type, + 'segmentation_id': parsed_args.segmentation_id}) + # TODO(Salvatore-Orlando): Do output formatting as + # any other command + print >>self.app.stdout, ( + _('Disconnected network from gateway %s') % gateway_id) diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 4b22e9837..feb69411e 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -220,6 +220,21 @@ def env(*_vars, **kwargs): 'quantumclient.quantum.v2_0.agent.DeleteAgent'), 'agent-update': utils.import_class( 'quantumclient.quantum.v2_0.agent.UpdateAgent'), + 'net-gateway-create': utils.import_class( + 'quantumclient.quantum.v2_0.nvpnetworkgateway.CreateNetworkGateway'), + 'net-gateway-update': utils.import_class( + 'quantumclient.quantum.v2_0.nvpnetworkgateway.UpdateNetworkGateway'), + 'net-gateway-delete': utils.import_class( + 'quantumclient.quantum.v2_0.nvpnetworkgateway.DeleteNetworkGateway'), + 'net-gateway-show': utils.import_class( + 'quantumclient.quantum.v2_0.nvpnetworkgateway.ShowNetworkGateway'), + 'net-gateway-list': utils.import_class( + 'quantumclient.quantum.v2_0.nvpnetworkgateway.ListNetworkGateway'), + 'net-gateway-connect': utils.import_class( + 'quantumclient.quantum.v2_0.nvpnetworkgateway.ConnectNetworkGateway'), + 'net-gateway-disconnect': utils.import_class( + 'quantumclient.quantum.v2_0.nvpnetworkgateway.' + 'DisconnectNetworkGateway') } COMMANDS = {'2.0': COMMAND_V2} diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index d2abaf94e..4e62edaad 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -159,12 +159,13 @@ def _test_create_resource(self, resource, cmd, name, myid, args, position_names, position_values, tenant_id=None, tags=None, admin_state_up=True, shared=False, - extra_body=None): + extra_body=None, **kwargs): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) non_admin_status_resources = ['subnet', 'floatingip', 'security_group', - 'security_group_rule', 'qos_queue'] + 'security_group_rule', 'qos_queue', + 'network_gateway'] if (resource in non_admin_status_resources): body = {resource: {}, } else: @@ -177,6 +178,7 @@ def _test_create_resource(self, resource, cmd, body[resource].update({'shared': shared}) if extra_body: body[resource].update(extra_body) + body[resource].update(kwargs) for i in xrange(len(position_names)): body[resource].update({position_names[i]: position_values[i]}) diff --git a/quantumclient/tests/unit/test_cli20_nvpnetworkgateway.py b/quantumclient/tests/unit/test_cli20_nvpnetworkgateway.py new file mode 100644 index 000000000..7f17571d4 --- /dev/null +++ b/quantumclient/tests/unit/test_cli20_nvpnetworkgateway.py @@ -0,0 +1,108 @@ +# Copyright 2012 Nicira, Inc +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from quantumclient.common import exceptions +from quantumclient.quantum.v2_0 import nvpnetworkgateway +from quantumclient.tests.unit.test_cli20 import CLITestV20Base +from quantumclient.tests.unit.test_cli20 import MyApp + + +class CLITestV20NetworkGateway(CLITestV20Base): + + resource = "network_gateway" + + def test_create_gateway(self): + cmd = nvpnetworkgateway.CreateNetworkGateway(MyApp(sys.stdout), None) + name = 'gw-test' + myid = 'myid' + args = [name, ] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(self.resource, cmd, name, myid, args, + position_names, position_values) + + def test_create_gateway_with_tenant(self): + cmd = nvpnetworkgateway.CreateNetworkGateway(MyApp(sys.stdout), None) + name = 'gw-test' + myid = 'myid' + args = ['--tenant_id', 'tenantid', name] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(self.resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') + + def test_create_gateway_with_device(self): + cmd = nvpnetworkgateway.CreateNetworkGateway(MyApp(sys.stdout), None) + name = 'gw-test' + myid = 'myid' + args = ['--device', 'device_id=test', name, ] + position_names = ['name', ] + position_values = [name, ] + _str = self._test_create_resource(self.resource, cmd, name, myid, args, + position_names, position_values, + devices=[{'device_id': 'test'}]) + + def test_list_gateways(self): + resources = '%ss' % self.resource + cmd = nvpnetworkgateway.ListNetworkGateway(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, True) + + def test_update_gateway(self): + cmd = nvpnetworkgateway.UpdateNetworkGateway(MyApp(sys.stdout), None) + self._test_update_resource(self.resource, cmd, 'myid', + ['myid', '--name', 'cavani'], + {'name': 'cavani'}) + + def test_delete_gateway(self): + cmd = nvpnetworkgateway.DeleteNetworkGateway(MyApp(sys.stdout), None) + myid = 'myid' + args = [myid] + self._test_delete_resource(self.resource, cmd, myid, args) + + def test_show_gateway(self): + cmd = nvpnetworkgateway.ShowNetworkGateway(MyApp(sys.stdout), None) + args = ['--fields', 'id', '--fields', 'name', self.test_id] + self._test_show_resource(self.resource, cmd, self.test_id, args, + ['id', 'name']) + + def test_connect_network_to_gateway(self): + cmd = nvpnetworkgateway.ConnectNetworkGateway(MyApp(sys.stdout), None) + args = ['gw_id', 'net_id', + '--segmentation-type', 'edi', + '--segmentation-id', '7'] + self._test_update_resource_action(self.resource, cmd, 'gw_id', + 'connect_network', + args, + {'network_id': 'net_id', + 'segmentation_type': 'edi', + 'segmentation_id': '7'}) + + def test_disconnect_network_from_gateway(self): + cmd = nvpnetworkgateway.DisconnectNetworkGateway(MyApp(sys.stdout), + None) + args = ['gw_id', 'net_id', + '--segmentation-type', 'edi', + '--segmentation-id', '7'] + self._test_update_resource_action(self.resource, cmd, 'gw_id', + 'disconnect_network', + args, + {'network_id': 'net_id', + 'segmentation_type': 'edi', + 'segmentation_id': '7'}) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 883b208be..3ee26e989 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -174,6 +174,8 @@ class Client(object): qos_queue_path = "/qos-queues/%s" agents_path = "/agents" agent_path = "/agents/%s" + network_gateways_path = "/network-gateways" + network_gateway_path = "/network-gateways/%s" # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', @@ -735,6 +737,57 @@ def delete_agent(self, agent): """ return self.delete(self.agent_path % (agent)) + @APIParamsCall + def list_network_gateways(self, **_params): + """ + Retrieve network gateways + """ + return self.get(self.network_gateways_path, params=_params) + + @APIParamsCall + def show_network_gateway(self, gateway_id, **_params): + """ + Fetch a network gateway + """ + return self.get(self.network_gateway_path % gateway_id, params=_params) + + @APIParamsCall + def create_network_gateway(self, body=None): + """ + Create a new network gateway + """ + return self.post(self.network_gateways_path, body=body) + + @APIParamsCall + def update_network_gateway(self, gateway_id, body=None): + """ + Update a network gateway + """ + return self.put(self.network_gateway_path % gateway_id, body=body) + + @APIParamsCall + def delete_network_gateway(self, gateway_id): + """ + Delete the specified network gateway + """ + return self.delete(self.network_gateway_path % gateway_id) + + @APIParamsCall + def connect_network_gateway(self, gateway_id, body=None): + """ + Connect a network gateway to the specified network + """ + base_uri = self.network_gateway_path % gateway_id + return self.put("%s/connect_network" % base_uri, body=body) + + @APIParamsCall + def disconnect_network_gateway(self, gateway_id, body=None): + """ + Disconnect a network from the specified gateway + """ + base_uri = self.network_gateway_path % gateway_id + return self.put("%s/disconnect_network" % base_uri, body=body) + def __init__(self, **kwargs): """ Initialize a new client for the Quantum v2.0 API. """ super(Client, self).__init__() From 406f1adf45be4e6cc74b9e524c53c8227d34fcd9 Mon Sep 17 00:00:00 2001 From: Tatyana Leontovich Date: Wed, 27 Feb 2013 11:28:03 +0200 Subject: [PATCH 081/135] quantumclient.common.serializer module cleanup Remove unused methods in quantumclient.common.serializer.XMLDeserializer that tries to access non existing attributes of xml node: * find_first_child_named * extract_text * find_children_named Change-Id: I0aff5933fa75e50748e9d0325d898c2f6836fa58 Fixes: bug #1132850 --- quantumclient/common/serializer.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py index 7eba93069..8dc1edf97 100644 --- a/quantumclient/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -321,27 +321,6 @@ def _from_xml_node(self, node, listnames): child, listnames) return result - def find_first_child_named(self, parent, name): - """Search a nodes children for the first child with a given name""" - for node in parent.childNodes: - if node.nodeName == name: - return node - return None - - def find_children_named(self, parent, name): - """Return all of a nodes children who have the given name""" - for node in parent.childNodes: - if node.nodeName == name: - yield node - - def extract_text(self, node): - """Get the text field contained by the given node""" - if len(node.childNodes) == 1: - child = node.childNodes[0] - if child.nodeType == child.TEXT_NODE: - return child.nodeValue - return "" - def default(self, datastring): return {'body': self._from_xml(datastring)} From 94a60cfe5b0db53099bbcc39997c5656679333e4 Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Wed, 27 Feb 2013 13:12:48 +0000 Subject: [PATCH 082/135] Update cliff dependency (1.3.1) Fixed bug 1134163 Change-Id: I2bae8dee2f95f5372bb1b513a40da31fdd7962b2 --- tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index cfa62bb55..a3d38d731 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,5 +1,5 @@ argparse -cliff>=1.2.1 +cliff>=1.3.1 httplib2 iso8601 prettytable>=0.6,<0.7 From b204b1b5967725456a6aec680e896f928aca57db Mon Sep 17 00:00:00 2001 From: gongysh Date: Wed, 6 Feb 2013 18:29:43 +0800 Subject: [PATCH 083/135] Client for agent scheduler extension blueprint quantum-scheduler Change-Id: I5bad096d4892ebf1d309fc0a625dca4b2407bc94 --- quantumclient/quantum/v2_0/__init__.py | 17 +- quantumclient/quantum/v2_0/agentscheduler.py | 233 +++++++++++++++++++ quantumclient/shell.py | 21 +- quantumclient/v2_0/client.py | 68 ++++++ 4 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 quantumclient/quantum/v2_0/agentscheduler.py diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index cba5ca1ad..cbf87b0d7 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -408,16 +408,21 @@ class ListCommand(QuantumCommand, lister.Lister): api = 'network' resource = None log = None - _formatters = None + _formatters = {} list_columns = [] + unknown_parts_flag = True def get_parser(self, prog_name): parser = super(ListCommand, self).get_parser(prog_name) add_show_list_common_argument(parser) + if self.unknown_parts_flag: + add_extra_argument(parser, 'filter_specs', 'filters options') return parser def args2search_opts(self, parsed_args): search_opts = {} + if self.unknown_parts_flag: + search_opts = parse_args_to_dict(parsed_args.filter_specs) fields = parsed_args.fields if parsed_args.fields: search_opts.update({'fields': fields}) @@ -425,6 +430,12 @@ def args2search_opts(self, parsed_args): search_opts.update({'verbose': 'True'}) return search_opts + def call_server(self, quantum_client, search_opts, parsed_args): + obj_lister = getattr(quantum_client, + "list_%ss" % self.resource) + data = obj_lister(**search_opts) + return data + def retrieve_list(self, parsed_args): """Retrieve a list of resources from Quantum server""" quantum_client = self.get_client() @@ -434,9 +445,7 @@ def retrieve_list(self, parsed_args): self.values_specs) search_opts = self.args2search_opts(parsed_args) search_opts.update(_extra_values) - obj_lister = getattr(quantum_client, - "list_%ss" % self.resource) - data = obj_lister(**search_opts) + data = self.call_server(quantum_client, search_opts, parsed_args) collection = self.resource + "s" return data.get(collection, []) diff --git a/quantumclient/quantum/v2_0/agentscheduler.py b/quantumclient/quantum/v2_0/agentscheduler.py new file mode 100644 index 000000000..3e8e8e2d7 --- /dev/null +++ b/quantumclient/quantum/v2_0/agentscheduler.py @@ -0,0 +1,233 @@ +# Copyright 2013 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import logging + +from quantumclient.quantum import v2_0 as quantumV20 +from quantumclient.quantum.v2_0 import network +from quantumclient.quantum.v2_0 import router +PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" + + +class AddNetworkToDhcpAgent(quantumV20.QuantumCommand): + """Add a network to a DHCP agent.""" + + log = logging.getLogger(__name__ + '.AddNetworkToDhcpAgent') + + def get_parser(self, prog_name): + parser = super(AddNetworkToDhcpAgent, self).get_parser(prog_name) + parser.add_argument( + 'dhcp_agent', + help='ID of the DHCP agent') + parser.add_argument( + 'network', + help='network to add') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + _net_id = quantumV20.find_resourceid_by_name_or_id( + quantum_client, 'network', parsed_args.network) + quantum_client.add_network_to_dhcp_agent(parsed_args.dhcp_agent, + {'network_id': _net_id}) + print >>self.app.stdout, ( + _('Added network %s to DHCP agent') % parsed_args.network) + + +class RemoveNetworkFromDhcpAgent(quantumV20.QuantumCommand): + """Remove a network from a DHCP agent.""" + log = logging.getLogger(__name__ + '.RemoveNetworkFromDhcpAgent') + + def get_parser(self, prog_name): + parser = super(RemoveNetworkFromDhcpAgent, self).get_parser(prog_name) + parser.add_argument( + 'dhcp_agent', + help='ID of the DHCP agent') + parser.add_argument( + 'network', + help='network to remove') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + _net_id = quantumV20.find_resourceid_by_name_or_id( + quantum_client, 'network', parsed_args.network) + quantum_client.remove_network_from_dhcp_agent( + parsed_args.dhcp_agent, _net_id) + print >>self.app.stdout, ( + _('Removed network %s to DHCP agent') % parsed_args.network) + + +class ListNetworksOnDhcpAgent(network.ListNetwork): + """List the networks on a DHCP agent.""" + + log = logging.getLogger(__name__ + '.ListNetworksOnDhcpAgent') + unknown_parts_flag = False + + def get_parser(self, prog_name): + parser = super(ListNetworksOnDhcpAgent, + self).get_parser(prog_name) + parser.add_argument( + 'dhcp_agent', + help='ID of the DHCP agent') + return parser + + def call_server(self, quantum_client, search_opts, parsed_args): + data = quantum_client.list_networks_on_dhcp_agent( + parsed_args.dhcp_agent, **search_opts) + return data + + +class ListDhcpAgentsHostingNetwork(quantumV20.ListCommand): + """List DHCP agents hosting a network.""" + + resource = 'agent' + _formatters = {} + log = logging.getLogger(__name__ + '.ListDhcpAgentsHostingNetwork') + list_columns = ['id', 'host', 'admin_state_up', 'alive'] + unknown_parts_flag = False + + def get_parser(self, prog_name): + parser = super(ListDhcpAgentsHostingNetwork, + self).get_parser(prog_name) + parser.add_argument( + 'network', + help='network to query') + return parser + + def extend_list(self, data, parsed_args): + for agent in data: + agent['alive'] = ":-)" if agent['alive'] else 'xxx' + + def call_server(self, quantum_client, search_opts, parsed_args): + _id = quantumV20.find_resourceid_by_name_or_id(quantum_client, + 'network', + parsed_args.network) + search_opts['network'] = _id + data = quantum_client.list_dhcp_agent_hosting_networks(**search_opts) + return data + + +class AddRouterToL3Agent(quantumV20.QuantumCommand): + """Add a router to a L3 agent.""" + + log = logging.getLogger(__name__ + '.AddRouterToL3Agent') + + def get_parser(self, prog_name): + parser = super(AddRouterToL3Agent, self).get_parser(prog_name) + parser.add_argument( + 'l3_agent', + help='ID of the L3 agent') + parser.add_argument( + 'router', + help='router to add') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + _id = quantumV20.find_resourceid_by_name_or_id( + quantum_client, 'router', parsed_args.router) + quantum_client.add_router_to_l3_agent(parsed_args.l3_agent, + {'router_id': _id}) + print >>self.app.stdout, ( + _('Added router %s to L3 agent') % parsed_args.router) + + +class RemoveRouterFromL3Agent(quantumV20.QuantumCommand): + """Remove a router from a L3 agent.""" + + log = logging.getLogger(__name__ + '.RemoveRouterFromL3Agent') + + def get_parser(self, prog_name): + parser = super(RemoveRouterFromL3Agent, self).get_parser(prog_name) + parser.add_argument( + 'l3_agent', + help='ID of the L3 agent') + parser.add_argument( + 'router', + help='router to remove') + return parser + + def run(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + _id = quantumV20.find_resourceid_by_name_or_id( + quantum_client, 'router', parsed_args.router) + quantum_client.remove_router_from_l3_agent( + parsed_args.l3_agent, _id) + print >>self.app.stdout, ( + _('Removed Router %s to L3 agent') % parsed_args.router) + + +class ListRoutersOnL3Agent(quantumV20.ListCommand): + """List the routers on a L3 agent.""" + + log = logging.getLogger(__name__ + '.ListRoutersOnL3Agent') + _formatters = {'external_gateway_info': + router._format_external_gateway_info} + list_columns = ['id', 'name', 'external_gateway_info'] + resource = 'router' + unknown_parts_flag = False + + def get_parser(self, prog_name): + parser = super(ListRoutersOnL3Agent, + self).get_parser(prog_name) + parser.add_argument( + 'l3_agent', + help='ID of the L3 agent to query') + return parser + + def call_server(self, quantum_client, search_opts, parsed_args): + data = quantum_client.list_routers_on_l3_agent( + parsed_args.l3_agent, **search_opts) + return data + + +class ListL3AgentsHostingRouter(quantumV20.ListCommand): + """List L3 agents hosting a router.""" + + resource = 'agent' + _formatters = {} + log = logging.getLogger(__name__ + '.ListL3AgentsHostingRouter') + list_columns = ['id', 'host', 'admin_state_up', 'alive', 'default'] + unknown_parts_flag = False + + def get_parser(self, prog_name): + parser = super(ListL3AgentsHostingRouter, + self).get_parser(prog_name) + parser.add_argument('router', + help='router to query') + return parser + + def extend_list(self, data, parsed_args): + for agent in data: + agent['alive'] = ":-)" if agent['alive'] else 'xxx' + + def call_server(self, quantum_client, search_opts, parsed_args): + _id = quantumV20.find_resourceid_by_name_or_id(quantum_client, + 'router', + parsed_args.router) + search_opts['router'] = _id + data = quantum_client.list_l3_agent_hosting_routers(**search_opts) + return data diff --git a/quantumclient/shell.py b/quantumclient/shell.py index feb69411e..b12e00e60 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -234,7 +234,26 @@ def env(*_vars, **kwargs): 'quantumclient.quantum.v2_0.nvpnetworkgateway.ConnectNetworkGateway'), 'net-gateway-disconnect': utils.import_class( 'quantumclient.quantum.v2_0.nvpnetworkgateway.' - 'DisconnectNetworkGateway') + 'DisconnectNetworkGateway'), + 'dhcp-agent-network-add': utils.import_class( + 'quantumclient.quantum.v2_0.agentscheduler.AddNetworkToDhcpAgent'), + 'dhcp-agent-network-remove': utils.import_class( + 'quantumclient.quantum.v2_0.agentscheduler.' + 'RemoveNetworkFromDhcpAgent'), + 'net-list-on-dhcp-agent': utils.import_class( + 'quantumclient.quantum.v2_0.agentscheduler.' + 'ListNetworksOnDhcpAgent'), + 'dhcp-agent-list-hosting-net': utils.import_class( + 'quantumclient.quantum.v2_0.agentscheduler.' + 'ListDhcpAgentsHostingNetwork'), + 'l3-agent-router-add': utils.import_class( + 'quantumclient.quantum.v2_0.agentscheduler.AddRouterToL3Agent'), + 'l3-agent-router-remove': utils.import_class( + 'quantumclient.quantum.v2_0.agentscheduler.RemoveRouterFromL3Agent'), + 'router-list-on-l3-agent': utils.import_class( + 'quantumclient.quantum.v2_0.agentscheduler.ListRoutersOnL3Agent'), + 'l3-agent-list-hosting-router': utils.import_class( + 'quantumclient.quantum.v2_0.agentscheduler.ListL3AgentsHostingRouter'), } COMMANDS = {'2.0': COMMAND_V2} diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 3ee26e989..aa40a76e6 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -177,6 +177,10 @@ class Client(object): network_gateways_path = "/network-gateways" network_gateway_path = "/network-gateways/%s" + DHCP_NETS = '/dhcp-networks' + DHCP_AGENTS = '/dhcp-agents' + L3_ROUTERS = '/l3-routers' + L3_AGENTS = '/l3-agents' # API has no way to report plurals, so we have to hard code them EXTED_PLURALS = {'routers': 'router', 'floatingips': 'floatingip', @@ -788,6 +792,70 @@ def disconnect_network_gateway(self, gateway_id, body=None): base_uri = self.network_gateway_path % gateway_id return self.put("%s/disconnect_network" % base_uri, body=body) + @APIParamsCall + def list_dhcp_agent_hosting_networks(self, network, **_params): + """ + Fetches a list of dhcp agents hosting a network. + """ + return self.get((self.network_path + self.DHCP_AGENTS) % network, + params=_params) + + @APIParamsCall + def list_networks_on_dhcp_agent(self, dhcp_agent, **_params): + """ + Fetches a list of dhcp agents hosting a network. + """ + return self.get((self.agent_path + self.DHCP_NETS) % dhcp_agent, + params=_params) + + @APIParamsCall + def add_network_to_dhcp_agent(self, dhcp_agent, body=None): + """ + Adds a network to dhcp agent. + """ + return self.post((self.agent_path + self.DHCP_NETS) % dhcp_agent, + body=body) + + @APIParamsCall + def remove_network_from_dhcp_agent(self, dhcp_agent, network_id): + """ + Remove a network from dhcp agent. + """ + return self.delete((self.agent_path + self.DHCP_NETS + "/%s") % ( + dhcp_agent, network_id)) + + @APIParamsCall + def list_l3_agent_hosting_routers(self, router, **_params): + """ + Fetches a list of L3 agents hosting a router. + """ + return self.get((self.router_path + self.L3_AGENTS) % router, + params=_params) + + @APIParamsCall + def list_routers_on_l3_agent(self, l3_agent, **_params): + """ + Fetches a list of L3 agents hosting a router. + """ + return self.get((self.agent_path + self.L3_ROUTERS) % l3_agent, + params=_params) + + @APIParamsCall + def add_router_to_l3_agent(self, l3_agent, body): + """ + Adds a router to L3 agent. + """ + return self.post((self.agent_path + self.L3_ROUTERS) % l3_agent, + body=body) + + @APIParamsCall + def remove_router_from_l3_agent(self, l3_agent, router_id): + """ + Remove a router from l3 agent. + """ + return self.delete((self.agent_path + self.L3_ROUTERS + "/%s") % ( + l3_agent, router_id)) + def __init__(self, **kwargs): """ Initialize a new client for the Quantum v2.0 API. """ super(Client, self).__init__() From d514812c74ee32b45220145b2227a0f4dc129fc5 Mon Sep 17 00:00:00 2001 From: Mark McClain Date: Mon, 25 Feb 2013 18:58:51 -0500 Subject: [PATCH 084/135] rename port to port_protocol for lbaas cli fixes bug 1133057 Change-Id: Ic4b35e0c6335e3fc65022203a56ea2cce89f975c --- quantumclient/quantum/v2_0/lb/member.py | 13 +++++++--- quantumclient/quantum/v2_0/lb/vip.py | 8 +++--- .../tests/unit/lb/test_cli20_member.py | 15 ++++++----- quantumclient/tests/unit/lb/test_cli20_vip.py | 26 +++++++++---------- 4 files changed, 34 insertions(+), 28 deletions(-) diff --git a/quantumclient/quantum/v2_0/lb/member.py b/quantumclient/quantum/v2_0/lb/member.py index 25707175a..c43b94bb1 100644 --- a/quantumclient/quantum/v2_0/lb/member.py +++ b/quantumclient/quantum/v2_0/lb/member.py @@ -27,7 +27,9 @@ class ListMember(quantumv20.ListCommand): resource = 'member' log = logging.getLogger(__name__ + '.ListMember') - list_columns = ['id', 'address', 'port', 'admin_state_up', 'status'] + list_columns = [ + 'id', 'address', 'protocol_port', 'admin_state_up', 'status' + ] _formatters = {} @@ -57,7 +59,7 @@ def add_known_arguments(self, parser): default=True, action='store_false', help='set admin state up to false') parser.add_argument( - '--port', + '--protocol-port', required=True, help='port on which the pool member listens for requests or ' 'connections. ') @@ -74,8 +76,11 @@ def args2body(self, parsed_args): 'admin_state_up': parsed_args.admin_state_down, }, } - quantumv20.update_dict(parsed_args, body[self.resource], - ['address', 'port', 'weight', 'tenant_id']) + quantumv20.update_dict( + parsed_args, + body[self.resource], + ['address', 'protocol_port', 'weight', 'tenant_id'] + ) return body diff --git a/quantumclient/quantum/v2_0/lb/vip.py b/quantumclient/quantum/v2_0/lb/vip.py index 741b14d67..5c2c30416 100644 --- a/quantumclient/quantum/v2_0/lb/vip.py +++ b/quantumclient/quantum/v2_0/lb/vip.py @@ -27,7 +27,7 @@ class ListVip(quantumv20.ListCommand): resource = 'vip' log = logging.getLogger(__name__ + '.ListVip') - list_columns = ['id', 'name', 'algorithm', 'protocol', + list_columns = ['id', 'name', 'algorithm', 'address', 'protocol', 'admin_state_up', 'status'] _formatters = {} @@ -68,7 +68,7 @@ def add_known_arguments(self, parser): required=True, help='name of the vip') parser.add_argument( - '--port', + '--protocol-port', required=True, help='TCP port on which to listen for client traffic that is ' 'associated with the vip address') @@ -92,8 +92,8 @@ def args2body(self, parsed_args): } quantumv20.update_dict(parsed_args, body[self.resource], ['address', 'connection_limit', 'description', - 'name', 'port', 'protocol', 'subnet_id', - 'tenant_id']) + 'name', 'protocol_port', 'protocol', + 'subnet_id', 'tenant_id']) return body diff --git a/quantumclient/tests/unit/lb/test_cli20_member.py b/quantumclient/tests/unit/lb/test_cli20_member.py index 7155073a5..609bba18a 100644 --- a/quantumclient/tests/unit/lb/test_cli20_member.py +++ b/quantumclient/tests/unit/lb/test_cli20_member.py @@ -33,12 +33,11 @@ def test_create_member(self): tenant_id = 'my-tenant' my_id = 'my-id' pool_id = 'pool-id' - args = ['--address', address, '--port', port, + args = ['--address', address, '--protocol-port', port, '--tenant-id', tenant_id, pool_id] - position_names = ['address', 'port', 'tenant_id', 'pool_id', + position_names = ['address', 'protocol_port', 'tenant_id', 'pool_id', 'admin_state_up'] - position_values = [address, port, tenant_id, pool_id, - True] + position_values = [address, port, tenant_id, pool_id, True] self._test_create_resource(resource, cmd, None, my_id, args, position_names, position_values, admin_state_up=None) @@ -55,10 +54,12 @@ def test_create_member_all_params(self): my_id = 'my-id' pool_id = 'pool-id' args = ['--address', address, '--admin-state-down', - '--port', port, '--weight', weight, + '--protocol-port', port, '--weight', weight, '--tenant-id', tenant_id, pool_id] - position_names = ['address', 'admin_state_up', 'port', 'weight', - 'tenant_id', 'pool_id'] + position_names = [ + 'address', 'admin_state_up', 'protocol_port', 'weight', + 'tenant_id', 'pool_id' + ] position_values = [address, admin_state_up, port, weight, tenant_id, pool_id] self._test_create_resource(resource, cmd, None, my_id, args, diff --git a/quantumclient/tests/unit/lb/test_cli20_vip.py b/quantumclient/tests/unit/lb/test_cli20_vip.py index 8b7d17621..880c20abd 100644 --- a/quantumclient/tests/unit/lb/test_cli20_vip.py +++ b/quantumclient/tests/unit/lb/test_cli20_vip.py @@ -31,19 +31,19 @@ def test_create_vip_with_mandatory_params(self): pool_id = 'my-pool-id' name = 'my-name' subnet_id = 'subnet-id' - port = '1000' + protocol_port = '1000' protocol = 'tcp' tenant_id = 'my-tenant' my_id = 'my-id' args = ['--name', name, - '--port', port, + '--protocol-port', protocol_port, '--protocol', protocol, '--subnet-id', subnet_id, '--tenant-id', tenant_id, pool_id] - position_names = ['pool_id', 'name', 'port', 'protocol', + position_names = ['pool_id', 'name', 'protocol_port', 'protocol', 'subnet_id', 'tenant_id'] - position_values = [pool_id, name, port, protocol, + position_values = [pool_id, name, protocol_port, protocol, subnet_id, tenant_id] self._test_create_resource(resource, cmd, name, my_id, args, position_names, position_values, @@ -60,7 +60,7 @@ def test_create_vip_with_all_params(self): admin_state = False connection_limit = '1000' subnet_id = 'subnet-id' - port = '80' + protocol_port = '80' protocol = 'tcp' tenant_id = 'my-tenant' my_id = 'my-id' @@ -69,17 +69,17 @@ def test_create_vip_with_all_params(self): '--address', address, '--admin-state-down', '--connection-limit', connection_limit, - '--port', port, + '--protocol-port', protocol_port, '--protocol', protocol, '--subnet-id', subnet_id, '--tenant-id', tenant_id, pool_id] position_names = ['pool_id', 'name', 'description', 'address', - 'admin_state_up', 'connection_limit', 'port', - 'protocol', 'subnet_id', + 'admin_state_up', 'connection_limit', + 'protocol_port', 'protocol', 'subnet_id', 'tenant_id'] position_values = [pool_id, name, description, address, - admin_state, connection_limit, port, + admin_state, connection_limit, protocol_port, protocol, subnet_id, tenant_id] self._test_create_resource(resource, cmd, name, my_id, args, @@ -92,12 +92,12 @@ def test_create_vip_with_session_persistence_params(self): pool_id = 'my-pool-id' name = 'my-name' subnet_id = 'subnet-id' - port = '1000' + protocol_port = '1000' protocol = 'tcp' tenant_id = 'my-tenant' my_id = 'my-id' args = ['--name', name, - '--port', port, + '--protocol-port', protocol_port, '--protocol', protocol, '--subnet-id', subnet_id, '--tenant-id', tenant_id, @@ -105,9 +105,9 @@ def test_create_vip_with_session_persistence_params(self): '--session-persistence', 'type=dict', 'type=cookie,cookie_name=pie', '--optional-param', 'any'] - position_names = ['pool_id', 'name', 'port', 'protocol', + position_names = ['pool_id', 'name', 'protocol_port', 'protocol', 'subnet_id', 'tenant_id', 'optional_param'] - position_values = [pool_id, name, port, protocol, + position_values = [pool_id, name, protocol_port, protocol, subnet_id, tenant_id, 'any'] extra_body = { 'session_persistence': { From 49982a3aeab3445b2f99bedd21935710e0ddef5a Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Thu, 17 Jan 2013 19:38:36 +0800 Subject: [PATCH 085/135] Add pagination support for client Fixes bug 1130625 Add pagination params: -P, --page-size SIZE Add sorting params: --sort-key FIELD --sort-dir {asc,desc} Add xml support Change-Id: I76abb6335f53176feade216413a8cabaaefe42be --- quantumclient/common/constants.py | 2 + quantumclient/common/serializer.py | 21 ++++- quantumclient/quantum/v2_0/__init__.py | 51 +++++++++++ quantumclient/quantum/v2_0/floatingip.py | 2 + .../quantum/v2_0/lb/healthmonitor.py | 2 + quantumclient/quantum/v2_0/lb/member.py | 2 + quantumclient/quantum/v2_0/lb/pool.py | 2 + quantumclient/quantum/v2_0/lb/vip.py | 2 + quantumclient/quantum/v2_0/network.py | 13 +++ quantumclient/quantum/v2_0/port.py | 4 + quantumclient/quantum/v2_0/router.py | 2 + quantumclient/quantum/v2_0/securitygroup.py | 13 +++ quantumclient/quantum/v2_0/subnet.py | 2 + .../tests/unit/lb/test_cli20_healthmonitor.py | 25 ++++++ .../tests/unit/lb/test_cli20_member.py | 22 +++++ .../tests/unit/lb/test_cli20_pool.py | 22 +++++ quantumclient/tests/unit/lb/test_cli20_vip.py | 22 +++++ quantumclient/tests/unit/test_cli20.py | 61 ++++++++++++- .../tests/unit/test_cli20_floatingips.py | 21 +++++ .../tests/unit/test_cli20_network.py | 45 +++++++++- quantumclient/tests/unit/test_cli20_port.py | 21 +++++ quantumclient/tests/unit/test_cli20_router.py | 21 +++++ .../tests/unit/test_cli20_securitygroup.py | 61 ++++++++++++- quantumclient/tests/unit/test_cli20_subnet.py | 21 +++++ quantumclient/v2_0/client.py | 86 ++++++++++++++----- 25 files changed, 516 insertions(+), 30 deletions(-) diff --git a/quantumclient/common/constants.py b/quantumclient/common/constants.py index 572baa941..7e56331aa 100644 --- a/quantumclient/common/constants.py +++ b/quantumclient/common/constants.py @@ -22,6 +22,8 @@ TYPE_XMLNS = "xmlns:quantum" TYPE_ATTR = "quantum:type" VIRTUAL_ROOT_KEY = "_v_root" +ATOM_NAMESPACE = "http://www.w3.org/2005/Atom" +ATOM_LINK_NOTATION = "{%s}link" % ATOM_NAMESPACE TYPE_BOOL = "bool" TYPE_INT = "int" diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py index 7eba93069..535171bc4 100644 --- a/quantumclient/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -245,18 +245,33 @@ def _get_key(self, tag): else: return tag + def _get_links(self, root_tag, node): + link_nodes = node.findall(constants.ATOM_LINK_NOTATION) + root_tag = self._get_key(node.tag) + link_key = "%s_links" % root_tag + link_list = [] + for link in link_nodes: + link_list.append({'rel': link.get('rel'), + 'href': link.get('href')}) + # Remove link node in order to avoid link node being + # processed as an item in _from_xml_node + node.remove(link) + return link_list and {link_key: link_list} or {} + def _from_xml(self, datastring): if datastring is None: return None plurals = set(self.metadata.get('plurals', {})) try: node = etree.fromstring(datastring) - result = self._from_xml_node(node, plurals) root_tag = self._get_key(node.tag) + links = self._get_links(root_tag, node) + result = self._from_xml_node(node, plurals) + # There is no case where root_tag = constants.VIRTUAL_ROOT_KEY + # and links is not None because of the way data are serialized if root_tag == constants.VIRTUAL_ROOT_KEY: return result - else: - return {root_tag: result} + return dict({root_tag: result}, **links) except Exception as e: parseError = False # Python2.7 diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index cbf87b0d7..6101db946 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -90,6 +90,35 @@ def add_show_list_common_argument(parser): default=[]) +def add_pagination_argument(parser): + parser.add_argument( + '-P', '--page-size', + dest='page_size', metavar='SIZE', type=int, + help=("specify retrieve unit of each request, then split one request " + "to several requests"), + default=None) + + +def add_sorting_argument(parser): + parser.add_argument( + '--sort-key', + dest='sort_key', metavar='FIELD', + action='append', + help=("sort list by specified fields (This option can be repeated), " + "The number of sort_dir and sort_key should match each other, " + "more sort_dir specified will be omitted, less will be filled " + "with asc as default direction "), + default=[]) + parser.add_argument( + '--sort-dir', + dest='sort_dir', metavar='{asc,desc}', + help=("sort list in specified directions " + "(This option can be repeated)"), + action='append', + default=[], + choices=['asc', 'desc']) + + def add_extra_argument(parser, name, _help): parser.add_argument( name, @@ -411,12 +440,18 @@ class ListCommand(QuantumCommand, lister.Lister): _formatters = {} list_columns = [] unknown_parts_flag = True + pagination_support = False + sorting_support = False def get_parser(self, prog_name): parser = super(ListCommand, self).get_parser(prog_name) add_show_list_common_argument(parser) if self.unknown_parts_flag: add_extra_argument(parser, 'filter_specs', 'filters options') + if self.pagination_support: + add_pagination_argument(parser) + if self.sorting_support: + add_sorting_argument(parser) return parser def args2search_opts(self, parsed_args): @@ -445,6 +480,22 @@ def retrieve_list(self, parsed_args): self.values_specs) search_opts = self.args2search_opts(parsed_args) search_opts.update(_extra_values) + if self.pagination_support: + page_size = parsed_args.page_size + if page_size: + search_opts.update({'limit': page_size}) + if self.sorting_support: + keys = parsed_args.sort_key + if keys: + search_opts.update({'sort_key': keys}) + dirs = parsed_args.sort_dir + len_diff = len(keys) - len(dirs) + if len_diff > 0: + dirs += ['asc'] * len_diff + elif len_diff < 0: + dirs = dirs[:len(keys)] + if dirs: + search_opts.update({'sort_dir': dirs}) data = self.call_server(quantum_client, search_opts, parsed_args) collection = self.resource + "s" return data.get(collection, []) diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index 3198dfeb7..f4141f33d 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -34,6 +34,8 @@ class ListFloatingIP(ListCommand): _formatters = {} list_columns = ['id', 'fixed_ip_address', 'floating_ip_address', 'port_id'] + pagination_support = True + sorting_support = True class ShowFloatingIP(ShowCommand): diff --git a/quantumclient/quantum/v2_0/lb/healthmonitor.py b/quantumclient/quantum/v2_0/lb/healthmonitor.py index 9aa287401..3071527ff 100644 --- a/quantumclient/quantum/v2_0/lb/healthmonitor.py +++ b/quantumclient/quantum/v2_0/lb/healthmonitor.py @@ -29,6 +29,8 @@ class ListHealthMonitor(quantumv20.ListCommand): log = logging.getLogger(__name__ + '.ListHealthMonitor') list_columns = ['id', 'type', 'admin_state_up', 'status'] _formatters = {} + pagination_support = True + sorting_support = True class ShowHealthMonitor(quantumv20.ShowCommand): diff --git a/quantumclient/quantum/v2_0/lb/member.py b/quantumclient/quantum/v2_0/lb/member.py index c43b94bb1..e3da789fe 100644 --- a/quantumclient/quantum/v2_0/lb/member.py +++ b/quantumclient/quantum/v2_0/lb/member.py @@ -31,6 +31,8 @@ class ListMember(quantumv20.ListCommand): 'id', 'address', 'protocol_port', 'admin_state_up', 'status' ] _formatters = {} + pagination_support = True + sorting_support = True class ShowMember(quantumv20.ShowCommand): diff --git a/quantumclient/quantum/v2_0/lb/pool.py b/quantumclient/quantum/v2_0/lb/pool.py index b39c68459..049ac6749 100644 --- a/quantumclient/quantum/v2_0/lb/pool.py +++ b/quantumclient/quantum/v2_0/lb/pool.py @@ -30,6 +30,8 @@ class ListPool(quantumv20.ListCommand): list_columns = ['id', 'name', 'lb_method', 'protocol', 'admin_state_up', 'status'] _formatters = {} + pagination_support = True + sorting_support = True class ShowPool(quantumv20.ShowCommand): diff --git a/quantumclient/quantum/v2_0/lb/vip.py b/quantumclient/quantum/v2_0/lb/vip.py index 5c2c30416..c41fafa57 100644 --- a/quantumclient/quantum/v2_0/lb/vip.py +++ b/quantumclient/quantum/v2_0/lb/vip.py @@ -30,6 +30,8 @@ class ListVip(quantumv20.ListCommand): list_columns = ['id', 'name', 'algorithm', 'address', 'protocol', 'admin_state_up', 'status'] _formatters = {} + pagination_support = True + sorting_support = True class ShowVip(quantumv20.ShowCommand): diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index dfc0171b6..c7f0d8e45 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -41,11 +41,22 @@ class ListNetwork(ListCommand): log = logging.getLogger(__name__ + '.ListNetwork') _formatters = {'subnets': _format_subnets, } list_columns = ['id', 'name', 'subnets'] + pagination_support = True + sorting_support = True def extend_list(self, data, parsed_args): """Add subnet information to a network list""" quantum_client = self.get_client() search_opts = {'fields': ['id', 'cidr']} + if self.pagination_support: + page_size = parsed_args.page_size + if page_size: + search_opts.update({'limit': page_size}) + subnet_ids = [] + for n in data: + if 'subnets' in n: + subnet_ids.extend(n['subnets']) + search_opts.update({'id': subnet_ids}) subnets = quantum_client.list_subnets(**search_opts).get('subnets', []) subnet_dict = dict([(s['id'], s) for s in subnets]) for n in data: @@ -58,6 +69,8 @@ class ListExternalNetwork(ListNetwork): """List external networks that belong to a given tenant""" log = logging.getLogger(__name__ + '.ListExternalNetwork') + pagination_support = True + sorting_support = True def retrieve_list(self, parsed_args): external = '--router:external=True' diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 753df0ec3..e9465113e 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -41,6 +41,8 @@ class ListPort(ListCommand): log = logging.getLogger(__name__ + '.ListPort') _formatters = {'fixed_ips': _format_fixed_ips, } list_columns = ['id', 'name', 'mac_address', 'fixed_ips'] + pagination_support = True + sorting_support = True class ListRouterPort(ListCommand): @@ -50,6 +52,8 @@ class ListRouterPort(ListCommand): log = logging.getLogger(__name__ + '.ListRouterPort') _formatters = {'fixed_ips': _format_fixed_ips, } list_columns = ['id', 'name', 'mac_address', 'fixed_ips'] + pagination_support = True + sorting_support = True def get_parser(self, prog_name): parser = super(ListRouterPort, self).get_parser(prog_name) diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index a0de0c835..86eaa7229 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -42,6 +42,8 @@ class ListRouter(ListCommand): log = logging.getLogger(__name__ + '.ListRouter') _formatters = {'external_gateway_info': _format_external_gateway_info, } list_columns = ['id', 'name', 'external_gateway_info'] + pagination_support = True + sorting_support = True class ShowRouter(ShowCommand): diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py index 9442cccda..971a2c83f 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -28,6 +28,8 @@ class ListSecurityGroup(quantumv20.ListCommand): log = logging.getLogger(__name__ + '.ListSecurityGroup') _formatters = {} list_columns = ['id', 'name', 'description'] + pagination_support = True + sorting_support = True class ShowSecurityGroup(quantumv20.ShowCommand): @@ -81,6 +83,8 @@ class ListSecurityGroupRule(quantumv20.ListCommand): 'source_ip_prefix', 'source_group_id'] replace_rules = {'security_group_id': 'security_group', 'source_group_id': 'source_group'} + pagination_support = True + sorting_support = True def get_parser(self, prog_name): parser = super(ListSecurityGroupRule, self).get_parser(prog_name) @@ -106,6 +110,15 @@ def extend_list(self, data, parsed_args): return quantum_client = self.get_client() search_opts = {'fields': ['id', 'name']} + if self.pagination_support: + page_size = parsed_args.page_size + if page_size: + search_opts.update({'limit': page_size}) + sec_group_ids = set() + for rule in data: + for key in self.replace_rules: + sec_group_ids.add(rule[key]) + search_opts.update({"id": sec_group_ids}) secgroups = quantum_client.list_security_groups(**search_opts) secgroups = secgroups.get('security_groups', []) sg_dict = dict([(sg['id'], sg['name']) diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 9339ff58b..4442b721f 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -61,6 +61,8 @@ class ListSubnet(ListCommand): 'dns_nameservers': _format_dns_nameservers, 'host_routes': _format_host_routes, } list_columns = ['id', 'name', 'cidr', 'allocation_pools'] + pagination_support = True + sorting_support = True class ShowSubnet(ShowCommand): diff --git a/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py b/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py index 657b24e73..ab5327c59 100644 --- a/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py +++ b/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py @@ -92,6 +92,31 @@ def test_list_healthmonitors(self): None) self._test_list_resources(resources, cmd, True) + def test_list_healthmonitors_pagination(self): + """lb-healthmonitor-list""" + resources = "health_monitors" + cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_healthmonitors_sort(self): + """lb-healthmonitor-list --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "health_monitors" + cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_healthmonitors_limit(self): + """lb-healthmonitor-list -P""" + resources = "health_monitors" + cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), + None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_healthmonitor_id(self): """lb-healthmonitor-show test_id""" resource = 'health_monitor' diff --git a/quantumclient/tests/unit/lb/test_cli20_member.py b/quantumclient/tests/unit/lb/test_cli20_member.py index 609bba18a..2913d4c45 100644 --- a/quantumclient/tests/unit/lb/test_cli20_member.py +++ b/quantumclient/tests/unit/lb/test_cli20_member.py @@ -72,6 +72,28 @@ def test_list_members(self): cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_members_pagination(self): + """lb-member-list""" + resources = "members" + cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_members_sort(self): + """lb-member-list --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "members" + cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_members_limit(self): + """lb-member-list -P""" + resources = "members" + cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_member_id(self): """lb-member-show test_id""" resource = 'member' diff --git a/quantumclient/tests/unit/lb/test_cli20_pool.py b/quantumclient/tests/unit/lb/test_cli20_pool.py index ee2f4e3eb..4bf6d3c47 100644 --- a/quantumclient/tests/unit/lb/test_cli20_pool.py +++ b/quantumclient/tests/unit/lb/test_cli20_pool.py @@ -80,6 +80,28 @@ def test_list_pools(self): cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_pools_pagination(self): + """lb-pool-list""" + resources = "pools" + cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_pools_sort(self): + """lb-pool-list --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "pools" + cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_pools_limit(self): + """lb-pool-list -P""" + resources = "pools" + cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_pool_id(self): """lb-pool-show test_id""" resource = 'pool' diff --git a/quantumclient/tests/unit/lb/test_cli20_vip.py b/quantumclient/tests/unit/lb/test_cli20_vip.py index 880c20abd..878e99f95 100644 --- a/quantumclient/tests/unit/lb/test_cli20_vip.py +++ b/quantumclient/tests/unit/lb/test_cli20_vip.py @@ -125,6 +125,28 @@ def test_list_vips(self): cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_vips_pagination(self): + """lb-vip-list""" + resources = "vips" + cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_vips_sort(self): + """lb-vip-list --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "vips" + cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_vips_limit(self): + """lb-vip-list -P""" + resources = "vips" + cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_vip_id(self): """lb-vip-show test_id""" resource = 'vip' diff --git a/quantumclient/tests/unit/test_cli20.py b/quantumclient/tests/unit/test_cli20.py index 4e62edaad..21b1b5179 100644 --- a/quantumclient/tests/unit/test_cli20.py +++ b/quantumclient/tests/unit/test_cli20.py @@ -225,7 +225,8 @@ def _test_list_columns(self, cmd, resources_collection, self.mox.UnsetStubs() def _test_list_resources(self, resources, cmd, detail=False, tags=[], - fields_1=[], fields_2=[]): + fields_1=[], fields_2=[], page_size=None, + sort_key=[], sort_dir=[]): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) @@ -263,6 +264,32 @@ def _test_list_resources(self, resources, cmd, detail=False, tags=[], query += "&fields=" + field else: query = "fields=" + field + if page_size: + args.append("--page-size") + args.append(str(page_size)) + if query: + query += "&limit=%s" % page_size + else: + query = "limit=%s" % page_size + if sort_key: + for key in sort_key: + args.append('--sort-key') + args.append(key) + if query: + query += '&' + query += 'sort_key=%s' % key + if sort_dir: + len_diff = len(sort_key) - len(sort_dir) + if len_diff > 0: + sort_dir += ['asc'] * len_diff + elif len_diff < 0: + sort_dir = sort_dir[:len(sort_key)] + for dir in sort_dir: + args.append('--sort-dir') + args.append(dir) + if query: + query += '&' + query += 'sort_dir=%s' % dir path = getattr(self.client, resources + "_path") self.client.httpclient.request( MyUrlComparator(end_url(path, query), self.client), 'GET', @@ -277,6 +304,38 @@ def _test_list_resources(self, resources, cmd, detail=False, tags=[], _str = self.fake_stdout.make_string() self.assertTrue('myid1' in _str) + def _test_list_resources_with_pagination(self, resources, cmd): + self.mox.StubOutWithMock(cmd, "get_client") + self.mox.StubOutWithMock(self.client.httpclient, "request") + cmd.get_client().MultipleTimes().AndReturn(self.client) + path = getattr(self.client, resources + "_path") + fake_query = "marker=myid2&limit=2" + reses1 = {resources: [{'id': 'myid1', }, + {'id': 'myid2', }], + '%s_links' % resources: [{'href': end_url(path, fake_query), + 'rel': 'next'}]} + reses2 = {resources: [{'id': 'myid3', }, + {'id': 'myid4', }]} + resstr1 = self.client.serialize(reses1) + resstr2 = self.client.serialize(reses2) + self.client.httpclient.request( + end_url(path, ""), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(200), resstr1)) + self.client.httpclient.request( + end_url(path, fake_query), 'GET', + body=None, + headers=ContainsKeyValue('X-Auth-Token', + TOKEN)).AndReturn((MyResp(200), resstr2)) + self.mox.ReplayAll() + cmd_parser = cmd.get_parser("list_" + resources) + + parsed_args = cmd_parser.parse_args("") + cmd.run(parsed_args) + self.mox.VerifyAll() + self.mox.UnsetStubs() + def _test_update_resource(self, resource, cmd, myid, args, extrafields): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") diff --git a/quantumclient/tests/unit/test_cli20_floatingips.py b/quantumclient/tests/unit/test_cli20_floatingips.py index 7b5912c00..620470d3c 100644 --- a/quantumclient/tests/unit/test_cli20_floatingips.py +++ b/quantumclient/tests/unit/test_cli20_floatingips.py @@ -86,6 +86,27 @@ def test_list_floatingips(self): cmd = ListFloatingIP(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_floatingips_pagination(self): + resources = 'floatingips' + cmd = ListFloatingIP(MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_floatingips_sort(self): + """list floatingips: --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = 'floatingips' + cmd = ListFloatingIP(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_floatingips_limit(self): + """list floatingips: -P.""" + resources = 'floatingips' + cmd = ListFloatingIP(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_delete_floatingip(self): """Delete floatingip: fip1""" resource = 'floatingip' diff --git a/quantumclient/tests/unit/test_cli20_network.py b/quantumclient/tests/unit/test_cli20_network.py index 6bc9ebd4c..a0a7c9064 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/quantumclient/tests/unit/test_cli20_network.py @@ -128,12 +128,47 @@ def test_list_nets_empty_with_column(self): self.assertEquals('\n', _str) def _test_list_networks(self, cmd, detail=False, tags=[], - fields_1=[], fields_2=[]): + fields_1=[], fields_2=[], page_size=None, + sort_key=[], sort_dir=[]): resources = "networks" self.mox.StubOutWithMock(ListNetwork, "extend_list") ListNetwork.extend_list(IsA(list), IgnoreArg()) self._test_list_resources(resources, cmd, detail, tags, - fields_1, fields_2) + fields_1, fields_2, page_size=page_size, + sort_key=sort_key, sort_dir=sort_dir) + + def test_list_nets_pagination(self): + cmd = ListNetwork(MyApp(sys.stdout), None) + self.mox.StubOutWithMock(ListNetwork, "extend_list") + ListNetwork.extend_list(IsA(list), IgnoreArg()) + self._test_list_resources_with_pagination("networks", cmd) + + def test_list_nets_sort(self): + """list nets: --sort-key name --sort-key id --sort-dir asc + --sort-dir desc + """ + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_networks(cmd, sort_key=['name', 'id'], + sort_dir=['asc', 'desc']) + + def test_list_nets_sort_with_keys_more_than_dirs(self): + """list nets: --sort-key name --sort-key id --sort-dir desc + """ + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_networks(cmd, sort_key=['name', 'id'], + sort_dir=['desc']) + + def test_list_nets_sort_with_dirs_more_than_keys(self): + """list nets: --sort-key name --sort-dir desc --sort-dir asc + """ + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_networks(cmd, sort_key=['name'], + sort_dir=['desc', 'asc']) + + def test_list_nets_limit(self): + """list nets: -P""" + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_networks(cmd, page_size=1000) def test_list_nets_detail(self): """list nets: -D.""" @@ -170,11 +205,15 @@ def setup_list_stub(resources, data, query): cmd.get_client().AndReturn(self.client) setup_list_stub('networks', data, '') cmd.get_client().AndReturn(self.client) + filters = '' + for n in data: + for s in n['subnets']: + filters = filters + "&id=%s" % s setup_list_stub('subnets', [{'id': 'mysubid1', 'cidr': '192.168.1.0/24'}, {'id': 'mysubid2', 'cidr': '172.16.0.0/24'}, {'id': 'mysubid3', 'cidr': '10.1.1.0/24'}], - query='fields=id&fields=cidr') + query='fields=id&fields=cidr' + filters) self.mox.ReplayAll() args = [] diff --git a/quantumclient/tests/unit/test_cli20_port.py b/quantumclient/tests/unit/test_cli20_port.py index faec572ea..65cfc99a5 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/quantumclient/tests/unit/test_cli20_port.py @@ -140,6 +140,27 @@ def test_list_ports(self): cmd = ListPort(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_ports_pagination(self): + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_ports_sort(self): + """list ports: --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_ports_limit(self): + """list ports: -P""" + resources = "ports" + cmd = ListPort(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_list_ports_tags(self): """List ports: -- --tags a b.""" resources = "ports" diff --git a/quantumclient/tests/unit/test_cli20_router.py b/quantumclient/tests/unit/test_cli20_router.py index 502365db3..1875b8abf 100644 --- a/quantumclient/tests/unit/test_cli20_router.py +++ b/quantumclient/tests/unit/test_cli20_router.py @@ -76,6 +76,27 @@ def test_list_routers_detail(self): cmd = ListRouter(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_routers_pagination(self): + resources = "routers" + cmd = ListRouter(MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_routers_sort(self): + """list routers: --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "routers" + cmd = ListRouter(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_routers_limit(self): + """list routers: -P""" + resources = "routers" + cmd = ListRouter(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_update_router_exception(self): """Update router: myid.""" resource = 'router' diff --git a/quantumclient/tests/unit/test_cli20_securitygroup.py b/quantumclient/tests/unit/test_cli20_securitygroup.py index 7a2e941b8..b5891f4e7 100644 --- a/quantumclient/tests/unit/test_cli20_securitygroup.py +++ b/quantumclient/tests/unit/test_cli20_securitygroup.py @@ -73,6 +73,26 @@ def test_list_security_groups(self): test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) + def test_list_security_groups_pagination(self): + resources = "security_groups" + cmd = securitygroup.ListSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_security_groups_sort(self): + resources = "security_groups" + cmd = securitygroup.ListSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_security_groups_limit(self): + resources = "security_groups" + cmd = securitygroup.ListSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_security_group_id(self): resource = 'security_group' cmd = securitygroup.ShowSecurityGroup( @@ -145,6 +165,38 @@ def test_list_security_group_rules(self): mox.IgnoreArg()) self._test_list_resources(resources, cmd, True) + def test_list_security_group_rules_pagination(self): + resources = "security_group_rules" + cmd = securitygroup.ListSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(securitygroup.ListSecurityGroupRule, + "extend_list") + securitygroup.ListSecurityGroupRule.extend_list(mox.IsA(list), + mox.IgnoreArg()) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_security_group_rules_sort(self): + resources = "security_group_rules" + cmd = securitygroup.ListSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(securitygroup.ListSecurityGroupRule, + "extend_list") + securitygroup.ListSecurityGroupRule.extend_list(mox.IsA(list), + mox.IgnoreArg()) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_security_group_rules_limit(self): + resources = "security_group_rules" + cmd = securitygroup.ListSecurityGroupRule( + test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(securitygroup.ListSecurityGroupRule, + "extend_list") + securitygroup.ListSecurityGroupRule.extend_list(mox.IsA(list), + mox.IgnoreArg()) + self._test_list_resources(resources, cmd, page_size=1000) + def test_show_security_group_rule(self): resource = 'security_group_rule' cmd = securitygroup.ShowSecurityGroupRule( @@ -196,11 +248,18 @@ def setup_list_stub(resources, data, query): setup_list_stub('security_group_rules', list_data, query) if conv: cmd.get_client().AndReturn(self.client) + sec_ids = set() + for n in data['data']: + sec_ids.add(n[1]) + sec_ids.add(n[2]) + filters = '' + for id in sec_ids: + filters = filters + "&id=%s" % id setup_list_stub('security_groups', [{'id': 'myid1', 'name': 'group1'}, {'id': 'myid2', 'name': 'group2'}, {'id': 'myid3', 'name': 'group3'}], - query='fields=id&fields=name') + query='fields=id&fields=name' + filters) self.mox.ReplayAll() cmd_parser = cmd.get_parser('list_security_group_rules') diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/quantumclient/tests/unit/test_cli20_subnet.py index 9b9650e16..ca2794502 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/quantumclient/tests/unit/test_cli20_subnet.py @@ -331,6 +331,27 @@ def test_list_subnets_fields(self): self._test_list_resources(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) + def test_list_subnets_pagination(self): + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources_with_pagination(resources, cmd) + + def test_list_subnets_sort(self): + """List subnets: --sort-key name --sort-key id --sort-key asc + --sort-key desc + """ + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, + sort_key=["name", "id"], + sort_dir=["asc", "desc"]) + + def test_list_subnets_limit(self): + """List subnets: -P.""" + resources = "subnets" + cmd = ListSubnet(MyApp(sys.stdout), None) + self._test_list_resources(resources, cmd, page_size=1000) + def test_update_subnet(self): """Update subnet: myid --name myname --tags a b.""" resource = 'subnet' diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index aa40a76e6..3010354cb 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -19,6 +19,7 @@ import logging import time import urllib +import urlparse from quantumclient.client import HTTPClient from quantumclient.common import _ @@ -245,12 +246,13 @@ def show_extension(self, ext_alias, **_params): return self.get(self.ext_path % ext_alias, params=_params) @APIParamsCall - def list_ports(self, **_params): + def list_ports(self, retrieve_all=True, **_params): """ Fetches a list of all networks for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.ports_path, params=_params) + return self.list('ports', self.ports_path, retrieve_all, + **_params) @APIParamsCall def show_port(self, port, **_params): @@ -281,12 +283,13 @@ def delete_port(self, port): return self.delete(self.port_path % (port)) @APIParamsCall - def list_networks(self, **_params): + def list_networks(self, retrieve_all=True, **_params): """ Fetches a list of all networks for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.networks_path, params=_params) + return self.list('networks', self.networks_path, retrieve_all, + **_params) @APIParamsCall def show_network(self, network, **_params): @@ -317,11 +320,12 @@ def delete_network(self, network): return self.delete(self.network_path % (network)) @APIParamsCall - def list_subnets(self, **_params): + def list_subnets(self, retrieve_all=True, **_params): """ Fetches a list of all networks for a tenant """ - return self.get(self.subnets_path, params=_params) + return self.list('subnets', self.subnets_path, retrieve_all, + **_params) @APIParamsCall def show_subnet(self, subnet, **_params): @@ -352,12 +356,13 @@ def delete_subnet(self, subnet): return self.delete(self.subnet_path % (subnet)) @APIParamsCall - def list_routers(self, **_params): + def list_routers(self, retrieve_all=True, **_params): """ Fetches a list of all routers for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.routers_path, params=_params) + return self.list('routers', self.routers_path, retrieve_all, + **_params) @APIParamsCall def show_router(self, router, **_params): @@ -420,12 +425,13 @@ def remove_gateway_router(self, router): body={'router': {'external_gateway_info': {}}}) @APIParamsCall - def list_floatingips(self, **_params): + def list_floatingips(self, retrieve_all=True, **_params): """ Fetches a list of all floatingips for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.floatingips_path, params=_params) + return self.list('floatingips', self.floatingips_path, retrieve_all, + **_params) @APIParamsCall def show_floatingip(self, floatingip, **_params): @@ -463,11 +469,12 @@ def create_security_group(self, body=None): return self.post(self.security_groups_path, body=body) @APIParamsCall - def list_security_groups(self, **_params): + def list_security_groups(self, retrieve_all=True, **_params): """ Fetches a list of all security groups for a tenant """ - return self.get(self.security_groups_path, params=_params) + return self.list('security_groups', self.security_groups_path, + retrieve_all, **_params) @APIParamsCall def show_security_group(self, security_group, **_params): @@ -500,11 +507,13 @@ def delete_security_group_rule(self, security_group_rule): (security_group_rule)) @APIParamsCall - def list_security_group_rules(self, **_params): + def list_security_group_rules(self, retrieve_all=True, **_params): """ Fetches a list of all security group rules for a tenant """ - return self.get(self.security_group_rules_path, params=_params) + return self.list('security_group_rules', + self.security_group_rules_path, + retrieve_all, **_params) @APIParamsCall def show_security_group_rule(self, security_group_rule, **_params): @@ -515,12 +524,13 @@ def show_security_group_rule(self, security_group_rule, **_params): params=_params) @APIParamsCall - def list_vips(self, **_params): + def list_vips(self, retrieve_all=True, **_params): """ Fetches a list of all load balancer vips for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.vips_path, params=_params) + return self.list('vips', self.vips_path, retrieve_all, + **_params) @APIParamsCall def show_vip(self, vip, **_params): @@ -551,12 +561,13 @@ def delete_vip(self, vip): return self.delete(self.vip_path % (vip)) @APIParamsCall - def list_pools(self, **_params): + def list_pools(self, retrieve_all=True, **_params): """ Fetches a list of all load balancer pools for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.pools_path, params=_params) + return self.list('pools', self.pools_path, retrieve_all, + **_params) @APIParamsCall def show_pool(self, pool, **_params): @@ -594,12 +605,13 @@ def retrieve_pool_stats(self, pool, **_params): return self.get(self.pool_path_stats % (pool), params=_params) @APIParamsCall - def list_members(self, **_params): + def list_members(self, retrieve_all=True, **_params): """ Fetches a list of all load balancer members for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.members_path, params=_params) + return self.list('members', self.members_path, retrieve_all, + **_params) @APIParamsCall def show_member(self, member, **_params): @@ -630,12 +642,13 @@ def delete_member(self, member): return self.delete(self.member_path % (member)) @APIParamsCall - def list_health_monitors(self, **_params): + def list_health_monitors(self, retrieve_all=True, **_params): """ Fetches a list of all load balancer health monitors for a tenant """ # Pass filters in "params" argument to do_request - return self.get(self.health_monitors_path, params=_params) + return self.list('health_monitors', self.health_monitors_path, + retrieve_all, **_params) @APIParamsCall def show_health_monitor(self, health_monitor, **_params): @@ -976,3 +989,32 @@ def post(self, action, body=None, headers=None, params=None): def put(self, action, body=None, headers=None, params=None): return self.retry_request("PUT", action, body=body, headers=headers, params=params) + + def list(self, collection, path, retrieve_all=True, **params): + if retrieve_all: + res = [] + for r in self._pagination(collection, path, **params): + res.extend(r[collection]) + return {collection: res} + else: + return self._pagination(collection, path, **params) + + def _pagination(self, collection, path, **params): + if params.get('page_reverse', False): + linkrel = 'previous' + else: + linkrel = 'next' + next = True + while next: + res = self.get(path, params=params) + yield res + next = False + try: + for link in res['%s_links' % collection]: + if link['rel'] == linkrel: + query_str = urlparse.urlparse(link['href']).query + params = urlparse.parse_qs(query_str) + next = True + break + except KeyError: + break From 77ea68e4527d41409f264286ae1cf296ab9152c2 Mon Sep 17 00:00:00 2001 From: Aaron Rosen Date: Mon, 4 Mar 2013 10:50:54 -0800 Subject: [PATCH 086/135] Rename source_(group_id/ip_prefix) to remote_(group_id/ip_prefix) Fixes bug 1144426 Change-Id: I3c5ac92f583ffce19f5ed38219d796bc6585e123 --- quantumclient/quantum/v2_0/securitygroup.py | 26 ++++++++-------- .../tests/unit/test_cli20_securitygroup.py | 30 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py index 971a2c83f..5c531895c 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -80,9 +80,9 @@ class ListSecurityGroupRule(quantumv20.ListCommand): log = logging.getLogger(__name__ + '.ListSecurityGroupRule') _formatters = {} list_columns = ['id', 'security_group_id', 'direction', 'protocol', - 'source_ip_prefix', 'source_group_id'] + 'remote_ip_prefix', 'remote_group_id'] replace_rules = {'security_group_id': 'security_group', - 'source_group_id': 'source_group'} + 'remote_group_id': 'remote_group'} pagination_support = True sorting_support = True @@ -185,16 +185,16 @@ def add_known_arguments(self, parser): '--port_range_max', help=argparse.SUPPRESS) parser.add_argument( - '--source-ip-prefix', + '--remote-ip-prefix', help='cidr to match on') parser.add_argument( - '--source_ip_prefix', + '--remote_ip_prefix', help=argparse.SUPPRESS) parser.add_argument( - '--source-group-id', metavar='SOURCE_GROUP', - help='source security group name or id to apply rule') + '--remote-group-id', metavar='SOURCE_GROUP', + help='remote security group name or id to apply rule') parser.add_argument( - '--source_group_id', + '--remote_group_id', help=argparse.SUPPRESS) def args2body(self, parsed_args): @@ -213,15 +213,15 @@ def args2body(self, parsed_args): if parsed_args.port_range_max: body['security_group_rule'].update( {'port_range_max': parsed_args.port_range_max}) - if parsed_args.source_ip_prefix: + if parsed_args.remote_ip_prefix: body['security_group_rule'].update( - {'source_ip_prefix': parsed_args.source_ip_prefix}) - if parsed_args.source_group_id: - _source_group_id = quantumv20.find_resourceid_by_name_or_id( + {'remote_ip_prefix': parsed_args.remote_ip_prefix}) + if parsed_args.remote_group_id: + _remote_group_id = quantumv20.find_resourceid_by_name_or_id( self.get_client(), 'security_group', - parsed_args.source_group_id) + parsed_args.remote_group_id) body['security_group_rule'].update( - {'source_group_id': _source_group_id}) + {'remote_group_id': _remote_group_id}) if parsed_args.tenant_id: body['security_group_rule'].update( {'tenant_id': parsed_args.tenant_id}) diff --git a/quantumclient/tests/unit/test_cli20_securitygroup.py b/quantumclient/tests/unit/test_cli20_securitygroup.py index b5891f4e7..2884e3b2c 100644 --- a/quantumclient/tests/unit/test_cli20_securitygroup.py +++ b/quantumclient/tests/unit/test_cli20_securitygroup.py @@ -129,19 +129,19 @@ def test_create_security_group_rule_full(self): protocol = 'tcp' port_range_min = '22' port_range_max = '22' - source_ip_prefix = '10.0.0.0/24' + remote_ip_prefix = '10.0.0.0/24' security_group_id = '1' - source_group_id = '1' - args = ['--source_ip_prefix', source_ip_prefix, '--direction', + remote_group_id = '1' + args = ['--remote_ip_prefix', remote_ip_prefix, '--direction', direction, '--ethertype', ethertype, '--protocol', protocol, '--port_range_min', port_range_min, '--port_range_max', - port_range_max, '--source_group_id', source_group_id, + port_range_max, '--remote_group_id', remote_group_id, security_group_id] - position_names = ['source_ip_prefix', 'direction', 'ethertype', + position_names = ['remote_ip_prefix', 'direction', 'ethertype', 'protocol', 'port_range_min', 'port_range_max', - 'source_group_id', 'security_group_id'] - position_values = [source_ip_prefix, direction, ethertype, protocol, - port_range_min, port_range_max, source_group_id, + 'remote_group_id', 'security_group_id'] + position_values = [remote_ip_prefix, direction, ethertype, protocol, + port_range_min, port_range_max, remote_group_id, security_group_id] self._test_create_resource(resource, cmd, None, myid, args, position_names, position_values) @@ -220,11 +220,11 @@ def setup_list_stub(resources, data, query): 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(resp) # Setup the default data - _data = {'cols': ['id', 'security_group_id', 'source_group_id'], + _data = {'cols': ['id', 'security_group_id', 'remote_group_id'], 'data': [('ruleid1', 'myid1', 'myid1'), ('ruleid2', 'myid2', 'myid3'), ('ruleid3', 'myid2', 'myid2')]} - _expected = {'cols': ['id', 'security_group', 'source_group'], + _expected = {'cols': ['id', 'security_group', 'remote_group'], 'data': [('ruleid1', 'group1', 'group1'), ('ruleid2', 'group2', 'group3'), ('ruleid3', 'group2', 'group2')]} @@ -280,7 +280,7 @@ def test_list_security_group_rules_extend_source_id(self): self._test_list_security_group_rules_extend() def test_list_security_group_rules_extend_no_nameconv(self): - expected = {'cols': ['id', 'security_group_id', 'source_group_id'], + expected = {'cols': ['id', 'security_group_id', 'remote_group_id'], 'data': [('ruleid1', 'myid1', 'myid1'), ('ruleid2', 'myid2', 'myid3'), ('ruleid3', 'myid2', 'myid2')]} @@ -289,19 +289,19 @@ def test_list_security_group_rules_extend_no_nameconv(self): args=args, conv=False) def test_list_security_group_rules_extend_with_columns(self): - args = '-c id -c security_group_id -c source_group_id'.split() + args = '-c id -c security_group_id -c remote_group_id'.split() self._test_list_security_group_rules_extend(args=args) def test_list_security_group_rules_extend_with_columns_no_id(self): - args = '-c id -c security_group -c source_group'.split() + args = '-c id -c security_group -c remote_group'.split() self._test_list_security_group_rules_extend(args=args) def test_list_security_group_rules_extend_with_fields(self): - args = '-F id -F security_group_id -F source_group_id'.split() + args = '-F id -F security_group_id -F remote_group_id'.split() self._test_list_security_group_rules_extend(args=args, query_field=True) def test_list_security_group_rules_extend_with_fields_no_id(self): - args = '-F id -F security_group -F source_group'.split() + args = '-F id -F security_group -F remote_group'.split() self._test_list_security_group_rules_extend(args=args, query_field=True) From 3709182a4632d61338b3360a09acdab5b6d8750c Mon Sep 17 00:00:00 2001 From: Gary Kotton Date: Wed, 6 Mar 2013 11:23:28 +0000 Subject: [PATCH 087/135] Add support for security group quotas Fixes bug 1149286 Change-Id: I7db9416c00296dfefdbf8fef880154c05fa2445a --- quantumclient/quantum/v2_0/quota.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/quantumclient/quantum/v2_0/quota.py b/quantumclient/quantum/v2_0/quota.py index 0e749491e..82dfbe75f 100644 --- a/quantumclient/quantum/v2_0/quota.py +++ b/quantumclient/quantum/v2_0/quota.py @@ -157,19 +157,25 @@ def get_parser(self, prog_name): help=argparse.SUPPRESS) parser.add_argument( '--network', metavar='networks', - help='the limit of network quota') + help='the limit of networks') parser.add_argument( '--subnet', metavar='subnets', - help='the limit of subnet quota') + help='the limit of subnets') parser.add_argument( '--port', metavar='ports', - help='the limit of port quota') + help='the limit of ports') parser.add_argument( '--router', metavar='routers', - help='the limit of router quota') + help='the limit of routers') parser.add_argument( '--floatingip', metavar='floatingips', - help='the limit of floating IP quota') + help='the limit of floating IPs') + parser.add_argument( + '--security-group', metavar='security_groups', + help='the limit of security groups') + parser.add_argument( + '--security-group-rule', metavar='security_group_rules', + help='the limit of security groups rules') quantumv20.add_extra_argument( parser, 'value_specs', 'new values for the %s' % self.resource) @@ -189,7 +195,8 @@ def get_data(self, parsed_args): quantum_client = self.get_client() quantum_client.format = parsed_args.request_format quota = {} - for resource in ('network', 'subnet', 'port', 'router', 'floatingip'): + for resource in ('network', 'subnet', 'port', 'router', 'floatingip', + 'security_group', 'security_group_rule'): if getattr(parsed_args, resource): quota[resource] = self._validate_int( resource, From f96219d67a56976e69a4b22d7efe7229d5e20345 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Thu, 7 Mar 2013 14:24:52 +0900 Subject: [PATCH 088/135] Fix a description of floatingip-id in (dis)associate commands Fixes bug 1151328 Also makes positional parameters of Show/Update/Delete commands upper. Change-Id: Ifc6b76954ad987379f765f346167bb73556034f1 --- quantumclient/quantum/v2_0/__init__.py | 6 +++--- quantumclient/quantum/v2_0/floatingip.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 6101db946..0f48b9bc5 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -358,7 +358,7 @@ class UpdateCommand(QuantumCommand): def get_parser(self, prog_name): parser = super(UpdateCommand, self).get_parser(prog_name) parser.add_argument( - 'id', metavar=self.resource, + 'id', metavar=self.resource.upper(), help='ID or name of %s to update' % self.resource) self.add_known_arguments(parser) return parser @@ -407,7 +407,7 @@ def get_parser(self, prog_name): else: help_str = 'ID of %s to delete' parser.add_argument( - 'id', metavar=self.resource, + 'id', metavar=self.resource.upper(), help=help_str % self.resource) return parser @@ -550,7 +550,7 @@ def get_parser(self, prog_name): else: help_str = 'ID of %s to look up' parser.add_argument( - 'id', metavar=self.resource, + 'id', metavar=self.resource.upper(), help=help_str % self.resource) return parser diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index f4141f33d..3916527c5 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -102,11 +102,11 @@ class AssociateFloatingIP(QuantumCommand): def get_parser(self, prog_name): parser = super(AssociateFloatingIP, self).get_parser(prog_name) parser.add_argument( - 'floatingip_id', metavar='floatingip-id', - help='IP address of the floating IP to associate') + 'floatingip_id', metavar='FLOATINGIP_ID', + help='ID of the floating IP to associate') parser.add_argument( - 'port_id', - help='ID of the port to be associated with the floatingip') + 'port_id', metavar='PORT', + help='ID or name of the port to be associated with the floatingip') parser.add_argument( '--fixed-ip-address', help=('IP address on the port (only required if port has multiple' @@ -142,8 +142,8 @@ class DisassociateFloatingIP(QuantumCommand): def get_parser(self, prog_name): parser = super(DisassociateFloatingIP, self).get_parser(prog_name) parser.add_argument( - 'floatingip_id', metavar='floatingip-id', - help='IP address of the floating IP to associate') + 'floatingip_id', metavar='FLOATINGIP_ID', + help='ID of the floating IP to associate') return parser def run(self, parsed_args): From 772aaba5d6d294730fbfb5c23435716d79c7abf7 Mon Sep 17 00:00:00 2001 From: Tatyana Leontovich Date: Fri, 1 Mar 2013 16:50:30 +0200 Subject: [PATCH 089/135] Improve unit tests for python-quantumclient Add tests for quantumclient.quantum.v2.quota Partially Fixes: bug #1137783 Change-Id: I62fef9334ac864eda2f980d8ad173c536b2f3b1b --- quantumclient/tests/unit/test_quota.py | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 quantumclient/tests/unit/test_quota.py diff --git a/quantumclient/tests/unit/test_quota.py b/quantumclient/tests/unit/test_quota.py new file mode 100644 index 000000000..72d589860 --- /dev/null +++ b/quantumclient/tests/unit/test_quota.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# Copyright (C) 2013 Yahoo! Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import sys + +from quantumclient.common import exceptions +from quantumclient.quantum.v2_0 import quota as test_quota +from quantumclient.tests.unit import test_cli20 + + +class CLITestV20Quota(test_cli20.CLITestV20Base): + def test_show_quota(self): + resource = 'quota' + cmd = test_quota.ShowQuota( + test_cli20.MyApp(sys.stdout), None) + args = ['--tenant-id', self.test_id] + self._test_show_resource(resource, cmd, self.test_id, args) + + def test_update_quota(self): + resource = 'quota' + cmd = test_quota.UpdateQuota( + test_cli20.MyApp(sys.stdout), None) + args = ['--tenant-id', self.test_id, '--network', 'test'] + self.assertRaises( + exceptions.QuantumClientException, self._test_update_resource, + resource, cmd, self.test_id, args=args, + extrafields={'network': 'new'}) + + def test_delete_quota_get_parser(self): + cmd = test_cli20.MyApp(sys.stdout) + test_quota.DeleteQuota(cmd, None).get_parser(cmd) From a13a87c7ef2188bd4edc3513f4fdcac9b5f44dbb Mon Sep 17 00:00:00 2001 From: Roman Podolyaka Date: Tue, 12 Mar 2013 16:14:08 +0200 Subject: [PATCH 090/135] Add a missing command line option: --insecure This option allows to disable SSL certificates validation in HTTPClient. Fixes bug 1153715 Change-Id: I2c5123c3b8482a932fa401c56827f1fffe9fa381 --- quantumclient/common/clientmanager.py | 7 +++++-- quantumclient/quantum/client.py | 3 ++- quantumclient/shell.py | 12 +++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py index 7346aa541..90aa48e4b 100644 --- a/quantumclient/common/clientmanager.py +++ b/quantumclient/common/clientmanager.py @@ -54,7 +54,8 @@ def __init__(self, token=None, url=None, username=None, password=None, region_name=None, api_version=None, - auth_strategy=None + auth_strategy=None, + insecure=False ): self._token = token self._url = url @@ -67,6 +68,7 @@ def __init__(self, token=None, url=None, self._api_version = api_version self._service_catalog = None self._auth_strategy = auth_strategy + self._insecure = insecure return def initialize(self): @@ -75,7 +77,8 @@ def initialize(self): tenant_name=self._tenant_name, password=self._password, region_name=self._region_name, - auth_url=self._auth_url) + auth_url=self._auth_url, + insecure=self._insecure) httpclient.authenticate() # Populate other password flow attributes self._token = httpclient.auth_token diff --git a/quantumclient/quantum/client.py b/quantumclient/quantum/client.py index c89408b9f..1661a63f8 100644 --- a/quantumclient/quantum/client.py +++ b/quantumclient/quantum/client.py @@ -44,7 +44,8 @@ def make_client(instance): auth_url=instance._auth_url, endpoint_url=url, token=instance._token, - auth_strategy=instance._auth_strategy) + auth_strategy=instance._auth_strategy, + insecure=instance._insecure) return client else: raise exceptions.UnsupportedVersion("API version %s is not supported" % diff --git a/quantumclient/shell.py b/quantumclient/shell.py index b12e00e60..b44e3d932 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -412,6 +412,15 @@ def build_option_parser(self, description, version): '--os_url', help=argparse.SUPPRESS) + parser.add_argument( + '--insecure', + action='store_true', + default=env('QUANTUMCLIENT_INSECURE', default=False), + help="Explicitly allow quantumclient to perform \"insecure\" " + "SSL (https) requests. The server's certificate will " + "not be verified against any certificate authorities. " + "This option should be used with caution.") + return parser def _bash_completion(self): @@ -572,7 +581,8 @@ def authenticate_user(self): password=self.options.os_password, region_name=self.options.os_region_name, api_version=self.api_version, - auth_strategy=self.options.os_auth_strategy, ) + auth_strategy=self.options.os_auth_strategy, + insecure=self.options.insecure, ) return def initialize_app(self, argv): From a63555120aba4d82004fe169faa383f8874f7a65 Mon Sep 17 00:00:00 2001 From: gongysh Date: Mon, 18 Mar 2013 15:25:34 +0800 Subject: [PATCH 091/135] add 2.2.0 release note in index.rst file Bug #1156480 Change-Id: Ib28266a362e76960455509b7a2af57d5315f70df --- doc/source/index.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/source/index.rst b/doc/source/index.rst index 1dd7caefc..41fe6ce3b 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -42,3 +42,14 @@ Release Notes 2.0 ----- * support Quantum API 2.0 + +2.2.0 +----- +* add security group commands +* add Lbaas commands +* allow options put after positional arguments +* add NVP queue and net gateway commands +* add commands for agent management extensions +* add commands for DHCP and L3 agents scheduling +* support XML request format +* support pagination options From 68ad8f8af72676849ff1c972cd7da17b1fb5757e Mon Sep 17 00:00:00 2001 From: Ilya Shakhat Date: Thu, 21 Mar 2013 14:12:40 +0400 Subject: [PATCH 092/135] Reordering of optional and required args in lbaas commands help In help info required args are changed to go after optional. The change affects 'lb-healthmonitor-create' and 'lb-member-create' commands. Fixes bug 1157502 Change-Id: I5bb2337796c8b725333ed23a662061a4f276985c --- .../quantum/v2_0/lb/healthmonitor.py | 20 +++++++++---------- quantumclient/quantum/v2_0/lb/member.py | 14 ++++++------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/quantumclient/quantum/v2_0/lb/healthmonitor.py b/quantumclient/quantum/v2_0/lb/healthmonitor.py index 3071527ff..cc4695d6f 100644 --- a/quantumclient/quantum/v2_0/lb/healthmonitor.py +++ b/quantumclient/quantum/v2_0/lb/healthmonitor.py @@ -51,11 +51,6 @@ def add_known_arguments(self, parser): '--admin-state-down', default=True, action='store_false', help='set admin state up to false') - parser.add_argument( - '--delay', - required=True, - help='the minimum time in seconds between regular connections ' - 'of the member.') parser.add_argument( '--expected-codes', help='the list of HTTP status codes expected in ' @@ -68,6 +63,16 @@ def add_known_arguments(self, parser): '--http-method', help='the HTTP method used for requests by the monitor of type ' 'HTTP.') + parser.add_argument( + '--url-path', + help='the HTTP path used in the HTTP request used by the monitor' + ' to test a member health. This must be a string ' + 'beginning with a / (forward slash)') + parser.add_argument( + '--delay', + required=True, + help='the minimum time in seconds between regular connections ' + 'of the member.') parser.add_argument( '--max-retries', required=True, @@ -83,11 +88,6 @@ def add_known_arguments(self, parser): '--type', required=True, help='one of predefined health monitor types, e.g. RoundRobin') - parser.add_argument( - '--url-path', - help='the HTTP path used in the HTTP request used by the monitor' - ' to test a member health. This must be a string ' - 'beginning with a / (forward slash)') def args2body(self, parsed_args): body = { diff --git a/quantumclient/quantum/v2_0/lb/member.py b/quantumclient/quantum/v2_0/lb/member.py index e3da789fe..5a2c71b63 100644 --- a/quantumclient/quantum/v2_0/lb/member.py +++ b/quantumclient/quantum/v2_0/lb/member.py @@ -52,22 +52,22 @@ def add_known_arguments(self, parser): parser.add_argument( 'pool_id', metavar='pool', help='Pool id or name this vip belongs to') - parser.add_argument( - '--address', - required=True, - help='IP address of the pool member on the pool network. ') parser.add_argument( '--admin-state-down', default=True, action='store_false', help='set admin state up to false') + parser.add_argument( + '--weight', + help='weight of pool member in the pool') + parser.add_argument( + '--address', + required=True, + help='IP address of the pool member on the pool network. ') parser.add_argument( '--protocol-port', required=True, help='port on which the pool member listens for requests or ' 'connections. ') - parser.add_argument( - '--weight', - help='weight of pool member in the pool') def args2body(self, parsed_args): _pool_id = quantumv20.find_resourceid_by_name_or_id( From 87867ff6b429f9e3305b7e6e3b2c730c92179f0c Mon Sep 17 00:00:00 2001 From: Roman Podolyaka Date: Wed, 13 Mar 2013 18:04:46 +0200 Subject: [PATCH 093/135] Add exception & gettextutils to openstack.common These 2 modules are useful for cleaning up of the exceptions hierarchy. Change-Id: I0bb84e1969523be2eea077711acc5702bc63b023 --- openstack-common.conf | 2 +- quantumclient/openstack/common/exception.py | 142 ++++++++++++++++++ .../openstack/common/gettextutils.py | 33 ++++ 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 quantumclient/openstack/common/exception.py create mode 100644 quantumclient/openstack/common/gettextutils.py diff --git a/openstack-common.conf b/openstack-common.conf index d52611484..9b7ed7706 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=jsonutils,setup,timeutils +modules=exception,gettextutils,jsonutils,setup,timeutils # The base module to hold the copy of openstack.common base=quantumclient diff --git a/quantumclient/openstack/common/exception.py b/quantumclient/openstack/common/exception.py new file mode 100644 index 000000000..d7dddf3f1 --- /dev/null +++ b/quantumclient/openstack/common/exception.py @@ -0,0 +1,142 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Exceptions common to OpenStack projects +""" + +import logging + +from quantumclient.openstack.common.gettextutils import _ + +_FATAL_EXCEPTION_FORMAT_ERRORS = False + + +class Error(Exception): + def __init__(self, message=None): + super(Error, self).__init__(message) + + +class ApiError(Error): + def __init__(self, message='Unknown', code='Unknown'): + self.message = message + self.code = code + super(ApiError, self).__init__('%s: %s' % (code, message)) + + +class NotFound(Error): + pass + + +class UnknownScheme(Error): + + msg = "Unknown scheme '%s' found in URI" + + def __init__(self, scheme): + msg = self.__class__.msg % scheme + super(UnknownScheme, self).__init__(msg) + + +class BadStoreUri(Error): + + msg = "The Store URI %s was malformed. Reason: %s" + + def __init__(self, uri, reason): + msg = self.__class__.msg % (uri, reason) + super(BadStoreUri, self).__init__(msg) + + +class Duplicate(Error): + pass + + +class NotAuthorized(Error): + pass + + +class NotEmpty(Error): + pass + + +class Invalid(Error): + pass + + +class BadInputError(Exception): + """Error resulting from a client sending bad input to a server""" + pass + + +class MissingArgumentError(Error): + pass + + +class DatabaseMigrationError(Error): + pass + + +class ClientConnectionError(Exception): + """Error resulting from a client connecting to a server""" + pass + + +def wrap_exception(f): + def _wrap(*args, **kw): + try: + return f(*args, **kw) + except Exception, e: + if not isinstance(e, Error): + #exc_type, exc_value, exc_traceback = sys.exc_info() + logging.exception(_('Uncaught exception')) + #logging.error(traceback.extract_stack(exc_traceback)) + raise Error(str(e)) + raise + _wrap.func_name = f.func_name + return _wrap + + +class OpenstackException(Exception): + """ + Base Exception + + To correctly use this class, inherit from it and define + a 'message' property. That message will get printf'd + with the keyword arguments provided to the constructor. + """ + message = "An unknown exception occurred" + + def __init__(self, **kwargs): + try: + self._error_string = self.message % kwargs + + except Exception as e: + if _FATAL_EXCEPTION_FORMAT_ERRORS: + raise e + else: + # at least get the core message out if something happened + self._error_string = self.message + + def __str__(self): + return self._error_string + + +class MalformedRequestBody(OpenstackException): + message = "Malformed message body: %(reason)s" + + +class InvalidContentType(OpenstackException): + message = "Invalid content type %(content_type)s" diff --git a/quantumclient/openstack/common/gettextutils.py b/quantumclient/openstack/common/gettextutils.py new file mode 100644 index 000000000..490f194d4 --- /dev/null +++ b/quantumclient/openstack/common/gettextutils.py @@ -0,0 +1,33 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +gettext for openstack-common modules. + +Usual usage in an openstack.common module: + + from quantumclient.openstack.common.gettextutils import _ +""" + +import gettext + + +t = gettext.translation('openstack-common', 'locale', fallback=True) + + +def _(msg): + return t.ugettext(msg) From 13d12b42c9683a4a3f164bc5751c295c29c1ffff Mon Sep 17 00:00:00 2001 From: Tatyana Leontovich Date: Fri, 1 Mar 2013 17:39:42 +0200 Subject: [PATCH 094/135] Improve unit tests for python-quantumclient Add tests for: quantumclient.shell Partially Fixes: bug #1137783 Change-Id: Ia6a856cf3c1da67419818ae1693d56f447c2f070 --- quantumclient/tests/unit/test_shell.py | 129 +++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 quantumclient/tests/unit/test_shell.py diff --git a/quantumclient/tests/unit/test_shell.py b/quantumclient/tests/unit/test_shell.py new file mode 100644 index 000000000..8491d0217 --- /dev/null +++ b/quantumclient/tests/unit/test_shell.py @@ -0,0 +1,129 @@ +# Copyright (C) 2013 Yahoo! Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import argparse +import cStringIO +import os +import re +import sys + +import fixtures +import testtools +from testtools import matchers + +from quantumclient.common import exceptions +from quantumclient import shell as openstack_shell + + +DEFAULT_USERNAME = 'username' +DEFAULT_PASSWORD = 'password' +DEFAULT_TENANT_ID = 'tenant_id' +DEFAULT_TENANT_NAME = 'tenant_name' +DEFAULT_AUTH_URL = 'http://127.0.0.1:5000/v2.0/' +DEFAULT_TOKEN = '3bcc3d3a03f44e3d8377f9247b0ad155' +DEFAULT_URL = 'http://quantum.example.org:9696/' + + +class NoExitArgumentParser(argparse.ArgumentParser): + def error(self, message): + raise exceptions.CommandError(message) + + +class ShellTest(testtools.TestCase): + + FAKE_ENV = { + 'OS_USERNAME': DEFAULT_USERNAME, + 'OS_PASSWORD': DEFAULT_PASSWORD, + 'OS_TENANT_ID': DEFAULT_TENANT_ID, + 'OS_TENANT_NAME': DEFAULT_TENANT_NAME, + 'OS_AUTH_URL': DEFAULT_AUTH_URL} + + def _tolerant_shell(self, cmd): + t_shell = openstack_shell.QuantumShell('2.0') + t_shell.run(cmd.split()) + + # Patch os.environ to avoid required auth info. + def setUp(self): + super(ShellTest, self).setUp() + for var in self.FAKE_ENV: + self.useFixture( + fixtures.EnvironmentVariable( + var, self.FAKE_ENV[var])) + + # Make a fake shell object, a helping wrapper to call it, and a quick + # way of asserting that certain API calls were made. + global shell, _shell, assert_called, assert_called_anytime + _shell = openstack_shell.QuantumShell('2.0') + shell = lambda cmd: _shell.run(cmd.split()) + + def shell(self, argstr): + orig = sys.stdout + clean_env = {} + _old_env, os.environ = os.environ, clean_env.copy() + try: + sys.stdout = cStringIO.StringIO() + _shell = openstack_shell.QuantumShell('2.0') + _shell.run(argstr.split()) + except SystemExit: + exc_type, exc_value, exc_traceback = sys.exc_info() + self.assertEqual(exc_value.code, 0) + finally: + out = sys.stdout.getvalue() + sys.stdout.close() + sys.stdout = orig + os.environ = _old_env + return out + + def test_run_unknown_command(self): + openstack_shell.QuantumShell('2.0').run('fake') + + def test_help(self): + required = 'usage:' + help_text = self.shell('help') + self.assertThat( + help_text, + matchers.MatchesRegex(required)) + + def test_help_on_subcommand(self): + required = [ + '.*?^usage: .* quota-list'] + stdout = self.shell('help quota-list') + for r in required: + self.assertThat( + stdout, + matchers.MatchesRegex(r, re.DOTALL | re.MULTILINE)) + + def test_help_command(self): + required = 'usage:' + help_text = self.shell('help network-create') + self.assertThat( + help_text, + matchers.MatchesRegex(required)) + + def test_unknown_auth_strategy(self): + self.shell('--os-auth-strategy fake quota-list') + + def test_auth(self): + self.shell(' --os-username test' + ' --os-password test' + ' --os-tenant-name test' + ' --os-auth-url http://127.0.0.1:5000/' + ' --os-auth-strategy keystone quota-list') + + def test_build_option_parser(self): + quant_shell = openstack_shell.QuantumShell('2.0') + result = quant_shell.build_option_parser('descr', '2.0') + self.assertEqual(True, isinstance(result, argparse.ArgumentParser)) From 714ade07ac4d1ac0cdf545f5b0823e88af0c03ae Mon Sep 17 00:00:00 2001 From: Chris Krelle Date: Fri, 22 Mar 2013 12:59:06 -0700 Subject: [PATCH 095/135] Update tools/pip-requires for prettytable changes pip-requires from: prettytable>=0.6,<0.7 to: prettytable>=0.6,<0.8 Change-Id: Ia4e7460525e73474c90cd0c50d3d1243f64f68eb Authored-by: Chris Krelle --- tools/pip-requires | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/pip-requires b/tools/pip-requires index a3d38d731..c5f007081 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -2,6 +2,6 @@ argparse cliff>=1.3.1 httplib2 iso8601 -prettytable>=0.6,<0.7 +prettytable>=0.6,<0.8 pyparsing>=1.5.6,<2.0 simplejson From 661aea13c1eb40694fa52e98b23bfd411d793e2f Mon Sep 17 00:00:00 2001 From: gongysh Date: Tue, 26 Mar 2013 14:50:35 +0800 Subject: [PATCH 096/135] remove remainder argument Bug #1160203 Change-Id: I01c6aed18ccd7fe61a5ea4562c79d47222175147 --- quantumclient/quantum/v2_0/__init__.py | 12 ------------ quantumclient/quantum/v2_0/quota.py | 27 +++++++++++++++----------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 0f48b9bc5..78590b538 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -119,14 +119,6 @@ def add_sorting_argument(parser): choices=['asc', 'desc']) -def add_extra_argument(parser, name, _help): - parser.add_argument( - name, - nargs=argparse.REMAINDER, - help=_help + ': --key1 [type=int|bool|...] value ' - '[--key2 [type=int|bool|...] value ...]') - - def is_number(s): try: float(s) # for int, long and float @@ -446,8 +438,6 @@ class ListCommand(QuantumCommand, lister.Lister): def get_parser(self, prog_name): parser = super(ListCommand, self).get_parser(prog_name) add_show_list_common_argument(parser) - if self.unknown_parts_flag: - add_extra_argument(parser, 'filter_specs', 'filters options') if self.pagination_support: add_pagination_argument(parser) if self.sorting_support: @@ -456,8 +446,6 @@ def get_parser(self, prog_name): def args2search_opts(self, parsed_args): search_opts = {} - if self.unknown_parts_flag: - search_opts = parse_args_to_dict(parsed_args.filter_specs) fields = parsed_args.fields if parsed_args.fields: search_opts.update({'fields': fields}) diff --git a/quantumclient/quantum/v2_0/quota.py b/quantumclient/quantum/v2_0/quota.py index 82dfbe75f..bdff0bd6c 100644 --- a/quantumclient/quantum/v2_0/quota.py +++ b/quantumclient/quantum/v2_0/quota.py @@ -176,9 +176,6 @@ def get_parser(self, prog_name): parser.add_argument( '--security-group-rule', metavar='security_group_rules', help='the limit of security groups rules') - quantumv20.add_extra_argument( - parser, 'value_specs', - 'new values for the %s' % self.resource) return parser def _validate_int(self, name, value): @@ -190,10 +187,7 @@ def _validate_int(self, name, value): raise exceptions.QuantumClientException(message=message) return return_value - def get_data(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + def args2body(self, parsed_args): quota = {} for resource in ('network', 'subnet', 'port', 'router', 'floatingip', 'security_group', 'security_group_rule'): @@ -201,14 +195,25 @@ def get_data(self, parsed_args): quota[resource] = self._validate_int( resource, getattr(parsed_args, resource)) - value_specs = parsed_args.value_specs - if value_specs: - quota.update(quantumv20.parse_args_to_dict(value_specs)) + return {self.resource: quota} + + def get_data(self, parsed_args): + self.log.debug('run(%s)' % parsed_args) + quantum_client = self.get_client() + quantum_client.format = parsed_args.request_format + _extra_values = quantumv20.parse_args_to_dict(self.values_specs) + quantumv20._merge_args(self, parsed_args, _extra_values, + self.values_specs) + body = self.args2body(parsed_args) + if self.resource in body: + body[self.resource].update(_extra_values) + else: + body[self.resource] = _extra_values obj_updator = getattr(quantum_client, "update_%s" % self.resource) tenant_id = get_tenant_id(parsed_args.tenant_id, quantum_client) - data = obj_updator(tenant_id, {self.resource: quota}) + data = obj_updator(tenant_id, body) if self.resource in data: for k, v in data[self.resource].iteritems(): if isinstance(v, list): From 109c1531401901ee2da717913a8321b053679c03 Mon Sep 17 00:00:00 2001 From: gongysh Date: Tue, 26 Mar 2013 20:06:51 +0800 Subject: [PATCH 097/135] Don't query the agent with name Bug #1160332 Change-Id: I4da1df900ff29f75e11b91186200d7841602bff4 --- quantumclient/quantum/v2_0/agent.py | 1 + 1 file changed, 1 insertion(+) diff --git a/quantumclient/quantum/v2_0/agent.py b/quantumclient/quantum/v2_0/agent.py index 1597465be..67bf87ed1 100644 --- a/quantumclient/quantum/v2_0/agent.py +++ b/quantumclient/quantum/v2_0/agent.py @@ -54,6 +54,7 @@ class DeleteAgent(quantumV20.DeleteCommand): log = logging.getLogger(__name__ + '.DeleteAgent') resource = 'agent' + allow_names = False class UpdateAgent(quantumV20.UpdateCommand): From a54dd21fcb6d730bdf53fd8f9d808762f2ff071b Mon Sep 17 00:00:00 2001 From: Oleg Bondarev Date: Mon, 18 Mar 2013 17:53:00 +0400 Subject: [PATCH 098/135] Handle auth_token and endpoint_url if passed to the http client constructor Fixes bug 1152427 Change-Id: Ic7811d928fd00cde0a72f451b5ede8351092a54c --- quantumclient/client.py | 47 ++++++++++++++-------- quantumclient/tests/unit/test_auth.py | 57 +++++++++++++++++++++------ 2 files changed, 74 insertions(+), 30 deletions(-) diff --git a/quantumclient/client.py b/quantumclient/client.py index 39e770e79..e9d594993 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -101,7 +101,6 @@ def __init__(self, username=None, tenant_name=None, self.auth_url = auth_url.rstrip('/') if auth_url else None self.region_name = region_name self.auth_token = token - self.token_retrieved = False self.content_type = 'application/json' self.endpoint_url = endpoint_url self.auth_strategy = auth_strategy @@ -134,30 +133,27 @@ def _cs_request(self, *args, **kwargs): return resp, body def do_request(self, url, method, **kwargs): - if not self.endpoint_url: + if not self.auth_token: self.authenticate() + elif not self.endpoint_url: + self.endpoint_url = self._get_endpoint_url() # Perform the request once. If we get a 401 back then it # might be because the auth token expired, so try to # re-authenticate and try again. If it still fails, bail. try: - if self.auth_token: - kwargs.setdefault('headers', {}) - kwargs['headers']['X-Auth-Token'] = self.auth_token + kwargs.setdefault('headers', {}) + kwargs['headers']['X-Auth-Token'] = self.auth_token resp, body = self._cs_request(self.endpoint_url + url, method, **kwargs) return resp, body - except exceptions.Unauthorized as ex: - if not self.endpoint_url or self.token_retrieved: - self.authenticate() - if self.auth_token: - kwargs.setdefault('headers', {}) - kwargs['headers']['X-Auth-Token'] = self.auth_token - resp, body = self._cs_request( - self.endpoint_url + url, method, **kwargs) - return resp, body - else: - raise ex + except exceptions.Unauthorized: + self.authenticate() + kwargs.setdefault('headers', {}) + kwargs['headers']['X-Auth-Token'] = self.auth_token + resp, body = self._cs_request( + self.endpoint_url + url, method, **kwargs) + return resp, body def _extract_service_catalog(self, body): """ Set the client's service catalog from the response data. """ @@ -167,7 +163,6 @@ def _extract_service_catalog(self, body): self.auth_token = sc['id'] self.auth_tenant_id = sc.get('tenant_id') self.auth_user_id = sc.get('user_id') - self.token_retrieved = True except KeyError: raise exceptions.Unauthorized() self.endpoint_url = self.service_catalog.url_for( @@ -205,6 +200,24 @@ def authenticate(self): body = None self._extract_service_catalog(body) + def _get_endpoint_url(self): + url = self.auth_url + '/tokens/%s/endpoints' % self.auth_token + try: + resp, body = self._cs_request(url, "GET") + except exceptions.Unauthorized: + # rollback to authenticate() to handle case when quantum client + # is initialized just before the token is expired + self.authenticate() + return self.endpoint_url + + body = json.loads(body) + for endpoint in body.get('endpoints', []): + if (endpoint['type'] == 'network' and + endpoint.get('region') == self.region_name): + return endpoint['adminURL'] + + raise exceptions.EndpointNotFound() + def get_status_code(self, response): """ Returns the integer status code from the response, which diff --git a/quantumclient/tests/unit/test_auth.py b/quantumclient/tests/unit/test_auth.py index ba1aa0b6a..e8e64b37e 100644 --- a/quantumclient/tests/unit/test_auth.py +++ b/quantumclient/tests/unit/test_auth.py @@ -17,7 +17,6 @@ import httplib2 import json -import unittest import uuid import mox @@ -25,7 +24,6 @@ import testtools from quantumclient.client import HTTPClient -from quantumclient.common import exceptions USERNAME = 'testuser' @@ -54,6 +52,17 @@ } } +ENDPOINTS_RESULT = { + 'endpoints': [{ + 'type': 'network', + 'name': 'Quantum Service', + 'region': REGION, + 'adminURL': ENDPOINT_URL, + 'internalURL': ENDPOINT_URL, + 'publicURL': ENDPOINT_URL + }] +} + class CLITestAuthKeystone(testtools.TestCase): @@ -84,46 +93,68 @@ def test_get_token(self): self.client.do_request('/resource', 'GET') self.assertEqual(self.client.endpoint_url, ENDPOINT_URL) self.assertEqual(self.client.auth_token, TOKEN) - self.assertEqual(self.client.token_retrieved, True) - def test_already_token_retrieved(self): + def test_refresh_token(self): self.mox.StubOutWithMock(self.client, "request") self.client.auth_token = TOKEN self.client.endpoint_url = ENDPOINT_URL - self.client.token_retrieved = True res200 = self.mox.CreateMock(httplib2.Response) res200.status = 200 + res401 = self.mox.CreateMock(httplib2.Response) + res401.status = 401 + # If a token is expired, quantum server retruns 401 + self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ + AndReturn((res401, '')) + self.client.request(AUTH_URL + '/tokens', 'POST', + body=IsA(str), headers=IsA(dict)).\ + AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ AndReturn((res200, '')) self.mox.ReplayAll() + self.client.do_request('/resource', 'GET') + + def test_get_endpoint_url(self): + self.mox.StubOutWithMock(self.client, "request") + self.client.auth_token = TOKEN + + res200 = self.mox.CreateMock(httplib2.Response) + res200.status = 200 + + self.client.request(StrContains(AUTH_URL + + '/tokens/%s/endpoints' % TOKEN), 'GET', + headers=IsA(dict)). \ + AndReturn((res200, json.dumps(ENDPOINTS_RESULT))) + self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=ContainsKeyValue('X-Auth-Token', TOKEN)). \ + AndReturn((res200, '')) + self.mox.ReplayAll() self.client.do_request('/resource', 'GET') - def test_refresh_token(self): + def test_get_endpoint_url_failed(self): self.mox.StubOutWithMock(self.client, "request") self.client.auth_token = TOKEN - self.client.endpoint_url = ENDPOINT_URL - self.client.token_retrieved = True res200 = self.mox.CreateMock(httplib2.Response) res200.status = 200 res401 = self.mox.CreateMock(httplib2.Response) res401.status = 401 - # If a token is expired, quantum server retruns 401 - self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', - headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ + self.client.request(StrContains(AUTH_URL + + '/tokens/%s/endpoints' % TOKEN), 'GET', + headers=IsA(dict)). \ AndReturn((res401, '')) self.client.request(AUTH_URL + '/tokens', 'POST', - body=IsA(str), headers=IsA(dict)).\ + body=IsA(str), headers=IsA(dict)). \ AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', - headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ + headers=ContainsKeyValue('X-Auth-Token', TOKEN)). \ AndReturn((res200, '')) self.mox.ReplayAll() self.client.do_request('/resource', 'GET') From 14582a8cd4106eedb60bb381546219f7384b0c27 Mon Sep 17 00:00:00 2001 From: Aaron Rosen Date: Tue, 26 Mar 2013 17:08:19 -0700 Subject: [PATCH 099/135] Update --remote-group-id metavar to REMOTE_GROUP The metavar value for --remote-group-id was left out in the patch that renamed source_(group_id|ip_prefix) to remote. This patch renames this instance of SOURCE to REMOTE. Change-Id: I99d538e1cdc0b29e62b499c9c8ba8b3a31d77f7a --- quantumclient/quantum/v2_0/securitygroup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py index 5c531895c..abc870a3e 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -191,7 +191,7 @@ def add_known_arguments(self, parser): '--remote_ip_prefix', help=argparse.SUPPRESS) parser.add_argument( - '--remote-group-id', metavar='SOURCE_GROUP', + '--remote-group-id', metavar='REMOTE_GROUP', help='remote security group name or id to apply rule') parser.add_argument( '--remote_group_id', From a629883e79ec63e4a1277dde60c5ac9055103717 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Wed, 27 Mar 2013 20:05:24 +0900 Subject: [PATCH 100/135] Add AUTHOR and .testrepository to .gitignore Also sort the entries in .gitignore in alphabetical order. Change-Id: I25e6355b16ef490d056f0fc02ed085db5c321e8e --- .gitignore | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 1a5761972..66f98c140 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,19 @@ *.pyc *.DS_Store +AUTHORS +ChangeLog build/* build-stamp -.coverage cover/* +doc/build/ +doc/source/api/ python_quantumclient.egg-info/* quantum/vcsversion.py +quantumclient/versioninfo run_tests.err.log run_tests.log -.venv/ -.tox/ .autogenerated -doc/build/ -doc/source/api/ -quantumclient/versioninfo -ChangeLog +.coverage +.testrepository/ +.tox/ +.venv/ From 8fbe6dd36ce90fb3aa2f4a3a322f539044351714 Mon Sep 17 00:00:00 2001 From: Alessio Ababilov Date: Mon, 18 Feb 2013 15:06:41 +0200 Subject: [PATCH 101/135] Move tests to project root Implements: blueprint tests-in-root Change-Id: I6a1dbc59720abcaafb6fd9b8585f3f6065dcde92 --- .testr.conf | 2 +- MANIFEST.in | 3 +-- {quantumclient/tests => tests}/__init__.py | 0 {quantumclient/tests => tests}/unit/__init__.py | 0 {quantumclient/tests => tests}/unit/lb/__init__.py | 0 .../tests => tests}/unit/lb/test_cli20_healthmonitor.py | 2 +- {quantumclient/tests => tests}/unit/lb/test_cli20_member.py | 2 +- {quantumclient/tests => tests}/unit/lb/test_cli20_pool.py | 2 +- {quantumclient/tests => tests}/unit/lb/test_cli20_vip.py | 2 +- {quantumclient/tests => tests}/unit/test_auth.py | 0 {quantumclient/tests => tests}/unit/test_casual_args.py | 0 {quantumclient/tests => tests}/unit/test_cli20.py | 0 .../tests => tests}/unit/test_cli20_floatingips.py | 4 ++-- {quantumclient/tests => tests}/unit/test_cli20_network.py | 6 +++--- {quantumclient/tests => tests}/unit/test_cli20_nvp_queue.py | 2 +- .../tests => tests}/unit/test_cli20_nvpnetworkgateway.py | 4 ++-- {quantumclient/tests => tests}/unit/test_cli20_port.py | 6 +++--- {quantumclient/tests => tests}/unit/test_cli20_router.py | 4 ++-- .../tests => tests}/unit/test_cli20_securitygroup.py | 2 +- {quantumclient/tests => tests}/unit/test_cli20_subnet.py | 4 ++-- {quantumclient/tests => tests}/unit/test_name_or_id.py | 2 +- {quantumclient/tests => tests}/unit/test_quota.py | 2 +- {quantumclient/tests => tests}/unit/test_shell.py | 0 23 files changed, 24 insertions(+), 25 deletions(-) rename {quantumclient/tests => tests}/__init__.py (100%) rename {quantumclient/tests => tests}/unit/__init__.py (100%) rename {quantumclient/tests => tests}/unit/lb/__init__.py (100%) rename {quantumclient/tests => tests}/unit/lb/test_cli20_healthmonitor.py (99%) rename {quantumclient/tests => tests}/unit/lb/test_cli20_member.py (99%) rename {quantumclient/tests => tests}/unit/lb/test_cli20_pool.py (99%) rename {quantumclient/tests => tests}/unit/lb/test_cli20_vip.py (99%) rename {quantumclient/tests => tests}/unit/test_auth.py (100%) rename {quantumclient/tests => tests}/unit/test_casual_args.py (100%) rename {quantumclient/tests => tests}/unit/test_cli20.py (100%) rename {quantumclient/tests => tests}/unit/test_cli20_floatingips.py (98%) rename {quantumclient/tests => tests}/unit/test_cli20_network.py (99%) rename {quantumclient/tests => tests}/unit/test_cli20_nvp_queue.py (98%) rename {quantumclient/tests => tests}/unit/test_cli20_nvpnetworkgateway.py (97%) rename {quantumclient/tests => tests}/unit/test_cli20_port.py (98%) rename {quantumclient/tests => tests}/unit/test_cli20_router.py (98%) rename {quantumclient/tests => tests}/unit/test_cli20_securitygroup.py (99%) rename {quantumclient/tests => tests}/unit/test_cli20_subnet.py (99%) rename {quantumclient/tests => tests}/unit/test_name_or_id.py (99%) rename {quantumclient/tests => tests}/unit/test_quota.py (97%) rename {quantumclient/tests => tests}/unit/test_shell.py (100%) diff --git a/.testr.conf b/.testr.conf index d356fcf1e..2109af6ce 100644 --- a/.testr.conf +++ b/.testr.conf @@ -1,4 +1,4 @@ [DEFAULT] -test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./quantumclient/tests $LISTOPT $IDOPTION +test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list diff --git a/MANIFEST.in b/MANIFEST.in index 41d72419d..251a20197 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -2,6 +2,5 @@ include tox.ini include LICENSE README HACKING.rst include ChangeLog include tools/* -include quantumclient/tests/* -include quantumclient/tests/unit/* include quantumclient/versioninfo +recursive-include tests * diff --git a/quantumclient/tests/__init__.py b/tests/__init__.py similarity index 100% rename from quantumclient/tests/__init__.py rename to tests/__init__.py diff --git a/quantumclient/tests/unit/__init__.py b/tests/unit/__init__.py similarity index 100% rename from quantumclient/tests/unit/__init__.py rename to tests/unit/__init__.py diff --git a/quantumclient/tests/unit/lb/__init__.py b/tests/unit/lb/__init__.py similarity index 100% rename from quantumclient/tests/unit/lb/__init__.py rename to tests/unit/lb/__init__.py diff --git a/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py b/tests/unit/lb/test_cli20_healthmonitor.py similarity index 99% rename from quantumclient/tests/unit/lb/test_cli20_healthmonitor.py rename to tests/unit/lb/test_cli20_healthmonitor.py index ab5327c59..ed0086a75 100644 --- a/quantumclient/tests/unit/lb/test_cli20_healthmonitor.py +++ b/tests/unit/lb/test_cli20_healthmonitor.py @@ -22,7 +22,7 @@ from mox import ContainsKeyValue from quantumclient.quantum.v2_0.lb import healthmonitor -from quantumclient.tests.unit import test_cli20 +from tests.unit import test_cli20 class CLITestV20LbHealthmonitor(test_cli20.CLITestV20Base): diff --git a/quantumclient/tests/unit/lb/test_cli20_member.py b/tests/unit/lb/test_cli20_member.py similarity index 99% rename from quantumclient/tests/unit/lb/test_cli20_member.py rename to tests/unit/lb/test_cli20_member.py index 2913d4c45..6f80fd845 100644 --- a/quantumclient/tests/unit/lb/test_cli20_member.py +++ b/tests/unit/lb/test_cli20_member.py @@ -20,7 +20,7 @@ import sys from quantumclient.quantum.v2_0.lb import member -from quantumclient.tests.unit import test_cli20 +from tests.unit import test_cli20 class CLITestV20LbMember(test_cli20.CLITestV20Base): diff --git a/quantumclient/tests/unit/lb/test_cli20_pool.py b/tests/unit/lb/test_cli20_pool.py similarity index 99% rename from quantumclient/tests/unit/lb/test_cli20_pool.py rename to tests/unit/lb/test_cli20_pool.py index 4bf6d3c47..b02b0baac 100644 --- a/quantumclient/tests/unit/lb/test_cli20_pool.py +++ b/tests/unit/lb/test_cli20_pool.py @@ -22,7 +22,7 @@ from mox import ContainsKeyValue from quantumclient.quantum.v2_0.lb import pool -from quantumclient.tests.unit import test_cli20 +from tests.unit import test_cli20 class CLITestV20LbPool(test_cli20.CLITestV20Base): diff --git a/quantumclient/tests/unit/lb/test_cli20_vip.py b/tests/unit/lb/test_cli20_vip.py similarity index 99% rename from quantumclient/tests/unit/lb/test_cli20_vip.py rename to tests/unit/lb/test_cli20_vip.py index 878e99f95..c42a29bd5 100644 --- a/quantumclient/tests/unit/lb/test_cli20_vip.py +++ b/tests/unit/lb/test_cli20_vip.py @@ -20,7 +20,7 @@ import sys from quantumclient.quantum.v2_0.lb import vip -from quantumclient.tests.unit import test_cli20 +from tests.unit import test_cli20 class CLITestV20LbVip(test_cli20.CLITestV20Base): diff --git a/quantumclient/tests/unit/test_auth.py b/tests/unit/test_auth.py similarity index 100% rename from quantumclient/tests/unit/test_auth.py rename to tests/unit/test_auth.py diff --git a/quantumclient/tests/unit/test_casual_args.py b/tests/unit/test_casual_args.py similarity index 100% rename from quantumclient/tests/unit/test_casual_args.py rename to tests/unit/test_casual_args.py diff --git a/quantumclient/tests/unit/test_cli20.py b/tests/unit/test_cli20.py similarity index 100% rename from quantumclient/tests/unit/test_cli20.py rename to tests/unit/test_cli20.py diff --git a/quantumclient/tests/unit/test_cli20_floatingips.py b/tests/unit/test_cli20_floatingips.py similarity index 98% rename from quantumclient/tests/unit/test_cli20_floatingips.py rename to tests/unit/test_cli20_floatingips.py index 620470d3c..854bf1ec0 100644 --- a/quantumclient/tests/unit/test_cli20_floatingips.py +++ b/tests/unit/test_cli20_floatingips.py @@ -25,8 +25,8 @@ from quantumclient.quantum.v2_0.floatingip import DisassociateFloatingIP from quantumclient.quantum.v2_0.floatingip import ListFloatingIP from quantumclient.quantum.v2_0.floatingip import ShowFloatingIP -from quantumclient.tests.unit.test_cli20 import CLITestV20Base -from quantumclient.tests.unit.test_cli20 import MyApp +from tests.unit.test_cli20 import CLITestV20Base +from tests.unit.test_cli20 import MyApp class CLITestV20FloatingIps(CLITestV20Base): diff --git a/quantumclient/tests/unit/test_cli20_network.py b/tests/unit/test_cli20_network.py similarity index 99% rename from quantumclient/tests/unit/test_cli20_network.py rename to tests/unit/test_cli20_network.py index a0a7c9064..18e3a2c42 100644 --- a/quantumclient/tests/unit/test_cli20_network.py +++ b/tests/unit/test_cli20_network.py @@ -29,9 +29,9 @@ from quantumclient.quantum.v2_0.network import ShowNetwork from quantumclient.quantum.v2_0.network import UpdateNetwork from quantumclient import shell -from quantumclient.tests.unit import test_cli20 -from quantumclient.tests.unit.test_cli20 import CLITestV20Base -from quantumclient.tests.unit.test_cli20 import MyApp +from tests.unit import test_cli20 +from tests.unit.test_cli20 import CLITestV20Base +from tests.unit.test_cli20 import MyApp class CLITestV20Network(CLITestV20Base): diff --git a/quantumclient/tests/unit/test_cli20_nvp_queue.py b/tests/unit/test_cli20_nvp_queue.py similarity index 98% rename from quantumclient/tests/unit/test_cli20_nvp_queue.py rename to tests/unit/test_cli20_nvp_queue.py index 96ed11a0d..c43f31acb 100644 --- a/quantumclient/tests/unit/test_cli20_nvp_queue.py +++ b/tests/unit/test_cli20_nvp_queue.py @@ -19,7 +19,7 @@ import sys from quantumclient.quantum.v2_0 import nvp_qos_queue as qos -from quantumclient.tests.unit import test_cli20 +from tests.unit import test_cli20 class CLITestV20NvpQosQueue(test_cli20.CLITestV20Base): diff --git a/quantumclient/tests/unit/test_cli20_nvpnetworkgateway.py b/tests/unit/test_cli20_nvpnetworkgateway.py similarity index 97% rename from quantumclient/tests/unit/test_cli20_nvpnetworkgateway.py rename to tests/unit/test_cli20_nvpnetworkgateway.py index 7f17571d4..31f7e1ae1 100644 --- a/quantumclient/tests/unit/test_cli20_nvpnetworkgateway.py +++ b/tests/unit/test_cli20_nvpnetworkgateway.py @@ -19,8 +19,8 @@ from quantumclient.common import exceptions from quantumclient.quantum.v2_0 import nvpnetworkgateway -from quantumclient.tests.unit.test_cli20 import CLITestV20Base -from quantumclient.tests.unit.test_cli20 import MyApp +from tests.unit.test_cli20 import CLITestV20Base +from tests.unit.test_cli20 import MyApp class CLITestV20NetworkGateway(CLITestV20Base): diff --git a/quantumclient/tests/unit/test_cli20_port.py b/tests/unit/test_cli20_port.py similarity index 98% rename from quantumclient/tests/unit/test_cli20_port.py rename to tests/unit/test_cli20_port.py index 65cfc99a5..84e11bff0 100644 --- a/quantumclient/tests/unit/test_cli20_port.py +++ b/tests/unit/test_cli20_port.py @@ -26,9 +26,9 @@ from quantumclient.quantum.v2_0.port import ListRouterPort from quantumclient.quantum.v2_0.port import ShowPort from quantumclient.quantum.v2_0.port import UpdatePort -from quantumclient.tests.unit import test_cli20 -from quantumclient.tests.unit.test_cli20 import CLITestV20Base -from quantumclient.tests.unit.test_cli20 import MyApp +from tests.unit import test_cli20 +from tests.unit.test_cli20 import CLITestV20Base +from tests.unit.test_cli20 import MyApp class CLITestV20Port(CLITestV20Base): diff --git a/quantumclient/tests/unit/test_cli20_router.py b/tests/unit/test_cli20_router.py similarity index 98% rename from quantumclient/tests/unit/test_cli20_router.py rename to tests/unit/test_cli20_router.py index 1875b8abf..e9e661c11 100644 --- a/quantumclient/tests/unit/test_cli20_router.py +++ b/tests/unit/test_cli20_router.py @@ -27,8 +27,8 @@ from quantumclient.quantum.v2_0.router import SetGatewayRouter from quantumclient.quantum.v2_0.router import ShowRouter from quantumclient.quantum.v2_0.router import UpdateRouter -from quantumclient.tests.unit.test_cli20 import CLITestV20Base -from quantumclient.tests.unit.test_cli20 import MyApp +from tests.unit.test_cli20 import CLITestV20Base +from tests.unit.test_cli20 import MyApp class CLITestV20Router(CLITestV20Base): diff --git a/quantumclient/tests/unit/test_cli20_securitygroup.py b/tests/unit/test_cli20_securitygroup.py similarity index 99% rename from quantumclient/tests/unit/test_cli20_securitygroup.py rename to tests/unit/test_cli20_securitygroup.py index 2884e3b2c..77cbdaf3d 100644 --- a/quantumclient/tests/unit/test_cli20_securitygroup.py +++ b/tests/unit/test_cli20_securitygroup.py @@ -21,7 +21,7 @@ import mox from quantumclient.quantum.v2_0 import securitygroup -from quantumclient.tests.unit import test_cli20 +from tests.unit import test_cli20 class CLITestV20SecurityGroups(test_cli20.CLITestV20Base): diff --git a/quantumclient/tests/unit/test_cli20_subnet.py b/tests/unit/test_cli20_subnet.py similarity index 99% rename from quantumclient/tests/unit/test_cli20_subnet.py rename to tests/unit/test_cli20_subnet.py index ca2794502..7bbe7ea0c 100644 --- a/quantumclient/tests/unit/test_cli20_subnet.py +++ b/tests/unit/test_cli20_subnet.py @@ -22,8 +22,8 @@ from quantumclient.quantum.v2_0.subnet import ListSubnet from quantumclient.quantum.v2_0.subnet import ShowSubnet from quantumclient.quantum.v2_0.subnet import UpdateSubnet -from quantumclient.tests.unit.test_cli20 import CLITestV20Base -from quantumclient.tests.unit.test_cli20 import MyApp +from tests.unit.test_cli20 import CLITestV20Base +from tests.unit.test_cli20 import MyApp class CLITestV20Subnet(CLITestV20Base): diff --git a/quantumclient/tests/unit/test_name_or_id.py b/tests/unit/test_name_or_id.py similarity index 99% rename from quantumclient/tests/unit/test_name_or_id.py rename to tests/unit/test_name_or_id.py index 3b01a1c9b..d5fd9fd14 100644 --- a/quantumclient/tests/unit/test_name_or_id.py +++ b/tests/unit/test_name_or_id.py @@ -23,7 +23,7 @@ from quantumclient.common import exceptions from quantumclient.quantum import v2_0 as quantumv20 -from quantumclient.tests.unit import test_cli20 +from tests.unit import test_cli20 from quantumclient.v2_0.client import Client diff --git a/quantumclient/tests/unit/test_quota.py b/tests/unit/test_quota.py similarity index 97% rename from quantumclient/tests/unit/test_quota.py rename to tests/unit/test_quota.py index 72d589860..633ff62af 100644 --- a/quantumclient/tests/unit/test_quota.py +++ b/tests/unit/test_quota.py @@ -19,7 +19,7 @@ from quantumclient.common import exceptions from quantumclient.quantum.v2_0 import quota as test_quota -from quantumclient.tests.unit import test_cli20 +from tests.unit import test_cli20 class CLITestV20Quota(test_cli20.CLITestV20Base): diff --git a/quantumclient/tests/unit/test_shell.py b/tests/unit/test_shell.py similarity index 100% rename from quantumclient/tests/unit/test_shell.py rename to tests/unit/test_shell.py From 6f7e76ec3b4777816e0a5d7eaeef0026d75b60e6 Mon Sep 17 00:00:00 2001 From: Oleg Bondarev Date: Tue, 12 Mar 2013 16:13:02 +0400 Subject: [PATCH 102/135] Add public api to get authentication info from client auth info (in particular token and endpoint_url) can be reused in different quantum client instances to reduce roundtrips with keystone fixes bug 1150051 Change-Id: I3b2f23f364099bdb08992130d5edb87222e8d387 --- quantumclient/client.py | 6 ++++++ quantumclient/v2_0/client.py | 3 +++ 2 files changed, 9 insertions(+) diff --git a/quantumclient/client.py b/quantumclient/client.py index e9d594993..85092a73c 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -218,6 +218,12 @@ def _get_endpoint_url(self): raise exceptions.EndpointNotFound() + def get_auth_info(self): + return {'auth_token': self.auth_token, + 'auth_tenant_id': self.auth_tenant_id, + 'auth_user_id': self.auth_user_id, + 'endpoint_url': self.endpoint_url} + def get_status_code(self, response): """ Returns the integer status code from the response, which diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 3010354cb..ecd30d964 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -911,6 +911,9 @@ def do_request(self, method, action, body=None, headers=None, params=None): else: self._handle_fault_response(status_code, replybody) + def get_auth_info(self): + return self.httpclient.get_auth_info() + def get_status_code(self, response): """ Returns the integer status code from the response, which From 3889ba2be00308ea9012742bf3185b3bde7cc40b Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 4 Apr 2013 13:34:33 -0400 Subject: [PATCH 103/135] Exclude top level 'tests dir' from packages. In 8fbe6d we moved 'tests' to be top level. As such it makes since to now exclude it from quantumclient packages. Change-Id: I98b0bd4f5433b13c276ff89b8e359cba17ce78a5 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c3d851cbd..e3e5ad682 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,7 @@ tests_require=tests_require, cmdclass=setup.get_cmdclass(), include_package_data=False, - packages=setuptools.find_packages('.'), + packages=setuptools.find_packages(exclude=['tests', 'tests.*']), package_data=PackageData, eager_resources=EagerResources, entry_points={ From d41d223e465dfdcb4c7d2a2f5dc55652bbf32bf7 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Fri, 5 Apr 2013 17:26:00 +0000 Subject: [PATCH 104/135] Switch to flake8 from pep8. * flake8 supports more checks than pep8 (e.g. detection of unused imports and variables), and has an extension mechanism. A plugin to support automatic HACKING validation is planned. * See: http://flake8.readthedocs.org/ Change-Id: I1cba551fadf87f3dbc40a002736c1009e7b9d5b5 --- quantumclient/common/clientmanager.py | 1 - quantumclient/quantum/v2_0/network.py | 1 - .../quantum/v2_0/nvpnetworkgateway.py | 1 - setup.py | 2 - tests/unit/lb/test_cli20_healthmonitor.py | 5 +- tests/unit/test_cli20_floatingips.py | 21 +++-- tests/unit/test_cli20_network.py | 4 - tests/unit/test_cli20_nvpnetworkgateway.py | 17 ++-- tests/unit/test_cli20_router.py | 16 ++-- tests/unit/test_cli20_securitygroup.py | 1 - tests/unit/test_cli20_subnet.py | 78 +++++++++---------- tools/test-requires | 2 +- tox.ini | 10 ++- 13 files changed, 78 insertions(+), 81 deletions(-) diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py index 90aa48e4b..9083d4551 100644 --- a/quantumclient/common/clientmanager.py +++ b/quantumclient/common/clientmanager.py @@ -21,7 +21,6 @@ import logging from quantumclient.client import HTTPClient -from quantumclient.common import exceptions as exc from quantumclient.quantum import client as quantum_client diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index c7f0d8e45..7a9a8ce4b 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -18,7 +18,6 @@ import argparse import logging -from quantumclient.common import utils from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand diff --git a/quantumclient/quantum/v2_0/nvpnetworkgateway.py b/quantumclient/quantum/v2_0/nvpnetworkgateway.py index 39573041c..2c668acf3 100644 --- a/quantumclient/quantum/v2_0/nvpnetworkgateway.py +++ b/quantumclient/quantum/v2_0/nvpnetworkgateway.py @@ -15,7 +15,6 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 -import argparse import logging from quantumclient.common import utils diff --git a/setup.py b/setup.py index c3d851cbd..42b053ff9 100644 --- a/setup.py +++ b/setup.py @@ -15,8 +15,6 @@ # License for the specific language governing permissions and limitations # under the License. -import os -import sys import setuptools from quantumclient.openstack.common import setup diff --git a/tests/unit/lb/test_cli20_healthmonitor.py b/tests/unit/lb/test_cli20_healthmonitor.py index ed0086a75..56c753c54 100644 --- a/tests/unit/lb/test_cli20_healthmonitor.py +++ b/tests/unit/lb/test_cli20_healthmonitor.py @@ -26,11 +26,12 @@ class CLITestV20LbHealthmonitor(test_cli20.CLITestV20Base): - def test_create_healthmonitor_with_all_params(self): + def test_create_healthmonitor_with_mandatory_params(self): """lb-healthmonitor-create with mandatory params only""" resource = 'health_monitor' cmd = healthmonitor.CreateHealthMonitor(test_cli20.MyApp(sys.stdout), None) + admin_state_up = False delay = '60' max_retries = '2' timeout = '10' @@ -45,7 +46,7 @@ def test_create_healthmonitor_with_all_params(self): '--tenant-id', tenant_id] position_names = ['admin_state_up', 'delay', 'max_retries', 'timeout', 'type', 'tenant_id'] - position_values = [True, delay, max_retries, timeout, type, + position_values = [admin_state_up, delay, max_retries, timeout, type, tenant_id] self._test_create_resource(resource, cmd, '', my_id, args, position_names, position_values) diff --git a/tests/unit/test_cli20_floatingips.py b/tests/unit/test_cli20_floatingips.py index 854bf1ec0..e70d6bd8f 100644 --- a/tests/unit/test_cli20_floatingips.py +++ b/tests/unit/test_cli20_floatingips.py @@ -18,7 +18,6 @@ import sys -from quantumclient.common import exceptions from quantumclient.quantum.v2_0.floatingip import AssociateFloatingIP from quantumclient.quantum.v2_0.floatingip import CreateFloatingIP from quantumclient.quantum.v2_0.floatingip import DeleteFloatingIP @@ -39,8 +38,8 @@ def test_create_floatingip(self): args = [name] position_names = ['floating_network_id'] position_values = [name] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) def test_create_floatingip_and_port(self): """Create floatingip: fip1.""" @@ -52,14 +51,14 @@ def test_create_floatingip_and_port(self): args = [name, '--port_id', pid] position_names = ['floating_network_id', 'port_id'] position_values = [name, pid] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) # Test dashed options args = [name, '--port-id', pid] position_names = ['floating_network_id', 'port_id'] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) def test_create_floatingip_and_port_and_address(self): """Create floatingip: fip1 with a given port and address""" @@ -72,13 +71,13 @@ def test_create_floatingip_and_port_and_address(self): args = [name, '--port_id', pid, '--fixed_ip_address', addr] position_names = ['floating_network_id', 'port_id', 'fixed_ip_address'] position_values = [name, pid, addr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) # Test dashed options args = [name, '--port-id', pid, '--fixed-ip-address', addr] position_names = ['floating_network_id', 'port_id', 'fixed_ip_address'] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) def test_list_floatingips(self): """list floatingips: -D.""" diff --git a/tests/unit/test_cli20_network.py b/tests/unit/test_cli20_network.py index 18e3a2c42..1c59b6fd2 100644 --- a/tests/unit/test_cli20_network.py +++ b/tests/unit/test_cli20_network.py @@ -17,7 +17,6 @@ import sys -import mox from mox import (ContainsKeyValue, IgnoreArg, IsA) from quantumclient.common import exceptions @@ -182,7 +181,6 @@ def test_list_nets_tags(self): def test_list_nets_detail_tags(self): """List nets: -D -- --tags a b.""" - resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) self._test_list_networks(cmd, detail=True, tags=['a', 'b']) @@ -198,7 +196,6 @@ def setup_list_stub(resources, data, query): headers=ContainsKeyValue( 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(resp) - resources = "networks" cmd = ListNetwork(test_cli20.MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, 'get_client') self.mox.StubOutWithMock(self.client.httpclient, 'request') @@ -249,7 +246,6 @@ def test_list_nets_extend_subnets_no_subnet(self): def test_list_nets_fields(self): """List nets: --fields a --fields b -- --fields c d.""" - resources = "networks" cmd = ListNetwork(MyApp(sys.stdout), None) self._test_list_networks(cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) diff --git a/tests/unit/test_cli20_nvpnetworkgateway.py b/tests/unit/test_cli20_nvpnetworkgateway.py index 31f7e1ae1..bf95a6d6a 100644 --- a/tests/unit/test_cli20_nvpnetworkgateway.py +++ b/tests/unit/test_cli20_nvpnetworkgateway.py @@ -17,7 +17,6 @@ import sys -from quantumclient.common import exceptions from quantumclient.quantum.v2_0 import nvpnetworkgateway from tests.unit.test_cli20 import CLITestV20Base from tests.unit.test_cli20 import MyApp @@ -34,8 +33,8 @@ def test_create_gateway(self): args = [name, ] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(self.resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(self.resource, cmd, name, myid, args, + position_names, position_values) def test_create_gateway_with_tenant(self): cmd = nvpnetworkgateway.CreateNetworkGateway(MyApp(sys.stdout), None) @@ -44,9 +43,9 @@ def test_create_gateway_with_tenant(self): args = ['--tenant_id', 'tenantid', name] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(self.resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(self.resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_gateway_with_device(self): cmd = nvpnetworkgateway.CreateNetworkGateway(MyApp(sys.stdout), None) @@ -55,9 +54,9 @@ def test_create_gateway_with_device(self): args = ['--device', 'device_id=test', name, ] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(self.resource, cmd, name, myid, args, - position_names, position_values, - devices=[{'device_id': 'test'}]) + self._test_create_resource(self.resource, cmd, name, myid, args, + position_names, position_values, + devices=[{'device_id': 'test'}]) def test_list_gateways(self): resources = '%ss' % self.resource diff --git a/tests/unit/test_cli20_router.py b/tests/unit/test_cli20_router.py index e9e661c11..262ce64e2 100644 --- a/tests/unit/test_cli20_router.py +++ b/tests/unit/test_cli20_router.py @@ -41,8 +41,8 @@ def test_create_router(self): args = [name, ] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) def test_create_router_tenant(self): """Create router: --tenant_id tenantid myname.""" @@ -53,9 +53,9 @@ def test_create_router_tenant(self): args = ['--tenant_id', 'tenantid', name] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_router_admin_state(self): """Create router: --admin_state_down myname.""" @@ -66,9 +66,9 @@ def test_create_router_admin_state(self): args = ['--admin_state_down', name, ] position_names = ['name', ] position_values = [name, ] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - admin_state_up=False) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + admin_state_up=False) def test_list_routers_detail(self): """list routers: -D.""" diff --git a/tests/unit/test_cli20_securitygroup.py b/tests/unit/test_cli20_securitygroup.py index 77cbdaf3d..3058f4dba 100644 --- a/tests/unit/test_cli20_securitygroup.py +++ b/tests/unit/test_cli20_securitygroup.py @@ -236,7 +236,6 @@ def setup_list_stub(resources, data, query): expected['cols'] = expected.get('cols', _expected['cols']) expected['data'] = expected.get('data', _expected['data']) - resources = "security_group_rules" cmd = securitygroup.ListSecurityGroupRule( test_cli20.MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, 'get_client') diff --git a/tests/unit/test_cli20_subnet.py b/tests/unit/test_cli20_subnet.py index 7bbe7ea0c..82b619a6f 100644 --- a/tests/unit/test_cli20_subnet.py +++ b/tests/unit/test_cli20_subnet.py @@ -40,8 +40,8 @@ def test_create_subnet(self): args = ['--gateway', gateway, netid, cidr] position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] position_values = [4, netid, cidr, gateway] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) def test_create_subnet_with_no_gateway(self): """Create subnet: --no-gateway netid cidr""" @@ -54,8 +54,8 @@ def test_create_subnet_with_no_gateway(self): args = ['--no-gateway', netid, cidr] position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] position_values = [4, netid, cidr, None] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) def test_create_subnet_with_bad_gateway_option(self): """Create sbunet: --no-gateway netid cidr""" @@ -70,8 +70,8 @@ def test_create_subnet_with_bad_gateway_option(self): position_names = ['ip_version', 'network_id', 'cidr', 'gateway_ip'] position_values = [4, netid, cidr, None] try: - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) except: return self.fail('No exception for bad gateway option') @@ -87,9 +87,9 @@ def test_create_subnet_tenant(self): args = ['--tenant_id', 'tenantid', netid, cidr] position_names = ['ip_version', 'network_id', 'cidr'] position_values = [4, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_subnet_tags(self): """Create subnet: netid cidr --tags a b.""" @@ -102,9 +102,9 @@ def test_create_subnet_tags(self): args = [netid, cidr, '--tags', 'a', 'b'] position_names = ['ip_version', 'network_id', 'cidr'] position_values = [4, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tags=['a', 'b']) + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tags=['a', 'b']) def test_create_subnet_allocation_pool(self): """Create subnet: --tenant_id tenantid netid cidr. @@ -123,9 +123,9 @@ def test_create_subnet_allocation_pool(self): 'cidr'] pool = [{'start': '1.1.1.10', 'end': '1.1.1.20'}] position_values = [4, pool, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_subnet_allocation_pools(self): """Create subnet: --tenant-id tenantid netid cidr. @@ -147,9 +147,9 @@ def test_create_subnet_allocation_pools(self): pools = [{'start': '1.1.1.10', 'end': '1.1.1.20'}, {'start': '1.1.1.30', 'end': '1.1.1.40'}] position_values = [4, pools, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_subnet_host_route(self): """Create subnet: --tenant_id tenantid netid cidr. @@ -169,9 +169,9 @@ def test_create_subnet_host_route(self): 'cidr'] route = [{'destination': '172.16.1.0/24', 'nexthop': '1.1.1.20'}] position_values = [4, route, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_subnet_host_routes(self): """Create subnet: --tenant-id tenantid netid cidr. @@ -194,9 +194,9 @@ def test_create_subnet_host_routes(self): routes = [{'destination': '172.16.1.0/24', 'nexthop': '1.1.1.20'}, {'destination': '172.17.7.0/24', 'nexthop': '1.1.1.40'}] position_values = [4, routes, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_subnet_dns_nameservers(self): """Create subnet: --tenant-id tenantid netid cidr. @@ -217,9 +217,9 @@ def test_create_subnet_dns_nameservers(self): 'cidr'] nameservers = ['1.1.1.20', '1.1.1.40'] position_values = [4, nameservers, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_subnet_with_disable_dhcp(self): """Create subnet: --tenant-id tenantid --disable-dhcp netid cidr.""" @@ -235,9 +235,9 @@ def test_create_subnet_with_disable_dhcp(self): position_names = ['ip_version', 'enable_dhcp', 'network_id', 'cidr'] position_values = [4, False, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_subnet_merge_single_plurar(self): resource = 'subnet' @@ -256,9 +256,9 @@ def test_create_subnet_merge_single_plurar(self): pools = [{'start': '1.1.1.10', 'end': '1.1.1.20'}, {'start': '1.1.1.30', 'end': '1.1.1.40'}] position_values = [4, pools, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_subnet_merge_plurar(self): resource = 'subnet' @@ -275,9 +275,9 @@ def test_create_subnet_merge_plurar(self): 'cidr'] pools = [{'start': '1.1.1.30', 'end': '1.1.1.40'}] position_values = [4, pools, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_create_subnet_merge_single_single(self): resource = 'subnet' @@ -296,9 +296,9 @@ def test_create_subnet_merge_single_single(self): pools = [{'start': '1.1.1.10', 'end': '1.1.1.20'}, {'start': '1.1.1.30', 'end': '1.1.1.40'}] position_values = [4, pools, netid, cidr] - _str = self._test_create_resource(resource, cmd, name, myid, args, - position_names, position_values, - tenant_id='tenantid') + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values, + tenant_id='tenantid') def test_list_subnets_detail(self): """List subnets: -D.""" diff --git a/tools/test-requires b/tools/test-requires index b82e805ed..1f1de174b 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -3,8 +3,8 @@ coverage discover distribute>=0.6.24 fixtures>=0.3.12 +flake8 mox -pep8 python-subunit sphinx>=1.1.2 testrepository>=0.0.13 diff --git a/tox.ini b/tox.ini index dbb367d4d..ade5655e7 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ deps = -r{toxinidir}/tools/test-requires commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] -commands = pep8 --repeat --show-source --ignore=E125 --exclude=.venv,.tox,dist,doc . +commands = flake8 [testenv:venv] commands = {posargs} @@ -21,3 +21,11 @@ commands = python setup.py testr --coverage --testr-args='{posargs}' [tox:jenkins] downloadcache = ~/cache/pip + +[flake8] +# E125 continuation line does not distinguish itself from next logical line +# H hacking.py - automatic checks of rules in HACKING.rst +ignore = E125,H +show-source = true +builtins = _ +exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,tools From 4421ab10b88cceded6924bda4a76bb7ba326891f Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Mon, 1 Apr 2013 13:27:28 +0800 Subject: [PATCH 105/135] Fix xml request doesn't work with unicode Fix bug 1160704 * httplib2 doesn't work with unicode url and params. So encode all unicode to utf-8 before request. * Fix xml serializer doesn't work with unicode * Decode command argument to unicode in main function * Exception's message maybe include unicode, decode message to unicode before logging or print. * Sync the changing of serializer/deserilizer's code with quantum server code https://review.openstack.org/#/c/25482/ https://review.openstack.org/#/c/25046/ https://review.openstack.org/#/c/21410/ * Enable xml test Change-Id: Ib140e34d54cc916e2ea172e4bad9e4a77388723a --- openstack-common.conf | 2 +- quantumclient/client.py | 2 + quantumclient/common/constants.py | 1 + quantumclient/common/serializer.py | 50 +++++--- quantumclient/common/utils.py | 23 ++++ quantumclient/openstack/common/strutils.py | 133 +++++++++++++++++++++ quantumclient/shell.py | 24 ++-- quantumclient/tests/unit/test_utils.py | 45 +++++++ quantumclient/v2_0/client.py | 2 + tests/unit/lb/test_cli20_healthmonitor.py | 6 +- tests/unit/lb/test_cli20_member.py | 9 +- tests/unit/lb/test_cli20_pool.py | 6 +- tests/unit/lb/test_cli20_vip.py | 9 +- tests/unit/test_cli20.py | 126 +++++++++++++++---- tests/unit/test_cli20_floatingips.py | 6 +- tests/unit/test_cli20_network.py | 37 +++++- tests/unit/test_cli20_nvp_queue.py | 10 +- tests/unit/test_cli20_nvpnetworkgateway.py | 11 +- tests/unit/test_cli20_port.py | 8 +- tests/unit/test_cli20_router.py | 6 +- tests/unit/test_cli20_securitygroup.py | 6 +- tests/unit/test_cli20_subnet.py | 8 +- tests/unit/test_shell.py | 16 +++ 23 files changed, 481 insertions(+), 65 deletions(-) create mode 100644 quantumclient/openstack/common/strutils.py create mode 100644 quantumclient/tests/unit/test_utils.py diff --git a/openstack-common.conf b/openstack-common.conf index 9b7ed7706..454676543 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=exception,gettextutils,jsonutils,setup,timeutils +modules=exception,gettextutils,jsonutils,setup,strutils,timeutils # The base module to hold the copy of openstack.common base=quantumclient diff --git a/quantumclient/client.py b/quantumclient/client.py index 85092a73c..5726000fe 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -122,6 +122,8 @@ def _cs_request(self, *args, **kwargs): if 'body' in kwargs: kargs['body'] = kwargs['body'] + args = utils.safe_encode_list(args) + kargs = utils.safe_encode_dict(kargs) utils.http_log_req(_logger, args, kargs) resp, body = self.request(*args, **kargs) utils.http_log_resp(_logger, resp, body) diff --git a/quantumclient/common/constants.py b/quantumclient/common/constants.py index 7e56331aa..a8e8276af 100644 --- a/quantumclient/common/constants.py +++ b/quantumclient/common/constants.py @@ -23,6 +23,7 @@ TYPE_ATTR = "quantum:type" VIRTUAL_ROOT_KEY = "_v_root" ATOM_NAMESPACE = "http://www.w3.org/2005/Atom" +ATOM_XMLNS = "xmlns:atom" ATOM_LINK_NOTATION = "{%s}link" % ATOM_NAMESPACE TYPE_BOOL = "bool" diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py index 5716a0cb1..e5cd76405 100644 --- a/quantumclient/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -58,7 +58,9 @@ class JSONDictSerializer(DictSerializer): """Default JSON request body serialization""" def default(self, data): - return jsonutils.dumps(data) + def sanitizer(obj): + return unicode(obj) + return jsonutils.dumps(data, default=sanitizer) class XMLDictSerializer(DictSerializer): @@ -78,21 +80,33 @@ def __init__(self, metadata=None, xmlns=None): self.xmlns = xmlns def default(self, data): - # We expect data to contain a single key which is the XML root or - # non root + """ + :param data: expect data to contain a single key as XML root, or + contain another '*_links' key as atom links. Other + case will use 'VIRTUAL_ROOT_KEY' as XML root. + """ try: - key_len = data and len(data.keys()) or 0 - if (key_len == 1): - root_key = data.keys()[0] - root_value = data[root_key] - else: + links = None + has_atom = False + if data is None: root_key = constants.VIRTUAL_ROOT_KEY - root_value = data + root_value = None + else: + link_keys = [k for k in data.iterkeys() or [] + if k.endswith('_links')] + if link_keys: + links = data.pop(link_keys[0], None) + has_atom = True + root_key = (len(data) == 1 and + data.keys()[0] or constants.VIRTUAL_ROOT_KEY) + root_value = data.get(root_key, data) doc = etree.Element("_temp_root") used_prefixes = [] self._to_xml_node(doc, self.metadata, root_key, root_value, used_prefixes) - return self.to_xml_string(list(doc)[0], used_prefixes) + if links: + self._create_link_nodes(list(doc)[0], links) + return self.to_xml_string(list(doc)[0], used_prefixes, has_atom) except AttributeError as e: LOG.exception(str(e)) return '' @@ -115,7 +129,7 @@ def _add_xmlns(self, node, used_prefixes, has_atom=False): node.set('xmlns', self.xmlns) node.set(constants.TYPE_XMLNS, self.xmlns) if has_atom: - node.set('xmlns:atom', "http://www.w3.org/2005/Atom") + node.set(constants.ATOM_XMLNS, constants.ATOM_NAMESPACE) node.set(constants.XSI_NIL_ATTR, constants.XSI_NAMESPACE) ext_ns = self.metadata.get(constants.EXT_NS, {}) for prefix in used_prefixes: @@ -179,19 +193,17 @@ def _to_xml_node(self, parent, metadata, nodename, data, used_prefixes): LOG.debug(_("Data %(data)s type is %(type)s"), {'data': data, 'type': type(data)}) - result.text = str(data) + if isinstance(data, str): + result.text = unicode(data, 'utf-8') + else: + result.text = unicode(data) return result def _create_link_nodes(self, xml_doc, links): - link_nodes = [] for link in links: - link_node = xml_doc.createElement('atom:link') + link_node = etree.SubElement(xml_doc, 'atom:link') link_node.set('rel', link['rel']) link_node.set('href', link['href']) - if 'type' in link: - link_node.set('type', link['type']) - link_nodes.append(link_node) - return link_nodes class TextDeserializer(ActionDispatcher): @@ -329,7 +341,7 @@ def _from_xml_node(self, node, listnames): attr == constants.XSI_ATTR or attr == constants.TYPE_ATTR): continue - result[self._get_key(attr)] = node.get[attr] + result[self._get_key(attr)] = node.get(attr) children = list(node) for child in children: result[self._get_key(child.tag)] = self._from_xml_node( diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py index 45c64a998..bd64dbe3a 100644 --- a/quantumclient/common/utils.py +++ b/quantumclient/common/utils.py @@ -27,6 +27,7 @@ import sys from quantumclient.common import exceptions +from quantumclient.openstack.common import strutils def env(*vars, **kwargs): @@ -165,6 +166,7 @@ def http_log_req(_logger, args, kwargs): if 'body' in kwargs and kwargs['body']: string_parts.append(" -d '%s'" % (kwargs['body'])) + string_parts = safe_encode_list(string_parts) _logger.debug("\nREQ: %s\n" % "".join(string_parts)) @@ -172,3 +174,24 @@ def http_log_resp(_logger, resp, body): if not _logger.isEnabledFor(logging.DEBUG): return _logger.debug("RESP:%s %s\n", resp, body) + + +def _safe_encode_without_obj(data): + if isinstance(data, basestring): + return strutils.safe_encode(data) + return data + + +def safe_encode_list(data): + return map(_safe_encode_without_obj, data) + + +def safe_encode_dict(data): + def _encode_item((k, v)): + if isinstance(v, list): + return (k, safe_encode_list(v)) + elif isinstance(v, dict): + return (k, safe_encode_dict(v)) + return (k, _safe_encode_without_obj(v)) + + return dict(map(_encode_item, data.items())) diff --git a/quantumclient/openstack/common/strutils.py b/quantumclient/openstack/common/strutils.py new file mode 100644 index 000000000..ecf3cfdc4 --- /dev/null +++ b/quantumclient/openstack/common/strutils.py @@ -0,0 +1,133 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +System-level utilities and helper functions. +""" + +import logging +import sys + +LOG = logging.getLogger(__name__) + + +def int_from_bool_as_string(subject): + """ + Interpret a string as a boolean and return either 1 or 0. + + Any string value in: + + ('True', 'true', 'On', 'on', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + return bool_from_string(subject) and 1 or 0 + + +def bool_from_string(subject): + """ + Interpret a string as a boolean. + + Any string value in: + + ('True', 'true', 'On', 'on', 'Yes', 'yes', '1') + + is interpreted as a boolean True. + + Useful for JSON-decoded stuff and config file parsing + """ + if isinstance(subject, bool): + return subject + if isinstance(subject, basestring): + if subject.strip().lower() in ('true', 'on', 'yes', '1'): + return True + return False + + +def safe_decode(text, incoming=None, errors='strict'): + """ + Decodes incoming str using `incoming` if they're + not already unicode. + + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a unicode `incoming` encoded + representation of it. + :raises TypeError: If text is not an isntance of basestring + """ + if not isinstance(text, basestring): + raise TypeError("%s can't be decoded" % type(text)) + + if isinstance(text, unicode): + return text + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + try: + return text.decode(incoming, errors) + except UnicodeDecodeError: + # Note(flaper87) If we get here, it means that + # sys.stdin.encoding / sys.getdefaultencoding + # didn't return a suitable encoding to decode + # text. This happens mostly when global LANG + # var is not set correctly and there's no + # default encoding. In this case, most likely + # python will use ASCII or ANSI encoders as + # default encodings but they won't be capable + # of decoding non-ASCII characters. + # + # Also, UTF-8 is being used since it's an ASCII + # extension. + return text.decode('utf-8', errors) + + +def safe_encode(text, incoming=None, + encoding='utf-8', errors='strict'): + """ + Encodes incoming str/unicode using `encoding`. If + incoming is not specified, text is expected to + be encoded with current python's default encoding. + (`sys.getdefaultencoding`) + + :param incoming: Text's current encoding + :param encoding: Expected encoding for text (Default UTF-8) + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a bytestring `encoding` encoded + representation of it. + :raises TypeError: If text is not an isntance of basestring + """ + if not isinstance(text, basestring): + raise TypeError("%s can't be encoded" % type(text)) + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + if isinstance(text, unicode): + return text.encode(encoding, errors) + elif text and encoding != incoming: + # Decode text before encoding it with `encoding` + text = safe_decode(text, incoming, errors) + return text.encode(encoding, errors) + + return text diff --git a/quantumclient/shell.py b/quantumclient/shell.py index b44e3d932..f531325dc 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -31,6 +31,7 @@ from quantumclient.common import clientmanager from quantumclient.common import exceptions as exc from quantumclient.common import utils +from quantumclient.openstack.common import strutils VERSION = '2.0' @@ -476,10 +477,10 @@ def run(self, argv): self.initialize_app(remainder) except Exception as err: if self.options.debug: - self.log.exception(err) + self.log.exception(unicode(err)) raise else: - self.log.error(err) + self.log.error(unicode(err)) return 1 result = 1 if self.interactive_mode: @@ -506,16 +507,16 @@ def run_subcommand(self, argv): return run_command(cmd, cmd_parser, sub_argv) except Exception as err: if self.options.debug: - self.log.exception(err) + self.log.exception(unicode(err)) else: - self.log.error(err) + self.log.error(unicode(err)) try: self.clean_up(cmd, result, err) except Exception as err2: if self.options.debug: - self.log.exception(err2) + self.log.exception(unicode(err2)) else: - self.log.error('Could not clean up: %s', err2) + self.log.error('Could not clean up: %s', unicode(err2)) if self.options.debug: raise else: @@ -523,9 +524,9 @@ def run_subcommand(self, argv): self.clean_up(cmd, result, None) except Exception as err3: if self.options.debug: - self.log.exception(err3) + self.log.exception(unicode(err3)) else: - self.log.error('Could not clean up: %s', err3) + self.log.error('Could not clean up: %s', unicode(err3)) return result def authenticate_user(self): @@ -608,7 +609,7 @@ def initialize_app(self, argv): def clean_up(self, cmd, result, err): self.log.debug('clean_up %s', cmd.__class__.__name__) if err: - self.log.debug('got an error: %s', err) + self.log.debug('got an error: %s', unicode(err)) def configure_logging(self): """Create logging handlers for any log output. @@ -637,11 +638,12 @@ def configure_logging(self): def main(argv=sys.argv[1:]): gettext.install('quantumclient', unicode=1) try: - return QuantumShell(QUANTUM_API_VERSION).run(argv) + return QuantumShell(QUANTUM_API_VERSION).run(map(strutils.safe_decode, + argv)) except exc.QuantumClientException: return 1 except Exception as e: - print e + print unicode(e) return 1 diff --git a/quantumclient/tests/unit/test_utils.py b/quantumclient/tests/unit/test_utils.py new file mode 100644 index 000000000..69ecf6dd3 --- /dev/null +++ b/quantumclient/tests/unit/test_utils.py @@ -0,0 +1,45 @@ +# Copyright 2013 OpenStack LLC. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import testtools + +from quantumclient.common import utils + + +class UtilsTest(testtools.TestCase): + def test_safe_encode_list(self): + o = object() + unicode_text = u'\u7f51\u7edc' + l = ['abc', unicode_text, unicode_text.encode('utf-8'), o] + expected = ['abc', unicode_text.encode('utf-8'), + unicode_text.encode('utf-8'), o] + self.assertEqual(utils.safe_encode_list(l), expected) + + def test_safe_encode_dict(self): + o = object() + unicode_text = u'\u7f51\u7edc' + d = {'test1': unicode_text, + 'test2': [unicode_text, o], + 'test3': o, + 'test4': {'test5': unicode_text}, + 'test6': unicode_text.encode('utf-8')} + expected = {'test1': unicode_text.encode('utf-8'), + 'test2': [unicode_text.encode('utf-8'), o], + 'test3': o, + 'test4': {'test5': unicode_text.encode('utf-8')}, + 'test6': unicode_text.encode('utf-8')} + self.assertEqual(utils.safe_encode_dict(d), expected) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index ecd30d964..a0d17d8d5 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -26,6 +26,7 @@ from quantumclient.common import constants from quantumclient.common import exceptions from quantumclient.common import serializer +from quantumclient.common import utils _logger = logging.getLogger(__name__) @@ -897,6 +898,7 @@ def do_request(self, method, action, body=None, headers=None, params=None): action += ".%s" % self.format action = self.action_prefix + action if type(params) is dict and params: + params = utils.safe_encode_dict(params) action += '?' + urllib.urlencode(params, doseq=1) if body: body = self.serialize(body) diff --git a/tests/unit/lb/test_cli20_healthmonitor.py b/tests/unit/lb/test_cli20_healthmonitor.py index 56c753c54..37f93e17d 100644 --- a/tests/unit/lb/test_cli20_healthmonitor.py +++ b/tests/unit/lb/test_cli20_healthmonitor.py @@ -25,7 +25,7 @@ from tests.unit import test_cli20 -class CLITestV20LbHealthmonitor(test_cli20.CLITestV20Base): +class CLITestV20LbHealthmonitorJSON(test_cli20.CLITestV20Base): def test_create_healthmonitor_with_mandatory_params(self): """lb-healthmonitor-create with mandatory params only""" resource = 'health_monitor' @@ -213,3 +213,7 @@ def test_disassociate_healthmonitor(self): cmd.run(parsed_args) self.mox.VerifyAll() self.mox.UnsetStubs() + + +class CLITestV20LbHealthmonitorXML(CLITestV20LbHealthmonitorJSON): + format = 'xml' diff --git a/tests/unit/lb/test_cli20_member.py b/tests/unit/lb/test_cli20_member.py index 6f80fd845..dc53ff77d 100644 --- a/tests/unit/lb/test_cli20_member.py +++ b/tests/unit/lb/test_cli20_member.py @@ -23,7 +23,10 @@ from tests.unit import test_cli20 -class CLITestV20LbMember(test_cli20.CLITestV20Base): +class CLITestV20LbMemberJSON(test_cli20.CLITestV20Base): + def setUp(self): + super(CLITestV20LbMemberJSON, self).setUp(plurals={'tags': 'tag'}) + def test_create_member(self): """lb-member-create with mandatory params only""" resource = 'member' @@ -125,3 +128,7 @@ def test_delete_member(self): my_id = 'my-id' args = [my_id] self._test_delete_resource(resource, cmd, my_id, args) + + +class CLITestV20LbMemberXML(CLITestV20LbMemberJSON): + format = 'xml' diff --git a/tests/unit/lb/test_cli20_pool.py b/tests/unit/lb/test_cli20_pool.py index b02b0baac..ce2264e29 100644 --- a/tests/unit/lb/test_cli20_pool.py +++ b/tests/unit/lb/test_cli20_pool.py @@ -25,7 +25,7 @@ from tests.unit import test_cli20 -class CLITestV20LbPool(test_cli20.CLITestV20Base): +class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base): def test_create_pool_with_mandatory_params(self): """lb-pool-create with mandatory params only""" @@ -165,3 +165,7 @@ def test_retrieve_pool_stats(self): _str = self.fake_stdout.make_string() self.assertTrue('bytes_in' in _str) self.assertTrue('bytes_out' in _str) + + +class CLITestV20LbPoolXML(CLITestV20LbPoolJSON): + format = 'xml' diff --git a/tests/unit/lb/test_cli20_vip.py b/tests/unit/lb/test_cli20_vip.py index c42a29bd5..af9dfa190 100644 --- a/tests/unit/lb/test_cli20_vip.py +++ b/tests/unit/lb/test_cli20_vip.py @@ -23,7 +23,10 @@ from tests.unit import test_cli20 -class CLITestV20LbVip(test_cli20.CLITestV20Base): +class CLITestV20LbVipJSON(test_cli20.CLITestV20Base): + def setUp(self): + super(CLITestV20LbVipJSON, self).setUp(plurals={'tags': 'tag'}) + def test_create_vip_with_mandatory_params(self): """lb-vip-create with all mandatory params""" resource = 'vip' @@ -205,3 +208,7 @@ def test_delete_vip(self): my_id = 'my-id' args = [my_id] self._test_delete_resource(resource, cmd, my_id, args) + + +class CLITestV20LbVipXML(CLITestV20LbVipJSON): + format = 'xml' diff --git a/tests/unit/test_cli20.py b/tests/unit/test_cli20.py index 21b1b5179..9f589ec32 100644 --- a/tests/unit/test_cli20.py +++ b/tests/unit/test_cli20.py @@ -15,12 +15,15 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 +import urllib + import fixtures import mox from mox import Comparator from mox import ContainsKeyValue import testtools +from quantumclient.common import constants from quantumclient import shell from quantumclient.v2_0.client import Client @@ -56,8 +59,8 @@ def __init__(self, _stdout): self.stdout = _stdout -def end_url(path, query=None): - _url_str = ENDURL + "/v" + API_VERSION + path + "." + FORMAT +def end_url(path, query=None, format=FORMAT): + _url_str = ENDURL + "/v" + API_VERSION + path + "." + format return query and _url_str + "?" + query or _url_str @@ -73,12 +76,12 @@ def __str__(self): if self.client and self.client.format != FORMAT: lhs_parts = self.lhs.split("?", 1) if len(lhs_parts) == 2: - lhs = ("%s%s?%s" % (lhs_parts[0][:-4], - self.client.format, - lhs_parts[1])) + lhs = ("%s.%s?%s" % (lhs_parts[0][:-4], + self.client.format, + lhs_parts[1])) else: - lhs = ("%s%s" % (lhs_parts[0][:-4], - self.client.format)) + lhs = ("%s.%s" % (lhs_parts[0][:-4], + self.client.format)) return lhs return self.lhs @@ -133,27 +136,47 @@ def equals(self, rhs): return self._com(self.lhs, rhs) def __repr__(self): + if self.client: + return self.client.serialize(self.lhs) return str(self.lhs) class CLITestV20Base(testtools.TestCase): + format = 'json' test_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' def _find_resourceid(self, client, resource, name_or_id): return name_or_id - def setUp(self): + def _get_attr_metadata(self): + return self.metadata + Client.EXTED_PLURALS.update(constants.PLURALS) + Client.EXTED_PLURALS.update({'tags': 'tag'}) + return {'plurals': Client.EXTED_PLURALS, + 'xmlns': constants.XML_NS_V20, + constants.EXT_NS: {'prefix': 'http://xxxx.yy.com'}} + + def setUp(self, plurals={}): """Prepare the test environment""" super(CLITestV20Base, self).setUp() + Client.EXTED_PLURALS.update(constants.PLURALS) + Client.EXTED_PLURALS.update(plurals) + self.metadata = {'plurals': Client.EXTED_PLURALS, + 'xmlns': constants.XML_NS_V20, + constants.EXT_NS: {'prefix': + 'http://xxxx.yy.com'}} self.mox = mox.Mox() self.endurl = ENDURL - self.client = Client(token=TOKEN, endpoint_url=self.endurl) self.fake_stdout = FakeStdout() self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.fake_stdout)) self.useFixture(fixtures.MonkeyPatch( 'quantumclient.quantum.v2_0.find_resourceid_by_name_or_id', self._find_resourceid)) + self.useFixture(fixtures.MonkeyPatch( + 'quantumclient.v2_0.client.Client.get_attr_metadata', + self._get_attr_metadata)) + self.client = Client(token=TOKEN, endpoint_url=self.endurl) def _test_create_resource(self, resource, cmd, name, myid, args, @@ -186,15 +209,17 @@ def _test_create_resource(self, resource, cmd, {'id': myid}, } if name: ress[resource].update({'name': name}) + self.client.format = self.format resstr = self.client.serialize(ress) # url method body path = getattr(self.client, resource + "s_path") self.client.httpclient.request( - end_url(path), 'POST', + end_url(path, format=self.format), 'POST', body=MyComparator(body, self.client), headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) + args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser('create_' + resource) shell.run_command(cmd, cmd_parser, args) @@ -210,14 +235,16 @@ def _test_list_columns(self, cmd, resources_collection, self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) + self.client.format = self.format resstr = self.client.serialize(resources_out) path = getattr(self.client, resources_collection + "_path") self.client.httpclient.request( - end_url(path), 'GET', + end_url(path, format=self.format), 'GET', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) + args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources_collection) shell.run_command(cmd, cmd_parser, args) @@ -232,10 +259,12 @@ def _test_list_resources(self, resources, cmd, detail=False, tags=[], cmd.get_client().MultipleTimes().AndReturn(self.client) reses = {resources: [{'id': 'myid1', }, {'id': 'myid2', }, ], } + self.client.format = self.format resstr = self.client.serialize(reses) # url method body query = "" args = detail and ['-D', ] or [] + args.extend(['--request-format', self.format]) if fields_1: for field in fields_1: args.append('--fields') @@ -245,11 +274,13 @@ def _test_list_resources(self, resources, cmd, detail=False, tags=[], args.append('--') args.append("--tag") for tag in tags: + args.append(tag) + if isinstance(tag, unicode): + tag = urllib.quote(tag.encode('utf-8')) if query: query += "&tag=" + tag else: query = "tag=" + tag - args.append(tag) if (not tags) and fields_2: args.append('--') if fields_2: @@ -292,7 +323,9 @@ def _test_list_resources(self, resources, cmd, detail=False, tags=[], query += 'sort_dir=%s' % dir path = getattr(self.client, resources + "_path") self.client.httpclient.request( - MyUrlComparator(end_url(path, query), self.client), 'GET', + MyUrlComparator(end_url(path, query, format=self.format), + self.client), + 'GET', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) @@ -316,23 +349,25 @@ def _test_list_resources_with_pagination(self, resources, cmd): 'rel': 'next'}]} reses2 = {resources: [{'id': 'myid3', }, {'id': 'myid4', }]} + self.client.format = self.format resstr1 = self.client.serialize(reses1) resstr2 = self.client.serialize(reses2) self.client.httpclient.request( - end_url(path, ""), 'GET', + end_url(path, "", format=self.format), 'GET', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr1)) self.client.httpclient.request( - end_url(path, fake_query), 'GET', + end_url(path, fake_query, format=self.format), 'GET', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr2)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) - - parsed_args = cmd_parser.parse_args("") - cmd.run(parsed_args) + args = ['--request-format', self.format] + shell.run_command(cmd, cmd_parser, args) + #parsed_args = cmd_parser.parse_args("") + #cmd.run(parsed_args) self.mox.VerifyAll() self.mox.UnsetStubs() @@ -343,10 +378,13 @@ def _test_update_resource(self, resource, cmd, myid, args, extrafields): body = {resource: extrafields} path = getattr(self.client, resource + "_path") self.client.httpclient.request( - MyUrlComparator(end_url(path % myid), self.client), 'PUT', + MyUrlComparator(end_url(path % myid, format=self.format), + self.client), + 'PUT', body=MyComparator(body, self.client), headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) + args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("update_" + resource) shell.run_command(cmd, cmd_parser, args) @@ -363,13 +401,15 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]): expected_res = {resource: {'id': myid, 'name': 'myname', }, } + self.client.format = self.format resstr = self.client.serialize(expected_res) path = getattr(self.client, resource + "_path") self.client.httpclient.request( - end_url(path % myid, query), 'GET', + end_url(path % myid, query, format=self.format), 'GET', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) + args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("show_" + resource) shell.run_command(cmd, cmd_parser, args) @@ -385,10 +425,11 @@ def _test_delete_resource(self, resource, cmd, myid, args): cmd.get_client().MultipleTimes().AndReturn(self.client) path = getattr(self.client, resource + "_path") self.client.httpclient.request( - end_url(path % myid), 'DELETE', + end_url(path % myid, format=self.format), 'DELETE', body=None, headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) + args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("delete_" + resource) shell.run_command(cmd, cmd_parser, args) @@ -405,10 +446,11 @@ def _test_update_resource_action(self, resource, cmd, myid, action, args, path = getattr(self.client, resource + "_path") path_action = '%s/%s' % (myid, action) self.client.httpclient.request( - end_url(path % path_action), 'PUT', + end_url(path % path_action, format=self.format), 'PUT', body=MyComparator(body, self.client), headers=ContainsKeyValue('X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) + args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("delete_" + resource) shell.run_command(cmd, cmd_parser, args) @@ -416,3 +458,43 @@ def _test_update_resource_action(self, resource, cmd, myid, action, args, self.mox.UnsetStubs() _str = self.fake_stdout.make_string() self.assertTrue(myid in _str) + + +class ClientV2UnicodeTestJson(CLITestV20Base): + def test_do_request(self): + self.client.format = self.format + self.mox.StubOutWithMock(self.client.httpclient, "request") + unicode_text = u'\u7f51\u7edc' + # url with unicode + action = u'/test' + expected_action = action.encode('utf-8') + # query string with unicode + params = {'test': unicode_text} + expect_query = urllib.urlencode({'test': + unicode_text.encode('utf-8')}) + # request body with unicode + body = params + expect_body = self.client.serialize(body) + # headers with unicode + self.client.httpclient.auth_token = unicode_text + expected_auth_token = unicode_text.encode('utf-8') + + self.client.httpclient.request( + end_url(expected_action, query=expect_query, format=self.format), + 'PUT', body=expect_body, + headers=ContainsKeyValue( + 'X-Auth-Token', + expected_auth_token)).AndReturn((MyResp(200), expect_body)) + + self.mox.ReplayAll() + res_body = self.client.do_request('PUT', action, body=body, + params=params) + self.mox.VerifyAll() + self.mox.UnsetStubs() + + # test response with unicode + self.assertEqual(res_body, body) + + +class ClientV2UnicodeTestXML(ClientV2UnicodeTestJson): + format = 'xml' diff --git a/tests/unit/test_cli20_floatingips.py b/tests/unit/test_cli20_floatingips.py index e70d6bd8f..19e7a8d39 100644 --- a/tests/unit/test_cli20_floatingips.py +++ b/tests/unit/test_cli20_floatingips.py @@ -28,7 +28,7 @@ from tests.unit.test_cli20 import MyApp -class CLITestV20FloatingIps(CLITestV20Base): +class CLITestV20FloatingIpsJSON(CLITestV20Base): def test_create_floatingip(self): """Create floatingip: fip1.""" resource = 'floatingip' @@ -139,3 +139,7 @@ def test_associate_ip(self): self._test_update_resource(resource, cmd, 'myid', args, {"port_id": "portid"} ) + + +class CLITestV20FloatingIpsXML(CLITestV20FloatingIpsJSON): + format = 'xml' diff --git a/tests/unit/test_cli20_network.py b/tests/unit/test_cli20_network.py index 1c59b6fd2..e5a6f3b43 100644 --- a/tests/unit/test_cli20_network.py +++ b/tests/unit/test_cli20_network.py @@ -33,7 +33,10 @@ from tests.unit.test_cli20 import MyApp -class CLITestV20Network(CLITestV20Base): +class CLITestV20NetworkJSON(CLITestV20Base): + def setUp(self): + super(CLITestV20NetworkJSON, self).setUp(plurals={'tags': 'tag'}) + def test_create_network(self): """Create net: myname.""" resource = 'network' @@ -46,6 +49,18 @@ def test_create_network(self): self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) + def test_create_network_with_unicode(self): + """Create net: u'\u7f51\u7edc'.""" + resource = 'network' + cmd = CreateNetwork(MyApp(sys.stdout), None) + name = u'\u7f51\u7edc' + myid = 'myid' + args = [name, ] + position_names = ['name', ] + position_values = [name, ] + self._test_create_resource(resource, cmd, name, myid, args, + position_names, position_values) + def test_create_network_tenant(self): """Create net: --tenant_id tenantid myname.""" resource = 'network' @@ -179,6 +194,11 @@ def test_list_nets_tags(self): cmd = ListNetwork(MyApp(sys.stdout), None) self._test_list_networks(cmd, tags=['a', 'b']) + def test_list_nets_tags_with_unicode(self): + """List nets: -- --tags u'\u7f51\u7edc'.""" + cmd = ListNetwork(MyApp(sys.stdout), None) + self._test_list_networks(cmd, tags=[u'\u7f51\u7edc']) + def test_list_nets_detail_tags(self): """List nets: -D -- --tags a b.""" cmd = ListNetwork(MyApp(sys.stdout), None) @@ -426,6 +446,17 @@ def test_update_network(self): {'name': 'myname', 'tags': ['a', 'b'], } ) + def test_update_network_with_unicode(self): + """Update net: myid --name u'\u7f51\u7edc' --tags a b.""" + resource = 'network' + cmd = UpdateNetwork(MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', u'\u7f51\u7edc', + '--tags', 'a', 'b'], + {'name': u'\u7f51\u7edc', + 'tags': ['a', 'b'], } + ) + def test_show_network(self): """Show net: --fields id --fields name myid.""" resource = 'network' @@ -441,3 +472,7 @@ def test_delete_network(self): myid = 'myid' args = [myid] self._test_delete_resource(resource, cmd, myid, args) + + +class CLITestV20NetworkXML(CLITestV20NetworkJSON): + format = 'xml' diff --git a/tests/unit/test_cli20_nvp_queue.py b/tests/unit/test_cli20_nvp_queue.py index c43f31acb..d84d9df7f 100644 --- a/tests/unit/test_cli20_nvp_queue.py +++ b/tests/unit/test_cli20_nvp_queue.py @@ -22,7 +22,11 @@ from tests.unit import test_cli20 -class CLITestV20NvpQosQueue(test_cli20.CLITestV20Base): +class CLITestV20NvpQosQueueJSON(test_cli20.CLITestV20Base): + def setUp(self): + super(CLITestV20NvpQosQueueJSON, self).setUp( + plurals={'qos_queues': 'qos_queue'}) + def test_create_qos_queue(self): """Create a qos queue.""" resource = 'qos_queue' @@ -78,3 +82,7 @@ def test_delete_qos_queue(self): myid = 'myid' args = [myid] self._test_delete_resource(resource, cmd, myid, args) + + +class CLITestV20NvpQosQueueXML(CLITestV20NvpQosQueueJSON): + format = 'xml' diff --git a/tests/unit/test_cli20_nvpnetworkgateway.py b/tests/unit/test_cli20_nvpnetworkgateway.py index bf95a6d6a..c460adadd 100644 --- a/tests/unit/test_cli20_nvpnetworkgateway.py +++ b/tests/unit/test_cli20_nvpnetworkgateway.py @@ -22,10 +22,15 @@ from tests.unit.test_cli20 import MyApp -class CLITestV20NetworkGateway(CLITestV20Base): +class CLITestV20NetworkGatewayJSON(CLITestV20Base): resource = "network_gateway" + def setUp(self): + super(CLITestV20NetworkGatewayJSON, self).setUp( + plurals={'devices': 'device', + 'network_gateways': 'network_gateway'}) + def test_create_gateway(self): cmd = nvpnetworkgateway.CreateNetworkGateway(MyApp(sys.stdout), None) name = 'gw-test' @@ -105,3 +110,7 @@ def test_disconnect_network_from_gateway(self): {'network_id': 'net_id', 'segmentation_type': 'edi', 'segmentation_id': '7'}) + + +class CLITestV20NetworkGatewayXML(CLITestV20NetworkGatewayJSON): + format = 'xml' diff --git a/tests/unit/test_cli20_port.py b/tests/unit/test_cli20_port.py index 84e11bff0..ed53d5ab1 100644 --- a/tests/unit/test_cli20_port.py +++ b/tests/unit/test_cli20_port.py @@ -31,7 +31,9 @@ from tests.unit.test_cli20 import MyApp -class CLITestV20Port(CLITestV20Base): +class CLITestV20PortJSON(CLITestV20Base): + def setUp(self): + super(CLITestV20PortJSON, self).setUp(plurals={'tags': 'tag'}) def test_create_port(self): """Create port: netid.""" @@ -304,3 +306,7 @@ def test_delete_port(self): myid = 'myid' args = [myid] self._test_delete_resource(resource, cmd, myid, args) + + +class CLITestV20PortXML(CLITestV20PortJSON): + format = 'xml' diff --git a/tests/unit/test_cli20_router.py b/tests/unit/test_cli20_router.py index 262ce64e2..6b1e8c879 100644 --- a/tests/unit/test_cli20_router.py +++ b/tests/unit/test_cli20_router.py @@ -31,7 +31,7 @@ from tests.unit.test_cli20 import MyApp -class CLITestV20Router(CLITestV20Base): +class CLITestV20RouterJSON(CLITestV20Base): def test_create_router(self): """Create router: router1.""" resource = 'router' @@ -170,3 +170,7 @@ def test_remove_gateway(self): self._test_update_resource(resource, cmd, 'externalid', args, {"external_gateway_info": {}} ) + + +class CLITestV20RouterXML(CLITestV20RouterJSON): + format = 'xml' diff --git a/tests/unit/test_cli20_securitygroup.py b/tests/unit/test_cli20_securitygroup.py index 3058f4dba..bb8f6a408 100644 --- a/tests/unit/test_cli20_securitygroup.py +++ b/tests/unit/test_cli20_securitygroup.py @@ -24,7 +24,7 @@ from tests.unit import test_cli20 -class CLITestV20SecurityGroups(test_cli20.CLITestV20Base): +class CLITestV20SecurityGroupsJSON(test_cli20.CLITestV20Base): def test_create_security_group(self): """Create security group: webservers.""" resource = 'security_group' @@ -304,3 +304,7 @@ def test_list_security_group_rules_extend_with_fields_no_id(self): args = '-F id -F security_group -F remote_group'.split() self._test_list_security_group_rules_extend(args=args, query_field=True) + + +class CLITestV20SecurityGroupsXML(CLITestV20SecurityGroupsJSON): + format = 'xml' diff --git a/tests/unit/test_cli20_subnet.py b/tests/unit/test_cli20_subnet.py index 82b619a6f..8acb9f069 100644 --- a/tests/unit/test_cli20_subnet.py +++ b/tests/unit/test_cli20_subnet.py @@ -26,7 +26,9 @@ from tests.unit.test_cli20 import MyApp -class CLITestV20Subnet(CLITestV20Base): +class CLITestV20SubnetJSON(CLITestV20Base): + def setUp(self): + super(CLITestV20SubnetJSON, self).setUp(plurals={'tags': 'tag'}) def test_create_subnet(self): """Create subnet: --gateway gateway netid cidr.""" @@ -399,3 +401,7 @@ def test_delete_subnet(self): myid = 'myid' args = [myid] self._test_delete_resource(resource, cmd, myid, args) + + +class CLITestV20SubnetXML(CLITestV20SubnetJSON): + format = 'xml' diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py index 8491d0217..3d8d0f050 100644 --- a/tests/unit/test_shell.py +++ b/tests/unit/test_shell.py @@ -21,6 +21,7 @@ import sys import fixtures +import mox import testtools from testtools import matchers @@ -58,6 +59,7 @@ def _tolerant_shell(self, cmd): # Patch os.environ to avoid required auth info. def setUp(self): super(ShellTest, self).setUp() + self.mox = mox.Mox() for var in self.FAKE_ENV: self.useFixture( fixtures.EnvironmentVariable( @@ -127,3 +129,17 @@ def test_build_option_parser(self): quant_shell = openstack_shell.QuantumShell('2.0') result = quant_shell.build_option_parser('descr', '2.0') self.assertEqual(True, isinstance(result, argparse.ArgumentParser)) + + def test_main_with_unicode(self): + self.mox.StubOutClassWithMocks(openstack_shell, 'QuantumShell') + qshell_mock = openstack_shell.QuantumShell('2.0') + #self.mox.StubOutWithMock(qshell_mock, 'run') + unicode_text = u'\u7f51\u7edc' + argv = ['net-list', unicode_text, unicode_text.encode('utf-8')] + qshell_mock.run([u'net-list', unicode_text, + unicode_text]).AndReturn(0) + self.mox.ReplayAll() + ret = openstack_shell.main(argv=argv) + self.mox.VerifyAll() + self.mox.UnsetStubs() + self.assertEqual(ret, 0) From ad7caef9c302575427dfc46cea1c5b84fef8e061 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Fri, 29 Mar 2013 03:15:55 +0900 Subject: [PATCH 106/135] Change variable name of admin_state_down to admin_state It is confusing that admin_state_down=False means admin-state is DOWN. It is easy to understand if the variable name matches its value. Also removes 'default=xxx' from options with store_true/false action since store_true/false sets the default value implicitly. Fixes bug 1161853 Change-Id: I3146dd8974990c94e5f9b3b5bf6a8b28a245d64e --- quantumclient/quantum/v2_0/lb/healthmonitor.py | 4 ++-- quantumclient/quantum/v2_0/lb/member.py | 4 ++-- quantumclient/quantum/v2_0/lb/pool.py | 4 ++-- quantumclient/quantum/v2_0/lb/vip.py | 4 ++-- quantumclient/quantum/v2_0/network.py | 9 ++++----- quantumclient/quantum/v2_0/port.py | 8 ++++---- quantumclient/quantum/v2_0/router.py | 6 +++--- quantumclient/quantum/v2_0/subnet.py | 2 +- 8 files changed, 20 insertions(+), 21 deletions(-) diff --git a/quantumclient/quantum/v2_0/lb/healthmonitor.py b/quantumclient/quantum/v2_0/lb/healthmonitor.py index cc4695d6f..a21d8033a 100644 --- a/quantumclient/quantum/v2_0/lb/healthmonitor.py +++ b/quantumclient/quantum/v2_0/lb/healthmonitor.py @@ -49,7 +49,7 @@ class CreateHealthMonitor(quantumv20.CreateCommand): def add_known_arguments(self, parser): parser.add_argument( '--admin-state-down', - default=True, action='store_false', + dest='admin_state', action='store_false', help='set admin state up to false') parser.add_argument( '--expected-codes', @@ -92,7 +92,7 @@ def add_known_arguments(self, parser): def args2body(self, parsed_args): body = { self.resource: { - 'admin_state_up': parsed_args.admin_state_down, + 'admin_state_up': parsed_args.admin_state, 'delay': parsed_args.delay, 'max_retries': parsed_args.max_retries, 'timeout': parsed_args.timeout, diff --git a/quantumclient/quantum/v2_0/lb/member.py b/quantumclient/quantum/v2_0/lb/member.py index 5a2c71b63..af90192d9 100644 --- a/quantumclient/quantum/v2_0/lb/member.py +++ b/quantumclient/quantum/v2_0/lb/member.py @@ -54,7 +54,7 @@ def add_known_arguments(self, parser): help='Pool id or name this vip belongs to') parser.add_argument( '--admin-state-down', - default=True, action='store_false', + dest='admin_state', action='store_false', help='set admin state up to false') parser.add_argument( '--weight', @@ -75,7 +75,7 @@ def args2body(self, parsed_args): body = { self.resource: { 'pool_id': _pool_id, - 'admin_state_up': parsed_args.admin_state_down, + 'admin_state_up': parsed_args.admin_state, }, } quantumv20.update_dict( diff --git a/quantumclient/quantum/v2_0/lb/pool.py b/quantumclient/quantum/v2_0/lb/pool.py index 049ac6749..50667199f 100644 --- a/quantumclient/quantum/v2_0/lb/pool.py +++ b/quantumclient/quantum/v2_0/lb/pool.py @@ -50,7 +50,7 @@ class CreatePool(quantumv20.CreateCommand): def add_known_arguments(self, parser): parser.add_argument( '--admin-state-down', - default=True, action='store_false', + dest='admin_state', action='store_false', help='set admin state up to false') parser.add_argument( '--description', @@ -76,7 +76,7 @@ def add_known_arguments(self, parser): def args2body(self, parsed_args): body = { self.resource: { - 'admin_state_up': parsed_args.admin_state_down, + 'admin_state_up': parsed_args.admin_state, }, } quantumv20.update_dict(parsed_args, body[self.resource], diff --git a/quantumclient/quantum/v2_0/lb/vip.py b/quantumclient/quantum/v2_0/lb/vip.py index c41fafa57..721c2801d 100644 --- a/quantumclient/quantum/v2_0/lb/vip.py +++ b/quantumclient/quantum/v2_0/lb/vip.py @@ -56,7 +56,7 @@ def add_known_arguments(self, parser): help='IP address of the vip') parser.add_argument( '--admin-state-down', - default=True, action='store_false', + dest='admin_state', action='store_false', help='set admin state up to false') parser.add_argument( '--connection-limit', @@ -89,7 +89,7 @@ def args2body(self, parsed_args): body = { self.resource: { 'pool_id': _pool_id, - 'admin_state_up': parsed_args.admin_state_down, + 'admin_state_up': parsed_args.admin_state, }, } quantumv20.update_dict(parsed_args, body[self.resource], diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 7a9a8ce4b..6dac21a3f 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -94,16 +94,15 @@ class CreateNetwork(CreateCommand): def add_known_arguments(self, parser): parser.add_argument( '--admin-state-down', - default=True, action='store_false', + dest='admin_state', action='store_false', help='Set Admin State Up to false') parser.add_argument( '--admin_state_down', - action='store_false', + dest='admin_state', action='store_false', help=argparse.SUPPRESS) parser.add_argument( '--shared', action='store_true', - default=argparse.SUPPRESS, help='Set the network as shared') parser.add_argument( 'name', metavar='NAME', @@ -112,10 +111,10 @@ def add_known_arguments(self, parser): def args2body(self, parsed_args): body = {'network': { 'name': parsed_args.name, - 'admin_state_up': parsed_args.admin_state_down}, } + 'admin_state_up': parsed_args.admin_state}, } if parsed_args.tenant_id: body['network'].update({'tenant_id': parsed_args.tenant_id}) - if hasattr(parsed_args, 'shared'): + if parsed_args.shared: body['network'].update({'shared': parsed_args.shared}) return body diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index e9465113e..38d625f9d 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -90,11 +90,11 @@ def add_known_arguments(self, parser): help='name of this port') parser.add_argument( '--admin-state-down', - default=True, action='store_false', + dest='admin_state', action='store_false', help='set admin state up to false') parser.add_argument( '--admin_state_down', - action='store_false', + dest='admin_state', action='store_false', help=argparse.SUPPRESS) parser.add_argument( '--mac-address', @@ -130,7 +130,7 @@ def add_known_arguments(self, parser): def args2body(self, parsed_args): _network_id = quantumv20.find_resourceid_by_name_or_id( self.get_client(), 'network', parsed_args.network_id) - body = {'port': {'admin_state_up': parsed_args.admin_state_down, + body = {'port': {'admin_state_up': parsed_args.admin_state, 'network_id': _network_id, }, } if parsed_args.mac_address: body['port'].update({'mac_address': parsed_args.mac_address}) @@ -179,7 +179,7 @@ class UpdatePort(UpdateCommand): def add_known_arguments(self, parser): parser.add_argument( '--no-security-groups', - default=False, action='store_true', + action='store_true', help='remove security groups from port') def args2body(self, parsed_args): diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index 86eaa7229..3f8ed6ccc 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -63,11 +63,11 @@ class CreateRouter(CreateCommand): def add_known_arguments(self, parser): parser.add_argument( '--admin-state-down', - default=True, action='store_false', + dest='admin_state', action='store_false', help='Set Admin State Up to false') parser.add_argument( '--admin_state_down', - action='store_false', + dest='admin_state', action='store_false', help=argparse.SUPPRESS) parser.add_argument( 'name', metavar='NAME', @@ -76,7 +76,7 @@ def add_known_arguments(self, parser): def args2body(self, parsed_args): body = {'router': { 'name': parsed_args.name, - 'admin_state_up': parsed_args.admin_state_down, }, } + 'admin_state_up': parsed_args.admin_state, }, } if parsed_args.tenant_id: body['router'].update({'tenant_id': parsed_args.tenant_id}) return body diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index 4442b721f..a711f057e 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -97,7 +97,7 @@ def add_known_arguments(self, parser): help='gateway ip of this subnet') parser.add_argument( '--no-gateway', - default=False, action='store_true', + action='store_true', help='No distribution of gateway') parser.add_argument( '--allocation-pool', metavar='start=IP_ADDR,end=IP_ADDR', From e901c5fe8071914a867bbb981da5773ff2a1db6e Mon Sep 17 00:00:00 2001 From: He Jie Xu Date: Mon, 8 Apr 2013 14:14:16 +0800 Subject: [PATCH 107/135] Add custom TableFormater for keep same empty list behavior as prettytable 0.6 Fix bug 1165962 And upgrade cliff to 1.3.2, because cliff 1.3.1 depends on prettytable<0.7 Change-Id: I6b38a2d77f6b9bf88d6d64f6c02b98b6c21fda3a --- quantumclient/quantum/v2_0/__init__.py | 19 +++++++++++++++++++ tools/pip-requires | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 78590b538..534935d0b 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -19,6 +19,7 @@ import logging import re +from cliff.formatters import table from cliff import lister from cliff import show @@ -252,12 +253,30 @@ def update_dict(obj, dict, attributes): dict[attribute] = getattr(obj, attribute) +class TableFormater(table.TableFormatter): + """This class is used to keep consistency with prettytable 0.6. + + https://bugs.launchpad.net/python-quantumclient/+bug/1165962 + """ + def emit_list(self, column_names, data, stdout, parsed_args): + if column_names: + super(TableFormater, self).emit_list(column_names, data, stdout, + parsed_args) + else: + stdout.write('\n') + + class QuantumCommand(command.OpenStackCommand): api = 'network' log = logging.getLogger(__name__ + '.QuantumCommand') values_specs = [] json_indent = None + def __init__(self, app, app_args): + super(QuantumCommand, self).__init__(app, app_args) + if hasattr(self, 'formatters'): + self.formatters['table'] = TableFormater() + def get_client(self): return self.app.client_manager.quantum diff --git a/tools/pip-requires b/tools/pip-requires index c5f007081..eed424499 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,5 +1,5 @@ argparse -cliff>=1.3.1 +cliff>=1.3.2 httplib2 iso8601 prettytable>=0.6,<0.8 From 3c193c9387308af6153c0e6e3d751b6672667c82 Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Fri, 5 Apr 2013 23:20:45 +0000 Subject: [PATCH 108/135] Enable automatic validation of many HACKING rules. * Add hacking to the tox build - a set of flake8 plugins that perform automatic validation of many HACKING.rst rules. * This patch configures hacking in the tox build and performs the mechanical cleanup required to allow the checks to pass. * See https://pypi.python.org/pypi/hacking Change-Id: Ib41313b5aae991e6ffef2a89dd69e83985bdc36d --- quantumclient/client.py | 11 ++++++----- quantumclient/common/exceptions.py | 7 ++++--- quantumclient/common/serializer.py | 6 +++--- quantumclient/common/utils.py | 5 ++++- quantumclient/quantum/v2_0/__init__.py | 2 +- quantumclient/quantum/v2_0/lb/healthmonitor.py | 2 +- quantumclient/quantum/v2_0/lb/member.py | 2 +- quantumclient/quantum/v2_0/lb/pool.py | 2 +- quantumclient/quantum/v2_0/lb/vip.py | 2 +- quantumclient/quantum/v2_0/network.py | 4 ++-- quantumclient/quantum/v2_0/port.py | 2 +- quantumclient/v2_0/client.py | 9 +++++---- tests/unit/lb/test_cli20_healthmonitor.py | 16 ++++++++-------- tests/unit/lb/test_cli20_member.py | 16 ++++++++-------- tests/unit/lb/test_cli20_pool.py | 18 +++++++++--------- tests/unit/lb/test_cli20_vip.py | 18 +++++++++--------- tests/unit/test_auth.py | 2 +- tests/unit/test_cli20.py | 2 +- tests/unit/test_cli20_floatingips.py | 8 ++++---- tests/unit/test_cli20_network.py | 2 +- tests/unit/test_cli20_port.py | 6 +++--- tests/unit/test_cli20_router.py | 10 +++++----- tests/unit/test_cli20_securitygroup.py | 2 +- tests/unit/test_cli20_subnet.py | 6 +++--- tests/unit/test_name_or_id.py | 4 ++-- tools/test-requires | 1 + tox.ini | 6 ++++-- 27 files changed, 90 insertions(+), 81 deletions(-) diff --git a/quantumclient/client.py b/quantumclient/client.py index 5726000fe..2ab94a7e1 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -47,14 +47,14 @@ def __init__(self, resource_dict): self.catalog = resource_dict def get_token(self): - """Fetch token details fron service catalog""" + """Fetch token details fron service catalog.""" token = {'id': self.catalog['access']['token']['id'], 'expires': self.catalog['access']['token']['expires'], } try: token['user_id'] = self.catalog['access']['user']['id'] token['tenant_id'] = ( self.catalog['access']['token']['tenant']['id']) - except: + except Exception: # just leave the tenant and user out if it doesn't exist pass return token @@ -63,7 +63,8 @@ def url_for(self, attr=None, filter_value=None, service_type='network', endpoint_type='adminURL'): """Fetch the admin URL from the Quantum service for a particular endpoint attribute. If none given, return - the first. See tests for sample service catalog.""" + the first. See tests for sample service catalog. + """ catalog = self.catalog['access'].get('serviceCatalog', []) matching_endpoints = [] @@ -85,7 +86,7 @@ def url_for(self, attr=None, filter_value=None, class HTTPClient(httplib2.Http): - """Handles the REST calls and responses, include authn""" + """Handles the REST calls and responses, include authn.""" USER_AGENT = 'python-quantumclient' @@ -158,7 +159,7 @@ def do_request(self, url, method, **kwargs): return resp, body def _extract_service_catalog(self, body): - """ Set the client's service catalog from the response data. """ + """Set the client's service catalog from the response data.""" self.service_catalog = ServiceCatalog(body) try: sc = self.service_catalog.get_token() diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py index 1df133d7b..513a016c6 100644 --- a/quantumclient/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -119,7 +119,7 @@ def __str__(self): class QuantumCLIError(QuantumClientException): - """ Exception raised when command line parsing fails """ + """Exception raised when command line parsing fails.""" pass @@ -128,7 +128,7 @@ class ConnectionFailed(QuantumClientException): class BadInputError(Exception): - """Error resulting from a client sending bad input to a server""" + """Error resulting from a client sending bad input to a server.""" pass @@ -151,7 +151,8 @@ class InvalidContentType(Invalid): class UnsupportedVersion(Exception): """Indicates that the user is trying to use an unsupported - version of the API""" + version of the API + """ pass diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py index e5cd76405..a9d8db0a5 100644 --- a/quantumclient/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -45,7 +45,7 @@ def default(self, data): class DictSerializer(ActionDispatcher): - """Default request body serialization""" + """Default request body serialization.""" def serialize(self, data, action='default'): return self.dispatch(data, action=action) @@ -55,7 +55,7 @@ def default(self, data): class JSONDictSerializer(DictSerializer): - """Default JSON request body serialization""" + """Default JSON request body serialization.""" def default(self, data): def sanitizer(obj): @@ -207,7 +207,7 @@ def _create_link_nodes(self, xml_doc, links): class TextDeserializer(ActionDispatcher): - """Default request body deserialization""" + """Default request body deserialization.""" def deserialize(self, datastring, action='default'): return self.dispatch(datastring, action=action) diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py index bd64dbe3a..b9fd0a869 100644 --- a/quantumclient/common/utils.py +++ b/quantumclient/common/utils.py @@ -141,7 +141,10 @@ def str2bool(strbool): def str2dict(strdict): - '''@param strdict: key1=value1,key2=value2''' + ''' + + :param strdict: key1=value1,key2=value2 + ''' _info = {} for kv_str in strdict.split(","): k, v = kv_str.split("=", 1) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 534935d0b..11bdc2e11 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -352,7 +352,7 @@ def get_data(self, parsed_args): # {u'network': {u'id': u'e9424a76-6db4-4c93-97b6-ec311cd51f19'}} info = self.resource in data and data[self.resource] or None if info: - print >>self.app.stdout, _('Created a new %s:' % self.resource) + print >>self.app.stdout, _('Created a new %s:') % self.resource else: info = {'': ''} return zip(*sorted(info.iteritems())) diff --git a/quantumclient/quantum/v2_0/lb/healthmonitor.py b/quantumclient/quantum/v2_0/lb/healthmonitor.py index cc4695d6f..23998e505 100644 --- a/quantumclient/quantum/v2_0/lb/healthmonitor.py +++ b/quantumclient/quantum/v2_0/lb/healthmonitor.py @@ -41,7 +41,7 @@ class ShowHealthMonitor(quantumv20.ShowCommand): class CreateHealthMonitor(quantumv20.CreateCommand): - """Create a healthmonitor""" + """Create a healthmonitor.""" resource = 'health_monitor' log = logging.getLogger(__name__ + '.CreateHealthMonitor') diff --git a/quantumclient/quantum/v2_0/lb/member.py b/quantumclient/quantum/v2_0/lb/member.py index 5a2c71b63..7be623d0d 100644 --- a/quantumclient/quantum/v2_0/lb/member.py +++ b/quantumclient/quantum/v2_0/lb/member.py @@ -43,7 +43,7 @@ class ShowMember(quantumv20.ShowCommand): class CreateMember(quantumv20.CreateCommand): - """Create a member""" + """Create a member.""" resource = 'member' log = logging.getLogger(__name__ + '.CreateMember') diff --git a/quantumclient/quantum/v2_0/lb/pool.py b/quantumclient/quantum/v2_0/lb/pool.py index 049ac6749..5cbb30a27 100644 --- a/quantumclient/quantum/v2_0/lb/pool.py +++ b/quantumclient/quantum/v2_0/lb/pool.py @@ -42,7 +42,7 @@ class ShowPool(quantumv20.ShowCommand): class CreatePool(quantumv20.CreateCommand): - """Create a pool""" + """Create a pool.""" resource = 'pool' log = logging.getLogger(__name__ + '.CreatePool') diff --git a/quantumclient/quantum/v2_0/lb/vip.py b/quantumclient/quantum/v2_0/lb/vip.py index c41fafa57..2fcfebf95 100644 --- a/quantumclient/quantum/v2_0/lb/vip.py +++ b/quantumclient/quantum/v2_0/lb/vip.py @@ -42,7 +42,7 @@ class ShowVip(quantumv20.ShowCommand): class CreateVip(quantumv20.CreateCommand): - """Create a vip""" + """Create a vip.""" resource = 'vip' log = logging.getLogger(__name__ + '.CreateVip') diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 7a9a8ce4b..ed8106840 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -44,7 +44,7 @@ class ListNetwork(ListCommand): sorting_support = True def extend_list(self, data, parsed_args): - """Add subnet information to a network list""" + """Add subnet information to a network list.""" quantum_client = self.get_client() search_opts = {'fields': ['id', 'cidr']} if self.pagination_support: @@ -65,7 +65,7 @@ def extend_list(self, data, parsed_args): class ListExternalNetwork(ListNetwork): - """List external networks that belong to a given tenant""" + """List external networks that belong to a given tenant.""" log = logging.getLogger(__name__ + '.ListExternalNetwork') pagination_support = True diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index e9465113e..0807714c9 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -46,7 +46,7 @@ class ListPort(ListCommand): class ListRouterPort(ListCommand): - """List ports that belong to a given tenant, with specified router""" + """List ports that belong to a given tenant, with specified router.""" resource = 'port' log = logging.getLogger(__name__ + '.ListRouterPort') diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index a0d17d8d5..d3191f5cb 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -33,7 +33,7 @@ def exception_handler_v20(status_code, error_content): - """ Exception handler for API v2.0 client + """Exception handler for API v2.0 client This routine generates the appropriate Quantum exception according to the contents of the @@ -213,7 +213,8 @@ def get_attr_metadata(self): @APIParamsCall def get_quotas_tenant(self, **_params): """Fetch tenant info in server's context for - following quota operation.""" + following quota operation. + """ return self.get(self.quota_path % 'tenant', params=_params) @APIParamsCall @@ -871,7 +872,7 @@ def remove_router_from_l3_agent(self, l3_agent, router_id): l3_agent, router_id)) def __init__(self, **kwargs): - """ Initialize a new client for the Quantum v2.0 API. """ + """Initialize a new client for the Quantum v2.0 API.""" super(Client, self).__init__() self.httpclient = HTTPClient(**kwargs) self.version = '2.0' @@ -886,7 +887,7 @@ def _handle_fault_response(self, status_code, response_body): # Add deserialized error message to exception arguments try: des_error_body = self.deserialize(response_body, status_code) - except: + except Exception: # If unable to deserialized body it is probably not a # Quantum error des_error_body = {'message': response_body} diff --git a/tests/unit/lb/test_cli20_healthmonitor.py b/tests/unit/lb/test_cli20_healthmonitor.py index 37f93e17d..cdff23188 100644 --- a/tests/unit/lb/test_cli20_healthmonitor.py +++ b/tests/unit/lb/test_cli20_healthmonitor.py @@ -27,7 +27,7 @@ class CLITestV20LbHealthmonitorJSON(test_cli20.CLITestV20Base): def test_create_healthmonitor_with_mandatory_params(self): - """lb-healthmonitor-create with mandatory params only""" + """lb-healthmonitor-create with mandatory params only.""" resource = 'health_monitor' cmd = healthmonitor.CreateHealthMonitor(test_cli20.MyApp(sys.stdout), None) @@ -52,7 +52,7 @@ def test_create_healthmonitor_with_mandatory_params(self): position_names, position_values) def test_create_healthmonitor_with_all_params(self): - """lb-healthmonitor-create with all params set""" + """lb-healthmonitor-create with all params set.""" resource = 'health_monitor' cmd = healthmonitor.CreateHealthMonitor(test_cli20.MyApp(sys.stdout), None) @@ -87,14 +87,14 @@ def test_create_healthmonitor_with_all_params(self): position_names, position_values) def test_list_healthmonitors(self): - """lb-healthmonitor-list""" + """lb-healthmonitor-list.""" resources = "health_monitors" cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_healthmonitors_pagination(self): - """lb-healthmonitor-list""" + """lb-healthmonitor-list.""" resources = "health_monitors" cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), None) @@ -112,14 +112,14 @@ def test_list_healthmonitors_sort(self): sort_dir=["asc", "desc"]) def test_list_healthmonitors_limit(self): - """lb-healthmonitor-list -P""" + """lb-healthmonitor-list -P.""" resources = "health_monitors" cmd = healthmonitor.ListHealthMonitor(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) def test_show_healthmonitor_id(self): - """lb-healthmonitor-show test_id""" + """lb-healthmonitor-show test_id.""" resource = 'health_monitor' cmd = healthmonitor.ShowHealthMonitor(test_cli20.MyApp(sys.stdout), None) @@ -127,7 +127,7 @@ def test_show_healthmonitor_id(self): self._test_show_resource(resource, cmd, self.test_id, args, ['id']) def test_show_healthmonitor_id_name(self): - """lb-healthmonitor-show""" + """lb-healthmonitor-show.""" resource = 'health_monitor' cmd = healthmonitor.ShowHealthMonitor(test_cli20.MyApp(sys.stdout), None) @@ -145,7 +145,7 @@ def test_update_health_monitor(self): {'timeout': '5', }) def test_delete_healthmonitor(self): - """lb-healthmonitor-delete my-id""" + """lb-healthmonitor-delete my-id.""" resource = 'health_monitor' cmd = healthmonitor.DeleteHealthMonitor(test_cli20.MyApp(sys.stdout), None) diff --git a/tests/unit/lb/test_cli20_member.py b/tests/unit/lb/test_cli20_member.py index dc53ff77d..9b75367bd 100644 --- a/tests/unit/lb/test_cli20_member.py +++ b/tests/unit/lb/test_cli20_member.py @@ -28,7 +28,7 @@ def setUp(self): super(CLITestV20LbMemberJSON, self).setUp(plurals={'tags': 'tag'}) def test_create_member(self): - """lb-member-create with mandatory params only""" + """lb-member-create with mandatory params only.""" resource = 'member' cmd = member.CreateMember(test_cli20.MyApp(sys.stdout), None) address = '10.0.0.1' @@ -46,7 +46,7 @@ def test_create_member(self): admin_state_up=None) def test_create_member_all_params(self): - """lb-member-create with all available params""" + """lb-member-create with all available params.""" resource = 'member' cmd = member.CreateMember(test_cli20.MyApp(sys.stdout), None) address = '10.0.0.1' @@ -70,13 +70,13 @@ def test_create_member_all_params(self): admin_state_up=None) def test_list_members(self): - """lb-member-list""" + """lb-member-list.""" resources = "members" cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_members_pagination(self): - """lb-member-list""" + """lb-member-list.""" resources = "members" cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) self._test_list_resources_with_pagination(resources, cmd) @@ -92,20 +92,20 @@ def test_list_members_sort(self): sort_dir=["asc", "desc"]) def test_list_members_limit(self): - """lb-member-list -P""" + """lb-member-list -P.""" resources = "members" cmd = member.ListMember(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) def test_show_member_id(self): - """lb-member-show test_id""" + """lb-member-show test_id.""" resource = 'member' cmd = member.ShowMember(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', self.test_id] self._test_show_resource(resource, cmd, self.test_id, args, ['id']) def test_show_member_id_name(self): - """lb-member-show""" + """lb-member-show.""" resource = 'member' cmd = member.ShowMember(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', '--fields', 'name', self.test_id] @@ -122,7 +122,7 @@ def test_update_member(self): {'name': 'myname', 'tags': ['a', 'b'], }) def test_delete_member(self): - """lb-member-delete my-id""" + """lb-member-delete my-id.""" resource = 'member' cmd = member.DeleteMember(test_cli20.MyApp(sys.stdout), None) my_id = 'my-id' diff --git a/tests/unit/lb/test_cli20_pool.py b/tests/unit/lb/test_cli20_pool.py index ce2264e29..3683b9d52 100644 --- a/tests/unit/lb/test_cli20_pool.py +++ b/tests/unit/lb/test_cli20_pool.py @@ -28,7 +28,7 @@ class CLITestV20LbPoolJSON(test_cli20.CLITestV20Base): def test_create_pool_with_mandatory_params(self): - """lb-pool-create with mandatory params only""" + """lb-pool-create with mandatory params only.""" resource = 'pool' cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None) name = 'my-name' @@ -50,7 +50,7 @@ def test_create_pool_with_mandatory_params(self): position_names, position_values) def test_create_pool_with_all_params(self): - """lb-pool-create with all params set""" + """lb-pool-create with all params set.""" resource = 'pool' cmd = pool.CreatePool(test_cli20.MyApp(sys.stdout), None) name = 'my-name' @@ -75,13 +75,13 @@ def test_create_pool_with_all_params(self): position_names, position_values) def test_list_pools(self): - """lb-pool-list""" + """lb-pool-list.""" resources = "pools" cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_pools_pagination(self): - """lb-pool-list""" + """lb-pool-list.""" resources = "pools" cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) self._test_list_resources_with_pagination(resources, cmd) @@ -97,20 +97,20 @@ def test_list_pools_sort(self): sort_dir=["asc", "desc"]) def test_list_pools_limit(self): - """lb-pool-list -P""" + """lb-pool-list -P.""" resources = "pools" cmd = pool.ListPool(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) def test_show_pool_id(self): - """lb-pool-show test_id""" + """lb-pool-show test_id.""" resource = 'pool' cmd = pool.ShowPool(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', self.test_id] self._test_show_resource(resource, cmd, self.test_id, args, ['id']) def test_show_pool_id_name(self): - """lb-pool-show""" + """lb-pool-show.""" resource = 'pool' cmd = pool.ShowPool(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', '--fields', 'name', self.test_id] @@ -126,7 +126,7 @@ def test_update_pool(self): {'name': 'newname', }) def test_delete_pool(self): - """lb-pool-delete my-id""" + """lb-pool-delete my-id.""" resource = 'pool' cmd = pool.DeletePool(test_cli20.MyApp(sys.stdout), None) my_id = 'my-id' @@ -134,7 +134,7 @@ def test_delete_pool(self): self._test_delete_resource(resource, cmd, my_id, args) def test_retrieve_pool_stats(self): - """lb-pool-stats test_id""" + """lb-pool-stats test_id.""" resource = 'pool' cmd = pool.RetrievePoolStats(test_cli20.MyApp(sys.stdout), None) my_id = self.test_id diff --git a/tests/unit/lb/test_cli20_vip.py b/tests/unit/lb/test_cli20_vip.py index af9dfa190..4358cfbec 100644 --- a/tests/unit/lb/test_cli20_vip.py +++ b/tests/unit/lb/test_cli20_vip.py @@ -28,7 +28,7 @@ def setUp(self): super(CLITestV20LbVipJSON, self).setUp(plurals={'tags': 'tag'}) def test_create_vip_with_mandatory_params(self): - """lb-vip-create with all mandatory params""" + """lb-vip-create with all mandatory params.""" resource = 'vip' cmd = vip.CreateVip(test_cli20.MyApp(sys.stdout), None) pool_id = 'my-pool-id' @@ -53,7 +53,7 @@ def test_create_vip_with_mandatory_params(self): admin_state_up=True) def test_create_vip_with_all_params(self): - """lb-vip-create with all params""" + """lb-vip-create with all params.""" resource = 'vip' cmd = vip.CreateVip(test_cli20.MyApp(sys.stdout), None) pool_id = 'my-pool-id' @@ -89,7 +89,7 @@ def test_create_vip_with_all_params(self): position_names, position_values) def test_create_vip_with_session_persistence_params(self): - """lb-vip-create with mandatory and session-persistence params""" + """lb-vip-create with mandatory and session-persistence params.""" resource = 'vip' cmd = vip.CreateVip(test_cli20.MyApp(sys.stdout), None) pool_id = 'my-pool-id' @@ -123,13 +123,13 @@ def test_create_vip_with_session_persistence_params(self): admin_state_up=True, extra_body=extra_body) def test_list_vips(self): - """lb-vip-list""" + """lb-vip-list.""" resources = "vips" cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_vips_pagination(self): - """lb-vip-list""" + """lb-vip-list.""" resources = "vips" cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) self._test_list_resources_with_pagination(resources, cmd) @@ -145,20 +145,20 @@ def test_list_vips_sort(self): sort_dir=["asc", "desc"]) def test_list_vips_limit(self): - """lb-vip-list -P""" + """lb-vip-list -P.""" resources = "vips" cmd = vip.ListVip(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) def test_show_vip_id(self): - """lb-vip-show test_id""" + """lb-vip-show test_id.""" resource = 'vip' cmd = vip.ShowVip(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', self.test_id] self._test_show_resource(resource, cmd, self.test_id, args, ['id']) def test_show_vip_id_name(self): - """lb-vip-show""" + """lb-vip-show.""" resource = 'vip' cmd = vip.ShowVip(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', '--fields', 'name', self.test_id] @@ -202,7 +202,7 @@ def test_update_vip_with_session_persistence_and_name(self): self._test_update_resource(resource, cmd, 'myid', args, body) def test_delete_vip(self): - """lb-vip-delete my-id""" + """lb-vip-delete my-id.""" resource = 'vip' cmd = vip.DeleteVip(test_cli20.MyApp(sys.stdout), None) my_id = 'my-id' diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index e8e64b37e..e5e855527 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -67,7 +67,7 @@ class CLITestAuthKeystone(testtools.TestCase): def setUp(self): - """Prepare the test environment""" + """Prepare the test environment.""" super(CLITestAuthKeystone, self).setUp() self.mox = mox.Mox() self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, diff --git a/tests/unit/test_cli20.py b/tests/unit/test_cli20.py index 9f589ec32..9370295df 100644 --- a/tests/unit/test_cli20.py +++ b/tests/unit/test_cli20.py @@ -158,7 +158,7 @@ def _get_attr_metadata(self): constants.EXT_NS: {'prefix': 'http://xxxx.yy.com'}} def setUp(self, plurals={}): - """Prepare the test environment""" + """Prepare the test environment.""" super(CLITestV20Base, self).setUp() Client.EXTED_PLURALS.update(constants.PLURALS) Client.EXTED_PLURALS.update(plurals) diff --git a/tests/unit/test_cli20_floatingips.py b/tests/unit/test_cli20_floatingips.py index 19e7a8d39..f38242e8f 100644 --- a/tests/unit/test_cli20_floatingips.py +++ b/tests/unit/test_cli20_floatingips.py @@ -61,7 +61,7 @@ def test_create_floatingip_and_port(self): position_names, position_values) def test_create_floatingip_and_port_and_address(self): - """Create floatingip: fip1 with a given port and address""" + """Create floatingip: fip1 with a given port and address.""" resource = 'floatingip' cmd = CreateFloatingIP(MyApp(sys.stdout), None) name = 'fip1' @@ -107,7 +107,7 @@ def test_list_floatingips_limit(self): self._test_list_resources(resources, cmd, page_size=1000) def test_delete_floatingip(self): - """Delete floatingip: fip1""" + """Delete floatingip: fip1.""" resource = 'floatingip' cmd = DeleteFloatingIP(MyApp(sys.stdout), None) myid = 'myid' @@ -123,7 +123,7 @@ def test_show_floatingip(self): args, ['id']) def test_disassociate_ip(self): - """Disassociate floating IP: myid""" + """Disassociate floating IP: myid.""" resource = 'floatingip' cmd = DisassociateFloatingIP(MyApp(sys.stdout), None) args = ['myid'] @@ -132,7 +132,7 @@ def test_disassociate_ip(self): ) def test_associate_ip(self): - """Associate floating IP: myid portid""" + """Associate floating IP: myid portid.""" resource = 'floatingip' cmd = AssociateFloatingIP(MyApp(sys.stdout), None) args = ['myid', 'portid'] diff --git a/tests/unit/test_cli20_network.py b/tests/unit/test_cli20_network.py index e5a6f3b43..c0928d3b9 100644 --- a/tests/unit/test_cli20_network.py +++ b/tests/unit/test_cli20_network.py @@ -180,7 +180,7 @@ def test_list_nets_sort_with_dirs_more_than_keys(self): sort_dir=['desc', 'asc']) def test_list_nets_limit(self): - """list nets: -P""" + """list nets: -P.""" cmd = ListNetwork(MyApp(sys.stdout), None) self._test_list_networks(cmd, page_size=1000) diff --git a/tests/unit/test_cli20_port.py b/tests/unit/test_cli20_port.py index ed53d5ab1..83c172b57 100644 --- a/tests/unit/test_cli20_port.py +++ b/tests/unit/test_cli20_port.py @@ -19,13 +19,13 @@ from mox import ContainsKeyValue -from quantumclient import shell from quantumclient.quantum.v2_0.port import CreatePort from quantumclient.quantum.v2_0.port import DeletePort from quantumclient.quantum.v2_0.port import ListPort from quantumclient.quantum.v2_0.port import ListRouterPort from quantumclient.quantum.v2_0.port import ShowPort from quantumclient.quantum.v2_0.port import UpdatePort +from quantumclient import shell from tests.unit import test_cli20 from tests.unit.test_cli20 import CLITestV20Base from tests.unit.test_cli20 import MyApp @@ -105,7 +105,7 @@ def test_create_port_tags(self): tags=['a', 'b']) def test_create_port_secgroup(self): - """Create port: --security-group sg1_id netid""" + """Create port: --security-group sg1_id netid.""" resource = 'port' cmd = CreatePort(MyApp(sys.stdout), None) name = 'myname' @@ -158,7 +158,7 @@ def test_list_ports_sort(self): sort_dir=["asc", "desc"]) def test_list_ports_limit(self): - """list ports: -P""" + """list ports: -P.""" resources = "ports" cmd = ListPort(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) diff --git a/tests/unit/test_cli20_router.py b/tests/unit/test_cli20_router.py index 6b1e8c879..5c545d8dd 100644 --- a/tests/unit/test_cli20_router.py +++ b/tests/unit/test_cli20_router.py @@ -92,7 +92,7 @@ def test_list_routers_sort(self): sort_dir=["asc", "desc"]) def test_list_routers_limit(self): - """list routers: -P""" + """list routers: -P.""" resources = "routers" cmd = ListRouter(MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) @@ -130,7 +130,7 @@ def test_show_router(self): ['id', 'name']) def test_add_interface(self): - """Add interface to router: myid subnetid""" + """Add interface to router: myid subnetid.""" resource = 'router' cmd = AddInterfaceRouter(MyApp(sys.stdout), None) args = ['myid', 'subnetid'] @@ -141,7 +141,7 @@ def test_add_interface(self): ) def test_del_interface(self): - """Delete interface from router: myid subnetid""" + """Delete interface from router: myid subnetid.""" resource = 'router' cmd = RemoveInterfaceRouter(MyApp(sys.stdout), None) args = ['myid', 'subnetid'] @@ -152,7 +152,7 @@ def test_del_interface(self): ) def test_set_gateway(self): - """Set external gateway for router: myid externalid""" + """Set external gateway for router: myid externalid.""" resource = 'router' cmd = SetGatewayRouter(MyApp(sys.stdout), None) args = ['myid', 'externalid'] @@ -163,7 +163,7 @@ def test_set_gateway(self): ) def test_remove_gateway(self): - """Remove external gateway from router: externalid""" + """Remove external gateway from router: externalid.""" resource = 'router' cmd = RemoveGatewayRouter(MyApp(sys.stdout), None) args = ['externalid'] diff --git a/tests/unit/test_cli20_securitygroup.py b/tests/unit/test_cli20_securitygroup.py index bb8f6a408..b1e9e32c6 100644 --- a/tests/unit/test_cli20_securitygroup.py +++ b/tests/unit/test_cli20_securitygroup.py @@ -119,7 +119,7 @@ def test_delete_security_group(self): self._test_delete_resource(resource, cmd, myid, args) def test_create_security_group_rule_full(self): - """Create security group rule""" + """Create security group rule.""" resource = 'security_group_rule' cmd = securitygroup.CreateSecurityGroupRule( test_cli20.MyApp(sys.stdout), None) diff --git a/tests/unit/test_cli20_subnet.py b/tests/unit/test_cli20_subnet.py index 8acb9f069..8ffa1a45b 100644 --- a/tests/unit/test_cli20_subnet.py +++ b/tests/unit/test_cli20_subnet.py @@ -46,7 +46,7 @@ def test_create_subnet(self): position_names, position_values) def test_create_subnet_with_no_gateway(self): - """Create subnet: --no-gateway netid cidr""" + """Create subnet: --no-gateway netid cidr.""" resource = 'subnet' cmd = CreateSubnet(MyApp(sys.stdout), None) name = 'myname' @@ -60,7 +60,7 @@ def test_create_subnet_with_no_gateway(self): position_names, position_values) def test_create_subnet_with_bad_gateway_option(self): - """Create sbunet: --no-gateway netid cidr""" + """Create sbunet: --no-gateway netid cidr.""" resource = 'subnet' cmd = CreateSubnet(MyApp(sys.stdout), None) name = 'myname' @@ -74,7 +74,7 @@ def test_create_subnet_with_bad_gateway_option(self): try: self._test_create_resource(resource, cmd, name, myid, args, position_names, position_values) - except: + except Exception: return self.fail('No exception for bad gateway option') diff --git a/tests/unit/test_name_or_id.py b/tests/unit/test_name_or_id.py index d5fd9fd14..42e4496bf 100644 --- a/tests/unit/test_name_or_id.py +++ b/tests/unit/test_name_or_id.py @@ -23,14 +23,14 @@ from quantumclient.common import exceptions from quantumclient.quantum import v2_0 as quantumv20 -from tests.unit import test_cli20 from quantumclient.v2_0.client import Client +from tests.unit import test_cli20 class CLITestNameorID(testtools.TestCase): def setUp(self): - """Prepare the test environment""" + """Prepare the test environment.""" super(CLITestNameorID, self).setUp() self.mox = mox.Mox() self.endurl = test_cli20.ENDURL diff --git a/tools/test-requires b/tools/test-requires index 1f1de174b..6395af8cd 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -4,6 +4,7 @@ discover distribute>=0.6.24 fixtures>=0.3.12 flake8 +hacking mox python-subunit sphinx>=1.1.2 diff --git a/tox.ini b/tox.ini index ade5655e7..903048d9b 100644 --- a/tox.ini +++ b/tox.ini @@ -24,8 +24,10 @@ downloadcache = ~/cache/pip [flake8] # E125 continuation line does not distinguish itself from next logical line -# H hacking.py - automatic checks of rules in HACKING.rst -ignore = E125,H +# H301 one import per line +# H302 import only modules +# TODO(marun) H404 multi line docstring should start with a summary +ignore = E125,H301,H302,H404 show-source = true builtins = _ exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,tools From b971ef0e3b3a757846667f66b4c96d54bb66d568 Mon Sep 17 00:00:00 2001 From: Roman Podolyaka Date: Fri, 19 Apr 2013 12:33:31 +0300 Subject: [PATCH 109/135] Fix a comment formatting to make pep8 test pass New version of pyflakes does checking of doctests. We have a comment that looks like a doctest, but in fact it's not. This makes pep8 test fail. Fixes bug 1170639. Change-Id: Iee4f1bb8fd8284c6d1bb0111e23a61f434f3f457 --- quantumclient/v2_0/client.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index d3191f5cb..99f4cde64 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -131,13 +131,13 @@ class Client(object): Example:: - >>> from quantumclient.v2_0 import client - >>> quantum = client.Client(username=USER, - password=PASS, - tenant_name=TENANT_NAME, - auth_url=KEYSTONE_URL) + from quantumclient.v2_0 import client + quantum = client.Client(username=USER, + password=PASS, + tenant_name=TENANT_NAME, + auth_url=KEYSTONE_URL) - >>> nets = quantum.list_networks() + nets = quantum.list_networks() ... """ From f42a06e9cc96035a7e8066038fe4641cbc544dab Mon Sep 17 00:00:00 2001 From: Tatyana Leontovich Date: Fri, 1 Mar 2013 17:46:03 +0200 Subject: [PATCH 110/135] Improve unit tests for python-quantumclient Partially fixes bug 1137783. Add tests for: quantumclient.common.utils Partially Fixes: bug #1137783 Change-Id: I6491eb881d3f465d11a649d55cde7caba77ed19b --- tests/unit/test_utils.py | 190 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 tests/unit/test_utils.py diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py new file mode 100644 index 000000000..d7d152000 --- /dev/null +++ b/tests/unit/test_utils.py @@ -0,0 +1,190 @@ +# Copyright (C) 2013 Yahoo! Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import datetime +import sys + +import testtools + +from quantumclient.common import exceptions +from quantumclient.common import utils + + +class TestUtils(testtools.TestCase): + def test_string_to_bool_true(self): + self.assertTrue(utils.str2bool('true')) + + def test_string_to_bool_false(self): + self.assertFalse(utils.str2bool('false')) + + def test_string_to_bool_None(self): + self.assertIsNone(utils.str2bool(None)) + + def test_string_to_dictionary(self): + input_str = 'key1=value1,key2=value2' + expected = {'key1': 'value1', 'key2': 'value2'} + self.assertEqual(expected, utils.str2dict(input_str)) + + def test_get_dict_item_properties(self): + item = {'name': 'test_name', 'id': 'test_id'} + fields = ('name', 'id') + actual = utils.get_item_properties(item=item, fields=fields) + self.assertEqual(('test_name', 'test_id'), actual) + + def test_get_object_item_properties_mixed_case_fields(self): + class Fake(object): + def __init__(self): + self.id = 'test_id' + self.name = 'test_name' + self.test_user = 'test' + + fields = ('name', 'id', 'test user') + mixed_fields = ('test user', 'ID') + item = Fake() + actual = utils.get_item_properties(item, fields, mixed_fields) + self.assertEqual(('test_name', 'test_id', 'test'), actual) + + def test_get_object_item_desired_fields_differ_from_item(self): + class Fake(object): + def __init__(self): + self.id = 'test_id_1' + self.name = 'test_name' + self.test_user = 'test' + + fields = ('name', 'id', 'test user') + item = Fake() + actual = utils.get_item_properties(item, fields) + self.assertNotEqual(('test_name', 'test_id', 'test'), actual) + + def test_get_object_item_desired_fields_is_empty(self): + class Fake(object): + def __init__(self): + self.id = 'test_id_1' + self.name = 'test_name' + self.test_user = 'test' + + fields = [] + item = Fake() + actual = utils.get_item_properties(item, fields) + self.assertEqual((), actual) + + def test_get_object_item_with_formatters(self): + class Fake(object): + def __init__(self): + self.id = 'test_id' + self.name = 'test_name' + self.test_user = 'test' + + class FakeCallable(object): + def __call__(self, *args, **kwargs): + return 'pass' + + fields = ('name', 'id', 'test user', 'is_public') + formatters = {'is_public': FakeCallable()} + item = Fake() + act = utils.get_item_properties(item, fields, formatters=formatters) + self.assertEqual(('test_name', 'test_id', 'test', 'pass'), act) + + +class JSONUtilsTestCase(testtools.TestCase): + def test_dumps(self): + self.assertEqual(utils.dumps({'a': 'b'}), '{"a": "b"}') + + def test_dumps_dict_with_date_value(self): + x = datetime.datetime(1920, 2, 3, 4, 5, 6, 7) + res = utils.dumps({1: 'a', 2: x}) + expected = '{"1": "a", "2": "1920-02-03 04:05:06.000007"}' + self.assertEqual(expected, res) + + def test_dumps_dict_with_spaces(self): + x = datetime.datetime(1920, 2, 3, 4, 5, 6, 7) + res = utils.dumps({1: 'a ', 2: x}) + expected = '{"1": "a ", "2": "1920-02-03 04:05:06.000007"}' + self.assertEqual(expected, res) + + def test_loads(self): + self.assertEqual(utils.loads('{"a": "b"}'), {'a': 'b'}) + + +class ToPrimitiveTestCase(testtools.TestCase): + def test_list(self): + self.assertEqual(utils.to_primitive([1, 2, 3]), [1, 2, 3]) + + def test_empty_list(self): + self.assertEqual(utils.to_primitive([]), []) + + def test_tuple(self): + self.assertEqual(utils.to_primitive((1, 2, 3)), [1, 2, 3]) + + def test_empty_tuple(self): + self.assertEqual(utils.to_primitive(()), []) + + def test_dict(self): + self.assertEqual( + utils.to_primitive(dict(a=1, b=2, c=3)), + dict(a=1, b=2, c=3)) + + def test_empty_dict(self): + self.assertEqual(utils.to_primitive({}), {}) + + def test_datetime(self): + x = datetime.datetime(1920, 2, 3, 4, 5, 6, 7) + self.assertEqual( + utils.to_primitive(x), + '1920-02-03 04:05:06.000007') + + def test_iter(self): + x = xrange(1, 6) + self.assertEqual(utils.to_primitive(x), [1, 2, 3, 4, 5]) + + def test_iteritems(self): + d = {'a': 1, 'b': 2, 'c': 3} + + class IterItemsClass(object): + def iteritems(self): + return d.iteritems() + + x = IterItemsClass() + p = utils.to_primitive(x) + self.assertEqual(p, {'a': 1, 'b': 2, 'c': 3}) + + def test_nasties(self): + def foo(): + pass + x = [datetime, foo, dir] + ret = utils.to_primitive(x) + self.assertEqual(len(ret), 3) + + def test_to_primitive_dict_with_date_value(self): + x = datetime.datetime(1920, 2, 3, 4, 5, 6, 7) + res = utils.to_primitive({'a': x}) + self.assertEqual({'a': '1920-02-03 04:05:06.000007'}, res) + + +class ImportClassTestCase(testtools.TestCase): + def test_import_class(self): + dt = utils.import_class('datetime.datetime') + self.assertTrue(sys.modules['datetime'].datetime is dt) + + def test_import_bad_class(self): + self.assertRaises( + ImportError, utils.import_class, + 'lol.u_mad.brah') + + def test_get_client_class_invalid_version(self): + self.assertRaises( + exceptions.UnsupportedVersion, + utils.get_client_class, 'image', '2', {'image': '2'}) From 7c6327e06207ea2107ba15deda76acd764d9d6ad Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Fri, 3 May 2013 19:46:11 +0200 Subject: [PATCH 111/135] Avoid error 414 when retrieving subnet cidrs for ListNetworks Bug 1172537 In order to avoif 414 the list subnet requests will be split in multiple requests. The total URI len for each of these requests will be lower than 8K (the default maximum for eventlet.wsgi.server). The patch tries to submit a single request, and if the URI is too long an exception is raised before the request is sent over the wire; the exception handler will split the subnet id list, and submit the new requests. This patch does not address the case in which the server is configured with a maximum URI length different from 8K. Change-Id: Ia2414cd5374a91d3d12215807037a5d46b836ad6 --- quantumclient/client.py | 4 +- quantumclient/common/exceptions.py | 8 ++++ quantumclient/quantum/v2_0/network.py | 27 ++++++++++- quantumclient/v2_0/client.py | 8 ++++ tests/unit/test_cli20_network.py | 67 ++++++++++++++++++++++++++- 5 files changed, 110 insertions(+), 4 deletions(-) diff --git a/quantumclient/client.py b/quantumclient/client.py index 2ab94a7e1..78e45a7aa 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -135,12 +135,14 @@ def _cs_request(self, *args, **kwargs): raise exceptions.Forbidden(message=body) return resp, body - def do_request(self, url, method, **kwargs): + def authenticate_and_fetch_endpoint_url(self): if not self.auth_token: self.authenticate() elif not self.endpoint_url: self.endpoint_url = self._get_endpoint_url() + def do_request(self, url, method, **kwargs): + self.authenticate_and_fetch_endpoint_url() # Perform the request once. If we get a 401 back then it # might be because the auth token expired, so try to # re-authenticate and try again. If it still fails, bail. diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py index 513a016c6..e88007ad5 100644 --- a/quantumclient/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -123,6 +123,14 @@ class QuantumCLIError(QuantumClientException): pass +class RequestURITooLong(QuantumClientException): + """Raised when a request fails with HTTP error 414.""" + + def __init__(self, **kwargs): + self.excess = kwargs.get('excess', 0) + super(RequestURITooLong, self).__init__(**kwargs) + + class ConnectionFailed(QuantumClientException): message = _("Connection to quantum failed: %(reason)s") diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index ed8106840..92ca6facd 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -18,6 +18,7 @@ import argparse import logging +from quantumclient.common import exceptions from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand from quantumclient.quantum.v2_0 import ListCommand @@ -36,6 +37,9 @@ def _format_subnets(network): class ListNetwork(ListCommand): """List networks that belong to a given tenant.""" + # Length of a query filter on subnet id + # id=& (with len(uuid)=36) + subnet_id_filter_len = 40 resource = 'network' log = logging.getLogger(__name__ + '.ListNetwork') _formatters = {'subnets': _format_subnets, } @@ -55,8 +59,27 @@ def extend_list(self, data, parsed_args): for n in data: if 'subnets' in n: subnet_ids.extend(n['subnets']) - search_opts.update({'id': subnet_ids}) - subnets = quantum_client.list_subnets(**search_opts).get('subnets', []) + + def _get_subnet_list(sub_ids): + search_opts['id'] = sub_ids + return quantum_client.list_subnets( + **search_opts).get('subnets', []) + + try: + subnets = _get_subnet_list(subnet_ids) + except exceptions.RequestURITooLong as uri_len_exc: + # The URI is too long because of too many subnet_id filters + # Use the excess attribute of the exception to know how many + # subnet_id filters can be inserted into a single request + subnet_count = len(subnet_ids) + max_size = ((self.subnet_id_filter_len * subnet_count) - + uri_len_exc.excess) + chunk_size = max_size / self.subnet_id_filter_len + subnets = [] + for i in xrange(0, subnet_count, chunk_size): + subnets.extend( + _get_subnet_list(subnet_ids[i: i + chunk_size])) + subnet_dict = dict([(s['id'], s) for s in subnets]) for n in data: if 'subnets' in n: diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 99f4cde64..94f0c365c 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -196,6 +196,8 @@ class Client(object): 'health_monitors': 'health_monitor', 'quotas': 'quota', } + # 8192 Is the default max URI len for eventlet.wsgi.server + MAX_URI_LEN = 8192 def get_attr_metadata(self): if self.format == 'json': @@ -901,6 +903,12 @@ def do_request(self, method, action, body=None, headers=None, params=None): if type(params) is dict and params: params = utils.safe_encode_dict(params) action += '?' + urllib.urlencode(params, doseq=1) + # Ensure client always has correct uri - do not guesstimate anything + self.httpclient.authenticate_and_fetch_endpoint_url() + uri_len = len(self.httpclient.endpoint_url) + len(action) + if uri_len > self.MAX_URI_LEN: + raise exceptions.RequestURITooLong( + excess=uri_len - self.MAX_URI_LEN) if body: body = self.serialize(body) self.httpclient.content_type = self.content_type() diff --git a/tests/unit/test_cli20_network.py b/tests/unit/test_cli20_network.py index c0928d3b9..ada6f0484 100644 --- a/tests/unit/test_cli20_network.py +++ b/tests/unit/test_cli20_network.py @@ -1,4 +1,3 @@ -# Copyright 2012 OpenStack LLC. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -473,6 +472,72 @@ def test_delete_network(self): args = [myid] self._test_delete_resource(resource, cmd, myid, args) + def _test_extend_list(self, mox_calls): + data = [{'id': 'netid%d' % i, 'name': 'net%d' % i, + 'subnets': ['mysubid%d' % i]} + for i in range(0, 10)] + self.mox.StubOutWithMock(self.client.httpclient, "request") + path = getattr(self.client, 'subnets_path') + cmd = ListNetwork(MyApp(sys.stdout), None) + self.mox.StubOutWithMock(cmd, "get_client") + cmd.get_client().MultipleTimes().AndReturn(self.client) + mox_calls(path, data) + self.mox.ReplayAll() + known_args, _vs = cmd.get_parser('create_subnets').parse_known_args() + cmd.extend_list(data, known_args) + self.mox.VerifyAll() + + def _build_test_data(self, data): + subnet_ids = [] + response = [] + filters = "" + for n in data: + if 'subnets' in n: + subnet_ids.extend(n['subnets']) + for subnet_id in n['subnets']: + filters = "%s&id=%s" % (filters, subnet_id) + response.append({'id': subnet_id, + 'cidr': '192.168.0.0/16'}) + resp_str = self.client.serialize({'subnets': response}) + resp = (test_cli20.MyResp(200), resp_str) + return filters, resp + + def test_extend_list(self): + def mox_calls(path, data): + filters, response = self._build_test_data(data) + self.client.httpclient.request( + test_cli20.end_url(path, 'fields=id&fields=cidr' + filters), + 'GET', + body=None, + headers=ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response) + + self._test_extend_list(mox_calls) + + def test_extend_list_exceed_max_uri_len(self): + def mox_calls(path, data): + sub_data_lists = [data[:len(data) - 1], data[len(data) - 1:]] + filters, response = self._build_test_data(data) + # 1 char of extra URI len will cause a split in 2 requests + self.client.httpclient.request( + test_cli20.end_url(path, 'fields=id&fields=cidr%s' % filters), + 'GET', + body=None, + headers=ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndRaise( + exceptions.RequestURITooLong(excess=1)) + for data in sub_data_lists: + filters, response = self._build_test_data(data) + self.client.httpclient.request( + test_cli20.end_url(path, + 'fields=id&fields=cidr%s' % filters), + 'GET', + body=None, + headers=ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response) + + self._test_extend_list(mox_calls) + class CLITestV20NetworkXML(CLITestV20NetworkJSON): format = 'xml' From ce11f7e859a5068e52655c94224273283353c255 Mon Sep 17 00:00:00 2001 From: Clark Boylan Date: Sat, 11 May 2013 12:13:13 -0700 Subject: [PATCH 112/135] Make flake8 deps consistent with other projects. Make the flake8 dependencies consistent with openstack/requirements and the other OpenStack projects. Fixes bug 1172444. Change-Id: I04bd814ad51cf53a3fa0026028a36de461da45d4 --- tools/test-requires | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tools/test-requires b/tools/test-requires index 6395af8cd..a9e4dffc3 100644 --- a/tools/test-requires +++ b/tools/test-requires @@ -1,10 +1,14 @@ +# Install bounded pep8/pyflakes first, then let flake8 install +pep8==1.4.5 +pyflakes==0.7.2 +flake8==2.0 +hacking>=0.5.3,<0.6 + cliff-tablib>=1.0 coverage discover distribute>=0.6.24 fixtures>=0.3.12 -flake8 -hacking mox python-subunit sphinx>=1.1.2 From 9dbc2c799763f2ea655547ea09abcb816e7163b7 Mon Sep 17 00:00:00 2001 From: Zhenguo Niu Date: Tue, 14 May 2013 15:25:13 +0800 Subject: [PATCH 113/135] Add update method of security group name and description make it possible to edit the name and description of security groups. Fixes: bug #918393 Change-Id: I7b9dd3f9ad2f59aee1b37e06350ce8f5e3a40f64 --- quantumclient/quantum/v2_0/securitygroup.py | 25 +++++++++++++++++++++ quantumclient/shell.py | 2 ++ quantumclient/v2_0/client.py | 8 +++++++ tests/unit/test_cli20_securitygroup.py | 23 +++++++++++++++++++ 4 files changed, 58 insertions(+) diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py index abc870a3e..c66d012c3 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -73,6 +73,31 @@ class DeleteSecurityGroup(quantumv20.DeleteCommand): allow_names = True +class UpdateSecurityGroup(quantumv20.UpdateCommand): + """Update a given security group.""" + + log = logging.getLogger(__name__ + '.UpdateSecurityGroup') + resource = 'security_group' + + def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help='Name of security group') + parser.add_argument( + '--description', + help='description of security group') + + def args2body(self, parsed_args): + body = {'security_group': {}} + if parsed_args.name: + body['security_group'].update( + {'name': parsed_args.name}) + if parsed_args.description: + body['security_group'].update( + {'description': parsed_args.description}) + return body + + class ListSecurityGroupRule(quantumv20.ListCommand): """List security group rules that belong to a given tenant.""" diff --git a/quantumclient/shell.py b/quantumclient/shell.py index f531325dc..b83b97944 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -150,6 +150,8 @@ def env(*_vars, **kwargs): 'quantumclient.quantum.v2_0.securitygroup.CreateSecurityGroup'), 'security-group-delete': utils.import_class( 'quantumclient.quantum.v2_0.securitygroup.DeleteSecurityGroup'), + 'security-group-update': utils.import_class( + 'quantumclient.quantum.v2_0.securitygroup.UpdateSecurityGroup'), 'security-group-rule-list': utils.import_class( 'quantumclient.quantum.v2_0.securitygroup.ListSecurityGroupRule'), 'security-group-rule-show': utils.import_class( diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 99f4cde64..e9a5627ce 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -470,6 +470,14 @@ def create_security_group(self, body=None): """ return self.post(self.security_groups_path, body=body) + @APIParamsCall + def update_security_group(self, security_group, body=None): + """ + Updates a security group + """ + return self.put(self.security_group_path % + security_group, body=body) + @APIParamsCall def list_security_groups(self, retrieve_all=True, **_params): """ diff --git a/tests/unit/test_cli20_securitygroup.py b/tests/unit/test_cli20_securitygroup.py index b1e9e32c6..4302093e1 100644 --- a/tests/unit/test_cli20_securitygroup.py +++ b/tests/unit/test_cli20_securitygroup.py @@ -118,6 +118,29 @@ def test_delete_security_group(self): args = [myid] self._test_delete_resource(resource, cmd, myid, args) + def test_update_security_group(self): + """Update security group: myid --name myname --description desc.""" + resource = 'security_group' + cmd = securitygroup.UpdateSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', 'myname', + '--description', 'mydescription'], + {'name': 'myname', + 'description': 'mydescription'} + ) + + def test_update_security_group_with_unicode(self): + resource = 'security_group' + cmd = securitygroup.UpdateSecurityGroup( + test_cli20.MyApp(sys.stdout), None) + self._test_update_resource(resource, cmd, 'myid', + ['myid', '--name', u'\u7f51\u7edc', + '--description', u'\u7f51\u7edc'], + {'name': u'\u7f51\u7edc', + 'description': u'\u7f51\u7edc'} + ) + def test_create_security_group_rule_full(self): """Create security group rule.""" resource = 'security_group_rule' From 2815be00b02320c22b04e888b573ab040e3f82c5 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 26 Mar 2013 09:01:29 +0100 Subject: [PATCH 114/135] CLI support for disabling SNAT blueprint l3-ext-gw-modes Adds the --disable-snat option to the router-gateway-set command Change-Id: I1e6e339e3332d7073f761f407318811f22d283b1 --- quantumclient/quantum/v2_0/router.py | 9 +++++++-- tests/unit/test_cli20_router.py | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index 3f8ed6ccc..cff4ace0c 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -166,6 +166,9 @@ def get_parser(self, prog_name): parser.add_argument( 'external_network_id', metavar='external-network-id', help='ID of the external network for the gateway') + parser.add_argument( + '--disable-snat', action='store_false', dest='enable_snat', + help='Disable Source NAT on the router gateway') return parser def run(self, parsed_args): @@ -176,8 +179,10 @@ def run(self, parsed_args): quantum_client, self.resource, parsed_args.router_id) _ext_net_id = quantumv20.find_resourceid_by_name_or_id( quantum_client, 'network', parsed_args.external_network_id) - quantum_client.add_gateway_router(_router_id, - {'network_id': _ext_net_id}) + quantum_client.add_gateway_router( + _router_id, + {'network_id': _ext_net_id, + 'enable_snat': parsed_args.enable_snat}) print >>self.app.stdout, ( _('Set gateway for router %s') % parsed_args.router_id) diff --git a/tests/unit/test_cli20_router.py b/tests/unit/test_cli20_router.py index 5c545d8dd..4868320ae 100644 --- a/tests/unit/test_cli20_router.py +++ b/tests/unit/test_cli20_router.py @@ -159,7 +159,8 @@ def test_set_gateway(self): self._test_update_resource(resource, cmd, 'myid', args, {"external_gateway_info": - {"network_id": "externalid"}} + {"network_id": "externalid", + "enable_snat": True}} ) def test_remove_gateway(self): From 06375f089b9938f1db7f6abc4b2144df2ff6b4fd Mon Sep 17 00:00:00 2001 From: Carl Baldwin Date: Tue, 30 Apr 2013 15:20:10 -0600 Subject: [PATCH 115/135] Allow the HTTPClient consumer to pass endpoint_type. Changes the default behavior of the client. It will now choose publicURL by default rather than adminURL. Now allows the consumer to pass adminURL, internalURL or some other endpoint type when constructing a Client to override this default behavior. Adds --endpoint-type option to the shell client. Defaults to the environment variable OS_ENDPOINT_TYPE or publicURL. This was patterned after the same option in the Nova client. Adds a new exception type to handle the case where a suitable endpoint type is not found in the catalog. Without this, the exception encountered is a KeyError that was not clearly reported to the caller of the quantum command line. Change-Id: Iaffcaff291d433a605d8379dc89c1308096d36c2 Fixes: Bug #1176197 --- quantumclient/client.py | 20 +++- quantumclient/common/clientmanager.py | 3 + quantumclient/common/exceptions.py | 8 ++ quantumclient/shell.py | 6 + quantumclient/v2_0/client.py | 3 + tests/unit/test_auth.py | 154 ++++++++++++++++++++++++++ tests/unit/test_shell.py | 28 +++++ 7 files changed, 216 insertions(+), 6 deletions(-) diff --git a/quantumclient/client.py b/quantumclient/client.py index 2ab94a7e1..6a4c05796 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -60,10 +60,10 @@ def get_token(self): return token def url_for(self, attr=None, filter_value=None, - service_type='network', endpoint_type='adminURL'): - """Fetch the admin URL from the Quantum service for - a particular endpoint attribute. If none given, return - the first. See tests for sample service catalog. + service_type='network', endpoint_type='publicURL'): + """Fetch the URL from the Quantum service for + a particular endpoint type. If none given, return + publicURL. """ catalog = self.catalog['access'].get('serviceCatalog', []) @@ -82,6 +82,9 @@ def url_for(self, attr=None, filter_value=None, elif len(matching_endpoints) > 1: raise exceptions.AmbiguousEndpoints(message=matching_endpoints) else: + if endpoint_type not in matching_endpoints[0]: + raise exceptions.EndpointTypeNotFound(message=endpoint_type) + return matching_endpoints[0][endpoint_type] @@ -94,12 +97,14 @@ def __init__(self, username=None, tenant_name=None, password=None, auth_url=None, token=None, region_name=None, timeout=None, endpoint_url=None, insecure=False, + endpoint_type='publicURL', auth_strategy='keystone', **kwargs): super(HTTPClient, self).__init__(timeout=timeout) self.username = username self.tenant_name = tenant_name self.password = password self.auth_url = auth_url.rstrip('/') if auth_url else None + self.endpoint_type = endpoint_type self.region_name = region_name self.auth_token = token self.content_type = 'application/json' @@ -170,7 +175,7 @@ def _extract_service_catalog(self, body): raise exceptions.Unauthorized() self.endpoint_url = self.service_catalog.url_for( attr='region', filter_value=self.region_name, - endpoint_type='adminURL') + endpoint_type=self.endpoint_type) def authenticate(self): if self.auth_strategy != 'keystone': @@ -217,7 +222,10 @@ def _get_endpoint_url(self): for endpoint in body.get('endpoints', []): if (endpoint['type'] == 'network' and endpoint.get('region') == self.region_name): - return endpoint['adminURL'] + if self.endpoint_type not in endpoint: + raise exceptions.EndpointTypeNotFound( + message=self.endpoint_type) + return endpoint[self.endpoint_type] raise exceptions.EndpointNotFound() diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py index 9083d4551..40550d466 100644 --- a/quantumclient/common/clientmanager.py +++ b/quantumclient/common/clientmanager.py @@ -49,6 +49,7 @@ class ClientManager(object): def __init__(self, token=None, url=None, auth_url=None, + endpoint_type=None, tenant_name=None, tenant_id=None, username=None, password=None, region_name=None, @@ -59,6 +60,7 @@ def __init__(self, token=None, url=None, self._token = token self._url = url self._auth_url = auth_url + self._endpoint_type = endpoint_type self._tenant_name = tenant_name self._tenant_id = tenant_id self._username = username @@ -77,6 +79,7 @@ def initialize(self): password=self._password, region_name=self._region_name, auth_url=self._auth_url, + endpoint_type=self._endpoint_type, insecure=self._insecure) httpclient.authenticate() # Populate other password flow attributes diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py index 513a016c6..a6dd44c44 100644 --- a/quantumclient/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -111,6 +111,14 @@ class EndpointNotFound(QuantumClientException): message = _("Could not find Service or Region in Service Catalog.") +class EndpointTypeNotFound(QuantumClientException): + """Could not find endpoint type in Service Catalog.""" + + def __str__(self): + msg = "Could not find endpoint type %s in Service Catalog." + return msg % repr(self.message) + + class AmbiguousEndpoints(QuantumClientException): """Found more than one matching endpoint in Service Catalog.""" diff --git a/quantumclient/shell.py b/quantumclient/shell.py index f531325dc..e91c09e80 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -405,6 +405,11 @@ def build_option_parser(self, description, version): '--os_token', help=argparse.SUPPRESS) + parser.add_argument( + '--endpoint-type', metavar='', + default=env('OS_ENDPOINT_TYPE', default='publicURL'), + help='Defaults to env[OS_ENDPOINT_TYPE] or publicURL.') + parser.add_argument( '--os-url', metavar='', default=env('OS_URL'), @@ -583,6 +588,7 @@ def authenticate_user(self): region_name=self.options.os_region_name, api_version=self.api_version, auth_strategy=self.options.os_auth_strategy, + endpoint_type=self.options.endpoint_type, insecure=self.options.insecure, ) return diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 99f4cde64..e7a0dbe5c 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -119,6 +119,9 @@ class Client(object): :param string token: Token for authentication. (optional) :param string tenant_name: Tenant name. (optional) :param string auth_url: Keystone service endpoint for authorization. + :param string endpoint_type: Network service endpoint type to pull from the + keystone catalog (e.g. 'publicURL', + 'internalURL', or 'adminURL') (optional) :param string region_name: Name of a region to select when choosing an endpoint from the service catalog. :param string endpoint_url: A user-supplied endpoint URL for the quantum diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index e5e855527..4f09eacfe 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -15,6 +15,7 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 +import copy import httplib2 import json import uuid @@ -23,7 +24,9 @@ from mox import ContainsKeyValue, IsA, StrContains import testtools +from quantumclient.client import exceptions from quantumclient.client import HTTPClient +from quantumclient.client import ServiceCatalog USERNAME = 'testuser' @@ -136,6 +139,27 @@ def test_get_endpoint_url(self): self.mox.ReplayAll() self.client.do_request('/resource', 'GET') + def test_get_endpoint_url_other(self): + self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, + password=PASSWORD, auth_url=AUTH_URL, + region_name=REGION, endpoint_type='otherURL') + self.mox.StubOutWithMock(self.client, "request") + + self.client.auth_token = TOKEN + + res200 = self.mox.CreateMock(httplib2.Response) + res200.status = 200 + + self.client.request(StrContains(AUTH_URL + + '/tokens/%s/endpoints' % TOKEN), 'GET', + headers=IsA(dict)). \ + AndReturn((res200, json.dumps(ENDPOINTS_RESULT))) + self.mox.ReplayAll() + self.assertRaises(exceptions.EndpointTypeNotFound, + self.client.do_request, + '/resource', + 'GET') + def test_get_endpoint_url_failed(self): self.mox.StubOutWithMock(self.client, "request") @@ -158,3 +182,133 @@ def test_get_endpoint_url_failed(self): AndReturn((res200, '')) self.mox.ReplayAll() self.client.do_request('/resource', 'GET') + + def test_url_for(self): + resources = copy.deepcopy(KS_TOKEN_RESULT) + + endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0] + endpoints['publicURL'] = 'public' + endpoints['internalURL'] = 'internal' + endpoints['adminURL'] = 'admin' + catalog = ServiceCatalog(resources) + + # endpoint_type not specified + url = catalog.url_for(attr='region', + filter_value=REGION) + self.assertEqual('public', url) + + # endpoint type specified (3 cases) + url = catalog.url_for(attr='region', + filter_value=REGION, + endpoint_type='adminURL') + self.assertEqual('admin', url) + + url = catalog.url_for(attr='region', + filter_value=REGION, + endpoint_type='publicURL') + self.assertEqual('public', url) + + url = catalog.url_for(attr='region', + filter_value=REGION, + endpoint_type='internalURL') + self.assertEqual('internal', url) + + # endpoint_type requested does not exist. + self.assertRaises(exceptions.EndpointTypeNotFound, + catalog.url_for, + attr='region', + filter_value=REGION, + endpoint_type='privateURL') + + # Test scenario with url_for when the service catalog only has publicURL. + def test_url_for_only_public_url(self): + resources = copy.deepcopy(KS_TOKEN_RESULT) + catalog = ServiceCatalog(resources) + + # Remove endpoints from the catalog. + endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0] + del endpoints['internalURL'] + del endpoints['adminURL'] + endpoints['publicURL'] = 'public' + + # Use publicURL when specified explicitly. + url = catalog.url_for(attr='region', + filter_value=REGION, + endpoint_type='publicURL') + self.assertEqual('public', url) + + # Use publicURL when specified explicitly. + url = catalog.url_for(attr='region', + filter_value=REGION) + self.assertEqual('public', url) + + # Test scenario with url_for when the service catalog only has adminURL. + def test_url_for_only_admin_url(self): + resources = copy.deepcopy(KS_TOKEN_RESULT) + catalog = ServiceCatalog(resources) + endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0] + del endpoints['internalURL'] + del endpoints['publicURL'] + endpoints['adminURL'] = 'admin' + + # Use publicURL when specified explicitly. + url = catalog.url_for(attr='region', + filter_value=REGION, + endpoint_type='adminURL') + self.assertEqual('admin', url) + + # But not when nothing is specified. + self.assertRaises(exceptions.EndpointTypeNotFound, + catalog.url_for, + attr='region', + filter_value=REGION) + + def test_endpoint_type(self): + resources = copy.deepcopy(KS_TOKEN_RESULT) + endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0] + endpoints['internalURL'] = 'internal' + endpoints['adminURL'] = 'admin' + endpoints['publicURL'] = 'public' + + # Test default behavior is to choose public. + self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, + password=PASSWORD, auth_url=AUTH_URL, + region_name=REGION) + + self.client._extract_service_catalog(resources) + self.assertEqual(self.client.endpoint_url, 'public') + + # Test admin url + self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, + password=PASSWORD, auth_url=AUTH_URL, + region_name=REGION, endpoint_type='adminURL') + + self.client._extract_service_catalog(resources) + self.assertEqual(self.client.endpoint_url, 'admin') + + # Test public url + self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, + password=PASSWORD, auth_url=AUTH_URL, + region_name=REGION, endpoint_type='publicURL') + + self.client._extract_service_catalog(resources) + self.assertEqual(self.client.endpoint_url, 'public') + + # Test internal url + self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, + password=PASSWORD, auth_url=AUTH_URL, + region_name=REGION, + endpoint_type='internalURL') + + self.client._extract_service_catalog(resources) + self.assertEqual(self.client.endpoint_url, 'internal') + + # Test url that isn't found in the service catalog + self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, + password=PASSWORD, auth_url=AUTH_URL, + region_name=REGION, + endpoint_type='privateURL') + + self.assertRaises(exceptions.EndpointTypeNotFound, + self.client._extract_service_catalog, + resources) diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py index 3d8d0f050..b6a8e14f1 100644 --- a/tests/unit/test_shell.py +++ b/tests/unit/test_shell.py @@ -143,3 +143,31 @@ def test_main_with_unicode(self): self.mox.VerifyAll() self.mox.UnsetStubs() self.assertEqual(ret, 0) + + def test_endpoint_option(self): + shell = openstack_shell.QuantumShell('2.0') + parser = shell.build_option_parser('descr', '2.0') + + # Neither $OS_ENDPOINT_TYPE nor --endpoint-type + namespace = parser.parse_args([]) + self.assertEqual('publicURL', namespace.endpoint_type) + + # --endpoint-type but not $OS_ENDPOINT_TYPE + namespace = parser.parse_args(['--endpoint-type=admin']) + self.assertEqual('admin', namespace.endpoint_type) + + def test_endpoint_environment_variable(self): + fixture = fixtures.EnvironmentVariable("OS_ENDPOINT_TYPE", + "public") + self.useFixture(fixture) + + shell = openstack_shell.QuantumShell('2.0') + parser = shell.build_option_parser('descr', '2.0') + + # $OS_ENDPOINT_TYPE but not --endpoint-type + namespace = parser.parse_args([]) + self.assertEqual("public", namespace.endpoint_type) + + # --endpoint-type and $OS_ENDPOINT_TYPE + namespace = parser.parse_args(['--endpoint-type=admin']) + self.assertEqual('admin', namespace.endpoint_type) From 898acc30f76b2629aa740976103b99f10801d593 Mon Sep 17 00:00:00 2001 From: Clark Boylan Date: Sat, 11 May 2013 12:38:24 -0700 Subject: [PATCH 116/135] Migrate to pbr. Fixes bug 1179007 Change-Id: I6d7a6b7c85361e1568719ad11035158f4f6d9b35 --- .gitignore | 1 + MANIFEST.in | 2 +- openstack-common.conf | 2 +- quantumclient/openstack/common/setup.py | 366 ------------------------ setup.cfg | 32 +++ setup.py | 88 ++---- tools/pip-requires | 2 + tox.ini | 4 +- 8 files changed, 55 insertions(+), 442 deletions(-) delete mode 100644 quantumclient/openstack/common/setup.py diff --git a/.gitignore b/.gitignore index 66f98c140..5b6e2cc85 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.pyc *.DS_Store +*.egg AUTHORS ChangeLog build/* diff --git a/MANIFEST.in b/MANIFEST.in index 251a20197..56a029e10 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ include tox.ini include LICENSE README HACKING.rst +include AUTHORS include ChangeLog include tools/* -include quantumclient/versioninfo recursive-include tests * diff --git a/openstack-common.conf b/openstack-common.conf index 454676543..cff7a3d6f 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -1,7 +1,7 @@ [DEFAULT] # The list of modules to copy from openstack-common -modules=exception,gettextutils,jsonutils,setup,strutils,timeutils +modules=exception,gettextutils,jsonutils,strutils,timeutils # The base module to hold the copy of openstack.common base=quantumclient diff --git a/quantumclient/openstack/common/setup.py b/quantumclient/openstack/common/setup.py deleted file mode 100644 index e6f72f034..000000000 --- a/quantumclient/openstack/common/setup.py +++ /dev/null @@ -1,366 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 OpenStack LLC. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -Utilities with minimum-depends for use in setup.py -""" - -import datetime -import os -import re -import subprocess -import sys - -from setuptools.command import sdist - - -def parse_mailmap(mailmap='.mailmap'): - mapping = {} - if os.path.exists(mailmap): - with open(mailmap, 'r') as fp: - for l in fp: - l = l.strip() - if not l.startswith('#') and ' ' in l: - canonical_email, alias = [x for x in l.split(' ') - if x.startswith('<')] - mapping[alias] = canonical_email - return mapping - - -def canonicalize_emails(changelog, mapping): - """Takes in a string and an email alias mapping and replaces all - instances of the aliases in the string with their real email. - """ - for alias, email in mapping.iteritems(): - changelog = changelog.replace(alias, email) - return changelog - - -# Get requirements from the first file that exists -def get_reqs_from_files(requirements_files): - for requirements_file in requirements_files: - if os.path.exists(requirements_file): - with open(requirements_file, 'r') as fil: - return fil.read().split('\n') - return [] - - -def parse_requirements(requirements_files=['requirements.txt', - 'tools/pip-requires']): - requirements = [] - for line in get_reqs_from_files(requirements_files): - # For the requirements list, we need to inject only the portion - # after egg= so that distutils knows the package it's looking for - # such as: - # -e git://github.com/openstack/nova/master#egg=nova - if re.match(r'\s*-e\s+', line): - requirements.append(re.sub(r'\s*-e\s+.*#egg=(.*)$', r'\1', - line)) - # such as: - # http://github.com/openstack/nova/zipball/master#egg=nova - elif re.match(r'\s*https?:', line): - requirements.append(re.sub(r'\s*https?:.*#egg=(.*)$', r'\1', - line)) - # -f lines are for index locations, and don't get used here - elif re.match(r'\s*-f\s+', line): - pass - # argparse is part of the standard library starting with 2.7 - # adding it to the requirements list screws distro installs - elif line == 'argparse' and sys.version_info >= (2, 7): - pass - else: - requirements.append(line) - - return requirements - - -def parse_dependency_links(requirements_files=['requirements.txt', - 'tools/pip-requires']): - dependency_links = [] - # dependency_links inject alternate locations to find packages listed - # in requirements - for line in get_reqs_from_files(requirements_files): - # skip comments and blank lines - if re.match(r'(\s*#)|(\s*$)', line): - continue - # lines with -e or -f need the whole line, minus the flag - if re.match(r'\s*-[ef]\s+', line): - dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line)) - # lines that are only urls can go in unmolested - elif re.match(r'\s*https?:', line): - dependency_links.append(line) - return dependency_links - - -def write_requirements(): - venv = os.environ.get('VIRTUAL_ENV', None) - if venv is not None: - with open("requirements.txt", "w") as req_file: - output = subprocess.Popen(["pip", "-E", venv, "freeze", "-l"], - stdout=subprocess.PIPE) - requirements = output.communicate()[0].strip() - req_file.write(requirements) - - -def _run_shell_command(cmd): - if os.name == 'nt': - output = subprocess.Popen(["cmd.exe", "/C", cmd], - stdout=subprocess.PIPE) - else: - output = subprocess.Popen(["/bin/sh", "-c", cmd], - stdout=subprocess.PIPE) - out = output.communicate() - if len(out) == 0: - return None - if len(out[0].strip()) == 0: - return None - return out[0].strip() - - -def _get_git_next_version_suffix(branch_name): - datestamp = datetime.datetime.now().strftime('%Y%m%d') - if branch_name == 'milestone-proposed': - revno_prefix = "r" - else: - revno_prefix = "" - _run_shell_command("git fetch origin +refs/meta/*:refs/remotes/meta/*") - milestone_cmd = "git show meta/openstack/release:%s" % branch_name - milestonever = _run_shell_command(milestone_cmd) - if milestonever: - first_half = "%s~%s" % (milestonever, datestamp) - else: - first_half = datestamp - - post_version = _get_git_post_version() - # post version should look like: - # 0.1.1.4.gcc9e28a - # where the bit after the last . is the short sha, and the bit between - # the last and second to last is the revno count - (revno, sha) = post_version.split(".")[-2:] - second_half = "%s%s.%s" % (revno_prefix, revno, sha) - return ".".join((first_half, second_half)) - - -def _get_git_current_tag(): - return _run_shell_command("git tag --contains HEAD") - - -def _get_git_tag_info(): - return _run_shell_command("git describe --tags") - - -def _get_git_post_version(): - current_tag = _get_git_current_tag() - if current_tag is not None: - return current_tag - else: - tag_info = _get_git_tag_info() - if tag_info is None: - base_version = "0.0" - cmd = "git --no-pager log --oneline" - out = _run_shell_command(cmd) - revno = len(out.split("\n")) - sha = _run_shell_command("git describe --always") - else: - tag_infos = tag_info.split("-") - base_version = "-".join(tag_infos[:-2]) - (revno, sha) = tag_infos[-2:] - return "%s.%s.%s" % (base_version, revno, sha) - - -def write_git_changelog(): - """Write a changelog based on the git changelog.""" - new_changelog = 'ChangeLog' - if not os.getenv('SKIP_WRITE_GIT_CHANGELOG'): - if os.path.isdir('.git'): - git_log_cmd = 'git log --stat' - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open(new_changelog, "w") as changelog_file: - changelog_file.write(canonicalize_emails(changelog, mailmap)) - else: - open(new_changelog, 'w').close() - - -def generate_authors(): - """Create AUTHORS file using git commits.""" - jenkins_email = 'jenkins@review.(openstack|stackforge).org' - old_authors = 'AUTHORS.in' - new_authors = 'AUTHORS' - if not os.getenv('SKIP_GENERATE_AUTHORS'): - if os.path.isdir('.git'): - # don't include jenkins email address in AUTHORS file - git_log_cmd = ("git log --format='%aN <%aE>' | sort -u | " - "egrep -v '" + jenkins_email + "'") - changelog = _run_shell_command(git_log_cmd) - mailmap = parse_mailmap() - with open(new_authors, 'w') as new_authors_fh: - new_authors_fh.write(canonicalize_emails(changelog, mailmap)) - if os.path.exists(old_authors): - with open(old_authors, "r") as old_authors_fh: - new_authors_fh.write('\n' + old_authors_fh.read()) - else: - open(new_authors, 'w').close() - - -_rst_template = """%(heading)s -%(underline)s - -.. automodule:: %(module)s - :members: - :undoc-members: - :show-inheritance: -""" - - -def read_versioninfo(project): - """Read the versioninfo file. If it doesn't exist, we're in a github - zipball, and there's really no way to know what version we really - are, but that should be ok, because the utility of that should be - just about nil if this code path is in use in the first place.""" - versioninfo_path = os.path.join(project, 'versioninfo') - if os.path.exists(versioninfo_path): - with open(versioninfo_path, 'r') as vinfo: - version = vinfo.read().strip() - else: - version = "0.0.0" - return version - - -def write_versioninfo(project, version): - """Write a simple file containing the version of the package.""" - with open(os.path.join(project, 'versioninfo'), 'w') as fil: - fil.write("%s\n" % version) - - -def get_cmdclass(): - """Return dict of commands to run from setup.py.""" - - cmdclass = dict() - - def _find_modules(arg, dirname, files): - for filename in files: - if filename.endswith('.py') and filename != '__init__.py': - arg["%s.%s" % (dirname.replace('/', '.'), - filename[:-3])] = True - - class LocalSDist(sdist.sdist): - """Builds the ChangeLog and Authors files from VC first.""" - - def run(self): - write_git_changelog() - generate_authors() - # sdist.sdist is an old style class, can't use super() - sdist.sdist.run(self) - - cmdclass['sdist'] = LocalSDist - - # If Sphinx is installed on the box running setup.py, - # enable setup.py to build the documentation, otherwise, - # just ignore it - try: - from sphinx.setup_command import BuildDoc - - class LocalBuildDoc(BuildDoc): - def generate_autoindex(self): - print "**Autodocumenting from %s" % os.path.abspath(os.curdir) - modules = {} - option_dict = self.distribution.get_option_dict('build_sphinx') - source_dir = os.path.join(option_dict['source_dir'][1], 'api') - if not os.path.exists(source_dir): - os.makedirs(source_dir) - for pkg in self.distribution.packages: - if '.' not in pkg: - os.path.walk(pkg, _find_modules, modules) - module_list = modules.keys() - module_list.sort() - autoindex_filename = os.path.join(source_dir, 'autoindex.rst') - with open(autoindex_filename, 'w') as autoindex: - autoindex.write(""".. toctree:: - :maxdepth: 1 - -""") - for module in module_list: - output_filename = os.path.join(source_dir, - "%s.rst" % module) - heading = "The :mod:`%s` Module" % module - underline = "=" * len(heading) - values = dict(module=module, heading=heading, - underline=underline) - - print "Generating %s" % output_filename - with open(output_filename, 'w') as output_file: - output_file.write(_rst_template % values) - autoindex.write(" %s.rst\n" % module) - - def run(self): - if not os.getenv('SPHINX_DEBUG'): - self.generate_autoindex() - - for builder in ['html', 'man']: - self.builder = builder - self.finalize_options() - self.project = self.distribution.get_name() - self.version = self.distribution.get_version() - self.release = self.distribution.get_version() - BuildDoc.run(self) - cmdclass['build_sphinx'] = LocalBuildDoc - except ImportError: - pass - - return cmdclass - - -def get_git_branchname(): - for branch in _run_shell_command("git branch --color=never").split("\n"): - if branch.startswith('*'): - _branch_name = branch.split()[1].strip() - if _branch_name == "(no": - _branch_name = "no-branch" - return _branch_name - - -def get_pre_version(projectname, base_version): - """Return a version which is leading up to a version that will - be released in the future.""" - if os.path.isdir('.git'): - current_tag = _get_git_current_tag() - if current_tag is not None: - version = current_tag - else: - branch_name = os.getenv('BRANCHNAME', - os.getenv('GERRIT_REFNAME', - get_git_branchname())) - version_suffix = _get_git_next_version_suffix(branch_name) - version = "%s~%s" % (base_version, version_suffix) - write_versioninfo(projectname, version) - return version - else: - version = read_versioninfo(projectname) - return version - - -def get_post_version(projectname): - """Return a version which is equal to the tag that's on the current - revision if there is one, or tag plus number of additional revisions - if the current revision has no tag.""" - - if os.path.isdir('.git'): - version = _get_git_post_version() - write_versioninfo(projectname, version) - return version - return read_versioninfo(projectname) diff --git a/setup.cfg b/setup.cfg index 11d2c4422..5d633ea3b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,35 @@ +[metadata] +name = python-quantumclient +summary = CLI and Client Library for OpenStack Networking +description-file = + README +author = OpenStack Networking Project +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Developers + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 2.6 + +[files] +packages = + quantumclient + +[global] +setup-hooks = + pbr.hooks.setup_hook + +[entry_points] +console_scripts = + quantum = quantumclient.shell:main + [build_sphinx] all_files = 1 build-dir = doc/build diff --git a/setup.py b/setup.py index 044a5a459..80b683420 100644 --- a/setup.py +++ b/setup.py @@ -1,79 +1,23 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Citrix Systems -# All Rights Reserved. +#!/usr/bin/env python +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at # -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 # -# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. +# vim: tabstop=4 shiftwidth=4 softtabstop=4 import setuptools -from quantumclient.openstack.common import setup - -Name = 'python-quantumclient' -Url = "https://launchpad.net/quantum" -Version = setup.get_post_version('quantumclient') -License = 'Apache License 2.0' -Author = 'OpenStack Quantum Project' -AuthorEmail = 'openstack-dev@lists.launchpad.net' -Maintainer = '' -Summary = 'CLI and python client library for OpenStack Quantum' -ShortDescription = Summary -Description = Summary - -dependency_links = setup.parse_dependency_links() -tests_require = setup.parse_requirements(['tools/test-requires']) - -EagerResources = [ -] - -ProjectScripts = [ -] - -PackageData = { -} - - setuptools.setup( - name=Name, - version=Version, - url=Url, - author=Author, - author_email=AuthorEmail, - description=ShortDescription, - long_description=Description, - license=License, - classifiers=[ - 'Environment :: OpenStack', - 'Intended Audience :: Developers', - 'Intended Audience :: Information Technology', - 'License :: OSI Approved :: Apache Software License', - 'Operating System :: POSIX :: Linux', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - ], - scripts=ProjectScripts, - dependency_links=dependency_links, - install_requires=setup.parse_requirements(), - tests_require=tests_require, - cmdclass=setup.get_cmdclass(), - include_package_data=False, - packages=setuptools.find_packages(exclude=['tests', 'tests.*']), - package_data=PackageData, - eager_resources=EagerResources, - entry_points={ - 'console_scripts': [ - 'quantum = quantumclient.shell:main', - ] - }, -) + setup_requires=['d2to1>=0.2.10,<0.3', 'pbr>=0.5.10,<0.6'], + d2to1=True) diff --git a/tools/pip-requires b/tools/pip-requires index eed424499..b1acf1825 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -1,3 +1,5 @@ +d2to1>=0.2.10,<0.3 +pbr>=0.5.10,<0.6 argparse cliff>=1.3.2 httplib2 diff --git a/tox.ini b/tox.ini index 903048d9b..46d491e84 100644 --- a/tox.ini +++ b/tox.ini @@ -6,8 +6,8 @@ setenv = VIRTUAL_ENV={envdir} LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=C - -deps = -r{toxinidir}/tools/test-requires +deps = -r{toxinidir}/tools/pip-requires + -r{toxinidir}/tools/test-requires commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] From b95000665d8f7a56c7b522f5b07000ebb1052e39 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Wed, 27 Feb 2013 19:13:54 +0900 Subject: [PATCH 117/135] Set default columns in ext-list Previously ext-list does not inherits ListCommand base class for listing and cannot specify columns to display. This commit changes ext-list to use ListCommand and adds a unit test for extensions. This also changes the default value of _formatters in ListCommand to {}. By this subclasses no longer override _formatters unless required. Fixes bug 1161866 Change-Id: Ifd9ab54d4e84a2b7a1d7eecd67f6270ec643db1d --- quantumclient/quantum/v2_0/extension.py | 71 +++---------------- quantumclient/quantum/v2_0/floatingip.py | 1 - .../quantum/v2_0/lb/healthmonitor.py | 1 - quantumclient/quantum/v2_0/lb/member.py | 1 - quantumclient/quantum/v2_0/lb/pool.py | 1 - quantumclient/quantum/v2_0/lb/vip.py | 1 - quantumclient/quantum/v2_0/nvp_qos_queue.py | 1 - .../quantum/v2_0/nvpnetworkgateway.py | 1 - quantumclient/quantum/v2_0/quota.py | 1 - quantumclient/quantum/v2_0/securitygroup.py | 2 - quantumclient/v2_0/client.py | 8 +-- tests/unit/test_cli20.py | 21 ++++-- tests/unit/test_cli20_extensions.py | 49 +++++++++++++ 13 files changed, 77 insertions(+), 82 deletions(-) create mode 100644 tests/unit/test_cli20_extensions.py diff --git a/quantumclient/quantum/v2_0/extension.py b/quantumclient/quantum/v2_0/extension.py index df4a88bbf..1ebea6fe7 100644 --- a/quantumclient/quantum/v2_0/extension.py +++ b/quantumclient/quantum/v2_0/extension.py @@ -17,79 +17,28 @@ import logging -from cliff import lister -from cliff import show +from quantumclient.quantum import v2_0 as cmd_base -from quantumclient.common import utils -from quantumclient.quantum.v2_0 import QuantumCommand +class ListExt(cmd_base.ListCommand): + """List all extensions.""" -class ListExt(QuantumCommand, lister.Lister): - """List all exts.""" - - api = 'network' resource = 'extension' log = logging.getLogger(__name__ + '.ListExt') - _formatters = None - - def get_parser(self, prog_name): - parser = super(ListExt, self).get_parser(prog_name) - return parser - - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.get_client() - search_opts = {} - quantum_client.format = parsed_args.request_format - obj_lister = getattr(quantum_client, - "list_%ss" % self.resource) - data = obj_lister(**search_opts) - info = [] - collection = self.resource + "s" - if collection in data: - info = data[collection] - _columns = len(info) > 0 and sorted(info[0].keys()) or [] - return (_columns, (utils.get_item_properties(s, _columns) - for s in info)) + list_columns = ['alias', 'name'] -class ShowExt(QuantumCommand, show.ShowOne): - """Show information of a given resource +class ShowExt(cmd_base.ShowCommand): + """Show information of a given resource.""" - """ - api = 'network' resource = "extension" log = logging.getLogger(__name__ + '.ShowExt') + allow_names = False def get_parser(self, prog_name): - parser = super(ShowExt, self).get_parser(prog_name) + parser = super(cmd_base.ShowCommand, self).get_parser(prog_name) + cmd_base.add_show_list_common_argument(parser) parser.add_argument( - 'ext_alias', metavar='ext-alias', + 'id', metavar='EXT-ALIAS', help='the extension alias') return parser - - def get_data(self, parsed_args): - self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - params = {} - obj_shower = getattr(quantum_client, - "show_%s" % self.resource) - data = obj_shower(parsed_args.ext_alias, **params) - if self.resource in data: - for k, v in data[self.resource].iteritems(): - if isinstance(v, list): - value = "" - for _item in v: - if value: - value += "\n" - if isinstance(_item, dict): - value += utils.dumps(_item) - else: - value += str(_item) - data[self.resource][k] = value - elif v is None: - data[self.resource][k] = '' - return zip(*sorted(data[self.resource].iteritems())) - else: - return None diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index 3916527c5..3a4982ab5 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -31,7 +31,6 @@ class ListFloatingIP(ListCommand): resource = 'floatingip' log = logging.getLogger(__name__ + '.ListFloatingIP') - _formatters = {} list_columns = ['id', 'fixed_ip_address', 'floating_ip_address', 'port_id'] pagination_support = True diff --git a/quantumclient/quantum/v2_0/lb/healthmonitor.py b/quantumclient/quantum/v2_0/lb/healthmonitor.py index a4e7ee6c4..3ecdd5665 100644 --- a/quantumclient/quantum/v2_0/lb/healthmonitor.py +++ b/quantumclient/quantum/v2_0/lb/healthmonitor.py @@ -28,7 +28,6 @@ class ListHealthMonitor(quantumv20.ListCommand): resource = 'health_monitor' log = logging.getLogger(__name__ + '.ListHealthMonitor') list_columns = ['id', 'type', 'admin_state_up', 'status'] - _formatters = {} pagination_support = True sorting_support = True diff --git a/quantumclient/quantum/v2_0/lb/member.py b/quantumclient/quantum/v2_0/lb/member.py index 18e479adf..05fb430e2 100644 --- a/quantumclient/quantum/v2_0/lb/member.py +++ b/quantumclient/quantum/v2_0/lb/member.py @@ -30,7 +30,6 @@ class ListMember(quantumv20.ListCommand): list_columns = [ 'id', 'address', 'protocol_port', 'admin_state_up', 'status' ] - _formatters = {} pagination_support = True sorting_support = True diff --git a/quantumclient/quantum/v2_0/lb/pool.py b/quantumclient/quantum/v2_0/lb/pool.py index 26bf0c927..3d1d58066 100644 --- a/quantumclient/quantum/v2_0/lb/pool.py +++ b/quantumclient/quantum/v2_0/lb/pool.py @@ -29,7 +29,6 @@ class ListPool(quantumv20.ListCommand): log = logging.getLogger(__name__ + '.ListPool') list_columns = ['id', 'name', 'lb_method', 'protocol', 'admin_state_up', 'status'] - _formatters = {} pagination_support = True sorting_support = True diff --git a/quantumclient/quantum/v2_0/lb/vip.py b/quantumclient/quantum/v2_0/lb/vip.py index 4ee03a1e3..c3795c3c8 100644 --- a/quantumclient/quantum/v2_0/lb/vip.py +++ b/quantumclient/quantum/v2_0/lb/vip.py @@ -29,7 +29,6 @@ class ListVip(quantumv20.ListCommand): log = logging.getLogger(__name__ + '.ListVip') list_columns = ['id', 'name', 'algorithm', 'address', 'protocol', 'admin_state_up', 'status'] - _formatters = {} pagination_support = True sorting_support = True diff --git a/quantumclient/quantum/v2_0/nvp_qos_queue.py b/quantumclient/quantum/v2_0/nvp_qos_queue.py index 040ce161c..386b8879e 100644 --- a/quantumclient/quantum/v2_0/nvp_qos_queue.py +++ b/quantumclient/quantum/v2_0/nvp_qos_queue.py @@ -25,7 +25,6 @@ class ListQoSQueue(quantumv20.ListCommand): resource = 'qos_queue' log = logging.getLogger(__name__ + '.ListQoSQueue') - _formatters = {} list_columns = ['id', 'name', 'min', 'max', 'qos_marking', 'dscp', 'default'] diff --git a/quantumclient/quantum/v2_0/nvpnetworkgateway.py b/quantumclient/quantum/v2_0/nvpnetworkgateway.py index 2c668acf3..72a83b347 100644 --- a/quantumclient/quantum/v2_0/nvpnetworkgateway.py +++ b/quantumclient/quantum/v2_0/nvpnetworkgateway.py @@ -27,7 +27,6 @@ class ListNetworkGateway(quantumv20.ListCommand): """List network gateways for a given tenant.""" resource = RESOURCE - _formatters = {} log = logging.getLogger(__name__ + '.ListNetworkGateway') list_columns = ['id', 'name'] diff --git a/quantumclient/quantum/v2_0/quota.py b/quantumclient/quantum/v2_0/quota.py index bdff0bd6c..2b1752fc3 100644 --- a/quantumclient/quantum/v2_0/quota.py +++ b/quantumclient/quantum/v2_0/quota.py @@ -70,7 +70,6 @@ class ListQuota(QuantumCommand, lister.Lister): api = 'network' resource = 'quota' log = logging.getLogger(__name__ + '.ListQuota') - _formatters = None def get_parser(self, prog_name): parser = super(ListQuota, self).get_parser(prog_name) diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/quantumclient/quantum/v2_0/securitygroup.py index c66d012c3..5fa899c7e 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/quantumclient/quantum/v2_0/securitygroup.py @@ -26,7 +26,6 @@ class ListSecurityGroup(quantumv20.ListCommand): resource = 'security_group' log = logging.getLogger(__name__ + '.ListSecurityGroup') - _formatters = {} list_columns = ['id', 'name', 'description'] pagination_support = True sorting_support = True @@ -103,7 +102,6 @@ class ListSecurityGroupRule(quantumv20.ListCommand): resource = 'security_group_rule' log = logging.getLogger(__name__ + '.ListSecurityGroupRule') - _formatters = {} list_columns = ['id', 'security_group_id', 'direction', 'protocol', 'remote_ip_prefix', 'remote_group_id'] replace_rules = {'security_group_id': 'security_group', diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index fb55b1e4e..e4982b403 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -153,8 +153,8 @@ class Client(object): subnet_path = "/subnets/%s" quotas_path = "/quotas" quota_path = "/quotas/%s" - exts_path = "/extensions" - ext_path = "/extensions/%s" + extensions_path = "/extensions" + extension_path = "/extensions/%s" routers_path = "/routers" router_path = "/routers/%s" floatingips_path = "/floatingips" @@ -245,12 +245,12 @@ def delete_quota(self, tenant_id): @APIParamsCall def list_extensions(self, **_params): """Fetch a list of all exts on server side.""" - return self.get(self.exts_path, params=_params) + return self.get(self.extensions_path, params=_params) @APIParamsCall def show_extension(self, ext_alias, **_params): """Fetch a list of all exts on server side.""" - return self.get(self.ext_path % ext_alias, params=_params) + return self.get(self.extension_path % ext_alias, params=_params) @APIParamsCall def list_ports(self, retrieve_all=True, **_params): diff --git a/tests/unit/test_cli20.py b/tests/unit/test_cli20.py index 9370295df..aed748724 100644 --- a/tests/unit/test_cli20.py +++ b/tests/unit/test_cli20.py @@ -145,6 +145,7 @@ class CLITestV20Base(testtools.TestCase): format = 'json' test_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' + id_field = 'id' def _find_resourceid(self, client, resource, name_or_id): return name_or_id @@ -206,7 +207,7 @@ def _test_create_resource(self, resource, cmd, for i in xrange(len(position_names)): body[resource].update({position_names[i]: position_values[i]}) ress = {resource: - {'id': myid}, } + {self.id_field: myid}, } if name: ress[resource].update({'name': name}) self.client.format = self.format @@ -253,12 +254,16 @@ def _test_list_columns(self, cmd, resources_collection, def _test_list_resources(self, resources, cmd, detail=False, tags=[], fields_1=[], fields_2=[], page_size=None, - sort_key=[], sort_dir=[]): + sort_key=[], sort_dir=[], response_contents=None): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) - reses = {resources: [{'id': 'myid1', }, - {'id': 'myid2', }, ], } + if response_contents is None: + contents = [{self.id_field: 'myid1', }, + {self.id_field: 'myid2', }, ] + else: + contents = response_contents + reses = {resources: contents} self.client.format = self.format resstr = self.client.serialize(reses) # url method body @@ -335,7 +340,9 @@ def _test_list_resources(self, resources, cmd, detail=False, tags=[], self.mox.VerifyAll() self.mox.UnsetStubs() _str = self.fake_stdout.make_string() - self.assertTrue('myid1' in _str) + if response_contents is None: + self.assertTrue('myid1' in _str) + return _str def _test_list_resources_with_pagination(self, resources, cmd): self.mox.StubOutWithMock(cmd, "get_client") @@ -399,8 +406,8 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]): cmd.get_client().MultipleTimes().AndReturn(self.client) query = "&".join(["fields=%s" % field for field in fields]) expected_res = {resource: - {'id': myid, - 'name': 'myname', }, } + {self.id_field: myid, + 'name': 'myname', }, } self.client.format = self.format resstr = self.client.serialize(expected_res) path = getattr(self.client, resource + "_path") diff --git a/tests/unit/test_cli20_extensions.py b/tests/unit/test_cli20_extensions.py new file mode 100644 index 000000000..874de77ba --- /dev/null +++ b/tests/unit/test_cli20_extensions.py @@ -0,0 +1,49 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 NEC Corporation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sys + +from quantumclient.quantum.v2_0.extension import ListExt +from quantumclient.quantum.v2_0.extension import ShowExt +from tests.unit.test_cli20 import CLITestV20Base +from tests.unit.test_cli20 import MyApp + + +class CLITestV20Extension(CLITestV20Base): + id_field = 'alias' + + def test_list_extensions(self): + resources = 'extensions' + cmd = ListExt(MyApp(sys.stdout), None) + contents = [{'alias': 'ext1', 'name': 'name1', 'other': 'other1'}, + {'alias': 'ext2', 'name': 'name2', 'other': 'other2'}] + ret = self._test_list_resources(resources, cmd, + response_contents=contents) + ret_words = set(ret.split()) + # Check only the default columns are shown. + self.assertTrue('name' in ret_words) + self.assertTrue('alias' in ret_words) + self.assertFalse('other' in ret_words) + + def test_show_extension(self): + # -F option does not work for ext-show at the moment, so -F option + # is not passed in the commandline args as other tests do. + resource = 'extension' + cmd = ShowExt(MyApp(sys.stdout), None) + args = [self.test_id] + ext_alias = self.test_id + self._test_show_resource(resource, cmd, ext_alias, args, fields=[]) From ae2a91f25513c4f572f70f2cf664353f7cbcb3aa Mon Sep 17 00:00:00 2001 From: "Carlos D. Garza" Date: Fri, 24 May 2013 19:25:21 -0500 Subject: [PATCH 118/135] Rename requires files to standard names. Rename tools/pip-requires to requirements.txt and tools/test-requires to test-requirements.txt. These are standard files, and tools in the general world are growing intelligence about them. adding test-requirements.txt to quantumclient/openstack/common/setup.py per review instructions. Change-Id: Ibf991b400bfbeb4222216649901586e01a0255b1 Fixes: bug #1179008 --- tools/pip-requires => requirements.txt | 0 tools/test-requires => test-requirements.txt | 0 tox.ini | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename tools/pip-requires => requirements.txt (100%) rename tools/test-requires => test-requirements.txt (100%) diff --git a/tools/pip-requires b/requirements.txt similarity index 100% rename from tools/pip-requires rename to requirements.txt diff --git a/tools/test-requires b/test-requirements.txt similarity index 100% rename from tools/test-requires rename to test-requirements.txt diff --git a/tox.ini b/tox.ini index 46d491e84..61a839c56 100644 --- a/tox.ini +++ b/tox.ini @@ -6,8 +6,8 @@ setenv = VIRTUAL_ENV={envdir} LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=C -deps = -r{toxinidir}/tools/pip-requires - -r{toxinidir}/tools/test-requires +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] From 92811c76f3a8308b36f81e61451ec17d227b453b Mon Sep 17 00:00:00 2001 From: Mark McClain Date: Wed, 29 May 2013 08:40:03 -0400 Subject: [PATCH 119/135] add readme for 2.2.2 Change-Id: Id32a4a72ec1d13992b306c4a38e73605758e26c7 --- doc/source/index.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/source/index.rst b/doc/source/index.rst index 41fe6ce3b..f68f1e103 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -53,3 +53,11 @@ Release Notes * add commands for DHCP and L3 agents scheduling * support XML request format * support pagination options + +2.2.2 +----- +* improved support for listing a large number of filtered subnets +* add --endpoint-type and OS_ENDPOINT_TYPE to shell client +* made the publicURL the default endpoint instead of adminURL +* add ability to update security group name (requires 2013.2-Havana or later) +* add flake8 and pbr support for testing and building From 5326cc713a428d5ec3905a1bffa2848ad80cdfbe Mon Sep 17 00:00:00 2001 From: Dirk Mueller Date: Sat, 1 Jun 2013 10:08:50 +0200 Subject: [PATCH 120/135] Rename README to README.rst README.rst seems to be more standard accross OpenStack modules. Change-Id: I82cd80e1e816b9c2e9774ad87c1a68bcf7dd8342 --- MANIFEST.in | 2 +- README => README.rst | 0 setup.cfg | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename README => README.rst (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 56a029e10..c0f014e74 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,5 @@ include tox.ini -include LICENSE README HACKING.rst +include LICENSE README.rst HACKING.rst include AUTHORS include ChangeLog include tools/* diff --git a/README b/README.rst similarity index 100% rename from README rename to README.rst diff --git a/setup.cfg b/setup.cfg index 5d633ea3b..3c67b09b9 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ name = python-quantumclient summary = CLI and Client Library for OpenStack Networking description-file = - README + README.rst author = OpenStack Networking Project author-email = openstack-dev@lists.openstack.org home-page = http://www.openstack.org/ From 2bc41fb43fe63d246af3a21e6bb845f64162eceb Mon Sep 17 00:00:00 2001 From: arosen Date: Fri, 7 Jun 2013 21:01:16 -0700 Subject: [PATCH 121/135] Add metavar for --fixed-ip This patch adds a metavar for the --fixed-ip option so that it's more clear that the user needs to pass in ip_address=IP_ADDR. Fixes bug 1188889 Change-Id: I66efd444a2df52603f124637072f194497e241e2 --- quantumclient/quantum/v2_0/port.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 1c1c91045..343497c51 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -109,7 +109,7 @@ def add_known_arguments(self, parser): '--device_id', help=argparse.SUPPRESS) parser.add_argument( - '--fixed-ip', + '--fixed-ip', metavar='ip_address=IP_ADDR', action='append', help='desired IP for this port: ' 'subnet_id=,ip_address=, ' From 5be031f81f76d68c6e4cbaad2247044aca179843 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Tue, 11 Jun 2013 11:38:09 -0700 Subject: [PATCH 122/135] Remove explicit distribute depend. Causes issues with the recent re-merge with setuptools. Advice from upstream is to stop doing explicit depends. Change-Id: I70638f239794e78ba049c60d2001190910a89c90 --- test-requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index a9e4dffc3..34b353c7c 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,7 +7,6 @@ hacking>=0.5.3,<0.6 cliff-tablib>=1.0 coverage discover -distribute>=0.6.24 fixtures>=0.3.12 mox python-subunit From b5a416ac344160512f95751ae16e6612aefd4a57 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Tue, 21 May 2013 15:56:26 +0900 Subject: [PATCH 123/135] Remove class-based import in the code repo Fixes bug 1167901 This commit also removes backslashes for line break. Change-Id: Id26fdfd2af4862652d7270aec132d40662efeb96 --- quantumclient/common/clientmanager.py | 16 +-- quantumclient/common/command.py | 4 +- quantumclient/quantum/v2_0/floatingip.py | 17 +-- quantumclient/quantum/v2_0/network.py | 16 +-- quantumclient/quantum/v2_0/port.py | 17 +-- quantumclient/quantum/v2_0/quota.py | 9 +- quantumclient/quantum/v2_0/router.py | 22 ++-- quantumclient/quantum/v2_0/subnet.py | 15 +-- quantumclient/shell.py | 8 +- quantumclient/v2_0/client.py | 4 +- tests/unit/lb/test_cli20_healthmonitor.py | 16 +-- tests/unit/lb/test_cli20_pool.py | 6 +- tests/unit/test_auth.py | 138 +++++++++++---------- tests/unit/test_cli20.py | 61 +++++---- tests/unit/test_cli20_floatingips.py | 34 +++-- tests/unit/test_cli20_network.py | 127 +++++++++---------- tests/unit/test_cli20_nvpnetworkgateway.py | 26 ++-- tests/unit/test_cli20_port.py | 62 ++++----- tests/unit/test_cli20_router.py | 49 +++----- tests/unit/test_cli20_subnet.py | 65 +++++----- tests/unit/test_name_or_id.py | 36 +++--- 21 files changed, 340 insertions(+), 408 deletions(-) diff --git a/quantumclient/common/clientmanager.py b/quantumclient/common/clientmanager.py index 40550d466..4d219e489 100644 --- a/quantumclient/common/clientmanager.py +++ b/quantumclient/common/clientmanager.py @@ -20,7 +20,7 @@ import logging -from quantumclient.client import HTTPClient +from quantumclient import client from quantumclient.quantum import client as quantum_client @@ -74,13 +74,13 @@ def __init__(self, token=None, url=None, def initialize(self): if not self._url: - httpclient = HTTPClient(username=self._username, - tenant_name=self._tenant_name, - password=self._password, - region_name=self._region_name, - auth_url=self._auth_url, - endpoint_type=self._endpoint_type, - insecure=self._insecure) + httpclient = client.HTTPClient(username=self._username, + tenant_name=self._tenant_name, + password=self._password, + region_name=self._region_name, + auth_url=self._auth_url, + endpoint_type=self._endpoint_type, + insecure=self._insecure) httpclient.authenticate() # Populate other password flow attributes self._token = httpclient.auth_token diff --git a/quantumclient/common/command.py b/quantumclient/common/command.py index f2706d019..719143642 100644 --- a/quantumclient/common/command.py +++ b/quantumclient/common/command.py @@ -19,10 +19,10 @@ OpenStack base command """ -from cliff.command import Command +from cliff import command -class OpenStackCommand(Command): +class OpenStackCommand(command.Command): """Base class for OpenStack commands """ diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index 3a4982ab5..7bce710c8 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -19,14 +19,9 @@ import logging from quantumclient.quantum import v2_0 as quantumv20 -from quantumclient.quantum.v2_0 import CreateCommand -from quantumclient.quantum.v2_0 import DeleteCommand -from quantumclient.quantum.v2_0 import ListCommand -from quantumclient.quantum.v2_0 import QuantumCommand -from quantumclient.quantum.v2_0 import ShowCommand -class ListFloatingIP(ListCommand): +class ListFloatingIP(quantumv20.ListCommand): """List floating ips that belong to a given tenant.""" resource = 'floatingip' @@ -37,7 +32,7 @@ class ListFloatingIP(ListCommand): sorting_support = True -class ShowFloatingIP(ShowCommand): +class ShowFloatingIP(quantumv20.ShowCommand): """Show information of a given floating ip.""" resource = 'floatingip' @@ -45,7 +40,7 @@ class ShowFloatingIP(ShowCommand): allow_names = False -class CreateFloatingIP(CreateCommand): +class CreateFloatingIP(quantumv20.CreateCommand): """Create a floating ip for a given tenant.""" resource = 'floatingip' @@ -83,7 +78,7 @@ def args2body(self, parsed_args): return body -class DeleteFloatingIP(DeleteCommand): +class DeleteFloatingIP(quantumv20.DeleteCommand): """Delete a given floating ip.""" log = logging.getLogger(__name__ + '.DeleteFloatingIP') @@ -91,7 +86,7 @@ class DeleteFloatingIP(DeleteCommand): allow_names = False -class AssociateFloatingIP(QuantumCommand): +class AssociateFloatingIP(quantumv20.QuantumCommand): """Create a mapping between a floating ip and a fixed ip.""" api = 'network' @@ -130,7 +125,7 @@ def run(self, parsed_args): _('Associated floatingip %s') % parsed_args.floatingip_id) -class DisassociateFloatingIP(QuantumCommand): +class DisassociateFloatingIP(quantumv20.QuantumCommand): """Remove a mapping from a floating ip to a fixed ip. """ diff --git a/quantumclient/quantum/v2_0/network.py b/quantumclient/quantum/v2_0/network.py index 52cbc46e9..31e621f3f 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/quantumclient/quantum/v2_0/network.py @@ -19,11 +19,7 @@ import logging from quantumclient.common import exceptions -from quantumclient.quantum.v2_0 import CreateCommand -from quantumclient.quantum.v2_0 import DeleteCommand -from quantumclient.quantum.v2_0 import ListCommand -from quantumclient.quantum.v2_0 import ShowCommand -from quantumclient.quantum.v2_0 import UpdateCommand +from quantumclient.quantum import v2_0 as quantumv20 def _format_subnets(network): @@ -34,7 +30,7 @@ def _format_subnets(network): return '' -class ListNetwork(ListCommand): +class ListNetwork(quantumv20.ListCommand): """List networks that belong to a given tenant.""" # Length of a query filter on subnet id @@ -101,14 +97,14 @@ def retrieve_list(self, parsed_args): return super(ListExternalNetwork, self).retrieve_list(parsed_args) -class ShowNetwork(ShowCommand): +class ShowNetwork(quantumv20.ShowCommand): """Show information of a given network.""" resource = 'network' log = logging.getLogger(__name__ + '.ShowNetwork') -class CreateNetwork(CreateCommand): +class CreateNetwork(quantumv20.CreateCommand): """Create a network for a given tenant.""" resource = 'network' @@ -142,14 +138,14 @@ def args2body(self, parsed_args): return body -class DeleteNetwork(DeleteCommand): +class DeleteNetwork(quantumv20.DeleteCommand): """Delete a given network.""" log = logging.getLogger(__name__ + '.DeleteNetwork') resource = 'network' -class UpdateNetwork(UpdateCommand): +class UpdateNetwork(quantumv20.UpdateCommand): """Update network's information.""" log = logging.getLogger(__name__ + '.UpdateNetwork') diff --git a/quantumclient/quantum/v2_0/port.py b/quantumclient/quantum/v2_0/port.py index 1c1c91045..832f86df2 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/quantumclient/quantum/v2_0/port.py @@ -20,11 +20,6 @@ from quantumclient.common import utils from quantumclient.quantum import v2_0 as quantumv20 -from quantumclient.quantum.v2_0 import CreateCommand -from quantumclient.quantum.v2_0 import DeleteCommand -from quantumclient.quantum.v2_0 import ListCommand -from quantumclient.quantum.v2_0 import ShowCommand -from quantumclient.quantum.v2_0 import UpdateCommand def _format_fixed_ips(port): @@ -34,7 +29,7 @@ def _format_fixed_ips(port): return '' -class ListPort(ListCommand): +class ListPort(quantumv20.ListCommand): """List ports that belong to a given tenant.""" resource = 'port' @@ -45,7 +40,7 @@ class ListPort(ListCommand): sorting_support = True -class ListRouterPort(ListCommand): +class ListRouterPort(quantumv20.ListCommand): """List ports that belong to a given tenant, with specified router.""" resource = 'port' @@ -71,14 +66,14 @@ def get_data(self, parsed_args): return super(ListRouterPort, self).get_data(parsed_args) -class ShowPort(ShowCommand): +class ShowPort(quantumv20.ShowCommand): """Show information of a given port.""" resource = 'port' log = logging.getLogger(__name__ + '.ShowPort') -class CreatePort(CreateCommand): +class CreatePort(quantumv20.CreateCommand): """Create a port for a given tenant.""" resource = 'port' @@ -163,14 +158,14 @@ def args2body(self, parsed_args): return body -class DeletePort(DeleteCommand): +class DeletePort(quantumv20.DeleteCommand): """Delete a given port.""" resource = 'port' log = logging.getLogger(__name__ + '.DeletePort') -class UpdatePort(UpdateCommand): +class UpdatePort(quantumv20.UpdateCommand): """Update port's information.""" resource = 'port' diff --git a/quantumclient/quantum/v2_0/quota.py b/quantumclient/quantum/v2_0/quota.py index 2b1752fc3..1213002c0 100644 --- a/quantumclient/quantum/v2_0/quota.py +++ b/quantumclient/quantum/v2_0/quota.py @@ -24,7 +24,6 @@ from quantumclient.common import exceptions from quantumclient.common import utils from quantumclient.quantum import v2_0 as quantumv20 -from quantumclient.quantum.v2_0 import QuantumCommand def get_tenant_id(tenant_id, client): @@ -32,7 +31,7 @@ def get_tenant_id(tenant_id, client): client.get_quotas_tenant()['tenant']['tenant_id']) -class DeleteQuota(QuantumCommand): +class DeleteQuota(quantumv20.QuantumCommand): """Delete defined quotas of a given tenant.""" api = 'network' @@ -64,7 +63,7 @@ def run(self, parsed_args): return -class ListQuota(QuantumCommand, lister.Lister): +class ListQuota(quantumv20.QuantumCommand, lister.Lister): """List defined quotas of all tenants.""" api = 'network' @@ -93,7 +92,7 @@ def get_data(self, parsed_args): for s in info)) -class ShowQuota(QuantumCommand, show.ShowOne): +class ShowQuota(quantumv20.QuantumCommand, show.ShowOne): """Show quotas of a given tenant """ @@ -140,7 +139,7 @@ def get_data(self, parsed_args): return None -class UpdateQuota(QuantumCommand, show.ShowOne): +class UpdateQuota(quantumv20.QuantumCommand, show.ShowOne): """Define tenant's quotas not to use defaults.""" resource = 'quota' diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index cff4ace0c..693fd5681 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -20,12 +20,6 @@ from quantumclient.common import utils from quantumclient.quantum import v2_0 as quantumv20 -from quantumclient.quantum.v2_0 import CreateCommand -from quantumclient.quantum.v2_0 import DeleteCommand -from quantumclient.quantum.v2_0 import ListCommand -from quantumclient.quantum.v2_0 import QuantumCommand -from quantumclient.quantum.v2_0 import ShowCommand -from quantumclient.quantum.v2_0 import UpdateCommand def _format_external_gateway_info(router): @@ -35,7 +29,7 @@ def _format_external_gateway_info(router): return '' -class ListRouter(ListCommand): +class ListRouter(quantumv20.ListCommand): """List routers that belong to a given tenant.""" resource = 'router' @@ -46,14 +40,14 @@ class ListRouter(ListCommand): sorting_support = True -class ShowRouter(ShowCommand): +class ShowRouter(quantumv20.ShowCommand): """Show information of a given router.""" resource = 'router' log = logging.getLogger(__name__ + '.ShowRouter') -class CreateRouter(CreateCommand): +class CreateRouter(quantumv20.CreateCommand): """Create a router for a given tenant.""" resource = 'router' @@ -82,21 +76,21 @@ def args2body(self, parsed_args): return body -class DeleteRouter(DeleteCommand): +class DeleteRouter(quantumv20.DeleteCommand): """Delete a given router.""" log = logging.getLogger(__name__ + '.DeleteRouter') resource = 'router' -class UpdateRouter(UpdateCommand): +class UpdateRouter(quantumv20.UpdateCommand): """Update router's information.""" log = logging.getLogger(__name__ + '.UpdateRouter') resource = 'router' -class RouterInterfaceCommand(QuantumCommand): +class RouterInterfaceCommand(quantumv20.QuantumCommand): """Based class to Add/Remove router interface.""" api = 'network' @@ -151,7 +145,7 @@ def run(self, parsed_args): _('Removed interface from router %s') % parsed_args.router_id) -class SetGatewayRouter(QuantumCommand): +class SetGatewayRouter(quantumv20.QuantumCommand): """Set the external network gateway for a router.""" log = logging.getLogger(__name__ + '.SetGatewayRouter') @@ -187,7 +181,7 @@ def run(self, parsed_args): _('Set gateway for router %s') % parsed_args.router_id) -class RemoveGatewayRouter(QuantumCommand): +class RemoveGatewayRouter(quantumv20.QuantumCommand): """Remove an external network gateway from a router.""" log = logging.getLogger(__name__ + '.RemoveGatewayRouter') diff --git a/quantumclient/quantum/v2_0/subnet.py b/quantumclient/quantum/v2_0/subnet.py index a711f057e..e79cdc050 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/quantumclient/quantum/v2_0/subnet.py @@ -21,11 +21,6 @@ from quantumclient.common import exceptions from quantumclient.common import utils from quantumclient.quantum import v2_0 as quantumv20 -from quantumclient.quantum.v2_0 import CreateCommand -from quantumclient.quantum.v2_0 import DeleteCommand -from quantumclient.quantum.v2_0 import ListCommand -from quantumclient.quantum.v2_0 import ShowCommand -from quantumclient.quantum.v2_0 import UpdateCommand def _format_allocation_pools(subnet): @@ -52,7 +47,7 @@ def _format_host_routes(subnet): return '' -class ListSubnet(ListCommand): +class ListSubnet(quantumv20.ListCommand): """List networks that belong to a given tenant.""" resource = 'subnet' @@ -65,14 +60,14 @@ class ListSubnet(ListCommand): sorting_support = True -class ShowSubnet(ShowCommand): +class ShowSubnet(quantumv20.ShowCommand): """Show information of a given subnet.""" resource = 'subnet' log = logging.getLogger(__name__ + '.ShowSubnet') -class CreateSubnet(CreateCommand): +class CreateSubnet(quantumv20.CreateCommand): """Create a subnet for a given tenant.""" resource = 'subnet' @@ -159,14 +154,14 @@ def args2body(self, parsed_args): return body -class DeleteSubnet(DeleteCommand): +class DeleteSubnet(quantumv20.DeleteCommand): """Delete a given subnet.""" resource = 'subnet' log = logging.getLogger(__name__ + '.DeleteSubnet') -class UpdateSubnet(UpdateCommand): +class UpdateSubnet(quantumv20.UpdateCommand): """Update subnet's information.""" resource = 'subnet' diff --git a/quantumclient/shell.py b/quantumclient/shell.py index afcabb7a0..7c6618be0 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -25,8 +25,8 @@ import os import sys -from cliff.app import App -from cliff.commandmanager import CommandManager +from cliff import app +from cliff import commandmanager from quantumclient.common import clientmanager from quantumclient.common import exceptions as exc @@ -287,7 +287,7 @@ def __call__(self, parser, namespace, values, option_string=None): sys.exit(0) -class QuantumShell(App): +class QuantumShell(app.App): CONSOLE_MESSAGE_FORMAT = '%(message)s' DEBUG_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' @@ -297,7 +297,7 @@ def __init__(self, apiversion): super(QuantumShell, self).__init__( description=__doc__.strip(), version=VERSION, - command_manager=CommandManager('quantum.cli'), ) + command_manager=commandmanager.CommandManager('quantum.cli'), ) self.commands = COMMANDS for k, v in self.commands[apiversion].items(): self.command_manager.add_command(k, v) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index e4982b403..9303ca967 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -21,7 +21,7 @@ import urllib import urlparse -from quantumclient.client import HTTPClient +from quantumclient import client from quantumclient.common import _ from quantumclient.common import constants from quantumclient.common import exceptions @@ -887,7 +887,7 @@ def remove_router_from_l3_agent(self, l3_agent, router_id): def __init__(self, **kwargs): """Initialize a new client for the Quantum v2.0 API.""" super(Client, self).__init__() - self.httpclient = HTTPClient(**kwargs) + self.httpclient = client.HTTPClient(**kwargs) self.version = '2.0' self.format = 'json' self.action_prefix = "/v%s" % (self.version) diff --git a/tests/unit/lb/test_cli20_healthmonitor.py b/tests/unit/lb/test_cli20_healthmonitor.py index cdff23188..0b87ff95a 100644 --- a/tests/unit/lb/test_cli20_healthmonitor.py +++ b/tests/unit/lb/test_cli20_healthmonitor.py @@ -19,7 +19,7 @@ import sys -from mox import ContainsKeyValue +import mox from quantumclient.quantum.v2_0.lb import healthmonitor from tests.unit import test_cli20 @@ -176,8 +176,8 @@ def test_associate_healthmonitor(self): self.client.httpclient.request( test_cli20.end_url(path), 'POST', body=test_cli20.MyComparator(body, self.client), - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn(return_tup) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(return_tup) self.mox.ReplayAll() cmd_parser = cmd.get_parser('test_' + resource) parsed_args = cmd_parser.parse_args(args) @@ -198,15 +198,15 @@ def test_disassociate_healthmonitor(self): self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) - path = getattr(self.client, - "disassociate_pool_health_monitors_path") % \ - {'pool': pool_id, 'health_monitor': health_monitor_id} + path = (getattr(self.client, + "disassociate_pool_health_monitors_path") % + {'pool': pool_id, 'health_monitor': health_monitor_id}) return_tup = (test_cli20.MyResp(204), None) self.client.httpclient.request( test_cli20.end_url(path), 'DELETE', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn(return_tup) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(return_tup) self.mox.ReplayAll() cmd_parser = cmd.get_parser('test_' + resource) parsed_args = cmd_parser.parse_args(args) diff --git a/tests/unit/lb/test_cli20_pool.py b/tests/unit/lb/test_cli20_pool.py index 3683b9d52..709c3d181 100644 --- a/tests/unit/lb/test_cli20_pool.py +++ b/tests/unit/lb/test_cli20_pool.py @@ -19,7 +19,7 @@ import sys -from mox import ContainsKeyValue +import mox from quantumclient.quantum.v2_0.lb import pool from tests.unit import test_cli20 @@ -152,8 +152,8 @@ def test_retrieve_pool_stats(self): self.client.httpclient.request( test_cli20.end_url(path % my_id, query), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn(return_tup) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(return_tup) self.mox.ReplayAll() cmd_parser = cmd.get_parser("test_" + resource) diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index 4f09eacfe..06cc2de0b 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -21,12 +21,10 @@ import uuid import mox -from mox import ContainsKeyValue, IsA, StrContains import testtools -from quantumclient.client import exceptions -from quantumclient.client import HTTPClient -from quantumclient.client import ServiceCatalog +from quantumclient import client +from quantumclient.common import exceptions USERNAME = 'testuser' @@ -73,9 +71,11 @@ def setUp(self): """Prepare the test environment.""" super(CLITestAuthKeystone, self).setUp() self.mox = mox.Mox() - self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, - password=PASSWORD, auth_url=AUTH_URL, - region_name=REGION) + self.client = client.HTTPClient(username=USERNAME, + tenant_name=TENANT_NAME, + password=PASSWORD, + auth_url=AUTH_URL, + region_name=REGION) self.addCleanup(self.mox.VerifyAll) self.addCleanup(self.mox.UnsetStubs) @@ -85,12 +85,14 @@ def test_get_token(self): res200 = self.mox.CreateMock(httplib2.Response) res200.status = 200 - self.client.request(AUTH_URL + '/tokens', 'POST', - body=IsA(str), headers=IsA(dict)).\ - AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) - self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', - headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ - AndReturn((res200, '')) + self.client.request( + AUTH_URL + '/tokens', 'POST', + body=mox.IsA(str), headers=mox.IsA(dict) + ).AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) + self.client.request( + mox.StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=mox.ContainsKeyValue('X-Auth-Token', TOKEN) + ).AndReturn((res200, '')) self.mox.ReplayAll() self.client.do_request('/resource', 'GET') @@ -109,15 +111,18 @@ def test_refresh_token(self): res401.status = 401 # If a token is expired, quantum server retruns 401 - self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', - headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ - AndReturn((res401, '')) - self.client.request(AUTH_URL + '/tokens', 'POST', - body=IsA(str), headers=IsA(dict)).\ - AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) - self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', - headers=ContainsKeyValue('X-Auth-Token', TOKEN)).\ - AndReturn((res200, '')) + self.client.request( + mox.StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=mox.ContainsKeyValue('X-Auth-Token', TOKEN) + ).AndReturn((res401, '')) + self.client.request( + AUTH_URL + '/tokens', 'POST', + body=mox.IsA(str), headers=mox.IsA(dict) + ).AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) + self.client.request( + mox.StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=mox.ContainsKeyValue('X-Auth-Token', TOKEN) + ).AndReturn((res200, '')) self.mox.ReplayAll() self.client.do_request('/resource', 'GET') @@ -129,20 +134,21 @@ def test_get_endpoint_url(self): res200 = self.mox.CreateMock(httplib2.Response) res200.status = 200 - self.client.request(StrContains(AUTH_URL + - '/tokens/%s/endpoints' % TOKEN), 'GET', - headers=IsA(dict)). \ - AndReturn((res200, json.dumps(ENDPOINTS_RESULT))) - self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', - headers=ContainsKeyValue('X-Auth-Token', TOKEN)). \ - AndReturn((res200, '')) + self.client.request( + mox.StrContains(AUTH_URL + '/tokens/%s/endpoints' % TOKEN), 'GET', + headers=mox.IsA(dict) + ).AndReturn((res200, json.dumps(ENDPOINTS_RESULT))) + self.client.request( + mox.StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=mox.ContainsKeyValue('X-Auth-Token', TOKEN) + ).AndReturn((res200, '')) self.mox.ReplayAll() self.client.do_request('/resource', 'GET') def test_get_endpoint_url_other(self): - self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, - password=PASSWORD, auth_url=AUTH_URL, - region_name=REGION, endpoint_type='otherURL') + self.client = client.HTTPClient( + username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, + auth_url=AUTH_URL, region_name=REGION, endpoint_type='otherURL') self.mox.StubOutWithMock(self.client, "request") self.client.auth_token = TOKEN @@ -150,10 +156,10 @@ def test_get_endpoint_url_other(self): res200 = self.mox.CreateMock(httplib2.Response) res200.status = 200 - self.client.request(StrContains(AUTH_URL + - '/tokens/%s/endpoints' % TOKEN), 'GET', - headers=IsA(dict)). \ - AndReturn((res200, json.dumps(ENDPOINTS_RESULT))) + self.client.request( + mox.StrContains(AUTH_URL + '/tokens/%s/endpoints' % TOKEN), 'GET', + headers=mox.IsA(dict) + ).AndReturn((res200, json.dumps(ENDPOINTS_RESULT))) self.mox.ReplayAll() self.assertRaises(exceptions.EndpointTypeNotFound, self.client.do_request, @@ -170,16 +176,18 @@ def test_get_endpoint_url_failed(self): res401 = self.mox.CreateMock(httplib2.Response) res401.status = 401 - self.client.request(StrContains(AUTH_URL + - '/tokens/%s/endpoints' % TOKEN), 'GET', - headers=IsA(dict)). \ - AndReturn((res401, '')) - self.client.request(AUTH_URL + '/tokens', 'POST', - body=IsA(str), headers=IsA(dict)). \ - AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) - self.client.request(StrContains(ENDPOINT_URL + '/resource'), 'GET', - headers=ContainsKeyValue('X-Auth-Token', TOKEN)). \ - AndReturn((res200, '')) + self.client.request( + mox.StrContains(AUTH_URL + '/tokens/%s/endpoints' % TOKEN), 'GET', + headers=mox.IsA(dict) + ).AndReturn((res401, '')) + self.client.request( + AUTH_URL + '/tokens', 'POST', + body=mox.IsA(str), headers=mox.IsA(dict) + ).AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) + self.client.request( + mox.StrContains(ENDPOINT_URL + '/resource'), 'GET', + headers=mox.ContainsKeyValue('X-Auth-Token', TOKEN) + ).AndReturn((res200, '')) self.mox.ReplayAll() self.client.do_request('/resource', 'GET') @@ -190,7 +198,7 @@ def test_url_for(self): endpoints['publicURL'] = 'public' endpoints['internalURL'] = 'internal' endpoints['adminURL'] = 'admin' - catalog = ServiceCatalog(resources) + catalog = client.ServiceCatalog(resources) # endpoint_type not specified url = catalog.url_for(attr='region', @@ -223,7 +231,7 @@ def test_url_for(self): # Test scenario with url_for when the service catalog only has publicURL. def test_url_for_only_public_url(self): resources = copy.deepcopy(KS_TOKEN_RESULT) - catalog = ServiceCatalog(resources) + catalog = client.ServiceCatalog(resources) # Remove endpoints from the catalog. endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0] @@ -245,7 +253,7 @@ def test_url_for_only_public_url(self): # Test scenario with url_for when the service catalog only has adminURL. def test_url_for_only_admin_url(self): resources = copy.deepcopy(KS_TOKEN_RESULT) - catalog = ServiceCatalog(resources) + catalog = client.ServiceCatalog(resources) endpoints = resources['access']['serviceCatalog'][0]['endpoints'][0] del endpoints['internalURL'] del endpoints['publicURL'] @@ -271,43 +279,41 @@ def test_endpoint_type(self): endpoints['publicURL'] = 'public' # Test default behavior is to choose public. - self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, - password=PASSWORD, auth_url=AUTH_URL, - region_name=REGION) + self.client = client.HTTPClient( + username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, + auth_url=AUTH_URL, region_name=REGION) self.client._extract_service_catalog(resources) self.assertEqual(self.client.endpoint_url, 'public') # Test admin url - self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, - password=PASSWORD, auth_url=AUTH_URL, - region_name=REGION, endpoint_type='adminURL') + self.client = client.HTTPClient( + username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, + auth_url=AUTH_URL, region_name=REGION, endpoint_type='adminURL') self.client._extract_service_catalog(resources) self.assertEqual(self.client.endpoint_url, 'admin') # Test public url - self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, - password=PASSWORD, auth_url=AUTH_URL, - region_name=REGION, endpoint_type='publicURL') + self.client = client.HTTPClient( + username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, + auth_url=AUTH_URL, region_name=REGION, endpoint_type='publicURL') self.client._extract_service_catalog(resources) self.assertEqual(self.client.endpoint_url, 'public') # Test internal url - self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, - password=PASSWORD, auth_url=AUTH_URL, - region_name=REGION, - endpoint_type='internalURL') + self.client = client.HTTPClient( + username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, + auth_url=AUTH_URL, region_name=REGION, endpoint_type='internalURL') self.client._extract_service_catalog(resources) self.assertEqual(self.client.endpoint_url, 'internal') # Test url that isn't found in the service catalog - self.client = HTTPClient(username=USERNAME, tenant_name=TENANT_NAME, - password=PASSWORD, auth_url=AUTH_URL, - region_name=REGION, - endpoint_type='privateURL') + self.client = client.HTTPClient( + username=USERNAME, tenant_name=TENANT_NAME, password=PASSWORD, + auth_url=AUTH_URL, region_name=REGION, endpoint_type='privateURL') self.assertRaises(exceptions.EndpointTypeNotFound, self.client._extract_service_catalog, diff --git a/tests/unit/test_cli20.py b/tests/unit/test_cli20.py index aed748724..0eec0c80c 100644 --- a/tests/unit/test_cli20.py +++ b/tests/unit/test_cli20.py @@ -19,13 +19,11 @@ import fixtures import mox -from mox import Comparator -from mox import ContainsKeyValue import testtools from quantumclient.common import constants from quantumclient import shell -from quantumclient.v2_0.client import Client +from quantumclient.v2_0 import client API_VERSION = "2.0" @@ -64,7 +62,7 @@ def end_url(path, query=None, format=FORMAT): return query and _url_str + "?" + query or _url_str -class MyUrlComparator(Comparator): +class MyUrlComparator(mox.Comparator): def __init__(self, lhs, client): self.lhs = lhs self.client = client @@ -89,7 +87,7 @@ def __repr__(self): return str(self) -class MyComparator(Comparator): +class MyComparator(mox.Comparator): def __init__(self, lhs, client): self.lhs = lhs self.client = client @@ -152,18 +150,18 @@ def _find_resourceid(self, client, resource, name_or_id): def _get_attr_metadata(self): return self.metadata - Client.EXTED_PLURALS.update(constants.PLURALS) - Client.EXTED_PLURALS.update({'tags': 'tag'}) - return {'plurals': Client.EXTED_PLURALS, + client.Client.EXTED_PLURALS.update(constants.PLURALS) + client.Client.EXTED_PLURALS.update({'tags': 'tag'}) + return {'plurals': client.Client.EXTED_PLURALS, 'xmlns': constants.XML_NS_V20, constants.EXT_NS: {'prefix': 'http://xxxx.yy.com'}} def setUp(self, plurals={}): """Prepare the test environment.""" super(CLITestV20Base, self).setUp() - Client.EXTED_PLURALS.update(constants.PLURALS) - Client.EXTED_PLURALS.update(plurals) - self.metadata = {'plurals': Client.EXTED_PLURALS, + client.Client.EXTED_PLURALS.update(constants.PLURALS) + client.Client.EXTED_PLURALS.update(plurals) + self.metadata = {'plurals': client.Client.EXTED_PLURALS, 'xmlns': constants.XML_NS_V20, constants.EXT_NS: {'prefix': 'http://xxxx.yy.com'}} @@ -177,7 +175,7 @@ def setUp(self, plurals={}): self.useFixture(fixtures.MonkeyPatch( 'quantumclient.v2_0.client.Client.get_attr_metadata', self._get_attr_metadata)) - self.client = Client(token=TOKEN, endpoint_url=self.endurl) + self.client = client.Client(token=TOKEN, endpoint_url=self.endurl) def _test_create_resource(self, resource, cmd, name, myid, args, @@ -217,9 +215,8 @@ def _test_create_resource(self, resource, cmd, self.client.httpclient.request( end_url(path, format=self.format), 'POST', body=MyComparator(body, self.client), - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(200), - resstr)) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser('create_' + resource) @@ -243,8 +240,8 @@ def _test_list_columns(self, cmd, resources_collection, self.client.httpclient.request( end_url(path, format=self.format), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(200), resstr)) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources_collection) @@ -332,8 +329,8 @@ def _test_list_resources(self, resources, cmd, detail=False, tags=[], self.client), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(200), resstr)) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) shell.run_command(cmd, cmd_parser, args) @@ -362,13 +359,13 @@ def _test_list_resources_with_pagination(self, resources, cmd): self.client.httpclient.request( end_url(path, "", format=self.format), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(200), resstr1)) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr1)) self.client.httpclient.request( end_url(path, fake_query, format=self.format), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(200), resstr2)) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr2)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) args = ['--request-format', self.format] @@ -389,8 +386,8 @@ def _test_update_resource(self, resource, cmd, myid, args, extrafields): self.client), 'PUT', body=MyComparator(body, self.client), - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(204), None)) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("update_" + resource) @@ -414,8 +411,8 @@ def _test_show_resource(self, resource, cmd, myid, args, fields=[]): self.client.httpclient.request( end_url(path % myid, query, format=self.format), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(200), resstr)) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(200), resstr)) args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("show_" + resource) @@ -434,8 +431,8 @@ def _test_delete_resource(self, resource, cmd, myid, args): self.client.httpclient.request( end_url(path % myid, format=self.format), 'DELETE', body=None, - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(204), None)) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("delete_" + resource) @@ -455,8 +452,8 @@ def _test_update_resource_action(self, resource, cmd, myid, action, args, self.client.httpclient.request( end_url(path % path_action, format=self.format), 'PUT', body=MyComparator(body, self.client), - headers=ContainsKeyValue('X-Auth-Token', - TOKEN)).AndReturn((MyResp(204), None)) + headers=mox.ContainsKeyValue( + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("delete_" + resource) @@ -489,7 +486,7 @@ def test_do_request(self): self.client.httpclient.request( end_url(expected_action, query=expect_query, format=self.format), 'PUT', body=expect_body, - headers=ContainsKeyValue( + headers=mox.ContainsKeyValue( 'X-Auth-Token', expected_auth_token)).AndReturn((MyResp(200), expect_body)) diff --git a/tests/unit/test_cli20_floatingips.py b/tests/unit/test_cli20_floatingips.py index f38242e8f..b640112b4 100644 --- a/tests/unit/test_cli20_floatingips.py +++ b/tests/unit/test_cli20_floatingips.py @@ -18,21 +18,15 @@ import sys -from quantumclient.quantum.v2_0.floatingip import AssociateFloatingIP -from quantumclient.quantum.v2_0.floatingip import CreateFloatingIP -from quantumclient.quantum.v2_0.floatingip import DeleteFloatingIP -from quantumclient.quantum.v2_0.floatingip import DisassociateFloatingIP -from quantumclient.quantum.v2_0.floatingip import ListFloatingIP -from quantumclient.quantum.v2_0.floatingip import ShowFloatingIP -from tests.unit.test_cli20 import CLITestV20Base -from tests.unit.test_cli20 import MyApp +from quantumclient.quantum.v2_0 import floatingip as fip +from tests.unit import test_cli20 -class CLITestV20FloatingIpsJSON(CLITestV20Base): +class CLITestV20FloatingIpsJSON(test_cli20.CLITestV20Base): def test_create_floatingip(self): """Create floatingip: fip1.""" resource = 'floatingip' - cmd = CreateFloatingIP(MyApp(sys.stdout), None) + cmd = fip.CreateFloatingIP(test_cli20.MyApp(sys.stdout), None) name = 'fip1' myid = 'myid' args = [name] @@ -44,7 +38,7 @@ def test_create_floatingip(self): def test_create_floatingip_and_port(self): """Create floatingip: fip1.""" resource = 'floatingip' - cmd = CreateFloatingIP(MyApp(sys.stdout), None) + cmd = fip.CreateFloatingIP(test_cli20.MyApp(sys.stdout), None) name = 'fip1' myid = 'myid' pid = 'mypid' @@ -63,7 +57,7 @@ def test_create_floatingip_and_port(self): def test_create_floatingip_and_port_and_address(self): """Create floatingip: fip1 with a given port and address.""" resource = 'floatingip' - cmd = CreateFloatingIP(MyApp(sys.stdout), None) + cmd = fip.CreateFloatingIP(test_cli20.MyApp(sys.stdout), None) name = 'fip1' myid = 'myid' pid = 'mypid' @@ -82,12 +76,12 @@ def test_create_floatingip_and_port_and_address(self): def test_list_floatingips(self): """list floatingips: -D.""" resources = 'floatingips' - cmd = ListFloatingIP(MyApp(sys.stdout), None) + cmd = fip.ListFloatingIP(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_floatingips_pagination(self): resources = 'floatingips' - cmd = ListFloatingIP(MyApp(sys.stdout), None) + cmd = fip.ListFloatingIP(test_cli20.MyApp(sys.stdout), None) self._test_list_resources_with_pagination(resources, cmd) def test_list_floatingips_sort(self): @@ -95,7 +89,7 @@ def test_list_floatingips_sort(self): --sort-key desc """ resources = 'floatingips' - cmd = ListFloatingIP(MyApp(sys.stdout), None) + cmd = fip.ListFloatingIP(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, sort_key=["name", "id"], sort_dir=["asc", "desc"]) @@ -103,13 +97,13 @@ def test_list_floatingips_sort(self): def test_list_floatingips_limit(self): """list floatingips: -P.""" resources = 'floatingips' - cmd = ListFloatingIP(MyApp(sys.stdout), None) + cmd = fip.ListFloatingIP(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) def test_delete_floatingip(self): """Delete floatingip: fip1.""" resource = 'floatingip' - cmd = DeleteFloatingIP(MyApp(sys.stdout), None) + cmd = fip.DeleteFloatingIP(test_cli20.MyApp(sys.stdout), None) myid = 'myid' args = [myid] self._test_delete_resource(resource, cmd, myid, args) @@ -117,7 +111,7 @@ def test_delete_floatingip(self): def test_show_floatingip(self): """Show floatingip: --fields id.""" resource = 'floatingip' - cmd = ShowFloatingIP(MyApp(sys.stdout), None) + cmd = fip.ShowFloatingIP(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', self.test_id] self._test_show_resource(resource, cmd, self.test_id, args, ['id']) @@ -125,7 +119,7 @@ def test_show_floatingip(self): def test_disassociate_ip(self): """Disassociate floating IP: myid.""" resource = 'floatingip' - cmd = DisassociateFloatingIP(MyApp(sys.stdout), None) + cmd = fip.DisassociateFloatingIP(test_cli20.MyApp(sys.stdout), None) args = ['myid'] self._test_update_resource(resource, cmd, 'myid', args, {"port_id": None} @@ -134,7 +128,7 @@ def test_disassociate_ip(self): def test_associate_ip(self): """Associate floating IP: myid portid.""" resource = 'floatingip' - cmd = AssociateFloatingIP(MyApp(sys.stdout), None) + cmd = fip.AssociateFloatingIP(test_cli20.MyApp(sys.stdout), None) args = ['myid', 'portid'] self._test_update_resource(resource, cmd, 'myid', args, {"port_id": "portid"} diff --git a/tests/unit/test_cli20_network.py b/tests/unit/test_cli20_network.py index ada6f0484..a64740455 100644 --- a/tests/unit/test_cli20_network.py +++ b/tests/unit/test_cli20_network.py @@ -16,30 +16,23 @@ import sys -from mox import (ContainsKeyValue, IgnoreArg, IsA) +import mox from quantumclient.common import exceptions from quantumclient.common import utils -from quantumclient.quantum.v2_0.network import CreateNetwork -from quantumclient.quantum.v2_0.network import DeleteNetwork -from quantumclient.quantum.v2_0.network import ListExternalNetwork -from quantumclient.quantum.v2_0.network import ListNetwork -from quantumclient.quantum.v2_0.network import ShowNetwork -from quantumclient.quantum.v2_0.network import UpdateNetwork +from quantumclient.quantum.v2_0 import network from quantumclient import shell from tests.unit import test_cli20 -from tests.unit.test_cli20 import CLITestV20Base -from tests.unit.test_cli20 import MyApp -class CLITestV20NetworkJSON(CLITestV20Base): +class CLITestV20NetworkJSON(test_cli20.CLITestV20Base): def setUp(self): super(CLITestV20NetworkJSON, self).setUp(plurals={'tags': 'tag'}) def test_create_network(self): """Create net: myname.""" resource = 'network' - cmd = CreateNetwork(MyApp(sys.stdout), None) + cmd = network.CreateNetwork(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' args = [name, ] @@ -51,7 +44,7 @@ def test_create_network(self): def test_create_network_with_unicode(self): """Create net: u'\u7f51\u7edc'.""" resource = 'network' - cmd = CreateNetwork(MyApp(sys.stdout), None) + cmd = network.CreateNetwork(test_cli20.MyApp(sys.stdout), None) name = u'\u7f51\u7edc' myid = 'myid' args = [name, ] @@ -63,7 +56,7 @@ def test_create_network_with_unicode(self): def test_create_network_tenant(self): """Create net: --tenant_id tenantid myname.""" resource = 'network' - cmd = CreateNetwork(MyApp(sys.stdout), None) + cmd = network.CreateNetwork(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' args = ['--tenant_id', 'tenantid', name] @@ -82,7 +75,7 @@ def test_create_network_tenant(self): def test_create_network_tags(self): """Create net: myname --tags a b.""" resource = 'network' - cmd = CreateNetwork(MyApp(sys.stdout), None) + cmd = network.CreateNetwork(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' args = [name, '--tags', 'a', 'b'] @@ -95,7 +88,7 @@ def test_create_network_tags(self): def test_create_network_state(self): """Create net: --admin_state_down myname.""" resource = 'network' - cmd = CreateNetwork(MyApp(sys.stdout), None) + cmd = network.CreateNetwork(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' args = ['--admin_state_down', name, ] @@ -113,11 +106,11 @@ def test_create_network_state(self): def test_list_nets_empty_with_column(self): resources = "networks" - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") - self.mox.StubOutWithMock(ListNetwork, "extend_list") - ListNetwork.extend_list(IsA(list), IgnoreArg()) + self.mox.StubOutWithMock(network.ListNetwork, "extend_list") + network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg()) cmd.get_client().MultipleTimes().AndReturn(self.client) reses = {resources: []} resstr = self.client.serialize(reses) @@ -128,7 +121,7 @@ def test_list_nets_empty_with_column(self): self.client.httpclient.request( test_cli20.end_url(path, query), 'GET', body=None, - headers=test_cli20.ContainsKeyValue( + headers=mox.ContainsKeyValue( 'X-Auth-Token', test_cli20.TOKEN)).AndReturn( (test_cli20.MyResp(200), resstr)) @@ -144,63 +137,63 @@ def _test_list_networks(self, cmd, detail=False, tags=[], fields_1=[], fields_2=[], page_size=None, sort_key=[], sort_dir=[]): resources = "networks" - self.mox.StubOutWithMock(ListNetwork, "extend_list") - ListNetwork.extend_list(IsA(list), IgnoreArg()) + self.mox.StubOutWithMock(network.ListNetwork, "extend_list") + network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg()) self._test_list_resources(resources, cmd, detail, tags, fields_1, fields_2, page_size=page_size, sort_key=sort_key, sort_dir=sort_dir) def test_list_nets_pagination(self): - cmd = ListNetwork(MyApp(sys.stdout), None) - self.mox.StubOutWithMock(ListNetwork, "extend_list") - ListNetwork.extend_list(IsA(list), IgnoreArg()) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) + self.mox.StubOutWithMock(network.ListNetwork, "extend_list") + network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg()) self._test_list_resources_with_pagination("networks", cmd) def test_list_nets_sort(self): """list nets: --sort-key name --sort-key id --sort-dir asc --sort-dir desc """ - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_networks(cmd, sort_key=['name', 'id'], sort_dir=['asc', 'desc']) def test_list_nets_sort_with_keys_more_than_dirs(self): """list nets: --sort-key name --sort-key id --sort-dir desc """ - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_networks(cmd, sort_key=['name', 'id'], sort_dir=['desc']) def test_list_nets_sort_with_dirs_more_than_keys(self): """list nets: --sort-key name --sort-dir desc --sort-dir asc """ - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_networks(cmd, sort_key=['name'], sort_dir=['desc', 'asc']) def test_list_nets_limit(self): """list nets: -P.""" - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_networks(cmd, page_size=1000) def test_list_nets_detail(self): """list nets: -D.""" - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_networks(cmd, True) def test_list_nets_tags(self): """List nets: -- --tags a b.""" - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_networks(cmd, tags=['a', 'b']) def test_list_nets_tags_with_unicode(self): """List nets: -- --tags u'\u7f51\u7edc'.""" - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_networks(cmd, tags=[u'\u7f51\u7edc']) def test_list_nets_detail_tags(self): """List nets: -D -- --tags a b.""" - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_networks(cmd, detail=True, tags=['a', 'b']) def _test_list_nets_extend_subnets(self, data, expected): @@ -212,10 +205,10 @@ def setup_list_stub(resources, data, query): self.client.httpclient.request( test_cli20.end_url(path, query), 'GET', body=None, - headers=ContainsKeyValue( + headers=mox.ContainsKeyValue( 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(resp) - cmd = ListNetwork(test_cli20.MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, 'get_client') self.mox.StubOutWithMock(self.client.httpclient, 'request') cmd.get_client().AndReturn(self.client) @@ -265,19 +258,19 @@ def test_list_nets_extend_subnets_no_subnet(self): def test_list_nets_fields(self): """List nets: --fields a --fields b -- --fields c d.""" - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_networks(cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) def _test_list_nets_columns(self, cmd, returned_body, args=['-f', 'json']): resources = 'networks' - self.mox.StubOutWithMock(ListNetwork, "extend_list") - ListNetwork.extend_list(IsA(list), IgnoreArg()) + self.mox.StubOutWithMock(network.ListNetwork, "extend_list") + network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg()) self._test_list_columns(cmd, resources, returned_body, args=args) def test_list_nets_defined_column(self): - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) returned_body = {"networks": [{"name": "buildname3", "id": "id3", "tenant_id": "tenant_3", @@ -287,12 +280,12 @@ def test_list_nets_defined_column(self): _str = self.fake_stdout.make_string() returned_networks = utils.loads(_str) self.assertEquals(1, len(returned_networks)) - network = returned_networks[0] - self.assertEquals(1, len(network)) - self.assertEquals("id", network.keys()[0]) + net = returned_networks[0] + self.assertEquals(1, len(net)) + self.assertEquals("id", net.keys()[0]) def test_list_nets_with_default_column(self): - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) returned_body = {"networks": [{"name": "buildname3", "id": "id3", "tenant_id": "tenant_3", @@ -301,18 +294,17 @@ def test_list_nets_with_default_column(self): _str = self.fake_stdout.make_string() returned_networks = utils.loads(_str) self.assertEquals(1, len(returned_networks)) - network = returned_networks[0] - self.assertEquals(3, len(network)) - self.assertEquals(0, len(set(network) ^ - set(cmd.list_columns))) + net = returned_networks[0] + self.assertEquals(3, len(net)) + self.assertEquals(0, len(set(net) ^ set(cmd.list_columns))) def test_list_external_nets_empty_with_column(self): resources = "networks" - cmd = ListExternalNetwork(MyApp(sys.stdout), None) + cmd = network.ListExternalNetwork(test_cli20.MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") - self.mox.StubOutWithMock(ListNetwork, "extend_list") - ListNetwork.extend_list(IsA(list), IgnoreArg()) + self.mox.StubOutWithMock(network.ListNetwork, "extend_list") + network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg()) cmd.get_client().MultipleTimes().AndReturn(self.client) reses = {resources: []} resstr = self.client.serialize(reses) @@ -323,7 +315,7 @@ def test_list_external_nets_empty_with_column(self): self.client.httpclient.request( test_cli20.end_url(path, query), 'GET', body=None, - headers=test_cli20.ContainsKeyValue( + headers=mox.ContainsKeyValue( 'X-Auth-Token', test_cli20.TOKEN)).AndReturn( (test_cli20.MyResp(200), resstr)) @@ -340,8 +332,8 @@ def _test_list_external_nets(self, resources, cmd, fields_1=[], fields_2=[]): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") - self.mox.StubOutWithMock(ListNetwork, "extend_list") - ListNetwork.extend_list(IsA(list), IgnoreArg()) + self.mox.StubOutWithMock(network.ListNetwork, "extend_list") + network.ListNetwork.extend_list(mox.IsA(list), mox.IgnoreArg()) cmd.get_client().MultipleTimes().AndReturn(self.client) reses = {resources: [{'id': 'myid1', }, {'id': 'myid2', }, ], } @@ -388,9 +380,8 @@ def _test_list_external_nets(self, resources, cmd, self.client.httpclient.request( test_cli20.end_url(path, query), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn( - (test_cli20.MyResp(200), resstr)) + headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) + ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) shell.run_command(cmd, cmd_parser, args) @@ -403,27 +394,27 @@ def _test_list_external_nets(self, resources, cmd, def test_list_external_nets_detail(self): """list external nets: -D.""" resources = "networks" - cmd = ListExternalNetwork(MyApp(sys.stdout), None) + cmd = network.ListExternalNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_external_nets(resources, cmd, True) def test_list_external_nets_tags(self): """List external nets: -- --tags a b.""" resources = "networks" - cmd = ListExternalNetwork(MyApp(sys.stdout), None) + cmd = network.ListExternalNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_external_nets(resources, cmd, tags=['a', 'b']) def test_list_external_nets_detail_tags(self): """List external nets: -D -- --tags a b.""" resources = "networks" - cmd = ListExternalNetwork(MyApp(sys.stdout), None) + cmd = network.ListExternalNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_external_nets(resources, cmd, detail=True, tags=['a', 'b']) def test_list_externel_nets_fields(self): """List external nets: --fields a --fields b -- --fields c d.""" resources = "networks" - cmd = ListExternalNetwork(MyApp(sys.stdout), None) + cmd = network.ListExternalNetwork(test_cli20.MyApp(sys.stdout), None) self._test_list_external_nets(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) @@ -431,14 +422,14 @@ def test_list_externel_nets_fields(self): def test_update_network_exception(self): """Update net: myid.""" resource = 'network' - cmd = UpdateNetwork(MyApp(sys.stdout), None) + cmd = network.UpdateNetwork(test_cli20.MyApp(sys.stdout), None) self.assertRaises(exceptions.CommandError, self._test_update_resource, resource, cmd, 'myid', ['myid'], {}) def test_update_network(self): """Update net: myid --name myname --tags a b.""" resource = 'network' - cmd = UpdateNetwork(MyApp(sys.stdout), None) + cmd = network.UpdateNetwork(test_cli20.MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', ['myid', '--name', 'myname', '--tags', 'a', 'b'], @@ -448,7 +439,7 @@ def test_update_network(self): def test_update_network_with_unicode(self): """Update net: myid --name u'\u7f51\u7edc' --tags a b.""" resource = 'network' - cmd = UpdateNetwork(MyApp(sys.stdout), None) + cmd = network.UpdateNetwork(test_cli20.MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', ['myid', '--name', u'\u7f51\u7edc', '--tags', 'a', 'b'], @@ -459,7 +450,7 @@ def test_update_network_with_unicode(self): def test_show_network(self): """Show net: --fields id --fields name myid.""" resource = 'network' - cmd = ShowNetwork(MyApp(sys.stdout), None) + cmd = network.ShowNetwork(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', '--fields', 'name', self.test_id] self._test_show_resource(resource, cmd, self.test_id, args, ['id', 'name']) @@ -467,7 +458,7 @@ def test_show_network(self): def test_delete_network(self): """Delete net: myid.""" resource = 'network' - cmd = DeleteNetwork(MyApp(sys.stdout), None) + cmd = network.DeleteNetwork(test_cli20.MyApp(sys.stdout), None) myid = 'myid' args = [myid] self._test_delete_resource(resource, cmd, myid, args) @@ -478,7 +469,7 @@ def _test_extend_list(self, mox_calls): for i in range(0, 10)] self.mox.StubOutWithMock(self.client.httpclient, "request") path = getattr(self.client, 'subnets_path') - cmd = ListNetwork(MyApp(sys.stdout), None) + cmd = network.ListNetwork(test_cli20.MyApp(sys.stdout), None) self.mox.StubOutWithMock(cmd, "get_client") cmd.get_client().MultipleTimes().AndReturn(self.client) mox_calls(path, data) @@ -509,7 +500,7 @@ def mox_calls(path, data): test_cli20.end_url(path, 'fields=id&fields=cidr' + filters), 'GET', body=None, - headers=ContainsKeyValue( + headers=mox.ContainsKeyValue( 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response) self._test_extend_list(mox_calls) @@ -523,7 +514,7 @@ def mox_calls(path, data): test_cli20.end_url(path, 'fields=id&fields=cidr%s' % filters), 'GET', body=None, - headers=ContainsKeyValue( + headers=mox.ContainsKeyValue( 'X-Auth-Token', test_cli20.TOKEN)).AndRaise( exceptions.RequestURITooLong(excess=1)) for data in sub_data_lists: @@ -533,7 +524,7 @@ def mox_calls(path, data): 'fields=id&fields=cidr%s' % filters), 'GET', body=None, - headers=ContainsKeyValue( + headers=mox.ContainsKeyValue( 'X-Auth-Token', test_cli20.TOKEN)).AndReturn(response) self._test_extend_list(mox_calls) diff --git a/tests/unit/test_cli20_nvpnetworkgateway.py b/tests/unit/test_cli20_nvpnetworkgateway.py index c460adadd..ebf730a17 100644 --- a/tests/unit/test_cli20_nvpnetworkgateway.py +++ b/tests/unit/test_cli20_nvpnetworkgateway.py @@ -17,12 +17,11 @@ import sys -from quantumclient.quantum.v2_0 import nvpnetworkgateway -from tests.unit.test_cli20 import CLITestV20Base -from tests.unit.test_cli20 import MyApp +from quantumclient.quantum.v2_0 import nvpnetworkgateway as nwgw +from tests.unit import test_cli20 -class CLITestV20NetworkGatewayJSON(CLITestV20Base): +class CLITestV20NetworkGatewayJSON(test_cli20.CLITestV20Base): resource = "network_gateway" @@ -32,7 +31,7 @@ def setUp(self): 'network_gateways': 'network_gateway'}) def test_create_gateway(self): - cmd = nvpnetworkgateway.CreateNetworkGateway(MyApp(sys.stdout), None) + cmd = nwgw.CreateNetworkGateway(test_cli20.MyApp(sys.stdout), None) name = 'gw-test' myid = 'myid' args = [name, ] @@ -42,7 +41,7 @@ def test_create_gateway(self): position_names, position_values) def test_create_gateway_with_tenant(self): - cmd = nvpnetworkgateway.CreateNetworkGateway(MyApp(sys.stdout), None) + cmd = nwgw.CreateNetworkGateway(test_cli20.MyApp(sys.stdout), None) name = 'gw-test' myid = 'myid' args = ['--tenant_id', 'tenantid', name] @@ -53,7 +52,7 @@ def test_create_gateway_with_tenant(self): tenant_id='tenantid') def test_create_gateway_with_device(self): - cmd = nvpnetworkgateway.CreateNetworkGateway(MyApp(sys.stdout), None) + cmd = nwgw.CreateNetworkGateway(test_cli20.MyApp(sys.stdout), None) name = 'gw-test' myid = 'myid' args = ['--device', 'device_id=test', name, ] @@ -65,29 +64,29 @@ def test_create_gateway_with_device(self): def test_list_gateways(self): resources = '%ss' % self.resource - cmd = nvpnetworkgateway.ListNetworkGateway(MyApp(sys.stdout), None) + cmd = nwgw.ListNetworkGateway(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_update_gateway(self): - cmd = nvpnetworkgateway.UpdateNetworkGateway(MyApp(sys.stdout), None) + cmd = nwgw.UpdateNetworkGateway(test_cli20.MyApp(sys.stdout), None) self._test_update_resource(self.resource, cmd, 'myid', ['myid', '--name', 'cavani'], {'name': 'cavani'}) def test_delete_gateway(self): - cmd = nvpnetworkgateway.DeleteNetworkGateway(MyApp(sys.stdout), None) + cmd = nwgw.DeleteNetworkGateway(test_cli20.MyApp(sys.stdout), None) myid = 'myid' args = [myid] self._test_delete_resource(self.resource, cmd, myid, args) def test_show_gateway(self): - cmd = nvpnetworkgateway.ShowNetworkGateway(MyApp(sys.stdout), None) + cmd = nwgw.ShowNetworkGateway(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', '--fields', 'name', self.test_id] self._test_show_resource(self.resource, cmd, self.test_id, args, ['id', 'name']) def test_connect_network_to_gateway(self): - cmd = nvpnetworkgateway.ConnectNetworkGateway(MyApp(sys.stdout), None) + cmd = nwgw.ConnectNetworkGateway(test_cli20.MyApp(sys.stdout), None) args = ['gw_id', 'net_id', '--segmentation-type', 'edi', '--segmentation-id', '7'] @@ -99,8 +98,7 @@ def test_connect_network_to_gateway(self): 'segmentation_id': '7'}) def test_disconnect_network_from_gateway(self): - cmd = nvpnetworkgateway.DisconnectNetworkGateway(MyApp(sys.stdout), - None) + cmd = nwgw.DisconnectNetworkGateway(test_cli20.MyApp(sys.stdout), None) args = ['gw_id', 'net_id', '--segmentation-type', 'edi', '--segmentation-id', '7'] diff --git a/tests/unit/test_cli20_port.py b/tests/unit/test_cli20_port.py index 83c172b57..545f35b39 100644 --- a/tests/unit/test_cli20_port.py +++ b/tests/unit/test_cli20_port.py @@ -17,28 +17,21 @@ import sys -from mox import ContainsKeyValue - -from quantumclient.quantum.v2_0.port import CreatePort -from quantumclient.quantum.v2_0.port import DeletePort -from quantumclient.quantum.v2_0.port import ListPort -from quantumclient.quantum.v2_0.port import ListRouterPort -from quantumclient.quantum.v2_0.port import ShowPort -from quantumclient.quantum.v2_0.port import UpdatePort +import mox + +from quantumclient.quantum.v2_0 import port from quantumclient import shell from tests.unit import test_cli20 -from tests.unit.test_cli20 import CLITestV20Base -from tests.unit.test_cli20 import MyApp -class CLITestV20PortJSON(CLITestV20Base): +class CLITestV20PortJSON(test_cli20.CLITestV20Base): def setUp(self): super(CLITestV20PortJSON, self).setUp(plurals={'tags': 'tag'}) def test_create_port(self): """Create port: netid.""" resource = 'port' - cmd = CreatePort(MyApp(sys.stdout), None) + cmd = port.CreatePort(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -52,7 +45,7 @@ def test_create_port(self): def test_create_port_full(self): """Create port: --mac_address mac --device_id deviceid netid.""" resource = 'port' - cmd = CreatePort(MyApp(sys.stdout), None) + cmd = port.CreatePort(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -71,7 +64,7 @@ def test_create_port_full(self): def test_create_port_tenant(self): """Create port: --tenant_id tenantid netid.""" resource = 'port' - cmd = CreatePort(MyApp(sys.stdout), None) + cmd = port.CreatePort(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -92,7 +85,7 @@ def test_create_port_tenant(self): def test_create_port_tags(self): """Create port: netid mac_address device_id --tags a b.""" resource = 'port' - cmd = CreatePort(MyApp(sys.stdout), None) + cmd = port.CreatePort(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -107,7 +100,7 @@ def test_create_port_tags(self): def test_create_port_secgroup(self): """Create port: --security-group sg1_id netid.""" resource = 'port' - cmd = CreatePort(MyApp(sys.stdout), None) + cmd = port.CreatePort(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -124,7 +117,7 @@ def test_create_port_secgroups(self): --security-group sg1_id --security-group sg2_id """ resource = 'port' - cmd = CreatePort(MyApp(sys.stdout), None) + cmd = port.CreatePort(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -139,12 +132,12 @@ def test_create_port_secgroups(self): def test_list_ports(self): """List ports: -D.""" resources = "ports" - cmd = ListPort(MyApp(sys.stdout), None) + cmd = port.ListPort(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_ports_pagination(self): resources = "ports" - cmd = ListPort(MyApp(sys.stdout), None) + cmd = port.ListPort(test_cli20.MyApp(sys.stdout), None) self._test_list_resources_with_pagination(resources, cmd) def test_list_ports_sort(self): @@ -152,7 +145,7 @@ def test_list_ports_sort(self): --sort-key desc """ resources = "ports" - cmd = ListPort(MyApp(sys.stdout), None) + cmd = port.ListPort(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, sort_key=["name", "id"], sort_dir=["asc", "desc"]) @@ -160,25 +153,25 @@ def test_list_ports_sort(self): def test_list_ports_limit(self): """list ports: -P.""" resources = "ports" - cmd = ListPort(MyApp(sys.stdout), None) + cmd = port.ListPort(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) def test_list_ports_tags(self): """List ports: -- --tags a b.""" resources = "ports" - cmd = ListPort(MyApp(sys.stdout), None) + cmd = port.ListPort(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, tags=['a', 'b']) def test_list_ports_detail_tags(self): """List ports: -D -- --tags a b.""" resources = "ports" - cmd = ListPort(MyApp(sys.stdout), None) + cmd = port.ListPort(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) def test_list_ports_fields(self): """List ports: --fields a --fields b -- --fields c d.""" resources = "ports" - cmd = ListPort(MyApp(sys.stdout), None) + cmd = port.ListPort(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) @@ -232,9 +225,8 @@ def _test_list_router_port(self, resources, cmd, self.client.httpclient.request( test_cli20.end_url(path, query % myid), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn( - (test_cli20.MyResp(200), resstr)) + headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) + ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() cmd_parser = cmd.get_parser("list_" + resources) shell.run_command(cmd, cmd_parser, args) @@ -247,28 +239,28 @@ def _test_list_router_port(self, resources, cmd, def test_list_router_ports(self): """List router ports: -D.""" resources = "ports" - cmd = ListRouterPort(MyApp(sys.stdout), None) + cmd = port.ListRouterPort(test_cli20.MyApp(sys.stdout), None) self._test_list_router_port(resources, cmd, self.test_id, True) def test_list_router_ports_tags(self): """List router ports: -- --tags a b.""" resources = "ports" - cmd = ListRouterPort(MyApp(sys.stdout), None) + cmd = port.ListRouterPort(test_cli20.MyApp(sys.stdout), None) self._test_list_router_port(resources, cmd, self.test_id, tags=['a', 'b']) def test_list_router_ports_detail_tags(self): """List router ports: -D -- --tags a b.""" resources = "ports" - cmd = ListRouterPort(MyApp(sys.stdout), None) + cmd = port.ListRouterPort(test_cli20.MyApp(sys.stdout), None) self._test_list_router_port(resources, cmd, self.test_id, detail=True, tags=['a', 'b']) def test_list_router_ports_fields(self): """List ports: --fields a --fields b -- --fields c d.""" resources = "ports" - cmd = ListRouterPort(MyApp(sys.stdout), None) + cmd = port.ListRouterPort(test_cli20.MyApp(sys.stdout), None) self._test_list_router_port(resources, cmd, self.test_id, fields_1=['a', 'b'], fields_2=['c', 'd']) @@ -276,7 +268,7 @@ def test_list_router_ports_fields(self): def test_update_port(self): """Update port: myid --name myname --tags a b.""" resource = 'port' - cmd = UpdatePort(MyApp(sys.stdout), None) + cmd = port.UpdatePort(test_cli20.MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', ['myid', '--name', 'myname', '--tags', 'a', 'b'], @@ -286,7 +278,7 @@ def test_update_port(self): def test_update_port_security_group_off(self): """Update port: --no-security-groups myid.""" resource = 'port' - cmd = UpdatePort(MyApp(sys.stdout), None) + cmd = port.UpdatePort(test_cli20.MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', ['--no-security-groups', 'myid'], {'security_groups': None}) @@ -294,7 +286,7 @@ def test_update_port_security_group_off(self): def test_show_port(self): """Show port: --fields id --fields name myid.""" resource = 'port' - cmd = ShowPort(MyApp(sys.stdout), None) + cmd = port.ShowPort(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', '--fields', 'name', self.test_id] self._test_show_resource(resource, cmd, self.test_id, args, ['id', 'name']) @@ -302,7 +294,7 @@ def test_show_port(self): def test_delete_port(self): """Delete port: myid.""" resource = 'port' - cmd = DeletePort(MyApp(sys.stdout), None) + cmd = port.DeletePort(test_cli20.MyApp(sys.stdout), None) myid = 'myid' args = [myid] self._test_delete_resource(resource, cmd, myid, args) diff --git a/tests/unit/test_cli20_router.py b/tests/unit/test_cli20_router.py index 4868320ae..96517f5fa 100644 --- a/tests/unit/test_cli20_router.py +++ b/tests/unit/test_cli20_router.py @@ -18,24 +18,15 @@ import sys from quantumclient.common import exceptions -from quantumclient.quantum.v2_0.router import AddInterfaceRouter -from quantumclient.quantum.v2_0.router import CreateRouter -from quantumclient.quantum.v2_0.router import DeleteRouter -from quantumclient.quantum.v2_0.router import ListRouter -from quantumclient.quantum.v2_0.router import RemoveGatewayRouter -from quantumclient.quantum.v2_0.router import RemoveInterfaceRouter -from quantumclient.quantum.v2_0.router import SetGatewayRouter -from quantumclient.quantum.v2_0.router import ShowRouter -from quantumclient.quantum.v2_0.router import UpdateRouter -from tests.unit.test_cli20 import CLITestV20Base -from tests.unit.test_cli20 import MyApp - - -class CLITestV20RouterJSON(CLITestV20Base): +from quantumclient.quantum.v2_0 import router +from tests.unit import test_cli20 + + +class CLITestV20RouterJSON(test_cli20.CLITestV20Base): def test_create_router(self): """Create router: router1.""" resource = 'router' - cmd = CreateRouter(MyApp(sys.stdout), None) + cmd = router.CreateRouter(test_cli20.MyApp(sys.stdout), None) name = 'router1' myid = 'myid' args = [name, ] @@ -47,7 +38,7 @@ def test_create_router(self): def test_create_router_tenant(self): """Create router: --tenant_id tenantid myname.""" resource = 'router' - cmd = CreateRouter(MyApp(sys.stdout), None) + cmd = router.CreateRouter(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' args = ['--tenant_id', 'tenantid', name] @@ -60,7 +51,7 @@ def test_create_router_tenant(self): def test_create_router_admin_state(self): """Create router: --admin_state_down myname.""" resource = 'router' - cmd = CreateRouter(MyApp(sys.stdout), None) + cmd = router.CreateRouter(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' args = ['--admin_state_down', name, ] @@ -73,12 +64,12 @@ def test_create_router_admin_state(self): def test_list_routers_detail(self): """list routers: -D.""" resources = "routers" - cmd = ListRouter(MyApp(sys.stdout), None) + cmd = router.ListRouter(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_routers_pagination(self): resources = "routers" - cmd = ListRouter(MyApp(sys.stdout), None) + cmd = router.ListRouter(test_cli20.MyApp(sys.stdout), None) self._test_list_resources_with_pagination(resources, cmd) def test_list_routers_sort(self): @@ -86,7 +77,7 @@ def test_list_routers_sort(self): --sort-key desc """ resources = "routers" - cmd = ListRouter(MyApp(sys.stdout), None) + cmd = router.ListRouter(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, sort_key=["name", "id"], sort_dir=["asc", "desc"]) @@ -94,20 +85,20 @@ def test_list_routers_sort(self): def test_list_routers_limit(self): """list routers: -P.""" resources = "routers" - cmd = ListRouter(MyApp(sys.stdout), None) + cmd = router.ListRouter(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) def test_update_router_exception(self): """Update router: myid.""" resource = 'router' - cmd = UpdateRouter(MyApp(sys.stdout), None) + cmd = router.UpdateRouter(test_cli20.MyApp(sys.stdout), None) self.assertRaises(exceptions.CommandError, self._test_update_resource, resource, cmd, 'myid', ['myid'], {}) def test_update_router(self): """Update router: myid --name myname --tags a b.""" resource = 'router' - cmd = UpdateRouter(MyApp(sys.stdout), None) + cmd = router.UpdateRouter(test_cli20.MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', ['myid', '--name', 'myname'], {'name': 'myname'} @@ -116,7 +107,7 @@ def test_update_router(self): def test_delete_router(self): """Delete router: myid.""" resource = 'router' - cmd = DeleteRouter(MyApp(sys.stdout), None) + cmd = router.DeleteRouter(test_cli20.MyApp(sys.stdout), None) myid = 'myid' args = [myid] self._test_delete_resource(resource, cmd, myid, args) @@ -124,7 +115,7 @@ def test_delete_router(self): def test_show_router(self): """Show router: myid.""" resource = 'router' - cmd = ShowRouter(MyApp(sys.stdout), None) + cmd = router.ShowRouter(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', '--fields', 'name', self.test_id] self._test_show_resource(resource, cmd, self.test_id, args, ['id', 'name']) @@ -132,7 +123,7 @@ def test_show_router(self): def test_add_interface(self): """Add interface to router: myid subnetid.""" resource = 'router' - cmd = AddInterfaceRouter(MyApp(sys.stdout), None) + cmd = router.AddInterfaceRouter(test_cli20.MyApp(sys.stdout), None) args = ['myid', 'subnetid'] self._test_update_resource_action(resource, cmd, 'myid', 'add_router_interface', @@ -143,7 +134,7 @@ def test_add_interface(self): def test_del_interface(self): """Delete interface from router: myid subnetid.""" resource = 'router' - cmd = RemoveInterfaceRouter(MyApp(sys.stdout), None) + cmd = router.RemoveInterfaceRouter(test_cli20.MyApp(sys.stdout), None) args = ['myid', 'subnetid'] self._test_update_resource_action(resource, cmd, 'myid', 'remove_router_interface', @@ -154,7 +145,7 @@ def test_del_interface(self): def test_set_gateway(self): """Set external gateway for router: myid externalid.""" resource = 'router' - cmd = SetGatewayRouter(MyApp(sys.stdout), None) + cmd = router.SetGatewayRouter(test_cli20.MyApp(sys.stdout), None) args = ['myid', 'externalid'] self._test_update_resource(resource, cmd, 'myid', args, @@ -166,7 +157,7 @@ def test_set_gateway(self): def test_remove_gateway(self): """Remove external gateway from router: externalid.""" resource = 'router' - cmd = RemoveGatewayRouter(MyApp(sys.stdout), None) + cmd = router.RemoveGatewayRouter(test_cli20.MyApp(sys.stdout), None) args = ['externalid'] self._test_update_resource(resource, cmd, 'externalid', args, {"external_gateway_info": {}} diff --git a/tests/unit/test_cli20_subnet.py b/tests/unit/test_cli20_subnet.py index 8ffa1a45b..5c048a605 100644 --- a/tests/unit/test_cli20_subnet.py +++ b/tests/unit/test_cli20_subnet.py @@ -17,23 +17,18 @@ import sys -from quantumclient.quantum.v2_0.subnet import CreateSubnet -from quantumclient.quantum.v2_0.subnet import DeleteSubnet -from quantumclient.quantum.v2_0.subnet import ListSubnet -from quantumclient.quantum.v2_0.subnet import ShowSubnet -from quantumclient.quantum.v2_0.subnet import UpdateSubnet -from tests.unit.test_cli20 import CLITestV20Base -from tests.unit.test_cli20 import MyApp +from quantumclient.quantum.v2_0 import subnet +from tests.unit import test_cli20 -class CLITestV20SubnetJSON(CLITestV20Base): +class CLITestV20SubnetJSON(test_cli20.CLITestV20Base): def setUp(self): super(CLITestV20SubnetJSON, self).setUp(plurals={'tags': 'tag'}) def test_create_subnet(self): """Create subnet: --gateway gateway netid cidr.""" resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -48,7 +43,7 @@ def test_create_subnet(self): def test_create_subnet_with_no_gateway(self): """Create subnet: --no-gateway netid cidr.""" resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -62,7 +57,7 @@ def test_create_subnet_with_no_gateway(self): def test_create_subnet_with_bad_gateway_option(self): """Create sbunet: --no-gateway netid cidr.""" resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -81,7 +76,7 @@ def test_create_subnet_with_bad_gateway_option(self): def test_create_subnet_tenant(self): """Create subnet: --tenant_id tenantid netid cidr.""" resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -96,7 +91,7 @@ def test_create_subnet_tenant(self): def test_create_subnet_tags(self): """Create subnet: netid cidr --tags a b.""" resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -113,7 +108,7 @@ def test_create_subnet_allocation_pool(self): The is --allocation_pool start=1.1.1.10,end=1.1.1.20 """ resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -135,7 +130,7 @@ def test_create_subnet_allocation_pools(self): --allocation_pool start=1.1.1.30,end=1.1.1.40 """ resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -159,7 +154,7 @@ def test_create_subnet_host_route(self): --host-route destination=172.16.1.0/24,nexthop=1.1.1.20 """ resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -182,7 +177,7 @@ def test_create_subnet_host_routes(self): --host-route destination=172.17.7.0/24,nexthop=1.1.1.40 """ resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -206,7 +201,7 @@ def test_create_subnet_dns_nameservers(self): --dns-nameserver 1.1.1.20 and --dns-nameserver 1.1.1.40 """ resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -226,7 +221,7 @@ def test_create_subnet_dns_nameservers(self): def test_create_subnet_with_disable_dhcp(self): """Create subnet: --tenant-id tenantid --disable-dhcp netid cidr.""" resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -243,7 +238,7 @@ def test_create_subnet_with_disable_dhcp(self): def test_create_subnet_merge_single_plurar(self): resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -264,7 +259,7 @@ def test_create_subnet_merge_single_plurar(self): def test_create_subnet_merge_plurar(self): resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -283,7 +278,7 @@ def test_create_subnet_merge_plurar(self): def test_create_subnet_merge_single_single(self): resource = 'subnet' - cmd = CreateSubnet(MyApp(sys.stdout), None) + cmd = subnet.CreateSubnet(test_cli20.MyApp(sys.stdout), None) name = 'myname' myid = 'myid' netid = 'netid' @@ -305,37 +300,37 @@ def test_create_subnet_merge_single_single(self): def test_list_subnets_detail(self): """List subnets: -D.""" resources = "subnets" - cmd = ListSubnet(MyApp(sys.stdout), None) + cmd = subnet.ListSubnet(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, True) def test_list_subnets_tags(self): """List subnets: -- --tags a b.""" resources = "subnets" - cmd = ListSubnet(MyApp(sys.stdout), None) + cmd = subnet.ListSubnet(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, tags=['a', 'b']) def test_list_subnets_known_option_after_unknown(self): """List subnets: -- --tags a b --request-format xml.""" resources = "subnets" - cmd = ListSubnet(MyApp(sys.stdout), None) + cmd = subnet.ListSubnet(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, tags=['a', 'b']) def test_list_subnets_detail_tags(self): """List subnets: -D -- --tags a b.""" resources = "subnets" - cmd = ListSubnet(MyApp(sys.stdout), None) + cmd = subnet.ListSubnet(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, detail=True, tags=['a', 'b']) def test_list_subnets_fields(self): """List subnets: --fields a --fields b -- --fields c d.""" resources = "subnets" - cmd = ListSubnet(MyApp(sys.stdout), None) + cmd = subnet.ListSubnet(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, fields_1=['a', 'b'], fields_2=['c', 'd']) def test_list_subnets_pagination(self): resources = "subnets" - cmd = ListSubnet(MyApp(sys.stdout), None) + cmd = subnet.ListSubnet(test_cli20.MyApp(sys.stdout), None) self._test_list_resources_with_pagination(resources, cmd) def test_list_subnets_sort(self): @@ -343,7 +338,7 @@ def test_list_subnets_sort(self): --sort-key desc """ resources = "subnets" - cmd = ListSubnet(MyApp(sys.stdout), None) + cmd = subnet.ListSubnet(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, sort_key=["name", "id"], sort_dir=["asc", "desc"]) @@ -351,13 +346,13 @@ def test_list_subnets_sort(self): def test_list_subnets_limit(self): """List subnets: -P.""" resources = "subnets" - cmd = ListSubnet(MyApp(sys.stdout), None) + cmd = subnet.ListSubnet(test_cli20.MyApp(sys.stdout), None) self._test_list_resources(resources, cmd, page_size=1000) def test_update_subnet(self): """Update subnet: myid --name myname --tags a b.""" resource = 'subnet' - cmd = UpdateSubnet(MyApp(sys.stdout), None) + cmd = subnet.UpdateSubnet(test_cli20.MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', ['myid', '--name', 'myname', '--tags', 'a', 'b'], @@ -368,7 +363,7 @@ def test_update_subnet_known_option_before_id(self): """Update subnet: --request-format json myid --name myname.""" # --request-format xml is known option resource = 'subnet' - cmd = UpdateSubnet(MyApp(sys.stdout), None) + cmd = subnet.UpdateSubnet(test_cli20.MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', ['--request-format', 'json', 'myid', '--name', 'myname'], @@ -379,7 +374,7 @@ def test_update_subnet_known_option_after_id(self): """Update subnet: myid --name myname --request-format json.""" # --request-format xml is known option resource = 'subnet' - cmd = UpdateSubnet(MyApp(sys.stdout), None) + cmd = subnet.UpdateSubnet(test_cli20.MyApp(sys.stdout), None) self._test_update_resource(resource, cmd, 'myid', ['myid', '--name', 'myname', '--request-format', 'json'], @@ -389,7 +384,7 @@ def test_update_subnet_known_option_after_id(self): def test_show_subnet(self): """Show subnet: --fields id --fields name myid.""" resource = 'subnet' - cmd = ShowSubnet(MyApp(sys.stdout), None) + cmd = subnet.ShowSubnet(test_cli20.MyApp(sys.stdout), None) args = ['--fields', 'id', '--fields', 'name', self.test_id] self._test_show_resource(resource, cmd, self.test_id, args, ['id', 'name']) @@ -397,7 +392,7 @@ def test_show_subnet(self): def test_delete_subnet(self): """Delete subnet: subnetid.""" resource = 'subnet' - cmd = DeleteSubnet(MyApp(sys.stdout), None) + cmd = subnet.DeleteSubnet(test_cli20.MyApp(sys.stdout), None) myid = 'myid' args = [myid] self._test_delete_resource(resource, cmd, myid, args) diff --git a/tests/unit/test_name_or_id.py b/tests/unit/test_name_or_id.py index 42e4496bf..fc3bdac5e 100644 --- a/tests/unit/test_name_or_id.py +++ b/tests/unit/test_name_or_id.py @@ -18,12 +18,11 @@ import uuid import mox -from mox import ContainsKeyValue import testtools from quantumclient.common import exceptions from quantumclient.quantum import v2_0 as quantumv20 -from quantumclient.v2_0.client import Client +from quantumclient.v2_0 import client from tests.unit import test_cli20 @@ -34,7 +33,8 @@ def setUp(self): super(CLITestNameorID, self).setUp() self.mox = mox.Mox() self.endurl = test_cli20.ENDURL - self.client = Client(token=test_cli20.TOKEN, endpoint_url=self.endurl) + self.client = client.Client(token=test_cli20.TOKEN, + endpoint_url=self.endurl) self.addCleanup(self.mox.VerifyAll) self.addCleanup(self.mox.UnsetStubs) @@ -47,9 +47,8 @@ def test_get_id_from_id(self): self.client.httpclient.request( test_cli20.end_url(path, "fields=id&id=" + _id), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn( - (test_cli20.MyResp(200), resstr)) + headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) + ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() returned_id = quantumv20.find_resourceid_by_name_or_id( self.client, 'network', _id) @@ -65,15 +64,13 @@ def test_get_id_from_id_then_name_empty(self): self.client.httpclient.request( test_cli20.end_url(path, "fields=id&id=" + _id), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn( - (test_cli20.MyResp(200), resstr1)) + headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) + ).AndReturn((test_cli20.MyResp(200), resstr1)) self.client.httpclient.request( test_cli20.end_url(path, "fields=id&name=" + _id), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn( - (test_cli20.MyResp(200), resstr)) + headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) + ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() returned_id = quantumv20.find_resourceid_by_name_or_id( self.client, 'network', _id) @@ -89,9 +86,8 @@ def test_get_id_from_name(self): self.client.httpclient.request( test_cli20.end_url(path, "fields=id&name=" + name), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn( - (test_cli20.MyResp(200), resstr)) + headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) + ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() returned_id = quantumv20.find_resourceid_by_name_or_id( self.client, 'network', name) @@ -107,9 +103,8 @@ def test_get_id_from_name_multiple(self): self.client.httpclient.request( test_cli20.end_url(path, "fields=id&name=" + name), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn( - (test_cli20.MyResp(200), resstr)) + headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) + ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() try: quantumv20.find_resourceid_by_name_or_id( @@ -126,9 +121,8 @@ def test_get_id_from_name_notfound(self): self.client.httpclient.request( test_cli20.end_url(path, "fields=id&name=" + name), 'GET', body=None, - headers=ContainsKeyValue('X-Auth-Token', - test_cli20.TOKEN)).AndReturn( - (test_cli20.MyResp(200), resstr)) + headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) + ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() try: quantumv20.find_resourceid_by_name_or_id( From 5ac0b450299c32f7794a022ba8f7753dabae8613 Mon Sep 17 00:00:00 2001 From: Mark McClain Date: Thu, 13 Jun 2013 15:45:46 -0400 Subject: [PATCH 124/135] update to latest pbr & remove distribute from tox Change-Id: I61eb9902446a4fd975d5ea6fa7909ddcb3f7e761 --- requirements.txt | 4 ++-- tox.ini | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b1acf1825..1eabc6f06 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ d2to1>=0.2.10,<0.3 -pbr>=0.5.10,<0.6 +pbr>=0.5.16,<0.6 argparse -cliff>=1.3.2 +cliff>=1.4 httplib2 iso8601 prettytable>=0.6,<0.8 diff --git a/tox.ini b/tox.ini index 61a839c56..fb9729e4b 100644 --- a/tox.ini +++ b/tox.ini @@ -12,6 +12,7 @@ commands = python setup.py testr --testr-args='{posargs}' [testenv:pep8] commands = flake8 +distribute = false [testenv:venv] commands = {posargs} From 2a5fd7e54775897719ef78fd5ec8756bc5172733 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 14 Jun 2013 10:44:23 -0700 Subject: [PATCH 125/135] Remove the monkey patching of _ into the builtins. Previous _ was monkey patched into builtins whenever certain modules were imported. This removes that and simply imports it when it is needed. Change-Id: I8b7cdc7a8da21ed3e8bc69b18414dfc89e8935b8 --- quantumclient/common/serializer.py | 1 + quantumclient/quantum/v2_0/__init__.py | 1 + quantumclient/quantum/v2_0/agentscheduler.py | 1 + quantumclient/quantum/v2_0/floatingip.py | 1 + quantumclient/quantum/v2_0/lb/healthmonitor.py | 1 + quantumclient/quantum/v2_0/nvpnetworkgateway.py | 1 + quantumclient/quantum/v2_0/quota.py | 1 + quantumclient/quantum/v2_0/router.py | 1 + quantumclient/shell.py | 2 -- tests/unit/__init__.py | 6 ------ tox.ini | 1 - 11 files changed, 8 insertions(+), 9 deletions(-) diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py index a9d8db0a5..d7e5c95a9 100644 --- a/quantumclient/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -26,6 +26,7 @@ from quantumclient.common import constants from quantumclient.common import exceptions as exception +from quantumclient.openstack.common.gettextutils import _ from quantumclient.openstack.common import jsonutils LOG = logging.getLogger(__name__) diff --git a/quantumclient/quantum/v2_0/__init__.py b/quantumclient/quantum/v2_0/__init__.py index 11bdc2e11..1be1d294f 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/quantumclient/quantum/v2_0/__init__.py @@ -26,6 +26,7 @@ from quantumclient.common import command from quantumclient.common import exceptions from quantumclient.common import utils +from quantumclient.openstack.common.gettextutils import _ HEX_ELEM = '[0-9A-Fa-f]' UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', diff --git a/quantumclient/quantum/v2_0/agentscheduler.py b/quantumclient/quantum/v2_0/agentscheduler.py index 3e8e8e2d7..8e4137637 100644 --- a/quantumclient/quantum/v2_0/agentscheduler.py +++ b/quantumclient/quantum/v2_0/agentscheduler.py @@ -17,6 +17,7 @@ import logging +from quantumclient.openstack.common.gettextutils import _ from quantumclient.quantum import v2_0 as quantumV20 from quantumclient.quantum.v2_0 import network from quantumclient.quantum.v2_0 import router diff --git a/quantumclient/quantum/v2_0/floatingip.py b/quantumclient/quantum/v2_0/floatingip.py index 3a4982ab5..98401df17 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/quantumclient/quantum/v2_0/floatingip.py @@ -18,6 +18,7 @@ import argparse import logging +from quantumclient.openstack.common.gettextutils import _ from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand diff --git a/quantumclient/quantum/v2_0/lb/healthmonitor.py b/quantumclient/quantum/v2_0/lb/healthmonitor.py index 3ecdd5665..f0a828b07 100644 --- a/quantumclient/quantum/v2_0/lb/healthmonitor.py +++ b/quantumclient/quantum/v2_0/lb/healthmonitor.py @@ -19,6 +19,7 @@ import logging +from quantumclient.openstack.common.gettextutils import _ from quantumclient.quantum import v2_0 as quantumv20 diff --git a/quantumclient/quantum/v2_0/nvpnetworkgateway.py b/quantumclient/quantum/v2_0/nvpnetworkgateway.py index 72a83b347..1823b6fe1 100644 --- a/quantumclient/quantum/v2_0/nvpnetworkgateway.py +++ b/quantumclient/quantum/v2_0/nvpnetworkgateway.py @@ -18,6 +18,7 @@ import logging from quantumclient.common import utils +from quantumclient.openstack.common.gettextutils import _ from quantumclient.quantum import v2_0 as quantumv20 RESOURCE = 'network_gateway' diff --git a/quantumclient/quantum/v2_0/quota.py b/quantumclient/quantum/v2_0/quota.py index 2b1752fc3..089c12d11 100644 --- a/quantumclient/quantum/v2_0/quota.py +++ b/quantumclient/quantum/v2_0/quota.py @@ -23,6 +23,7 @@ from quantumclient.common import exceptions from quantumclient.common import utils +from quantumclient.openstack.common.gettextutils import _ from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.quantum.v2_0 import QuantumCommand diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index cff4ace0c..b9ccfdcc5 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -19,6 +19,7 @@ import logging from quantumclient.common import utils +from quantumclient.openstack.common.gettextutils import _ from quantumclient.quantum import v2_0 as quantumv20 from quantumclient.quantum.v2_0 import CreateCommand from quantumclient.quantum.v2_0 import DeleteCommand diff --git a/quantumclient/shell.py b/quantumclient/shell.py index afcabb7a0..402c8bf48 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -20,7 +20,6 @@ """ import argparse -import gettext import logging import os import sys @@ -644,7 +643,6 @@ def configure_logging(self): def main(argv=sys.argv[1:]): - gettext.install('quantumclient', unicode=1) try: return QuantumShell(QUANTUM_API_VERSION).run(map(strutils.safe_decode, argv)) diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py index 353303fb4..1668497e7 100644 --- a/tests/unit/__init__.py +++ b/tests/unit/__init__.py @@ -14,9 +14,3 @@ # under the License. # # vim: tabstop=4 shiftwidth=4 softtabstop=4 - -import gettext - -# Because we installed '_' for quantum cli in shell.py, this help unittest -# have definition of '_' -gettext.install('quantumclient', unicode=1) diff --git a/tox.ini b/tox.ini index fb9729e4b..08dc3055f 100644 --- a/tox.ini +++ b/tox.ini @@ -30,5 +30,4 @@ downloadcache = ~/cache/pip # TODO(marun) H404 multi line docstring should start with a summary ignore = E125,H301,H302,H404 show-source = true -builtins = _ exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,tools From 1a17997839a638231e8aeb1ad71e27b063b38c7d Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Sun, 16 Jun 2013 19:26:51 +0900 Subject: [PATCH 126/135] Allow subnet name in lb-vip-create and lb-pool-create Fix bug 1168998 Change-Id: I25bf357d069b99db467db43900d293f30c2a7d30 --- quantumclient/quantum/v2_0/lb/pool.py | 5 ++++- quantumclient/quantum/v2_0/lb/vip.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/quantumclient/quantum/v2_0/lb/pool.py b/quantumclient/quantum/v2_0/lb/pool.py index 3d1d58066..869cf7a35 100644 --- a/quantumclient/quantum/v2_0/lb/pool.py +++ b/quantumclient/quantum/v2_0/lb/pool.py @@ -73,14 +73,17 @@ def add_known_arguments(self, parser): help='the subnet on which the members of the pool will be located') def args2body(self, parsed_args): + _subnet_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'subnet', parsed_args.subnet_id) body = { self.resource: { 'admin_state_up': parsed_args.admin_state, + 'subnet_id': _subnet_id, }, } quantumv20.update_dict(parsed_args, body[self.resource], ['description', 'lb_method', 'name', - 'subnet_id', 'protocol', 'tenant_id']) + 'protocol', 'tenant_id']) return body diff --git a/quantumclient/quantum/v2_0/lb/vip.py b/quantumclient/quantum/v2_0/lb/vip.py index c3795c3c8..ced5b20dc 100644 --- a/quantumclient/quantum/v2_0/lb/vip.py +++ b/quantumclient/quantum/v2_0/lb/vip.py @@ -85,16 +85,19 @@ def add_known_arguments(self, parser): def args2body(self, parsed_args): _pool_id = quantumv20.find_resourceid_by_name_or_id( self.get_client(), 'pool', parsed_args.pool_id) + _subnet_id = quantumv20.find_resourceid_by_name_or_id( + self.get_client(), 'subnet', parsed_args.subnet_id) body = { self.resource: { 'pool_id': _pool_id, 'admin_state_up': parsed_args.admin_state, + 'subnet_id': _subnet_id, }, } quantumv20.update_dict(parsed_args, body[self.resource], ['address', 'connection_limit', 'description', 'name', 'protocol_port', 'protocol', - 'subnet_id', 'tenant_id']) + 'tenant_id']) return body From 01a82383fcfe9a5145f7cc63a9b4a57e65383a34 Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Thu, 7 Mar 2013 03:07:50 +0900 Subject: [PATCH 127/135] Support router-interface-add/delete by port_id Fixes bug 1061638 Change-Id: Ie36126a38627a6707e82116fb6b7bd97755f8fd0 --- quantumclient/quantum/v2_0/router.py | 79 ++++++++++++++++++---------- tests/unit/test_cli20.py | 4 +- tests/unit/test_cli20_router.py | 57 +++++++++++++++----- 3 files changed, 96 insertions(+), 44 deletions(-) diff --git a/quantumclient/quantum/v2_0/router.py b/quantumclient/quantum/v2_0/router.py index 693fd5681..e0387fb52 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/quantumclient/quantum/v2_0/router.py @@ -18,6 +18,7 @@ import argparse import logging +from quantumclient.common import exceptions from quantumclient.common import utils from quantumclient.quantum import v2_0 as quantumv20 @@ -94,55 +95,77 @@ class RouterInterfaceCommand(quantumv20.QuantumCommand): """Based class to Add/Remove router interface.""" api = 'network' - log = logging.getLogger(__name__ + '.AddInterfaceRouter') resource = 'router' + def call_api(self, quantum_client, router_id, body): + raise NotImplementedError() + + def success_message(self, router_id, portinfo): + raise NotImplementedError() + def get_parser(self, prog_name): parser = super(RouterInterfaceCommand, self).get_parser(prog_name) parser.add_argument( 'router_id', metavar='router-id', help='ID of the router') parser.add_argument( - 'subnet_id', metavar='subnet-id', - help='ID of the internal subnet for the interface') + 'interface', metavar='INTERFACE', + help='The format is "SUBNET|subnet=SUBNET|port=PORT". ' + 'Either a subnet or port must be specified. ' + 'Both ID and name are accepted as SUBNET or PORT. ' + 'Note that "subnet=" can be omitted when specifying subnet.') return parser - -class AddInterfaceRouter(RouterInterfaceCommand): - """Add an internal network interface to a router.""" - def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) quantum_client = self.get_client() quantum_client.format = parsed_args.request_format - #TODO(danwent): handle passing in port-id + + if '=' in parsed_args.interface: + resource, value = parsed_args.interface.split('=', 1) + if resource not in ['subnet', 'port']: + exceptions.CommandError('You must specify either subnet or ' + 'port for INTERFACE parameter.') + else: + resource = 'subnet' + value = parsed_args.interface + _router_id = quantumv20.find_resourceid_by_name_or_id( quantum_client, self.resource, parsed_args.router_id) - _subnet_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, 'subnet', parsed_args.subnet_id) - quantum_client.add_interface_router(_router_id, - {'subnet_id': _subnet_id}) - #TODO(danwent): print port ID that is added - print >>self.app.stdout, ( - _('Added interface to router %s') % parsed_args.router_id) + + _interface_id = quantumv20.find_resourceid_by_name_or_id( + quantum_client, resource, value) + body = {'%s_id' % resource: _interface_id} + + portinfo = self.call_api(quantum_client, _router_id, body) + print >>self.app.stdout, self.success_message(parsed_args.router_id, + portinfo) + + +class AddInterfaceRouter(RouterInterfaceCommand): + """Add an internal network interface to a router.""" + + log = logging.getLogger(__name__ + '.AddInterfaceRouter') + + def call_api(self, quantum_client, router_id, body): + return quantum_client.add_interface_router(router_id, body) + + def success_message(self, router_id, portinfo): + return (_('Added interface %(port)s to router %(router)s.') % + {'router': router_id, 'port': portinfo['port_id']}) class RemoveInterfaceRouter(RouterInterfaceCommand): """Remove an internal network interface from a router.""" - def run(self, parsed_args): - self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - #TODO(danwent): handle passing in port-id - _router_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, self.resource, parsed_args.router_id) - _subnet_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, 'subnet', parsed_args.subnet_id) - quantum_client.remove_interface_router(_router_id, - {'subnet_id': _subnet_id}) - print >>self.app.stdout, ( - _('Removed interface from router %s') % parsed_args.router_id) + log = logging.getLogger(__name__ + '.RemoveInterfaceRouter') + + def call_api(self, quantum_client, router_id, body): + return quantum_client.remove_interface_router(router_id, body) + + def success_message(self, router_id, portinfo): + # portinfo is not used since it is None for router-interface-delete. + return _('Removed interface from router %s.') % router_id class SetGatewayRouter(quantumv20.QuantumCommand): diff --git a/tests/unit/test_cli20.py b/tests/unit/test_cli20.py index 0eec0c80c..c89cdfa78 100644 --- a/tests/unit/test_cli20.py +++ b/tests/unit/test_cli20.py @@ -443,7 +443,7 @@ def _test_delete_resource(self, resource, cmd, myid, args): self.assertTrue(myid in _str) def _test_update_resource_action(self, resource, cmd, myid, action, args, - body): + body, retval=None): self.mox.StubOutWithMock(cmd, "get_client") self.mox.StubOutWithMock(self.client.httpclient, "request") cmd.get_client().MultipleTimes().AndReturn(self.client) @@ -453,7 +453,7 @@ def _test_update_resource_action(self, resource, cmd, myid, action, args, end_url(path % path_action, format=self.format), 'PUT', body=MyComparator(body, self.client), headers=mox.ContainsKeyValue( - 'X-Auth-Token', TOKEN)).AndReturn((MyResp(204), None)) + 'X-Auth-Token', TOKEN)).AndReturn((MyResp(204), retval)) args.extend(['--request-format', self.format]) self.mox.ReplayAll() cmd_parser = cmd.get_parser("delete_" + resource) diff --git a/tests/unit/test_cli20_router.py b/tests/unit/test_cli20_router.py index 96517f5fa..35ac577a1 100644 --- a/tests/unit/test_cli20_router.py +++ b/tests/unit/test_cli20_router.py @@ -120,27 +120,56 @@ def test_show_router(self): self._test_show_resource(resource, cmd, self.test_id, args, ['id', 'name']) - def test_add_interface(self): - """Add interface to router: myid subnetid.""" + def _test_add_remove_interface(self, action, mode, cmd, args): resource = 'router' + subcmd = '%s_router_interface' % action + if mode == 'port': + body = {'port_id': 'portid'} + else: + body = {'subnet_id': 'subnetid'} + if action == 'add': + retval = {'subnet_id': 'subnetid', 'port_id': 'portid'} + else: + retval = None + self._test_update_resource_action(resource, cmd, 'myid', + subcmd, args, + body, retval) + + def test_add_interface_compat(self): + """Add interface to router: myid subnetid.""" cmd = router.AddInterfaceRouter(test_cli20.MyApp(sys.stdout), None) args = ['myid', 'subnetid'] - self._test_update_resource_action(resource, cmd, 'myid', - 'add_router_interface', - args, - {'subnet_id': 'subnetid'} - ) + self._test_add_remove_interface('add', 'subnet', cmd, args) + + def test_add_interface_by_subnet(self): + """Add interface to router: myid subnet=subnetid.""" + cmd = router.AddInterfaceRouter(test_cli20.MyApp(sys.stdout), None) + args = ['myid', 'subnet=subnetid'] + self._test_add_remove_interface('add', 'subnet', cmd, args) - def test_del_interface(self): + def test_add_interface_by_port(self): + """Add interface to router: myid port=portid.""" + cmd = router.AddInterfaceRouter(test_cli20.MyApp(sys.stdout), None) + args = ['myid', 'port=portid'] + self._test_add_remove_interface('add', 'port', cmd, args) + + def test_del_interface_compat(self): """Delete interface from router: myid subnetid.""" - resource = 'router' cmd = router.RemoveInterfaceRouter(test_cli20.MyApp(sys.stdout), None) args = ['myid', 'subnetid'] - self._test_update_resource_action(resource, cmd, 'myid', - 'remove_router_interface', - args, - {'subnet_id': 'subnetid'} - ) + self._test_add_remove_interface('remove', 'subnet', cmd, args) + + def test_del_interface_by_subnet(self): + """Delete interface from router: myid subnet=subnetid.""" + cmd = router.RemoveInterfaceRouter(test_cli20.MyApp(sys.stdout), None) + args = ['myid', 'subnet=subnetid'] + self._test_add_remove_interface('remove', 'subnet', cmd, args) + + def test_del_interface_by_port(self): + """Delete interface from router: myid port=portid.""" + cmd = router.RemoveInterfaceRouter(test_cli20.MyApp(sys.stdout), None) + args = ['myid', 'port=portid'] + self._test_add_remove_interface('remove', 'port', cmd, args) def test_set_gateway(self): """Set external gateway for router: myid externalid.""" From a5076e620762a5560e694bb10f2c742cff3c57db Mon Sep 17 00:00:00 2001 From: Akihiro MOTOKI Date: Mon, 10 Jun 2013 22:04:56 +0900 Subject: [PATCH 128/135] Enables H404 check (multi line docstring) in flake8 This commit ensures docstring follows the docstring convention. Also enables H301 (one import per line) in flake8. There is already no violation the source codes. Change-Id: Icf6ce5bae40b2e6492c060a28587d825a1837b43 --- quantumclient/client.py | 7 +- quantumclient/common/exceptions.py | 7 - quantumclient/common/serializer.py | 9 +- quantumclient/common/utils.py | 10 +- quantumclient/shell.py | 5 +- quantumclient/v2_0/client.py | 365 ++++++++--------------------- tox.ini | 4 +- 7 files changed, 115 insertions(+), 292 deletions(-) diff --git a/quantumclient/client.py b/quantumclient/client.py index da91700cf..a4669c44a 100644 --- a/quantumclient/client.py +++ b/quantumclient/client.py @@ -238,9 +238,10 @@ def get_auth_info(self): 'endpoint_url': self.endpoint_url} def get_status_code(self, response): - """ - Returns the integer status code from the response, which - can be either a Webob.Response (used in testing) or httplib.Response + """Returns the integer status code from the response. + + Either a Webob.Response (used in testing) or httplib.Response + is returned. """ if hasattr(response, 'status_int'): return response.status_int diff --git a/quantumclient/common/exceptions.py b/quantumclient/common/exceptions.py index 24a92f63c..ee33ef7b8 100644 --- a/quantumclient/common/exceptions.py +++ b/quantumclient/common/exceptions.py @@ -91,17 +91,10 @@ class AlreadyAttachedClient(QuantumClientException): class Unauthorized(QuantumClientException): - """ - HTTP 401 - Unauthorized: bad credentials. - """ message = _("Unauthorized: bad credentials.") class Forbidden(QuantumClientException): - """ - HTTP 403 - Forbidden: your credentials don't give you access to this - resource. - """ message = _("Forbidden: your credentials don't give you access to this " "resource.") diff --git a/quantumclient/common/serializer.py b/quantumclient/common/serializer.py index a9d8db0a5..fa3a89103 100644 --- a/quantumclient/common/serializer.py +++ b/quantumclient/common/serializer.py @@ -66,7 +66,8 @@ def sanitizer(obj): class XMLDictSerializer(DictSerializer): def __init__(self, metadata=None, xmlns=None): - """ + """XMLDictSerializer constructor. + :param metadata: information needed to deserialize xml into a dictionary. :param xmlns: XML namespace to include with serialized xml @@ -80,7 +81,8 @@ def __init__(self, metadata=None, xmlns=None): self.xmlns = xmlns def default(self, data): - """ + """Default serializer of XMLDictSerializer. + :param data: expect data to contain a single key as XML root, or contain another '*_links' key as atom links. Other case will use 'VIRTUAL_ROOT_KEY' as XML root. @@ -232,7 +234,8 @@ def default(self, datastring): class XMLDeserializer(TextDeserializer): def __init__(self, metadata=None): - """ + """XMLDeserializer constructor. + :param metadata: information needed to deserialize xml into a dictionary. """ diff --git a/quantumclient/common/utils.py b/quantumclient/common/utils.py index b9fd0a869..192b46f37 100644 --- a/quantumclient/common/utils.py +++ b/quantumclient/common/utils.py @@ -31,9 +31,9 @@ def env(*vars, **kwargs): - """ - returns the first environment variable set - if none are non-empty, defaults to '' or keyword arg default + """Returns the first environment variable set. + + if none are non-empty, defaults to '' or keyword arg default. """ for v in vars: value = os.environ.get(v) @@ -76,7 +76,7 @@ def loads(s): def import_class(import_str): - """Returns a class from a string including module and class + """Returns a class from a string including module and class. :param import_str: a string representation of the class name :rtype: the requested class @@ -141,7 +141,7 @@ def str2bool(strbool): def str2dict(strdict): - ''' + '''Convert key1=value1,key2=value2,... string into dictionary. :param strdict: key1=value1,key2=value2 ''' diff --git a/quantumclient/shell.py b/quantumclient/shell.py index 7c6618be0..8e5d10592 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -432,10 +432,7 @@ def build_option_parser(self, description, version): return parser def _bash_completion(self): - """ - Prints all of the commands and options to stdout so that the - quantum's bash-completion script doesn't have to hard code them. - """ + """Prints all of the commands and options for bash-completion.""" commands = set() options = set() for option, _action in self.parser._option_string_actions.items(): diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 9303ca967..a2f3e5046 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -254,633 +254,465 @@ def show_extension(self, ext_alias, **_params): @APIParamsCall def list_ports(self, retrieve_all=True, **_params): - """ - Fetches a list of all networks for a tenant - """ + """Fetches a list of all networks for a tenant.""" # Pass filters in "params" argument to do_request return self.list('ports', self.ports_path, retrieve_all, **_params) @APIParamsCall def show_port(self, port, **_params): - """ - Fetches information of a certain network - """ + """Fetches information of a certain network.""" return self.get(self.port_path % (port), params=_params) @APIParamsCall def create_port(self, body=None): - """ - Creates a new port - """ + """Creates a new port.""" return self.post(self.ports_path, body=body) @APIParamsCall def update_port(self, port, body=None): - """ - Updates a port - """ + """Updates a port.""" return self.put(self.port_path % (port), body=body) @APIParamsCall def delete_port(self, port): - """ - Deletes the specified port - """ + """Deletes the specified port.""" return self.delete(self.port_path % (port)) @APIParamsCall def list_networks(self, retrieve_all=True, **_params): - """ - Fetches a list of all networks for a tenant - """ + """Fetches a list of all networks for a tenant.""" # Pass filters in "params" argument to do_request return self.list('networks', self.networks_path, retrieve_all, **_params) @APIParamsCall def show_network(self, network, **_params): - """ - Fetches information of a certain network - """ + """Fetches information of a certain network.""" return self.get(self.network_path % (network), params=_params) @APIParamsCall def create_network(self, body=None): - """ - Creates a new network - """ + """Creates a new network.""" return self.post(self.networks_path, body=body) @APIParamsCall def update_network(self, network, body=None): - """ - Updates a network - """ + """Updates a network.""" return self.put(self.network_path % (network), body=body) @APIParamsCall def delete_network(self, network): - """ - Deletes the specified network - """ + """Deletes the specified network.""" return self.delete(self.network_path % (network)) @APIParamsCall def list_subnets(self, retrieve_all=True, **_params): - """ - Fetches a list of all networks for a tenant - """ + """Fetches a list of all networks for a tenant.""" return self.list('subnets', self.subnets_path, retrieve_all, **_params) @APIParamsCall def show_subnet(self, subnet, **_params): - """ - Fetches information of a certain subnet - """ + """Fetches information of a certain subnet.""" return self.get(self.subnet_path % (subnet), params=_params) @APIParamsCall def create_subnet(self, body=None): - """ - Creates a new subnet - """ + """Creates a new subnet.""" return self.post(self.subnets_path, body=body) @APIParamsCall def update_subnet(self, subnet, body=None): - """ - Updates a subnet - """ + """Updates a subnet.""" return self.put(self.subnet_path % (subnet), body=body) @APIParamsCall def delete_subnet(self, subnet): - """ - Deletes the specified subnet - """ + """Deletes the specified subnet.""" return self.delete(self.subnet_path % (subnet)) @APIParamsCall def list_routers(self, retrieve_all=True, **_params): - """ - Fetches a list of all routers for a tenant - """ + """Fetches a list of all routers for a tenant.""" # Pass filters in "params" argument to do_request return self.list('routers', self.routers_path, retrieve_all, **_params) @APIParamsCall def show_router(self, router, **_params): - """ - Fetches information of a certain router - """ + """Fetches information of a certain router.""" return self.get(self.router_path % (router), params=_params) @APIParamsCall def create_router(self, body=None): - """ - Creates a new router - """ + """Creates a new router.""" return self.post(self.routers_path, body=body) @APIParamsCall def update_router(self, router, body=None): - """ - Updates a router - """ + """Updates a router.""" return self.put(self.router_path % (router), body=body) @APIParamsCall def delete_router(self, router): - """ - Deletes the specified router - """ + """Deletes the specified router.""" return self.delete(self.router_path % (router)) @APIParamsCall def add_interface_router(self, router, body=None): - """ - Adds an internal network interface to the specified router - """ + """Adds an internal network interface to the specified router.""" return self.put((self.router_path % router) + "/add_router_interface", body=body) @APIParamsCall def remove_interface_router(self, router, body=None): - """ - Removes an internal network interface from the specified router - """ + """Removes an internal network interface from the specified router.""" return self.put((self.router_path % router) + "/remove_router_interface", body=body) @APIParamsCall def add_gateway_router(self, router, body=None): - """ - Adds an external network gateway to the specified router - """ + """Adds an external network gateway to the specified router.""" return self.put((self.router_path % router), body={'router': {'external_gateway_info': body}}) @APIParamsCall def remove_gateway_router(self, router): - """ - Removes an external network gateway from the specified router - """ + """Removes an external network gateway from the specified router.""" return self.put((self.router_path % router), body={'router': {'external_gateway_info': {}}}) @APIParamsCall def list_floatingips(self, retrieve_all=True, **_params): - """ - Fetches a list of all floatingips for a tenant - """ + """Fetches a list of all floatingips for a tenant.""" # Pass filters in "params" argument to do_request return self.list('floatingips', self.floatingips_path, retrieve_all, **_params) @APIParamsCall def show_floatingip(self, floatingip, **_params): - """ - Fetches information of a certain floatingip - """ + """Fetches information of a certain floatingip.""" return self.get(self.floatingip_path % (floatingip), params=_params) @APIParamsCall def create_floatingip(self, body=None): - """ - Creates a new floatingip - """ + """Creates a new floatingip.""" return self.post(self.floatingips_path, body=body) @APIParamsCall def update_floatingip(self, floatingip, body=None): - """ - Updates a floatingip - """ + """Updates a floatingip.""" return self.put(self.floatingip_path % (floatingip), body=body) @APIParamsCall def delete_floatingip(self, floatingip): - """ - Deletes the specified floatingip - """ + """Deletes the specified floatingip.""" return self.delete(self.floatingip_path % (floatingip)) @APIParamsCall def create_security_group(self, body=None): - """ - Creates a new security group - """ + """Creates a new security group.""" return self.post(self.security_groups_path, body=body) @APIParamsCall def update_security_group(self, security_group, body=None): - """ - Updates a security group - """ + """Updates a security group.""" return self.put(self.security_group_path % security_group, body=body) @APIParamsCall def list_security_groups(self, retrieve_all=True, **_params): - """ - Fetches a list of all security groups for a tenant - """ + """Fetches a list of all security groups for a tenant.""" return self.list('security_groups', self.security_groups_path, retrieve_all, **_params) @APIParamsCall def show_security_group(self, security_group, **_params): - """ - Fetches information of a certain security group - """ + """Fetches information of a certain security group.""" return self.get(self.security_group_path % (security_group), params=_params) @APIParamsCall def delete_security_group(self, security_group): - """ - Deletes the specified security group - """ + """Deletes the specified security group.""" return self.delete(self.security_group_path % (security_group)) @APIParamsCall def create_security_group_rule(self, body=None): - """ - Creates a new security group rule - """ + """Creates a new security group rule.""" return self.post(self.security_group_rules_path, body=body) @APIParamsCall def delete_security_group_rule(self, security_group_rule): - """ - Deletes the specified security group rule - """ + """Deletes the specified security group rule.""" return self.delete(self.security_group_rule_path % (security_group_rule)) @APIParamsCall def list_security_group_rules(self, retrieve_all=True, **_params): - """ - Fetches a list of all security group rules for a tenant - """ + """Fetches a list of all security group rules for a tenant.""" return self.list('security_group_rules', self.security_group_rules_path, retrieve_all, **_params) @APIParamsCall def show_security_group_rule(self, security_group_rule, **_params): - """ - Fetches information of a certain security group rule - """ + """Fetches information of a certain security group rule.""" return self.get(self.security_group_rule_path % (security_group_rule), params=_params) @APIParamsCall def list_vips(self, retrieve_all=True, **_params): - """ - Fetches a list of all load balancer vips for a tenant - """ + """Fetches a list of all load balancer vips for a tenant.""" # Pass filters in "params" argument to do_request return self.list('vips', self.vips_path, retrieve_all, **_params) @APIParamsCall def show_vip(self, vip, **_params): - """ - Fetches information of a certain load balancer vip - """ + """Fetches information of a certain load balancer vip.""" return self.get(self.vip_path % (vip), params=_params) @APIParamsCall def create_vip(self, body=None): - """ - Creates a new load balancer vip - """ + """Creates a new load balancer vip.""" return self.post(self.vips_path, body=body) @APIParamsCall def update_vip(self, vip, body=None): - """ - Updates a load balancer vip - """ + """Updates a load balancer vip.""" return self.put(self.vip_path % (vip), body=body) @APIParamsCall def delete_vip(self, vip): - """ - Deletes the specified load balancer vip - """ + """Deletes the specified load balancer vip.""" return self.delete(self.vip_path % (vip)) @APIParamsCall def list_pools(self, retrieve_all=True, **_params): - """ - Fetches a list of all load balancer pools for a tenant - """ + """Fetches a list of all load balancer pools for a tenant.""" # Pass filters in "params" argument to do_request return self.list('pools', self.pools_path, retrieve_all, **_params) @APIParamsCall def show_pool(self, pool, **_params): - """ - Fetches information of a certain load balancer pool - """ + """Fetches information of a certain load balancer pool.""" return self.get(self.pool_path % (pool), params=_params) @APIParamsCall def create_pool(self, body=None): - """ - Creates a new load balancer pool - """ + """Creates a new load balancer pool.""" return self.post(self.pools_path, body=body) @APIParamsCall def update_pool(self, pool, body=None): - """ - Updates a load balancer pool - """ + """Updates a load balancer pool.""" return self.put(self.pool_path % (pool), body=body) @APIParamsCall def delete_pool(self, pool): - """ - Deletes the specified load balancer pool - """ + """Deletes the specified load balancer pool.""" return self.delete(self.pool_path % (pool)) @APIParamsCall def retrieve_pool_stats(self, pool, **_params): - """ - Retrieves stats for a certain load balancer pool - """ + """Retrieves stats for a certain load balancer pool.""" return self.get(self.pool_path_stats % (pool), params=_params) @APIParamsCall def list_members(self, retrieve_all=True, **_params): - """ - Fetches a list of all load balancer members for a tenant - """ + """Fetches a list of all load balancer members for a tenant.""" # Pass filters in "params" argument to do_request return self.list('members', self.members_path, retrieve_all, **_params) @APIParamsCall def show_member(self, member, **_params): - """ - Fetches information of a certain load balancer member - """ + """Fetches information of a certain load balancer member.""" return self.get(self.member_path % (member), params=_params) @APIParamsCall def create_member(self, body=None): - """ - Creates a new load balancer member - """ + """Creates a new load balancer member.""" return self.post(self.members_path, body=body) @APIParamsCall def update_member(self, member, body=None): - """ - Updates a load balancer member - """ + """Updates a load balancer member.""" return self.put(self.member_path % (member), body=body) @APIParamsCall def delete_member(self, member): - """ - Deletes the specified load balancer member - """ + """Deletes the specified load balancer member.""" return self.delete(self.member_path % (member)) @APIParamsCall def list_health_monitors(self, retrieve_all=True, **_params): - """ - Fetches a list of all load balancer health monitors for a tenant - """ + """Fetches a list of all load balancer health monitors for a tenant.""" # Pass filters in "params" argument to do_request return self.list('health_monitors', self.health_monitors_path, retrieve_all, **_params) @APIParamsCall def show_health_monitor(self, health_monitor, **_params): - """ - Fetches information of a certain load balancer health monitor - """ + """Fetches information of a certain load balancer health monitor.""" return self.get(self.health_monitor_path % (health_monitor), params=_params) @APIParamsCall def create_health_monitor(self, body=None): - """ - Creates a new load balancer health monitor - """ + """Creates a new load balancer health monitor.""" return self.post(self.health_monitors_path, body=body) @APIParamsCall def update_health_monitor(self, health_monitor, body=None): - """ - Updates a load balancer health monitor - """ + """Updates a load balancer health monitor.""" return self.put(self.health_monitor_path % (health_monitor), body=body) @APIParamsCall def delete_health_monitor(self, health_monitor): - """ - Deletes the specified load balancer health monitor - """ + """Deletes the specified load balancer health monitor.""" return self.delete(self.health_monitor_path % (health_monitor)) @APIParamsCall def associate_health_monitor(self, pool, body): - """ - Associate specified load balancer health monitor and pool - """ + """Associate specified load balancer health monitor and pool.""" return self.post(self.associate_pool_health_monitors_path % (pool), body=body) @APIParamsCall def disassociate_health_monitor(self, pool, health_monitor): - """ - Disassociate specified load balancer health monitor and pool - """ + """Disassociate specified load balancer health monitor and pool.""" path = (self.disassociate_pool_health_monitors_path % {'pool': pool, 'health_monitor': health_monitor}) return self.delete(path) @APIParamsCall def create_qos_queue(self, body=None): - """ - Creates a new queue - """ + """Creates a new queue.""" return self.post(self.qos_queues_path, body=body) @APIParamsCall def list_qos_queues(self, **_params): - """ - Fetches a list of all queues for a tenant - """ + """Fetches a list of all queues for a tenant.""" return self.get(self.qos_queues_path, params=_params) @APIParamsCall def show_qos_queue(self, queue, **_params): - """ - Fetches information of a certain queue - """ + """Fetches information of a certain queue.""" return self.get(self.qos_queue_path % (queue), params=_params) @APIParamsCall def delete_qos_queue(self, queue): - """ - Deletes the specified queue - """ + """Deletes the specified queue.""" return self.delete(self.qos_queue_path % (queue)) @APIParamsCall def list_agents(self, **_params): - """ - Fetches agents - """ + """Fetches agents.""" # Pass filters in "params" argument to do_request return self.get(self.agents_path, params=_params) @APIParamsCall def show_agent(self, agent, **_params): - """ - Fetches information of a certain agent - """ + """Fetches information of a certain agent.""" return self.get(self.agent_path % (agent), params=_params) @APIParamsCall def update_agent(self, agent, body=None): - """ - Updates an agent - """ + """Updates an agent.""" return self.put(self.agent_path % (agent), body=body) @APIParamsCall def delete_agent(self, agent): - """ - Deletes the specified agent - """ + """Deletes the specified agent.""" return self.delete(self.agent_path % (agent)) @APIParamsCall def list_network_gateways(self, **_params): - """ - Retrieve network gateways - """ + """Retrieve network gateways.""" return self.get(self.network_gateways_path, params=_params) @APIParamsCall def show_network_gateway(self, gateway_id, **_params): - """ - Fetch a network gateway - """ + """Fetch a network gateway.""" return self.get(self.network_gateway_path % gateway_id, params=_params) @APIParamsCall def create_network_gateway(self, body=None): - """ - Create a new network gateway - """ + """Create a new network gateway.""" return self.post(self.network_gateways_path, body=body) @APIParamsCall def update_network_gateway(self, gateway_id, body=None): - """ - Update a network gateway - """ + """Update a network gateway.""" return self.put(self.network_gateway_path % gateway_id, body=body) @APIParamsCall def delete_network_gateway(self, gateway_id): - """ - Delete the specified network gateway - """ + """Delete the specified network gateway.""" return self.delete(self.network_gateway_path % gateway_id) @APIParamsCall def connect_network_gateway(self, gateway_id, body=None): - """ - Connect a network gateway to the specified network - """ + """Connect a network gateway to the specified network.""" base_uri = self.network_gateway_path % gateway_id return self.put("%s/connect_network" % base_uri, body=body) @APIParamsCall def disconnect_network_gateway(self, gateway_id, body=None): - """ - Disconnect a network from the specified gateway - """ + """Disconnect a network from the specified gateway.""" base_uri = self.network_gateway_path % gateway_id return self.put("%s/disconnect_network" % base_uri, body=body) @APIParamsCall def list_dhcp_agent_hosting_networks(self, network, **_params): - """ - Fetches a list of dhcp agents hosting a network. - """ + """Fetches a list of dhcp agents hosting a network.""" return self.get((self.network_path + self.DHCP_AGENTS) % network, params=_params) @APIParamsCall def list_networks_on_dhcp_agent(self, dhcp_agent, **_params): - """ - Fetches a list of dhcp agents hosting a network. - """ + """Fetches a list of dhcp agents hosting a network.""" return self.get((self.agent_path + self.DHCP_NETS) % dhcp_agent, params=_params) @APIParamsCall def add_network_to_dhcp_agent(self, dhcp_agent, body=None): - """ - Adds a network to dhcp agent. - """ + """Adds a network to dhcp agent.""" return self.post((self.agent_path + self.DHCP_NETS) % dhcp_agent, body=body) @APIParamsCall def remove_network_from_dhcp_agent(self, dhcp_agent, network_id): - """ - Remove a network from dhcp agent. - """ + """Remove a network from dhcp agent.""" return self.delete((self.agent_path + self.DHCP_NETS + "/%s") % ( dhcp_agent, network_id)) @APIParamsCall def list_l3_agent_hosting_routers(self, router, **_params): - """ - Fetches a list of L3 agents hosting a router. - """ + """Fetches a list of L3 agents hosting a router.""" return self.get((self.router_path + self.L3_AGENTS) % router, params=_params) @APIParamsCall def list_routers_on_l3_agent(self, l3_agent, **_params): - """ - Fetches a list of L3 agents hosting a router. - """ + """Fetches a list of L3 agents hosting a router.""" return self.get((self.agent_path + self.L3_ROUTERS) % l3_agent, params=_params) @APIParamsCall def add_router_to_l3_agent(self, l3_agent, body): - """ - Adds a router to L3 agent. - """ + """Adds a router to L3 agent.""" return self.post((self.agent_path + self.L3_ROUTERS) % l3_agent, body=body) @APIParamsCall def remove_router_from_l3_agent(self, l3_agent, router_id): - """ - Remove a router from l3 agent. - """ + """Remove a router from l3 agent.""" return self.delete((self.agent_path + self.L3_ROUTERS + "/%s") % ( l3_agent, router_id)) @@ -937,9 +769,10 @@ def get_auth_info(self): return self.httpclient.get_auth_info() def get_status_code(self, response): - """ - Returns the integer status code from the response, which - can be either a Webob.Response (used in testing) or httplib.Response + """Returns the integer status code from the response. + + Either a Webob.Response (used in testing) or httplib.Response + is returned. """ if hasattr(response, 'status_int'): return response.status_int @@ -947,9 +780,10 @@ def get_status_code(self, response): return response.status def serialize(self, data): - """ - Serializes a dictionary with a single key (which can contain any - structure) into either xml or json + """Serializes a dictionary into either xml or json. + + A dictionary with a single key can be passed and + it can contain any structure. """ if data is None: return None @@ -961,28 +795,25 @@ def serialize(self, data): type(data)) def deserialize(self, data, status_code): - """ - Deserializes an xml or json string into a dictionary - """ + """Deserializes an xml or json string into a dictionary.""" if status_code == 204: return data return serializer.Serializer(self.get_attr_metadata()).deserialize( data, self.content_type())['body'] def content_type(self, _format=None): - """ - Returns the mime-type for either 'xml' or 'json'. Defaults to the - currently set format + """Returns the mime-type for either 'xml' or 'json'. + + Defaults to the currently set format. """ _format = _format or self.format return "application/%s" % (_format) def retry_request(self, method, action, body=None, headers=None, params=None): - """ - Call do_request with the default retry configuration. Only - idempotent requests should retry failed connection attempts. + """Call do_request with the default retry configuration. + Only idempotent requests should retry failed connection attempts. :raises: ConnectionFailed if the maximum # of retries is exceeded """ max_attempts = self.retries + 1 diff --git a/tox.ini b/tox.ini index fb9729e4b..b0f1aca4f 100644 --- a/tox.ini +++ b/tox.ini @@ -25,10 +25,8 @@ downloadcache = ~/cache/pip [flake8] # E125 continuation line does not distinguish itself from next logical line -# H301 one import per line # H302 import only modules -# TODO(marun) H404 multi line docstring should start with a summary -ignore = E125,H301,H302,H404 +ignore = E125,H302 show-source = true builtins = _ exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,tools From e7593e1a8401de7b8d9e3dc3f67533cb2455132f Mon Sep 17 00:00:00 2001 From: Roman Podolyaka Date: Tue, 18 Jun 2013 17:47:13 +0300 Subject: [PATCH 129/135] Fix mocking of HTTPClient.request() method. The test_extend_list_exceed_max_uri_len test case mocks the request() method of HTTPClient class so that it raises the RequestURITooLong exception. But the real method can't possibly raise it (Client.do_request() does this instead). Mocks should emulate the behavior of methods they stub out and not change it in any way. Fixes bug 1192197. Change-Id: I62b2db111ef251f95eb9aa9c9cc00b53fdbccc68 --- quantumclient/v2_0/client.py | 12 ++++++++---- tests/unit/test_cli20_network.py | 13 ++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/quantumclient/v2_0/client.py b/quantumclient/v2_0/client.py index 9303ca967..15b0193b2 100644 --- a/quantumclient/v2_0/client.py +++ b/quantumclient/v2_0/client.py @@ -907,6 +907,12 @@ def _handle_fault_response(self, status_code, response_body): # Raise the appropriate exception exception_handler_v20(status_code, des_error_body) + def _check_uri_length(self, action): + uri_len = len(self.httpclient.endpoint_url) + len(action) + if uri_len > self.MAX_URI_LEN: + raise exceptions.RequestURITooLong( + excess=uri_len - self.MAX_URI_LEN) + def do_request(self, method, action, body=None, headers=None, params=None): # Add format and tenant_id action += ".%s" % self.format @@ -916,10 +922,8 @@ def do_request(self, method, action, body=None, headers=None, params=None): action += '?' + urllib.urlencode(params, doseq=1) # Ensure client always has correct uri - do not guesstimate anything self.httpclient.authenticate_and_fetch_endpoint_url() - uri_len = len(self.httpclient.endpoint_url) + len(action) - if uri_len > self.MAX_URI_LEN: - raise exceptions.RequestURITooLong( - excess=uri_len - self.MAX_URI_LEN) + self._check_uri_length(action) + if body: body = self.serialize(body) self.httpclient.content_type = self.content_type() diff --git a/tests/unit/test_cli20_network.py b/tests/unit/test_cli20_network.py index a64740455..7df54ffbf 100644 --- a/tests/unit/test_cli20_network.py +++ b/tests/unit/test_cli20_network.py @@ -509,16 +509,15 @@ def test_extend_list_exceed_max_uri_len(self): def mox_calls(path, data): sub_data_lists = [data[:len(data) - 1], data[len(data) - 1:]] filters, response = self._build_test_data(data) + # 1 char of extra URI len will cause a split in 2 requests - self.client.httpclient.request( - test_cli20.end_url(path, 'fields=id&fields=cidr%s' % filters), - 'GET', - body=None, - headers=mox.ContainsKeyValue( - 'X-Auth-Token', test_cli20.TOKEN)).AndRaise( - exceptions.RequestURITooLong(excess=1)) + self.mox.StubOutWithMock(self.client, "_check_uri_length") + self.client._check_uri_length(mox.IgnoreArg()).AndRaise( + exceptions.RequestURITooLong(excess=1)) + for data in sub_data_lists: filters, response = self._build_test_data(data) + self.client._check_uri_length(mox.IgnoreArg()).AndReturn(None) self.client.httpclient.request( test_cli20.end_url(path, 'fields=id&fields=cidr%s' % filters), From 9389b782a9860fc0d28cc13ac4b041fd3fd79e87 Mon Sep 17 00:00:00 2001 From: Carl Baldwin Date: Thu, 13 Jun 2013 22:11:34 -0600 Subject: [PATCH 130/135] Make --version option print a more detailed client version. The python quantum client was printing "quantum 2.0" regardless of the version of the client. This seems to be the version of the API but people expect the version of the client to be printed. I looked at the implementation of --version in nova and keystone. The one in keystone worked and was fairly straight-forward utilizing code from the pbr package. I added this code to version.py rather than __init__.py. Change-Id: I53264b454eb16d5c294ad40ab6494387637950db Fixes: Bug #1190652 --- quantumclient/shell.py | 3 ++- quantumclient/version.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 quantumclient/version.py diff --git a/quantumclient/shell.py b/quantumclient/shell.py index afcabb7a0..53b5c93e7 100644 --- a/quantumclient/shell.py +++ b/quantumclient/shell.py @@ -32,6 +32,7 @@ from quantumclient.common import exceptions as exc from quantumclient.common import utils from quantumclient.openstack.common import strutils +from quantumclient.version import __version__ VERSION = '2.0' @@ -324,7 +325,7 @@ def build_option_parser(self, description, version): parser.add_argument( '--version', action='version', - version='%(prog)s {0}'.format(version), ) + version=__version__, ) parser.add_argument( '-v', '--verbose', action='count', diff --git a/quantumclient/version.py b/quantumclient/version.py new file mode 100644 index 000000000..937548452 --- /dev/null +++ b/quantumclient/version.py @@ -0,0 +1,22 @@ +# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# @author: Carl Baldwin, Hewlett-Packard + +import pbr.version + + +__version__ = pbr.version.VersionInfo('python-quantumclient').version_string() From ca7fb4b26e91829db5363ad10fcb3b1eac05cc8b Mon Sep 17 00:00:00 2001 From: Chuck Short Date: Sat, 1 Jun 2013 19:54:08 -0500 Subject: [PATCH 131/135] python3: Introduce py33 to tox.ini Introduce py33 to tox.ini to make testing with python3 easier. Change-Id: Id50ca397612d4948325a0ad0f39451a16b86d5bd Signed-off-by: Chuck Short --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index fb9729e4b..a298991eb 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py26,py27,pep8 +envlist = py26,py27,py33,pep8 [testenv] setenv = VIRTUAL_ENV={envdir} From 93ac15bfeb51ee6a097d878e4eeef69cd2b3a6bc Mon Sep 17 00:00:00 2001 From: Mark McClain Date: Tue, 2 Jul 2013 18:44:42 -0400 Subject: [PATCH 132/135] Rename quantumclient to neutronclient Implements Blueprint: remove-use-of-quantum Change-Id: Idebe92d56d277435ffd23f292984f9b8b8fdb2df --- .gitignore | 6 +- HACKING.rst | 16 +- README.rst | 2 +- doc/source/conf.py | 2 +- doc/source/index.rst | 22 +- quantum_test.sh => neutron_test.sh | 78 ++-- {quantumclient => neutronclient}/__init__.py | 0 {quantumclient => neutronclient}/client.py | 13 +- .../common/__init__.py | 2 +- .../common/clientmanager.py | 6 +- .../common/command.py | 0 .../common/constants.py | 0 .../common/exceptions.py | 48 +-- .../common/serializer.py | 12 +- .../common/utils.py | 4 +- .../neutron}/__init__.py | 0 .../neutron}/client.py | 18 +- .../neutron}/v2_0/__init__.py | 78 ++-- .../neutron}/v2_0/agent.py | 10 +- .../neutron}/v2_0/agentscheduler.py | 82 ++--- .../neutron}/v2_0/extension.py | 2 +- .../neutron}/v2_0/floatingip.py | 30 +- .../neutron}/v2_0/lb/__init__.py | 0 .../neutron}/v2_0/lb/healthmonitor.py | 40 +-- .../neutron}/v2_0/lb/member.py | 16 +- .../neutron}/v2_0/lb/pool.py | 24 +- .../neutron}/v2_0/lb/vip.py | 18 +- .../neutron}/v2_0/network.py | 18 +- .../neutron}/v2_0/nvp_qos_queue.py | 10 +- .../neutron}/v2_0/nvpnetworkgateway.py | 38 +- .../neutron}/v2_0/port.py | 30 +- .../neutron}/v2_0/quota.py | 52 +-- .../neutron}/v2_0/router.py | 72 ++-- .../neutron}/v2_0/securitygroup.py | 28 +- .../neutron}/v2_0/subnet.py | 18 +- .../openstack/__init__.py | 0 .../openstack/common/__init__.py | 0 .../openstack/common/exception.py | 2 +- .../openstack/common/gettextutils.py | 2 +- .../openstack/common/jsonutils.py | 2 +- .../openstack/common/strutils.py | 0 .../openstack/common/timeutils.py | 0 {quantumclient => neutronclient}/shell.py | 332 +++++++----------- .../tests/unit/test_utils.py | 2 +- .../v2_0/__init__.py | 0 .../v2_0/client.py | 52 +-- {quantumclient => neutronclient}/version.py | 2 +- openstack-common.conf | 2 +- setup.cfg | 6 +- tests/unit/lb/test_cli20_healthmonitor.py | 2 +- tests/unit/lb/test_cli20_member.py | 2 +- tests/unit/lb/test_cli20_pool.py | 2 +- tests/unit/lb/test_cli20_vip.py | 2 +- tests/unit/test_auth.py | 10 +- tests/unit/test_casual_args.py | 32 +- tests/unit/test_cli20.py | 10 +- tests/unit/test_cli20_extensions.py | 4 +- tests/unit/test_cli20_floatingips.py | 2 +- tests/unit/test_cli20_network.py | 8 +- tests/unit/test_cli20_nvp_queue.py | 2 +- tests/unit/test_cli20_nvpnetworkgateway.py | 2 +- tests/unit/test_cli20_port.py | 4 +- tests/unit/test_cli20_router.py | 4 +- tests/unit/test_cli20_securitygroup.py | 2 +- tests/unit/test_cli20_subnet.py | 2 +- tests/unit/test_name_or_id.py | 20 +- tests/unit/test_quota.py | 6 +- tests/unit/test_shell.py | 24 +- tests/unit/test_utils.py | 4 +- tools/neutron.bash_completion | 27 ++ tools/quantum.bash_completion | 27 -- 71 files changed, 658 insertions(+), 737 deletions(-) rename quantum_test.sh => neutron_test.sh (55%) rename {quantumclient => neutronclient}/__init__.py (100%) rename {quantumclient => neutronclient}/client.py (96%) rename {quantumclient => neutronclient}/common/__init__.py (93%) rename {quantumclient => neutronclient}/common/clientmanager.py (95%) rename {quantumclient => neutronclient}/common/command.py (100%) rename {quantumclient => neutronclient}/common/constants.py (100%) rename {quantumclient => neutronclient}/common/exceptions.py (74%) rename {quantumclient => neutronclient}/common/serializer.py (98%) rename {quantumclient => neutronclient}/common/utils.py (98%) rename {quantumclient/quantum => neutronclient/neutron}/__init__.py (100%) rename {quantumclient/quantum => neutronclient/neutron}/client.py (82%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/__init__.py (90%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/agent.py (88%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/agentscheduler.py (73%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/extension.py (96%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/floatingip.py (85%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/lb/__init__.py (100%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/lb/healthmonitor.py (83%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/lb/member.py (87%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/lb/pool.py (84%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/lb/vip.py (88%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/network.py (92%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/nvp_qos_queue.py (91%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/nvpnetworkgateway.py (82%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/port.py (88%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/quota.py (84%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/router.py (76%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/securitygroup.py (92%) rename {quantumclient/quantum => neutronclient/neutron}/v2_0/subnet.py (93%) rename {quantumclient => neutronclient}/openstack/__init__.py (100%) rename {quantumclient => neutronclient}/openstack/common/__init__.py (100%) rename {quantumclient => neutronclient}/openstack/common/exception.py (98%) rename {quantumclient => neutronclient}/openstack/common/gettextutils.py (93%) rename {quantumclient => neutronclient}/openstack/common/jsonutils.py (99%) rename {quantumclient => neutronclient}/openstack/common/strutils.py (100%) rename {quantumclient => neutronclient}/openstack/common/timeutils.py (100%) rename {quantumclient => neutronclient}/shell.py (59%) rename {quantumclient => neutronclient}/tests/unit/test_utils.py (97%) rename {quantumclient => neutronclient}/v2_0/__init__.py (100%) rename {quantumclient => neutronclient}/v2_0/client.py (96%) rename {quantumclient => neutronclient}/version.py (92%) create mode 100644 tools/neutron.bash_completion delete mode 100644 tools/quantum.bash_completion diff --git a/.gitignore b/.gitignore index 5b6e2cc85..841355240 100644 --- a/.gitignore +++ b/.gitignore @@ -8,9 +8,9 @@ build-stamp cover/* doc/build/ doc/source/api/ -python_quantumclient.egg-info/* -quantum/vcsversion.py -quantumclient/versioninfo +python_neutronclient.egg-info/* +neutron/vcsversion.py +neutronclient/versioninfo run_tests.err.log run_tests.log .autogenerated diff --git a/HACKING.rst b/HACKING.rst index c7643238e..ea5e86b7b 100644 --- a/HACKING.rst +++ b/HACKING.rst @@ -1,4 +1,4 @@ -QuantumClient Style Commandments +Neutron Style Commandments ================================ - Step 1: Read http://www.python.org/dev/peps/pep-0008/ @@ -39,7 +39,7 @@ Example:: \n {{third-party lib imports in human alphabetical order}} \n - {{quantum imports in human alphabetical order}} + {{neutron imports in human alphabetical order}} \n \n {{begin your code}} @@ -59,12 +59,12 @@ Example:: import eventlet import webob.exc - import quantum.api.networks - from quantum.api import ports - from quantum.db import models - from quantum.extensions import multiport - import quantum.manager - from quantum import service + import neutron.api.networks + from neutron.api import ports + from neutron.db import models + from neutron.extensions import multiport + import neutron.manager + from neutron import service Docstrings diff --git a/README.rst b/README.rst index 20b7a8978..ea8e44c88 100644 --- a/README.rst +++ b/README.rst @@ -1 +1 @@ -This is the client API library for Quantum. +This is the client API library for Neutron. diff --git a/doc/source/conf.py b/doc/source/conf.py index fba29db68..1d6023e2d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -4,7 +4,7 @@ import sys import os -project = 'python-quantumclient' +project = 'python-neutronclient' # -- General configuration --------------------------------------------- diff --git a/doc/source/index.rst b/doc/source/index.rst index f68f1e103..612a41ee1 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,19 +1,19 @@ Python bindings to the OpenStack Network API ============================================ -In order to use the python quantum client directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so:: +In order to use the python neutron client directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so:: >>> import logging - >>> from quantumclient.quantum import client + >>> from neutronclient.neutron import client >>> logging.basicConfig(level=logging.DEBUG) - >>> quantum = client.Client('2.0', endpoint_url=OS_URL, token=OS_TOKEN) - >>> quantum.format = 'json' + >>> neutron = client.Client('2.0', endpoint_url=OS_URL, token=OS_TOKEN) + >>> neutron.format = 'json' >>> network = {'name': 'mynetwork', 'admin_state_up': True} - >>> quantum.create_network({'network':network}) - >>> networks = quantum.list_networks(name='mynetwork') + >>> neutron.create_network({'network':network}) + >>> networks = neutron.list_networks(name='mynetwork') >>> print networks >>> network_id = networks['networks'][0]['id'] - >>> quantum.delete_network(network_id) + >>> neutron.delete_network(network_id) Command-line Tool @@ -27,21 +27,21 @@ In order to use the CLI, you must provide your OpenStack username, password, ten The command line tool will attempt to reauthenticate using your provided credentials for every request. You can override this behavior by manually supplying an auth token using ``--os-url`` and ``--os-auth-token``. You can alternatively set these environment variables:: - export OS_URL=http://quantum.example.org:9696/ + export OS_URL=http://neutron.example.org:9696/ export OS_TOKEN=3bcc3d3a03f44e3d8377f9247b0ad155 -If quantum server does not require authentication, besides these two arguments or environment variables (We can use any value as token.), we need manually supply ``--os-auth-strategy`` or set the environment variable:: +If neutron server does not require authentication, besides these two arguments or environment variables (We can use any value as token.), we need manually supply ``--os-auth-strategy`` or set the environment variable:: export OS_AUTH_STRATEGY=noauth -Once you've configured your authentication parameters, you can run ``quantum -h`` to see a complete listing of available commands. +Once you've configured your authentication parameters, you can run ``neutron -h`` to see a complete listing of available commands. Release Notes ============= 2.0 ----- -* support Quantum API 2.0 +* support Neutron API 2.0 2.2.0 ----- diff --git a/quantum_test.sh b/neutron_test.sh similarity index 55% rename from quantum_test.sh rename to neutron_test.sh index b42299beb..53aee7cf4 100755 --- a/quantum_test.sh +++ b/neutron_test.sh @@ -18,111 +18,111 @@ FORMAT=" --request-format xml" # test the CRUD of network network=mynet1 -quantum net-create $FORMAT $NOAUTH $network || die "fail to create network $network" -temp=`quantum net-list $FORMAT -- --name $network --fields id | wc -l` +neutron net-create $FORMAT $NOAUTH $network || die "fail to create network $network" +temp=`neutron net-list $FORMAT -- --name $network --fields id | wc -l` echo $temp if [ $temp -ne 5 ]; then die "networks with name $network is not unique or found" fi -network_id=`quantum net-list -- --name $network --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` +network_id=`neutron net-list -- --name $network --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` echo "ID of network with name $network is $network_id" -quantum net-show $FORMAT $network || die "fail to show network $network" -quantum net-show $FORMAT $network_id || die "fail to show network $network_id" +neutron net-show $FORMAT $network || die "fail to show network $network" +neutron net-show $FORMAT $network_id || die "fail to show network $network_id" -quantum net-update $FORMAT $network --admin_state_up False || die "fail to update network $network" -quantum net-update $FORMAT $network_id --admin_state_up True || die "fail to update network $network_id" +neutron net-update $FORMAT $network --admin_state_up False || die "fail to update network $network" +neutron net-update $FORMAT $network_id --admin_state_up True || die "fail to update network $network_id" -quantum net-list $FORMAT -c id -- --id fakeid || die "fail to list networks with column selection on empty list" +neutron net-list $FORMAT -c id -- --id fakeid || die "fail to list networks with column selection on empty list" # test the CRUD of subnet subnet=mysubnet1 cidr=10.0.1.3/24 -quantum subnet-create $FORMAT $NOAUTH $network $cidr --name $subnet || die "fail to create subnet $subnet" -tempsubnet=`quantum subnet-list $FORMAT -- --name $subnet --fields id | wc -l` +neutron subnet-create $FORMAT $NOAUTH $network $cidr --name $subnet || die "fail to create subnet $subnet" +tempsubnet=`neutron subnet-list $FORMAT -- --name $subnet --fields id | wc -l` echo $tempsubnet if [ $tempsubnet -ne 5 ]; then die "subnets with name $subnet is not unique or found" fi -subnet_id=`quantum subnet-list $FORMAT -- --name $subnet --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` +subnet_id=`neutron subnet-list $FORMAT -- --name $subnet --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` echo "ID of subnet with name $subnet is $subnet_id" -quantum subnet-show $FORMAT $subnet || die "fail to show subnet $subnet" -quantum subnet-show $FORMAT $subnet_id || die "fail to show subnet $subnet_id" +neutron subnet-show $FORMAT $subnet || die "fail to show subnet $subnet" +neutron subnet-show $FORMAT $subnet_id || die "fail to show subnet $subnet_id" -quantum subnet-update $FORMAT $subnet --dns_namesevers host1 || die "fail to update subnet $subnet" -quantum subnet-update $FORMAT $subnet_id --dns_namesevers host2 || die "fail to update subnet $subnet_id" +neutron subnet-update $FORMAT $subnet --dns_namesevers host1 || die "fail to update subnet $subnet" +neutron subnet-update $FORMAT $subnet_id --dns_namesevers host2 || die "fail to update subnet $subnet_id" # test the crud of ports port=myport1 -quantum port-create $FORMAT $NOAUTH $network --name $port || die "fail to create port $port" -tempport=`quantum port-list $FORMAT -- --name $port --fields id | wc -l` +neutron port-create $FORMAT $NOAUTH $network --name $port || die "fail to create port $port" +tempport=`neutron port-list $FORMAT -- --name $port --fields id | wc -l` echo $tempport if [ $tempport -ne 5 ]; then die "ports with name $port is not unique or found" fi -port_id=`quantum port-list $FORMAT -- --name $port --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` +port_id=`neutron port-list $FORMAT -- --name $port --fields id | tail -n 2 | head -n 1 | cut -d' ' -f 2` echo "ID of port with name $port is $port_id" -quantum port-show $FORMAT $port || die "fail to show port $port" -quantum port-show $FORMAT $port_id || die "fail to show port $port_id" +neutron port-show $FORMAT $port || die "fail to show port $port" +neutron port-show $FORMAT $port_id || die "fail to show port $port_id" -quantum port-update $FORMAT $port --device_id deviceid1 || die "fail to update port $port" -quantum port-update $FORMAT $port_id --device_id deviceid2 || die "fail to update port $port_id" +neutron port-update $FORMAT $port --device_id deviceid1 || die "fail to update port $port" +neutron port-update $FORMAT $port_id --device_id deviceid2 || die "fail to update port $port_id" # test quota commands RUD DEFAULT_NETWORKS=10 DEFAULT_PORTS=50 tenant_id=tenant_a tenant_id_b=tenant_b -quantum quota-update $FORMAT --tenant_id $tenant_id --network 30 || die "fail to update quota for tenant $tenant_id" -quantum quota-update $FORMAT --tenant_id $tenant_id_b --network 20 || die "fail to update quota for tenant $tenant_id" -networks=`quantum quota-list $FORMAT -c network -c tenant_id | grep $tenant_id | awk '{print $2}'` +neutron quota-update $FORMAT --tenant_id $tenant_id --network 30 || die "fail to update quota for tenant $tenant_id" +neutron quota-update $FORMAT --tenant_id $tenant_id_b --network 20 || die "fail to update quota for tenant $tenant_id" +networks=`neutron quota-list $FORMAT -c network -c tenant_id | grep $tenant_id | awk '{print $2}'` if [ $networks -ne 30 ]; then die "networks quota should be 30" fi -networks=`quantum quota-list $FORMAT -c network -c tenant_id | grep $tenant_id_b | awk '{print $2}'` +networks=`neutron quota-list $FORMAT -c network -c tenant_id | grep $tenant_id_b | awk '{print $2}'` if [ $networks -ne 20 ]; then die "networks quota should be 20" fi -networks=`quantum quota-show $FORMAT --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` +networks=`neutron quota-show $FORMAT --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` if [ $networks -ne 30 ]; then die "networks quota should be 30" fi -quantum quota-delete $FORMAT --tenant_id $tenant_id || die "fail to delete quota for tenant $tenant_id" -networks=`quantum quota-show $FORMAT --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` +neutron quota-delete $FORMAT --tenant_id $tenant_id || die "fail to delete quota for tenant $tenant_id" +networks=`neutron quota-show $FORMAT --tenant_id $tenant_id | grep network | awk -F'|' '{print $3}'` if [ $networks -ne $DEFAULT_NETWORKS ]; then die "networks quota should be $DEFAULT_NETWORKS" fi # update self if [ "t$NOAUTH" = "t" ]; then # with auth - quantum quota-update $FORMAT --port 99 || die "fail to update quota for self" - ports=`quantum quota-show $FORMAT | grep port | awk -F'|' '{print $3}'` + neutron quota-update $FORMAT --port 99 || die "fail to update quota for self" + ports=`neutron quota-show $FORMAT | grep port | awk -F'|' '{print $3}'` if [ $ports -ne 99 ]; then die "ports quota should be 99" fi - - ports=`quantum quota-list $FORMAT -c port | grep 99 | awk '{print $2}'` + + ports=`neutron quota-list $FORMAT -c port | grep 99 | awk '{print $2}'` if [ $ports -ne 99 ]; then die "ports quota should be 99" fi - quantum quota-delete $FORMAT || die "fail to delete quota for tenant self" - ports=`quantum quota-show $FORMAT | grep port | awk -F'|' '{print $3}'` + neutron quota-delete $FORMAT || die "fail to delete quota for tenant self" + ports=`neutron quota-show $FORMAT | grep port | awk -F'|' '{print $3}'` if [ $ports -ne $DEFAULT_PORTS ]; then die "ports quota should be $DEFAULT_PORTS" fi else # without auth - quantum quota-update $FORMAT --port 100 + neutron quota-update $FORMAT --port 100 if [ $? -eq 0 ]; then die "without valid context on server, quota update command should fail." fi - quantum quota-show $FORMAT + neutron quota-show $FORMAT if [ $? -eq 0 ]; then die "without valid context on server, quota show command should fail." fi - quantum quota-delete $FORMAT + neutron quota-delete $FORMAT if [ $? -eq 0 ]; then die "without valid context on server, quota delete command should fail." fi - quantum quota-list $FORMAT || die "fail to update quota for self" + neutron quota-list $FORMAT || die "fail to update quota for self" fi diff --git a/quantumclient/__init__.py b/neutronclient/__init__.py similarity index 100% rename from quantumclient/__init__.py rename to neutronclient/__init__.py diff --git a/quantumclient/client.py b/neutronclient/client.py similarity index 96% rename from quantumclient/client.py rename to neutronclient/client.py index a4669c44a..9dc80a354 100644 --- a/quantumclient/client.py +++ b/neutronclient/client.py @@ -29,12 +29,13 @@ import httplib2 -from quantumclient.common import exceptions -from quantumclient.common import utils +from neutronclient.common import exceptions +from neutronclient.common import utils _logger = logging.getLogger(__name__) -if 'QUANTUMCLIENT_DEBUG' in os.environ and os.environ['QUANTUMCLIENT_DEBUG']: + +if os.environ.get('NEUTRONCLIENT_DEBUG'): ch = logging.StreamHandler() _logger.setLevel(logging.DEBUG) _logger.addHandler(ch) @@ -61,7 +62,7 @@ def get_token(self): def url_for(self, attr=None, filter_value=None, service_type='network', endpoint_type='publicURL'): - """Fetch the URL from the Quantum service for + """Fetch the URL from the Neutron service for a particular endpoint type. If none given, return publicURL. """ @@ -91,7 +92,7 @@ def url_for(self, attr=None, filter_value=None, class HTTPClient(httplib2.Http): """Handles the REST calls and responses, include authn.""" - USER_AGENT = 'python-quantumclient' + USER_AGENT = 'python-neutronclient' def __init__(self, username=None, tenant_name=None, password=None, auth_url=None, @@ -215,7 +216,7 @@ def _get_endpoint_url(self): try: resp, body = self._cs_request(url, "GET") except exceptions.Unauthorized: - # rollback to authenticate() to handle case when quantum client + # rollback to authenticate() to handle case when neutron client # is initialized just before the token is expired self.authenticate() return self.endpoint_url diff --git a/quantumclient/common/__init__.py b/neutronclient/common/__init__.py similarity index 93% rename from quantumclient/common/__init__.py rename to neutronclient/common/__init__.py index 1415c50a3..47ca7088f 100644 --- a/quantumclient/common/__init__.py +++ b/neutronclient/common/__init__.py @@ -17,7 +17,7 @@ import gettext -t = gettext.translation('quantumclient', fallback=True) +t = gettext.translation('neutronclient', fallback=True) def _(msg): diff --git a/quantumclient/common/clientmanager.py b/neutronclient/common/clientmanager.py similarity index 95% rename from quantumclient/common/clientmanager.py rename to neutronclient/common/clientmanager.py index 4d219e489..8e0614d9f 100644 --- a/quantumclient/common/clientmanager.py +++ b/neutronclient/common/clientmanager.py @@ -20,8 +20,8 @@ import logging -from quantumclient import client -from quantumclient.quantum import client as quantum_client +from neutronclient import client +from neutronclient.neutron import client as neutron_client LOG = logging.getLogger(__name__) @@ -45,7 +45,7 @@ def __get__(self, instance, owner): class ClientManager(object): """Manages access to API clients, including authentication. """ - quantum = ClientCache(quantum_client.make_client) + neutron = ClientCache(neutron_client.make_client) def __init__(self, token=None, url=None, auth_url=None, diff --git a/quantumclient/common/command.py b/neutronclient/common/command.py similarity index 100% rename from quantumclient/common/command.py rename to neutronclient/common/command.py diff --git a/quantumclient/common/constants.py b/neutronclient/common/constants.py similarity index 100% rename from quantumclient/common/constants.py rename to neutronclient/common/constants.py diff --git a/quantumclient/common/exceptions.py b/neutronclient/common/exceptions.py similarity index 74% rename from quantumclient/common/exceptions.py rename to neutronclient/common/exceptions.py index ee33ef7b8..e02be309c 100644 --- a/quantumclient/common/exceptions.py +++ b/neutronclient/common/exceptions.py @@ -15,15 +15,15 @@ # License for the specific language governing permissions and limitations # under the License. -from quantumclient.common import _ +from neutronclient.common import _ """ -Quantum base exception handling. +Neutron base exception handling. """ -class QuantumException(Exception): - """Base Quantum Exception +class NeutronException(Exception): + """Base Neutron Exception Taken from nova.exception.NovaException To correctly use this class, inherit from it and define @@ -45,66 +45,66 @@ def __str__(self): return self._error_string -class NotFound(QuantumException): +class NotFound(NeutronException): pass -class QuantumClientException(QuantumException): +class NeutronClientException(NeutronException): def __init__(self, **kwargs): message = kwargs.get('message') self.status_code = kwargs.get('status_code', 0) if message: self.message = message - super(QuantumClientException, self).__init__(**kwargs) + super(NeutronClientException, self).__init__(**kwargs) # NOTE: on the client side, we use different exception types in order # to allow client library users to handle server exceptions in try...except # blocks. The actual error message is the one generated on the server side -class NetworkNotFoundClient(QuantumClientException): +class NetworkNotFoundClient(NeutronClientException): pass -class PortNotFoundClient(QuantumClientException): +class PortNotFoundClient(NeutronClientException): pass -class MalformedResponseBody(QuantumException): +class MalformedResponseBody(NeutronException): message = _("Malformed response body: %(reason)s") -class StateInvalidClient(QuantumClientException): +class StateInvalidClient(NeutronClientException): pass -class NetworkInUseClient(QuantumClientException): +class NetworkInUseClient(NeutronClientException): pass -class PortInUseClient(QuantumClientException): +class PortInUseClient(NeutronClientException): pass -class AlreadyAttachedClient(QuantumClientException): +class AlreadyAttachedClient(NeutronClientException): pass -class Unauthorized(QuantumClientException): +class Unauthorized(NeutronClientException): message = _("Unauthorized: bad credentials.") -class Forbidden(QuantumClientException): +class Forbidden(NeutronClientException): message = _("Forbidden: your credentials don't give you access to this " "resource.") -class EndpointNotFound(QuantumClientException): +class EndpointNotFound(NeutronClientException): """Could not find Service or Region in Service Catalog.""" message = _("Could not find Service or Region in Service Catalog.") -class EndpointTypeNotFound(QuantumClientException): +class EndpointTypeNotFound(NeutronClientException): """Could not find endpoint type in Service Catalog.""" def __str__(self): @@ -112,19 +112,19 @@ def __str__(self): return msg % repr(self.message) -class AmbiguousEndpoints(QuantumClientException): +class AmbiguousEndpoints(NeutronClientException): """Found more than one matching endpoint in Service Catalog.""" def __str__(self): return "AmbiguousEndpoints: %s" % repr(self.message) -class QuantumCLIError(QuantumClientException): +class NeutronCLIError(NeutronClientException): """Exception raised when command line parsing fails.""" pass -class RequestURITooLong(QuantumClientException): +class RequestURITooLong(NeutronClientException): """Raised when a request fails with HTTP error 414.""" def __init__(self, **kwargs): @@ -132,8 +132,8 @@ def __init__(self, **kwargs): super(RequestURITooLong, self).__init__(**kwargs) -class ConnectionFailed(QuantumClientException): - message = _("Connection to quantum failed: %(reason)s") +class ConnectionFailed(NeutronClientException): + message = _("Connection to neutron failed: %(reason)s") class BadInputError(Exception): @@ -146,7 +146,7 @@ def __init__(self, message=None): super(Error, self).__init__(message) -class MalformedRequestBody(QuantumException): +class MalformedRequestBody(NeutronException): message = _("Malformed request body: %(reason)s") diff --git a/quantumclient/common/serializer.py b/neutronclient/common/serializer.py similarity index 98% rename from quantumclient/common/serializer.py rename to neutronclient/common/serializer.py index 9c595e48c..b8f1c19ed 100644 --- a/quantumclient/common/serializer.py +++ b/neutronclient/common/serializer.py @@ -16,7 +16,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 ### -### Codes from quantum wsgi +### Codes from neutron wsgi ### import logging @@ -24,10 +24,10 @@ from xml.etree import ElementTree as etree from xml.parsers import expat -from quantumclient.common import constants -from quantumclient.common import exceptions as exception -from quantumclient.openstack.common.gettextutils import _ -from quantumclient.openstack.common import jsonutils +from neutronclient.common import constants +from neutronclient.common import exceptions as exception +from neutronclient.openstack.common.gettextutils import _ +from neutronclient.openstack.common import jsonutils LOG = logging.getLogger(__name__) @@ -360,7 +360,7 @@ def __call__(self, datastring): return self.default(datastring) -# NOTE(maru): this class is duplicated from quantum.wsgi +# NOTE(maru): this class is duplicated from neutron.wsgi class Serializer(object): """Serializes and deserializes dictionaries to certain MIME types.""" diff --git a/quantumclient/common/utils.py b/neutronclient/common/utils.py similarity index 98% rename from quantumclient/common/utils.py rename to neutronclient/common/utils.py index 192b46f37..ca2e6ecc4 100644 --- a/quantumclient/common/utils.py +++ b/neutronclient/common/utils.py @@ -26,8 +26,8 @@ import os import sys -from quantumclient.common import exceptions -from quantumclient.openstack.common import strutils +from neutronclient.common import exceptions +from neutronclient.openstack.common import strutils def env(*vars, **kwargs): diff --git a/quantumclient/quantum/__init__.py b/neutronclient/neutron/__init__.py similarity index 100% rename from quantumclient/quantum/__init__.py rename to neutronclient/neutron/__init__.py diff --git a/quantumclient/quantum/client.py b/neutronclient/neutron/client.py similarity index 82% rename from quantumclient/quantum/client.py rename to neutronclient/neutron/client.py index 1661a63f8..daf04815a 100644 --- a/quantumclient/quantum/client.py +++ b/neutronclient/neutron/client.py @@ -15,20 +15,20 @@ # # vim: tabstop=4 shiftwidth=4 softtabstop=4 -from quantumclient.common import exceptions -from quantumclient.common import utils +from neutronclient.common import exceptions +from neutronclient.common import utils API_NAME = 'network' API_VERSIONS = { - '2.0': 'quantumclient.v2_0.client.Client', + '2.0': 'neutronclient.v2_0.client.Client', } def make_client(instance): - """Returns an quantum client. + """Returns an neutron client. """ - quantum_client = utils.get_client_class( + neutron_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], API_VERSIONS, @@ -37,7 +37,7 @@ def make_client(instance): url = instance._url url = url.rstrip("/") if '2.0' == instance._api_version[API_NAME]: - client = quantum_client(username=instance._username, + client = neutron_client(username=instance._username, tenant_name=instance._tenant_name, password=instance._password, region_name=instance._region_name, @@ -53,12 +53,12 @@ def make_client(instance): def Client(api_version, *args, **kwargs): - """Return an quantum client. + """Return an neutron client. @param api_version: only 2.0 is supported now """ - quantum_client = utils.get_client_class( + neutron_client = utils.get_client_class( API_NAME, api_version, API_VERSIONS, ) - return quantum_client(*args, **kwargs) + return neutron_client(*args, **kwargs) diff --git a/quantumclient/quantum/v2_0/__init__.py b/neutronclient/neutron/v2_0/__init__.py similarity index 90% rename from quantumclient/quantum/v2_0/__init__.py rename to neutronclient/neutron/v2_0/__init__.py index 1be1d294f..9cb8cd1e2 100644 --- a/quantumclient/quantum/v2_0/__init__.py +++ b/neutronclient/neutron/v2_0/__init__.py @@ -23,10 +23,10 @@ from cliff import lister from cliff import show -from quantumclient.common import command -from quantumclient.common import exceptions -from quantumclient.common import utils -from quantumclient.openstack.common.gettextutils import _ +from neutronclient.common import command +from neutronclient.common import exceptions +from neutronclient.common import utils +from neutronclient.openstack.common.gettextutils import _ HEX_ELEM = '[0-9A-Fa-f]' UUID_PATTERN = '-'.join([HEX_ELEM + '{8}', HEX_ELEM + '{4}', @@ -55,14 +55,14 @@ def _find_resourceid_by_name(client, resource, name): msg = (_("Multiple %(resource)s matches found for name '%(name)s'," " use an ID to be more specific.") % {'resource': resource, 'name': name}) - raise exceptions.QuantumClientException( + raise exceptions.NeutronClientException( message=msg) elif len(info) == 0: not_found_message = (_("Unable to find %(resource)s with name " "'%(name)s'") % {'resource': resource, 'name': name}) # 404 is used to simulate server side behavior - raise exceptions.QuantumClientException( + raise exceptions.NeutronClientException( message=not_found_message, status_code=404) else: return info[0]['id'] @@ -257,7 +257,7 @@ def update_dict(obj, dict, attributes): class TableFormater(table.TableFormatter): """This class is used to keep consistency with prettytable 0.6. - https://bugs.launchpad.net/python-quantumclient/+bug/1165962 + https://bugs.launchpad.net/python-neutronclient/+bug/1165962 """ def emit_list(self, column_names, data, stdout, parsed_args): if column_names: @@ -267,22 +267,22 @@ def emit_list(self, column_names, data, stdout, parsed_args): stdout.write('\n') -class QuantumCommand(command.OpenStackCommand): +class NeutronCommand(command.OpenStackCommand): api = 'network' - log = logging.getLogger(__name__ + '.QuantumCommand') + log = logging.getLogger(__name__ + '.NeutronCommand') values_specs = [] json_indent = None def __init__(self, app, app_args): - super(QuantumCommand, self).__init__(app, app_args) + super(NeutronCommand, self).__init__(app, app_args) if hasattr(self, 'formatters'): self.formatters['table'] = TableFormater() def get_client(self): - return self.app.client_manager.quantum + return self.app.client_manager.neutron def get_parser(self, prog_name): - parser = super(QuantumCommand, self).get_parser(prog_name) + parser = super(NeutronCommand, self).get_parser(prog_name) parser.add_argument( '--request-format', help=_('the xml or json request format'), @@ -317,7 +317,7 @@ def args2body(self, parsed_args): return {} -class CreateCommand(QuantumCommand, show.ShowOne): +class CreateCommand(NeutronCommand, show.ShowOne): """Create a resource for a given tenant """ @@ -339,14 +339,14 @@ def get_parser(self, prog_name): def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format _extra_values = parse_args_to_dict(self.values_specs) _merge_args(self, parsed_args, _extra_values, self.values_specs) body = self.args2body(parsed_args) body[self.resource].update(_extra_values) - obj_creator = getattr(quantum_client, + obj_creator = getattr(neutron_client, "create_%s" % self.resource) data = obj_creator(body) self.format_output_data(data) @@ -359,7 +359,7 @@ def get_data(self, parsed_args): return zip(*sorted(info.iteritems())) -class UpdateCommand(QuantumCommand): +class UpdateCommand(NeutronCommand): """Update resource's information """ @@ -377,8 +377,8 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format _extra_values = parse_args_to_dict(self.values_specs) _merge_args(self, parsed_args, _extra_values, self.values_specs) @@ -390,10 +390,10 @@ def run(self, parsed_args): if not body[self.resource]: raise exceptions.CommandError( "Must specify new values to update %s" % self.resource) - _id = find_resourceid_by_name_or_id(quantum_client, + _id = find_resourceid_by_name_or_id(neutron_client, self.resource, parsed_args.id) - obj_updator = getattr(quantum_client, + obj_updator = getattr(neutron_client, "update_%s" % self.resource) obj_updator(_id, body) print >>self.app.stdout, ( @@ -402,7 +402,7 @@ def run(self, parsed_args): return -class DeleteCommand(QuantumCommand): +class DeleteCommand(NeutronCommand): """Delete a given resource """ @@ -425,12 +425,12 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - obj_deleter = getattr(quantum_client, + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + obj_deleter = getattr(neutron_client, "delete_%s" % self.resource) if self.allow_names: - _id = find_resourceid_by_name_or_id(quantum_client, self.resource, + _id = find_resourceid_by_name_or_id(neutron_client, self.resource, parsed_args.id) else: _id = parsed_args.id @@ -441,7 +441,7 @@ def run(self, parsed_args): return -class ListCommand(QuantumCommand, lister.Lister): +class ListCommand(NeutronCommand, lister.Lister): """List resources that belong to a given tenant """ @@ -473,16 +473,16 @@ def args2search_opts(self, parsed_args): search_opts.update({'verbose': 'True'}) return search_opts - def call_server(self, quantum_client, search_opts, parsed_args): - obj_lister = getattr(quantum_client, + def call_server(self, neutron_client, search_opts, parsed_args): + obj_lister = getattr(neutron_client, "list_%ss" % self.resource) data = obj_lister(**search_opts) return data def retrieve_list(self, parsed_args): - """Retrieve a list of resources from Quantum server""" - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + """Retrieve a list of resources from Neutron server""" + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format _extra_values = parse_args_to_dict(self.values_specs) _merge_args(self, parsed_args, _extra_values, self.values_specs) @@ -504,7 +504,7 @@ def retrieve_list(self, parsed_args): dirs = dirs[:len(keys)] if dirs: search_opts.update({'sort_dir': dirs}) - data = self.call_server(quantum_client, search_opts, parsed_args) + data = self.call_server(neutron_client, search_opts, parsed_args) collection = self.resource + "s" return data.get(collection, []) @@ -512,7 +512,7 @@ def extend_list(self, data, parsed_args): """Update a retrieved list. This method provides a way to modify a original list returned from - the quantum server. For example, you can add subnet cidr information + the neutron server. For example, you can add subnet cidr information to a list network. """ pass @@ -540,7 +540,7 @@ def get_data(self, parsed_args): return self.setup_columns(data, parsed_args) -class ShowCommand(QuantumCommand, show.ShowOne): +class ShowCommand(NeutronCommand, show.ShowOne): """Show information of a given resource """ @@ -564,8 +564,8 @@ def get_parser(self, prog_name): def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format params = {} if parsed_args.show_details: @@ -573,12 +573,12 @@ def get_data(self, parsed_args): if parsed_args.fields: params = {'fields': parsed_args.fields} if self.allow_names: - _id = find_resourceid_by_name_or_id(quantum_client, self.resource, + _id = find_resourceid_by_name_or_id(neutron_client, self.resource, parsed_args.id) else: _id = parsed_args.id - obj_shower = getattr(quantum_client, "show_%s" % self.resource) + obj_shower = getattr(neutron_client, "show_%s" % self.resource) data = obj_shower(_id, **params) self.format_output_data(data) resource = data[self.resource] diff --git a/quantumclient/quantum/v2_0/agent.py b/neutronclient/neutron/v2_0/agent.py similarity index 88% rename from quantumclient/quantum/v2_0/agent.py rename to neutronclient/neutron/v2_0/agent.py index 67bf87ed1..8c85f5441 100644 --- a/quantumclient/quantum/v2_0/agent.py +++ b/neutronclient/neutron/v2_0/agent.py @@ -17,7 +17,7 @@ import logging -from quantumclient.quantum import v2_0 as quantumV20 +from neutronclient.neutron import v2_0 as neutronV20 def _format_timestamp(component): @@ -27,7 +27,7 @@ def _format_timestamp(component): return '' -class ListAgent(quantumV20.ListCommand): +class ListAgent(neutronV20.ListCommand): """List agents.""" resource = 'agent' @@ -40,7 +40,7 @@ def extend_list(self, data, parsed_args): agent['alive'] = ":-)" if agent['alive'] else 'xxx' -class ShowAgent(quantumV20.ShowCommand): +class ShowAgent(neutronV20.ShowCommand): """Show information of a given agent.""" resource = 'agent' @@ -49,7 +49,7 @@ class ShowAgent(quantumV20.ShowCommand): json_indent = 5 -class DeleteAgent(quantumV20.DeleteCommand): +class DeleteAgent(neutronV20.DeleteCommand): """Delete a given agent.""" log = logging.getLogger(__name__ + '.DeleteAgent') @@ -57,7 +57,7 @@ class DeleteAgent(quantumV20.DeleteCommand): allow_names = False -class UpdateAgent(quantumV20.UpdateCommand): +class UpdateAgent(neutronV20.UpdateCommand): """Update a given agent.""" log = logging.getLogger(__name__ + '.UpdateAgent') diff --git a/quantumclient/quantum/v2_0/agentscheduler.py b/neutronclient/neutron/v2_0/agentscheduler.py similarity index 73% rename from quantumclient/quantum/v2_0/agentscheduler.py rename to neutronclient/neutron/v2_0/agentscheduler.py index 8e4137637..d76112551 100644 --- a/quantumclient/quantum/v2_0/agentscheduler.py +++ b/neutronclient/neutron/v2_0/agentscheduler.py @@ -17,14 +17,14 @@ import logging -from quantumclient.openstack.common.gettextutils import _ -from quantumclient.quantum import v2_0 as quantumV20 -from quantumclient.quantum.v2_0 import network -from quantumclient.quantum.v2_0 import router +from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.neutron.v2_0 import network +from neutronclient.neutron.v2_0 import router +from neutronclient.openstack.common.gettextutils import _ PERFECT_TIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" -class AddNetworkToDhcpAgent(quantumV20.QuantumCommand): +class AddNetworkToDhcpAgent(neutronV20.NeutronCommand): """Add a network to a DHCP agent.""" log = logging.getLogger(__name__ + '.AddNetworkToDhcpAgent') @@ -41,17 +41,17 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - _net_id = quantumV20.find_resourceid_by_name_or_id( - quantum_client, 'network', parsed_args.network) - quantum_client.add_network_to_dhcp_agent(parsed_args.dhcp_agent, + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + _net_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, 'network', parsed_args.network) + neutron_client.add_network_to_dhcp_agent(parsed_args.dhcp_agent, {'network_id': _net_id}) print >>self.app.stdout, ( _('Added network %s to DHCP agent') % parsed_args.network) -class RemoveNetworkFromDhcpAgent(quantumV20.QuantumCommand): +class RemoveNetworkFromDhcpAgent(neutronV20.NeutronCommand): """Remove a network from a DHCP agent.""" log = logging.getLogger(__name__ + '.RemoveNetworkFromDhcpAgent') @@ -67,11 +67,11 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - _net_id = quantumV20.find_resourceid_by_name_or_id( - quantum_client, 'network', parsed_args.network) - quantum_client.remove_network_from_dhcp_agent( + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + _net_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, 'network', parsed_args.network) + neutron_client.remove_network_from_dhcp_agent( parsed_args.dhcp_agent, _net_id) print >>self.app.stdout, ( _('Removed network %s to DHCP agent') % parsed_args.network) @@ -91,13 +91,13 @@ def get_parser(self, prog_name): help='ID of the DHCP agent') return parser - def call_server(self, quantum_client, search_opts, parsed_args): - data = quantum_client.list_networks_on_dhcp_agent( + def call_server(self, neutron_client, search_opts, parsed_args): + data = neutron_client.list_networks_on_dhcp_agent( parsed_args.dhcp_agent, **search_opts) return data -class ListDhcpAgentsHostingNetwork(quantumV20.ListCommand): +class ListDhcpAgentsHostingNetwork(neutronV20.ListCommand): """List DHCP agents hosting a network.""" resource = 'agent' @@ -118,16 +118,16 @@ def extend_list(self, data, parsed_args): for agent in data: agent['alive'] = ":-)" if agent['alive'] else 'xxx' - def call_server(self, quantum_client, search_opts, parsed_args): - _id = quantumV20.find_resourceid_by_name_or_id(quantum_client, + def call_server(self, neutron_client, search_opts, parsed_args): + _id = neutronV20.find_resourceid_by_name_or_id(neutron_client, 'network', parsed_args.network) search_opts['network'] = _id - data = quantum_client.list_dhcp_agent_hosting_networks(**search_opts) + data = neutron_client.list_dhcp_agent_hosting_networks(**search_opts) return data -class AddRouterToL3Agent(quantumV20.QuantumCommand): +class AddRouterToL3Agent(neutronV20.NeutronCommand): """Add a router to a L3 agent.""" log = logging.getLogger(__name__ + '.AddRouterToL3Agent') @@ -144,17 +144,17 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - _id = quantumV20.find_resourceid_by_name_or_id( - quantum_client, 'router', parsed_args.router) - quantum_client.add_router_to_l3_agent(parsed_args.l3_agent, + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + _id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, 'router', parsed_args.router) + neutron_client.add_router_to_l3_agent(parsed_args.l3_agent, {'router_id': _id}) print >>self.app.stdout, ( _('Added router %s to L3 agent') % parsed_args.router) -class RemoveRouterFromL3Agent(quantumV20.QuantumCommand): +class RemoveRouterFromL3Agent(neutronV20.NeutronCommand): """Remove a router from a L3 agent.""" log = logging.getLogger(__name__ + '.RemoveRouterFromL3Agent') @@ -171,17 +171,17 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - _id = quantumV20.find_resourceid_by_name_or_id( - quantum_client, 'router', parsed_args.router) - quantum_client.remove_router_from_l3_agent( + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + _id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, 'router', parsed_args.router) + neutron_client.remove_router_from_l3_agent( parsed_args.l3_agent, _id) print >>self.app.stdout, ( _('Removed Router %s to L3 agent') % parsed_args.router) -class ListRoutersOnL3Agent(quantumV20.ListCommand): +class ListRoutersOnL3Agent(neutronV20.ListCommand): """List the routers on a L3 agent.""" log = logging.getLogger(__name__ + '.ListRoutersOnL3Agent') @@ -199,13 +199,13 @@ def get_parser(self, prog_name): help='ID of the L3 agent to query') return parser - def call_server(self, quantum_client, search_opts, parsed_args): - data = quantum_client.list_routers_on_l3_agent( + def call_server(self, neutron_client, search_opts, parsed_args): + data = neutron_client.list_routers_on_l3_agent( parsed_args.l3_agent, **search_opts) return data -class ListL3AgentsHostingRouter(quantumV20.ListCommand): +class ListL3AgentsHostingRouter(neutronV20.ListCommand): """List L3 agents hosting a router.""" resource = 'agent' @@ -225,10 +225,10 @@ def extend_list(self, data, parsed_args): for agent in data: agent['alive'] = ":-)" if agent['alive'] else 'xxx' - def call_server(self, quantum_client, search_opts, parsed_args): - _id = quantumV20.find_resourceid_by_name_or_id(quantum_client, + def call_server(self, neutron_client, search_opts, parsed_args): + _id = neutronV20.find_resourceid_by_name_or_id(neutron_client, 'router', parsed_args.router) search_opts['router'] = _id - data = quantum_client.list_l3_agent_hosting_routers(**search_opts) + data = neutron_client.list_l3_agent_hosting_routers(**search_opts) return data diff --git a/quantumclient/quantum/v2_0/extension.py b/neutronclient/neutron/v2_0/extension.py similarity index 96% rename from quantumclient/quantum/v2_0/extension.py rename to neutronclient/neutron/v2_0/extension.py index 1ebea6fe7..b2a641135 100644 --- a/quantumclient/quantum/v2_0/extension.py +++ b/neutronclient/neutron/v2_0/extension.py @@ -17,7 +17,7 @@ import logging -from quantumclient.quantum import v2_0 as cmd_base +from neutronclient.neutron import v2_0 as cmd_base class ListExt(cmd_base.ListCommand): diff --git a/quantumclient/quantum/v2_0/floatingip.py b/neutronclient/neutron/v2_0/floatingip.py similarity index 85% rename from quantumclient/quantum/v2_0/floatingip.py rename to neutronclient/neutron/v2_0/floatingip.py index 1d82444f2..7931f31f3 100644 --- a/quantumclient/quantum/v2_0/floatingip.py +++ b/neutronclient/neutron/v2_0/floatingip.py @@ -18,11 +18,11 @@ import argparse import logging -from quantumclient.openstack.common.gettextutils import _ -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.openstack.common.gettextutils import _ -class ListFloatingIP(quantumv20.ListCommand): +class ListFloatingIP(neutronV20.ListCommand): """List floating ips that belong to a given tenant.""" resource = 'floatingip' @@ -33,7 +33,7 @@ class ListFloatingIP(quantumv20.ListCommand): sorting_support = True -class ShowFloatingIP(quantumv20.ShowCommand): +class ShowFloatingIP(neutronV20.ShowCommand): """Show information of a given floating ip.""" resource = 'floatingip' @@ -41,7 +41,7 @@ class ShowFloatingIP(quantumv20.ShowCommand): allow_names = False -class CreateFloatingIP(quantumv20.CreateCommand): +class CreateFloatingIP(neutronV20.CreateCommand): """Create a floating ip for a given tenant.""" resource = 'floatingip' @@ -66,7 +66,7 @@ def add_known_arguments(self, parser): help=argparse.SUPPRESS) def args2body(self, parsed_args): - _network_id = quantumv20.find_resourceid_by_name_or_id( + _network_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'network', parsed_args.floating_network_id) body = {self.resource: {'floating_network_id': _network_id}} if parsed_args.port_id: @@ -79,7 +79,7 @@ def args2body(self, parsed_args): return body -class DeleteFloatingIP(quantumv20.DeleteCommand): +class DeleteFloatingIP(neutronV20.DeleteCommand): """Delete a given floating ip.""" log = logging.getLogger(__name__ + '.DeleteFloatingIP') @@ -87,7 +87,7 @@ class DeleteFloatingIP(quantumv20.DeleteCommand): allow_names = False -class AssociateFloatingIP(quantumv20.QuantumCommand): +class AssociateFloatingIP(neutronV20.NeutronCommand): """Create a mapping between a floating ip and a fixed ip.""" api = 'network' @@ -113,20 +113,20 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format update_dict = {} if parsed_args.port_id: update_dict['port_id'] = parsed_args.port_id if parsed_args.fixed_ip_address: update_dict['fixed_ip_address'] = parsed_args.fixed_ip_address - quantum_client.update_floatingip(parsed_args.floatingip_id, + neutron_client.update_floatingip(parsed_args.floatingip_id, {'floatingip': update_dict}) print >>self.app.stdout, ( _('Associated floatingip %s') % parsed_args.floatingip_id) -class DisassociateFloatingIP(quantumv20.QuantumCommand): +class DisassociateFloatingIP(neutronV20.NeutronCommand): """Remove a mapping from a floating ip to a fixed ip. """ @@ -143,9 +143,9 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - quantum_client.update_floatingip(parsed_args.floatingip_id, + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + neutron_client.update_floatingip(parsed_args.floatingip_id, {'floatingip': {'port_id': None}}) print >>self.app.stdout, ( _('Disassociated floatingip %s') % parsed_args.floatingip_id) diff --git a/quantumclient/quantum/v2_0/lb/__init__.py b/neutronclient/neutron/v2_0/lb/__init__.py similarity index 100% rename from quantumclient/quantum/v2_0/lb/__init__.py rename to neutronclient/neutron/v2_0/lb/__init__.py diff --git a/quantumclient/quantum/v2_0/lb/healthmonitor.py b/neutronclient/neutron/v2_0/lb/healthmonitor.py similarity index 83% rename from quantumclient/quantum/v2_0/lb/healthmonitor.py rename to neutronclient/neutron/v2_0/lb/healthmonitor.py index f0a828b07..a6e847a9d 100644 --- a/quantumclient/quantum/v2_0/lb/healthmonitor.py +++ b/neutronclient/neutron/v2_0/lb/healthmonitor.py @@ -19,11 +19,11 @@ import logging -from quantumclient.openstack.common.gettextutils import _ -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.openstack.common.gettextutils import _ -class ListHealthMonitor(quantumv20.ListCommand): +class ListHealthMonitor(neutronV20.ListCommand): """List healthmonitors that belong to a given tenant.""" resource = 'health_monitor' @@ -33,14 +33,14 @@ class ListHealthMonitor(quantumv20.ListCommand): sorting_support = True -class ShowHealthMonitor(quantumv20.ShowCommand): +class ShowHealthMonitor(neutronV20.ShowCommand): """Show information of a given healthmonitor.""" resource = 'health_monitor' log = logging.getLogger(__name__ + '.ShowHealthMonitor') -class CreateHealthMonitor(quantumv20.CreateCommand): +class CreateHealthMonitor(neutronV20.CreateCommand): """Create a healthmonitor.""" resource = 'health_monitor' @@ -99,27 +99,27 @@ def args2body(self, parsed_args): 'type': parsed_args.type, }, } - quantumv20.update_dict(parsed_args, body[self.resource], + neutronV20.update_dict(parsed_args, body[self.resource], ['expected_codes', 'http_method', 'url_path', 'tenant_id']) return body -class UpdateHealthMonitor(quantumv20.UpdateCommand): +class UpdateHealthMonitor(neutronV20.UpdateCommand): """Update a given healthmonitor.""" resource = 'health_monitor' log = logging.getLogger(__name__ + '.UpdateHealthMonitor') -class DeleteHealthMonitor(quantumv20.DeleteCommand): +class DeleteHealthMonitor(neutronV20.DeleteCommand): """Delete a given healthmonitor.""" resource = 'health_monitor' log = logging.getLogger(__name__ + '.DeleteHealthMonitor') -class AssociateHealthMonitor(quantumv20.QuantumCommand): +class AssociateHealthMonitor(neutronV20.NeutronCommand): """Create a mapping between a health monitor and a pool.""" log = logging.getLogger(__name__ + '.AssociateHealthMonitor') @@ -136,17 +136,17 @@ def get_parser(self, prog_name): return parser def run(self, parsed_args): - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format body = {'health_monitor': {'id': parsed_args.health_monitor_id}} - pool_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, 'pool', parsed_args.pool_id) - quantum_client.associate_health_monitor(pool_id, body) + pool_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, 'pool', parsed_args.pool_id) + neutron_client.associate_health_monitor(pool_id, body) print >>self.app.stdout, (_('Associated health monitor ' '%s') % parsed_args.health_monitor_id) -class DisassociateHealthMonitor(quantumv20.QuantumCommand): +class DisassociateHealthMonitor(neutronV20.NeutronCommand): """Remove a mapping from a health monitor to a pool.""" log = logging.getLogger(__name__ + '.DisassociateHealthMonitor') @@ -163,11 +163,11 @@ def get_parser(self, prog_name): return parser def run(self, parsed_args): - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - pool_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, 'pool', parsed_args.pool_id) - quantum_client.disassociate_health_monitor(pool_id, + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + pool_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, 'pool', parsed_args.pool_id) + neutron_client.disassociate_health_monitor(pool_id, parsed_args .health_monitor_id) print >>self.app.stdout, (_('Disassociated health monitor ' diff --git a/quantumclient/quantum/v2_0/lb/member.py b/neutronclient/neutron/v2_0/lb/member.py similarity index 87% rename from quantumclient/quantum/v2_0/lb/member.py rename to neutronclient/neutron/v2_0/lb/member.py index 05fb430e2..b13fdcd86 100644 --- a/quantumclient/quantum/v2_0/lb/member.py +++ b/neutronclient/neutron/v2_0/lb/member.py @@ -19,10 +19,10 @@ import logging -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.neutron import v2_0 as neutronV20 -class ListMember(quantumv20.ListCommand): +class ListMember(neutronV20.ListCommand): """List members that belong to a given tenant.""" resource = 'member' @@ -34,14 +34,14 @@ class ListMember(quantumv20.ListCommand): sorting_support = True -class ShowMember(quantumv20.ShowCommand): +class ShowMember(neutronV20.ShowCommand): """Show information of a given member.""" resource = 'member' log = logging.getLogger(__name__ + '.ShowMember') -class CreateMember(quantumv20.CreateCommand): +class CreateMember(neutronV20.CreateCommand): """Create a member.""" resource = 'member' @@ -69,7 +69,7 @@ def add_known_arguments(self, parser): 'connections. ') def args2body(self, parsed_args): - _pool_id = quantumv20.find_resourceid_by_name_or_id( + _pool_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'pool', parsed_args.pool_id) body = { self.resource: { @@ -77,7 +77,7 @@ def args2body(self, parsed_args): 'admin_state_up': parsed_args.admin_state, }, } - quantumv20.update_dict( + neutronV20.update_dict( parsed_args, body[self.resource], ['address', 'protocol_port', 'weight', 'tenant_id'] @@ -85,14 +85,14 @@ def args2body(self, parsed_args): return body -class UpdateMember(quantumv20.UpdateCommand): +class UpdateMember(neutronV20.UpdateCommand): """Update a given member.""" resource = 'member' log = logging.getLogger(__name__ + '.UpdateMember') -class DeleteMember(quantumv20.DeleteCommand): +class DeleteMember(neutronV20.DeleteCommand): """Delete a given member.""" resource = 'member' diff --git a/quantumclient/quantum/v2_0/lb/pool.py b/neutronclient/neutron/v2_0/lb/pool.py similarity index 84% rename from quantumclient/quantum/v2_0/lb/pool.py rename to neutronclient/neutron/v2_0/lb/pool.py index 869cf7a35..4fb1e79a8 100644 --- a/quantumclient/quantum/v2_0/lb/pool.py +++ b/neutronclient/neutron/v2_0/lb/pool.py @@ -19,10 +19,10 @@ import logging -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.neutron import v2_0 as neutronV20 -class ListPool(quantumv20.ListCommand): +class ListPool(neutronV20.ListCommand): """List pools that belong to a given tenant.""" resource = 'pool' @@ -33,14 +33,14 @@ class ListPool(quantumv20.ListCommand): sorting_support = True -class ShowPool(quantumv20.ShowCommand): +class ShowPool(neutronV20.ShowCommand): """Show information of a given pool.""" resource = 'pool' log = logging.getLogger(__name__ + '.ShowPool') -class CreatePool(quantumv20.CreateCommand): +class CreatePool(neutronV20.CreateCommand): """Create a pool.""" resource = 'pool' @@ -73,7 +73,7 @@ def add_known_arguments(self, parser): help='the subnet on which the members of the pool will be located') def args2body(self, parsed_args): - _subnet_id = quantumv20.find_resourceid_by_name_or_id( + _subnet_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'subnet', parsed_args.subnet_id) body = { self.resource: { @@ -81,27 +81,27 @@ def args2body(self, parsed_args): 'subnet_id': _subnet_id, }, } - quantumv20.update_dict(parsed_args, body[self.resource], + neutronV20.update_dict(parsed_args, body[self.resource], ['description', 'lb_method', 'name', 'protocol', 'tenant_id']) return body -class UpdatePool(quantumv20.UpdateCommand): +class UpdatePool(neutronV20.UpdateCommand): """Update a given pool.""" resource = 'pool' log = logging.getLogger(__name__ + '.UpdatePool') -class DeletePool(quantumv20.DeleteCommand): +class DeletePool(neutronV20.DeleteCommand): """Delete a given pool.""" resource = 'pool' log = logging.getLogger(__name__ + '.DeletePool') -class RetrievePoolStats(quantumv20.ShowCommand): +class RetrievePoolStats(neutronV20.ShowCommand): """Retrieve stats for a given pool.""" resource = 'pool' @@ -109,13 +109,13 @@ class RetrievePoolStats(quantumv20.ShowCommand): def get_data(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format params = {} if parsed_args.fields: params = {'fields': parsed_args.fields} - data = quantum_client.retrieve_pool_stats(parsed_args.id, **params) + data = neutron_client.retrieve_pool_stats(parsed_args.id, **params) self.format_output_data(data) stats = data['stats'] if 'stats' in data: diff --git a/quantumclient/quantum/v2_0/lb/vip.py b/neutronclient/neutron/v2_0/lb/vip.py similarity index 88% rename from quantumclient/quantum/v2_0/lb/vip.py rename to neutronclient/neutron/v2_0/lb/vip.py index ced5b20dc..f836862bf 100644 --- a/quantumclient/quantum/v2_0/lb/vip.py +++ b/neutronclient/neutron/v2_0/lb/vip.py @@ -19,10 +19,10 @@ import logging -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.neutron import v2_0 as neutronV20 -class ListVip(quantumv20.ListCommand): +class ListVip(neutronV20.ListCommand): """List vips that belong to a given tenant.""" resource = 'vip' @@ -33,14 +33,14 @@ class ListVip(quantumv20.ListCommand): sorting_support = True -class ShowVip(quantumv20.ShowCommand): +class ShowVip(neutronV20.ShowCommand): """Show information of a given vip.""" resource = 'vip' log = logging.getLogger(__name__ + '.ShowVip') -class CreateVip(quantumv20.CreateCommand): +class CreateVip(neutronV20.CreateCommand): """Create a vip.""" resource = 'vip' @@ -83,9 +83,9 @@ def add_known_arguments(self, parser): help='the subnet on which to allocate the vip address') def args2body(self, parsed_args): - _pool_id = quantumv20.find_resourceid_by_name_or_id( + _pool_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'pool', parsed_args.pool_id) - _subnet_id = quantumv20.find_resourceid_by_name_or_id( + _subnet_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'subnet', parsed_args.subnet_id) body = { self.resource: { @@ -94,21 +94,21 @@ def args2body(self, parsed_args): 'subnet_id': _subnet_id, }, } - quantumv20.update_dict(parsed_args, body[self.resource], + neutronV20.update_dict(parsed_args, body[self.resource], ['address', 'connection_limit', 'description', 'name', 'protocol_port', 'protocol', 'tenant_id']) return body -class UpdateVip(quantumv20.UpdateCommand): +class UpdateVip(neutronV20.UpdateCommand): """Update a given vip.""" resource = 'vip' log = logging.getLogger(__name__ + '.UpdateVip') -class DeleteVip(quantumv20.DeleteCommand): +class DeleteVip(neutronV20.DeleteCommand): """Delete a given vip.""" resource = 'vip' diff --git a/quantumclient/quantum/v2_0/network.py b/neutronclient/neutron/v2_0/network.py similarity index 92% rename from quantumclient/quantum/v2_0/network.py rename to neutronclient/neutron/v2_0/network.py index 31e621f3f..0f384ab95 100644 --- a/quantumclient/quantum/v2_0/network.py +++ b/neutronclient/neutron/v2_0/network.py @@ -18,8 +18,8 @@ import argparse import logging -from quantumclient.common import exceptions -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.common import exceptions +from neutronclient.neutron import v2_0 as neutronV20 def _format_subnets(network): @@ -30,7 +30,7 @@ def _format_subnets(network): return '' -class ListNetwork(quantumv20.ListCommand): +class ListNetwork(neutronV20.ListCommand): """List networks that belong to a given tenant.""" # Length of a query filter on subnet id @@ -45,7 +45,7 @@ class ListNetwork(quantumv20.ListCommand): def extend_list(self, data, parsed_args): """Add subnet information to a network list.""" - quantum_client = self.get_client() + neutron_client = self.get_client() search_opts = {'fields': ['id', 'cidr']} if self.pagination_support: page_size = parsed_args.page_size @@ -58,7 +58,7 @@ def extend_list(self, data, parsed_args): def _get_subnet_list(sub_ids): search_opts['id'] = sub_ids - return quantum_client.list_subnets( + return neutron_client.list_subnets( **search_opts).get('subnets', []) try: @@ -97,14 +97,14 @@ def retrieve_list(self, parsed_args): return super(ListExternalNetwork, self).retrieve_list(parsed_args) -class ShowNetwork(quantumv20.ShowCommand): +class ShowNetwork(neutronV20.ShowCommand): """Show information of a given network.""" resource = 'network' log = logging.getLogger(__name__ + '.ShowNetwork') -class CreateNetwork(quantumv20.CreateCommand): +class CreateNetwork(neutronV20.CreateCommand): """Create a network for a given tenant.""" resource = 'network' @@ -138,14 +138,14 @@ def args2body(self, parsed_args): return body -class DeleteNetwork(quantumv20.DeleteCommand): +class DeleteNetwork(neutronV20.DeleteCommand): """Delete a given network.""" log = logging.getLogger(__name__ + '.DeleteNetwork') resource = 'network' -class UpdateNetwork(quantumv20.UpdateCommand): +class UpdateNetwork(neutronV20.UpdateCommand): """Update network's information.""" log = logging.getLogger(__name__ + '.UpdateNetwork') diff --git a/quantumclient/quantum/v2_0/nvp_qos_queue.py b/neutronclient/neutron/v2_0/nvp_qos_queue.py similarity index 91% rename from quantumclient/quantum/v2_0/nvp_qos_queue.py rename to neutronclient/neutron/v2_0/nvp_qos_queue.py index 386b8879e..79a1801dd 100644 --- a/quantumclient/quantum/v2_0/nvp_qos_queue.py +++ b/neutronclient/neutron/v2_0/nvp_qos_queue.py @@ -17,10 +17,10 @@ import logging -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.neutron import v2_0 as neutronV20 -class ListQoSQueue(quantumv20.ListCommand): +class ListQoSQueue(neutronV20.ListCommand): """List queues that belong to a given tenant.""" resource = 'qos_queue' @@ -29,7 +29,7 @@ class ListQoSQueue(quantumv20.ListCommand): 'qos_marking', 'dscp', 'default'] -class ShowQoSQueue(quantumv20.ShowCommand): +class ShowQoSQueue(neutronV20.ShowCommand): """Show information of a given queue.""" resource = 'qos_queue' @@ -37,7 +37,7 @@ class ShowQoSQueue(quantumv20.ShowCommand): allow_names = True -class CreateQoSQueue(quantumv20.CreateCommand): +class CreateQoSQueue(neutronV20.CreateCommand): """Create a queue.""" resource = 'qos_queue' @@ -81,7 +81,7 @@ def args2body(self, parsed_args): return {'qos_queue': params} -class DeleteQoSQueue(quantumv20.DeleteCommand): +class DeleteQoSQueue(neutronV20.DeleteCommand): """Delete a given queue.""" log = logging.getLogger(__name__ + '.DeleteQoSQueue') diff --git a/quantumclient/quantum/v2_0/nvpnetworkgateway.py b/neutronclient/neutron/v2_0/nvpnetworkgateway.py similarity index 82% rename from quantumclient/quantum/v2_0/nvpnetworkgateway.py rename to neutronclient/neutron/v2_0/nvpnetworkgateway.py index 1823b6fe1..1853514ef 100644 --- a/quantumclient/quantum/v2_0/nvpnetworkgateway.py +++ b/neutronclient/neutron/v2_0/nvpnetworkgateway.py @@ -17,14 +17,14 @@ import logging -from quantumclient.common import utils -from quantumclient.openstack.common.gettextutils import _ -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.common import utils +from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.openstack.common.gettextutils import _ RESOURCE = 'network_gateway' -class ListNetworkGateway(quantumv20.ListCommand): +class ListNetworkGateway(neutronV20.ListCommand): """List network gateways for a given tenant.""" resource = RESOURCE @@ -32,14 +32,14 @@ class ListNetworkGateway(quantumv20.ListCommand): list_columns = ['id', 'name'] -class ShowNetworkGateway(quantumv20.ShowCommand): +class ShowNetworkGateway(neutronV20.ShowCommand): """Show information of a given network gateway.""" resource = RESOURCE log = logging.getLogger(__name__ + '.ShowNetworkGateway') -class CreateNetworkGateway(quantumv20.CreateCommand): +class CreateNetworkGateway(neutronV20.CreateCommand): """Create a network gateway.""" resource = RESOURCE @@ -71,21 +71,21 @@ def args2body(self, parsed_args): return body -class DeleteNetworkGateway(quantumv20.DeleteCommand): +class DeleteNetworkGateway(neutronV20.DeleteCommand): """Delete a given network gateway.""" resource = RESOURCE log = logging.getLogger(__name__ + '.DeleteNetworkGateway') -class UpdateNetworkGateway(quantumv20.UpdateCommand): +class UpdateNetworkGateway(neutronV20.UpdateCommand): """Update the name for a network gateway.""" resource = RESOURCE log = logging.getLogger(__name__ + '.UpdateNetworkGateway') -class NetworkGatewayInterfaceCommand(quantumv20.QuantumCommand): +class NetworkGatewayInterfaceCommand(neutronV20.NeutronCommand): """Base class for connecting/disconnecting networks to/from a gateway.""" resource = RESOURCE @@ -110,9 +110,9 @@ def get_parser(self, prog_name): return parser def retrieve_ids(self, client, args): - gateway_id = quantumv20.find_resourceid_by_name_or_id( + gateway_id = neutronV20.find_resourceid_by_name_or_id( client, self.resource, args.net_gateway_id) - network_id = quantumv20.find_resourceid_by_name_or_id( + network_id = neutronV20.find_resourceid_by_name_or_id( client, 'network', args.network_id) return (gateway_id, network_id) @@ -124,11 +124,11 @@ class ConnectNetworkGateway(NetworkGatewayInterfaceCommand): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - (gateway_id, network_id) = self.retrieve_ids(quantum_client, + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + (gateway_id, network_id) = self.retrieve_ids(neutron_client, parsed_args) - quantum_client.connect_network_gateway( + neutron_client.connect_network_gateway( gateway_id, {'network_id': network_id, 'segmentation_type': parsed_args.segmentation_type, 'segmentation_id': parsed_args.segmentation_id}) @@ -145,11 +145,11 @@ class DisconnectNetworkGateway(NetworkGatewayInterfaceCommand): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - (gateway_id, network_id) = self.retrieve_ids(quantum_client, + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + (gateway_id, network_id) = self.retrieve_ids(neutron_client, parsed_args) - quantum_client.disconnect_network_gateway( + neutron_client.disconnect_network_gateway( gateway_id, {'network_id': network_id, 'segmentation_type': parsed_args.segmentation_type, 'segmentation_id': parsed_args.segmentation_id}) diff --git a/quantumclient/quantum/v2_0/port.py b/neutronclient/neutron/v2_0/port.py similarity index 88% rename from quantumclient/quantum/v2_0/port.py rename to neutronclient/neutron/v2_0/port.py index 0d6af60b9..f8ac9a9f4 100644 --- a/quantumclient/quantum/v2_0/port.py +++ b/neutronclient/neutron/v2_0/port.py @@ -18,8 +18,8 @@ import argparse import logging -from quantumclient.common import utils -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.common import utils +from neutronclient.neutron import v2_0 as neutronV20 def _format_fixed_ips(port): @@ -29,7 +29,7 @@ def _format_fixed_ips(port): return '' -class ListPort(quantumv20.ListCommand): +class ListPort(neutronV20.ListCommand): """List ports that belong to a given tenant.""" resource = 'port' @@ -40,7 +40,7 @@ class ListPort(quantumv20.ListCommand): sorting_support = True -class ListRouterPort(quantumv20.ListCommand): +class ListRouterPort(neutronV20.ListCommand): """List ports that belong to a given tenant, with specified router.""" resource = 'port' @@ -58,22 +58,22 @@ def get_parser(self, prog_name): return parser def get_data(self, parsed_args): - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - _id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, 'router', parsed_args.id) + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + _id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, 'router', parsed_args.id) self.values_specs.append('--device_id=%s' % _id) return super(ListRouterPort, self).get_data(parsed_args) -class ShowPort(quantumv20.ShowCommand): +class ShowPort(neutronV20.ShowCommand): """Show information of a given port.""" resource = 'port' log = logging.getLogger(__name__ + '.ShowPort') -class CreatePort(quantumv20.CreateCommand): +class CreatePort(neutronV20.CreateCommand): """Create a port for a given tenant.""" resource = 'port' @@ -123,7 +123,7 @@ def add_known_arguments(self, parser): help='Network id or name this port belongs to') def args2body(self, parsed_args): - _network_id = quantumv20.find_resourceid_by_name_or_id( + _network_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'network', parsed_args.network_id) body = {'port': {'admin_state_up': parsed_args.admin_state, 'network_id': _network_id, }, } @@ -141,7 +141,7 @@ def args2body(self, parsed_args): ip_dict = utils.str2dict(ip_spec) if 'subnet_id' in ip_dict: subnet_name_id = ip_dict['subnet_id'] - _subnet_id = quantumv20.find_resourceid_by_name_or_id( + _subnet_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'subnet', subnet_name_id) ip_dict['subnet_id'] = _subnet_id ips.append(ip_dict) @@ -150,7 +150,7 @@ def args2body(self, parsed_args): _sgids = [] for sg in parsed_args.security_groups: - _sgids.append(quantumv20.find_resourceid_by_name_or_id( + _sgids.append(neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'security_group', sg)) if _sgids: body['port']['security_groups'] = _sgids @@ -158,14 +158,14 @@ def args2body(self, parsed_args): return body -class DeletePort(quantumv20.DeleteCommand): +class DeletePort(neutronV20.DeleteCommand): """Delete a given port.""" resource = 'port' log = logging.getLogger(__name__ + '.DeletePort') -class UpdatePort(quantumv20.UpdateCommand): +class UpdatePort(neutronV20.UpdateCommand): """Update port's information.""" resource = 'port' diff --git a/quantumclient/quantum/v2_0/quota.py b/neutronclient/neutron/v2_0/quota.py similarity index 84% rename from quantumclient/quantum/v2_0/quota.py rename to neutronclient/neutron/v2_0/quota.py index 1c15192d7..bc568efd4 100644 --- a/quantumclient/quantum/v2_0/quota.py +++ b/neutronclient/neutron/v2_0/quota.py @@ -21,10 +21,10 @@ from cliff import lister from cliff import show -from quantumclient.common import exceptions -from quantumclient.common import utils -from quantumclient.openstack.common.gettextutils import _ -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.common import exceptions +from neutronclient.common import utils +from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.openstack.common.gettextutils import _ def get_tenant_id(tenant_id, client): @@ -32,7 +32,7 @@ def get_tenant_id(tenant_id, client): client.get_quotas_tenant()['tenant']['tenant_id']) -class DeleteQuota(quantumv20.QuantumCommand): +class DeleteQuota(neutronV20.NeutronCommand): """Delete defined quotas of a given tenant.""" api = 'network' @@ -51,11 +51,11 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format tenant_id = get_tenant_id(parsed_args.tenant_id, - quantum_client) - obj_deleter = getattr(quantum_client, + neutron_client) + obj_deleter = getattr(neutron_client, "delete_%s" % self.resource) obj_deleter(tenant_id) print >>self.app.stdout, (_('Deleted %(resource)s: %(tenant_id)s') @@ -64,7 +64,7 @@ def run(self, parsed_args): return -class ListQuota(quantumv20.QuantumCommand, lister.Lister): +class ListQuota(neutronV20.NeutronCommand, lister.Lister): """List defined quotas of all tenants.""" api = 'network' @@ -77,11 +77,11 @@ def get_parser(self, prog_name): def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.get_client() + neutron_client = self.get_client() search_opts = {} self.log.debug('search options: %s', search_opts) - quantum_client.format = parsed_args.request_format - obj_lister = getattr(quantum_client, + neutron_client.format = parsed_args.request_format + obj_lister = getattr(neutron_client, "list_%ss" % self.resource) data = obj_lister(**search_opts) info = [] @@ -93,7 +93,7 @@ def get_data(self, parsed_args): for s in info)) -class ShowQuota(quantumv20.QuantumCommand, show.ShowOne): +class ShowQuota(neutronV20.NeutronCommand, show.ShowOne): """Show quotas of a given tenant """ @@ -113,12 +113,12 @@ def get_parser(self, prog_name): def get_data(self, parsed_args): self.log.debug('get_data(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format tenant_id = get_tenant_id(parsed_args.tenant_id, - quantum_client) + neutron_client) params = {} - obj_shower = getattr(quantum_client, + obj_shower = getattr(neutron_client, "show_%s" % self.resource) data = obj_shower(tenant_id, **params) if self.resource in data: @@ -140,7 +140,7 @@ def get_data(self, parsed_args): return None -class UpdateQuota(quantumv20.QuantumCommand, show.ShowOne): +class UpdateQuota(neutronV20.NeutronCommand, show.ShowOne): """Define tenant's quotas not to use defaults.""" resource = 'quota' @@ -183,7 +183,7 @@ def _validate_int(self, name, value): except Exception: message = (_('quota limit for %(name)s must be an integer') % {'name': name}) - raise exceptions.QuantumClientException(message=message) + raise exceptions.NeutronClientException(message=message) return return_value def args2body(self, parsed_args): @@ -198,20 +198,20 @@ def args2body(self, parsed_args): def get_data(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - _extra_values = quantumv20.parse_args_to_dict(self.values_specs) - quantumv20._merge_args(self, parsed_args, _extra_values, + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + _extra_values = neutronV20.parse_args_to_dict(self.values_specs) + neutronV20._merge_args(self, parsed_args, _extra_values, self.values_specs) body = self.args2body(parsed_args) if self.resource in body: body[self.resource].update(_extra_values) else: body[self.resource] = _extra_values - obj_updator = getattr(quantum_client, + obj_updator = getattr(neutron_client, "update_%s" % self.resource) tenant_id = get_tenant_id(parsed_args.tenant_id, - quantum_client) + neutron_client) data = obj_updator(tenant_id, body) if self.resource in data: for k, v in data[self.resource].iteritems(): diff --git a/quantumclient/quantum/v2_0/router.py b/neutronclient/neutron/v2_0/router.py similarity index 76% rename from quantumclient/quantum/v2_0/router.py rename to neutronclient/neutron/v2_0/router.py index c5b15704d..54b811738 100644 --- a/quantumclient/quantum/v2_0/router.py +++ b/neutronclient/neutron/v2_0/router.py @@ -18,10 +18,10 @@ import argparse import logging -from quantumclient.common import exceptions -from quantumclient.common import utils -from quantumclient.openstack.common.gettextutils import _ -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.common import exceptions +from neutronclient.common import utils +from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.openstack.common.gettextutils import _ def _format_external_gateway_info(router): @@ -31,7 +31,7 @@ def _format_external_gateway_info(router): return '' -class ListRouter(quantumv20.ListCommand): +class ListRouter(neutronV20.ListCommand): """List routers that belong to a given tenant.""" resource = 'router' @@ -42,14 +42,14 @@ class ListRouter(quantumv20.ListCommand): sorting_support = True -class ShowRouter(quantumv20.ShowCommand): +class ShowRouter(neutronV20.ShowCommand): """Show information of a given router.""" resource = 'router' log = logging.getLogger(__name__ + '.ShowRouter') -class CreateRouter(quantumv20.CreateCommand): +class CreateRouter(neutronV20.CreateCommand): """Create a router for a given tenant.""" resource = 'router' @@ -78,27 +78,27 @@ def args2body(self, parsed_args): return body -class DeleteRouter(quantumv20.DeleteCommand): +class DeleteRouter(neutronV20.DeleteCommand): """Delete a given router.""" log = logging.getLogger(__name__ + '.DeleteRouter') resource = 'router' -class UpdateRouter(quantumv20.UpdateCommand): +class UpdateRouter(neutronV20.UpdateCommand): """Update router's information.""" log = logging.getLogger(__name__ + '.UpdateRouter') resource = 'router' -class RouterInterfaceCommand(quantumv20.QuantumCommand): +class RouterInterfaceCommand(neutronV20.NeutronCommand): """Based class to Add/Remove router interface.""" api = 'network' resource = 'router' - def call_api(self, quantum_client, router_id, body): + def call_api(self, neutron_client, router_id, body): raise NotImplementedError() def success_message(self, router_id, portinfo): @@ -119,8 +119,8 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format if '=' in parsed_args.interface: resource, value = parsed_args.interface.split('=', 1) @@ -131,14 +131,14 @@ def run(self, parsed_args): resource = 'subnet' value = parsed_args.interface - _router_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, self.resource, parsed_args.router_id) + _router_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, self.resource, parsed_args.router_id) - _interface_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, resource, value) + _interface_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, resource, value) body = {'%s_id' % resource: _interface_id} - portinfo = self.call_api(quantum_client, _router_id, body) + portinfo = self.call_api(neutron_client, _router_id, body) print >>self.app.stdout, self.success_message(parsed_args.router_id, portinfo) @@ -148,8 +148,8 @@ class AddInterfaceRouter(RouterInterfaceCommand): log = logging.getLogger(__name__ + '.AddInterfaceRouter') - def call_api(self, quantum_client, router_id, body): - return quantum_client.add_interface_router(router_id, body) + def call_api(self, neutron_client, router_id, body): + return neutron_client.add_interface_router(router_id, body) def success_message(self, router_id, portinfo): return (_('Added interface %(port)s to router %(router)s.') % @@ -161,15 +161,15 @@ class RemoveInterfaceRouter(RouterInterfaceCommand): log = logging.getLogger(__name__ + '.RemoveInterfaceRouter') - def call_api(self, quantum_client, router_id, body): - return quantum_client.remove_interface_router(router_id, body) + def call_api(self, neutron_client, router_id, body): + return neutron_client.remove_interface_router(router_id, body) def success_message(self, router_id, portinfo): # portinfo is not used since it is None for router-interface-delete. return _('Removed interface from router %s.') % router_id -class SetGatewayRouter(quantumv20.QuantumCommand): +class SetGatewayRouter(neutronV20.NeutronCommand): """Set the external network gateway for a router.""" log = logging.getLogger(__name__ + '.SetGatewayRouter') @@ -191,13 +191,13 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - _router_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, self.resource, parsed_args.router_id) - _ext_net_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, 'network', parsed_args.external_network_id) - quantum_client.add_gateway_router( + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + _router_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, self.resource, parsed_args.router_id) + _ext_net_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, 'network', parsed_args.external_network_id) + neutron_client.add_gateway_router( _router_id, {'network_id': _ext_net_id, 'enable_snat': parsed_args.enable_snat}) @@ -205,7 +205,7 @@ def run(self, parsed_args): _('Set gateway for router %s') % parsed_args.router_id) -class RemoveGatewayRouter(quantumv20.QuantumCommand): +class RemoveGatewayRouter(neutronV20.NeutronCommand): """Remove an external network gateway from a router.""" log = logging.getLogger(__name__ + '.RemoveGatewayRouter') @@ -221,10 +221,10 @@ def get_parser(self, prog_name): def run(self, parsed_args): self.log.debug('run(%s)' % parsed_args) - quantum_client = self.get_client() - quantum_client.format = parsed_args.request_format - _router_id = quantumv20.find_resourceid_by_name_or_id( - quantum_client, self.resource, parsed_args.router_id) - quantum_client.remove_gateway_router(_router_id) + neutron_client = self.get_client() + neutron_client.format = parsed_args.request_format + _router_id = neutronV20.find_resourceid_by_name_or_id( + neutron_client, self.resource, parsed_args.router_id) + neutron_client.remove_gateway_router(_router_id) print >>self.app.stdout, ( _('Removed gateway from router %s') % parsed_args.router_id) diff --git a/quantumclient/quantum/v2_0/securitygroup.py b/neutronclient/neutron/v2_0/securitygroup.py similarity index 92% rename from quantumclient/quantum/v2_0/securitygroup.py rename to neutronclient/neutron/v2_0/securitygroup.py index 5fa899c7e..92570fa1b 100644 --- a/quantumclient/quantum/v2_0/securitygroup.py +++ b/neutronclient/neutron/v2_0/securitygroup.py @@ -18,10 +18,10 @@ import argparse import logging -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.neutron import v2_0 as neutronV20 -class ListSecurityGroup(quantumv20.ListCommand): +class ListSecurityGroup(neutronV20.ListCommand): """List security groups that belong to a given tenant.""" resource = 'security_group' @@ -31,7 +31,7 @@ class ListSecurityGroup(quantumv20.ListCommand): sorting_support = True -class ShowSecurityGroup(quantumv20.ShowCommand): +class ShowSecurityGroup(neutronV20.ShowCommand): """Show information of a given security group.""" resource = 'security_group' @@ -39,7 +39,7 @@ class ShowSecurityGroup(quantumv20.ShowCommand): allow_names = True -class CreateSecurityGroup(quantumv20.CreateCommand): +class CreateSecurityGroup(neutronV20.CreateCommand): """Create a security group.""" resource = 'security_group' @@ -64,7 +64,7 @@ def args2body(self, parsed_args): return body -class DeleteSecurityGroup(quantumv20.DeleteCommand): +class DeleteSecurityGroup(neutronV20.DeleteCommand): """Delete a given security group.""" log = logging.getLogger(__name__ + '.DeleteSecurityGroup') @@ -72,7 +72,7 @@ class DeleteSecurityGroup(quantumv20.DeleteCommand): allow_names = True -class UpdateSecurityGroup(quantumv20.UpdateCommand): +class UpdateSecurityGroup(neutronV20.UpdateCommand): """Update a given security group.""" log = logging.getLogger(__name__ + '.UpdateSecurityGroup') @@ -97,7 +97,7 @@ def args2body(self, parsed_args): return body -class ListSecurityGroupRule(quantumv20.ListCommand): +class ListSecurityGroupRule(neutronV20.ListCommand): """List security group rules that belong to a given tenant.""" resource = 'security_group_rule' @@ -131,7 +131,7 @@ def retrieve_list(self, parsed_args): def extend_list(self, data, parsed_args): if parsed_args.no_nameconv: return - quantum_client = self.get_client() + neutron_client = self.get_client() search_opts = {'fields': ['id', 'name']} if self.pagination_support: page_size = parsed_args.page_size @@ -142,7 +142,7 @@ def extend_list(self, data, parsed_args): for key in self.replace_rules: sec_group_ids.add(rule[key]) search_opts.update({"id": sec_group_ids}) - secgroups = quantum_client.list_security_groups(**search_opts) + secgroups = neutron_client.list_security_groups(**search_opts) secgroups = secgroups.get('security_groups', []) sg_dict = dict([(sg['id'], sg['name']) for sg in secgroups if sg['name']]) @@ -166,7 +166,7 @@ def setup_columns(self, info, parsed_args): return (cols, info[1]) -class ShowSecurityGroupRule(quantumv20.ShowCommand): +class ShowSecurityGroupRule(neutronV20.ShowCommand): """Show information of a given security group rule.""" resource = 'security_group_rule' @@ -174,7 +174,7 @@ class ShowSecurityGroupRule(quantumv20.ShowCommand): allow_names = False -class CreateSecurityGroupRule(quantumv20.CreateCommand): +class CreateSecurityGroupRule(neutronV20.CreateCommand): """Create a security group rule.""" resource = 'security_group_rule' @@ -221,7 +221,7 @@ def add_known_arguments(self, parser): help=argparse.SUPPRESS) def args2body(self, parsed_args): - _security_group_id = quantumv20.find_resourceid_by_name_or_id( + _security_group_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'security_group', parsed_args.security_group_id) body = {'security_group_rule': { 'security_group_id': _security_group_id, @@ -240,7 +240,7 @@ def args2body(self, parsed_args): body['security_group_rule'].update( {'remote_ip_prefix': parsed_args.remote_ip_prefix}) if parsed_args.remote_group_id: - _remote_group_id = quantumv20.find_resourceid_by_name_or_id( + _remote_group_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'security_group', parsed_args.remote_group_id) body['security_group_rule'].update( @@ -251,7 +251,7 @@ def args2body(self, parsed_args): return body -class DeleteSecurityGroupRule(quantumv20.DeleteCommand): +class DeleteSecurityGroupRule(neutronV20.DeleteCommand): """Delete a given security group rule.""" log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') diff --git a/quantumclient/quantum/v2_0/subnet.py b/neutronclient/neutron/v2_0/subnet.py similarity index 93% rename from quantumclient/quantum/v2_0/subnet.py rename to neutronclient/neutron/v2_0/subnet.py index e79cdc050..3674db89d 100644 --- a/quantumclient/quantum/v2_0/subnet.py +++ b/neutronclient/neutron/v2_0/subnet.py @@ -18,9 +18,9 @@ import argparse import logging -from quantumclient.common import exceptions -from quantumclient.common import utils -from quantumclient.quantum import v2_0 as quantumv20 +from neutronclient.common import exceptions +from neutronclient.common import utils +from neutronclient.neutron import v2_0 as neutronV20 def _format_allocation_pools(subnet): @@ -47,7 +47,7 @@ def _format_host_routes(subnet): return '' -class ListSubnet(quantumv20.ListCommand): +class ListSubnet(neutronV20.ListCommand): """List networks that belong to a given tenant.""" resource = 'subnet' @@ -60,14 +60,14 @@ class ListSubnet(quantumv20.ListCommand): sorting_support = True -class ShowSubnet(quantumv20.ShowCommand): +class ShowSubnet(neutronV20.ShowCommand): """Show information of a given subnet.""" resource = 'subnet' log = logging.getLogger(__name__ + '.ShowSubnet') -class CreateSubnet(quantumv20.CreateCommand): +class CreateSubnet(neutronV20.CreateCommand): """Create a subnet for a given tenant.""" resource = 'subnet' @@ -124,7 +124,7 @@ def add_known_arguments(self, parser): help='cidr of subnet to create') def args2body(self, parsed_args): - _network_id = quantumv20.find_resourceid_by_name_or_id( + _network_id = neutronV20.find_resourceid_by_name_or_id( self.get_client(), 'network', parsed_args.network_id) body = {'subnet': {'cidr': parsed_args.cidr, 'network_id': _network_id, @@ -154,14 +154,14 @@ def args2body(self, parsed_args): return body -class DeleteSubnet(quantumv20.DeleteCommand): +class DeleteSubnet(neutronV20.DeleteCommand): """Delete a given subnet.""" resource = 'subnet' log = logging.getLogger(__name__ + '.DeleteSubnet') -class UpdateSubnet(quantumv20.UpdateCommand): +class UpdateSubnet(neutronV20.UpdateCommand): """Update subnet's information.""" resource = 'subnet' diff --git a/quantumclient/openstack/__init__.py b/neutronclient/openstack/__init__.py similarity index 100% rename from quantumclient/openstack/__init__.py rename to neutronclient/openstack/__init__.py diff --git a/quantumclient/openstack/common/__init__.py b/neutronclient/openstack/common/__init__.py similarity index 100% rename from quantumclient/openstack/common/__init__.py rename to neutronclient/openstack/common/__init__.py diff --git a/quantumclient/openstack/common/exception.py b/neutronclient/openstack/common/exception.py similarity index 98% rename from quantumclient/openstack/common/exception.py rename to neutronclient/openstack/common/exception.py index d7dddf3f1..bb6e6dc6a 100644 --- a/quantumclient/openstack/common/exception.py +++ b/neutronclient/openstack/common/exception.py @@ -21,7 +21,7 @@ import logging -from quantumclient.openstack.common.gettextutils import _ +from neutronclient.openstack.common.gettextutils import _ _FATAL_EXCEPTION_FORMAT_ERRORS = False diff --git a/quantumclient/openstack/common/gettextutils.py b/neutronclient/openstack/common/gettextutils.py similarity index 93% rename from quantumclient/openstack/common/gettextutils.py rename to neutronclient/openstack/common/gettextutils.py index 490f194d4..5c597c7e2 100644 --- a/quantumclient/openstack/common/gettextutils.py +++ b/neutronclient/openstack/common/gettextutils.py @@ -20,7 +20,7 @@ Usual usage in an openstack.common module: - from quantumclient.openstack.common.gettextutils import _ + from neutronclient.openstack.common.gettextutils import _ """ import gettext diff --git a/quantumclient/openstack/common/jsonutils.py b/neutronclient/openstack/common/jsonutils.py similarity index 99% rename from quantumclient/openstack/common/jsonutils.py rename to neutronclient/openstack/common/jsonutils.py index ad76e0655..9ff741597 100644 --- a/quantumclient/openstack/common/jsonutils.py +++ b/neutronclient/openstack/common/jsonutils.py @@ -39,7 +39,7 @@ import json import xmlrpclib -from quantumclient.openstack.common import timeutils +from neutronclient.openstack.common import timeutils def to_primitive(value, convert_instances=False, level=0): diff --git a/quantumclient/openstack/common/strutils.py b/neutronclient/openstack/common/strutils.py similarity index 100% rename from quantumclient/openstack/common/strutils.py rename to neutronclient/openstack/common/strutils.py diff --git a/quantumclient/openstack/common/timeutils.py b/neutronclient/openstack/common/timeutils.py similarity index 100% rename from quantumclient/openstack/common/timeutils.py rename to neutronclient/openstack/common/timeutils.py diff --git a/quantumclient/shell.py b/neutronclient/shell.py similarity index 59% rename from quantumclient/shell.py rename to neutronclient/shell.py index 29c56f00c..8cc644142 100644 --- a/quantumclient/shell.py +++ b/neutronclient/shell.py @@ -16,7 +16,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 """ -Command-line interface to the Quantum APIs +Command-line interface to the Neutron APIs """ import argparse @@ -27,15 +27,31 @@ from cliff import app from cliff import commandmanager -from quantumclient.common import clientmanager -from quantumclient.common import exceptions as exc -from quantumclient.common import utils -from quantumclient.openstack.common import strutils -from quantumclient.version import __version__ +from neutronclient.common import clientmanager +from neutronclient.common import exceptions as exc +from neutronclient.common import utils +from neutronclient.neutron.v2_0 import agent +from neutronclient.neutron.v2_0 import agentscheduler +from neutronclient.neutron.v2_0 import extension +from neutronclient.neutron.v2_0 import floatingip +from neutronclient.neutron.v2_0.lb import healthmonitor as lb_healthmonitor +from neutronclient.neutron.v2_0.lb import member as lb_member +from neutronclient.neutron.v2_0.lb import pool as lb_pool +from neutronclient.neutron.v2_0.lb import vip as lb_vip +from neutronclient.neutron.v2_0 import network +from neutronclient.neutron.v2_0 import nvp_qos_queue +from neutronclient.neutron.v2_0 import nvpnetworkgateway +from neutronclient.neutron.v2_0 import port +from neutronclient.neutron.v2_0 import quota +from neutronclient.neutron.v2_0 import router +from neutronclient.neutron.v2_0 import securitygroup +from neutronclient.neutron.v2_0 import subnet +from neutronclient.openstack.common import strutils +from neutronclient.version import __version__ VERSION = '2.0' -QUANTUM_API_VERSION = '2.0' +NEUTRON_API_VERSION = '2.0' def run_command(cmd, cmd_parser, sub_argv): @@ -66,197 +82,101 @@ def env(*_vars, **kwargs): COMMAND_V2 = { - 'net-list': utils.import_class( - 'quantumclient.quantum.v2_0.network.ListNetwork'), - 'net-external-list': utils.import_class( - 'quantumclient.quantum.v2_0.network.ListExternalNetwork'), - 'net-show': utils.import_class( - 'quantumclient.quantum.v2_0.network.ShowNetwork'), - 'net-create': utils.import_class( - 'quantumclient.quantum.v2_0.network.CreateNetwork'), - 'net-delete': utils.import_class( - 'quantumclient.quantum.v2_0.network.DeleteNetwork'), - 'net-update': utils.import_class( - 'quantumclient.quantum.v2_0.network.UpdateNetwork'), - 'subnet-list': utils.import_class( - 'quantumclient.quantum.v2_0.subnet.ListSubnet'), - 'subnet-show': utils.import_class( - 'quantumclient.quantum.v2_0.subnet.ShowSubnet'), - 'subnet-create': utils.import_class( - 'quantumclient.quantum.v2_0.subnet.CreateSubnet'), - 'subnet-delete': utils.import_class( - 'quantumclient.quantum.v2_0.subnet.DeleteSubnet'), - 'subnet-update': utils.import_class( - 'quantumclient.quantum.v2_0.subnet.UpdateSubnet'), - 'port-list': utils.import_class( - 'quantumclient.quantum.v2_0.port.ListPort'), - 'port-show': utils.import_class( - 'quantumclient.quantum.v2_0.port.ShowPort'), - 'port-create': utils.import_class( - 'quantumclient.quantum.v2_0.port.CreatePort'), - 'port-delete': utils.import_class( - 'quantumclient.quantum.v2_0.port.DeletePort'), - 'port-update': utils.import_class( - 'quantumclient.quantum.v2_0.port.UpdatePort'), - 'quota-list': utils.import_class( - 'quantumclient.quantum.v2_0.quota.ListQuota'), - 'quota-show': utils.import_class( - 'quantumclient.quantum.v2_0.quota.ShowQuota'), - 'quota-delete': utils.import_class( - 'quantumclient.quantum.v2_0.quota.DeleteQuota'), - 'quota-update': utils.import_class( - 'quantumclient.quantum.v2_0.quota.UpdateQuota'), - 'ext-list': utils.import_class( - 'quantumclient.quantum.v2_0.extension.ListExt'), - 'ext-show': utils.import_class( - 'quantumclient.quantum.v2_0.extension.ShowExt'), - 'router-list': utils.import_class( - 'quantumclient.quantum.v2_0.router.ListRouter'), - 'router-port-list': utils.import_class( - 'quantumclient.quantum.v2_0.port.ListRouterPort'), - 'router-show': utils.import_class( - 'quantumclient.quantum.v2_0.router.ShowRouter'), - 'router-create': utils.import_class( - 'quantumclient.quantum.v2_0.router.CreateRouter'), - 'router-delete': utils.import_class( - 'quantumclient.quantum.v2_0.router.DeleteRouter'), - 'router-update': utils.import_class( - 'quantumclient.quantum.v2_0.router.UpdateRouter'), - 'router-interface-add': utils.import_class( - 'quantumclient.quantum.v2_0.router.AddInterfaceRouter'), - 'router-interface-delete': utils.import_class( - 'quantumclient.quantum.v2_0.router.RemoveInterfaceRouter'), - 'router-gateway-set': utils.import_class( - 'quantumclient.quantum.v2_0.router.SetGatewayRouter'), - 'router-gateway-clear': utils.import_class( - 'quantumclient.quantum.v2_0.router.RemoveGatewayRouter'), - 'floatingip-list': utils.import_class( - 'quantumclient.quantum.v2_0.floatingip.ListFloatingIP'), - 'floatingip-show': utils.import_class( - 'quantumclient.quantum.v2_0.floatingip.ShowFloatingIP'), - 'floatingip-create': utils.import_class( - 'quantumclient.quantum.v2_0.floatingip.CreateFloatingIP'), - 'floatingip-delete': utils.import_class( - 'quantumclient.quantum.v2_0.floatingip.DeleteFloatingIP'), - 'floatingip-associate': utils.import_class( - 'quantumclient.quantum.v2_0.floatingip.AssociateFloatingIP'), - 'floatingip-disassociate': utils.import_class( - 'quantumclient.quantum.v2_0.floatingip.DisassociateFloatingIP'), - 'security-group-list': utils.import_class( - 'quantumclient.quantum.v2_0.securitygroup.ListSecurityGroup'), - 'security-group-show': utils.import_class( - 'quantumclient.quantum.v2_0.securitygroup.ShowSecurityGroup'), - 'security-group-create': utils.import_class( - 'quantumclient.quantum.v2_0.securitygroup.CreateSecurityGroup'), - 'security-group-delete': utils.import_class( - 'quantumclient.quantum.v2_0.securitygroup.DeleteSecurityGroup'), - 'security-group-update': utils.import_class( - 'quantumclient.quantum.v2_0.securitygroup.UpdateSecurityGroup'), - 'security-group-rule-list': utils.import_class( - 'quantumclient.quantum.v2_0.securitygroup.ListSecurityGroupRule'), - 'security-group-rule-show': utils.import_class( - 'quantumclient.quantum.v2_0.securitygroup.ShowSecurityGroupRule'), - 'security-group-rule-create': utils.import_class( - 'quantumclient.quantum.v2_0.securitygroup.CreateSecurityGroupRule'), - 'security-group-rule-delete': utils.import_class( - 'quantumclient.quantum.v2_0.securitygroup.DeleteSecurityGroupRule'), - 'lb-vip-list': utils.import_class( - 'quantumclient.quantum.v2_0.lb.vip.ListVip'), - 'lb-vip-show': utils.import_class( - 'quantumclient.quantum.v2_0.lb.vip.ShowVip'), - 'lb-vip-create': utils.import_class( - 'quantumclient.quantum.v2_0.lb.vip.CreateVip'), - 'lb-vip-update': utils.import_class( - 'quantumclient.quantum.v2_0.lb.vip.UpdateVip'), - 'lb-vip-delete': utils.import_class( - 'quantumclient.quantum.v2_0.lb.vip.DeleteVip'), - 'lb-pool-list': utils.import_class( - 'quantumclient.quantum.v2_0.lb.pool.ListPool'), - 'lb-pool-show': utils.import_class( - 'quantumclient.quantum.v2_0.lb.pool.ShowPool'), - 'lb-pool-create': utils.import_class( - 'quantumclient.quantum.v2_0.lb.pool.CreatePool'), - 'lb-pool-update': utils.import_class( - 'quantumclient.quantum.v2_0.lb.pool.UpdatePool'), - 'lb-pool-delete': utils.import_class( - 'quantumclient.quantum.v2_0.lb.pool.DeletePool'), - 'lb-pool-stats': utils.import_class( - 'quantumclient.quantum.v2_0.lb.pool.RetrievePoolStats'), - 'lb-member-list': utils.import_class( - 'quantumclient.quantum.v2_0.lb.member.ListMember'), - 'lb-member-show': utils.import_class( - 'quantumclient.quantum.v2_0.lb.member.ShowMember'), - 'lb-member-create': utils.import_class( - 'quantumclient.quantum.v2_0.lb.member.CreateMember'), - 'lb-member-update': utils.import_class( - 'quantumclient.quantum.v2_0.lb.member.UpdateMember'), - 'lb-member-delete': utils.import_class( - 'quantumclient.quantum.v2_0.lb.member.DeleteMember'), - 'lb-healthmonitor-list': utils.import_class( - 'quantumclient.quantum.v2_0.lb.healthmonitor.ListHealthMonitor'), - 'lb-healthmonitor-show': utils.import_class( - 'quantumclient.quantum.v2_0.lb.healthmonitor.ShowHealthMonitor'), - 'lb-healthmonitor-create': utils.import_class( - 'quantumclient.quantum.v2_0.lb.healthmonitor.CreateHealthMonitor'), - 'lb-healthmonitor-update': utils.import_class( - 'quantumclient.quantum.v2_0.lb.healthmonitor.UpdateHealthMonitor'), - 'lb-healthmonitor-delete': utils.import_class( - 'quantumclient.quantum.v2_0.lb.healthmonitor.DeleteHealthMonitor'), - 'lb-healthmonitor-associate': utils.import_class( - 'quantumclient.quantum.v2_0.lb.healthmonitor.AssociateHealthMonitor'), - 'lb-healthmonitor-disassociate': utils.import_class( - 'quantumclient.quantum.v2_0.lb.healthmonitor' - '.DisassociateHealthMonitor'), - 'queue-create': utils.import_class( - 'quantumclient.quantum.v2_0.nvp_qos_queue.CreateQoSQueue'), - 'queue-delete': utils.import_class( - 'quantumclient.quantum.v2_0.nvp_qos_queue.DeleteQoSQueue'), - 'queue-show': utils.import_class( - 'quantumclient.quantum.v2_0.nvp_qos_queue.ShowQoSQueue'), - 'queue-list': utils.import_class( - 'quantumclient.quantum.v2_0.nvp_qos_queue.ListQoSQueue'), - 'agent-list': utils.import_class( - 'quantumclient.quantum.v2_0.agent.ListAgent'), - 'agent-show': utils.import_class( - 'quantumclient.quantum.v2_0.agent.ShowAgent'), - 'agent-delete': utils.import_class( - 'quantumclient.quantum.v2_0.agent.DeleteAgent'), - 'agent-update': utils.import_class( - 'quantumclient.quantum.v2_0.agent.UpdateAgent'), - 'net-gateway-create': utils.import_class( - 'quantumclient.quantum.v2_0.nvpnetworkgateway.CreateNetworkGateway'), - 'net-gateway-update': utils.import_class( - 'quantumclient.quantum.v2_0.nvpnetworkgateway.UpdateNetworkGateway'), - 'net-gateway-delete': utils.import_class( - 'quantumclient.quantum.v2_0.nvpnetworkgateway.DeleteNetworkGateway'), - 'net-gateway-show': utils.import_class( - 'quantumclient.quantum.v2_0.nvpnetworkgateway.ShowNetworkGateway'), - 'net-gateway-list': utils.import_class( - 'quantumclient.quantum.v2_0.nvpnetworkgateway.ListNetworkGateway'), - 'net-gateway-connect': utils.import_class( - 'quantumclient.quantum.v2_0.nvpnetworkgateway.ConnectNetworkGateway'), - 'net-gateway-disconnect': utils.import_class( - 'quantumclient.quantum.v2_0.nvpnetworkgateway.' - 'DisconnectNetworkGateway'), - 'dhcp-agent-network-add': utils.import_class( - 'quantumclient.quantum.v2_0.agentscheduler.AddNetworkToDhcpAgent'), - 'dhcp-agent-network-remove': utils.import_class( - 'quantumclient.quantum.v2_0.agentscheduler.' - 'RemoveNetworkFromDhcpAgent'), - 'net-list-on-dhcp-agent': utils.import_class( - 'quantumclient.quantum.v2_0.agentscheduler.' - 'ListNetworksOnDhcpAgent'), - 'dhcp-agent-list-hosting-net': utils.import_class( - 'quantumclient.quantum.v2_0.agentscheduler.' - 'ListDhcpAgentsHostingNetwork'), - 'l3-agent-router-add': utils.import_class( - 'quantumclient.quantum.v2_0.agentscheduler.AddRouterToL3Agent'), - 'l3-agent-router-remove': utils.import_class( - 'quantumclient.quantum.v2_0.agentscheduler.RemoveRouterFromL3Agent'), - 'router-list-on-l3-agent': utils.import_class( - 'quantumclient.quantum.v2_0.agentscheduler.ListRoutersOnL3Agent'), - 'l3-agent-list-hosting-router': utils.import_class( - 'quantumclient.quantum.v2_0.agentscheduler.ListL3AgentsHostingRouter'), + 'net-list': network.ListNetwork, + 'net-external-list': network.ListExternalNetwork, + 'net-show': network.ShowNetwork, + 'net-create': network.CreateNetwork, + 'net-delete': network.DeleteNetwork, + 'net-update': network.UpdateNetwork, + 'subnet-list': subnet.ListSubnet, + 'subnet-show': subnet.ShowSubnet, + 'subnet-create': subnet.CreateSubnet, + 'subnet-delete': subnet.DeleteSubnet, + 'subnet-update': subnet.UpdateSubnet, + 'port-list': port.ListPort, + 'port-show': port.ShowPort, + 'port-create': port.CreatePort, + 'port-delete': port.DeletePort, + 'port-update': port.UpdatePort, + 'quota-list': quota.ListQuota, + 'quota-show': quota.ShowQuota, + 'quota-delete': quota.DeleteQuota, + 'quota-update': quota.UpdateQuota, + 'ext-list': extension.ListExt, + 'ext-show': extension.ShowExt, + 'router-list': router.ListRouter, + 'router-port-list': port.ListRouterPort, + 'router-show': router.ShowRouter, + 'router-create': router.CreateRouter, + 'router-delete': router.DeleteRouter, + 'router-update': router.UpdateRouter, + 'router-interface-add': router.AddInterfaceRouter, + 'router-interface-delete': router.RemoveInterfaceRouter, + 'router-gateway-set': router.SetGatewayRouter, + 'router-gateway-clear': router.RemoveGatewayRouter, + 'floatingip-list': floatingip.ListFloatingIP, + 'floatingip-show': floatingip.ShowFloatingIP, + 'floatingip-create': floatingip.CreateFloatingIP, + 'floatingip-delete': floatingip.DeleteFloatingIP, + 'floatingip-associate': floatingip.AssociateFloatingIP, + 'floatingip-disassociate': floatingip.DisassociateFloatingIP, + 'security-group-list': securitygroup.ListSecurityGroup, + 'security-group-show': securitygroup.ShowSecurityGroup, + 'security-group-create': securitygroup.CreateSecurityGroup, + 'security-group-delete': securitygroup.DeleteSecurityGroup, + 'security-group-update': securitygroup.UpdateSecurityGroup, + 'security-group-rule-list': securitygroup.ListSecurityGroupRule, + 'security-group-rule-show': securitygroup.ShowSecurityGroupRule, + 'security-group-rule-create': securitygroup.CreateSecurityGroupRule, + 'security-group-rule-delete': securitygroup.DeleteSecurityGroupRule, + 'lb-vip-list': lb_vip.ListVip, + 'lb-vip-show': lb_vip.ShowVip, + 'lb-vip-create': lb_vip.CreateVip, + 'lb-vip-update': lb_vip.UpdateVip, + 'lb-vip-delete': lb_vip.DeleteVip, + 'lb-pool-list': lb_pool.ListPool, + 'lb-pool-show': lb_pool.ShowPool, + 'lb-pool-create': lb_pool.CreatePool, + 'lb-pool-update': lb_pool.UpdatePool, + 'lb-pool-delete': lb_pool.DeletePool, + 'lb-pool-stats': lb_pool.RetrievePoolStats, + 'lb-member-list': lb_member.ListMember, + 'lb-member-show': lb_member.ShowMember, + 'lb-member-create': lb_member.CreateMember, + 'lb-member-update': lb_member.UpdateMember, + 'lb-member-delete': lb_member.DeleteMember, + 'lb-healthmonitor-list': lb_healthmonitor.ListHealthMonitor, + 'lb-healthmonitor-show': lb_healthmonitor.ShowHealthMonitor, + 'lb-healthmonitor-create': lb_healthmonitor.CreateHealthMonitor, + 'lb-healthmonitor-update': lb_healthmonitor.UpdateHealthMonitor, + 'lb-healthmonitor-delete': lb_healthmonitor.DeleteHealthMonitor, + 'lb-healthmonitor-associate': lb_healthmonitor.AssociateHealthMonitor, + 'lb-healthmonitor-disassociate': ( + lb_healthmonitor.DisassociateHealthMonitor + ), + 'queue-create': nvp_qos_queue.CreateQoSQueue, + 'queue-delete': nvp_qos_queue.DeleteQoSQueue, + 'queue-show': nvp_qos_queue.ShowQoSQueue, + 'queue-list': nvp_qos_queue.ListQoSQueue, + 'agent-list': agent.ListAgent, + 'agent-show': agent.ShowAgent, + 'agent-delete': agent.DeleteAgent, + 'agent-update': agent.UpdateAgent, + 'net-gateway-create': nvpnetworkgateway.CreateNetworkGateway, + 'net-gateway-update': nvpnetworkgateway.UpdateNetworkGateway, + 'net-gateway-delete': nvpnetworkgateway.DeleteNetworkGateway, + 'net-gateway-show': nvpnetworkgateway.ShowNetworkGateway, + 'net-gateway-list': nvpnetworkgateway.ListNetworkGateway, + 'net-gateway-connect': nvpnetworkgateway.ConnectNetworkGateway, + 'net-gateway-disconnect': nvpnetworkgateway.DisconnectNetworkGateway, + 'dhcp-agent-network-add': agentscheduler.AddNetworkToDhcpAgent, + 'dhcp-agent-network-remove': agentscheduler.RemoveNetworkFromDhcpAgent, + 'net-list-on-dhcp-agent': agentscheduler.ListNetworksOnDhcpAgent, + 'dhcp-agent-list-hosting-net': agentscheduler.ListDhcpAgentsHostingNetwork, + 'l3-agent-router-add': agentscheduler.AddRouterToL3Agent, + 'l3-agent-router-remove': agentscheduler.RemoveRouterFromL3Agent, + 'router-list-on-l3-agent': agentscheduler.ListRoutersOnL3Agent, + 'l3-agent-list-hosting-router': agentscheduler.ListL3AgentsHostingRouter, } COMMANDS = {'2.0': COMMAND_V2} @@ -287,17 +207,17 @@ def __call__(self, parser, namespace, values, option_string=None): sys.exit(0) -class QuantumShell(app.App): +class NeutronShell(app.App): CONSOLE_MESSAGE_FORMAT = '%(message)s' DEBUG_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' log = logging.getLogger(__name__) def __init__(self, apiversion): - super(QuantumShell, self).__init__( + super(NeutronShell, self).__init__( description=__doc__.strip(), version=VERSION, - command_manager=commandmanager.CommandManager('quantum.cli'), ) + command_manager=commandmanager.CommandManager('neutron.cli'), ) self.commands = COMMANDS for k, v in self.commands[apiversion].items(): self.command_manager.add_command(k, v) @@ -423,8 +343,8 @@ def build_option_parser(self, description, version): parser.add_argument( '--insecure', action='store_true', - default=env('QUANTUMCLIENT_INSECURE', default=False), - help="Explicitly allow quantumclient to perform \"insecure\" " + default=env('NEUTRONCLIENT_INSECURE', default=False), + help="Explicitly allow neutronclient to perform \"insecure\" " "SSL (https) requests. The server's certificate will " "not be verified against any certificate authorities. " "This option should be used with caution.") @@ -598,7 +518,7 @@ def initialize_app(self, argv): * validate authentication info """ - super(QuantumShell, self).initialize_app(argv) + super(NeutronShell, self).initialize_app(argv) self.api_version = {'network': self.api_version} @@ -642,9 +562,9 @@ def configure_logging(self): def main(argv=sys.argv[1:]): try: - return QuantumShell(QUANTUM_API_VERSION).run(map(strutils.safe_decode, + return NeutronShell(NEUTRON_API_VERSION).run(map(strutils.safe_decode, argv)) - except exc.QuantumClientException: + except exc.NeutronClientException: return 1 except Exception as e: print unicode(e) diff --git a/quantumclient/tests/unit/test_utils.py b/neutronclient/tests/unit/test_utils.py similarity index 97% rename from quantumclient/tests/unit/test_utils.py rename to neutronclient/tests/unit/test_utils.py index 69ecf6dd3..22c4ba45d 100644 --- a/quantumclient/tests/unit/test_utils.py +++ b/neutronclient/tests/unit/test_utils.py @@ -17,7 +17,7 @@ import testtools -from quantumclient.common import utils +from neutronclient.common import utils class UtilsTest(testtools.TestCase): diff --git a/quantumclient/v2_0/__init__.py b/neutronclient/v2_0/__init__.py similarity index 100% rename from quantumclient/v2_0/__init__.py rename to neutronclient/v2_0/__init__.py diff --git a/quantumclient/v2_0/client.py b/neutronclient/v2_0/client.py similarity index 96% rename from quantumclient/v2_0/client.py rename to neutronclient/v2_0/client.py index 93539dfd1..946361418 100644 --- a/quantumclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -21,12 +21,12 @@ import urllib import urlparse -from quantumclient import client -from quantumclient.common import _ -from quantumclient.common import constants -from quantumclient.common import exceptions -from quantumclient.common import serializer -from quantumclient.common import utils +from neutronclient import client +from neutronclient.common import _ +from neutronclient.common import constants +from neutronclient.common import exceptions +from neutronclient.common import serializer +from neutronclient.common import utils _logger = logging.getLogger(__name__) @@ -36,14 +36,14 @@ def exception_handler_v20(status_code, error_content): """Exception handler for API v2.0 client This routine generates the appropriate - Quantum exception according to the contents of the + Neutron exception according to the contents of the response body :param status_code: HTTP error status code :param error_content: deserialized body of error response """ - quantum_errors = { + neutron_errors = { 'NetworkNotFound': exceptions.NetworkNotFoundClient, 'NetworkInUse': exceptions.NetworkInUseClient, 'PortNotFound': exceptions.PortNotFoundClient, @@ -53,23 +53,23 @@ def exception_handler_v20(status_code, error_content): error_dict = None if isinstance(error_content, dict): - error_dict = error_content.get('QuantumError') + error_dict = error_content.get('NeutronError') # Find real error type - bad_quantum_error_flag = False + bad_neutron_error_flag = False if error_dict: - # If QuantumError key is found, it will definitely contain + # If Neutron key is found, it will definitely contain # a 'message' and 'type' keys? try: error_type = error_dict['type'] error_message = (error_dict['message'] + "\n" + error_dict['detail']) except Exception: - bad_quantum_error_flag = True - if not bad_quantum_error_flag: + bad_neutron_error_flag = True + if not bad_neutron_error_flag: ex = None try: # raise the appropriate error! - ex = quantum_errors[error_type](message=error_message) + ex = neutron_errors[error_type](message=error_message) ex.args = ([dict(status_code=status_code, message=error_message)], ) except Exception: @@ -77,19 +77,19 @@ def exception_handler_v20(status_code, error_content): if ex: raise ex else: - raise exceptions.QuantumClientException(status_code=status_code, + raise exceptions.NeutronClientException(status_code=status_code, message=error_dict) else: message = None if isinstance(error_content, dict): message = error_content.get('message', None) if message: - raise exceptions.QuantumClientException(status_code=status_code, + raise exceptions.NeutronClientException(status_code=status_code, message=message) - # If we end up here the exception was not a quantum error + # If we end up here the exception was not a neutron error msg = "%s-%s" % (status_code, error_content) - raise exceptions.QuantumClientException(status_code=status_code, + raise exceptions.NeutronClientException(status_code=status_code, message=msg) @@ -112,7 +112,7 @@ def with_params(*args, **kwargs): class Client(object): - """Client for the OpenStack Quantum v2.0 API. + """Client for the OpenStack Neutron v2.0 API. :param string username: Username for authentication. (optional) :param string password: Password for authentication. (optional) @@ -124,7 +124,7 @@ class Client(object): 'internalURL', or 'adminURL') (optional) :param string region_name: Name of a region to select when choosing an endpoint from the service catalog. - :param string endpoint_url: A user-supplied endpoint URL for the quantum + :param string endpoint_url: A user-supplied endpoint URL for the neutron service. Lazy-authentication is possible for API service calls if endpoint is set at instantiation.(optional) @@ -134,13 +134,13 @@ class Client(object): Example:: - from quantumclient.v2_0 import client - quantum = client.Client(username=USER, + from neutronclient.v2_0 import client + neutron = client.Client(username=USER, password=PASS, tenant_name=TENANT_NAME, auth_url=KEYSTONE_URL) - nets = quantum.list_networks() + nets = neutron.list_networks() ... """ @@ -717,7 +717,7 @@ def remove_router_from_l3_agent(self, l3_agent, router_id): l3_agent, router_id)) def __init__(self, **kwargs): - """Initialize a new client for the Quantum v2.0 API.""" + """Initialize a new client for the Neutron v2.0 API.""" super(Client, self).__init__() self.httpclient = client.HTTPClient(**kwargs) self.version = '2.0' @@ -734,7 +734,7 @@ def _handle_fault_response(self, status_code, response_body): des_error_body = self.deserialize(response_body, status_code) except Exception: # If unable to deserialized body it is probably not a - # Quantum error + # Neutron error des_error_body = {'message': response_body} # Raise the appropriate exception exception_handler_v20(status_code, des_error_body) @@ -828,7 +828,7 @@ def retry_request(self, method, action, body=None, except exceptions.ConnectionFailed: # Exception has already been logged by do_request() if i < self.retries: - _logger.debug(_('Retrying connection to quantum service')) + _logger.debug(_('Retrying connection to Neutron service')) time.sleep(self.retry_interval) raise exceptions.ConnectionFailed(reason=_("Maximum attempts reached")) diff --git a/quantumclient/version.py b/neutronclient/version.py similarity index 92% rename from quantumclient/version.py rename to neutronclient/version.py index 937548452..9315671c3 100644 --- a/quantumclient/version.py +++ b/neutronclient/version.py @@ -19,4 +19,4 @@ import pbr.version -__version__ = pbr.version.VersionInfo('python-quantumclient').version_string() +__version__ = pbr.version.VersionInfo('python-neutronclient').version_string() diff --git a/openstack-common.conf b/openstack-common.conf index cff7a3d6f..ecd7b3ef7 100644 --- a/openstack-common.conf +++ b/openstack-common.conf @@ -4,4 +4,4 @@ modules=exception,gettextutils,jsonutils,strutils,timeutils # The base module to hold the copy of openstack.common -base=quantumclient +base=neutronclient diff --git a/setup.cfg b/setup.cfg index 3c67b09b9..d8224c91f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = python-quantumclient +name = python-neutronclient summary = CLI and Client Library for OpenStack Networking description-file = README.rst @@ -20,7 +20,7 @@ classifier = [files] packages = - quantumclient + neutronclient [global] setup-hooks = @@ -28,7 +28,7 @@ setup-hooks = [entry_points] console_scripts = - quantum = quantumclient.shell:main + neutron = neutronclient.shell:main [build_sphinx] all_files = 1 diff --git a/tests/unit/lb/test_cli20_healthmonitor.py b/tests/unit/lb/test_cli20_healthmonitor.py index 0b87ff95a..51bc89429 100644 --- a/tests/unit/lb/test_cli20_healthmonitor.py +++ b/tests/unit/lb/test_cli20_healthmonitor.py @@ -21,7 +21,7 @@ import mox -from quantumclient.quantum.v2_0.lb import healthmonitor +from neutronclient.neutron.v2_0.lb import healthmonitor from tests.unit import test_cli20 diff --git a/tests/unit/lb/test_cli20_member.py b/tests/unit/lb/test_cli20_member.py index 9b75367bd..b280320cb 100644 --- a/tests/unit/lb/test_cli20_member.py +++ b/tests/unit/lb/test_cli20_member.py @@ -19,7 +19,7 @@ import sys -from quantumclient.quantum.v2_0.lb import member +from neutronclient.neutron.v2_0.lb import member from tests.unit import test_cli20 diff --git a/tests/unit/lb/test_cli20_pool.py b/tests/unit/lb/test_cli20_pool.py index 709c3d181..f54d71dbe 100644 --- a/tests/unit/lb/test_cli20_pool.py +++ b/tests/unit/lb/test_cli20_pool.py @@ -21,7 +21,7 @@ import mox -from quantumclient.quantum.v2_0.lb import pool +from neutronclient.neutron.v2_0.lb import pool from tests.unit import test_cli20 diff --git a/tests/unit/lb/test_cli20_vip.py b/tests/unit/lb/test_cli20_vip.py index 4358cfbec..f34efd6e9 100644 --- a/tests/unit/lb/test_cli20_vip.py +++ b/tests/unit/lb/test_cli20_vip.py @@ -19,7 +19,7 @@ import sys -from quantumclient.quantum.v2_0.lb import vip +from neutronclient.neutron.v2_0.lb import vip from tests.unit import test_cli20 diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index 06cc2de0b..52f021464 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -23,8 +23,8 @@ import mox import testtools -from quantumclient import client -from quantumclient.common import exceptions +from neutronclient import client +from neutronclient.common import exceptions USERNAME = 'testuser' @@ -48,7 +48,7 @@ 'publicURL': ENDPOINT_URL, 'region': REGION}], 'type': 'network', - 'name': 'Quantum Service'} + 'name': 'Neutron Service'} ] } } @@ -56,7 +56,7 @@ ENDPOINTS_RESULT = { 'endpoints': [{ 'type': 'network', - 'name': 'Quantum Service', + 'name': 'Neutron Service', 'region': REGION, 'adminURL': ENDPOINT_URL, 'internalURL': ENDPOINT_URL, @@ -110,7 +110,7 @@ def test_refresh_token(self): res401 = self.mox.CreateMock(httplib2.Response) res401.status = 401 - # If a token is expired, quantum server retruns 401 + # If a token is expired, neutron server retruns 401 self.client.request( mox.StrContains(ENDPOINT_URL + '/resource'), 'GET', headers=mox.ContainsKeyValue('X-Auth-Token', TOKEN) diff --git a/tests/unit/test_casual_args.py b/tests/unit/test_casual_args.py index fcfff9331..1722e683d 100644 --- a/tests/unit/test_casual_args.py +++ b/tests/unit/test_casual_args.py @@ -17,83 +17,83 @@ import testtools -from quantumclient.common import exceptions -from quantumclient.quantum import v2_0 as quantumV20 +from neutronclient.common import exceptions +from neutronclient.neutron import v2_0 as neutronV20 class CLITestArgs(testtools.TestCase): def test_empty(self): - _mydict = quantumV20.parse_args_to_dict([]) + _mydict = neutronV20.parse_args_to_dict([]) self.assertEqual({}, _mydict) def test_default_bool(self): _specs = ['--my_bool', '--arg1', 'value1'] - _mydict = quantumV20.parse_args_to_dict(_specs) + _mydict = neutronV20.parse_args_to_dict(_specs) self.assertTrue(_mydict['my_bool']) def test_bool_true(self): _specs = ['--my-bool', 'type=bool', 'true', '--arg1', 'value1'] - _mydict = quantumV20.parse_args_to_dict(_specs) + _mydict = neutronV20.parse_args_to_dict(_specs) self.assertTrue(_mydict['my_bool']) def test_bool_false(self): _specs = ['--my_bool', 'type=bool', 'false', '--arg1', 'value1'] - _mydict = quantumV20.parse_args_to_dict(_specs) + _mydict = neutronV20.parse_args_to_dict(_specs) self.assertFalse(_mydict['my_bool']) def test_nargs(self): _specs = ['--tag', 'x', 'y', '--arg1', 'value1'] - _mydict = quantumV20.parse_args_to_dict(_specs) + _mydict = neutronV20.parse_args_to_dict(_specs) self.assertTrue('x' in _mydict['tag']) self.assertTrue('y' in _mydict['tag']) def test_badarg(self): _specs = ['--tag=t', 'x', 'y', '--arg1', 'value1'] self.assertRaises(exceptions.CommandError, - quantumV20.parse_args_to_dict, _specs) + neutronV20.parse_args_to_dict, _specs) def test_badarg_with_minus(self): _specs = ['--arg1', 'value1', '-D'] self.assertRaises(exceptions.CommandError, - quantumV20.parse_args_to_dict, _specs) + neutronV20.parse_args_to_dict, _specs) def test_goodarg_with_minus_number(self): _specs = ['--arg1', 'value1', '-1', '-1.0'] - _mydict = quantumV20.parse_args_to_dict(_specs) + _mydict = neutronV20.parse_args_to_dict(_specs) self.assertEqual(['value1', '-1', '-1.0'], _mydict['arg1']) def test_badarg_duplicate(self): _specs = ['--tag=t', '--arg1', 'value1', '--arg1', 'value1'] self.assertRaises(exceptions.CommandError, - quantumV20.parse_args_to_dict, _specs) + neutronV20.parse_args_to_dict, _specs) def test_badarg_early_type_specification(self): _specs = ['type=dict', 'key=value'] self.assertRaises(exceptions.CommandError, - quantumV20.parse_args_to_dict, _specs) + neutronV20.parse_args_to_dict, _specs) def test_arg(self): _specs = ['--tag=t', '--arg1', 'value1'] self.assertEqual('value1', - quantumV20.parse_args_to_dict(_specs)['arg1']) + neutronV20.parse_args_to_dict(_specs)['arg1']) def test_dict_arg(self): _specs = ['--tag=t', '--arg1', 'type=dict', 'key1=value1,key2=value2'] - arg1 = quantumV20.parse_args_to_dict(_specs)['arg1'] + arg1 = neutronV20.parse_args_to_dict(_specs)['arg1'] self.assertEqual('value1', arg1['key1']) self.assertEqual('value2', arg1['key2']) def test_dict_arg_with_attribute_named_type(self): _specs = ['--tag=t', '--arg1', 'type=dict', 'type=value1,key2=value2'] - arg1 = quantumV20.parse_args_to_dict(_specs)['arg1'] + arg1 = neutronV20.parse_args_to_dict(_specs)['arg1'] self.assertEqual('value1', arg1['type']) self.assertEqual('value2', arg1['key2']) def test_list_of_dict_arg(self): _specs = ['--tag=t', '--arg1', 'type=dict', 'list=true', 'key1=value1,key2=value2'] - arg1 = quantumV20.parse_args_to_dict(_specs)['arg1'] + arg1 = neutronV20.parse_args_to_dict(_specs)['arg1'] self.assertEqual('value1', arg1[0]['key1']) self.assertEqual('value2', arg1[0]['key2']) diff --git a/tests/unit/test_cli20.py b/tests/unit/test_cli20.py index c89cdfa78..b1b649093 100644 --- a/tests/unit/test_cli20.py +++ b/tests/unit/test_cli20.py @@ -21,9 +21,9 @@ import mox import testtools -from quantumclient.common import constants -from quantumclient import shell -from quantumclient.v2_0 import client +from neutronclient.common import constants +from neutronclient import shell +from neutronclient.v2_0 import client API_VERSION = "2.0" @@ -170,10 +170,10 @@ def setUp(self, plurals={}): self.fake_stdout = FakeStdout() self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.fake_stdout)) self.useFixture(fixtures.MonkeyPatch( - 'quantumclient.quantum.v2_0.find_resourceid_by_name_or_id', + 'neutronclient.neutron.v2_0.find_resourceid_by_name_or_id', self._find_resourceid)) self.useFixture(fixtures.MonkeyPatch( - 'quantumclient.v2_0.client.Client.get_attr_metadata', + 'neutronclient.v2_0.client.Client.get_attr_metadata', self._get_attr_metadata)) self.client = client.Client(token=TOKEN, endpoint_url=self.endurl) diff --git a/tests/unit/test_cli20_extensions.py b/tests/unit/test_cli20_extensions.py index 874de77ba..16664aca6 100644 --- a/tests/unit/test_cli20_extensions.py +++ b/tests/unit/test_cli20_extensions.py @@ -17,8 +17,8 @@ import sys -from quantumclient.quantum.v2_0.extension import ListExt -from quantumclient.quantum.v2_0.extension import ShowExt +from neutronclient.neutron.v2_0.extension import ListExt +from neutronclient.neutron.v2_0.extension import ShowExt from tests.unit.test_cli20 import CLITestV20Base from tests.unit.test_cli20 import MyApp diff --git a/tests/unit/test_cli20_floatingips.py b/tests/unit/test_cli20_floatingips.py index b640112b4..8e7e5882f 100644 --- a/tests/unit/test_cli20_floatingips.py +++ b/tests/unit/test_cli20_floatingips.py @@ -18,7 +18,7 @@ import sys -from quantumclient.quantum.v2_0 import floatingip as fip +from neutronclient.neutron.v2_0 import floatingip as fip from tests.unit import test_cli20 diff --git a/tests/unit/test_cli20_network.py b/tests/unit/test_cli20_network.py index 7df54ffbf..4fc8235bd 100644 --- a/tests/unit/test_cli20_network.py +++ b/tests/unit/test_cli20_network.py @@ -18,10 +18,10 @@ import mox -from quantumclient.common import exceptions -from quantumclient.common import utils -from quantumclient.quantum.v2_0 import network -from quantumclient import shell +from neutronclient.common import exceptions +from neutronclient.common import utils +from neutronclient.neutron.v2_0 import network +from neutronclient import shell from tests.unit import test_cli20 diff --git a/tests/unit/test_cli20_nvp_queue.py b/tests/unit/test_cli20_nvp_queue.py index d84d9df7f..f74552639 100644 --- a/tests/unit/test_cli20_nvp_queue.py +++ b/tests/unit/test_cli20_nvp_queue.py @@ -18,7 +18,7 @@ import sys -from quantumclient.quantum.v2_0 import nvp_qos_queue as qos +from neutronclient.neutron.v2_0 import nvp_qos_queue as qos from tests.unit import test_cli20 diff --git a/tests/unit/test_cli20_nvpnetworkgateway.py b/tests/unit/test_cli20_nvpnetworkgateway.py index ebf730a17..81185aa10 100644 --- a/tests/unit/test_cli20_nvpnetworkgateway.py +++ b/tests/unit/test_cli20_nvpnetworkgateway.py @@ -17,7 +17,7 @@ import sys -from quantumclient.quantum.v2_0 import nvpnetworkgateway as nwgw +from neutronclient.neutron.v2_0 import nvpnetworkgateway as nwgw from tests.unit import test_cli20 diff --git a/tests/unit/test_cli20_port.py b/tests/unit/test_cli20_port.py index 545f35b39..150cf8f34 100644 --- a/tests/unit/test_cli20_port.py +++ b/tests/unit/test_cli20_port.py @@ -19,8 +19,8 @@ import mox -from quantumclient.quantum.v2_0 import port -from quantumclient import shell +from neutronclient.neutron.v2_0 import port +from neutronclient import shell from tests.unit import test_cli20 diff --git a/tests/unit/test_cli20_router.py b/tests/unit/test_cli20_router.py index 35ac577a1..c8ef88284 100644 --- a/tests/unit/test_cli20_router.py +++ b/tests/unit/test_cli20_router.py @@ -17,8 +17,8 @@ import sys -from quantumclient.common import exceptions -from quantumclient.quantum.v2_0 import router +from neutronclient.common import exceptions +from neutronclient.neutron.v2_0 import router from tests.unit import test_cli20 diff --git a/tests/unit/test_cli20_securitygroup.py b/tests/unit/test_cli20_securitygroup.py index 4302093e1..b7bea09fc 100644 --- a/tests/unit/test_cli20_securitygroup.py +++ b/tests/unit/test_cli20_securitygroup.py @@ -20,7 +20,7 @@ import mox -from quantumclient.quantum.v2_0 import securitygroup +from neutronclient.neutron.v2_0 import securitygroup from tests.unit import test_cli20 diff --git a/tests/unit/test_cli20_subnet.py b/tests/unit/test_cli20_subnet.py index 5c048a605..c7fc9fd54 100644 --- a/tests/unit/test_cli20_subnet.py +++ b/tests/unit/test_cli20_subnet.py @@ -17,7 +17,7 @@ import sys -from quantumclient.quantum.v2_0 import subnet +from neutronclient.neutron.v2_0 import subnet from tests.unit import test_cli20 diff --git a/tests/unit/test_name_or_id.py b/tests/unit/test_name_or_id.py index fc3bdac5e..09de0add3 100644 --- a/tests/unit/test_name_or_id.py +++ b/tests/unit/test_name_or_id.py @@ -20,9 +20,9 @@ import mox import testtools -from quantumclient.common import exceptions -from quantumclient.quantum import v2_0 as quantumv20 -from quantumclient.v2_0 import client +from neutronclient.common import exceptions +from neutronclient.neutron import v2_0 as neutronV20 +from neutronclient.v2_0 import client from tests.unit import test_cli20 @@ -50,7 +50,7 @@ def test_get_id_from_id(self): headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() - returned_id = quantumv20.find_resourceid_by_name_or_id( + returned_id = neutronV20.find_resourceid_by_name_or_id( self.client, 'network', _id) self.assertEqual(_id, returned_id) @@ -72,7 +72,7 @@ def test_get_id_from_id_then_name_empty(self): headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() - returned_id = quantumv20.find_resourceid_by_name_or_id( + returned_id = neutronV20.find_resourceid_by_name_or_id( self.client, 'network', _id) self.assertEqual(_id, returned_id) @@ -89,7 +89,7 @@ def test_get_id_from_name(self): headers=mox.ContainsKeyValue('X-Auth-Token', test_cli20.TOKEN) ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() - returned_id = quantumv20.find_resourceid_by_name_or_id( + returned_id = neutronV20.find_resourceid_by_name_or_id( self.client, 'network', name) self.assertEqual(_id, returned_id) @@ -107,9 +107,9 @@ def test_get_id_from_name_multiple(self): ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() try: - quantumv20.find_resourceid_by_name_or_id( + neutronV20.find_resourceid_by_name_or_id( self.client, 'network', name) - except exceptions.QuantumClientException as ex: + except exceptions.NeutronClientException as ex: self.assertTrue('Multiple' in ex.message) def test_get_id_from_name_notfound(self): @@ -125,8 +125,8 @@ def test_get_id_from_name_notfound(self): ).AndReturn((test_cli20.MyResp(200), resstr)) self.mox.ReplayAll() try: - quantumv20.find_resourceid_by_name_or_id( + neutronV20.find_resourceid_by_name_or_id( self.client, 'network', name) - except exceptions.QuantumClientException as ex: + except exceptions.NeutronClientException as ex: self.assertTrue('Unable to find' in ex.message) self.assertEqual(404, ex.status_code) diff --git a/tests/unit/test_quota.py b/tests/unit/test_quota.py index 633ff62af..e971f46cc 100644 --- a/tests/unit/test_quota.py +++ b/tests/unit/test_quota.py @@ -17,8 +17,8 @@ import sys -from quantumclient.common import exceptions -from quantumclient.quantum.v2_0 import quota as test_quota +from neutronclient.common import exceptions +from neutronclient.neutron.v2_0 import quota as test_quota from tests.unit import test_cli20 @@ -36,7 +36,7 @@ def test_update_quota(self): test_cli20.MyApp(sys.stdout), None) args = ['--tenant-id', self.test_id, '--network', 'test'] self.assertRaises( - exceptions.QuantumClientException, self._test_update_resource, + exceptions.NeutronClientException, self._test_update_resource, resource, cmd, self.test_id, args=args, extrafields={'network': 'new'}) diff --git a/tests/unit/test_shell.py b/tests/unit/test_shell.py index b6a8e14f1..a3ac68bbc 100644 --- a/tests/unit/test_shell.py +++ b/tests/unit/test_shell.py @@ -25,8 +25,8 @@ import testtools from testtools import matchers -from quantumclient.common import exceptions -from quantumclient import shell as openstack_shell +from neutronclient.common import exceptions +from neutronclient import shell as openstack_shell DEFAULT_USERNAME = 'username' @@ -53,7 +53,7 @@ class ShellTest(testtools.TestCase): 'OS_AUTH_URL': DEFAULT_AUTH_URL} def _tolerant_shell(self, cmd): - t_shell = openstack_shell.QuantumShell('2.0') + t_shell = openstack_shell.NeutronShell('2.0') t_shell.run(cmd.split()) # Patch os.environ to avoid required auth info. @@ -68,7 +68,7 @@ def setUp(self): # Make a fake shell object, a helping wrapper to call it, and a quick # way of asserting that certain API calls were made. global shell, _shell, assert_called, assert_called_anytime - _shell = openstack_shell.QuantumShell('2.0') + _shell = openstack_shell.NeutronShell('2.0') shell = lambda cmd: _shell.run(cmd.split()) def shell(self, argstr): @@ -77,7 +77,7 @@ def shell(self, argstr): _old_env, os.environ = os.environ, clean_env.copy() try: sys.stdout = cStringIO.StringIO() - _shell = openstack_shell.QuantumShell('2.0') + _shell = openstack_shell.NeutronShell('2.0') _shell.run(argstr.split()) except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() @@ -90,7 +90,7 @@ def shell(self, argstr): return out def test_run_unknown_command(self): - openstack_shell.QuantumShell('2.0').run('fake') + openstack_shell.NeutronShell('2.0').run('fake') def test_help(self): required = 'usage:' @@ -126,13 +126,13 @@ def test_auth(self): ' --os-auth-strategy keystone quota-list') def test_build_option_parser(self): - quant_shell = openstack_shell.QuantumShell('2.0') - result = quant_shell.build_option_parser('descr', '2.0') + neutron_shell = openstack_shell.NeutronShell('2.0') + result = neutron_shell.build_option_parser('descr', '2.0') self.assertEqual(True, isinstance(result, argparse.ArgumentParser)) def test_main_with_unicode(self): - self.mox.StubOutClassWithMocks(openstack_shell, 'QuantumShell') - qshell_mock = openstack_shell.QuantumShell('2.0') + self.mox.StubOutClassWithMocks(openstack_shell, 'NeutronShell') + qshell_mock = openstack_shell.NeutronShell('2.0') #self.mox.StubOutWithMock(qshell_mock, 'run') unicode_text = u'\u7f51\u7edc' argv = ['net-list', unicode_text, unicode_text.encode('utf-8')] @@ -145,7 +145,7 @@ def test_main_with_unicode(self): self.assertEqual(ret, 0) def test_endpoint_option(self): - shell = openstack_shell.QuantumShell('2.0') + shell = openstack_shell.NeutronShell('2.0') parser = shell.build_option_parser('descr', '2.0') # Neither $OS_ENDPOINT_TYPE nor --endpoint-type @@ -161,7 +161,7 @@ def test_endpoint_environment_variable(self): "public") self.useFixture(fixture) - shell = openstack_shell.QuantumShell('2.0') + shell = openstack_shell.NeutronShell('2.0') parser = shell.build_option_parser('descr', '2.0') # $OS_ENDPOINT_TYPE but not --endpoint-type diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index d7d152000..e9b4d42ad 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -19,8 +19,8 @@ import testtools -from quantumclient.common import exceptions -from quantumclient.common import utils +from neutronclient.common import exceptions +from neutronclient.common import utils class TestUtils(testtools.TestCase): diff --git a/tools/neutron.bash_completion b/tools/neutron.bash_completion new file mode 100644 index 000000000..3e542e0a9 --- /dev/null +++ b/tools/neutron.bash_completion @@ -0,0 +1,27 @@ +_neutron_opts="" # lazy init +_neutron_flags="" # lazy init +_neutron_opts_exp="" # lazy init +_neutron() +{ + local cur prev nbc cflags + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [ "x$_neutron_opts" == "x" ] ; then + nbc="`neutron bash-completion`" + _neutron_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" + _neutron_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" + _neutron_opts_exp="`echo "$_neutron_opts" | sed -e "s/\s/|/g"`" + fi + + if [[ " ${COMP_WORDS[@]} " =~ " "($_neutron_opts_exp)" " && "$prev" != "help" ]] ; then + COMPLETION_CACHE=~/.neutronclient/*/*-cache + cflags="$_neutron_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ') + COMPREPLY=($(compgen -W "${cflags}" -- ${cur})) + else + COMPREPLY=($(compgen -W "${_neutron_opts}" -- ${cur})) + fi + return 0 +} +complete -F _neutron neutron diff --git a/tools/quantum.bash_completion b/tools/quantum.bash_completion deleted file mode 100644 index 311f53309..000000000 --- a/tools/quantum.bash_completion +++ /dev/null @@ -1,27 +0,0 @@ -_quantum_opts="" # lazy init -_quantum_flags="" # lazy init -_quantum_opts_exp="" # lazy init -_quantum() -{ - local cur prev nbc cflags - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" - - if [ "x$_quantum_opts" == "x" ] ; then - nbc="`quantum bash-completion`" - _quantum_opts="`echo "$nbc" | sed -e "s/--[a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" - _quantum_flags="`echo " $nbc" | sed -e "s/ [^-][^-][a-z0-9_-]*//g" -e "s/\s\s*/ /g"`" - _quantum_opts_exp="`echo "$_quantum_opts" | sed -e "s/\s/|/g"`" - fi - - if [[ " ${COMP_WORDS[@]} " =~ " "($_quantum_opts_exp)" " && "$prev" != "help" ]] ; then - COMPLETION_CACHE=~/.quantumclient/*/*-cache - cflags="$_quantum_flags "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ') - COMPREPLY=($(compgen -W "${cflags}" -- ${cur})) - else - COMPREPLY=($(compgen -W "${_quantum_opts}" -- ${cur})) - fi - return 0 -} -complete -F _quantum quantum From 4e3be8d3426e619bdbb266d475a4d025e0446759 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sat, 6 Jul 2013 12:29:05 -0400 Subject: [PATCH 133/135] Renamed quantum to neutron in .gitreview Change-Id: I08794753f0a4c1c35c89d712011638bd601a6622 --- .gitreview | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitreview b/.gitreview index 40817febd..994b07a43 100644 --- a/.gitreview +++ b/.gitreview @@ -1,4 +1,4 @@ [gerrit] host=review.openstack.org port=29418 -project=openstack/python-quantumclient.git +project=openstack/python-neutronclient.git From 86a56d3cfdfa2ae769c75855dedb6a50e422bec1 Mon Sep 17 00:00:00 2001 From: Roman Podolyaka Date: Fri, 19 Apr 2013 16:15:20 +0300 Subject: [PATCH 134/135] Don't convert httplib2 exceptions to status codes Currently HTTPClient class sets the 'force_exception_to_status_code' instance variable to True. This actually leads to the next problem: there is no way to distinguish between errors which have the same status code. E. g. both SSL handshake error and a general Bad Request error have status code 400, and the only way to distinguish between them is to analyze the error message content, that is both error-prone and ugly. The proposed solution: - leaves the 'force_exception_to_status_code' set to False - adds a try/except wrapper for the request() call to handle httplib2 exceptions (e. g. host not found, socket timeout, etc) Blueprint: quantum-client-ssl Change-Id: Ib168fadc67568d5d6247f7addbe731e8832a39de --- neutronclient/client.py | 9 ++++-- tests/unit/test_http.py | 64 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 tests/unit/test_http.py diff --git a/neutronclient/client.py b/neutronclient/client.py index 9dc80a354..0d61cb4d2 100644 --- a/neutronclient/client.py +++ b/neutronclient/client.py @@ -112,7 +112,6 @@ def __init__(self, username=None, tenant_name=None, self.endpoint_url = endpoint_url self.auth_strategy = auth_strategy # httplib2 overrides - self.force_exception_to_status_code = True self.disable_ssl_certificate_validation = insecure def _cs_request(self, *args, **kwargs): @@ -132,7 +131,13 @@ def _cs_request(self, *args, **kwargs): args = utils.safe_encode_list(args) kargs = utils.safe_encode_dict(kargs) utils.http_log_req(_logger, args, kargs) - resp, body = self.request(*args, **kargs) + try: + resp, body = self.request(*args, **kargs) + except Exception as e: + # Wrap the low-level connection error (socket timeout, redirect + # limit, decompression error, etc) into our custom high-level + # connection exception (it is excepted in the upper layers of code) + raise exceptions.ConnectionFailed(reason=e) utils.http_log_resp(_logger, resp, body) status_code = self.get_status_code(resp) if status_code == 401: diff --git a/tests/unit/test_http.py b/tests/unit/test_http.py new file mode 100644 index 000000000..09182324f --- /dev/null +++ b/tests/unit/test_http.py @@ -0,0 +1,64 @@ +# Copyright (C) 2013 OpenStack Foundation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +import httplib2 +import mox +import testtools + +from neutronclient.client import HTTPClient +from neutronclient.common import exceptions +from tests.unit.test_cli20 import MyResp + + +AUTH_TOKEN = 'test_token' +END_URL = 'test_url' +METHOD = 'GET' +URL = 'http://test.test:1234/v2.0/test' + + +class TestHTTPClient(testtools.TestCase): + def setUp(self): + super(TestHTTPClient, self).setUp() + + self.mox = mox.Mox() + self.mox.StubOutWithMock(httplib2.Http, 'request') + self.addCleanup(self.mox.UnsetStubs) + + self.http = HTTPClient(token=AUTH_TOKEN, endpoint_url=END_URL) + + def test_request_error(self): + httplib2.Http.request( + URL, METHOD, headers=mox.IgnoreArg() + ).AndRaise(Exception('error msg')) + self.mox.ReplayAll() + + self.assertRaises( + exceptions.ConnectionFailed, + self.http._cs_request, + URL, METHOD + ) + self.mox.VerifyAll() + + def test_request_success(self): + rv_should_be = MyResp(200), 'test content' + + httplib2.Http.request( + URL, METHOD, headers=mox.IgnoreArg() + ).AndReturn(rv_should_be) + self.mox.ReplayAll() + + self.assertEqual(rv_should_be, self.http._cs_request(URL, METHOD)) + self.mox.VerifyAll() From 037497da521b5c1cf8cb53837f9af10eb755210e Mon Sep 17 00:00:00 2001 From: Phil Day Date: Mon, 1 Jul 2013 12:01:00 +0100 Subject: [PATCH 135/135] Allow tenant ID for authentication Under the Keystone v3 API Tenant names are not necessarily uniques across Domains for a User, so the client should also allow authentication by tenant_id Fixes bug 1196486 Change-Id: I3f385a19c1d3d66f5539f901796bbaa22d315762 --- neutronclient/client.py | 17 +++++++---- neutronclient/common/clientmanager.py | 1 + neutronclient/shell.py | 14 +++++++-- neutronclient/v2_0/client.py | 1 + tests/unit/test_auth.py | 41 ++++++++++++++++++++++++++- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/neutronclient/client.py b/neutronclient/client.py index 9dc80a354..1afa37096 100644 --- a/neutronclient/client.py +++ b/neutronclient/client.py @@ -94,7 +94,7 @@ class HTTPClient(httplib2.Http): USER_AGENT = 'python-neutronclient' - def __init__(self, username=None, tenant_name=None, + def __init__(self, username=None, tenant_name=None, tenant_id=None, password=None, auth_url=None, token=None, region_name=None, timeout=None, endpoint_url=None, insecure=False, @@ -103,6 +103,7 @@ def __init__(self, username=None, tenant_name=None, super(HTTPClient, self).__init__(timeout=timeout) self.username = username self.tenant_name = tenant_name + self.tenant_id = tenant_id self.password = password self.auth_url = auth_url.rstrip('/') if auth_url else None self.endpoint_type = endpoint_type @@ -183,10 +184,16 @@ def _extract_service_catalog(self, body): def authenticate(self): if self.auth_strategy != 'keystone': raise exceptions.Unauthorized(message='unknown auth strategy') - body = {'auth': {'passwordCredentials': - {'username': self.username, - 'password': self.password, }, - 'tenantName': self.tenant_name, }, } + if self.tenant_id: + body = {'auth': {'passwordCredentials': + {'username': self.username, + 'password': self.password, }, + 'tenantId': self.tenant_id, }, } + else: + body = {'auth': {'passwordCredentials': + {'username': self.username, + 'password': self.password, }, + 'tenantName': self.tenant_name, }, } token_url = self.auth_url + "/tokens" diff --git a/neutronclient/common/clientmanager.py b/neutronclient/common/clientmanager.py index 8e0614d9f..f9e886e01 100644 --- a/neutronclient/common/clientmanager.py +++ b/neutronclient/common/clientmanager.py @@ -76,6 +76,7 @@ def initialize(self): if not self._url: httpclient = client.HTTPClient(username=self._username, tenant_name=self._tenant_name, + tenant_id=self._tenant_id, password=self._password, region_name=self._region_name, auth_url=self._auth_url, diff --git a/neutronclient/shell.py b/neutronclient/shell.py index 8cc644142..89c7adf0c 100644 --- a/neutronclient/shell.py +++ b/neutronclient/shell.py @@ -295,6 +295,11 @@ def build_option_parser(self, description, version): '--os_tenant_name', help=argparse.SUPPRESS) + parser.add_argument( + '--os-tenant-id', metavar='', + default=env('OS_TENANT_ID'), + help='Authentication tenant name (Env: OS_TENANT_ID)') + parser.add_argument( '--os-username', metavar='', default=utils.env('OS_USERNAME'), @@ -482,10 +487,12 @@ def authenticate_user(self): "You must provide a password via" " either --os-password or env[OS_PASSWORD]") - if not (self.options.os_tenant_name): + if (not self.options.os_tenant_name + and not self.options.os_tenant_id): raise exc.CommandError( - "You must provide a tenant_name via" - " either --os-tenant-name or via env[OS_TENANT_NAME]") + "You must provide a tenant_name or tenant_id via" + " --os-tenant-name, env[OS_TENANT_NAME]" + " --os-tenant-id, or via env[OS_TENANT_ID]") if not self.options.os_auth_url: raise exc.CommandError( @@ -502,6 +509,7 @@ def authenticate_user(self): url=self.options.os_url, auth_url=self.options.os_auth_url, tenant_name=self.options.os_tenant_name, + tenant_id=self.options.os_tenant_id, username=self.options.os_username, password=self.options.os_password, region_name=self.options.os_region_name, diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py index 946361418..e2e1c792c 100644 --- a/neutronclient/v2_0/client.py +++ b/neutronclient/v2_0/client.py @@ -118,6 +118,7 @@ class Client(object): :param string password: Password for authentication. (optional) :param string token: Token for authentication. (optional) :param string tenant_name: Tenant name. (optional) + :param string tenant_id: Tenant id. (optional) :param string auth_url: Keystone service endpoint for authorization. :param string endpoint_type: Network service endpoint type to pull from the keystone catalog (e.g. 'publicURL', diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index 52f021464..3929ad4b8 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -29,6 +29,7 @@ USERNAME = 'testuser' TENANT_NAME = 'testtenant' +TENANT_ID = 'testtenantid' PASSWORD = 'password' AUTH_URL = 'authurl' ENDPOINT_URL = 'localurl' @@ -67,6 +68,9 @@ class CLITestAuthKeystone(testtools.TestCase): + # Auth Body expected when using tenant name + auth_type = 'tenantName' + def setUp(self): """Prepare the test environment.""" super(CLITestAuthKeystone, self).setUp() @@ -87,7 +91,7 @@ def test_get_token(self): self.client.request( AUTH_URL + '/tokens', 'POST', - body=mox.IsA(str), headers=mox.IsA(dict) + body=mox.StrContains(self.auth_type), headers=mox.IsA(dict) ).AndReturn((res200, json.dumps(KS_TOKEN_RESULT))) self.client.request( mox.StrContains(ENDPOINT_URL + '/resource'), 'GET', @@ -318,3 +322,38 @@ def test_endpoint_type(self): self.assertRaises(exceptions.EndpointTypeNotFound, self.client._extract_service_catalog, resources) + + +class CLITestAuthKeystoneWithId(CLITestAuthKeystone): + + # Auth Body expected when using tenant Id + auth_type = 'tenantId' + + def setUp(self): + """Prepare the test environment.""" + super(CLITestAuthKeystone, self).setUp() + self.mox = mox.Mox() + self.client = client.HTTPClient(username=USERNAME, + tenant_id=TENANT_ID, + password=PASSWORD, + auth_url=AUTH_URL, + region_name=REGION) + self.addCleanup(self.mox.VerifyAll) + self.addCleanup(self.mox.UnsetStubs) + + +class CLITestAuthKeystoneWithIdandName(CLITestAuthKeystone): + + # Auth Body expected when using tenant Id + auth_type = 'tenantId' + + def setUp(self): + """Prepare the test environment.""" + super(CLITestAuthKeystone, self).setUp() + self.mox = mox.Mox() + self.client = client.HTTPClient(username=USERNAME, + tenant_id=TENANT_ID, + tenant_name=TENANT_NAME, + password=PASSWORD, + auth_url=AUTH_URL, + region_name=REGION)