Fix port-traffic create 500 from legacy task_id NOT NULL.

Brownfield tables still require task_id while ORM only wrote device_id; mirror both columns on write and relax the leftover constraint.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
oliver 2026-08-02 19:50:21 +08:00
parent ce835dde3d
commit d26e4ca2fc
2 changed files with 61 additions and 15 deletions

View file

@ -3,13 +3,20 @@ from __future__ import annotations
from datetime import datetime
from uuid import uuid4
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint
from sqlalchemy import BigInteger, Boolean, DateTime, Float, ForeignKey, Integer, LargeBinary, String, Text, UniqueConstraint, event
from sqlalchemy.orm import Mapped, mapped_column, relationship
from ..db import Base
from ..timeutil import utcnow_naive
from ._types import JsonType as _JsonType
def _mirror_device_task_id(target: object) -> None:
"""Keep legacy task_id column aligned with device_id (brownfield NOT NULL)."""
did = str(getattr(target, "device_id", None) or getattr(target, "task_id", None) or "")
target.device_id = did # type: ignore[attr-defined]
target.task_id = did # type: ignore[attr-defined]
class PortTrafficDevice(Base):
"""Per-NE port traffic monitoring config (device-centric)."""
@ -49,18 +56,12 @@ class PortTrafficSeries(Base):
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
device_id: Mapped[str] = mapped_column(String(64), default="", index=True)
# Legacy column retained for brownfield DBs that still enforce NOT NULL task_id.
task_id: Mapped[str] = mapped_column(String(64), default="")
title: Mapped[str] = mapped_column(String(256), default="")
status: Mapped[str] = mapped_column(String(32), default="active", index=True) # active|disabled
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
@property
def task_id(self) -> str:
return str(self.device_id or "")
@task_id.setter
def task_id(self, value: str) -> None:
self.device_id = str(value or "")
class PortTrafficTarget(Base):
"""Monitored interface under a device monitoring config."""
@ -69,6 +70,8 @@ class PortTrafficTarget(Base):
id: Mapped[str] = mapped_column(String(64), primary_key=True, default=lambda: uuid4().hex)
device_id: Mapped[str] = mapped_column(String(64), default="", index=True)
# Legacy column retained for brownfield DBs that still enforce NOT NULL task_id.
task_id: Mapped[str] = mapped_column(String(64), default="")
series_id: Mapped[str] = mapped_column(String(64), default="", index=True)
source: Mapped[str] = mapped_column(String(32), default="managed", index=True)
target_id: Mapped[str] = mapped_column(String(128), index=True) # NE id
@ -83,13 +86,17 @@ class PortTrafficTarget(Base):
last_sample_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, default=utcnow_naive)
@property
def task_id(self) -> str:
return str(self.device_id or "")
@task_id.setter
def task_id(self, value: str) -> None:
self.device_id = str(value or "")
@event.listens_for(PortTrafficSeries, "before_insert")
@event.listens_for(PortTrafficSeries, "before_update")
def _sync_series_task_id(mapper, connection, target) -> None: # noqa: ANN001
_mirror_device_task_id(target)
@event.listens_for(PortTrafficTarget, "before_insert")
@event.listens_for(PortTrafficTarget, "before_update")
def _sync_target_task_id(mapper, connection, target) -> None: # noqa: ANN001
_mirror_device_task_id(target)
class PortTrafficSample(Base):

View file

@ -63,6 +63,27 @@ def ensure_port_traffic_series_schema(conn) -> None:
conn.exec_driver_sql(
"ALTER TABLE port_traffic_series ADD COLUMN IF NOT EXISTS task_id VARCHAR(64) DEFAULT ''"
)
# Older installs created task_id as NOT NULL before the device-centric rename.
# Keep both columns populated and relax NOT NULL so ORM inserts that only set
# device_id (or only task_id) do not 500.
for sql in (
"""
UPDATE port_traffic_series
SET device_id = task_id
WHERE COALESCE(device_id, '') = '' AND COALESCE(task_id, '') <> ''
""",
"""
UPDATE port_traffic_series
SET task_id = device_id
WHERE COALESCE(task_id, '') = '' AND COALESCE(device_id, '') <> ''
""",
"ALTER TABLE port_traffic_series ALTER COLUMN task_id DROP NOT NULL",
"ALTER TABLE port_traffic_series ALTER COLUMN task_id SET DEFAULT ''",
):
try:
conn.exec_driver_sql(sql)
except Exception:
pass
conn.exec_driver_sql(
"CREATE INDEX IF NOT EXISTS ix_port_traffic_series_device_id ON port_traffic_series (device_id)"
)
@ -79,6 +100,24 @@ def ensure_port_traffic_series_schema(conn) -> None:
conn.exec_driver_sql(
"ALTER TABLE port_traffic_target ADD COLUMN IF NOT EXISTS task_id VARCHAR(64) DEFAULT ''"
)
for sql in (
"""
UPDATE port_traffic_target
SET device_id = task_id
WHERE COALESCE(device_id, '') = '' AND COALESCE(task_id, '') <> ''
""",
"""
UPDATE port_traffic_target
SET task_id = device_id
WHERE COALESCE(task_id, '') = '' AND COALESCE(device_id, '') <> ''
""",
"ALTER TABLE port_traffic_target ALTER COLUMN task_id DROP NOT NULL",
"ALTER TABLE port_traffic_target ALTER COLUMN task_id SET DEFAULT ''",
):
try:
conn.exec_driver_sql(sql)
except Exception:
pass
conn.exec_driver_sql(
"CREATE INDEX IF NOT EXISTS ix_port_traffic_target_series_id ON port_traffic_target (series_id)"
)