From: kginon Date: Mon, 2 Mar 2026 14:44:05 +0000 (+0200) Subject: mgr/cephadm: fix mgmt-gateway startup on IPv6 VIP X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=05bd55e53f7d3a73c2fb9d5dfd9954d67c7d7226;p=ceph.git mgr/cephadm: fix mgmt-gateway startup on IPv6 VIP Ensure mgmt-gateway service starts correctly when it is configured to listen on an IPv6 VIP address. Fixes: https://tracker.ceph.com/issues/75267 (original PR) Fixes: https://tracker.ceph.com/issues/75914 (Tentacle ticket) Signed-off-by: Kobi Ginon (cherry picked from commit e68e302a7389c59ed92fbebc1684b9b5d252d854) --- diff --git a/src/pybind/mgr/cephadm/services/cephadmservice.py b/src/pybind/mgr/cephadm/services/cephadmservice.py index 9e618d42866d..53ad6d9166ae 100644 --- a/src/pybind/mgr/cephadm/services/cephadmservice.py +++ b/src/pybind/mgr/cephadm/services/cephadmservice.py @@ -21,7 +21,7 @@ from ceph.deployment.service_spec import ( RGWSpec, ServiceSpec, ) -from ceph.deployment.utils import is_ipv6, unwrap_ipv6 +from ceph.deployment.utils import is_ipv6, unwrap_ipv6, wrap_ipv6 from mgr_util import build_url, merge_dicts from orchestrator import ( OrchestratorError, @@ -96,8 +96,10 @@ def get_dashboard_endpoints(svc: 'CephadmService') -> Tuple[List[str], Optional[ if not port: continue assert dd.hostname is not None + # fqdn may already be a name or numeric address; ensure IPv6 + # literals are bracketed. addr = svc.mgr.get_fqdn(dd.hostname) - dashboard_endpoints.append(f'{addr}:{port}') + dashboard_endpoints.append(f'{wrap_ipv6(addr)}:{port}') return dashboard_endpoints, protocol diff --git a/src/pybind/mgr/cephadm/services/mgmt_gateway.py b/src/pybind/mgr/cephadm/services/mgmt_gateway.py index 5a89c96187f8..bcabf60f30b5 100644 --- a/src/pybind/mgr/cephadm/services/mgmt_gateway.py +++ b/src/pybind/mgr/cephadm/services/mgmt_gateway.py @@ -1,6 +1,8 @@ import logging from typing import List, Any, Tuple, Dict, cast, Optional, TYPE_CHECKING +from ceph.deployment.utils import wrap_ipv6 + from orchestrator import DaemonDescription from ceph.deployment.service_spec import MgmtGatewaySpec, GrafanaSpec, ServiceSpec from cephadm.services.cephadmservice import CephadmService, CephadmDaemonDeploySpec, get_dashboard_endpoints @@ -26,12 +28,14 @@ class MgmtGatewayService(CephadmService): return daemon_spec def get_service_endpoints(self, service_name: str) -> List[str]: + # return host:port strings for every daemon of the given service + # wrap IPv6 addresses in square brackets so a port can be added later srv_entries = [] for dd in self.mgr.cache.get_daemons_by_service(service_name): assert dd.hostname is not None addr = dd.ip if dd.ip else self.mgr.inventory.get_addr(dd.hostname) port = dd.ports[0] if dd.ports else None - srv_entries.append(f'{addr}:{port}') + srv_entries.append(f'{wrap_ipv6(addr)}:{port}') return srv_entries def get_active_daemon(self, daemon_descrs: List[DaemonDescription]) -> DaemonDescription: @@ -83,11 +87,14 @@ class MgmtGatewayService(CephadmService): return self.mgr.cert_mgr.generate_cert(host_fqdn, ip) def get_service_discovery_endpoints(self) -> List[str]: + # the mgmt gateway uses this internally when generating its nginx + # configuration and the URL prefixes that we publish to the world. + # A literal IPv6 address needs to be wrapped in brackets. sd_endpoints = [] for dd in self.mgr.cache.get_daemons_by_service('mgr'): assert dd.hostname is not None addr = dd.ip if dd.ip else self.mgr.inventory.get_addr(dd.hostname) - sd_endpoints.append(f"{addr}:{self.mgr.service_discovery_port}") + sd_endpoints.append(f"{wrap_ipv6(addr)}:{self.mgr.service_discovery_port}") return sd_endpoints @classmethod diff --git a/src/pybind/mgr/cephadm/tests/test_services.py b/src/pybind/mgr/cephadm/tests/test_services.py index 7e3b1f2a3837..f84445fe0ffd 100644 --- a/src/pybind/mgr/cephadm/tests/test_services.py +++ b/src/pybind/mgr/cephadm/tests/test_services.py @@ -4035,6 +4035,27 @@ class TestSMB: class TestMgmtGateway: + def test_ipv6_formatting_helpers(self, cephadm_module: CephadmOrchestrator): + # verify that endpoints generated by the mgmt-gateway helper methods + # correctly bracket IPv6 addresses before the port portion is added. + svc = service_registry.get_service('mgmt-gateway') + + # service discovery endpoints use a fixed port from the orchestrator + port = cephadm_module.service_discovery_port + mgr_daemons = [ + DaemonDescription(daemon_type='mgr', hostname='h1', ip='fe80::1', ports=[port]), + DaemonDescription(daemon_type='mgr', hostname='h2', ip='192.0.2.1', ports=[port]), + ] + cephadm_module.cache.get_daemons_by_service = lambda name: mgr_daemons if name == 'mgr' else [] + + sd = svc.get_service_discovery_endpoints() + assert sd == [f'[fe80::1]:{port}', f'192.0.2.1:{port}'] + + # generic service endpoints also need the same treatment + foo_daemons = [DaemonDescription(daemon_type='foo', hostname='f1', ip='fe80::2', ports=[8080])] + cephadm_module.cache.get_daemons_by_service = lambda name: foo_daemons if name == 'foo' else [] + assert svc.get_service_endpoints('foo') == ['[fe80::2]:8080'] + @patch("cephadm.serve.CephadmServe._run_cephadm") @patch("cephadm.services.mgmt_gateway.MgmtGatewayService.get_service_endpoints") @patch("cephadm.services.mgmt_gateway.MgmtGatewayService.get_service_discovery_endpoints")