Skip to content

Commit

Permalink
cable-guy: add watchdog
Browse files Browse the repository at this point in the history
  • Loading branch information
Williangalvani committed Jan 24, 2025
1 parent 34e50f5 commit 4288ff1
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 5 deletions.
78 changes: 75 additions & 3 deletions core/services/cable_guy/api/manager.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import asyncio
import re
import subprocess
import time
from socket import AddressFamily
from typing import Any, Dict, List, Optional, Tuple
from typing import Any, Dict, List, Optional, Set, Tuple

import psutil
from commonwealth.utils.decorators import temporary_cache
Expand Down Expand Up @@ -84,13 +85,15 @@ def save(self) -> None:
result = [interface.dict(exclude={"info"}) for interface in self.result]
self.settings.save(result)

def set_configuration(self, interface: NetworkInterface) -> None:
def set_configuration(self, interface: NetworkInterface, watchdog_call: bool = False) -> None:
"""Modify hardware based in the configuration
Args:
interface: NetworkInterface
watchdog_call: Whether this is a watchdog call
"""
self.network_handler.cleanup_interface_connections(interface.name)
if not watchdog_call:
self.network_handler.cleanup_interface_connections(interface.name)
interfaces = self.get_ethernet_interfaces()
valid_names = [interface.name for interface in interfaces]
if interface.name not in valid_names:
Expand Down Expand Up @@ -533,3 +536,72 @@ def stop(self) -> None:

def __del__(self) -> None:
self.stop()

def priorities_mismatch(self) -> bool:
"""Check if the current interface priorities differ from the saved ones.
Uses sets for order-independent comparison of NetworkInterfaceMetric objects,
which compare only name and priority fields.
Returns:
bool: True if priorities don't match, False if they do
"""
if "priorities" not in self.settings.root:
return False

current = set(self.get_interfaces_priority())
# Convert saved priorities to NetworkInterfaceMetric, index value doesn't matter for comparison
saved = {NetworkInterfaceMetric(index=0, **iface) for iface in self.settings.root["priorities"]}

return current != saved

def config_mismatch(self) -> Set[NetworkInterface]:
"""Check if the current interface config differs from the saved ones.
Returns:
bool: True if config doesn't match, False if it does
"""

mismatches: Set[NetworkInterface] = set()
current = self.get_ethernet_interfaces()
if "content" not in self.settings.root:
logger.debug("No saved configuration found")
logger.debug(f"Current configuration: {self.settings.root}")
return mismatches

saved = self.settings.root["content"]
saved_interfaces = {interface["name"]: NetworkInterface(**interface) for interface in saved}

for interface in current:
if interface.name not in saved_interfaces:
logger.debug(f"Interface {interface.name} not in saved configuration, skipping")
continue

for address in saved_interfaces[interface.name].addresses:
if address not in interface.addresses:
logger.info(
f"Mismatch detected for {interface.name}: "
f"saved address {address.ip} ({address.mode}) not found in current addresses "
f"[{', '.join(f'{addr.ip} ({addr.mode})' for addr in interface.addresses)}]"
)
mismatches.add(saved_interfaces[interface.name])
return mismatches

async def watchdog(self) -> None:
"""
periodically checks the interfaces states against the saved settings,
if there is a mismatch, it will apply the saved settings
"""
while True:
if self.priorities_mismatch():
logger.warning("Interface priorities mismatch, applying saved settings.")
try:
self.set_interfaces_priority(self.settings.root["priorities"])
except Exception as error:
logger.error(f"Failed to set interface priorities: {error}")
mismatches = self.config_mismatch()
if mismatches:
logger.warning("Interface config mismatch, applying saved settings.")
logger.debug(f"Mismatches: {mismatches}")
for interface in mismatches:
self.set_configuration(interface, watchdog_call=True)
await asyncio.sleep(5)
4 changes: 2 additions & 2 deletions core/services/cable_guy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,8 @@ async def root() -> HTMLResponse:

loop = asyncio.new_event_loop()

# # Running uvicorn with log disabled so loguru can handle it
# Running uvicorn with log disabled so loguru can handle it
config = Config(app=app, loop=loop, host="0.0.0.0", port=9090, log_config=None)
server = Server(config)

loop.create_task(manager.watchdog())
loop.run_until_complete(server.serve())
9 changes: 9 additions & 0 deletions core/services/cable_guy/typedefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@ class AddressMode(str, Enum):
Server = "server"
Unmanaged = "unmanaged"

def __hash__(self) -> int:
return hash(self.value)


class InterfaceAddress(BaseModel):
ip: str
mode: AddressMode

def __hash__(self) -> int:
return hash(self.ip) + hash(self.mode)


class InterfaceInfo(BaseModel):
connected: bool
Expand All @@ -26,6 +32,9 @@ class NetworkInterface(BaseModel):
addresses: List[InterfaceAddress]
info: Optional[InterfaceInfo]

def __hash__(self) -> int:
return hash(self.name) + sum(hash(address) for address in self.addresses)


class NetworkInterfaceMetric(BaseModel):
name: str
Expand Down

0 comments on commit 4288ff1

Please sign in to comment.