Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion coordinator/lib/charms/grafana_k8s/v1/grafana_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,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 = 0
LIBPATCH = 1

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -420,6 +420,7 @@ def __init__(
unit_datasources: bool = False,
app_datasource_url: Optional[str] = None,
unit_datasource_url: Optional[str] = None,
app_datasources: Optional[List[Dict]] = None,
) -> None:
"""Construct a Grafana charm client.

Expand Down Expand Up @@ -499,6 +500,11 @@ def __init__(
is used. This value is per-unit. Pass the base URL only: for
``source_type == "mimir"`` the library appends the ``/prometheus`` query
path itself.
app_datasources: an optional list of additional application-level datasource
dicts to publish (under ``grafana_source_datasources``) alongside the
default datasource. Each entry supports the same keys as the consumer's
source dicts: ``source_name``, ``source_type``, ``url`` and, optionally,
``extra_fields`` and ``secure_extra_fields``.
"""
_validate_relation_by_interface_and_direction(
charm, relation_name, RELATION_INTERFACE_NAME, RelationRole.provides
Expand All @@ -518,6 +524,7 @@ def __init__(

self._extra_fields = extra_fields
self._secure_extra_fields = secure_extra_fields
self._extra_app_datasources = app_datasources or []

if not refresh_event:
if len(self._charm.meta.containers) == 1:
Expand Down Expand Up @@ -571,6 +578,33 @@ def update_app_source(self, app_datasource_url: Optional[str] = ""):
continue
self._set_sources(rel)

def update_app_datasources(
self, datasources: Optional[List[Dict]], app_datasource_url: Optional[str] = None
) -> None:
"""Publish extra application-level datasources and re-publish relation data.

The provided datasources are written to the app databag under
``grafana_source_datasources``. Each entry must provide ``source_name``,
``source_type``, ``url`` and, optionally, ``extra_fields`` and
``secure_extra_fields`` (matching the shape of the consumer's source dicts).

Passing an empty list removes the key and reverts to publishing only the
default application-level datasource.

Args:
datasources: a list of datasource dicts to publish, or empty/None to clear.
app_datasource_url: an optional application-level URL to republish alongside
the extra datasources (e.g. to refresh after an ingress change).
"""
self._extra_app_datasources = list(datasources or [])
if app_datasource_url is not None:
self._app_datasource_url = self._sanitize_source_url(app_datasource_url)

for rel in self._charm.model.relations.get(self._relation_name, []):
if not rel:
continue
self._set_sources(rel)

def update_unit_source(self, unit_datasource_url: Optional[str] = ""):
"""Update this unit's datasource URL and re-publish relation data.

Expand Down Expand Up @@ -632,6 +666,13 @@ def _set_sources(self, rel: Relation):
logger.debug("Setting Grafana data sources: %s", self._scrape_data)
rel.data[self._charm.app]["grafana_source_data"] = json.dumps(self._scrape_data)

if self._extra_app_datasources:
rel.data[self._charm.app]["grafana_source_datasources"] = json.dumps(
self._extra_app_datasources
)
elif "grafana_source_datasources" in rel.data[self._charm.app]:
del rel.data[self._charm.app]["grafana_source_datasources"]

@property
def _scrape_data(self) -> Dict:
"""Generate source metadata.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@

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

PYDEPS = ["cosl"]

Expand Down Expand Up @@ -404,6 +404,7 @@ def __init__(
peer_relation_name: str,
forward_alert_rules: bool = True,
extra_alert_labels: Dict = {},
mimir_tenant_id: Optional[str] = None,
):
"""API to manage a required relation with the `prometheus_remote_write` interface.

Expand All @@ -420,6 +421,9 @@ def __init__(
peer_relation_name: Name of the peer relation containing units of this charm.
forward_alert_rules: Flag to toggle forwarding of charmed alert rules.
extra_alert_labels: Dict of extra labels to inject alert rules with.
mimir_tenant_id: The Mimir tenant ID to be announced to the remote-write
provider (e.g. Mimir) over the relation app data bag. If empty or None,
no tenant ID is sent.

Raises:
RelationNotFoundError: If there is no relation in the charm's metadata.yaml
Expand Down Expand Up @@ -450,6 +454,7 @@ def __init__(
self._alert_rules_path = alert_rules_path
self._forward_alert_rules = forward_alert_rules
self._extra_alert_labels = extra_alert_labels
self._mimir_tenant_id = mimir_tenant_id or ""
self._peer_relation_name = peer_relation_name
self.topology = JujuTopology.from_charm(charm)
self._tool = CosTool("promql")
Expand Down Expand Up @@ -528,6 +533,12 @@ def _push_alerts_to_relation_databag(self, relation: Relation) -> None:
)
relation.data[self._charm.app]["alert_rules"] = json.dumps(alert_rules_as_dict)

# Announce the Mimir tenant ID (if any) to the provider side (e.g. Mimir).
if self._mimir_tenant_id:
relation.data[self._charm.app]["mimir_tenant_id"] = self._mimir_tenant_id
elif "mimir_tenant_id" in relation.data[self._charm.app]:
del relation.data[self._charm.app]["mimir_tenant_id"]

def reload_alerts(self) -> None:
"""Reload alert rules from disk and push to relation data."""
self._push_alerts_to_all_relation_databags(None)
Expand Down Expand Up @@ -1019,3 +1030,23 @@ def _has_relation_error(self, error_key: str, error_label: str) -> bool:

return False

@property
def mimir_tenant_ids(self) -> List[str]:
"""Map relation IDs to the Mimir tenant IDs reported by consumers.

Consumers announce the tenant ID they are configured with (e.g. via a
``mimir_tenant_id`` config option) over the relation app data bag. This
helper exposes the ids announced by each related consumer.

Returns:
A mapping of relation ID to tenant ID for each relation whose consumer
announced a non-empty tenant ID.
"""
tenants: List[str] = []
for relation in self._charm.model.relations.get(self._relation_name, []):
if not relation.app:
continue
if tenant := relation.data[relation.app].get("mimir_tenant_id", ""):
tenants.append(tenant)
return tenants

26 changes: 23 additions & 3 deletions coordinator/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,28 @@ def _update_prometheus_api(self) -> None:
ingress_url=f"{self.external_url}/prometheus" if self.external_url else None,
)

def _update_grafana_sources(self) -> None:
"""Publish the anonymous Mimir datasource plus one per announced tenant."""
if not self.unit.is_leader():
return

base_url = f"{self.external_url or self._service_url}/prometheus"
extra_fields = self._build_grafana_source_extra_fields()
tenant_datasources = [
{
"source_name": f"mimir-tenant-{tenant}",
"source_type": "prometheus",
"url": base_url,
"extra_fields": extra_fields,
"secure_extra_fields": {"httpHeaderValue1": tenant},
}
for tenant in sorted(set(self.remote_write_provider.mimir_tenant_ids))
]
self.grafana_source.update_app_datasources(
tenant_datasources,
app_datasource_url=base_url,
)

def _update_datasource_exchange(self) -> None:
"""Update the grafana-datasource-exchange relations."""
if not self.unit.is_leader():
Expand Down Expand Up @@ -481,9 +503,7 @@ def _reconcile(self):
self._ensure_mimirtool()
self._update_prometheus_api()
self._update_datasource_exchange()
self.grafana_source.update_app_source(
app_datasource_url=f"{self.external_url or self._service_url}/prometheus"
)
self._update_grafana_sources()
self.remote_write_provider.update_endpoint()

# Open necessary service ports. needed for telemetry proxying.
Expand Down
2 changes: 1 addition & 1 deletion worker/src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ def pebble_layer(self, worker: Worker) -> Layer:
"mimir": {
"override": "replace",
"summary": "mimir worker daemon",
"command": f"/bin/mimir --config.file={CONFIG_FILE} -target {targets} -auth.multitenancy-enabled=false",
"command": f"/bin/mimir --config.file={CONFIG_FILE} -target {targets}",
"startup": "enabled",
"environment": env,
}
Expand Down
Loading