mirror of
https://github.com/hansjone/netx.git
synced 2026-10-11 05:43:20 +08:00
Fix topology sync failing when ume_topo_link ifname columns are missing.
Ensure startup safety-net adds a_ifname/z_ifname on brownfield DBs, and normalize ADD COLUMN IF NOT EXISTS for SQLite tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
9ba4d051dc
commit
527472976a
2 changed files with 59 additions and 2 deletions
|
|
@ -12,6 +12,7 @@ columns and indexes that ``create_all`` will not evolve on existing DBs.
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
from sqlalchemy import text
|
from sqlalchemy import text
|
||||||
from sqlalchemy.engine import Connection, Engine
|
from sqlalchemy.engine import Connection, Engine
|
||||||
|
|
@ -23,6 +24,24 @@ def _dialect_name(conn: Connection) -> str:
|
||||||
return str(getattr(conn.dialect, "name", "") or "").lower()
|
return str(getattr(conn.dialect, "name", "") or "").lower()
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_ddl_for_dialect(conn: Connection, sql: str) -> str:
|
||||||
|
"""SQLite lacks ``ADD COLUMN IF NOT EXISTS`` (PG 9.5+/11+). Strip for quiet retry."""
|
||||||
|
stmt = str(sql or "").strip()
|
||||||
|
if not stmt:
|
||||||
|
return stmt
|
||||||
|
if _dialect_name(conn) == "sqlite":
|
||||||
|
# Keep CREATE/INDEX IF NOT EXISTS; only ALTER TABLE … ADD COLUMN needs help.
|
||||||
|
upper = stmt.upper()
|
||||||
|
if "ADD COLUMN IF NOT EXISTS" in upper:
|
||||||
|
stmt = re.sub(
|
||||||
|
r"(?i)\bADD\s+COLUMN\s+IF\s+NOT\s+EXISTS\b",
|
||||||
|
"ADD COLUMN",
|
||||||
|
stmt,
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
return stmt
|
||||||
|
|
||||||
|
|
||||||
def _run_sql(conn: Connection, sql: str, *, quiet: bool = True) -> None:
|
def _run_sql(conn: Connection, sql: str, *, quiet: bool = True) -> None:
|
||||||
"""Run one DDL/DML statement.
|
"""Run one DDL/DML statement.
|
||||||
|
|
||||||
|
|
@ -30,7 +49,7 @@ def _run_sql(conn: Connection, sql: str, *, quiet: bool = True) -> None:
|
||||||
patches must use a SAVEPOINT (``begin_nested``) so Alembic can still stamp
|
patches must use a SAVEPOINT (``begin_nested``) so Alembic can still stamp
|
||||||
``alembic_version`` after a best-effort ALTER fails.
|
``alembic_version`` after a best-effort ALTER fails.
|
||||||
"""
|
"""
|
||||||
stmt = str(sql or "").strip()
|
stmt = _normalize_ddl_for_dialect(conn, sql)
|
||||||
if not stmt:
|
if not stmt:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -143,6 +162,16 @@ def apply_topology_schema_safety_net(conn: Connection) -> None:
|
||||||
conn,
|
conn,
|
||||||
"CREATE INDEX IF NOT EXISTS ix_topo_fabric_node_world_y ON topo_fabric_node (world_y)",
|
"CREATE INDEX IF NOT EXISTS ix_topo_fabric_node_world_y ON topo_fabric_node (world_y)",
|
||||||
)
|
)
|
||||||
|
# UME link ifnames — required by topology sync/apply; missing when DB predates the columns
|
||||||
|
# or Alembic was stamped head without running domain patches.
|
||||||
|
_run_sql(
|
||||||
|
conn,
|
||||||
|
"ALTER TABLE ume_topo_link ADD COLUMN IF NOT EXISTS a_ifname VARCHAR(128) DEFAULT ''",
|
||||||
|
)
|
||||||
|
_run_sql(
|
||||||
|
conn,
|
||||||
|
"ALTER TABLE ume_topo_link ADD COLUMN IF NOT EXISTS z_ifname VARCHAR(128) DEFAULT ''",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def apply_key_alert_schema_patches(
|
def apply_key_alert_schema_patches(
|
||||||
|
|
@ -408,6 +437,8 @@ def apply_domain_schema_patches(conn: Connection) -> None:
|
||||||
z_ume_ne_id VARCHAR(128) DEFAULT '',
|
z_ume_ne_id VARCHAR(128) DEFAULT '',
|
||||||
a_ptp VARCHAR(256) DEFAULT '',
|
a_ptp VARCHAR(256) DEFAULT '',
|
||||||
z_ptp VARCHAR(256) DEFAULT '',
|
z_ptp VARCHAR(256) DEFAULT '',
|
||||||
|
a_ifname VARCHAR(128) DEFAULT '',
|
||||||
|
z_ifname VARCHAR(128) DEFAULT '',
|
||||||
first_seen_at TIMESTAMP,
|
first_seen_at TIMESTAMP,
|
||||||
last_seen_at TIMESTAMP,
|
last_seen_at TIMESTAMP,
|
||||||
raw_json TEXT DEFAULT '{}'
|
raw_json TEXT DEFAULT '{}'
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,11 @@ from sqlalchemy.orm import sessionmaker
|
||||||
|
|
||||||
from netx_api.db import Base
|
from netx_api.db import Base
|
||||||
import netx_api.models # noqa: F401
|
import netx_api.models # noqa: F401
|
||||||
from netx_api.schema_patches import apply_auth_schema_patches, apply_domain_schema_patches
|
from netx_api.schema_patches import (
|
||||||
|
apply_auth_schema_patches,
|
||||||
|
apply_domain_schema_patches,
|
||||||
|
apply_topology_schema_safety_net,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SchemaPatchesTests(unittest.TestCase):
|
class SchemaPatchesTests(unittest.TestCase):
|
||||||
|
|
@ -34,6 +38,28 @@ class SchemaPatchesTests(unittest.TestCase):
|
||||||
self.assertIn("expires_at", token_cols)
|
self.assertIn("expires_at", token_cols)
|
||||||
self.assertIn("auth_session", insp.get_table_names())
|
self.assertIn("auth_session", insp.get_table_names())
|
||||||
|
|
||||||
|
def test_topology_safety_net_adds_ume_link_ifnames(self) -> None:
|
||||||
|
"""Brownfield: table without ifnames; safety-net must add them (sync depends on it)."""
|
||||||
|
with self.engine.begin() as conn:
|
||||||
|
conn.execute(text("DROP TABLE IF EXISTS ume_topo_link"))
|
||||||
|
conn.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
CREATE TABLE ume_topo_link (
|
||||||
|
link_id VARCHAR(128) PRIMARY KEY,
|
||||||
|
name VARCHAR(1024) DEFAULT '',
|
||||||
|
a_ptp VARCHAR(256) DEFAULT '',
|
||||||
|
z_ptp VARCHAR(256) DEFAULT ''
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
apply_topology_schema_safety_net(conn)
|
||||||
|
apply_topology_schema_safety_net(conn)
|
||||||
|
cols = {c["name"] for c in inspect(self.engine).get_columns("ume_topo_link")}
|
||||||
|
self.assertIn("a_ifname", cols)
|
||||||
|
self.assertIn("z_ifname", cols)
|
||||||
|
|
||||||
def test_domain_patches_do_not_raise(self) -> None:
|
def test_domain_patches_do_not_raise(self) -> None:
|
||||||
with self.engine.begin() as conn:
|
with self.engine.begin() as conn:
|
||||||
apply_domain_schema_patches(conn)
|
apply_domain_schema_patches(conn)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue