fix: disable global API key middleware and add test miner creation endpoint
All checks were successful
API Endpoint Tests / test-api-endpoints (push) Successful in 47s
Documentation Validation / validate-docs (push) Successful in 17s
Integration Tests / test-service-integration (push) Successful in 2m11s
Python Tests / test-python (push) Successful in 5m49s
Security Scanning / security-scan (push) Successful in 4m1s
Systemd Sync / sync-systemd (push) Successful in 14s

API Key Middleware Changes:
- Disabled global API key middleware in favor of dependency injection
- Added comment explaining the change
- Preserves existing middleware code for reference

Admin Router Enhancements:
 NEW ENDPOINT: POST /debug/create-test-miner for debugging marketplace sync
- Creates test miner with id "debug-test-miner"
- Updates existing miner to ONLINE status if already exists
- Returns miner_id and session_token for testing
- Requires
This commit is contained in:
aitbc
2026-03-30 22:25:23 +02:00
parent 58020b7eeb
commit 0985308331
7 changed files with 373 additions and 34 deletions

View File

@@ -248,21 +248,21 @@ def create_app() -> FastAPI:
]
)
# API Key middleware (if configured)
required_key = os.getenv("COORDINATOR_API_KEY")
if required_key:
@app.middleware("http")
async def api_key_middleware(request: Request, call_next):
# Health endpoints are exempt
if request.url.path in ("/health", "/v1/health", "/health/live", "/health/ready", "/metrics", "/rate-limit-metrics"):
return await call_next(request)
provided = request.headers.get("X-Api-Key")
if provided != required_key:
return JSONResponse(
status_code=401,
content={"detail": "Invalid or missing API key"}
)
return await call_next(request)
# API Key middleware (if configured) - DISABLED in favor of dependency injection
# required_key = os.getenv("COORDINATOR_API_KEY")
# if required_key:
# @app.middleware("http")
# async def api_key_middleware(request: Request, call_next):
# # Health endpoints are exempt
# if request.url.path in ("/health", "/v1/health", "/health/live", "/health/ready", "/metrics", "/rate-limit-metrics"):
# return await call_next(request)
# provided = request.headers.get("X-Api-Key")
# if provided != required_key:
# return JSONResponse(
# status_code=401,
# content={"detail": "Invalid or missing API key"}
# )
# return await call_next(request)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

View File

@@ -29,6 +29,68 @@ async def debug_settings() -> dict: # type: ignore[arg-type]
}
@router.post("/debug/create-test-miner", summary="Create a test miner for debugging")
async def create_test_miner(
session: Annotated[Session, Depends(get_session)],
admin_key: str = Depends(require_admin_key())
) -> dict[str, str]: # type: ignore[arg-type]
"""Create a test miner for debugging marketplace sync"""
try:
from ..domain import Miner
from uuid import uuid4
miner_id = "debug-test-miner"
session_token = uuid4().hex
# Check if miner already exists
existing_miner = session.get(Miner, miner_id)
if existing_miner:
# Update existing miner to ONLINE
existing_miner.status = "ONLINE"
existing_miner.last_heartbeat = datetime.utcnow()
existing_miner.session_token = session_token
session.add(existing_miner)
session.commit()
return {"status": "updated", "miner_id": miner_id, "message": "Existing miner updated to ONLINE"}
# Create new test miner
miner = Miner(
id=miner_id,
capabilities={
"gpu_memory": 8192,
"models": ["qwen3:8b"],
"pricing_per_hour": 0.50,
"gpu": "RTX 4090",
"gpu_memory_gb": 8192,
"gpu_count": 1,
"cuda_version": "12.0",
"supported_models": ["qwen3:8b"]
},
concurrency=1,
region="test-region",
session_token=session_token,
status="ONLINE",
inflight=0,
last_heartbeat=datetime.utcnow()
)
session.add(miner)
session.commit()
session.refresh(miner)
logger.info(f"Created test miner: {miner_id}")
return {
"status": "created",
"miner_id": miner_id,
"session_token": session_token,
"message": "Test miner created successfully"
}
except Exception as e:
logger.error(f"Failed to create test miner: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/test-key", summary="Test API key validation")
async def test_key(
api_key: str = Header(default=None, alias="X-Api-Key")
@@ -102,23 +164,26 @@ async def list_jobs(session: Annotated[Session, Depends(get_session)], admin_key
@router.get("/miners", summary="List miners")
async def list_miners(session: Annotated[Session, Depends(get_session)], admin_key: str = Depends(require_admin_key())) -> dict[str, list[dict]]: # type: ignore[arg-type]
miner_service = MinerService(session)
miners = [
from sqlmodel import select
from ..domain import Miner
miners = session.execute(select(Miner)).scalars().all()
miner_list = [
{
"miner_id": record.id,
"status": record.status,
"inflight": record.inflight,
"concurrency": record.concurrency,
"region": record.region,
"last_heartbeat": record.last_heartbeat.isoformat(),
"average_job_duration_ms": record.average_job_duration_ms,
"jobs_completed": record.jobs_completed,
"jobs_failed": record.jobs_failed,
"last_receipt_id": record.last_receipt_id,
"miner_id": miner.id,
"status": miner.status,
"inflight": miner.inflight,
"concurrency": miner.concurrency,
"region": miner.region,
"last_heartbeat": miner.last_heartbeat.isoformat(),
"average_job_duration_ms": miner.average_job_duration_ms,
"jobs_completed": miner.jobs_completed,
"jobs_failed": miner.jobs_failed,
"last_receipt_id": miner.last_receipt_id,
}
for record in miner_service.list_records()
for miner in miners
]
return {"items": miners}
return {"items": miner_list}
@router.get("/status", summary="Get system status", response_model=None)

View File

@@ -24,9 +24,10 @@ async def sync_offers(
"""Create marketplace offers from all registered miners"""
# Get all registered miners
miners = session.execute(select(Miner).where(Miner.status == "ONLINE")).all()
miners = session.execute(select(Miner).where(Miner.status == "ONLINE")).scalars().all()
created_offers = []
offer_objects = []
for miner in miners:
# Check if offer already exists
@@ -54,10 +55,14 @@ async def sync_offers(
)
session.add(offer)
created_offers.append(offer.id)
offer_objects.append(offer)
session.commit()
# Collect offer IDs after commit (when IDs are generated)
for offer in offer_objects:
created_offers.append(offer.id)
return {
"status": "ok",
"created_offers": len(created_offers),

View File

@@ -55,7 +55,8 @@ async def heartbeat(
async def poll(
req: PollRequest,
session: Annotated[Session, Depends(get_session)],
miner_id: str = Depends(require_miner_key()),
api_key: str = Depends(require_miner_key()),
miner_id: str = Depends(get_miner_id()),
) -> AssignedJob | Response: # type: ignore[arg-type]
job = MinerService(session).poll(miner_id, req.max_wait_seconds)
if job is None: