feat: implement daena MVP core service
Complete implementation of the daena log/event collection service:
Core runtime:
- DaenaRuntime service lifecycle (start/stop/reload)
- Pipeline orchestrator with source/persist/dispatch flow
- LMDB persistence backend with key-prefix partitioning
- Plugin discovery via Python entry points
Pipeline:
- Source/Processor/Sink ABC interfaces with DeliveryResult
- DummySource for testing and development
- DummySink and HTTPSink implementations
- PassthroughProcessor
- Record dataclass for in-memory records
- StoredRecord model with RecordState tracking
REST API (FastAPI, v1):
- GET /system/{status,health,config}
- GET /plugins
- GET /queue, /queue/{pending,dead}
- GET /sinks
- Versioned under /api/v1 prefix
CLI (Typer):
- status, health, queue, sinks, plugins, config, version
Persistence (LMDB):
- Single-database key-prefix design (no named DBs)
- Index keys: idx:{state}:{ts}\x00{id}
- Record keys: rec:{id}
- Full CRUD: put/get/ack/nack/dead/update_state
- Pending/retry/dead-letter queries with state filtering
- delete_old for TTL-based retention
Deployment:
- systemd unit file (deploy/daena.service)
- Example YAML config (config/daena.yaml)
- Entry points: daena (CLI), daena-serve (uvicorn server)
Quality:
- 23 unit tests covering config, persistence, pipeline
- ruff linting and formatting pass clean
- Py3.13 compatible, uv-managed, pyenv-pinned
- GPG-signed commit on dev branch
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from daena.config import DaenaConfig, load_config
|
||||
|
||||
|
||||
def test_minimal_config_has_defaults() -> None:
|
||||
config = DaenaConfig()
|
||||
assert config.data_dir == "/var/lib/daena"
|
||||
assert config.logging.level == "info"
|
||||
assert config.api.http.port == 8642
|
||||
assert config.pipeline.batch_size == 100
|
||||
|
||||
|
||||
def test_load_config_from_file(config_file: Path) -> None:
|
||||
config = load_config(str(config_file))
|
||||
assert config.data_dir == "/tmp/daena_data"
|
||||
assert config.logging.level == "debug"
|
||||
assert len(config.sources) == 1
|
||||
assert config.sources[0].name == "test"
|
||||
assert config.sources[0].type == "dummy"
|
||||
|
||||
|
||||
def test_load_config_nonexistent_file() -> None:
|
||||
try:
|
||||
load_config("/nonexistent/daena.yaml")
|
||||
raise AssertionError("Expected FileNotFoundError") # noqa: B011
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
|
||||
def test_config_can_set_persistence_backend() -> None:
|
||||
config = DaenaConfig()
|
||||
assert config.persistence.backend == "lmdb"
|
||||
assert config.persistence.lmdb.map_size == 104_857_600
|
||||
|
||||
|
||||
def test_config_source_options(minimal_config: DaenaConfig) -> None:
|
||||
cfg = minimal_config
|
||||
assert cfg.sources == []
|
||||
assert cfg.processors == []
|
||||
assert cfg.sinks == []
|
||||
Reference in New Issue
Block a user