Allow any Keystone domain for cinder store

add two new config options for cinder store

- cinder_store_user_domain_name
- cinder_store_project_domain_name

that allow to set the internal user and project to Keystone domains
other that the 'Default' one.

Closes-Bug: #1930299
Change-Id: I1d6c07b6c0e7e6a4da9adabaa026f024b64bb029
This commit is contained in:
Pavlo Shchelokovskyy 2021-05-31 13:34:19 +00:00
parent 04e5ead7c0
commit 4ea3313951
5 changed files with 125 additions and 40 deletions

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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',

View File

@ -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.