]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/cephadm: add option for grafana to bind on specific network
authorAdam King <adking@redhat.com>
Wed, 18 Oct 2023 20:22:18 +0000 (16:22 -0400)
committerAdam King <adking@redhat.com>
Tue, 19 Mar 2024 16:41:09 +0000 (12:41 -0400)
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 <adking@redhat.com>
(cherry picked from commit 6ee5fff0c8dc71c4789496a587e2599ff2e0bc13)

src/pybind/mgr/cephadm/module.py
src/pybind/mgr/cephadm/services/monitoring.py
src/python-common/ceph/deployment/service_spec.py

index dd5ec0cd75260c787148f1f40f836d31c0153af4..40ff122cef63b8abe5e415eb357ff24d1e6848e2 100644 (file)
@@ -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:
index d3439c04d04f506d07f5c2a1204d90d434ee5caf..bdcf214721c674eabe07f21a6ae6a23824074280 100644 (file)
@@ -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:
index dd07d4f9923de6a0b9641ebcc3b9b056ac95198e..20b3cbc5f9f1aa53d33ca894d38fb6e97b223b92 100644 (file)
@@ -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']: