Remove all code related to HTTPClient
In previous patch we switched to use SessionClient in all cases. It means that all HTTPClient code is redundant now. Change-Id: I5a0da970fd82c79db3d88f1b49279133bbdba639
This commit is contained in:
@@ -20,35 +20,20 @@
|
|||||||
OpenStack Client interface. Handles the REST calls and responses.
|
OpenStack Client interface. Handles the REST calls and responses.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import copy
|
|
||||||
import functools
|
|
||||||
import hashlib
|
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import re
|
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from keystoneauth1 import adapter
|
from keystoneauth1 import adapter
|
||||||
from keystoneauth1 import identity
|
from keystoneauth1 import identity
|
||||||
from keystoneauth1 import session as ksession
|
from keystoneauth1 import session as ksession
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
from oslo_utils import netutils
|
|
||||||
import pkg_resources
|
import pkg_resources
|
||||||
import requests
|
|
||||||
|
|
||||||
try:
|
|
||||||
import json
|
|
||||||
except ImportError:
|
|
||||||
import simplejson as json
|
|
||||||
|
|
||||||
from six.moves.urllib import parse
|
|
||||||
|
|
||||||
from novaclient import api_versions
|
from novaclient import api_versions
|
||||||
from novaclient import exceptions
|
from novaclient import exceptions
|
||||||
from novaclient import extension as ext
|
from novaclient import extension as ext
|
||||||
from novaclient.i18n import _, _LW
|
from novaclient.i18n import _, _LW
|
||||||
from novaclient import service_catalog
|
|
||||||
from novaclient import utils
|
from novaclient import utils
|
||||||
|
|
||||||
|
|
||||||
@@ -58,19 +43,6 @@ from novaclient import utils
|
|||||||
extensions_ignored_name = ["__init__"]
|
extensions_ignored_name = ["__init__"]
|
||||||
|
|
||||||
|
|
||||||
class _ClientConnectionPool(object):
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._adapters = {}
|
|
||||||
|
|
||||||
def get(self, url):
|
|
||||||
"""Store and reuse HTTP adapters per Service URL."""
|
|
||||||
if url not in self._adapters:
|
|
||||||
self._adapters[url] = ksession.TCPKeepAliveAdapter()
|
|
||||||
|
|
||||||
return self._adapters[url]
|
|
||||||
|
|
||||||
|
|
||||||
def _log_request_id(logger, resp, service_name):
|
def _log_request_id(logger, resp, service_name):
|
||||||
request_id = (resp.headers.get('x-openstack-request-id') or
|
request_id = (resp.headers.get('x-openstack-request-id') or
|
||||||
resp.headers.get('x-compute-request-id'))
|
resp.headers.get('x-compute-request-id'))
|
||||||
@@ -138,564 +110,6 @@ class SessionClient(adapter.LegacyJsonAdapter):
|
|||||||
self.endpoint_override = value
|
self.endpoint_override = value
|
||||||
|
|
||||||
|
|
||||||
def _original_only(f):
|
|
||||||
"""Decorator to indicate and enforce original HTTPClient object.
|
|
||||||
|
|
||||||
Indicates and enforces that this function can only be used if we are
|
|
||||||
using the original HTTPClient object.
|
|
||||||
We use this to specify that if you use the newer Session HTTP client then
|
|
||||||
you are aware that the way you use your client has been updated and certain
|
|
||||||
functions are no longer allowed to be used.
|
|
||||||
"""
|
|
||||||
@functools.wraps(f)
|
|
||||||
def wrapper(self, *args, **kwargs):
|
|
||||||
if isinstance(self.client, SessionClient):
|
|
||||||
msg = ('This call is no longer available. The operation should '
|
|
||||||
'be performed on the session object instead.')
|
|
||||||
raise exceptions.InvalidUsage(msg)
|
|
||||||
|
|
||||||
return f(self, *args, **kwargs)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPClient(object):
|
|
||||||
USER_AGENT = 'python-novaclient'
|
|
||||||
|
|
||||||
def __init__(self, user, password, projectid=None, auth_url=None,
|
|
||||||
insecure=False, timeout=None, proxy_tenant_id=None,
|
|
||||||
proxy_token=None, region_name=None,
|
|
||||||
endpoint_type='publicURL', service_type=None,
|
|
||||||
service_name=None, volume_service_name=None,
|
|
||||||
timings=False, bypass_url=None,
|
|
||||||
os_cache=False, no_cache=True,
|
|
||||||
http_log_debug=False, auth_token=None,
|
|
||||||
cacert=None, tenant_id=None, user_id=None,
|
|
||||||
connection_pool=False, api_version=None,
|
|
||||||
logger=None):
|
|
||||||
self.user = user
|
|
||||||
self.user_id = user_id
|
|
||||||
self.password = password
|
|
||||||
self.projectid = projectid
|
|
||||||
self.tenant_id = tenant_id
|
|
||||||
self.api_version = api_version or api_versions.APIVersion()
|
|
||||||
|
|
||||||
self._connection_pool = (_ClientConnectionPool()
|
|
||||||
if connection_pool else None)
|
|
||||||
|
|
||||||
# This will be called by #_get_password if self.password is None.
|
|
||||||
# EG if a password can only be obtained by prompting the user, but a
|
|
||||||
# token is available, you don't want to prompt until the token has
|
|
||||||
# been proven invalid
|
|
||||||
self.password_func = None
|
|
||||||
|
|
||||||
self.auth_url = auth_url.rstrip('/') if auth_url else auth_url
|
|
||||||
self.version = 'v1.1'
|
|
||||||
self.region_name = region_name
|
|
||||||
self.endpoint_type = endpoint_type
|
|
||||||
self.service_type = service_type
|
|
||||||
self.service_name = service_name
|
|
||||||
self.volume_service_name = volume_service_name
|
|
||||||
self.timings = timings
|
|
||||||
self.bypass_url = bypass_url.rstrip('/') if bypass_url else bypass_url
|
|
||||||
self.os_cache = os_cache or not no_cache
|
|
||||||
self.http_log_debug = http_log_debug
|
|
||||||
if timeout is not None:
|
|
||||||
self.timeout = float(timeout)
|
|
||||||
else:
|
|
||||||
self.timeout = None
|
|
||||||
|
|
||||||
self.times = [] # [("item", starttime, endtime), ...]
|
|
||||||
|
|
||||||
self.management_url = self.bypass_url or None
|
|
||||||
self.auth_token = auth_token
|
|
||||||
self.proxy_token = proxy_token
|
|
||||||
self.proxy_tenant_id = proxy_tenant_id
|
|
||||||
self.keyring_saver = None
|
|
||||||
self.keyring_saved = False
|
|
||||||
|
|
||||||
if insecure:
|
|
||||||
self.verify_cert = False
|
|
||||||
else:
|
|
||||||
if cacert:
|
|
||||||
self.verify_cert = cacert
|
|
||||||
else:
|
|
||||||
self.verify_cert = True
|
|
||||||
|
|
||||||
self._session = None
|
|
||||||
self._current_url = None
|
|
||||||
self._logger = logger or logging.getLogger(__name__)
|
|
||||||
|
|
||||||
if (self.http_log_debug and logger is None and
|
|
||||||
not self._logger.handlers):
|
|
||||||
# Logging level is already set on the root logger
|
|
||||||
ch = logging.StreamHandler()
|
|
||||||
self._logger.addHandler(ch)
|
|
||||||
self._logger.propagate = False
|
|
||||||
if hasattr(requests, 'logging'):
|
|
||||||
rql = requests.logging.getLogger(requests.__name__)
|
|
||||||
rql.addHandler(ch)
|
|
||||||
# Since we have already setup the root logger on debug, we
|
|
||||||
# have to set it up here on WARNING (its original level)
|
|
||||||
# otherwise we will get all the requests logging messages
|
|
||||||
rql.setLevel(logging.WARNING)
|
|
||||||
|
|
||||||
self.service_catalog = None
|
|
||||||
self.services_url = {}
|
|
||||||
self.last_request_id = None
|
|
||||||
|
|
||||||
def use_token_cache(self, use_it):
|
|
||||||
self.os_cache = use_it
|
|
||||||
|
|
||||||
def unauthenticate(self):
|
|
||||||
"""Forget all of our authentication information."""
|
|
||||||
self.management_url = None
|
|
||||||
self.auth_token = None
|
|
||||||
|
|
||||||
def set_management_url(self, url):
|
|
||||||
self.management_url = url
|
|
||||||
|
|
||||||
def get_timings(self):
|
|
||||||
return self.times
|
|
||||||
|
|
||||||
def reset_timings(self):
|
|
||||||
self.times = []
|
|
||||||
|
|
||||||
def _redact(self, target, path, text=None):
|
|
||||||
"""Replace the value of a key in `target`.
|
|
||||||
|
|
||||||
The key can be at the top level by specifying a list with a single
|
|
||||||
key as the path. Nested dictionaries are also supported by passing a
|
|
||||||
list of keys to be navigated to find the one that should be replaced.
|
|
||||||
In this case the last one is the one that will be replaced.
|
|
||||||
|
|
||||||
:param dict target: the dictionary that may have a key to be redacted;
|
|
||||||
modified in place
|
|
||||||
:param list path: a list representing the nested structure in `target`
|
|
||||||
that should be redacted; modified in place
|
|
||||||
:param string text: optional text to use as a replacement for the
|
|
||||||
redacted key. if text is not specified, the
|
|
||||||
default text will be sha1 hash of the value being
|
|
||||||
redacted
|
|
||||||
"""
|
|
||||||
|
|
||||||
key = path.pop()
|
|
||||||
|
|
||||||
# move to the most nested dict
|
|
||||||
for p in path:
|
|
||||||
try:
|
|
||||||
target = target[p]
|
|
||||||
except KeyError:
|
|
||||||
return
|
|
||||||
|
|
||||||
if key in target:
|
|
||||||
if text:
|
|
||||||
target[key] = text
|
|
||||||
elif target[key] is not None:
|
|
||||||
# because in python3 byte string handling is ... ug
|
|
||||||
value = target[key].encode('utf-8')
|
|
||||||
sha1sum = hashlib.sha1(value)
|
|
||||||
target[key] = "{SHA1}%s" % sha1sum.hexdigest()
|
|
||||||
|
|
||||||
def http_log_req(self, method, url, kwargs):
|
|
||||||
if not self.http_log_debug:
|
|
||||||
return
|
|
||||||
|
|
||||||
string_parts = ['curl -g -i']
|
|
||||||
|
|
||||||
if not kwargs.get('verify', True):
|
|
||||||
string_parts.append(' --insecure')
|
|
||||||
|
|
||||||
string_parts.append(" '%s'" % url)
|
|
||||||
string_parts.append(' -X %s' % method)
|
|
||||||
|
|
||||||
headers = copy.deepcopy(kwargs['headers'])
|
|
||||||
self._redact(headers, ['X-Auth-Token'])
|
|
||||||
# because dict ordering changes from 2 to 3
|
|
||||||
keys = sorted(headers.keys())
|
|
||||||
for name in keys:
|
|
||||||
value = headers[name]
|
|
||||||
header = ' -H "%s: %s"' % (name, value)
|
|
||||||
string_parts.append(header)
|
|
||||||
|
|
||||||
if 'data' in kwargs:
|
|
||||||
data = json.loads(kwargs['data'])
|
|
||||||
self._redact(data, ['auth', 'passwordCredentials', 'password'])
|
|
||||||
string_parts.append(" -d '%s'" % json.dumps(data))
|
|
||||||
self._logger.debug("REQ: %s" % "".join(string_parts))
|
|
||||||
|
|
||||||
def http_log_resp(self, resp):
|
|
||||||
if not self.http_log_debug:
|
|
||||||
return
|
|
||||||
|
|
||||||
if resp.text and resp.status_code != 400:
|
|
||||||
try:
|
|
||||||
body = json.loads(resp.text)
|
|
||||||
self._redact(body, ['access', 'token', 'id'])
|
|
||||||
except ValueError:
|
|
||||||
body = None
|
|
||||||
else:
|
|
||||||
body = None
|
|
||||||
|
|
||||||
self._logger.debug("RESP: [%(status)s] %(headers)s\nRESP BODY: "
|
|
||||||
"%(text)s\n", {'status': resp.status_code,
|
|
||||||
'headers': resp.headers,
|
|
||||||
'text': json.dumps(body)})
|
|
||||||
|
|
||||||
# if service name is None then use service_type for logging
|
|
||||||
service = self.service_name or self.service_type
|
|
||||||
_log_request_id(self._logger, resp, service)
|
|
||||||
|
|
||||||
def open_session(self):
|
|
||||||
if not self._connection_pool:
|
|
||||||
self._session = requests.Session()
|
|
||||||
|
|
||||||
def close_session(self):
|
|
||||||
if self._session and not self._connection_pool:
|
|
||||||
self._session.close()
|
|
||||||
self._session = None
|
|
||||||
|
|
||||||
def _get_session(self, url):
|
|
||||||
if self._connection_pool:
|
|
||||||
magic_tuple = parse.urlsplit(url)
|
|
||||||
scheme, netloc, path, query, frag = magic_tuple
|
|
||||||
service_url = '%s://%s' % (scheme, netloc)
|
|
||||||
if self._current_url != service_url:
|
|
||||||
# Invalidate Session object in case the url is somehow changed
|
|
||||||
if self._session:
|
|
||||||
self._session.close()
|
|
||||||
self._current_url = service_url
|
|
||||||
self._logger.debug(
|
|
||||||
"New session created for: (%s)" % service_url)
|
|
||||||
self._session = requests.Session()
|
|
||||||
self._session.mount(service_url,
|
|
||||||
self._connection_pool.get(service_url))
|
|
||||||
return self._session
|
|
||||||
elif self._session:
|
|
||||||
return self._session
|
|
||||||
|
|
||||||
def request(self, url, method, **kwargs):
|
|
||||||
kwargs.setdefault('headers', kwargs.get('headers', {}))
|
|
||||||
kwargs['headers']['User-Agent'] = self.USER_AGENT
|
|
||||||
kwargs['headers']['Accept'] = 'application/json'
|
|
||||||
if 'body' in kwargs:
|
|
||||||
kwargs['headers']['Content-Type'] = 'application/json'
|
|
||||||
kwargs['data'] = json.dumps(kwargs.pop('body'))
|
|
||||||
api_versions.update_headers(kwargs["headers"], self.api_version)
|
|
||||||
if self.timeout is not None:
|
|
||||||
kwargs.setdefault('timeout', self.timeout)
|
|
||||||
kwargs['verify'] = self.verify_cert
|
|
||||||
|
|
||||||
self.http_log_req(method, url, kwargs)
|
|
||||||
|
|
||||||
request_func = requests.request
|
|
||||||
session = self._get_session(url)
|
|
||||||
if session:
|
|
||||||
request_func = session.request
|
|
||||||
|
|
||||||
resp = request_func(
|
|
||||||
method,
|
|
||||||
url,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
# TODO(andreykurilin): uncomment this line, when we will be able to
|
|
||||||
# check only nova-related calls
|
|
||||||
# api_versions.check_headers(resp, self.api_version)
|
|
||||||
|
|
||||||
self.http_log_resp(resp)
|
|
||||||
|
|
||||||
if resp.text:
|
|
||||||
# TODO(dtroyer): verify the note below in a requests context
|
|
||||||
# NOTE(alaski): Because force_exceptions_to_status_code=True
|
|
||||||
# httplib2 returns a connection refused event as a 400 response.
|
|
||||||
# To determine if it is a bad request or refused connection we need
|
|
||||||
# to check the body. httplib2 tests check for 'Connection refused'
|
|
||||||
# or 'actively refused' in the body, so that's what we'll do.
|
|
||||||
if resp.status_code == 400:
|
|
||||||
if ('Connection refused' in resp.text or
|
|
||||||
'actively refused' in resp.text):
|
|
||||||
raise exceptions.ConnectionRefused(resp.text)
|
|
||||||
try:
|
|
||||||
body = json.loads(resp.text)
|
|
||||||
except ValueError:
|
|
||||||
body = None
|
|
||||||
else:
|
|
||||||
body = None
|
|
||||||
|
|
||||||
self.last_request_id = (resp.headers.get('x-openstack-request-id')
|
|
||||||
if resp.headers else None)
|
|
||||||
if resp.status_code >= 400:
|
|
||||||
raise exceptions.from_response(resp, body, url, method)
|
|
||||||
|
|
||||||
return resp, body
|
|
||||||
|
|
||||||
def _time_request(self, url, method, **kwargs):
|
|
||||||
with utils.record_time(self.times, self.timings, method, url):
|
|
||||||
resp, body = self.request(url, method, **kwargs)
|
|
||||||
return resp, body
|
|
||||||
|
|
||||||
def _cs_request(self, url, method, **kwargs):
|
|
||||||
if not self.management_url:
|
|
||||||
self.authenticate()
|
|
||||||
if url is None:
|
|
||||||
# To get API version information, it is necessary to GET
|
|
||||||
# a nova endpoint directly without "v2/<tenant-id>".
|
|
||||||
magic_tuple = parse.urlsplit(self.management_url)
|
|
||||||
scheme, netloc, path, query, frag = magic_tuple
|
|
||||||
path = re.sub(r'v[1-9](\.[1-9][0-9]*)?/[a-z0-9]+$', '', path)
|
|
||||||
url = parse.urlunsplit((scheme, netloc, path, None, None))
|
|
||||||
else:
|
|
||||||
if self.service_catalog and not self.bypass_url:
|
|
||||||
url = self.get_service_url(self.service_type) + url
|
|
||||||
else:
|
|
||||||
url = self.management_url + url
|
|
||||||
|
|
||||||
# Perform the request once. If we get a 401 back then it
|
|
||||||
# might be because the auth token expired, so try to
|
|
||||||
# re-authenticate and try again. If it still fails, bail.
|
|
||||||
try:
|
|
||||||
kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token
|
|
||||||
if self.projectid:
|
|
||||||
kwargs['headers']['X-Auth-Project-Id'] = self.projectid
|
|
||||||
|
|
||||||
resp, body = self._time_request(url, method, **kwargs)
|
|
||||||
return resp, body
|
|
||||||
except exceptions.Unauthorized as e:
|
|
||||||
try:
|
|
||||||
# first discard auth token, to avoid the possibly expired
|
|
||||||
# token being re-used in the re-authentication attempt
|
|
||||||
self.unauthenticate()
|
|
||||||
# overwrite bad token
|
|
||||||
self.keyring_saved = False
|
|
||||||
self.authenticate()
|
|
||||||
kwargs['headers']['X-Auth-Token'] = self.auth_token
|
|
||||||
resp, body = self._time_request(url, method, **kwargs)
|
|
||||||
return resp, body
|
|
||||||
except exceptions.Unauthorized:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def _get_password(self):
|
|
||||||
if not self.password and self.password_func:
|
|
||||||
self.password = self.password_func()
|
|
||||||
return self.password
|
|
||||||
|
|
||||||
def get(self, url, **kwargs):
|
|
||||||
return self._cs_request(url, 'GET', **kwargs)
|
|
||||||
|
|
||||||
def post(self, url, **kwargs):
|
|
||||||
return self._cs_request(url, 'POST', **kwargs)
|
|
||||||
|
|
||||||
def put(self, url, **kwargs):
|
|
||||||
return self._cs_request(url, 'PUT', **kwargs)
|
|
||||||
|
|
||||||
def delete(self, url, **kwargs):
|
|
||||||
return self._cs_request(url, 'DELETE', **kwargs)
|
|
||||||
|
|
||||||
def get_service_url(self, service_type):
|
|
||||||
if service_type not in self.services_url:
|
|
||||||
url = self.service_catalog.url_for(
|
|
||||||
attr='region',
|
|
||||||
filter_value=self.region_name,
|
|
||||||
endpoint_type=self.endpoint_type,
|
|
||||||
service_type=service_type,
|
|
||||||
service_name=self.service_name,
|
|
||||||
volume_service_name=self.volume_service_name,)
|
|
||||||
url = url.rstrip('/')
|
|
||||||
self.services_url[service_type] = url
|
|
||||||
return self.services_url[service_type]
|
|
||||||
|
|
||||||
def _extract_service_catalog(self, url, resp, body, extract_token=True):
|
|
||||||
"""Extract service catalog from input resource body.
|
|
||||||
|
|
||||||
See what the auth service told us and process the response.
|
|
||||||
We may get redirected to another site, fail or actually get
|
|
||||||
back a service catalog with a token and our endpoints.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# content must always present
|
|
||||||
if resp.status_code == 200 or resp.status_code == 201:
|
|
||||||
try:
|
|
||||||
self.auth_url = url
|
|
||||||
self.service_catalog = \
|
|
||||||
service_catalog.ServiceCatalog(body)
|
|
||||||
if extract_token:
|
|
||||||
self.auth_token = self.service_catalog.get_token()
|
|
||||||
self.tenant_id = self.service_catalog.get_tenant_id()
|
|
||||||
|
|
||||||
self.management_url = self.get_service_url(self.service_type)
|
|
||||||
return None
|
|
||||||
except exceptions.AmbiguousEndpoints:
|
|
||||||
print(_("Found more than one valid endpoint. Use a more "
|
|
||||||
"restrictive filter"))
|
|
||||||
raise
|
|
||||||
except KeyError:
|
|
||||||
raise exceptions.AuthorizationFailure()
|
|
||||||
except exceptions.EndpointNotFound:
|
|
||||||
print(_("Could not find any suitable endpoint. Correct "
|
|
||||||
"region?"))
|
|
||||||
raise
|
|
||||||
|
|
||||||
elif resp.status_code == 305:
|
|
||||||
return resp.headers['location']
|
|
||||||
else:
|
|
||||||
raise exceptions.from_response(resp, body, url)
|
|
||||||
|
|
||||||
def _fetch_endpoints_from_auth(self, url):
|
|
||||||
"""Fetch endpoint using token.
|
|
||||||
|
|
||||||
We have a token, but don't know the final endpoint for
|
|
||||||
the region. We have to go back to the auth service and
|
|
||||||
ask again. This request requires an admin-level token
|
|
||||||
to work. The proxy token supplied could be from a low-level enduser.
|
|
||||||
|
|
||||||
We can't get this from the keystone service endpoint, we have to use
|
|
||||||
the admin endpoint.
|
|
||||||
|
|
||||||
This will overwrite our admin token with the user token.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# GET ...:5001/v2.0/tokens/#####/endpoints
|
|
||||||
url = '/'.join([url, 'tokens', '%s?belongsTo=%s'
|
|
||||||
% (self.proxy_token, self.proxy_tenant_id)])
|
|
||||||
self._logger.debug("Using Endpoint URL: %s" % url)
|
|
||||||
resp, body = self._time_request(
|
|
||||||
url, "GET", headers={'X-Auth-Token': self.auth_token})
|
|
||||||
return self._extract_service_catalog(url, resp, body,
|
|
||||||
extract_token=False)
|
|
||||||
|
|
||||||
def authenticate(self):
|
|
||||||
if not self.auth_url:
|
|
||||||
msg = _("Authentication requires 'auth_url', which should be "
|
|
||||||
"specified in '%s'") % self.__class__.__name__
|
|
||||||
raise exceptions.AuthorizationFailure(msg)
|
|
||||||
magic_tuple = netutils.urlsplit(self.auth_url)
|
|
||||||
scheme, netloc, path, query, frag = magic_tuple
|
|
||||||
port = magic_tuple.port
|
|
||||||
if port is None:
|
|
||||||
port = 80
|
|
||||||
path_parts = path.split('/')
|
|
||||||
for part in path_parts:
|
|
||||||
if len(part) > 0 and part[0] == 'v':
|
|
||||||
self.version = part
|
|
||||||
break
|
|
||||||
|
|
||||||
if self.auth_token and self.management_url:
|
|
||||||
self._save_keys()
|
|
||||||
return
|
|
||||||
|
|
||||||
# TODO(sandy): Assume admin endpoint is 35357 for now.
|
|
||||||
# Ideally this is going to have to be provided by the service catalog.
|
|
||||||
new_netloc = netloc.replace(':%d' % port, ':%d' % (35357,))
|
|
||||||
admin_url = parse.urlunsplit(
|
|
||||||
(scheme, new_netloc, path, query, frag))
|
|
||||||
|
|
||||||
auth_url = self.auth_url
|
|
||||||
if self.version == "v2.0": # FIXME(chris): This should be better.
|
|
||||||
while auth_url:
|
|
||||||
auth_url = self._v2_auth(auth_url)
|
|
||||||
|
|
||||||
# Are we acting on behalf of another user via an
|
|
||||||
# existing token? If so, our actual endpoints may
|
|
||||||
# be different than that of the admin token.
|
|
||||||
if self.proxy_token:
|
|
||||||
if self.bypass_url:
|
|
||||||
self.set_management_url(self.bypass_url)
|
|
||||||
else:
|
|
||||||
self._fetch_endpoints_from_auth(admin_url)
|
|
||||||
# Since keystone no longer returns the user token
|
|
||||||
# with the endpoints any more, we need to replace
|
|
||||||
# our service account token with the user token.
|
|
||||||
self.auth_token = self.proxy_token
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
while auth_url:
|
|
||||||
auth_url = self._v1_auth(auth_url)
|
|
||||||
# In some configurations nova makes redirection to
|
|
||||||
# v2.0 keystone endpoint. Also, new location does not contain
|
|
||||||
# real endpoint, only hostname and port.
|
|
||||||
except exceptions.AuthorizationFailure:
|
|
||||||
if auth_url.find('v2.0') < 0:
|
|
||||||
auth_url = auth_url + '/v2.0'
|
|
||||||
self._v2_auth(auth_url)
|
|
||||||
|
|
||||||
if self.bypass_url:
|
|
||||||
self.set_management_url(self.bypass_url)
|
|
||||||
elif not self.management_url:
|
|
||||||
raise exceptions.Unauthorized('Nova Client')
|
|
||||||
|
|
||||||
self._save_keys()
|
|
||||||
|
|
||||||
def _save_keys(self):
|
|
||||||
# Store the token/mgmt url in the keyring for later requests.
|
|
||||||
if (self.keyring_saver and self.os_cache and not self.keyring_saved and
|
|
||||||
self.auth_token and self.management_url and
|
|
||||||
self.tenant_id):
|
|
||||||
self.keyring_saver.save(self.auth_token,
|
|
||||||
self.management_url,
|
|
||||||
self.tenant_id)
|
|
||||||
# Don't save it again
|
|
||||||
self.keyring_saved = True
|
|
||||||
|
|
||||||
def _v1_auth(self, url):
|
|
||||||
if self.proxy_token:
|
|
||||||
raise exceptions.NoTokenLookupException()
|
|
||||||
|
|
||||||
headers = {'X-Auth-User': self.user,
|
|
||||||
'X-Auth-Key': self._get_password()}
|
|
||||||
if self.projectid:
|
|
||||||
headers['X-Auth-Project-Id'] = self.projectid
|
|
||||||
|
|
||||||
resp, body = self._time_request(url, 'GET', headers=headers)
|
|
||||||
if resp.status_code in (200, 204): # in some cases we get No Content
|
|
||||||
try:
|
|
||||||
mgmt_header = 'x-server-management-url'
|
|
||||||
self.management_url = resp.headers[mgmt_header].rstrip('/')
|
|
||||||
self.auth_token = resp.headers['x-auth-token']
|
|
||||||
self.auth_url = url
|
|
||||||
except (KeyError, TypeError):
|
|
||||||
raise exceptions.AuthorizationFailure()
|
|
||||||
elif resp.status_code == 305:
|
|
||||||
return resp.headers['location']
|
|
||||||
else:
|
|
||||||
raise exceptions.from_response(resp, body, url)
|
|
||||||
|
|
||||||
def _v2_auth(self, url):
|
|
||||||
"""Authenticate against a v2.0 auth service."""
|
|
||||||
if self.auth_token:
|
|
||||||
body = {"auth": {
|
|
||||||
"token": {"id": self.auth_token}}}
|
|
||||||
elif self.user_id:
|
|
||||||
body = {"auth": {
|
|
||||||
"passwordCredentials": {"userId": self.user_id,
|
|
||||||
"password": self._get_password()}}}
|
|
||||||
else:
|
|
||||||
body = {"auth": {
|
|
||||||
"passwordCredentials": {"username": self.user,
|
|
||||||
"password": self._get_password()}}}
|
|
||||||
|
|
||||||
if self.tenant_id:
|
|
||||||
body['auth']['tenantId'] = self.tenant_id
|
|
||||||
elif self.projectid:
|
|
||||||
body['auth']['tenantName'] = self.projectid
|
|
||||||
|
|
||||||
return self._authenticate(url, body)
|
|
||||||
|
|
||||||
def _authenticate(self, url, body, **kwargs):
|
|
||||||
"""Authenticate and extract the service catalog."""
|
|
||||||
method = "POST"
|
|
||||||
token_url = url + "/tokens"
|
|
||||||
|
|
||||||
# Make sure we follow redirects when trying to reach Keystone
|
|
||||||
resp, respbody = self._time_request(
|
|
||||||
token_url,
|
|
||||||
method,
|
|
||||||
body=body,
|
|
||||||
allow_redirects=True,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
return self._extract_service_catalog(url, resp, respbody)
|
|
||||||
|
|
||||||
|
|
||||||
def _construct_http_client(api_version=None,
|
def _construct_http_client(api_version=None,
|
||||||
auth=None,
|
auth=None,
|
||||||
auth_token=None,
|
auth_token=None,
|
||||||
|
@@ -1,89 +0,0 @@
|
|||||||
# Copyright 2011 OpenStack Foundation
|
|
||||||
# Copyright 2011, Piston Cloud Computing, 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 novaclient.exceptions
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceCatalog(object):
|
|
||||||
"""Helper methods for dealing with a Keystone Service Catalog."""
|
|
||||||
|
|
||||||
def __init__(self, resource_dict):
|
|
||||||
self.catalog = resource_dict
|
|
||||||
|
|
||||||
def get_token(self):
|
|
||||||
return self.catalog['access']['token']['id']
|
|
||||||
|
|
||||||
def get_tenant_id(self):
|
|
||||||
return self.catalog['access']['token']['tenant']['id']
|
|
||||||
|
|
||||||
def url_for(self, attr=None, filter_value=None,
|
|
||||||
service_type=None, endpoint_type='publicURL',
|
|
||||||
service_name=None, volume_service_name=None):
|
|
||||||
"""Fetch the public URL from the Compute service for
|
|
||||||
a particular endpoint attribute. If none given, return
|
|
||||||
the first. See tests for sample service catalog.
|
|
||||||
"""
|
|
||||||
matching_endpoints = []
|
|
||||||
if 'endpoints' in self.catalog:
|
|
||||||
# We have a bastardized service catalog. Treat it special. :/
|
|
||||||
for endpoint in self.catalog['endpoints']:
|
|
||||||
if not filter_value or endpoint[attr] == filter_value:
|
|
||||||
# Ignore 1.0 compute endpoints
|
|
||||||
if endpoint.get("type") == 'compute' and \
|
|
||||||
endpoint.get('versionId') in (None, '1.1', '2'):
|
|
||||||
matching_endpoints.append(endpoint)
|
|
||||||
if not matching_endpoints:
|
|
||||||
raise novaclient.exceptions.EndpointNotFound()
|
|
||||||
|
|
||||||
# We don't always get a service catalog back ...
|
|
||||||
if 'serviceCatalog' not in self.catalog['access']:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Full catalog ...
|
|
||||||
catalog = self.catalog['access']['serviceCatalog']
|
|
||||||
|
|
||||||
for service in catalog:
|
|
||||||
if service.get("type") != service_type:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if (service_name and service_type == 'compute' and
|
|
||||||
service.get('name') != service_name):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if (volume_service_name and service_type == 'volume' and
|
|
||||||
service.get('name') != volume_service_name):
|
|
||||||
continue
|
|
||||||
|
|
||||||
endpoints = service['endpoints']
|
|
||||||
for endpoint in endpoints:
|
|
||||||
# Ignore 1.0 compute endpoints
|
|
||||||
if (service.get("type") == 'compute' and
|
|
||||||
endpoint.get('versionId', '2') not in ('1.1', '2')):
|
|
||||||
continue
|
|
||||||
if (not filter_value or
|
|
||||||
endpoint.get(attr).lower() == filter_value.lower()):
|
|
||||||
endpoint["serviceName"] = service.get("name")
|
|
||||||
matching_endpoints.append(endpoint)
|
|
||||||
|
|
||||||
if not matching_endpoints:
|
|
||||||
raise novaclient.exceptions.EndpointNotFound()
|
|
||||||
elif len(matching_endpoints) > 1:
|
|
||||||
raise novaclient.exceptions.AmbiguousEndpoints(
|
|
||||||
endpoints=matching_endpoints)
|
|
||||||
else:
|
|
||||||
return matching_endpoints[0][endpoint_type]
|
|
@@ -444,7 +444,7 @@ class V1(Base):
|
|||||||
assert len(body.keys()) == 1
|
assert len(body.keys()) == 1
|
||||||
action = list(body)[0]
|
action = list(body)[0]
|
||||||
|
|
||||||
if v2_fakes.FakeHTTPClient.check_server_actions(body):
|
if v2_fakes.FakeSessionClient.check_server_actions(body):
|
||||||
# NOTE(snikitin): No need to do any operations here. This 'pass'
|
# NOTE(snikitin): No need to do any operations here. This 'pass'
|
||||||
# is needed to avoid AssertionError in the last 'else' statement
|
# is needed to avoid AssertionError in the last 'else' statement
|
||||||
# if we found 'action' in method check_server_actions and
|
# if we found 'action' in method check_server_actions and
|
||||||
|
@@ -14,9 +14,7 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import logging
|
|
||||||
|
|
||||||
import fixtures
|
|
||||||
from keystoneauth1 import session
|
from keystoneauth1 import session
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
@@ -27,129 +25,7 @@ from novaclient.tests.unit import utils
|
|||||||
import novaclient.v2.client
|
import novaclient.v2.client
|
||||||
|
|
||||||
|
|
||||||
class ClientConnectionPoolTest(utils.TestCase):
|
|
||||||
|
|
||||||
@mock.patch("keystoneauth1.session.TCPKeepAliveAdapter")
|
|
||||||
def test_get(self, mock_http_adapter):
|
|
||||||
mock_http_adapter.side_effect = lambda: mock.Mock()
|
|
||||||
pool = novaclient.client._ClientConnectionPool()
|
|
||||||
self.assertEqual(pool.get("abc"), pool.get("abc"))
|
|
||||||
self.assertNotEqual(pool.get("abc"), pool.get("def"))
|
|
||||||
|
|
||||||
|
|
||||||
class ClientTest(utils.TestCase):
|
class ClientTest(utils.TestCase):
|
||||||
|
|
||||||
def test_client_with_timeout(self):
|
|
||||||
auth_url = "http://example.com"
|
|
||||||
instance = novaclient.client.HTTPClient(user='user',
|
|
||||||
password='password',
|
|
||||||
projectid='project',
|
|
||||||
timeout=2,
|
|
||||||
auth_url=auth_url)
|
|
||||||
self.assertEqual(2, instance.timeout)
|
|
||||||
|
|
||||||
headers = {
|
|
||||||
'x-server-management-url': 'example.com',
|
|
||||||
'x-auth-token': 'blah',
|
|
||||||
}
|
|
||||||
|
|
||||||
self.requests_mock.get(auth_url, headers=headers)
|
|
||||||
|
|
||||||
instance.authenticate()
|
|
||||||
|
|
||||||
self.assertEqual(2, self.requests_mock.last_request.timeout)
|
|
||||||
|
|
||||||
def test_client_reauth(self):
|
|
||||||
auth_url = "http://www.example.com"
|
|
||||||
instance = novaclient.client.HTTPClient(user='user',
|
|
||||||
password='password',
|
|
||||||
projectid='project',
|
|
||||||
timeout=2,
|
|
||||||
auth_url=auth_url)
|
|
||||||
instance.auth_token = 'foobar'
|
|
||||||
mgmt_url = "http://mgmt.example.com"
|
|
||||||
instance.management_url = mgmt_url
|
|
||||||
instance.get_service_url = mock.Mock(return_value=mgmt_url)
|
|
||||||
instance.version = 'v2.0'
|
|
||||||
|
|
||||||
auth = self.requests_mock.post(auth_url + '/tokens', status_code=401)
|
|
||||||
detail = self.requests_mock.get(mgmt_url + '/servers/detail',
|
|
||||||
status_code=401)
|
|
||||||
|
|
||||||
self.assertRaises(novaclient.exceptions.Unauthorized,
|
|
||||||
instance.get,
|
|
||||||
'/servers/detail')
|
|
||||||
|
|
||||||
self.assertEqual(2, self.requests_mock.call_count)
|
|
||||||
self.assertTrue(detail.called_once)
|
|
||||||
self.assertTrue(auth.called_once)
|
|
||||||
|
|
||||||
detail_headers = detail.last_request.headers
|
|
||||||
self.assertEqual('project', detail_headers['X-Auth-Project-Id'])
|
|
||||||
self.assertEqual('foobar', detail_headers['X-Auth-Token'])
|
|
||||||
self.assertEqual('python-novaclient', detail_headers['User-Agent'])
|
|
||||||
self.assertEqual('application/json', detail_headers['Accept'])
|
|
||||||
|
|
||||||
reauth_headers = auth.last_request.headers
|
|
||||||
self.assertEqual('application/json', reauth_headers['Content-Type'])
|
|
||||||
self.assertEqual('application/json', reauth_headers['Accept'])
|
|
||||||
self.assertEqual('python-novaclient', reauth_headers['User-Agent'])
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"auth": {
|
|
||||||
"tenantName": "project",
|
|
||||||
"passwordCredentials": {
|
|
||||||
"username": "user",
|
|
||||||
"password": "password"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.assertEqual(data, auth.last_request.json())
|
|
||||||
|
|
||||||
def _check_version_url(self, management_url, version_url):
|
|
||||||
projectid = '25e469aa1848471b875e68cde6531bc5'
|
|
||||||
auth_url = "http://example.com"
|
|
||||||
instance = novaclient.client.HTTPClient(user='user',
|
|
||||||
password='password',
|
|
||||||
projectid=projectid,
|
|
||||||
auth_url=auth_url)
|
|
||||||
instance.auth_token = 'foobar'
|
|
||||||
instance.management_url = management_url % projectid
|
|
||||||
mock_get_service_url = mock.Mock(return_value=instance.management_url)
|
|
||||||
instance.get_service_url = mock_get_service_url
|
|
||||||
instance.version = 'v2.0'
|
|
||||||
|
|
||||||
versions = self.requests_mock.get(version_url, json={'versions': []})
|
|
||||||
servers = self.requests_mock.get(instance.management_url + 'servers')
|
|
||||||
|
|
||||||
# If passing None as the part of url, a client accesses the url which
|
|
||||||
# doesn't include "v2/<projectid>" for getting API version info.
|
|
||||||
instance.get(None)
|
|
||||||
|
|
||||||
self.assertTrue(versions.called_once)
|
|
||||||
|
|
||||||
# Otherwise, a client accesses the url which includes "v2/<projectid>".
|
|
||||||
self.assertFalse(servers.called_once)
|
|
||||||
instance.get('servers')
|
|
||||||
self.assertTrue(servers.called_once)
|
|
||||||
|
|
||||||
def test_client_version_url(self):
|
|
||||||
self._check_version_url('http://example.com/v2/%s',
|
|
||||||
'http://example.com/')
|
|
||||||
self._check_version_url('http://example.com/v2.1/%s',
|
|
||||||
'http://example.com/')
|
|
||||||
self._check_version_url('http://example.com/v3.785/%s',
|
|
||||||
'http://example.com/')
|
|
||||||
|
|
||||||
def test_client_version_url_with_project_name(self):
|
|
||||||
self._check_version_url('http://example.com/nova/v2/%s',
|
|
||||||
'http://example.com/nova/')
|
|
||||||
self._check_version_url('http://example.com/nova/v2.1/%s',
|
|
||||||
'http://example.com/nova/')
|
|
||||||
self._check_version_url('http://example.com/nova/v3.785/%s',
|
|
||||||
'http://example.com/nova/')
|
|
||||||
|
|
||||||
def test_get_client_class_v2(self):
|
def test_get_client_class_v2(self):
|
||||||
output = novaclient.client.get_client_class('2')
|
output = novaclient.client.get_client_class('2')
|
||||||
self.assertEqual(output, novaclient.v2.client.Client)
|
self.assertEqual(output, novaclient.v2.client.Client)
|
||||||
@@ -172,239 +48,6 @@ class ClientTest(utils.TestCase):
|
|||||||
self.assertRaises(novaclient.exceptions.UnsupportedVersion,
|
self.assertRaises(novaclient.exceptions.UnsupportedVersion,
|
||||||
novaclient.client.get_client_class, '2.latest')
|
novaclient.client.get_client_class, '2.latest')
|
||||||
|
|
||||||
def test_client_get_reset_timings_v2(self):
|
|
||||||
cs = novaclient.client.Client("2", "user", "password", "project_id",
|
|
||||||
auth_url="foo/v2")
|
|
||||||
self.assertEqual(0, len(cs.get_timings()))
|
|
||||||
cs.client.times.append("somevalue")
|
|
||||||
self.assertEqual(1, len(cs.get_timings()))
|
|
||||||
self.assertEqual("somevalue", cs.get_timings()[0])
|
|
||||||
|
|
||||||
cs.reset_timings()
|
|
||||||
self.assertEqual(0, len(cs.get_timings()))
|
|
||||||
|
|
||||||
def test_get_password_simple(self):
|
|
||||||
cs = novaclient.client.HTTPClient("user", "password", "", "")
|
|
||||||
cs.password_func = mock.Mock()
|
|
||||||
self.assertEqual("password", cs._get_password())
|
|
||||||
self.assertFalse(cs.password_func.called)
|
|
||||||
|
|
||||||
def test_get_password_none(self):
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "", "")
|
|
||||||
self.assertIsNone(cs._get_password())
|
|
||||||
|
|
||||||
def test_get_password_func(self):
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "", "")
|
|
||||||
cs.password_func = mock.Mock(return_value="password")
|
|
||||||
self.assertEqual("password", cs._get_password())
|
|
||||||
cs.password_func.assert_called_once_with()
|
|
||||||
|
|
||||||
cs.password_func = mock.Mock()
|
|
||||||
self.assertEqual("password", cs._get_password())
|
|
||||||
self.assertFalse(cs.password_func.called)
|
|
||||||
|
|
||||||
def test_auth_url_rstrip_slash(self):
|
|
||||||
cs = novaclient.client.HTTPClient("user", "password", "project_id",
|
|
||||||
auth_url="foo/v2/")
|
|
||||||
self.assertEqual("foo/v2", cs.auth_url)
|
|
||||||
|
|
||||||
def test_token_and_bypass_url(self):
|
|
||||||
cs = novaclient.client.HTTPClient(None, None, None,
|
|
||||||
auth_token="12345",
|
|
||||||
bypass_url="compute/v100/")
|
|
||||||
self.assertIsNone(cs.auth_url)
|
|
||||||
self.assertEqual("12345", cs.auth_token)
|
|
||||||
self.assertEqual("compute/v100", cs.bypass_url)
|
|
||||||
self.assertEqual("compute/v100", cs.management_url)
|
|
||||||
|
|
||||||
def test_service_url_lookup(self):
|
|
||||||
service_type = 'compute'
|
|
||||||
cs = novaclient.client.HTTPClient(None, None, None,
|
|
||||||
auth_url='foo/v2',
|
|
||||||
service_type=service_type)
|
|
||||||
|
|
||||||
self.requests_mock.get('http://mgmt.example.com/compute/v5/servers')
|
|
||||||
|
|
||||||
@mock.patch.object(cs,
|
|
||||||
'get_service_url',
|
|
||||||
return_value='http://mgmt.example.com/compute/v5')
|
|
||||||
@mock.patch.object(cs, 'authenticate')
|
|
||||||
def do_test(mock_auth, mock_get):
|
|
||||||
|
|
||||||
def set_service_catalog():
|
|
||||||
cs.service_catalog = 'catalog'
|
|
||||||
|
|
||||||
mock_auth.side_effect = set_service_catalog
|
|
||||||
cs.get('/servers')
|
|
||||||
mock_get.assert_called_once_with(service_type)
|
|
||||||
mock_auth.assert_called_once_with()
|
|
||||||
|
|
||||||
do_test()
|
|
||||||
|
|
||||||
self.assertEqual(1, self.requests_mock.call_count)
|
|
||||||
|
|
||||||
self.assertEqual('/compute/v5/servers',
|
|
||||||
self.requests_mock.last_request.path)
|
|
||||||
|
|
||||||
def test_bypass_url_no_service_url_lookup(self):
|
|
||||||
bypass_url = 'http://mgmt.compute.com/v100'
|
|
||||||
cs = novaclient.client.HTTPClient(None, None, None,
|
|
||||||
auth_url='foo/v2',
|
|
||||||
bypass_url=bypass_url)
|
|
||||||
|
|
||||||
get = self.requests_mock.get('http://mgmt.compute.com/v100/servers')
|
|
||||||
|
|
||||||
@mock.patch.object(cs, 'get_service_url')
|
|
||||||
def do_test(mock_get):
|
|
||||||
cs.get('/servers')
|
|
||||||
self.assertFalse(mock_get.called)
|
|
||||||
|
|
||||||
do_test()
|
|
||||||
self.assertTrue(get.called_once)
|
|
||||||
|
|
||||||
@mock.patch("novaclient.client.requests.Session")
|
|
||||||
def test_session(self, mock_session):
|
|
||||||
fake_session = mock.Mock()
|
|
||||||
mock_session.return_value = fake_session
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "", "")
|
|
||||||
cs.open_session()
|
|
||||||
self.assertEqual(cs._session, fake_session)
|
|
||||||
cs.close_session()
|
|
||||||
self.assertIsNone(cs._session)
|
|
||||||
|
|
||||||
def test_session_connection_pool(self):
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "",
|
|
||||||
"", connection_pool=True)
|
|
||||||
cs.open_session()
|
|
||||||
self.assertIsNone(cs._session)
|
|
||||||
cs.close_session()
|
|
||||||
self.assertIsNone(cs._session)
|
|
||||||
|
|
||||||
def test_get_session(self):
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "", "")
|
|
||||||
self.assertIsNone(cs._get_session("http://example.com"))
|
|
||||||
|
|
||||||
@mock.patch("novaclient.client.requests.Session")
|
|
||||||
def test_get_session_open_session(self, mock_session):
|
|
||||||
fake_session = mock.Mock()
|
|
||||||
mock_session.return_value = fake_session
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "", "")
|
|
||||||
cs.open_session()
|
|
||||||
self.assertEqual(fake_session, cs._get_session("http://example.com"))
|
|
||||||
|
|
||||||
@mock.patch("novaclient.client.requests.Session")
|
|
||||||
@mock.patch("novaclient.client._ClientConnectionPool")
|
|
||||||
def test_get_session_connection_pool(self, mock_pool, mock_session):
|
|
||||||
service_url = "http://service.example.com"
|
|
||||||
|
|
||||||
pool = mock.MagicMock()
|
|
||||||
pool.get.return_value = "http_adapter"
|
|
||||||
mock_pool.return_value = pool
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "",
|
|
||||||
"", connection_pool=True)
|
|
||||||
cs._current_url = "http://current.example.com"
|
|
||||||
|
|
||||||
session = cs._get_session(service_url)
|
|
||||||
self.assertEqual(session, mock_session.return_value)
|
|
||||||
pool.get.assert_called_once_with(service_url)
|
|
||||||
mock_session().mount.assert_called_once_with(service_url,
|
|
||||||
'http_adapter')
|
|
||||||
|
|
||||||
def test_init_without_connection_pool(self):
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "", "")
|
|
||||||
self.assertIsNone(cs._connection_pool)
|
|
||||||
|
|
||||||
@mock.patch("novaclient.client._ClientConnectionPool")
|
|
||||||
def test_init_with_proper_connection_pool(self, mock_pool):
|
|
||||||
fake_pool = mock.Mock()
|
|
||||||
mock_pool.return_value = fake_pool
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "",
|
|
||||||
connection_pool=True)
|
|
||||||
self.assertEqual(cs._connection_pool, fake_pool)
|
|
||||||
|
|
||||||
def test_log_req(self):
|
|
||||||
self.logger = self.useFixture(
|
|
||||||
fixtures.FakeLogger(
|
|
||||||
format="%(message)s",
|
|
||||||
level=logging.DEBUG,
|
|
||||||
nuke_handlers=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "",
|
|
||||||
connection_pool=True)
|
|
||||||
cs.http_log_debug = True
|
|
||||||
cs.http_log_req('GET', '/foo', {'headers': {}})
|
|
||||||
cs.http_log_req('GET', '/foo', {'headers':
|
|
||||||
{'X-Auth-Token': None}})
|
|
||||||
cs.http_log_req('GET', '/foo', {'headers':
|
|
||||||
{'X-Auth-Token': 'totally_bogus'}})
|
|
||||||
cs.http_log_req('GET', '/foo', {'headers':
|
|
||||||
{'X-Foo': 'bar',
|
|
||||||
'X-Auth-Token': 'totally_bogus'}})
|
|
||||||
cs.http_log_req('GET', '/foo', {'headers': {},
|
|
||||||
'data':
|
|
||||||
'{"auth": {"passwordCredentials": '
|
|
||||||
'{"password": "zhaoqin"}}}'})
|
|
||||||
|
|
||||||
output = self.logger.output.split('\n')
|
|
||||||
|
|
||||||
self.assertIn("REQ: curl -g -i '/foo' -X GET", output)
|
|
||||||
self.assertIn(
|
|
||||||
"REQ: curl -g -i '/foo' -X GET -H "
|
|
||||||
'"X-Auth-Token: None"',
|
|
||||||
output)
|
|
||||||
self.assertIn(
|
|
||||||
"REQ: curl -g -i '/foo' -X GET -H "
|
|
||||||
'"X-Auth-Token: {SHA1}b42162b6ffdbd7c3c37b7c95b7ba9f51dda0236d"',
|
|
||||||
output)
|
|
||||||
self.assertIn(
|
|
||||||
"REQ: curl -g -i '/foo' -X GET -H "
|
|
||||||
'"X-Auth-Token: {SHA1}b42162b6ffdbd7c3c37b7c95b7ba9f51dda0236d"'
|
|
||||||
' -H "X-Foo: bar"',
|
|
||||||
output)
|
|
||||||
self.assertIn(
|
|
||||||
"REQ: curl -g -i '/foo' -X GET -d "
|
|
||||||
'\'{"auth": {"passwordCredentials": {"password":'
|
|
||||||
' "{SHA1}4fc49c6a671ce889078ff6b250f7066cf6d2ada2"}}}\'',
|
|
||||||
output)
|
|
||||||
|
|
||||||
def test_log_resp(self):
|
|
||||||
self.logger = self.useFixture(
|
|
||||||
fixtures.FakeLogger(
|
|
||||||
format="%(message)s",
|
|
||||||
level=logging.DEBUG,
|
|
||||||
nuke_handlers=True
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
cs = novaclient.client.HTTPClient("user", None, "",
|
|
||||||
connection_pool=True)
|
|
||||||
cs.http_log_debug = True
|
|
||||||
text = ('{"access": {"token": {"id": "zhaoqin"}}}')
|
|
||||||
resp = utils.TestResponse({'status_code': 200, 'headers': {},
|
|
||||||
'text': text})
|
|
||||||
|
|
||||||
cs.http_log_resp(resp)
|
|
||||||
output = self.logger.output.split('\n')
|
|
||||||
|
|
||||||
self.assertIn('RESP: [200] {}', output)
|
|
||||||
self.assertIn('RESP BODY: {"access": {"token": {"id":'
|
|
||||||
' "{SHA1}4fc49c6a671ce889078ff6b250f7066cf6d2ada2"}}}',
|
|
||||||
output)
|
|
||||||
|
|
||||||
def test_timings(self):
|
|
||||||
self.requests_mock.get('http://no.where')
|
|
||||||
|
|
||||||
client = novaclient.client.HTTPClient(user='zqfan', password='')
|
|
||||||
client._time_request("http://no.where", 'GET')
|
|
||||||
self.assertEqual(0, len(client.times))
|
|
||||||
|
|
||||||
client = novaclient.client.HTTPClient(user='zqfan', password='',
|
|
||||||
timings=True)
|
|
||||||
client._time_request("http://no.where", 'GET')
|
|
||||||
self.assertEqual(1, len(client.times))
|
|
||||||
self.assertEqual('GET http://no.where', client.times[0][0])
|
|
||||||
|
|
||||||
|
|
||||||
class SessionClientTest(utils.TestCase):
|
class SessionClientTest(utils.TestCase):
|
||||||
|
|
||||||
@@ -422,6 +65,16 @@ class SessionClientTest(utils.TestCase):
|
|||||||
self.assertEqual(1, len(client.times))
|
self.assertEqual(1, len(client.times))
|
||||||
self.assertEqual('GET http://no.where', client.times[0][0])
|
self.assertEqual('GET http://no.where', client.times[0][0])
|
||||||
|
|
||||||
|
def test_client_get_reset_timings_v2(self):
|
||||||
|
cs = novaclient.client.SessionClient(session=session.Session())
|
||||||
|
self.assertEqual(0, len(cs.get_timings()))
|
||||||
|
cs.times.append("somevalue")
|
||||||
|
self.assertEqual(1, len(cs.get_timings()))
|
||||||
|
self.assertEqual("somevalue", cs.get_timings()[0])
|
||||||
|
|
||||||
|
cs.reset_timings()
|
||||||
|
self.assertEqual(0, len(cs.get_timings()))
|
||||||
|
|
||||||
@mock.patch.object(novaclient.client, '_log_request_id')
|
@mock.patch.object(novaclient.client, '_log_request_id')
|
||||||
def test_log_request_id(self, mock_log_request_id):
|
def test_log_request_id(self, mock_log_request_id):
|
||||||
self.requests_mock.get('http://no.where')
|
self.requests_mock.get('http://no.where')
|
||||||
|
@@ -1,220 +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 requests
|
|
||||||
import six
|
|
||||||
|
|
||||||
from novaclient import client
|
|
||||||
from novaclient import exceptions
|
|
||||||
from novaclient.tests.unit import utils
|
|
||||||
|
|
||||||
|
|
||||||
fake_response = utils.TestResponse({
|
|
||||||
"status_code": 200,
|
|
||||||
"text": '{"hi": "there"}',
|
|
||||||
})
|
|
||||||
mock_request = mock.Mock(return_value=(fake_response))
|
|
||||||
|
|
||||||
refused_response = utils.TestResponse({
|
|
||||||
"status_code": 400,
|
|
||||||
"text": '[Errno 111] Connection refused',
|
|
||||||
})
|
|
||||||
refused_mock_request = mock.Mock(return_value=(refused_response))
|
|
||||||
|
|
||||||
bad_req_response = utils.TestResponse({
|
|
||||||
"status_code": 400,
|
|
||||||
"text": '',
|
|
||||||
})
|
|
||||||
bad_req_mock_request = mock.Mock(return_value=(bad_req_response))
|
|
||||||
|
|
||||||
unknown_error_response = utils.TestResponse({
|
|
||||||
"status_code": 503,
|
|
||||||
"text": '',
|
|
||||||
})
|
|
||||||
unknown_error_mock_request = mock.Mock(return_value=unknown_error_response)
|
|
||||||
|
|
||||||
retry_after_response = utils.TestResponse({
|
|
||||||
"status_code": 413,
|
|
||||||
"text": '',
|
|
||||||
"headers": {
|
|
||||||
"retry-after": "5"
|
|
||||||
},
|
|
||||||
})
|
|
||||||
retry_after_mock_request = mock.Mock(return_value=retry_after_response)
|
|
||||||
|
|
||||||
retry_after_no_headers_response = utils.TestResponse({
|
|
||||||
"status_code": 413,
|
|
||||||
"text": '',
|
|
||||||
})
|
|
||||||
retry_after_no_headers_mock_request = mock.Mock(
|
|
||||||
return_value=retry_after_no_headers_response)
|
|
||||||
|
|
||||||
retry_after_non_supporting_response = utils.TestResponse({
|
|
||||||
"status_code": 403,
|
|
||||||
"text": '',
|
|
||||||
"headers": {
|
|
||||||
"retry-after": "5"
|
|
||||||
},
|
|
||||||
})
|
|
||||||
retry_after_non_supporting_mock_request = mock.Mock(
|
|
||||||
return_value=retry_after_non_supporting_response)
|
|
||||||
|
|
||||||
|
|
||||||
def get_client(**kwargs):
|
|
||||||
cl = client.HTTPClient("username", "password",
|
|
||||||
"project_id",
|
|
||||||
utils.AUTH_URL_V2,
|
|
||||||
**kwargs)
|
|
||||||
return cl
|
|
||||||
|
|
||||||
|
|
||||||
def get_authed_client(**kwargs):
|
|
||||||
cl = get_client(**kwargs)
|
|
||||||
cl.management_url = "http://example.com"
|
|
||||||
cl.auth_token = "token"
|
|
||||||
cl.get_service_url = mock.Mock(return_value="http://example.com")
|
|
||||||
return cl
|
|
||||||
|
|
||||||
|
|
||||||
class ClientTest(utils.TestCase):
|
|
||||||
|
|
||||||
def test_get(self):
|
|
||||||
cl = get_authed_client()
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", mock_request)
|
|
||||||
@mock.patch('time.time', mock.Mock(return_value=1234))
|
|
||||||
def test_get_call():
|
|
||||||
resp, body = cl.get("/hi")
|
|
||||||
headers = {"X-Auth-Token": "token",
|
|
||||||
"X-Auth-Project-Id": "project_id",
|
|
||||||
"User-Agent": cl.USER_AGENT,
|
|
||||||
'Accept': 'application/json'}
|
|
||||||
mock_request.assert_called_with(
|
|
||||||
"GET",
|
|
||||||
"http://example.com/hi",
|
|
||||||
headers=headers,
|
|
||||||
**self.TEST_REQUEST_BASE)
|
|
||||||
# Automatic JSON parsing
|
|
||||||
self.assertEqual({"hi": "there"}, body)
|
|
||||||
|
|
||||||
test_get_call()
|
|
||||||
|
|
||||||
def test_post(self):
|
|
||||||
cl = get_authed_client()
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", mock_request)
|
|
||||||
def test_post_call():
|
|
||||||
cl.post("/hi", body=[1, 2, 3])
|
|
||||||
headers = {
|
|
||||||
"X-Auth-Token": "token",
|
|
||||||
"X-Auth-Project-Id": "project_id",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
'Accept': 'application/json',
|
|
||||||
"User-Agent": cl.USER_AGENT
|
|
||||||
}
|
|
||||||
mock_request.assert_called_with(
|
|
||||||
"POST",
|
|
||||||
"http://example.com/hi",
|
|
||||||
headers=headers,
|
|
||||||
data='[1, 2, 3]',
|
|
||||||
**self.TEST_REQUEST_BASE)
|
|
||||||
|
|
||||||
test_post_call()
|
|
||||||
|
|
||||||
def test_auth_failure(self):
|
|
||||||
cl = get_client()
|
|
||||||
|
|
||||||
# response must not have x-server-management-url header
|
|
||||||
@mock.patch.object(requests.Session, "request", mock_request)
|
|
||||||
def test_auth_call():
|
|
||||||
self.assertRaises(exceptions.AuthorizationFailure, cl.authenticate)
|
|
||||||
|
|
||||||
test_auth_call()
|
|
||||||
|
|
||||||
def test_auth_failure_due_to_miss_of_auth_url(self):
|
|
||||||
cl = client.HTTPClient("username", "password")
|
|
||||||
|
|
||||||
self.assertRaises(exceptions.AuthorizationFailure, cl.authenticate)
|
|
||||||
|
|
||||||
def test_connection_refused(self):
|
|
||||||
cl = get_client()
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", refused_mock_request)
|
|
||||||
def test_refused_call():
|
|
||||||
self.assertRaises(exceptions.ConnectionRefused, cl.get, "/hi")
|
|
||||||
|
|
||||||
test_refused_call()
|
|
||||||
|
|
||||||
def test_bad_request(self):
|
|
||||||
cl = get_client()
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", bad_req_mock_request)
|
|
||||||
def test_refused_call():
|
|
||||||
self.assertRaises(exceptions.BadRequest, cl.get, "/hi")
|
|
||||||
|
|
||||||
test_refused_call()
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", mock_request)
|
|
||||||
@mock.patch.object(client, '_log_request_id')
|
|
||||||
@mock.patch.object(client.HTTPClient, 'http_log_req')
|
|
||||||
def test_client_logger(self, mock_http_log_req, mock_log_request_id):
|
|
||||||
cl = get_authed_client(service_name='compute', http_log_debug=True)
|
|
||||||
self.assertIsNotNone(cl._logger)
|
|
||||||
|
|
||||||
cl.post("/hi", body=[1, 2, 3])
|
|
||||||
mock_log_request_id.assert_called_once_with(cl._logger, fake_response,
|
|
||||||
'compute')
|
|
||||||
|
|
||||||
@mock.patch.object(requests, 'request', unknown_error_mock_request)
|
|
||||||
def test_unknown_server_error(self):
|
|
||||||
cl = get_client()
|
|
||||||
# This would be cleaner with the context manager version of
|
|
||||||
# assertRaises or assertRaisesRegexp, but both only appeared in
|
|
||||||
# Python 2.7 and testtools doesn't match that implementation yet
|
|
||||||
try:
|
|
||||||
cl.get('/hi')
|
|
||||||
except exceptions.ClientException as exc:
|
|
||||||
self.assertIn('Unknown Error', six.text_type(exc))
|
|
||||||
else:
|
|
||||||
self.fail('Expected exceptions.ClientException')
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", retry_after_mock_request)
|
|
||||||
def test_retry_after_request(self):
|
|
||||||
cl = get_client()
|
|
||||||
|
|
||||||
try:
|
|
||||||
cl.get("/hi")
|
|
||||||
except exceptions.OverLimit as exc:
|
|
||||||
self.assertEqual(5, exc.retry_after)
|
|
||||||
else:
|
|
||||||
self.fail('Expected exceptions.OverLimit')
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request",
|
|
||||||
retry_after_no_headers_mock_request)
|
|
||||||
def test_retry_after_request_no_headers(self):
|
|
||||||
cl = get_client()
|
|
||||||
|
|
||||||
try:
|
|
||||||
cl.get("/hi")
|
|
||||||
except exceptions.OverLimit as exc:
|
|
||||||
self.assertEqual(0, exc.retry_after)
|
|
||||||
else:
|
|
||||||
self.fail('Expected exceptions.OverLimit')
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request",
|
|
||||||
retry_after_non_supporting_mock_request)
|
|
||||||
def test_retry_after_request_non_supporting_exc(self):
|
|
||||||
cl = get_client()
|
|
||||||
|
|
||||||
self.assertRaises(exceptions.Forbidden, cl.get, "/hi")
|
|
@@ -1,60 +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 keystoneauth1 import fixture
|
|
||||||
|
|
||||||
from novaclient import exceptions
|
|
||||||
from novaclient import service_catalog
|
|
||||||
from novaclient.tests.unit import utils
|
|
||||||
|
|
||||||
|
|
||||||
SERVICE_CATALOG = fixture.V2Token()
|
|
||||||
SERVICE_CATALOG.set_scope()
|
|
||||||
|
|
||||||
_s = SERVICE_CATALOG.add_service('compute')
|
|
||||||
_e = _s.add_endpoint("https://compute1.host/v1/1")
|
|
||||||
_e["tenantId"] = "1"
|
|
||||||
_e["versionId"] = "1.0"
|
|
||||||
_e = _s.add_endpoint("https://compute1.host/v1.1/2", region="North")
|
|
||||||
_e["tenantId"] = "2"
|
|
||||||
_e["versionId"] = "1.1"
|
|
||||||
_e = _s.add_endpoint("https://compute1.host/v2/1", region="North")
|
|
||||||
_e["tenantId"] = "1"
|
|
||||||
_e["versionId"] = "2"
|
|
||||||
|
|
||||||
_s = SERVICE_CATALOG.add_service('volume')
|
|
||||||
_e = _s.add_endpoint("https://volume1.host/v1/1", region="South")
|
|
||||||
_e["tenantId"] = "1"
|
|
||||||
_e = _s.add_endpoint("https://volume1.host/v1.1/2", region="South")
|
|
||||||
_e["tenantId"] = "2"
|
|
||||||
|
|
||||||
|
|
||||||
class ServiceCatalogTest(utils.TestCase):
|
|
||||||
def test_building_a_service_catalog(self):
|
|
||||||
sc = service_catalog.ServiceCatalog(SERVICE_CATALOG)
|
|
||||||
|
|
||||||
self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for,
|
|
||||||
service_type='compute')
|
|
||||||
self.assertEqual("https://compute1.host/v2/1",
|
|
||||||
sc.url_for('tenantId', '1', service_type='compute'))
|
|
||||||
self.assertEqual("https://compute1.host/v1.1/2",
|
|
||||||
sc.url_for('tenantId', '2', service_type='compute'))
|
|
||||||
|
|
||||||
self.assertRaises(exceptions.EndpointNotFound, sc.url_for,
|
|
||||||
"region", "South", service_type='compute')
|
|
||||||
|
|
||||||
def test_building_a_service_catalog_insensitive_case(self):
|
|
||||||
sc = service_catalog.ServiceCatalog(SERVICE_CATALOG)
|
|
||||||
# Matching south (and catalog has South).
|
|
||||||
self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for,
|
|
||||||
'region', 'south', service_type='volume')
|
|
@@ -61,21 +61,24 @@ class FakeClient(fakes.FakeClient, client.Client):
|
|||||||
project_id='project_id', auth_url='auth_url',
|
project_id='project_id', auth_url='auth_url',
|
||||||
extensions=kwargs.get('extensions'),
|
extensions=kwargs.get('extensions'),
|
||||||
direct_use=False, api_version=api_version)
|
direct_use=False, api_version=api_version)
|
||||||
self.client = FakeHTTPClient(api_version=api_version, **kwargs)
|
self.client = FakeSessionClient(api_version=api_version, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class FakeHTTPClient(base_client.HTTPClient):
|
class FakeSessionClient(base_client.SessionClient):
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
self.username = 'username'
|
|
||||||
self.password = 'password'
|
|
||||||
self.auth_url = 'auth_url'
|
|
||||||
self.tenant_id = 'tenant_id'
|
|
||||||
self.callstack = []
|
self.callstack = []
|
||||||
self.projectid = 'projectid'
|
self.auth = mock.Mock()
|
||||||
self.user = 'user'
|
self.session = mock.Mock()
|
||||||
self.region_name = 'region_name'
|
self.service_type = 'service_type'
|
||||||
|
self.service_name = None
|
||||||
|
self.endpoint_override = None
|
||||||
|
self.interface = None
|
||||||
|
self.region_name = None
|
||||||
|
self.version = None
|
||||||
|
self.api_version = kwargs.get('api_version')
|
||||||
|
self.auth.get_auth_ref.return_value.project_id = 'tenant_id'
|
||||||
# determines which endpoint to return in get_endpoint()
|
# determines which endpoint to return in get_endpoint()
|
||||||
# NOTE(augustina): this is a hacky workaround, ultimately
|
# NOTE(augustina): this is a hacky workaround, ultimately
|
||||||
# we need to fix our whole mocking architecture (fixtures?)
|
# we need to fix our whole mocking architecture (fixtures?)
|
||||||
@@ -83,17 +86,19 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
self.endpoint_type = kwargs['endpoint_type']
|
self.endpoint_type = kwargs['endpoint_type']
|
||||||
else:
|
else:
|
||||||
self.endpoint_type = 'endpoint_type'
|
self.endpoint_type = 'endpoint_type'
|
||||||
|
self.logger = mock.MagicMock()
|
||||||
|
|
||||||
self.service_type = 'service_type'
|
def get_endpoint(self, **kwargs):
|
||||||
self.service_name = 'service_name'
|
# check if endpoint matches expected format (eg, v2.1)
|
||||||
self.volume_service_name = 'volume_service_name'
|
if (hasattr(self, 'endpoint_type') and
|
||||||
self.timings = 'timings'
|
ENDPOINT_TYPE_RE.search(self.endpoint_type)):
|
||||||
self.bypass_url = 'bypass_url'
|
return "http://nova-api:8774/%s/" % self.endpoint_type
|
||||||
self.os_cache = 'os_cache'
|
else:
|
||||||
self.http_log_debug = 'http_log_debug'
|
return (
|
||||||
self.last_request_id = None
|
"http://nova-api:8774/v2.1/190a755eef2e4aac9f06aa6be9786385")
|
||||||
self.management_url = self.get_endpoint()
|
|
||||||
self.api_version = kwargs.get("api_version")
|
def request(self, url, method, **kwargs):
|
||||||
|
return self._cs_request(url, method, **kwargs)
|
||||||
|
|
||||||
def _cs_request(self, url, method, **kwargs):
|
def _cs_request(self, url, method, **kwargs):
|
||||||
# Check that certain things are called correctly
|
# Check that certain things are called correctly
|
||||||
@@ -156,15 +161,6 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
})
|
})
|
||||||
return r, body
|
return r, body
|
||||||
|
|
||||||
def get_endpoint(self, **kwargs):
|
|
||||||
# check if endpoint matches expected format (eg, v2.1)
|
|
||||||
if (hasattr(self, 'endpoint_type') and
|
|
||||||
ENDPOINT_TYPE_RE.search(self.endpoint_type)):
|
|
||||||
return "http://nova-api:8774/%s/" % self.endpoint_type
|
|
||||||
else:
|
|
||||||
return (
|
|
||||||
"http://nova-api:8774/v2.1/190a755eef2e4aac9f06aa6be9786385")
|
|
||||||
|
|
||||||
def get_versions(self):
|
def get_versions(self):
|
||||||
return (200, FAKE_RESPONSE_HEADERS, {
|
return (200, FAKE_RESPONSE_HEADERS, {
|
||||||
"versions": [
|
"versions": [
|
||||||
@@ -2357,35 +2353,3 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
'status': 'completed',
|
'status': 'completed',
|
||||||
'tag': 'tag',
|
'tag': 'tag',
|
||||||
'server_uuid': 'fake-uuid2'}]})
|
'server_uuid': 'fake-uuid2'}]})
|
||||||
|
|
||||||
|
|
||||||
class FakeSessionClient(fakes.FakeClient, client.Client):
|
|
||||||
|
|
||||||
def __init__(self, api_version, *args, **kwargs):
|
|
||||||
client.Client.__init__(self, username='username', password='password',
|
|
||||||
project_id='project_id', auth_url='auth_url',
|
|
||||||
extensions=kwargs.get('extensions'),
|
|
||||||
direct_use=False, api_version=api_version)
|
|
||||||
self.client = FakeSessionMockClient(api_version=api_version, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeSessionMockClient(base_client.SessionClient, FakeHTTPClient):
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
self.callstack = []
|
|
||||||
self.auth = mock.Mock()
|
|
||||||
self.session = mock.Mock()
|
|
||||||
self.session.get_endpoint.return_value = FakeHTTPClient.get_endpoint(
|
|
||||||
self)
|
|
||||||
self.service_type = 'service_type'
|
|
||||||
self.service_name = None
|
|
||||||
self.endpoint_override = None
|
|
||||||
self.interface = None
|
|
||||||
self.region_name = None
|
|
||||||
self.version = None
|
|
||||||
self.api_version = kwargs.get('api_version')
|
|
||||||
self.auth.get_auth_ref.return_value.project_id = 'tenant_id'
|
|
||||||
|
|
||||||
def request(self, url, method, **kwargs):
|
|
||||||
return self._cs_request(url, method, **kwargs)
|
|
||||||
|
@@ -1,392 +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 copy
|
|
||||||
import json
|
|
||||||
|
|
||||||
from keystoneauth1 import fixture
|
|
||||||
import mock
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from novaclient import client
|
|
||||||
from novaclient import exceptions
|
|
||||||
from novaclient.tests.unit import utils
|
|
||||||
|
|
||||||
|
|
||||||
def Client(*args, **kwargs):
|
|
||||||
return client.Client("2", *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AuthenticateAgainstKeystoneTests, self).setUp()
|
|
||||||
self.skipTest("This TestCase checks deprecated authentication "
|
|
||||||
"methods, which will be removed in separate patch.")
|
|
||||||
|
|
||||||
def get_token(self, **kwargs):
|
|
||||||
resp = fixture.V2Token(**kwargs)
|
|
||||||
resp.set_scope()
|
|
||||||
|
|
||||||
s = resp.add_service('compute')
|
|
||||||
s.add_endpoint('http://localhost:8774/v1.1', region='RegionOne')
|
|
||||||
|
|
||||||
return resp
|
|
||||||
|
|
||||||
def test_authenticate_success(self):
|
|
||||||
cs = Client("username", "password", project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL_V2, service_type='compute')
|
|
||||||
resp = self.get_token()
|
|
||||||
|
|
||||||
auth_response = utils.TestResponse({
|
|
||||||
"status_code": 200,
|
|
||||||
"text": json.dumps(resp),
|
|
||||||
})
|
|
||||||
|
|
||||||
mock_request = mock.Mock(return_value=(auth_response))
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", mock_request)
|
|
||||||
def test_auth_call():
|
|
||||||
cs.client.authenticate()
|
|
||||||
headers = {
|
|
||||||
'User-Agent': cs.client.USER_AGENT,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
}
|
|
||||||
body = {
|
|
||||||
'auth': {
|
|
||||||
'passwordCredentials': {
|
|
||||||
'username': cs.client.user,
|
|
||||||
'password': cs.client.password,
|
|
||||||
},
|
|
||||||
'tenantName': cs.client.projectid,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
token_url = cs.client.auth_url + "/tokens"
|
|
||||||
mock_request.assert_called_with(
|
|
||||||
"POST",
|
|
||||||
token_url,
|
|
||||||
headers=headers,
|
|
||||||
data=json.dumps(body),
|
|
||||||
allow_redirects=True,
|
|
||||||
**self.TEST_REQUEST_BASE)
|
|
||||||
|
|
||||||
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
|
|
||||||
public_url = endpoints[0]["publicURL"].rstrip('/')
|
|
||||||
self.assertEqual(cs.client.management_url, public_url)
|
|
||||||
token_id = resp["access"]["token"]["id"]
|
|
||||||
self.assertEqual(cs.client.auth_token, token_id)
|
|
||||||
|
|
||||||
test_auth_call()
|
|
||||||
|
|
||||||
def test_authenticate_failure(self):
|
|
||||||
cs = Client("username", "password", project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL_V2)
|
|
||||||
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
|
|
||||||
auth_response = utils.TestResponse({
|
|
||||||
"status_code": 401,
|
|
||||||
"text": json.dumps(resp),
|
|
||||||
})
|
|
||||||
|
|
||||||
mock_request = mock.Mock(return_value=(auth_response))
|
|
||||||
|
|
||||||
@mock.patch.object(requests.Session, "request", mock_request)
|
|
||||||
def test_auth_call():
|
|
||||||
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
|
|
||||||
|
|
||||||
test_auth_call()
|
|
||||||
|
|
||||||
def test_v1_auth_redirect(self):
|
|
||||||
cs = Client("username", "password", project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL_V1, service_type='compute')
|
|
||||||
dict_correct_response = self.get_token()
|
|
||||||
correct_response = json.dumps(dict_correct_response)
|
|
||||||
dict_responses = [
|
|
||||||
{"headers": {'location': 'http://127.0.0.1:5001'},
|
|
||||||
"status_code": 305,
|
|
||||||
"text": "Use proxy"},
|
|
||||||
# Configured on admin port, nova redirects to v2.0 port.
|
|
||||||
# When trying to connect on it, keystone auth succeed by v1.0
|
|
||||||
# protocol (through headers) but tokens are being returned in
|
|
||||||
# body (looks like keystone bug). Leaved for compatibility.
|
|
||||||
{"headers": {},
|
|
||||||
"status_code": 200,
|
|
||||||
"text": correct_response},
|
|
||||||
{"headers": {},
|
|
||||||
"status_code": 200,
|
|
||||||
"text": correct_response}
|
|
||||||
]
|
|
||||||
|
|
||||||
responses = [(utils.TestResponse(resp)) for resp in dict_responses]
|
|
||||||
|
|
||||||
def side_effect(*args, **kwargs):
|
|
||||||
return responses.pop(0)
|
|
||||||
|
|
||||||
mock_request = mock.Mock(side_effect=side_effect)
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", mock_request)
|
|
||||||
def test_auth_call():
|
|
||||||
cs.client.authenticate()
|
|
||||||
headers = {
|
|
||||||
'User-Agent': cs.client.USER_AGENT,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
}
|
|
||||||
body = {
|
|
||||||
'auth': {
|
|
||||||
'passwordCredentials': {
|
|
||||||
'username': cs.client.user,
|
|
||||||
'password': cs.client.password,
|
|
||||||
},
|
|
||||||
'tenantName': cs.client.projectid,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
token_url = cs.client.auth_url + "/tokens"
|
|
||||||
kwargs = copy.copy(self.TEST_REQUEST_BASE)
|
|
||||||
kwargs['headers'] = headers
|
|
||||||
kwargs['data'] = json.dumps(body)
|
|
||||||
mock_request.assert_called_with(
|
|
||||||
"POST",
|
|
||||||
token_url,
|
|
||||||
allow_redirects=True,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
resp = dict_correct_response
|
|
||||||
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
|
|
||||||
public_url = endpoints[0]["publicURL"].rstrip('/')
|
|
||||||
self.assertEqual(cs.client.management_url, public_url)
|
|
||||||
token_id = resp["access"]["token"]["id"]
|
|
||||||
self.assertEqual(cs.client.auth_token, token_id)
|
|
||||||
|
|
||||||
test_auth_call()
|
|
||||||
|
|
||||||
def test_v2_auth_redirect(self):
|
|
||||||
cs = Client("username", "password", project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL_V2, service_type='compute')
|
|
||||||
dict_correct_response = self.get_token()
|
|
||||||
correct_response = json.dumps(dict_correct_response)
|
|
||||||
dict_responses = [
|
|
||||||
{"headers": {'location': 'http://127.0.0.1:5001'},
|
|
||||||
"status_code": 305,
|
|
||||||
"text": "Use proxy"},
|
|
||||||
# Configured on admin port, nova redirects to v2.0 port.
|
|
||||||
# When trying to connect on it, keystone auth succeed by v1.0
|
|
||||||
# protocol (through headers) but tokens are being returned in
|
|
||||||
# body (looks like keystone bug). Leaved for compatibility.
|
|
||||||
{"headers": {},
|
|
||||||
"status_code": 200,
|
|
||||||
"text": correct_response},
|
|
||||||
{"headers": {},
|
|
||||||
"status_code": 200,
|
|
||||||
"text": correct_response}
|
|
||||||
]
|
|
||||||
|
|
||||||
responses = [(utils.TestResponse(resp)) for resp in dict_responses]
|
|
||||||
|
|
||||||
def side_effect(*args, **kwargs):
|
|
||||||
return responses.pop(0)
|
|
||||||
|
|
||||||
mock_request = mock.Mock(side_effect=side_effect)
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", mock_request)
|
|
||||||
def test_auth_call():
|
|
||||||
cs.client.authenticate()
|
|
||||||
headers = {
|
|
||||||
'User-Agent': cs.client.USER_AGENT,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
}
|
|
||||||
body = {
|
|
||||||
'auth': {
|
|
||||||
'passwordCredentials': {
|
|
||||||
'username': cs.client.user,
|
|
||||||
'password': cs.client.password,
|
|
||||||
},
|
|
||||||
'tenantName': cs.client.projectid,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
token_url = cs.client.auth_url + "/tokens"
|
|
||||||
kwargs = copy.copy(self.TEST_REQUEST_BASE)
|
|
||||||
kwargs['headers'] = headers
|
|
||||||
kwargs['data'] = json.dumps(body)
|
|
||||||
mock_request.assert_called_with(
|
|
||||||
"POST",
|
|
||||||
token_url,
|
|
||||||
allow_redirects=True,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
resp = dict_correct_response
|
|
||||||
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
|
|
||||||
public_url = endpoints[0]["publicURL"].rstrip('/')
|
|
||||||
self.assertEqual(cs.client.management_url, public_url)
|
|
||||||
token_id = resp["access"]["token"]["id"]
|
|
||||||
self.assertEqual(cs.client.auth_token, token_id)
|
|
||||||
|
|
||||||
test_auth_call()
|
|
||||||
|
|
||||||
def test_ambiguous_endpoints(self):
|
|
||||||
cs = Client("username", "password", project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL_V2, service_type='compute')
|
|
||||||
resp = self.get_token()
|
|
||||||
|
|
||||||
# duplicate existing service
|
|
||||||
s = resp.add_service('compute')
|
|
||||||
s.add_endpoint('http://localhost:8774/v1.1', region='RegionOne')
|
|
||||||
|
|
||||||
auth_response = utils.TestResponse({
|
|
||||||
"status_code": 200,
|
|
||||||
"text": json.dumps(resp),
|
|
||||||
})
|
|
||||||
|
|
||||||
mock_request = mock.Mock(return_value=(auth_response))
|
|
||||||
|
|
||||||
@mock.patch.object(requests.Session, "request", mock_request)
|
|
||||||
def test_auth_call():
|
|
||||||
self.assertRaises(exceptions.AmbiguousEndpoints,
|
|
||||||
cs.client.authenticate)
|
|
||||||
|
|
||||||
test_auth_call()
|
|
||||||
|
|
||||||
def test_authenticate_with_token_success(self):
|
|
||||||
cs = Client("username", None, project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL_V2, service_type='compute')
|
|
||||||
cs.client.auth_token = "FAKE_ID"
|
|
||||||
resp = self.get_token(token_id="FAKE_ID")
|
|
||||||
auth_response = utils.TestResponse({
|
|
||||||
"status_code": 200,
|
|
||||||
"text": json.dumps(resp),
|
|
||||||
})
|
|
||||||
|
|
||||||
mock_request = mock.Mock(return_value=(auth_response))
|
|
||||||
|
|
||||||
with mock.patch.object(requests, "request", mock_request):
|
|
||||||
cs.client.authenticate()
|
|
||||||
headers = {
|
|
||||||
'User-Agent': cs.client.USER_AGENT,
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Accept': 'application/json',
|
|
||||||
}
|
|
||||||
body = {
|
|
||||||
'auth': {
|
|
||||||
'token': {
|
|
||||||
'id': cs.client.auth_token,
|
|
||||||
},
|
|
||||||
'tenantName': cs.client.projectid,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
token_url = cs.client.auth_url + "/tokens"
|
|
||||||
mock_request.assert_called_with(
|
|
||||||
"POST",
|
|
||||||
token_url,
|
|
||||||
headers=headers,
|
|
||||||
data=json.dumps(body),
|
|
||||||
allow_redirects=True,
|
|
||||||
**self.TEST_REQUEST_BASE)
|
|
||||||
|
|
||||||
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
|
|
||||||
public_url = endpoints[0]["publicURL"].rstrip('/')
|
|
||||||
self.assertEqual(cs.client.management_url, public_url)
|
|
||||||
token_id = resp["access"]["token"]["id"]
|
|
||||||
self.assertEqual(cs.client.auth_token, token_id)
|
|
||||||
|
|
||||||
def test_authenticate_with_token_failure(self):
|
|
||||||
cs = Client("username", None, project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL_V2)
|
|
||||||
cs.client.auth_token = "FAKE_ID"
|
|
||||||
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
|
|
||||||
auth_response = utils.TestResponse({
|
|
||||||
"status_code": 401,
|
|
||||||
"text": json.dumps(resp),
|
|
||||||
})
|
|
||||||
|
|
||||||
mock_request = mock.Mock(return_value=(auth_response))
|
|
||||||
|
|
||||||
with mock.patch.object(requests.Session, "request", mock_request):
|
|
||||||
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationTests(utils.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(AuthenticationTests, self).setUp()
|
|
||||||
self.skipTest("This TestCase checks deprecated authentication "
|
|
||||||
"methods, which will be removed in separate patch.")
|
|
||||||
|
|
||||||
def test_authenticate_success(self):
|
|
||||||
cs = Client("username", "password", project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL)
|
|
||||||
management_url = 'https://localhost/v1.1/443470'
|
|
||||||
auth_response = utils.TestResponse({
|
|
||||||
'status_code': 204,
|
|
||||||
'headers': {
|
|
||||||
'x-server-management-url': management_url,
|
|
||||||
'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
mock_request = mock.Mock(return_value=(auth_response))
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", mock_request)
|
|
||||||
def test_auth_call():
|
|
||||||
cs.client.authenticate()
|
|
||||||
headers = {
|
|
||||||
'Accept': 'application/json',
|
|
||||||
'X-Auth-User': 'username',
|
|
||||||
'X-Auth-Key': 'password',
|
|
||||||
'X-Auth-Project-Id': 'project_id',
|
|
||||||
'User-Agent': cs.client.USER_AGENT
|
|
||||||
}
|
|
||||||
mock_request.assert_called_with(
|
|
||||||
"GET",
|
|
||||||
cs.client.auth_url,
|
|
||||||
headers=headers,
|
|
||||||
**self.TEST_REQUEST_BASE)
|
|
||||||
|
|
||||||
self.assertEqual(cs.client.management_url,
|
|
||||||
auth_response.headers['x-server-management-url'])
|
|
||||||
self.assertEqual(cs.client.auth_token,
|
|
||||||
auth_response.headers['x-auth-token'])
|
|
||||||
|
|
||||||
test_auth_call()
|
|
||||||
|
|
||||||
def test_authenticate_failure(self):
|
|
||||||
cs = Client("username", "password", project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL)
|
|
||||||
auth_response = utils.TestResponse({'status_code': 401})
|
|
||||||
mock_request = mock.Mock(return_value=(auth_response))
|
|
||||||
|
|
||||||
@mock.patch.object(requests, "request", mock_request)
|
|
||||||
def test_auth_call():
|
|
||||||
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
|
|
||||||
|
|
||||||
test_auth_call()
|
|
||||||
|
|
||||||
def test_auth_automatic(self):
|
|
||||||
cs = Client("username", "password", project_name="project_id",
|
|
||||||
auth_url=utils.AUTH_URL, direct_use=False)
|
|
||||||
http_client = cs.client
|
|
||||||
http_client.management_url = ''
|
|
||||||
http_client.get_service_url = mock.Mock(return_value='')
|
|
||||||
mock_request = mock.Mock(return_value=(None, None))
|
|
||||||
|
|
||||||
@mock.patch.object(http_client, 'request', mock_request)
|
|
||||||
@mock.patch.object(http_client, 'authenticate')
|
|
||||||
def test_auth_call(m):
|
|
||||||
http_client.get('/')
|
|
||||||
self.assertTrue(m.called)
|
|
||||||
self.assertTrue(mock_request.called)
|
|
||||||
|
|
||||||
test_auth_call()
|
|
@@ -75,7 +75,6 @@ class ShellTest(utils.TestCase):
|
|||||||
self.useFixture(fixtures.EnvironmentVariable(var,
|
self.useFixture(fixtures.EnvironmentVariable(var,
|
||||||
self.FAKE_ENV[var]))
|
self.FAKE_ENV[var]))
|
||||||
self.shell = self.useFixture(ShellFixture()).shell
|
self.shell = self.useFixture(ShellFixture()).shell
|
||||||
|
|
||||||
self.useFixture(fixtures.MonkeyPatch(
|
self.useFixture(fixtures.MonkeyPatch(
|
||||||
'novaclient.client.Client', fakes.FakeClient))
|
'novaclient.client.Client', fakes.FakeClient))
|
||||||
|
|
||||||
@@ -920,7 +919,7 @@ class ShellTest(utils.TestCase):
|
|||||||
|
|
||||||
@mock.patch('novaclient.v2.client.Client.has_neutron', return_value=False)
|
@mock.patch('novaclient.v2.client.Client.has_neutron', return_value=False)
|
||||||
@mock.patch(
|
@mock.patch(
|
||||||
'novaclient.tests.unit.v2.fakes.FakeHTTPClient.get_os_networks')
|
'novaclient.tests.unit.v2.fakes.FakeSessionClient.get_os_networks')
|
||||||
def test_boot_nics_net_name_multiple_matches(self, mock_networks_list,
|
def test_boot_nics_net_name_multiple_matches(self, mock_networks_list,
|
||||||
has_neutron):
|
has_neutron):
|
||||||
mock_networks_list.return_value = (200, {}, {
|
mock_networks_list.return_value = (200, {}, {
|
||||||
@@ -3251,16 +3250,6 @@ class ShellTest(utils.TestCase):
|
|||||||
self.assert_called('GET', '/servers/detail?not-tags-any=tag1%2Ctag2')
|
self.assert_called('GET', '/servers/detail?not-tags-any=tag1%2Ctag2')
|
||||||
|
|
||||||
|
|
||||||
class ShellWithSessionClientTest(ShellTest):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
"""Run before each test."""
|
|
||||||
super(ShellWithSessionClientTest, self).setUp()
|
|
||||||
|
|
||||||
self.useFixture(fixtures.MonkeyPatch(
|
|
||||||
'novaclient.client.Client', fakes.FakeSessionClient))
|
|
||||||
|
|
||||||
|
|
||||||
class GetSecgroupTest(utils.TestCase):
|
class GetSecgroupTest(utils.TestCase):
|
||||||
def test_with_integer(self):
|
def test_with_integer(self):
|
||||||
cs = mock.Mock(**{
|
cs = mock.Mock(**{
|
||||||
|
@@ -15,7 +15,6 @@
|
|||||||
import mock
|
import mock
|
||||||
|
|
||||||
from novaclient import api_versions
|
from novaclient import api_versions
|
||||||
from novaclient import base
|
|
||||||
from novaclient import exceptions as exc
|
from novaclient import exceptions as exc
|
||||||
from novaclient.tests.unit import utils
|
from novaclient.tests.unit import utils
|
||||||
from novaclient.tests.unit.v2 import fakes
|
from novaclient.tests.unit.v2 import fakes
|
||||||
@@ -28,73 +27,24 @@ class VersionsTest(utils.TestCase):
|
|||||||
self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"))
|
self.cs = fakes.FakeClient(api_versions.APIVersion("2.0"))
|
||||||
self.service_type = versions.Version
|
self.service_type = versions.Version
|
||||||
|
|
||||||
@mock.patch.object(versions.VersionManager, '_is_session_client',
|
def test_list_services(self):
|
||||||
return_value=False)
|
|
||||||
def test_list_services_with_http_client(self, mock_is_session_client):
|
|
||||||
vl = self.cs.versions.list()
|
|
||||||
self.assert_request_id(vl, fakes.FAKE_REQUEST_ID_LIST)
|
|
||||||
self.cs.assert_called('GET', None)
|
|
||||||
|
|
||||||
@mock.patch.object(versions.VersionManager, '_is_session_client',
|
|
||||||
return_value=True)
|
|
||||||
def test_list_services_with_session_client(self, mock_is_session_client):
|
|
||||||
vl = self.cs.versions.list()
|
vl = self.cs.versions.list()
|
||||||
self.assert_request_id(vl, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(vl, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.cs.assert_called('GET', 'http://nova-api:8774/')
|
self.cs.assert_called('GET', 'http://nova-api:8774/')
|
||||||
|
|
||||||
@mock.patch.object(versions.VersionManager, '_is_session_client',
|
def test_get_current(self):
|
||||||
return_value=False)
|
|
||||||
@mock.patch.object(versions.VersionManager, 'list')
|
|
||||||
def test_get_current_with_http_client(self, mock_list,
|
|
||||||
mock_is_session_client):
|
|
||||||
headers = {'x-openstack-request-id': fakes.FAKE_REQUEST_ID}
|
|
||||||
resp = utils.TestResponse({"headers": headers})
|
|
||||||
current_version = versions.Version(
|
|
||||||
None, {"links": [{"href": "http://nova-api:8774/v2.1"}]},
|
|
||||||
loaded=True)
|
|
||||||
|
|
||||||
all_versions = [
|
|
||||||
versions.Version(
|
|
||||||
None, {"links": [{"href": "http://url/v1"}]}, loaded=True),
|
|
||||||
versions.Version(
|
|
||||||
None, {"links": [{"href": "http://url/v2"}]}, loaded=True),
|
|
||||||
versions.Version(
|
|
||||||
None, {"links": [{"href": "http://url/v3"}]}, loaded=True),
|
|
||||||
current_version,
|
|
||||||
versions.Version(
|
|
||||||
None, {"links": [{"href": "http://url/v21"}]}, loaded=True)]
|
|
||||||
mock_list.return_value = base.ListWithMeta(all_versions, resp)
|
|
||||||
v = self.cs.versions.get_current()
|
|
||||||
self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST)
|
|
||||||
self.assertEqual(current_version, v)
|
|
||||||
|
|
||||||
@mock.patch.object(versions.VersionManager, '_is_session_client',
|
|
||||||
return_value=True)
|
|
||||||
def test_get_current_with_session_client(self, mock_is_session_client):
|
|
||||||
self.cs.callback = []
|
self.cs.callback = []
|
||||||
v = self.cs.versions.get_current()
|
v = self.cs.versions.get_current()
|
||||||
self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(v, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.cs.assert_called('GET', 'http://nova-api:8774/v2.1/')
|
self.cs.assert_called('GET', 'http://nova-api:8774/v2.1/')
|
||||||
|
|
||||||
@mock.patch.object(versions.VersionManager, '_is_session_client',
|
|
||||||
return_value=True)
|
|
||||||
@mock.patch.object(versions.VersionManager, '_get',
|
@mock.patch.object(versions.VersionManager, '_get',
|
||||||
side_effect=exc.Unauthorized("401 RAX"))
|
side_effect=exc.Unauthorized("401 RAX"))
|
||||||
def test_get_current_with_rax_workaround(self, session, get):
|
def test_get_current_with_rax_workaround(self, get):
|
||||||
self.cs.callback = []
|
self.cs.callback = []
|
||||||
self.assertIsNone(self.cs.versions.get_current())
|
self.assertIsNone(self.cs.versions.get_current())
|
||||||
|
|
||||||
@mock.patch.object(versions.VersionManager, '_is_session_client',
|
def test_get_endpoint_without_project_id(self):
|
||||||
return_value=False)
|
|
||||||
@mock.patch.object(versions.VersionManager, '_list',
|
|
||||||
side_effect=exc.Unauthorized("401 RAX"))
|
|
||||||
def test_get_current_with_rax_auth_plugin_workaround(self, session, _list):
|
|
||||||
self.cs.callback = []
|
|
||||||
self.assertIsNone(self.cs.versions.get_current())
|
|
||||||
|
|
||||||
@mock.patch.object(versions.VersionManager, '_is_session_client',
|
|
||||||
return_value=True)
|
|
||||||
def test_get_endpoint_without_project_id(self, mock_is_session_client):
|
|
||||||
# create a fake client such that get_endpoint()
|
# create a fake client such that get_endpoint()
|
||||||
# doesn't return uuid in url
|
# doesn't return uuid in url
|
||||||
endpoint_type = 'v2.1'
|
endpoint_type = 'v2.1'
|
||||||
@@ -106,15 +56,11 @@ class VersionsTest(utils.TestCase):
|
|||||||
self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.assertEqual(result.manager.api.client.endpoint_type,
|
self.assertEqual(result.manager.api.client.endpoint_type,
|
||||||
endpoint_type, "Check endpoint_type was set")
|
endpoint_type, "Check endpoint_type was set")
|
||||||
self.assertEqual(result.manager.api.client.management_url,
|
|
||||||
expected_endpoint, "Check endpoint without uuid")
|
|
||||||
|
|
||||||
# check that the full request works as expected
|
# check that the full request works as expected
|
||||||
cs_2_1.assert_called('GET', 'http://nova-api:8774/v2.1/')
|
cs_2_1.assert_called('GET', expected_endpoint)
|
||||||
|
|
||||||
@mock.patch.object(versions.VersionManager, '_is_session_client',
|
def test_v2_get_endpoint_without_project_id(self):
|
||||||
return_value=True)
|
|
||||||
def test_v2_get_endpoint_without_project_id(self, mock_is_session_client):
|
|
||||||
# create a fake client such that get_endpoint()
|
# create a fake client such that get_endpoint()
|
||||||
# doesn't return uuid in url
|
# doesn't return uuid in url
|
||||||
endpoint_type = 'v2'
|
endpoint_type = 'v2'
|
||||||
@@ -126,8 +72,6 @@ class VersionsTest(utils.TestCase):
|
|||||||
self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST)
|
self.assert_request_id(result, fakes.FAKE_REQUEST_ID_LIST)
|
||||||
self.assertEqual(result.manager.api.client.endpoint_type,
|
self.assertEqual(result.manager.api.client.endpoint_type,
|
||||||
endpoint_type, "Check v2 endpoint_type was set")
|
endpoint_type, "Check v2 endpoint_type was set")
|
||||||
self.assertEqual(result.manager.api.client.management_url,
|
|
||||||
expected_endpoint, "Check v2 endpoint without uuid")
|
|
||||||
|
|
||||||
# check that the full request works as expected
|
# check that the full request works as expected
|
||||||
cs_2.assert_called('GET', 'http://nova-api:8774/v2/')
|
cs_2.assert_called('GET', expected_endpoint)
|
||||||
|
@@ -19,7 +19,6 @@ version interface
|
|||||||
from six.moves import urllib
|
from six.moves import urllib
|
||||||
|
|
||||||
from novaclient import base
|
from novaclient import base
|
||||||
from novaclient import client
|
|
||||||
from novaclient import exceptions as exc
|
from novaclient import exceptions as exc
|
||||||
|
|
||||||
|
|
||||||
@@ -34,50 +33,37 @@ class Version(base.Resource):
|
|||||||
class VersionManager(base.ManagerWithFind):
|
class VersionManager(base.ManagerWithFind):
|
||||||
resource_class = Version
|
resource_class = Version
|
||||||
|
|
||||||
def _is_session_client(self):
|
|
||||||
return isinstance(self.api.client, client.SessionClient)
|
|
||||||
|
|
||||||
def _get_current(self):
|
def _get_current(self):
|
||||||
"""Returns info about current version."""
|
"""Returns info about current version."""
|
||||||
|
|
||||||
# TODO(sdague): we've now got to make up to 3 HTTP requests to
|
# TODO(sdague): we've now got to make up to 3 HTTP requests to
|
||||||
# determine what version we are running, due to differences in
|
# determine what version we are running, due to differences in
|
||||||
# deployments and versions. We really need to cache the
|
# deployments and versions. We really need to cache the
|
||||||
# results of this per endpoint and keep the results of it for
|
# results of this per endpoint and keep the results of it for
|
||||||
# some reasonable TTL (like 24 hours) to reduce our round trip
|
# some reasonable TTL (like 24 hours) to reduce our round trip
|
||||||
# traffic.
|
# traffic.
|
||||||
if self._is_session_client():
|
try:
|
||||||
try:
|
# Assume that the value of get_endpoint() is something
|
||||||
# Assume that the value of get_endpoint() is something
|
# we can get the version of. This is a 404 for Nova <
|
||||||
# we can get the version of. This is a 404 for Nova <
|
# Mitaka if the service catalog contains project_id.
|
||||||
# Mitaka if the service catalog contains project_id.
|
#
|
||||||
#
|
# TODO(sdague): add microversion for when this will
|
||||||
# TODO(sdague): add microversion for when this will
|
# change
|
||||||
# change
|
url = "%s" % self.api.client.get_endpoint()
|
||||||
url = "%s" % self.api.client.get_endpoint()
|
return self._get(url, "version")
|
||||||
return self._get(url, "version")
|
except exc.NotFound:
|
||||||
except exc.NotFound:
|
# If that's a 404, we can instead try hacking together
|
||||||
# If that's a 404, we can instead try hacking together
|
# an endpoint root url by chopping off the last 2 /s.
|
||||||
# an endpoint root url by chopping off the last 2 /s.
|
# This is kind of gross, but we've had this baked in
|
||||||
# This is kind of gross, but we've had this baked in
|
# so long people got used to this hard coding.
|
||||||
# so long people got used to this hard coding.
|
#
|
||||||
#
|
# NOTE(sdague): many service providers don't really
|
||||||
# NOTE(sdague): many service providers don't really
|
# implement GET / in the expected way, if we do a GET
|
||||||
# implement GET / in the expected way, if we do a GET
|
# /v2 that's actually a 300 redirect to
|
||||||
# /v2 that's actually a 300 redirect to
|
# /v2/... because of how paste works. So adding the
|
||||||
# /v2/... because of how paste works. So adding the
|
# end slash is really important.
|
||||||
# end slash is really important.
|
url = "%s/" % url.rsplit("/", 1)[0]
|
||||||
url = "%s/" % url.rsplit("/", 1)[0]
|
return self._get(url, "version")
|
||||||
return self._get(url, "version")
|
|
||||||
else:
|
|
||||||
# NOTE(andreykurilin): HTTPClient doesn't have ability to send get
|
|
||||||
# request without token in the url, so `self._get` doesn't work.
|
|
||||||
all_versions = self.list()
|
|
||||||
url = self.client.management_url.rsplit("/", 1)[0]
|
|
||||||
for version in all_versions:
|
|
||||||
for link in version.links:
|
|
||||||
if link["href"].rstrip('/') == url:
|
|
||||||
version.append_request_ids(all_versions.request_ids)
|
|
||||||
return version
|
|
||||||
|
|
||||||
def get_current(self):
|
def get_current(self):
|
||||||
try:
|
try:
|
||||||
@@ -92,13 +78,11 @@ class VersionManager(base.ManagerWithFind):
|
|||||||
def list(self):
|
def list(self):
|
||||||
"""List all versions."""
|
"""List all versions."""
|
||||||
|
|
||||||
version_url = None
|
# NOTE: "list versions" API needs to be accessed without base
|
||||||
if self._is_session_client():
|
# URI (like "v2/{project-id}"), so here should be a scheme("http",
|
||||||
# NOTE: "list versions" API needs to be accessed without base
|
# etc.) and a hostname.
|
||||||
# URI (like "v2/{project-id}"), so here should be a scheme("http",
|
endpoint = self.api.client.get_endpoint()
|
||||||
# etc.) and a hostname.
|
url = urllib.parse.urlparse(endpoint)
|
||||||
endpoint = self.api.client.get_endpoint()
|
version_url = '%s://%s/' % (url.scheme, url.netloc)
|
||||||
url = urllib.parse.urlparse(endpoint)
|
|
||||||
version_url = '%s://%s/' % (url.scheme, url.netloc)
|
|
||||||
|
|
||||||
return self._list(version_url, "versions")
|
return self._list(version_url, "versions")
|
||||||
|
Reference in New Issue
Block a user