From: Adam King Date: Wed, 18 Oct 2023 20:22:18 +0000 (-0400) Subject: mgr/cephadm: add option for grafana to bind on specific network X-Git-Tag: v18.2.4~126^2~1 X-Git-Url: http://git.apps.os.sepia.ceph.com/?a=commitdiff_plain;h=97dd71cdf18ba3ea01e80129914f4b6a57328890;p=ceph.git mgr/cephadm: add option for grafana to bind on specific network For example, with a spec like ``` [root@vm-00 ~]# cat grafana.yaml service_type: grafana service_name: grafana placement: count: 1 networks: - 10.2.1.0/24 spec: anonymous_access: true protocol: https only_bind_port_on_networks: true ``` where the networks is set and the "only_bind_port_on_networks" option is set to true, the grafana daemon will bind to its port (3000 in this case since it's the default and I didn't set a port) only on an IP from that network. I tested this by holding port 3000 on an IP from a different network on the host and then deploying grafana. Without this patch it would have failed with a port conflict error. ``` [root@vm-00 ~]# netstat -tulpn | grep 3000 tcp 0 0 10.2.1.61:3000 0.0.0.0:* LISTEN 34178/grafana tcp 0 0 192.168.122.251:3000 0.0.0.0:* LISTEN 33274/nc ``` Signed-off-by: Adam King (cherry picked from commit 6ee5fff0c8dc71c4789496a587e2599ff2e0bc13) --- diff --git a/src/pybind/mgr/cephadm/module.py b/src/pybind/mgr/cephadm/module.py index dd5ec0cd75260..40ff122cef63b 100644 --- a/src/pybind/mgr/cephadm/module.py +++ b/src/pybind/mgr/cephadm/module.py @@ -989,6 +989,17 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule, self.set_health_warning('CEPHADM_FAILED_DAEMON', f'{len(failed_daemons)} failed cephadm daemon(s)', len( failed_daemons), failed_daemons) + def get_first_matching_network_ip(self, host: str, sspec: ServiceSpec) -> Optional[str]: + sspec_networks = sspec.networks + for subnet, ifaces in self.cache.networks.get(host, {}).items(): + host_network = ipaddress.ip_network(subnet) + for spec_network_str in sspec_networks: + spec_network = ipaddress.ip_network(spec_network_str) + if host_network.overlaps(spec_network): + return list(ifaces.values())[0][0] + logger.error(f'{spec_network} from {sspec.service_name()} spec does not overlap with {host_network} on {host}') + return None + @staticmethod def can_run() -> Tuple[bool, str]: if asyncssh is not None: diff --git a/src/pybind/mgr/cephadm/services/monitoring.py b/src/pybind/mgr/cephadm/services/monitoring.py index d3439c04d04f5..bdcf214721c67 100644 --- a/src/pybind/mgr/cephadm/services/monitoring.py +++ b/src/pybind/mgr/cephadm/services/monitoring.py @@ -67,13 +67,24 @@ class GrafanaService(CephadmService): spec: GrafanaSpec = cast( GrafanaSpec, self.mgr.spec_store.active_specs[daemon_spec.service_name]) + + grafana_port = daemon_spec.ports[0] if daemon_spec.ports else self.DEFAULT_SERVICE_PORT + grafana_ip = daemon_spec.ip if daemon_spec.ip else '' + + if spec.only_bind_port_on_networks and spec.networks: + assert daemon_spec.host is not None + ip_to_bind_to = self.mgr.get_first_matching_network_ip(daemon_spec.host, spec) + if ip_to_bind_to: + daemon_spec.port_ips = {str(grafana_port): ip_to_bind_to} + grafana_ip = ip_to_bind_to + grafana_ini = self.mgr.template.render( 'services/grafana/grafana.ini.j2', { 'anonymous_access': spec.anonymous_access, 'initial_admin_password': spec.initial_admin_password, - 'http_port': daemon_spec.ports[0] if daemon_spec.ports else self.DEFAULT_SERVICE_PORT, + 'http_port': grafana_port, 'protocol': spec.protocol, - 'http_addr': daemon_spec.ip if daemon_spec.ip else '' + 'http_addr': grafana_ip }) if 'dashboard' in self.mgr.get('mgr_map')['modules'] and spec.initial_admin_password: diff --git a/src/python-common/ceph/deployment/service_spec.py b/src/python-common/ceph/deployment/service_spec.py index dd07d4f9923de..20b3cbc5f9f1a 100644 --- a/src/python-common/ceph/deployment/service_spec.py +++ b/src/python-common/ceph/deployment/service_spec.py @@ -1561,6 +1561,7 @@ class GrafanaSpec(MonitoringSpec): preview_only: bool = False, config: Optional[Dict[str, str]] = None, networks: Optional[List[str]] = None, + only_bind_port_on_networks: bool = False, port: Optional[int] = None, protocol: Optional[str] = 'https', initial_admin_password: Optional[str] = None, @@ -1581,6 +1582,12 @@ class GrafanaSpec(MonitoringSpec): self.anonymous_access = anonymous_access self.protocol = protocol + # whether ports daemons for this service bind to should + # bind to only hte networks listed in networks param, or + # to all networks. Defaults to false which is saying to bind + # on all networks. + self.only_bind_port_on_networks = only_bind_port_on_networks + def validate(self) -> None: super(GrafanaSpec, self).validate() if self.protocol not in ['http', 'https']: