diff --git a/probes/README.md b/probes/README.md new file mode 100644 index 0000000..f0e79f8 --- /dev/null +++ b/probes/README.md @@ -0,0 +1,15 @@ +# External probes + +These probes are meant to be run from the host where the juju client is installed, + +```bash +juju export-bundle | ./bundle/probe_bundle.py +juju status --format=yaml | ./status/probe_status.py +``` + +or by piping in the bundle yaml, + +```bash +cat bundle.yaml | ./bundle/probe_bundle.py +cat status.yaml | ./status/probe_status.py +``` diff --git a/probes/bundle/probe_bundle.py b/probes/bundle/probe_bundle.py new file mode 100644 index 0000000..bd8e567 --- /dev/null +++ b/probes/bundle/probe_bundle.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 + +import sys +import yaml +from itertools import combinations +from collections import defaultdict + + +def one_grafana_agent_per_machine(bundle: dict): + applications = bundle["applications"] + relations = bundle["relations"] + + # A mapping from app name to the machines it is deployed on + principals_to_machines = {k: set(v["to"]) for k, v in applications.items() if "to" in v} + + # List of grafana-agent app names + grafana_agent_names = [k for k, v in applications.items() if v["charm"] == "grafana-agent"] + + principals_related_to_grafana_agent = [] + subordinates_related_to_grafana_agent = [] + for rel_pair in relations: + # Truncate the relation names + apps_pair = [s.split(':',-1)[0] for s in rel_pair] + + related_app = None + if apps_pair[0] in grafana_agent_names: + related_app = apps_pair[1] + elif apps_pair[1] in grafana_agent_names: + related_app = apps_pair[0] + + if related_app: + if related_app in principals_to_machines: + principals_related_to_grafana_agent.append(related_app) + elif related_app in applications: + subordinates_related_to_grafana_agent.append(related_app) + + for app1,app2 in combinations(principals_related_to_grafana_agent, 2): + if isect := principals_to_machines[app1].intersection(principals_to_machines[app2]): + print(f"grafana-agent is related to '{app1}', '{app2}' on the same machine(s) {isect}") + + +def one_grafana_agent_per_app(bundle: dict): + applications = bundle["applications"] + relations = bundle["relations"] + + # List of grafana-agent app names + grafana_agent_names = [k for k, v in applications.items() if v["charm"] == "grafana-agent"] + + counter = defaultdict(list) + for rel_pair in relations: + # Truncate the relation names + apps_pair = [s.split(':',-1)[0] for s in rel_pair] + + if apps_pair[0] in grafana_agent_names and apps_pair[1] in applications: + counter[apps_pair[1]].append(apps_pair[0]) + elif apps_pair[1] in grafana_agent_names and apps_pair[0] in applications: + counter[apps_pair[0]].append(apps_pair[1]) + + for k, v in counter.items(): + if len(v) > 1: + print(f"App '{k}' related to multiple grafana-agent apps '{v}'") + +if __name__ == '__main__': + bundle = yaml.safe_load(sys.stdin.read()) + one_grafana_agent_per_machine(bundle) + one_grafana_agent_per_app(bundle) + diff --git a/probes/status/probe_status.py b/probes/status/probe_status.py new file mode 100644 index 0000000..f2d25e0 --- /dev/null +++ b/probes/status/probe_status.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 + +import sys +import yaml + + +def one_grafana_agent_per_machine(status: dict): + # A mapping from grafana-agent app name to the list of apps it's subordinate to + agents = { + k: v["subordinate-to"] + for k, v in status["applications"].items() + if v["charm"] == "grafana-agent" + } + + for agent, principals in agents.items(): + # A mapping from app name to machines + machines = { + p: [u["machine"] for u in status["applications"][p].get("units", {}).values()] + for p in principals + } + + from itertools import combinations + + for p1, p2 in combinations(principals, 2): + if overlap := set(machines[p1]) & set(machines[p2]): + print( + f"{agent} is subordinate to both '{p1}', '{p2}' in the same machines {overlap}" + ) + + +def one_grafana_agent_per_app(status: dict): + # A mapping from grafana-agent app name to the list of apps it's subordinate to + agents = { + k: v["subordinate-to"] + for k, v in status["applications"].items() + if v["charm"] == "grafana-agent" + } + + for agent, principals in agents.items(): + for p in principals: + for name, unit in status["applications"][p].get("units", {}).items(): + subord_apps = {u.split("/", -1)[0] for u in unit["subordinates"].keys()} + subord_agents = subord_apps & agents.keys() + if len(subord_agents) > 1: + print( + f"{name} is related to more than one grafana-agent subordinate: {subord_agents}" + ) + + +if __name__ == '__main__': + status = yaml.safe_load(sys.stdin.read()) + one_grafana_agent_per_machine(status) + one_grafana_agent_per_app(status)