Add identity-credentials relation support

Implement support for the identity-credentials relation as an
alternative way to get keystone credentials when we are not registering
a service endpoint via the identity-service relation.

This solves an issue where the image volume cache does not work when the
cinder volume service is deployed as a second cinder application
('cinder-volume') having enabled-services=volume set.

Previously the following items were missing from cinder.conf:
cinder_internal_tenant_project_id
cinder_internal_tenant_user_id

Resulting in the image cache not functioning with the following warnings:
Unable to get internal tenant context: Missing required config
parameters.
Unable to get Cinder internal context, will not use image-volume cache.

As there are now two possible interfaces to keystone ('identity-service'
and 'identity-credentials') any existing bundles that don't specify the
interface 'identity-service' when relating to keystone will fail to
deploy and will need to be updated.

Closes-Bug: #1978452
Change-Id: Ieef500c9c55eb3968b3e2e231a8ff6e2a5ec148d
(cherry picked from commit ba8d8fc3e1)
This commit is contained in:
Trent Lloyd 2023-01-20 14:53:39 +08:00
parent 0016bd17b5
commit 388e96c444
8 changed files with 98 additions and 0 deletions

View File

@ -66,6 +66,32 @@ Cinder can be backed by a Pure Storage appliance reachable by its API endpoint.
This functionality is provided by the This functionality is provided by the
[cinder-purestorage][cinder-purestorage-charm] subordinate charm. [cinder-purestorage][cinder-purestorage-charm] subordinate charm.
## Separate Volume Service
For certain operations when an instance is not involved, the cinder application
will connect directly to the storage for operations such as cloning a volume
from a glance image. You can deploy a second cinder application for the volume
service only where the primary cinder application cannot connect to this
storage. This may be required for iSCSI connections because LXD containers
cannot create iSCSI connections or where you need a physical Fibre Channel
connection. This is not required for Ceph deployments which use userspace RBD
tools.
1. Deploy cinder with enabled-services=api,scheduler
2. Deploy a second application of cinder named 'cinder-volume' with
enabled-services=volume
3. Relate the storage subordinate (e.g. cinder-purestorage) to the
cinder-volume application only (not to the 'cinder' application)
4. Keystone should be related to cinder:identity-__service__ but
cinder-volume:identity-__credentials__
The primary cinder application gets keystone credentials when registering a
service endpoint via the identity-service relation. The cinder-volume
application does not register a service, so we need to relate
identity-credentials instead. The image volume cache will not work without
this relation.
5. Both cinder and cinder-volume should otherwise have the same relations
## High availability ## High availability
This charm supports high availability via HAcluster. This charm supports high availability via HAcluster.

View File

@ -469,6 +469,21 @@ def identity_changed():
configure_https() configure_https()
@hooks.hook('identity-credentials-relation-joined')
def identity_credentials_joined(rid=None):
if service_enabled('volume') and not service_enabled('api'):
settings = {'username': 'cinder', 'requested_roles': 'Admin'}
relation_set(relation_id=rid, **settings)
@hooks.hook('identity-credentials-relation-changed')
@restart_on_change(restart_map())
def identity_credentials_changed():
if service_enabled('volume') and not service_enabled('api'):
if 'identity-credentials' in CONFIGS.complete_contexts():
CONFIGS.write(CINDER_CONF)
@hooks.hook('ceph-relation-joined') @hooks.hook('ceph-relation-joined')
def ceph_joined(): def ceph_joined():
if not os.path.isdir('/etc/ceph'): if not os.path.isdir('/etc/ceph'):
@ -586,6 +601,7 @@ def image_service_changed():
@hooks.hook('amqp-relation-broken', @hooks.hook('amqp-relation-broken',
'identity-service-relation-broken', 'identity-service-relation-broken',
'identity-credentials-relation-broken',
'image-service-relation-broken', 'image-service-relation-broken',
'shared-db-relation-broken') 'shared-db-relation-broken')
@restart_on_change(restart_map(), stopstart=True) @restart_on_change(restart_map(), stopstart=True)

View File

