summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--os_client_config/cloud_config.py139
-rw-r--r--os_client_config/tests/test_cloud_config.py135
-rw-r--r--test-requirements.txt2
3 files changed, 276 insertions, 0 deletions
diff --git a/os_client_config/cloud_config.py b/os_client_config/cloud_config.py
index 63ff8d2..6085a64 100644
--- a/os_client_config/cloud_config.py
+++ b/os_client_config/cloud_config.py
@@ -14,6 +14,11 @@
import warnings
+from keystoneauth1 import plugin
+from keystoneauth1 import session
+
+from os_client_config import exceptions
+
class CloudConfig(object):
def __init__(self, name, region, config,
@@ -25,6 +30,7 @@ class CloudConfig(object):
self._force_ipv4 = force_ipv4
self._auth = auth_plugin
self._openstack_config = openstack_config
+ self._keystone_session = None
def __getattr__(self, key):
"""Return arbitrary attributes."""
@@ -119,6 +125,139 @@ class CloudConfig(object):
"""Return a keystoneauth plugin from the auth credentials."""
return self._auth
+ def get_session(self):
+ """Return a keystoneauth session based on the auth credentials."""
+ if self._keystone_session is None:
+ if not self._auth:
+ raise exceptions.OpenStackConfigException(
+ "Problem with auth parameters")
+ (verify, cert) = self.get_requests_verify_args()
+ self._keystone_session = session.Session(
+ auth=self._auth,
+ verify=verify,
+ cert=cert,
+ timeout=self.config['api_timeout'])
+ return self._keystone_session
+
+ def get_session_endpoint(self, service_key):
+ """Return the endpoint from config or the catalog.
+
+ If a configuration lists an explicit endpoint for a service,
+ return that. Otherwise, fetch the service catalog from the
+ keystone session and return the appropriate endpoint.
+
+ :param service_key: Generic key for service, such as 'compute' or
+ 'network'
+
+ :returns: Endpoint for the service, or None if not found
+ """
+
+ override_endpoint = self.get_endpoint(service_key)
+ if override_endpoint:
+ return override_endpoint
+ # keystone is a special case in keystone, because what?
+ session = self.get_session()
+ if service_key == 'identity':
+ endpoint = session.get_endpoint(interface=plugin.AUTH_INTERFACE)
+ else:
+ endpoint = session.get_endpoint(
+ service_type=self.get_service_type(service_key),
+ service_name=self.get_service_name(service_key),
+ interface=self.get_interface(service_key),
+ region_name=self.region)
+ return endpoint
+
+ def get_legacy_client(
+ self, service_key, client_class, interface_key=None,
+ pass_version_arg=True, **kwargs):
+ """Return a legacy OpenStack client object for the given config.
+
+ Most of the OpenStack python-*client libraries have the same
+ interface for their client constructors, but there are several
+ parameters one wants to pass given a :class:`CloudConfig` object.
+
+ In the future, OpenStack API consumption should be done through
+ the OpenStack SDK, but that's not ready yet. This is for getting
+ Client objects from python-*client only.
+
+ :param service_key: Generic key for service, such as 'compute' or
+ 'network'
+ :param client_class: Class of the client to be instantiated. This
+ should be the unversioned version if there
+ is one, such as novaclient.client.Client, or
+ the versioned one, such as
+ neutronclient.v2_0.client.Client if there isn't
+ :param interface_key: (optional) Some clients, such as glanceclient
+ only accept the parameter 'interface' instead
+ of 'endpoint_type' - this is a get-out-of-jail
+ parameter for those until they can be aligned.
+ os-client-config understands this to be the
+ case if service_key is image, so this is really
+ only for use with other unknown broken clients.
+ :param pass_version_arg: (optional) If a versioned Client constructor
+ was passed to client_class, set this to
+ False, which will tell get_client to not
+ pass a version parameter. os-client-config
+ already understand that this is the
+ case for network, so it can be omitted in
+ that case.
+ :param kwargs: (optional) keyword args are passed through to the
+ Client constructor, so this is in case anything
+ additional needs to be passed in.
+ """
+ # Because of course swift is different
+ if service_key == 'object-store':
+ return self._get_swift_client(client_class=client_class, **kwargs)
+ interface = self.get_interface(service_key)
+ # trigger exception on lack of service
+ endpoint = self.get_session_endpoint(service_key)
+
+ if not interface_key:
+ if service_key == 'image':
+ interface_key = 'interface'
+ else:
+ interface_key = 'endpoint_type'
+ if service_key == 'network':
+ pass_version_arg = False
+
+ constructor_args = dict(
+ session=self.get_session(),
+ service_name=self.get_service_name(service_key),
+ service_type=self.get_service_type(service_key),
+ region_name=self.region)
+
+ if service_key == 'image':
+ # os-client-config does not depend on glanceclient, but if
+ # the user passed in glanceclient.client.Client, which they
+ # would need to do if they were requesting 'image' - then
+ # they necessarily have glanceclient installed
+ from glanceclient.common import utils as glance_utils
+ endpoint, version = glance_utils.strip_version(endpoint)
+ constructor_args['endpoint'] = endpoint
+ constructor_args.update(kwargs)
+ constructor_args[interface_key] = interface
+ if pass_version_arg:
+ version = self.get_api_version(service_key)
+ constructor_args['version'] = version
+ return client_class(**constructor_args)
+
+ def _get_swift_client(self, client_class, **kwargs):
+ session = self.get_session()
+ token = session.get_token()
+ endpoint = self.get_session_endpoint(service_key='object-store')
+ if not endpoint:
+ return None
+ return client_class(
+ preauthurl=endpoint,
+ preauthtoken=token,
+ auth_version=self.get_api_version('identity'),
+ os_options=dict(
+ auth_token=token,
+ object_storage_url=endpoint,
+ region_name=self.get_region_name()),
+ timeout=self.api_timeout,
+ )
+
def get_cache_expiration_time(self):
if self._openstack_config:
return self._openstack_config.get_cache_expiration_time()
diff --git a/os_client_config/tests/test_cloud_config.py b/os_client_config/tests/test_cloud_config.py
index c9317ad..f5ceac7 100644
--- a/os_client_config/tests/test_cloud_config.py
+++ b/os_client_config/tests/test_cloud_config.py
@@ -12,7 +12,13 @@
import copy
+from keystoneauth1 import plugin as ksa_plugin
+from keystoneauth1 import session as ksa_session
+import mock
+
from os_client_config import cloud_config
+from os_client_config import defaults
+from os_client_config import exceptions
from os_client_config.tests import base
@@ -142,3 +148,132 @@ class TestCloudConfig(base.TestCase):
cc.get_endpoint('image'))
self.assertEqual(None, cc.get_service_name('compute'))
self.assertEqual('locks', cc.get_service_name('identity'))
+
+ def test_get_session_no_auth(self):
+ config_dict = defaults.get_defaults()
+ config_dict.update(fake_services_dict)
+ cc = cloud_config.CloudConfig("test1", "region-al", config_dict)
+ self.assertRaises(
+ exceptions.OpenStackConfigException,
+ cc.get_session)
+
+ @mock.patch.object(ksa_session, 'Session')
+ def test_get_session(self, mock_session):
+ config_dict = defaults.get_defaults()
+ config_dict.update(fake_services_dict)
+ cc = cloud_config.CloudConfig(
+ "test1", "region-al", config_dict, auth_plugin=mock.Mock())
+ cc.get_session()
+ mock_session.assert_called_with(
+ auth=mock.ANY,
+ verify=True, cert=None, timeout=None)
+
+ @mock.patch.object(ksa_session, 'Session')
+ def test_override_session_endpoint(self, mock_session):
+ config_dict = defaults.get_defaults()
+ config_dict.update(fake_services_dict)
+ cc = cloud_config.CloudConfig(
+ "test1", "region-al", config_dict, auth_plugin=mock.Mock())
+ self.assertEqual(
+ cc.get_session_endpoint('compute'),
+ fake_services_dict['compute_endpoint'])
+
+ @mock.patch.object(cloud_config.CloudConfig, 'get_session')
+ def test_session_endpoint_identity(self, mock_get_session):
+ mock_session = mock.Mock()
+ mock_get_session.return_value = mock_session
+ config_dict = defaults.get_defaults()
+ config_dict.update(fake_services_dict)
+ cc = cloud_config.CloudConfig(
+ "test1", "region-al", config_dict, auth_plugin=mock.Mock())
+ cc.get_session_endpoint('identity')
+ mock_session.get_endpoint.assert_called_with(
+ interface=ksa_plugin.AUTH_INTERFACE)
+
+ @mock.patch.object(cloud_config.CloudConfig, 'get_session')
+ def test_session_endpoint(self, mock_get_session):
+ mock_session = mock.Mock()
+ mock_get_session.return_value = mock_session
+ config_dict = defaults.get_defaults()
+ config_dict.update(fake_services_dict)
+ cc = cloud_config.CloudConfig(
+ "test1", "region-al", config_dict, auth_plugin=mock.Mock())
+ cc.get_session_endpoint('orchestration')
+ mock_session.get_endpoint.assert_called_with(
+ interface='public',
+ service_name=None,
+ region_name='region-al',
+ service_type='orchestration')
+
+ @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
+ def test_legacy_client_object_store(self, mock_get_session_endpoint):
+ mock_client = mock.Mock()
+ mock_get_session_endpoint.return_value = 'http://example.com/v2'
+ config_dict = defaults.get_defaults()
+ config_dict.update(fake_services_dict)
+ cc = cloud_config.CloudConfig(
+ "test1", "region-al", config_dict, auth_plugin=mock.Mock())
+ cc.get_legacy_client('object-store', mock_client)
+ mock_client.assert_called_with(
+ preauthtoken=mock.ANY,
+ os_options={
+ 'auth_token': mock.ANY,
+ 'region_name': 'region-al',
+ 'object_storage_url': 'http://example.com/v2'
+ },
+ preauthurl='http://example.com/v2',
+ auth_version='2.0',
+ timeout=None)
+
+ @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
+ def test_legacy_client_image(self, mock_get_session_endpoint):
+ mock_client = mock.Mock()
+ mock_get_session_endpoint.return_value = 'http://example.com/v2'
+ config_dict = defaults.get_defaults()
+ config_dict.update(fake_services_dict)
+ cc = cloud_config.CloudConfig(
+ "test1", "region-al", config_dict, auth_plugin=mock.Mock())
+ cc.get_legacy_client('image', mock_client)
+ mock_client.assert_called_with(
+ version='2',
+ service_name=None,
+ endpoint='http://example.com',
+ region_name='region-al',
+ interface='public',
+ session=mock.ANY,
+ # Not a typo - the config dict above overrides this
+ service_type='mage'
+ )
+
+ @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
+ def test_legacy_client_network(self, mock_get_session_endpoint):
+ mock_client = mock.Mock()
+ mock_get_session_endpoint.return_value = 'http://example.com/v2'
+ config_dict = defaults.get_defaults()
+ config_dict.update(fake_services_dict)
+ cc = cloud_config.CloudConfig(
+ "test1", "region-al", config_dict, auth_plugin=mock.Mock())
+ cc.get_legacy_client('network', mock_client)
+ mock_client.assert_called_with(
+ endpoint_type='public',
+ region_name='region-al',
+ service_type='network',
+ session=mock.ANY,
+ service_name=None)
+
+ @mock.patch.object(cloud_config.CloudConfig, 'get_session_endpoint')
+ def test_legacy_client_compute(self, mock_get_session_endpoint):
+ mock_client = mock.Mock()
+ mock_get_session_endpoint.return_value = 'http://example.com/v2'
+ config_dict = defaults.get_defaults()
+ config_dict.update(fake_services_dict)
+ cc = cloud_config.CloudConfig(
+ "test1", "region-al", config_dict, auth_plugin=mock.Mock())
+ cc.get_legacy_client('compute', mock_client)
+ mock_client.assert_called_with(
+ version=2,
+ endpoint_type='public',
+ region_name='region-al',
+ service_type='compute',
+ session=mock.ANY,
+ service_name=None)
diff --git a/test-requirements.txt b/test-requirements.txt
index 9a02b04..7053051 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -9,6 +9,8 @@ extras
fixtures>=0.3.14
discover
jsonschema>=2.0.0,<3.0.0,!=2.5.0
+mock>=1.2
+python-glanceclient>=0.18.0
python-keystoneclient>=1.1.0
python-subunit>=0.0.18
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3