From 055fb499b84c204809921f218d919f9cc4f4f3f6 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Mon, 13 Jun 2022 16:33:05 +0000 Subject: [PATCH] Support Trilio 4.2 Share hashes The Trilio 4.2 changes the way that hashes are generated for nfs mount points. The hash is now generated from the directory only, the source IP address is ignored. This patch updates the ghost-share action to accommodate that. Change-Id: I64a1cc95a3a78ce79d57f5b840edb29996f04f9c --- charms_openstack/plugins/__init__.py | 2 + charms_openstack/plugins/trilio.py | 56 +++++++++++- .../charms_openstack/plugins/test_trilio.py | 87 ++++++++++++++++++- 3 files changed, 140 insertions(+), 5 deletions(-) diff --git a/charms_openstack/plugins/__init__.py b/charms_openstack/plugins/__init__.py index 0b2a1c5..12b193d 100644 --- a/charms_openstack/plugins/__init__.py +++ b/charms_openstack/plugins/__init__.py @@ -25,6 +25,7 @@ from charms_openstack.plugins.trilio import ( TrilioVaultCharm, TrilioVaultSubordinateCharm, TrilioVaultCharmGhostAction, + TrilioVault42CharmGhostAction, ) __all__ = ( @@ -35,4 +36,5 @@ __all__ = ( "TrilioVaultCharm", "TrilioVaultSubordinateCharm", "TrilioVaultCharmGhostAction", + "TrilioVault42CharmGhostAction", ) diff --git a/charms_openstack/plugins/trilio.py b/charms_openstack/plugins/trilio.py index 59ac4c1..dffa61b 100644 --- a/charms_openstack/plugins/trilio.py +++ b/charms_openstack/plugins/trilio.py @@ -598,6 +598,20 @@ class TrilioVaultCharmGhostAction(object): for local_share, ghost_share in share_mappings: self._ghost_nfs_share(local_share, ghost_share) + def trilio_share_mounted(self, share_path): + """Check if share_path is mounted + + :param local_share: Local NFS share URL + :type local_share: str + :returns: Whether share is mounted + :rtype: bool + """ + _share_path = os.path.join( + TV_MOUNTS, + self._encode_endpoint(share_path)) + current_mounts = [mount[0] for mount in ch_core.host.mounts()] + return _share_path in current_mounts + def _ghost_nfs_share(self, local_share, ghost_share): """Bind mount a local unit NFS share to another sites location @@ -614,9 +628,7 @@ class TrilioVaultCharmGhostAction(object): TV_MOUNTS, self._encode_endpoint(ghost_share) ) - current_mounts = [mount[0] for mount in ch_core.host.mounts()] - - if nfs_share_path not in current_mounts: + if not self.trilio_share_mounted(local_share): # Trilio has not mounted the NFS share so return raise NFSShareNotMountedException( "nfs-share ({}) not mounted".format( @@ -624,7 +636,7 @@ class TrilioVaultCharmGhostAction(object): ) ) - if ghost_share_path in current_mounts: + if self.trilio_share_mounted(ghost_share): # bind mount already setup so return raise GhostShareAlreadyMountedException( "ghost mountpoint ({}) already bound".format(ghost_share_path) @@ -634,3 +646,39 @@ class TrilioVaultCharmGhostAction(object): os.mkdir(ghost_share_path) ch_core.host.mount(nfs_share_path, ghost_share_path, options="bind") + + +class TrilioVault42CharmGhostAction(TrilioVaultCharmGhostAction): + + def _encode_endpoint_uri(self, backup_endpoint): + """base64 encode a backup uri for cross mounting support""" + return base64.b64encode(backup_endpoint.encode()).decode() + + def _encode_endpoint_path(self, backup_endpoint): + """base64 encode an backup path for cross mounting support""" + return base64.b64encode( + str.encode(urlparse(backup_endpoint).path)).decode() + + def _encode_endpoint(self, backup_endpoint): + """base64 encode an backup endpoint for cross mounting support""" + return self._encode_endpoint_path(backup_endpoint) + + def trilio_share_mounted(self, share_path): + """Check if share_path is mounted + + :param local_share: Local NFS share URL + :type local_share: str + :returns: Whether share is mounted + :rtype: bool + """ + mount_paths = [ + os.path.join( + TV_MOUNTS, + self._encode_endpoint_path(share_path) + ), + os.path.join( + TV_MOUNTS, + self._encode_endpoint_uri(share_path) + )] + current_mounts = [mount[0] for mount in ch_core.host.mounts()] + return any(m in current_mounts for m in mount_paths) diff --git a/unit_tests/charms_openstack/plugins/test_trilio.py b/unit_tests/charms_openstack/plugins/test_trilio.py index 17ad1eb..142df95 100644 --- a/unit_tests/charms_openstack/plugins/test_trilio.py +++ b/unit_tests/charms_openstack/plugins/test_trilio.py @@ -30,7 +30,7 @@ class TrilioVaultFoobarSubordinate(trilio.TrilioVaultSubordinateCharm): class TestTrilioCharmGhostAction(BaseOpenStackCharmTest): _nfs_share = "10.20.30.40:/srv/trilioshare" - _ghost_share = "50.20.30.40:/srv/trilioshare" + _ghost_share = "50.20.30.40:/srv/trilioghostshare" def setUp(self): super().setUp(trilio.TrilioVaultCharmGhostAction, {}) @@ -112,6 +112,91 @@ class TestTrilioCharmGhostAction(BaseOpenStackCharmTest): ) +class TestTrilioVault42CharmGhostAction(BaseOpenStackCharmTest): + + _nfs_share = "10.20.30.40:/srv/trilioshare" + _ghost_share = "50.20.30.40:/srv/trilioghostshare" + + def setUp(self): + super().setUp(trilio.TrilioVaultCharmGhostAction, {}) + self.patch_object(trilio.ch_core.hookenv, "config") + self.patch_object(trilio.ch_core.host, "mounts") + self.patch_object(trilio.ch_core.host, "mount") + self.patch_object(trilio.os.path, "exists") + self.patch_object(trilio.os, "mkdir") + + self.trilio_charm = trilio.TrilioVault42CharmGhostAction() + self._nfs_path = os.path.join( + trilio.TV_MOUNTS, + self.trilio_charm._encode_endpoint(self._nfs_share), + ) + self._ghost_path = os.path.join( + trilio.TV_MOUNTS, + self.trilio_charm._encode_endpoint(self._ghost_share), + ) + + def test__ghost_nfs_share(self): + self.config.return_value = self._nfs_share + self.mounts.return_value = [ + ["/srv/nova", "/dev/sda"], + [self._nfs_path, self._nfs_share], + ] + self.exists.return_value = False + self.trilio_charm._ghost_nfs_share(self._nfs_share, + self._ghost_share) + self.exists.assert_called_once_with(self._ghost_path) + self.mkdir.assert_called_once_with(self._ghost_path) + self.mount.assert_called_once_with( + self._nfs_path, self._ghost_path, options="bind" + ) + + def test__ghost_nfs_share_already_bound(self): + self.config.return_value = self._nfs_share + self.mounts.return_value = [ + ["/srv/nova", "/dev/sda"], + [self._nfs_path, self._nfs_share], + [self._ghost_path, self._nfs_share], + ] + with self.assertRaises(trilio.GhostShareAlreadyMountedException): + self.trilio_charm._ghost_nfs_share(self._nfs_share, + self._ghost_share) + self.mount.assert_not_called() + + def test__ghost_nfs_share_nfs_unmounted(self): + self.config.return_value = self._nfs_share + self.mounts.return_value = [["/srv/nova", "/dev/sda"]] + self.exists.return_value = False + with self.assertRaises(trilio.NFSShareNotMountedException): + self.trilio_charm._ghost_nfs_share(self._nfs_share, + self._ghost_share) + self.mount.assert_not_called() + + def test_ghost_nfs_share(self): + self.patch_object(self.trilio_charm, "_ghost_nfs_share") + self.config.return_value = ( + "10.20.30.40:/srv/trilioshare,10.20.30.40:/srv/trilioshare2" + ) + self.trilio_charm.ghost_nfs_share( + "50.20.30.40:/srv/trilioshare,50.20.30.40:/srv/trilioshare2" + ) + self._ghost_nfs_share.assert_has_calls([ + mock.call("10.20.30.40:/srv/trilioshare", + "50.20.30.40:/srv/trilioshare"), + mock.call("10.20.30.40:/srv/trilioshare2", + "50.20.30.40:/srv/trilioshare2") + ]) + + def test_ghost_nfs_share_mismatch(self): + self.patch_object(self.trilio_charm, "_ghost_nfs_share") + self.config.return_value = ( + "10.20.30.40:/srv/trilioshare,10.20.30.40:/srv/trilioshare2" + ) + with self.assertRaises(trilio.MismatchedConfigurationException): + self.trilio_charm.ghost_nfs_share( + "50.20.30.40:/srv/trilioshare" + ) + + class TestTrilioCommonBehaviours(BaseOpenStackCharmTest): def setUp(self):