diff --git a/lib/charms/grafana_agent/v0/cos_agent.py b/lib/charms/grafana_agent/v0/cos_agent.py index 259a901..002056a 100644 --- a/lib/charms/grafana_agent/v0/cos_agent.py +++ b/lib/charms/grafana_agent/v0/cos_agent.py @@ -211,14 +211,14 @@ def __init__(self, *args): from collections import namedtuple from itertools import chain from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Optional, Set, Union +from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Optional, Set, Tuple, Union import pydantic from cosl import GrafanaDashboard, JujuTopology from cosl.rules import AlertRules from ops.charm import RelationChangedEvent from ops.framework import EventBase, EventSource, Object, ObjectEvents -from ops.model import Relation, Unit +from ops.model import Relation from ops.testing import CharmType if TYPE_CHECKING: @@ -258,6 +258,8 @@ class CosAgentProviderUnitData(pydantic.BaseModel): metrics_alert_rules: dict log_alert_rules: dict dashboards: List[GrafanaDashboard] + # 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] # The following entries may vary across units of the same principal app. @@ -277,9 +279,9 @@ class CosAgentPeersUnitData(pydantic.BaseModel): # We need the principal unit name and relation metadata to be able to render identifiers # (e.g. topology) on the leader side, after all the data moves into peer data (the grafana # agent leader can only see its own principal, because it is a subordinate charm). - principal_unit_name: str - principal_relation_id: str - principal_relation_name: str + unit_name: str + relation_id: str + relation_name: str # The only data that is forwarded to the leader is data that needs to go into the app databags # of the outgoing o11y relations. @@ -299,7 +301,7 @@ def app_name(self) -> str: TODO: Switch to using `model_post_init` when pydantic v2 is released? https://github.com/pydantic/pydantic/issues/1729#issuecomment-1300576214 """ - return self.principal_unit_name.split("/")[0] + return self.unit_name.split("/")[0] class COSAgentProvider(Object): @@ -375,7 +377,7 @@ def _on_refresh(self, event): dashboards=self._dashboards, metrics_scrape_jobs=self._scrape_jobs, log_slots=self._log_slots, - subordinate=self._charm.meta.subordinate, + subordinate=None, ) relation.data[self._charm.unit][data.KEY] = data.json() except ( @@ -468,12 +470,6 @@ class COSAgentRequirerEvents(ObjectEvents): validation_error = EventSource(COSAgentValidationError) -class MultiplePrincipalsError(Exception): - """Custom exception for when there are multiple principal applications.""" - - pass - - class COSAgentRequirer(Object): """Integration endpoint wrapper for the Requirer side of the cos_agent interface.""" @@ -559,13 +555,13 @@ def _on_relation_data_changed(self, event: RelationChangedEvent): if not (provider_data := self._validated_provider_data(raw)): return - # Copy data from the principal relation to the peer relation, so the leader could + # Copy data from the cos_agent relation to the peer relation, so the leader could # follow up. # Save the originating unit name, so it could be used for topology later on by the leader. data = CosAgentPeersUnitData( # peer relation databag model - principal_unit_name=event.unit.name, - principal_relation_id=str(event.relation.id), - principal_relation_name=event.relation.name, + unit_name=event.unit.name, + relation_id=str(event.relation.id), + relation_name=event.relation.name, metrics_alert_rules=provider_data.metrics_alert_rules, log_alert_rules=provider_data.log_alert_rules, dashboards=provider_data.dashboards, @@ -592,39 +588,7 @@ def trigger_refresh(self, _): self.on.data_changed.emit() # pyright: ignore @property - def _principal_unit(self) -> Optional[Unit]: - """Return the principal unit for a relation. - - Assumes that the relation is of type subordinate. - Relies on the fact that, for subordinate relations, the only remote unit visible to - *this unit* is the principal unit that this unit is attached to. - """ - if relations := self._principal_relations: - # Technically it's a list, but for subordinates there can only be one relation - principal_relation = next(iter(relations)) - if units := principal_relation.units: - # Technically it's a list, but for subordinates there can only be one - return next(iter(units)) - - return None - - @property - def _principal_relations(self): - relations = [] - for relation in self._charm.model.relations[self._relation_name]: - if not json.loads(relation.data[next(iter(relation.units))]["config"]).get( - ["subordinate"], False - ): - relations.append(relation) - if len(relations) > 1: - logger.error( - "Multiple applications claiming to be principal. Update the cos-agent library in the client application charms." - ) - raise MultiplePrincipalsError("Multiple principal applications.") - return relations - - @property - def _remote_data(self) -> List[CosAgentProviderUnitData]: + def _remote_data(self) -> List[Tuple[CosAgentProviderUnitData, JujuTopology]]: """Return a list of remote data from each of the related units. Assumes that the relation is of type subordinate. @@ -641,7 +605,16 @@ def _remote_data(self) -> List[CosAgentProviderUnitData]: continue if not (provider_data := self._validated_provider_data(raw)): continue - all_data.append(provider_data) + + # Apply topology + topology = JujuTopology( + model=self._charm.model.name, + model_uuid=self._charm.model.uuid, + application=unit.app.name, + unit=unit.name, + ) + + all_data.append((provider_data, topology)) return all_data @@ -711,7 +684,7 @@ def metrics_alerts(self) -> Dict[str, Any]: def metrics_jobs(self) -> List[Dict]: """Parse the relation data contents and extract the metrics jobs.""" scrape_jobs = [] - for data in self._remote_data: + for data, topology in self._remote_data: for job in data.metrics_scrape_jobs: # In #220, relation schema changed from a simplified dict to the standard # `scrape_configs`. @@ -727,6 +700,14 @@ def metrics_jobs(self) -> List[Dict]: "tls_config": {"insecure_skip_verify": True}, } + # Apply labels to the scrape jobs + for static_config in job.get("static_configs", []): + static_config["labels"] = { + # Be sure to keep labels from static_config + **static_config.get("labels", {}), + **topology.label_matcher_dict, + } + scrape_jobs.append(job) return scrape_jobs @@ -735,7 +716,7 @@ def metrics_jobs(self) -> List[Dict]: def snap_log_endpoints(self) -> List[SnapEndpoint]: """Fetch logging endpoints exposed by related snaps.""" plugs = [] - for data in self._remote_data: + for data, topology in self._remote_data: targets = data.log_slots if targets: for target in targets: @@ -775,7 +756,7 @@ def logs_alerts(self) -> Dict[str, Any]: model=self._charm.model.name, model_uuid=self._charm.model.uuid, application=app_name, - # For the topology unit, we could use `data.principal_unit_name`, but that unit + # For the topology unit, we could use `data.unit_name`, but that unit # name may not be very stable: `_gather_peer_data` de-duplicates by app name so # the exact unit name that turns up first in the iterator may vary from time to # time. So using the grafana-agent unit name instead. @@ -808,9 +789,9 @@ def dashboards(self) -> List[Dict[str, str]]: dashboards.append( { - "relation_id": data.principal_relation_id, + "relation_id": data.relation_id, # We have the remote charm name - use it for the identifier - "charm": f"{data.principal_relation_name}-{app_name}", + "charm": f"{data.relation_name}-{app_name}", "content": content, "title": title, } diff --git a/src/charm.py b/src/charm.py index 779c305..4dd5d2b 100755 --- a/src/charm.py +++ b/src/charm.py @@ -4,7 +4,6 @@ # See LICENSE file for licensing details. """A juju charm for Grafana Agent on Kubernetes.""" -import json import logging import os import re @@ -13,13 +12,13 @@ from pathlib import Path from typing import Any, Dict, List, Optional, Union -from charms.grafana_agent.v0.cos_agent import COSAgentRequirer, MultiplePrincipalsError +from charms.grafana_agent.v0.cos_agent import COSAgentRequirer from charms.operator_libs_linux.v2 import snap # type: ignore from cosl import JujuTopology from cosl.rules import AlertRules from grafana_agent import METRICS_RULES_SRC_PATH, GrafanaAgentCharm from ops.main import main -from ops.model import BlockedStatus, MaintenanceStatus, Relation, Unit +from ops.model import BlockedStatus, MaintenanceStatus, Relation logger = logging.getLogger(__name__) @@ -191,19 +190,12 @@ def _on_juju_info_joined(self, _event): def _on_cos_data_changed(self, event): """Trigger renewals of all data if there is a change.""" - try: - self._connect_logging_snap_endpoints() - self._update_config() - self._update_status() - self._update_metrics_alerts() - self._update_loki_alerts() - self._update_grafana_dashboards() - except MultiplePrincipalsError: - logger.error( - "Multiple applications claiming to be principle. Update the cos-agent library in the client application charms." - ) - self.unit.status = BlockedStatus("Multiple Principal Applications") - event.defer() + self._connect_logging_snap_endpoints() + self._update_config() + self._update_status() + self._update_metrics_alerts() + self._update_loki_alerts() + self._update_grafana_dashboards() def _on_cos_validation_error(self, event): msg_text = "Validation errors for cos-agent relation - check juju debug-log." @@ -265,25 +257,7 @@ def metrics_rules(self) -> Dict[str, Any]: """Return a list of metrics rules.""" rules = self._cos.metrics_alerts - # Determine the principal topology. - principal_topology = self.principal_topology - if principal_topology: - topology = JujuTopology( - model=principal_topology["juju_model"], - model_uuid=principal_topology["juju_model_uuid"], - application=principal_topology["juju_application"], - unit=principal_topology["juju_unit"], - ) - else: - return {} - - # Replace any existing topology labels with those from the principal. - for identifier in rules: - for group in rules[identifier]["groups"]: - for rule in group["rules"]: - rule["labels"]["juju_model"] = principal_topology["juju_model"] - rule["labels"]["juju_model_uuid"] = principal_topology["juju_model_uuid"] - rule["labels"]["juju_application"] = principal_topology["juju_application"] + topology = JujuTopology.from_charm(self) # Get the rules defined by Grafana Agent itself. own_rules = AlertRules(query_type="promql", topology=topology) @@ -297,16 +271,7 @@ def metrics_rules(self) -> Dict[str, Any]: def metrics_jobs(self) -> list: """Return a list of metrics scrape jobs.""" - jobs = self._cos.metrics_jobs - for job in jobs: - static_configs = job.get("static_configs", []) - for static_config in static_configs: - static_config["labels"] = { - # Be sure to keep labels from static_config - **static_config.get("labels", {}), - **self._principal_labels, - } - return jobs + return self._cos.metrics_jobs def logs_rules(self) -> Dict[str, Any]: """Return a list of logging rules.""" @@ -412,7 +377,7 @@ def _additional_integrations(self) -> Dict[str, Any]: "replacement": node_exporter_job_name, }, ] - + self._principal_relabeling_config, + + self.relabeling_config, } } @@ -439,14 +404,14 @@ def _additional_log_configs(self) -> List[Dict[str, Any]]: "targets": ["localhost"], "labels": { "__path__": "/var/log/**/*log", - **self._principal_labels, + **self._instance_labels, }, } ], }, { "job_name": "syslog", - "journal": {"labels": self._principal_labels}, + "journal": {"labels": self._instance_labels}, "pipeline_stages": [ { "drop": { @@ -466,69 +431,8 @@ def _agent_relations(self) -> List[Relation]: return self.model.relations["cos-agent"] + self.model.relations["juju-info"] @property - def _principal_relation(self) -> Optional[Relation]: - """The cos-agent relation, if the charm we're related to supports it, else juju-info.""" - # juju relate will do "the right thing" and default to cos-agent, falling back to - # juju-info if no cos-agent endpoint is available on the principal. - # Technically, if the charm is executing, there MUST be one of these two relations - # (otherwise, the subordinate won't even execute). However, for the sake of juju maybe not - # showing us the relation until after the first few install/start/config-changed, we err on - # the safe side and type this as Optional. - principal_relations = [] - for relation in self._agent_relations: - if not relation.units: - continue - if relation.name == "juju-info": - principal_relations.append(relation) - continue - relation_data = json.loads( - relation.data[next(iter(relation.units))].get("config", "{}") - ) - if not relation_data: - continue - if not relation_data.get("subordinate", False): - principal_relations.append(relation) - if len(principal_relations) > 1: - raise MultiplePrincipalsError("Multiple Principle Applications") - if len(principal_relations) == 1: - return principal_relations[0] - return None - - @property - def principal_unit(self) -> Optional[Unit]: - """Return the principal unit this charm is subordinated to.""" - relation = self._principal_relation - if relation and relation.units: - # Here, we could have popped the set and put the unit back or - # memoized the function, but in the interest of backwards compatibility - # with older python versions and avoiding adding temporary state to - # the charm instance, we choose this somewhat unsightly option. - return next(iter(relation.units)) - return None - - @property - def principal_topology( - self, - ) -> Dict[str, str]: - """Return the topology of the principal unit.""" - unit = self.principal_unit - if unit: - # Note we can't include juju_charm as that information is not available to us. - return { - "juju_model": self.model.name, - "juju_model_uuid": self.model.uuid, - "juju_application": unit.app.name, - "juju_unit": unit.name, - } - return {} - - @property - def _instance_topology(self) -> Dict[str, str]: - return self.principal_topology - - @property - def _principal_labels(self) -> Dict[str, str]: - """Return a dict with labels from the topology of the principal charm.""" + def _instance_labels(self) -> Dict[str, str]: + """Return a dict with labels from the topology of the this charm.""" return { # Dict ordering will give the appropriate result here "instance": self._instance_name, @@ -536,7 +440,7 @@ def _principal_labels(self) -> Dict[str, str]: } @property - def _principal_relabeling_config(self) -> list: + def relabeling_config(self) -> list: """Return a relabelling config with labels from the topology of the principal charm.""" topology_relabels = ( [ @@ -547,7 +451,7 @@ def _principal_relabeling_config(self) -> list: } for key, value in self._instance_topology.items() ] - if self._principal_labels + if self._instance_labels else [] ) diff --git a/src/grafana_agent.py b/src/grafana_agent.py index 1c5c23f..b49e8ba 100644 --- a/src/grafana_agent.py +++ b/src/grafana_agent.py @@ -22,7 +22,6 @@ from charms.certificate_transfer_interface.v0.certificate_transfer import ( CertificateTransferRequires, ) -from charms.grafana_agent.v0.cos_agent import MultiplePrincipalsError from charms.grafana_cloud_integrator.v0.cloud_config_requirer import ( GrafanaCloudConfigRequirer, ) @@ -549,11 +548,7 @@ def _update_config(self) -> None: else: self.delete_file(self._ca_path) - try: - config = self._generate_config() - except MultiplePrincipalsError as e: - self.status.update_config = BlockedStatus(str(e)) - return + config = self._generate_config() try: old_config = yaml.safe_load(self.read_file(CONFIG_PATH)) diff --git a/tests/scenario/test_machine_charm/test_alert_labels.py b/tests/scenario/test_machine_charm/test_alert_labels.py index f7962b6..bd46aaa 100644 --- a/tests/scenario/test_machine_charm/test_alert_labels.py +++ b/tests/scenario/test_machine_charm/test_alert_labels.py @@ -118,4 +118,10 @@ def test_metrics_alert_rule_labels(vroot): alert_rules = json.loads(state_2.relations[2].local_app_data["alert_rules"]) for group in alert_rules["groups"]: for rule in group["rules"]: - assert rule["labels"]["juju_application"] == "primary" + if "grafana-agent_alertgroup_alerts" in group["name"]: + assert ( + rule["labels"]["juju_application"] == "primary" + or rule["labels"]["juju_application"] == "subordinate" + ) + else: + assert rule["labels"]["juju_application"] == "grafana-agent" diff --git a/tests/scenario/test_machine_charm/test_cos_agent_e2e.py b/tests/scenario/test_machine_charm/test_cos_agent_e2e.py index 0521af6..991803c 100644 --- a/tests/scenario/test_machine_charm/test_cos_agent_e2e.py +++ b/tests/scenario/test_machine_charm/test_cos_agent_e2e.py @@ -22,6 +22,7 @@ def placeholder_cfg_path(tmp_path): return tmp_path / "foo.yaml" +PROVIDER_NAME = "mock-principal" PROM_RULE = """alert: HostCpuHighIowait expr: avg by (instance) (rate(node_cpu_seconds_total{mode="iowait"}[5m])) * 100 > 10 for: 0m @@ -93,7 +94,7 @@ def snap_is_installed(): def provider_charm(): class MyPrincipal(CharmBase): META = { - "name": "mock-principal", + "name": PROVIDER_NAME, "provides": { "cos-agent": {"interface": "cos_agent", "scope": "container"}, }, @@ -189,9 +190,9 @@ def test_subordinate_update(requirer_ctx): peer_out_data = json.loads( peer_out.local_unit_data[f"{CosAgentPeersUnitData.KEY}-mock-principal/0"] ) - assert peer_out_data["principal_unit_name"] == "mock-principal/0" - assert peer_out_data["principal_relation_id"] == str(cos_agent1.relation_id) - assert peer_out_data["principal_relation_name"] == cos_agent1.endpoint + assert peer_out_data["unit_name"] == f"{PROVIDER_NAME}/0" + assert peer_out_data["relation_id"] == str(cos_agent1.relation_id) + assert peer_out_data["relation_name"] == cos_agent1.endpoint # passthrough as-is assert peer_out_data["metrics_alert_rules"] == config["metrics_alert_rules"] diff --git a/tests/scenario/test_machine_charm/test_multiple_subordinates.py b/tests/scenario/test_machine_charm/test_multiple_subordinates.py index 92dbe75..9fc0800 100644 --- a/tests/scenario/test_machine_charm/test_multiple_subordinates.py +++ b/tests/scenario/test_machine_charm/test_multiple_subordinates.py @@ -4,8 +4,6 @@ import json import charm -import pytest -from charms.grafana_agent.v0.cos_agent import MultiplePrincipalsError from scenario import Context, PeerRelation, State, SubordinateRelation from tests.scenario.helpers import get_charm_meta @@ -18,7 +16,6 @@ def post_event(charm: charm.GrafanaAgentMachineCharm): assert not charm._cos.logs_alerts assert not charm._cos.metrics_alerts assert len(charm._cos.metrics_jobs) == 1 - assert charm._principal_relation.name == "juju-info" cos_agent_data = { "config": json.dumps( @@ -64,8 +61,6 @@ def post_event(charm: charm.GrafanaAgentMachineCharm): assert not charm._cos.logs_alerts assert not charm._cos.metrics_alerts assert len(charm._cos.metrics_jobs) == 2 - assert charm._principal_relation.name == "cos-agent" - assert charm._principal_relation.app.name == "primary" cos_agent_primary_data = { "config": json.dumps( @@ -127,68 +122,3 @@ def post_event(charm: charm.GrafanaAgentMachineCharm): context.run( event=cos_agent_subordinate_relation.changed_event, state=out_state, post_event=post_event ) - - -def test_two_cos_primary_relations(vroot): - def post_event(charm: charm.GrafanaAgentMachineCharm): - with pytest.raises(MultiplePrincipalsError): - charm._principal_relation - - cos_agent_primary_data = { - "config": json.dumps( - { - "subordinate": False, - "metrics_alert_rules": {}, - "log_alert_rules": {}, - "dashboards": [ - "/Td6WFoAAATm1rRGAgAhARYAAAB0L+WjAQAmCnsKICAidGl0bGUiOiAi" - "Zm9vIiwKICAiYmFyIiA6ICJiYXoiCn0KAACkcc0YFt15xAABPyd8KlLdH7bzfQEAAAAABFla" - ], - "metrics_scrape_jobs": [ - {"job_name": "primary_0", "path": "/metrics", "port": "8080"} - ], - "log_slots": ["foo:bar"], - } - ) - } - - cos_agent_subordinate_data = { - "config": json.dumps( - { - "subordinate": False, - "metrics_alert_rules": {}, - "log_alert_rules": {}, - "dashboards": [ - "/Td6WFoAAATm1rRGAgAhARYAAAB0L+WjAQAmCnsKICAidGl0bGUiOiAi" - "Zm9vIiwKICAiYmFyIiA6ICJiYXoiCn0KAACkcc0YFt15xAABPyd8KlLdH7bzfQEAAAAABFla" - ], - "metrics_scrape_jobs": [ - {"job_name": "subordinate_0", "path": "/metrics", "port": "8081"} - ], - "log_slots": ["oh:snap"], - } - ) - } - - cos_agent_primary_relation = SubordinateRelation( - "cos-agent", remote_app_name="primary", remote_unit_data=cos_agent_primary_data - ) - cos_agent_subordinate_relation = SubordinateRelation( - "cos-agent", remote_app_name="subordinate", remote_unit_data=cos_agent_subordinate_data - ) - - context = Context( - charm_type=charm.GrafanaAgentMachineCharm, - meta=get_charm_meta(charm.GrafanaAgentMachineCharm), - charm_root=vroot, - ) - state = State( - relations=[ - cos_agent_primary_relation, - cos_agent_subordinate_relation, - PeerRelation("peers"), - ] - ) - context.run( - event=cos_agent_subordinate_relation.changed_event, state=state, post_event=post_event - ) diff --git a/tests/scenario/test_machine_charm/test_peer_relation.py b/tests/scenario/test_machine_charm/test_peer_relation.py index 0b2bac1..c415004 100644 --- a/tests/scenario/test_machine_charm/test_peer_relation.py +++ b/tests/scenario/test_machine_charm/test_peer_relation.py @@ -30,9 +30,9 @@ def test_fetch_data_from_relation(): relation.units = [] # there should be remote units in here, presumably config = { - "principal_unit_name": "principal/0", - "principal_relation_id": "0", - "principal_relation_name": "foo", + "unit_name": "principal/0", + "relation_id": "0", + "relation_name": "foo", "dashboards": [encode_as_dashboard(py_dash)], } relation.app = app @@ -201,9 +201,9 @@ def test_cosagent_to_peer_data_flow_relation(leader): peers_data={ 1: { f"{CosAgentPeersUnitData.KEY}-primary/0": CosAgentPeersUnitData( - principal_unit_name="primary/0", - principal_relation_id="42", - principal_relation_name="foobar-relation", + unit_name="primary/0", + relation_id="42", + relation_name="foobar-relation", dashboards=[encode_as_dashboard(raw_dashboard_1)], ).json() } @@ -311,9 +311,9 @@ def test_cosagent_to_peer_data_app_vs_unit(leader): peers_data={ 1: { f"{CosAgentPeersUnitData.KEY}-primary/23": CosAgentPeersUnitData( - principal_unit_name="primary/23", - principal_relation_id="42", - principal_relation_name="cos-agent", + unit_name="primary/23", + relation_id="42", + relation_name="cos-agent", # data coming from `primary` is here: dashboards=data_1.dashboards, metrics_alert_rules=data_1.metrics_alert_rules, diff --git a/tests/scenario/test_machine_charm/test_relation_priority.py b/tests/scenario/test_machine_charm/test_relation_priority.py index 2de659a..257e5df 100644 --- a/tests/scenario/test_machine_charm/test_relation_priority.py +++ b/tests/scenario/test_machine_charm/test_relation_priority.py @@ -6,7 +6,6 @@ import charm import pytest -from charms.grafana_agent.v0.cos_agent import MultiplePrincipalsError from cosl import GrafanaDashboard from scenario import Context, PeerRelation, State, SubordinateRelation @@ -43,9 +42,6 @@ def post_event(charm: charm.GrafanaAgentMachineCharm): assert not charm._cos.metrics_jobs assert not charm._cos.snap_log_endpoints - assert not charm._principal_relation - assert not charm.principal_unit - set_run_out(mock_run, 0) trigger("start", State(), post_event=post_event, vroot=vroot) @@ -59,9 +55,6 @@ def post_event(charm: charm.GrafanaAgentMachineCharm): assert not charm._cos.metrics_jobs assert not charm._cos.snap_log_endpoints - assert charm._principal_relation - assert charm.principal_unit - set_run_out(mock_run, 0) trigger( "start", @@ -86,9 +79,6 @@ def post_event(charm: charm.GrafanaAgentMachineCharm): assert not charm._cos.metrics_alerts assert charm._cos.metrics_jobs - assert charm._principal_relation.name == "cos-agent" - assert charm.principal_unit.name == "mock-principal/0" - set_run_out(mock_run, 0) cos_agent_data = { @@ -111,9 +101,9 @@ def post_event(charm: charm.GrafanaAgentMachineCharm): peer_data = { "config": json.dumps( { - "principal_unit_name": "foo", - "principal_relation_id": "2", - "principal_relation_name": "peers", + "unit_name": "foo", + "relation_id": "2", + "relation_name": "peers", "metrics_alert_rules": {}, "log_alert_rules": {}, "dashboards": [GrafanaDashboard._serialize('{"very long": "dashboard"}')], @@ -146,10 +136,6 @@ def post_event(charm: charm.GrafanaAgentMachineCharm): assert not charm._cos.metrics_alerts assert charm._cos.metrics_jobs - # Trying to get the principal should raise an exception. - with pytest.raises(MultiplePrincipalsError): - assert charm._principal_relation - set_run_out(mock_run, 0) cos_agent_data = { @@ -172,9 +158,9 @@ def post_event(charm: charm.GrafanaAgentMachineCharm): peer_data = { "config": json.dumps( { - "principal_unit_name": "foo", - "principal_relation_id": "2", - "principal_relation_name": "peers", + "unit_name": "foo", + "relation_id": "2", + "relation_name": "peers", "metrics_alert_rules": {}, "log_alert_rules": {}, "dashboards": [GrafanaDashboard._serialize('{"very long": "dashboard"}')], diff --git a/tox.ini b/tox.ini index 3d4267b..fe185ea 100644 --- a/tox.ini +++ b/tox.ini @@ -17,6 +17,7 @@ setenv = PYTHONPATH = {toxinidir}:{toxinidir}/lib:{[vars]src_path} PYTHONBREAKPOINT=ipdb.set_trace PY_COLORS=1 +skip_install=True #passenv = # PYTHONPATH # HOME @@ -28,7 +29,6 @@ setenv = # NO_PROXY [testenv:fmt] -skip_install=True description = Apply coding style standards to code deps = black @@ -38,7 +38,6 @@ commands = black {[vars]all_path} [testenv:lint] -skip_install=True description = Check code against coding style standards deps = black @@ -50,7 +49,6 @@ commands = black --check --diff {[vars]all_path} [testenv:static-{charm,lib}] -skip_install=True description = Run static analysis checks deps = pyright @@ -61,7 +59,6 @@ commands = lib: pyright {[vars]lib_path} [testenv:unit] -skip_install=True description = Run machine charm unit tests deps = -r{toxinidir}/requirements.txt @@ -103,7 +100,6 @@ commands = pytest -v --tb native --log-cli-level=INFO -s {posargs} {[vars]tst_path}/integration [testenv:check] -skip_install=True depends = lint static