Make glanceclient accept a session object
To make this work we create a different HTTPClient that extends the basic keystoneclient Adapter. The Adapter is a standard set of parameters that all clients should know how to use like region_name and user_agent. We extend this with the glance specific response manipulation like loading and sending iterables. Implements: bp session-objects Change-Id: Ie8eb4bbf7d1a037099a6d4b272cab70525fbfc85
This commit is contained in:
parent
db6420b447
commit
5ce9c7dc96
@ -18,31 +18,44 @@ import warnings
|
|||||||
from glanceclient.common import utils
|
from glanceclient.common import utils
|
||||||
|
|
||||||
|
|
||||||
def Client(version=None, endpoint=None, *args, **kwargs):
|
def Client(version=None, endpoint=None, session=None, *args, **kwargs):
|
||||||
"""Client for the OpenStack Images API.
|
"""Client for the OpenStack Images API.
|
||||||
|
|
||||||
Generic client for the OpenStack Images API. See version classes
|
Generic client for the OpenStack Images API. See version classes
|
||||||
for specific details.
|
for specific details.
|
||||||
|
|
||||||
:param string version: The version of API to use. Note this is
|
:param string version: The version of API to use.
|
||||||
deprecated and should be passed as part of the URL
|
:param session: A keystoneclient session that should be used for transport.
|
||||||
(http://$HOST:$PORT/v$VERSION_NUMBER).
|
:type session: keystoneclient.session.Session
|
||||||
"""
|
"""
|
||||||
if version is not None:
|
# FIXME(jamielennox): Add a deprecation warning if no session is passed.
|
||||||
warnings.warn(("`version` keyword is being deprecated. Please pass the"
|
# Leaving it as an option until we can ensure nothing break when we switch.
|
||||||
" version as part of the URL. "
|
if session:
|
||||||
"http://$HOST:$PORT/v$VERSION_NUMBER"),
|
if endpoint:
|
||||||
DeprecationWarning)
|
kwargs.setdefault('endpoint_override', endpoint)
|
||||||
|
|
||||||
endpoint, url_version = utils.strip_version(endpoint)
|
if not version:
|
||||||
|
__, version = utils.strip_version(endpoint)
|
||||||
|
|
||||||
if not url_version and not version:
|
if not version:
|
||||||
msg = ("Please provide either the version or an url with the form "
|
msg = ("You must provide a client version when using session")
|
||||||
"http://$HOST:$PORT/v$VERSION_NUMBER")
|
raise RuntimeError(msg)
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
version = int(version or url_version)
|
else:
|
||||||
|
if version is not None:
|
||||||
|
warnings.warn(("`version` keyword is being deprecated. Please pass"
|
||||||
|
" the version as part of the URL. "
|
||||||
|
"http://$HOST:$PORT/v$VERSION_NUMBER"),
|
||||||
|
DeprecationWarning)
|
||||||
|
|
||||||
module = utils.import_versioned_module(version, 'client')
|
endpoint, url_version = utils.strip_version(endpoint)
|
||||||
|
version = version or url_version
|
||||||
|
|
||||||
|
if not version:
|
||||||
|
msg = ("Please provide either the version or an url with the form "
|
||||||
|
"http://$HOST:$PORT/v$VERSION_NUMBER")
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
|
module = utils.import_versioned_module(int(version), 'client')
|
||||||
client_class = getattr(module, 'Client')
|
client_class = getattr(module, 'Client')
|
||||||
return client_class(endpoint, *args, **kwargs)
|
return client_class(endpoint, *args, session=session, **kwargs)
|
||||||
|
@ -17,6 +17,8 @@ import copy
|
|||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
|
from keystoneclient import adapter
|
||||||
|
from keystoneclient import exceptions as ksc_exc
|
||||||
from oslo_utils import importutils
|
from oslo_utils import importutils
|
||||||
from oslo_utils import netutils
|
from oslo_utils import netutils
|
||||||
import requests
|
import requests
|
||||||
@ -50,7 +52,71 @@ USER_AGENT = 'python-glanceclient'
|
|||||||
CHUNKSIZE = 1024 * 64 # 64kB
|
CHUNKSIZE = 1024 * 64 # 64kB
|
||||||
|
|
||||||
|
|
||||||
class HTTPClient(object):
|
class _BaseHTTPClient(object):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _chunk_body(body):
|
||||||
|
chunk = body
|
||||||
|
while chunk:
|
||||||
|
chunk = body.read(CHUNKSIZE)
|
||||||
|
if chunk == '':
|
||||||
|
break
|
||||||
|
yield chunk
|
||||||
|
|
||||||
|
def _set_common_request_kwargs(self, headers, kwargs):
|
||||||
|
"""Handle the common parameters used to send the request."""
|
||||||
|
|
||||||
|
# Default Content-Type is octet-stream
|
||||||
|
content_type = headers.get('Content-Type', 'application/octet-stream')
|
||||||
|
|
||||||
|
# NOTE(jamielennox): remove this later. Managers should pass json= if
|
||||||
|
# they want to send json data.
|
||||||
|
data = kwargs.pop("data", None)
|
||||||
|
if data is not None and not isinstance(data, six.string_types):
|
||||||
|
try:
|
||||||
|
data = json.dumps(data)
|
||||||
|
content_type = 'application/json'
|
||||||
|
except TypeError:
|
||||||
|
# Here we assume it's
|
||||||
|
# a file-like object
|
||||||
|
# and we'll chunk it
|
||||||
|
data = self._chunk_body(data)
|
||||||
|
|
||||||
|
headers['Content-Type'] = content_type
|
||||||
|
kwargs['stream'] = content_type == 'application/octet-stream'
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _handle_response(self, resp):
|
||||||
|
if not resp.ok:
|
||||||
|
LOG.debug("Request returned failure status %s." % resp.status_code)
|
||||||
|
raise exc.from_response(resp, resp.content)
|
||||||
|
elif resp.status_code == requests.codes.MULTIPLE_CHOICES:
|
||||||
|
raise exc.from_response(resp)
|
||||||
|
|
||||||
|
content_type = resp.headers.get('Content-Type')
|
||||||
|
|
||||||
|
# Read body into string if it isn't obviously image data
|
||||||
|
if content_type == 'application/octet-stream':
|
||||||
|
# Do not read all response in memory when downloading an image.
|
||||||
|
body_iter = _close_after_stream(resp, CHUNKSIZE)
|
||||||
|
else:
|
||||||
|
content = resp.text
|
||||||
|
if content_type and content_type.startswith('application/json'):
|
||||||
|
# Let's use requests json method, it should take care of
|
||||||
|
# response encoding
|
||||||
|
body_iter = resp.json()
|
||||||
|
else:
|
||||||
|
body_iter = six.StringIO(content)
|
||||||
|
try:
|
||||||
|
body_iter = json.loads(''.join([c for c in body_iter]))
|
||||||
|
except ValueError:
|
||||||
|
body_iter = None
|
||||||
|
|
||||||
|
return resp, body_iter
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPClient(_BaseHTTPClient):
|
||||||
|
|
||||||
def __init__(self, endpoint, **kwargs):
|
def __init__(self, endpoint, **kwargs):
|
||||||
self.endpoint = endpoint
|
self.endpoint = endpoint
|
||||||
@ -123,15 +189,16 @@ class HTTPClient(object):
|
|||||||
LOG.debug(msg)
|
LOG.debug(msg)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def log_http_response(resp, body=None):
|
def log_http_response(resp):
|
||||||
status = (resp.raw.version / 10.0, resp.status_code, resp.reason)
|
status = (resp.raw.version / 10.0, resp.status_code, resp.reason)
|
||||||
dump = ['\nHTTP/%.1f %s %s' % status]
|
dump = ['\nHTTP/%.1f %s %s' % status]
|
||||||
headers = resp.headers.items()
|
headers = resp.headers.items()
|
||||||
dump.extend(['%s: %s' % safe_header(k, v) for k, v in headers])
|
dump.extend(['%s: %s' % safe_header(k, v) for k, v in headers])
|
||||||
dump.append('')
|
dump.append('')
|
||||||
if body:
|
content_type = resp.headers.get('Content-Type')
|
||||||
body = encodeutils.safe_decode(body)
|
|
||||||
dump.extend([body, ''])
|
if content_type != 'application/octet-stream':
|
||||||
|
dump.extend([resp.text, ''])
|
||||||
LOG.debug('\n'.join([encodeutils.safe_decode(x, errors='ignore')
|
LOG.debug('\n'.join([encodeutils.safe_decode(x, errors='ignore')
|
||||||
for x in dump]))
|
for x in dump]))
|
||||||
|
|
||||||
@ -155,37 +222,13 @@ class HTTPClient(object):
|
|||||||
as setting headers and error handling.
|
as setting headers and error handling.
|
||||||
"""
|
"""
|
||||||
# Copy the kwargs so we can reuse the original in case of redirects
|
# Copy the kwargs so we can reuse the original in case of redirects
|
||||||
headers = kwargs.pop("headers", {})
|
headers = copy.deepcopy(kwargs.pop('headers', {}))
|
||||||
headers = headers and copy.deepcopy(headers) or {}
|
|
||||||
|
|
||||||
if self.identity_headers:
|
if self.identity_headers:
|
||||||
for k, v in six.iteritems(self.identity_headers):
|
for k, v in six.iteritems(self.identity_headers):
|
||||||
headers.setdefault(k, v)
|
headers.setdefault(k, v)
|
||||||
|
|
||||||
# Default Content-Type is octet-stream
|
data = self._set_common_request_kwargs(headers, kwargs)
|
||||||
content_type = headers.get('Content-Type', 'application/octet-stream')
|
|
||||||
|
|
||||||
def chunk_body(body):
|
|
||||||
chunk = body
|
|
||||||
while chunk:
|
|
||||||
chunk = body.read(CHUNKSIZE)
|
|
||||||
if chunk == '':
|
|
||||||
break
|
|
||||||
yield chunk
|
|
||||||
|
|
||||||
data = kwargs.pop("data", None)
|
|
||||||
if data is not None and not isinstance(data, six.string_types):
|
|
||||||
try:
|
|
||||||
data = json.dumps(data)
|
|
||||||
content_type = 'application/json'
|
|
||||||
except TypeError:
|
|
||||||
# Here we assume it's
|
|
||||||
# a file-like object
|
|
||||||
# and we'll chunk it
|
|
||||||
data = chunk_body(data)
|
|
||||||
|
|
||||||
headers['Content-Type'] = content_type
|
|
||||||
stream = True if content_type == 'application/octet-stream' else False
|
|
||||||
|
|
||||||
if osprofiler_web:
|
if osprofiler_web:
|
||||||
headers.update(osprofiler_web.get_trace_id_headers())
|
headers.update(osprofiler_web.get_trace_id_headers())
|
||||||
@ -195,20 +238,20 @@ class HTTPClient(object):
|
|||||||
# complain.
|
# complain.
|
||||||
headers = self.encode_headers(headers)
|
headers = self.encode_headers(headers)
|
||||||
|
|
||||||
|
if self.endpoint.endswith("/") or url.startswith("/"):
|
||||||
|
conn_url = "%s%s" % (self.endpoint, url)
|
||||||
|
else:
|
||||||
|
conn_url = "%s/%s" % (self.endpoint, url)
|
||||||
|
self.log_curl_request(method, conn_url, headers, data, kwargs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if self.endpoint.endswith("/") or url.startswith("/"):
|
|
||||||
conn_url = "%s%s" % (self.endpoint, url)
|
|
||||||
else:
|
|
||||||
conn_url = "%s/%s" % (self.endpoint, url)
|
|
||||||
self.log_curl_request(method, conn_url, headers, data, kwargs)
|
|
||||||
resp = self.session.request(method,
|
resp = self.session.request(method,
|
||||||
conn_url,
|
conn_url,
|
||||||
data=data,
|
data=data,
|
||||||
stream=stream,
|
|
||||||
headers=headers,
|
headers=headers,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
except requests.exceptions.Timeout as e:
|
except requests.exceptions.Timeout as e:
|
||||||
message = ("Error communicating with %(endpoint)s %(e)s" %
|
message = ("Error communicating with %(endpoint)s: %(e)s" %
|
||||||
dict(url=conn_url, e=e))
|
dict(url=conn_url, e=e))
|
||||||
raise exc.InvalidEndpoint(message=message)
|
raise exc.InvalidEndpoint(message=message)
|
||||||
except (requests.exceptions.ConnectionError, ProtocolError) as e:
|
except (requests.exceptions.ConnectionError, ProtocolError) as e:
|
||||||
@ -225,34 +268,8 @@ class HTTPClient(object):
|
|||||||
{'endpoint': endpoint, 'e': e})
|
{'endpoint': endpoint, 'e': e})
|
||||||
raise exc.CommunicationError(message=message)
|
raise exc.CommunicationError(message=message)
|
||||||
|
|
||||||
if not resp.ok:
|
resp, body_iter = self._handle_response(resp)
|
||||||
LOG.debug("Request returned failure status %s." % resp.status_code)
|
self.log_http_response(resp)
|
||||||
raise exc.from_response(resp, resp.text)
|
|
||||||
elif resp.status_code == requests.codes.MULTIPLE_CHOICES:
|
|
||||||
raise exc.from_response(resp)
|
|
||||||
|
|
||||||
content_type = resp.headers.get('Content-Type')
|
|
||||||
|
|
||||||
# Read body into string if it isn't obviously image data
|
|
||||||
if content_type == 'application/octet-stream':
|
|
||||||
# Do not read all response in memory when
|
|
||||||
# downloading an image.
|
|
||||||
body_iter = _close_after_stream(resp, CHUNKSIZE)
|
|
||||||
self.log_http_response(resp)
|
|
||||||
else:
|
|
||||||
content = resp.text
|
|
||||||
self.log_http_response(resp, content)
|
|
||||||
if content_type and content_type.startswith('application/json'):
|
|
||||||
# Let's use requests json method,
|
|
||||||
# it should take care of response
|
|
||||||
# encoding
|
|
||||||
body_iter = resp.json()
|
|
||||||
else:
|
|
||||||
body_iter = six.StringIO(content)
|
|
||||||
try:
|
|
||||||
body_iter = json.loads(''.join([c for c in body_iter]))
|
|
||||||
except ValueError:
|
|
||||||
body_iter = None
|
|
||||||
return resp, body_iter
|
return resp, body_iter
|
||||||
|
|
||||||
def head(self, url, **kwargs):
|
def head(self, url, **kwargs):
|
||||||
@ -283,3 +300,45 @@ def _close_after_stream(response, chunk_size):
|
|||||||
# This will return the connection to the HTTPConnectionPool in urllib3
|
# This will return the connection to the HTTPConnectionPool in urllib3
|
||||||
# and ideally reduce the number of HTTPConnectionPool full warnings.
|
# and ideally reduce the number of HTTPConnectionPool full warnings.
|
||||||
response.close()
|
response.close()
|
||||||
|
|
||||||
|
|
||||||
|
class SessionClient(adapter.Adapter, _BaseHTTPClient):
|
||||||
|
|
||||||
|
def __init__(self, session, **kwargs):
|
||||||
|
kwargs.setdefault('user_agent', USER_AGENT)
|
||||||
|
kwargs.setdefault('service_type', 'image')
|
||||||
|
super(SessionClient, self).__init__(session, **kwargs)
|
||||||
|
|
||||||
|
def request(self, url, method, **kwargs):
|
||||||
|
headers = kwargs.pop('headers', {})
|
||||||
|
kwargs['raise_exc'] = False
|
||||||
|
data = self._set_common_request_kwargs(headers, kwargs)
|
||||||
|
|
||||||
|
try:
|
||||||
|
resp = super(SessionClient, self).request(url,
|
||||||
|
method,
|
||||||
|
headers=headers,
|
||||||
|
data=data,
|
||||||
|
**kwargs)
|
||||||
|
except ksc_exc.RequestTimeout as e:
|
||||||
|
message = ("Error communicating with %(endpoint)s %(e)s" %
|
||||||
|
dict(url=conn_url, e=e))
|
||||||
|
raise exc.InvalidEndpoint(message=message)
|
||||||
|
except ksc_exc.ConnectionRefused as e:
|
||||||
|
conn_url = self.get_endpoint(auth=kwargs.get('auth'))
|
||||||
|
conn_url = "%s/%s" % (conn_url.rstrip('/'), url.lstrip('/'))
|
||||||
|
message = ("Error finding address for %(url)s: %(e)s" %
|
||||||
|
dict(url=conn_url, e=e))
|
||||||
|
raise exc.CommunicationError(message=message)
|
||||||
|
|
||||||
|
return self._handle_response(resp)
|
||||||
|
|
||||||
|
|
||||||
|
def get_http_client(endpoint=None, session=None, **kwargs):
|
||||||
|
if session:
|
||||||
|
return SessionClient(session, **kwargs)
|
||||||
|
elif endpoint:
|
||||||
|
return HTTPClient(endpoint, **kwargs)
|
||||||
|
else:
|
||||||
|
raise AttributeError('Constructing a client must contain either an '
|
||||||
|
'endpoint or a session')
|
||||||
|
@ -428,6 +428,14 @@ def safe_header(name, value):
|
|||||||
return name, value
|
return name, value
|
||||||
|
|
||||||
|
|
||||||
|
def endpoint_version_from_url(endpoint, default_version=None):
|
||||||
|
if endpoint:
|
||||||
|
endpoint, version = strip_version(endpoint)
|
||||||
|
return endpoint, version or default_version
|
||||||
|
else:
|
||||||
|
return None, default_version
|
||||||
|
|
||||||
|
|
||||||
class IterableWithLength(object):
|
class IterableWithLength(object):
|
||||||
def __init__(self, iterable, length):
|
def __init__(self, iterable, length):
|
||||||
self.iterable = iterable
|
self.iterable = iterable
|
||||||
|
@ -12,13 +12,17 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import functools
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from keystoneclient.auth import token_endpoint
|
||||||
|
from keystoneclient import session
|
||||||
import mock
|
import mock
|
||||||
import requests
|
import requests
|
||||||
from requests_mock.contrib import fixture
|
from requests_mock.contrib import fixture
|
||||||
import six
|
import six
|
||||||
from six.moves.urllib import parse
|
from six.moves.urllib import parse
|
||||||
|
from testscenarios import load_tests_apply_scenarios as load_tests # noqa
|
||||||
import testtools
|
import testtools
|
||||||
from testtools import matchers
|
from testtools import matchers
|
||||||
import types
|
import types
|
||||||
@ -30,15 +34,39 @@ from glanceclient import exc
|
|||||||
from glanceclient.tests import utils
|
from glanceclient.tests import utils
|
||||||
|
|
||||||
|
|
||||||
|
def original_only(f):
|
||||||
|
@functools.wraps(f)
|
||||||
|
def wrapper(self, *args, **kwargs):
|
||||||
|
if not hasattr(self.client, 'log_curl_request'):
|
||||||
|
self.skipTest('Skip logging tests for session client')
|
||||||
|
|
||||||
|
return f(self, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class TestClient(testtools.TestCase):
|
class TestClient(testtools.TestCase):
|
||||||
|
|
||||||
|
scenarios = [
|
||||||
|
('httpclient', {'create_client': '_create_http_client'}),
|
||||||
|
('session', {'create_client': '_create_session_client'})
|
||||||
|
]
|
||||||
|
|
||||||
|
def _create_http_client(self):
|
||||||
|
return http.HTTPClient(self.endpoint, token=self.token)
|
||||||
|
|
||||||
|
def _create_session_client(self):
|
||||||
|
auth = token_endpoint.Token(self.endpoint, self.token)
|
||||||
|
sess = session.Session(auth=auth)
|
||||||
|
return http.SessionClient(sess)
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestClient, self).setUp()
|
super(TestClient, self).setUp()
|
||||||
self.mock = self.useFixture(fixture.Fixture())
|
self.mock = self.useFixture(fixture.Fixture())
|
||||||
|
|
||||||
self.endpoint = 'http://example.com:9292'
|
self.endpoint = 'http://example.com:9292'
|
||||||
self.ssl_endpoint = 'https://example.com:9292'
|
self.ssl_endpoint = 'https://example.com:9292'
|
||||||
self.client = http.HTTPClient(self.endpoint, token=u'abc123')
|
self.token = u'abc123'
|
||||||
|
|
||||||
|
self.client = getattr(self, self.create_client)()
|
||||||
|
|
||||||
def test_identity_headers_and_token(self):
|
def test_identity_headers_and_token(self):
|
||||||
identity_headers = {
|
identity_headers = {
|
||||||
@ -140,6 +168,9 @@ class TestClient(testtools.TestCase):
|
|||||||
self.assertEqual(text, resp.text)
|
self.assertEqual(text, resp.text)
|
||||||
|
|
||||||
def test_headers_encoding(self):
|
def test_headers_encoding(self):
|
||||||
|
if not hasattr(self.client, 'encode_headers'):
|
||||||
|
self.skipTest('Cannot do header encoding check on SessionClient')
|
||||||
|
|
||||||
value = u'ni\xf1o'
|
value = u'ni\xf1o'
|
||||||
headers = {"test": value, "none-val": None}
|
headers = {"test": value, "none-val": None}
|
||||||
encoded = self.client.encode_headers(headers)
|
encoded = self.client.encode_headers(headers)
|
||||||
@ -206,6 +237,7 @@ class TestClient(testtools.TestCase):
|
|||||||
self.assertTrue(isinstance(body, types.GeneratorType))
|
self.assertTrue(isinstance(body, types.GeneratorType))
|
||||||
self.assertEqual([data], list(body))
|
self.assertEqual([data], list(body))
|
||||||
|
|
||||||
|
@original_only
|
||||||
def test_log_http_response_with_non_ascii_char(self):
|
def test_log_http_response_with_non_ascii_char(self):
|
||||||
try:
|
try:
|
||||||
response = 'Ok'
|
response = 'Ok'
|
||||||
@ -216,6 +248,7 @@ class TestClient(testtools.TestCase):
|
|||||||
except UnicodeDecodeError as e:
|
except UnicodeDecodeError as e:
|
||||||
self.fail("Unexpected UnicodeDecodeError exception '%s'" % e)
|
self.fail("Unexpected UnicodeDecodeError exception '%s'" % e)
|
||||||
|
|
||||||
|
@original_only
|
||||||
def test_log_curl_request_with_non_ascii_char(self):
|
def test_log_curl_request_with_non_ascii_char(self):
|
||||||
try:
|
try:
|
||||||
headers = {'header1': 'value1\xa5\xa6'}
|
headers = {'header1': 'value1\xa5\xa6'}
|
||||||
@ -225,6 +258,7 @@ class TestClient(testtools.TestCase):
|
|||||||
except UnicodeDecodeError as e:
|
except UnicodeDecodeError as e:
|
||||||
self.fail("Unexpected UnicodeDecodeError exception '%s'" % e)
|
self.fail("Unexpected UnicodeDecodeError exception '%s'" % e)
|
||||||
|
|
||||||
|
@original_only
|
||||||
@mock.patch('glanceclient.common.http.LOG.debug')
|
@mock.patch('glanceclient.common.http.LOG.debug')
|
||||||
def test_log_curl_request_with_body_and_header(self, mock_log):
|
def test_log_curl_request_with_body_and_header(self, mock_log):
|
||||||
hd_name = 'header1'
|
hd_name = 'header1'
|
||||||
|
@ -29,10 +29,9 @@ class Client(object):
|
|||||||
http requests. (optional)
|
http requests. (optional)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, endpoint, **kwargs):
|
def __init__(self, endpoint=None, **kwargs):
|
||||||
"""Initialize a new client for the Images v1 API."""
|
"""Initialize a new client for the Images v1 API."""
|
||||||
endpoint, version = utils.strip_version(endpoint)
|
endpoint, self.version = utils.endpoint_version_from_url(endpoint, 1.0)
|
||||||
self.version = version or 1.0
|
self.http_client = http.get_http_client(endpoint=endpoint, **kwargs)
|
||||||
self.http_client = http.HTTPClient(endpoint, **kwargs)
|
|
||||||
self.images = ImageManager(self.http_client)
|
self.images = ImageManager(self.http_client)
|
||||||
self.image_members = ImageMemberManager(self.http_client)
|
self.image_members = ImageMemberManager(self.http_client)
|
||||||
|
@ -34,11 +34,9 @@ class Client(object):
|
|||||||
http requests. (optional)
|
http requests. (optional)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, endpoint, **kwargs):
|
def __init__(self, endpoint=None, **kwargs):
|
||||||
endpoint, version = utils.strip_version(endpoint)
|
endpoint, self.version = utils.endpoint_version_from_url(endpoint, 2.0)
|
||||||
self.version = version or 2.0
|
self.http_client = http.get_http_client(endpoint=endpoint, **kwargs)
|
||||||
self.http_client = http.HTTPClient(endpoint, **kwargs)
|
|
||||||
|
|
||||||
self.schemas = schemas.Controller(self.http_client)
|
self.schemas = schemas.Controller(self.http_client)
|
||||||
|
|
||||||
self.images = images.Controller(self.http_client, self.schemas)
|
self.images = images.Controller(self.http_client, self.schemas)
|
||||||
|
@ -10,6 +10,7 @@ oslosphinx>=2.5.0 # Apache-2.0
|
|||||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
||||||
testrepository>=0.0.18
|
testrepository>=0.0.18
|
||||||
testtools>=0.9.36,!=1.2.0
|
testtools>=0.9.36,!=1.2.0
|
||||||
|
testscenarios>=0.4
|
||||||
fixtures>=0.3.14
|
fixtures>=0.3.14
|
||||||
requests-mock>=0.6.0 # Apache-2.0
|
requests-mock>=0.6.0 # Apache-2.0
|
||||||
tempest-lib>=0.5.0
|
tempest-lib>=0.5.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user