Valence exception messages are not displayed properly. This commit is to fix the same. Change-Id: If4da51f736d28e60a3cfa78094b2e60bedecdd83 Closes-Bug:#1715580
177 lines
6.1 KiB
Python
177 lines
6.1 KiB
Python
# Copyright 2017 99cloud, 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 functools
|
|
import logging
|
|
import requests
|
|
import time
|
|
|
|
import six
|
|
from six.moves import http_client
|
|
import six.moves.urllib.parse as urlparse
|
|
|
|
|
|
from oslo_serialization import jsonutils
|
|
from oslo_utils import strutils
|
|
|
|
from valenceclient.common.i18n import _
|
|
from valenceclient.common.i18n import _LE
|
|
from valenceclient import exc
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
USER_AGENT = 'python-valenceclient'
|
|
|
|
API_VERSION = '/v1'
|
|
DEFAULT_VERSION = 1
|
|
|
|
DEFAULT_MAX_RETRIES = 5
|
|
DEFAULT_RETRY_INTERVAL = 2
|
|
|
|
|
|
def _trim_endpoint_api_version(url):
|
|
"""Trim API version and trailing slash from endpoint."""
|
|
|
|
return url.rstrip('/').rstip(API_VERSION)
|
|
|
|
|
|
def _extract_error_json(body):
|
|
return jsonutils.loads(body)
|
|
|
|
|
|
def with_retries(func):
|
|
"""Wrapper for _http_request adding support for retries."""
|
|
|
|
@functools.wraps(func)
|
|
def wrapper(self, url, method, **kwargs):
|
|
if self.conflict_max_retries is None:
|
|
self.conflict_max_retries = DEFAULT_MAX_RETRIES
|
|
if self.conflict_retry_interval is None:
|
|
self.conflict_retry_interval = DEFAULT_RETRY_INTERVAL
|
|
|
|
num_attempts = self.conflict_max_retries + 1
|
|
for attempt in range(1, num_attempts + 1):
|
|
try:
|
|
return func(url, method, **kwargs)
|
|
except Exception as error:
|
|
msg = (_LE("Error contacting Valence server: %(error)s."
|
|
"Attempt %(attempt)d of %(total)d") %
|
|
{'attempt': attempt,
|
|
'total': num_attempts,
|
|
'error': error})
|
|
if attempt == num_attempts:
|
|
LOG.error(msg)
|
|
else:
|
|
LOG.debug(msg)
|
|
time.sleep(self.conflict_retry_interval)
|
|
return wrapper
|
|
|
|
|
|
class HTTPClient(object):
|
|
|
|
def __init__(self, **kwargs):
|
|
self.valence_api_version = kwargs.get('valence_api_version',
|
|
DEFAULT_VERSION)
|
|
self.valence_url = kwargs.get('valence_url')
|
|
self.session = requests.Session()
|
|
|
|
def log_curl_request(self, method, url, kwargs):
|
|
curl = ['curl -i -X %s' % method]
|
|
|
|
for (key, value) in kwargs['headers'].items():
|
|
header = "-H '%s: %s'" % (key, value)
|
|
curl.append(header)
|
|
|
|
if 'body' in kwargs:
|
|
body = strutils.mask_password(kwargs['body'])
|
|
curl.append("-d '%s'" % body)
|
|
|
|
curl.append(urlparse.urljoin('v1', self.valence_url))
|
|
LOG.debug(" ".join(curl))
|
|
|
|
@staticmethod
|
|
def log_http_response(resp, body=None):
|
|
status = (resp.raw.version / 10.0, resp.status_code, resp.reason)
|
|
dump = ['\nHTTP/%.1f %s %s' % status]
|
|
dump.extend(['%s: %s' % (k, v) for k, v in resp.headers.items()])
|
|
dump.append('')
|
|
if body:
|
|
body = strutils.mask_password(body)
|
|
dump.extend([body, ''])
|
|
LOG.debug('\n'.join(dump))
|
|
|
|
def _make_connection_url(self, url):
|
|
return urlparse.urljoin(self.valence_url, url)
|
|
|
|
def _http_request(self, conn_url, method, **kwargs):
|
|
"""Send an http request with the specified characteristics
|
|
|
|
Wrapper around request.Session.request to handle tasks such as
|
|
setting headers and error handling.
|
|
"""
|
|
kwargs['headers'] = kwargs.get('headers', {})
|
|
kwargs['headers'].setdefault('User-agent', USER_AGENT)
|
|
|
|
self.log_curl_request(method, conn_url, kwargs)
|
|
|
|
body = kwargs.pop('body', None)
|
|
headers = kwargs.pop('headers')
|
|
|
|
conn_url = self._make_connection_url(conn_url)
|
|
try:
|
|
resp = self.session.request(method, conn_url, headers=headers,
|
|
data=body, json=kwargs)
|
|
except requests.exceptions.RequestException as e:
|
|
msg = (_("Error has occured while handling request for "
|
|
"%(url)s: %(e)s") % dict(url=conn_url, e=e))
|
|
if isinstance(e, ValueError):
|
|
raise exc.ValidationError(msg)
|
|
raise exc.ConnectionRefuse(msg)
|
|
|
|
self.log_http_response(resp, resp.text)
|
|
body_iter = six.StringIO(resp.text)
|
|
if resp.status_code >= http_client.BAD_REQUEST:
|
|
error_json = _extract_error_json(resp.text)
|
|
raise exc.from_response(resp, error_json, method, conn_url)
|
|
elif resp.status_code in (http_client.FOUND,
|
|
http_client.USE_PROXY):
|
|
return self._http_request(resp['location'], method, **kwargs)
|
|
|
|
return resp, body_iter
|
|
|
|
def json_request(self, method, conn_url, **kwargs):
|
|
kwargs.setdefault('headers', {})
|
|
kwargs['headers'].setdefault('Content-Type', 'application/json')
|
|
kwargs['headers'].setdefault('Accept', 'application/json')
|
|
if 'body' in kwargs:
|
|
kwargs['body'] = jsonutils.dump_as_bytes(kwargs['body'])
|
|
|
|
resp, body_iter = self._http_request(conn_url, method, **kwargs)
|
|
content_type = resp.headers.get('Content-Type')
|
|
if(resp.status_code in (http_client.NO_CONTENT,
|
|
http_client.RESET_CONTENT)
|
|
or content_type is None):
|
|
return resp, list()
|
|
|
|
if 'application/json' in content_type:
|
|
body = ''.join([chunk for chunk in body_iter])
|
|
try:
|
|
body = jsonutils.loads(body)
|
|
except ValueError:
|
|
LOG.error(_LE('Could not decode response body as JSON'))
|
|
else:
|
|
body = None
|
|
return resp, body
|