Add action to support nfs share ghosting.

This commit is contained in:
James Page 2020-04-17 09:05:36 +01:00
parent 4a8a0a5025
commit 72429cac85
6 changed files with 190 additions and 3 deletions

8
src/actions.yaml Normal file
View File

@ -0,0 +1,8 @@
ghost-share:
description: "Bind mount NFS share 'host' for secondary Trilio Vault deployment"
properties:
nfs-shares:
type: string
description: Exact nfs-shares configuration option from secondary deployment
required:
- nfs-shares

64
src/actions/actions.py Executable file
View File

@ -0,0 +1,64 @@
#!/usr/local/sbin/charm-env python3
# Copyright 2018,2020 Canonical Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
# Load modules from $CHARM_DIR/lib
sys.path.append("lib")
from charms.layer import basic
basic.bootstrap_charm_deps()
basic.init_config_states()
import charmhelpers.core.hookenv as hookenv
import charms_openstack.charm
# import the trilio_wlm module to get the charm definitions created.
import charm.openstack.trilio_dm # noqa
def ghost_share(*args):
"""Ghost mount secondard TV deployment nfs-share
"""
secondary_nfs_share = hookenv.function_get("nfs-shares")
with charms_openstack.charm.provide_charm_instance() as trilio_wlm_charm:
trilio_wlm_charm.ghost_nfs_share(secondary_nfs_share)
# Actions to function mapping, to allow for illegal python action names that
# can map to a python function.
ACTIONS = {
"ghost-share": ghost_share,
}
def main(args):
action_name = os.path.basename(args[0])
try:
action = ACTIONS[action_name]
except KeyError:
return "Action %s undefined" % action_name
else:
try:
action(args)
except Exception as e:
hookenv.function_fail(str(e))
if __name__ == "__main__":
sys.exit(main(sys.argv))

1
src/actions/ghost-share Symbolic link
View File

@ -0,0 +1 @@
actions.py

View File

@ -14,8 +14,10 @@
import base64
import collections
import os
import charmhelpers.core.hookenv as hookenv
import charmhelpers.core.host as host
import charmhelpers.fetch as fetch
import charms_openstack.charm
@ -23,6 +25,19 @@ import charms_openstack.adapters as os_adapters
VALID_BACKUP_TARGETS = ["nfs"]
TV_MOUNTS = "/var/triliovault-mounts"
class NFSShareNotMountedException(Exception):
"""Signal that the trilio nfs share is not mount"""
pass
class GhostShareAlreadyMountedException(Exception):
"""Signal that a ghost share is already mounted"""
pass
class TrilioDataMoverCharm(charms_openstack.charm.OpenStackCharm):
@ -77,10 +92,45 @@ class TrilioDataMoverCharm(charms_openstack.charm.OpenStackCharm):
def restart_map(self):
return {self.data_mover_conf: self.services}
def install(self):
self.configure_source()
super().install()
def _encode_endpoint(self, backup_endpoint):
"""base64 encode an backup endpoint for cross mounting support"""
return base64.b64encode(backup_endpoint.encode()).decode()
def install(self):
self.configure_source()
super().install()
# TODO: refactor into a layer/module
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(hookenv.config("nfs-shares"))
)
ghost_share_path = os.path.join(
TV_MOUNTS, self._encode_endpoint(ghost_share)
)
current_mounts = [mount[0] for mount in 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(
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)
host.mount(nfs_share_path, ghost_share_path, options="bind")

View File

@ -50,6 +50,11 @@ basepython = python3.7
deps = -r{toxinidir}/test-requirements.txt
commands = stestr run --slowest {posargs}
[testenv:py38]
basepython = python3.8
deps = -r{toxinidir}/test-requirements.txt
commands = stestr run --slowest {posargs}
[testenv:pep8]
basepython = python3
deps = -r{toxinidir}/test-requirements.txt

View File

@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import charm.openstack.trilio_dm as trilio_dm
import charms_openstack.test_utils as test_utils
@ -51,3 +52,61 @@ class TestTrilioDataMoverCharms(Helper):
self.assertEqual(
dm_charm.services, ["tvault-contego", "tvault-object-store"]
)
# TODO: refactor into a layer/module
class TestTrilioDataMoverCharmGhostShareAction(Helper):
_nfs_shares = "10.20.30.40:/srv/trilioshare"
_ghost_shares = "50.20.30.40:/srv/trilioshare"
def setUp(self):
super().setUp()
self.patch_object(trilio_dm.hookenv, "config")
self.patch_object(trilio_dm.host, "mounts")
self.patch_object(trilio_dm.host, "mount")
self.patch_object(trilio_dm.os.path, "exists")
self.patch_object(trilio_dm.os, "mkdir")
self.trilio_wlm_charm = trilio_dm.TrilioDataMoverCharm()
self._nfs_path = os.path.join(
trilio_dm.TV_MOUNTS,
self.trilio_wlm_charm._encode_endpoint(self._nfs_shares),
)
self._ghost_path = os.path.join(
trilio_dm.TV_MOUNTS,
self.trilio_wlm_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_wlm_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(trilio_dm.GhostShareAlreadyMountedException):
self.trilio_wlm_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(trilio_dm.NFSShareNotMountedException):
self.trilio_wlm_charm.ghost_nfs_share(self._ghost_shares)
self.mount.assert_not_called()