Keystone auth integration
Keystoneauth integration. Change-Id: I692705731c02b2c3abf1643c5b12dfa1c7da05cf
This commit is contained in:
parent
dadb1c0f3c
commit
a0e791b775
@ -1,520 +0,0 @@
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from openstack.auth import service_catalog as catalog
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Do not use token before expiration
|
||||
BEST_BEFORE_SECONDS = 30
|
||||
|
||||
|
||||
class AccessInfo(object):
|
||||
"""Encapsulates a raw authentication token from keystone.
|
||||
|
||||
Provides helper methods for extracting useful values from that token.
|
||||
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
"""Construct access info."""
|
||||
self._info = kwargs
|
||||
|
||||
@classmethod
|
||||
def factory(cls, resp=None, body=None, **kwargs):
|
||||
"""AccessInfo factory.
|
||||
|
||||
Create AccessInfo object given a successful auth response & body
|
||||
or a user-provided dict.
|
||||
"""
|
||||
|
||||
if body is not None or len(kwargs):
|
||||
if AccessInfoV3.is_valid(body, **kwargs):
|
||||
token = None
|
||||
if resp:
|
||||
token = resp.headers['X-Subject-Token']
|
||||
if body:
|
||||
return AccessInfoV3(token, **body['token'])
|
||||
else:
|
||||
return AccessInfoV3(token, **kwargs)
|
||||
elif AccessInfoV2.is_valid(body, **kwargs):
|
||||
if body:
|
||||
return AccessInfoV2(**body['access'])
|
||||
else:
|
||||
return AccessInfoV2(**kwargs)
|
||||
else:
|
||||
raise NotImplementedError('Unrecognized auth response')
|
||||
else:
|
||||
return AccessInfoV2(**kwargs)
|
||||
|
||||
def will_expire_soon(self, best_before=BEST_BEFORE_SECONDS):
|
||||
"""Determines if expiration is about to occur.
|
||||
|
||||
:return: boolean : true if expiration is within the given duration
|
||||
|
||||
"""
|
||||
norm_expires = timeutils.normalize_time(self.expires)
|
||||
soon = (timeutils.utcnow() + datetime.timedelta(seconds=best_before))
|
||||
expiring = norm_expires < soon
|
||||
|
||||
if expiring:
|
||||
logger.debug("Token expiring at %s", norm_expires)
|
||||
|
||||
return expiring
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, body, **kwargs):
|
||||
"""Valid v2 or v3 token
|
||||
|
||||
Determines if processing v2 or v3 token given a successful
|
||||
auth body or a user-provided dict.
|
||||
|
||||
:return: boolean : true if auth body matches implementing class
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def has_service_catalog(self):
|
||||
"""Returns true if the authorization token has a service catalog.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
"""Authorize token
|
||||
|
||||
Returns the token_id associated with the auth request, to be used
|
||||
in headers for authenticating OpenStack API requests.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def expires(self):
|
||||
"""Returns the token expiration (as datetime object)
|
||||
|
||||
:returns: datetime
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
"""User name
|
||||
|
||||
Returns the username associated with the authentication request.
|
||||
Follows the pattern defined in the V2 API of first looking for 'name',
|
||||
returning that if available, and falling back to 'username' if name
|
||||
is unavailable.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
"""Returns the user id associated with the authentication request.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def user_domain_id(self):
|
||||
"""Users domain id
|
||||
|
||||
Returns the domain id of the user associated with the authentication
|
||||
request. For v2, it always returns 'default' which may be different
|
||||
from the Keystone configuration.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def user_domain_name(self):
|
||||
"""Users domain name
|
||||
|
||||
Returns the domain name of the user associated with the authentication
|
||||
request. For v2, it always returns 'Default' which may be different
|
||||
from the Keystone configuration.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def role_names(self):
|
||||
"""Role names
|
||||
|
||||
Returns a list of role names of the user associated with the
|
||||
authentication request.
|
||||
|
||||
:returns: a list of strings of role names
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def domain_name(self):
|
||||
"""Returns the domain name associated with the authentication token.
|
||||
|
||||
:returns: str or None (if no domain associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def domain_id(self):
|
||||
"""Returns the domain id associated with the authentication token.
|
||||
|
||||
:returns: str or None (if no domain associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
"""Returns the project name associated with the authentication request.
|
||||
|
||||
:returns: str or None (if no project associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def tenant_name(self):
|
||||
"""Synonym for project_name."""
|
||||
return self.project_name
|
||||
|
||||
@property
|
||||
def project_scoped(self):
|
||||
"""Returns true if the authorization token was scoped to a project.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def domain_scoped(self):
|
||||
"""Returns true if the authorization token was scoped to a domain.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
"""Returns the trust id associated with the authentication token.
|
||||
|
||||
:returns: str or None (if no trust associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def trust_scoped(self):
|
||||
"""Delegated to a trust.
|
||||
|
||||
Returns true if the authorization token was scoped as delegated in a
|
||||
trust, via the OS-TRUST v3 extension.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def project_id(self):
|
||||
"""Project id.
|
||||
|
||||
Returns the project ID associated with the authentication request,
|
||||
or None if the authentication request wasn't scoped to a project.
|
||||
|
||||
:returns: str or None (if no project associated with the token)
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def tenant_id(self):
|
||||
"""Synonym for project_id."""
|
||||
return self.project_id
|
||||
|
||||
@property
|
||||
def project_domain_id(self):
|
||||
"""Project domain.
|
||||
|
||||
Returns the domain id of the project associated with the authentication
|
||||
request. For v2, it returns 'default' if a project is scoped or None
|
||||
which may be different from the keystone configuration.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def project_domain_name(self):
|
||||
"""Project domain name.
|
||||
|
||||
Returns the domain name of the project associated with the
|
||||
authentication request. For v2, it returns 'Default' if a project is
|
||||
scoped or None which may be different from the keystone configuration.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""Returns the version of the auth token from identity service.
|
||||
|
||||
:returns: str
|
||||
"""
|
||||
return self._info.get('version')
|
||||
|
||||
def __repr__(self):
|
||||
return str(self._info)
|
||||
|
||||
|
||||
class AccessInfoV2(AccessInfo):
|
||||
"""Object for encapsulating a raw v2 auth token from identity service."""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(AccessInfoV2, self).__init__(**kwargs)
|
||||
self._info.update(version='v2.0')
|
||||
service_catalog = self._info['serviceCatalog']
|
||||
self.service_catalog = catalog.ServiceCatalogV2(service_catalog)
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, body, **kwargs):
|
||||
if body:
|
||||
return 'access' in body
|
||||
elif kwargs:
|
||||
return kwargs.get('version') == 'v2.0'
|
||||
else:
|
||||
return False
|
||||
|
||||
def has_service_catalog(self):
|
||||
return 'serviceCatalog' in self._info
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
return self._info['token']['id']
|
||||
|
||||
@property
|
||||
def expires(self):
|
||||
return timeutils.parse_isotime(self._info['token']['expires'])
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
user = self._info['user']
|
||||
return user.get('name', user.get('username'))
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
return self._info['user']['id']
|
||||
|
||||
@property
|
||||
def user_domain_id(self):
|
||||
return 'default'
|
||||
|
||||
@property
|
||||
def user_domain_name(self):
|
||||
return 'Default'
|
||||
|
||||
@property
|
||||
def role_names(self):
|
||||
return [r['name'] for r in self._info['user'].get('roles', [])]
|
||||
|
||||
@property
|
||||
def domain_name(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def domain_id(self):
|
||||
return None
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
try:
|
||||
tenant_dict = self._info['token']['tenant']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return tenant_dict.get('name')
|
||||
|
||||
# pre grizzly
|
||||
try:
|
||||
return self._info['user']['tenantName']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# pre diablo, keystone only provided a tenantId
|
||||
try:
|
||||
return self._info['token']['tenantId']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def project_scoped(self):
|
||||
return 'tenant' in self._info['token']
|
||||
|
||||
@property
|
||||
def domain_scoped(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
return self._info.get('trust', {}).get('id')
|
||||
|
||||
@property
|
||||
def trust_scoped(self):
|
||||
return 'trust' in self._info
|
||||
|
||||
@property
|
||||
def project_id(self):
|
||||
try:
|
||||
tenant_dict = self._info['token']['tenant']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return tenant_dict.get('id')
|
||||
|
||||
# pre grizzly
|
||||
try:
|
||||
return self._info['user']['tenantId']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# pre diablo
|
||||
try:
|
||||
return self._info['token']['tenantId']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@property
|
||||
def project_domain_id(self):
|
||||
if self.project_id:
|
||||
return 'default'
|
||||
|
||||
@property
|
||||
def project_domain_name(self):
|
||||
if self.project_id:
|
||||
return 'Default'
|
||||
|
||||
|
||||
class AccessInfoV3(AccessInfo):
|
||||
"""Object for encapsulating a raw v3 auth token from identity service."""
|
||||
|
||||
def __init__(self, token, **kwargs):
|
||||
super(AccessInfoV3, self).__init__(**kwargs)
|
||||
self._info.update(version='v3')
|
||||
self.service_catalog = catalog.ServiceCatalog(
|
||||
self._info.get('catalog'))
|
||||
if token:
|
||||
self._info.update(auth_token=token)
|
||||
|
||||
@classmethod
|
||||
def is_valid(cls, body, **kwargs):
|
||||
if body:
|
||||
return 'token' in body
|
||||
elif kwargs:
|
||||
return kwargs.get('version') == 'v3'
|
||||
else:
|
||||
return False
|
||||
|
||||
def has_service_catalog(self):
|
||||
return 'catalog' in self._info
|
||||
|
||||
@property
|
||||
def auth_token(self):
|
||||
return self._info['auth_token']
|
||||
|
||||
@property
|
||||
def expires(self):
|
||||
return timeutils.parse_isotime(self._info['expires_at'])
|
||||
|
||||
@property
|
||||
def user_id(self):
|
||||
return self._info['user']['id']
|
||||
|
||||
@property
|
||||
def user_domain_id(self):
|
||||
return self._info['user']['domain']['id']
|
||||
|
||||
@property
|
||||
def user_domain_name(self):
|
||||
return self._info['user']['domain']['name']
|
||||
|
||||
@property
|
||||
def role_names(self):
|
||||
return [r['name'] for r in self._info.get('roles', [])]
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self._info['user']['name']
|
||||
|
||||
@property
|
||||
def domain_name(self):
|
||||
domain = self._info.get('domain')
|
||||
if domain:
|
||||
return domain['name']
|
||||
|
||||
@property
|
||||
def domain_id(self):
|
||||
domain = self._info.get('domain')
|
||||
if domain:
|
||||
return domain['id']
|
||||
|
||||
@property
|
||||
def project_id(self):
|
||||
project = self._info.get('project')
|
||||
if project:
|
||||
return project['id']
|
||||
|
||||
@property
|
||||
def project_domain_id(self):
|
||||
project = self._info.get('project')
|
||||
if project:
|
||||
return project['domain']['id']
|
||||
|
||||
@property
|
||||
def project_domain_name(self):
|
||||
project = self._info.get('project')
|
||||
if project:
|
||||
return project['domain']['name']
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
project = self._info.get('project')
|
||||
if project:
|
||||
return project['name']
|
||||
|
||||
@property
|
||||
def project_scoped(self):
|
||||
return 'project' in self._info
|
||||
|
||||
@property
|
||||
def domain_scoped(self):
|
||||
return 'domain' in self._info
|
||||
|
||||
@property
|
||||
def trust_id(self):
|
||||
return self._info.get('OS-TRUST:trust', {}).get('id')
|
||||
|
||||
@property
|
||||
def trust_scoped(self):
|
||||
return 'OS-TRUST:trust' in self._info
|
@ -1,105 +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.
|
||||
|
||||
"""
|
||||
The base class for an authenticator. A plugin must define the get_token,
|
||||
get_endpoint, and get_versions methods. The simpliest example would be
|
||||
something that is just given such as::
|
||||
|
||||
class SimpleAuthenticator(base.BaseAuthPlugin):
|
||||
def __init__(self, token, endpoint, versions):
|
||||
super(SimpleAuthenticator, self).__init__()
|
||||
self.token = token
|
||||
self.endpoint = endpoint
|
||||
self.versions = versions
|
||||
|
||||
def get_token(self, transport, **kwargs):
|
||||
return self.token
|
||||
|
||||
def get_endpoint(self, transport, service, **kwargs):
|
||||
return self.endpoint
|
||||
|
||||
def get_versions(self, transport, service, **kwargs):
|
||||
return self.versions
|
||||
"""
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseAuthPlugin(object):
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_token(self, transport, **kwargs):
|
||||
"""Obtain a token.
|
||||
|
||||
How the token is obtained is up to the authenticator. If it is still
|
||||
valid it may be re-used, retrieved from cache or invoke an
|
||||
authentication request against a server.
|
||||
|
||||
There are no required kwargs. They are implementation specific to
|
||||
an authenticator.
|
||||
|
||||
An authenticator may raise an exception if it fails to retrieve a
|
||||
token.
|
||||
|
||||
:param transport: A transport object so the authenticator can make
|
||||
HTTP calls.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
:return string: A token to use.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_endpoint(self, transport, service, **kwargs):
|
||||
"""Return an endpoint for the client.
|
||||
|
||||
There are no required keyword arguments to ``get_endpoint`` as an
|
||||
authenticator should use best effort with the information available to
|
||||
determine the endpoint.
|
||||
|
||||
:param transport: Authenticator may need to make HTTP calls.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
:param service: Filter to identify the desired service.
|
||||
:type service: :class:`~openstack.service_filter.ServiceFilter`
|
||||
|
||||
:returns string: The base URL that will be used to talk to the
|
||||
required service or None if not available.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_versions(self, transport, service, **kwargs):
|
||||
"""Return the valid versions for the given service.
|
||||
|
||||
:param transport: Authenticator may need to make HTTP calls.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
:param service: Filter to identify the desired service.
|
||||
:type service: :class:`~openstack.service_filter.ServiceFilter`
|
||||
|
||||
:returns list: Returns list of versions that match the filter.
|
||||
"""
|
||||
|
||||
def invalidate(self):
|
||||
"""Invalidate the current authentication data.
|
||||
|
||||
This should result in fetching a new token on next call.
|
||||
|
||||
A plugin may be invalidated if an Unauthorized HTTP response is
|
||||
returned to indicate that the token may have been revoked or is
|
||||
otherwise now invalid.
|
||||
|
||||
:returns bool: True if there was something that the plugin did to
|
||||
invalidate. This means that it makes sense to try again.
|
||||
If nothing happens returns False to indicate give up.
|
||||
"""
|
||||
return False
|
@ -1,164 +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.
|
||||
|
||||
"""
|
||||
The base identity plugin. Identity plugins must define the authorize method.
|
||||
For examples of this class, see the v2 and v3 authentication plugins.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
from openstack.auth import base
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class BaseIdentityPlugin(base.BaseAuthPlugin):
|
||||
|
||||
#: Consider a token valid if it does not expire for this many seconds
|
||||
BEST_BEFORE_SECONDS = 1
|
||||
|
||||
def __init__(self, auth_url=None, reauthenticate=True):
|
||||
"""Create an identity authorization plugin.
|
||||
|
||||
:param string auth_url: Authorization URL
|
||||
:param bool reauthenticate: Should the plugin attempt reauthorization.
|
||||
"""
|
||||
super(BaseIdentityPlugin, self).__init__()
|
||||
self.auth_url = auth_url
|
||||
self.access_info = None
|
||||
self.reauthenticate = reauthenticate
|
||||
|
||||
@abc.abstractmethod
|
||||
def authorize(self, transport, **kwargs):
|
||||
"""Obtain access information from an OpenStack Identity Service.
|
||||
|
||||
Thus method will authenticate and fetch a new AccessInfo when
|
||||
invoked.
|
||||
|
||||
:param transport: A transport object for the authenticator.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
|
||||
:raises InvalidResponse: The response returned wasn't appropriate.
|
||||
:raises HttpError: An error from an invalid HTTP response.
|
||||
|
||||
:returns AccessInfo: Token access information.
|
||||
"""
|
||||
|
||||
def get_token(self, transport, **kwargs):
|
||||
"""Return a valid auth token.
|
||||
|
||||
If a valid token is not present then a new one will be fetched.
|
||||
|
||||
:param transport: A transport object for the authenticator.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
|
||||
:raises HttpError: An error from an invalid HTTP response.
|
||||
|
||||
:return string: A valid token.
|
||||
"""
|
||||
return self.get_access(transport).auth_token
|
||||
|
||||
def _needs_reauthenticate(self):
|
||||
"""Return if the existing token needs to be re-authenticated.
|
||||
|
||||
The token should be refreshed if it is about to expire.
|
||||
|
||||
:returns: True if the plugin should fetch a new token. False otherwise.
|
||||
"""
|
||||
if not self.access_info:
|
||||
# authentication was never fetched.
|
||||
return True
|
||||
|
||||
if not self.reauthenticate:
|
||||
# don't re-authenticate if it has been disallowed.
|
||||
return False
|
||||
|
||||
if self.access_info.will_expire_soon(self.BEST_BEFORE_SECONDS):
|
||||
# if it's about to expire we should re-authenticate now.
|
||||
self.invalidate()
|
||||
return True
|
||||
|
||||
# otherwise it's fine and use the existing one.
|
||||
return False
|
||||
|
||||
def get_access(self, transport):
|
||||
"""Fetch or return a current AccessInfo object.
|
||||
|
||||
If a valid AccessInfo is present then it is returned otherwise a new
|
||||
one will be fetched.
|
||||
|
||||
:param transport: A transport object for the authenticator.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
|
||||
:raises HttpError: An error from an invalid HTTP response.
|
||||
|
||||
:returns AccessInfo: Valid AccessInfo
|
||||
"""
|
||||
if self._needs_reauthenticate():
|
||||
logger.debug("Re-authentication required")
|
||||
self.access_info = self.authorize(transport)
|
||||
|
||||
return self.access_info
|
||||
|
||||
def invalidate(self):
|
||||
"""Invalidate the current authentication data.
|
||||
|
||||
This should result in fetching a new token on next call.
|
||||
|
||||
A plugin may be invalidated if an Unauthorized HTTP response is
|
||||
returned to indicate that the token may have been revoked or is
|
||||
otherwise now invalid.
|
||||
|
||||
:returns bool: True if there was something that the plugin did to
|
||||
invalidate. This means that it makes sense to try again.
|
||||
If nothing happens returns False to indicate give up.
|
||||
"""
|
||||
self.access_info = None
|
||||
return True
|
||||
|
||||
def get_endpoint(self, transport, service, **kwargs):
|
||||
"""Return a valid endpoint for a service.
|
||||
|
||||
If a valid token is not present then a new one will be fetched using
|
||||
the transport.
|
||||
|
||||
:param transport: A transport object for the authenticator.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
:param service: The filter to identify the desired service.
|
||||
:type service: :class:`~openstack.service_filter.ServiceFilter`
|
||||
|
||||
:raises HttpError: An error from an invalid HTTP response.
|
||||
|
||||
:return string or None: A valid endpoint URL or None if not available.
|
||||
"""
|
||||
service_catalog = self.get_access(transport).service_catalog
|
||||
return service_catalog.get_url(service)
|
||||
|
||||
def get_versions(self, transport, service, **kwargs):
|
||||
"""Return the valid versions for the given service.
|
||||
|
||||
:param Transport transport: Authenticator may need to make HTTP calls.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
:param ServiceFilter service: Filter to identify the desired service.
|
||||
:type service: :class:`~openstack.service_filter.ServiceFilter`
|
||||
|
||||
:returns list: Returns list of versions that match the filter.
|
||||
"""
|
||||
service_catalog = self.get_access(transport).service_catalog
|
||||
return service_catalog.get_versions(service)
|
@ -1,87 +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.
|
||||
|
||||
"""
|
||||
Identity discoverable authorization plugin must be constructed with an
|
||||
auhorization URL and a user id, user name or token. A user id or user name
|
||||
would also require a password. The arguments that apply to the selected v2
|
||||
or v3 plugin will be used. The rest of the arguments will be ignored. For
|
||||
example::
|
||||
|
||||
from openstack.auth.identity import discoverable
|
||||
from openstack import transport
|
||||
|
||||
args = {
|
||||
'password': 'openSesame',
|
||||
'auth_url': 'https://10.1.1.1:5000/v3/',
|
||||
'username': 'alibaba',
|
||||
}
|
||||
auth = discoverable.Auth(**args)
|
||||
xport = transport.Transport()
|
||||
accessInfo = auth.authorize(xport)
|
||||
"""
|
||||
|
||||
from openstack.auth.identity import base
|
||||
from openstack.auth.identity import v2
|
||||
from openstack.auth.identity import v3
|
||||
from openstack import exceptions
|
||||
|
||||
|
||||
class Auth(base.BaseIdentityPlugin):
|
||||
|
||||
#: Valid options for this plugin
|
||||
valid_options = set(list(v3.Password.valid_options)
|
||||
+ list(v3.Token.valid_options)
|
||||
+ list(v2.Password.valid_options)
|
||||
+ list(v2.Token.valid_options))
|
||||
|
||||
def __init__(self, auth_url=None, **auth_args):
|
||||
"""Construct an Identity Authentication Plugin.
|
||||
|
||||
This authorization plugin should be constructed with an auth_url
|
||||
and everything needed by either a v2 or v3 identity plugin.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authentication.
|
||||
|
||||
:raises TypeError: if a user_id, username or token is not provided.
|
||||
"""
|
||||
|
||||
super(Auth, self).__init__(auth_url=auth_url)
|
||||
|
||||
if not auth_url:
|
||||
msg = ("The authorization URL auth_url was not provided.")
|
||||
raise exceptions.AuthorizationFailure(msg)
|
||||
endpoint_version = auth_url.split('v')[-1][0]
|
||||
if endpoint_version == '2':
|
||||
if auth_args.get('token'):
|
||||
plugin = v2.Token
|
||||
else:
|
||||
plugin = v2.Password
|
||||
else:
|
||||
if auth_args.get('token'):
|
||||
plugin = v3.Token
|
||||
else:
|
||||
plugin = v3.Password
|
||||
valid_list = plugin.valid_options
|
||||
args = dict((n, auth_args[n]) for n in valid_list if n in auth_args)
|
||||
self.auth_plugin = plugin(auth_url, **args)
|
||||
|
||||
@property
|
||||
def token_url(self):
|
||||
"""The full URL where we will send authentication data."""
|
||||
return self.auth_plugin.token_url
|
||||
|
||||
def authorize(self, transport, **kwargs):
|
||||
return self.auth_plugin.authorize(transport, **kwargs)
|
||||
|
||||
def invalidate(self):
|
||||
return self.auth_plugin.invalidate()
|
@ -1,194 +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.
|
||||
|
||||
"""
|
||||
Identity v2 authorization plugins. The plugin must be constructed with an
|
||||
auhorization URL and a user id, user name or token. A user id or user name
|
||||
would also require a password. For example::
|
||||
|
||||
from openstack.auth.identity import v2
|
||||
from openstack import transport
|
||||
|
||||
args = {
|
||||
'password': 'openSesame',
|
||||
'auth_url': 'https://10.1.1.1:5000/v2.0/',
|
||||
'username': 'alibaba',
|
||||
}
|
||||
auth = v2.Auth(**args)
|
||||
xport = transport.Transport()
|
||||
accessInfo = auth.authorize(xport)
|
||||
"""
|
||||
|
||||
import abc
|
||||
import logging
|
||||
|
||||
import six
|
||||
|
||||
from openstack.auth import access
|
||||
from openstack.auth.identity import base
|
||||
from openstack import exceptions
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Auth(base.BaseIdentityPlugin):
|
||||
|
||||
def __init__(self, auth_url,
|
||||
trust_id=None,
|
||||
project_id=None,
|
||||
project_name=None,
|
||||
tenant_id=None,
|
||||
tenant_name=None,
|
||||
reauthenticate=True):
|
||||
"""Construct an Identity V2 Authentication Plugin.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param string project_id: Project ID for scoping.
|
||||
:param string project_name: Project name for scoping.
|
||||
:param string tenant_id: Tenant ID for project scoping.
|
||||
:param string tenant_name: Tenant name for project scoping.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current
|
||||
one is going to expire.
|
||||
(optional) default True
|
||||
"""
|
||||
super(Auth, self).__init__(auth_url=auth_url,
|
||||
reauthenticate=reauthenticate)
|
||||
|
||||
self.trust_id = trust_id
|
||||
self.tenant_id = project_id
|
||||
if not self.tenant_id:
|
||||
self.tenant_id = tenant_id
|
||||
self.tenant_name = project_name
|
||||
if not self.tenant_name:
|
||||
self.tenant_name = tenant_name
|
||||
|
||||
def authorize(self, transport, **kwargs):
|
||||
"""Obtain access information from an OpenStack Identity Service."""
|
||||
headers = {'Accept': 'application/json'}
|
||||
url = self.auth_url.rstrip('/') + '/tokens'
|
||||
params = {'auth': self.get_auth_data(headers)}
|
||||
|
||||
if self.tenant_id:
|
||||
params['auth']['tenantId'] = self.tenant_id
|
||||
elif self.tenant_name:
|
||||
params['auth']['tenantName'] = self.tenant_name
|
||||
if self.trust_id:
|
||||
params['auth']['trust_id'] = self.trust_id
|
||||
|
||||
_logger.debug('Making authentication request to %s', url)
|
||||
resp = transport.post(url, json=params, headers=headers)
|
||||
|
||||
try:
|
||||
resp_data = resp.json()['access']
|
||||
except (KeyError, ValueError):
|
||||
raise exceptions.InvalidResponse(response=resp)
|
||||
|
||||
return access.AccessInfoV2(**resp_data)
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_auth_data(self, headers=None):
|
||||
"""Return the authentication section of an auth plugin.
|
||||
|
||||
:param dict headers: The headers that will be sent with the auth
|
||||
request if a plugin needs to add to them.
|
||||
:return dict: A dict of authentication data for the auth type.
|
||||
"""
|
||||
|
||||
|
||||
_NOT_PASSED = object()
|
||||
|
||||
|
||||
class Password(Auth):
|
||||
|
||||
#: Valid options for Password plugin
|
||||
valid_options = {
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'username',
|
||||
'user_id',
|
||||
'password',
|
||||
'project_id',
|
||||
'project_name',
|
||||
'tenant_name',
|
||||
'tenant_id',
|
||||
'reauthenticate',
|
||||
'trust_id',
|
||||
}
|
||||
|
||||
def __init__(self, auth_url, username=_NOT_PASSED, password=None,
|
||||
user_id=_NOT_PASSED, **kwargs):
|
||||
"""A plugin for authenticating with a username and password.
|
||||
|
||||
A username or user_id must be provided.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string username: Username for authentication.
|
||||
:param string password: Password for authentication.
|
||||
:param string user_id: User ID for authentication.
|
||||
|
||||
:raises TypeError: if a user_id or username is not provided.
|
||||
"""
|
||||
super(Password, self).__init__(auth_url, **kwargs)
|
||||
|
||||
if username is _NOT_PASSED and user_id is _NOT_PASSED:
|
||||
msg = 'You need to specify either a username or user_id'
|
||||
raise TypeError(msg)
|
||||
if username is _NOT_PASSED:
|
||||
username = None
|
||||
if user_id is _NOT_PASSED:
|
||||
user_id = None
|
||||
|
||||
self.user_id = user_id
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def get_auth_data(self, headers=None):
|
||||
auth = {'password': self.password}
|
||||
|
||||
if self.username:
|
||||
auth['username'] = self.username
|
||||
elif self.user_id:
|
||||
auth['userId'] = self.user_id
|
||||
|
||||
return {'passwordCredentials': auth}
|
||||
|
||||
|
||||
class Token(Auth):
|
||||
|
||||
#: Valid options for this plugin
|
||||
valid_options = {
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'project_id',
|
||||
'project_name',
|
||||
'tenant_name',
|
||||
'tenant_id',
|
||||
'reauthenticate',
|
||||
'token',
|
||||
'trust_id',
|
||||
}
|
||||
|
||||
def __init__(self, auth_url, token, **kwargs):
|
||||
"""A plugin for authenticating with an existing token.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authorization.
|
||||
:param string token: Existing token for authentication.
|
||||
"""
|
||||
super(Token, self).__init__(auth_url, **kwargs)
|
||||
self.token = token
|
||||
|
||||
def get_auth_data(self, headers=None):
|
||||
if headers is not None:
|
||||
headers['X-Auth-Token'] = self.token
|
||||
return {'token': {'id': self.token}}
|
@ -1,314 +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.
|
||||
|
||||
"""
|
||||
Identity v3 authorization plugins. The plugin must be constructed with an
|
||||
auhorization URL and a user id, user name or token. A user id or user name
|
||||
would also require a password. For example::
|
||||
|
||||
from openstack.auth.identity import v3
|
||||
from openstack import transport
|
||||
|
||||
args = {
|
||||
'password': 'openSesame',
|
||||
'auth_url': 'https://10.1.1.1:5000/v3/',
|
||||
'username': 'alibaba',
|
||||
}
|
||||
auth = v3.Auth(**args)
|
||||
xport = transport.Transport()
|
||||
accessInfo = auth.authorize(xport)
|
||||
"""
|
||||
|
||||
import abc
|
||||
import logging
|
||||
|
||||
import six
|
||||
|
||||
from openstack.auth import access
|
||||
from openstack.auth.identity import base
|
||||
from openstack import exceptions
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Auth(base.BaseIdentityPlugin):
|
||||
|
||||
def __init__(self, auth_url, auth_methods,
|
||||
trust_id=None,
|
||||
domain_id=None,
|
||||
domain_name=None,
|
||||
project_id=None,
|
||||
project_name=None,
|
||||
project_domain_id=None,
|
||||
project_domain_name=None,
|
||||
reauthenticate=True,
|
||||
include_catalog=True):
|
||||
"""Construct an Identity V3 Authentication Plugin.
|
||||
|
||||
:param string auth_url: Identity service endpoint for authentication.
|
||||
:param list auth_methods: A collection of methods to authenticate with.
|
||||
:param string trust_id: Trust ID for trust scoping.
|
||||
:param string domain_id: Domain ID for domain scoping.
|
||||
:param string domain_name: Domain name for domain scoping.
|
||||
:param string project_id: Project ID for project scoping.
|
||||
:param string project_name: Project name for project scoping.
|
||||
:param string project_domain_id: Project's domain ID for project.
|
||||
:param string project_domain_name: Project's domain name for project.
|
||||
:param bool reauthenticate: Allow fetching a new token if the current
|
||||
one is going to expire.
|
||||
(optional) default True
|
||||
:param bool include_catalog: Include the service catalog in the
|
||||
returned token. (optional) default True.
|
||||
"""
|
||||
|
||||
super(Auth, self).__init__(auth_url=auth_url,
|
||||
reauthenticate=reauthenticate)
|
||||
|
||||
self.auth_methods = auth_methods
|
||||
self.trust_id = trust_id
|
||||
self.domain_id = domain_id
|
||||
self.domain_name = domain_name
|
||||
self.project_id = project_id
|
||||
self.project_name = project_name
|
||||
self.project_domain_id = project_domain_id
|
||||
self.project_domain_name = project_domain_name
|
||||
self.include_catalog = include_catalog
|
||||
|
||||
@property
|
||||
def token_url(self):
|
||||
"""The full URL where we will send authentication data."""
|
||||
return '%s/auth/tokens' % self.auth_url.rstrip('/')
|
||||
|
||||
def authorize(self, transport, **kwargs):
|
||||
"""Obtain access information from an OpenStack Identity Service."""
|
||||
headers = {'Accept': 'application/json'}
|
||||
body = {'auth': {'identity': {}}}
|
||||
ident = body['auth']['identity']
|
||||
|
||||
for method in self.auth_methods:
|
||||
name, auth_data = method.get_auth_data(transport, self, headers)
|
||||
ident.setdefault('methods', []).append(name)
|
||||
ident[name] = auth_data
|
||||
|
||||
if not ident:
|
||||
raise exceptions.AuthorizationFailure('Authentication method '
|
||||
'required (e.g. password)')
|
||||
|
||||
mutual_exclusion = [bool(self.domain_id or self.domain_name),
|
||||
bool(self.project_id or self.project_name),
|
||||
bool(self.trust_id)]
|
||||
|
||||
if sum(mutual_exclusion) > 1:
|
||||
raise exceptions.AuthorizationFailure('Authentication cannot be '
|
||||
'scoped to multiple '
|
||||
'targets. Pick one of: '
|
||||
'project, domain or trust')
|
||||
|
||||
if self.domain_id:
|
||||
body['auth']['scope'] = {'domain': {'id': self.domain_id}}
|
||||
elif self.domain_name:
|
||||
body['auth']['scope'] = {'domain': {'name': self.domain_name}}
|
||||
elif self.project_id:
|
||||
body['auth']['scope'] = {'project': {'id': self.project_id}}
|
||||
elif self.project_name:
|
||||
scope = body['auth']['scope'] = {'project': {}}
|
||||
scope['project']['name'] = self.project_name
|
||||
|
||||
if self.project_domain_id:
|
||||
scope['project']['domain'] = {'id': self.project_domain_id}
|
||||
elif self.project_domain_name:
|
||||
scope['project']['domain'] = {'name': self.project_domain_name}
|
||||
elif self.trust_id:
|
||||
body['auth']['scope'] = {'OS-TRUST:trust': {'id': self.trust_id}}
|
||||
|
||||
# NOTE(jamielennox): we add nocatalog here rather than in token_url
|
||||
# directly as some federation plugins require the base token_url
|
||||
token_url = self.token_url
|
||||
if not self.include_catalog:
|
||||
token_url += '?nocatalog'
|
||||
|
||||
_logger.debug('Making authentication request to %s', token_url)
|
||||
resp = transport.post(token_url, json=body, headers=headers)
|
||||
|
||||
try:
|
||||
resp_data = resp.json()['token']
|
||||
except (KeyError, ValueError):
|
||||
raise exceptions.InvalidResponse(response=resp)
|
||||
|
||||
return access.AccessInfoV3(resp.headers['X-Subject-Token'],
|
||||
**resp_data)
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AuthMethod(object):
|
||||
"""One part of a V3 Authentication strategy.
|
||||
|
||||
V3 Tokens allow multiple methods to be presented when authentication
|
||||
against the server. Each one of these methods is implemented by an
|
||||
AuthMethod.
|
||||
|
||||
Note: When implementing an AuthMethod use the method_parameters
|
||||
and do not use positional arguments. Otherwise they can't be picked up by
|
||||
the factory method and don't work as well with AuthConstructors.
|
||||
"""
|
||||
|
||||
_method_parameters = []
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
for param in self._method_parameters:
|
||||
setattr(self, param, kwargs.pop(param, None))
|
||||
|
||||
if kwargs:
|
||||
msg = "Unexpected Attributes: %s" % ", ".join(kwargs.keys())
|
||||
raise AttributeError(msg)
|
||||
|
||||
@classmethod
|
||||
def _extract_kwargs(cls, kwargs):
|
||||
"""Remove parameters related to this method from other kwargs."""
|
||||
return dict([(p, kwargs.pop(p, None))
|
||||
for p in cls._method_parameters])
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_auth_data(self, transport, auth, headers, **kwargs):
|
||||
"""Return the authentication section of an auth plugin.
|
||||
|
||||
:param Transport transport: The communication transport.
|
||||
:param Auth auth: The auth plugin calling the method.
|
||||
:param dict headers: The headers that will be sent with the auth
|
||||
request if a plugin needs to add to them.
|
||||
:return tuple(string, dict): The identifier of this plugin and a dict
|
||||
of authentication data for the auth type.
|
||||
"""
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class AuthConstructor(Auth):
|
||||
"""AuthConstructor creates an authentication plugin with one method.
|
||||
|
||||
AuthConstructor is a means of creating an authentication plugin that
|
||||
contains only one authentication method. This is generally the required
|
||||
usage.
|
||||
|
||||
An AuthConstructor creates an AuthMethod based on the method's
|
||||
arguments and the auth_method_class defined by the plugin. It then
|
||||
creates the auth plugin with only that authentication method.
|
||||
"""
|
||||
|
||||
_auth_method_class = None
|
||||
|
||||
def __init__(self, auth_url, *args, **kwargs):
|
||||
method_kwargs = self._auth_method_class._extract_kwargs(kwargs)
|
||||
method = self._auth_method_class(*args, **method_kwargs)
|
||||
super(AuthConstructor, self).__init__(auth_url, [method], **kwargs)
|
||||
|
||||
|
||||
class PasswordMethod(AuthMethod):
|
||||
|
||||
_method_parameters = ['user_id',
|
||||
'username',
|
||||
'user_domain_id',
|
||||
'user_domain_name',
|
||||
'password']
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Construct a User/Password based authentication method.
|
||||
|
||||
:param string password: Password for authentication.
|
||||
:param string username: Username for authentication.
|
||||
:param string user_id: User ID for authentication.
|
||||
:param string user_domain_id: User's domain ID for authentication.
|
||||
:param string user_domain_name: User's domain name for authentication.
|
||||
"""
|
||||
super(PasswordMethod, self).__init__(**kwargs)
|
||||
|
||||
def get_auth_data(self, transport, auth, headers, **kwargs):
|
||||
"""Identity v3 password authentication data."""
|
||||
user = {'password': self.password}
|
||||
|
||||
if self.user_id:
|
||||
user['id'] = self.user_id
|
||||
elif self.username:
|
||||
user['name'] = self.username
|
||||
|
||||
if self.user_domain_id:
|
||||
user['domain'] = {'id': self.user_domain_id}
|
||||
elif self.user_domain_name:
|
||||
user['domain'] = {'name': self.user_domain_name}
|
||||
|
||||
return 'password', {'user': user}
|
||||
|
||||
|
||||
class Password(AuthConstructor):
|
||||
|
||||
#: Valid options for this plugin
|
||||
valid_options = {
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'domain_id',
|
||||
'domain_name',
|
||||
'password',
|
||||
'project_domain_id',
|
||||
'project_domain_name',
|
||||
'project_id',
|
||||
'project_name',
|
||||
'reauthenticate',
|
||||
'trust_id',
|
||||
'user_domain_id',
|
||||
'user_domain_name',
|
||||
'user_id',
|
||||
'username',
|
||||
}
|
||||
|
||||
_auth_method_class = PasswordMethod
|
||||
|
||||
|
||||
class TokenMethod(AuthMethod):
|
||||
|
||||
_method_parameters = ['token',
|
||||
'user_domain_id',
|
||||
'user_domain_name']
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
"""Construct an Auth plugin to fetch a token from a token.
|
||||
|
||||
:param string token: Token for authentication.
|
||||
"""
|
||||
super(TokenMethod, self).__init__(**kwargs)
|
||||
|
||||
def get_auth_data(self, transport, auth, headers, **kwargs):
|
||||
headers['X-Auth-Token'] = self.token
|
||||
return 'token', {'id': self.token}
|
||||
|
||||
|
||||
class Token(AuthConstructor):
|
||||
|
||||
#: Valid options for this plugin
|
||||
valid_options = {
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'domain_id',
|
||||
'domain_name',
|
||||
'project_domain_id',
|
||||
'project_domain_name',
|
||||
'project_id',
|
||||
'project_name',
|
||||
'reauthenticate',
|
||||
'token',
|
||||
'trust_id',
|
||||
'user_domain_id',
|
||||
'user_domain_name',
|
||||
}
|
||||
|
||||
_auth_method_class = TokenMethod
|
||||
|
||||
def __init__(self, auth_url, token, **kwargs):
|
||||
super(Token, self).__init__(auth_url, token=token, **kwargs)
|
@ -1,165 +0,0 @@
|
||||
# Copyright 2011 OpenStack Foundation
|
||||
# Copyright 2011, Piston Cloud Computing, Inc.
|
||||
# Copyright 2011 Nebula, Inc.
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import copy
|
||||
import re
|
||||
|
||||
import six
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from openstack import exceptions
|
||||
|
||||
|
||||
class ServiceCatalog(object):
|
||||
"""Helper methods for dealing with a Keystone Service Catalog."""
|
||||
|
||||
def __init__(self, catalog):
|
||||
if catalog is None:
|
||||
self.catalog = []
|
||||
raise exceptions.EmptyCatalog('The service catalog is missing')
|
||||
self.catalog = copy.deepcopy(catalog)
|
||||
self._normalize()
|
||||
self._parse_endpoints()
|
||||
|
||||
def _normalize(self):
|
||||
return
|
||||
|
||||
def _parse_endpoints(self):
|
||||
pattern = re.compile('/v\d[\d.]*')
|
||||
for service in self.catalog:
|
||||
for endpoint in service.get('endpoints', []):
|
||||
url = endpoint.get('url', '')
|
||||
if not url:
|
||||
continue
|
||||
split = parse.urlsplit(url)
|
||||
split = list(split)
|
||||
path = split[2]
|
||||
vstr = pattern.search(path)
|
||||
if not vstr:
|
||||
endpoint['url'] = (endpoint['url'].rstrip('/') +
|
||||
'/%(version)s')
|
||||
continue
|
||||
start, end = vstr.span()
|
||||
endpoint['version'] = path[start + 1:end]
|
||||
path = path[:start] + '/%(version)s' + path[end:]
|
||||
path = path.rstrip('/')
|
||||
split[2] = path
|
||||
endpoint['url'] = parse.urlunsplit(split)
|
||||
|
||||
def _get_endpoints(self, filtration):
|
||||
"""Fetch and filter urls and version tuples for the specified service.
|
||||
|
||||
Returns a tuple containting the url and version for the specified
|
||||
service (or all) containing the specified type, name, region and
|
||||
interface.
|
||||
"""
|
||||
eps = []
|
||||
for service in self.catalog:
|
||||
if not filtration.match_service_type(service.get('type')):
|
||||
continue
|
||||
if not filtration.match_service_name(service.get('name')):
|
||||
continue
|
||||
for endpoint in service.get('endpoints', []):
|
||||
if not filtration.match_region(endpoint.get('region', None)):
|
||||
continue
|
||||
if not filtration.match_interface(endpoint.get('interface')):
|
||||
continue
|
||||
url = endpoint.get('url', None)
|
||||
if not url:
|
||||
continue
|
||||
version = endpoint.get('version', None)
|
||||
eps.append((url, version))
|
||||
return eps
|
||||
|
||||
def get_urls(self, filtration):
|
||||
"""Fetch the urls based on the given service filter.
|
||||
|
||||
Returns a list of urls based on the service filter. If not endpoints
|
||||
are found that match the service filter, an empty list is returned.
|
||||
The filter may specify type, name, region, version and interface.
|
||||
"""
|
||||
urls = []
|
||||
for url, version in self._get_endpoints(filtration):
|
||||
version = filtration.get_version_path(version)
|
||||
url = url % {'version': version}
|
||||
urls.append(url)
|
||||
return urls
|
||||
|
||||
def get_versions(self, filtration):
|
||||
"""Fetch the versions based on the given service filter.
|
||||
|
||||
Returns a list of versions based on the service filter. If there is
|
||||
no endpoint matching the filter, None will be returned. An empty
|
||||
list of versions means the service is supported, but no version is
|
||||
specified in the service catalog. The filter may specify type, name,
|
||||
region, version and interface.
|
||||
"""
|
||||
vers = None
|
||||
for url, version in self._get_endpoints(filtration):
|
||||
vers = vers or []
|
||||
if not version:
|
||||
continue
|
||||
if filtration.version and version != filtration.version:
|
||||
continue
|
||||
vers.append(version)
|
||||
return vers
|
||||
|
||||
def get_url(self, service):
|
||||
"""Fetch an endpoint from the service catalog.
|
||||
|
||||
Get the first endpoint that matches the service filter.
|
||||
|
||||
:param ServiceFilter service: The filter to identify the desired
|
||||
service.
|
||||
"""
|
||||
urls = self.get_urls(service)
|
||||
if len(urls) < 1:
|
||||
message = "Endpoint not found for %s" % six.text_type(service)
|
||||
raise exceptions.EndpointNotFound(message)
|
||||
return urls[0]
|
||||
|
||||
|
||||
class ServiceCatalogV2(ServiceCatalog):
|
||||
"""The V2 service catalog from Keystone."""
|
||||
|
||||
def _extract_details(self, endpoint, interface):
|
||||
value = {
|
||||
'interface': interface,
|
||||
'url': endpoint['%sURL' % interface]
|
||||
}
|
||||
region = endpoint.get('region', None)
|
||||
if region:
|
||||
value['region'] = region
|
||||
|
||||
return value
|
||||
|
||||
def _normalize(self):
|
||||
"""Handle differences in the way v2 and v3 catalogs specify endpoints.
|
||||
|
||||
Normallize the v2 service catalog to the endpoint types used in v3.
|
||||
"""
|
||||
for service in self.catalog:
|
||||
eps = []
|
||||
for endpoint in service['endpoints']:
|
||||
if 'publicURL' in endpoint:
|
||||
eps += [self._extract_details(endpoint, "public")]
|
||||
if 'internalURL' in endpoint:
|
||||
eps += [self._extract_details(endpoint, "internal")]
|
||||
if 'adminURL' in endpoint:
|
||||
eps += [self._extract_details(endpoint, "admin")]
|
||||
service['endpoints'] = eps
|
@ -1,31 +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 openstack.auth import base
|
||||
|
||||
|
||||
class Token(base.BaseAuthPlugin):
|
||||
"""A provider that will always use the given token and endpoint.
|
||||
|
||||
This is really only useful for testing and in certain CLI cases where you
|
||||
have a known endpoint and admin token that you want to use.
|
||||
"""
|
||||
|
||||
def __init__(self, endpoint, token):
|
||||
# NOTE(jamielennox): endpoint is reserved for when plugins
|
||||
# can be used to provide that information
|
||||
self.endpoint = endpoint
|
||||
self.token = token
|
||||
|
||||
def get_token(self, session):
|
||||
return self.token
|
@ -73,8 +73,8 @@ class Cluster(resource.Resource):
|
||||
|
||||
def action(self, session, body):
|
||||
url = utils.urljoin(self.base_path, self.id, 'action')
|
||||
resp = session.put(url, service=self.service, json=body).body
|
||||
return resp
|
||||
resp = session.put(url, endpoint_filter=self.service, json=body)
|
||||
return resp.json()
|
||||
|
||||
def add_nodes(self, session, nodes):
|
||||
body = {
|
||||
|
@ -76,8 +76,8 @@ class Node(resource.Resource):
|
||||
:param body: The body of action to be sent.
|
||||
"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'action')
|
||||
resp = session.put(url, service=self.service, json=body).body
|
||||
return resp
|
||||
resp = session.put(url, endpoint_filter=self.service, json=body)
|
||||
return resp.json()
|
||||
|
||||
def join(self, session, cluster_id):
|
||||
"""An action procedure for the node to join a cluster.
|
||||
|
@ -90,11 +90,12 @@ class Server(resource.Resource):
|
||||
"""Preform server actions given the message body."""
|
||||
url = utils.urljoin(self.base_path, self.id, 'action')
|
||||
if has_response:
|
||||
resp = session.post(url, service=self.service, json=body)
|
||||
resp = session.post(url, endpoint_filter=self.service, json=body)
|
||||
else:
|
||||
headers = {'Accept': ''}
|
||||
resp = session.post(
|
||||
url, service=self.service, json=body, accept=None)
|
||||
return resp
|
||||
url, endpoint_filter=self.service, json=body, headers=headers)
|
||||
return resp.json()
|
||||
|
||||
def change_password(self, session, new_password):
|
||||
"""Change the administrator password to the given password."""
|
||||
|
@ -39,9 +39,10 @@ class ServerIP(resource.Resource):
|
||||
@classmethod
|
||||
def list(cls, session, path_args=None, **params):
|
||||
url = cls._get_url(path_args)
|
||||
resp = session.get(url, service=cls.service, params=params)
|
||||
resp = session.get(url, endpoint_filter=cls.service, params=params)
|
||||
resp = resp.json()
|
||||
ray = []
|
||||
for network_label, addresses in six.iteritems(resp.body['addresses']):
|
||||
for network_label, addresses in six.iteritems(resp['addresses']):
|
||||
for address in addresses:
|
||||
record = {
|
||||
'server_id': path_args['server_id'],
|
||||
|
@ -41,7 +41,8 @@ class ServerMeta(resource.Resource):
|
||||
def create_by_id(cls, session, attrs, resource_id=None, path_args=None):
|
||||
url = cls._get_url(path_args, resource_id)
|
||||
body = {cls.resource_key: {attrs['key']: attrs['value']}}
|
||||
resp = session.put(url, service=cls.service, json=body).body
|
||||
resp = session.put(url, endpoint_filter=cls.service, json=body)
|
||||
resp = resp.json()
|
||||
return {'key': resource_id,
|
||||
'value': resp[cls.resource_key][resource_id]}
|
||||
|
||||
@ -49,7 +50,8 @@ class ServerMeta(resource.Resource):
|
||||
def get_data_by_id(cls, session, resource_id, path_args=None,
|
||||
include_headers=False):
|
||||
url = cls._get_url(path_args, resource_id)
|
||||
resp = session.get(url, service=cls.service).body
|
||||
resp = session.get(url, endpoint_filter=cls.service)
|
||||
resp = resp.json()
|
||||
return {'key': resource_id,
|
||||
'value': resp[cls.resource_key][resource_id]}
|
||||
|
||||
@ -60,12 +62,14 @@ class ServerMeta(resource.Resource):
|
||||
@classmethod
|
||||
def delete_by_id(cls, session, resource_id, path_args=None):
|
||||
url = cls._get_url(path_args, resource_id)
|
||||
session.delete(url, service=cls.service, accept=None)
|
||||
headers = {'Accept': ''}
|
||||
session.delete(url, endpoint_filter=cls.service, headers=headers)
|
||||
|
||||
@classmethod
|
||||
def list(cls, session, path_args=None, **params):
|
||||
url = '/servers/%(server_id)s/metadata' % path_args
|
||||
resp = session.get(url, service=cls.service, params=params).body
|
||||
resp = session.get(url, endpoint_filter=cls.service, params=params)
|
||||
resp = resp.json()
|
||||
resp = resp['metadata']
|
||||
return [cls.existing(server_id=path_args['server_id'], key=key,
|
||||
value=value)
|
||||
|
@ -35,7 +35,8 @@ class ServerMetadata(resource.Resource):
|
||||
no_id.pop('server_id')
|
||||
body = {"metadata": no_id}
|
||||
url = cls._get_url(path_args)
|
||||
resp = session.put(url, service=cls.service, json=body).body
|
||||
resp = session.put(url, endpoint_filter=cls.service, json=body)
|
||||
resp = resp.json()
|
||||
attrs = resp["metadata"].copy()
|
||||
attrs['server_id'] = resource_id
|
||||
return attrs
|
||||
@ -44,8 +45,8 @@ class ServerMetadata(resource.Resource):
|
||||
def get_data_by_id(cls, session, resource_id, path_args=None,
|
||||
include_headers=False):
|
||||
url = cls._get_url(path_args)
|
||||
resp = session.get(url, service=cls.service).body
|
||||
return resp[cls.resource_key]
|
||||
resp = session.get(url, endpoint_filter=cls.service)
|
||||
return resp.json()[cls.resource_key]
|
||||
|
||||
@classmethod
|
||||
def update_by_id(cls, session, resource_id, attrs, path_args=None):
|
||||
|
@ -60,13 +60,12 @@ try to find it and if that fails, you would create it::
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from keystoneauth1.loading import base as ksa_loader
|
||||
import os_client_config
|
||||
|
||||
from openstack import module_loader
|
||||
from openstack import profile
|
||||
from openstack import profile as _profile
|
||||
from openstack import proxy
|
||||
from openstack import session
|
||||
from openstack import transport as xport
|
||||
from openstack import session as _session
|
||||
from openstack import utils
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
@ -95,7 +94,7 @@ def from_config(cloud_name=None, cloud_config=None, options=None):
|
||||
"""
|
||||
# TODO(thowe): I proposed that service name defaults to None in OCC
|
||||
defaults = {}
|
||||
prof = profile.Profile()
|
||||
prof = _profile.Profile()
|
||||
services = [service.service_type for service in prof.get_services()]
|
||||
for service in services:
|
||||
defaults[service + '_service_name'] = None
|
||||
@ -119,16 +118,25 @@ def from_config(cloud_name=None, cloud_config=None, options=None):
|
||||
version = str(version)
|
||||
if not version.startswith("v"):
|
||||
version = "v" + version
|
||||
prof.set_version(service, version)
|
||||
prof.set_name(service, cloud_config.get_service_name(service))
|
||||
prof.set_interface(
|
||||
service, cloud_config.get_interface(service))
|
||||
prof.set_region(service, cloud_config.get_region_name(service))
|
||||
prof.set_version(service, version)
|
||||
name = cloud_config.get_service_name(service)
|
||||
if name:
|
||||
prof.set_name(service, name)
|
||||
interface = cloud_config.get_interface(service)
|
||||
if interface:
|
||||
prof.set_interface(service, interface)
|
||||
|
||||
region = cloud_config.get_region_name(service)
|
||||
if region:
|
||||
for service in services:
|
||||
prof.set_region(service, region)
|
||||
|
||||
# Auth
|
||||
auth = cloud_config.config['auth']
|
||||
# TODO(thowe) We should be using auth_type
|
||||
auth['auth_plugin'] = cloud_config.config['auth_type']
|
||||
if 'cacert' in auth:
|
||||
auth['verify'] = auth.pop('cacert')
|
||||
if 'cacert' in cloud_config.config:
|
||||
auth['verify'] = cloud_config.config['cacert']
|
||||
if 'insecure' in cloud_config.config:
|
||||
@ -139,22 +147,20 @@ def from_config(cloud_name=None, cloud_config=None, options=None):
|
||||
|
||||
class Connection(object):
|
||||
|
||||
def __init__(self, transport=None, authenticator=None, profile=None,
|
||||
verify=True, user_agent=None,
|
||||
auth_plugin=None, **auth_args):
|
||||
def __init__(self, session=None, authenticator=None, profile=None,
|
||||
verify=True, user_agent=None, auth_plugin=None, **auth_args):
|
||||
"""Create a context for a connection to a cloud provider.
|
||||
|
||||
A connection needs a transport and an authenticator. The user may pass
|
||||
in a transport and authenticator they want to use or they may pass in
|
||||
the parameters to create a transport and authenticator. The connection
|
||||
creates a
|
||||
:class:`~openstack.session.Session` which uses the transport
|
||||
:class:`~openstack.session.Session` which uses the profile
|
||||
and authenticator to perform HTTP requests.
|
||||
|
||||
:param transport: A transport object such as that was previously
|
||||
created. If this parameter is not passed in, the connection will
|
||||
create a transport.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
:param session: A session object compatible with
|
||||
:class:`~openstack.session.Session`.
|
||||
:type session: :class:`~openstack.session.Session`
|
||||
:param authenticator: An authenticator derived from the base
|
||||
authenticator plugin that was previously created. Two common
|
||||
authentication identity plugins are
|
||||
@ -187,33 +193,33 @@ class Connection(object):
|
||||
authentication arguments that are used by the authentication
|
||||
plugin.
|
||||
"""
|
||||
self.transport = self._create_transport(transport, verify, user_agent)
|
||||
self.authenticator = self._create_authenticator(authenticator,
|
||||
auth_plugin,
|
||||
**auth_args)
|
||||
self.session = session.Session(self.transport, self.authenticator,
|
||||
profile)
|
||||
self.profile = profile if profile else _profile.Profile()
|
||||
self.session = session if session else _session.Session(
|
||||
self.profile, auth=self.authenticator, verify=verify,
|
||||
user_agent=user_agent)
|
||||
self._open()
|
||||
|
||||
def _create_transport(self, transport, verify, user_agent):
|
||||
if transport:
|
||||
return transport
|
||||
return xport.Transport(verify=verify, user_agent=user_agent)
|
||||
|
||||
def _create_authenticator(self, authenticator, auth_plugin, **auth_args):
|
||||
def _create_authenticator(self, authenticator, auth_plugin, **args):
|
||||
if authenticator:
|
||||
return authenticator
|
||||
plugin = module_loader.ModuleLoader().get_auth_plugin(auth_plugin)
|
||||
valid_list = plugin.valid_options
|
||||
args = dict((n, auth_args[n]) for n in valid_list if n in auth_args)
|
||||
return plugin(**args)
|
||||
# TODO(thowe): Jamie was suggesting we should support other
|
||||
# ways of loading the plugin
|
||||
loader = ksa_loader.get_plugin_loader(auth_plugin)
|
||||
load_args = {}
|
||||
for opt in loader.get_options():
|
||||
if args.get(opt.dest):
|
||||
load_args[opt.dest] = args[opt.dest]
|
||||
return loader.load_from_options(**load_args)
|
||||
|
||||
def _open(self):
|
||||
"""Open the connection.
|
||||
|
||||
NOTE(thowe): Have this set up some lazy loader instead.
|
||||
"""
|
||||
for service in self.session.get_services():
|
||||
for service in self.profile.get_services():
|
||||
self._load(service)
|
||||
|
||||
def _load(self, service):
|
||||
|
@ -52,8 +52,8 @@ class Instance(resource.Resource):
|
||||
the login credentials.
|
||||
"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'root')
|
||||
resp = session.post(url, service=self.service).body
|
||||
return resp['user']
|
||||
resp = session.post(url, endpoint_filter=self.service)
|
||||
return resp.json()['user']
|
||||
|
||||
def is_root_enabled(self, session):
|
||||
"""Determine if root is enabled on an instance.
|
||||
@ -66,8 +66,8 @@ class Instance(resource.Resource):
|
||||
instance or ``False`` otherwise.
|
||||
"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'root')
|
||||
resp = session.get(url, service=self.service).body
|
||||
return resp['rootEnabled']
|
||||
resp = session.get(url, endpoint_filter=self.service)
|
||||
return resp.json()['rootEnabled']
|
||||
|
||||
def restart(self, session):
|
||||
"""Restart the database instance
|
||||
@ -76,7 +76,7 @@ class Instance(resource.Resource):
|
||||
"""
|
||||
body = {'restart': {}}
|
||||
url = utils.urljoin(self.base_path, self.id, 'action')
|
||||
session.post(url, service=self.service, json=body)
|
||||
session.post(url, endpoint_filter=self.service, json=body)
|
||||
|
||||
def resize(self, session, flavor_reference):
|
||||
"""Resize the database instance
|
||||
@ -85,7 +85,7 @@ class Instance(resource.Resource):
|
||||
"""
|
||||
body = {'resize': {'flavorRef': flavor_reference}}
|
||||
url = utils.urljoin(self.base_path, self.id, 'action')
|
||||
session.post(url, service=self.service, json=body)
|
||||
session.post(url, endpoint_filter=self.service, json=body)
|
||||
|
||||
def resize_volume(self, session, volume_size):
|
||||
"""Resize the volume attached to the instance
|
||||
@ -94,4 +94,4 @@ class Instance(resource.Resource):
|
||||
"""
|
||||
body = {'resize': {'volume': volume_size}}
|
||||
url = utils.urljoin(self.base_path, self.id, 'action')
|
||||
session.post(url, service=self.service, json=body)
|
||||
session.post(url, endpoint_filter=self.service, json=body)
|
||||
|
@ -42,5 +42,5 @@ class User(resource.Resource):
|
||||
url = cls._get_url(path_args)
|
||||
# Create expects an array of users
|
||||
body = {'users': [attrs]}
|
||||
resp = session.post(url, service=cls.service, json=body).body
|
||||
return resp
|
||||
resp = session.post(url, endpoint_filter=cls.service, json=body)
|
||||
return resp.json()
|
||||
|
@ -46,6 +46,8 @@ class Extension(resource.Resource):
|
||||
|
||||
@classmethod
|
||||
def list(cls, session, **params):
|
||||
resp = session.get(cls.base_path, service=cls.service, params=params)
|
||||
for data in resp.body[cls.resources_key]['values']:
|
||||
resp = session.get(cls.base_path, endpoint_filter=cls.service,
|
||||
params=params)
|
||||
resp = resp.json()
|
||||
for data in resp[cls.resources_key]['values']:
|
||||
yield cls.existing(**data)
|
||||
|
@ -32,6 +32,8 @@ class Version(resource.Resource):
|
||||
|
||||
@classmethod
|
||||
def list(cls, session, **params):
|
||||
resp = session.get(cls.base_path, service=cls.service, params=params)
|
||||
for data in resp.body[cls.resources_key]['values']:
|
||||
resp = session.get(cls.base_path, endpoint_filter=cls.service,
|
||||
params=params)
|
||||
resp = resp.json()
|
||||
for data in resp[cls.resources_key]['values']:
|
||||
yield cls.existing(**data)
|
||||
|
@ -83,6 +83,6 @@ class Image(resource.Resource):
|
||||
|
||||
headers = self.get_headers()
|
||||
headers['Content-Type'] = 'application/octet-stream'
|
||||
|
||||
session.put(url, service=self.service, data=self.data, accept=None,
|
||||
headers['Accept'] = ''
|
||||
session.put(url, endpoint_filter=self.service, data=self.data,
|
||||
headers=headers)
|
||||
|
@ -37,7 +37,8 @@ class Tag(resource.Resource):
|
||||
:return: ``None``
|
||||
"""
|
||||
url = self._get_url({"image": self.image.id}, tag)
|
||||
session.put(url, service=self.service, accept=None)
|
||||
headers = {'Accept': ''}
|
||||
session.put(url, endpoint_filter=self.service, headers=headers)
|
||||
|
||||
def delete(self, session, tag):
|
||||
"""Delete a tag on the image
|
||||
@ -49,4 +50,5 @@ class Tag(resource.Resource):
|
||||
:return: ``None``
|
||||
"""
|
||||
url = self._get_url({"image": self.image.id}, tag)
|
||||
session.delete(url, service=self.service, accept=None)
|
||||
headers = {'Accept': ''}
|
||||
session.delete(url, endpoint_filter=self.service, headers=headers)
|
||||
|
@ -60,10 +60,11 @@ class Claim(resource.Resource):
|
||||
body = []
|
||||
|
||||
try:
|
||||
resp = session.post(url, service=cls.service, headers=headers,
|
||||
resp = session.post(url, endpoint_filter=cls.service,
|
||||
headers=headers,
|
||||
data=json.dumps(claim, cls=ClaimEncoder),
|
||||
params=params)
|
||||
body = resp.body
|
||||
body = resp.json()
|
||||
except exceptions.InvalidResponse as e:
|
||||
# The Message Service will respond with a 204 and no content in
|
||||
# the body when there are no messages to claim. The transport
|
||||
|
@ -69,11 +69,12 @@ class Message(resource.Resource):
|
||||
url = cls._get_url({'queue_name': messages[0].queue})
|
||||
headers = {'Client-ID': messages[0].client}
|
||||
|
||||
resp = session.post(url, service=cls.service, headers=headers,
|
||||
resp = session.post(url, endpoint_filter=cls.service, headers=headers,
|
||||
data=json.dumps(messages, cls=MessageEncoder))
|
||||
resp = resp.json()
|
||||
|
||||
messages_created = []
|
||||
hrefs = resp.body['resources']
|
||||
hrefs = resp['resources']
|
||||
|
||||
for i, href in enumerate(hrefs):
|
||||
message = Message.existing(**messages[i])
|
||||
@ -94,10 +95,11 @@ class Message(resource.Resource):
|
||||
@classmethod
|
||||
def delete_by_id(cls, session, message, path_args=None):
|
||||
url = cls._strip_version(message.href)
|
||||
headers = {'Client-ID': message.client}
|
||||
|
||||
session.delete(url, service=cls.service,
|
||||
headers=headers, accept=None)
|
||||
headers = {
|
||||
'Client-ID': message.client,
|
||||
'Accept': '',
|
||||
}
|
||||
session.delete(url, endpoint_filter=cls.service, headers=headers)
|
||||
|
||||
|
||||
class MessageEncoder(json.JSONEncoder):
|
||||
|
@ -29,5 +29,6 @@ class Queue(resource.Resource):
|
||||
@classmethod
|
||||
def create_by_id(cls, session, attrs, resource_id=None, path_args=None):
|
||||
url = cls._get_url(path_args, resource_id)
|
||||
session.put(url, service=cls.service, accept=None)
|
||||
headers = {'Accept': ''}
|
||||
session.put(url, endpoint_filter=cls.service, headers=headers)
|
||||
return {cls.id_attribute: resource_id}
|
||||
|
@ -15,8 +15,6 @@ Load various modules for authorization and eventually services.
|
||||
"""
|
||||
from stevedore import extension
|
||||
|
||||
from openstack import exceptions
|
||||
|
||||
|
||||
def load_service_plugins(namespace):
|
||||
service_plugins = extension.ExtensionManager(
|
||||
@ -26,30 +24,6 @@ def load_service_plugins(namespace):
|
||||
services = {}
|
||||
for service in service_plugins:
|
||||
service = service.obj
|
||||
service.set_interface(None)
|
||||
service.interface = None
|
||||
services[service.service_type] = service
|
||||
return services
|
||||
|
||||
|
||||
class ModuleLoader(object):
|
||||
|
||||
def __init__(self):
|
||||
"""Create a module loader."""
|
||||
self.auth_mgr = extension.ExtensionManager(
|
||||
namespace="openstack.auth.plugin",
|
||||
invoke_on_load=False,
|
||||
)
|
||||
|
||||
def get_auth_plugin(self, plugin_name):
|
||||
"""Get an authentication plugin by name."""
|
||||
if not plugin_name:
|
||||
plugin_name = 'password'
|
||||
try:
|
||||
return self.auth_mgr[plugin_name].plugin
|
||||
except KeyError:
|
||||
msg = ('Could not find authorization plugin <%s>' % plugin_name)
|
||||
raise exceptions.NoMatchingPlugin(msg)
|
||||
|
||||
def list_auth_plugins(self):
|
||||
"""Get a list of all the authentication plugins."""
|
||||
return self.auth_mgr.names()
|
||||
|
@ -52,8 +52,8 @@ class Router(resource.Resource):
|
||||
"""
|
||||
body = {'subnet_id': subnet_id}
|
||||
url = utils.urljoin(self.base_path, self.id, 'add_router_interface')
|
||||
resp = session.put(url, service=self.service, json=body).body
|
||||
return resp
|
||||
resp = session.put(url, endpoint_filter=self.service, json=body)
|
||||
return resp.json()
|
||||
|
||||
def remove_interface(self, session, subnet_id):
|
||||
"""Remove an internal interface from a logical router.
|
||||
@ -66,5 +66,5 @@ class Router(resource.Resource):
|
||||
"""
|
||||
body = {'subnet_id': subnet_id}
|
||||
url = utils.urljoin(self.base_path, self.id, 'remove_router_interface')
|
||||
resp = session.put(url, service=self.service, json=body).body
|
||||
return resp
|
||||
resp = session.put(url, endpoint_filter=self.service, json=body)
|
||||
return resp.json()
|
||||
|
@ -35,5 +35,6 @@ class BaseResource(resource.Resource):
|
||||
"""
|
||||
url = cls._get_url(None, resource_id)
|
||||
headers = attrs.get(resource.HEADERS, dict())
|
||||
return session.post(url, service=cls.service, accept=None,
|
||||
headers['Accept'] = ''
|
||||
return session.post(url, endpoint_filter=cls.service,
|
||||
headers=headers).headers
|
||||
|
@ -95,7 +95,8 @@ class Container(_base.BaseResource):
|
||||
"""
|
||||
url = cls._get_url(None, resource_id)
|
||||
headers = attrs.get(resource.HEADERS, dict())
|
||||
return session.put(url, service=cls.service, accept=None,
|
||||
headers['Accept'] = ''
|
||||
return session.put(url, endpoint_filter=cls.service,
|
||||
headers=headers).headers
|
||||
|
||||
def create(self, session):
|
||||
|
@ -149,7 +149,9 @@ class Object(resource.Resource):
|
||||
def get(self, session):
|
||||
url = self._get_url(self, self.id)
|
||||
# TODO(thowe): Add filter header support bug #1488269
|
||||
resp = session.get(url, service=self.service, accept="bytes").content
|
||||
headers = {'Accept': 'bytes'}
|
||||
resp = session.get(url, endpoint_filter=self.service, headers=headers)
|
||||
resp = resp.content
|
||||
return resp
|
||||
|
||||
def create(self, session):
|
||||
@ -157,11 +159,13 @@ class Object(resource.Resource):
|
||||
url = self._get_url(self, self.id)
|
||||
|
||||
headers = self.get_headers()
|
||||
headers['Accept'] = ''
|
||||
if self.data is not None:
|
||||
resp = session.put(url, service=self.service, data=self.data,
|
||||
accept="bytes", headers=headers).headers
|
||||
resp = session.put(url, endpoint_filter=self.service,
|
||||
data=self.data,
|
||||
headers=headers).headers
|
||||
else:
|
||||
resp = session.post(url, service=self.service, data=None,
|
||||
accept=None, headers=headers).headers
|
||||
resp = session.post(url, endpoint_filter=self.service, data=None,
|
||||
headers=headers).headers
|
||||
self.set_headers(resp)
|
||||
return self
|
||||
|
@ -68,8 +68,8 @@ class Stack(resource.Resource):
|
||||
def _action(self, session, body):
|
||||
"""Perform stack actions"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'actions')
|
||||
resp = session.post(url, service=self.service, json=body).body
|
||||
return resp
|
||||
resp = session.post(url, endpoint_filter=self.service, json=body)
|
||||
return resp.json()
|
||||
|
||||
def check(self, session):
|
||||
return self._action(session, {'check': ''})
|
||||
@ -80,7 +80,8 @@ class Stack(resource.Resource):
|
||||
body.pop('id', None)
|
||||
body.pop('name', None)
|
||||
url = cls.base_path
|
||||
resp = session.post(url, service=cls.service, json=body).body
|
||||
resp = session.post(url, endpoint_filter=cls.service, json=body)
|
||||
resp = resp.json()
|
||||
return resp[cls.resource_key]
|
||||
|
||||
@classmethod
|
||||
@ -91,5 +92,5 @@ class Stack(resource.Resource):
|
||||
body.pop('id', None)
|
||||
body.pop('name', None)
|
||||
url = cls._get_url(path_args, resource_id)
|
||||
session.put(url, service=cls.service, json=body)
|
||||
session.put(url, endpoint_filter=cls.service, json=body)
|
||||
return cls.get_by_id(session, resource_id)
|
||||
|
@ -37,7 +37,7 @@ normally be something like 'compute', 'identity', 'object-store', etc.::
|
||||
prof.set_version('identity', 'v3')
|
||||
prof.set_interface('object-store', 'internal')
|
||||
for service in prof.get_services():
|
||||
print str(prof.get_preference(service.service_type))
|
||||
print(prof.get_filter(service.service_type)
|
||||
|
||||
The resulting preference print out would look something like::
|
||||
|
||||
@ -51,6 +51,7 @@ The resulting preference print out would look something like::
|
||||
service_type=identity,region=zion,version=v3
|
||||
"""
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import six
|
||||
|
||||
@ -88,7 +89,6 @@ class Profile(object):
|
||||
Services are identified by their service type, e.g.: 'identity',
|
||||
'compute', etc.
|
||||
"""
|
||||
self._preferences = {}
|
||||
self._services = {}
|
||||
self._add_service(cluster_service.ClusterService())
|
||||
self._add_service(compute_service.ComputeService())
|
||||
@ -107,13 +107,13 @@ class Profile(object):
|
||||
if plugins:
|
||||
for plugin in plugins:
|
||||
self._load_plugin(plugin)
|
||||
self.service_names = sorted(self._services.keys())
|
||||
self.service_keys = sorted(self._services.keys())
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self._preferences)
|
||||
return repr(self._services)
|
||||
|
||||
def _add_service(self, serv):
|
||||
serv.set_interface(None)
|
||||
serv.interface = None
|
||||
self._services[serv.service_type] = serv
|
||||
|
||||
def _load_plugin(self, namespace):
|
||||
@ -128,12 +128,24 @@ class Profile(object):
|
||||
services[service_type])
|
||||
self._add_service(services[service_type])
|
||||
|
||||
def get_preference(self, service):
|
||||
def get_filter(self, service):
|
||||
"""Get a service preference.
|
||||
|
||||
:param str service: Desired service type.
|
||||
"""
|
||||
return self._preferences.get(service, None)
|
||||
return copy.copy(self._get_filter(service))
|
||||
|
||||
def _get_filter(self, service):
|
||||
"""Get a service preference.
|
||||
|
||||
:param str service: Desired service type.
|
||||
"""
|
||||
serv = self._services.get(service, None)
|
||||
if serv is not None:
|
||||
return serv
|
||||
msg = ("Service %s not in list of valid services: %s" %
|
||||
(service, self.service_keys))
|
||||
raise exceptions.SDKException(msg)
|
||||
|
||||
def get_services(self):
|
||||
"""Get a list of all the known services."""
|
||||
@ -142,16 +154,6 @@ class Profile(object):
|
||||
services.append(service)
|
||||
return services
|
||||
|
||||
def _get_service(self, service):
|
||||
"""Get a valid service filter."""
|
||||
serv = self._services.get(service, None)
|
||||
if serv is not None:
|
||||
self._preferences[service] = serv
|
||||
return serv
|
||||
msg = ("Service %s not in list of valid services: %s" %
|
||||
(service, self.service_names))
|
||||
raise exceptions.SDKException(msg)
|
||||
|
||||
def set_name(self, service, name):
|
||||
"""Set the desired name for the specified service.
|
||||
|
||||
@ -159,11 +161,11 @@ class Profile(object):
|
||||
:param str name: Desired service name.
|
||||
"""
|
||||
if service == self.ALL:
|
||||
services = self.service_names
|
||||
services = self.service_keys
|
||||
else:
|
||||
services = [service]
|
||||
for service in services:
|
||||
self._get_service(service).service_name = name
|
||||
self._get_filter(service).service_name = name
|
||||
|
||||
def set_region(self, service, region):
|
||||
"""Set the desired region for the specified service.
|
||||
@ -172,11 +174,11 @@ class Profile(object):
|
||||
:param str region: Desired service region.
|
||||
"""
|
||||
if service == self.ALL:
|
||||
services = self.service_names
|
||||
services = self.service_keys
|
||||
else:
|
||||
services = [service]
|
||||
for service in services:
|
||||
self._get_service(service).region = region
|
||||
self._get_filter(service).region = region
|
||||
|
||||
def set_version(self, service, version):
|
||||
"""Set the desired version for the specified service.
|
||||
@ -184,7 +186,7 @@ class Profile(object):
|
||||
:param str service: Service type.
|
||||
:param str version: Desired service version.
|
||||
"""
|
||||
self._get_service(service).version = version
|
||||
self._get_filter(service).version = version
|
||||
|
||||
def set_interface(self, service, interface):
|
||||
"""Set the desired interface for the specified service.
|
||||
@ -193,8 +195,8 @@ class Profile(object):
|
||||
:param str interface: Desired service interface.
|
||||
"""
|
||||
if service == self.ALL:
|
||||
services = self.service_names
|
||||
services = self.service_keys
|
||||
else:
|
||||
services = [service]
|
||||
for service in services:
|
||||
self._get_service(service).set_interface(interface)
|
||||
self._get_filter(service).interface = interface
|
||||
|
@ -35,6 +35,7 @@ import copy
|
||||
import itertools
|
||||
import time
|
||||
|
||||
from keystoneauth1 import exceptions as ksa_exceptions
|
||||
import six
|
||||
|
||||
from openstack import exceptions
|
||||
@ -540,9 +541,10 @@ class Resource(collections.MutableMapping):
|
||||
if headers:
|
||||
args[HEADERS] = headers
|
||||
if resource_id:
|
||||
resp = session.put(url, service=cls.service, **args).body
|
||||
resp = session.put(url, endpoint_filter=cls.service, **args)
|
||||
else:
|
||||
resp = session.post(url, service=cls.service, **args).body
|
||||
resp = session.post(url, endpoint_filter=cls.service, **args)
|
||||
resp = resp.json()
|
||||
|
||||
if cls.resource_key:
|
||||
resp = resp[cls.resource_key]
|
||||
@ -588,8 +590,8 @@ class Resource(collections.MutableMapping):
|
||||
raise exceptions.MethodNotSupported(cls, 'retrieve')
|
||||
|
||||
url = cls._get_url(path_args, resource_id)
|
||||
response = session.get(url, service=cls.service)
|
||||
body = response.body
|
||||
response = session.get(url, endpoint_filter=cls.service)
|
||||
body = response.json()
|
||||
|
||||
if cls.resource_key:
|
||||
body = body[cls.resource_key]
|
||||
@ -664,9 +666,10 @@ class Resource(collections.MutableMapping):
|
||||
|
||||
url = cls._get_url(path_args, resource_id)
|
||||
|
||||
data = session.head(url, service=cls.service, accept=None).headers
|
||||
headers = {'Accept': ''}
|
||||
resp = session.head(url, endpoint_filter=cls.service, headers=headers)
|
||||
|
||||
return {HEADERS: data}
|
||||
return {HEADERS: resp.headers}
|
||||
|
||||
@classmethod
|
||||
def head_by_id(cls, session, resource_id, path_args=None):
|
||||
@ -739,9 +742,10 @@ class Resource(collections.MutableMapping):
|
||||
if headers:
|
||||
args[HEADERS] = headers
|
||||
if cls.patch_update:
|
||||
resp = session.patch(url, service=cls.service, **args).body
|
||||
resp = session.patch(url, endpoint_filter=cls.service, **args)
|
||||
else:
|
||||
resp = session.put(url, service=cls.service, **args).body
|
||||
resp = session.put(url, endpoint_filter=cls.service, **args)
|
||||
resp = resp.json()
|
||||
|
||||
if cls.resource_key and cls.resource_key in resp.keys():
|
||||
resp = resp[cls.resource_key]
|
||||
@ -794,7 +798,8 @@ class Resource(collections.MutableMapping):
|
||||
raise exceptions.MethodNotSupported(cls, 'delete')
|
||||
|
||||
url = cls._get_url(path_args, resource_id)
|
||||
session.delete(url, service=cls.service, accept=None)
|
||||
headers = {'Accept': ''}
|
||||
session.delete(url, endpoint_filter=cls.service, headers=headers)
|
||||
|
||||
def delete(self, session):
|
||||
"""Delete the remote resource associated with this instance.
|
||||
@ -841,8 +846,11 @@ class Resource(collections.MutableMapping):
|
||||
more_data = True
|
||||
params = {} if params is None else params
|
||||
url = cls._get_url(path_args)
|
||||
headers = {'Accept': 'application/json'}
|
||||
while more_data:
|
||||
resp = session.get(url, service=cls.service, params=params).body
|
||||
resp = session.get(url, endpoint_filter=cls.service,
|
||||
headers=headers, params=params)
|
||||
resp = resp.json()
|
||||
if cls.resources_key:
|
||||
resp = resp[cls.resources_key]
|
||||
|
||||
@ -919,7 +927,7 @@ class Resource(collections.MutableMapping):
|
||||
try:
|
||||
if cls.allow_retrieve:
|
||||
return cls.get_by_id(session, name_or_id, path_args=path_args)
|
||||
except exceptions.HttpException:
|
||||
except ksa_exceptions.http.NotFound:
|
||||
pass
|
||||
|
||||
data = cls.list(session, path_args=path_args)
|
||||
@ -995,7 +1003,7 @@ def wait_for_delete(session, resource, interval, wait):
|
||||
while total_sleep < wait:
|
||||
try:
|
||||
resource.get(session)
|
||||
except exceptions.NotFoundException:
|
||||
except ksa_exceptions.http.NotFound:
|
||||
return resource
|
||||
time.sleep(interval)
|
||||
total_sleep += interval
|
||||
|
@ -51,8 +51,6 @@ The resulting output from the code::
|
||||
matches=True
|
||||
"""
|
||||
|
||||
from openstack import exceptions
|
||||
|
||||
|
||||
class ValidVersion(object):
|
||||
|
||||
@ -66,16 +64,14 @@ class ValidVersion(object):
|
||||
self.path = path or module
|
||||
|
||||
|
||||
class ServiceFilter(object):
|
||||
class ServiceFilter(dict):
|
||||
UNVERSIONED = ''
|
||||
ANY = 'any'
|
||||
PUBLIC = 'public'
|
||||
INTERNAL = 'internal'
|
||||
ADMIN = 'admin'
|
||||
INTERFACE = [PUBLIC, INTERNAL, ADMIN]
|
||||
valid_versions = []
|
||||
|
||||
def __init__(self, service_type=ANY, interface=PUBLIC, region=None,
|
||||
def __init__(self, service_type, interface=PUBLIC, region=None,
|
||||
service_name=None, version=None):
|
||||
"""Create a service identifier.
|
||||
|
||||
@ -86,95 +82,72 @@ class ServiceFilter(object):
|
||||
:param string service_name: Name of the service
|
||||
:param string version: Version of service to use.
|
||||
"""
|
||||
self.service_type = service_type.lower()
|
||||
self.set_interface(interface)
|
||||
self.region = region
|
||||
self.service_name = service_name
|
||||
self.version = version
|
||||
self['service_type'] = service_type.lower()
|
||||
self['interface'] = interface
|
||||
self['region_name'] = region
|
||||
self['service_name'] = service_name
|
||||
self['version'] = version
|
||||
|
||||
def __repr__(self):
|
||||
ret = "service_type=%s" % self.service_type
|
||||
if self.interface is not None:
|
||||
ret += ",interface=%s" % self.interface
|
||||
if self.region is not None:
|
||||
ret += ",region=%s" % self.region
|
||||
if self.service_name:
|
||||
ret += ",service_name=%s" % self.service_name
|
||||
if self.version:
|
||||
ret += ",version=%s" % self.version
|
||||
return ret
|
||||
@property
|
||||
def service_type(self):
|
||||
return self['service_type']
|
||||
|
||||
def join(self, default):
|
||||
"""Create a new service filter by joining filters.
|
||||
@property
|
||||
def interface(self):
|
||||
return self['interface']
|
||||
|
||||
Create a new service filter by joining this service preference with
|
||||
the default service identifier.
|
||||
@interface.setter
|
||||
def interface(self, value):
|
||||
self['interface'] = value
|
||||
|
||||
:param default: Default service identifier from the resource.
|
||||
:type default: :class:`~openstack.service_filter.ServiceFilter`
|
||||
"""
|
||||
if default.version == self.UNVERSIONED:
|
||||
version = default.version
|
||||
else:
|
||||
version = self.version
|
||||
response = ServiceFilter()
|
||||
response.service_type = default.service_type
|
||||
response.service_name = self.service_name
|
||||
response.valid_versions = default.valid_versions
|
||||
response.interface = default.interface
|
||||
if self.interface:
|
||||
response.interface = self.interface
|
||||
if self.region:
|
||||
response.region = self.region
|
||||
response.version = version
|
||||
return response
|
||||
@property
|
||||
def region(self):
|
||||
return self['region_name']
|
||||
|
||||
def match_service_type(self, service_type):
|
||||
"""Service types are equavilent."""
|
||||
if self.service_type == self.ANY:
|
||||
return True
|
||||
return self.service_type == service_type
|
||||
@region.setter
|
||||
def region(self, value):
|
||||
self['region_name'] = value
|
||||
|
||||
def match_service_name(self, service_name):
|
||||
"""Service names are equavilent."""
|
||||
if not self.service_name:
|
||||
return True
|
||||
if self.service_name == service_name:
|
||||
return True
|
||||
return False
|
||||
@property
|
||||
def service_name(self):
|
||||
return self['service_name']
|
||||
|
||||
def match_region(self, region):
|
||||
"""Service regions are equavilent."""
|
||||
if not self.region:
|
||||
return True
|
||||
if self.region == region:
|
||||
return True
|
||||
return False
|
||||
@service_name.setter
|
||||
def service_name(self, value):
|
||||
self['service_name'] = value
|
||||
|
||||
def match_interface(self, interface):
|
||||
"""Service interfaces are equavilent."""
|
||||
if not self.interface:
|
||||
return True
|
||||
return self.interface == interface
|
||||
@property
|
||||
def version(self):
|
||||
return self['version']
|
||||
|
||||
def set_interface(self, interface):
|
||||
"""Set the interface of the service filter."""
|
||||
if not interface:
|
||||
self.interface = None
|
||||
return
|
||||
interface = interface.replace('URL', '')
|
||||
interface = interface.lower()
|
||||
if interface not in self.INTERFACE:
|
||||
msg = "Interface <%s> not in %s" % (interface, self.INTERFACE)
|
||||
raise exceptions.SDKException(msg)
|
||||
self.interface = interface
|
||||
@version.setter
|
||||
def version(self, value):
|
||||
self['version'] = value
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
return self['path']
|
||||
|
||||
@path.setter
|
||||
def path(self, value):
|
||||
self['path'] = value
|
||||
|
||||
def get_path(self, version=None):
|
||||
if not self.version:
|
||||
self.version = version
|
||||
return self.get('path', self._get_valid_version().path)
|
||||
|
||||
def get_filter(self):
|
||||
filter = dict(self)
|
||||
del filter['version']
|
||||
return filter
|
||||
|
||||
def _get_valid_version(self):
|
||||
if self.valid_versions:
|
||||
if self.version:
|
||||
for valid in self.valid_versions:
|
||||
# NOTE(thowe): should support fuzzy match e.g: v2.1==v2
|
||||
if self.version == valid.module:
|
||||
if self.version.startswith(valid.module):
|
||||
return valid
|
||||
return self.valid_versions[0]
|
||||
return ValidVersion('')
|
||||
@ -194,17 +167,3 @@ class ServiceFilter(object):
|
||||
is `object_store`.
|
||||
"""
|
||||
return self.__class__.__module__.split('.')[1]
|
||||
|
||||
def get_version_path(self, version):
|
||||
"""Get the desired version path.
|
||||
|
||||
If the service does not have a version, use the suggested version.
|
||||
"""
|
||||
if self.version is not None:
|
||||
return self.version
|
||||
valid = self._get_valid_version()
|
||||
if valid.path:
|
||||
return valid.path
|
||||
if version:
|
||||
return version
|
||||
return ''
|
||||
|
@ -11,152 +11,53 @@
|
||||
# under the License.
|
||||
|
||||
"""
|
||||
The :class:`~openstack.session.Session` is the class that maintains session
|
||||
layer similar to the OSI model session layer. The session has a transport
|
||||
and an authenticator. The transport is used by the session and authenticator
|
||||
for HTTP layer transport. The authenticator is responsible for providing an
|
||||
authentication token and an endpoint to communicate with.
|
||||
The :class:`~openstack.session.Session` overrides
|
||||
:class:`~keystoneauth1.session.Session` to provide end point filtering.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
The following examples use the example authenticator which takes the token
|
||||
and endpoint as arguments.
|
||||
|
||||
Create a session
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Constructor::
|
||||
|
||||
from examples import authenticator
|
||||
from openstack import session
|
||||
from openstack import transport
|
||||
xport = transport.Transport()
|
||||
token = 'SecretToken'
|
||||
endpoint = 'http://cloud.example.com:3333'
|
||||
auther = authenticator.TestAuthenticator(token, endpoint)
|
||||
sess = session.Session(xport, auther)
|
||||
|
||||
HTTP GET
|
||||
~~~~~~~~
|
||||
|
||||
Making a basic HTTP GET call::
|
||||
|
||||
containers = sess.get('/').json()
|
||||
|
||||
The containers variable will contain a list of dict describing the containers.
|
||||
|
||||
HTTP PUT
|
||||
~~~~~~~~
|
||||
|
||||
Creating a new object::
|
||||
|
||||
objay_data = 'roland garros'
|
||||
objay_len = len(objay_data)
|
||||
headers = {"Content-Length": objay_len, "Content-Type": "text/plain"}
|
||||
resp = sess.put('/pilots/french.txt', headers=headers, data=objay_data)
|
||||
"""
|
||||
import re
|
||||
from six.moves.urllib import parse
|
||||
|
||||
import logging
|
||||
|
||||
from openstack import profile as _profile
|
||||
from openstack import utils
|
||||
from keystoneauth1 import session as _session
|
||||
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
VERSION_PATTERN = re.compile('/v\d[\d.]*')
|
||||
|
||||
|
||||
class Session(object):
|
||||
def parse_url(filt, url):
|
||||
result = parse.urlparse(url)
|
||||
path = result.path
|
||||
vstr = VERSION_PATTERN.search(path)
|
||||
if not vstr:
|
||||
return result.scheme + "://" + result.netloc + "/" + filt.get_path()
|
||||
start, end = vstr.span()
|
||||
prefix = path[:start]
|
||||
version = '/' + filt.get_path(path[start + 1:end])
|
||||
postfix = path[end:].rstrip('/') if path[end:] else ''
|
||||
url = result.scheme + "://" + result.netloc + prefix + version + postfix
|
||||
return url
|
||||
|
||||
def __init__(self, transport, authenticator, profile=None):
|
||||
"""Create a new object with a transport and authenticator.
|
||||
|
||||
Session layer which uses the transport for communication. The
|
||||
authenticator also uses the transport to keep authenticated.
|
||||
class Session(_session.Session):
|
||||
|
||||
def __init__(self, profile, **kwargs):
|
||||
"""Create a new Keystone auth session with a profile.
|
||||
|
||||
:param transport: A transport that provides an HTTP request method.
|
||||
The transport is also to be used by the authenticator, if needed.
|
||||
:type transport: :class:`~openstack.transport.Transport`
|
||||
:param authenticator: An authenticator that provides get_token and
|
||||
get_endpoint methods for the session.
|
||||
:type authenticator: :class:`~openstack.auth.base.BaseAuthPlugin`
|
||||
:param profile: If the user has any special profiles such as the
|
||||
service name, region, version or interface, they may be provided
|
||||
in the profile object. If no profiles are provided, the
|
||||
services that appear first in the service catalog will be used.
|
||||
:type profile: :class:`~openstack.profile.Profile`
|
||||
|
||||
All the other methods of the session accept the following parameters:
|
||||
|
||||
:param str path: Path relative to service base url.
|
||||
:param service: a service filter for the authenticator to determine
|
||||
the correct endpoint to use.
|
||||
:type service: :class:`~openstack.service_filter.ServiceFilter`
|
||||
:param bool authenticate: A flag that indicates if a token should be
|
||||
attached to the request. This parameter defaults to true.
|
||||
:param kwargs: The remaining arguments are passed to the transport
|
||||
request method.
|
||||
"""
|
||||
self.transport = transport
|
||||
self.authenticator = authenticator
|
||||
self.profile = profile or _profile.Profile()
|
||||
super(Session, self).__init__(**kwargs)
|
||||
self.profile = profile
|
||||
|
||||
def _request(self, path, method, service=None, authenticate=True,
|
||||
**kwargs):
|
||||
"""Send an HTTP request with the specified characteristics.
|
||||
def get_endpoint(self, auth=None, interface=None, **kwargs):
|
||||
"""Override get endpoint to automate endpoint filering"""
|
||||
|
||||
Handle a session level request.
|
||||
|
||||
:param string path: Path relative to authentictor base url.
|
||||
:param string method: The http method to use. (eg. 'GET', 'POST').
|
||||
:param service: Object that filters service to the authenticator.
|
||||
:type service: :class:`~openstack.service_filter.ServiceFilter`
|
||||
:param bool authenticate: True if a token should be attached
|
||||
:param kwargs: any other parameter that can be passed to transport
|
||||
and authenticator.
|
||||
|
||||
:returns: The response to the request.
|
||||
"""
|
||||
|
||||
headers = kwargs.setdefault('headers', dict())
|
||||
if authenticate:
|
||||
token = self.authenticator.get_token(self.transport)
|
||||
if token:
|
||||
headers['X-Auth-Token'] = token
|
||||
if service:
|
||||
profile = self.profile.get_preference(service.service_type)
|
||||
if profile:
|
||||
service = profile.join(service)
|
||||
|
||||
endpoint = self.authenticator.get_endpoint(self.transport, service)
|
||||
url = utils.urljoin(endpoint, path)
|
||||
|
||||
return self.transport.request(method, url, **kwargs)
|
||||
|
||||
def head(self, path, **kwargs):
|
||||
"""Perform an HTTP HEAD request."""
|
||||
return self._request(path, 'HEAD', **kwargs)
|
||||
|
||||
def get(self, path, **kwargs):
|
||||
"""Perform an HTTP GET request."""
|
||||
return self._request(path, 'GET', **kwargs)
|
||||
|
||||
def post(self, path, **kwargs):
|
||||
"""Perform an HTTP POST request."""
|
||||
return self._request(path, 'POST', **kwargs)
|
||||
|
||||
def put(self, path, **kwargs):
|
||||
"""Perform an HTTP PUT request."""
|
||||
return self._request(path, 'PUT', **kwargs)
|
||||
|
||||
def delete(self, path, **kwargs):
|
||||
"""Perform an HTTP DELETE request."""
|
||||
return self._request(path, 'DELETE', **kwargs)
|
||||
|
||||
def patch(self, path, **kwargs):
|
||||
"""Perform an HTTP PATCH request."""
|
||||
return self._request(path, 'PATCH', **kwargs)
|
||||
|
||||
def get_services(self):
|
||||
"""Get list of services from profiles."""
|
||||
return self.profile.get_services()
|
||||
service_type = kwargs.get('service_type')
|
||||
filt = self.profile.get_filter(service_type)
|
||||
if filt.interface is None:
|
||||
filt.interface = interface
|
||||
url = super(Session, self).get_endpoint(auth, **filt.get_filter())
|
||||
return parse_url(filt, url)
|
||||
|
@ -71,8 +71,8 @@ class Alarm(resource.Resource):
|
||||
The next_state may be one of: 'ok' 'insufficient data' 'alarm'
|
||||
"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'state')
|
||||
resp = session.put(url, service=self.service, json=next_state).body
|
||||
return resp
|
||||
resp = session.put(url, endpoint_filter=self.service, json=next_state)
|
||||
return resp.json()
|
||||
|
||||
def check_state(self, session):
|
||||
"""Retrieve the current state of an alarm from the service.
|
||||
@ -80,6 +80,7 @@ class Alarm(resource.Resource):
|
||||
The properties of the alarm are not modified.
|
||||
"""
|
||||
url = utils.urljoin(self.base_path, self.id, 'state')
|
||||
resp = session.get(url, service=self.service).body
|
||||
resp = session.get(url, endpoint_filter=self.service)
|
||||
resp = resp.json()
|
||||
current_state = resp.replace('\"', '')
|
||||
return current_state
|
||||
|
@ -45,5 +45,6 @@ class AlarmChange(resource.Resource):
|
||||
def list(cls, session, limit=None, marker=None, path_args=None,
|
||||
paginated=False, **params):
|
||||
url = cls._get_url(path_args)
|
||||
for item in session.get(url, service=cls.service, params=params).body:
|
||||
resp = session.get(url, endpoint_filter=cls.service, params=params)
|
||||
for item in resp.json():
|
||||
yield cls.existing(**item)
|
||||
|
@ -31,6 +31,8 @@ class Capability(resource.Resource):
|
||||
@classmethod
|
||||
def list(cls, session, limit=None, marker=None, path_args=None,
|
||||
paginated=False, **params):
|
||||
resp = session.get(cls.base_path, service=cls.service, params=params)
|
||||
for key, value in six.iteritems(resp.body['api']):
|
||||
resp = session.get(cls.base_path, endpoint_filter=cls.service,
|
||||
params=params)
|
||||
resp = resp.json()
|
||||
for key, value in six.iteritems(resp['api']):
|
||||
yield cls.existing(id=key, enabled=value)
|
||||
|
@ -52,7 +52,8 @@ class Sample(resource.Resource):
|
||||
def list(cls, session, limit=None, marker=None, path_args=None,
|
||||
paginated=False, **params):
|
||||
url = cls._get_url(path_args)
|
||||
for item in session.get(url, service=cls.service, params=params).body:
|
||||
resp = session.get(url, endpoint_filter=cls.service, params=params)
|
||||
for item in resp.json():
|
||||
yield cls.existing(**item)
|
||||
|
||||
def create(self, session):
|
||||
@ -60,7 +61,9 @@ class Sample(resource.Resource):
|
||||
# telemetry expects a list of samples
|
||||
attrs = self._attrs.copy()
|
||||
attrs.pop('meter', None)
|
||||
resp = session.post(url, service=self.service, json=[attrs])
|
||||
self.update_attrs(**resp.body.pop())
|
||||
resp = session.post(url, endpoint_filter=self.service,
|
||||
json=[attrs])
|
||||
resp = resp.json()
|
||||
self.update_attrs(**resp.pop())
|
||||
self._reset_dirty()
|
||||
return self
|
||||
|
@ -60,5 +60,6 @@ class Statistics(resource.Resource):
|
||||
def list(cls, session, limit=None, marker=None, path_args=None,
|
||||
paginated=False, **params):
|
||||
url = cls._get_url(path_args)
|
||||
for stat in session.get(url, service=cls.service, params=params).body:
|
||||
resp = session.get(url, endpoint_filter=cls.service, params=params)
|
||||
for stat in resp.json():
|
||||
yield cls.existing(**stat)
|
||||
|
@ -25,7 +25,7 @@ class TestObject(base.BaseFunctionalTest):
|
||||
def setUpClass(cls):
|
||||
super(TestObject, cls).setUpClass()
|
||||
cls.conn.object_store.create_container(name=cls.FOLDER)
|
||||
cls.sot = cls.conn.object_store.create_object(
|
||||
cls.sot = cls.conn.object_store.upload_object(
|
||||
container=cls.FOLDER, name=cls.FILE, data=cls.DATA)
|
||||
|
||||
@classmethod
|
||||
|
@ -26,4 +26,4 @@ class TestMeter(base.BaseFunctionalTest):
|
||||
self.conn.object_store.delete_container(tainer)
|
||||
|
||||
names = set([o.name for o in self.conn.telemetry.meters()])
|
||||
self.assertIn('storage.objects', names)
|
||||
self.assertIn('storage.objects.incoming.bytes', names)
|
||||
|
@ -1,333 +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.
|
||||
|
||||
TEST_ADMIN_URL = 'http://identity.region1.admin/v1.1/123123'
|
||||
TEST_DOMAIN_ID = '1'
|
||||
TEST_DOMAIN_NAME = 'aDomain'
|
||||
TEST_EXPIRES = '2020-01-01 00:00:10.000123+00:00'
|
||||
TEST_PASS = 'wasspord'
|
||||
TEST_PROJECT_ID = 'pid'
|
||||
TEST_PROJECT_NAME = 'pname'
|
||||
TEST_SUBJECT = 'subjay'
|
||||
TEST_TOKEN = 'atoken'
|
||||
TEST_TENANT_ID = 'tid'
|
||||
TEST_TENANT_NAME = 'tname'
|
||||
TEST_TRUST_ID = 'trusty'
|
||||
TEST_USER = 'youzer'
|
||||
TEST_USER_ID = 'youid'
|
||||
|
||||
TEST_SERVICE_CATALOG_V2 = [
|
||||
{
|
||||
"endpoints": [{
|
||||
"adminURL": "http://compute.region0.admin/v1.1/",
|
||||
"internalURL": "http://compute.region0.internal/v1.1/",
|
||||
"publicURL": "http://compute.region0.public/v1.1/",
|
||||
}],
|
||||
"type": "compute",
|
||||
"name": "nova0"
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"adminURL": "http://compute.region2.admin/v1/",
|
||||
"region": "RegionTwo",
|
||||
"internalURL": "http://compute.region2.internal/v1/",
|
||||
"publicURL": "http://compute.region2.public/v1/",
|
||||
}],
|
||||
"type": "compute",
|
||||
"name": "nova2"
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"adminURL": "http://compute.region1.admin/v2.0/",
|
||||
"region": "RegionOne",
|
||||
"internalURL": "http://compute.region1.internal/v2.0/",
|
||||
"publicURL": "http://compute.region1.public/v2.0/",
|
||||
}],
|
||||
"type": "compute",
|
||||
"name": "nova"
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"adminURL": "http://image.region1.admin/v2",
|
||||
"region": "RegionOne",
|
||||
"internalURL": "http://image.region1.internal/v2",
|
||||
"publicURL": "http://image.region1.public/v2",
|
||||
}],
|
||||
"type": "image",
|
||||
"name": "glance"
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"adminURL": TEST_ADMIN_URL,
|
||||
"region": "RegionOne",
|
||||
"internalURL": "http://identity.region1.internal/v1.1/123123",
|
||||
"publicURL": "http://identity.region1.public/v1.1/123123",
|
||||
}],
|
||||
"type": "identity",
|
||||
"name": "keystone"
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"adminURL": "http://object-store.region1.admin/",
|
||||
"region": "RegionOne",
|
||||
"internalURL": "http://object-store.region1.internal/",
|
||||
"publicURL": "http://object-store.region1.public/",
|
||||
}],
|
||||
"type": "object-store",
|
||||
"name": "swift"
|
||||
}]
|
||||
TEST_SERVICE_CATALOG_NORMALIZED = [
|
||||
{
|
||||
"endpoints": [{
|
||||
"interface": "public",
|
||||
"url": "http://compute.region0.public/%(version)s",
|
||||
'version': 'v1.1',
|
||||
}, {
|
||||
"interface": "internal",
|
||||
"url": "http://compute.region0.internal/%(version)s",
|
||||
'version': 'v1.1',
|
||||
}, {
|
||||
"interface": "admin",
|
||||
"url": "http://compute.region0.admin/%(version)s",
|
||||
'version': 'v1.1',
|
||||
}],
|
||||
"type": "compute",
|
||||
"name": "nova0"
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"interface": "public",
|
||||
"region": "RegionTwo",
|
||||
"url": "http://compute.region2.public/%(version)s",
|
||||
'version': 'v1',
|
||||
}, {
|
||||
"interface": "internal",
|
||||
"region": "RegionTwo",
|
||||
"url": "http://compute.region2.internal/%(version)s",
|
||||
'version': 'v1',
|
||||
}, {
|
||||
"interface": "admin",
|
||||
"region": "RegionTwo",
|
||||
"url": "http://compute.region2.admin/%(version)s",
|
||||
'version': 'v1',
|
||||
}],
|
||||
"type": "compute",
|
||||
"name": "nova2"
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"interface": "public",
|
||||
"region": "RegionOne",
|
||||
"url": "http://compute.region1.public/%(version)s",
|
||||
'version': 'v2.0',
|
||||
}, {
|
||||
"interface": "internal",
|
||||
"region": "RegionOne",
|
||||
"url": "http://compute.region1.internal/%(version)s",
|
||||
'version': 'v2.0',
|
||||
}, {
|
||||
"interface": "admin",
|
||||
"region": "RegionOne",
|
||||
"url": "http://compute.region1.admin/%(version)s",
|
||||
'version': 'v2.0',
|
||||
}],
|
||||
"type": "compute",
|
||||
"name": "nova"
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"interface": "public",
|
||||
"region": "RegionOne",
|
||||
"url": "http://image.region1.public/%(version)s",
|
||||
'version': 'v2',
|
||||
}, {
|
||||
"interface": "internal",
|
||||
"region": "RegionOne",
|
||||
"url": "http://image.region1.internal/%(version)s",
|
||||
'version': 'v2',
|
||||
}, {
|
||||
"interface": "admin",
|
||||
"region": "RegionOne",
|
||||
"url": "http://image.region1.admin/%(version)s",
|
||||
'version': 'v2',
|
||||
}],
|
||||
"type": "image",
|
||||
"name": "glance",
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"interface": "public",
|
||||
"region": "RegionOne",
|
||||
"url": "http://identity.region1.public/%(version)s/123123",
|
||||
'version': 'v1.1',
|
||||
}, {
|
||||
"interface": "internal",
|
||||
"region": "RegionOne",
|
||||
"url": "http://identity.region1.internal/%(version)s/123123",
|
||||
'version': 'v1.1',
|
||||
}, {
|
||||
"interface": "admin",
|
||||
"region": "RegionOne",
|
||||
"url": "http://identity.region1.admin/%(version)s/123123",
|
||||
'version': 'v1.1',
|
||||
}],
|
||||
"type": "identity",
|
||||
"name": "keystone",
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"interface": "public",
|
||||
"region": "RegionOne",
|
||||
"url": "http://object-store.region1.public/%(version)s",
|
||||
}, {
|
||||
"interface": "internal",
|
||||
"region": "RegionOne",
|
||||
"url": "http://object-store.region1.internal/%(version)s",
|
||||
}, {
|
||||
"interface": "admin",
|
||||
"region": "RegionOne",
|
||||
"url": "http://object-store.region1.admin/%(version)s",
|
||||
}],
|
||||
"type": "object-store",
|
||||
"name": "swift",
|
||||
}]
|
||||
TEST_RESPONSE_DICT_V2 = {
|
||||
"access": {
|
||||
"token": {
|
||||
"expires": TEST_EXPIRES,
|
||||
"id": TEST_TOKEN,
|
||||
"tenant": {
|
||||
"id": TEST_TENANT_ID
|
||||
},
|
||||
},
|
||||
"user": {
|
||||
"id": TEST_USER_ID
|
||||
},
|
||||
"serviceCatalog": TEST_SERVICE_CATALOG_V2,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
TEST_SERVICE_CATALOG_V3 = [
|
||||
{
|
||||
"endpoints": [{
|
||||
"url": "http://compute.region0.public/v1.1/",
|
||||
"interface": "public"
|
||||
}, {
|
||||
"url": "http://compute.region0.internal/v1.1/",
|
||||
"interface": "internal"
|
||||
}, {
|
||||
"url": "http://compute.region0.admin/v1.1/",
|
||||
"interface": "admin"
|
||||
}],
|
||||
"type": "compute",
|
||||
"name": "nova0",
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"url": "http://compute.region2.public/v1/",
|
||||
"region": "RegionTwo",
|
||||
"interface": "public"
|
||||
}, {
|
||||
"url": "http://compute.region2.internal/v1/",
|
||||
"region": "RegionTwo",
|
||||
"interface": "internal"
|
||||
}, {
|
||||
"url": "http://compute.region2.admin/v1/",
|
||||
"region": "RegionTwo",
|
||||
"interface": "admin"
|
||||
}],
|
||||
"type": "compute",
|
||||
"name": "nova2",
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"url": "http://compute.region1.public/v2.0/",
|
||||
"region": "RegionOne",
|
||||
"interface": "public"
|
||||
}, {
|
||||
"url": "http://compute.region1.internal/v2.0/",
|
||||
"region": "RegionOne",
|
||||
"interface": "internal"
|
||||
}, {
|
||||
"url": "http://compute.region1.admin/v2.0/",
|
||||
"region": "RegionOne",
|
||||
"interface": "admin"
|
||||
}],
|
||||
"type": "compute",
|
||||
"name": "nova",
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"url": "http://image.region1.public/v2",
|
||||
"region": "RegionOne",
|
||||
"interface": "public"
|
||||
}, {
|
||||
"url": "http://image.region1.internal/v2",
|
||||
"region": "RegionOne",
|
||||
"interface": "internal"
|
||||
}, {
|
||||
"url": "http://image.region1.admin/v2",
|
||||
"region": "RegionOne",
|
||||
"interface": "admin"
|
||||
}],
|
||||
"type": "image",
|
||||
"name": "glance",
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"url": "http://identity.region1.public/v1.1/123123",
|
||||
"region": "RegionOne",
|
||||
"interface": "public"
|
||||
}, {
|
||||
"url": "http://identity.region1.internal/v1.1/123123",
|
||||
"region": "RegionOne",
|
||||
"interface": "internal"
|
||||
}, {
|
||||
"url": "http://identity.region1.admin/v1.1/123123",
|
||||
"region": "RegionOne",
|
||||
"interface": "admin"
|
||||
}],
|
||||
"type": "identity",
|
||||
"name": "keystone",
|
||||
}, {
|
||||
"endpoints": [{
|
||||
"url": "http://object-store.region1.public/",
|
||||
"region": "RegionOne",
|
||||
"interface": "public"
|
||||
}, {
|
||||
"url": "http://object-store.region1.internal/",
|
||||
"region": "RegionOne",
|
||||
"interface": "internal"
|
||||
}, {
|
||||
"url": "http://object-store.region1.admin/",
|
||||
"region": "RegionOne",
|
||||
"interface": "admin"
|
||||
}],
|
||||
"type": "object-store",
|
||||
"name": "swift",
|
||||
}]
|
||||
|
||||
TEST_RESPONSE_DICT_V3 = {
|
||||
"token": {
|
||||
"methods": [
|
||||
"token",
|
||||
"password"
|
||||
],
|
||||
|
||||
"expires_at": TEST_EXPIRES,
|
||||
"project": {
|
||||
"domain": {
|
||||
"id": TEST_DOMAIN_ID,
|
||||
"name": TEST_DOMAIN_NAME
|
||||
},
|
||||
"id": TEST_PROJECT_ID,
|
||||
"name": TEST_PROJECT_NAME
|
||||
},
|
||||
"user": {
|
||||
"domain": {
|
||||
"id": TEST_DOMAIN_ID,
|
||||
"name": TEST_DOMAIN_NAME
|
||||
},
|
||||
"id": TEST_USER_ID,
|
||||
"name": TEST_USER
|
||||
},
|
||||
"issued_at": "2013-05-29T16:55:21.468960Z",
|
||||
"catalog": TEST_SERVICE_CATALOG_V3
|
||||
},
|
||||
}
|
@ -1,99 +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.
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from openstack.auth.identity import discoverable
|
||||
from openstack import exceptions
|
||||
from openstack.tests.unit.auth import common
|
||||
|
||||
|
||||
class TestDiscoverableAuth(testtools.TestCase):
|
||||
def test_valid_options(self):
|
||||
expected = {
|
||||
'access_info',
|
||||
'auth_url',
|
||||
'domain_id',
|
||||
'domain_name',
|
||||
'password',
|
||||
'project_domain_id',
|
||||
'project_domain_name',
|
||||
'project_id',
|
||||
'project_name',
|
||||
'reauthenticate',
|
||||
'tenant_id',
|
||||
'tenant_name',
|
||||
'token',
|
||||
'trust_id',
|
||||
'user_domain_id',
|
||||
'user_domain_name',
|
||||
'user_id',
|
||||
'username',
|
||||
}
|
||||
self.assertEqual(expected, discoverable.Auth.valid_options)
|
||||
|
||||
def test_create2(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost/v2',
|
||||
'username': '1',
|
||||
'password': '2',
|
||||
}
|
||||
auth = discoverable.Auth(**auth_args)
|
||||
self.assertEqual('openstack.auth.identity.v2',
|
||||
auth.auth_plugin.__class__.__module__)
|
||||
|
||||
def test_create3(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost/v3',
|
||||
'username': '1',
|
||||
'password': '2',
|
||||
}
|
||||
auth = discoverable.Auth(**auth_args)
|
||||
self.assertEqual('openstack.auth.identity.v3',
|
||||
auth.auth_plugin.__class__.__module__)
|
||||
|
||||
def test_create_who_knows(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost:5000/',
|
||||
'username': '1',
|
||||
'password': '2',
|
||||
}
|
||||
auth = discoverable.Auth(**auth_args)
|
||||
self.assertEqual('openstack.auth.identity.v3',
|
||||
auth.auth_plugin.__class__.__module__)
|
||||
|
||||
def test_create_authenticator_no_nothing(self):
|
||||
self.assertRaises(
|
||||
exceptions.AuthorizationFailure,
|
||||
discoverable.Auth,
|
||||
)
|
||||
|
||||
def test_methods(self):
|
||||
auth_args = {
|
||||
'auth_url': 'http://localhost:5000/',
|
||||
'username': '1',
|
||||
'password': '2',
|
||||
}
|
||||
auth = discoverable.Auth(**auth_args)
|
||||
self.assertEqual('http://localhost:5000/auth/tokens', auth.token_url)
|
||||
xport = mock.MagicMock()
|
||||
xport.post = mock.Mock()
|
||||
response = mock.Mock()
|
||||
response.json = mock.Mock()
|
||||
response.json.return_value = common.TEST_RESPONSE_DICT_V3
|
||||
response.headers = {'X-Subject-Token': common.TEST_SUBJECT}
|
||||
xport.post.return_value = response
|
||||
|
||||
result = auth.authorize(xport)
|
||||
self.assertEqual(common.TEST_SUBJECT, result.auth_token)
|
||||
self.assertEqual(True, auth.invalidate())
|
@ -1,142 +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.
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from openstack.auth.identity import v2
|
||||
from openstack import exceptions
|
||||
from openstack.tests.unit.auth import common
|
||||
|
||||
TEST_URL = 'http://127.0.0.1:5000/v2.0'
|
||||
|
||||
TEST_SERVICE_CATALOG = common.TEST_SERVICE_CATALOG_V2
|
||||
TEST_RESPONSE_DICT = common.TEST_RESPONSE_DICT_V2
|
||||
|
||||
|
||||
class TestV2Auth(testtools.TestCase):
|
||||
def test_password(self):
|
||||
kargs = {'trust_id': common.TEST_TRUST_ID,
|
||||
'project_id': common.TEST_TENANT_ID,
|
||||
'project_name': common.TEST_TENANT_NAME}
|
||||
|
||||
sot = v2.Password(TEST_URL, common.TEST_USER, common.TEST_PASS,
|
||||
**kargs)
|
||||
|
||||
self.assertEqual(common.TEST_USER, sot.username)
|
||||
self.assertEqual(common.TEST_PASS, sot.password)
|
||||
self.assertEqual(common.TEST_TRUST_ID, sot.trust_id)
|
||||
self.assertEqual(common.TEST_TENANT_ID, sot.tenant_id)
|
||||
self.assertEqual(common.TEST_TENANT_NAME, sot.tenant_name)
|
||||
expected = {'passwordCredentials': {'password': common.TEST_PASS,
|
||||
'username': common.TEST_USER}}
|
||||
self.assertEqual(expected, sot.get_auth_data())
|
||||
|
||||
def test_password_no_user(self):
|
||||
kargs = {'trust_id': common.TEST_TRUST_ID,
|
||||
'project_id': common.TEST_TENANT_ID,
|
||||
'project_name': common.TEST_TENANT_NAME}
|
||||
|
||||
sot = v2.Password(TEST_URL, None, common.TEST_PASS,
|
||||
**kargs)
|
||||
|
||||
self.assertEqual(None, sot.username)
|
||||
self.assertEqual(common.TEST_PASS, sot.password)
|
||||
self.assertEqual(common.TEST_TRUST_ID, sot.trust_id)
|
||||
self.assertEqual(common.TEST_TENANT_ID, sot.tenant_id)
|
||||
self.assertEqual(common.TEST_TENANT_NAME, sot.tenant_name)
|
||||
expected = {'passwordCredentials': {'password': common.TEST_PASS}}
|
||||
self.assertEqual(expected, sot.get_auth_data())
|
||||
|
||||
def test_password_no_nothing(self):
|
||||
self.assertRaises(TypeError, v2.Password, TEST_URL)
|
||||
|
||||
def test_token(self):
|
||||
kargs = {'trust_id': common.TEST_TRUST_ID,
|
||||
'tenant_id': common.TEST_TENANT_ID,
|
||||
'tenant_name': common.TEST_TENANT_NAME}
|
||||
|
||||
sot = v2.Token(TEST_URL, common.TEST_TOKEN, **kargs)
|
||||
|
||||
self.assertEqual(common.TEST_TOKEN, sot.token)
|
||||
self.assertEqual(common.TEST_TRUST_ID, sot.trust_id)
|
||||
self.assertEqual(common.TEST_TENANT_ID, sot.tenant_id)
|
||||
self.assertEqual(common.TEST_TENANT_NAME, sot.tenant_name)
|
||||
expected = {'token': {'id': common.TEST_TOKEN}}
|
||||
self.assertEqual(expected, sot.get_auth_data())
|
||||
|
||||
def create_mock_transport(self, xresp):
|
||||
transport = mock.Mock()
|
||||
transport.post = mock.Mock()
|
||||
response = mock.Mock()
|
||||
response.json = mock.Mock()
|
||||
response.json.return_value = xresp
|
||||
transport.post.return_value = response
|
||||
return transport
|
||||
|
||||
def test_authorize_tenant_id(self):
|
||||
kargs = {'trust_id': common.TEST_TRUST_ID,
|
||||
'tenant_id': common.TEST_TENANT_ID,
|
||||
'tenant_name': common.TEST_TENANT_NAME}
|
||||
sot = v2.Token(TEST_URL, common.TEST_TOKEN, **kargs)
|
||||
xport = self.create_mock_transport(TEST_RESPONSE_DICT)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
ejson = {'auth': {'token': {'id': common.TEST_TOKEN},
|
||||
'trust_id': common.TEST_TRUST_ID,
|
||||
'tenantId': common.TEST_TENANT_ID}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = TEST_RESPONSE_DICT['access'].copy()
|
||||
ecatalog['version'] = 'v2.0'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_tenant_name(self):
|
||||
kargs = {'tenant_name': common.TEST_TENANT_NAME}
|
||||
sot = v2.Token(TEST_URL, common.TEST_TOKEN, **kargs)
|
||||
xport = self.create_mock_transport(TEST_RESPONSE_DICT)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
ejson = {'auth': {'token': {'id': common.TEST_TOKEN},
|
||||
'tenantName': common.TEST_TENANT_NAME}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = TEST_RESPONSE_DICT['access'].copy()
|
||||
ecatalog['version'] = 'v2.0'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_only(self):
|
||||
sot = v2.Token(TEST_URL, common.TEST_TOKEN)
|
||||
xport = self.create_mock_transport(TEST_RESPONSE_DICT)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
ejson = {'auth': {'token': {'id': common.TEST_TOKEN}}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = TEST_RESPONSE_DICT['access'].copy()
|
||||
ecatalog['version'] = 'v2.0'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_bad_response(self):
|
||||
sot = v2.Token(TEST_URL, common.TEST_TOKEN)
|
||||
xport = self.create_mock_transport({})
|
||||
|
||||
self.assertRaises(exceptions.InvalidResponse, sot.authorize, xport)
|
@ -1,341 +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.
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from openstack.auth.identity import v3
|
||||
from openstack import exceptions
|
||||
from openstack.tests.unit.auth import common
|
||||
|
||||
TEST_URL = 'http://127.0.0.1:5000/v3.0'
|
||||
|
||||
|
||||
class TestV3Auth(testtools.TestCase):
|
||||
def test_password_user_domain(self):
|
||||
kargs = {'trust_id': common.TEST_TRUST_ID,
|
||||
'project_id': common.TEST_PROJECT_ID,
|
||||
'project_name': common.TEST_PROJECT_NAME}
|
||||
|
||||
method = v3.PasswordMethod(username=common.TEST_USER,
|
||||
user_id=common.TEST_USER_ID,
|
||||
user_domain_id=common.TEST_DOMAIN_ID,
|
||||
user_domain_name=common.TEST_DOMAIN_NAME,
|
||||
password=common.TEST_PASS)
|
||||
sot = v3.Auth(TEST_URL, [method], **kargs)
|
||||
|
||||
self.assertEqual(1, len(sot.auth_methods))
|
||||
auther = sot.auth_methods[0]
|
||||
self.assertEqual(common.TEST_USER_ID, auther.user_id)
|
||||
self.assertEqual(common.TEST_USER, auther.username)
|
||||
self.assertEqual(common.TEST_DOMAIN_ID, auther.user_domain_id)
|
||||
self.assertEqual(common.TEST_DOMAIN_NAME, auther.user_domain_name)
|
||||
self.assertEqual(common.TEST_PASS, auther.password)
|
||||
expected = ('password', {'user': {'id': common.TEST_USER_ID,
|
||||
'password': common.TEST_PASS}})
|
||||
self.assertEqual(expected, auther.get_auth_data(None, None, {}))
|
||||
self.assertEqual(common.TEST_TRUST_ID, sot.trust_id)
|
||||
self.assertEqual(None, sot.domain_id)
|
||||
self.assertEqual(None, sot.domain_name)
|
||||
self.assertEqual(common.TEST_PROJECT_ID, sot.project_id)
|
||||
self.assertEqual(common.TEST_PROJECT_NAME, sot.project_name)
|
||||
self.assertEqual(None, sot.project_domain_id)
|
||||
self.assertEqual(None, sot.project_domain_name)
|
||||
self.assertEqual(TEST_URL + '/auth/tokens', sot.token_url)
|
||||
self.assertTrue(sot.include_catalog)
|
||||
|
||||
def test_password_domain(self):
|
||||
kargs = {'domain_id': common.TEST_DOMAIN_ID,
|
||||
'domain_name': common.TEST_DOMAIN_NAME,
|
||||
'trust_id': common.TEST_TRUST_ID,
|
||||
'project_id': common.TEST_PROJECT_ID,
|
||||
'project_name': common.TEST_PROJECT_NAME}
|
||||
|
||||
methods = [v3.PasswordMethod(username=common.TEST_USER,
|
||||
user_id=common.TEST_USER_ID,
|
||||
password=common.TEST_PASS)]
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
|
||||
self.assertEqual(1, len(sot.auth_methods))
|
||||
auther = sot.auth_methods[0]
|
||||
self.assertEqual(common.TEST_USER_ID, auther.user_id)
|
||||
self.assertEqual(common.TEST_USER, auther.username)
|
||||
self.assertEqual(None, auther.user_domain_id)
|
||||
self.assertEqual(None, auther.user_domain_name)
|
||||
self.assertEqual(common.TEST_PASS, auther.password)
|
||||
expected = ('password', {'user': {'id': common.TEST_USER_ID,
|
||||
'password': common.TEST_PASS}})
|
||||
self.assertEqual(expected, auther.get_auth_data(None, None, {}))
|
||||
self.assertEqual(common.TEST_TRUST_ID, sot.trust_id)
|
||||
self.assertEqual(common.TEST_DOMAIN_ID, sot.domain_id)
|
||||
self.assertEqual(common.TEST_DOMAIN_NAME, sot.domain_name)
|
||||
self.assertEqual(common.TEST_PROJECT_ID, sot.project_id)
|
||||
self.assertEqual(common.TEST_PROJECT_NAME, sot.project_name)
|
||||
self.assertEqual(None, sot.project_domain_id)
|
||||
self.assertEqual(None, sot.project_domain_name)
|
||||
self.assertEqual(TEST_URL + '/auth/tokens', sot.token_url)
|
||||
self.assertTrue(sot.include_catalog)
|
||||
|
||||
def test_token_project_domain(self):
|
||||
kargs = {'project_domain_id': common.TEST_DOMAIN_ID,
|
||||
'project_domain_name': common.TEST_DOMAIN_NAME,
|
||||
'trust_id': common.TEST_TRUST_ID,
|
||||
'project_id': common.TEST_PROJECT_ID,
|
||||
'project_name': common.TEST_PROJECT_NAME}
|
||||
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
|
||||
self.assertEqual(1, len(sot.auth_methods))
|
||||
auther = sot.auth_methods[0]
|
||||
self.assertEqual(common.TEST_TOKEN, auther.token)
|
||||
expected = ('token', {'id': common.TEST_TOKEN})
|
||||
self.assertEqual(expected, auther.get_auth_data(None, None, {}))
|
||||
self.assertEqual(common.TEST_TRUST_ID, sot.trust_id)
|
||||
self.assertEqual(None, sot.domain_id)
|
||||
self.assertEqual(None, sot.domain_name)
|
||||
self.assertEqual(common.TEST_PROJECT_ID, sot.project_id)
|
||||
self.assertEqual(common.TEST_PROJECT_NAME, sot.project_name)
|
||||
self.assertEqual(common.TEST_DOMAIN_ID, sot.project_domain_id)
|
||||
self.assertEqual(common.TEST_DOMAIN_NAME, sot.project_domain_name)
|
||||
self.assertEqual(TEST_URL + '/auth/tokens', sot.token_url)
|
||||
|
||||
def create_mock_transport(self, xresp):
|
||||
transport = mock.Mock()
|
||||
transport.post = mock.Mock()
|
||||
response = mock.Mock()
|
||||
response.json = mock.Mock()
|
||||
response.json.return_value = xresp
|
||||
response.headers = {'X-Subject-Token': common.TEST_SUBJECT}
|
||||
transport.post.return_value = response
|
||||
return transport
|
||||
|
||||
def test_authorize_token(self):
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/auth/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
ejson = {'auth': {'identity': {'token': {'id': common.TEST_TOKEN},
|
||||
'methods': ['token']}}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
ecatalog['auth_token'] = common.TEST_SUBJECT
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_include_catalog(self):
|
||||
kargs = {'include_catalog': False}
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/auth/tokens?nocatalog'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
ejson = {'auth': {'identity': {'token': {'id': common.TEST_TOKEN},
|
||||
'methods': ['token']}}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
ecatalog['auth_token'] = common.TEST_SUBJECT
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_domain_id(self):
|
||||
kargs = {'domain_id': common.TEST_DOMAIN_ID}
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/auth/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
ejson = {'auth': {'identity': {'token': {'id': common.TEST_TOKEN},
|
||||
'methods': ['token']},
|
||||
'scope': {'domain': {'id': common.TEST_DOMAIN_ID}}}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
ecatalog['auth_token'] = common.TEST_SUBJECT
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_domain_name(self):
|
||||
kargs = {'domain_name': common.TEST_DOMAIN_NAME}
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/auth/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
scope = {'domain': {'name': common.TEST_DOMAIN_NAME}}
|
||||
ejson = {'auth': {'identity': {'token': {'id': common.TEST_TOKEN},
|
||||
'methods': ['token']},
|
||||
'scope': scope}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
ecatalog['auth_token'] = common.TEST_SUBJECT
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_project_id(self):
|
||||
kargs = {'project_id': common.TEST_PROJECT_ID}
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/auth/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
scope = {'project': {'id': common.TEST_PROJECT_ID}}
|
||||
ejson = {'auth': {'identity': {'token': {'id': common.TEST_TOKEN},
|
||||
'methods': ['token']},
|
||||
'scope': scope}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
ecatalog['auth_token'] = common.TEST_SUBJECT
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_project_name(self):
|
||||
kargs = {'project_name': common.TEST_PROJECT_NAME,
|
||||
'project_domain_id': common.TEST_DOMAIN_ID}
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/auth/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
domain = {'domain': {'id': common.TEST_DOMAIN_ID},
|
||||
'name': common.TEST_PROJECT_NAME}
|
||||
scope = {'project': domain}
|
||||
ejson = {'auth': {'identity': {'methods': ['token'],
|
||||
'token': {'id': common.TEST_TOKEN}},
|
||||
'scope': scope}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
ecatalog['auth_token'] = common.TEST_SUBJECT
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_project_name_domain_name(self):
|
||||
kargs = {'project_name': common.TEST_PROJECT_NAME,
|
||||
'project_domain_name': common.TEST_DOMAIN_NAME}
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/auth/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
domain = {'domain': {'name': common.TEST_DOMAIN_NAME},
|
||||
'name': common.TEST_PROJECT_NAME}
|
||||
scope = {'project': domain}
|
||||
ejson = {'auth': {'identity': {'methods': ['token'],
|
||||
'token': {'id': common.TEST_TOKEN}},
|
||||
'scope': scope}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
ecatalog['auth_token'] = common.TEST_SUBJECT
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_token_trust_id(self):
|
||||
kargs = {'trust_id': common.TEST_TRUST_ID}
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/auth/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
scope = {'OS-TRUST:trust': {'id': common.TEST_TRUST_ID}}
|
||||
ejson = {'auth': {'identity': {'token': {'id': common.TEST_TOKEN},
|
||||
'methods': ['token']},
|
||||
'scope': scope}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
ecatalog['auth_token'] = common.TEST_SUBJECT
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_multi_method(self):
|
||||
methods = [v3.PasswordMethod(username=common.TEST_USER,
|
||||
password=common.TEST_PASS),
|
||||
v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods)
|
||||
xport = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
|
||||
resp = sot.authorize(xport)
|
||||
|
||||
eurl = TEST_URL + '/auth/tokens'
|
||||
eheaders = {'Accept': 'application/json',
|
||||
'X-Auth-Token': common.TEST_TOKEN}
|
||||
up = {'password': common.TEST_PASS, 'name': common.TEST_USER}
|
||||
ejson = {'auth': {'identity': {'token': {'id': common.TEST_TOKEN},
|
||||
'password': {'user': up},
|
||||
'methods': ['password', 'token']}}}
|
||||
xport.post.assert_called_with(eurl, headers=eheaders, json=ejson)
|
||||
ecatalog = common.TEST_RESPONSE_DICT_V3['token'].copy()
|
||||
ecatalog['auth_token'] = common.TEST_SUBJECT
|
||||
ecatalog['version'] = 'v3'
|
||||
self.assertEqual(ecatalog, resp._info)
|
||||
|
||||
def test_authorize_mutually_exclusive(self):
|
||||
x = self.create_mock_transport(common.TEST_RESPONSE_DICT_V3)
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
|
||||
kargs = {'domain_id': 'a',
|
||||
'project_id': 'b'}
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
self.assertRaises(exceptions.AuthorizationFailure, sot.authorize, x)
|
||||
|
||||
kargs = {'domain_name': 'a',
|
||||
'project_name': 'b'}
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
self.assertRaises(exceptions.AuthorizationFailure, sot.authorize, x)
|
||||
|
||||
kargs = {'domain_name': 'a',
|
||||
'trust_id': 'b'}
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
self.assertRaises(exceptions.AuthorizationFailure, sot.authorize, x)
|
||||
|
||||
kargs = {'project_id': 'a',
|
||||
'trust_id': 'b'}
|
||||
sot = v3.Auth(TEST_URL, methods, **kargs)
|
||||
self.assertRaises(exceptions.AuthorizationFailure, sot.authorize, x)
|
||||
|
||||
def test_authorize_bad_response(self):
|
||||
methods = [v3.TokenMethod(token=common.TEST_TOKEN)]
|
||||
sot = v3.Auth(TEST_URL, methods)
|
||||
xport = self.create_mock_transport({})
|
||||
|
||||
self.assertRaises(exceptions.InvalidResponse, sot.authorize, xport)
|
@ -1,88 +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.
|
||||
|
||||
import mock
|
||||
import testtools
|
||||
|
||||
from openstack.auth import access
|
||||
from openstack.tests.unit.auth import common
|
||||
|
||||
|
||||
class TestAccessInfo(testtools.TestCase):
|
||||
def test_is_valid(self):
|
||||
v2body = common.TEST_RESPONSE_DICT_V2
|
||||
v3body = common.TEST_RESPONSE_DICT_V3
|
||||
self.assertTrue(access.AccessInfoV2.is_valid(v2body))
|
||||
self.assertFalse(access.AccessInfoV2.is_valid(v3body))
|
||||
self.assertFalse(access.AccessInfoV3.is_valid(v2body))
|
||||
self.assertTrue(access.AccessInfoV3.is_valid(v3body))
|
||||
|
||||
def test_factory_v2(self):
|
||||
sot = access.AccessInfo.factory(body=common.TEST_RESPONSE_DICT_V2)
|
||||
|
||||
self.assertTrue(isinstance(sot, access.AccessInfoV2))
|
||||
self.assertFalse(sot.will_expire_soon())
|
||||
self.assertTrue(sot.has_service_catalog())
|
||||
self.assertEqual(common.TEST_TOKEN, sot.auth_token)
|
||||
self.assertEqual(common.TEST_EXPIRES, str(sot.expires))
|
||||
self.assertEqual(None, sot.username)
|
||||
self.assertEqual(common.TEST_USER_ID, sot.user_id)
|
||||
self.assertEqual('default', sot.user_domain_id)
|
||||
self.assertEqual('Default', sot.user_domain_name)
|
||||
self.assertEqual([], sot.role_names)
|
||||
self.assertEqual(None, sot.domain_id)
|
||||
self.assertEqual(None, sot.domain_name)
|
||||
self.assertEqual(None, sot.project_name)
|
||||
self.assertEqual(None, sot.tenant_name)
|
||||
self.assertTrue(sot.project_scoped)
|
||||
self.assertFalse(sot.domain_scoped)
|
||||
self.assertEqual(None, sot.trust_id)
|
||||
self.assertFalse(sot.trust_scoped)
|
||||
self.assertEqual(common.TEST_TENANT_ID, sot.project_id)
|
||||
self.assertEqual(common.TEST_TENANT_ID, sot.tenant_id)
|
||||
self.assertEqual('default', sot.project_domain_id)
|
||||
self.assertEqual('Default', sot.project_domain_name)
|
||||
self.assertEqual('v2.0', sot.version)
|
||||
|
||||
def test_factory_v3(self):
|
||||
response = mock.Mock()
|
||||
response.headers = {'X-Subject-Token': common.TEST_TOKEN}
|
||||
sot = access.AccessInfo.factory(body=common.TEST_RESPONSE_DICT_V3,
|
||||
resp=response)
|
||||
|
||||
self.assertTrue(isinstance(sot, access.AccessInfoV3))
|
||||
self.assertFalse(sot.will_expire_soon())
|
||||
self.assertTrue(sot.has_service_catalog())
|
||||
self.assertEqual(common.TEST_TOKEN, sot.auth_token)
|
||||
self.assertEqual(common.TEST_EXPIRES, str(sot.expires))
|
||||
self.assertEqual(common.TEST_USER, sot.username)
|
||||
self.assertEqual(common.TEST_USER_ID, sot.user_id)
|
||||
self.assertEqual(common.TEST_DOMAIN_ID, sot.user_domain_id)
|
||||
self.assertEqual(common.TEST_DOMAIN_NAME, sot.user_domain_name)
|
||||
self.assertEqual([], sot.role_names)
|
||||
self.assertEqual(None, sot.domain_id)
|
||||
self.assertEqual(None, sot.domain_name)
|
||||
self.assertEqual(common.TEST_PROJECT_NAME, sot.project_name)
|
||||
self.assertEqual(common.TEST_PROJECT_NAME, sot.tenant_name)
|
||||
self.assertTrue(sot.project_scoped)
|
||||
self.assertFalse(sot.domain_scoped)
|
||||
self.assertEqual(None, sot.trust_id)
|
||||
self.assertFalse(sot.trust_scoped)
|
||||
self.assertEqual(common.TEST_PROJECT_ID, sot.project_id)
|
||||
self.assertEqual(common.TEST_PROJECT_ID, sot.tenant_id)
|
||||
self.assertEqual(common.TEST_DOMAIN_ID, sot.project_domain_id)
|
||||
self.assertEqual(common.TEST_DOMAIN_NAME, sot.project_domain_name)
|
||||
self.assertEqual('v3', sot.version)
|
||||
|
||||
def test_factory_raises(self):
|
||||
self.assertRaises(NotImplementedError, access.AccessInfo.factory,
|
||||
body={})
|
@ -1,151 +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.
|
||||
|
||||
import testtools
|
||||
|
||||
from openstack.auth import service_catalog as catalog
|
||||
from openstack.compute import compute_service
|
||||
from openstack import exceptions as exc
|
||||
from openstack.identity import identity_service
|
||||
from openstack.image import image_service
|
||||
from openstack.network import network_service
|
||||
from openstack.object_store import object_store_service
|
||||
from openstack import service_filter
|
||||
from openstack.tests.unit.auth import common
|
||||
|
||||
|
||||
class TestServiceCatalog(testtools.TestCase):
|
||||
def get_urls(self, sot):
|
||||
sf = service_filter.ServiceFilter(service_type='compute')
|
||||
exp = ["http://compute.region0.public/v1.1",
|
||||
"http://compute.region2.public/v1",
|
||||
"http://compute.region1.public/v2.0"]
|
||||
self.assertEqual(exp, sot.get_urls(sf))
|
||||
sf = service_filter.ServiceFilter(service_type='image')
|
||||
self.assertEqual(["http://image.region1.public/v2"],
|
||||
sot.get_urls(sf))
|
||||
sf = service_filter.ServiceFilter(service_type='identity')
|
||||
self.assertEqual(["http://identity.region1.public/v1.1/123123"],
|
||||
sot.get_urls(sf))
|
||||
sf = service_filter.ServiceFilter(service_type='object-store')
|
||||
self.assertEqual(["http://object-store.region1.public/"],
|
||||
sot.get_urls(sf))
|
||||
sf = service_filter.ServiceFilter(service_type='object-store',
|
||||
version='v9')
|
||||
self.assertEqual(["http://object-store.region1.public/v9"],
|
||||
sot.get_urls(sf))
|
||||
sf = service_filter.ServiceFilter(service_type='object-store')
|
||||
osf = object_store_service.ObjectStoreService()
|
||||
sf = sf.join(osf)
|
||||
self.assertEqual(["http://object-store.region1.public/v1"],
|
||||
sot.get_urls(sf))
|
||||
|
||||
def get_urls_name(self, sot):
|
||||
sf = service_filter.ServiceFilter(service_type='compute',
|
||||
service_name='nova')
|
||||
self.assertEqual(["http://compute.region1.public/v2.0"],
|
||||
sot.get_urls(sf))
|
||||
sf = service_filter.ServiceFilter(service_type='compute',
|
||||
service_name='nova2')
|
||||
self.assertEqual(["http://compute.region2.public/v1"],
|
||||
sot.get_urls(sf))
|
||||
|
||||
def get_urls_region(self, sot):
|
||||
sf = service_filter.ServiceFilter(service_type='compute',
|
||||
region='RegionTwo')
|
||||
self.assertEqual(["http://compute.region2.public/v1"],
|
||||
sot.get_urls(sf))
|
||||
sf = service_filter.ServiceFilter(service_type='compute',
|
||||
region='RegionOne')
|
||||
self.assertEqual(["http://compute.region1.public/v2.0"],
|
||||
sot.get_urls(sf))
|
||||
|
||||
def get_urls_interface(self, sot):
|
||||
sf = service_filter.ServiceFilter(service_type='identity',
|
||||
interface='admin')
|
||||
self.assertEqual(["http://identity.region1.admin/v1.1/123123"],
|
||||
sot.get_urls(sf))
|
||||
sf = service_filter.ServiceFilter(service_type='identity',
|
||||
interface='internal')
|
||||
self.assertEqual(
|
||||
["http://identity.region1.internal/v1.1/123123"],
|
||||
sot.get_urls(sf)
|
||||
)
|
||||
sf = service_filter.ServiceFilter(service_type='identity',
|
||||
interface='public')
|
||||
self.assertEqual(["http://identity.region1.public/v1.1/123123"],
|
||||
sot.get_urls(sf))
|
||||
|
||||
|
||||
class TestServiceCatalogV2(TestServiceCatalog):
|
||||
def test_catalog(self):
|
||||
sot = catalog.ServiceCatalogV2(common.TEST_SERVICE_CATALOG_V2)
|
||||
self.assertEqual(common.TEST_SERVICE_CATALOG_NORMALIZED,
|
||||
sot.catalog)
|
||||
|
||||
def test_catalog_empty(self):
|
||||
self.assertRaises(exc.EmptyCatalog, catalog.ServiceCatalogV2, None)
|
||||
|
||||
def test_get_urls(self):
|
||||
sot = catalog.ServiceCatalogV2(common.TEST_SERVICE_CATALOG_V2)
|
||||
self.get_urls(sot)
|
||||
|
||||
def test_get_urls_name(self):
|
||||
sot = catalog.ServiceCatalogV2(common.TEST_SERVICE_CATALOG_V2)
|
||||
self.get_urls_name(sot)
|
||||
|
||||
def test_get_urls_region(self):
|
||||
sot = catalog.ServiceCatalogV2(common.TEST_SERVICE_CATALOG_V2)
|
||||
self.get_urls_region(sot)
|
||||
|
||||
def test_get_urls_interface(self):
|
||||
sot = catalog.ServiceCatalogV2(common.TEST_SERVICE_CATALOG_V2)
|
||||
self.get_urls_interface(sot)
|
||||
|
||||
|
||||
class TestServiceCatalogV3(TestServiceCatalog):
|
||||
def test_catalog(self):
|
||||
sot = catalog.ServiceCatalog(common.TEST_SERVICE_CATALOG_V3)
|
||||
self.assertEqual(common.TEST_SERVICE_CATALOG_NORMALIZED,
|
||||
sot.catalog)
|
||||
|
||||
def test_catalog_empty(self):
|
||||
self.assertRaises(exc.EmptyCatalog, catalog.ServiceCatalog, None)
|
||||
|
||||
def test_get_urls(self):
|
||||
sot = catalog.ServiceCatalog(common.TEST_SERVICE_CATALOG_V3)
|
||||
self.get_urls(sot)
|
||||
|
||||
def test_get_urls_name(self):
|
||||
sot = catalog.ServiceCatalog(common.TEST_SERVICE_CATALOG_V3)
|
||||
self.get_urls_name(sot)
|
||||
|
||||
def test_get_urls_region(self):
|
||||
sot = catalog.ServiceCatalog(common.TEST_SERVICE_CATALOG_V3)
|
||||
self.get_urls_region(sot)
|
||||
|
||||
def test_get_urls_interface(self):
|
||||
sot = catalog.ServiceCatalog(common.TEST_SERVICE_CATALOG_V3)
|
||||
self.get_urls_interface(sot)
|
||||
|
||||
def test_get_versions(self):
|
||||
sot = catalog.ServiceCatalog(common.TEST_SERVICE_CATALOG_V3)
|
||||
service = compute_service.ComputeService()
|
||||
self.assertEqual(['v1.1', 'v1', 'v2.0'], sot.get_versions(service))
|
||||
service = identity_service.IdentityService()
|
||||
self.assertEqual(['v1.1'], sot.get_versions(service))
|
||||
service = image_service.ImageService()
|
||||
self.assertEqual(['v2'], sot.get_versions(service))
|
||||
service = network_service.NetworkService()
|
||||
self.assertEqual(None, sot.get_versions(service))
|
||||
service = object_store_service.ObjectStoreService()
|
||||
self.assertEqual([], sot.get_versions(service))
|
@ -95,33 +95,35 @@ class TestCluster(testtools.TestCase):
|
||||
sot['id'] = 'IDENTIFIER'
|
||||
|
||||
resp = mock.Mock()
|
||||
resp.body = ''
|
||||
resp.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock(return_value=resp)
|
||||
self.assertEqual('', sot.add_nodes(sess, ['node-33']))
|
||||
url = 'clusters/%s/action' % sot.id
|
||||
body = {'add_nodes': {'nodes': ['node-33']}}
|
||||
sess.put.assert_called_once_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_del_nodes(self):
|
||||
sot = cluster.Cluster(FAKE)
|
||||
sot['id'] = 'IDENTIFIER'
|
||||
|
||||
resp = mock.Mock()
|
||||
resp.body = ''
|
||||
resp.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock(return_value=resp)
|
||||
self.assertEqual('', sot.delete_nodes(sess, ['node-11']))
|
||||
url = 'clusters/%s/action' % sot.id
|
||||
body = {'del_nodes': {'nodes': ['node-11']}}
|
||||
sess.put.assert_called_once_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_policy_attach(self):
|
||||
sot = cluster.Cluster(FAKE)
|
||||
sot['id'] = 'IDENTIFIER'
|
||||
|
||||
resp = mock.Mock()
|
||||
resp.body = ''
|
||||
resp.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock(return_value=resp)
|
||||
self.assertEqual('', sot.policy_attach(sess, 'POLICY', 1, 2, 0, True))
|
||||
@ -136,28 +138,30 @@ class TestCluster(testtools.TestCase):
|
||||
'enabled': True,
|
||||
}
|
||||
}
|
||||
sess.put.assert_called_once_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_policy_detach(self):
|
||||
sot = cluster.Cluster(FAKE)
|
||||
sot['id'] = 'IDENTIFIER'
|
||||
|
||||
resp = mock.Mock()
|
||||
resp.body = ''
|
||||
resp.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock(return_value=resp)
|
||||
self.assertEqual('', sot.policy_detach(sess, 'POLICY'))
|
||||
|
||||
url = 'clusters/%s/action' % sot.id
|
||||
body = {'policy_detach': {'policy_id': 'POLICY'}}
|
||||
sess.put.assert_called_once_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_policy_update(self):
|
||||
sot = cluster.Cluster(FAKE)
|
||||
sot['id'] = 'IDENTIFIER'
|
||||
|
||||
resp = mock.Mock()
|
||||
resp.body = ''
|
||||
resp.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock(return_value=resp)
|
||||
self.assertEqual('', sot.policy_update(sess, 'POLICY', 3, 4, 5, False))
|
||||
@ -172,14 +176,15 @@ class TestCluster(testtools.TestCase):
|
||||
'enabled': False
|
||||
}
|
||||
}
|
||||
sess.put.assert_called_once_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_policy_enable(self):
|
||||
sot = cluster.Cluster(FAKE)
|
||||
sot['id'] = 'IDENTIFIER'
|
||||
|
||||
resp = mock.Mock()
|
||||
resp.body = ''
|
||||
resp.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock(return_value=resp)
|
||||
self.assertEqual('', sot.policy_enable(sess, 'POLICY'))
|
||||
@ -191,14 +196,15 @@ class TestCluster(testtools.TestCase):
|
||||
'enabled': True,
|
||||
}
|
||||
}
|
||||
sess.put.assert_called_once_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_policy_disable(self):
|
||||
sot = cluster.Cluster(FAKE)
|
||||
sot['id'] = 'IDENTIFIER'
|
||||
|
||||
resp = mock.Mock()
|
||||
resp.body = ''
|
||||
resp.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock(return_value=resp)
|
||||
self.assertEqual('', sot.policy_disable(sess, 'POLICY'))
|
||||
@ -210,4 +216,5 @@ class TestCluster(testtools.TestCase):
|
||||
'enabled': False,
|
||||
}
|
||||
}
|
||||
sess.put.assert_called_once_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
@ -87,12 +87,14 @@ class TestNode(testtools.TestCase):
|
||||
|
||||
resp = mock.Mock()
|
||||
resp.body = {'action': '1234-5678-abcd'}
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock(return_value=resp)
|
||||
self.assertEqual(resp.body, sot.join(sess, 'cluster-b'))
|
||||
url = 'nodes/%s/action' % sot.id
|
||||
body = {'join': {'cluster_id': 'cluster-b'}}
|
||||
sess.put.assert_called_once_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_leave(self):
|
||||
sot = node.Node(FAKE)
|
||||
@ -100,9 +102,11 @@ class TestNode(testtools.TestCase):
|
||||
|
||||
resp = mock.Mock()
|
||||
resp.body = {'action': '2345-6789-bbbb'}
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock(return_value=resp)
|
||||
self.assertEqual(resp.body, sot.leave(sess))
|
||||
url = 'nodes/%s/action' % sot.id
|
||||
body = {'leave': {}}
|
||||
sess.put.assert_called_once_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
@ -45,7 +45,8 @@ class TestServer(testtools.TestCase):
|
||||
def setUp(self):
|
||||
super(TestServer, self).setUp()
|
||||
self.resp = mock.Mock()
|
||||
self.resp = ''
|
||||
self.resp.body = ''
|
||||
self.resp.json = mock.Mock(return_value=self.resp.body)
|
||||
self.sess = mock.Mock()
|
||||
self.sess.post = mock.MagicMock()
|
||||
self.sess.post.return_value = self.resp
|
||||
@ -105,28 +106,30 @@ class TestServer(testtools.TestCase):
|
||||
def test_change_passowrd(self):
|
||||
sot = server.Server(EXAMPLE)
|
||||
|
||||
self.assertEqual(self.resp, sot.change_password(self.sess, 'a'))
|
||||
self.assertEqual(self.resp.body, sot.change_password(self.sess, 'a'))
|
||||
|
||||
url = 'servers/IDENTIFIER/action'
|
||||
body = {"changePassword": {"adminPass": "a"}}
|
||||
headers = {'Accept': ''}
|
||||
self.sess.post.assert_called_with(
|
||||
url, service=sot.service, json=body, accept=None)
|
||||
url, endpoint_filter=sot.service, json=body, headers=headers)
|
||||
|
||||
def test_reboot(self):
|
||||
sot = server.Server(EXAMPLE)
|
||||
|
||||
self.assertEqual(self.resp, sot.reboot(self.sess, 'HARD'))
|
||||
self.assertEqual(self.resp.body, sot.reboot(self.sess, 'HARD'))
|
||||
|
||||
url = 'servers/IDENTIFIER/action'
|
||||
body = {"reboot": {"type": "HARD"}}
|
||||
headers = {'Accept': ''}
|
||||
self.sess.post.assert_called_with(
|
||||
url, service=sot.service, json=body, accept=None)
|
||||
url, endpoint_filter=sot.service, json=body, headers=headers)
|
||||
|
||||
def test_rebuild(self):
|
||||
sot = server.Server(EXAMPLE)
|
||||
|
||||
self.assertEqual(
|
||||
self.resp,
|
||||
self.resp.body,
|
||||
sot.rebuild(
|
||||
self.sess,
|
||||
name='noo',
|
||||
@ -151,14 +154,15 @@ class TestServer(testtools.TestCase):
|
||||
"personality": [{"path": "/etc/motd", "contents": "foo"}],
|
||||
}
|
||||
}
|
||||
headers = {'Accept': ''}
|
||||
self.sess.post.assert_called_with(
|
||||
url, service=sot.service, json=body, accept=None)
|
||||
url, endpoint_filter=sot.service, json=body, headers=headers)
|
||||
|
||||
def test_rebuild_minimal(self):
|
||||
sot = server.Server(EXAMPLE)
|
||||
|
||||
self.assertEqual(
|
||||
self.resp,
|
||||
self.resp.body,
|
||||
sot.rebuild(
|
||||
self.sess,
|
||||
name='nootoo',
|
||||
@ -175,38 +179,42 @@ class TestServer(testtools.TestCase):
|
||||
"adminPass": "seekr3two",
|
||||
}
|
||||
}
|
||||
headers = {'Accept': ''}
|
||||
self.sess.post.assert_called_with(
|
||||
url, service=sot.service, json=body, accept=None)
|
||||
url, endpoint_filter=sot.service, json=body, headers=headers)
|
||||
|
||||
def test_resize(self):
|
||||
sot = server.Server(EXAMPLE)
|
||||
|
||||
self.assertEqual(self.resp, sot.resize(self.sess, '2'))
|
||||
self.assertEqual(self.resp.body, sot.resize(self.sess, '2'))
|
||||
|
||||
url = 'servers/IDENTIFIER/action'
|
||||
body = {"resize": {"flavorRef": "2"}}
|
||||
headers = {'Accept': ''}
|
||||
self.sess.post.assert_called_with(
|
||||
url, service=sot.service, json=body, accept=None)
|
||||
url, endpoint_filter=sot.service, json=body, headers=headers)
|
||||
|
||||
def test_confirm_resize(self):
|
||||
sot = server.Server(EXAMPLE)
|
||||
|
||||
self.assertEqual(self.resp, sot.confirm_resize(self.sess))
|
||||
self.assertEqual(self.resp.body, sot.confirm_resize(self.sess))
|
||||
|
||||
url = 'servers/IDENTIFIER/action'
|
||||
body = {"confirmResize": None}
|
||||
headers = {'Accept': ''}
|
||||
self.sess.post.assert_called_with(
|
||||
url, service=sot.service, json=body, accept=None)
|
||||
url, endpoint_filter=sot.service, json=body, headers=headers)
|
||||
|
||||
def test_revert_resize(self):
|
||||
sot = server.Server(EXAMPLE)
|
||||
|
||||
self.assertEqual(self.resp, sot.revert_resize(self.sess))
|
||||
self.assertEqual(self.resp.body, sot.revert_resize(self.sess))
|
||||
|
||||
url = 'servers/IDENTIFIER/action'
|
||||
body = {"revertResize": None}
|
||||
headers = {'Accept': ''}
|
||||
self.sess.post.assert_called_with(
|
||||
url, service=sot.service, json=body, accept=None)
|
||||
url, endpoint_filter=sot.service, json=body, headers=headers)
|
||||
|
||||
def test_create_image(self):
|
||||
sot = server.Server(EXAMPLE)
|
||||
@ -214,28 +222,30 @@ class TestServer(testtools.TestCase):
|
||||
metadata = {'nu': 'image', 'created': 'today'}
|
||||
|
||||
self.assertEqual(
|
||||
self.resp,
|
||||
self.resp.body,
|
||||
sot.create_image(self.sess, name, metadata)
|
||||
)
|
||||
|
||||
url = 'servers/IDENTIFIER/action'
|
||||
body = {"createImage": {'name': name, 'metadata': metadata}}
|
||||
headers = {'Accept': ''}
|
||||
self.sess.post.assert_called_with(
|
||||
url, service=sot.service, json=body, accept=None)
|
||||
url, endpoint_filter=sot.service, json=body, headers=headers)
|
||||
|
||||
def test_create_image_minimal(self):
|
||||
sot = server.Server(EXAMPLE)
|
||||
name = 'noo'
|
||||
|
||||
self.assertEqual(
|
||||
self.resp,
|
||||
self.resp.body,
|
||||
sot.create_image(self.sess, name)
|
||||
)
|
||||
|
||||
url = 'servers/IDENTIFIER/action'
|
||||
body = {"createImage": {'name': name}}
|
||||
headers = {'Accept': ''}
|
||||
self.sess.post.assert_called_with(
|
||||
url, service=sot.service, json=body, accept=None)
|
||||
url, endpoint_filter=dict(sot.service), json=body, headers=headers)
|
||||
|
||||
def test_get_ips(self):
|
||||
name = "jenkins"
|
||||
|
@ -81,7 +81,7 @@ class TestServerIP(testtools.TestCase):
|
||||
def test_list(self):
|
||||
sess = mock.Mock()
|
||||
resp = mock.Mock()
|
||||
resp.body = BODY
|
||||
resp.json = mock.Mock(return_value=BODY)
|
||||
sess.get = mock.Mock(return_value=resp)
|
||||
path_args = {'server_id': IDENTIFIER}
|
||||
|
||||
|
@ -50,6 +50,7 @@ class TestServerMeta(testtools.TestCase):
|
||||
def test_create(self):
|
||||
resp = mock.Mock()
|
||||
resp.body = FAKE_RESPONSE
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock()
|
||||
sess.put.return_value = resp
|
||||
@ -59,7 +60,8 @@ class TestServerMeta(testtools.TestCase):
|
||||
|
||||
url = 'servers/' + FAKE_SERVER_ID + '/metadata/' + FAKE_KEY
|
||||
body = {"meta": {FAKE_KEY: FAKE_VALUE}}
|
||||
sess.put.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
self.assertEqual(FAKE_VALUE, sot.value)
|
||||
self.assertEqual(FAKE_KEY, sot.key)
|
||||
self.assertEqual(FAKE_SERVER_ID, sot.server_id)
|
||||
@ -67,6 +69,7 @@ class TestServerMeta(testtools.TestCase):
|
||||
def test_get(self):
|
||||
resp = mock.Mock()
|
||||
resp.body = FAKE_RESPONSES
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.get = mock.MagicMock()
|
||||
sess.get.return_value = resp
|
||||
@ -76,7 +79,8 @@ class TestServerMeta(testtools.TestCase):
|
||||
resp = sot.list(sess, path_args=path_args)
|
||||
|
||||
url = '/servers/' + FAKE_SERVER_ID + '/metadata'
|
||||
sess.get.assert_called_with(url, service=sot.service, params={})
|
||||
sess.get.assert_called_with(url, endpoint_filter=sot.service,
|
||||
params={})
|
||||
self.assertEqual(1, len(resp))
|
||||
self.assertEqual(FAKE_SERVER_ID, resp[0].server_id)
|
||||
self.assertEqual(FAKE_KEY, resp[0].key)
|
||||
@ -85,6 +89,7 @@ class TestServerMeta(testtools.TestCase):
|
||||
def test_update(self):
|
||||
resp = mock.Mock()
|
||||
resp.body = FAKE_RESPONSE
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock()
|
||||
sess.put.return_value = resp
|
||||
@ -94,7 +99,8 @@ class TestServerMeta(testtools.TestCase):
|
||||
|
||||
url = 'servers/' + FAKE_SERVER_ID + '/metadata/' + FAKE_KEY
|
||||
body = {"meta": {FAKE_KEY: FAKE_VALUE}}
|
||||
sess.put.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
self.assertEqual(FAKE_VALUE, sot.value)
|
||||
self.assertEqual(FAKE_KEY, sot.key)
|
||||
self.assertEqual(FAKE_SERVER_ID, sot.server_id)
|
||||
@ -102,6 +108,7 @@ class TestServerMeta(testtools.TestCase):
|
||||
def test_delete(self):
|
||||
resp = mock.Mock()
|
||||
resp.body = FAKE_RESPONSES
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.delete = mock.MagicMock()
|
||||
sess.delete.return_value = resp
|
||||
@ -110,11 +117,14 @@ class TestServerMeta(testtools.TestCase):
|
||||
sot.delete(sess)
|
||||
|
||||
url = 'servers/' + FAKE_SERVER_ID + '/metadata/' + FAKE_KEY
|
||||
sess.delete.assert_called_with(url, service=sot.service, accept=None)
|
||||
headers = {'Accept': ''}
|
||||
sess.delete.assert_called_with(url, endpoint_filter=sot.service,
|
||||
headers=headers)
|
||||
|
||||
def test_list(self):
|
||||
resp = mock.Mock()
|
||||
resp.body = FAKE_RESPONSES
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.get = mock.MagicMock()
|
||||
sess.get.return_value = resp
|
||||
@ -124,7 +134,8 @@ class TestServerMeta(testtools.TestCase):
|
||||
resp = sot.list(sess, path_args=path_args)
|
||||
|
||||
url = '/servers/' + FAKE_SERVER_ID + '/metadata'
|
||||
sess.get.assert_called_with(url, service=sot.service, params={})
|
||||
sess.get.assert_called_with(url, endpoint_filter=sot.service,
|
||||
params={})
|
||||
self.assertEqual(1, len(resp))
|
||||
self.assertEqual(FAKE_SERVER_ID, resp[0].server_id)
|
||||
self.assertEqual(FAKE_KEY, resp[0].key)
|
||||
|
@ -49,6 +49,7 @@ class TestServerMetadata(testtools.TestCase):
|
||||
def test_create(self):
|
||||
resp = mock.Mock()
|
||||
resp.body = FAKE_RESPONSE
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock()
|
||||
sess.put.return_value = resp
|
||||
@ -58,13 +59,15 @@ class TestServerMetadata(testtools.TestCase):
|
||||
|
||||
url = '/servers/' + FAKE_SERVER_ID + '/metadata'
|
||||
body = {"metadata": {FAKE_KEY: FAKE_VALUE}}
|
||||
sess.put.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
self.assertEqual(FAKE_SERVER_ID, sot.server_id)
|
||||
self.assertEqual(FAKE_VALUE, sot[FAKE_KEY])
|
||||
|
||||
def test_get(self):
|
||||
resp = mock.Mock()
|
||||
resp.body = FAKE_RESPONSE
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.get = mock.MagicMock()
|
||||
sess.get.return_value = resp
|
||||
@ -73,7 +76,7 @@ class TestServerMetadata(testtools.TestCase):
|
||||
sot.get(sess)
|
||||
|
||||
url = '/servers/' + FAKE_SERVER_ID + '/metadata'
|
||||
sess.get.assert_called_with(url, service=sot.service)
|
||||
sess.get.assert_called_with(url, endpoint_filter=sot.service)
|
||||
self.assertEqual(FAKE_SERVER_ID, sot.server_id)
|
||||
self.assertEqual(FAKE_VALUE, sot[FAKE_KEY])
|
||||
self.assertEqual(FAKE_VALUE2, sot[FAKE_KEY2])
|
||||
@ -81,6 +84,7 @@ class TestServerMetadata(testtools.TestCase):
|
||||
def test_update(self):
|
||||
resp = mock.Mock()
|
||||
resp.body = FAKE_RESPONSE
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock()
|
||||
sess.put.return_value = resp
|
||||
@ -90,6 +94,7 @@ class TestServerMetadata(testtools.TestCase):
|
||||
|
||||
url = '/servers/' + FAKE_SERVER_ID + '/metadata'
|
||||
body = {"metadata": {FAKE_KEY: FAKE_VALUE}}
|
||||
sess.put.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
self.assertEqual(FAKE_SERVER_ID, sot.server_id)
|
||||
self.assertEqual(FAKE_VALUE, sot[FAKE_KEY])
|
||||
|
@ -53,6 +53,7 @@ class TestInstance(testtools.TestCase):
|
||||
sot = instance.Instance(EXAMPLE)
|
||||
response = mock.Mock()
|
||||
response.body = {'user': {'name': 'root', 'password': 'foo'}}
|
||||
response.json = mock.Mock(return_value=response.body)
|
||||
sess = mock.Mock()
|
||||
sess.post = mock.MagicMock()
|
||||
sess.post.return_value = response
|
||||
@ -60,12 +61,13 @@ class TestInstance(testtools.TestCase):
|
||||
self.assertEqual(response.body['user'], sot.enable_root_user(sess))
|
||||
|
||||
url = ("instances/%s/root" % IDENTIFIER)
|
||||
sess.post.assert_called_with(url, service=sot.service)
|
||||
sess.post.assert_called_with(url, endpoint_filter=sot.service)
|
||||
|
||||
def test_is_root_enabled(self):
|
||||
sot = instance.Instance(EXAMPLE)
|
||||
response = mock.Mock()
|
||||
response.body = {'rootEnabled': True}
|
||||
response.json = mock.Mock(return_value=response.body)
|
||||
sess = mock.Mock()
|
||||
sess.get = mock.MagicMock()
|
||||
sess.get.return_value = response
|
||||
@ -73,12 +75,12 @@ class TestInstance(testtools.TestCase):
|
||||
self.assertEqual(True, sot.is_root_enabled(sess))
|
||||
|
||||
url = ("instances/%s/root" % IDENTIFIER)
|
||||
sess.get.assert_called_with(url, service=sot.service)
|
||||
sess.get.assert_called_with(url, endpoint_filter=sot.service)
|
||||
|
||||
def test_action_restart(self):
|
||||
sot = instance.Instance(EXAMPLE)
|
||||
response = mock.Mock()
|
||||
response.body = ''
|
||||
response.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.post = mock.MagicMock()
|
||||
sess.post.return_value = response
|
||||
@ -87,12 +89,13 @@ class TestInstance(testtools.TestCase):
|
||||
|
||||
url = ("instances/%s/action" % IDENTIFIER)
|
||||
body = {'restart': {}}
|
||||
sess.post.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.post.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_action_resize(self):
|
||||
sot = instance.Instance(EXAMPLE)
|
||||
response = mock.Mock()
|
||||
response.body = ''
|
||||
response.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.post = mock.MagicMock()
|
||||
sess.post.return_value = response
|
||||
@ -102,12 +105,13 @@ class TestInstance(testtools.TestCase):
|
||||
|
||||
url = ("instances/%s/action" % IDENTIFIER)
|
||||
body = {'resize': {'flavorRef': flavor}}
|
||||
sess.post.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.post.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_action_resize_volume(self):
|
||||
sot = instance.Instance(EXAMPLE)
|
||||
response = mock.Mock()
|
||||
response.body = ''
|
||||
response.json = mock.Mock(return_value='')
|
||||
sess = mock.Mock()
|
||||
sess.post = mock.MagicMock()
|
||||
sess.post.return_value = response
|
||||
@ -117,4 +121,5 @@ class TestInstance(testtools.TestCase):
|
||||
|
||||
url = ("instances/%s/action" % IDENTIFIER)
|
||||
body = {'resize': {'volume': size}}
|
||||
sess.post.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.post.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
@ -65,5 +65,5 @@ class TestUser(testtools.TestCase):
|
||||
payload = {'users': [CREATING]}
|
||||
|
||||
user.User.create_by_id(sess, CREATING, path_args=path_args)
|
||||
sess.post.assert_called_with(url, service=user.User.service,
|
||||
sess.post.assert_called_with(url, endpoint_filter=user.User.service,
|
||||
json=payload)
|
||||
|
@ -55,6 +55,7 @@ class TestVersion(testtools.TestCase):
|
||||
]
|
||||
}
|
||||
}
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
session = mock.MagicMock()
|
||||
session.get = mock.MagicMock()
|
||||
session.get.return_value = resp
|
||||
|
@ -59,6 +59,7 @@ class TestExtension(testtools.TestCase):
|
||||
]
|
||||
}
|
||||
}
|
||||
resp.json = mock.Mock(return_value=resp.body)
|
||||
session = mock.MagicMock()
|
||||
session.get = mock.MagicMock()
|
||||
session.get.return_value = resp
|
||||
|
@ -77,8 +77,11 @@ class TestImage(testtools.TestCase):
|
||||
sot = image.Image(EXAMPLE)
|
||||
sot.upload_image(self.sess)
|
||||
|
||||
headers = {'Content-Type': 'application/octet-stream'}
|
||||
headers = {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'Accept': '',
|
||||
}
|
||||
|
||||
self.sess.put.assert_called_with(
|
||||
'images/IDENTIFIER/file', service=sot.service,
|
||||
data=EXAMPLE['data'], accept=None, headers=headers)
|
||||
'images/IDENTIFIER/file', endpoint_filter=sot.service,
|
||||
data=EXAMPLE['data'], headers=headers)
|
||||
|
@ -53,8 +53,9 @@ class TestTag(testtools.TestCase):
|
||||
url = 'images/%(image)s/tags/%(tag)s' % {
|
||||
"image": self.img.get_id(self.img), "tag": test_tag}
|
||||
self.assertIsNone(rv)
|
||||
session_method.assert_called_with(url, service=sot.service,
|
||||
accept=None)
|
||||
headers = {'Accept': ''}
|
||||
session_method.assert_called_with(url, endpoint_filter=sot.service,
|
||||
headers=headers)
|
||||
|
||||
def test_create(self):
|
||||
self._test_action("create", self.session.put)
|
||||
|
@ -58,7 +58,7 @@ class TestClaim(testtools.TestCase):
|
||||
|
||||
url = '/queues/%s/claims' % QUEUE
|
||||
sess.post.assert_called_with(
|
||||
url, service=sot.service,
|
||||
url, endpoint_filter=sot.service,
|
||||
headers={'Client-ID': CLIENT}, params=None,
|
||||
data=json.dumps(FAKE, cls=claim.ClaimEncoder))
|
||||
|
||||
|
@ -59,7 +59,7 @@ class TestMessage(testtools.TestCase):
|
||||
|
||||
url = '/queues/%s/messages' % QUEUE
|
||||
sess.post.assert_called_with(
|
||||
url, service=sot.service,
|
||||
url, endpoint_filter=sot.service,
|
||||
headers={'Client-ID': CLIENT},
|
||||
data=mock.ANY)
|
||||
|
||||
@ -78,5 +78,5 @@ class TestMessage(testtools.TestCase):
|
||||
|
||||
url = '/queues/%s/messages/1234' % QUEUE
|
||||
sess.delete.assert_called_with(
|
||||
url, service=sot.service, accept=None,
|
||||
headers={'Client-ID': CLIENT})
|
||||
url, endpoint_filter=sot.service,
|
||||
headers={'Client-ID': CLIENT, 'Accept': ''})
|
||||
|
@ -48,6 +48,8 @@ class TestQueue(testtools.TestCase):
|
||||
sot.create(sess)
|
||||
|
||||
url = 'queues/%s' % FAKE_NAME
|
||||
sess.put.assert_called_with(url, service=sot.service, accept=None)
|
||||
headers = {'Accept': ''}
|
||||
sess.put.assert_called_with(url, endpoint_filter=sot.service,
|
||||
headers=headers)
|
||||
self.assertEqual(FAKE_NAME, sot.id)
|
||||
self.assertEqual(FAKE_NAME, sot.name)
|
||||
|
@ -57,25 +57,30 @@ class TestFloatingIP(testtools.TestCase):
|
||||
mock_session = mock.Mock()
|
||||
mock_get = mock.Mock()
|
||||
mock_session.get = mock_get
|
||||
mock_session.get_filter = mock.Mock(return_value={})
|
||||
data = {'id': 'one', 'floating_ip_address': '10.0.0.1'}
|
||||
fake_response = mock.Mock()
|
||||
fake_response.body = {floating_ip.FloatingIP.resources_key: [data]}
|
||||
body = {floating_ip.FloatingIP.resources_key: [data]}
|
||||
fake_response.json = mock.Mock(return_value=body)
|
||||
mock_get.return_value = fake_response
|
||||
|
||||
result = floating_ip.FloatingIP.find_available(mock_session)
|
||||
|
||||
self.assertEqual('one', result.id)
|
||||
p = {'fields': 'id', 'port_id': ''}
|
||||
mock_get.assert_called_with(floating_ip.FloatingIP.base_path,
|
||||
params=p,
|
||||
service=floating_ip.FloatingIP.service)
|
||||
mock_get.assert_called_with(
|
||||
floating_ip.FloatingIP.base_path,
|
||||
endpoint_filter=floating_ip.FloatingIP.service,
|
||||
headers={'Accept': 'application/json'},
|
||||
params=p)
|
||||
|
||||
def test_find_available_nada(self):
|
||||
mock_session = mock.Mock()
|
||||
mock_get = mock.Mock()
|
||||
mock_session.get = mock_get
|
||||
fake_response = mock.Mock()
|
||||
fake_response.body = {floating_ip.FloatingIP.resources_key: []}
|
||||
body = {floating_ip.FloatingIP.resources_key: []}
|
||||
fake_response.json = mock.Mock(return_value=body)
|
||||
mock_get.return_value = fake_response
|
||||
|
||||
self.assertEqual(None,
|
||||
|
@ -54,6 +54,7 @@ class TestRouter(testtools.TestCase):
|
||||
sot = router.Router(EXAMPLE)
|
||||
response = mock.Mock()
|
||||
response.body = {"subnet_id": "3", "port_id": "2"}
|
||||
response.json = mock.Mock(return_value=response.body)
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock()
|
||||
sess.put.return_value = response
|
||||
@ -62,12 +63,14 @@ class TestRouter(testtools.TestCase):
|
||||
|
||||
url = 'routers/IDENTIFIER/add_router_interface'
|
||||
body = {"subnet_id": "3"}
|
||||
sess.put.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
||||
def test_remove_interface(self):
|
||||
sot = router.Router(EXAMPLE)
|
||||
response = mock.Mock()
|
||||
response.body = {"subnet_id": "3", "port_id": "2"}
|
||||
response.json = mock.Mock(return_value=response.body)
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.MagicMock()
|
||||
sess.put.return_value = response
|
||||
@ -76,4 +79,5 @@ class TestRouter(testtools.TestCase):
|
||||
|
||||
url = 'routers/IDENTIFIER/remove_router_interface'
|
||||
body = {"subnet_id": "3"}
|
||||
sess.put.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.put.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
|
@ -59,6 +59,7 @@ class TestContainer(testtools.TestCase):
|
||||
super(TestContainer, self).setUp()
|
||||
self.resp = mock.Mock()
|
||||
self.resp.body = {}
|
||||
self.resp.json = mock.Mock(return_value=self.resp.body)
|
||||
self.resp.headers = {"X-Trans-Id": "abcdef"}
|
||||
self.sess = mock.Mock()
|
||||
self.sess.put = mock.MagicMock()
|
||||
@ -136,12 +137,13 @@ class TestContainer(testtools.TestCase):
|
||||
headers = {
|
||||
"x-container-read": "some ACL",
|
||||
"x-container-write": "another ACL",
|
||||
"x-detect-content-type": True
|
||||
"x-detect-content-type": True,
|
||||
"Accept": "",
|
||||
}
|
||||
sot_call(self.sess)
|
||||
|
||||
url = "/%s" % CONTAINER_NAME
|
||||
sess_method.assert_called_with(url, service=sot.service, accept=None,
|
||||
sess_method.assert_called_with(url, endpoint_filter=sot.service,
|
||||
headers=headers)
|
||||
|
||||
def test_create(self):
|
||||
@ -156,8 +158,9 @@ class TestContainer(testtools.TestCase):
|
||||
sot = container.Container.new(name=CONTAINER_NAME)
|
||||
sot.create(self.sess)
|
||||
url = "/%s" % CONTAINER_NAME
|
||||
self.sess.put.assert_called_with(url, service=sot.service,
|
||||
accept=None, headers=dict())
|
||||
headers = {'Accept': ''}
|
||||
self.sess.put.assert_called_with(url, endpoint_filter=sot.service,
|
||||
headers=headers)
|
||||
|
||||
def test_create_no_headers(self):
|
||||
sot = container.Container.new(name=CONTAINER_NAME)
|
||||
|
@ -114,21 +114,22 @@ class TestObject(testtools.TestCase):
|
||||
# "x-newest": True,
|
||||
# "if-match": {"who": "what"}
|
||||
# }
|
||||
self.sess.get.assert_called_with(url, service=sot.service,
|
||||
accept="bytes")
|
||||
headers = {'Accept': 'bytes'}
|
||||
self.sess.get.assert_called_with(url, endpoint_filter=sot.service,
|
||||
headers=headers)
|
||||
self.assertEqual(self.resp.content, rv)
|
||||
|
||||
def _test_create(self, method, data, accept):
|
||||
sot = obj.Object.new(container=CONTAINER_NAME, name=OBJECT_NAME,
|
||||
data=data)
|
||||
sot.newest = True
|
||||
headers = {"x-newest": True}
|
||||
headers = {"x-newest": True, "Accept": ""}
|
||||
|
||||
rv = sot.create(self.sess)
|
||||
|
||||
url = "%s/%s" % (CONTAINER_NAME, OBJECT_NAME)
|
||||
method.assert_called_with(url, service=sot.service, data=data,
|
||||
accept=accept, headers=headers)
|
||||
method.assert_called_with(url, endpoint_filter=sot.service, data=data,
|
||||
headers=headers)
|
||||
self.assertEqual(self.resp.headers, rv.get_headers())
|
||||
|
||||
def test_create_data(self):
|
||||
|
@ -17,10 +17,7 @@ from openstack.object_store.v1 import _proxy
|
||||
from openstack.object_store.v1 import account
|
||||
from openstack.object_store.v1 import container
|
||||
from openstack.object_store.v1 import obj
|
||||
from openstack import session
|
||||
from openstack.tests.unit import fakes
|
||||
from openstack.tests.unit import test_proxy_base
|
||||
from openstack import transport
|
||||
|
||||
|
||||
class TestObjectStoreProxy(test_proxy_base.TestProxyBase):
|
||||
@ -95,10 +92,6 @@ class Test_containers(TestObjectStoreProxy):
|
||||
|
||||
def setUp(self):
|
||||
super(Test_containers, self).setUp()
|
||||
self.transport = transport.Transport(accept=transport.JSON)
|
||||
self.auth = fakes.FakeAuthenticator()
|
||||
self.session = session.Session(self.transport, self.auth)
|
||||
|
||||
self.proxy = _proxy.Proxy(self.session)
|
||||
|
||||
self.containers_body = []
|
||||
@ -174,10 +167,6 @@ class Test_objects(TestObjectStoreProxy):
|
||||
|
||||
def setUp(self):
|
||||
super(Test_objects, self).setUp()
|
||||
self.transport = transport.Transport(accept=transport.JSON)
|
||||
self.auth = fakes.FakeAuthenticator()
|
||||
self.session = session.Session(self.transport, self.auth)
|
||||
|
||||
self.proxy = _proxy.Proxy(self.session)
|
||||
|
||||
self.container_name = six.text_type("my_container")
|
||||
|
@ -84,7 +84,7 @@ class TestStack(testtools.TestCase):
|
||||
|
||||
def test_create(self):
|
||||
resp = mock.MagicMock()
|
||||
resp.body = FAKE_CREATE_RESPONSE
|
||||
resp.json = mock.Mock(return_value=FAKE_CREATE_RESPONSE)
|
||||
sess = mock.Mock()
|
||||
sess.post = mock.MagicMock()
|
||||
sess.post.return_value = resp
|
||||
@ -96,7 +96,8 @@ class TestStack(testtools.TestCase):
|
||||
body = sot._attrs.copy()
|
||||
body.pop('id', None)
|
||||
body.pop('name', None)
|
||||
sess.post.assert_called_with(url, service=sot.service, json=body)
|
||||
sess.post.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json=body)
|
||||
self.assertEqual(FAKE_ID, sot.id)
|
||||
self.assertEqual(FAKE_NAME, sot.name)
|
||||
|
||||
@ -105,10 +106,11 @@ class TestStack(testtools.TestCase):
|
||||
resp_update = mock.Mock()
|
||||
resp_update.status_code = 202
|
||||
sess = mock.Mock()
|
||||
sess.put = mock.Mock(return_value=resp_update)
|
||||
sess.patch = mock.Mock(return_value=resp_update)
|
||||
|
||||
resp_get = mock.Mock()
|
||||
resp_get.body = {'stack': FAKE_CREATE_RESPONSE}
|
||||
resp_get.json = mock.Mock(return_value=resp_get.body)
|
||||
sess.get = mock.Mock(return_value=resp_get)
|
||||
|
||||
# create a stack for update
|
||||
@ -122,9 +124,9 @@ class TestStack(testtools.TestCase):
|
||||
|
||||
url = 'stacks/%s' % sot.id
|
||||
new_body['timeout_mins'] = 119
|
||||
sess.put.assert_called_once_with(url, service=sot.service,
|
||||
sess.put.assert_called_once_with(url, endpoint_filter=sot.service,
|
||||
json=new_body)
|
||||
sess.get.assert_called_once_with(url, service=sot.service)
|
||||
sess.get.assert_called_once_with(url, endpoint_filter=sot.service)
|
||||
self.assertEqual(sot, resp)
|
||||
|
||||
def test_check(self):
|
||||
|
@ -50,6 +50,7 @@ class TestAlarm(testtools.TestCase):
|
||||
super(TestAlarm, self).setUp()
|
||||
self.resp = mock.Mock()
|
||||
self.resp.body = ''
|
||||
self.resp.json = mock.Mock(return_value=self.resp.body)
|
||||
self.sess = mock.Mock()
|
||||
self.sess.put = mock.MagicMock()
|
||||
self.sess.put.return_value = self.resp
|
||||
@ -94,12 +95,12 @@ class TestAlarm(testtools.TestCase):
|
||||
sot.check_state(self.sess)
|
||||
|
||||
url = 'alarms/IDENTIFIER/state'
|
||||
self.sess.get.assert_called_with(url, service=sot.service)
|
||||
self.sess.get.assert_called_with(url, endpoint_filter=sot.service)
|
||||
|
||||
def test_change_status(self):
|
||||
sot = alarm.Alarm(EXAMPLE)
|
||||
self.assertEqual(self.resp.body, sot.change_state(self.sess, 'alarm'))
|
||||
|
||||
url = 'alarms/IDENTIFIER/state'
|
||||
self.sess.put.assert_called_with(url, service=sot.service,
|
||||
self.sess.put.assert_called_with(url, endpoint_filter=sot.service,
|
||||
json='alarm')
|
||||
|
@ -57,7 +57,7 @@ class TestAlarmChange(testtools.TestCase):
|
||||
def test_list(self):
|
||||
sess = mock.Mock()
|
||||
resp = mock.Mock()
|
||||
resp.body = [EXAMPLE, EXAMPLE]
|
||||
resp.json = mock.Mock(return_value=[EXAMPLE, EXAMPLE])
|
||||
sess.get = mock.Mock(return_value=resp)
|
||||
path_args = {'alarm_id': IDENTIFIER}
|
||||
|
||||
|
@ -51,7 +51,7 @@ class TestCapability(testtools.TestCase):
|
||||
def test_list(self):
|
||||
sess = mock.Mock()
|
||||
resp = mock.Mock()
|
||||
resp.body = BODY
|
||||
resp.json = mock.Mock(return_value=BODY)
|
||||
sess.get = mock.Mock(return_value=resp)
|
||||
|
||||
caps = capability.Capability.list(sess)
|
||||
|
@ -95,7 +95,7 @@ class TestSample(testtools.TestCase):
|
||||
def test_list(self):
|
||||
sess = mock.Mock()
|
||||
resp = mock.Mock()
|
||||
resp.body = [SAMPLE, OLD_SAMPLE]
|
||||
resp.json = mock.Mock(return_value=[SAMPLE, OLD_SAMPLE])
|
||||
sess.get = mock.Mock(return_value=resp)
|
||||
path_args = {'counter_name': 'name_of_meter'}
|
||||
|
||||
@ -120,7 +120,7 @@ class TestSample(testtools.TestCase):
|
||||
def test_create(self):
|
||||
sess = mock.Mock()
|
||||
resp = mock.Mock()
|
||||
resp.body = [SAMPLE]
|
||||
resp.json = mock.Mock(return_value=[SAMPLE])
|
||||
sess.post = mock.Mock(return_value=resp)
|
||||
|
||||
data = {'id': None,
|
||||
@ -134,6 +134,6 @@ class TestSample(testtools.TestCase):
|
||||
|
||||
new_sample.create(sess)
|
||||
url = '/meters/temperature'
|
||||
sess.post.assert_called_with(url, service=new_sample.service,
|
||||
sess.post.assert_called_with(url, endpoint_filter=new_sample.service,
|
||||
json=[data])
|
||||
self.assertIsNone(new_sample.id)
|
||||
|
@ -68,7 +68,7 @@ class TestStatistics(testtools.TestCase):
|
||||
def test_list(self):
|
||||
sess = mock.Mock()
|
||||
resp = mock.Mock()
|
||||
resp.body = [EXAMPLE]
|
||||
resp.json = mock.Mock(return_value=[EXAMPLE])
|
||||
sess.get = mock.Mock(return_value=resp)
|
||||
|
||||
args = {'meter_name': 'example'}
|
||||
@ -76,6 +76,7 @@ class TestStatistics(testtools.TestCase):
|
||||
|
||||
url = '/meters/example/statistics'
|
||||
stat = next(reply)
|
||||
sess.get.assert_called_with(url, service=stat.service, params={})
|
||||
sess.get.assert_called_with(url, endpoint_filter=stat.service,
|
||||
params={})
|
||||
self.assertEqual(EXAMPLE, stat)
|
||||
self.assertRaises(StopIteration, next, reply)
|
||||
|
@ -16,11 +16,9 @@ import tempfile
|
||||
import mock
|
||||
import os_client_config
|
||||
|
||||
from openstack.auth.identity import v2
|
||||
from openstack import connection
|
||||
from openstack import profile
|
||||
from openstack.tests.unit import base
|
||||
from openstack import transport
|
||||
|
||||
|
||||
CONFIG_AUTH_URL = "http://127.0.0.1:5000/v2.0"
|
||||
@ -42,47 +40,53 @@ clouds:
|
||||
|
||||
|
||||
class TestConnection(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestConnection, self).setUp()
|
||||
self.xport = transport.Transport()
|
||||
self.auth = v2.Token(auth_url='http://127.0.0.1/v2', token='b')
|
||||
self.prof = profile.Profile()
|
||||
self.conn = connection.Connection(authenticator=mock.MagicMock(),
|
||||
transport=mock.MagicMock())
|
||||
self.conn.session = mock.MagicMock()
|
||||
@mock.patch("openstack.session.Session")
|
||||
def test_other_parameters(self, mock_session_init):
|
||||
mock_session_init.return_value = mock_session_init
|
||||
mock_profile = mock.Mock()
|
||||
mock_profile.get_services = mock.Mock(return_value=[])
|
||||
conn = connection.Connection(profile=mock_profile, authenticator='2',
|
||||
verify=True, user_agent='1')
|
||||
args = {'auth': '2', 'user_agent': '1', 'verify': True}
|
||||
mock_session_init.assert_called_with(mock_profile, **args)
|
||||
self.assertEqual(mock_session_init, conn.session)
|
||||
|
||||
def test_create_transport(self):
|
||||
conn = connection.Connection(authenticator='2', verify=True,
|
||||
user_agent='1')
|
||||
self.assertTrue(conn.transport.verify)
|
||||
self.assertIn('1', conn.transport._user_agent)
|
||||
|
||||
def test_create_authenticator(self):
|
||||
@mock.patch("keystoneauth1.loading.base.get_plugin_loader")
|
||||
def test_create_authenticator(self, mock_get_plugin):
|
||||
mock_plugin = mock.Mock()
|
||||
mock_loader = mock.Mock()
|
||||
mock_options = [
|
||||
mock.Mock(dest="auth_url"),
|
||||
mock.Mock(dest="password"),
|
||||
mock.Mock(dest="username"),
|
||||
]
|
||||
mock_loader.get_options = mock.Mock(return_value=mock_options)
|
||||
mock_loader.load_from_options = mock.Mock(return_value=mock_plugin)
|
||||
mock_get_plugin.return_value = mock_loader
|
||||
auth_args = {
|
||||
'auth_url': '0',
|
||||
'username': '1',
|
||||
'password': '2',
|
||||
}
|
||||
conn = connection.Connection(transport='0', auth_plugin='password',
|
||||
**auth_args)
|
||||
self.assertEqual('0', conn.authenticator.auth_url)
|
||||
self.assertEqual(
|
||||
'1',
|
||||
conn.authenticator.auth_plugin.auth_methods[0].username)
|
||||
self.assertEqual(
|
||||
'2',
|
||||
conn.authenticator.auth_plugin.auth_methods[0].password)
|
||||
conn = connection.Connection(auth_plugin='v2password', **auth_args)
|
||||
mock_get_plugin.assert_called_with('v2password')
|
||||
mock_loader.load_from_options.assert_called_with(**auth_args)
|
||||
self.assertEqual(mock_plugin, conn.authenticator)
|
||||
|
||||
@mock.patch("keystoneauth1.loading.base.get_plugin_loader")
|
||||
def test_pass_authenticator(self, mock_get_plugin):
|
||||
mock_plugin = mock.Mock()
|
||||
mock_get_plugin.return_value = None
|
||||
conn = connection.Connection(authenticator=mock_plugin)
|
||||
self.assertFalse(mock_get_plugin.called)
|
||||
self.assertEqual(mock_plugin, conn.authenticator)
|
||||
|
||||
def test_create_session(self):
|
||||
args = {
|
||||
'transport': self.xport,
|
||||
'authenticator': self.auth,
|
||||
'profile': self.prof,
|
||||
}
|
||||
conn = connection.Connection(**args)
|
||||
self.assertEqual(self.xport, conn.session.transport)
|
||||
self.assertEqual(self.auth, conn.session.authenticator)
|
||||
self.assertEqual(self.prof, conn.session.profile)
|
||||
auth = mock.Mock()
|
||||
prof = profile.Profile()
|
||||
conn = connection.Connection(authenticator=auth, profile=prof)
|
||||
self.assertEqual(auth, conn.authenticator)
|
||||
self.assertEqual(prof, conn.profile)
|
||||
self.assertEqual('openstack.cluster.v1._proxy',
|
||||
conn.cluster.__class__.__module__)
|
||||
self.assertEqual('openstack.compute.v2._proxy',
|
||||
@ -102,12 +106,6 @@ class TestConnection(base.TestCase):
|
||||
self.assertEqual('openstack.telemetry.v2._proxy',
|
||||
conn.telemetry.__class__.__module__)
|
||||
|
||||
def test_custom_user_agent(self):
|
||||
user_agent = "MyProgram/1.0"
|
||||
conn = connection.Connection(authenticator=self.auth,
|
||||
user_agent=user_agent)
|
||||
self.assertTrue(conn.transport._user_agent.startswith(user_agent))
|
||||
|
||||
def _prepare_test_config(self):
|
||||
# Create a temporary directory where our test config will live
|
||||
# and insert it into the search path via OS_CLIENT_CONFIG_FILE.
|
||||
@ -130,13 +128,13 @@ class TestConnection(base.TestCase):
|
||||
sot = connection.from_config(cloud_config=data)
|
||||
|
||||
self.assertEqual(CONFIG_USERNAME,
|
||||
sot.authenticator.auth_plugin.username)
|
||||
sot.authenticator._username)
|
||||
self.assertEqual(CONFIG_PASSWORD,
|
||||
sot.authenticator.auth_plugin.password)
|
||||
sot.authenticator._password)
|
||||
self.assertEqual(CONFIG_AUTH_URL,
|
||||
sot.authenticator.auth_plugin.auth_url)
|
||||
sot.authenticator.auth_url)
|
||||
self.assertEqual(CONFIG_PROJECT,
|
||||
sot.authenticator.auth_plugin.tenant_name)
|
||||
sot.authenticator._project_name)
|
||||
|
||||
def test_from_config_given_name(self):
|
||||
self._prepare_test_config()
|
||||
@ -144,13 +142,13 @@ class TestConnection(base.TestCase):
|
||||
sot = connection.from_config(cloud_name="sample")
|
||||
|
||||
self.assertEqual(CONFIG_USERNAME,
|
||||
sot.authenticator.auth_plugin.username)
|
||||
sot.authenticator._username)
|
||||
self.assertEqual(CONFIG_PASSWORD,
|
||||
sot.authenticator.auth_plugin.password)
|
||||
sot.authenticator._password)
|
||||
self.assertEqual(CONFIG_AUTH_URL,
|
||||
sot.authenticator.auth_plugin.auth_url)
|
||||
sot.authenticator.auth_url)
|
||||
self.assertEqual(CONFIG_PROJECT,
|
||||
sot.authenticator.auth_plugin.tenant_name)
|
||||
sot.authenticator._project_name)
|
||||
|
||||
def test_from_config_given_options(self):
|
||||
self._prepare_test_config()
|
||||
@ -162,7 +160,7 @@ class TestConnection(base.TestCase):
|
||||
|
||||
sot = connection.from_config(cloud_name="sample", options=Opts)
|
||||
|
||||
pref = sot.session.profile.get_preference("compute")
|
||||
pref = sot.session.profile.get_filter("compute")
|
||||
|
||||
# NOTE: Along the way, the `v` prefix gets added so we can build
|
||||
# up URLs with it.
|
||||
|
@ -1,56 +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 openstack import exceptions
|
||||
from openstack import module_loader
|
||||
from openstack.tests.unit import base
|
||||
|
||||
|
||||
class TestModuleLoader(base.TestCase):
|
||||
def test_load_identity_v2(self):
|
||||
loader = module_loader.ModuleLoader()
|
||||
plugin = loader.get_auth_plugin('v2password')
|
||||
self.assertEqual('openstack.auth.identity.v2', str(plugin.__module__))
|
||||
plugin = loader.get_auth_plugin('v2token')
|
||||
self.assertEqual('openstack.auth.identity.v2', str(plugin.__module__))
|
||||
|
||||
def test_load_identity_v3(self):
|
||||
loader = module_loader.ModuleLoader()
|
||||
plugin = loader.get_auth_plugin('v3password')
|
||||
self.assertEqual('openstack.auth.identity.v3', str(plugin.__module__))
|
||||
plugin = loader.get_auth_plugin('v3token')
|
||||
self.assertEqual('openstack.auth.identity.v3', str(plugin.__module__))
|
||||
|
||||
def test_load_identity_discoverable(self):
|
||||
plugin = module_loader.ModuleLoader().get_auth_plugin('password')
|
||||
self.assertEqual('openstack.auth.identity.discoverable',
|
||||
str(plugin.__module__))
|
||||
|
||||
def test_load_none(self):
|
||||
plugin = module_loader.ModuleLoader().get_auth_plugin(None)
|
||||
self.assertEqual('openstack.auth.identity.discoverable',
|
||||
str(plugin.__module__))
|
||||
|
||||
def test_load_empty(self):
|
||||
plugin = module_loader.ModuleLoader().get_auth_plugin('')
|
||||
self.assertEqual('openstack.auth.identity.discoverable',
|
||||
str(plugin.__module__))
|
||||
|
||||
def test_load_bogus(self):
|
||||
self.assertRaises(exceptions.NoMatchingPlugin,
|
||||
module_loader.ModuleLoader().get_auth_plugin, 'wot')
|
||||
|
||||
def test_list_auth_plugins(self):
|
||||
plugins = sorted(module_loader.ModuleLoader().list_auth_plugins())
|
||||
expected = ['password', 'token', 'v2password', 'v2token',
|
||||
'v3password', 'v3token']
|
||||
self.assertEqual(expected, plugins)
|
@ -33,40 +33,30 @@ class TestProfile(base.TestCase):
|
||||
'orchestration',
|
||||
'volume',
|
||||
]
|
||||
self.assertEqual(expected, prof.service_names)
|
||||
self.assertEqual(expected, prof.service_keys)
|
||||
|
||||
def test_set(self):
|
||||
prof = profile.Profile()
|
||||
self.assertEqual(None, prof.get_preference('compute'))
|
||||
prof.set_version('compute', 'v2')
|
||||
self.assertEqual('v2', prof.get_preference('compute').version)
|
||||
self.assertEqual(None, prof.get_preference('clustering'))
|
||||
self.assertEqual('v2', prof.get_filter('compute').version)
|
||||
prof.set_version('clustering', 'v1')
|
||||
self.assertEqual('v1', prof.get_preference('clustering').version)
|
||||
self.assertEqual(None, prof.get_preference('database'))
|
||||
self.assertEqual('v1', prof.get_filter('clustering').version)
|
||||
prof.set_version('database', 'v3')
|
||||
self.assertEqual('v3', prof.get_preference('database').version)
|
||||
self.assertEqual(None, prof.get_preference('identity'))
|
||||
self.assertEqual('v3', prof.get_filter('database').version)
|
||||
prof.set_version('identity', 'v4')
|
||||
self.assertEqual('v4', prof.get_preference('identity').version)
|
||||
self.assertEqual(None, prof.get_preference('image'))
|
||||
self.assertEqual('v4', prof.get_filter('identity').version)
|
||||
prof.set_version('image', 'v5')
|
||||
self.assertEqual('v5', prof.get_preference('image').version)
|
||||
self.assertEqual(None, prof.get_preference('metering'))
|
||||
self.assertEqual('v5', prof.get_filter('image').version)
|
||||
prof.set_version('metering', 'v6')
|
||||
self.assertEqual('v6', prof.get_preference('metering').version)
|
||||
self.assertEqual(None, prof.get_preference('metric'))
|
||||
self.assertEqual('v6', prof.get_filter('metering').version)
|
||||
prof.set_version('metric', 'v9')
|
||||
self.assertEqual('v9', prof.get_preference('metric').version)
|
||||
self.assertEqual(None, prof.get_preference('network'))
|
||||
self.assertEqual('v9', prof.get_filter('metric').version)
|
||||
prof.set_version('network', 'v7')
|
||||
self.assertEqual('v7', prof.get_preference('network').version)
|
||||
self.assertEqual(None, prof.get_preference('object-store'))
|
||||
self.assertEqual('v7', prof.get_filter('network').version)
|
||||
prof.set_version('object-store', 'v8')
|
||||
self.assertEqual('v8', prof.get_preference('object-store').version)
|
||||
self.assertEqual(None, prof.get_preference('orchestration'))
|
||||
self.assertEqual('v8', prof.get_filter('object-store').version)
|
||||
prof.set_version('orchestration', 'v9')
|
||||
self.assertEqual('v9', prof.get_preference('orchestration').version)
|
||||
self.assertEqual('v9', prof.get_filter('orchestration').version)
|
||||
|
||||
def test_set_version_bad_service(self):
|
||||
prof = profile.Profile()
|
||||
@ -78,7 +68,7 @@ class TestProfile(base.TestCase):
|
||||
prof.set_name(prof.ALL, 'fee')
|
||||
prof.set_region(prof.ALL, 'fie')
|
||||
prof.set_interface(prof.ALL, 'public')
|
||||
for service in prof.service_names:
|
||||
self.assertEqual('fee', prof.get_preference(service).service_name)
|
||||
self.assertEqual('fie', prof.get_preference(service).region)
|
||||
self.assertEqual('public', prof.get_preference(service).interface)
|
||||
for service in prof.service_keys:
|
||||
self.assertEqual('fee', prof.get_filter(service).service_name)
|
||||
self.assertEqual('fie', prof.get_filter(service).region)
|
||||
self.assertEqual('public', prof.get_filter(service).interface)
|
||||
|
@ -14,6 +14,8 @@ import copy
|
||||
import json
|
||||
import os
|
||||
|
||||
from keystoneauth1 import exceptions as ksa_exceptions
|
||||
from keystoneauth1 import session
|
||||
import mock
|
||||
import requests
|
||||
from testtools import matchers
|
||||
@ -21,7 +23,6 @@ from testtools import matchers
|
||||
from openstack import exceptions
|
||||
from openstack import format
|
||||
from openstack import resource
|
||||
from openstack import session
|
||||
from openstack.tests.unit import base
|
||||
from openstack import utils
|
||||
|
||||
@ -249,7 +250,8 @@ class HeaderTests(base.TestCase):
|
||||
sot.ho = "johnny"
|
||||
sot.letsgo = "deedee"
|
||||
response = mock.MagicMock()
|
||||
response.body = {'id': 1}
|
||||
response_body = {'id': 1}
|
||||
response.json = mock.Mock(return_value=response_body)
|
||||
sess = mock.MagicMock()
|
||||
sess.post = mock.MagicMock(return_value=response)
|
||||
sess.put = mock.MagicMock(return_value=response)
|
||||
@ -257,7 +259,7 @@ class HeaderTests(base.TestCase):
|
||||
sot.create(sess)
|
||||
headers = {'guitar': 'johnny', 'bass': 'deedee'}
|
||||
sess.post.assert_called_with(HeaderTests.Test.base_path,
|
||||
service=HeaderTests.Test.service,
|
||||
endpoint_filter=HeaderTests.Test.service,
|
||||
headers=headers,
|
||||
json={})
|
||||
|
||||
@ -266,7 +268,7 @@ class HeaderTests(base.TestCase):
|
||||
headers = {'guitar': 'johnny', 'bass': 'cj'}
|
||||
sot.update(sess)
|
||||
sess.put.assert_called_with('ramones/1',
|
||||
service=HeaderTests.Test.service,
|
||||
endpoint_filter=HeaderTests.Test.service,
|
||||
headers=headers,
|
||||
json={})
|
||||
|
||||
@ -276,6 +278,7 @@ class ResourceTests(base.TestCase):
|
||||
def setUp(self):
|
||||
super(ResourceTests, self).setUp()
|
||||
self.session = mock.Mock(spec=session.Session)
|
||||
self.session.get_filter = mock.Mock(return_value={})
|
||||
|
||||
def assertCalledURL(self, method, url):
|
||||
# call_args gives a tuple of *args and tuple of **kwargs.
|
||||
@ -283,7 +286,9 @@ class ResourceTests(base.TestCase):
|
||||
self.assertEqual(method.call_args[0][0], url)
|
||||
|
||||
def test_empty_id(self):
|
||||
self.session.get.return_value = mock.Mock(body=fake_body)
|
||||
resp = mock.Mock()
|
||||
resp.json = mock.Mock(return_value=fake_body)
|
||||
self.session.get.return_value = resp
|
||||
|
||||
obj = FakeResource.new(**fake_arguments)
|
||||
self.assertEqual(obj, obj.get(self.session))
|
||||
@ -344,7 +349,7 @@ class ResourceTests(base.TestCase):
|
||||
service = "my_service"
|
||||
|
||||
response = mock.MagicMock()
|
||||
response.body = response_body
|
||||
response.json = mock.Mock(return_value=response_body)
|
||||
|
||||
sess = mock.MagicMock()
|
||||
sess.put = mock.MagicMock(return_value=response)
|
||||
@ -353,7 +358,7 @@ class ResourceTests(base.TestCase):
|
||||
resp = FakeResource2.create_by_id(sess, attrs)
|
||||
self.assertEqual(response_value, resp)
|
||||
sess.post.assert_called_with(FakeResource2.base_path,
|
||||
service=FakeResource2.service,
|
||||
endpoint_filter=FakeResource2.service,
|
||||
json=json_body)
|
||||
|
||||
r_id = "my_id"
|
||||
@ -361,14 +366,14 @@ class ResourceTests(base.TestCase):
|
||||
self.assertEqual(response_value, resp)
|
||||
sess.put.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path, r_id),
|
||||
service=FakeResource2.service,
|
||||
endpoint_filter=FakeResource2.service,
|
||||
json=json_body)
|
||||
|
||||
path_args = {"parent_name": "my_name"}
|
||||
resp = FakeResource2.create_by_id(sess, attrs, path_args=path_args)
|
||||
self.assertEqual(response_value, resp)
|
||||
sess.post.assert_called_with(FakeResource2.base_path % path_args,
|
||||
service=FakeResource2.service,
|
||||
endpoint_filter=FakeResource2.service,
|
||||
json=json_body)
|
||||
|
||||
resp = FakeResource2.create_by_id(sess, attrs, resource_id=r_id,
|
||||
@ -376,7 +381,7 @@ class ResourceTests(base.TestCase):
|
||||
self.assertEqual(response_value, resp)
|
||||
sess.put.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path % path_args, r_id),
|
||||
service=FakeResource2.service,
|
||||
endpoint_filter=FakeResource2.service,
|
||||
json=json_body)
|
||||
|
||||
def test_create_without_resource_key(self):
|
||||
@ -403,7 +408,7 @@ class ResourceTests(base.TestCase):
|
||||
service = "my_service"
|
||||
|
||||
response = mock.MagicMock()
|
||||
response.body = response_body
|
||||
response.json = mock.Mock(return_value=response_body)
|
||||
|
||||
sess = mock.MagicMock()
|
||||
sess.get = mock.MagicMock(return_value=response)
|
||||
@ -413,7 +418,7 @@ class ResourceTests(base.TestCase):
|
||||
self.assertEqual(response_value, resp)
|
||||
sess.get.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path, r_id),
|
||||
service=FakeResource2.service)
|
||||
endpoint_filter=FakeResource2.service)
|
||||
|
||||
path_args = {"parent_name": "my_name"}
|
||||
resp = FakeResource2.get_data_by_id(sess, resource_id=r_id,
|
||||
@ -421,7 +426,7 @@ class ResourceTests(base.TestCase):
|
||||
self.assertEqual(response_value, resp)
|
||||
sess.get.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path % path_args, r_id),
|
||||
service=FakeResource2.service)
|
||||
endpoint_filter=FakeResource2.service)
|
||||
|
||||
def test_get_data_without_resource_key(self):
|
||||
key = None
|
||||
@ -449,19 +454,21 @@ class ResourceTests(base.TestCase):
|
||||
r_id = "my_id"
|
||||
resp = FakeResource2.head_data_by_id(sess, resource_id=r_id)
|
||||
self.assertEqual({'headers': response_value}, resp)
|
||||
headers = {'Accept': ''}
|
||||
sess.head.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path, r_id),
|
||||
service=FakeResource2.service,
|
||||
accept=None)
|
||||
endpoint_filter=FakeResource2.service,
|
||||
headers=headers)
|
||||
|
||||
path_args = {"parent_name": "my_name"}
|
||||
resp = FakeResource2.head_data_by_id(sess, resource_id=r_id,
|
||||
path_args=path_args)
|
||||
self.assertEqual({'headers': response_value}, resp)
|
||||
headers = {'Accept': ''}
|
||||
sess.head.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path % path_args, r_id),
|
||||
service=FakeResource2.service,
|
||||
accept=None)
|
||||
endpoint_filter=FakeResource2.service,
|
||||
headers=headers)
|
||||
|
||||
def test_head_data_without_resource_key(self):
|
||||
key = None
|
||||
@ -482,7 +489,7 @@ class ResourceTests(base.TestCase):
|
||||
service = "my_service"
|
||||
|
||||
response = mock.MagicMock()
|
||||
response.body = response_body
|
||||
response.json = mock.Mock(return_value=response_body)
|
||||
|
||||
sess = mock.MagicMock()
|
||||
sess.patch = mock.MagicMock(return_value=response)
|
||||
@ -492,7 +499,7 @@ class ResourceTests(base.TestCase):
|
||||
self.assertEqual(response_value, resp)
|
||||
sess.patch.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path, r_id),
|
||||
service=FakeResource2.service,
|
||||
endpoint_filter=FakeResource2.service,
|
||||
json=json_body)
|
||||
|
||||
path_args = {"parent_name": "my_name"}
|
||||
@ -501,7 +508,7 @@ class ResourceTests(base.TestCase):
|
||||
self.assertEqual(response_value, resp)
|
||||
sess.patch.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path % path_args, r_id),
|
||||
service=FakeResource2.service,
|
||||
endpoint_filter=FakeResource2.service,
|
||||
json=json_body)
|
||||
|
||||
def test_update_without_resource_key(self):
|
||||
@ -532,21 +539,24 @@ class ResourceTests(base.TestCase):
|
||||
r_id = "my_id"
|
||||
resp = FakeResource2.delete_by_id(sess, r_id)
|
||||
self.assertIsNone(resp)
|
||||
headers = {'Accept': ''}
|
||||
sess.delete.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path, r_id),
|
||||
service=FakeResource2.service,
|
||||
accept=None)
|
||||
endpoint_filter=FakeResource2.service,
|
||||
headers=headers)
|
||||
|
||||
path_args = {"parent_name": "my_name"}
|
||||
resp = FakeResource2.delete_by_id(sess, r_id, path_args=path_args)
|
||||
self.assertIsNone(resp)
|
||||
headers = {'Accept': ''}
|
||||
sess.delete.assert_called_with(
|
||||
utils.urljoin(FakeResource2.base_path % path_args, r_id),
|
||||
service=FakeResource2.service,
|
||||
accept=None)
|
||||
endpoint_filter=FakeResource2.service,
|
||||
headers=headers)
|
||||
|
||||
def test_create(self):
|
||||
resp = mock.Mock(body=fake_body)
|
||||
resp = mock.Mock()
|
||||
resp.json = mock.Mock(return_value=fake_body)
|
||||
self.session.post = mock.Mock(return_value=resp)
|
||||
|
||||
obj = FakeResource.new(parent_name=fake_parent,
|
||||
@ -580,7 +590,8 @@ class ResourceTests(base.TestCase):
|
||||
self.assertEqual(fake_attr2, obj.second)
|
||||
|
||||
def test_get(self):
|
||||
resp = mock.Mock(body=fake_body)
|
||||
resp = mock.Mock()
|
||||
resp.json = mock.Mock(return_value=fake_body)
|
||||
self.session.get = mock.Mock(return_value=resp)
|
||||
|
||||
obj = FakeResource.get_by_id(self.session, fake_id,
|
||||
@ -606,7 +617,8 @@ class ResourceTests(base.TestCase):
|
||||
headers = {"header1": header1,
|
||||
"header2": header2}
|
||||
|
||||
resp = mock.Mock(body=fake_body, headers=headers)
|
||||
resp = mock.Mock(headers=headers)
|
||||
resp.json = mock.Mock(return_value=fake_body)
|
||||
self.session.get = mock.Mock(return_value=resp)
|
||||
|
||||
class FakeResource2(FakeResource):
|
||||
@ -659,7 +671,8 @@ class ResourceTests(base.TestCase):
|
||||
class FakeResourcePatch(FakeResource):
|
||||
patch_update = True
|
||||
|
||||
resp = mock.Mock(body=fake_body)
|
||||
resp = mock.Mock()
|
||||
resp.json = mock.Mock(return_value=fake_body)
|
||||
self.session.patch = mock.Mock(return_value=resp)
|
||||
|
||||
obj = FakeResourcePatch.new(id=fake_id, parent_name=fake_parent,
|
||||
@ -693,7 +706,8 @@ class ResourceTests(base.TestCase):
|
||||
# This is False by default, but explicit for this test.
|
||||
patch_update = False
|
||||
|
||||
resp = mock.Mock(body=fake_body)
|
||||
resp = mock.Mock()
|
||||
resp.json = mock.Mock(return_value=fake_body)
|
||||
self.session.put = mock.Mock(return_value=resp)
|
||||
|
||||
obj = FakeResourcePut.new(id=fake_id, parent_name=fake_parent,
|
||||
@ -756,8 +770,11 @@ class ResourceTests(base.TestCase):
|
||||
else:
|
||||
body = self._get_expected_results()
|
||||
sentinel = []
|
||||
self.session.get.side_effect = [mock.Mock(body=body),
|
||||
mock.Mock(body=sentinel)]
|
||||
resp1 = mock.Mock()
|
||||
resp1.json = mock.Mock(return_value=body)
|
||||
resp2 = mock.Mock()
|
||||
resp2.json = mock.Mock(return_value=sentinel)
|
||||
self.session.get.side_effect = [resp1, resp2]
|
||||
|
||||
objs = list(resource_class.list(self.session, path_args=fake_arguments,
|
||||
paginated=True))
|
||||
@ -786,8 +803,9 @@ class ResourceTests(base.TestCase):
|
||||
def _test_list_call_count(self, paginated):
|
||||
# Test that we've only made one call to receive all data
|
||||
results = [fake_data.copy(), fake_data.copy(), fake_data.copy()]
|
||||
body = mock.Mock(body={fake_resources: results})
|
||||
attrs = {"get.return_value": body}
|
||||
resp = mock.Mock()
|
||||
resp.json = mock.Mock(return_value={fake_resources: results})
|
||||
attrs = {"get.return_value": resp}
|
||||
session = mock.Mock(**attrs)
|
||||
|
||||
list(FakeResource.list(session, params={'limit': len(results) + 1},
|
||||
@ -812,9 +830,11 @@ class ResourceTests(base.TestCase):
|
||||
session = mock.MagicMock()
|
||||
session.get = mock.MagicMock()
|
||||
full_response = mock.MagicMock()
|
||||
full_response.body = {FakeResource.resources_key: full_page}
|
||||
response_body = {FakeResource.resources_key: full_page}
|
||||
full_response.json = mock.Mock(return_value=response_body)
|
||||
last_response = mock.MagicMock()
|
||||
last_response.body = {FakeResource.resources_key: last_page}
|
||||
response_body = {FakeResource.resources_key: last_page}
|
||||
last_response.json = mock.Mock(return_value=response_body)
|
||||
pages = [full_response, full_response, last_response]
|
||||
session.get.side_effect = pages
|
||||
|
||||
@ -832,7 +852,8 @@ class ResourceTests(base.TestCase):
|
||||
session = mock.Mock()
|
||||
session.get = mock.Mock()
|
||||
full_response = mock.Mock()
|
||||
full_response.body = {FakeResource.resources_key: page}
|
||||
response_body = {FakeResource.resources_key: page}
|
||||
full_response.json = mock.Mock(return_value=response_body)
|
||||
pages = [full_response]
|
||||
session.get.side_effect = pages
|
||||
|
||||
@ -1152,7 +1173,9 @@ class ResourceMapping(base.TestCase):
|
||||
json.dumps(attrs)
|
||||
except TypeError as e:
|
||||
self.fail("Unable to serialize _attrs: %s" % e)
|
||||
return mock.Mock(body=attrs)
|
||||
resp = mock.Mock()
|
||||
resp.json = mock.Mock(return_value=attrs)
|
||||
return resp
|
||||
|
||||
session = mock.Mock()
|
||||
setattr(session, session_method, mock.Mock(side_effect=fake_call))
|
||||
@ -1173,6 +1196,9 @@ class FakeResponse(object):
|
||||
def __init__(self, response):
|
||||
self.body = response
|
||||
|
||||
def json(self):
|
||||
return self.body
|
||||
|
||||
|
||||
class TestFind(base.TestCase):
|
||||
NAME = 'matrix'
|
||||
@ -1188,7 +1214,7 @@ class TestFind(base.TestCase):
|
||||
|
||||
def test_name(self):
|
||||
self.mock_get.side_effect = [
|
||||
exceptions.HttpException(404, 'not found'),
|
||||
ksa_exceptions.http.NotFound(),
|
||||
FakeResponse({FakeResource.resources_key: [self.matrix]})
|
||||
]
|
||||
|
||||
@ -1210,7 +1236,7 @@ class TestFind(base.TestCase):
|
||||
self.assertEqual(self.PROP, result.prop)
|
||||
|
||||
path = "fakes/" + fake_parent + "/data/" + self.ID
|
||||
self.mock_get.assert_any_call(path, service=None)
|
||||
self.mock_get.assert_any_call(path, endpoint_filter=None)
|
||||
|
||||
def test_id_no_retrieve(self):
|
||||
self.mock_get.side_effect = [
|
||||
@ -1231,7 +1257,7 @@ class TestFind(base.TestCase):
|
||||
dupe['id'] = 'different'
|
||||
self.mock_get.side_effect = [
|
||||
# Raise a 404 first so we get out of the ID search and into name.
|
||||
exceptions.HttpException(404, 'not found'),
|
||||
ksa_exceptions.http.NotFound(),
|
||||
FakeResponse({FakeResource.resources_key: [self.matrix, dupe]})
|
||||
]
|
||||
|
||||
@ -1255,11 +1281,11 @@ class TestFind(base.TestCase):
|
||||
|
||||
p = {'ip_address': "127.0.0.1"}
|
||||
path = fake_path + "?limit=2"
|
||||
self.mock_get.called_once_with(path, params=p, service=None)
|
||||
self.mock_get.called_once_with(path, params=p, endpoint_filter=None)
|
||||
|
||||
def test_nada(self):
|
||||
self.mock_get.side_effect = [
|
||||
exceptions.HttpException(404, 'not found'),
|
||||
ksa_exceptions.http.NotFound(),
|
||||
FakeResponse({FakeResource.resources_key: []})
|
||||
]
|
||||
|
||||
@ -1267,7 +1293,7 @@ class TestFind(base.TestCase):
|
||||
|
||||
def test_no_name(self):
|
||||
self.mock_get.side_effect = [
|
||||
exceptions.HttpException(404, 'not found'),
|
||||
ksa_exceptions.http.NotFound(),
|
||||
FakeResponse({FakeResource.resources_key: [self.matrix]})
|
||||
]
|
||||
FakeResource.name_attribute = None
|
||||
@ -1276,7 +1302,7 @@ class TestFind(base.TestCase):
|
||||
|
||||
def test_nada_not_ignored(self):
|
||||
self.mock_get.side_effect = [
|
||||
exceptions.HttpException(404, 'not found'),
|
||||
ksa_exceptions.http.NotFound(),
|
||||
FakeResponse({FakeResource.resources_key: []})
|
||||
]
|
||||
|
||||
@ -1350,7 +1376,9 @@ class TestWaitForDelete(base.TestCase):
|
||||
sess = mock.Mock()
|
||||
sot = FakeResource.new(**fake_data)
|
||||
sot.get = mock.MagicMock()
|
||||
sot.get.side_effect = [sot, exceptions.NotFoundException(mock.Mock())]
|
||||
sot.get.side_effect = [
|
||||
sot,
|
||||
ksa_exceptions.http.NotFound()]
|
||||
|
||||
self.assertEqual(sot, resource.wait_for_delete(sess, sot, 1, 2))
|
||||
|
||||
|
@ -10,149 +10,21 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
import testtools
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack.identity import identity_service
|
||||
from openstack import service_filter as filt
|
||||
|
||||
|
||||
class TestServiceFilter(testtools.TestCase):
|
||||
def test_minimum(self):
|
||||
sot = filt.ServiceFilter()
|
||||
self.assertEqual("service_type=any,interface=public",
|
||||
six.text_type(sot))
|
||||
|
||||
def test_maximum(self):
|
||||
sot = filt.ServiceFilter(service_type='compute', interface='admin',
|
||||
region='b', service_name='c')
|
||||
exp = "service_type=compute,interface=admin,region=b,service_name=c"
|
||||
self.assertEqual(exp, six.text_type(sot))
|
||||
|
||||
def test_interface(self):
|
||||
sot = filt.ServiceFilter(service_type='identity', interface='public')
|
||||
self.assertEqual("service_type=identity,interface=public",
|
||||
six.text_type(sot))
|
||||
sot = filt.ServiceFilter(service_type='identity',
|
||||
interface='internal')
|
||||
self.assertEqual("service_type=identity,interface=internal",
|
||||
six.text_type(sot))
|
||||
sot = filt.ServiceFilter(service_type='identity', interface='admin')
|
||||
self.assertEqual("service_type=identity,interface=admin",
|
||||
six.text_type(sot))
|
||||
sot = filt.ServiceFilter(service_type='identity',
|
||||
interface='publicURL')
|
||||
self.assertEqual("service_type=identity,interface=public",
|
||||
six.text_type(sot))
|
||||
sot = filt.ServiceFilter(service_type='identity',
|
||||
interface='internalURL')
|
||||
self.assertEqual("service_type=identity,interface=internal",
|
||||
six.text_type(sot))
|
||||
sot = filt.ServiceFilter(service_type='identity',
|
||||
interface='adminURL')
|
||||
self.assertEqual("service_type=identity,interface=admin",
|
||||
six.text_type(sot))
|
||||
self.assertRaises(exceptions.SDKException, filt.ServiceFilter,
|
||||
service_type='identity', interface='b')
|
||||
sot = filt.ServiceFilter(service_type='identity', interface=None)
|
||||
self.assertEqual("service_type=identity", six.text_type(sot))
|
||||
|
||||
def test_match_service_type(self):
|
||||
sot = filt.ServiceFilter(service_type='identity')
|
||||
self.assertTrue(sot.match_service_type('identity'))
|
||||
self.assertFalse(sot.match_service_type('compute'))
|
||||
|
||||
def test_match_service_type_any(self):
|
||||
sot = filt.ServiceFilter()
|
||||
self.assertTrue(sot.match_service_type('identity'))
|
||||
self.assertTrue(sot.match_service_type('compute'))
|
||||
|
||||
def test_match_service_name(self):
|
||||
sot = filt.ServiceFilter(service_type='identity')
|
||||
self.assertTrue(sot.match_service_name('keystone'))
|
||||
self.assertTrue(sot.match_service_name('ldap'))
|
||||
self.assertTrue(sot.match_service_name(None))
|
||||
sot = filt.ServiceFilter(service_type='identity',
|
||||
service_name='keystone')
|
||||
self.assertTrue(sot.match_service_name('keystone'))
|
||||
self.assertFalse(sot.match_service_name('ldap'))
|
||||
self.assertFalse(sot.match_service_name(None))
|
||||
|
||||
def test_match_region(self):
|
||||
sot = filt.ServiceFilter(service_type='identity')
|
||||
self.assertTrue(sot.match_region('East'))
|
||||
self.assertTrue(sot.match_region('West'))
|
||||
self.assertTrue(sot.match_region(None))
|
||||
sot = filt.ServiceFilter(service_type='identity', region='East')
|
||||
self.assertTrue(sot.match_region('East'))
|
||||
self.assertFalse(sot.match_region('West'))
|
||||
self.assertFalse(sot.match_region(None))
|
||||
|
||||
def test_match_interface(self):
|
||||
sot = filt.ServiceFilter(service_type='identity',
|
||||
interface='internal')
|
||||
self.assertFalse(sot.match_interface('admin'))
|
||||
self.assertTrue(sot.match_interface('internal'))
|
||||
self.assertFalse(sot.match_interface('public'))
|
||||
|
||||
def test_join(self):
|
||||
a = filt.ServiceFilter(region='east')
|
||||
b = filt.ServiceFilter(service_type='identity')
|
||||
result = a.join(b)
|
||||
self.assertEqual("service_type=identity,interface=public,region=east",
|
||||
six.text_type(result))
|
||||
self.assertEqual("service_type=any,interface=public,region=east",
|
||||
six.text_type(a))
|
||||
self.assertEqual("service_type=identity,interface=public",
|
||||
six.text_type(b))
|
||||
|
||||
def test_join_interface(self):
|
||||
user_preference = filt.ServiceFilter(interface='public')
|
||||
service_default = filt.ServiceFilter(interface='admin')
|
||||
result = user_preference.join(service_default)
|
||||
self.assertEqual("public", result.interface)
|
||||
user_preference = filt.ServiceFilter(interface=None)
|
||||
service_default = filt.ServiceFilter(interface='admin')
|
||||
result = user_preference.join(service_default)
|
||||
self.assertEqual("admin", result.interface)
|
||||
|
||||
def test_join_version(self):
|
||||
user_preference = filt.ServiceFilter(version='v2')
|
||||
service_default = filt.ServiceFilter()
|
||||
self.assertEqual('v2', user_preference.join(service_default).version)
|
||||
service_default = filt.ServiceFilter(
|
||||
version=filt.ServiceFilter.UNVERSIONED
|
||||
)
|
||||
self.assertEqual('', user_preference.join(service_default).version)
|
||||
|
||||
def test_set_interface(self):
|
||||
sot = filt.ServiceFilter()
|
||||
sot.set_interface("PUBLICURL")
|
||||
self.assertEqual('public', sot.interface)
|
||||
sot.set_interface("INTERNALURL")
|
||||
self.assertEqual('internal', sot.interface)
|
||||
sot.set_interface("ADMINURL")
|
||||
self.assertEqual('admin', sot.interface)
|
||||
|
||||
def test_get_module(self):
|
||||
sot = identity_service.IdentityService()
|
||||
self.assertEqual('openstack.identity.v3', sot.get_module())
|
||||
self.assertEqual('identity', sot.get_service_module())
|
||||
|
||||
def test_get_version_path(self):
|
||||
sot = identity_service.IdentityService()
|
||||
self.assertEqual('v3', sot.get_version_path('v2'))
|
||||
sot = identity_service.IdentityService(version='v2')
|
||||
self.assertEqual('v2', sot.get_version_path('v3'))
|
||||
sot = identity_service.IdentityService(version='v2.1')
|
||||
self.assertEqual('v2.1', sot.get_version_path('v3'))
|
||||
sot = identity_service.IdentityService(version='')
|
||||
self.assertEqual('', sot.get_version_path('v3'))
|
||||
from openstack import service_filter
|
||||
|
||||
|
||||
class TestValidVersion(testtools.TestCase):
|
||||
def test_constructor(self):
|
||||
sot = filt.ValidVersion('v1.0', 'v1')
|
||||
sot = service_filter.ValidVersion('v1.0', 'v1')
|
||||
self.assertEqual('v1.0', sot.module)
|
||||
self.assertEqual('v1', sot.path)
|
||||
|
||||
|
||||
class TestServiceFilter(testtools.TestCase):
|
||||
def test_get_module(self):
|
||||
sot = identity_service.IdentityService()
|
||||
self.assertEqual('openstack.identity.v3', sot.get_module())
|
||||
self.assertEqual('identity', sot.get_service_module())
|
||||
|
@ -10,74 +10,26 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstack import service_filter
|
||||
import testtools
|
||||
|
||||
from openstack.image import image_service
|
||||
from openstack import session
|
||||
from openstack.tests.unit import base
|
||||
from openstack.tests.unit import fakes
|
||||
|
||||
|
||||
class TestSession(base.TestCase):
|
||||
class TestSession(testtools.TestCase):
|
||||
|
||||
TEST_PATH = '/test/path'
|
||||
|
||||
def setUp(self):
|
||||
super(TestSession, self).setUp()
|
||||
self.xport = fakes.FakeTransport()
|
||||
self.auth = fakes.FakeAuthenticator()
|
||||
self.serv = service_filter.ServiceFilter(service_type='identity')
|
||||
self.sess = session.Session(self.xport, self.auth)
|
||||
self.expected = {'headers': {'X-Auth-Token': self.auth.TOKEN}}
|
||||
|
||||
def test_head(self):
|
||||
resp = self.sess.head(self.TEST_PATH, service=self.serv)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('HEAD', url, **self.expected)
|
||||
|
||||
def test_get(self):
|
||||
resp = self.sess.get(self.TEST_PATH, service=self.serv)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('GET', url, **self.expected)
|
||||
|
||||
def test_post(self):
|
||||
resp = self.sess.post(self.TEST_PATH, service=self.serv)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('POST', url, **self.expected)
|
||||
|
||||
def test_put(self):
|
||||
resp = self.sess.put(self.TEST_PATH, service=self.serv)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('PUT', url, **self.expected)
|
||||
|
||||
def test_delete(self):
|
||||
resp = self.sess.delete(self.TEST_PATH, service=self.serv)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('DELETE', url, **self.expected)
|
||||
|
||||
def test_patch(self):
|
||||
resp = self.sess.patch(self.TEST_PATH, service=self.serv)
|
||||
|
||||
self.assertEqual(self.xport.RESPONSE, resp)
|
||||
self.auth.get_token.assert_called_with(self.xport)
|
||||
self.auth.get_endpoint.assert_called_with(self.xport, self.serv)
|
||||
url = self.auth.ENDPOINT + self.TEST_PATH
|
||||
self.xport.request.assert_called_with('PATCH', url, **self.expected)
|
||||
def test_parse_url(self):
|
||||
filt = image_service.ImageService()
|
||||
self.assertEqual(
|
||||
"http://127.0.0.1:9292/v1",
|
||||
session.parse_url(filt, "http://127.0.0.1:9292"))
|
||||
self.assertEqual(
|
||||
"http://127.0.0.1:9292/v2",
|
||||
session.parse_url(filt, "http://127.0.0.1:9292/v2.0"))
|
||||
filt.version = 'v1'
|
||||
self.assertEqual(
|
||||
"http://127.0.0.1:9292/v1/mytenant",
|
||||
session.parse_url(filt, "http://127.0.0.1:9292/v2.0/mytenant/"))
|
||||
self.assertEqual(
|
||||
"http://127.0.0.1:9292/wot/v1/mytenant",
|
||||
session.parse_url(filt, "http://127.0.0.1:9292/wot/v2.0/mytenant"))
|
||||
|
@ -1,556 +0,0 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
# 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 json
|
||||
|
||||
import mock
|
||||
import requests
|
||||
import requests_mock
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack.tests.unit import base
|
||||
from openstack import transport
|
||||
|
||||
|
||||
fake_request = 'Now is the time...'
|
||||
fake_response = 'for the quick brown fox...'
|
||||
fake_response_json = '{"response": "for the quick brown fox..."}'
|
||||
fake_redirect = 'redirect text'
|
||||
|
||||
fake_record1 = {
|
||||
'key1': {
|
||||
'id': '123',
|
||||
'name': 'OneTwoThree',
|
||||
'random': 'qwertyuiop',
|
||||
},
|
||||
}
|
||||
|
||||
fake_record2 = {
|
||||
'hello': 'world',
|
||||
}
|
||||
|
||||
|
||||
class TestTransportBase(base.TestCase):
|
||||
|
||||
TEST_URL = 'http://www.root.url'
|
||||
|
||||
def assertRequestHeaderEqual(self, mocked_req, name, val):
|
||||
"""Verify that the last request made contains a header and its value
|
||||
|
||||
"""
|
||||
headers = mocked_req.last_request.headers
|
||||
self.assertEqual(val, headers.get(name))
|
||||
|
||||
def assertResponseOK(self, resp, status=200, body=None):
|
||||
"""Verify the Response object contains expected values
|
||||
|
||||
Tests our defaults for a successful request.
|
||||
"""
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertEqual(status, resp.status_code)
|
||||
if body:
|
||||
self.assertEqual(body, resp.text)
|
||||
|
||||
|
||||
class TestTransport(TestTransportBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestTransport, self).setUp()
|
||||
|
||||
self._orig_user_agent = transport.USER_AGENT
|
||||
self.test_user_agent = transport.USER_AGENT = "testing/1.0"
|
||||
|
||||
def tearDown(self):
|
||||
super(TestTransport, self).tearDown()
|
||||
|
||||
transport.USER_AGENT = self._orig_user_agent
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_request(self, mock_req):
|
||||
mock_req.get(self.TEST_URL, text=fake_response)
|
||||
req = "GET"
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.request(req, self.TEST_URL, accept=None)
|
||||
|
||||
self.assertEqual(req, mock_req.last_request.method)
|
||||
self.assertResponseOK(resp, body=fake_response)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_request_json(self, mock_req):
|
||||
mock_req.get(self.TEST_URL, json=fake_record1)
|
||||
req = "GET"
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.request(req, self.TEST_URL, accept=None)
|
||||
|
||||
self.assertEqual(req, mock_req.last_request.method)
|
||||
self.assertResponseOK(resp, body=json.dumps(fake_record1))
|
||||
self.assertEqual(fake_record1, resp.json())
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_delete(self, mock_req):
|
||||
mock_req.delete(self.TEST_URL, text=fake_response)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.delete(self.TEST_URL, accept=None)
|
||||
|
||||
self.assertEqual("DELETE", mock_req.last_request.method)
|
||||
self.assertResponseOK(resp, body=fake_response)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_get(self, mock_req):
|
||||
mock_req.get(self.TEST_URL, text=fake_response)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.get(self.TEST_URL, accept=None)
|
||||
|
||||
self.assertEqual("GET", mock_req.last_request.method)
|
||||
self.assertResponseOK(resp, body=fake_response)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_head(self, mock_req):
|
||||
mock_req.head(self.TEST_URL, text="")
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.head(self.TEST_URL, accept=None)
|
||||
|
||||
self.assertEqual("HEAD", mock_req.last_request.method)
|
||||
self.assertResponseOK(resp, body='')
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_patch(self, mock_req):
|
||||
mock_req.patch(self.TEST_URL, text=fake_response_json)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.patch(self.TEST_URL, json=fake_record2)
|
||||
|
||||
self.assertEqual("PATCH", mock_req.last_request.method)
|
||||
self.assertEqual(
|
||||
json.dumps(fake_record2),
|
||||
mock_req.last_request.body,
|
||||
)
|
||||
self.assertResponseOK(resp, body=fake_response_json)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_post(self, mock_req):
|
||||
mock_req.post(self.TEST_URL, text=fake_response_json)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.post(self.TEST_URL, json=fake_record2)
|
||||
|
||||
self.assertEqual("POST", mock_req.last_request.method)
|
||||
self.assertEqual(
|
||||
json.dumps(fake_record2),
|
||||
mock_req.last_request.body,
|
||||
)
|
||||
self.assertResponseOK(resp, body=fake_response_json)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_put(self, mock_req):
|
||||
mock_req.put(self.TEST_URL, text=fake_response)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.put(self.TEST_URL, data=fake_request, accept=None)
|
||||
|
||||
self.assertEqual("PUT", mock_req.last_request.method)
|
||||
self.assertEqual(
|
||||
fake_request,
|
||||
mock_req.last_request.body,
|
||||
)
|
||||
self.assertResponseOK(resp, body=fake_response)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_put_json(self, mock_req):
|
||||
mock_req.put(self.TEST_URL, text=fake_response_json)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.put(self.TEST_URL, json=fake_record2)
|
||||
|
||||
self.assertEqual("PUT", mock_req.last_request.method)
|
||||
self.assertEqual(
|
||||
json.dumps(fake_record2),
|
||||
mock_req.last_request.body,
|
||||
)
|
||||
self.assertResponseOK(resp, body=fake_response_json)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_request_accept(self, mock_req):
|
||||
fake_record1_str = json.dumps(fake_record1)
|
||||
mock_req.post(self.TEST_URL, text=fake_record1_str)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.post(self.TEST_URL, json=fake_record2, accept=None)
|
||||
|
||||
self.assertRequestHeaderEqual(mock_req, 'Accept', '*/*')
|
||||
self.assertEqual(fake_record1, resp.json())
|
||||
|
||||
resp = xport.post(self.TEST_URL, json=fake_record2,
|
||||
accept=transport.JSON)
|
||||
|
||||
self.assertRequestHeaderEqual(mock_req, 'Accept', transport.JSON)
|
||||
self.assertEqual(fake_record1, resp.json())
|
||||
|
||||
xport = transport.Transport(accept=transport.JSON)
|
||||
resp = xport.post(self.TEST_URL, json=fake_record2)
|
||||
|
||||
self.assertRequestHeaderEqual(mock_req, 'Accept', transport.JSON)
|
||||
self.assertEqual(fake_record1, resp.json())
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_request_status_202(self, mock_req):
|
||||
mock_req.put(self.TEST_URL, text='', status_code=202)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.put(self.TEST_URL, json=fake_record2)
|
||||
|
||||
self.assertEqual(202, resp.status_code)
|
||||
self.assertEqual({}, resp.body)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_user_agent_no_arg(self, mock_req):
|
||||
mock_req.get(self.TEST_URL, text=fake_response)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.get(self.TEST_URL, accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent',
|
||||
self.test_user_agent)
|
||||
|
||||
resp = xport.get(self.TEST_URL, headers={'User-Agent': None},
|
||||
accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent', None)
|
||||
|
||||
resp = xport.get(self.TEST_URL, user_agent=None, accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent',
|
||||
self.test_user_agent)
|
||||
|
||||
resp = xport.get(self.TEST_URL, headers={'User-Agent': ''},
|
||||
accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent', '')
|
||||
|
||||
resp = xport.get(self.TEST_URL, user_agent='', accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent',
|
||||
self.test_user_agent)
|
||||
|
||||
new_agent = 'new-agent'
|
||||
resp = xport.get(self.TEST_URL, headers={'User-Agent': new_agent},
|
||||
accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent', new_agent)
|
||||
|
||||
resp = xport.get(self.TEST_URL, user_agent=new_agent, accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent', '%s %s' % (
|
||||
new_agent, self.test_user_agent))
|
||||
|
||||
custom_value = 'new-agent'
|
||||
resp = xport.get(self.TEST_URL, headers={'User-Agent': custom_value},
|
||||
user_agent=None, accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent', custom_value)
|
||||
|
||||
override = 'overrides-agent'
|
||||
resp = xport.get(self.TEST_URL, headers={'User-Agent': None},
|
||||
user_agent=override, accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent', '%s %s' % (
|
||||
override, self.test_user_agent))
|
||||
|
||||
resp = xport.get(self.TEST_URL, headers={'User-Agent': custom_value},
|
||||
user_agent=override, accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent', '%s %s' % (
|
||||
override, self.test_user_agent))
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_user_agent_arg_none(self, mock_req):
|
||||
# None gets converted to the transport.USER_AGENT by default.
|
||||
mock_req.get(self.TEST_URL, text=fake_response)
|
||||
|
||||
xport = transport.Transport(user_agent=None)
|
||||
resp = xport.get(self.TEST_URL, accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent',
|
||||
self.test_user_agent)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_user_agent_arg_default(self, mock_req):
|
||||
mock_req.get(self.TEST_URL, text=fake_response)
|
||||
|
||||
agent = 'test-agent'
|
||||
xport = transport.Transport(user_agent=agent)
|
||||
resp = xport.get(self.TEST_URL, accept=None)
|
||||
|
||||
self.assertTrue(resp.ok)
|
||||
self.assertRequestHeaderEqual(mock_req, 'User-Agent', '%s %s' % (
|
||||
agent, self.test_user_agent))
|
||||
|
||||
def test_verify_no_arg(self):
|
||||
xport = transport.Transport()
|
||||
self.assertTrue(xport.verify)
|
||||
|
||||
def test_verify_arg_false(self):
|
||||
xport = transport.Transport(verify=False)
|
||||
self.assertFalse(xport.verify)
|
||||
|
||||
def test_verify_arg_true(self):
|
||||
xport = transport.Transport(verify=True)
|
||||
self.assertTrue(xport.verify)
|
||||
|
||||
def test_verify_arg_file(self):
|
||||
xport = transport.Transport(verify='ca-file')
|
||||
self.assertEqual('ca-file', xport.verify)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_not_found(self, mock_req):
|
||||
xport = transport.Transport()
|
||||
status = 404
|
||||
|
||||
mock_req.get(self.TEST_URL, status_code=status)
|
||||
|
||||
exc = self.assertRaises(exceptions.NotFoundException, xport.get,
|
||||
self.TEST_URL)
|
||||
self.assertEqual(status, exc.status_code)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_server_error(self, mock_req):
|
||||
xport = transport.Transport()
|
||||
status = 500
|
||||
|
||||
mock_req.get(self.TEST_URL, status_code=500)
|
||||
|
||||
exc = self.assertRaises(exceptions.HttpException, xport.get,
|
||||
self.TEST_URL)
|
||||
self.assertEqual(status, exc.status_code)
|
||||
|
||||
|
||||
class TestTransportRedirects(TestTransportBase):
|
||||
|
||||
REDIRECT_CHAIN = [
|
||||
'http://myhost:3445/',
|
||||
'http://anotherhost:6555/',
|
||||
'http://thirdhost/',
|
||||
'http://finaldestination:55/',
|
||||
]
|
||||
|
||||
def setup_redirects(self, mocked_req, method="GET", status_code=305,
|
||||
redirect_kwargs=None, final_kwargs=None):
|
||||
if redirect_kwargs is None:
|
||||
redirect_kwargs = {}
|
||||
|
||||
if final_kwargs is None:
|
||||
final_kwargs = {}
|
||||
|
||||
redirect_kwargs.setdefault('text', fake_redirect)
|
||||
|
||||
for s, d in zip(self.REDIRECT_CHAIN, self.REDIRECT_CHAIN[1:]):
|
||||
mocked_req.register_uri(method, s, status_code=status_code,
|
||||
headers={"location": d}, **redirect_kwargs)
|
||||
|
||||
final_kwargs.setdefault('status_code', 200)
|
||||
final_kwargs.setdefault('text', fake_response)
|
||||
mocked_req.register_uri(method, self.REDIRECT_CHAIN[-1],
|
||||
**final_kwargs)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_get_redirect(self, mock_req):
|
||||
self.setup_redirects(mock_req)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.get(self.REDIRECT_CHAIN[-2], accept=None)
|
||||
|
||||
self.assertResponseOK(resp, body=fake_response)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_get_redirect_json(self, mock_req):
|
||||
self.setup_redirects(mock_req,
|
||||
final_kwargs={'text': fake_response_json})
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.get(self.REDIRECT_CHAIN[-2])
|
||||
|
||||
self.assertResponseOK(resp, body=fake_response_json)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_post_keeps_correct_method(self, mock_req):
|
||||
self.setup_redirects(mock_req, method="POST", status_code=301)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.post(self.REDIRECT_CHAIN[-2], accept=None)
|
||||
|
||||
self.assertResponseOK(resp, body=fake_response)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_post_keeps_correct_method_json(self, mock_req):
|
||||
self.setup_redirects(mock_req, method="POST", status_code=301,
|
||||
final_kwargs={'text': fake_response_json})
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.post(self.REDIRECT_CHAIN[-2])
|
||||
|
||||
self.assertResponseOK(resp, body=fake_response_json)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_redirect_forever(self, mock_req):
|
||||
self.setup_redirects(mock_req)
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.get(self.REDIRECT_CHAIN[0], accept=None)
|
||||
|
||||
self.assertResponseOK(resp)
|
||||
# Request history length is 1 less than the source chain due to the
|
||||
# last response not being a redirect and not added to the history.
|
||||
self.assertEqual(len(self.REDIRECT_CHAIN) - 1, len(resp.history))
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_redirect_forever_json(self, mock_req):
|
||||
self.setup_redirects(mock_req,
|
||||
final_kwargs={'text': fake_response_json})
|
||||
|
||||
xport = transport.Transport()
|
||||
resp = xport.get(self.REDIRECT_CHAIN[0])
|
||||
|
||||
self.assertResponseOK(resp)
|
||||
# Request history length is 1 less than the source chain due to the
|
||||
# last response not being a redirect and not added to the history.
|
||||
self.assertEqual(len(self.REDIRECT_CHAIN) - 1, len(resp.history))
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_no_redirect(self, mock_req):
|
||||
self.setup_redirects(mock_req)
|
||||
|
||||
xport = transport.Transport(redirect=False)
|
||||
resp = xport.get(self.REDIRECT_CHAIN[0], accept=None)
|
||||
|
||||
self.assertEqual(305, resp.status_code)
|
||||
self.assertEqual(self.REDIRECT_CHAIN[0], resp.url)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_no_redirect_json(self, mock_req):
|
||||
self.setup_redirects(mock_req,
|
||||
final_kwargs={'text': fake_response_json})
|
||||
|
||||
xport = transport.Transport(redirect=False)
|
||||
self.assertRaises(exceptions.InvalidResponse, xport.get,
|
||||
self.REDIRECT_CHAIN[0])
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_redirect_limit(self, mock_req):
|
||||
self.setup_redirects(mock_req)
|
||||
|
||||
for i in (1, 2):
|
||||
xport = transport.Transport(redirect=i)
|
||||
resp = xport.get(self.REDIRECT_CHAIN[0], accept=None)
|
||||
|
||||
self.assertResponseOK(resp, status=305, body=fake_redirect)
|
||||
self.assertEqual(self.REDIRECT_CHAIN[i], resp.url)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_redirect_limit_json(self, mock_req):
|
||||
self.setup_redirects(mock_req,
|
||||
final_kwargs={'text': fake_response_json})
|
||||
|
||||
for i in (1, 2):
|
||||
xport = transport.Transport(redirect=i)
|
||||
self.assertRaises(exceptions.InvalidResponse, xport.get,
|
||||
self.REDIRECT_CHAIN[0])
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_history_matches_requests(self, mock_req):
|
||||
self.setup_redirects(mock_req, status_code=301)
|
||||
|
||||
xport = transport.Transport(redirect=True, accept=None)
|
||||
req_resp = requests.get(self.REDIRECT_CHAIN[0], allow_redirects=True)
|
||||
resp = xport.get(self.REDIRECT_CHAIN[0])
|
||||
|
||||
self.assertEqual(type(resp.history), type(req_resp.history))
|
||||
self.assertEqual(len(resp.history), len(req_resp.history))
|
||||
|
||||
for r, s in zip(req_resp.history, resp.history):
|
||||
self.assertEqual(s.url, r.url)
|
||||
self.assertEqual(s.status_code, r.status_code)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_history_matches_requests_json(self, mock_req):
|
||||
self.setup_redirects(mock_req, status_code=301,
|
||||
final_kwargs={'text': fake_response_json})
|
||||
|
||||
xport = transport.Transport(redirect=True)
|
||||
req_resp = requests.get(self.REDIRECT_CHAIN[0], allow_redirects=True)
|
||||
resp = xport.get(self.REDIRECT_CHAIN[0])
|
||||
|
||||
self.assertEqual(type(resp.history), type(req_resp.history))
|
||||
self.assertEqual(len(resp.history), len(req_resp.history))
|
||||
|
||||
for r, s in zip(req_resp.history, resp.history):
|
||||
self.assertEqual(s.url, r.url)
|
||||
self.assertEqual(s.status_code, r.status_code)
|
||||
|
||||
def test_parse_error_response(self):
|
||||
xport = transport.Transport(redirect=True)
|
||||
resp = mock.Mock()
|
||||
resp.json = mock.Mock()
|
||||
resp.json.return_value = {"badRequest": {"message": "Not defined"}}
|
||||
self.assertEqual("Not defined", xport._parse_error_response(resp))
|
||||
resp.json.return_value = {"message": {"response": "Not Allowed"}}
|
||||
self.assertEqual("Not Allowed", xport._parse_error_response(resp))
|
||||
resp.json.return_value = {"itemNotFound": {"message": "Not found"}}
|
||||
self.assertEqual("Not found", xport._parse_error_response(resp))
|
||||
resp.json.return_value = {"instanceFault": {"message": "Wot?"}}
|
||||
self.assertEqual("Wot?", xport._parse_error_response(resp))
|
||||
resp.json.return_value = {"QuantumError": "Network error"}
|
||||
self.assertEqual("Network error", xport._parse_error_response(resp))
|
||||
|
||||
|
||||
class TestLogging(base.TestCase):
|
||||
METHOD = 'PUT'
|
||||
URL = 'http://example.com/'
|
||||
|
||||
def setUp(self):
|
||||
super(TestLogging, self).setUp()
|
||||
self.xport = transport.Transport()
|
||||
mock_logger = mock.Mock()
|
||||
mock_logger.isEnabledFor = mock.Mock()
|
||||
mock_logger.isEnabledFor.return_value = True
|
||||
self.mock_debug = mock.Mock()
|
||||
mock_logger.debug = self.mock_debug
|
||||
transport._logger = mock_logger
|
||||
self.expected = (u"REQ: curl -i -X '%s' '%s'" %
|
||||
(self.METHOD, self.URL))
|
||||
|
||||
def test_data(self):
|
||||
self.xport._log_request(self.METHOD, self.URL, data="payload",
|
||||
headers={})
|
||||
self.mock_debug.assert_called_with(self.expected + " --data 'payload'")
|
||||
|
||||
def test_unicode(self):
|
||||
self.xport._log_request(self.METHOD, self.URL, data=u'拱心石',
|
||||
headers={})
|
||||
self.mock_debug.assert_called_with(self.expected + u" --data '拱心石'")
|
@ -25,7 +25,7 @@ class Test_enable_logging(testtools.TestCase):
|
||||
|
||||
utils.enable_logging(debug=debug, stream=stream)
|
||||
|
||||
self.assertEqual(the_logger.addHandler.call_count, 1)
|
||||
self.assertEqual(the_logger.addHandler.call_count, 2)
|
||||
the_logger.setLevel.assert_called_with(level)
|
||||
|
||||
def _file_tests(self, fake_logging, level, debug):
|
||||
@ -36,7 +36,7 @@ class Test_enable_logging(testtools.TestCase):
|
||||
utils.enable_logging(debug=debug, path=fake_path)
|
||||
|
||||
fake_logging.FileHandler.assert_called_with(fake_path)
|
||||
self.assertEqual(the_logger.addHandler.call_count, 1)
|
||||
self.assertEqual(the_logger.addHandler.call_count, 2)
|
||||
the_logger.setLevel.assert_called_with(level)
|
||||
|
||||
def test_none(self):
|
||||
|
@ -1,406 +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.
|
||||
|
||||
"""
|
||||
The :class:`~openstack.transport.Transport` is a subclass of
|
||||
``requests.Session`` that adds some features that are common in OpenStack
|
||||
APIs or can be globally controlled by an application. Its use is incredibly
|
||||
similar to ``requests.Session`` such that we only will cover the differences
|
||||
in detail here.
|
||||
|
||||
The common OpenStack functionality added include:
|
||||
|
||||
* Log all requests and responses at debug level.
|
||||
* Support json encoding in the request() method.
|
||||
* Set the default user_agent at Transport creation. If it is set to None to
|
||||
skip the header.
|
||||
* Set the default verify at Transport creation.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Basic HTTP GET
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Making a basic HTTP GET call is very simple::
|
||||
|
||||
from openstack import transport
|
||||
trans = transport.Transport()
|
||||
versions = trans.get('http://cloud.example.com:5000').json()
|
||||
|
||||
will retrieve the version data served by the Identity API into a Python dict.
|
||||
|
||||
HTTP POST
|
||||
~~~~~~~~~
|
||||
|
||||
Creating a new object in an OpenStack service is similarly simple::
|
||||
|
||||
from openstack import transport
|
||||
trans = transport.Transport()
|
||||
new_record = {'name': 'The White Albumn', 'artist': 'The Beatles'}
|
||||
resp = trans.post('http://cloud.example.com:4999/record', json=new_record)
|
||||
|
||||
Passing in the new_record dict with the ``json`` keyword argument performs the
|
||||
``json.dumps()`` prior to the request being sent. This is an addition to
|
||||
the capabilities of ``requests.Session``.
|
||||
|
||||
Additional HTTP Methods
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Just as in ``requests.Session``, all of the HTTP verbs have corresponding
|
||||
methods in the :class:`~openstack.transport.Transport` object.
|
||||
|
||||
SSL/TLS and Certificates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``verify`` argument to ``Transport.request()`` can now be set when the
|
||||
Transport object is created. It can still be overwritten during any
|
||||
individual call to ``request()`` or the HTTP verb methods.
|
||||
|
||||
To set the default hostname verification for the Transport to use a custom
|
||||
CA certificate file::
|
||||
|
||||
from openstack import transport
|
||||
trans = transport.Transport(verify='/etc/tls/local-ca-certs.crt')
|
||||
|
||||
The same usage from ``requests`` is still available. To use the default CA
|
||||
certificate file for a single request::
|
||||
|
||||
versions = trans.get('https://cloud.example.com:5000', verify=True)
|
||||
|
||||
Or hit on a host with a self-signed certificate::
|
||||
|
||||
versions = trans.get('https://cloud.example.com:5000', verify=None)
|
||||
|
||||
Redirection
|
||||
~~~~~~~~~~~
|
||||
|
||||
Redirection handling differs from ``requests`` by default as this module is
|
||||
expected to be primarily used for querying REST API servers. The redirection
|
||||
model differs in that ``requests`` follows some browser patterns where it
|
||||
will redirect POSTs as GETs for certain statuses which is not want we want
|
||||
for an API.
|
||||
|
||||
See: https://en.wikipedia.org/wiki/Post/Redirect/Get
|
||||
|
||||
User-Agent
|
||||
~~~~~~~~~~
|
||||
|
||||
User-Agent handling as constructed by this class follows
|
||||
`RFC 7231 Section 5.5.3 <http://tools.ietf.org/html/rfc7231#section-5.5.3>`_.
|
||||
A well-formed user-agent is constructed on name/version product identifiers,
|
||||
such that ``MyProgram/1.0`` is a proper user-agent.
|
||||
|
||||
* The default :attr:`~openstack.transport.USER_AGENT` contains
|
||||
the SDK version as well as RFC-compliant values from
|
||||
``requests.utils.default_user_agent``, including versions of ``requests``,
|
||||
Python, and the operating system.
|
||||
* Any ``user_agent`` argument passed when creating a
|
||||
:class:`~openstack.transport.Transport` is prepended to the default.
|
||||
* Any ``user_agent`` passed in a
|
||||
:meth:`~openstack.transport.Transport.request` call is prepended
|
||||
to one used for that ``Transport`` instance.
|
||||
* Any string passed as the ``User-Agent`` in a dictionary of
|
||||
headers to :meth:`~openstack.transport.Transport.request` will be
|
||||
used directly. If at the same time a ``user_agent`` argument has been passed
|
||||
to ``request()``, it will be used and follows the rules above.
|
||||
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import six
|
||||
from six.moves import urllib
|
||||
|
||||
import openstack
|
||||
from openstack import exceptions
|
||||
|
||||
#: Default value for the HTTP User-Agent header. The default includes the
|
||||
#: version information of the SDK as well as ``requests``, Python,
|
||||
#: and the operating system.
|
||||
USER_AGENT = "openstacksdk/%s %s" % (
|
||||
openstack.__version__, requests.utils.default_user_agent())
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
JSON = 'application/json'
|
||||
|
||||
|
||||
class Transport(requests.Session):
|
||||
|
||||
REDIRECT_STATUSES = (301, 302, 303, 305, 307)
|
||||
DEFAULT_REDIRECT_LIMIT = 30
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_agent=None,
|
||||
verify=True,
|
||||
redirect=DEFAULT_REDIRECT_LIMIT,
|
||||
accept=JSON,
|
||||
):
|
||||
"""Create a new :class:`~openstack.transport.Transport` object.
|
||||
|
||||
In addition to those listed below, all arguments available to
|
||||
``requests.Session`` are available here:
|
||||
|
||||
:param string user_agent: Set the ``User-Agent`` header. When
|
||||
no value is provided, the default of
|
||||
:attr:`~openstack.transport.USER_AGENT`
|
||||
will be used. When a value is provided,
|
||||
it will be prepended to
|
||||
:attr:`~openstack.transport.USER_AGENT`.
|
||||
:param boolean/string verify: If ``True``, the SSL cert will be
|
||||
verified. A CA_BUNDLE path can also be
|
||||
provided.
|
||||
:param boolean/integer redirect: (integer) The maximum number of
|
||||
redirections followed in a request.
|
||||
(boolean) No redirections if False,
|
||||
requests.Session handles redirection
|
||||
if True. (optional)
|
||||
:param string accept: Type of output to accept
|
||||
|
||||
"""
|
||||
|
||||
super(Transport, self).__init__()
|
||||
|
||||
# Per RFC 7231 Section 5.5.3, identifiers in a user-agent should
|
||||
# be ordered by decreasing significance. If a user sets their product,
|
||||
# we prepend it to the SDK version and then the Python version.
|
||||
if user_agent is None:
|
||||
self._user_agent = USER_AGENT
|
||||
else:
|
||||
self._user_agent = "%s %s" % (user_agent, USER_AGENT)
|
||||
|
||||
self.verify = verify
|
||||
self._redirect = redirect
|
||||
self._accept = accept
|
||||
|
||||
def request(self, method, url, redirect=None, **kwargs):
|
||||
"""Send a request
|
||||
|
||||
Perform an HTTP request. The following arguments differ from
|
||||
``requests.Session``:
|
||||
|
||||
:param string method: Request HTTP method
|
||||
:param string url: Request URL
|
||||
:param boolean/integer redirect: (integer) The maximum number of
|
||||
redirections followed in a request.
|
||||
(boolean) No redirections if False,
|
||||
requests.Session handles redirection
|
||||
if True. (optional)
|
||||
|
||||
The following additional keyword arguments are supported:
|
||||
|
||||
:param object json: Request body to be encoded as JSON
|
||||
Overwrites ``data`` argument if present
|
||||
:param string accept: Set the ``Accept`` header; overwrites any value
|
||||
that may be in the headers dict. Header is
|
||||
omitted if ``None``.
|
||||
:param string user_agent: Prepend an additional value to the existing
|
||||
``User-Agent`` header.
|
||||
|
||||
Remaining keyword arguments from requests.Session.request() supported
|
||||
"""
|
||||
|
||||
headers = kwargs.setdefault('headers', {})
|
||||
|
||||
# JSON-encode the data in json arg if present
|
||||
# Overwrites any existing 'data' value
|
||||
json_data = kwargs.pop('json', None)
|
||||
if json_data is not None:
|
||||
kwargs['data'] = json.dumps(json_data)
|
||||
headers['Content-Type'] = JSON
|
||||
|
||||
# Prepend the caller's user_agent to User-Agent header if included,
|
||||
# or use the default that this transport was created with.
|
||||
# Note: Only attempt to work with strings and avoid needlessly
|
||||
# concatenating an empty string.
|
||||
user_agent = kwargs.pop('user_agent', None)
|
||||
if isinstance(user_agent, six.string_types) and user_agent != '':
|
||||
headers['User-Agent'] = '%s %s' % (user_agent, self._user_agent)
|
||||
elif 'User-Agent' in headers:
|
||||
# If they've specified their own headers with a User-Agent,
|
||||
# use that directly.
|
||||
pass
|
||||
else:
|
||||
headers.setdefault('User-Agent', self._user_agent)
|
||||
|
||||
if redirect is None:
|
||||
redirect = self._redirect
|
||||
|
||||
if isinstance(redirect, bool) and redirect:
|
||||
# Fall back to requests redirect handling
|
||||
kwargs['allow_redirects'] = True
|
||||
else:
|
||||
# Force disable requests redirect handling, we will manage
|
||||
# redirections below
|
||||
kwargs['allow_redirects'] = False
|
||||
if 'accept' in kwargs:
|
||||
accept = kwargs.pop('accept')
|
||||
else:
|
||||
accept = self._accept
|
||||
if accept:
|
||||
headers.setdefault('Accept', accept)
|
||||
|
||||
resp = self._send_request(method, url, redirect, **kwargs)
|
||||
|
||||
try:
|
||||
resp.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
if resp.status_code == 404:
|
||||
exc_type = exceptions.NotFoundException
|
||||
else:
|
||||
exc_type = exceptions.HttpException
|
||||
|
||||
raise exc_type(six.text_type(e),
|
||||
details=self._parse_error_response(resp),
|
||||
status_code=resp.status_code)
|
||||
|
||||
if resp.status_code == 202:
|
||||
resp.body = {}
|
||||
elif accept == JSON:
|
||||
try:
|
||||
resp.body = resp.json()
|
||||
except ValueError as e:
|
||||
# this may be simplejson.decode.JSONDecodeError
|
||||
# Re-raise into our own exception
|
||||
raise exceptions.InvalidResponse(response=resp)
|
||||
|
||||
return resp
|
||||
|
||||
def _send_request(self, method, url, redirect, **kwargs):
|
||||
# NOTE(jamielennox): We handle redirection manually because the
|
||||
# requests lib follows some browser patterns where it will redirect
|
||||
# POSTs as GETs for certain statuses which is not want we want for an
|
||||
# API. See: https://en.wikipedia.org/wiki/Post/Redirect/Get
|
||||
|
||||
self._log_request(method, url, **kwargs)
|
||||
|
||||
resp = super(Transport, self).request(method, url, **kwargs)
|
||||
|
||||
self._log_response(resp)
|
||||
|
||||
if resp.status_code in self.REDIRECT_STATUSES:
|
||||
# Be careful here in python True == 1 and False == 0
|
||||
if isinstance(redirect, bool):
|
||||
redirect_allowed = redirect
|
||||
else:
|
||||
redirect -= 1
|
||||
redirect_allowed = redirect >= 0
|
||||
|
||||
if redirect_allowed:
|
||||
try:
|
||||
location = resp.headers['location']
|
||||
except KeyError:
|
||||
_logger.warn(
|
||||
"Redirection from %s failed, no location provided",
|
||||
resp.url,
|
||||
)
|
||||
else:
|
||||
new_resp = self._send_request(
|
||||
method,
|
||||
location,
|
||||
redirect,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
new_resp.history = list(new_resp.history)
|
||||
new_resp.history.insert(0, resp)
|
||||
resp = new_resp
|
||||
|
||||
return resp
|
||||
|
||||
def _parse_error_response(self, resp):
|
||||
try:
|
||||
jresp = resp.json()
|
||||
# compute
|
||||
if "badRequest" in jresp and "message" in jresp["badRequest"]:
|
||||
return jresp["badRequest"]["message"]
|
||||
# identity
|
||||
if "message" in jresp and "response" in jresp["message"]:
|
||||
return jresp["message"]["response"]
|
||||
# network
|
||||
if "QuantumError" in jresp:
|
||||
return jresp["QuantumError"]
|
||||
# database
|
||||
if "itemNotFound" in jresp and "message" in jresp["itemNotFound"]:
|
||||
return jresp["itemNotFound"]["message"]
|
||||
if "instanceFault" in jresp:
|
||||
if "message" in jresp["instanceFault"]:
|
||||
return jresp["instanceFault"]["message"]
|
||||
except ValueError:
|
||||
pass
|
||||
return resp.text
|
||||
|
||||
def _log_request(self, method, url, **kwargs):
|
||||
if not _logger.isEnabledFor(logging.DEBUG):
|
||||
return
|
||||
|
||||
if 'params' in kwargs and kwargs['params']:
|
||||
url += '?' + urllib.parse.urlencode(kwargs['params'])
|
||||
|
||||
string_parts = [
|
||||
"curl -i",
|
||||
"-X '%s'" % method,
|
||||
"'%s'" % url,
|
||||
]
|
||||
|
||||
# kwargs overrides the default
|
||||
if (('verify' in kwargs and kwargs['verify'] is False) or
|
||||
not self.verify):
|
||||
string_parts.append('--insecure')
|
||||
|
||||
for element in kwargs['headers'].items():
|
||||
header = " -H '%s: %s'" % element
|
||||
string_parts.append(header)
|
||||
|
||||
if 'data' in kwargs and kwargs['data'] is not None:
|
||||
string_parts.append("--data")
|
||||
|
||||
data = kwargs['data']
|
||||
# Only log text strings and byte strings that can be decoded
|
||||
# in ascii. Raw byte strings and files both mess up the actual
|
||||
# writing of the data to any log stream because we'd be mixing
|
||||
# text and bytes, and they are generally overly long strings
|
||||
# that would make the logs unreadable anyway.
|
||||
if isinstance(data, six.binary_type):
|
||||
# Some data, such as auth creds, is generally decodable
|
||||
# to ascii. If it works, log it, otherwise put in a
|
||||
# placeholder to specify that it's a blob of binary data.
|
||||
try:
|
||||
data = data.decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
data = "<binary data>"
|
||||
elif getattr(data, 'read', False):
|
||||
data = "<file data>"
|
||||
|
||||
string_parts.append("'" + data + "'")
|
||||
|
||||
_logger.debug("REQ: %s" % " ".join(string_parts))
|
||||
|
||||
def _log_response(self, response):
|
||||
_logger.debug(
|
||||
"RESP: [%s] %r" % (
|
||||
response.status_code,
|
||||
response.headers,
|
||||
),
|
||||
)
|
||||
if response._content_consumed:
|
||||
_logger.debug(
|
||||
"RESP BODY: %s",
|
||||
response.text,
|
||||
)
|
||||
_logger.debug(
|
||||
"encoding: %s",
|
||||
response.encoding,
|
||||
)
|
@ -40,6 +40,7 @@ def enable_logging(debug=False, path=None, stream=None):
|
||||
raise ValueError("path and/or stream must be set")
|
||||
|
||||
logger = logging.getLogger('openstack')
|
||||
ksalog = logging.getLogger('keystoneauth')
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s %(levelname)s: %(name)s %(message)s')
|
||||
|
||||
@ -47,13 +48,16 @@ def enable_logging(debug=False, path=None, stream=None):
|
||||
console = logging.StreamHandler(stream)
|
||||
console.setFormatter(formatter)
|
||||
logger.addHandler(console)
|
||||
ksalog.addHandler(console)
|
||||
|
||||
if path is not None:
|
||||
file_handler = logging.FileHandler(path)
|
||||
file_handler.setFormatter(formatter)
|
||||
logger.addHandler(file_handler)
|
||||
ksalog.addHandler(file_handler)
|
||||
|
||||
logger.setLevel(logging.DEBUG if debug else logging.WARNING)
|
||||
ksalog.setLevel(logging.DEBUG if debug else logging.WARNING)
|
||||
|
||||
|
||||
def urljoin(*args):
|
||||
|
@ -7,3 +7,4 @@ six>=1.9.0
|
||||
stevedore>=1.5.0 # Apache-2.0
|
||||
oslo.utils>=2.8.0 # Apache-2.0
|
||||
os-client-config!=1.6.2,>=1.4.0
|
||||
keystoneauth1>=1.0.0
|
||||
|
@ -48,12 +48,3 @@ output_file = openstack/locale/python-openstacksdk.pot
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
||||
|
||||
[entry_points]
|
||||
openstack.auth.plugin =
|
||||
v2password = openstack.auth.identity.v2:Password
|
||||
v2token = openstack.auth.identity.v2:Token
|
||||
v3password = openstack.auth.identity.v3:Password
|
||||
v3token = openstack.auth.identity.v3:Token
|
||||
password = openstack.auth.identity.discoverable:Auth
|
||||
token = openstack.auth.identity.discoverable:Auth
|
||||
|
Loading…
Reference in New Issue
Block a user