]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/nfs: Add parameter to nfs cluster create to pass ingress placement 66136/head
authorShweta Bhosale <Shweta.Bhosale1@ibm.com>
Tue, 4 Nov 2025 15:18:34 +0000 (20:48 +0530)
committerShweta Bhosale <Shweta.Bhosale1@ibm.com>
Mon, 6 Jul 2026 14:21:25 +0000 (19:51 +0530)
Fixes: https://tracker.ceph.com/issues/73718
Signed-off-by: Shweta Bhosale <Shweta.Bhosale1@ibm.com>
doc/mgr/nfs.rst
src/pybind/mgr/nfs/cluster.py
src/pybind/mgr/nfs/module.py
src/pybind/mgr/nfs/tests/test_nfs.py

index 4ef7a9581b909803fa10429cd43e5c88c955ddad..bf2005ec69541cc266ff1700421775a23cad0adb 100644 (file)
@@ -31,7 +31,7 @@ Create NFS Ganesha Cluster
 
 .. prompt:: bash #
 
-   ceph nfs cluster create <cluster_id> [<placement>] [--ingress] [--virtual_ip <value>] [--ingress-mode {default|keepalive-only|haproxy-standard|haproxy-protocol}] [--port <int>] [--enable-rdma] [--rdma_port <int>] [-i <spec_file>] [--enable-nfsv3]
+   ceph nfs cluster create <cluster_id> [<placement>] [--ingress] [--virtual_ip <value>] [--ingress-mode {default|keepalive-only|haproxy-standard|haproxy-protocol}] [--ingress-placement <placement>] [--port <int>] [--enable-rdma] [--rdma_port <int>] [--enable-nfsv3] [-i <spec_file>]
 
 This creates a common recovery pool for all NFS Ganesha daemons, new user based on
 ``cluster_id``, and a common NFS Ganesha config RADOS object.
@@ -111,6 +111,18 @@ of the details of NFS redirecting traffic on the virtual IP to the
 appropriate backend NFS servers, and redeploying NFS servers when they
 fail.
 
+By default, the *ingress* service follows the same placement as the NFS
+Ganesha daemons (the optional ``<placement>`` argument).  To schedule
+keepalived and HAProxy on a different set of hosts, pass
+``--ingress-placement`` with a separate placement string.  For example,
+to run three NFS daemons on ``host1`` and ``host2`` while colocating
+ingress on three dedicated nodes::
+
+    ceph nfs cluster create mynfs "2 host1 host2" --ingress --virtual_ip 192.168.1.100/24 --ingress-placement "3 host3 host4 host5"
+
+If ``--ingress-placement`` is omitted, both services share the NFS
+placement.
+
 An optional ``--ingress-mode`` parameter can be provided to choose
 how the *ingress* service is configured:
 
index d67ca4cb1e3b8fdc8a5242961a7dfff3fcbd005c..f022cd69ed155d4728f1c908d668e915ed7fb800 100644 (file)
@@ -172,6 +172,7 @@ class NFSCluster:
             tls_ciphers: Optional[str] = None,
             enable_rdma: bool = False,
             rdma_port: Optional[int] = None,
+            ingress_placement: Optional[str] = None,
     ) -> None:
         if not port:
             port = 2049   # default nfs port
@@ -182,9 +183,12 @@ class NFSCluster:
                 ingress_mode = IngressType.default
             ingress_mode = ingress_mode.canonicalize()
             pspec = PlacementSpec.from_string(placement)
+            ingress_pspec = PlacementSpec.from_string(ingress_placement) if ingress_placement else None
             if ingress_mode == IngressType.keepalive_only:
                 # enforce count=1 for nfs over keepalive only
                 pspec.count = 1
+                if ingress_pspec:
+                    ingress_pspec.count = 1
 
             ganesha_port = 10000 + port  # semi-arbitrary, fix me someday
             frontend_port: Optional[int] = port
