diff --git a/README.md b/README.md index f13ea2f8..1b615158 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,32 @@ Cinder can be backed by a Pure Storage appliance reachable by its API endpoint. This functionality is provided by the [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 This charm supports high availability via HAcluster. diff --git a/hooks/cinder_hooks.py b/hooks/cinder_hooks.py index 091f82f6..5439e464 100755 --- a/hooks/cinder_hooks.py +++ b/hooks/cinder_hooks.py @@ -469,6 +469,21 @@ def identity_changed(): 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') def ceph_joined(): if not os.path.isdir('/etc/ceph'): @@ -586,6 +601,7 @@ def image_service_changed(): @hooks.hook('amqp-relation-broken', 'identity-service-relation-broken', + 'identity-credentials-relation-broken', 'image-service-relation-broken', 'shared-db-relation-broken') @restart_on_change(restart_map(), stopstart=True) diff --git a/hooks/cinder_utils.py b/hooks/cinder_utils.py index 08b309d6..fa1d519f 100644 --- a/hooks/cinder_utils.py +++ b/hooks/cinder_utils.py @@ -224,6 +224,9 @@ BASE_RESOURCE_MAP = OrderedDict([ config_file=CINDER_CONF), cinder_contexts.StorageBackendContext(), cinder_contexts.LoggingConfigContext(), + context.IdentityCredentialsContext( + service='cinder', + service_user='cinder'), context.IdentityServiceContext( service='cinder', service_user='cinder'), @@ -931,6 +934,9 @@ def get_optional_interfaces(): if relation_ids('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 diff --git a/hooks/identity-credentials-relation-broken b/hooks/identity-credentials-relation-broken new file mode 120000 index 00000000..6dcd0084 --- /dev/null +++ b/hooks/identity-credentials-relation-broken @@ -0,0 +1 @@ +cinder_hooks.py \ No newline at end of file diff --git a/hooks/identity-credentials-relation-changed b/hooks/identity-credentials-relation-changed new file mode 120000 index 00000000..6dcd0084 --- /dev/null +++ b/hooks/identity-credentials-relation-changed @@ -0,0 +1 @@ +cinder_hooks.py \ No newline at end of file diff --git a/hooks/identity-credentials-relation-joined b/hooks/identity-credentials-relation-joined new file mode 120000 index 00000000..6dcd0084 --- /dev/null +++ b/hooks/identity-credentials-relation-joined @@ -0,0 +1 @@ +cinder_hooks.py \ No newline at end of file diff --git a/metadata.yaml b/metadata.yaml index 1e80c10f..b113682c 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -27,6 +27,9 @@ requires: interface: rabbitmq identity-service: interface: keystone + optional: true + identity-credentials: + interface: keystone-credentials ceph: interface: ceph-client image-service: diff --git a/unit_tests/test_cinder_hooks.py b/unit_tests/test_cinder_hooks.py index 3a604096..79323b31 100644 --- a/unit_tests/test_cinder_hooks.py +++ b/unit_tests/test_cinder_hooks.py @@ -127,6 +127,14 @@ class TestChangedHooks(CharmTestCase): '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): super(TestChangedHooks, self).setUp(hooks, TO_PATCH) self.config.side_effect = self.test_config.get @@ -329,6 +337,42 @@ class TestChangedHooks(CharmTestCase): hooks.hooks.execute(['hooks/identity-service-relation-changed']) 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') def test_configure_https_enable(self, identity_joined): 'It enables https from hooks when we have https data'