From ac608899777c143ab7366dcb235e7bd5109180c6 Mon Sep 17 00:00:00 2001 From: John Mulligan Date: Mon, 16 Jun 2025 16:05:01 -0400 Subject: [PATCH] mgr/cephadm: prepare schedule.py for per-service-type host filtering Prepare schedule.py for per-service-type host filtering based on allowed host addresses/networks. Add a new HostSelector protocol type to the module defining what the filtering interface looks like. This interface is intended allows CephService classes to "take over" the network based filtering of nodes prior to placement and customize the behavior of this step in cephamd's placement algorithm. Note that the type must be passed in to the HostAssignment class as an optional argument. If nothing is passed the class behaves as it did before. Signed-off-by: John Mulligan (cherry picked from commit e3a3334f2ccf46c436ee61db14378629b1e72df8) Conflicts: src/pybind/mgr/cephadm/schedule.py --- src/pybind/mgr/cephadm/schedule.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/src/pybind/mgr/cephadm/schedule.py b/src/pybind/mgr/cephadm/schedule.py index e71c0345f7560..448fc20bdad26 100644 --- a/src/pybind/mgr/cephadm/schedule.py +++ b/src/pybind/mgr/cephadm/schedule.py @@ -2,7 +2,17 @@ import ipaddress import hashlib import logging import random -from typing import List, Optional, Callable, TypeVar, Tuple, NamedTuple, Dict +from typing import ( + Callable, + Dict, + Iterable, + List, + NamedTuple, + Optional, + Protocol, + Tuple, + TypeVar, +) import orchestrator from ceph.deployment.service_spec import ServiceSpec @@ -143,6 +153,15 @@ class DaemonPlacement(NamedTuple): return rank_map[dd.rank][dd.rank_generation] == dd.daemon_id +class HostSelector(Protocol): + def filter_host_candidates( + self, + spec: ServiceSpec, + candidates: Iterable[DaemonPlacement], + ) -> List[DaemonPlacement]: + "Filter candidates for host/ip matching." + + class HostAssignment(object): def __init__(self, @@ -159,7 +178,8 @@ class HostAssignment(object): per_host_daemon_type: Optional[str] = None, rank_map: Optional[Dict[int, Dict[int, Optional[str]]]] = None, blocking_daemon_hosts: Optional[List[orchestrator.HostSpec]] = None, - upgrade_in_progress: bool = False + upgrade_in_progress: bool = False, + host_selector: Optional[HostSelector] = None, ): assert spec self.spec = spec # type: ServiceSpec @@ -178,6 +198,7 @@ class HostAssignment(object): self.ports_start = spec.get_port_start() self.rank_map = rank_map self.upgrade_in_progress = upgrade_in_progress + self.host_selector = host_selector def hosts_by_label(self, label: str) -> List[orchestrator.HostSpec]: return [h for h in self.hosts if label in h.labels] @@ -467,7 +488,9 @@ class HostAssignment(object): "placement spec is empty: no hosts, no label, no pattern, no count") # allocate an IP? - if self.spec.networks: + if self.host_selector: + ls = self.host_selector.filter_host_candidates(self.spec, ls) + elif self.spec.networks: orig = ls.copy() ls = [] for p in orig: -- 2.39.5