]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: Scale down ingress service only when the backend NFS service is scaled...
authorShweta Bhosale <Shweta.Bhosale1@ibm.com>
Tue, 28 Apr 2026 16:08:22 +0000 (21:38 +0530)
committerShweta Bhosale <Shweta.Bhosale1@ibm.com>
Tue, 30 Jun 2026 12:52:11 +0000 (18:22 +0530)
When scaling ingress down with a ranked NFS backend that still has more
daemons than its placement target, remove ingress from hosts without NFS
first and skip removing co-located ingress on NFS hosts until the next
iteration after NFS scales down.

Fixes: https://tracker.ceph.com/issues/76304
Signed-off-by: Shweta Bhosale <Shweta.Bhosale1@ibm.com>
src/pybind/mgr/cephadm/schedule.py
src/pybind/mgr/cephadm/serve.py
src/pybind/mgr/cephadm/tests/test_scheduling.py

index d2846a853cd77fed3943bfbcbec48c5e8baedf51..b4f9570a3eecf31c4b79dd18806828353d7d5e70 100644 (file)
@@ -219,6 +219,7 @@ class HostAssignment(object):
                  draining_hosts: List[orchestrator.HostSpec],
                  daemons: List[orchestrator.DaemonDescription],
                  related_service_daemons: Optional[List[DaemonDescription]] = None,
+                 related_service_required_count: Optional[int] = None,
                  networks: Dict[str, Dict[str, Dict[str, List[str]]]] = {},
                  filter_new_host: Optional[Callable[[str, ServiceSpec], bool]] = None,
                  allow_colo: bool = False,
@@ -240,6 +241,7 @@ class HostAssignment(object):
         self.service_name = spec.service_name()
         self.daemons = daemons
         self.related_service_daemons = related_service_daemons
+        self.related_service_required_count = related_service_required_count
         self.networks = networks
         self.allow_colo = allow_colo
         self.per_host_daemon_type = per_host_daemon_type
@@ -473,8 +475,27 @@ class HostAssignment(object):
                     # If we still need to remove more, remove from hosts with related services
                     remaining_needed = total_excess - len(to_delete)
                     if remaining_needed > 0:
-                        remaining = [dd for dd in existing if dd not in to_delete]
-                        to_delete.extend(remaining[count:])
+                        # Restrict defer-removal behavior to ingress only.
+                        should_defer_related_removal = False
+                        if self.spec.service_type == 'ingress':
+                            related_service_count = len(self.related_service_daemons)
+                            related_service_required_count = (
+                                self.related_service_required_count or count
+                            )
+                            related_service_is_ranked = any(
+                                dd.rank is not None
+                                for dd in self.related_service_daemons
+                            )
+                            # For ingress with ranked backends (e.g. NFS), if backend
+                            # scale-down is still pending, defer deleting co-located
+                            # ingress daemons until a later iteration.
+                            should_defer_related_removal = (
+                                related_service_is_ranked
+                                and related_service_count > related_service_required_count
+                            )
+                        if not should_defer_related_removal:
+                            remaining = [dd for dd in existing if dd not in to_delete]
+                            to_delete.extend(remaining[count:])
 
                     non_matching_daemons = to_delete
                 else:
index fe2798984d766c7f99b3ffbe33156fa036ee0e4b..24e7e581a9e45c7052cc36d0127ebf35e49269ee 100644 (file)
@@ -870,6 +870,17 @@ class CephadmServe:
         rank_map = None
         if svc.ranked(spec):
             rank_map = self.mgr.spec_store[spec.service_name()].rank_map or {}
+        related_service_required_count = None
+        if service_type == 'ingress':
+            ingress_spec = cast(IngressSpec, spec)
+            assert ingress_spec.backend_service
+            backend_spec = self.mgr.spec_store.active_specs.get(
+                ingress_spec.backend_service
+            )
+            if backend_spec and backend_spec.placement:
+                related_service_required_count = backend_spec.placement.get_target_count(
+                    self.mgr.cache.get_schedulable_hosts()
+                )
         host_selector = _host_selector(svc)
         ha = HostAssignment(
             spec=spec,
@@ -880,6 +891,7 @@ class CephadmServe:
             blocking_daemon_hosts=blocking_daemon_hosts,
             daemons=daemons,
             related_service_daemons=related_service_daemons,
+            related_service_required_count=related_service_required_count,
             networks=self.mgr.cache.networks,
             filter_new_host=host_filters.get(service_type, None),
             allow_colo=svc.allow_colo(),
index d76204c7ffdcc4f73e6454364716a711dd52b3cd..5d3595b95e162c33149987bb04f5bad58c9ef126 100644 (file)
@@ -2082,3 +2082,58 @@ def test_related_service_downsize(service_type, placement, hosts, daemons, relat
     assert sorted(got_add) == sorted(expected_add)
 
     assert sorted([d.name() for d in to_remove]) == sorted(expected_remove)
+
+
+def test_ingress_scale_down_defers_related_when_ranked_backend_exceeds_required():
+    """
+    With ingress count below current daemons, ranked NFS backends still above
+    placement target (related_service_required_count), remove only ingress on
+    hosts without NFS first; defer removing co-located ingress until NFS scales
+    down (next iteration).
+    """
+    spec = IngressSpec(
+        service_type='ingress',
+        service_id='nfs.test',
+        frontend_port=443,
+        monitor_port=8888,
+        virtual_ip='10.0.0.20/8',
+        backend_service='nfs.test',
+        placement=PlacementSpec(count=1),
+    )
+    hosts = [HostSpec(h) for h in 'host1 host2 host3'.split()]
+    daemons = [
+        DaemonDescription('haproxy', 'ingress1', 'host1'),
+        DaemonDescription('keepalived', 'ingress1', 'host1'),
+        DaemonDescription('haproxy', 'ingress2', 'host2'),
+        DaemonDescription('keepalived', 'ingress2', 'host2'),
+        DaemonDescription('haproxy', 'ingress3', 'host3'),
+        DaemonDescription('keepalived', 'ingress3', 'host3'),
+    ]
+    related_service_daemons = [
+        DaemonDescription('nfs', 'nfs1', 'host1', rank=0, rank_generation=0),
+        DaemonDescription('nfs', 'nfs2', 'host2', rank=1, rank_generation=0),
+    ]
+    all_slots, to_add, to_remove = HostAssignment(
+        spec=spec,
+        hosts=hosts,
+        unreachable_hosts=[],
+        draining_hosts=[],
+        daemons=daemons,
+        related_service_daemons=related_service_daemons,
+        related_service_required_count=1,
+        primary_daemon_type='haproxy',
+        per_host_daemon_type='keepalived',
+        rank_map=None,
+    ).place()
+
+    assert sorted(str(p) for p in all_slots) == sorted([
+        'haproxy:host1(*:443,8888,1024)',
+        'haproxy:host2(*:443,8888,1024)',
+        'keepalived:host1',
+        'keepalived:host2',
+    ])
+    assert to_add == []
+    assert sorted(d.name() for d in to_remove) == sorted([
+        'haproxy.ingress3',
+        'keepalived.ingress3',
+    ])