task-queue-system/README.md
2025-12-11 16:58:27 +01:00

280 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

***
# Task Queue System (FastAPI + PostgreSQL + RabbitMQ) v0.3
Produktionsreifes Beispielsystem für Job/TaskVerarbeitung mit **Python3.12**, **FastAPI**, **SQLAlchemy (async)**, **PostgreSQL**, **RabbitMQ (aiopika)**, **argparseCLI** und migrationsfähiger DBStruktur via **Alembic**.
## Inhalt
* Features
* Architektur
* Voraussetzungen
* Schnellstart (lokal)
* Konfiguration
* API
* CLI
* Worker
* Alembic (Migrationen)
* Troubleshooting
* Nächste Schritte
***
## Features
* **JobType → Tasks** (z.B. `MailCheck`):
`MXValidation`, `SPFValidation`, `TLSCheck`, `CertValidity`, `MTASTSCheck`, `TLSRPTCheck`
* **TaskAbhängigkeiten (DAG)**:
`TLSCheck` & `CertValidity` warten auf `MXValidation`; andere Tasks laufen unabhängig.
* **Persistenz**: Jobs/Tasks in PostgreSQL (UUID, JSONB, Enums, Timestamps).
* **RabbitMQ** (TopicExchange): Routing per Key `<JobType>.<TaskName>`.
* **Worker (async)** mit Retries & ExponentialBackoff; StatusUpdates in DB.
* **API (FastAPI)**: Job erstellen & Status abfragen.
* **CLI (argparse)**: `create-job` und `get-job`.
* **Alembic**: saubere Baseline & künftige Migrationen (async env.py, OnlineModus).
***
## Architektur
Client/CLI → FastAPI → PostgreSQL
↘ RabbitMQ (Topic)
↘ Worker (MX/SPF/TLS/Cert/MTA-STS/TLS-RPT)
* **Job erstellen**: API legt Job + Tasks (abhängig vom JobType) an, published Tasks via RabbitMQ.
* **Worker**: Konsumieren TaskMessages, führen Checks aus (DNS, SMTP STARTTLS, HTTP), speichern Ergebnisse in DB, releasen abhängige Tasks.
* **Status**: JobStatus wird aus TaskStatus aggregiert.
***
## Voraussetzungen
* Python **3.12**
* PostgreSQL **16** (lokal oder via Docker)
* RabbitMQ **3** (lokal oder via Docker)
* Optional: `poetry` oder `uv` (Pakete/venv)
***
## Schnellstart (lokal)
### 1) Services (optional per Docker)
```bash
docker-compose up -d
# Ports:
# PostgreSQL → 5432
# RabbitMQ → 5672 (AMQP), 15672 (UI)
```
### 2) Virtuelles Environment & Pakete
```bash
python3.12 -m venv .venv
source .venv/bin/activate
pip install -U pip wheel
pip install fastapi uvicorn[standard] sqlalchemy asyncpg pydantic aio-pika typer httpx python-dotenv dnspython alembic
```
### 3) Umgebungsvariablen
```bash
export DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/tasks"
export RABBITMQ_URL="amqp://guest:guest@localhost/"
export RABBITMQ_EXCHANGE="tasks"
export MAX_RETRIES=3
export DNS_TIMEOUT=3.0
export DNS_LIFETIME=5.0
export SMTP_TIMEOUT=10.0
export HTTP_TIMEOUT=8.0
```
### 4) Alembic Baseline anwenden
> **Wichtig**: Die App führt **kein** `create_all()` mehr aus; Tabellen werden ausschließlich über **Alembic** erstellt.
```bash
# (Bereits vorhandene DB ohne Tabellen)
PYTHONPATH=. alembic upgrade head
```
### 5) API starten
```bash
uvicorn app.api:app --host 0.0.0.0 --port 8000 --reload
```
### 6) Worker starten (jeweils in separaten Terminals)
```bash
python -m app.workers.mail_mx_worker
python -m app.workers.mail_spf_worker
python -m app.workers.mail_tls_worker
python -m app.workers.mail_cert_worker
python -m app.workers.mail_mtasts_worker
python -m app.workers.mail_tlsrpt_worker
```
### 7) CLI (argparse)
```bash
python -m app.cli create-job MailCheck --domain example.com --api-url http://127.0.0.1:8000
python -m app.cli get-job <JOB_ID> --api-url http://127.0.0.1:8000
```
***
## Konfiguration
* `.env` (optional): Du kannst obige Variablen in eine `.env` legen und z.B. via `python-dotenv` laden.
* **RHELfreundlich**: CLI nutzt `argparse` statt Typer/Click; keine Abhängigkeit von ClickVersionen.
***
## API
### POST `/jobs`
**Body:**
```json
{
"job_type": "MailCheck",
"domain": "example.com",
"payload": {}
}
```
**Antwort (verkürzt):**
```json
{
"id": "UUID",
"job_type": "MailCheck",
"status": "queued|running|success|failed",
"domain": "example.com",
"payload": {},
"tasks": [
{"id":"UUID","name":"MXValidation","status":"queued", ...},
...
]
}
```
### GET `/jobs/{job_id}`
Gibt Job + TaskDetails (Result/Errors) zurück.
***
## CLI
### Job anlegen
```bash
python -m app.cli create-job MailCheck --domain example.com --api-url http://127.0.0.1:8000
```
### Job abfragen
```bash
python -m app.cli get-job <JOB_ID> --api-url http://127.0.0.1:8000
```
> Tipp: Setze `TASK_QUEUE_API_URL="http://127.0.0.1:8000"` und spare dir `--api-url`.
***
## Worker
* **MXValidation**: DNS MXLookup (`dnspython`), sortiert nach Preference.
* **SPFValidation**: TXT SPFErkennung, BasisParsing (Mechanismen, `all`Qualifier).
* **TLSCheck**: SMTP EHLO → STARTTLS (Port 25); TLSVersion/Cipher/Zertifikat; nutzt MXHosts (aus `MXValidation` oder Fallback DNS).
* **CertValidity**: Zertifikat `notBefore/notAfter`; Gültigkeit + Tage bis Ablauf.
* **MTASTSCheck**: `_mta-sts.<domain>` TXT + `https://mta-sts.<domain>/.well-known/mta-sts.txt`.
* **TLSRPTCheck**: `_smtp._tls.<domain>` TXT → `rua=mailto:…`.
**Retry/Backoff**: Bei Fehlern `retries++`, Status `QUEUED`, erneutes Publish mit ExponentialBackoff.
***
## Alembic (Migrationen)
### Async `env.py`
* Lädt **`Base.metadata`** und importiert **Modelle** (`from app import models`) wichtig, damit alle Tabellen in `metadata.tables` stehen.
* Führt `run_migrations()` **direkt beim Import** aus (kein `__main__`Guard).
* **OnlineModus** mit `create_async_engine(...)` und `run_sync(...)`:
```python
def do_run_migrations(connection: Connection) -> None:
context.configure(
connection=connection,
target_metadata=Base.metadata,
compare_type=True,
compare_server_default=True,
include_schemas=True,
process_revision_directives=process_revision_directives, # skip empty
)
with context.begin_transaction():
context.run_migrations()
await async_conn.run_sync(do_run_migrations)
```
### Baseline anwenden
```bash
PYTHONPATH=. alembic upgrade head
```
### Neue Revision erzeugen
1. Modelle ändern (z.B. Spalte ergänzen).
2. Revision:
```bash
PYTHONPATH=. alembic revision --autogenerate -m "add tasks.priority"
```
3. Prüfen und anwenden:
```bash
PYTHONPATH=. alembic upgrade head
```
> **Hinweis (Enums)**: Beim **Erweitern** von EnumWerten ggf. manuell ergänzen:
```python
op.execute("ALTER TYPE jobstatus ADD VALUE IF NOT EXISTS 'paused'")
```
***
## Troubleshooting
### Alembic erzeugt leere Revisionen
* Prüfe, ob `env.py` **beim Import** `run_migrations()` aufruft (kein `if __name__ == "__main__"`).
* Sicherstellen: `context.configure(connection=..., target_metadata=...)` wird **im OnlinePfad** gesetzt.
* **Modelle importieren** (`from app import models`), damit `Base.metadata.tables` **nicht leer** ist.
* **Kein OfflineRun** (`--sql` vermeiden); OfflinePfad ggf. bewusst deaktivieren.
* Starte Alembic aus dem **ProjektRoot** mit `PYTHONPATH=.`, damit `from app ...` funktioniert.
### `MissingGreenlet` beim APIZugriff
* Ursache: **LazyLoading** einer Beziehung in async Kontext (z.B. `job.tasks`).
* **Fix**: Beziehungen standardmäßig **eager** laden (`lazy="selectin"`) **oder** im Query `options(selectinload(Job.tasks))` setzen.
### Typer/ClickFehler in CLI (RHEL)
* CLI nutzt **argparse** (keine Abhängigkeit zu Typer/Click).
***
## Nächste Schritte
* **Makefile**/Scripts ergänzen (z.B. `make migrate`, `make revision m="..."`).
* **Docker Entrypoint**: vor AppStart `alembic upgrade head`.
* **Observability**: Logging strukturieren; Metriken/Tracing (optional).
* **Tests**: Unit/IntegrationTests (z.B. WorkerProbes mit Mocks, DNS/SMTP/HTTP Timeouts).
***