Add TrilioVault charm classes
Add two helper classes to support common behaviour across the TrilioVault charm set. Change-Id: Ibe6c920f577326dfe0f6c3339de69a3cc060eb36
This commit is contained in:
parent
f0a486c59d
commit
037ea8bdf7
|
@ -12,6 +12,7 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import base64
|
||||
import collections
|
||||
import os
|
||||
import shutil
|
||||
|
@ -22,8 +23,11 @@ import charms_openstack.charm
|
|||
from charms_openstack.charm.classes import SNAP_PATH_PREFIX_FORMAT
|
||||
|
||||
import charmhelpers.core as ch_core
|
||||
import charmhelpers.fetch as fetch
|
||||
import charmhelpers.contrib.openstack.policyd as ch_policyd
|
||||
|
||||
import charms.reactive as reactive
|
||||
|
||||
|
||||
class BaseOpenStackCephCharm(object):
|
||||
"""Base class for Ceph classes.
|
||||
|
@ -408,3 +412,134 @@ class PolicydOverridePlugin(object):
|
|||
args, kwargs = self._policyd_function_args()
|
||||
ch_policyd.maybe_do_policyd_overrides_on_config_changed(
|
||||
*args, **kwargs)
|
||||
|
||||
|
||||
TV_MOUNTS = "/var/triliovault-mounts"
|
||||
|
||||
|
||||
class NFSShareNotMountedException(Exception):
|
||||
"""Signal that the trilio nfs share is not mount"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class UnitNotLeaderException(Exception):
|
||||
"""Signal that the unit is not the application leader"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class GhostShareAlreadyMountedException(Exception):
|
||||
"""Signal that a ghost share is already mounted"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class TrilioVaultCharm(charms_openstack.charm.HAOpenStackCharm):
|
||||
"""The TrilioVaultCharm class provides common specialisation of certain
|
||||
functions for the Trilio charm set and is designed for use alongside
|
||||
other base charms.openstack classes
|
||||
"""
|
||||
|
||||
abstract_class = True
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(TrilioVaultCharm, self).__init__(**kwargs)
|
||||
|
||||
def configure_source(self):
|
||||
"""Configure triliovault specific package sources in addition to
|
||||
any general openstack package sources (via openstack-origin)
|
||||
"""
|
||||
with open(
|
||||
"/etc/apt/sources.list.d/trilio-gemfury-sources.list", "w"
|
||||
) as tsources:
|
||||
tsources.write(ch_core.hookenv.config("triliovault-pkg-source"))
|
||||
super().configure_source()
|
||||
|
||||
def install(self):
|
||||
"""Install packages dealing with Trilio nuances for upgrades as well
|
||||
|
||||
Set the 'upgrade.triliovault' flag to ensure that any triliovault
|
||||
packages are upgraded.
|
||||
"""
|
||||
self.configure_source()
|
||||
packages = self.all_packages
|
||||
if not reactive.is_flag_set("upgrade.triliovault"):
|
||||
packages = fetch.filter_installed_packages(
|
||||
self.all_packages)
|
||||
|
||||
if packages:
|
||||
ch_core.hookenv.status_set('maintenance',
|
||||
'Installing/upgrading packages')
|
||||
fetch.apt_install(packages, fatal=True)
|
||||
|
||||
# AJK: we set this as charms can use it to detect installed state
|
||||
self.set_state('{}-installed'.format(self.name))
|
||||
self.update_api_ports()
|
||||
|
||||
# NOTE(jamespage): clear upgrade flag if set
|
||||
if reactive.is_flag_set("upgrade.triliovault"):
|
||||
reactive.clear_flag('upgrade.triliovault')
|
||||
|
||||
def series_upgrade_complete(self):
|
||||
"""Re-configure sources post series upgrade"""
|
||||
super().series_upgrade_complete()
|
||||
self.configure_source()
|
||||
|
||||
def _encode_endpoint(self, backup_endpoint):
|
||||
"""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
|
||||
|
||||
: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"))
|
||||
)
|
||||
ghost_share_path = os.path.join(
|
||||
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:
|
||||
# Trilio has not mounted the NFS share so return
|
||||
raise NFSShareNotMountedException(
|
||||
"nfs-shares ({}) not mounted".format(
|
||||
ch_core.hookenv.config("nfs-shares")
|
||||
)
|
||||
)
|
||||
|
||||
if ghost_share_path in current_mounts:
|
||||
# bind mount already setup so return
|
||||
raise GhostShareAlreadyMountedException(
|
||||
"ghost mountpoint ({}) already bound".format(ghost_share_path)
|
||||
)
|
||||
|
||||
if not os.path.exists(ghost_share_path):
|
||||
os.mkdir(ghost_share_path)
|
||||
|
||||
ch_core.host.mount(nfs_share_path, ghost_share_path, options="bind")
|
||||
|
||||
|
||||
class TrilioVaultSubordinateCharm(TrilioVaultCharm):
|
||||
"""The TrilioVaultSubordinateCharm class provides common specialisation
|
||||
of certain functions for the Trilio charm set and is designed for usei
|
||||
alongside other base charms.openstack classes for subordinate charms
|
||||
"""
|
||||
|
||||
abstract_class = True
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(TrilioVaultSubordinateCharm, self).__init__(**kwargs)
|
||||
|
||||
def configure_source(self):
|
||||
"""Configure triliovault specific package sources in addition to
|
||||
any general openstack package sources (via openstack-origin)
|
||||
"""
|
||||
super().configure_source()
|
||||
fetch.apt_update(fatal=True)
|
||||
|
|
|
@ -3,10 +3,12 @@ import os
|
|||
import subprocess
|
||||
|
||||
from unit_tests.charms_openstack.charm.utils import BaseOpenStackCharmTest
|
||||
from unit_tests.utils import patch_open
|
||||
|
||||
import charms_openstack.charm.classes as chm
|
||||
import charms_openstack.plugins.classes as cpl
|
||||
|
||||
|
||||
TEST_CONFIG = {'config': True,
|
||||
'openstack-origin': None}
|
||||
|
||||
|
@ -289,3 +291,137 @@ class TestPolicydOverridePlugin(BaseOpenStackCharmTest):
|
|||
self._policyd_function_args.assert_called_once_with()
|
||||
self.mock_policyd_call.assert_called_once_with(
|
||||
"args", kwargs=1)
|
||||
|
||||
|
||||
class TestTrilioCharmGhostShareAction(BaseOpenStackCharmTest):
|
||||
|
||||
_nfs_shares = "10.20.30.40:/srv/trilioshare"
|
||||
_ghost_shares = "50.20.30.40:/srv/trilioshare"
|
||||
|
||||
def setUp(self):
|
||||
super().setUp(cpl.TrilioVaultCharm, {})
|
||||
self.patch_object(cpl.ch_core.hookenv, "config")
|
||||
self.patch_object(cpl.ch_core.host, "mounts")
|
||||
self.patch_object(cpl.ch_core.host, "mount")
|
||||
self.patch_object(cpl.os.path, "exists")
|
||||
self.patch_object(cpl.os, "mkdir")
|
||||
|
||||
self.trilio_charm = cpl.TrilioVaultCharm()
|
||||
self._nfs_path = os.path.join(
|
||||
cpl.TV_MOUNTS,
|
||||
self.trilio_charm._encode_endpoint(self._nfs_shares),
|
||||
)
|
||||
self._ghost_path = os.path.join(
|
||||
cpl.TV_MOUNTS,
|
||||
self.trilio_charm._encode_endpoint(self._ghost_shares),
|
||||
)
|
||||
|
||||
def test_ghost_share(self):
|
||||
self.config.return_value = self._nfs_shares
|
||||
self.mounts.return_value = [
|
||||
["/srv/nova", "/dev/sda"],
|
||||
[self._nfs_path, self._nfs_shares],
|
||||
]
|
||||
self.exists.return_value = False
|
||||
self.trilio_charm.ghost_nfs_share(self._ghost_shares)
|
||||
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
|
||||
self.mounts.return_value = [
|
||||
["/srv/nova", "/dev/sda"],
|
||||
[self._nfs_path, self._nfs_shares],
|
||||
[self._ghost_path, self._nfs_shares],
|
||||
]
|
||||
with self.assertRaises(cpl.GhostShareAlreadyMountedException):
|
||||
self.trilio_charm.ghost_nfs_share(self._ghost_shares)
|
||||
self.mount.assert_not_called()
|
||||
|
||||
def test_ghost_share_nfs_unmounted(self):
|
||||
self.config.return_value = self._nfs_shares
|
||||
self.mounts.return_value = [["/srv/nova", "/dev/sda"]]
|
||||
self.exists.return_value = False
|
||||
with self.assertRaises(cpl.NFSShareNotMountedException):
|
||||
self.trilio_charm.ghost_nfs_share(self._ghost_shares)
|
||||
self.mount.assert_not_called()
|
||||
|
||||
|
||||
class TrilioVaultFoobar(cpl.TrilioVaultCharm):
|
||||
|
||||
abstract_class = True
|
||||
name = 'test'
|
||||
all_packages = ['foo', 'bar']
|
||||
|
||||
|
||||
class TestTrilioCharmBehaviours(BaseOpenStackCharmTest):
|
||||
|
||||
def setUp(self):
|
||||
super().setUp(TrilioVaultFoobar, {})
|
||||
self.patch_object(cpl.ch_core.hookenv, "config")
|
||||
self.patch_object(cpl.ch_core.hookenv, "status_set")
|
||||
self.patch_object(cpl.fetch, "filter_installed_packages")
|
||||
self.patch_object(cpl.fetch, "apt_install")
|
||||
self.patch_object(cpl.reactive, "is_flag_set")
|
||||
self.patch_object(cpl.reactive, "clear_flag")
|
||||
self.patch_target('update_api_ports')
|
||||
self.patch_target('set_state')
|
||||
self.filter_installed_packages.side_effect = lambda p: p
|
||||
|
||||
def test_install(self):
|
||||
self.patch_target('configure_source')
|
||||
self.is_flag_set.return_value = False
|
||||
|
||||
self.target.install()
|
||||
|
||||
self.is_flag_set.assert_called_with('upgrade.triliovault')
|
||||
self.filter_installed_packages.assert_called_once_with(
|
||||
self.target.all_packages
|
||||
)
|
||||
self.apt_install.assert_called_once_with(
|
||||
self.target.all_packages,
|
||||
fatal=True
|
||||
)
|
||||
self.clear_flag.assert_not_called()
|
||||
self.set_state.assert_called_once_with('test-installed')
|
||||
self.update_api_ports.assert_called_once()
|
||||
self.configure_source.assert_called_once_with()
|
||||
|
||||
def test_upgrade(self):
|
||||
self.patch_target('configure_source')
|
||||
self.is_flag_set.return_value = True
|
||||
|
||||
self.target.install()
|
||||
|
||||
self.is_flag_set.assert_called_with('upgrade.triliovault')
|
||||
self.filter_installed_packages.assert_not_called()
|
||||
self.apt_install.assert_called_once_with(
|
||||
self.target.all_packages,
|
||||
fatal=True
|
||||
)
|
||||
self.clear_flag.assert_called_once_with('upgrade.triliovault')
|
||||
self.set_state.assert_called_once_with('test-installed')
|
||||
self.update_api_ports.assert_called_once()
|
||||
self.configure_source.assert_called_once_with()
|
||||
|
||||
def test_configure_source(self):
|
||||
self.config.return_value = 'testsource'
|
||||
self.patch_object(cpl.charms_openstack.charm.HAOpenStackCharm,
|
||||
'configure_source')
|
||||
with patch_open() as (_open, _file):
|
||||
self.target.configure_source()
|
||||
_open.assert_called_with(
|
||||
"/etc/apt/sources.list.d/trilio-gemfury-sources.list",
|
||||
"w"
|
||||
)
|
||||
_file.write.assert_called_once_with('testsource')
|
||||
|
||||
def test_series_upgrade_complete(self):
|
||||
self.patch_object(cpl.charms_openstack.charm.HAOpenStackCharm,
|
||||
'series_upgrade_complete')
|
||||
self.patch_target('configure_source')
|
||||
self.target.series_upgrade_complete()
|
||||
self.configure_source.assert_called_once_with()
|
||||
|
|
Loading…
Reference in New Issue