From: Shweta Bhosale Date: Tue, 28 Apr 2026 16:08:22 +0000 (+0530) Subject: mgr/cephadm: Scale down ingress service only when the backend NFS service is scaled... X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=6ec51414a9ea2b1bc586356d9511aa2c19bbb951;p=ceph.git mgr/cephadm: Scale down ingress service only when the backend NFS service is scaled down 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 --- diff --git a/src/pybind/mgr/cephadm/schedule.py b/src/pybind/mgr/cephadm/schedule.py index d2846a853cd..b4f9570a3ee 100644 --- a/src/pybind/mgr/cephadm/schedule.py +++ b/src/pybind/mgr/cephadm/schedule.py @@ -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: diff --git a/src/pybind/mgr/cephadm/serve.py b/src/pybind/mgr/cephadm/serve.py index fe2798984d7..24e7e581a9e 100644 --- a/src/pybind/mgr/cephadm/serve.py +++ b/src/pybind/mgr/cephadm/serve.py @@ -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(), diff --git a/src/pybind/mgr/cephadm/tests/test_scheduling.py b/src/pybind/mgr/cephadm/tests/test_scheduling.py index d76204c7ffd..5d3595b95e1 100644 --- a/src/pybind/mgr/cephadm/tests/test_scheduling.py +++ b/src/pybind/mgr/cephadm/tests/test_scheduling.py @@ -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', + ])