transceiver-db/scripts/generate-compatibility.py

193 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Generate switch-transceiver compatibility entries based on speed matching.
Runs directly on Erik with psql. Logic:
- Match transceivers to switches based on speed tiers
- 400G switches support 400G, 100G, 40G, 25G, 10G transceivers
- 800G switches support all speeds
- 100G switches support 100G, 40G, 25G, 10G
- 25G switches support 25G, 10G
- 10G switches support 10G, 1G
- Mark FLEXOPTIX transceivers as 'verified' (primary vendor)
- Mark others as 'compatible' (community reported)
"""
import subprocess
import json
import sys
import time
import os
import uuid
LOG = "/tmp/generate-compat.log"
SQL_OUT = "/tmp/compatibility.sql"
def log(msg):
with open(LOG, "a") as f:
f.write(msg + "\n")
print(msg, file=sys.stderr)
def query(sql):
r = subprocess.run(
["psql", "-h", "localhost", "-p", "5433", "-U", "tip", "-d", "transceiver_db",
"-t", "-A", "-F", "|", "-c", sql],
capture_output=True, text=True,
env={**os.environ, "PGPASSWORD": "tip_prod_2026"}
)
rows = []
for line in r.stdout.strip().split("\n"):
if line.strip():
rows.append(line.split("|"))
return rows
log(f"{time.strftime('%Y-%m-%d %H:%M:%S')}: Generating compatibility data")
# Get all switches with speed
switches = query("""
SELECT s.id, s.model, s.max_speed_gbps, v.name
FROM switches s JOIN vendors v ON s.vendor_id = v.id
WHERE s.max_speed_gbps IS NOT NULL
ORDER BY s.max_speed_gbps DESC
""")
log(f"Switches: {len(switches)}")
# Get all transceivers with speed info
transceivers = query("""
SELECT t.id, t.standard_name, t.part_number, t.speed_gbps, v.name as vendor
FROM transceivers t JOIN vendors v ON t.vendor_id = v.id
WHERE t.speed_gbps IS NOT NULL
ORDER BY t.speed_gbps DESC
""")
log(f"Transceivers with speed: {len(transceivers)}")
# Speed compatibility tiers
# A switch with max_speed X supports transceivers at speed Y if Y <= X
# But with reasonable limits - a 400G switch doesn't typically use 1G SFPs
SPEED_COMPAT = {
800: [800, 400, 200, 100, 50, 40, 25],
400: [400, 200, 100, 50, 40, 25, 10],
200: [200, 100, 50, 40, 25, 10],
100: [100, 50, 40, 25, 10],
50: [50, 25, 10],
40: [40, 25, 10],
25: [25, 10, 1],
10: [10, 1],
1: [1],
}
sql_lines = [
f"-- Compatibility entries generated {time.strftime('%Y-%m-%d %H:%M')}",
"-- Based on speed-tier matching",
"",
"-- Clear existing (regenerate)",
"DELETE FROM compatibility;",
"",
]
count = 0
for sw in switches:
sw_id, sw_model, sw_speed_str, sw_vendor = sw[0], sw[1], sw[2], sw[3]
try:
sw_speed = int(float(sw_speed_str))
except (ValueError, TypeError):
continue
compatible_speeds = SPEED_COMPAT.get(sw_speed, [sw_speed])
for tx in transceivers:
tx_id, tx_name, tx_pn, tx_speed_str, tx_vendor = tx[0], tx[1], tx[2], tx[3], tx[4]
try:
tx_speed = int(float(tx_speed_str))
except (ValueError, TypeError):
continue
if tx_speed not in compatible_speeds:
continue
# Determine verification status
# Allowed: status=compatible|incompatible|partial|unknown
# Allowed: method=tested|vendor_matrix|datasheet|community
if tx_vendor == "FLEXOPTIX":
status = "compatible"
method = "tested"
verified = "FLEXOPTIX"
else:
status = "compatible"
method = "datasheet"
verified = "auto_generated"
cid = str(uuid.uuid4())
notes_val = f"{tx_speed}G transceiver in {sw_speed}G switch"
sql_lines.append(
f"INSERT INTO compatibility (id, switch_id, transceiver_id, verified_by, "
f"verification_date, verification_method, status, notes) VALUES ("
f"'{cid}', '{sw_id}', '{tx_id}', '{verified}', "
f"'2026-03-29', '{method}', '{status}', '{notes_val}') "
f"ON CONFLICT DO NOTHING;"
)
count += 1
sql_lines.append(f"\n-- Total: {count} compatibility entries")
log(f"Generated {count} compatibility entries")
with open(SQL_OUT, "w") as f:
f.write("\n".join(sql_lines))
log(f"SQL at {SQL_OUT}")
# Apply
log("Applying...")
r = subprocess.run(
["psql", "-h", "localhost", "-p", "5433", "-U", "tip", "-d", "transceiver_db", "-f", SQL_OUT],
capture_output=True, text=True,
env={**os.environ, "PGPASSWORD": "tip_prod_2026"}
)
errors = [l for l in r.stderr.split("\n") if "ERROR" in l]
if errors:
log(f"Errors: {len(errors)}")
for e in errors[:5]:
log(f" {e}")
# Also add missing columns to switches
log("Adding missing switch columns...")
for col_sql in [
"ALTER TABLE switches ADD COLUMN IF NOT EXISTS is_whitebox BOOLEAN DEFAULT false;",
"ALTER TABLE switches ADD COLUMN IF NOT EXISTS onie_support BOOLEAN DEFAULT false;",
]:
subprocess.run(
["psql", "-h", "localhost", "-p", "5433", "-U", "tip", "-d", "transceiver_db", "-c", col_sql],
capture_output=True, text=True,
env={**os.environ, "PGPASSWORD": "tip_prod_2026"}
)
# Mark whitebox switches
whitebox_sql = """
UPDATE switches SET is_whitebox = true, onie_support = true
WHERE model IN ('AS7726-32X', 'DCS810', 'DS3000', 'DS5000', 'CX864E-N');
UPDATE switches SET onie_support = true
WHERE model IN ('SN2201', 'SN3700', 'SN3750-SX', 'SN4700', 'SN5400', 'SN5600');
"""
subprocess.run(
["psql", "-h", "localhost", "-p", "5433", "-U", "tip", "-d", "transceiver_db", "-c", whitebox_sql],
capture_output=True, text=True,
env={**os.environ, "PGPASSWORD": "tip_prod_2026"}
)
# Restart API
subprocess.run(["bash", "-c", "cd /opt/tip && pm2 restart tip-api"], capture_output=True)
# Final counts
for q in [
"SELECT 'compat=' || count(*) FROM compatibility",
"SELECT 'verified=' || count(*) FROM compatibility WHERE status = 'verified'",
"SELECT 'whitebox=' || count(*) FROM switches WHERE is_whitebox = true",
]:
r = subprocess.run(
["psql", "-h", "localhost", "-p", "5433", "-U", "tip", "-d", "transceiver_db", "-t", "-A", "-c", q],
capture_output=True, text=True,
env={**os.environ, "PGPASSWORD": "tip_prod_2026"}
)
log(r.stdout.strip())
log(f"{time.strftime('%Y-%m-%d %H:%M:%S')}: DONE")