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 8f1ab4e commit 96edd30
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 24 deletions.
42 changes: 20 additions & 22 deletions core/services/cable_guy/api/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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 @@ -85,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 @@ -552,58 +554,54 @@ def priorities_mismatch(self) -> bool:

return current != saved

def config_mismatch(self) -> bool:
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 False
return mismatches

saved = self.settings.root["content"]
saved_interfaces = [NetworkInterface(**iface) for iface in saved]
saved_interfaces_names = [interface.name for interface in saved_interfaces]
# for each interface, lets check all ip addresses, dhcp_server status, and dynamic ip status
# we won't remove additional ips, but we will add missing ones
# we will skip interfaces that are not present on either current or saved
saved_interfaces = {interface["name"]: NetworkInterface(**interface) for interface in saved}

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

saved_addresses = [
InterfaceAddress(ip=address["ip"], mode=address["mode"])
for address in saved[interface.name]["addresses"]
]
for address in saved_addresses:
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)}]"
)
return True
return False
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
"""
logger.info("Starting watchdog...")
while True:
logger.debug("Watchdog checking interfaces...")
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}")
if self.config_mismatch():
mismatches = self.config_mismatch()
if mismatches:
logger.warning("Interface config mismatch, applying saved settings.")
self.set_configuration(self.settings.root["configuration"])
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 96edd30

Please sign in to comment.