From f8affcbb4a56e2aabf2b30358b648c7c839a43e4 Mon Sep 17 00:00:00 2001 From: Julien Danjou Date: Tue, 10 Nov 2015 14:12:39 +0100 Subject: Remove deprecated `apiclient' This has been deprecated for a while and there are better alternative (keystoneauth1 and cliff) to deal with that. Change-Id: I2020d73fa9cedb3b3b87c1d8dbc8d437857ec7c2 --- MAINTAINERS | 6 - openstack/common/apiclient/__init__.py | 0 openstack/common/apiclient/auth.py | 234 ------------- openstack/common/apiclient/base.py | 533 ------------------------------ openstack/common/apiclient/client.py | 391 ---------------------- openstack/common/apiclient/exceptions.py | 479 --------------------------- openstack/common/apiclient/fake_client.py | 189 ----------- openstack/common/apiclient/utils.py | 100 ------ tests/unit/apiclient/__init__.py | 0 tests/unit/apiclient/test_auth.py | 178 ---------- tests/unit/apiclient/test_base.py | 348 ------------------- tests/unit/apiclient/test_client.py | 335 ------------------- tests/unit/apiclient/test_exceptions.py | 137 -------- tests/unit/apiclient/test_utils.py | 115 ------- 14 files changed, 3045 deletions(-) delete mode 100644 openstack/common/apiclient/__init__.py delete mode 100644 openstack/common/apiclient/auth.py delete mode 100644 openstack/common/apiclient/base.py delete mode 100644 openstack/common/apiclient/client.py delete mode 100644 openstack/common/apiclient/exceptions.py delete mode 100644 openstack/common/apiclient/fake_client.py delete mode 100644 openstack/common/apiclient/utils.py delete mode 100644 tests/unit/apiclient/__init__.py delete mode 100644 tests/unit/apiclient/test_auth.py delete mode 100644 tests/unit/apiclient/test_base.py delete mode 100644 tests/unit/apiclient/test_client.py delete mode 100644 tests/unit/apiclient/test_exceptions.py delete mode 100644 tests/unit/apiclient/test_utils.py diff --git a/MAINTAINERS b/MAINTAINERS index 64b973b6..a3d52ccb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -48,12 +48,6 @@ Each API has an entry with the following keys: Obsolete: Replaced by newer code, or a dead end, or out-dated F: Wildcard patterns, relative to openstack/common/ -== apiclient == - -M: Alessio Ababilov -S: Deprecated -F: apiclient/ - == cache == S: Deprecated diff --git a/openstack/common/apiclient/__init__.py b/openstack/common/apiclient/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/openstack/common/apiclient/auth.py b/openstack/common/apiclient/auth.py deleted file mode 100644 index b98d9cf6..00000000 --- a/openstack/common/apiclient/auth.py +++ /dev/null @@ -1,234 +0,0 @@ -# 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-oslo-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 openstack.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 = "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 six.iteritems(_discovered_plugins): - 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 - """ diff --git a/openstack/common/apiclient/base.py b/openstack/common/apiclient/base.py deleted file mode 100644 index f36fa5af..00000000 --- a/openstack/common/apiclient/base.py +++ /dev/null @@ -1,533 +0,0 @@ -# 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-oslo-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 openstack.common._i18n import _ -from openstack.common.apiclient import exceptions - - -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 six.iteritems(kwargs.copy()): - 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 "" % 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 six.iteritems(info): - 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) - if self.manager.client.last_request_id: - 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) diff --git a/openstack/common/apiclient/client.py b/openstack/common/apiclient/client.py deleted file mode 100644 index 2ee580fe..00000000 --- a/openstack/common/apiclient/client.py +++ /dev/null @@ -1,391 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss -# Copyright 2011 OpenStack Foundation -# Copyright 2011 Piston Cloud Computing, Inc. -# Copyright 2013 Alessio Ababilov -# Copyright 2013 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. - -""" -OpenStack Client interface. Handles the REST calls and responses. -""" - -# E0202: An attribute inherited from %s hide this method -# pylint: disable=E0202 - -import hashlib -import logging -import time - -try: - import simplejson as json -except ImportError: - import json - -from oslo_utils import encodeutils -from oslo_utils import importutils -import requests - -from openstack.common._i18n import _ -from openstack.common.apiclient import exceptions - -_logger = logging.getLogger(__name__) -SENSITIVE_HEADERS = ('X-Auth-Token', 'X-Subject-Token',) - - -class HTTPClient(object): - """This client handles sending HTTP requests to OpenStack servers. - - Features: - - - share authentication information between several clients to different - services (e.g., for compute and image clients); - - reissue authentication request for expired tokens; - - encode/decode JSON bodies; - - raise exceptions on HTTP errors; - - pluggable authentication; - - store authentication information in a keyring; - - store time spent for requests; - - register clients for particular services, so one can use - `http_client.identity` or `http_client.compute`; - - log requests and responses in a format that is easy to copy-and-paste - into terminal and send the same request with curl. - """ - - user_agent = "openstack.common.apiclient" - - def __init__(self, - auth_plugin, - region_name=None, - endpoint_type="publicURL", - original_ip=None, - verify=True, - cert=None, - timeout=None, - timings=False, - keyring_saver=None, - debug=False, - user_agent=None, - http=None): - self.auth_plugin = auth_plugin - - self.endpoint_type = endpoint_type - self.region_name = region_name - - self.original_ip = original_ip - self.timeout = timeout - self.verify = verify - self.cert = cert - - self.keyring_saver = keyring_saver - self.debug = debug - self.user_agent = user_agent or self.user_agent - - self.times = [] # [("item", starttime, endtime), ...] - self.timings = timings - - # requests within the same session can reuse TCP connections from pool - self.http = http or requests.Session() - - self.cached_token = None - self.last_request_id = None - - def _safe_header(self, name, value): - if name in SENSITIVE_HEADERS: - # because in python3 byte string handling is ... ug - v = value.encode('utf-8') - h = hashlib.sha1(v) - d = h.hexdigest() - return encodeutils.safe_decode(name), "{SHA1}%s" % d - else: - return (encodeutils.safe_decode(name), - encodeutils.safe_decode(value)) - - def _http_log_req(self, method, url, kwargs): - if not self.debug: - return - - string_parts = [ - "curl -g -i", - "-X '%s'" % method, - "'%s'" % url, - ] - - if not kwargs.get('verify', self.verify): - string_parts.insert(1, '--insecure') - - for element in kwargs['headers']: - header = ("-H '%s: %s'" % - self._safe_header(element, kwargs['headers'][element])) - string_parts.append(header) - - _logger.debug("REQ: %s" % " ".join(string_parts)) - if 'data' in kwargs: - _logger.debug("REQ BODY: %s\n" % (kwargs['data'])) - - def _http_log_resp(self, resp): - if not self.debug: - return - _logger.debug( - "RESP: [%s] %s\n", - resp.status_code, - resp.headers) - if resp._content_consumed: - _logger.debug( - "RESP BODY: %s\n", - resp.text) - - def serialize(self, kwargs): - if kwargs.get('json') is not None: - kwargs['headers']['Content-Type'] = 'application/json' - kwargs['data'] = json.dumps(kwargs['json']) - try: - del kwargs['json'] - except KeyError: - pass - - def get_timings(self): - return self.times - - def reset_timings(self): - self.times = [] - - def request(self, method, url, **kwargs): - """Send an http request with the specified characteristics. - - Wrapper around `requests.Session.request` to handle tasks such as - setting headers, JSON encoding/decoding, and error handling. - - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - requests.Session.request (such as `headers`) or `json` - that will be encoded as JSON and used as `data` argument - """ - kwargs.setdefault("headers", {}) - kwargs["headers"]["User-Agent"] = self.user_agent - if self.original_ip: - kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( - self.original_ip, self.user_agent) - if self.timeout is not None: - kwargs.setdefault("timeout", self.timeout) - kwargs.setdefault("verify", self.verify) - if self.cert is not None: - kwargs.setdefault("cert", self.cert) - self.serialize(kwargs) - - self._http_log_req(method, url, kwargs) - if self.timings: - start_time = time.time() - resp = self.http.request(method, url, **kwargs) - if self.timings: - self.times.append(("%s %s" % (method, url), - start_time, time.time())) - self._http_log_resp(resp) - - self.last_request_id = resp.headers.get('x-openstack-request-id') - - if resp.status_code >= 400: - _logger.debug( - "Request returned failure status: %s", - resp.status_code) - raise exceptions.from_response(resp, method, url) - - return resp - - @staticmethod - def concat_url(endpoint, url): - """Concatenate endpoint and final URL. - - E.g., "http://keystone/v2.0/" and "/tokens" are concatenated to - "http://keystone/v2.0/tokens". - - :param endpoint: the base URL - :param url: the final URL - """ - return "%s/%s" % (endpoint.rstrip("/"), url.strip("/")) - - def client_request(self, client, method, url, **kwargs): - """Send an http request using `client`'s endpoint and specified `url`. - - If request was rejected as unauthorized (possibly because the token is - expired), issue one authorization attempt and send the request once - again. - - :param client: instance of BaseClient descendant - :param method: method of HTTP request - :param url: URL of HTTP request - :param kwargs: any other parameter that can be passed to - `HTTPClient.request` - """ - - filter_args = { - "endpoint_type": client.endpoint_type or self.endpoint_type, - "service_type": client.service_type, - } - token, endpoint = (self.cached_token, client.cached_endpoint) - just_authenticated = False - if not (token and endpoint): - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - pass - if not (token and endpoint): - self.authenticate() - just_authenticated = True - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - if not (token and endpoint): - raise exceptions.AuthorizationFailure( - _("Cannot find endpoint or token for request")) - - old_token_endpoint = (token, endpoint) - kwargs.setdefault("headers", {})["X-Auth-Token"] = token - self.cached_token = token - client.cached_endpoint = endpoint - # Perform the request once. If we get Unauthorized, then it - # might be because the auth token expired, so try to - # re-authenticate and try again. If it still fails, bail. - try: - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - except exceptions.Unauthorized as unauth_ex: - if just_authenticated: - raise - self.cached_token = None - client.cached_endpoint = None - if self.auth_plugin.opts.get('token'): - self.auth_plugin.opts['token'] = None - if self.auth_plugin.opts.get('endpoint'): - self.auth_plugin.opts['endpoint'] = None - self.authenticate() - try: - token, endpoint = self.auth_plugin.token_and_endpoint( - **filter_args) - except exceptions.EndpointException: - raise unauth_ex - if (not (token and endpoint) or - old_token_endpoint == (token, endpoint)): - raise unauth_ex - self.cached_token = token - client.cached_endpoint = endpoint - kwargs["headers"]["X-Auth-Token"] = token - return self.request( - method, self.concat_url(endpoint, url), **kwargs) - - def add_client(self, base_client_instance): - """Add a new instance of :class:`BaseClient` descendant. - - `self` will store a reference to `base_client_instance`. - - Example: - - >>> def test_clients(): - ... from keystoneclient.auth import keystone - ... from openstack.common.apiclient import client - ... auth = keystone.KeystoneAuthPlugin( - ... username="user", password="pass", tenant_name="tenant", - ... auth_url="http://auth:5000/v2.0") - ... openstack_client = client.HTTPClient(auth) - ... # create nova client - ... from novaclient.v1_1 import client - ... client.Client(openstack_client) - ... # create keystone client - ... from keystoneclient.v2_0 import client - ... client.Client(openstack_client) - ... # use them - ... openstack_client.identity.tenants.list() - ... openstack_client.compute.servers.list() - """ - service_type = base_client_instance.service_type - if service_type and not hasattr(self, service_type): - setattr(self, service_type, base_client_instance) - - def authenticate(self): - self.auth_plugin.authenticate(self) - # Store the authentication results in the keyring for later requests - if self.keyring_saver: - self.keyring_saver.save(self) - - -class BaseClient(object): - """Top-level object to access the OpenStack API. - - This client uses :class:`HTTPClient` to send requests. :class:`HTTPClient` - will handle a bunch of issues such as authentication. - """ - - service_type = None - endpoint_type = None # "publicURL" will be used - cached_endpoint = None - - def __init__(self, http_client, extensions=None): - self.http_client = http_client - http_client.add_client(self) - - # Add in any extensions... - if extensions: - for extension in extensions: - if extension.manager_class: - setattr(self, extension.name, - extension.manager_class(self)) - - def client_request(self, method, url, **kwargs): - return self.http_client.client_request( - self, method, url, **kwargs) - - @property - def last_request_id(self): - return self.http_client.last_request_id - - def head(self, url, **kwargs): - return self.client_request("HEAD", url, **kwargs) - - def get(self, url, **kwargs): - return self.client_request("GET", url, **kwargs) - - def post(self, url, **kwargs): - return self.client_request("POST", url, **kwargs) - - def put(self, url, **kwargs): - return self.client_request("PUT", url, **kwargs) - - def delete(self, url, **kwargs): - return self.client_request("DELETE", url, **kwargs) - - def patch(self, url, **kwargs): - return self.client_request("PATCH", url, **kwargs) - - @staticmethod - def get_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 %(api_name)s client version '%(version)s'. " - "Must be one of: %(version_map)s") % { - 'api_name': api_name, - 'version': version, - 'version_map': ', '.join(version_map.keys())} - raise exceptions.UnsupportedVersion(msg) - - return importutils.import_class(client_path) diff --git a/openstack/common/apiclient/exceptions.py b/openstack/common/apiclient/exceptions.py deleted file mode 100644 index 7eb93f8c..00000000 --- a/openstack/common/apiclient/exceptions.py +++ /dev/null @@ -1,479 +0,0 @@ -# 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-oslo-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 openstack.common._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 six.iteritems(vars(sys.modules[__name__])) - 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) diff --git a/openstack/common/apiclient/fake_client.py b/openstack/common/apiclient/fake_client.py deleted file mode 100644 index a7662dbe..00000000 --- a/openstack/common/apiclient/fake_client.py +++ /dev/null @@ -1,189 +0,0 @@ -# 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. - -""" -A fake server that "responds" to API methods with pre-canned responses. - -All of these responses come from the spec, so if for some reason the spec's -wrong the tests might raise AssertionError. I've indicated in comments the -places where actual behavior differs from the spec. -""" - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-oslo-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -# W0102: Dangerous default value %s as argument -# pylint: disable=W0102 - -import json - -import requests -import six -from six.moves.urllib import parse - -from openstack.common.apiclient import client - - -def assert_has_keys(dct, required=None, optional=None): - required = required or [] - optional = optional or [] - for k in required: - try: - assert k in dct - except AssertionError: - extra_keys = set(dct.keys()).difference(set(required + optional)) - raise AssertionError("found unexpected keys: %s" % - list(extra_keys)) - - -class TestResponse(requests.Response): - """Wrap requests.Response and provide a convenient initialization. - """ - - def __init__(self, data): - super(TestResponse, self).__init__() - self._content_consumed = True - if isinstance(data, dict): - self.status_code = data.get('status_code', 200) - # Fake the text attribute to streamline Response creation - text = data.get('text', "") - if isinstance(text, (dict, list)): - self._content = json.dumps(text) - default_headers = { - "Content-Type": "application/json", - } - else: - self._content = text - default_headers = {} - if six.PY3 and isinstance(self._content, six.string_types): - self._content = self._content.encode('utf-8', 'strict') - self.headers = data.get('headers') or default_headers - else: - self.status_code = data - - def __eq__(self, other): - return (self.status_code == other.status_code and - self.headers == other.headers and - self._content == other._content) - - -class FakeHTTPClient(client.HTTPClient): - - def __init__(self, *args, **kwargs): - self.callstack = [] - self.fixtures = kwargs.pop("fixtures", None) or {} - if not args and "auth_plugin" not in kwargs: - args = (None, ) - super(FakeHTTPClient, self).__init__(*args, **kwargs) - - def assert_called(self, method, url, body=None, pos=-1): - """Assert than an API method was just called. - """ - expected = (method, url) - called = self.callstack[pos][0:2] - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - assert expected == called, 'Expected %s %s; got %s %s' % \ - (expected + called) - - if body is not None: - if self.callstack[pos][3] != body: - raise AssertionError('%r != %r' % - (self.callstack[pos][3], body)) - - def assert_called_anytime(self, method, url, body=None): - """Assert than an API method was called anytime in the test. - """ - expected = (method, url) - - assert self.callstack, \ - "Expected %s %s but no calls were made." % expected - - found = False - entry = None - for entry in self.callstack: - if expected == entry[0:2]: - found = True - break - - assert found, 'Expected %s %s; got %s' % \ - (method, url, self.callstack) - if body is not None: - assert entry[3] == body, "%s != %s" % (entry[3], body) - - self.callstack = [] - - def clear_callstack(self): - self.callstack = [] - - def authenticate(self): - pass - - def client_request(self, client, method, url, **kwargs): - # Check that certain things are called correctly - if method in ["GET", "DELETE"]: - assert "json" not in kwargs - - # Note the call - self.callstack.append( - (method, - url, - kwargs.get("headers") or {}, - kwargs.get("json") or kwargs.get("data"))) - try: - fixture = self.fixtures[url][method] - except KeyError: - pass - else: - return TestResponse({"headers": fixture[0], - "text": fixture[1]}) - - # Call the method - args = parse.parse_qsl(parse.urlparse(url)[4]) - kwargs.update(args) - munged_url = url.rsplit('?', 1)[0] - munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_') - munged_url = munged_url.replace('-', '_') - - callback = "%s_%s" % (method.lower(), munged_url) - - if not hasattr(self, callback): - raise AssertionError('Called unknown API method: %s %s, ' - 'expected fakes method name: %s' % - (method, url, callback)) - - resp = getattr(self, callback)(**kwargs) - if len(resp) == 3: - status, headers, body = resp - else: - status, body = resp - headers = {} - self.last_request_id = headers.get('x-openstack-request-id') - return TestResponse({ - "status_code": status, - "text": body, - "headers": headers, - }) diff --git a/openstack/common/apiclient/utils.py b/openstack/common/apiclient/utils.py deleted file mode 100644 index c713d622..00000000 --- a/openstack/common/apiclient/utils.py +++ /dev/null @@ -1,100 +0,0 @@ -# -# 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. - -######################################################################## -# -# THIS MODULE IS DEPRECATED -# -# Please refer to -# https://etherpad.openstack.org/p/kilo-oslo-library-proposals for -# the discussion leading to this deprecation. -# -# We recommend checking out the python-openstacksdk project -# (https://launchpad.net/python-openstacksdk) instead. -# -######################################################################## - -from oslo_utils import encodeutils -from oslo_utils import uuidutils -import six - -from openstack.common._i18n import _ -from openstack.common.apiclient import exceptions - - -def find_resource(manager, name_or_id, **find_args): - """Look for resource in a given manager. - - Used as a helper for the _find_* methods. - Example: - - .. code-block:: python - - def _find_hypervisor(cs, hypervisor): - #Get a hypervisor by name or ID. - return cliutils.find_resource(cs.hypervisors, hypervisor) - """ - # first try to get entity as integer id - try: - return manager.get(int(name_or_id)) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # now try to get entity as uuid - try: - if six.PY2: - tmp_id = encodeutils.safe_encode(name_or_id) - else: - tmp_id = encodeutils.safe_decode(name_or_id) - - if uuidutils.is_uuid_like(tmp_id): - return manager.get(tmp_id) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # for str id which is not uuid - if getattr(manager, 'is_alphanum_id_allowed', False): - try: - return manager.get(name_or_id) - except exceptions.NotFound: - pass - - try: - try: - return manager.find(human_id=name_or_id, **find_args) - except exceptions.NotFound: - pass - - # finally try to find entity by name - try: - resource = getattr(manager, 'resource_class', None) - name_attr = resource.NAME_ATTR if resource else 'name' - kwargs = {name_attr: name_or_id} - kwargs.update(find_args) - return manager.find(**kwargs) - except exceptions.NotFound: - msg = _("No %(name)s with a name or " - "ID of '%(name_or_id)s' exists.") % \ - { - "name": manager.resource_class.__name__.lower(), - "name_or_id": name_or_id - } - raise exceptions.CommandError(msg) - except exceptions.NoUniqueMatch: - msg = _("Multiple %(name)s matches found for " - "'%(name_or_id)s', use an ID to be more specific.") % \ - { - "name": manager.resource_class.__name__.lower(), - "name_or_id": name_or_id - } - raise exceptions.CommandError(msg) diff --git a/tests/unit/apiclient/__init__.py b/tests/unit/apiclient/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/unit/apiclient/test_auth.py b/tests/unit/apiclient/test_auth.py deleted file mode 100644 index a9806d8b..00000000 --- a/tests/unit/apiclient/test_auth.py +++ /dev/null @@ -1,178 +0,0 @@ -# Copyright 2012 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. - -import argparse - -import fixtures -import mock -from oslotest import base as test_base -import requests -from stevedore import extension - -try: - import json -except ImportError: - import simplejson as json - -from openstack.common.apiclient import auth -from openstack.common.apiclient import client -from openstack.common.apiclient import fake_client - - -TEST_REQUEST_BASE = { - 'verify': True, -} - - -def mock_http_request(resp=None): - """Mock an HTTP Request.""" - if not resp: - resp = { - "access": { - "token": { - "expires": "12345", - "id": "FAKE_ID", - "tenant": { - "id": "FAKE_TENANT_ID", - } - }, - "serviceCatalog": [ - { - "type": "compute", - "endpoints": [ - { - "region": "RegionOne", - "adminURL": "http://localhost:8774/v1.1", - "internalURL": "http://localhost:8774/v1.1", - "publicURL": "http://localhost:8774/v1.1/", - }, - ], - }, - ], - }, - } - - auth_response = fake_client.TestResponse({ - "status_code": 200, - "text": json.dumps(resp), - }) - return mock.Mock(return_value=(auth_response)) - - -def requested_headers(cs): - """Return requested passed headers.""" - return { - 'User-Agent': cs.user_agent, - 'Content-Type': 'application/json', - } - - -class BaseFakePlugin(auth.BaseAuthPlugin): - def _do_authenticate(self, http_client): - pass - - def token_and_endpoint(self, endpoint_type, service_type): - pass - - -class GlobalFunctionsTest(test_base.BaseTestCase): - - def test_load_auth_system_opts(self): - self.useFixture(fixtures.MonkeyPatch( - "os.environ", - {"OS_TENANT_NAME": "fake-project", - "OS_USERNAME": "fake-username"})) - parser = argparse.ArgumentParser() - auth.load_auth_system_opts(parser) - options = parser.parse_args( - ["--os-auth-url=fake-url", "--os_auth_system=fake-system"]) - self.assertEqual(options.os_tenant_name, "fake-project") - self.assertEqual(options.os_username, "fake-username") - self.assertEqual(options.os_auth_url, "fake-url") - self.assertEqual(options.os_auth_system, "fake-system") - - -class MockEntrypoint(object): - def __init__(self, name, plugin): - self.name = name - self.plugin = plugin - - -class AuthPluginTest(test_base.BaseTestCase): - @mock.patch.object(requests.Session, "request") - @mock.patch.object(extension.ExtensionManager, "map") - def test_auth_system_success(self, mock_mgr_map, mock_request): - """Test that we can authenticate using the auth system.""" - class FakePlugin(BaseFakePlugin): - def authenticate(self, cls): - cls.request( - "POST", "http://auth/tokens", - json={"fake": "me"}, allow_redirects=True) - - mock_mgr_map.side_effect = ( - lambda func: func(MockEntrypoint("fake", FakePlugin))) - - mock_request.side_effect = mock_http_request() - - auth.discover_auth_systems() - plugin = auth.load_plugin("fake") - cs = client.HTTPClient(auth_plugin=plugin) - cs.authenticate() - - headers = requested_headers(cs) - - mock_request.assert_called_with( - "POST", - "http://auth/tokens", - headers=headers, - data='{"fake": "me"}', - allow_redirects=True, - **TEST_REQUEST_BASE) - - @mock.patch.object(extension.ExtensionManager, "map") - def test_discover_auth_system_options(self, mock_mgr_map): - """Test that we can load the auth system options.""" - class FakePlugin(BaseFakePlugin): - @classmethod - def add_opts(cls, parser): - parser.add_argument('--auth_system_opt', - default=False, - action='store_true', - help="Fake option.") - - mock_mgr_map.side_effect = ( - lambda func: func(MockEntrypoint("fake", FakePlugin))) - - parser = argparse.ArgumentParser() - auth.discover_auth_systems() - auth.load_auth_system_opts(parser) - opts, _args = parser.parse_known_args(['--auth_system_opt']) - - self.assertTrue(opts.auth_system_opt) - - @mock.patch.object(extension.ExtensionManager, "map") - def test_parse_auth_system_options(self, mock_mgr_map): - """Test that we can parse the auth system options.""" - class FakePlugin(BaseFakePlugin): - opt_names = ["fake_argument"] - - mock_mgr_map.side_effect = ( - lambda func: func(MockEntrypoint("fake", FakePlugin))) - - auth.discover_auth_systems() - plugin = auth.load_plugin("fake") - - plugin.parse_opts([]) - self.assertIn("fake_argument", plugin.opts) diff --git a/tests/unit/apiclient/test_base.py b/tests/unit/apiclient/test_base.py deleted file mode 100644 index f7bff083..00000000 --- a/tests/unit/apiclient/test_base.py +++ /dev/null @@ -1,348 +0,0 @@ -# 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. - -import mock -from oslotest import base as test_base -from testtools import matchers - -from openstack.common.apiclient import base -from openstack.common.apiclient import client -from openstack.common.apiclient import exceptions -from openstack.common.apiclient import fake_client - - -class HumanResource(base.Resource): - HUMAN_ID = True - - -class HumanResourceManager(base.ManagerWithFind): - resource_class = HumanResource - - def list(self): - return self._list("/human_resources", "human_resources") - - def get(self, human_resource): - return self._get( - "/human_resources/%s" % base.getid(human_resource), - "human_resource") - - def update(self, human_resource, name): - body = { - "human_resource": { - "name": name, - }, - } - return self._put( - "/human_resources/%s" % base.getid(human_resource), - body, - "human_resource") - - -class CrudResource(base.Resource): - pass - - -class CrudResourceManager(base.CrudManager): - """Manager class for manipulating Identity crud_resources.""" - resource_class = CrudResource - collection_key = 'crud_resources' - key = 'crud_resource' - - def get(self, crud_resource): - return super(CrudResourceManager, self).get( - crud_resource_id=base.getid(crud_resource)) - - -class FakeHTTPClient(fake_client.FakeHTTPClient): - crud_resource_json = {"id": "1", "domain_id": "my-domain"} - - def get_human_resources(self, **kw): - return (200, {}, - {'human_resources': [ - {'id': 1, 'name': '256 MB Server'}, - {'id': 2, 'name': '512 MB Server'}, - {'id': 'aa1', 'name': '128 MB Server'}, - {'id': 3, 'name': '4 MB Server'}, - ]}) - - def get_human_resources_1(self, **kw): - res = self.get_human_resources()[2]['human_resources'][0] - return (200, {}, {'human_resource': res}) - - def get_human_resources_3(self, **kw): - res = self.get_human_resources()[2]['human_resources'][3] - return (200, {'x-openstack-request-id': 'req-flappy'}, - {'human_resource': res}) - - def put_human_resources_1(self, **kw): - kw = kw["json"]["human_resource"].copy() - kw["id"] = "1" - return (200, {}, {'human_resource': kw}) - - def post_crud_resources(self, **kw): - return (200, {}, {"crud_resource": {"id": "1"}}) - - def get_crud_resources(self, **kw): - crud_resources = [] - if kw.get("domain_id") == self.crud_resource_json["domain_id"]: - crud_resources = [self.crud_resource_json] - else: - crud_resources = [] - return (200, {}, {"crud_resources": crud_resources}) - - def get_crud_resources_1(self, **kw): - return (200, {}, {"crud_resource": self.crud_resource_json}) - - def head_crud_resources_1(self, **kw): - return (204, {}, None) - - def patch_crud_resources_1(self, **kw): - self.crud_resource_json.update(kw) - return (200, {}, {"crud_resource": self.crud_resource_json}) - - def delete_crud_resources_1(self, **kw): - return (202, {}, None) - - -class TestClient(client.BaseClient): - - service_type = "test" - - def __init__(self, http_client, extensions=None): - super(TestClient, self).__init__( - http_client, extensions=extensions) - - self.human_resources = HumanResourceManager(self) - self.crud_resources = CrudResourceManager(self) - - -class ResourceTest(test_base.BaseTestCase): - def test_resource_repr(self): - r = base.Resource(None, dict(foo="bar", baz="spam")) - self.assertEqual(repr(r), "") - - def test_getid(self): - class TmpObject(base.Resource): - id = "4" - self.assertEqual(base.getid(TmpObject(None, {})), "4") - - def test_human_id(self): - r = base.Resource(None, {"name": "1"}) - self.assertIsNone(r.human_id) - r = HumanResource(None, {"name": "1"}) - self.assertEqual(r.human_id, "1") - r = HumanResource(None, {"name": None}) - self.assertIsNone(r.human_id) - - -class BaseManagerTest(test_base.BaseTestCase): - - def setUp(self): - super(BaseManagerTest, self).setUp() - self.http_client = FakeHTTPClient() - self.tc = TestClient(self.http_client) - - def test_resource_lazy_getattr(self): - f = HumanResource(self.tc.human_resources, {'id': 1}) - self.assertEqual(f.name, '256 MB Server') - self.http_client.assert_called('GET', '/human_resources/1') - - # Missing stuff still fails after a second get - self.assertRaises(AttributeError, getattr, f, 'blahblah') - - def test_resource_req_id(self): - f = HumanResource(self.tc.human_resources, {'id': 1}) - self.assertEqual(f.name, '256 MB Server') - self.assertFalse(hasattr(f, 'x_request_id')) - self.http_client.assert_called('GET', '/human_resources/1') - - def test_resource_req_id_header(self): - f = HumanResource(self.tc.human_resources, {'id': 3}) - self.assertEqual(f.name, '4 MB Server') - self.assertEqual(f.x_request_id, 'req-flappy') - self.http_client.assert_called('GET', '/human_resources/3') - - def test_eq(self): - # Two resources of the same type with the same id: equal - r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) - r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) - self.assertEqual(r1, r2) - - # Two resources of different types: never equal - r1 = base.Resource(None, {'id': 1}) - r2 = HumanResource(None, {'id': 1}) - self.assertNotEqual(r1, r2) - - # Two resources with no ID: equal if their info is equal - r1 = base.Resource(None, {'name': 'joe', 'age': 12}) - r2 = base.Resource(None, {'name': 'joe', 'age': 12}) - self.assertEqual(r1, r2) - - def test_findall_invalid_attribute(self): - # Make sure findall with an invalid attribute doesn't cause errors. - # The following should not raise an exception. - self.tc.human_resources.findall(vegetable='carrot') - - # However, find() should raise an error - self.assertRaises(exceptions.NotFound, - self.tc.human_resources.find, - vegetable='carrot') - - def test_update(self): - name = "new-name" - human_resource = self.tc.human_resources.update("1", name) - self.assertEqual(human_resource.id, "1") - self.assertEqual(human_resource.name, name) - - -class BaseManagerTestCase(test_base.BaseTestCase): - - def setUp(self): - super(BaseManagerTestCase, self).setUp() - - self.response = mock.MagicMock() - self.http_client = mock.MagicMock() - self.http_client.get.return_value = self.response - self.http_client.post.return_value = self.response - - self.manager = base.BaseManager(self.http_client) - self.manager.resource_class = HumanResource - - def test_list(self): - self.response.json.return_value = {'human_resources': [{'id': 42}]} - expected = [HumanResource(self.manager, {'id': 42}, loaded=True)] - result = self.manager._list("/human_resources", "human_resources") - self.assertEqual(expected, result) - - def test_list_no_response_key(self): - self.response.json.return_value = [{'id': 42}] - expected = [HumanResource(self.manager, {'id': 42}, loaded=True)] - result = self.manager._list("/human_resources") - self.assertEqual(expected, result) - - def test_list_get(self): - self.manager._list("/human_resources", "human_resources") - self.manager.client.get.assert_called_with("/human_resources") - - def test_list_post(self): - self.manager._list("/human_resources", "human_resources", - json={'id': 42}) - self.manager.client.post.assert_called_with("/human_resources", - json={'id': 42}) - - def test_get(self): - self.response.json.return_value = {'human_resources': {'id': 42}} - expected = HumanResource(self.manager, {'id': 42}, loaded=True) - result = self.manager._get("/human_resources/42", "human_resources") - self.manager.client.get.assert_called_with("/human_resources/42") - self.assertEqual(expected, result) - - def test_get_no_response_key(self): - self.response.json.return_value = {'id': 42} - expected = HumanResource(self.manager, {'id': 42}, loaded=True) - result = self.manager._get("/human_resources/42") - self.manager.client.get.assert_called_with("/human_resources/42") - self.assertEqual(expected, result) - - def test_post(self): - self.response.json.return_value = {'human_resources': {'id': 42}} - expected = HumanResource(self.manager, {'id': 42}, loaded=True) - result = self.manager._post("/human_resources", - response_key="human_resources", - json={'id': 42}) - self.manager.client.post.assert_called_with("/human_resources", - json={'id': 42}) - self.assertEqual(expected, result) - - def test_post_return_raw(self): - self.response.json.return_value = {'human_resources': {'id': 42}} - result = self.manager._post("/human_resources", - response_key="human_resources", - json={'id': 42}, return_raw=True) - self.manager.client.post.assert_called_with("/human_resources", - json={'id': 42}) - self.assertEqual(result, {'id': 42}) - - def test_post_no_response_key(self): - self.response.json.return_value = {'id': 42} - expected = HumanResource(self.manager, {'id': 42}, loaded=True) - result = self.manager._post("/human_resources", json={'id': 42}) - self.manager.client.post.assert_called_with("/human_resources", - json={'id': 42}) - self.assertEqual(expected, result) - - -class CrudManagerTest(test_base.BaseTestCase): - - domain_id = "my-domain" - crud_resource_id = "1" - - def setUp(self): - super(CrudManagerTest, self).setUp() - self.http_client = FakeHTTPClient() - self.tc = TestClient(self.http_client) - - def test_create(self): - crud_resource = self.tc.crud_resources.create() - self.assertEqual(crud_resource.id, self.crud_resource_id) - - def test_list(self, domain=None, user=None): - crud_resources = self.tc.crud_resources.list( - base_url=None, - domain_id=self.domain_id) - self.assertEqual(len(crud_resources), 1) - self.assertEqual(crud_resources[0].id, self.crud_resource_id) - self.assertEqual(crud_resources[0].domain_id, self.domain_id) - crud_resources = self.tc.crud_resources.list( - base_url=None, - domain_id="another-domain", - another_attr=None) - self.assertEqual(len(crud_resources), 0) - - def test_get(self): - crud_resource = self.tc.crud_resources.get(self.crud_resource_id) - self.assertEqual(crud_resource.id, self.crud_resource_id) - fake_client.assert_has_keys( - crud_resource._info, - required=["id", "domain_id"], - optional=["missing-attr"]) - - def test_update(self): - crud_resource = self.tc.crud_resources.update( - crud_resource_id=self.crud_resource_id, - domain_id=self.domain_id) - self.assertEqual(crud_resource.id, self.crud_resource_id) - self.assertEqual(crud_resource.domain_id, self.domain_id) - - def test_delete(self): - resp = self.tc.crud_resources.delete( - crud_resource_id=self.crud_resource_id) - self.assertEqual(resp.status_code, 202) - - def test_head(self): - ret = self.tc.crud_resources.head( - crud_resource_id=self.crud_resource_id) - self.assertTrue(ret) - - def test_find(self): - ret = self.tc.crud_resources.find(domain_id=self.domain_id) - self.assertTrue(ret) - - def test_find_receives_a_NotFound_error(self): - e = self.assertRaises(exceptions.NotFound, - self.tc.crud_resources.find, - resource_id=self.crud_resource_id) - self.assertThat(str(e), - matchers.StartsWith('No CrudResource matching ')) diff --git a/tests/unit/apiclient/test_client.py b/tests/unit/apiclient/test_client.py deleted file mode 100644 index 1911c4e7..00000000 --- a/tests/unit/apiclient/test_client.py +++ /dev/null @@ -1,335 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright 2012 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. - -import logging - -import fixtures -import mock -from oslotest import base as test_base -import requests - -from openstack.common.apiclient import auth -from openstack.common.apiclient import client -from openstack.common.apiclient import exceptions -from openstack.common.apiclient import fake_client - - -class TestClient(client.BaseClient): - service_type = "test" - cached_endpoint = 'old_endpoint' - - -class FakeAuthPlugin(auth.BaseAuthPlugin): - auth_system = "fake" - attempt = -1 - - def _do_authenticate(self, http_client): - pass - - def token_and_endpoint(self, endpoint_type, service_type): - self.attempt = self.attempt + 1 - return ("token-%s" % self.attempt, "/endpoint-%s" % self.attempt) - - -class ClientTest(test_base.BaseTestCase): - - def test_client_with_timeout(self): - http_client = client.HTTPClient(None, timeout=2) - self.assertEqual(http_client.timeout, 2) - mock_request = mock.Mock() - mock_request.return_value = requests.Response() - mock_request.return_value.status_code = 200 - with mock.patch("requests.Session.request", mock_request): - http_client.request("GET", "/", json={"1": "2"}) - requests.Session.request.assert_called_with( - "GET", - "/", - timeout=2, - headers=mock.ANY, - verify=mock.ANY, - data=mock.ANY) - - def test_concat_url(self): - self.assertEqual(client.HTTPClient.concat_url("/a", "/b"), "/a/b") - self.assertEqual(client.HTTPClient.concat_url("/a", "b"), "/a/b") - self.assertEqual(client.HTTPClient.concat_url("/a/", "/b"), "/a/b") - - def test_client_request(self): - http_client = client.HTTPClient(FakeAuthPlugin()) - mock_request = mock.Mock() - mock_request.return_value = requests.Response() - mock_request.return_value.status_code = 200 - with mock.patch("requests.Session.request", mock_request): - http_client.client_request( - TestClient(http_client), "GET", "/resource", json={"1": "2"}) - requests.Session.request.assert_called_with( - "GET", - "/endpoint-0/resource", - headers={ - "User-Agent": http_client.user_agent, - "Content-Type": "application/json", - "X-Auth-Token": "token-0" - }, - data='{"1": "2"}', - verify=True) - - def test_client_request_expired_token(self): - side_effect_rv = [False, True] - - def side_effect(*args, **kwargs): - if side_effect_rv.pop(): - raise exceptions.Unauthorized() - return - - def new_creds(*args, **kwargs): - return 'new_token', 'new_endpoint' - - with mock.patch('%s.FakeAuthPlugin.token_and_endpoint' % __name__, - mock.MagicMock()) as mocked_token_and_endpoint: - mocked_token_and_endpoint.side_effect = new_creds - - http_client = client.HTTPClient(FakeAuthPlugin()) - http_client.cached_token = 'old_token' - http_client.request = mock.MagicMock(side_effect=side_effect) - - http_client.client_request( - TestClient(http_client), "GET", "/resource", json={"1": "2"}) - http_client.request.assert_called_with( - 'GET', - 'new_endpoint/resource', - headers={'X-Auth-Token': 'new_token'}, - json={'1': '2'}) - self.assertEqual('new_token', http_client.cached_token) - - def test_client_request_reissue(self): - reject_token = None - - def fake_request(method, url, **kwargs): - if kwargs["headers"]["X-Auth-Token"] == reject_token: - raise exceptions.Unauthorized(method=method, url=url) - return "%s %s" % (method, url) - - http_client = client.HTTPClient(FakeAuthPlugin()) - test_client = TestClient(http_client) - http_client.request = fake_request - - self.assertEqual( - http_client.client_request( - test_client, "GET", "/resource"), - "GET /endpoint-0/resource") - reject_token = "token-0" - self.assertEqual( - http_client.client_request( - test_client, "GET", "/resource"), - "GET /endpoint-1/resource") - - def test_client_with_response_400_status_code(self): - http_client = client.HTTPClient(FakeAuthPlugin()) - mock_request = mock.Mock() - mock_request.return_value = requests.Response() - mock_request.return_value.status_code = 400 - with mock.patch("requests.Session.request", mock_request): - self.assertRaises( - exceptions.BadRequest, http_client.client_request, - TestClient(http_client), "GET", "/resource", - json={"bad": "request"}) - - def test_client_with_no_token_and_no_endpoint(self): - with mock.patch('%s.FakeAuthPlugin.token_and_endpoint' % __name__, - mock.MagicMock()) as mocked_token_and_endpoint: - mocked_token_and_endpoint.return_value = (None, None) - - http_client = client.HTTPClient(FakeAuthPlugin()) - self.assertRaises( - exceptions.AuthorizationFailure, http_client.client_request, - TestClient(http_client), "GET", "/resource", json={"1": "2"}) - - def test_client_raising_unauthorized(self): - side_effect_rv = [True, False] - - def side_effect(*args, **kwargs): - if side_effect_rv.pop(): - raise exceptions.EndpointException() - return ("token-%s" % len(side_effect_rv), - "/endpoint-%s" % len(side_effect_rv)) - - with mock.patch('%s.FakeAuthPlugin.token_and_endpoint' % __name__, - mock.MagicMock()) as mocked_token_and_endpoint: - mocked_token_and_endpoint.side_effect = side_effect - - http_client = client.HTTPClient(FakeAuthPlugin()) - http_client.request = mock.MagicMock( - side_effect=exceptions.Unauthorized()) - self.assertRaises( - exceptions.Unauthorized, http_client.client_request, - TestClient(http_client), "GET", "/resource", json={"1": "2"}) - - def test_client_raising_unauthorized_with_equal_token_and_endpoint(self): - with mock.patch('%s.FakeAuthPlugin.token_and_endpoint' % __name__, - mock.MagicMock()) as mocked_token_and_endpoint: - mocked_token_and_endpoint.return_value = ('token-0', '/endpoint-0') - http_client = client.HTTPClient(FakeAuthPlugin()) - http_client.request = mock.MagicMock( - side_effect=exceptions.Unauthorized()) - self.assertRaises( - exceptions.Unauthorized, http_client.client_request, - TestClient(http_client), "GET", "/resource", json={"1": "2"}) - - def test_client_raising_unauthorized_with_just_authenticated(self): - side_effect_rv = [True, False, True] - - def side_effect(*args, **kwargs): - if side_effect_rv.pop(): - raise exceptions.EndpointException() - return ("token-%s" % len(side_effect_rv), - "/endpoint-%s" % len(side_effect_rv)) - - with mock.patch('%s.FakeAuthPlugin.token_and_endpoint' % __name__, - mock.MagicMock()) as mocked_token_and_endpoint: - mocked_token_and_endpoint.side_effect = side_effect - - http_client = client.HTTPClient(FakeAuthPlugin()) - http_client.request = mock.MagicMock( - side_effect=exceptions.Unauthorized()) - self.assertRaises( - exceptions.Unauthorized, http_client.client_request, - TestClient(http_client), "GET", "/resource", json={"1": "2"}) - - def test_log_req(self): - self.logger = self.useFixture( - fixtures.FakeLogger( - format="%(message)s", - level=logging.DEBUG, - nuke_handlers=True - ) - ) - cs = client.HTTPClient(FakeAuthPlugin()) - cs.debug = True - cs._http_log_req('GET', '/foo', {'headers': {}}) - BOGUS_HEADERS_1 = {'headers': {'X-Auth-Token': 'totally_bogus'}} - cs._http_log_req('GET', '/foo', BOGUS_HEADERS_1) - BOGUS_HEADERS_2 = {'headers': { - 'X-Subject-Token': 'totally_bogus_subject_token'} - } - cs._http_log_req('GET', '/foo/bar', BOGUS_HEADERS_2) - BOGUS_HEADERS_3 = {'headers': { - 'X-Foo': 'bar', - 'X-Auth-Token': 'totally_bogus', - 'X-Subject-Token': 'totally_bogus_subject_token' - } - } - cs._http_log_req('GET', '/foo', BOGUS_HEADERS_3) - - output = self.logger.output.split('\n') - - self.assertIn("REQ: curl -g -i -X 'GET' '/foo'", output) - self.assertIn("REQ: curl -g -i -X 'GET' '/foo' -H 'X-Auth-Token: " - "{SHA1}b42162b6ffdbd7c3c37b7c95b7ba9f51dda0236d'", - output) - self.assertIn("REQ: curl -g -i -X 'GET' '/foo/bar'" - " -H 'X-Subject-Token: " - "{SHA1}1999873bee4b1aed81b7abb168044a8cbbbccbd6'", - output) - self.assertIn("REQ: curl -g -i -X 'GET' '/foo' " - "-H 'X-Subject-Token: " - "{SHA1}1999873bee4b1aed81b7abb168044a8cbbbccbd6' " - "-H 'X-Foo: bar' " - "-H 'X-Auth-Token: " - "{SHA1}b42162b6ffdbd7c3c37b7c95b7ba9f51dda0236d'", - output) - - def _test_log_req_insecure(self, verify, expected): - self.logger = self.useFixture( - fixtures.FakeLogger( - format="%(message)s", - level=logging.DEBUG, - nuke_handlers=True - ) - ) - - cs = client.HTTPClient(FakeAuthPlugin(), debug=True, verify=verify) - cs._http_log_req('GET', '/foo', {'headers': {}}) - output = self.logger.output.split('\n') - self.assertIn(expected, output) - - def test_log_req_insecure(self): - expected = "REQ: curl -g -i -X 'GET' '/foo'" - self._test_log_req_insecure(True, expected) - expected = "REQ: curl -g -i --insecure -X 'GET' '/foo'" - self._test_log_req_insecure(False, expected) - - -class FakeClientTest(test_base.BaseTestCase): - - def setUp(self): - super(FakeClientTest, self).setUp() - fixtures = { - '/endpoint/resource': { - 'GET': ( - {}, - {'foo': u'bär'} - ) - } - } - fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures) - self.test_client = TestClient(fake_http_client) - - def test_fake_client_request(self): - resp = self.test_client.get('/endpoint/resource') - self.assertEqual(resp.status_code, 200) - resp_data = resp.json() - self.assertEqual(u'bär', resp_data['foo']) - - def test_fake_client_encode(self): - def guess_json_utf(data): - self.assertIsInstance(data, bytes) - return 'utf-8' - - with mock.patch("requests.utils.guess_json_utf", guess_json_utf): - resp = self.test_client.get('/endpoint/resource') - self.assertEqual(resp.status_code, 200) - - -class FakeClient1(object): - pass - - -class FakeClient21(object): - pass - - -class GetClientClassTestCase(test_base.BaseTestCase): - version_map = { - "1": "%s.FakeClient1" % __name__, - "2.1": "%s.FakeClient21" % __name__, - } - - def test_get_int(self): - self.assertEqual( - client.BaseClient.get_class("fake", 1, self.version_map), - FakeClient1) - - def test_get_str(self): - self.assertEqual( - client.BaseClient.get_class("fake", "2.1", self.version_map), - FakeClient21) - - def test_unsupported_version(self): - self.assertRaises( - exceptions.UnsupportedVersion, - client.BaseClient.get_class, - "fake", "7", self.version_map) diff --git a/tests/unit/apiclient/test_exceptions.py b/tests/unit/apiclient/test_exceptions.py deleted file mode 100644 index ead088e0..00000000 --- a/tests/unit/apiclient/test_exceptions.py +++ /dev/null @@ -1,137 +0,0 @@ -# Copyright 2012 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. - -from oslotest import base as test_base -import six - -from openstack.common.apiclient import exceptions - - -class FakeResponse(object): - json_data = {} - - def __init__(self, **kwargs): - for key, value in six.iteritems(kwargs): - setattr(self, key, value) - - def json(self): - return self.json_data - - -class ExceptionsArgsTest(test_base.BaseTestCase): - - def assert_exception(self, ex_cls, method, url, status_code, json_data, - error_msg=None, error_details=None, - check_description=True): - ex = exceptions.from_response( - FakeResponse(status_code=status_code, - headers={"Content-Type": "application/json"}, - json_data=json_data), - method, - url) - self.assertTrue(isinstance(ex, ex_cls)) - if check_description: - expected_msg = error_msg or json_data["error"]["message"] - expected_details = error_details or json_data["error"]["details"] - self.assertEqual(ex.message, expected_msg) - self.assertEqual(ex.details, expected_details) - self.assertEqual(ex.method, method) - self.assertEqual(ex.url, url) - self.assertEqual(ex.http_status, status_code) - - def test_from_response_known(self): - method = "GET" - url = "/fake" - status_code = 400 - json_data = {"error": {"message": "fake message", - "details": "fake details"}} - self.assert_exception( - exceptions.BadRequest, method, url, status_code, json_data) - - def test_from_response_unknown(self): - method = "POST" - url = "/fake-unknown" - status_code = 499 - json_data = {"error": {"message": "fake unknown message", - "details": "fake unknown details"}} - self.assert_exception( - exceptions.HTTPClientError, method, url, status_code, json_data) - status_code = 600 - self.assert_exception( - exceptions.HttpError, method, url, status_code, json_data) - - def test_from_response_non_openstack(self): - method = "POST" - url = "/fake-unknown" - status_code = 400 - json_data = {"alien": 123} - self.assert_exception( - exceptions.BadRequest, method, url, status_code, json_data, - check_description=False) - - def test_from_response_with_different_response_format(self): - method = "GET" - url = "/fake-wsme" - status_code = 400 - json_data1 = {"error_message": {"debuginfo": None, - "faultcode": "Client", - "faultstring": "fake message"}} - message = six.text_type( - json_data1["error_message"]["faultstring"]) - details = six.text_type(json_data1) - self.assert_exception( - exceptions.BadRequest, method, url, status_code, json_data1, - message, details) - - json_data2 = {"badRequest": {"message": "fake message", "code": 400}} - message = six.text_type(json_data2["badRequest"]["message"]) - details = six.text_type(json_data2) - self.assert_exception( - exceptions.BadRequest, method, url, status_code, json_data2, - message, details) - - def test_from_response_with_text_response_format(self): - method = "GET" - url = "/fake-wsme" - status_code = 400 - text_data1 = "error_message: fake message" - - ex = exceptions.from_response( - FakeResponse(status_code=status_code, - headers={"Content-Type": "text/html"}, - text=text_data1), - method, - url) - self.assertTrue(isinstance(ex, exceptions.BadRequest)) - self.assertEqual(ex.details, text_data1) - self.assertEqual(ex.method, method) - self.assertEqual(ex.url, url) - self.assertEqual(ex.http_status, status_code) - - def test_from_response_with_text_response_format_with_no_body(self): - method = "GET" - url = "/fake-wsme" - status_code = 401 - - ex = exceptions.from_response( - FakeResponse(status_code=status_code, - headers={"Content-Type": "text/html"}), - method, - url) - self.assertTrue(isinstance(ex, exceptions.Unauthorized)) - self.assertEqual(ex.details, '') - self.assertEqual(ex.method, method) - self.assertEqual(ex.url, url) - self.assertEqual(ex.http_status, status_code) diff --git a/tests/unit/apiclient/test_utils.py b/tests/unit/apiclient/test_utils.py deleted file mode 100644 index 3c8af512..00000000 --- a/tests/unit/apiclient/test_utils.py +++ /dev/null @@ -1,115 +0,0 @@ -# -# 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 oslotest import base as test_base - -from openstack.common.apiclient import base -from openstack.common.apiclient import exceptions -from openstack.common.apiclient import utils - - -UUID = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0' - - -class FakeResource(object): - NAME_ATTR = 'name' - - def __init__(self, _id, properties): - self.id = _id - try: - self.name = properties['name'] - except KeyError: - pass - - -class FakeManager(base.ManagerWithFind): - - resource_class = FakeResource - - resources = [ - FakeResource('1234', {'name': 'entity_one'}), - FakeResource(UUID, {'name': 'entity_two'}), - FakeResource('5678', {'name': '9876'}) - ] - - def get(self, resource_id): - for resource in self.resources: - if resource.id == str(resource_id): - return resource - raise exceptions.NotFound(resource_id) - - def list(self): - return self.resources - - -class FakeDisplayResource(object): - NAME_ATTR = 'display_name' - - def __init__(self, _id, properties): - self.id = _id - try: - self.display_name = properties['display_name'] - except KeyError: - pass - - -class FakeDisplayManager(FakeManager): - - resource_class = FakeDisplayResource - - resources = [ - FakeDisplayResource('4242', {'display_name': 'entity_three'}), - ] - - -class FindResourceTestCase(test_base.BaseTestCase): - - def setUp(self): - super(FindResourceTestCase, self).setUp() - self.manager = FakeManager(None) - - def test_find_none(self): - """Test a few non-valid inputs.""" - self.assertRaises(exceptions.CommandError, - utils.find_resource, - self.manager, - 'asdf') - self.assertRaises(exceptions.CommandError, - utils.find_resource, - self.manager, - None) - self.assertRaises(exceptions.CommandError, - utils.find_resource, - self.manager, - {}) - - def test_find_by_integer_id(self): - output = utils.find_resource(self.manager, 1234) - self.assertEqual(output, self.manager.get('1234')) - - def test_find_by_str_id(self): - output = utils.find_resource(self.manager, '1234') - self.assertEqual(output, self.manager.get('1234')) - - def test_find_by_uuid(self): - output = utils.find_resource(self.manager, UUID) - self.assertEqual(output, self.manager.get(UUID)) - - def test_find_by_str_name(self): - output = utils.find_resource(self.manager, 'entity_one') - self.assertEqual(output, self.manager.get('1234')) - - def test_find_by_str_displayname(self): - display_manager = FakeDisplayManager(None) - output = utils.find_resource(display_manager, 'entity_three') - self.assertEqual(output, display_manager.get('4242')) -- cgit v1.2.1