style: clear whitespace, unused-import and blank-line lint
Strip trailing whitespace, drop unused imports/variables/global statements, split multi-imports, fix E3xx/E1xx spacing and E712 comparisons. No behaviour change; only E501 long lines remain in flake8. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,3 +1,3 @@
|
|||||||
"""HeartBeat Client (hbc) - System monitoring client."""
|
"""HeartBeat Client (hbc) - System monitoring client."""
|
||||||
|
|
||||||
from hbd import __version__
|
from hbd import __version__ # noqa: F401
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|||||||
+2
-3
@@ -16,7 +16,7 @@ import sys
|
|||||||
import time
|
import time
|
||||||
from logging.handlers import SysLogHandler
|
from logging.handlers import SysLogHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
# Import protocol and config
|
# Import protocol and config
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
@@ -572,7 +572,7 @@ async def cleanup(connections: List[AsyncConnection]):
|
|||||||
|
|
||||||
async def async_main(args, config):
|
async def async_main(args, config):
|
||||||
"""Async main function."""
|
"""Async main function."""
|
||||||
global running, shutdown_event, active_tasks, send_shutdown, allow_remote_command
|
global shutdown_event, active_tasks, send_shutdown, allow_remote_command
|
||||||
|
|
||||||
# Create shutdown event
|
# Create shutdown event
|
||||||
shutdown_event = asyncio.Event()
|
shutdown_event = asyncio.Event()
|
||||||
@@ -838,7 +838,6 @@ def build_parser():
|
|||||||
|
|
||||||
def main(argv=None):
|
def main(argv=None):
|
||||||
"""Main entry point."""
|
"""Main entry point."""
|
||||||
global running, dorestart
|
|
||||||
|
|
||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
args = parser.parse_args(argv)
|
args = parser.parse_args(argv)
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ load average, and process counts.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Import from parent package
|
# Import from parent package
|
||||||
from hbd.client.plugin import MonitorPlugin
|
from hbd.client.plugin import MonitorPlugin
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Collects disk usage and I/O statistics using psutil.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, Optional, List
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ def _zfs_arc_bytes() -> int:
|
|||||||
pass
|
pass
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Collects network interface statistics and connection information using psutil.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, Any, Optional, List
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import psutil
|
import psutil
|
||||||
@@ -72,7 +72,7 @@ class NetworkMonitorPlugin(MonitorPlugin):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
logger.info(f"Network monitor initialized (interval: {self.interval}s, "
|
logger.info(f"Network monitor initialized (interval: {self.interval}s, "
|
||||||
f"connections: {self.include_connections})")
|
f"connections: {self.include_connections})")
|
||||||
|
|
||||||
# Initialize I/O counters
|
# Initialize I/O counters
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ kernel, architecture, and distribution details.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import platform
|
import platform
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""Common utilities shared between hbc and hbd."""
|
"""Common utilities shared between hbc and hbd."""
|
||||||
|
|
||||||
from hbd import __version__
|
from hbd import __version__ # noqa: F401
|
||||||
|
|||||||
+1
-2
@@ -8,7 +8,7 @@ Message Types:
|
|||||||
PLG: Plugin data message (client -> server)
|
PLG: Plugin data message (client -> server)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Dict, Any, Union
|
from typing import Dict, Any
|
||||||
import json
|
import json
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
@@ -159,4 +159,3 @@ def decode_plugin_data(msg: bytes) -> Dict[str, Any]:
|
|||||||
Dictionary with 'ID', 'plugin', and plugin data fields
|
Dictionary with 'ID', 'plugin', and plugin data fields
|
||||||
"""
|
"""
|
||||||
return stodict(msg)
|
return stodict(msg)
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
"""HeartBeat Daemon (hbd) - Server/daemon component."""
|
"""HeartBeat Daemon (hbd) - Server/daemon component."""
|
||||||
|
|
||||||
from hbd import __version__
|
from hbd import __version__ # noqa: F401
|
||||||
|
|||||||
+10
-4
@@ -120,7 +120,7 @@ def cmd_passwd(args):
|
|||||||
def cmd_notify(args):
|
def cmd_notify(args):
|
||||||
"""Send a test message via a single notification channel."""
|
"""Send a test message via a single notification channel."""
|
||||||
from .config import load_config
|
from .config import load_config
|
||||||
from .notify import Notification, _dispatch_to_channel, setup
|
from .notify import Notification, setup
|
||||||
|
|
||||||
config = load_config(args.configfile)
|
config = load_config(args.configfile)
|
||||||
setup(config)
|
setup(config)
|
||||||
@@ -193,7 +193,9 @@ def _read_pid(configfile) -> int | None:
|
|||||||
|
|
||||||
|
|
||||||
def cmd_stop(args):
|
def cmd_stop(args):
|
||||||
import os, signal as _signal, time
|
import os
|
||||||
|
import signal as _signal
|
||||||
|
import time
|
||||||
pid = _read_pid(args.configfile)
|
pid = _read_pid(args.configfile)
|
||||||
if pid is None:
|
if pid is None:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -212,7 +214,8 @@ def cmd_stop(args):
|
|||||||
|
|
||||||
|
|
||||||
def cmd_reload(args):
|
def cmd_reload(args):
|
||||||
import os, signal as _signal
|
import os
|
||||||
|
import signal as _signal
|
||||||
pid = _read_pid(args.configfile)
|
pid = _read_pid(args.configfile)
|
||||||
if pid is None:
|
if pid is None:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@@ -222,7 +225,10 @@ def cmd_reload(args):
|
|||||||
|
|
||||||
|
|
||||||
def cmd_restart(args):
|
def cmd_restart(args):
|
||||||
import os, signal as _signal, time, subprocess
|
import os
|
||||||
|
import signal as _signal
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
pid = _read_pid(args.configfile)
|
pid = _read_pid(args.configfile)
|
||||||
if pid is not None:
|
if pid is not None:
|
||||||
print(f"Stopping hbd (pid {pid})...")
|
print(f"Stopping hbd (pid {pid})...")
|
||||||
|
|||||||
+8
-11
@@ -43,10 +43,10 @@ SERVER_DEFAULTS = {
|
|||||||
|
|
||||||
# Host management
|
# Host management
|
||||||
"hosts": {}, # Unified host definitions
|
"hosts": {}, # Unified host definitions
|
||||||
"dyndomains": ["example.org"], # Domains to update via nsupdate when a host with dyndns: true is updated
|
"dyndomains": ["example.org"], # Domains to update via nsupdate when a host with dyndns: true is updated
|
||||||
|
|
||||||
# DNS updates
|
# DNS updates
|
||||||
"nsupdate_bin": "/usr/bin/nsupdate", # Path to nsupdate binary
|
"nsupdate_bin": "/usr/bin/nsupdate", # Path to nsupdate binary
|
||||||
|
|
||||||
# WebSocket settings
|
# WebSocket settings
|
||||||
"ws_port": 50005,
|
"ws_port": 50005,
|
||||||
@@ -80,7 +80,7 @@ THRESHOLD_DEFAULTS = {
|
|||||||
'critical': 90.0
|
'critical': 90.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'memory_monitor': {
|
'memory_monitor': {
|
||||||
'memory_percent': {
|
'memory_percent': {
|
||||||
'warning': 85.0,
|
'warning': 85.0,
|
||||||
'critical': 95.0
|
'critical': 95.0
|
||||||
@@ -90,7 +90,7 @@ THRESHOLD_DEFAULTS = {
|
|||||||
'critical': 75.0
|
'critical': 75.0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'disk_monitor': {
|
'disk_monitor': {
|
||||||
'partitions': {
|
'partitions': {
|
||||||
'/': {
|
'/': {
|
||||||
'percent': {
|
'percent': {
|
||||||
@@ -100,18 +100,18 @@ THRESHOLD_DEFAULTS = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'rtt': {
|
'rtt': {
|
||||||
'warning': 200,
|
'warning': 200,
|
||||||
'critical': 250.0,
|
'critical': 250.0,
|
||||||
'count': 3 # Optional: number of consecutive breaches before alerting
|
'count': 3 # Optional: number of consecutive breaches before alerting
|
||||||
},
|
},
|
||||||
'nagios_runner': {
|
'nagios_runner': {
|
||||||
'status_code': {
|
'status_code': {
|
||||||
'display': '{check_name} {output}',
|
'display': '{check_name} {output}',
|
||||||
'operator': "nagios"
|
'operator': "nagios"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'zfs_monitor': {
|
'zfs_monitor': {
|
||||||
'pools': {
|
'pools': {
|
||||||
'*': {
|
'*': {
|
||||||
'status': {
|
'status': {
|
||||||
@@ -205,9 +205,6 @@ class ReloadableConfig:
|
|||||||
# Load new config
|
# Load new config
|
||||||
new_config = load_config(path)
|
new_config = load_config(path)
|
||||||
|
|
||||||
# Store old config for rollback if needed
|
|
||||||
old_config = self._config
|
|
||||||
|
|
||||||
# Update config
|
# Update config
|
||||||
self._config = new_config
|
self._config = new_config
|
||||||
self._logger.info(f"Configuration reloaded from {path}")
|
self._logger.info(f"Configuration reloaded from {path}")
|
||||||
@@ -319,7 +316,7 @@ def get_host_access(config, hostname) -> dict:
|
|||||||
"""
|
"""
|
||||||
host_cfg = get_host_config(config, hostname)
|
host_cfg = get_host_config(config, hostname)
|
||||||
|
|
||||||
owner = host_cfg.get("owner") # or get_default_owner(config)
|
owner = host_cfg.get("owner") # or get_default_owner(config)
|
||||||
|
|
||||||
managers = host_cfg.get("managers", [])
|
managers = host_cfg.get("managers", [])
|
||||||
if isinstance(managers, str):
|
if isinstance(managers, str):
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ def _make_yaml() -> YAML:
|
|||||||
y.preserve_quotes = True
|
y.preserve_quotes = True
|
||||||
return y
|
return y
|
||||||
|
|
||||||
|
|
||||||
# Top-level keys managed by the 'server' logical section
|
# Top-level keys managed by the 'server' logical section
|
||||||
_SERVER_KEYS = [
|
_SERVER_KEYS = [
|
||||||
"hbd_port", "hbd_host", "ws_port", "wss_port", "hb_port",
|
"hbd_port", "hbd_host", "ws_port", "wss_port", "hb_port",
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
msgs = [] # in-memory list of recent messages for new websocket clients; also logged to file via notify.eventlog
|
msgs = [] # in-memory list of recent messages for new websocket clients; also logged to file via notify.eventlog
|
||||||
|
|
||||||
|
|
||||||
class Data:
|
class Data:
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
|
|||||||
@@ -224,7 +224,7 @@ class Connection:
|
|||||||
try:
|
try:
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
self.overdue_timer = loop.call_later(timeout_seconds,
|
self.overdue_timer = loop.call_later(timeout_seconds,
|
||||||
lambda: asyncio.create_task(timer_expired()))
|
lambda: asyncio.create_task(timer_expired()))
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
# No event loop running yet
|
# No event loop running yet
|
||||||
pass
|
pass
|
||||||
|
|||||||
+1
-1
@@ -109,6 +109,7 @@ def _insert_threshold_metric(thresholds: dict, metric_path: str, values: dict) -
|
|||||||
else:
|
else:
|
||||||
thresholds[plugin].setdefault(intermediate, {})[leaf] = cfg
|
thresholds[plugin].setdefault(intermediate, {})[leaf] = cfg
|
||||||
|
|
||||||
|
|
||||||
def _render_template(html_str: str, **context) -> str:
|
def _render_template(html_str: str, **context) -> str:
|
||||||
tmpl = jinja2.Template(html_str)
|
tmpl = jinja2.Template(html_str)
|
||||||
return tmpl.render(**context)
|
return tmpl.render(**context)
|
||||||
@@ -1132,7 +1133,6 @@ async def start(
|
|||||||
elif is_mon:
|
elif is_mon:
|
||||||
monitored.append(hostname)
|
monitored.append(hostname)
|
||||||
|
|
||||||
|
|
||||||
# Resolve notification channel configs for display
|
# Resolve notification channel configs for display
|
||||||
notif_channels = []
|
notif_channels = []
|
||||||
if current_user:
|
if current_user:
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ class MessageJournal:
|
|||||||
|
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
logger.info(f"Message journal initialized: {self.journal_path} "
|
logger.info(f"Message journal initialized: {self.journal_path} "
|
||||||
f"(current size: {self._current_size:,} bytes, "
|
f"(current size: {self._current_size:,} bytes, "
|
||||||
f"max: {self.max_size:,} bytes)")
|
f"max: {self.max_size:,} bytes)")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -200,7 +200,7 @@ class MessageJournal:
|
|||||||
if self.journal_path.exists():
|
if self.journal_path.exists():
|
||||||
self.journal_path.rename(backup_path)
|
self.journal_path.rename(backup_path)
|
||||||
logger.info(f"Rotated journal: {backup_path} "
|
logger.info(f"Rotated journal: {backup_path} "
|
||||||
f"(size: {self._current_size:,} bytes)")
|
f"(size: {self._current_size:,} bytes)")
|
||||||
|
|
||||||
# Open new journal file
|
# Open new journal file
|
||||||
self._file_handle = open(self.journal_path, 'a', encoding='utf-8')
|
self._file_handle = open(self.journal_path, 'a', encoding='utf-8')
|
||||||
|
|||||||
+1
-2
@@ -5,8 +5,6 @@ import logging
|
|||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import signal
|
import signal
|
||||||
import sys
|
|
||||||
import ssl
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
|
||||||
from . import udp
|
from . import udp
|
||||||
@@ -23,6 +21,7 @@ eventlog = notify_mod.eventlog
|
|||||||
|
|
||||||
# shared runtime collections and helpers
|
# shared runtime collections and helpers
|
||||||
|
|
||||||
|
|
||||||
def save_state(config, hbdclass):
|
def save_state(config, hbdclass):
|
||||||
"""Save current state to pickle file. Safe to call at any time."""
|
"""Save current state to pickle file. Safe to call at any time."""
|
||||||
import pickle
|
import pickle
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ This module can be extended for additional monitoring tasks.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import asyncio
|
|
||||||
import time
|
|
||||||
from . import notify as notify_mod
|
from . import notify as notify_mod
|
||||||
|
|
||||||
DROPOVERDUE = 7 * 24 * 3600
|
DROPOVERDUE = 7 * 24 * 3600
|
||||||
@@ -25,4 +23,3 @@ async def cleanup_connections(hbdclass):
|
|||||||
for conn_type, conn in host.connections.items():
|
for conn_type, conn in host.connections.items():
|
||||||
if hasattr(conn, 'cancel_overdue_timer'):
|
if hasattr(conn, 'cancel_overdue_timer'):
|
||||||
conn.cancel_overdue_timer()
|
conn.cancel_overdue_timer()
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import smtplib
|
|||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from . import data
|
from . import data
|
||||||
@@ -49,6 +49,7 @@ logf = None
|
|||||||
|
|
||||||
_LEVEL_ORDER = {"RECOVER": 0, "INFO": 0, "WARNING": 1, "CRITICAL": 2}
|
_LEVEL_ORDER = {"RECOVER": 0, "INFO": 0, "WARNING": 1, "CRITICAL": 2}
|
||||||
|
|
||||||
|
|
||||||
def _level_value(level: str) -> int:
|
def _level_value(level: str) -> int:
|
||||||
return _LEVEL_ORDER.get(level.upper(), 0)
|
return _LEVEL_ORDER.get(level.upper(), 0)
|
||||||
|
|
||||||
@@ -103,7 +104,6 @@ def initlog(logfile):
|
|||||||
|
|
||||||
|
|
||||||
def closelog():
|
def closelog():
|
||||||
global logf
|
|
||||||
if logf and logf != sys.stderr:
|
if logf and logf != sys.stderr:
|
||||||
try:
|
try:
|
||||||
logf.close()
|
logf.close()
|
||||||
@@ -331,8 +331,6 @@ async def _send_sms_voipms_async(channel_cfg: dict, notif: Notification) -> bool
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def _send_matrix_async(channel_cfg: dict, notif: Notification) -> bool:
|
async def _send_matrix_async(channel_cfg: dict, notif: Notification) -> bool:
|
||||||
"""Send a Matrix message using matrix-nio."""
|
"""Send a Matrix message using matrix-nio."""
|
||||||
try:
|
try:
|
||||||
@@ -341,7 +339,6 @@ async def _send_matrix_async(channel_cfg: dict, notif: Notification) -> bool:
|
|||||||
logger.error("matrix-nio not installed; pip install matrix-nio")
|
logger.error("matrix-nio not installed; pip install matrix-nio")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
from nio import AsyncClient
|
|
||||||
homeserver = channel_cfg.get("homeserver", "")
|
homeserver = channel_cfg.get("homeserver", "")
|
||||||
access_token = channel_cfg.get("access_token", "")
|
access_token = channel_cfg.get("access_token", "")
|
||||||
room_id = channel_cfg.get("room_id", "")
|
room_id = channel_cfg.get("room_id", "")
|
||||||
@@ -359,7 +356,6 @@ async def _send_matrix_async(channel_cfg: dict, notif: Notification) -> bool:
|
|||||||
client = AsyncClient(homeserver)
|
client = AsyncClient(homeserver)
|
||||||
client.access_token = access_token
|
client.access_token = access_token
|
||||||
try:
|
try:
|
||||||
from nio import RoomSendResponse
|
|
||||||
content = {
|
content = {
|
||||||
"msgtype": "m.text",
|
"msgtype": "m.text",
|
||||||
"body": text,
|
"body": text,
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ def get_settings_sections(config: dict, threshold_checker=None, user=None) -> li
|
|||||||
"fields": [
|
"fields": [
|
||||||
field("journal_enabled", "Enabled", "boolean",
|
field("journal_enabled", "Enabled", "boolean",
|
||||||
"Turn journalling on or off.", editable=True),
|
"Turn journalling on or off.", editable=True),
|
||||||
field("journal_dir", "Journal directory","path",
|
field("journal_dir", "Journal directory", "path",
|
||||||
"Directory where journal files are written.", editable=True),
|
"Directory where journal files are written.", editable=True),
|
||||||
field("journal_file", "Journal filename", "text",
|
field("journal_file", "Journal filename", "text",
|
||||||
"Base filename for the journal (rotated copies get a numeric suffix)."),
|
"Base filename for the journal (rotated copies get a numeric suffix)."),
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List, Any, Optional, Tuple, Callable
|
from typing import Dict, List, Any, Optional, Tuple
|
||||||
from . import notify as notify_mod
|
from . import notify as notify_mod
|
||||||
from .config import THRESHOLD_DEFAULTS
|
from .config import THRESHOLD_DEFAULTS
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
eventlog = notify_mod.eventlog
|
eventlog = notify_mod.eventlog
|
||||||
|
|
||||||
|
|
||||||
class AlertLevel(Enum):
|
class AlertLevel(Enum):
|
||||||
"""Alert severity levels."""
|
"""Alert severity levels."""
|
||||||
OK = 0
|
OK = 0
|
||||||
@@ -182,6 +183,7 @@ class AlertState:
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.to_dict().__str__()
|
return self.to_dict().__str__()
|
||||||
|
|
||||||
|
|
||||||
class ThresholdConfig:
|
class ThresholdConfig:
|
||||||
"""Configuration for a single threshold check."""
|
"""Configuration for a single threshold check."""
|
||||||
|
|
||||||
@@ -946,6 +948,7 @@ class ThresholdChecker:
|
|||||||
self._check_pending_or_renotify(host_name, alert_state, metric_path, value, threshold, None)
|
self._check_pending_or_renotify(host_name, alert_state, metric_path, value, threshold, None)
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _find_threshold(
|
def _find_threshold(
|
||||||
self, thresholds: Dict[str, "ThresholdConfig"], metric_path: str
|
self, thresholds: Dict[str, "ThresholdConfig"], metric_path: str
|
||||||
) -> Tuple[Optional["ThresholdConfig"], Optional[str]]:
|
) -> Tuple[Optional["ThresholdConfig"], Optional[str]]:
|
||||||
|
|||||||
+3
-3
@@ -30,7 +30,7 @@ if platform == "Darwin":
|
|||||||
elif platform == "Linux":
|
elif platform == "Linux":
|
||||||
_SO_TIMESTAMP = 29 # Linux value (not in older Python versions)
|
_SO_TIMESTAMP = 29 # Linux value (not in older Python versions)
|
||||||
elif platform == "FreeBSD":
|
elif platform == "FreeBSD":
|
||||||
_SO_TIMESTAMP = 32 # FreeBSD value (not in older Python versions)
|
_SO_TIMESTAMP = 32 # FreeBSD value (not in older Python versions)
|
||||||
else:
|
else:
|
||||||
logger.warning("SO_TIMESTAMP may not be supported on this platform (%s)", platform)
|
logger.warning("SO_TIMESTAMP may not be supported on this platform (%s)", platform)
|
||||||
_SO_TIMESTAMP = None
|
_SO_TIMESTAMP = None
|
||||||
@@ -71,6 +71,7 @@ class RecvmsgTransport:
|
|||||||
Exposes the same sendto() / close() interface as asyncio's DatagramTransport
|
Exposes the same sendto() / close() interface as asyncio's DatagramTransport
|
||||||
so the rest of the code does not need to know which path is in use.
|
so the rest of the code does not need to know which path is in use.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, loop, sock):
|
def __init__(self, loop, sock):
|
||||||
self._loop = loop
|
self._loop = loop
|
||||||
self._sock = sock
|
self._sock = sock
|
||||||
@@ -432,7 +433,6 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
|
|||||||
hbdcls = ctx.get("hbdclass")
|
hbdcls = ctx.get("hbdclass")
|
||||||
msg_to_websockets = ctx.get("msg_to_websockets")
|
msg_to_websockets = ctx.get("msg_to_websockets")
|
||||||
DEBUG = ctx.get("DEBUG", 0)
|
DEBUG = ctx.get("DEBUG", 0)
|
||||||
verbose = ctx.get("verbose", False)
|
|
||||||
|
|
||||||
# normalize addr (ip, port)
|
# normalize addr (ip, port)
|
||||||
ip = addr[0] if isinstance(addr, (list, tuple)) else addr
|
ip = addr[0] if isinstance(addr, (list, tuple)) else addr
|
||||||
@@ -523,7 +523,7 @@ def handle_datagram(msg: dict, addr, transport, ctx: dict):
|
|||||||
|
|
||||||
# If os_info reports an owner and none is configured server-side, apply it
|
# If os_info reports an owner and none is configured server-side, apply it
|
||||||
if plugin_name == "os_info":
|
if plugin_name == "os_info":
|
||||||
config_owner = config_mod.get_host_access(cfg, uname).get("owner")
|
config_owner = config_mod.get_host_access(cfg, uname).get("owner")
|
||||||
default_owner = config_mod.get_default_owner(cfg)
|
default_owner = config_mod.get_default_owner(cfg)
|
||||||
inferred_owner = config_owner or plugin_data.get("owner") or default_owner
|
inferred_owner = config_owner or plugin_data.get("owner") or default_owner
|
||||||
host.owner = inferred_owner
|
host.owner = inferred_owner
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import glob
|
|
||||||
import os
|
|
||||||
import pytest
|
import pytest
|
||||||
from hbd.server import configio
|
from hbd.server import configio
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"""Tests for the config read/write API helpers in http.py."""
|
"""Tests for the config read/write API helpers in http.py."""
|
||||||
import pytest
|
|
||||||
from hbd.server import http
|
from hbd.server import http
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"""Tests for PUT /api/0/users/me logic."""
|
"""Tests for PUT /api/0/users/me logic."""
|
||||||
import pytest
|
|
||||||
from hbd.server import users as users_mod
|
from hbd.server import users as users_mod
|
||||||
|
|
||||||
|
|
||||||
@@ -87,7 +86,6 @@ def test_put_users_me_notification_channels(tmp_path):
|
|||||||
|
|
||||||
def test_visible_channels_excludes_private_from_others():
|
def test_visible_channels_excludes_private_from_others():
|
||||||
"""Private channels owned by another user must not appear in the visible set."""
|
"""Private channels owned by another user must not appear in the visible set."""
|
||||||
from hbd.server import settings as settings_mod
|
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
"notification_channels": {
|
"notification_channels": {
|
||||||
@@ -105,7 +103,7 @@ def test_visible_channels_excludes_private_from_others():
|
|||||||
self.admin = admin
|
self.admin = admin
|
||||||
|
|
||||||
alice = FakeUser("alice")
|
alice = FakeUser("alice")
|
||||||
bob = FakeUser("bob")
|
bob = FakeUser("bob")
|
||||||
admin = FakeUser("admin", admin=True)
|
admin = FakeUser("admin", admin=True)
|
||||||
|
|
||||||
# Simulate _visible_channels_for_user logic (mirrors http.py implementation)
|
# Simulate _visible_channels_for_user logic (mirrors http.py implementation)
|
||||||
@@ -119,5 +117,5 @@ def test_visible_channels_excludes_private_from_others():
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert visible(alice) == {"public_ch", "alice_priv"}
|
assert visible(alice) == {"public_ch", "alice_priv"}
|
||||||
assert visible(bob) == {"public_ch", "bob_priv"}
|
assert visible(bob) == {"public_ch", "bob_priv"}
|
||||||
assert visible(admin) == {"public_ch", "alice_priv", "bob_priv"}
|
assert visible(admin) == {"public_ch", "alice_priv", "bob_priv"}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
"""Tests for notification channel CRUD via configio helpers and visibility logic."""
|
"""Tests for notification channel CRUD via configio helpers and visibility logic."""
|
||||||
import pytest
|
|
||||||
from hbd.server import configio, settings as settings_mod
|
from hbd.server import configio, settings as settings_mod
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ def reset_users_dict():
|
|||||||
users_mod.users = original
|
users_mod.users = original
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_state_returns_unique_tokens():
|
def test_make_state_returns_unique_tokens():
|
||||||
s1 = oauth.make_state()
|
s1 = oauth.make_state()
|
||||||
s2 = oauth.make_state()
|
s2 = oauth.make_state()
|
||||||
@@ -124,7 +123,6 @@ def test_provision_oauth_user_survives_config_reload():
|
|||||||
assert "oauthonly" in users_mod.users
|
assert "oauthonly" in users_mod.users
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Integration-style tests: callback logic chain
|
# Integration-style tests: callback logic chain
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import pytest
|
|
||||||
from hbd.server import settings as settings_mod
|
from hbd.server import settings as settings_mod
|
||||||
|
|
||||||
CFG = {
|
CFG = {
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ Tests cover:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# Add parent directory to path for imports
|
# Add parent directory to path for imports
|
||||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
from hbd.server.threshold import (
|
from hbd.server.threshold import ( # noqa: E402
|
||||||
ThresholdChecker,
|
ThresholdChecker,
|
||||||
ThresholdConfig,
|
ThresholdConfig,
|
||||||
AlertLevel,
|
AlertLevel,
|
||||||
@@ -170,23 +169,23 @@ def test_alert_state():
|
|||||||
|
|
||||||
# Update to WARNING - should trigger notification
|
# Update to WARNING - should trigger notification
|
||||||
changed = alert.update(AlertLevel.WARNING, 85.0)
|
changed = alert.update(AlertLevel.WARNING, 85.0)
|
||||||
assert changed == True, "State change should return True"
|
assert changed is True, "State change should return True"
|
||||||
assert alert.level == AlertLevel.WARNING
|
assert alert.level == AlertLevel.WARNING
|
||||||
assert alert.last_value == 85.0
|
assert alert.last_value == 85.0
|
||||||
|
|
||||||
# Update with same level - no notification
|
# Update with same level - no notification
|
||||||
changed = alert.update(AlertLevel.WARNING, 86.0)
|
changed = alert.update(AlertLevel.WARNING, 86.0)
|
||||||
assert changed == False, "No state change should return False"
|
assert changed is False, "No state change should return False"
|
||||||
assert alert.last_value == 86.0
|
assert alert.last_value == 86.0
|
||||||
|
|
||||||
# Escalate to CRITICAL
|
# Escalate to CRITICAL
|
||||||
changed = alert.update(AlertLevel.CRITICAL, 95.0)
|
changed = alert.update(AlertLevel.CRITICAL, 95.0)
|
||||||
assert changed == True, "Escalation should trigger notification"
|
assert changed is True, "Escalation should trigger notification"
|
||||||
assert alert.level == AlertLevel.CRITICAL
|
assert alert.level == AlertLevel.CRITICAL
|
||||||
|
|
||||||
# Recover to OK
|
# Recover to OK
|
||||||
changed = alert.update(AlertLevel.OK, 50.0)
|
changed = alert.update(AlertLevel.OK, 50.0)
|
||||||
assert changed == True, "Recovery should trigger notification"
|
assert changed is True, "Recovery should trigger notification"
|
||||||
assert alert.level == AlertLevel.OK
|
assert alert.level == AlertLevel.OK
|
||||||
|
|
||||||
print(" ✓ Alert state tracking works correctly")
|
print(" ✓ Alert state tracking works correctly")
|
||||||
|
|||||||
Reference in New Issue
Block a user