diff --git a/glance_store/_drivers/cinder.py b/glance_store/_drivers/cinder.py index 2db8e9b1..37662ddb 100644 --- a/glance_store/_drivers/cinder.py +++ b/glance_store/_drivers/cinder.py @@ -25,6 +25,9 @@ import time from keystoneauth1.access import service_catalog as keystone_sc from keystoneauth1 import exceptions as keystone_exc +from keystoneauth1 import identity as ksa_identity +from keystoneauth1 import session as ksa_session +from keystoneauth1 import token_endpoint as ksa_token_endpoint from oslo_concurrency import processutils from oslo_config import cfg from oslo_utils import units @@ -79,6 +82,8 @@ Related options: * cinder_store_user_name * cinder_store_project_name * cinder_store_password + * cinder_store_project_domain_name + * cinder_store_user_domain_name """), cfg.StrOpt('cinder_endpoint_template', @@ -104,6 +109,8 @@ Related options: * cinder_store_user_name * cinder_store_project_name * cinder_store_password + * cinder_store_project_domain_name + * cinder_store_user_domain_name * cinder_catalog_info """), @@ -215,6 +222,8 @@ Related options: * cinder_store_user_name * cinder_store_password * cinder_store_project_name + * cinder_store_project_domain_name + * cinder_store_user_domain_name """), cfg.StrOpt('cinder_store_user_name', @@ -222,8 +231,9 @@ Related options: help=""" User name to authenticate against cinder. -This must be used with all the following related options. If any of these are -not specified, the user of the current context is used. +This must be used with all the following non-domain-related options. +If any of these are not specified (except domain-related options), +the user of the current context is used. Possible values: * A valid user name @@ -232,14 +242,33 @@ Related options: * cinder_store_auth_address * cinder_store_password * cinder_store_project_name + * cinder_store_project_domain_name + * cinder_store_user_domain_name + +"""), + cfg.StrOpt('cinder_store_user_domain_name', + default='Default', + help=""" +Domain of the user to authenticate against cinder. + +Possible values: + * A valid domain name for the user specified by ``cinder_store_user_name`` + +Related options: + * cinder_store_auth_address + * cinder_store_password + * cinder_store_project_name + * cinder_store_project_domain_name + * cinder_store_user_name """), cfg.StrOpt('cinder_store_password', secret=True, help=""" Password for the user authenticating against cinder. -This must be used with all the following related options. If any of these are -not specified, the user of the current context is used. +This must be used with all the following related options. +If any of these are not specified (except domain-related options), +the user of the current context is used. Possible values: * A valid password for the user specified by ``cinder_store_user_name`` @@ -248,6 +277,8 @@ Related options: * cinder_store_auth_address * cinder_store_user_name * cinder_store_project_name + * cinder_store_project_domain_name + * cinder_store_user_domain_name """), cfg.StrOpt('cinder_store_project_name', @@ -258,8 +289,9 @@ Project name where the image volume is stored in cinder. If this configuration option is not set, the project in current context is used. -This must be used with all the following related options. If any of these are -not specified, the project of the current context is used. +This must be used with all the following related options. +If any of these are not specified (except domain-related options), +the user of the current context is used. Possible values: * A valid project name @@ -268,6 +300,25 @@ Related options: * ``cinder_store_auth_address`` * ``cinder_store_user_name`` * ``cinder_store_password`` + * ``cinder_store_project_domain_name`` + * ``cinder_store_user_domain_name`` + +"""), + cfg.StrOpt('cinder_store_project_domain_name', + default='Default', + help=""" +Domain of the project where the image volume is stored in cinder. + +Possible values: + * A valid domain name of the project specified by + ``cinder_store_project_name`` + +Related options: + * ``cinder_store_auth_address`` + * ``cinder_store_user_name`` + * ``cinder_store_password`` + * ``cinder_store_project_domain_name`` + * ``cinder_store_user_domain_name`` """), cfg.StrOpt('rootwrap_config', @@ -350,6 +401,34 @@ Possible values: """), ] +CINDER_SESSION = None + + +def _reset_cinder_session(): + global CINDER_SESSION + CINDER_SESSION = None + + +def get_cinder_session(conf): + global CINDER_SESSION + if not CINDER_SESSION: + auth = ksa_identity.V3Password( + password=conf.cinder_store_password, + username=conf.cinder_store_user_name, + user_domain_name=conf.cinder_store_user_domain_name, + project_name=conf.cinder_store_project_name, + project_domain_name=conf.cinder_store_project_domain_name, + auth_url=conf.cinder_store_auth_address + ) + if conf.cinder_api_insecure: + verify = False + elif conf.cinder_ca_certificates_file: + verify = conf.cinder_ca_certificates_file + else: + verify = True + CINDER_SESSION = ksa_session.Session(auth=auth, verify=verify) + return CINDER_SESSION + class StoreLocation(glance_store.location.StoreLocation): @@ -476,15 +555,18 @@ class Store(glance_store.driver.Store): else: user_overriden = self.is_user_overriden() + session = get_cinder_session(self.store_conf) + if user_overriden: username = self.store_conf.cinder_store_user_name - password = self.store_conf.cinder_store_password - project = self.store_conf.cinder_store_project_name url = self.store_conf.cinder_store_auth_address + # use auth that is already in the session + auth = None else: username = context.user_id - password = context.auth_token project = context.project_id + # noauth extracts user_id:project_id from auth_token + token = context.auth_token or '%s:%s' % (username, project) if self.store_conf.cinder_endpoint_template: template = self.store_conf.cinder_endpoint_template @@ -504,23 +586,17 @@ class Store(glance_store.driver.Store): reason = _("Failed to find Cinder from a service catalog.") raise exceptions.BadStoreConfiguration(store_name="cinder", reason=reason) + auth = ksa_token_endpoint.Token(endpoint=url, token=token) c = cinderclient.Client( - username, password, project, auth_url=url, + session=session, auth=auth, region_name=self.store_conf.cinder_os_region_name, - insecure=self.store_conf.cinder_api_insecure, - retries=self.store_conf.cinder_http_retries, - cacert=self.store_conf.cinder_ca_certificates_file) + retries=self.store_conf.cinder_http_retries) LOG.debug( 'Cinderclient connection created for user %(user)s using URL: ' '%(url)s.', {'user': username, 'url': url}) - # noauth extracts user_id:project_id from auth_token - if not user_overriden: - c.client.auth_token = context.auth_token or '%s:%s' % (username, - project) - c.client.management_url = url return c @contextlib.contextmanager diff --git a/glance_store/tests/unit/test_cinder_store.py b/glance_store/tests/unit/test_cinder_store.py index 7ba655da..7abe0c37 100644 --- a/glance_store/tests/unit/test_cinder_store.py +++ b/glance_store/tests/unit/test_cinder_store.py @@ -26,7 +26,6 @@ import tempfile import time import uuid -from cinderclient.v3 import client as cinderclient from os_brick.initiator import connector from oslo_concurrency import processutils from oslo_utils.secretutils import md5 @@ -67,31 +66,30 @@ class TestCinderStore(base.StoreBaseTest, auth_token='fake_token', project_id='fake_project') self.hash_algo = 'sha256' + cinder._reset_cinder_session() def test_get_cinderclient(self): cc = self.store.get_cinderclient(self.context) - self.assertEqual('fake_token', cc.client.auth_token) - self.assertEqual('http://foo/public_url', cc.client.management_url) + self.assertEqual('fake_token', cc.client.auth.token) + self.assertEqual('http://foo/public_url', cc.client.auth.endpoint) - def test_get_cinderclient_with_user_overriden(self): + def _test_get_cinderclient_with_user_overriden(self): self.config(cinder_store_user_name='test_user') self.config(cinder_store_password='test_password') self.config(cinder_store_project_name='test_project') self.config(cinder_store_auth_address='test_address') cc = self.store.get_cinderclient(self.context) - self.assertIsNone(cc.client.auth_token) - self.assertEqual('test_address', cc.client.management_url) + self.assertEqual('test_project', cc.client.session.auth.project_name) + self.assertEqual('Default', cc.client.session.auth.project_domain_name) + return cc + + def test_get_cinderclient_with_user_overriden(self): + self._test_get_cinderclient_with_user_overriden() def test_get_cinderclient_with_user_overriden_and_region(self): self.config(cinder_os_region_name='test_region') - fake_client = FakeObject(client=FakeObject(auth_token=None)) - with mock.patch.object(cinderclient, 'Client', - return_value=fake_client) as mock_client: - self.test_get_cinderclient_with_user_overriden() - mock_client.assert_called_once_with( - 'test_user', 'test_password', 'test_project', - auth_url='test_address', cacert=None, insecure=False, - region_name='test_region', retries=3) + cc = self._test_get_cinderclient_with_user_overriden() + self.assertEqual('test_region', cc.client.region_name) def test_temporary_chown(self): class fake_stat(object): diff --git a/glance_store/tests/unit/test_multistore_cinder.py b/glance_store/tests/unit/test_multistore_cinder.py index 1c6ae26e..d87bdfcc 100644 --- a/glance_store/tests/unit/test_multistore_cinder.py +++ b/glance_store/tests/unit/test_multistore_cinder.py @@ -98,14 +98,15 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, user_id='admin_user', auth_token='admin_token', project_id='admin_project') + cinder._reset_cinder_session() def test_location_url_prefix_is_set(self): self.assertEqual("cinder://cinder1", self.store.url_prefix) def test_get_cinderclient(self): cc = self.store.get_cinderclient(self.context) - self.assertEqual('fake_token', cc.client.auth_token) - self.assertEqual('http://foo/public_url', cc.client.management_url) + self.assertEqual('fake_token', cc.client.auth.token) + self.assertEqual('http://foo/public_url', cc.client.auth.endpoint) def test_get_cinderclient_with_user_overriden(self): self.config(cinder_store_user_name='test_user', group="cinder1") @@ -113,16 +114,14 @@ class TestMultiCinderStore(base.MultiStoreBaseTest, self.config(cinder_store_project_name='test_project', group="cinder1") self.config(cinder_store_auth_address='test_address', group="cinder1") cc = self.store.get_cinderclient(self.context) - self.assertIsNone(cc.client.auth_token) - self.assertEqual('test_address', cc.client.management_url) + self.assertEqual('Default', cc.client.session.auth.project_domain_name) + self.assertEqual('test_project', cc.client.session.auth.project_name) def test_get_cinderclient_legacy_update(self): cc = self.store.get_cinderclient(self.fake_admin_context, legacy_update=True) - self.assertEqual('admin_token', cc.client.auth_token) - self.assertEqual('admin_user', cc.client.user) - self.assertEqual('admin_project', cc.client.projectid) - self.assertEqual('http://foo/public_url', cc.client.management_url) + self.assertEqual('admin_token', cc.client.auth.token) + self.assertEqual('http://foo/public_url', cc.client.auth.endpoint) def test_temporary_chown(self): class fake_stat(object): diff --git a/glance_store/tests/unit/test_opts.py b/glance_store/tests/unit/test_opts.py index 6f46d600..5928d4fe 100644 --- a/glance_store/tests/unit/test_opts.py +++ b/glance_store/tests/unit/test_opts.py @@ -77,8 +77,10 @@ class OptsTestCase(base.StoreBaseTest): 'cinder_state_transition_timeout', 'cinder_store_auth_address', 'cinder_store_user_name', + 'cinder_store_user_domain_name', 'cinder_store_password', 'cinder_store_project_name', + 'cinder_store_project_domain_name', 'cinder_volume_type', 'cinder_use_multipath', 'cinder_enforce_multipath', diff --git a/releasenotes/notes/support-cinder-user-domain-420c76053dd50534.yaml b/releasenotes/notes/support-cinder-user-domain-420c76053dd50534.yaml new file mode 100644 index 00000000..7763366a --- /dev/null +++ b/releasenotes/notes/support-cinder-user-domain-420c76053dd50534.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + For the Cinder store, if using an internal user to store images, + it is now possible to have the internal user and the internal project + in Keystone domains other than the ``Default`` one. + Two new config options ``cinder_store_user_domain_name`` and + ``cinder_store_project_domain_name`` are added + (both default to ``Default``) and now are possible to use in the + configuration of the Cinder store.