summaryrefslogtreecommitdiff
path: root/novaclient/client.py
diff options
context:
space:
mode:
authorAndrey Kurilin <akurilin@mirantis.com>2016-12-08 19:09:48 +0200
committerAndrey Kurilin <akurilin@mirantis.com>2016-12-16 12:56:18 +0000
commit2e5576dafc21906a95196054f85c80b57c48663a (patch)
tree786bec0f29530d2b55f0fd3566af772eba08997b /novaclient/client.py
parent8409e006c5f362922baae9470f14c12e0443dd70 (diff)
downloadpython-novaclient-2e5576dafc21906a95196054f85c80b57c48663a.tar.gz
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
Diffstat (limited to 'novaclient/client.py')
-rw-r--r--novaclient/client.py586
1 files changed, 0 insertions, 586 deletions
diff --git a/novaclient/client.py b/novaclient/client.py
index 7780c9a6..ec07c38f 100644
--- a/novaclient/client.py
+++ b/novaclient/client.py
@@ -20,35 +20,20 @@
OpenStack Client interface. Handles the REST calls and responses.
"""
-import copy
-import functools
-import hashlib
import itertools
-import logging
import pkgutil
-import re
import warnings
from keystoneauth1 import adapter
from keystoneauth1 import identity
from keystoneauth1 import session as ksession
from oslo_utils import importutils
-from oslo_utils import netutils
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 exceptions
from novaclient import extension as ext
from novaclient.i18n import _, _LW
-from novaclient import service_catalog
from novaclient import utils
@@ -58,19 +43,6 @@ from novaclient import utils
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):
request_id = (resp.headers.get('x-openstack-request-id') or
resp.headers.get('x-compute-request-id'))
@@ -138,564 +110,6 @@ class SessionClient(adapter.LegacyJsonAdapter):
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,
auth=None,
auth_token=None,