feat(host-protocol): long poll assignments
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Awaitable, Callable
|
||||
from time import monotonic
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Request, status
|
||||
@@ -8,7 +11,13 @@ from cloud.auth import (
|
||||
AuthProvider,
|
||||
HostAuthorizationError,
|
||||
)
|
||||
from cloud.internal_api.models import HeartbeatRequest, HeartbeatResponse
|
||||
from cloud.internal_api.models import (
|
||||
AssignmentModel,
|
||||
ClaimRequest,
|
||||
ClaimResponse,
|
||||
HeartbeatRequest,
|
||||
HeartbeatResponse,
|
||||
)
|
||||
from core.models import Device, utc_now
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -20,7 +29,11 @@ def create_internal_router(
|
||||
pool: DevicePool,
|
||||
auth_provider: AuthProvider,
|
||||
version_prefix: str = "/internal/v1",
|
||||
claim_poll_interval_seconds: float = 0.1,
|
||||
sleep: Callable[[float], Awaitable[None]] = asyncio.sleep,
|
||||
) -> APIRouter:
|
||||
if claim_poll_interval_seconds <= 0:
|
||||
raise ValueError("claim_poll_interval_seconds must be greater than zero")
|
||||
router = APIRouter(prefix=version_prefix, tags=["host-agent"])
|
||||
|
||||
def authorize_host(request: Request, host_id: str) -> None:
|
||||
@@ -66,6 +79,44 @@ def create_internal_router(
|
||||
received_at=utc_now(),
|
||||
)
|
||||
|
||||
@router.post(
|
||||
"/hosts/{host_id}/assignments/claim",
|
||||
response_model=ClaimResponse,
|
||||
)
|
||||
async def claim_assignment(
|
||||
host_id: str,
|
||||
payload: ClaimRequest,
|
||||
request: Request,
|
||||
) -> ClaimResponse:
|
||||
authorize_host(request, host_id)
|
||||
if payload.host_id != host_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail="claim host_id must match the request path",
|
||||
)
|
||||
|
||||
deadline = monotonic() + payload.timeout_seconds
|
||||
while True:
|
||||
assignment = pool.store.claim_assignment(host_id=host_id, now=utc_now())
|
||||
if assignment is not None:
|
||||
return ClaimResponse(
|
||||
assignment=AssignmentModel(
|
||||
task_id=assignment.task_id,
|
||||
attempt=assignment.attempt,
|
||||
lease_id=assignment.lease_id,
|
||||
lease_expires_at=assignment.lease_expires_at,
|
||||
host_id=assignment.host_id,
|
||||
device_id=assignment.device_id,
|
||||
goal=assignment.goal,
|
||||
workflow_definition_id=assignment.workflow_definition_id,
|
||||
)
|
||||
)
|
||||
|
||||
remaining = deadline - monotonic()
|
||||
if remaining <= 0:
|
||||
return ClaimResponse(timed_out=True)
|
||||
await sleep(min(claim_poll_interval_seconds, remaining))
|
||||
|
||||
return router
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user