diff --git a/hbd/server/http.py b/hbd/server/http.py index bf5e7c6..762381d 100644 --- a/hbd/server/http.py +++ b/hbd/server/http.py @@ -126,6 +126,46 @@ def _mask_config_for_api(config) -> dict: return result +def _build_host_info(host, threshold_checker=None) -> dict: + """Assemble the info payload for GET /api/0/hosts/{hostname}/info.""" + hbc_version = None + hbc_type = None + latest_os = host.get_latest_plugin_data("os_info") + if latest_os: + _, os_data = latest_os + hbc_version = os_data.get("hbc_version") + hbc_type = os_data.get("hbc_type") + + last_packet = None + if host.connections: + last_packet = max(conn.lastbeat for conn in host.connections.values()) + + thresholds = None + if threshold_checker is not None: + raw = threshold_checker.get_thresholds_for_host(host.name) + thresholds = sorted( + [ + { + "metric": tc.metric_path, + "warning": tc.warning, + "critical": tc.critical, + "operator": tc.operator.value, + } + for tc in raw.values() + ], + key=lambda x: x["metric"], + ) + + return { + "owner": getattr(host, "owner", None), + "managers": list(getattr(host, "managers", [])), + "hbc_version": hbc_version, + "hbc_type": hbc_type, + "last_packet": last_packet, + "thresholds": thresholds, + } + + async def start( host: str, port: int, diff --git a/tests/test_http_host_info.py b/tests/test_http_host_info.py new file mode 100644 index 0000000..39b2171 --- /dev/null +++ b/tests/test_http_host_info.py @@ -0,0 +1,129 @@ +"""Tests for _build_host_info helper in http.py.""" +import pytest +from unittest.mock import MagicMock +from hbd.server.http import _build_host_info + + +class _FakeConn: + def __init__(self, lastbeat): + self.lastbeat = lastbeat + + +class _FakeHost: + def __init__(self, name="myhost", owner=None, managers=None, + connections=None, os_data=None): + self.name = name + self.owner = owner + self.managers = managers or [] + self.connections = connections or {} + self._os_data = os_data + + def get_latest_plugin_data(self, plugin_name): + if plugin_name == "os_info" and self._os_data is not None: + return (1234567890.0, self._os_data) + return None + + +def test_build_host_info_basic_fields(): + host = _FakeHost(owner="alice", managers=["bob", "carol"]) + result = _build_host_info(host) + assert result["owner"] == "alice" + assert result["managers"] == ["bob", "carol"] + assert result["hbc_version"] is None + assert result["hbc_type"] is None + assert result["last_packet"] is None + assert result["thresholds"] is None + + +def test_build_host_info_no_owner(): + host = _FakeHost() + result = _build_host_info(host) + assert result["owner"] is None + assert result["managers"] == [] + + +def test_build_host_info_reads_hbc_from_os_info(): + host = _FakeHost(os_data={"hbc_version": "5.3.0", "hbc_type": "full"}) + result = _build_host_info(host) + assert result["hbc_version"] == "5.3.0" + assert result["hbc_type"] == "full" + + +def test_build_host_info_hbc_none_when_no_os_info(): + host = _FakeHost(os_data=None) + result = _build_host_info(host) + assert result["hbc_version"] is None + assert result["hbc_type"] is None + + +def test_build_host_info_last_packet_is_max_lastbeat(): + host = _FakeHost(connections={ + "IPv4": _FakeConn(1000.0), + "IPv6": _FakeConn(2000.0), + }) + result = _build_host_info(host) + assert result["last_packet"] == 2000.0 + + +def test_build_host_info_last_packet_none_when_no_connections(): + host = _FakeHost(connections={}) + result = _build_host_info(host) + assert result["last_packet"] is None + + +def test_build_host_info_thresholds_none_without_checker(): + host = _FakeHost() + result = _build_host_info(host, threshold_checker=None) + assert result["thresholds"] is None + + +def test_build_host_info_thresholds_sorted_by_metric(): + from hbd.server.threshold import ThresholdConfig + tc_cpu = ThresholdConfig("cpu_monitor.cpu_percent", warning=80.0, critical=95.0) + tc_mem = ThresholdConfig("memory_monitor.memory_percent", warning=85.0, critical=98.0) + + checker = MagicMock() + checker.get_thresholds_for_host.return_value = { + "memory_monitor.memory_percent": tc_mem, + "cpu_monitor.cpu_percent": tc_cpu, + } + + host = _FakeHost() + result = _build_host_info(host, threshold_checker=checker) + + assert result["thresholds"] is not None + assert len(result["thresholds"]) == 2 + assert result["thresholds"][0]["metric"] == "cpu_monitor.cpu_percent" + assert result["thresholds"][0]["warning"] == 80.0 + assert result["thresholds"][0]["critical"] == 95.0 + assert result["thresholds"][0]["operator"] == ">" + assert result["thresholds"][1]["metric"] == "memory_monitor.memory_percent" + + +def test_build_host_info_thresholds_empty_list_when_no_thresholds(): + checker = MagicMock() + checker.get_thresholds_for_host.return_value = {} + host = _FakeHost() + result = _build_host_info(host, threshold_checker=checker) + assert result["thresholds"] == [] + + +def test_build_host_info_threshold_null_warning_critical(): + from hbd.server.threshold import ThresholdConfig + tc = ThresholdConfig("rtt.myhost", warning=None, critical=500.0) + checker = MagicMock() + checker.get_thresholds_for_host.return_value = {"rtt.myhost": tc} + host = _FakeHost() + result = _build_host_info(host, threshold_checker=checker) + assert result["thresholds"][0]["warning"] is None + assert result["thresholds"][0]["critical"] == 500.0 + + +def test_build_host_info_nagios_operator_serialized(): + from hbd.server.threshold import ThresholdConfig + tc = ThresholdConfig("nagios_runner.check_http", operator="nagios") + checker = MagicMock() + checker.get_thresholds_for_host.return_value = {"nagios_runner.check_http": tc} + host = _FakeHost() + result = _build_host_info(host, threshold_checker=checker) + assert result["thresholds"][0]["operator"] == "nagios"