-
Notifications
You must be signed in to change notification settings - Fork 28
feat(otlp): Update otlp to use generic rules class #437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
| GenericRules, | ||
| generic_alert_groups, | ||
| ) | ||
| 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) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new |
||
| ) | ||
| if logql_result.rules and not logql_result.errmsg: | ||
| rules.logql.add(logql_result.rules) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'] | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the provier does not do topology injection anymore so we can remove this part of the test |
||
| assert logql.get('groups') | ||
| assert promql.get('groups') | ||
|
|
||
|
|
||
| def _make_store() -> RuleStore: | ||
|
|
||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self._rules.promqlis of typeGenericRulesnow and doesn't expose a tool attribute anymore so we can either useself._rules.promql.backend.tool.inject_label_matchersor we can use CosTool directly. I decided to use CosTool to keep it simple.