refactor(A1): remove Flamenco, simplify render pipeline to Celery-only
- Remove flamenco-manager and flamenco-worker from docker-compose.yml - Delete flamenco_client.py, flamenco_tasks.py, docker_scaler.py - Simplify render_dispatcher.py to Celery-only (removes ~300 lines) - Remove Flamenco beat schedule from celery_app.py - Clean admin.py: remove flamenco settings, endpoints, threejs validation - Clean orders.py cancel-render: Celery revoke only - Clean worker.py: remove flamenco_job_id from activity response - Migration 032: cancel lingering flamenco jobs, remove flamenco settings - PLAN.md: mark all decisions confirmed, status IN UMSETZUNG Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import asyncio
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
@@ -17,27 +16,21 @@ from app.utils.auth import require_admin, hash_password
|
||||
|
||||
router = APIRouter(prefix="/admin", tags=["admin"])
|
||||
|
||||
VALID_RENDERERS = {"pillow", "blender", "threejs"}
|
||||
VALID_ENGINES = {"cycles", "eevee"}
|
||||
VALID_THREEJS_SIZES = {512, 1024, 2048}
|
||||
VALID_FORMATS = {"jpg", "png"}
|
||||
VALID_STL_QUALITIES = {"low", "high"}
|
||||
VALID_RENDERERS = {"pillow", "blender"}
|
||||
VALID_ENGINES = {"cycles", "eevee"}
|
||||
VALID_FORMATS = {"jpg", "png"}
|
||||
VALID_STL_QUALITIES = {"low", "high"}
|
||||
VALID_CYCLES_DEVICES = {"auto", "gpu", "cpu"}
|
||||
VALID_RENDER_BACKENDS = {"celery", "flamenco", "auto"}
|
||||
|
||||
SETTINGS_DEFAULTS: dict[str, str] = {
|
||||
"thumbnail_renderer": "pillow",
|
||||
"thumbnail_renderer": "blender",
|
||||
"blender_engine": "cycles",
|
||||
"blender_cycles_samples": "256",
|
||||
"blender_eevee_samples": "64",
|
||||
"threejs_render_size": "1024",
|
||||
"thumbnail_format": "jpg",
|
||||
"stl_quality": "low",
|
||||
"blender_smooth_angle": "30",
|
||||
"cycles_device": "auto",
|
||||
"render_backend": "celery",
|
||||
"flamenco_manager_url": "http://flamenco-manager:8080",
|
||||
"flamenco_worker_count": "1",
|
||||
"blender_max_concurrent_renders": "3",
|
||||
"product_thumbnail_priority": '["latest_render","cad_thumbnail"]',
|
||||
"render_stall_timeout_minutes": "120",
|
||||
@@ -45,18 +38,15 @@ SETTINGS_DEFAULTS: dict[str, str] = {
|
||||
|
||||
|
||||
class SettingsOut(BaseModel):
|
||||
thumbnail_renderer: str = "pillow"
|
||||
thumbnail_renderer: str = "blender"
|
||||
blender_engine: str = "cycles"
|
||||
blender_cycles_samples: int = 256
|
||||
blender_eevee_samples: int = 64
|
||||
threejs_render_size: int = 1024
|
||||
thumbnail_format: str = "jpg"
|
||||
stl_quality: str = "low"
|
||||
blender_smooth_angle: int = 30
|
||||
cycles_device: str = "auto"
|
||||
render_backend: str = "celery"
|
||||
flamenco_manager_url: str = "http://flamenco-manager:8080"
|
||||
flamenco_worker_count: int = 1
|
||||
blender_max_concurrent_renders: int = 3
|
||||
product_thumbnail_priority: str = '["latest_render","cad_thumbnail"]'
|
||||
render_stall_timeout_minutes: int = 120
|
||||
@@ -67,14 +57,11 @@ class SettingsUpdate(BaseModel):
|
||||
blender_engine: str | None = None
|
||||
blender_cycles_samples: int | None = None
|
||||
blender_eevee_samples: int | None = None
|
||||
threejs_render_size: int | None = None
|
||||
thumbnail_format: str | None = None
|
||||
stl_quality: str | None = None
|
||||
blender_smooth_angle: int | None = None
|
||||
cycles_device: str | None = None
|
||||
render_backend: str | None = None
|
||||
flamenco_manager_url: str | None = None
|
||||
flamenco_worker_count: int | None = None
|
||||
blender_max_concurrent_renders: int | None = None
|
||||
product_thumbnail_priority: str | None = None
|
||||
render_stall_timeout_minutes: int | None = None
|
||||
@@ -171,14 +158,11 @@ def _settings_to_out(raw: dict[str, str]) -> SettingsOut:
|
||||
blender_engine=raw["blender_engine"],
|
||||
blender_cycles_samples=int(raw["blender_cycles_samples"]),
|
||||
blender_eevee_samples=int(raw["blender_eevee_samples"]),
|
||||
threejs_render_size=int(raw["threejs_render_size"]),
|
||||
thumbnail_format=raw["thumbnail_format"],
|
||||
stl_quality=raw["stl_quality"],
|
||||
blender_smooth_angle=int(raw["blender_smooth_angle"]),
|
||||
cycles_device=raw["cycles_device"],
|
||||
render_backend=raw["render_backend"],
|
||||
flamenco_manager_url=raw["flamenco_manager_url"],
|
||||
flamenco_worker_count=int(raw["flamenco_worker_count"]),
|
||||
blender_max_concurrent_renders=int(raw["blender_max_concurrent_renders"]),
|
||||
product_thumbnail_priority=raw.get("product_thumbnail_priority", '["latest_render","cad_thumbnail"]'),
|
||||
render_stall_timeout_minutes=int(raw.get("render_stall_timeout_minutes", "120")),
|
||||
@@ -207,8 +191,6 @@ async def update_settings(
|
||||
raise HTTPException(400, detail="blender_cycles_samples must be 1–4096")
|
||||
if body.blender_eevee_samples is not None and not (1 <= body.blender_eevee_samples <= 1024):
|
||||
raise HTTPException(400, detail="blender_eevee_samples must be 1–1024")
|
||||
if body.threejs_render_size is not None and body.threejs_render_size not in VALID_THREEJS_SIZES:
|
||||
raise HTTPException(400, detail=f"Invalid threejs_render_size. Choose: {', '.join(str(s) for s in sorted(VALID_THREEJS_SIZES))}")
|
||||
if body.thumbnail_format is not None and body.thumbnail_format not in VALID_FORMATS:
|
||||
raise HTTPException(400, detail=f"Invalid thumbnail_format. Choose: {', '.join(sorted(VALID_FORMATS))}")
|
||||
if body.stl_quality is not None and body.stl_quality not in VALID_STL_QUALITIES:
|
||||
@@ -217,10 +199,6 @@ async def update_settings(
|
||||
raise HTTPException(400, detail="blender_smooth_angle must be 0–180 degrees")
|
||||
if body.cycles_device is not None and body.cycles_device not in VALID_CYCLES_DEVICES:
|
||||
raise HTTPException(400, detail=f"Invalid cycles_device. Choose: {', '.join(sorted(VALID_CYCLES_DEVICES))}")
|
||||
if body.render_backend is not None and body.render_backend not in VALID_RENDER_BACKENDS:
|
||||
raise HTTPException(400, detail=f"Invalid render_backend. Choose: {', '.join(sorted(VALID_RENDER_BACKENDS))}")
|
||||
if body.flamenco_worker_count is not None and not (1 <= body.flamenco_worker_count <= 16):
|
||||
raise HTTPException(400, detail="flamenco_worker_count must be 1–16")
|
||||
if body.blender_max_concurrent_renders is not None and not (1 <= body.blender_max_concurrent_renders <= 16):
|
||||
raise HTTPException(400, detail="blender_max_concurrent_renders must be 1–16")
|
||||
if body.render_stall_timeout_minutes is not None and not (10 <= body.render_stall_timeout_minutes <= 10080):
|
||||
@@ -252,8 +230,6 @@ async def update_settings(
|
||||
updates["blender_cycles_samples"] = str(body.blender_cycles_samples)
|
||||
if body.blender_eevee_samples is not None:
|
||||
updates["blender_eevee_samples"] = str(body.blender_eevee_samples)
|
||||
if body.threejs_render_size is not None:
|
||||
updates["threejs_render_size"] = str(body.threejs_render_size)
|
||||
if body.thumbnail_format is not None:
|
||||
updates["thumbnail_format"] = body.thumbnail_format
|
||||
if body.stl_quality is not None:
|
||||
@@ -264,10 +240,6 @@ async def update_settings(
|
||||
updates["cycles_device"] = body.cycles_device
|
||||
if body.render_backend is not None:
|
||||
updates["render_backend"] = body.render_backend
|
||||
if body.flamenco_manager_url is not None:
|
||||
updates["flamenco_manager_url"] = body.flamenco_manager_url
|
||||
if body.flamenco_worker_count is not None:
|
||||
updates["flamenco_worker_count"] = str(body.flamenco_worker_count)
|
||||
if body.blender_max_concurrent_renders is not None:
|
||||
updates["blender_max_concurrent_renders"] = str(body.blender_max_concurrent_renders)
|
||||
if body.render_stall_timeout_minutes is not None:
|
||||
@@ -392,7 +364,6 @@ async def renderer_status(
|
||||
services = {
|
||||
"pillow": {"url": None, "available": True, "note": "Built-in (always available)"},
|
||||
"blender": {"url": "http://blender-renderer:8100/health", "available": False, "note": ""},
|
||||
"threejs": {"url": "http://threejs-renderer:8101/health", "available": False, "note": ""},
|
||||
}
|
||||
async with httpx.AsyncClient(timeout=3.0) as client:
|
||||
for name, info in services.items():
|
||||
@@ -409,78 +380,3 @@ async def renderer_status(
|
||||
return services
|
||||
|
||||
|
||||
@router.get("/settings/flamenco-status")
|
||||
async def flamenco_status(
|
||||
admin: User = Depends(require_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Check Flamenco Manager health and list workers."""
|
||||
raw = await _load_settings(db)
|
||||
manager_url = raw.get("flamenco_manager_url", "http://flamenco-manager:8080")
|
||||
|
||||
from app.services.flamenco_client import get_flamenco_client
|
||||
client = get_flamenco_client(manager_url)
|
||||
|
||||
health = client.health_check()
|
||||
workers: list[dict] = []
|
||||
|
||||
if health["available"]:
|
||||
try:
|
||||
workers = client.list_workers()
|
||||
except Exception as exc:
|
||||
workers = [{"error": str(exc)[:200]}]
|
||||
|
||||
return {
|
||||
"manager": health,
|
||||
"workers": workers,
|
||||
"manager_url": manager_url,
|
||||
}
|
||||
|
||||
|
||||
class WorkerCountBody(BaseModel):
|
||||
count: int
|
||||
|
||||
|
||||
@router.get("/settings/flamenco-worker-actual")
|
||||
async def get_flamenco_worker_actual(admin: User = Depends(require_admin)):
|
||||
"""Return the number of flamenco-worker containers currently running."""
|
||||
from app.services.docker_scaler import get_running_worker_count
|
||||
count = await asyncio.get_event_loop().run_in_executor(None, get_running_worker_count)
|
||||
return {"running": count, "available": count >= 0}
|
||||
|
||||
|
||||
@router.post("/settings/flamenco-worker-count")
|
||||
async def set_flamenco_worker_count(
|
||||
body: WorkerCountBody,
|
||||
admin: User = Depends(require_admin),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
"""Scale Flamenco worker containers to the requested count via Docker socket."""
|
||||
if not (1 <= body.count <= 16):
|
||||
raise HTTPException(400, detail="Worker count must be 1–16")
|
||||
|
||||
# Save desired count to settings first
|
||||
await _save_setting(db, "flamenco_worker_count", str(body.count))
|
||||
await db.commit()
|
||||
|
||||
# Perform actual Docker scaling in a thread (blocking SDK call)
|
||||
from app.services.docker_scaler import scale_workers
|
||||
try:
|
||||
result = await asyncio.get_event_loop().run_in_executor(None, scale_workers, body.count)
|
||||
return {
|
||||
"count": body.count,
|
||||
"previous": result["previous"],
|
||||
"current": result["current"],
|
||||
"delta": result["delta"],
|
||||
"message": result["message"],
|
||||
}
|
||||
except Exception as exc:
|
||||
# Scaling failed — return a warning but keep the saved setting
|
||||
return {
|
||||
"count": body.count,
|
||||
"previous": -1,
|
||||
"current": -1,
|
||||
"delta": 0,
|
||||
"message": f"Setting saved, but Docker scaling failed: {exc}. "
|
||||
f"Run `docker compose up -d --scale flamenco-worker={body.count}` manually.",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user