From 7dc4a5670432a39cec7b4b97c5d4a16f831dc835 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Thu, 16 Apr 2026 11:30:37 +0530 Subject: [PATCH 1/2] Update otlp to use generic rules class --- interfaces/otlp/pyproject.toml | 5 ++- .../src/charmlibs/interfaces/otlp/_otlp.py | 37 +++++++++------- interfaces/otlp/tests/unit/test_rules.py | 42 +++---------------- interfaces/otlp/uv.lock | 10 ++--- 4 files changed, 36 insertions(+), 58 deletions(-) diff --git a/interfaces/otlp/pyproject.toml b/interfaces/otlp/pyproject.toml index eeff2bb23..fcff34ad5 100644 --- a/interfaces/otlp/pyproject.toml +++ b/interfaces/otlp/pyproject.toml @@ -16,7 +16,7 @@ classifiers = [ dynamic = ["version"] dependencies = [ # "ops", - "cosl>=1.6.1", + "cosl @ git+https://github.com/swetha1654/cos-lib@feat/rewrite-rules", "requests", ] @@ -42,6 +42,9 @@ integration = [ # installed for `just integration otlp` requires = ["hatchling"] build-backend = "hatchling.build" +[tool.hatch.metadata] +allow-direct-references = true + [tool.hatch.build.targets.wheel] packages = ["src/charmlibs"] diff --git a/interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py b/interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py index 098405af7..fc5c40fc5 100644 --- a/interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py +++ b/interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py @@ -24,14 +24,21 @@ import logging import re from collections import OrderedDict -from collections.abc import Sequence +from collections.abc import Mapping, Sequence from dataclasses import dataclass, field from pathlib import Path -from typing import Any, Final, Literal +from typing import Any, Final, Literal, cast +from cosl.backends.loki import LokiRuleBackend +from cosl.backends.prometheus import PrometheusRuleBackend +from cosl.cos_tool import CosTool from cosl.juju_topology import JujuTopology -from cosl.rules import HOST_METRICS_MISSING_RULE_NAME, Rules, generic_alert_groups -from cosl.types import OfficialRuleFileFormat, SingleRuleFormat +from cosl.rules import ( + HOST_METRICS_MISSING_RULE_NAME, # pyright: ignore[reportPrivateImportUsage] + GenericRules, + generic_alert_groups, # pyright: ignore[reportPrivateImportUsage] +) +from cosl.types import OfficialRuleFileFormat, OfficialRuleFileItem, SingleRuleFormat from cosl.utils import LZMABase64 from ops import CharmBase from pydantic import ( @@ -56,12 +63,12 @@ class RuleStore: """An API for users to provide rules of different types to the OtlpRequirer.""" topology: JujuTopology - logql: Rules = field(init=False) - promql: Rules = field(init=False) + logql: GenericRules[OfficialRuleFileItem] = field(init=False) + promql: GenericRules[OfficialRuleFileItem] = field(init=False) def __post_init__(self): - self.logql = Rules(query_type='logql', topology=self.topology) - self.promql = Rules(query_type='promql', topology=self.topology) + self.logql = GenericRules(backend=LokiRuleBackend(topology=self.topology)) + self.promql = GenericRules(backend=PrometheusRuleBackend(topology=self.topology)) def add_logql( self, @@ -319,7 +326,9 @@ def _duplicate_rules_per_unit( for juju_unit in sorted(peer_unit_names): rule_copy = copy.deepcopy(rule) rule_copy.get('labels', {})['juju_unit'] = juju_unit - rule_copy['expr'] = self._rules.promql.tool.inject_label_matchers( + rule_copy['expr'] = CosTool( + default_query_type='promql' + ).inject_label_matchers( expression=re.sub(r'%%juju_unit%%,?', '', rule_copy['expr']), topology={'juju_unit': juju_unit}, ) @@ -488,13 +497,13 @@ def rules(self) -> dict[int, RuleStore]: logger.error('OTLP databag failed validation: %s', e) continue - # Create a RuleStore for this relation's rules, and inject topology labels + # Validate and build RuleStore from requirer rules rules = RuleStore(self._topology) - logql_result = rules.logql.inject_and_validate_rules( - requirer.rules.logql, requirer.metadata + logql_result = rules.logql.validate( + cast('Mapping[str, list[OfficialRuleFileItem]]', requirer.rules.logql) ) - promql_result = rules.promql.inject_and_validate_rules( - requirer.rules.promql, requirer.metadata + promql_result = rules.promql.validate( + cast('Mapping[str, list[OfficialRuleFileItem]]', requirer.rules.promql) ) if logql_result.rules and not logql_result.errmsg: rules.logql.add(logql_result.rules) diff --git a/interfaces/otlp/tests/unit/test_rules.py b/interfaces/otlp/tests/unit/test_rules.py index a31e5a84b..db06a03f1 100644 --- a/interfaces/otlp/tests/unit/test_rules.py +++ b/interfaces/otlp/tests/unit/test_rules.py @@ -9,7 +9,7 @@ import ops import pytest from cosl.juju_topology import JujuTopology -from cosl.rules import HOST_METRICS_MISSING_RULE_NAME +from cosl.rules import HOST_METRICS_MISSING_RULE_NAME # pyright: ignore[reportPrivateImportUsage] from cosl.utils import LZMABase64 from ops import testing from ops.testing import Model, PeerRelation, Relation, State @@ -204,23 +204,8 @@ def test_metadata(otlp_requirer_ctx: testing.Context[ops.CharmBase]): } -@pytest.mark.parametrize( - 'metadata', - [ - {}, - { - 'model': MODEL_NAME, - 'model_uuid': MODEL_UUID, - 'application': 'otlp-requirer', - 'charm_name': 'otlp-requirer', - 'unit': 'otlp-requirer/0', - }, - ], -) -def test_provider_rules( - otlp_provider_ctx: testing.Context[ops.CharmBase], metadata: dict[str, Any] -): - # GIVEN a requirer offers unlabeled rules (of various types) in the databag +def test_provider_rules(otlp_provider_ctx: testing.Context[ops.CharmBase]): + # GIVEN a requirer offers rules in the databag rules = { 'logql': { 'groups': [ @@ -236,7 +221,7 @@ def test_provider_rules( }, } receiver = Relation( - RECEIVE, remote_app_data={'rules': json.dumps(rules), 'metadata': json.dumps(metadata)} + RECEIVE, remote_app_data={'rules': json.dumps(rules), 'metadata': json.dumps({})} ) state = State(leader=True, relations=[receiver], model=MODEL) with otlp_provider_ctx(otlp_provider_ctx.on.update_status(), state=state) as mgr: @@ -247,23 +232,8 @@ def test_provider_rules( # THEN LogQL and PromQL rules exist in the RuleStore assert logql assert promql - for result in [logql, promql]: - app = metadata['application'] if metadata else 'otlp-provider' - charm = metadata['charm_name'] if metadata else 'otlp-provider' - groups = result.get('groups', []) - assert groups - for group in groups: - for rule in group.get('rules', []): - # AND the rules are labeled with the provider's topology - assert rule.get('labels', {}).get('juju_model') == MODEL_NAME - assert rule.get('labels', {}).get('juju_model_uuid') == MODEL_UUID - assert rule.get('labels', {}).get('juju_application') == app - assert rule.get('labels', {}).get('juju_charm') == charm - - # AND the expressions are labeled - assert f'juju_model="{MODEL_NAME}"' in rule['expr'] - assert f'juju_model_uuid="{MODEL_UUID}"' in rule['expr'] - assert f'juju_application="{app}"' in rule['expr'] + assert logql.get('groups') + assert promql.get('groups') def _make_store() -> RuleStore: diff --git a/interfaces/otlp/uv.lock b/interfaces/otlp/uv.lock index dcfd08de9..2bf1ca6ca 100644 --- a/interfaces/otlp/uv.lock +++ b/interfaces/otlp/uv.lock @@ -39,7 +39,7 @@ unit = [ [package.metadata] requires-dist = [ - { name = "cosl", specifier = ">=1.6.1" }, + { name = "cosl", git = "https://github.com/swetha1654/cos-lib?rev=feat%2Frewrite-rules" }, { name = "requests" }, ] @@ -152,8 +152,8 @@ wheels = [ [[package]] name = "cosl" -version = "1.9.0" -source = { registry = "https://pypi.org/simple" } +version = "1.10.0" +source = { git = "https://github.com/swetha1654/cos-lib?rev=feat%2Frewrite-rules#48d7c5c759a9f3ef4200d839dcd883b7b96dcf48" } dependencies = [ { name = "ops" }, { name = "pydantic" }, @@ -161,10 +161,6 @@ dependencies = [ { name = "tenacity" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/21/96/bf85615902a6cc36bfe1c72b1dbc4e97076c6e05a28fa675a124146fc67c/cosl-1.9.0.tar.gz", hash = "sha256:360491aa2b6d37be36cefbeb31855aea19bf61ce36d6078dc1d09a51b5eefa92", size = 149850, upload-time = "2026-03-30T20:42:17.642Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/68/7e/65837d35b2dab96b70fe55aa4a4b4fb7d2cd902e87c6074493392de769d3/cosl-1.9.0-py3-none-any.whl", hash = "sha256:026700e712cae75a5db038710997b0ffa27612fddc736a5a23d94111dec1f533", size = 37936, upload-time = "2026-03-30T20:42:16.487Z" }, -] [[package]] name = "exceptiongroup" From c3c13393d5d19c41969825f81f28895858e40c71 Mon Sep 17 00:00:00 2001 From: Swetha Swaminathan Date: Wed, 22 Apr 2026 18:36:22 +0530 Subject: [PATCH 2/2] update cosl version --- interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py | 4 ++-- interfaces/otlp/tests/unit/test_rules.py | 2 +- interfaces/otlp/uv.lock | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py b/interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py index fc5c40fc5..298bcabfb 100644 --- a/interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py +++ b/interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py @@ -34,9 +34,9 @@ from cosl.cos_tool import CosTool from cosl.juju_topology import JujuTopology from cosl.rules import ( - HOST_METRICS_MISSING_RULE_NAME, # pyright: ignore[reportPrivateImportUsage] + HOST_METRICS_MISSING_RULE_NAME, GenericRules, - generic_alert_groups, # pyright: ignore[reportPrivateImportUsage] + generic_alert_groups, ) from cosl.types import OfficialRuleFileFormat, OfficialRuleFileItem, SingleRuleFormat from cosl.utils import LZMABase64 diff --git a/interfaces/otlp/tests/unit/test_rules.py b/interfaces/otlp/tests/unit/test_rules.py index db06a03f1..2097e0ff9 100644 --- a/interfaces/otlp/tests/unit/test_rules.py +++ b/interfaces/otlp/tests/unit/test_rules.py @@ -9,7 +9,7 @@ import ops import pytest from cosl.juju_topology import JujuTopology -from cosl.rules import HOST_METRICS_MISSING_RULE_NAME # pyright: ignore[reportPrivateImportUsage] +from cosl.rules import HOST_METRICS_MISSING_RULE_NAME from cosl.utils import LZMABase64 from ops import testing from ops.testing import Model, PeerRelation, Relation, State diff --git a/interfaces/otlp/uv.lock b/interfaces/otlp/uv.lock index 2bf1ca6ca..3e534d32f 100644 --- a/interfaces/otlp/uv.lock +++ b/interfaces/otlp/uv.lock @@ -153,7 +153,7 @@ wheels = [ [[package]] name = "cosl" version = "1.10.0" -source = { git = "https://github.com/swetha1654/cos-lib?rev=feat%2Frewrite-rules#48d7c5c759a9f3ef4200d839dcd883b7b96dcf48" } +source = { git = "https://github.com/swetha1654/cos-lib?rev=feat%2Frewrite-rules#c2ba23ace7b1951a6bd68d0053dcd44c7b271dc1" } dependencies = [ { name = "ops" }, { name = "pydantic" },