]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: fix mgmt-gateway startup on IPv6 VIP 67610/head
authorkginon <kobi.ginon.ext@nokia.com>
Mon, 2 Mar 2026 14:44:05 +0000 (16:44 +0200)
committerKobi Ginon <kginon@redhat.com>
Mon, 16 Mar 2026 16:22:16 +0000 (18:22 +0200)
Ensure mgmt-gateway service starts correctly when it is configured
to listen on an IPv6 VIP address.

Fixes: https://tracker.ceph.com/issues/75267
Signed-off-by: Kobi Ginon <kginon@redhat.com>
src/pybind/mgr/cephadm/services/cephadmservice.py
src/pybind/mgr/cephadm/services/mgmt_gateway.py
src/pybind/mgr/cephadm/tests/services/test_mgmt_gateway.py

index 482414db4869aca8008d001f31f087e1a37df939..af14355e2610971dd1af2b537954d79bb8fd0567 100644 (file)
@@ -23,7 +23,7 @@ from ceph.deployment.service_spec import (
     CertificateSource,
     RequiresCertificatesEntry
 )
-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,
@@ -100,8 +100,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
 
index f5f01a913a1da988309b1795584cf2ee6f62dede..1088b7b725011179c46463515d00eb471d4c693f 100644 (file)
@@ -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
@@ -30,12 +32,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:
@@ -56,11 +60,14 @@ class MgmtGatewayService(CephadmService):
         self.mgr.set_module_option_ex('dashboard', 'standby_behaviour', 'error')
 
     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
index 5747cd7a16911770bcc59483f0f34fe31133ab29..f754f873f0a83815bf92e9d9cc9b217b4e210249 100644 (file)
@@ -3,11 +3,14 @@ from textwrap import dedent
 from unittest.mock import patch
 from typing import List
 
+from orchestrator._interface import DaemonDescription
+
 from cephadm.module import CephadmOrchestrator
 from ceph.deployment.service_spec import (
     MgmtGatewaySpec,
     OAuth2ProxySpec
 )
+from cephadm.services.service_registry import service_registry
 from cephadm.tests.fixtures import with_host, with_service, async_side_effect
 from cephadm.tlsobject_types import TLSCredentials
 
@@ -20,6 +23,27 @@ ceph_generated_key = """-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQE
 
 
 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")