@@ -223,7 +227,7 @@ class NFSCluster:
             ispec = IngressSpec(service_type='ingress',
                                 service_id='nfs.' + cluster_id,
                                 backend_service='nfs.' + cluster_id,
-                                placement=pspec,
+                                placement=ingress_pspec or pspec,
                                 frontend_port=frontend_port,
                                 monitor_port=7000 + port,   # semi-arbitrary, fix me someday
                                 virtual_ip=virtual_ip,
@@ -283,6 +287,7 @@ class NFSCluster:
             tls_ciphers: Optional[str] = None,
             enable_rdma: bool = False,
             rdma_port: Optional[int] = None,
+            ingress_placement: Optional[str] = None,
     ) -> None:
         try:
             if virtual_ip:
@@ -323,7 +328,8 @@ class NFSCluster:
                     tls_min_version=tls_min_version,
                     tls_ciphers=tls_ciphers,
                     enable_rdma=enable_rdma,
-                    rdma_port=rdma_port
+                    rdma_port=rdma_port,
+                    ingress_placement=ingress_placement
                 )
                 return
             raise NonFatalError(f"{cluster_id} cluster already exists")
index 0f4ed118d94da39e39b6c8e1bdd3f247094a03b7..79549038cdef5092b5e56a5ee15e456e36011c42 100644 (file)
@@ -165,6 +165,7 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
                                 enable_rdma: bool = False,
                                 rdma_port: Optional[int] = None,
                                 enable_nfsv3: bool = False,
+                                ingress_placement: Optional[str] = None,
                                 inbuf: Optional[str] = None) -> None:
         """Create an NFS Cluster"""
         cluster_qos_config = None
@@ -196,7 +197,8 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
                                            tls_min_version=tls_min_version,
                                            tls_ciphers=tls_ciphers,
                                            enable_rdma=enable_rdma,
-                                           rdma_port=rdma_port)
+                                           rdma_port=rdma_port,
+                                           ingress_placement=ingress_placement)
 
     @NFSCLICommand('nfs cluster rm', perm='rw')
     @object_format.EmptyResponder()
index 0349e3e905ba176fe271a39baa67a4e8d57b7efa..d1b6c7d6195c98dff75b16e9c17a06abe71ca330 100644 (file)
@@ -11,7 +11,7 @@ from mgr_module import MgrModule, NFS_POOL_NAME
 
 from rados import ObjectNotFound
 
-from ceph.deployment.service_spec import NFSServiceSpec
+from ceph.deployment.service_spec import NFSServiceSpec, PlacementSpec
 from ceph.utils import with_units_to_int, bytes_to_human
 from nfs import Module
 from nfs.export import ExportMgr, normalize_path
@@ -1798,6 +1798,30 @@ EXPORT {
         self._do_mock_test(self._do_test_export_qos_bw_ops, qos_type, clust_bw_params, clust_ops_params, export_bw_params, export_ops_params)
 
 
+class TestNFSClusterIngressPlacement:
+    cluster_id = 'mynfs'
+    virtual_ip = '192.168.1.100/24'
+    nfs_placement = '2 host1 host2'
+    ingress_placement = '3 host3 host4 host5'
+
+    def test_create_nfs_cluster_passes_ingress_placement(self):
+        mgr = MagicMock()
+        cluster = NFSCluster(mgr)
+        with mock.patch('nfs.cluster.create_ganesha_pool'), \
+                mock.patch.object(cluster, 'create_empty_rados_obj'), \
+                mock.patch('nfs.cluster.available_clusters', return_value=[]), \
+                mock.patch.object(cluster, '_call_orch_apply_nfs') as mock_apply:
+            cluster.create_nfs_cluster(
+                cluster_id=self.cluster_id,
+                placement='host1',
+                virtual_ip=self.virtual_ip,
+                ingress=True,
+                ingress_placement=self.ingress_placement,
+            )
+            mock_apply.assert_called_once()
+            assert mock_apply.call_args.kwargs['ingress_placement'] == self.ingress_placement
+
+
 @pytest.mark.parametrize(
     "path,expected",
     [