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)
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:
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:
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,
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']: