Sync with oslo-incubator
Apiclient[1] and cliutils[2] is removed form Oslo incubator, we should keep the local copy by ourself. 1. copy openstack/common/apiclient to common/apiclient 2. only keep base, auth, exceptions from apiclient, others are unused 3. copy openstack/common/cliutils.py to common/cliutils.py 4. update all magnumclient.openstack.common.* users to use magnumclient.common.* This is the first step to delete openstack common modules. [1] Change: I2020d73fa9cedb3b3b87c1d8dbc8d437857ec7c2 [2] Change: Ibc5a8f11c6e5c308cec15a60eeb07a898254f9b7 Partial-Bug: #1545957 Change-Id: Ide3814d87ecdf3255868bf3b78fdceb58bd14295
This commit is contained in:
		
							
								
								
									
										0
									
								
								magnumclient/common/apiclient/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								magnumclient/common/apiclient/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										231
									
								
								magnumclient/common/apiclient/auth.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								magnumclient/common/apiclient/auth.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,231 @@
 | 
			
		||||
# Copyright 2013 OpenStack Foundation
 | 
			
		||||
# Copyright 2013 Spanish National Research Council.
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
# E0202: An attribute inherited from %s hide this method
 | 
			
		||||
# pylint: disable=E0202
 | 
			
		||||
 | 
			
		||||
########################################################################
 | 
			
		||||
#
 | 
			
		||||
# THIS MODULE IS DEPRECATED
 | 
			
		||||
#
 | 
			
		||||
# Please refer to
 | 
			
		||||
# https://etherpad.openstack.org/p/kilo-magnumclient-library-proposals for
 | 
			
		||||
# the discussion leading to this deprecation.
 | 
			
		||||
#
 | 
			
		||||
# We recommend checking out the python-openstacksdk project
 | 
			
		||||
# (https://launchpad.net/python-openstacksdk) instead.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
 | 
			
		||||
import abc
 | 
			
		||||
import argparse
 | 
			
		||||
import os
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
from stevedore import extension
 | 
			
		||||
 | 
			
		||||
from magnumclient.common.apiclient import exceptions
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
_discovered_plugins = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def discover_auth_systems():
 | 
			
		||||
    """Discover the available auth-systems.
 | 
			
		||||
 | 
			
		||||
    This won't take into account the old style auth-systems.
 | 
			
		||||
    """
 | 
			
		||||
    global _discovered_plugins
 | 
			
		||||
    _discovered_plugins = {}
 | 
			
		||||
 | 
			
		||||
    def add_plugin(ext):
 | 
			
		||||
        _discovered_plugins[ext.name] = ext.plugin
 | 
			
		||||
 | 
			
		||||
    ep_namespace = "magnumclient.openstack.common.apiclient.auth"
 | 
			
		||||
    mgr = extension.ExtensionManager(ep_namespace)
 | 
			
		||||
    mgr.map(add_plugin)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_auth_system_opts(parser):
 | 
			
		||||
    """Load options needed by the available auth-systems into a parser.
 | 
			
		||||
 | 
			
		||||
    This function will try to populate the parser with options from the
 | 
			
		||||
    available plugins.
 | 
			
		||||
    """
 | 
			
		||||
    group = parser.add_argument_group("Common auth options")
 | 
			
		||||
    BaseAuthPlugin.add_common_opts(group)
 | 
			
		||||
    for name, auth_plugin in _discovered_plugins.items():
 | 
			
		||||
        group = parser.add_argument_group(
 | 
			
		||||
            "Auth-system '%s' options" % name,
 | 
			
		||||
            conflict_handler="resolve")
 | 
			
		||||
        auth_plugin.add_opts(group)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_plugin(auth_system):
 | 
			
		||||
    try:
 | 
			
		||||
        plugin_class = _discovered_plugins[auth_system]
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        raise exceptions.AuthSystemNotFound(auth_system)
 | 
			
		||||
    return plugin_class(auth_system=auth_system)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def load_plugin_from_args(args):
 | 
			
		||||
    """Load required plugin and populate it with options.
 | 
			
		||||
 | 
			
		||||
    Try to guess auth system if it is not specified. Systems are tried in
 | 
			
		||||
    alphabetical order.
 | 
			
		||||
 | 
			
		||||
    :type args: argparse.Namespace
 | 
			
		||||
    :raises: AuthPluginOptionsMissing
 | 
			
		||||
    """
 | 
			
		||||
    auth_system = args.os_auth_system
 | 
			
		||||
    if auth_system:
 | 
			
		||||
        plugin = load_plugin(auth_system)
 | 
			
		||||
        plugin.parse_opts(args)
 | 
			
		||||
        plugin.sufficient_options()
 | 
			
		||||
        return plugin
 | 
			
		||||
 | 
			
		||||
    for plugin_auth_system in sorted(six.iterkeys(_discovered_plugins)):
 | 
			
		||||
        plugin_class = _discovered_plugins[plugin_auth_system]
 | 
			
		||||
        plugin = plugin_class()
 | 
			
		||||
        plugin.parse_opts(args)
 | 
			
		||||
        try:
 | 
			
		||||
            plugin.sufficient_options()
 | 
			
		||||
        except exceptions.AuthPluginOptionsMissing:
 | 
			
		||||
            continue
 | 
			
		||||
        return plugin
 | 
			
		||||
    raise exceptions.AuthPluginOptionsMissing(["auth_system"])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@six.add_metaclass(abc.ABCMeta)
 | 
			
		||||
class BaseAuthPlugin(object):
 | 
			
		||||
    """Base class for authentication plugins.
 | 
			
		||||
 | 
			
		||||
    An authentication plugin needs to override at least the authenticate
 | 
			
		||||
    method to be a valid plugin.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    auth_system = None
 | 
			
		||||
    opt_names = []
 | 
			
		||||
    common_opt_names = [
 | 
			
		||||
        "auth_system",
 | 
			
		||||
        "username",
 | 
			
		||||
        "password",
 | 
			
		||||
        "tenant_name",
 | 
			
		||||
        "token",
 | 
			
		||||
        "auth_url",
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    def __init__(self, auth_system=None, **kwargs):
 | 
			
		||||
        self.auth_system = auth_system or self.auth_system
 | 
			
		||||
        self.opts = dict((name, kwargs.get(name))
 | 
			
		||||
                         for name in self.opt_names)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def _parser_add_opt(parser, opt):
 | 
			
		||||
        """Add an option to parser in two variants.
 | 
			
		||||
 | 
			
		||||
        :param opt: option name (with underscores)
 | 
			
		||||
        """
 | 
			
		||||
        dashed_opt = opt.replace("_", "-")
 | 
			
		||||
        env_var = "OS_%s" % opt.upper()
 | 
			
		||||
        arg_default = os.environ.get(env_var, "")
 | 
			
		||||
        arg_help = "Defaults to env[%s]." % env_var
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            "--os-%s" % dashed_opt,
 | 
			
		||||
            metavar="<%s>" % dashed_opt,
 | 
			
		||||
            default=arg_default,
 | 
			
		||||
            help=arg_help)
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            "--os_%s" % opt,
 | 
			
		||||
            metavar="<%s>" % dashed_opt,
 | 
			
		||||
            help=argparse.SUPPRESS)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_opts(cls, parser):
 | 
			
		||||
        """Populate the parser with the options for this plugin."""
 | 
			
		||||
        for opt in cls.opt_names:
 | 
			
		||||
            # use `BaseAuthPlugin.common_opt_names` since it is never
 | 
			
		||||
            # changed in child classes
 | 
			
		||||
            if opt not in BaseAuthPlugin.common_opt_names:
 | 
			
		||||
                cls._parser_add_opt(parser, opt)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_common_opts(cls, parser):
 | 
			
		||||
        """Add options that are common for several plugins."""
 | 
			
		||||
        for opt in cls.common_opt_names:
 | 
			
		||||
            cls._parser_add_opt(parser, opt)
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_opt(opt_name, args):
 | 
			
		||||
        """Return option name and value.
 | 
			
		||||
 | 
			
		||||
        :param opt_name: name of the option, e.g., "username"
 | 
			
		||||
        :param args: parsed arguments
 | 
			
		||||
        """
 | 
			
		||||
        return (opt_name, getattr(args, "os_%s" % opt_name, None))
 | 
			
		||||
 | 
			
		||||
    def parse_opts(self, args):
 | 
			
		||||
        """Parse the actual auth-system options if any.
 | 
			
		||||
 | 
			
		||||
        This method is expected to populate the attribute `self.opts` with a
 | 
			
		||||
        dict containing the options and values needed to make authentication.
 | 
			
		||||
        """
 | 
			
		||||
        self.opts.update(dict(self.get_opt(opt_name, args)
 | 
			
		||||
                              for opt_name in self.opt_names))
 | 
			
		||||
 | 
			
		||||
    def authenticate(self, http_client):
 | 
			
		||||
        """Authenticate using plugin defined method.
 | 
			
		||||
 | 
			
		||||
        The method usually analyses `self.opts` and performs
 | 
			
		||||
        a request to authentication server.
 | 
			
		||||
 | 
			
		||||
        :param http_client: client object that needs authentication
 | 
			
		||||
        :type http_client: HTTPClient
 | 
			
		||||
        :raises: AuthorizationFailure
 | 
			
		||||
        """
 | 
			
		||||
        self.sufficient_options()
 | 
			
		||||
        self._do_authenticate(http_client)
 | 
			
		||||
 | 
			
		||||
    @abc.abstractmethod
 | 
			
		||||
    def _do_authenticate(self, http_client):
 | 
			
		||||
        """Protected method for authentication."""
 | 
			
		||||
 | 
			
		||||
    def sufficient_options(self):
 | 
			
		||||
        """Check if all required options are present.
 | 
			
		||||
 | 
			
		||||
        :raises: AuthPluginOptionsMissing
 | 
			
		||||
        """
 | 
			
		||||
        missing = [opt
 | 
			
		||||
                   for opt in self.opt_names
 | 
			
		||||
                   if not self.opts.get(opt)]
 | 
			
		||||
        if missing:
 | 
			
		||||
            raise exceptions.AuthPluginOptionsMissing(missing)
 | 
			
		||||
 | 
			
		||||
    @abc.abstractmethod
 | 
			
		||||
    def token_and_endpoint(self, endpoint_type, service_type):
 | 
			
		||||
        """Return token and endpoint.
 | 
			
		||||
 | 
			
		||||
        :param service_type: Service type of the endpoint
 | 
			
		||||
        :type service_type: string
 | 
			
		||||
        :param endpoint_type: Type of endpoint.
 | 
			
		||||
                              Possible values: public or publicURL,
 | 
			
		||||
                              internal or internalURL,
 | 
			
		||||
                              admin or adminURL
 | 
			
		||||
        :type endpoint_type: string
 | 
			
		||||
        :returns: tuple of token and endpoint strings
 | 
			
		||||
        :raises: EndpointException
 | 
			
		||||
        """
 | 
			
		||||
							
								
								
									
										531
									
								
								magnumclient/common/apiclient/base.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										531
									
								
								magnumclient/common/apiclient/base.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,531 @@
 | 
			
		||||
# Copyright 2010 Jacob Kaplan-Moss
 | 
			
		||||
# Copyright 2011 OpenStack Foundation
 | 
			
		||||
# Copyright 2012 Grid Dynamics
 | 
			
		||||
# Copyright 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.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Base utilities to build API operation managers and objects on top of.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
########################################################################
 | 
			
		||||
#
 | 
			
		||||
# THIS MODULE IS DEPRECATED
 | 
			
		||||
#
 | 
			
		||||
# Please refer to
 | 
			
		||||
# https://etherpad.openstack.org/p/kilo-magnumclient-library-proposals for
 | 
			
		||||
# the discussion leading to this deprecation.
 | 
			
		||||
#
 | 
			
		||||
# We recommend checking out the python-openstacksdk project
 | 
			
		||||
# (https://launchpad.net/python-openstacksdk) instead.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# E1102: %s is not callable
 | 
			
		||||
# pylint: disable=E1102
 | 
			
		||||
 | 
			
		||||
import abc
 | 
			
		||||
import copy
 | 
			
		||||
 | 
			
		||||
from oslo_utils import strutils
 | 
			
		||||
import six
 | 
			
		||||
from six.moves.urllib import parse
 | 
			
		||||
 | 
			
		||||
from magnumclient.common.apiclient import exceptions
 | 
			
		||||
from magnumclient.i18n import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getid(obj):
 | 
			
		||||
    """Return id if argument is a Resource.
 | 
			
		||||
 | 
			
		||||
    Abstracts the common pattern of allowing both an object or an object's ID
 | 
			
		||||
    (UUID) as a parameter when dealing with relationships.
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        if obj.uuid:
 | 
			
		||||
            return obj.uuid
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        pass
 | 
			
		||||
    try:
 | 
			
		||||
        return obj.id
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# TODO(aababilov): call run_hooks() in HookableMixin's child classes
 | 
			
		||||
class HookableMixin(object):
 | 
			
		||||
    """Mixin so classes can register and run hooks."""
 | 
			
		||||
    _hooks_map = {}
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def add_hook(cls, hook_type, hook_func):
 | 
			
		||||
        """Add a new hook of specified type.
 | 
			
		||||
 | 
			
		||||
        :param cls: class that registers hooks
 | 
			
		||||
        :param hook_type: hook type, e.g., '__pre_parse_args__'
 | 
			
		||||
        :param hook_func: hook function
 | 
			
		||||
        """
 | 
			
		||||
        if hook_type not in cls._hooks_map:
 | 
			
		||||
            cls._hooks_map[hook_type] = []
 | 
			
		||||
 | 
			
		||||
        cls._hooks_map[hook_type].append(hook_func)
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def run_hooks(cls, hook_type, *args, **kwargs):
 | 
			
		||||
        """Run all hooks of specified type.
 | 
			
		||||
 | 
			
		||||
        :param cls: class that registers hooks
 | 
			
		||||
        :param hook_type: hook type, e.g., '__pre_parse_args__'
 | 
			
		||||
        :param args: args to be passed to every hook function
 | 
			
		||||
        :param kwargs: kwargs to be passed to every hook function
 | 
			
		||||
        """
 | 
			
		||||
        hook_funcs = cls._hooks_map.get(hook_type) or []
 | 
			
		||||
        for hook_func in hook_funcs:
 | 
			
		||||
            hook_func(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseManager(HookableMixin):
 | 
			
		||||
    """Basic manager type providing common operations.
 | 
			
		||||
 | 
			
		||||
    Managers interact with a particular type of API (servers, flavors, images,
 | 
			
		||||
    etc.) and provide CRUD operations for them.
 | 
			
		||||
    """
 | 
			
		||||
    resource_class = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, client):
 | 
			
		||||
        """Initializes BaseManager with `client`.
 | 
			
		||||
 | 
			
		||||
        :param client: instance of BaseClient descendant for HTTP requests
 | 
			
		||||
        """
 | 
			
		||||
        super(BaseManager, self).__init__()
 | 
			
		||||
        self.client = client
 | 
			
		||||
 | 
			
		||||
    def _list(self, url, response_key=None, obj_class=None, json=None):
 | 
			
		||||
        """List the collection.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'servers'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        :param obj_class: class for constructing the returned objects
 | 
			
		||||
            (self.resource_class will be used by default)
 | 
			
		||||
        :param json: data that will be encoded as JSON and passed in POST
 | 
			
		||||
            request (GET will be sent by default)
 | 
			
		||||
        """
 | 
			
		||||
        if json:
 | 
			
		||||
            body = self.client.post(url, json=json).json()
 | 
			
		||||
        else:
 | 
			
		||||
            body = self.client.get(url).json()
 | 
			
		||||
 | 
			
		||||
        if obj_class is None:
 | 
			
		||||
            obj_class = self.resource_class
 | 
			
		||||
 | 
			
		||||
        data = body[response_key] if response_key is not None else body
 | 
			
		||||
        # NOTE(ja): keystone returns values as list as {'values': [ ... ]}
 | 
			
		||||
        #           unlike other services which just return the list...
 | 
			
		||||
        try:
 | 
			
		||||
            data = data['values']
 | 
			
		||||
        except (KeyError, TypeError):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
        return [obj_class(self, res, loaded=True) for res in data if res]
 | 
			
		||||
 | 
			
		||||
    def _get(self, url, response_key=None):
 | 
			
		||||
        """Get an object from collection.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'server'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        """
 | 
			
		||||
        body = self.client.get(url).json()
 | 
			
		||||
        data = body[response_key] if response_key is not None else body
 | 
			
		||||
        return self.resource_class(self, data, loaded=True)
 | 
			
		||||
 | 
			
		||||
    def _head(self, url):
 | 
			
		||||
        """Retrieve request headers for an object.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        """
 | 
			
		||||
        resp = self.client.head(url)
 | 
			
		||||
        return resp.status_code == 204
 | 
			
		||||
 | 
			
		||||
    def _post(self, url, json, response_key=None, return_raw=False):
 | 
			
		||||
        """Create an object.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param json: data that will be encoded as JSON and passed in POST
 | 
			
		||||
            request (GET will be sent by default)
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'server'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        :param return_raw: flag to force returning raw JSON instead of
 | 
			
		||||
            Python object of self.resource_class
 | 
			
		||||
        """
 | 
			
		||||
        body = self.client.post(url, json=json).json()
 | 
			
		||||
        data = body[response_key] if response_key is not None else body
 | 
			
		||||
        if return_raw:
 | 
			
		||||
            return data
 | 
			
		||||
        return self.resource_class(self, data)
 | 
			
		||||
 | 
			
		||||
    def _put(self, url, json=None, response_key=None):
 | 
			
		||||
        """Update an object with PUT method.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param json: data that will be encoded as JSON and passed in POST
 | 
			
		||||
            request (GET will be sent by default)
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'servers'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        """
 | 
			
		||||
        resp = self.client.put(url, json=json)
 | 
			
		||||
        # PUT requests may not return a body
 | 
			
		||||
        if resp.content:
 | 
			
		||||
            body = resp.json()
 | 
			
		||||
            if response_key is not None:
 | 
			
		||||
                return self.resource_class(self, body[response_key])
 | 
			
		||||
            else:
 | 
			
		||||
                return self.resource_class(self, body)
 | 
			
		||||
 | 
			
		||||
    def _patch(self, url, json=None, response_key=None):
 | 
			
		||||
        """Update an object with PATCH method.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers'
 | 
			
		||||
        :param json: data that will be encoded as JSON and passed in POST
 | 
			
		||||
            request (GET will be sent by default)
 | 
			
		||||
        :param response_key: the key to be looked up in response dictionary,
 | 
			
		||||
            e.g., 'servers'. If response_key is None - all response body
 | 
			
		||||
            will be used.
 | 
			
		||||
        """
 | 
			
		||||
        body = self.client.patch(url, json=json).json()
 | 
			
		||||
        if response_key is not None:
 | 
			
		||||
            return self.resource_class(self, body[response_key])
 | 
			
		||||
        else:
 | 
			
		||||
            return self.resource_class(self, body)
 | 
			
		||||
 | 
			
		||||
    def _delete(self, url):
 | 
			
		||||
        """Delete an object.
 | 
			
		||||
 | 
			
		||||
        :param url: a partial URL, e.g., '/servers/my-server'
 | 
			
		||||
        """
 | 
			
		||||
        return self.client.delete(url)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@six.add_metaclass(abc.ABCMeta)
 | 
			
		||||
