diff --git a/charms_openstack/plugins/trilio.py b/charms_openstack/plugins/trilio.py index b4a8103..285ca14 100644 --- a/charms_openstack/plugins/trilio.py +++ b/charms_openstack/plugins/trilio.py @@ -44,6 +44,12 @@ class GhostShareAlreadyMountedException(Exception): pass +class MismatchedConfigurationException(Exception): + """Signal that nfs-shares and ghost-shares are mismatched""" + + pass + + def _configure_triliovault_source(): """Configure triliovault specific package sources in addition to any general openstack package sources (via openstack-origin) @@ -155,15 +161,37 @@ class TrilioVaultCharmGhostAction(object): """base64 encode an backup endpoint for cross mounting support""" return base64.b64encode(backup_endpoint.encode()).decode() - def ghost_nfs_share(self, ghost_share): - """Bind mount the local units nfs share to another sites location + def ghost_nfs_share(self, ghost_shares): + """Bind mount local NFS shares to remote NFS paths + :param ghost_shares: Comma separated NFS shares URL to ghost + :type ghost_shares: str + """ + ghost_shares = ghost_shares.split(',') + nfs_shares = ch_core.hookenv.config("nfs-shares").split(',') + try: + share_mappings = [ + (nfs_shares[i], ghost_shares[i]) + for i in range(0, len(nfs_shares)) + ] + except IndexError: + raise MismatchedConfigurationException( + "ghost-shares and nfs-shares are different lengths" + ) + for local_share, ghost_share in share_mappings: + self._ghost_nfs_share(local_share, ghost_share) + + def _ghost_nfs_share(self, local_share, ghost_share): + """Bind mount a local unit NFS share to another sites location + + :param local_share: Local NFS share URL + :type local_share: str :param ghost_share: NFS share URL to ghost :type ghost_share: str """ nfs_share_path = os.path.join( TV_MOUNTS, - self._encode_endpoint(ch_core.hookenv.config("nfs-shares")) + self._encode_endpoint(local_share) ) ghost_share_path = os.path.join( TV_MOUNTS, self._encode_endpoint(ghost_share) @@ -174,8 +202,8 @@ class TrilioVaultCharmGhostAction(object): if nfs_share_path not in current_mounts: # Trilio has not mounted the NFS share so return raise NFSShareNotMountedException( - "nfs-shares ({}) not mounted".format( - ch_core.hookenv.config("nfs-shares") + "nfs-share ({}) not mounted".format( + local_share ) ) diff --git a/unit_tests/charms_openstack/plugins/test_trilio.py b/unit_tests/charms_openstack/plugins/test_trilio.py index 10080a1..4b7f6b5 100644 --- a/unit_tests/charms_openstack/plugins/test_trilio.py +++ b/unit_tests/charms_openstack/plugins/test_trilio.py @@ -1,3 +1,4 @@ +import unittest.mock as mock import os from unit_tests.charms_openstack.charm.utils import BaseOpenStackCharmTest @@ -22,8 +23,8 @@ class TrilioVaultFoobarSubordinate(trilio.TrilioVaultSubordinateCharm): class TestTrilioCharmGhostAction(BaseOpenStackCharmTest): - _nfs_shares = "10.20.30.40:/srv/trilioshare" - _ghost_shares = "50.20.30.40:/srv/trilioshare" + _nfs_share = "10.20.30.40:/srv/trilioshare" + _ghost_share = "50.20.30.40:/srv/trilioshare" def setUp(self): super().setUp(trilio.TrilioVaultCharmGhostAction, {}) @@ -36,46 +37,74 @@ class TestTrilioCharmGhostAction(BaseOpenStackCharmTest): self.trilio_charm = trilio.TrilioVaultCharmGhostAction() self._nfs_path = os.path.join( trilio.TV_MOUNTS, - self.trilio_charm._encode_endpoint(self._nfs_shares), + self.trilio_charm._encode_endpoint(self._nfs_share), ) self._ghost_path = os.path.join( trilio.TV_MOUNTS, - self.trilio_charm._encode_endpoint(self._ghost_shares), + self.trilio_charm._encode_endpoint(self._ghost_share), ) - def test_ghost_share(self): - self.config.return_value = self._nfs_shares + 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_shares], + [self._nfs_path, self._nfs_share], ] self.exists.return_value = False - self.trilio_charm.ghost_nfs_share(self._ghost_shares) + 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_share_already_bound(self): - self.config.return_value = self._nfs_shares + 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_shares], - [self._ghost_path, self._nfs_shares], + [self._nfs_path, self._nfs_share], + [self._ghost_path, self._nfs_share], ] with self.assertRaises(trilio.GhostShareAlreadyMountedException): - self.trilio_charm.ghost_nfs_share(self._ghost_shares) + self.trilio_charm._ghost_nfs_share(self._nfs_share, + self._ghost_share) self.mount.assert_not_called() - def test_ghost_share_nfs_unmounted(self): - self.config.return_value = self._nfs_shares + 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._ghost_shares) + 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):