From e03f6a6a1cb1ab0811bc97f8a65789b358925f1a Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Tue, 9 Jun 2020 12:20:43 +0200 Subject: [PATCH] plugins/ceph: Add support for core Ceph charms Also adds missing release combinations to package_codenames. Change-Id: I6fbde855ba8f83ef5e265bd5b5dfb0d01eae830b Related-Bug: #1879072 --- charms_openstack/plugins/classes.py | 75 ++++++++++++++++--- .../charms_openstack/plugins/test_classes.py | 42 ++++++++++- 2 files changed, 105 insertions(+), 12 deletions(-) diff --git a/charms_openstack/plugins/classes.py b/charms_openstack/plugins/classes.py index a24abe3..c7754e0 100644 --- a/charms_openstack/plugins/classes.py +++ b/charms_openstack/plugins/classes.py @@ -13,6 +13,7 @@ # limitations under the License. import collections +import enum import os import shutil import socket @@ -50,9 +51,27 @@ class BaseOpenStackCephCharm(object): # ceph service name is determined from `application_name` property. # If this does not fit your use case you can override. ceph_service_name_override = '' + # Unless you are writing a charm providing Ceph mon|osd|mgr|mds services # this should probably be left as-is. - ceph_service_type = 'client' + + class CephServiceType(enum.Enum): + """Ceph service type.""" + client = 'client' + mds = 'mds' + mgr = 'mgr' + mon = 'mon' + osd = 'osd' + + def __str__(self): + """Return string representation of value. + + :returns: string representation of value. + :rtype: str + """ + return self.value + + ceph_service_type = CephServiceType.client # Path prefix to where the Ceph keyring should be stored. ceph_keyring_path_prefix = '/etc/ceph' @@ -102,9 +121,13 @@ class BaseOpenStackCephCharm(object): :returns: Ceph key name :rtype: str """ - base_key_name = '{}.{}'.format( - self.ceph_service_type, - self.ceph_service_name) + if self.ceph_service_type == self.CephServiceType.client: + base_key_name = '{}.{}'.format( + self.ceph_service_type, + self.ceph_service_name) + else: + base_key_name = self.ceph_service_name + if self.ceph_key_per_unit_name: return '{}.{}'.format( base_key_name, @@ -131,9 +154,13 @@ class BaseOpenStackCephCharm(object): :returns: Absolute path to keyring file :rtype: str """ - keyring_name = ('{}.{}.keyring' - .format(cluster_name or self.ceph_cluster_name, - self.ceph_key_name)) + if self.ceph_service_type == self.CephServiceType.client: + keyring_name = ('{}.{}.keyring' + .format(cluster_name or self.ceph_cluster_name, + self.ceph_key_name)) + else: + keyring_name = 'keyring' + keyring_absolute_path = os.path.join(self.ceph_keyring_path, keyring_name) return keyring_absolute_path @@ -216,6 +243,8 @@ class CephCharm(charms_openstack.charm.OpenStackCharm, ('10', 'mitaka'), # 10.2.x Jewel ('12', 'pike'), # 12.2.x Luminous ('13', 'rocky'), # 13.2.x Mimic + ('14', 'train'), # 14.2.x Nautilus + ('15', 'ussuri'), # 15.2.x Octopus ]), } @@ -250,6 +279,11 @@ class CephCharm(charms_openstack.charm.OpenStackCharm, # Path prefix to where the Ceph keyring should be stored. ceph_keyring_path_prefix = '/var/lib/ceph' + def __init__(self, **kwargs): + """Initialize class.""" + super().__init__(**kwargs) + self.hostname = socket.gethostname() + @property def ceph_keyring_path(self): """Provide a path to where the Ceph keyring should be stored. @@ -257,14 +291,33 @@ class CephCharm(charms_openstack.charm.OpenStackCharm, :returns: Path to directory :rtype: str """ - return os.path.join(self.snap_path_prefix, - self.ceph_keyring_path_prefix, - self.ceph_service_name) + keyring_path_components = ( + self.snap_path_prefix, + self.ceph_keyring_path_prefix, + self.ceph_service_name) + + if self.ceph_service_type != self.CephServiceType.client: + keyring_path_components = ( + *keyring_path_components, + '{}-{}'.format(self.ceph_cluster_name, + self.hostname)) + + return os.path.join(*keyring_path_components) def configure_ceph_keyring(self, key, cluster_name=None): - """Override parent function to add symlink in ``/etc/ceph``.""" + """Override parent method for Ceph service providing charms. + + :param cluster_name: (Optional) Name of Ceph cluster to operate on. + Defaults to value of ``self.ceph_cluster_name``. + :type cluster_name: str + :raises: OSError + """ keyring_absolute_path = super().configure_ceph_keyring( key, cluster_name=cluster_name) + if self.ceph_service_type != self.CephServiceType.client: + return + # If the service is a client-type sevice (sych as RBD Mirror) add + # symlink to key in ``/etc/ceph``. symlink_absolute_path = os.path.join( '/etc/ceph', os.path.basename(keyring_absolute_path)) diff --git a/unit_tests/charms_openstack/plugins/test_classes.py b/unit_tests/charms_openstack/plugins/test_classes.py index 196f487..28ec141 100644 --- a/unit_tests/charms_openstack/plugins/test_classes.py +++ b/unit_tests/charms_openstack/plugins/test_classes.py @@ -14,9 +14,19 @@ TEST_CONFIG = {'config': True, class FakeOpenStackCephConsumingCharm( chm.OpenStackCharm, cpl.BaseOpenStackCephCharm): + abstract_class = True +class FakeCephCharm(cpl.CephCharm): + + abstract_class = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.hostname = 'somehost' + + class TestOpenStackCephConsumingCharm(BaseOpenStackCharmTest): def setUp(self): @@ -127,7 +137,7 @@ class TestOpenStackCephConsumingCharm(BaseOpenStackCharmTest): class TestCephCharm(BaseOpenStackCharmTest): def setUp(self): - super(TestCephCharm, self).setUp(cpl.CephCharm, {'source': None}) + super(TestCephCharm, self).setUp(FakeCephCharm, {'source': None}) def test_ceph_keyring_path(self): self.patch_object(cpl.ch_core.hookenv, 'application_name', @@ -140,6 +150,16 @@ class TestCephCharm(BaseOpenStackCharmTest): self.target.ceph_keyring_path, os.path.join(cpl.SNAP_PATH_PREFIX_FORMAT.format('gnocchi'), '/var/lib/ceph/charmname')) + self.target.snaps = [] + self.target.ceph_service_type = self.target.CephServiceType.mds + self.assertEqual( + self.target.ceph_keyring_path, + '/var/lib/ceph/charmname/ceph-somehost') + self.target.snaps = ['somecephsnap'] + self.assertEqual( + self.target.ceph_keyring_path, + os.path.join(cpl.SNAP_PATH_PREFIX_FORMAT.format('gnocchi'), + '/var/lib/ceph/charmname/ceph-somehost')) def test_configure_ceph_keyring(self): self.patch_object(cpl.os.path, 'isdir', return_value=False) @@ -154,6 +174,21 @@ class TestCephCharm(BaseOpenStackCharmTest): self.patch_object(cpl.os, 'readlink') self.patch_object(cpl.os, 'remove') self.readlink.side_effect = OSError + self.target.ceph_service_type = self.target.CephServiceType.mds + self.target.configure_ceph_keyring(key) + self.isdir.assert_called_with('/var/lib/ceph/sarepta/ceph-somehost') + self.mkdir.assert_called_with('/var/lib/ceph/sarepta/ceph-somehost', + owner='root', group='root', perms=0o750) + self.check_call.assert_called_with([ + 'ceph-authtool', + '/var/lib/ceph/sarepta/ceph-somehost/keyring', + '--create-keyring', '--name=sarepta', '--add-key', 'KEY', + '--mode', '0600', + ]) + self.exists.assert_not_called() + self.readlink.assert_not_called() + self.symlink.assert_not_called() + self.target.ceph_service_type = self.target.CephServiceType.client self.target.configure_ceph_keyring(key) self.isdir.assert_called_with('/var/lib/ceph/sarepta') self.mkdir.assert_called_with('/var/lib/ceph/sarepta', @@ -184,6 +219,11 @@ class TestCephCharm(BaseOpenStackCharmTest): self.target.delete_ceph_keyring() self.remove.assert_called_once_with( '/var/lib/ceph/sarepta/ceph.client.sarepta.keyring') + self.remove.reset_mock() + self.target.ceph_service_type = self.target.CephServiceType.mds + self.target.delete_ceph_keyring() + self.remove.assert_called_once_with( + '/var/lib/ceph/sarepta/ceph-somehost/keyring') def test_install(self): self.patch_object(cpl.subprocess, 'check_output', return_value=b'\n')