Skip to content

Commit

Permalink
forward grafana source UIDs
Browse files Browse the repository at this point in the history
  • Loading branch information
PietroPasotti committed Nov 25, 2024
1 parent d36ffa5 commit c330307
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 8 deletions.
24 changes: 23 additions & 1 deletion lib/charms/grafana_k8s/v0/grafana_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def __init__(self, *args):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 21
LIBPATCH = 22

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -429,6 +429,17 @@ def update_source(self, source_url: Optional[str] = ""):
continue
self._set_sources(rel)

def get_source_uids(self) -> Dict[str, Dict[str, str]]:
"""Get the datasource UID(s) assigned by the remote end(s) to this datasource.
Returns a mapping from remote application names to unit names to datasource uids."""
uids = {}
for rel in self._charm.model.relations.get(self._relation_name, []):
if not rel:
continue
uids[rel.app.name] = json.loads(rel.data[rel.app]["datasource_uids"])
return uids

def _set_sources_from_event(self, event: RelationJoinedEvent) -> None:
"""Get a `Relation` object from the event to pass on."""
self._set_sources(event.relation)
Expand Down Expand Up @@ -551,6 +562,13 @@ def _on_grafana_peer_changed(self, _: RelationChangedEvent) -> None:
self.on.sources_changed.emit() # pyright: ignore
self.on.sources_to_delete_changed.emit() # pyright: ignore

def _publish_source_uids(self, rel: Relation, uids: Dict[str, str]):
"""Share the datasource UIDs back to the datasources.
Assumes only leader unit will call this method
"""
rel.data[self._charm.app]["datasource_uids"] = json.dumps(uids)

def _get_source_config(self, rel: Relation):
"""Generate configuration from data stored in relation data by providers."""
source_data = json.loads(rel.data[rel.app].get("grafana_source_data", "{}")) # type: ignore
Expand Down Expand Up @@ -588,6 +606,10 @@ def _get_source_config(self, rel: Relation):
sources_to_delete.remove(host_data["source_name"])

data.append(host_data)

# share the unique source names back to the datasource units
self._publish_source_uids(rel, {ds["unit"]: ds["source_name"] for ds in data})

self.set_peer_data("sources_to_delete", list(sources_to_delete))
return data

Expand Down
20 changes: 20 additions & 0 deletions lib/charms/prometheus_k8s/v1/prometheus_remote_write.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ def __init__(
*,
server_url_func: Callable[[], str] = lambda: f"http://{socket.getfqdn()}:9090",
endpoint_path: str = "/api/v1/write",
datasource_uids: Optional[Dict[str,Dict[str, str]]] = None
):
"""API to manage a provided relation with the `prometheus_remote_write` interface.
Expand All @@ -609,6 +610,9 @@ def __init__(
defined in metadata.yaml.
server_url_func: A callable returning the URL for your prometheus server.
endpoint_path: The path of the server's remote_write endpoint.
datasource_uids: The uids of the grafana datasources provisioned for these
prometheus units. A mapping from grafana applications to
local unit IDs to datasource UIDs (str).
Raises:
RelationNotFoundError: If there is no relation in the charm's metadata.yaml
Expand All @@ -631,6 +635,11 @@ def __init__(
self._get_server_url = server_url_func
self._endpoint_path = endpoint_path

# we might have datasource relations with multiple grafana's.
# for each one of them, our datasource UID might be different
# (although, with the current implementation, it isn't).
self._datasource_uids = datasource_uids

on_relation = self._charm.on[self._relation_name]
self.framework.observe(
on_relation.relation_created,
Expand Down Expand Up @@ -673,6 +682,17 @@ def update_endpoint(self, relation: Optional[Relation] = None) -> None:
for relation in relations:
self._set_endpoint_on_relation(relation)

if self._charm.unit.is_leader() and self._datasource_uids:
self._set_datasource_ids_on_relation(relation)

def _set_datasource_ids_on_relation(self, relation: Relation) -> None:
"""Set the remote_write endpoint on relations.
Args:
relation: The relation whose data to update.
"""
relation.data[self._charm.app]["datasource_uids"] = json.dumps(self._datasource_uids)

def _set_endpoint_on_relation(self, relation: Relation) -> None:
"""Set the remote_write endpoint on relations.
Expand Down
16 changes: 9 additions & 7 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,6 @@ def __init__(self, *args):
)
self._prometheus_client = Prometheus(self.internal_url)

