Allow us to use keystoneclient's session
The session object is a cross-client means of standardizing the transport layer. Novaclient's HTTPClient object has diverged significantly from other clients. It is easier to simply replace it if a session is provided. If a session is provided then users of the library need to be aware that functions such as authenticate() will no longer have any effect/are in error because this is no longer managed by nova. Change-Id: I8f146b878908239d9b6c1c7d6cdc01c7e124f4e5
This commit is contained in:
parent
b9538d8280
commit
cc7364067f
@ -21,6 +21,7 @@ OpenStack Client interface. Handles the REST calls and responses.
|
||||
"""
|
||||
|
||||
import errno
|
||||
import functools
|
||||
import glob
|
||||
import hashlib
|
||||
import logging
|
||||
@ -132,6 +133,86 @@ class CompletionCache(object):
|
||||
self._write_attribute(resource, attribute, value)
|
||||
|
||||
|
||||
class SessionClient(object):
|
||||
|
||||
def __init__(self, session, auth, interface, service_type, region_name):
|
||||
self.session = session
|
||||
self.auth = auth
|
||||
|
||||
self.interface = interface
|
||||
self.service_type = service_type
|
||||
self.region_name = region_name
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
kwargs.setdefault('user_agent', 'python-novaclient')
|
||||
kwargs.setdefault('auth', self.auth)
|
||||
kwargs.setdefault('authenticated', False)
|
||||
|
||||
try:
|
||||
kwargs['json'] = kwargs.pop('body')
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
headers = kwargs.setdefault('headers', {})
|
||||
headers.setdefault('Accept', 'application/json')
|
||||
|
||||
endpoint_filter = kwargs.setdefault('endpoint_filter', {})
|
||||
endpoint_filter.setdefault('interface', self.interface)
|
||||
endpoint_filter.setdefault('service_type', self.service_type)
|
||||
endpoint_filter.setdefault('region_name', self.region_name)
|
||||
|
||||
resp = self.session.request(url, method, raise_exc=False, **kwargs)
|
||||
|
||||
body = None
|
||||
if resp.text:
|
||||
try:
|
||||
body = resp.json()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if resp.status_code >= 400:
|
||||
raise exceptions.from_response(resp, body, url, method)
|
||||
|
||||
return resp, body
|
||||
|
||||
def _cs_request(self, url, method, **kwargs):
|
||||
# this function is mostly redundant but makes compatibility easier
|
||||
kwargs.setdefault('authenticated', True)
|
||||
return self.request(url, method, **kwargs)
|
||||
|
||||
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 _original_only(f):
|
||||
"""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'
|
||||
|
||||
@ -606,6 +687,53 @@ class HTTPClient(object):
|
||||
return self._extract_service_catalog(url, resp, respbody)
|
||||
|
||||
|
||||
def _construct_http_client(username=None, password=None, project_id=None,
|
||||
auth_url=None, insecure=False, timeout=None,
|
||||
proxy_tenant_id=None, proxy_token=None,
|
||||
region_name=None, endpoint_type='publicURL',
|
||||
extensions=None, service_type='compute',
|
||||
service_name=None, volume_service_name=None,
|
||||
timings=False, bypass_url=None, os_cache=False,
|
||||
no_cache=True, http_log_debug=False,
|
||||
auth_system='keystone', auth_plugin=None,
|
||||
auth_token=None, cacert=None, tenant_id=None,
|
||||
user_id=None, connection_pool=False, session=None,
|
||||
auth=None):
|
||||
if session:
|
||||
return SessionClient(session=session,
|
||||
auth=auth,
|
||||
interface=endpoint_type,
|
||||
service_type=service_type,
|
||||
region_name=region_name)
|
||||
else:
|
||||
# FIXME(jamielennox): username and password are now optional. Need
|
||||
# to test that they were provided in this mode.
|
||||
return HTTPClient(username,
|
||||
password,
|
||||
user_id=user_id,
|
||||
projectid=project_id,
|
||||
tenant_id=tenant_id,
|
||||
auth_url=auth_url,
|
||||
auth_token=auth_token,
|
||||
insecure=insecure,
|
||||
timeout=timeout,
|
||||
auth_system=auth_system,
|
||||
auth_plugin=auth_plugin,
|
||||
proxy_token=proxy_token,
|
||||
proxy_tenant_id=proxy_tenant_id,
|
||||
region_name=region_name,
|
||||
endpoint_type=endpoint_type,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
volume_service_name=volume_service_name,
|
||||
timings=timings,
|
||||
bypass_url=bypass_url,
|
||||
os_cache=os_cache,
|
||||
http_log_debug=http_log_debug,
|
||||
cacert=cacert,
|
||||
connection_pool=connection_pool)
|
||||
|
||||
|
||||
def get_client_class(version):
|
||||
version_map = {
|
||||
'1.1': 'novaclient.v1_1.client.Client',
|
||||
|
@ -64,6 +64,16 @@ _code_map = dict(
|
||||
)
|
||||
|
||||
|
||||
class InvalidUsage(RuntimeError):
|
||||
"""This function call is invalid in the way you are using this client.
|
||||
|
||||
Due to the transition to using keystoneclient some function calls are no
|
||||
longer available. You should make a similar call to the session object
|
||||
instead.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def from_response(response, body, url, method=None):
|
||||
"""
|
||||
Return an instance of an HttpError or subclass
|
||||
|
@ -12,6 +12,8 @@
|
||||
|
||||
import fixtures
|
||||
import httpretty
|
||||
from keystoneclient.auth.identity import v2
|
||||
from keystoneclient import session
|
||||
|
||||
from novaclient.openstack.common import jsonutils
|
||||
from novaclient.v1_1 import client as v1_1client
|
||||
@ -107,3 +109,19 @@ class V3(V1):
|
||||
password='xx',
|
||||
project_id='xx',
|
||||
auth_url=self.identity_url)
|
||||
|
||||
|
||||
class SessionV1(V1):
|
||||
|
||||
def new_client(self):
|
||||
self.session = session.Session()
|
||||
self.session.auth = v2.Password(self.identity_url, 'xx', 'xx')
|
||||
return v1_1client.Client(session=self.session)
|
||||
|
||||
|
||||
class SessionV3(V1):
|
||||
|
||||
def new_client(self):
|
||||
self.session = session.Session()
|
||||
self.session.auth = v2.Password(self.identity_url, 'xx', 'xx')
|
||||
return v3client.Client(session=self.session)
|
||||
|
@ -17,6 +17,7 @@ import fixtures
|
||||
import httpretty
|
||||
import requests
|
||||
import six
|
||||
import testscenarios
|
||||
import testtools
|
||||
|
||||
from novaclient.openstack.common import jsonutils
|
||||
@ -43,7 +44,7 @@ class TestCase(testtools.TestCase):
|
||||
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
|
||||
|
||||
|
||||
class FixturedTestCase(TestCase):
|
||||
class FixturedTestCase(testscenarios.TestWithScenarios, TestCase):
|
||||
|
||||
client_fixture_class = None
|
||||
data_fixture_class = None
|
||||
|
@ -24,9 +24,11 @@ from novaclient.v1_1 import agents
|
||||
|
||||
class AgentsTest(utils.FixturedTestCase):
|
||||
|
||||
client_fixture_class = client.V1
|
||||
data_fixture_class = data.Fixture
|
||||
|
||||
scenarios = [('original', {'client_fixture_class': client.V1}),
|
||||
('session', {'client_fixture_class': client.SessionV1})]
|
||||
|
||||
def stub_hypervisors(self, hypervisor='kvm'):
|
||||
get_os_agents = {'agents':
|
||||
[
|
||||
|
@ -21,9 +21,11 @@ from novaclient.v1_1 import aggregates
|
||||
|
||||
class AggregatesTest(utils.FixturedTestCase):
|
||||
|
||||
client_fixture_class = client.V1
|
||||
data_fixture_class = data.Fixture
|
||||
|
||||
scenarios = [('original', {'client_fixture_class': client.V1}),
|
||||
('session', {'client_fixture_class': client.SessionV1})]
|
||||
|
||||
def test_list_aggregates(self):
|
||||
result = self.cs.aggregates.list()
|
||||
self.assert_called('GET', '/os-aggregates')
|
||||
|
@ -27,9 +27,11 @@ class AvailabilityZoneTest(utils.FixturedTestCase):
|
||||
# this class can inherit off the v3 version of shell
|
||||
from novaclient.v1_1 import shell # noqa
|
||||
|
||||
client_fixture_class = client.V1
|
||||
data_fixture_class = data.V1
|
||||
|
||||
scenarios = [('original', {'client_fixture_class': client.V1}),
|
||||
('session', {'client_fixture_class': client.SessionV1})]
|
||||
|
||||
def setUp(self):
|
||||
super(AvailabilityZoneTest, self).setUp()
|
||||
self.availability_zone_type = self._get_availability_zone_type()
|
||||
|
@ -19,10 +19,12 @@ from novaclient.v1_1 import certs
|
||||
|
||||
class CertsTest(utils.FixturedTestCase):
|
||||
|
||||
client_fixture_class = client.V1
|
||||
data_fixture_class = data.Fixture
|
||||
cert_type = certs.Certificate
|
||||
|
||||
scenarios = [('original', {'client_fixture_class': client.V1}),
|
||||
('session', {'client_fixture_class': client.SessionV1})]
|
||||
|
||||
def test_create_cert(self):
|
||||
cert = self.cs.certs.create()
|
||||
self.assert_called('POST', '/os-certificates')
|
||||
|
@ -21,9 +21,11 @@ from novaclient.v1_1 import cloudpipe
|
||||
|
||||
class CloudpipeTest(utils.FixturedTestCase):
|
||||
|
||||
client_fixture_class = client.V1
|
||||
data_fixture_class = data.Fixture
|
||||
|
||||
scenarios = [('original', {'client_fixture_class': client.V1}),
|
||||
('session', {'client_fixture_class': client.SessionV1})]
|
||||
|
||||
def test_list_cloudpipes(self):
|
||||
cp = self.cs.cloudpipe.list()
|
||||
self.assert_called('GET', '/os-cloudpipe')
|
||||
|
@ -20,9 +20,11 @@ from novaclient.tests import utils
|
||||
|
||||
class FixedIpsTest(utils.FixturedTestCase):
|
||||
|
||||
client_fixture_class = client.V1
|
||||
data_fixture_class = data.Fixture
|
||||
|
||||
scenarios = [('original', {'client_fixture_class': client.V1}),
|
||||
('session', {'client_fixture_class': client.SessionV1})]
|
||||
|
||||
def test_get_fixed_ip(self):
|
||||
info = self.cs.fixed_ips.get(fixed_ip='192.168.1.1')
|
||||
self.assert_called('GET', '/os-fixed-ips/192.168.1.1')
|
||||
|
@ -19,7 +19,8 @@ from novaclient.tests.v1_1 import test_agents
|
||||
|
||||
class AgentsTest(test_agents.AgentsTest):
|
||||
|
||||
client_fixture_class = client.V3
|
||||
scenarios = [('original', {'client_fixture_class': client.V3}),
|
||||
('session', {'client_fixture_class': client.SessionV3})]
|
||||
|
||||
def _build_example_update_body(self):
|
||||
return {"agent": {
|
||||
|
@ -17,4 +17,6 @@ from novaclient.tests.v1_1 import test_aggregates
|
||||
|
||||
|
||||
class AggregatesTest(test_aggregates.AggregatesTest):
|
||||
client_fixture = client.V3
|
||||
|
||||
scenarios = [('original', {'client_fixture_class': client.V3}),
|
||||
('session', {'client_fixture_class': client.SessionV3})]
|
||||
|
@ -23,9 +23,11 @@ from novaclient.v3 import availability_zones
|
||||
class AvailabilityZoneTest(test_availability_zone.AvailabilityZoneTest):
|
||||
from novaclient.v3 import shell # noqa
|
||||
|
||||
client_fixture_class = client.V3
|
||||
data_fixture_class = data.V3
|
||||
|
||||
scenarios = [('original', {'client_fixture_class': client.V3}),
|
||||
('session', {'client_fixture_class': client.SessionV3})]
|
||||
|
||||
def _assertZone(self, zone, name, status):
|
||||
self.assertEqual(zone.zone_name, name)
|
||||
self.assertEqual(zone.zone_state, status)
|
||||
|
@ -17,4 +17,5 @@ from novaclient.tests.v1_1 import test_certs
|
||||
|
||||
class CertsTest(test_certs.CertsTest):
|
||||
|
||||
client_fixture_data = client.V3
|
||||
scenarios = [('original', {'client_fixture_class': client.V3}),
|
||||
('session', {'client_fixture_class': client.SessionV3})]
|
||||
|
@ -78,18 +78,17 @@ class Client(object):
|
||||
... AUTH_URL, connection_pool=True)
|
||||
"""
|
||||
|
||||
# FIXME(jesse): project_id isn't required to authenticate
|
||||
def __init__(self, username, api_key, project_id, auth_url=None,
|
||||
insecure=False, timeout=None, proxy_tenant_id=None,
|
||||
proxy_token=None, region_name=None,
|
||||
endpoint_type='publicURL', extensions=None,
|
||||
service_type='compute', service_name=None,
|
||||
volume_service_name=None, timings=False,
|
||||
bypass_url=None, os_cache=False, no_cache=True,
|
||||
http_log_debug=False, auth_system='keystone',
|
||||
auth_plugin=None, auth_token=None,
|
||||
cacert=None, tenant_id=None, user_id=None,
|
||||
connection_pool=False, completion_cache=None):
|
||||
def __init__(self, username=None, api_key=None, project_id=None,
|
||||
auth_url=None, insecure=False, timeout=None,
|
||||
proxy_tenant_id=None, proxy_token=None, region_name=None,
|
||||
endpoint_type='publicURL', extensions=None,
|
||||
service_type='compute', service_name=None,
|
||||
volume_service_name=None, timings=False, bypass_url=None,
|
||||
os_cache=False, no_cache=True, http_log_debug=False,
|
||||
auth_system='keystone', auth_plugin=None, auth_token=None,
|
||||
cacert=None, tenant_id=None, user_id=None,
|
||||
connection_pool=False, session=None, auth=None,
|
||||
completion_cache=None):
|
||||
# FIXME(comstud): Rename the api_key argument above when we
|
||||
# know it's not being used as keyword argument
|
||||
|
||||
@ -149,30 +148,33 @@ class Client(object):
|
||||
setattr(self, extension.name,
|
||||
extension.manager_class(self))
|
||||
|
||||
self.client = client.HTTPClient(username,
|
||||
password,
|
||||
user_id=user_id,
|
||||
projectid=project_id,
|
||||
tenant_id=tenant_id,
|
||||
auth_url=auth_url,
|
||||
auth_token=auth_token,
|
||||
insecure=insecure,
|
||||
timeout=timeout,
|
||||
auth_system=auth_system,
|
||||
auth_plugin=auth_plugin,
|
||||
proxy_token=proxy_token,
|
||||
proxy_tenant_id=proxy_tenant_id,
|
||||
region_name=region_name,
|
||||
endpoint_type=endpoint_type,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
volume_service_name=volume_service_name,
|
||||
timings=timings,
|
||||
bypass_url=bypass_url,
|
||||
os_cache=self.os_cache,
|
||||
http_log_debug=http_log_debug,
|
||||
cacert=cacert,
|
||||
connection_pool=connection_pool)
|
||||
self.client = client._construct_http_client(
|
||||
username=username,
|
||||
password=password,
|
||||
user_id=user_id,
|
||||
project_id=project_id,
|
||||
tenant_id=tenant_id,
|
||||
auth_url=auth_url,
|
||||
auth_token=auth_token,
|
||||
insecure=insecure,
|
||||
timeout=timeout,
|
||||
auth_system=auth_system,
|
||||
auth_plugin=auth_plugin,
|
||||
proxy_token=proxy_token,
|
||||
proxy_tenant_id=proxy_tenant_id,
|
||||
region_name=region_name,
|
||||
endpoint_type=endpoint_type,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
volume_service_name=volume_service_name,
|
||||
timings=timings,
|
||||
bypass_url=bypass_url,
|
||||
os_cache=self.os_cache,
|
||||
http_log_debug=http_log_debug,
|
||||
cacert=cacert,
|
||||
connection_pool=connection_pool,
|
||||
session=session,
|
||||
auth=auth)
|
||||
|
||||
self.completion_cache = completion_cache
|
||||
|
||||
@ -184,22 +186,28 @@ class Client(object):
|
||||
if self.completion_cache:
|
||||
self.completion_cache.clear_class(obj_class)
|
||||
|
||||
@client._original_only
|
||||
def __enter__(self):
|
||||
self.client.open_session()
|
||||
return self
|
||||
|
||||
@client._original_only
|
||||
def __exit__(self, t, v, tb):
|
||||
self.client.close_session()
|
||||
|
||||
@client._original_only
|
||||
def set_management_url(self, url):
|
||||
self.client.set_management_url(url)
|
||||
|
||||
@client._original_only
|
||||
def get_timings(self):
|
||||
return self.client.get_timings()
|
||||
|
||||
@client._original_only
|
||||
def reset_timings(self):
|
||||
self.client.reset_timings()
|
||||
|
||||
@client._original_only
|
||||
def authenticate(self):
|
||||
"""
|
||||
Authenticate against the server.
|
||||
|
@ -63,18 +63,17 @@ class Client(object):
|
||||
... AUTH_URL, connection_pool=True)
|
||||
"""
|
||||
|
||||
# FIXME(jesse): project_id isn't required to authenticate
|
||||
def __init__(self, username, password, project_id, auth_url=None,
|
||||
insecure=False, timeout=None, proxy_tenant_id=None,
|
||||
proxy_token=None, region_name=None,
|
||||
endpoint_type='publicURL', extensions=None,
|
||||
service_type='computev3', service_name=None,
|
||||
volume_service_name=None, timings=False,
|
||||
bypass_url=None, os_cache=False, no_cache=True,
|
||||
http_log_debug=False, auth_system='keystone',
|
||||
auth_plugin=None, auth_token=None,
|
||||
cacert=None, tenant_id=None, user_id=None,
|
||||
connection_pool=False, completion_cache=None):
|
||||
def __init__(self, username=None, password=None, project_id=None,
|
||||
auth_url=None, insecure=False, timeout=None,
|
||||
proxy_tenant_id=None, proxy_token=None, region_name=None,
|
||||
endpoint_type='publicURL', extensions=None,
|
||||
service_type='computev3', service_name=None,
|
||||
volume_service_name=None, timings=False, bypass_url=None,
|
||||
os_cache=False, no_cache=True, http_log_debug=False,
|
||||
auth_system='keystone', auth_plugin=None, auth_token=None,
|
||||
cacert=None, tenant_id=None, user_id=None,
|
||||
connection_pool=False, session=None, auth=None,
|
||||
completion_cache=None):
|
||||
# NOTE(cyeoh): In the novaclient context (unlike Nova) the
|
||||
# project_id is not the same as the tenant_id. Here project_id
|
||||
# is a name (what the Nova API often refers to as a project or
|
||||
@ -111,30 +110,33 @@ class Client(object):
|
||||
setattr(self, extension.name,
|
||||
extension.manager_class(self))
|
||||
|
||||
self.client = client.HTTPClient(username,
|
||||
password,
|
||||
user_id=user_id,
|
||||
projectid=project_id,
|
||||
tenant_id=tenant_id,
|
||||
auth_url=auth_url,
|
||||
insecure=insecure,
|
||||
timeout=timeout,
|
||||
auth_system=auth_system,
|
||||
auth_plugin=auth_plugin,
|
||||
auth_token=auth_token,
|
||||
proxy_token=proxy_token,
|
||||
proxy_tenant_id=proxy_tenant_id,
|
||||
region_name=region_name,
|
||||
endpoint_type=endpoint_type,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
volume_service_name=volume_service_name,
|
||||
timings=timings,
|
||||
bypass_url=bypass_url,
|
||||
os_cache=os_cache,
|
||||
http_log_debug=http_log_debug,
|
||||
cacert=cacert,
|
||||
connection_pool=connection_pool)
|
||||
self.client = client._construct_http_client(
|
||||
username=username,
|
||||
password=password,
|
||||
user_id=user_id,
|
||||
project_id=project_id,
|
||||
tenant_id=tenant_id,
|
||||
auth_url=auth_url,
|
||||
auth_token=auth_token,
|
||||
insecure=insecure,
|
||||
timeout=timeout,
|
||||
auth_system=auth_system,
|
||||
auth_plugin=auth_plugin,
|
||||
proxy_token=proxy_token,
|
||||
proxy_tenant_id=proxy_tenant_id,
|
||||
region_name=region_name,
|
||||
endpoint_type=endpoint_type,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
volume_service_name=volume_service_name,
|
||||
timings=timings,
|
||||
bypass_url=bypass_url,
|
||||
os_cache=self.os_cache,
|
||||
http_log_debug=http_log_debug,
|
||||
cacert=cacert,
|
||||
connection_pool=connection_pool,
|
||||
session=session,
|
||||
auth=auth)
|
||||
|
||||
self.completion_cache = completion_cache
|
||||
|
||||
@ -146,22 +148,28 @@ class Client(object):
|
||||
if self.completion_cache:
|
||||
self.completion_cache.clear_class(obj_class)
|
||||
|
||||
@client._original_only
|
||||
def __enter__(self):
|
||||
self.client.open_session()
|
||||
return self
|
||||
|
||||
@client._original_only
|
||||
def __exit__(self, t, v, tb):
|
||||
self.client.close_session()
|
||||
|
||||
@client._original_only
|
||||
def set_management_url(self, url):
|
||||
self.client.set_management_url(url)
|
||||
|
||||
@client._original_only
|
||||
def get_timings(self):
|
||||
return self.client.get_timings()
|
||||
|
||||
@client._original_only
|
||||
def reset_timings(self):
|
||||
self.client.reset_timings()
|
||||
|
||||
@client._original_only
|
||||
def authenticate(self):
|
||||
"""
|
||||
Authenticate against the server.
|
||||
|
@ -7,6 +7,8 @@ httpretty>=0.8.0,!=0.8.1,!=0.8.2
|
||||
keyring>=2.1
|
||||
mock>=1.0
|
||||
sphinx>=1.1.2,!=1.2.0,<1.3
|
||||
python-keystoneclient>=0.9.0
|
||||
oslosphinx
|
||||
testrepository>=0.0.18
|
||||
testscenarios>=0.4
|
||||
testtools>=0.9.34
|
||||
|
Loading…
Reference in New Issue
Block a user