From 2ab0103e8221fcb437763859fb11967ffdb025bf Mon Sep 17 00:00:00 2001 From: Avan Thakkar Date: Wed, 11 Sep 2024 16:19:41 +0530 Subject: [PATCH] mgr/nfs: earmark resolver for subvolume Signed-off-by: Avan Thakkar --- src/pybind/mgr/nfs/export.py | 54 ++++++++++++++++++++++++++++++------ src/pybind/mgr/nfs/module.py | 9 ++++-- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/pybind/mgr/nfs/export.py b/src/pybind/mgr/nfs/export.py index 3ba75e60b5c..aff6779bb16 100644 --- a/src/pybind/mgr/nfs/export.py +++ b/src/pybind/mgr/nfs/export.py @@ -13,8 +13,10 @@ from typing import ( Set, cast) from os.path import normpath +from ceph.fs.earmarking import EarmarkTopScope import cephfs +from mgr_util import CephFSEarmarkResolver from rados import TimedOut, ObjectNotFound, Rados from object_format import ErrorResponse @@ -535,7 +537,8 @@ class ExportMgr: # This method is used by the dashboard module (../dashboard/controllers/nfs.py) # Do not change interface without updating the Dashboard code - def apply_export(self, cluster_id: str, export_config: str) -> AppliedExportResults: + def apply_export(self, cluster_id: str, export_config: str, + earmark_resolver: Optional[CephFSEarmarkResolver] = None) -> AppliedExportResults: try: exports = self._read_export_config(cluster_id, export_config) except Exception as e: @@ -544,7 +547,7 @@ class ExportMgr: aeresults = AppliedExportResults() for export in exports: - changed_export = self._change_export(cluster_id, export) + changed_export = self._change_export(cluster_id, export, earmark_resolver) # This will help figure out which export blocks in conf/json file # are problematic. if changed_export.get("state", "") == "error": @@ -573,9 +576,10 @@ class ExportMgr: return j # j is already a list object return [j] # return a single object list, with j as the only item - def _change_export(self, cluster_id: str, export: Dict) -> Dict[str, Any]: + def _change_export(self, cluster_id: str, export: Dict, + earmark_resolver: Optional[CephFSEarmarkResolver] = None) -> Dict[str, Any]: try: - return self._apply_export(cluster_id, export) + return self._apply_export(cluster_id, export, earmark_resolver) except NotImplementedError: # in theory, the NotImplementedError here may be raised by a hook back to # an orchestration module. If the orchestration module supports it the NFS @@ -651,10 +655,34 @@ class ExportMgr: log.info(f"Export user created is {json_res[0]['entity']}") return json_res[0]['key'] + def _check_earmark(self, earmark_resolver: CephFSEarmarkResolver, path: str, + fs_name: str) -> None: + earmark = earmark_resolver.get_earmark( + path, + fs_name, + ) + if not earmark: + earmark_resolver.set_earmark( + path, + fs_name, + EarmarkTopScope.NFS.value, + ) + else: + if not earmark_resolver.check_earmark( + earmark, EarmarkTopScope.NFS + ): + raise NFSException( + 'earmark has already been set by ' + earmark.split('.')[0], + -errno.EAGAIN + ) + return None + def create_export_from_dict(self, cluster_id: str, ex_id: int, - ex_dict: Dict[str, Any]) -> Export: + ex_dict: Dict[str, Any], + earmark_resolver: Optional[CephFSEarmarkResolver] = None + ) -> Export: pseudo_path = ex_dict.get("pseudo") if not pseudo_path: raise NFSInvalidOperation("export must specify pseudo path") @@ -677,6 +705,11 @@ class ExportMgr: raise FSNotFound(fs_name) validate_cephfs_path(self.mgr, fs_name, path) + + # Check if earmark is set for the path, given path is of subvolume + if earmark_resolver: + self._check_earmark(earmark_resolver, path, fs_name) + if fsal["cmount_path"] != "/": _validate_cmount_path(fsal["cmount_path"], path) # type: ignore @@ -707,7 +740,9 @@ class ExportMgr: access_type: str, clients: list = [], sectype: Optional[List[str]] = None, - cmount_path: Optional[str] = "/") -> Dict[str, Any]: + cmount_path: Optional[str] = "/", + earmark_resolver: Optional[CephFSEarmarkResolver] = None + ) -> Dict[str, Any]: validate_cephfs_path(self.mgr, fs_name, path) if cmount_path != "/": @@ -731,7 +766,8 @@ class ExportMgr: }, "clients": clients, "sectype": sectype, - } + }, + earmark_resolver ) log.debug("creating cephfs export %s", export) self._ensure_cephfs_export_user(export) @@ -795,6 +831,7 @@ class ExportMgr: self, cluster_id: str, new_export_dict: Dict, + earmark_resolver: Optional[CephFSEarmarkResolver] = None ) -> Dict[str, str]: for k in ['path', 'pseudo']: if k not in new_export_dict: @@ -834,7 +871,8 @@ class ExportMgr: new_export = self.create_export_from_dict( cluster_id, new_export_dict.get('export_id', self._gen_export_id(cluster_id)), - new_export_dict + new_export_dict, + earmark_resolver ) if not old_export: diff --git a/src/pybind/mgr/nfs/module.py b/src/pybind/mgr/nfs/module.py index be43112f396..80490ac8e7f 100644 --- a/src/pybind/mgr/nfs/module.py +++ b/src/pybind/mgr/nfs/module.py @@ -6,6 +6,7 @@ from mgr_module import MgrModule, CLICommand, Option, CLICheckNonemptyFileInput import object_format import orchestrator from orchestrator.module import IngressType +from mgr_util import CephFSEarmarkResolver from .export import ExportMgr, AppliedExportResults from .cluster import NFSCluster @@ -41,6 +42,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): cmount_path: Optional[str] = "/" ) -> Dict[str, Any]: """Create a CephFS export""" + earmark_resolver = CephFSEarmarkResolver(self) return self.export_mgr.create_export( fsal_type='cephfs', fs_name=fsname, @@ -51,7 +53,8 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): squash=squash, addr=client_addr, sectype=sectype, - cmount_path=cmount_path + cmount_path=cmount_path, + earmark_resolver=earmark_resolver ) @CLICommand('nfs export create rgw', perm='rw') @@ -114,8 +117,10 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule): @CLICheckNonemptyFileInput(desc='Export JSON or Ganesha EXPORT specification') @object_format.Responder() def _cmd_nfs_export_apply(self, cluster_id: str, inbuf: str) -> AppliedExportResults: + earmark_resolver = CephFSEarmarkResolver(self) """Create or update an export by `-i `""" - return self.export_mgr.apply_export(cluster_id, export_config=inbuf) + return self.export_mgr.apply_export(cluster_id, export_config=inbuf, + earmark_resolver=earmark_resolver) @CLICommand('nfs cluster create', perm='rw') @object_format.EmptyResponder() -- 2.39.5