README update

This commit is contained in:
Frank Agerholm 2025-12-11 16:58:27 +01:00
commit 8505e9217a

283
README.md
View file

@ -1,53 +1,280 @@
# Task Queue System v0.3.0 ***
**FastAPI + PostgreSQL + RabbitMQ** mit produktionsreifen Mail-Prüfungen: # Task Queue System (FastAPI + PostgreSQL + RabbitMQ) v0.3
- MXValidation, SPFValidation, **TLSCheck**, **CertValidity**, **MTASTSCheck**, **TLSRPTCheck**
- **Task-Abhängigkeiten**: z.B. TLS & CertValidity warten auf MXValidation Produktionsreifes Beispielsystem für Job/TaskVerarbeitung mit **Python3.12**, **FastAPI**, **SQLAlchemy (async)**, **PostgreSQL**, **RabbitMQ (aiopika)**, **argparseCLI** und migrationsfähiger DBStruktur via **Alembic**.
- TLS-Checks mit STARTTLS (Port 25), TLS-Version/Cipher, Zertifikatsdaten & Gültigkeit
- MTA-STS (Policy-Datei + TXT-Id) und TLS-RPT (rua) ## 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)
## Quickstart
```bash ```bash
docker-compose up -d 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 DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/tasks"
export RABBITMQ_URL="amqp://guest:guest@localhost/" export RABBITMQ_URL="amqp://guest:guest@localhost/"
export RABBITMQ_EXCHANGE="tasks" export RABBITMQ_EXCHANGE="tasks"
export MAX_RETRIES=3 export MAX_RETRIES=3
export DNS_TIMEOUT=3.0; export DNS_LIFETIME=5.0; export SMTP_TIMEOUT=10.0 export DNS_TIMEOUT=3.0
export DNS_LIFETIME=5.0
export SMTP_TIMEOUT=10.0
export HTTP_TIMEOUT=8.0
```
# API ### 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 uvicorn app.api:app --host 0.0.0.0 --port 8000 --reload
```
# Worker ### 6) Worker starten (jeweils in separaten Terminals)
```bash
python -m app.workers.mail_mx_worker python -m app.workers.mail_mx_worker
python -m app.workers.mail_spf_worker python -m app.workers.mail_spf_worker
python -m app.workers.mail_tls_worker python -m app.workers.mail_tls_worker
python -m app.workers.mail_cert_worker python -m app.workers.mail_cert_worker
python -m app.workers.mail_mtasts_worker python -m app.workers.mail_mtasts_worker
python -m app.workers.mail_tlsrpt_worker python -m app.workers.mail_tlsrpt_worker
# CLI
python -m app.cli create-job MailCheck --domain example.com
``` ```
## Tasks & Dependencies ### 7) CLI (argparse)
```
MailCheck ```bash
├─ MXValidation (no deps) python -m app.cli create-job MailCheck --domain example.com --api-url http://127.0.0.1:8000
├─ SPFValidation (no deps) python -m app.cli get-job <JOB_ID> --api-url http://127.0.0.1:8000
├─ TLSCheck (depends on: MXValidation)
├─ CertValidity (depends on: MXValidation)
├─ MTASTSCheck (no deps)
└─ TLSRPTCheck (no deps)
``` ```
***
## 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 ## API
- `POST /jobs``{ job_type: "MailCheck", domain: "example.com" }`
- `GET /jobs/{job_id}`
## Hinweise ### POST `/jobs`
- Für Produktion: Migrationen mit Alembic.
- MTA-STS-Datei: `https://mta-sts.<domain>/.well-known/mta-sts.txt`
- TLS nur Port 25 (STARTTLS). 465/587 nicht erforderlich laut Anforderung.
**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).
***