test nic state link api
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
"""Network inspection and bridge management endpoints."""
|
||||
|
||||
from typing import List, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel, Field
|
||||
from pyroute2 import IPRoute, NDB
|
||||
|
||||
from src.config import settings
|
||||
from src.utilities.bridge_link_state_manager import bridge_link_state_manager
|
||||
from src.utilities.interface_bridge_helpers import get_bridge_ports_once
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
ip: IPRoute | None = None
|
||||
@@ -79,6 +83,41 @@ class BridgeRemoveRequest(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
class BridgeLinkStateEnableRequest(BaseModel):
|
||||
"""Payload for enabling bridge member link-state propagation."""
|
||||
|
||||
poll_interval_seconds: float = Field(
|
||||
settings.bridge_link_state_poll_interval_seconds,
|
||||
gt=0,
|
||||
description="Polling interval for member link-state checks.",
|
||||
)
|
||||
|
||||
|
||||
class BridgeMemberLinkStateInfo(BaseModel):
|
||||
"""Current link-state snapshot for one bridge member."""
|
||||
|
||||
ifname: str = Field(..., description="Interface name.")
|
||||
admin_up: Optional[bool] = Field(None, description="Whether the interface has IFF_UP set.")
|
||||
carrier_up: Optional[bool] = Field(None, description="Whether the interface currently reports carrier.")
|
||||
operstate: Optional[str] = Field(None, description="Kernel operational state string.")
|
||||
link_ready: bool = Field(..., description="Whether the member currently looks usable for forwarding.")
|
||||
suppressed: bool = Field(..., description="Whether the watcher administratively suppressed this member.")
|
||||
|
||||
|
||||
class BridgeLinkStateWatcherStatus(BaseModel):
|
||||
"""Status for one bridge link-state propagation watcher."""
|
||||
|
||||
bridge: str = Field(..., description="Bridge interface name.")
|
||||
active: bool = Field(..., description="Whether the watcher thread is currently active.")
|
||||
poll_interval_seconds: Optional[float] = Field(None, description="Watcher polling interval.")
|
||||
last_poll_ts: Optional[float] = Field(None, description="Unix timestamp of the last poll.")
|
||||
last_error: Optional[str] = Field(None, description="Most recent watcher error, if any.")
|
||||
last_action: Optional[str] = Field(None, description="Most recent propagation action.")
|
||||
suppressed_members: List[str] = Field(default_factory=list, description="Members currently forced down by the watcher.")
|
||||
members: Dict[str, BridgeMemberLinkStateInfo] = Field(default_factory=dict, description="Per-member link-state snapshot.")
|
||||
message: Optional[str] = Field(None, description="Optional informational message.")
|
||||
|
||||
|
||||
def init_network_api() -> None:
|
||||
"""Initialize lazy pyroute2 clients."""
|
||||
global ip, ndb
|
||||
@@ -91,6 +130,7 @@ def init_network_api() -> None:
|
||||
def shutdown_network_api() -> None:
|
||||
"""Close pyroute2 clients if they were initialized."""
|
||||
global ip, ndb
|
||||
bridge_link_state_manager.stop()
|
||||
if ip:
|
||||
ip.close()
|
||||
ip = None
|
||||
@@ -167,6 +207,11 @@ def bridge_exists(name: str, ip_route: IPRoute) -> bool:
|
||||
return bool(ip_route.link_lookup(ifname=name))
|
||||
|
||||
|
||||
def _watcher_status_response(payload: dict[str, Any]) -> BridgeLinkStateWatcherStatus:
|
||||
"""Convert an internal watcher status dictionary to the API response model."""
|
||||
return BridgeLinkStateWatcherStatus.model_validate(payload)
|
||||
|
||||
|
||||
@router.get("/interfaces", response_model=List[InterfaceInfo])
|
||||
def get_interfaces(ip: IPRoute = Depends(get_iproute)) -> List[InterfaceInfo]:
|
||||
"""List host interfaces with addresses and decoded flags."""
|
||||
@@ -346,3 +391,64 @@ def remove_bridge(req: BridgeRemoveRequest, ip: IPRoute = Depends(get_iproute))
|
||||
"status": "ok",
|
||||
"deleted": req.name,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/bridge/link-state-watchers", response_model=List[BridgeLinkStateWatcherStatus])
|
||||
def list_bridge_link_state_watchers() -> List[BridgeLinkStateWatcherStatus]:
|
||||
"""List all bridge member link-state propagation watchers."""
|
||||
return [_watcher_status_response(status) for status in bridge_link_state_manager.list_statuses()]
|
||||
|
||||
|
||||
@router.get("/bridge/{bridge_name}/link-state-watcher", response_model=BridgeLinkStateWatcherStatus)
|
||||
def get_bridge_link_state_watcher(
|
||||
bridge_name: str,
|
||||
ip: IPRoute = Depends(get_iproute),
|
||||
) -> BridgeLinkStateWatcherStatus:
|
||||
"""Return the watcher status for one bridge."""
|
||||
if not bridge_exists(bridge_name, ip):
|
||||
raise HTTPException(status_code=404, detail=f"Bridge {bridge_name} not found")
|
||||
|
||||
status = bridge_link_state_manager.get_status(bridge_name)
|
||||
if status is None:
|
||||
return BridgeLinkStateWatcherStatus(
|
||||
bridge=bridge_name,
|
||||
active=False,
|
||||
message="watcher not enabled",
|
||||
)
|
||||
return _watcher_status_response(status)
|
||||
|
||||
|
||||
@router.post("/bridge/{bridge_name}/link-state-watcher/enable", response_model=BridgeLinkStateWatcherStatus)
|
||||
def enable_bridge_link_state_watcher(
|
||||
bridge_name: str,
|
||||
req: BridgeLinkStateEnableRequest,
|
||||
ip: IPRoute = Depends(get_iproute),
|
||||
) -> BridgeLinkStateWatcherStatus:
|
||||
"""Enable member link-state propagation for a bridge."""
|
||||
if not bridge_exists(bridge_name, ip):
|
||||
raise HTTPException(status_code=404, detail=f"Bridge {bridge_name} not found")
|
||||
|
||||
members = get_bridge_ports_once(bridge_name)
|
||||
if len(members) < 2:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Bridge {bridge_name} must have at least two member interfaces",
|
||||
)
|
||||
|
||||
status = bridge_link_state_manager.enable(
|
||||
bridge_name=bridge_name,
|
||||
poll_interval_seconds=req.poll_interval_seconds,
|
||||
)
|
||||
return _watcher_status_response(status)
|
||||
|
||||
|
||||
@router.post("/bridge/{bridge_name}/link-state-watcher/disable", response_model=BridgeLinkStateWatcherStatus)
|
||||
def disable_bridge_link_state_watcher(
|
||||
bridge_name: str,
|
||||
ip: IPRoute = Depends(get_iproute),
|
||||
) -> BridgeLinkStateWatcherStatus:
|
||||
"""Disable member link-state propagation for a bridge."""
|
||||
if not bridge_exists(bridge_name, ip):
|
||||
raise HTTPException(status_code=404, detail=f"Bridge {bridge_name} not found")
|
||||
|
||||
return _watcher_status_response(bridge_link_state_manager.disable(bridge_name))
|
||||
|
||||
Reference in New Issue
Block a user