Add support for deleting a share

This commit is contained in:
Chris MacNaughton 2022-01-28 13:52:07 +01:00
parent bbc463f679
commit 95cb7c5839
5 changed files with 123 additions and 9 deletions

View File

@ -22,6 +22,17 @@ create-share:
Name of the share that will be exported.
type: string
default:
delete-share:
description: |
Delete a CephFS Backed NFS export. Note that this does not delete
the backing CephFS share.
params:
name:
description: |
Name of the share that will be deleted. If this share doesn't
exist then this action will have no effect.
type: string
default:
list-shares:
description: List all shares that this application is managing
# TODO: Update, delete share

View File

@ -188,6 +188,10 @@ class CephNfsCharm(
self.framework.observe(
self.on.list_shares_action,
self.list_shares_action)
self.framework.observe(
self.on.delete_share_action,
self.delete_share_action
)
def config_get(self, key, default=None):
"""Retrieve config option.
@ -362,6 +366,18 @@ class CephNfsCharm(
"exports": [{"id": export.export_id, "name": export.name} for export in exports]
})
def delete_share_action(self, event):
if not self.model.unit.is_leader():
event.fail("Share creation needs to be run from the application leader")
return
client = GaneshaNfs(self.client_name, self.pool_name)
name = event.params.get('name')
client.delete_share(name)
self.peers.trigger_reload()
event.set_results({
"message": "Share deleted",
})
@ops_openstack.core.charm_class
class CephNFSCharmOcto(CephNfsCharm):

View File

@ -47,6 +47,9 @@ EXPORT {{
class Export(object):
"""Object that encodes and decodes Ganesha export blocks"""
name = None
def __init__(self, export_options: Optional[Dict] = None):
if export_options is None:
export_options = {}
@ -104,16 +107,35 @@ class GaneshaNfs(object):
if size is not None:
size_in_bytes = size * 1024 * 1024
if access_ips is None:
access_ips = ['0.0.0.0/0']
access_ips = ['0.0.0.0']
# Ganesha deals with networks just fine, except when the network is
# 0.0.0.0/0, then it has to be 0.0.0.0 which works as expected :-/
if '0.0.0.0/0' in access_ips:
access_ips[access_ips.index('0.0.0.0/0')] = '0.0.0.0'
access_id = 'ganesha-{}'.format(name)
self.export_path = self._create_cephfs_share(name, size_in_bytes)
export_id = self._get_next_export_id()
export = Export(
export_id=export_id,
path=self.export_path,
user_id=access_id,
access_key=self._ceph_auth_key(access_id),
clients=access_ips
{
'EXPORT': {
'Export_Id': export_id,
'Path': self.export_path,
'FSAL': {
'Name': 'Ceph',
'User_Id': access_id,
'Secret_Access_Key': self._ceph_auth_key(access_id)
},
'Pseudo': self.export_path,
'Squash': 'None',
'CLIENT': [
{
'Access_Type': 'RW',
'Clients': ', '.join(access_ips),
}
]
}
}
)
export_template = export.to_export()
logging.debug("Export template::\n{}".format(export_template))
@ -141,6 +163,19 @@ class GaneshaNfs(object):
logging.warning("Encountered an independently created export")
return exports
def delete_share(self, name: str):
share = [share for share in self.list_shares() if share.name == name]
if share:
share = share[0]
else:
return
logging.info("About to remove export {} ({})".format(share.name, share.export_id))
self._ganesha_remove_export(share.export_id)
logging.debug("Removing export from index")
self._remove_share_from_index(share.export_id)
logging.debug("Removing export file from RADOS")
self._rados_rm('ganesha-export-{}'.format(share.export_id))
def get_share(self, id):
pass
@ -153,6 +188,13 @@ class GaneshaNfs(object):
'ExportMgr', 'AddExport',
'string:{}'.format(tmp_path), 'string:EXPORT(Path={})'.format(export_path))
def _ganesha_remove_export(self, share_id: int):
"""Remove a configured NFS export from Ganesha"""
self._dbus_send(
'ExportMgr',
'RemoveExport',
"uint16:{}".format(share_id))
def _dbus_send(self, section: str, action: str, *args):
"""Send a command to Ganesha via Dbus"""
cmd = [
@ -266,8 +308,44 @@ class GaneshaNfs(object):
logging.debug("About to call: {}".format(cmd))
subprocess.check_call(cmd)
def _rados_rm(self, name: str):
"""Remove a named RADOS object.
:param name: Name of the RADOS object to remove
:param source: Path to a file to upload to RADOS.
:returns: None
"""
cmd = [
'rados', '-p', self.ceph_pool, '--id', self.client_name,
'rm', name
]
logging.debug("About to call: {}".format(cmd))
subprocess.check_call(cmd)
def _add_share_to_index(self, export_id: int):
index = self._rados_get(self.export_index)
index += '\n%url rados://{}/ganesha-export-{}'.format(self.ceph_pool, export_id)
tmpfile = self._tmpfile(index)
"""Add an export RADOS object's URL to the RADOS URL index."""
index_data = self._rados_get(self.export_index)
url = '%url rados://{}/ganesha-export-{}'.format(self.ceph_pool, export_id)
rados_urls = index_data.split('\n')
if url not in rados_urls:
rados_urls.append(url)
tmpfile = self._tmpfile('\n'.join(rados_urls))
self._rados_put(self.export_index, tmpfile.name)
def _remove_share_from_index(self, export_id: int):
"""Remove an export RADOS object's URL from the RADOS URL index."""
index_data = self._rados_get(self.export_index)
if not index_data:
return
unwanted_url = "%url rados://{0}/{1}".format(
self.ceph_pool,
'ganesha-export-{}'.format(export_id))
logging.debug("Looking for '{}' in index".format(unwanted_url))
rados_urls = index_data.split('\n')
logging.debug("Index URLs: {}".format(rados_urls))
index = [url.strip() for url in rados_urls if url != unwanted_url]
logging.debug("Index URLs without unwanted: {}".format(index))
tmpfile = self._tmpfile('\n'.join(index))
self._rados_put(self.export_index, tmpfile.name)

0
tests/__init__.py Normal file
View File

View File

@ -28,6 +28,7 @@ class NfsGaneshaTest(unittest.TestCase):
mount_dir = '/mnt/test'
share_protocol = 'nfs'
mounts_share = False
created_share = None
def tearDown(self):
if self.mounts_share:
@ -40,6 +41,13 @@ class NfsGaneshaTest(unittest.TestCase):
cmd='sudo umount /mnt/test && sudo rmdir /mnt/test')
except subprocess.CalledProcessError:
logging.warning("Failed to cleanup mounts")
if self.created_share:
zaza.model.run_action_on_leader(
'ceph-nfs',
'delete-share',
action_params={
'name': self.created_share,
})
def _create_share(self, name: str, size: int = 10) -> Dict[str, str]:
action = zaza.model.run_action_on_leader(
@ -50,6 +58,7 @@ class NfsGaneshaTest(unittest.TestCase):
'size': size,
})
self.assertEqual(action.status, 'completed')
self.created_share = name
results = action.results
logging.debug("Action results: {}".format(results))
return results