From 27d41674d0acac888fdb5eb77f7cc18a6cc9c38c Mon Sep 17 00:00:00 2001 From: Noctua <98737499+observability-noctua-bot@users.noreply.github.com> Date: Fri, 10 Mar 2023 21:06:54 +0100 Subject: [PATCH] chore: update charm libraries (#137) Co-authored-by: Github Actions --- .../prometheus_k8s/v0/prometheus_scrape.py | 109 +++++++++++++----- 1 file changed, 82 insertions(+), 27 deletions(-) diff --git a/lib/charms/prometheus_k8s/v0/prometheus_scrape.py b/lib/charms/prometheus_k8s/v0/prometheus_scrape.py index 752cfce6..5e75ac06 100644 --- a/lib/charms/prometheus_k8s/v0/prometheus_scrape.py +++ b/lib/charms/prometheus_k8s/v0/prometheus_scrape.py @@ -368,7 +368,7 @@ def _on_scrape_targets_changed(self, event): # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 31 +LIBPATCH = 32 logger = logging.getLogger(__name__) @@ -1125,6 +1125,7 @@ def jobs(self) -> list: return scrape_jobs + @property def alerts(self) -> dict: """Fetch alerts for all relations. @@ -1175,37 +1176,40 @@ def alerts(self) -> dict: if not alert_rules: continue - try: - scrape_metadata = json.loads(relation.data[relation.app]["scrape_metadata"]) - identifier = JujuTopology.from_dict(scrape_metadata).identifier - alerts[identifier] = self._tool.apply_label_matchers(alert_rules) - - except KeyError as e: - logger.debug( - "Relation %s has no 'scrape_metadata': %s", - relation.id, - e, - ) - identifier = self._get_identifier_by_alert_rules(alert_rules) + alert_rules = self._inject_alert_expr_labels(alert_rules) + + identifier, topology = self._get_identifier_by_alert_rules(alert_rules) + if not topology: + try: + scrape_metadata = json.loads(relation.data[relation.app]["scrape_metadata"]) + identifier = JujuTopology.from_dict(scrape_metadata).identifier + alerts[identifier] = self._tool.apply_label_matchers(alert_rules) # type: ignore + + except KeyError as e: + logger.debug( + "Relation %s has no 'scrape_metadata': %s", + relation.id, + e, + ) if not identifier: logger.error( - "Alert rules were found but no usable group or identifier was present" + "Alert rules were found but no usable group or identifier was present." ) continue - alerts[identifier] = alert_rules - _, errmsg = self._tool.validate_alert_rules(alert_rules) if errmsg: - if alerts[identifier]: - del alerts[identifier] relation.data[self._charm.app]["event"] = json.dumps({"errors": errmsg}) continue + alerts[identifier] = alert_rules + return alerts - def _get_identifier_by_alert_rules(self, rules: dict) -> Union[str, None]: + def _get_identifier_by_alert_rules( + self, rules: dict + ) -> Tuple[Union[str, None], Union[JujuTopology, None]]: """Determine an appropriate dict key for alert rules. The key is used as the filename when writing alerts to disk, so the structure @@ -1213,21 +1217,28 @@ def _get_identifier_by_alert_rules(self, rules: dict) -> Union[str, None]: Args: rules: a dict of alert rules + Returns: + A tuple containing an identifier, if found, and a JujuTopology, if it could + be constructed. """ if "groups" not in rules: logger.debug("No alert groups were found in relation data") - return None + return None, None # Construct an ID based on what's in the alert rules if they have labels for group in rules["groups"]: try: labels = group["rules"][0]["labels"] - identifier = "{}_{}_{}".format( - labels["juju_model"], - labels["juju_model_uuid"], - labels["juju_application"], + topology = JujuTopology( + # Don't try to safely get required constructor fields. There's already + # a handler for KeyErrors + model_uuid=labels["juju_model_uuid"], + model=labels["juju_model"], + application=labels["juju_application"], + unit=labels.get("juju_unit", ""), + charm_name=labels.get("juju_charm", ""), ) - return identifier + return topology.identifier, topology except KeyError: logger.debug("Alert rules were found but no usable labels were present") continue @@ -1238,11 +1249,55 @@ def _get_identifier_by_alert_rules(self, rules: dict) -> Union[str, None]: ) try: for group in rules["groups"]: - return group["name"] + return group["name"], None except KeyError: logger.debug("No group name was found to use as identifier") - return None + return None, None + + def _inject_alert_expr_labels(self, rules: Dict[str, Any]) -> Dict[str, Any]: + """Iterate through alert rules and inject topology into expressions. + + Args: + rules: a dict of alert rules + """ + if "groups" not in rules: + return rules + + modified_groups = [] + for group in rules["groups"]: + # Copy off rules, so we don't modify an object we're iterating over + rules_copy = group["rules"] + for idx, rule in enumerate(rules_copy): + labels = rule.get("labels") + + if labels: + try: + topology = JujuTopology( + # Don't try to safely get required constructor fields. There's already + # a handler for KeyErrors + model_uuid=labels["juju_model_uuid"], + model=labels["juju_model"], + application=labels["juju_application"], + unit=labels.get("juju_unit", ""), + charm_name=labels.get("juju_charm", ""), + ) + + # Inject topology and put it back in the list + rule["expr"] = self._tool.inject_label_matchers( + re.sub(r"%%juju_topology%%,?", "", rule["expr"]), + topology.label_matcher_dict, + ) + except KeyError: + # Some required JujuTopology key is missing. Just move on. + pass + + group["rules"][idx] = rule + + modified_groups.append(group) + + rules["groups"] = modified_groups + return rules def _static_scrape_config(self, relation) -> list: """Generate the static scrape configuration for a single relation.