]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/nfs: earmark resolver for subvolume
authorAvan Thakkar <athakkar@redhat.com>
Wed, 11 Sep 2024 10:49:41 +0000 (16:19 +0530)
committerAvan Thakkar <athakkar@redhat.com>
Wed, 25 Sep 2024 12:06:01 +0000 (17:36 +0530)
Signed-off-by: Avan Thakkar <athakkar@redhat.com>
src/pybind/mgr/nfs/export.py
src/pybind/mgr/nfs/module.py

index 3ba75e60b5cfa610e9e3dddf51851464826fe5e1..aff6779bb16e4c11e502c31837723aff609192cc 100644 (file)
@@ -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:
index be43112f39600cb369721b9e2c5e4ef845a67c7c..80490ac8e7fe5f0195cf526a81181f83a397c51b 100644 (file)
@@ -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 <json_or_ganesha_export_file>`"""
-        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()