@ -224,6 +224,9 @@ BASE_RESOURCE_MAP = OrderedDict([
config_file=CINDER_CONF), config_file=CINDER_CONF),
cinder_contexts.StorageBackendContext(), cinder_contexts.StorageBackendContext(),
cinder_contexts.LoggingConfigContext(), cinder_contexts.LoggingConfigContext(),
context.IdentityCredentialsContext(
service='cinder',
service_user='cinder'),
context.IdentityServiceContext( context.IdentityServiceContext(
service='cinder', service='cinder',
service_user='cinder'), service_user='cinder'),
@ -931,6 +934,9 @@ def get_optional_interfaces():
if relation_ids('image-service'): if relation_ids('image-service'):
optional_interfaces['image'] = ['image-service'] optional_interfaces['image'] = ['image-service']
if service_enabled('volume') and not service_enabled('api'):
optional_interfaces['identity-credentials'] = ['identity-credentials']
return optional_interfaces return optional_interfaces

View File

@ -0,0 +1 @@
cinder_hooks.py

View File

@ -0,0 +1 @@
cinder_hooks.py

View File

@ -0,0 +1 @@
cinder_hooks.py

View File

@ -27,6 +27,9 @@ requires:
interface: rabbitmq interface: rabbitmq
identity-service: identity-service:
interface: keystone interface: keystone
optional: true
identity-credentials:
interface: keystone-credentials
ceph: ceph:
interface: ceph-client interface: ceph-client
image-service: image-service:

View File

@ -127,6 +127,14 @@ class TestChangedHooks(CharmTestCase):
'identity-service': ['identity-service:1'], 'identity-service': ['identity-service:1'],
} }
def svc_enabled(self, svc):
enabled = self.test_config.get('enabled-services')
if enabled == 'all':
return True
return svc in enabled
def setUp(self): def setUp(self):
super(TestChangedHooks, self).setUp(hooks, TO_PATCH) super(TestChangedHooks, self).setUp(hooks, TO_PATCH)
self.config.side_effect = self.test_config.get self.config.side_effect = self.test_config.get
@ -329,6 +337,42 @@ class TestChangedHooks(CharmTestCase):
hooks.hooks.execute(['hooks/identity-service-relation-changed']) hooks.hooks.execute(['hooks/identity-service-relation-changed'])
self.assertFalse(self.CONFIGS.write.called) self.assertFalse(self.CONFIGS.write.called)
@patch.object(hooks, 'service_enabled')
def test_identity_credentials_joined_without_api(self, service_enabled):
'It requests keystone credentials without API service'
self.test_config.set('enabled-services', 'volume')
service_enabled.side_effect = self.svc_enabled
hooks.hooks.execute(['hooks/identity-credentials-relation-joined'])
expected = {'relation_id': None,
'username': 'cinder',
'requested_roles': 'Admin'}
self.relation_set.assert_called_with(**expected)
@patch.object(hooks, 'service_enabled')
def test_identity_credentials_joined_with_api(self, service_enabled):
'It requests keystone credentials with API service'
self.test_config.set('enabled-services', 'all')
service_enabled.side_effect = self.svc_enabled
hooks.hooks.execute(['hooks/identity-credentials-relation-joined'])
self.relation_set.assert_not_called()
@patch.object(hooks, 'service_enabled')
def test_identity_credentials_changed(self, service_enabled):
'It writes out cinder.conf on identity-credentials changed'
self.CONFIGS.complete_contexts.return_value = ['identity-credentials']
self.test_config.set('enabled-services', 'volume')
service_enabled.side_effect = self.svc_enabled
hooks.hooks.execute(['hooks/identity-credentials-relation-changed'])
self.CONFIGS.write.assert_called_with('/etc/cinder/cinder.conf')
@patch.object(hooks, 'service_enabled')
def test_identity_credentials_changed_incomplete(self, service_enabled):
'It does not write cinder.conf with incomplete identity-service'
self.test_config.set('enabled-services', 'volume')
service_enabled.side_effect = self.svc_enabled
hooks.hooks.execute(['hooks/identity-credentials-relation-changed'])
self.assertFalse(self.CONFIGS.write.called)
@patch.object(hooks, 'identity_joined') @patch.object(hooks, 'identity_joined')
def test_configure_https_enable(self, identity_joined): def test_configure_https_enable(self, identity_joined):
'It enables https from hooks when we have https data' 'It enables https from hooks when we have https data'