Skip to content

Commit

Permalink
Autogenerate dashboard_alt_uid on the provider side
Browse files Browse the repository at this point in the history
  • Loading branch information
sed-i committed Jan 19, 2025
1 parent 15df18c commit 6fa0266
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 24 deletions.
29 changes: 19 additions & 10 deletions lib/charms/grafana_agent/v0/cos_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ def __init__(self, *args):
)

import pydantic
from cosl import GrafanaDashboard, JujuTopology
from cosl import DashboardPath40UID, JujuTopology, LZMABase64
from cosl.rules import AlertRules
from ops.charm import RelationChangedEvent
from ops.framework import EventBase, EventSource, Object, ObjectEvents
Expand All @@ -254,9 +254,10 @@ class _MetricsEndpointDict(TypedDict):

LIBID = "dc15fa84cef84ce58155fb84f6c6213a"
LIBAPI = 0
LIBPATCH = 16
LIBPATCH = 17

PYDEPS = ["cosl", "pydantic"]
# TODO revert to "cosl" after merged
PYDEPS = ["cosl >= 0.50.0", "pydantic"]

DEFAULT_RELATION_NAME = "cos-agent"
DEFAULT_PEER_RELATION_NAME = "peers"
Expand Down Expand Up @@ -481,7 +482,7 @@ class CosAgentProviderUnitData(DatabagModel):
# this needs to make its way to the gagent leader
metrics_alert_rules: dict
log_alert_rules: dict
dashboards: List[GrafanaDashboard]
dashboards: List[str]
# subordinate is no longer used but we should keep it until we bump the library to ensure
# we don't break compatibility.
subordinate: Optional[bool] = None
Expand Down Expand Up @@ -514,7 +515,7 @@ class CosAgentPeersUnitData(DatabagModel):
# of the outgoing o11y relations.
metrics_alert_rules: Optional[dict]
log_alert_rules: Optional[dict]
dashboards: Optional[List[GrafanaDashboard]]
dashboards: Optional[List[str]]

# when this whole datastructure is dumped into a databag, it will be nested under this key.
# while not strictly necessary (we could have it 'flattened out' into the databag),
Expand Down Expand Up @@ -742,12 +743,20 @@ def _log_alert_rules(self) -> Dict:
return alert_rules.as_dict()

@property
def _dashboards(self) -> List[GrafanaDashboard]:
dashboards: List[GrafanaDashboard] = []
def _dashboards(self) -> List[str]:
dashboards: List[str] = []
for d in self._dashboard_dirs:
for path in Path(d).glob("*"):
dashboard = GrafanaDashboard._serialize(path.read_bytes())
dashboards.append(dashboard)
with open(path, "rt") as fp:
dashboard = json.load(fp)
rel_path = str(
path.relative_to(self._charm.charm_dir) if path.is_absolute() else path
)
# COSAgentProvider is somewhat analogous to GrafanaDashboardProvider. We need to overwrite the uid here
# because there is currently no other way to communicate the dashboard path separately.
# https://github.com/canonical/grafana-k8s-operator/pull/363
dashboard["uid"] = DashboardPath40UID.generate(self._charm.meta.name, rel_path)
dashboards.append(LZMABase64.compress(json.dumps(dashboard)))
return dashboards

@property
Expand Down Expand Up @@ -1318,7 +1327,7 @@ def dashboards(self) -> List[Dict[str, str]]:
seen_apps.append(app_name)

for encoded_dashboard in data.dashboards or ():
content = GrafanaDashboard(encoded_dashboard)._deserialize()
content = json.loads(LZMABase64.decompress(encoded_dashboard))

title = content.get("title", "no_title")

Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# FIXME: Packing the charm with 2.2.0+139.gd011d92 will not include dependencies in PYDEPS key:
# https://chat.charmhub.io/charmhub/pl/wngp665ycjnb78ar9ojrfhxjkr
# That's why we are including cosl here until the bug in charmcraft is solved
cosl
cosl >= 0.50.0
ops > 2.5.0
pydantic < 2
requests
Expand Down
12 changes: 6 additions & 6 deletions tests/scenario/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

import pydantic
import pytest
from charms.grafana_agent.v0.cos_agent import CosAgentProviderUnitData, GrafanaDashboard
from charms.grafana_agent.v0.cos_agent import CosAgentProviderUnitData, LZMABase64


class Foo(pydantic.BaseModel):
dash: List[GrafanaDashboard]
dash: List[str]


def test_dashboard_validation():
Expand All @@ -20,7 +20,7 @@ def test_dashboard_validation():

