Merge "Register glance user in keystoneauth plugin"
This commit is contained in:
@@ -29,6 +29,7 @@ import urllib.parse
|
|||||||
|
|
||||||
import glanceclient
|
import glanceclient
|
||||||
import glanceclient.exc
|
import glanceclient.exc
|
||||||
|
from keystoneauth1 import loading as ks_loading
|
||||||
from keystoneauth1.loading import session as ks_session
|
from keystoneauth1.loading import session as ks_session
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
@@ -87,6 +88,14 @@ CONF = cfg.CONF
|
|||||||
CONF.register_opts(image_opts)
|
CONF.register_opts(image_opts)
|
||||||
CONF.register_opts(glance_core_properties_opts)
|
CONF.register_opts(glance_core_properties_opts)
|
||||||
|
|
||||||
|
# Register keystoneauth options to create service user
|
||||||
|
# to talk to glance.
|
||||||
|
GLANCE_GROUP = 'glance'
|
||||||
|
glance_session_opts = ks_loading.get_session_conf_options()
|
||||||
|
glance_auth_opts = ks_loading.get_auth_common_conf_options()
|
||||||
|
CONF.register_opts(glance_session_opts, group=GLANCE_GROUP)
|
||||||
|
CONF.register_opts(glance_auth_opts, group=GLANCE_GROUP)
|
||||||
|
|
||||||
_SESSION = None
|
_SESSION = None
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@@ -107,12 +116,17 @@ def _parse_image_ref(image_href: str) -> tuple[str, str, bool]:
|
|||||||
return (image_id, netloc, use_ssl)
|
return (image_id, netloc, use_ssl)
|
||||||
|
|
||||||
|
|
||||||
def _create_glance_client(context: context.RequestContext,
|
def _create_glance_client(
|
||||||
netloc: str,
|
context: context.RequestContext,
|
||||||
use_ssl: bool) -> glanceclient.Client:
|
netloc: str,
|
||||||
|
use_ssl: bool,
|
||||||
|
privileged_user: bool = False) -> glanceclient.Client:
|
||||||
"""Instantiate a new glanceclient.Client object."""
|
"""Instantiate a new glanceclient.Client object."""
|
||||||
params = {'global_request_id': context.global_id}
|
params = {'global_request_id': context.global_id}
|
||||||
|
g_auth = None
|
||||||
|
if privileged_user and CONF[GLANCE_GROUP].auth_type:
|
||||||
|
LOG.debug('Creating Keystone auth plugin from conf')
|
||||||
|
g_auth = ks_loading.load_auth_from_conf_options(CONF, GLANCE_GROUP)
|
||||||
if use_ssl and CONF.auth_strategy == 'noauth':
|
if use_ssl and CONF.auth_strategy == 'noauth':
|
||||||
params = {'insecure': CONF.glance_api_insecure,
|
params = {'insecure': CONF.glance_api_insecure,
|
||||||
'cacert': CONF.glance_ca_certificates_file,
|
'cacert': CONF.glance_ca_certificates_file,
|
||||||
@@ -131,7 +145,7 @@ def _create_glance_client(context: context.RequestContext,
|
|||||||
}
|
}
|
||||||
_SESSION = ks_session.Session().load_from_options(**config_options)
|
_SESSION = ks_session.Session().load_from_options(**config_options)
|
||||||
|
|
||||||
auth = service_auth.get_auth_plugin(context)
|
auth = service_auth.get_auth_plugin(context, auth=g_auth)
|
||||||
params['auth'] = auth
|
params['auth'] = auth
|
||||||
params['session'] = _SESSION
|
params['session'] = _SESSION
|
||||||
|
|
||||||
@@ -186,44 +200,51 @@ class GlanceClientWrapper(object):
|
|||||||
def __init__(self,
|
def __init__(self,
|
||||||
context: Optional[context.RequestContext] = None,
|
context: Optional[context.RequestContext] = None,
|
||||||
netloc: Optional[str] = None,
|
netloc: Optional[str] = None,
|
||||||
use_ssl: bool = False):
|
use_ssl: bool = False,
|
||||||
|
privileged_user: bool = False):
|
||||||
self.client: Optional[glanceclient.Client]
|
self.client: Optional[glanceclient.Client]
|
||||||
if netloc is not None:
|
if netloc is not None:
|
||||||
assert context is not None
|
assert context is not None
|
||||||
self.client = self._create_static_client(context,
|
self.client = self._create_static_client(context,
|
||||||
netloc,
|
netloc,
|
||||||
use_ssl)
|
use_ssl,
|
||||||
|
privileged_user)
|
||||||
else:
|
else:
|
||||||
self.client = None
|
self.client = None
|
||||||
self.api_servers: Optional[Iterable] = None
|
self.api_servers: Optional[Iterable] = None
|
||||||
|
|
||||||
def _create_static_client(self,
|
def _create_static_client(
|
||||||
context: context.RequestContext,
|
self,
|
||||||
netloc: str,
|
context: context.RequestContext,
|
||||||
use_ssl: bool) -> glanceclient.Client:
|
netloc: str,
|
||||||
|
use_ssl: bool,
|
||||||
|
privileged_user: bool = False) -> glanceclient.Client:
|
||||||
"""Create a client that we'll use for every call."""
|
"""Create a client that we'll use for every call."""
|
||||||
self.netloc = netloc
|
self.netloc = netloc
|
||||||
self.use_ssl = use_ssl
|
self.use_ssl = use_ssl
|
||||||
return _create_glance_client(context,
|
return _create_glance_client(context,
|
||||||
self.netloc,
|
self.netloc,
|
||||||
self.use_ssl)
|
self.use_ssl,
|
||||||
|
privileged_user)
|
||||||
|
|
||||||
def _create_onetime_client(
|
def _create_onetime_client(
|
||||||
self,
|
self,
|
||||||
context: context.RequestContext) -> glanceclient.Client:
|
context: context.RequestContext,
|
||||||
|
privileged_user: bool = False) -> glanceclient.Client:
|
||||||
"""Create a client that will be used for one call."""
|
"""Create a client that will be used for one call."""
|
||||||
if self.api_servers is None:
|
if self.api_servers is None:
|
||||||
self.api_servers = get_api_servers(context)
|
self.api_servers = get_api_servers(context)
|
||||||
self.netloc, self.use_ssl = next(self.api_servers) # type: ignore
|
self.netloc, self.use_ssl = next(self.api_servers) # type: ignore
|
||||||
return _create_glance_client(context,
|
return _create_glance_client(context,
|
||||||
self.netloc,
|
self.netloc,
|
||||||
self.use_ssl)
|
self.use_ssl,
|
||||||
|
privileged_user)
|
||||||
|
|
||||||
def call(self,
|
def call(self,
|
||||||
context: context.RequestContext,
|
context: context.RequestContext,
|
||||||
method: str,
|
method: str,
|
||||||
*args: Any,
|
*args: Any,
|
||||||
**kwargs: str) -> Any:
|
**kwargs: Any) -> Any:
|
||||||
"""Call a glance client method.
|
"""Call a glance client method.
|
||||||
|
|
||||||
If we get a connection error,
|
If we get a connection error,
|
||||||
@@ -237,9 +258,11 @@ class GlanceClientWrapper(object):
|
|||||||
glance_controller = kwargs.pop('controller', 'images')
|
glance_controller = kwargs.pop('controller', 'images')
|
||||||
store_id = kwargs.pop('store_id', None)
|
store_id = kwargs.pop('store_id', None)
|
||||||
base_image_ref = kwargs.pop('base_image_ref', None)
|
base_image_ref = kwargs.pop('base_image_ref', None)
|
||||||
|
privileged_user = kwargs.pop('privileged_user', False)
|
||||||
|
|
||||||
for attempt in range(1, num_attempts + 1):
|
for attempt in range(1, num_attempts + 1):
|
||||||
client = self.client or self._create_onetime_client(context)
|
client = self.client or self._create_onetime_client(
|
||||||
|
context, privileged_user)
|
||||||
|
|
||||||
keys = ('x-image-meta-store', 'x-openstack-base-image-ref',)
|
keys = ('x-image-meta-store', 'x-openstack-base-image-ref',)
|
||||||
values = (store_id, base_image_ref,)
|
values = (store_id, base_image_ref,)
|
||||||
@@ -387,8 +410,16 @@ class GlanceImageService(object):
|
|||||||
try_methods = ('add_image_location', 'add_location')
|
try_methods = ('add_image_location', 'add_location')
|
||||||
for method in try_methods:
|
for method in try_methods:
|
||||||
try:
|
try:
|
||||||
|
# NOTE(gmaan): Glance add_image_location API policy rule is
|
||||||
|
# default to 'service' role so cinder needs to load the auth
|
||||||
|
# plugin from the keystoneauth which has the 'service' role.
|
||||||
|
if method == 'add_image_location':
|
||||||
|
privileged_user = True
|
||||||
|
else:
|
||||||
|
privileged_user = False
|
||||||
return client.call(context, method,
|
return client.call(context, method,
|
||||||
image_id, url, metadata)
|
image_id, url, metadata,
|
||||||
|
privileged_user=privileged_user)
|
||||||
except glanceclient.exc.HTTPNotImplemented:
|
except glanceclient.exc.HTTPNotImplemented:
|
||||||
LOG.debug('Glance method %s not available', method)
|
LOG.debug('Glance method %s not available', method)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@@ -453,6 +453,11 @@ def list_opts():
|
|||||||
cinder_volume_manager.volume_backend_opts,
|
cinder_volume_manager.volume_backend_opts,
|
||||||
cinder_volume_targets_spdknvmf.spdk_opts,
|
cinder_volume_targets_spdknvmf.spdk_opts,
|
||||||
)),
|
)),
|
||||||
|
('glance',
|
||||||
|
itertools.chain(
|
||||||
|
cinder_image_glance.glance_session_opts,
|
||||||
|
cinder_image_glance.glance_auth_opts,
|
||||||
|
)),
|
||||||
('nova',
|
('nova',
|
||||||
itertools.chain(
|
itertools.chain(
|
||||||
cinder_compute_nova.nova_opts,
|
cinder_compute_nova.nova_opts,
|
||||||
|
@@ -21,6 +21,7 @@ from unittest import mock
|
|||||||
|
|
||||||
import ddt
|
import ddt
|
||||||
import glanceclient.exc
|
import glanceclient.exc
|
||||||
|
from keystoneauth1 import loading as ksloading
|
||||||
from keystoneauth1.loading import session as ks_session
|
from keystoneauth1.loading import session as ks_session
|
||||||
from keystoneauth1 import session
|
from keystoneauth1 import session
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
@@ -112,7 +113,8 @@ class TestGlanceImageService(test.TestCase):
|
|||||||
self.mock_object(glance.time, 'sleep', return_value=None)
|
self.mock_object(glance.time, 'sleep', return_value=None)
|
||||||
|
|
||||||
def _create_image_service(self, client):
|
def _create_image_service(self, client):
|
||||||
def _fake_create_glance_client(context, netloc, use_ssl):
|
def _fake_create_glance_client(
|
||||||
|
context, netloc, use_ssl, privileged_user=False):
|
||||||
return client
|
return client
|
||||||
|
|
||||||
self.mock_object(glance, '_create_glance_client',
|
self.mock_object(glance, '_create_glance_client',
|
||||||
@@ -782,7 +784,8 @@ class TestGlanceImageService(test.TestCase):
|
|||||||
|
|
||||||
service.add_location(self.context, image_id, url, metadata)
|
service.add_location(self.context, image_id, url, metadata)
|
||||||
mock_call.assert_called_once_with(
|
mock_call.assert_called_once_with(
|
||||||
self.context, 'add_image_location', image_id, url, metadata)
|
self.context, 'add_image_location',
|
||||||
|
image_id, url, metadata, privileged_user=True)
|
||||||
|
|
||||||
@mock.patch.object(glance.GlanceClientWrapper, 'call')
|
@mock.patch.object(glance.GlanceClientWrapper, 'call')
|
||||||
def test_add_location_old(self, mock_call):
|
def test_add_location_old(self, mock_call):
|
||||||
@@ -795,9 +798,11 @@ class TestGlanceImageService(test.TestCase):
|
|||||||
service.add_location(self.context, image_id, url, metadata)
|
service.add_location(self.context, image_id, url, metadata)
|
||||||
calls = [
|
calls = [
|
||||||
mock.call.call(
|
mock.call.call(
|
||||||
self.context, 'add_image_location', image_id, url, metadata),
|
self.context, 'add_image_location',
|
||||||
|
image_id, url, metadata, privileged_user=True),
|
||||||
mock.call.call(
|
mock.call.call(
|
||||||
self.context, 'add_location', image_id, url, metadata)]
|
self.context, 'add_location',
|
||||||
|
image_id, url, metadata, privileged_user=False)]
|
||||||
mock_call.assert_has_calls(calls)
|
mock_call.assert_has_calls(calls)
|
||||||
|
|
||||||
def test_download_with_retries(self):
|
def test_download_with_retries(self):
|
||||||
@@ -1278,7 +1283,7 @@ class TestGlanceImageServiceClient(test.TestCase):
|
|||||||
client = glance._create_glance_client(self.context, 'fake_host:9292',
|
client = glance._create_glance_client(self.context, 'fake_host:9292',
|
||||||
False)
|
False)
|
||||||
self.assertIsInstance(client, MyGlanceStubClient)
|
self.assertIsInstance(client, MyGlanceStubClient)
|
||||||
mock_get_auth_plugin.assert_called_once_with(self.context)
|
mock_get_auth_plugin.assert_called_once_with(self.context, auth=None)
|
||||||
mock_load.assert_called_once_with(**config_options)
|
mock_load.assert_called_once_with(**config_options)
|
||||||
|
|
||||||
@mock.patch('cinder.service_auth.get_auth_plugin')
|
@mock.patch('cinder.service_auth.get_auth_plugin')
|
||||||
@@ -1314,7 +1319,7 @@ class TestGlanceImageServiceClient(test.TestCase):
|
|||||||
client = glance._create_glance_client(self.context, 'fake_host:9292',
|
client = glance._create_glance_client(self.context, 'fake_host:9292',
|
||||||
True)
|
True)
|
||||||
self.assertIsInstance(client, MyGlanceStubClient)
|
self.assertIsInstance(client, MyGlanceStubClient)
|
||||||
mock_get_auth_plugin.assert_called_once_with(self.context)
|
mock_get_auth_plugin.assert_called_once_with(self.context, auth=None)
|
||||||
mock_load.assert_called_once_with(**config_options)
|
mock_load.assert_called_once_with(**config_options)
|
||||||
|
|
||||||
def test_create_glance_client_auth_strategy_noauth_with_protocol_https(
|
def test_create_glance_client_auth_strategy_noauth_with_protocol_https(
|
||||||
@@ -1358,3 +1363,15 @@ class TestGlanceImageServiceClient(test.TestCase):
|
|||||||
client = glance._create_glance_client(self.context, 'fake_host:9292',
|
client = glance._create_glance_client(self.context, 'fake_host:9292',
|
||||||
False)
|
False)
|
||||||
self.assertIsInstance(client, MyGlanceStubClient)
|
self.assertIsInstance(client, MyGlanceStubClient)
|
||||||
|
|
||||||
|
@mock.patch('cinder.service_auth.get_auth_plugin')
|
||||||
|
@mock.patch.object(ksloading, 'load_auth_from_conf_options')
|
||||||
|
def test_create_glance_client_with_privileged_user(
|
||||||
|
self, mock_load, mock_get_auth_plugin):
|
||||||
|
self.flags(auth_strategy='keystone')
|
||||||
|
self.flags(auth_type='password', group='glance')
|
||||||
|
mock_load.return_value = 'fake_auth_plugin'
|
||||||
|
glance.GlanceClientWrapper(self.context, 'fake_host:9292', True, True)
|
||||||
|
mock_load.assert_called_once()
|
||||||
|
mock_get_auth_plugin.assert_called_once_with(
|
||||||
|
self.context, auth='fake_auth_plugin')
|
||||||
|
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
upgrade:
|
||||||
|
- |
|
||||||
|
With the adoption of New Location APIs, we need a mechanism
|
||||||
|
to perform service-to-service communication to access the
|
||||||
|
``add_image_location`` and ``get_image_locations`` APIs.
|
||||||
|
To achieve the desired functionality, we will need to perform
|
||||||
|
two additional changes during the deployment:
|
||||||
|
|
||||||
|
1. Assign the ``admin`` and ``service`` role to the ``glance`` user
|
||||||
|
2. Configure a ``[glance]`` section in cinder configuration file
|
||||||
|
with the credentials of ``glance`` user and ``service`` project.
|
||||||
|
Refer to the ``[nova]`` or ``[service_user]`` section for reference.
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added support for service-to-service communication between Cinder and
|
||||||
|
Glance.
|
||||||
|
Currently the service-to-service communication is leveraged by the new
|
||||||
|
location APIs for which we will need to configure a dedicated ``[glance]``
|
||||||
|
section in cinder configuration file with the credentials of ``glance``
|
||||||
|
user and ``service`` project.
|
||||||
|
Refer to the ``[nova]`` or ``[service_user]`` section for reference.
|
@@ -228,6 +228,9 @@ if __name__ == "__main__":
|
|||||||
if (group_name == 'NOVA_GROUP'):
|
if (group_name == 'NOVA_GROUP'):
|
||||||
group_name = nova.NOVA_GROUP
|
group_name = nova.NOVA_GROUP
|
||||||
|
|
||||||
|
if (group_name == 'GLANCE_GROUP'):
|
||||||
|
group_name = 'glance'
|
||||||
|
|
||||||
if group_name in registered_opts_dict:
|
if group_name in registered_opts_dict:
|
||||||
line = key + "." + formatted_opt
|
line = key + "." + formatted_opt
|
||||||
registered_opts_dict[group_name].append(line)
|
registered_opts_dict[group_name].append(line)
|
||||||
|
Reference in New Issue
Block a user