class ManagerWithFind(BaseManager):
 | 
			
		||||
    """Manager with additional `find()`/`findall()` methods."""
 | 
			
		||||
 | 
			
		||||
    @abc.abstractmethod
 | 
			
		||||
    def list(self):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def find(self, **kwargs):
 | 
			
		||||
        """Find a single item with attributes matching ``**kwargs``.
 | 
			
		||||
 | 
			
		||||
        This isn't very efficient: it loads the entire list then filters on
 | 
			
		||||
        the Python side.
 | 
			
		||||
        """
 | 
			
		||||
        matches = self.findall(**kwargs)
 | 
			
		||||
        num_matches = len(matches)
 | 
			
		||||
        if num_matches == 0:
 | 
			
		||||
            msg = _("No %(name)s matching %(args)s.") % {
 | 
			
		||||
                'name': self.resource_class.__name__,
 | 
			
		||||
                'args': kwargs
 | 
			
		||||
            }
 | 
			
		||||
            raise exceptions.NotFound(msg)
 | 
			
		||||
        elif num_matches > 1:
 | 
			
		||||
            raise exceptions.NoUniqueMatch()
 | 
			
		||||
        else:
 | 
			
		||||
            return matches[0]
 | 
			
		||||
 | 
			
		||||
    def findall(self, **kwargs):
 | 
			
		||||
        """Find all items with attributes matching ``**kwargs``.
 | 
			
		||||
 | 
			
		||||
        This isn't very efficient: it loads the entire list then filters on
 | 
			
		||||
        the Python side.
 | 
			
		||||
        """
 | 
			
		||||
        found = []
 | 
			
		||||
        searches = kwargs.items()
 | 
			
		||||
 | 
			
		||||
        for obj in self.list():
 | 
			
		||||
            try:
 | 
			
		||||
                if all(getattr(obj, attr) == value
 | 
			
		||||
                       for (attr, value) in searches):
 | 
			
		||||
                    found.append(obj)
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
        return found
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CrudManager(BaseManager):
 | 
			
		||||
    """Base manager class for manipulating entities.
 | 
			
		||||
 | 
			
		||||
    Children of this class are expected to define a `collection_key` and `key`.
 | 
			
		||||
 | 
			
		||||
    - `collection_key`: Usually a plural noun by convention (e.g. `entities`);
 | 
			
		||||
      used to refer collections in both URL's (e.g.  `/v3/entities`) and JSON
 | 
			
		||||
      objects containing a list of member resources (e.g. `{'entities': [{},
 | 
			
		||||
      {}, {}]}`).
 | 
			
		||||
    - `key`: Usually a singular noun by convention (e.g. `entity`); used to
 | 
			
		||||
      refer to an individual member of the collection.
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    collection_key = None
 | 
			
		||||
    key = None
 | 
			
		||||
 | 
			
		||||
    def build_url(self, base_url=None, **kwargs):
 | 
			
		||||
        """Builds a resource URL for the given kwargs.
 | 
			
		||||
 | 
			
		||||
        Given an example collection where `collection_key = 'entities'` and
 | 
			
		||||
        `key = 'entity'`, the following URL's could be generated.
 | 
			
		||||
 | 
			
		||||
        By default, the URL will represent a collection of entities, e.g.::
 | 
			
		||||
 | 
			
		||||
            /entities
 | 
			
		||||
 | 
			
		||||
        If kwargs contains an `entity_id`, then the URL will represent a
 | 
			
		||||
        specific member, e.g.::
 | 
			
		||||
 | 
			
		||||
            /entities/{entity_id}
 | 
			
		||||
 | 
			
		||||
        :param base_url: if provided, the generated URL will be appended to it
 | 
			
		||||
        """
 | 
			
		||||
        url = base_url if base_url is not None else ''
 | 
			
		||||
 | 
			
		||||
        url += '/%s' % self.collection_key
 | 
			
		||||
 | 
			
		||||
        # do we have a specific entity?
 | 
			
		||||
        entity_id = kwargs.get('%s_id' % self.key)
 | 
			
		||||
        if entity_id is not None:
 | 
			
		||||
            url += '/%s' % entity_id
 | 
			
		||||
 | 
			
		||||
        return url
 | 
			
		||||
 | 
			
		||||
    def _filter_kwargs(self, kwargs):
 | 
			
		||||
        """Drop null values and handle ids."""
 | 
			
		||||
        for key, ref in kwargs.copy().items():
 | 
			
		||||
            if ref is None:
 | 
			
		||||
                kwargs.pop(key)
 | 
			
		||||
            else:
 | 
			
		||||
                if isinstance(ref, Resource):
 | 
			
		||||
                    kwargs.pop(key)
 | 
			
		||||
                    kwargs['%s_id' % key] = getid(ref)
 | 
			
		||||
        return kwargs
 | 
			
		||||
 | 
			
		||||
    def create(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
        return self._post(
 | 
			
		||||
            self.build_url(**kwargs),
 | 
			
		||||
            {self.key: kwargs},
 | 
			
		||||
            self.key)
 | 
			
		||||
 | 
			
		||||
    def get(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
        return self._get(
 | 
			
		||||
            self.build_url(**kwargs),
 | 
			
		||||
            self.key)
 | 
			
		||||
 | 
			
		||||
    def head(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
        return self._head(self.build_url(**kwargs))
 | 
			
		||||
 | 
			
		||||
    def list(self, base_url=None, **kwargs):
 | 
			
		||||
        """List the collection.
 | 
			
		||||
 | 
			
		||||
        :param base_url: if provided, the generated URL will be appended to it
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
 | 
			
		||||
        return self._list(
 | 
			
		||||
            '%(base_url)s%(query)s' % {
 | 
			
		||||
                'base_url': self.build_url(base_url=base_url, **kwargs),
 | 
			
		||||
                'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
 | 
			
		||||
            },
 | 
			
		||||
            self.collection_key)
 | 
			
		||||
 | 
			
		||||
    def put(self, base_url=None, **kwargs):
 | 
			
		||||
        """Update an element.
 | 
			
		||||
 | 
			
		||||
        :param base_url: if provided, the generated URL will be appended to it
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
 | 
			
		||||
        return self._put(self.build_url(base_url=base_url, **kwargs))
 | 
			
		||||
 | 
			
		||||
    def update(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
        params = kwargs.copy()
 | 
			
		||||
        params.pop('%s_id' % self.key)
 | 
			
		||||
 | 
			
		||||
        return self._patch(
 | 
			
		||||
            self.build_url(**kwargs),
 | 
			
		||||
            {self.key: params},
 | 
			
		||||
            self.key)
 | 
			
		||||
 | 
			
		||||
    def delete(self, **kwargs):
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
 | 
			
		||||
        return self._delete(
 | 
			
		||||
            self.build_url(**kwargs))
 | 
			
		||||
 | 
			
		||||
    def find(self, base_url=None, **kwargs):
 | 
			
		||||
        """Find a single item with attributes matching ``**kwargs``.
 | 
			
		||||
 | 
			
		||||
        :param base_url: if provided, the generated URL will be appended to it
 | 
			
		||||
        """
 | 
			
		||||
        kwargs = self._filter_kwargs(kwargs)
 | 
			
		||||
 | 
			
		||||
        rl = self._list(
 | 
			
		||||
            '%(base_url)s%(query)s' % {
 | 
			
		||||
                'base_url': self.build_url(base_url=base_url, **kwargs),
 | 
			
		||||
                'query': '?%s' % parse.urlencode(kwargs) if kwargs else '',
 | 
			
		||||
            },
 | 
			
		||||
            self.collection_key)
 | 
			
		||||
        num = len(rl)
 | 
			
		||||
 | 
			
		||||
        if num == 0:
 | 
			
		||||
            msg = _("No %(name)s matching %(args)s.") % {
 | 
			
		||||
                'name': self.resource_class.__name__,
 | 
			
		||||
                'args': kwargs
 | 
			
		||||
            }
 | 
			
		||||
            raise exceptions.NotFound(msg)
 | 
			
		||||
        elif num > 1:
 | 
			
		||||
            raise exceptions.NoUniqueMatch
 | 
			
		||||
        else:
 | 
			
		||||
            return rl[0]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Extension(HookableMixin):
 | 
			
		||||
    """Extension descriptor."""
 | 
			
		||||
 | 
			
		||||
    SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__')
 | 
			
		||||
    manager_class = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, name, module):
 | 
			
		||||
        super(Extension, self).__init__()
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.module = module
 | 
			
		||||
        self._parse_extension_module()
 | 
			
		||||
 | 
			
		||||
    def _parse_extension_module(self):
 | 
			
		||||
        self.manager_class = None
 | 
			
		||||
        for attr_name, attr_value in self.module.__dict__.items():
 | 
			
		||||
            if attr_name in self.SUPPORTED_HOOKS:
 | 
			
		||||
                self.add_hook(attr_name, attr_value)
 | 
			
		||||
            else:
 | 
			
		||||
                try:
 | 
			
		||||
                    if issubclass(attr_value, BaseManager):
 | 
			
		||||
                        self.manager_class = attr_value
 | 
			
		||||
                except TypeError:
 | 
			
		||||
                    pass
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        return "<Extension '%s'>" % self.name
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Resource(object):
 | 
			
		||||
    """Base class for OpenStack resources (tenant, user, etc.).
 | 
			
		||||
 | 
			
		||||
    This is pretty much just a bag for attributes.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    HUMAN_ID = False
 | 
			
		||||
    NAME_ATTR = 'name'
 | 
			
		||||
 | 
			
		||||
    def __init__(self, manager, info, loaded=False):
 | 
			
		||||
        """Populate and bind to a manager.
 | 
			
		||||
 | 
			
		||||
        :param manager: BaseManager object
 | 
			
		||||
        :param info: dictionary representing resource attributes
 | 
			
		||||
        :param loaded: prevent lazy-loading if set to True
 | 
			
		||||
        """
 | 
			
		||||
        self.manager = manager
 | 
			
		||||
        self._info = info
 | 
			
		||||
        self._add_details(info)
 | 
			
		||||
        self._loaded = loaded
 | 
			
		||||
 | 
			
		||||
    def __repr__(self):
 | 
			
		||||
        reprkeys = sorted(k
 | 
			
		||||
                          for k in self.__dict__.keys()
 | 
			
		||||
                          if k[0] != '_' and k != 'manager')
 | 
			
		||||
        info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys)
 | 
			
		||||
        return "<%s %s>" % (self.__class__.__name__, info)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def human_id(self):
 | 
			
		||||
        """Human-readable ID which can be used for bash completion."""
 | 
			
		||||
        if self.HUMAN_ID:
 | 
			
		||||
            name = getattr(self, self.NAME_ATTR, None)
 | 
			
		||||
            if name is not None:
 | 
			
		||||
                return strutils.to_slug(name)
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def _add_details(self, info):
 | 
			
		||||
        for (k, v) in info.items():
 | 
			
		||||
            try:
 | 
			
		||||
                setattr(self, k, v)
 | 
			
		||||
                self._info[k] = v
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                # In this case we already defined the attribute on the class
 | 
			
		||||
                pass
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, k):
 | 
			
		||||
        if k not in self.__dict__:
 | 
			
		||||
            # NOTE(bcwaldon): disallow lazy-loading if already loaded once
 | 
			
		||||
            if not self.is_loaded():
 | 
			
		||||
                self.get()
 | 
			
		||||
                return self.__getattr__(k)
 | 
			
		||||
 | 
			
		||||
            raise AttributeError(k)
 | 
			
		||||
        else:
 | 
			
		||||
            return self.__dict__[k]
 | 
			
		||||
 | 
			
		||||
    def get(self):
 | 
			
		||||
        """Support for lazy loading details.
 | 
			
		||||
 | 
			
		||||
        Some clients, such as novaclient have the option to lazy load the
 | 
			
		||||
        details, details which can be loaded with this function.
 | 
			
		||||
        """
 | 
			
		||||
        # set_loaded() first ... so if we have to bail, we know we tried.
 | 
			
		||||
        self.set_loaded(True)
 | 
			
		||||
        if not hasattr(self.manager, 'get'):
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        new = self.manager.get(self.id)
 | 
			
		||||
        if new:
 | 
			
		||||
            self._add_details(new._info)
 | 
			
		||||
            self._add_details(
 | 
			
		||||
                {'x_request_id': self.manager.client.last_request_id})
 | 
			
		||||
 | 
			
		||||
    def __eq__(self, other):
 | 
			
		||||
        if not isinstance(other, Resource):
 | 
			
		||||
            return NotImplemented
 | 
			
		||||
        # two resources of different types are not equal
 | 
			
		||||
        if not isinstance(other, self.__class__):
 | 
			
		||||
            return False
 | 
			
		||||
        if hasattr(self, 'id') and hasattr(other, 'id'):
 | 
			
		||||
            return self.id == other.id
 | 
			
		||||
        return self._info == other._info
 | 
			
		||||
 | 
			
		||||
    def is_loaded(self):
 | 
			
		||||
        return self._loaded
 | 
			
		||||
 | 
			
		||||
    def set_loaded(self, val):
 | 
			
		||||
        self._loaded = val
 | 
			
		||||
 | 
			
		||||
    def to_dict(self):
 | 
			
		||||
        return copy.deepcopy(self._info)
 | 
			
		||||
							
								
								
									
										477
									
								
								magnumclient/common/apiclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										477
									
								
								magnumclient/common/apiclient/exceptions.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,477 @@
 | 
			
		||||
