193 lines
6.2 KiB
Python
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")
|