mirror of
https://github.com/FunnyWolf/agentic-soc-platform.git
synced 2026-08-22 13:12:56 +02:00
Fix worker config refresh and frontend healthcheck
Refresh runtime configuration before worker iterations so long-running worker processes do not keep stale lru_cache values after settings changes. Also probe the frontend healthcheck through 127.0.0.1 to match the nginx listener. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import SimpleTestCase
|
||||
|
||||
from apps.common.worker_runner import _run_once_or_raise
|
||||
|
||||
|
||||
class WorkerRunnerTests(SimpleTestCase):
|
||||
def test_run_once_refreshes_runtime_config_before_work(self):
|
||||
calls = []
|
||||
|
||||
def run_once():
|
||||
calls.append("run_once")
|
||||
return True
|
||||
|
||||
with patch("apps.common.worker_runner._refresh_runtime_config_cache") as refresh:
|
||||
result = _run_once_or_raise("test worker", run_once)
|
||||
|
||||
self.assertTrue(result.processed)
|
||||
refresh.assert_called_once()
|
||||
self.assertEqual(calls, ["run_once"])
|
||||
@@ -49,8 +49,15 @@ def _worker_label(worker_name):
|
||||
return worker_name if worker_name.endswith("worker") else f"{worker_name} worker"
|
||||
|
||||
|
||||
def _refresh_runtime_config_cache():
|
||||
from apps.settings.runtime_config import invalidate
|
||||
|
||||
invalidate()
|
||||
|
||||
|
||||
def _run_once_or_raise(worker_label, run_once):
|
||||
try:
|
||||
_refresh_runtime_config_cache()
|
||||
return _coerce_result(run_once())
|
||||
except CommandError:
|
||||
raise
|
||||
@@ -95,7 +102,7 @@ def run_worker(
|
||||
try:
|
||||
while True:
|
||||
try:
|
||||
result = _coerce_result(run_once())
|
||||
result = _run_once_or_raise(worker_label, run_once)
|
||||
if result.message:
|
||||
command.stdout.write(result.message)
|
||||
if _should_sleep(result, sleep_policy):
|
||||
|
||||
@@ -5,12 +5,14 @@ from datetime import UTC, timedelta
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.settings.runtime_config import get_elk_config
|
||||
from apps.settings.runtime_config import get_elk_config, invalidate
|
||||
from apps.webhook.service import handle_kibana_webhook
|
||||
from integrations.siem.clients import get_elk_client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ELK_CLIENT_CONFIG_FIELDS = ("host", "api_key", "verify_certs", "request_timeout_seconds")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ProcessResult:
|
||||
@@ -25,7 +27,7 @@ def format_es_time(value):
|
||||
|
||||
class ELKActionProcessor:
|
||||
def __init__(self, *, elk_client=None, index_name=None, interval_seconds=None, size=None):
|
||||
config = get_elk_config()
|
||||
config = self.load_config()
|
||||
self.elk_client = elk_client
|
||||
self.index_name_override = index_name
|
||||
self.interval_seconds_override = interval_seconds
|
||||
@@ -33,8 +35,18 @@ class ELKActionProcessor:
|
||||
self.index_name = index_name if index_name is not None else config["action_index"]
|
||||
self.interval_seconds = interval_seconds if interval_seconds is not None else config["action_poll_interval_seconds"]
|
||||
self.size = size if size is not None else config["action_size"]
|
||||
self.elk_client_config = self.client_config(config)
|
||||
self.last_check_time = None
|
||||
|
||||
@staticmethod
|
||||
def load_config():
|
||||
invalidate("elk")
|
||||
return get_elk_config()
|
||||
|
||||
@staticmethod
|
||||
def client_config(config):
|
||||
return tuple(config[field] for field in ELK_CLIENT_CONFIG_FIELDS)
|
||||
|
||||
@staticmethod
|
||||
def parse_hits(hits):
|
||||
if isinstance(hits, str):
|
||||
@@ -44,7 +56,11 @@ class ELKActionProcessor:
|
||||
return hits if isinstance(hits, list) else []
|
||||
|
||||
def refresh_config(self):
|
||||
config = get_elk_config()
|
||||
config = self.load_config()
|
||||
elk_client_config = self.client_config(config)
|
||||
if self.elk_client is None and elk_client_config != self.elk_client_config:
|
||||
get_elk_client.cache_clear()
|
||||
self.elk_client_config = elk_client_config
|
||||
if self.index_name_override is None:
|
||||
self.index_name = config["action_index"]
|
||||
if self.interval_seconds_override is None:
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
from datetime import timedelta
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.test import SimpleTestCase, TestCase
|
||||
from django.urls import Resolver404, resolve
|
||||
from django.utils import timezone
|
||||
from rest_framework.test import APIClient
|
||||
|
||||
from apps.settings.models import RuntimeConfig
|
||||
from apps.settings.runtime_config import invalidate
|
||||
from apps.settings.models import RuntimeConfig, SiemElkConfig
|
||||
from apps.settings.runtime_config import get_elk_config, invalidate
|
||||
from apps.webhook.elk_actions import ELKActionProcessor
|
||||
from apps.webhook.schemas import WebhookResult
|
||||
from apps.webhook.service import (
|
||||
WebhookRedisError,
|
||||
@@ -28,6 +31,26 @@ class FailingRedisClient:
|
||||
raise RuntimeError("redis unavailable")
|
||||
|
||||
|
||||
class FakeElkClient:
|
||||
def __init__(self):
|
||||
self.searches = []
|
||||
|
||||
def search(self, **kwargs):
|
||||
self.searches.append(kwargs)
|
||||
return {
|
||||
"hits": {
|
||||
"hits": [
|
||||
{
|
||||
"_source": {
|
||||
"rule": {"name": "rule-without-hits"},
|
||||
"context": {"hits": []},
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class WebhookServiceTests(TestCase):
|
||||
def set_stream_maxlen(self, value):
|
||||
config = RuntimeConfig.get_current()
|
||||
@@ -108,6 +131,30 @@ class WebhookServiceTests(TestCase):
|
||||
)
|
||||
|
||||
|
||||
class ELKActionProcessorTests(TestCase):
|
||||
def test_process_once_refreshes_config_cached_before_settings_change(self):
|
||||
config = SiemElkConfig.get_current()
|
||||
config.host = "https://elk.example.com:9200"
|
||||
config.api_key = "test-api-key"
|
||||
config.process_alert_from_index_enabled = False
|
||||
config.save()
|
||||
invalidate("elk")
|
||||
|
||||
elk_client = FakeElkClient()
|
||||
processor = ELKActionProcessor(elk_client=elk_client, interval_seconds=60)
|
||||
self.assertFalse(get_elk_config()["process_alert_from_index_enabled"])
|
||||
|
||||
config.process_alert_from_index_enabled = True
|
||||
config.save(update_fields=["process_alert_from_index_enabled", "updated_at"])
|
||||
|
||||
end_time = timezone.now()
|
||||
result = processor.process_once(start_time=end_time - timedelta(seconds=60), end_time=end_time)
|
||||
|
||||
self.assertEqual(result.actions, 1)
|
||||
self.assertEqual(result.skipped, 1)
|
||||
self.assertEqual(len(elk_client.searches), 1)
|
||||
|
||||
|
||||
class WebhookAPITests(SimpleTestCase):
|
||||
def setUp(self):
|
||||
self.client = APIClient()
|
||||
|
||||
@@ -35,7 +35,7 @@ services:
|
||||
asp-asgi:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --no-check-certificate -qO- https://localhost/ >/dev/null"]
|
||||
test: ["CMD-SHELL", "wget --no-check-certificate -qO- https://127.0.0.1/ >/dev/null"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
Reference in New Issue
Block a user