134 lines
4.7 KiB
Python
134 lines
4.7 KiB
Python
import enum
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from typing import TYPE_CHECKING
|
|
|
|
from sqlalchemy import Boolean, Column, DateTime, Enum, Float, Integer, String
|
|
from sqlalchemy.orm import DeclarativeBase
|
|
|
|
if TYPE_CHECKING:
|
|
from app.config import Settings
|
|
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
class DeviceTypeEnum(str, enum.Enum):
|
|
server = "server" # 服务器
|
|
olt = "olt" # OLT
|
|
switch = "switch" # 交换机
|
|
firewall = "firewall" # 防火墙
|
|
other = "other" # 其他
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class DeviceMonitoringPolicy:
|
|
"""Resolved monitoring thresholds for one device.
|
|
|
|
Nullable device fields intentionally inherit the runtime defaults so a
|
|
configuration change can apply to devices that do not need exceptions.
|
|
"""
|
|
|
|
probe_packets_per_round: int
|
|
offline_consecutive_rounds: int
|
|
degraded_window_rounds: int
|
|
degraded_loss_percent: float
|
|
recovery_consecutive_clean_rounds: int
|
|
|
|
@classmethod
|
|
def from_device(
|
|
cls,
|
|
device: "Device",
|
|
settings: "Settings",
|
|
) -> "DeviceMonitoringPolicy":
|
|
"""Resolve device overrides without changing the stored device."""
|
|
return cls(
|
|
probe_packets_per_round=(
|
|
device.probe_packets_per_round
|
|
if device.probe_packets_per_round is not None
|
|
else settings.probe_packets_per_round
|
|
),
|
|
offline_consecutive_rounds=(
|
|
device.offline_consecutive_rounds
|
|
if device.offline_consecutive_rounds is not None
|
|
else settings.offline_consecutive_rounds
|
|
),
|
|
degraded_window_rounds=(
|
|
device.degraded_window_rounds
|
|
if device.degraded_window_rounds is not None
|
|
else settings.degraded_window_rounds
|
|
),
|
|
degraded_loss_percent=(
|
|
device.degraded_loss_percent
|
|
if device.degraded_loss_percent is not None
|
|
else settings.degraded_loss_percent
|
|
),
|
|
recovery_consecutive_clean_rounds=(
|
|
device.recovery_consecutive_clean_rounds
|
|
if device.recovery_consecutive_clean_rounds is not None
|
|
else settings.recovery_consecutive_clean_rounds
|
|
),
|
|
)
|
|
|
|
|
|
class Device(Base):
|
|
__tablename__ = "devices"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
name = Column(String(128), nullable=False, comment="设备名称")
|
|
ip = Column(String(45), nullable=False, index=True, comment="IP 地址")
|
|
device_type = Column(Enum(DeviceTypeEnum), nullable=False, default=DeviceTypeEnum.other, comment="设备类型")
|
|
location = Column(String(256), default="", comment="物理位置/地址")
|
|
project_name = Column(String(256), default="", comment="所属项目")
|
|
tags = Column(String(512), default="", comment="标签,逗号分隔")
|
|
|
|
# Ping 设置
|
|
ping_interval = Column(Integer, default=30, comment="ping 间隔(秒)")
|
|
alert_threshold = Column(Integer, default=5, comment="连续失败次数判离线")
|
|
is_enabled = Column(Boolean, default=True, comment="是否启用监控")
|
|
|
|
# 设备级监测策略覆盖;NULL 时使用运行环境中的全局默认值。
|
|
probe_packets_per_round = Column(
|
|
Integer,
|
|
nullable=True,
|
|
comment="单轮探测发包数覆盖",
|
|
)
|
|
offline_consecutive_rounds = Column(
|
|
Integer,
|
|
nullable=True,
|
|
comment="连续全丢包离线轮数覆盖",
|
|
)
|
|
degraded_window_rounds = Column(
|
|
Integer,
|
|
nullable=True,
|
|
comment="故障丢包滑动窗口轮数覆盖",
|
|
)
|
|
degraded_loss_percent = Column(
|
|
Float,
|
|
nullable=True,
|
|
comment="故障丢包率阈值覆盖",
|
|
)
|
|
recovery_consecutive_clean_rounds = Column(
|
|
Integer,
|
|
nullable=True,
|
|
comment="连续零丢包恢复轮数覆盖",
|
|
)
|
|
|
|
# 运行状态
|
|
current_status = Column(
|
|
String(16),
|
|
default="unknown",
|
|
comment="当前状态: online/degraded/offline/unknown",
|
|
)
|
|
consecutive_failures = Column(Integer, default=0, comment="当前连续失败次数")
|
|
last_ping_time = Column(DateTime, nullable=True, comment="最后一次 ping 时间")
|
|
last_online_time = Column(DateTime, nullable=True, comment="最后一次在线时间")
|
|
last_offline_time = Column(DateTime, nullable=True, comment="最后一次离线时间")
|
|
|
|
created_at = Column(DateTime, default=datetime.now, comment="创建时间")
|
|
updated_at = Column(DateTime, default=datetime.now, onupdate=datetime.now, comment="更新时间")
|
|
|
|
def __repr__(self):
|
|
return f"<Device(id={self.id}, name={self.name}, ip={self.ip})>"
|