# Copyright 2010 Jacob Kaplan-Moss
 | 
			
		||||
# Copyright 2011 Nebula, Inc.
 | 
			
		||||
# Copyright 2013 Alessio Ababilov
 | 
			
		||||
# Copyright 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.
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
Exception definitions.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
########################################################################
 | 
			
		||||
#
 | 
			
		||||
# THIS MODULE IS DEPRECATED
 | 
			
		||||
#
 | 
			
		||||
# Please refer to
 | 
			
		||||
# https://etherpad.openstack.org/p/kilo-magnumclient-library-proposals for
 | 
			
		||||
# the discussion leading to this deprecation.
 | 
			
		||||
#
 | 
			
		||||
# We recommend checking out the python-openstacksdk project
 | 
			
		||||
# (https://launchpad.net/python-openstacksdk) instead.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
 | 
			
		||||
import inspect
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from magnumclient.i18n import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ClientException(Exception):
 | 
			
		||||
    """The base exception class for all exceptions this library raises."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ValidationError(ClientException):
 | 
			
		||||
    """Error in validation on API client side."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsupportedVersion(ClientException):
 | 
			
		||||
    """User is trying to use an unsupported version of the API."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class CommandError(ClientException):
 | 
			
		||||
    """Error in CLI tool."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthorizationFailure(ClientException):
 | 
			
		||||
    """Cannot authorize API client."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConnectionError(ClientException):
 | 
			
		||||
    """Cannot connect to API service."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ConnectionRefused(ConnectionError):
 | 
			
		||||
    """Connection refused while trying to connect to API service."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthPluginOptionsMissing(AuthorizationFailure):
 | 
			
		||||
    """Auth plugin misses some options."""
 | 
			
		||||
    def __init__(self, opt_names):
 | 
			
		||||
        super(AuthPluginOptionsMissing, self).__init__(
 | 
			
		||||
            _("Authentication failed. Missing options: %s") %
 | 
			
		||||
            ", ".join(opt_names))
 | 
			
		||||
        self.opt_names = opt_names
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AuthSystemNotFound(AuthorizationFailure):
 | 
			
		||||
    """User has specified an AuthSystem that is not installed."""
 | 
			
		||||
    def __init__(self, auth_system):
 | 
			
		||||
        super(AuthSystemNotFound, self).__init__(
 | 
			
		||||
            _("AuthSystemNotFound: %r") % auth_system)
 | 
			
		||||
        self.auth_system = auth_system
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NoUniqueMatch(ClientException):
 | 
			
		||||
    """Multiple entities found instead of one."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EndpointException(ClientException):
 | 
			
		||||
    """Something is rotten in Service Catalog."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EndpointNotFound(EndpointException):
 | 
			
		||||
    """Could not find requested endpoint in Service Catalog."""
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class AmbiguousEndpoints(EndpointException):
 | 
			
		||||
    """Found more than one matching endpoint in Service Catalog."""
 | 
			
		||||
    def __init__(self, endpoints=None):
 | 
			
		||||
        super(AmbiguousEndpoints, self).__init__(
 | 
			
		||||
            _("AmbiguousEndpoints: %r") % endpoints)
 | 
			
		||||
        self.endpoints = endpoints
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HttpError(ClientException):
 | 
			
		||||
    """The base exception class for all HTTP exceptions."""
 | 
			
		||||
    http_status = 0
 | 
			
		||||
    message = _("HTTP Error")
 | 
			
		||||
 | 
			
		||||
    def __init__(self, message=None, details=None,
 | 
			
		||||
                 response=None, request_id=None,
 | 
			
		||||
                 url=None, method=None, http_status=None):
 | 
			
		||||
        self.http_status = http_status or self.http_status
 | 
			
		||||
        self.message = message or self.message
 | 
			
		||||
        self.details = details
 | 
			
		||||
        self.request_id = request_id
 | 
			
		||||
        self.response = response
 | 
			
		||||
        self.url = url
 | 
			
		||||
        self.method = method
 | 
			
		||||
        formatted_string = "%s (HTTP %s)" % (self.message, self.http_status)
 | 
			
		||||
        if request_id:
 | 
			
		||||
            formatted_string += " (Request-ID: %s)" % request_id
 | 
			
		||||
        super(HttpError, self).__init__(formatted_string)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPRedirection(HttpError):
 | 
			
		||||
    """HTTP Redirection."""
 | 
			
		||||
    message = _("HTTP Redirection")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HTTPClientError(HttpError):
 | 
			
		||||
    """Client-side HTTP error.
 | 
			
		||||
 | 
			
		||||
    Exception for cases in which the client seems to have erred.
 | 
			
		||||
    """
 | 
			
		||||
    message = _("HTTP Client Error")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HttpServerError(HttpError):
 | 
			
		||||
    """Server-side HTTP error.
 | 
			
		||||
 | 
			
		||||
    Exception for cases in which the server is aware that it has
 | 
			
		||||
    erred or is incapable of performing the request.
 | 
			
		||||
    """
 | 
			
		||||
    message = _("HTTP Server Error")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MultipleChoices(HTTPRedirection):
 | 
			
		||||
    """HTTP 300 - Multiple Choices.
 | 
			
		||||
 | 
			
		||||
    Indicates multiple options for the resource that the client may follow.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    http_status = 300
 | 
			
		||||
    message = _("Multiple Choices")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BadRequest(HTTPClientError):
 | 
			
		||||
    """HTTP 400 - Bad Request.
 | 
			
		||||
 | 
			
		||||
    The request cannot be fulfilled due to bad syntax.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 400
 | 
			
		||||
    message = _("Bad Request")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Unauthorized(HTTPClientError):
 | 
			
		||||
    """HTTP 401 - Unauthorized.
 | 
			
		||||
 | 
			
		||||
    Similar to 403 Forbidden, but specifically for use when authentication
 | 
			
		||||
    is required and has failed or has not yet been provided.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 401
 | 
			
		||||
    message = _("Unauthorized")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PaymentRequired(HTTPClientError):
 | 
			
		||||
    """HTTP 402 - Payment Required.
 | 
			
		||||
 | 
			
		||||
    Reserved for future use.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 402
 | 
			
		||||
    message = _("Payment Required")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Forbidden(HTTPClientError):
 | 
			
		||||
    """HTTP 403 - Forbidden.
 | 
			
		||||
 | 
			
		||||
    The request was a valid request, but the server is refusing to respond
 | 
			
		||||
    to it.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 403
 | 
			
		||||
    message = _("Forbidden")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotFound(HTTPClientError):
 | 
			
		||||
    """HTTP 404 - Not Found.
 | 
			
		||||
 | 
			
		||||
    The requested resource could not be found but may be available again
 | 
			
		||||
    in the future.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 404
 | 
			
		||||
    message = _("Not Found")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MethodNotAllowed(HTTPClientError):
 | 
			
		||||
    """HTTP 405 - Method Not Allowed.
 | 
			
		||||
 | 
			
		||||
    A request was made of a resource using a request method not supported
 | 
			
		||||
    by that resource.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 405
 | 
			
		||||
    message = _("Method Not Allowed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class NotAcceptable(HTTPClientError):
 | 
			
		||||
    """HTTP 406 - Not Acceptable.
 | 
			
		||||
 | 
			
		||||
    The requested resource is only capable of generating content not
 | 
			
		||||
    acceptable according to the Accept headers sent in the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 406
 | 
			
		||||
    message = _("Not Acceptable")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ProxyAuthenticationRequired(HTTPClientError):
 | 
			
		||||
    """HTTP 407 - Proxy Authentication Required.
 | 
			
		||||
 | 
			
		||||
    The client must first authenticate itself with the proxy.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 407
 | 
			
		||||
    message = _("Proxy Authentication Required")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestTimeout(HTTPClientError):
 | 
			
		||||
    """HTTP 408 - Request Timeout.
 | 
			
		||||
 | 
			
		||||
    The server timed out waiting for the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 408
 | 
			
		||||
    message = _("Request Timeout")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Conflict(HTTPClientError):
 | 
			
		||||
    """HTTP 409 - Conflict.
 | 
			
		||||
 | 
			
		||||
    Indicates that the request could not be processed because of conflict
 | 
			
		||||
    in the request, such as an edit conflict.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 409
 | 
			
		||||
    message = _("Conflict")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Gone(HTTPClientError):
 | 
			
		||||
    """HTTP 410 - Gone.
 | 
			
		||||
 | 
			
		||||
    Indicates that the resource requested is no longer available and will
 | 
			
		||||
    not be available again.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 410
 | 
			
		||||
    message = _("Gone")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LengthRequired(HTTPClientError):
 | 
			
		||||
    """HTTP 411 - Length Required.
 | 
			
		||||
 | 
			
		||||
    The request did not specify the length of its content, which is
 | 
			
		||||
    required by the requested resource.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 411
 | 
			
		||||
    message = _("Length Required")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PreconditionFailed(HTTPClientError):
 | 
			
		||||
    """HTTP 412 - Precondition Failed.
 | 
			
		||||
 | 
			
		||||
    The server does not meet one of the preconditions that the requester
 | 
			
		||||
    put on the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 412
 | 
			
		||||
    message = _("Precondition Failed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestEntityTooLarge(HTTPClientError):
 | 
			
		||||
    """HTTP 413 - Request Entity Too Large.
 | 
			
		||||
 | 
			
		||||
    The request is larger than the server is willing or able to process.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 413
 | 
			
		||||
    message = _("Request Entity Too Large")
 | 
			
		||||
 | 
			
		||||
    def __init__(self, *args, **kwargs):
 | 
			
		||||
        try:
 | 
			
		||||
            self.retry_after = int(kwargs.pop('retry_after'))
 | 
			
		||||
        except (KeyError, ValueError):
 | 
			
		||||
            self.retry_after = 0
 | 
			
		||||
 | 
			
		||||
        super(RequestEntityTooLarge, self).__init__(*args, **kwargs)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestUriTooLong(HTTPClientError):
 | 
			
		||||
    """HTTP 414 - Request-URI Too Long.
 | 
			
		||||
 | 
			
		||||
    The URI provided was too long for the server to process.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 414
 | 
			
		||||
    message = _("Request-URI Too Long")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnsupportedMediaType(HTTPClientError):
 | 
			
		||||
    """HTTP 415 - Unsupported Media Type.
 | 
			
		||||
 | 
			
		||||
    The request entity has a media type which the server or resource does
 | 
			
		||||
    not support.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 415
 | 
			
		||||
    message = _("Unsupported Media Type")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RequestedRangeNotSatisfiable(HTTPClientError):
 | 
			
		||||
    """HTTP 416 - Requested Range Not Satisfiable.
 | 
			
		||||
 | 
			
		||||
    The client has asked for a portion of the file, but the server cannot
 | 
			
		||||
    supply that portion.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 416
 | 
			
		||||
    message = _("Requested Range Not Satisfiable")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ExpectationFailed(HTTPClientError):
 | 
			
		||||
    """HTTP 417 - Expectation Failed.
 | 
			
		||||
 | 
			
		||||
    The server cannot meet the requirements of the Expect request-header field.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 417
 | 
			
		||||
    message = _("Expectation Failed")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class UnprocessableEntity(HTTPClientError):
 | 
			
		||||
    """HTTP 422 - Unprocessable Entity.
 | 
			
		||||
 | 
			
		||||
    The request was well-formed but was unable to be followed due to semantic
 | 
			
		||||
    errors.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 422
 | 
			
		||||
    message = _("Unprocessable Entity")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InternalServerError(HttpServerError):
 | 
			
		||||
    """HTTP 500 - Internal Server Error.
 | 
			
		||||
 | 
			
		||||
    A generic error message, given when no more specific message is suitable.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 500
 | 
			
		||||
    message = _("Internal Server Error")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NotImplemented is a python keyword.
 | 
			
		||||
class HttpNotImplemented(HttpServerError):
 | 
			
		||||
    """HTTP 501 - Not Implemented.
 | 
			
		||||
 | 
			
		||||
    The server either does not recognize the request method, or it lacks
 | 
			
		||||
    the ability to fulfill the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 501
 | 
			
		||||
    message = _("Not Implemented")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BadGateway(HttpServerError):
 | 
			
		||||
    """HTTP 502 - Bad Gateway.
 | 
			
		||||
 | 
			
		||||
    The server was acting as a gateway or proxy and received an invalid
 | 
			
		||||
    response from the upstream server.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 502
 | 
			
		||||
    message = _("Bad Gateway")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ServiceUnavailable(HttpServerError):
 | 
			
		||||
    """HTTP 503 - Service Unavailable.
 | 
			
		||||
 | 
			
		||||
    The server is currently unavailable.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 503
 | 
			
		||||
    message = _("Service Unavailable")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GatewayTimeout(HttpServerError):
 | 
			
		||||
    """HTTP 504 - Gateway Timeout.
 | 
			
		||||
 | 
			
		||||
    The server was acting as a gateway or proxy and did not receive a timely
 | 
			
		||||
    response from the upstream server.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 504
 | 
			
		||||
    message = _("Gateway Timeout")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class HttpVersionNotSupported(HttpServerError):
 | 
			
		||||
    """HTTP 505 - HttpVersion Not Supported.
 | 
			
		||||
 | 
			
		||||
    The server does not support the HTTP protocol version used in the request.
 | 
			
		||||
    """
 | 
			
		||||
    http_status = 505
 | 
			
		||||
    message = _("HTTP Version Not Supported")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# _code_map contains all the classes that have http_status attribute.
 | 
			
		||||
_code_map = dict(
 | 
			
		||||
    (getattr(obj, 'http_status', None), obj)
 | 
			
		||||
    for name, obj in vars(sys.modules[__name__]).items()
 | 
			
		||||
    if inspect.isclass(obj) and getattr(obj, 'http_status', False)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def from_response(response, method, url):
 | 
			
		||||
    """Returns an instance of :class:`HttpError` or subclass based on response.
 | 
			
		||||
 | 
			
		||||
    :param response: instance of `requests.Response` class
 | 
			
		||||
    :param method: HTTP method used for request
 | 
			
		||||
    :param url: URL used for request
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    req_id = response.headers.get("x-openstack-request-id")
 | 
			
		||||
    # NOTE(hdd) true for older versions of nova and cinder
 | 
			
		||||
    if not req_id:
 | 
			
		||||
        req_id = response.headers.get("x-compute-request-id")
 | 
			
		||||
    kwargs = {
 | 
			
		||||
        "http_status": response.status_code,
 | 
			
		||||
        "response": response,
 | 
			
		||||
        "method": method,
 | 
			
		||||
        "url": url,
 | 
			
		||||
        "request_id": req_id,
 | 
			
		||||
    }
 | 
			
		||||
    if "retry-after" in response.headers:
 | 
			
		||||
        kwargs["retry_after"] = response.headers["retry-after"]
 | 
			
		||||
 | 
			
		||||
    content_type = response.headers.get("Content-Type", "")
 | 
			
		||||
    if content_type.startswith("application/json"):
 | 
			
		||||
        try:
 | 
			
		||||
            body = response.json()
 | 
			
		||||
        except ValueError:
 | 
			
		||||
            pass
 | 
			
		||||
        else:
 | 
			
		||||
            if isinstance(body, dict):
 | 
			
		||||
                error = body.get(list(body)[0])
 | 
			
		||||
                if isinstance(error, dict):
 | 
			
		||||
                    kwargs["message"] = (error.get("message") or
 | 
			
		||||
                                         error.get("faultstring"))
 | 
			
		||||
                    kwargs["details"] = (error.get("details") or
 | 
			
		||||
                                         six.text_type(body))
 | 
			
		||||
    elif content_type.startswith("text/"):
 | 
			
		||||
        kwargs["details"] = getattr(response, 'text', '')
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        cls = _code_map[response.status_code]
 | 
			
		||||
    except KeyError:
 | 
			
		||||
        if 500 <= response.status_code < 600:
 | 
			
		||||
            cls = HttpServerError
 | 
			
		||||
        elif 400 <= response.status_code < 500:
 | 
			
		||||
            cls = HTTPClientError
 | 
			
		||||
        else:
 | 
			
		||||
            cls = HttpError
 | 
			
		||||
    return cls(**kwargs)
 | 
			
		||||
@@ -23,7 +23,7 @@ import copy
 | 
			
		||||
 | 
			
		||||
import six.moves.urllib.parse as urlparse
 | 
			
		||||
 | 
			
		||||
from magnumclient.openstack.common.apiclient import base
 | 
			
		||||
from magnumclient.common.apiclient import base
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def getid(obj):
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										289
									
								
								magnumclient/common/cliutils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										289
									
								
								magnumclient/common/cliutils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,289 @@
 | 
			
		||||
# Copyright 2012 Red Hat, 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.
 | 
			
		||||
 | 
			
		||||
# W0603: Using the global statement
 | 
			
		||||
# W0621: Redefining name %s from outer scope
 | 
			
		||||
# pylint: disable=W0603,W0621
 | 
			
		||||
 | 
			
		||||
from __future__ import print_function
 | 
			
		||||
 | 
			
		||||
import getpass
 | 
			
		||||
import inspect
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import textwrap
 | 
			
		||||
 | 
			
		||||
from oslo_utils import encodeutils
 | 
			
		||||
from oslo_utils import strutils
 | 
			
		||||
import prettytable
 | 
			
		||||
import six
 | 
			
		||||
from six import moves
 | 
			
		||||
 | 
			
		||||
from magnumclient.i18n import _
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MissingArgs(Exception):
 | 
			
		||||
    """Supplied arguments are not sufficient for calling a function."""
 | 
			
		||||
    def __init__(self, missing):
 | 
			
		||||
        self.missing = missing
 | 
			
		||||
        msg = _("Missing arguments: %s") % ", ".join(missing)
 | 
			
		||||
        super(MissingArgs, self).__init__(msg)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_args(fn, *args, **kwargs):
 | 
			
		||||
    """Check that the supplied args are sufficient for calling a function.
 | 
			
		||||
 | 
			
		||||
    >>> validate_args(lambda a: None)
 | 
			
		||||
    Traceback (most recent call last):
 | 
			
		||||
        ...
 | 
			
		||||
    MissingArgs: Missing argument(s): a
 | 
			
		||||
    >>> validate_args(lambda a, b, c, d: None, 0, c=1)
 | 
			
		||||
    Traceback (most recent call last):
 | 
			
		||||
        ...
 | 
			
		||||
    MissingArgs: Missing argument(s): b, d
 | 
			
		||||
 | 
			
		||||
    :param fn: the function to check
 | 
			
		||||
    :param arg: the positional arguments supplied
 | 
			
		||||
    :param kwargs: the keyword arguments supplied
 | 
			
		||||
    """
 | 
			
		||||
    argspec = inspect.getargspec(fn)
 | 
			
		||||
 | 
			
		||||
    num_defaults = len(argspec.defaults or [])
 | 
			
		||||
    required_args = argspec.args[:len(argspec.args) - num_defaults]
 | 
			
		||||
 | 
			
		||||
    def isbound(method):
 | 
			
		||||
        return getattr(method, '__self__', None) is not None
 | 
			
		||||
 | 
			
		||||
    if isbound(fn):
 | 
			
		||||
        required_args.pop(0)
 | 
			
		||||
 | 
			
		||||
    missing = [arg for arg in required_args if arg not in kwargs]
 | 
			
		||||
    missing = missing[len(args):]
 | 
			
		||||
    if missing:
 | 
			
		||||
        raise MissingArgs(missing)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def arg(*args, **kwargs):
 | 
			
		||||
    """Decorator for CLI args.
 | 
			
		||||
 | 
			
		||||
    Example:
 | 
			
		||||
 | 
			
		||||
    >>> @arg("name", help="Name of the new entity")
 | 
			
		||||
    ... def entity_create(args):
 | 
			
		||||
    ...     pass
 | 
			
		||||
    """
 | 
			
		||||
    def _decorator(func):
 | 
			
		||||
        add_arg(func, *args, **kwargs)
 | 
			
		||||
        return func
 | 
			
		||||
    return _decorator
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def env(*args, **kwargs):
 | 
			
		||||
    """Returns the first environment variable set.
 | 
			
		||||
 | 
			
		||||
    If all are empty, defaults to '' or keyword arg `default`.
 | 
			
		||||
    """
 | 
			
		||||
    for arg in args:
 | 
			
		||||
        value = os.environ.get(arg)
 | 
			
		||||
        if value:
 | 
			
		||||
            return value
 | 
			
		||||
    return kwargs.get('default', '')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_arg(func, *args, **kwargs):
 | 
			
		||||
    """Bind CLI arguments to a shell.py `do_foo` function."""
 | 
			
		||||
 | 
			
		||||
    if not hasattr(func, 'arguments'):
 | 
			
		||||
        func.arguments = []
 | 
			
		||||
 | 
			
		||||
    # NOTE(sirp): avoid dups that can occur when the module is shared across
 | 
			
		||||
    # tests.
 | 
			
		||||
    if (args, kwargs) not in func.arguments:
 | 
			
		||||
        # Because of the semantics of decorator composition if we just append
 | 
			
		||||
        # to the options list positional options will appear to be backwards.
 | 
			
		||||
        func.arguments.insert(0, (args, kwargs))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def unauthenticated(func):
 | 
			
		||||
    """Adds 'unauthenticated' attribute to decorated function.
 | 
			
		||||
 | 
			
		||||
    Usage:
 | 
			
		||||
 | 
			
		||||
    >>> @unauthenticated
 | 
			
		||||
    ... def mymethod(f):
 | 
			
		||||
    ...     pass
 | 
			
		||||
    """
 | 
			
		||||
    func.unauthenticated = True
 | 
			
		||||
    return func
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def isunauthenticated(func):
 | 
			
		||||
    """Checks if the function does not require authentication.
 | 
			
		||||
 | 
			
		||||
    Mark such functions with the `@unauthenticated` decorator.
 | 
			
		||||
 | 
			
		||||
    :returns: bool
 | 
			
		||||
    """
 | 
			
		||||
    return getattr(func, 'unauthenticated', False)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_list(objs, fields, formatters=None, sortby_index=0,
 | 
			
		||||
               mixed_case_fields=None, field_labels=None):
 | 
			
		||||
    """Print a list or objects as a table, one row per object.
 | 
			
		||||
 | 
			
		||||
    :param objs: iterable of :class:`Resource`
 | 
			
		||||
    :param fields: attributes that correspond to columns, in order
 | 
			
		||||
    :param formatters: `dict` of callables for field formatting
 | 
			
		||||
    :param sortby_index: index of the field for sorting table rows
 | 
			
		||||
    :param mixed_case_fields: fields corresponding to object attributes that
 | 
			
		||||
        have mixed case names (e.g., 'serverId')
 | 
			
		||||
    :param field_labels: Labels to use in the heading of the table, default to
 | 
			
		||||
        fields.
 | 
			
		||||
    """
 | 
			
		||||
    formatters = formatters or {}
 | 
			
		||||
    mixed_case_fields = mixed_case_fields or []
 | 
			
		||||
    field_labels = field_labels or fields
 | 
			
		||||
    if len(field_labels) != len(fields):
 | 
			
		||||
        raise ValueError(_("Field labels list %(labels)s has different number "
 | 
			
		||||
                           "of elements than fields list %(fields)s"),
 | 
			
		||||
                         {'labels': field_labels, 'fields': fields})
 | 
			
		||||
 | 
			
		||||
    if sortby_index is None:
 | 
			
		||||
        kwargs = {}
 | 
			
		||||
    else:
 | 
			
		||||
        kwargs = {'sortby': field_labels[sortby_index]}
 | 
			
		||||
    pt = prettytable.PrettyTable(field_labels)
 | 
			
		||||
    pt.align = 'l'
 | 
			
		||||
 | 
			
		||||
    for o in objs:
 | 
			
		||||
        row = []
 | 
			
		||||
        for field in fields:
 | 
			
		||||
            if field in formatters:
 | 
			
		||||
                row.append(formatters[field](o))
 | 
			
		||||
            else:
 | 
			
		||||
                if field in mixed_case_fields:
 | 
			
		||||
                    field_name = field.replace(' ', '_')
 | 
			
		||||
                else:
 | 
			
		||||
                    field_name = field.lower().replace(' ', '_')
 | 
			
		||||
                data = getattr(o, field_name, '')
 | 
			
		||||
                row.append(data)
 | 
			
		||||
        pt.add_row(row)
 | 
			
		||||
 | 
			
		||||
    if six.PY3:
 | 
			
		||||
        print(encodeutils.safe_encode(pt.get_string(**kwargs)).decode())
 | 
			
		||||
    else:
 | 
			
		||||
        print(encodeutils.safe_encode(pt.get_string(**kwargs)))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def keys_and_vals_to_strs(dictionary):
 | 
			
		||||
    """Recursively convert a dictionary's keys and values to strings.
 | 
			
		||||
 | 
			
		||||
    :param dictionary: dictionary whose keys/vals are to be converted to strs
 | 
			
		||||
    """
 | 
			
		||||
    def to_str(k_or_v):
 | 
			
		||||
        if isinstance(k_or_v, dict):
 | 
			
		||||
            return keys_and_vals_to_strs(k_or_v)
 | 
			
		||||
        elif isinstance(k_or_v, six.text_type):
 | 
			
		||||
            return str(k_or_v)
 | 
			
		||||
        else:
 | 
			
		||||
            return k_or_v
 | 
			
		||||
    return dict((to_str(k), to_str(v)) for k, v in dictionary.items())
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def print_dict(dct, dict_property="Property", wrap=0):
 | 
			
		||||
    """Print a `dict` as a table of two columns.
 | 
			
		||||
 | 
			
		||||
    :param dct: `dict` to print
 | 
			
		||||
    :param dict_property: name of the first column
 | 
			
		||||
    :param wrap: wrapping for the second column
 | 
			
		||||
    """
 | 
			
		||||
    pt = prettytable.PrettyTable([dict_property, 'Value'])
 | 
			
		||||
    pt.align = 'l'
 | 
			
		||||
    for k, v in dct.items():
 | 
			
		||||
        # convert dict to str to check length
 | 
			
		||||
        if isinstance(v, dict):
 | 
			
		||||
            v = six.text_type(keys_and_vals_to_strs(v))
 | 
			
		||||
        if wrap > 0:
 | 
			
		||||
            v = textwrap.fill(six.text_type(v), wrap)
 | 
			
		||||
        # if value has a newline, add in multiple rows
 | 
			
		||||
        # e.g. fault with stacktrace
 | 
			
		||||
        if v and isinstance(v, six.string_types) and r'\n' in v:
 | 
			
		||||
            lines = v.strip().split(r'\n')
 | 
			
		||||
            col1 = k
 | 
			
		||||
            for line in lines:
 | 
			
		||||
                pt.add_row([col1, line])
 | 
			
		||||
                col1 = ''
 | 
			
		||||
        elif isinstance(v, list):
 | 
			
		||||
            val = str([str(i) for i in v])
 | 
			
		||||
            pt.add_row([k, val])
 | 
			
		||||
        else:
 | 
			
		||||
            pt.add_row([k, v])
 | 
			
		||||
 | 
			
		||||
    if six.PY3:
 | 
			
		||||
        print(encodeutils.safe_encode(pt.get_string()).decode())
 | 
			
		||||
    else:
 | 
			
		||||
        print(encodeutils.safe_encode(pt.get_string()))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_password(max_password_prompts=3):
 | 
			
		||||
    """Read password from TTY."""
 | 
			
		||||
    verify = strutils.bool_from_string(env("OS_VERIFY_PASSWORD"))
 | 
			
		||||
    pw = None
 | 
			
		||||
    if hasattr(sys.stdin, "isatty") and sys.stdin.isatty():
 | 
			
		||||
        # Check for Ctrl-D
 | 
			
		||||
        try:
 | 
			
		||||
            for __ in moves.range(max_password_prompts):
 | 
			
		||||
                pw1 = getpass.getpass("OS Password: ")
 | 
			
		||||
                if verify:
 | 
			
		||||
                    pw2 = getpass.getpass("Please verify: ")
 | 
			
		||||
                else:
 | 
			
		||||
                    pw2 = pw1
 | 
			
		||||
                if pw1 == pw2 and pw1:
 | 
			
		||||
                    pw = pw1
 | 
			
		||||
                    break
 | 
			
		||||
        except EOFError:
 | 
			
		||||
            pass
 | 
			
		||||
    return pw
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def service_type(stype):
 | 
			
		||||
    """Adds 'service_type' attribute to decorated function.
 | 
			
		||||
 | 
			
		||||
    Usage:
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
       @service_type('volume')
 | 
			
		||||
       def mymethod(f):
 | 
			
		||||
       ...
 | 
			
		||||
    """
 | 
			
		||||
    def inner(f):
 | 
			
		||||
        f.service_type = stype
 | 
			
		||||
        return f
 | 
			
		||||
    return inner
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_service_type(f):
 | 
			
		||||
    """Retrieves service type from function."""
 | 
			
		||||
    return getattr(f, 'service_type', None)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def pretty_choice_list(l):
 | 
			
		||||
    return ', '.join("'%s'" % i for i in l)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def exit(msg=''):
 | 
			
		||||
    if msg:
 | 
			
		||||
        print (msg, file=sys.stderr)
 | 
			
		||||
    sys.exit(1)
 | 
			
		||||
@@ -12,8 +12,8 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
from magnumclient.openstack.common.apiclient import exceptions
 | 
			
		||||
from magnumclient.openstack.common.apiclient.exceptions import *  # noqa
 | 
			
		||||
from magnumclient.common.apiclient import exceptions
 | 
			
		||||
from magnumclient.common.apiclient.exceptions import *  # noqa
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# NOTE(akurilin): This alias is left here since v.0.1.3 to support backwards
 | 
			
		||||
@@ -64,7 +64,7 @@ def from_response(response, message=None, traceback=None, method=None,
 | 
			
		||||
    if (response.headers.get('Content-Type', '').startswith('text/') and
 | 
			
		||||
            not hasattr(response, 'text')):
 | 
			
		||||
        # NOTE(clif_h): There seems to be a case in the
 | 
			
		||||
        # openstack.common.apiclient.exceptions module where if the
 | 
			
		||||
        # common.apiclient.exceptions module where if the
 | 
			
		||||
        # content-type of the response is text/* then it expects
 | 
			
		||||
        # the response to have a 'text' attribute, but that
 | 
			
		||||
        # doesn't always seem to necessarily be the case.
 | 
			
		||||
 
 | 
			
		||||
@@ -49,9 +49,9 @@ try:
 | 
			
		||||
except ImportError:
 | 
			
		||||
    pass
 | 
			
		||||
 | 
			
		||||
from magnumclient.common.apiclient import auth
 | 
			
		||||
from magnumclient.common import cliutils
 | 
			
		||||
from magnumclient import exceptions as exc
 | 
			
		||||
from magnumclient.openstack.common.apiclient import auth
 | 
			
		||||
from magnumclient.openstack.common import cliutils
 | 
			
		||||
from magnumclient.v1 import client as client_v1
 | 
			
		||||
from magnumclient.v1 import shell as shell_v1
 | 
			
		||||
from magnumclient import version
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,9 @@ import json
 | 
			
		||||
import mock
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from magnumclient.common.apiclient.exceptions import GatewayTimeout
 | 
			
		||||
from magnumclient.common import httpclient as http
 | 
			
		||||
from magnumclient import exceptions as exc
 | 
			
		||||
from magnumclient.openstack.common.apiclient.exceptions import GatewayTimeout
 | 
			
		||||
from magnumclient.tests import utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -79,7 +79,7 @@ class ShellTest(utils.TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        super(ShellTest, self).setUp()
 | 
			
		||||
        self.nc_util = mock.patch(
 | 
			
		||||
            'magnumclient.openstack.common.cliutils.isunauthenticated').start()
 | 
			
		||||
            'magnumclient.common.cliutils.isunauthenticated').start()
 | 
			
		||||
        self.nc_util.return_value = False
 | 
			
		||||
 | 
			
		||||
    def test_help_unknown_command(self):
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,9 @@
 | 
			
		||||
import collections
 | 
			
		||||
import six
 | 
			
		||||
 | 
			
		||||
from magnumclient.common import cliutils
 | 
			
		||||
from magnumclient.common import utils
 | 
			
		||||
from magnumclient import exceptions as exc
 | 
			
		||||
from magnumclient.openstack.common import cliutils
 | 
			
		||||
from magnumclient.tests import utils as test_utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,8 @@
 | 
			
		||||
 | 
			
		||||
import os.path
 | 
			
		||||
 | 
			
		||||
from magnumclient.common import cliutils as utils
 | 
			
		||||
from magnumclient.common import utils as magnum_utils
 | 
			
		||||
from magnumclient.openstack.common import cliutils as utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _show_baymodel(baymodel):
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@
 | 
			
		||||
#    License for the specific language governing permissions and limitations
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
from magnumclient.common import cliutils as utils
 | 
			
		||||
from magnumclient.common import utils as magnum_utils
 | 
			
		||||
from magnumclient.openstack.common import cliutils as utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _show_bay(bay):
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@
 | 
			
		||||
 | 
			
		||||
import os.path
 | 
			
		||||
 | 
			
		||||
from magnumclient.openstack.common import cliutils as utils
 | 
			
		||||
from magnumclient.common import cliutils as utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _show_cert(certificate):
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,9 @@
 | 
			
		||||
 | 
			
		||||
import json
 | 
			
		||||
 | 
			
		||||
from magnumclient.common import cliutils as utils
 | 
			
		||||
from magnumclient.common import utils as magnum_utils
 | 
			
		||||
from magnumclient import exceptions
 | 
			
		||||
from magnumclient.openstack.common import cliutils as utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _show_container(container):
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,8 @@
 | 
			
		||||
#    under the License.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
from magnumclient.common import cliutils as utils
 | 
			
		||||
from magnumclient.common import utils as magnum_utils
 | 
			
		||||
from magnumclient.openstack.common import cliutils as utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def do_service_list(cs, args):
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,9 @@
 | 
			
		||||
 | 
			
		||||
import os.path
 | 
			
		||||
 | 
			
		||||
from magnumclient.common import cliutils as utils
 | 
			
		||||
from magnumclient.common import utils as magnum_utils
 | 
			
		||||
from magnumclient import exceptions
 | 
			
		||||
from magnumclient.openstack.common import cliutils as utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _show_pod(pod):
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,9 @@
 | 
			
		||||
 | 
			
		||||
import os.path
 | 
			
		||||
 | 
			
		||||
from magnumclient.common import cliutils as utils
 | 
			
		||||
from magnumclient.common import utils as magnum_utils
 | 
			
		||||
from magnumclient import exceptions
 | 
			
		||||
from magnumclient.openstack.common import cliutils as utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _show_rc(rc):
 | 
			
		||||
 
 | 
			
		||||
@@ -14,8 +14,8 @@
 | 
			
		||||
 | 
			
		||||
import os.path
 | 
			
		||||
 | 
			
		||||
from magnumclient.common import cliutils as utils
 | 
			
		||||
from magnumclient.common import utils as magnum_utils
 | 
			
		||||
from magnumclient.openstack.common import cliutils as utils
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _show_coe_service(service):
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user