network/gaiia/compare_pppoe.py
2026-07-19 14:42:02 -05:00

249 lines
9.4 KiB
Python

"""Compare PPPoE active sessions from climax MikroTik against Gaiia accounts.
Checks:
1. Is the PPPoE username found as a Gaiia account?
2. Does the Gaiia account's inventory item IP match the PPPoE IP?
Usage: cd /Users/graham/dev/network/gaiia && uv run python compare_pppoe.py
"""
from __future__ import annotations
import os
import sys
from typing import Any
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
from gaiia.client import GaiiaClient
# PPPoE active sessions from climax (2026-07-02)
PPPOE_SESSIONS: list[dict[str, str]] = [
{"name": "charlesboone", "address": "100.64.7.247", "mac": "C8:9E:43:E3:99:EA"},
{"name": "amberprater", "address": "100.64.7.243", "mac": "6C:CD:D6:2C:31:94"},
{"name": "matthewgoodwin", "address": "100.64.7.241", "mac": "34:98:B5:44:42:A4"},
{"name": "robbymccollom", "address": "100.64.7.239", "mac": "D8:B3:70:92:0C:C6"},
{"name": "gordonhamilton", "address": "100.64.7.237", "mac": "A0:63:91:3A:29:B1"},
{"name": "chasewilliams", "address": "100.64.7.235", "mac": "38:94:ED:5B:82:E9"},
{"name": "cynthiajones", "address": "100.64.7.233", "mac": "E4:FA:C4:56:CA:D9"},
{"name": "janicealexander", "address": "100.64.7.225", "mac": "E0:D3:62:E8:65:C5"},
{"name": "michaelray", "address": "204.110.188.38", "mac": "9C:05:D6:59:FF:63"},
{"name": "marysmelser", "address": "100.64.7.218", "mac": "00:01:9F:33:54:B9"},
{"name": "eddieyarbrough", "address": "100.64.7.216", "mac": "10:C3:7B:42:D8:B8"},
{"name": "timgilbert", "address": "100.64.7.213", "mac": "94:A6:7E:EE:98:B8"},
{"name": "chadwhitsell", "address": "100.64.7.211", "mac": "04:95:E6:76:3C:91"},
{"name": "donnacampbell", "address": "100.64.7.209", "mac": "74:FE:CE:3A:6D:AC"},
{"name": "bryangoulart", "address": "100.64.7.205", "mac": "8C:90:2D:E9:90:7A"},
{"name": "crystalharney", "address": "100.64.7.201", "mac": "98:25:4A:04:B6:43"},
{"name": "johnvayo", "address": "100.64.7.197", "mac": "98:25:4A:04:B5:89"},
{"name": "richardbarragan", "address": "100.64.7.193", "mac": "8C:90:2D:E9:98:E1"},
{"name": "carolstrickland", "address": "100.64.7.189", "mac": "E8:DA:00:18:54:45"},
{"name": "timbagert", "address": "100.64.7.140", "mac": "74:FE:CE:3A:6D:8E"},
{"name": "douggarber", "address": "100.64.7.136", "mac": "00:01:9F:33:79:A9"},
{"name": "janetkern", "address": "100.64.7.61", "mac": "28:C6:8E:C1:06:AF"},
{"name": "gregmcintire", "address": "100.64.7.59", "mac": "66:62:8B:10:10:65"},
{"name": "jenniferboon", "address": "100.64.7.35", "mac": "98:25:4A:04:BA:EA"},
{"name": "glendabeauchamp", "address": "100.64.7.15", "mac": "8C:90:2D:E9:97:85"},
{"name": "jmichaelculverhouse", "address": "100.64.7.250", "mac": "34:98:B5:31:D3:3A"},
{"name": "elviraquezada", "address": "100.64.7.249", "mac": "E0:C2:50:47:DC:1A"},
{"name": "tammieventris", "address": "100.64.7.232", "mac": "98:25:4A:04:A5:E1"},
{"name": "taekim", "address": "204.110.188.42", "mac": "9C:5C:8E:44:85:B4"},
{"name": "ruthfengler", "address": "100.64.7.246", "mac": "A0:63:91:25:F6:E5"},
{"name": "buddyswan", "address": "100.64.7.245", "mac": "00:01:9F:34:D2:21"},
{"name": "rhondabolton", "address": "100.64.7.231", "mac": "A0:04:60:94:D5:FF"},
]
# Known PPPoE-name → Gaiia-displayName mismatches
MANUAL_MAP: dict[str, str] = {
"bryangoulart": "Brian Goulart", # typo in PPPoE username
}
def flatten(name: str) -> str:
"""Lowercase and remove spaces/punctuation from a name."""
return name.lower().replace(" ", "").replace("-", "").replace("'", "")
def fetch_all_accounts(client: GaiiaClient) -> list[dict[str, Any]]:
"""Fetch all Gaiia accounts (id, readableId, displayName)."""
query = """
query($after: String) {
accounts(first: 50, after: $after) {
nodes { id readableId displayName }
pageInfo { hasNextPage endCursor }
totalCount
}
}
"""
accounts: list[dict[str, Any]] = []
cursor: str | None = None
has_next = True
while has_next:
variables: dict[str, Any] = {}
if cursor:
variables["after"] = cursor
data = client.query(query, variables)
conn = data["accounts"]
accounts.extend(conn.get("nodes") or [])
has_next = conn["pageInfo"]["hasNextPage"]
cursor = conn["pageInfo"].get("endCursor")
print(f" Fetched {len(accounts)}/{conn.get('totalCount', '?')} accounts...",
file=sys.stderr)
return accounts
def fetch_account_inventory(
client: GaiiaClient, account_id: str
) -> list[dict[str, Any]]:
"""Fetch inventory items for a specific account, including IP addresses."""
query = """
query($id: GlobalID!) {
account(id: $id) {
assignedInventoryItems(first: 10) {
nodes {
inventoryItem {
id
ipAddressV4
ipAddressV6
model {
name
manufacturer { name }
}
}
}
}
}
}
"""
data = client.query(query, {"id": account_id})
acct = data.get("account")
if not acct:
return []
items = acct.get("assignedInventoryItems", {})
assignations = items.get("nodes") or []
# Extract the actual inventory items from assignations
result = []
for assign in assignations:
item = assign.get("inventoryItem")
if item:
result.append(item)
return result
def main() -> None:
api_key = os.environ.get("GAIIA_KEY")
if not api_key:
print("Error: GAIIA_KEY env var not set", file=sys.stderr)
sys.exit(1)
client = GaiiaClient(api_key, timezone="America/Chicago")
print("Fetching all accounts from Gaiia...", file=sys.stderr)
accounts = fetch_all_accounts(client)
print(f"Total: {len(accounts)} accounts\n", file=sys.stderr)
# Build lookup: flattened displayName → account
by_flat: dict[str, dict[str, Any]] = {}
for a in accounts:
by_flat[flatten(a.get("displayName", ""))] = a
# --- Step 1: Match PPPoE names to Gaiia accounts ---
print("=" * 75)
print("CLIMAX PPPoE → GAIIA ACCOUNT MATCHING")
print("=" * 75)
matched: list[dict[str, Any]] = []
unmatched: list[dict[str, Any]] = []
for session in PPPOE_SESSIONS:
pname = session["name"]
flat = flatten(pname)
account = by_flat.get(flat)
# Try manual map
if not account and pname in MANUAL_MAP:
mapped_flat = flatten(MANUAL_MAP[pname])
account = by_flat.get(mapped_flat)
session["_account"] = account
if account:
matched.append(session)
else:
unmatched.append(session)
print(f"{'PPPoE':<25} {'PPPoE IP':<18} {'Gaiia Name':<25} {'Account ID':<8}")
print("-" * 78)
for s in matched:
acc = s["_account"]
print(f"{s['name']:<25} {s['address']:<18} {acc['displayName']:<25} {acc['readableId']:<8}")
for s in unmatched:
print(f"{s['name']:<25} {s['address']:<18} {'(not in Gaiia)':<25} {'-':<8}")
print(f"\nMatched: {len(matched)} | Not in Gaiia: {len(unmatched)}")
if unmatched:
print("Not in Gaiia:")
for s in unmatched:
print(f" - {s['name']} ({s['address']})")
# --- Step 2: Compare IP addresses from inventory items ---
print(f"\n{'=' * 75}")
print("IP ADDRESS COMPARISON (PPPoE active IP vs Gaiia inventory item IP)")
print("=" * 75)
ip_matched = 0
ip_mismatched = 0
ip_no_inventory = 0
ip_not_checked = 0
for s in matched:
acc = s["_account"]
pppoe_ip = s["address"]
pppoe_name = s["name"]
# Fetch inventory items for this account
items = fetch_account_inventory(client, acc["id"])
s["_items"] = items
if not items:
print(f"{pppoe_name:<25} {pppoe_ip:<18} (no inventory items in Gaiia)")
ip_no_inventory += 1
continue
# Check if any inventory item has an IP matching the PPPoE IP
found_match = False
for item in items:
item_ip = item.get("ipAddressV4")
if item_ip == pppoe_ip:
model = item.get("model", {})
model_name = model.get("name", "?") if model else "?"
print(f"{pppoe_name:<25} {pppoe_ip:<18} MATCH (item: {model_name})")
ip_matched += 1
found_match = True
break
if not found_match:
item_ips = [i.get("ipAddressV4") for i in items if i.get("ipAddressV4")]
if item_ips:
print(f"{pppoe_name:<25} {pppoe_ip:<18} MISMATCH (Gaiia has: {', '.join(item_ips)})")
ip_mismatched += 1
else:
print(f"{pppoe_name:<25} {pppoe_ip:<18} NO IP SET (has inventory items but no IPs)")
ip_no_inventory += 1
print(f"\n--- Summary ---")
print(f"Accounts in Gaiia: {len(matched)}/{len(PPPOE_SESSIONS)}")
print(f"Not in Gaiia: {len(unmatched)}")
print(f"IP matches PPPoE: {ip_matched}")
print(f"IP mismatch (different IP): {ip_mismatched}")
print(f"No inventory / no IP in Gaiia: {ip_no_inventory}")
if unmatched:
print(f"\nAccounts missing from Gaiia (need investigation):")
for s in unmatched:
mac = s.get("mac", "")
print(f" {s['name']} IP={s['address']} MAC={mac}")
client.close()
if __name__ == "__main__":
main()