self.remote_write_provider = PrometheusRemoteWriteProvider(
charm=self,
relation_name=DEFAULT_REMOTE_WRITE_RELATION_NAME,
server_url_func=lambda: PrometheusCharm.external_url.fget(self), # type: ignore
endpoint_path="/api/v1/write",
)

self.grafana_source_provider = GrafanaSourceProvider(
charm=self,
source_type="prometheus",
Expand All @@ -221,6 +214,14 @@ def __init__(self, *args):
extra_fields={"timeInterval": PROMETHEUS_GLOBAL_SCRAPE_INTERVAL},
)

self.remote_write_provider = PrometheusRemoteWriteProvider(
charm=self,
relation_name=DEFAULT_REMOTE_WRITE_RELATION_NAME,
server_url_func=lambda: PrometheusCharm.external_url.fget(self), # type: ignore
endpoint_path="/api/v1/write",
datasource_uids=self.grafana_source_provider.get_source_uids()
)

self.catalogue = CatalogueConsumer(charm=self, item=self._catalogue_item)
self.charm_tracing = TracingEndpointRequirer(
self, relation_name="charm-tracing", protocols=["otlp_http"]
Expand All @@ -237,6 +238,7 @@ def __init__(self, *args):
self.framework.observe(self.on.config_changed, self._configure)
self.framework.observe(self.on.upgrade_charm, self._configure)
self.framework.observe(self.on.update_status, self._update_status)
self.framework.observe(self.on.grafana_source_relation_changed, self._configure)
self.framework.observe(self.ingress.on.ready_for_unit, self._on_ingress_ready)
self.framework.observe(self.ingress.on.revoked_for_unit, self._on_ingress_revoked)
self.framework.observe(self.cert_handler.on.cert_changed, self._on_server_cert_changed)
Expand Down
61 changes: 61 additions & 0 deletions tests/scenario/test_grafana_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.

import json

import pytest
import yaml
from scenario import Container, ExecOutput, Relation, State


@pytest.mark.parametrize("this_app", ("prometheus", "prom"))
@pytest.mark.parametrize("this_unit_id", (0, 42))
def test_remote_write_dashboard_uid_propagation(context, this_app, this_unit_id):
"""Check that the grafana dashboard UIds are propagated over remote-write."""
# GIVEN a remote-write relation

remote_write_relation = Relation(
endpoint="receive-remote-write",
)

# AND a grafana-source relation
grafana_source_relation = Relation(
endpoint="grafana-source",
remote_app_name="grafana",
local_unit_data={
"grafana_source_host": "some-hostname"
},
local_app_data={
"grafana_source_data": json.dumps(
{"model": "foo", "model_uuid": "bar", "application": "baz", "type": "tempo"}
)
},

remote_app_data={
# the datasources provisioned by grafana for this relation
"datasource_uids": json.dumps(
{
f"{this_app}/{this_unit_id}": f"juju_foo_bar_{this_app}_{this_unit_id}",
# some peer unit
f"{this_app}/{this_unit_id+1}": f"juju_foo_bar_{this_app}_{this_unit_id+1}",
}
)
}
)

container = Container(
name="prometheus",
can_connect=True,
exec_mock={("update-ca-certificates", "--fresh"): ExecOutput(return_code=0, stdout="")},
)
state = State(leader=True, containers=[container], relations=[remote_write_relation, grafana_source_relation])

state_out = context.run(event=grafana_source_relation.changed_event, state=state)

remote_write_out = state_out.get_relations("receive-remote-write")[0]
shared_ds_uids = remote_write_out.local_app_data.get("datasource_uids")
assert shared_ds_uids




0 comments on commit c330307

Please sign in to comment.