def test_dashboard_serialization():
raw_dash = {"title": "foo", "bar": "baz"}
encoded_dashboard = GrafanaDashboard._serialize(json.dumps(raw_dash))
encoded_dashboard = LZMABase64.compress(json.dumps(raw_dash))
data = Foo(dash=[encoded_dashboard])
assert data.json() == '{"dash": ["{encoded_dashboard}"]}'.replace(
"{encoded_dashboard}", encoded_dashboard
Expand All @@ -29,7 +29,7 @@ def test_dashboard_serialization():

def test_cos_agent_provider_unit_data_dashboard_serialization():
raw_dash = {"title": "title", "foo": "bar"}
encoded_dashboard = GrafanaDashboard()._serialize(json.dumps(raw_dash))
encoded_dashboard = LZMABase64.compress(json.dumps(raw_dash))
data = CosAgentProviderUnitData(
metrics_alert_rules={},
log_alert_rules={},
Expand All @@ -51,7 +51,7 @@ def test_cos_agent_provider_unit_data_dashboard_serialization():

def test_dashboard_deserialization_roundtrip():
raw_dash = {"title": "title", "foo": "bar"}
encoded_dashboard = GrafanaDashboard()._serialize(json.dumps(raw_dash))
encoded_dashboard = LZMABase64.compress(json.dumps(raw_dash))
raw = {
"metrics_alert_rules": {},
"log_alert_rules": {},
Expand All @@ -60,7 +60,7 @@ def test_dashboard_deserialization_roundtrip():
"dashboards": [encoded_dashboard],
}
data = CosAgentProviderUnitData(**raw)
assert GrafanaDashboard(data.dashboards[0])._deserialize() == raw_dash
assert json.loads(LZMABase64.decompress(data.dashboards[0])) == raw_dash


def test_cos_agent_provider_tracing_protocols_are_passed():
Expand Down
6 changes: 3 additions & 3 deletions tests/scenario/test_peer_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@
from charms.prometheus_k8s.v1.prometheus_remote_write import (
PrometheusRemoteWriteConsumer,
)
from cosl import GrafanaDashboard
from cosl import LZMABase64
from ops.charm import CharmBase
from ops.framework import Framework
from ops.testing import Context, PeerRelation, State, SubordinateRelation


def encode_as_dashboard(dct: dict):
return GrafanaDashboard._serialize(json.dumps(dct).encode("utf-8"))
return LZMABase64.compress(json.dumps(dct))


def test_fetch_data_from_relation():
Expand Down Expand Up @@ -48,7 +48,7 @@ def test_fetch_data_from_relation():
data_peer_1 = data[0]
assert len(data_peer_1.dashboards) == 1
dash_out_raw = data_peer_1.dashboards[0]
assert GrafanaDashboard(dash_out_raw)._deserialize() == py_dash
assert json.loads(LZMABase64.decompress(dash_out_raw)) == py_dash


class MyRequirerCharm(CharmBase):
Expand Down
6 changes: 3 additions & 3 deletions tests/scenario/test_relation_priority.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from unittest.mock import patch

import pytest
from cosl import GrafanaDashboard
from cosl import LZMABase64
from ops.testing import Context, PeerRelation, State, SubordinateRelation

import charm
Expand Down Expand Up @@ -90,7 +90,7 @@ def test_cos_machine_relation(mock_run, charm_config):
"relation_name": "peers",
"metrics_alert_rules": {},
"log_alert_rules": {},
"dashboards": [GrafanaDashboard._serialize('{"very long": "dashboard"}')],
"dashboards": [LZMABase64.compress(json.dumps('{"very long": "dashboard"}'))],
}
)
}
Expand Down Expand Up @@ -147,7 +147,7 @@ def test_both_relations(mock_run, charm_config):
"relation_name": "peers",
"metrics_alert_rules": {},
"log_alert_rules": {},
"dashboards": [GrafanaDashboard._serialize('{"very long": "dashboard"}')],
"dashboards": [LZMABase64.compress(json.dumps('{"very long": "dashboard"}'))],
}
)
}
Expand Down
3 changes: 2 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ deps =
fs
toml
responses
cosl >= 0.50.0
commands =
coverage run \
--source={[vars]src_path} \
Expand All @@ -69,7 +70,7 @@ description = Run scenario tests on LXD
deps =
-r{toxinidir}/requirements.txt
pytest
cosl
cosl >= 0.50.0
ops[testing]
commands =
pytest -vv --tb native --log-cli-level=INFO -s {posargs} {[vars]tst_path}/scenario
Expand Down

0 comments on commit 6fa0266

Please sign in to comment.