feat: sync execution with 3s progress polling (stage 2)
- pulp_client: get_repo / sync_repo / get_task (비동기 task href 반환)
- POST /repos/{uuid}/sync: remote 확인 후 동기화 시작, 진행률 조각 반환
- GET /repos/{uuid}/progress: progress_reports 합산 → 바/퍼센트,
완료/실패 시 hx-trigger 제거로 폴링 중단
- task_store: uuid→task_href 메모리 추적 (단일 인스턴스), 대시보드 '동기화 중' 집계
- views.task_progress: state/done/total/percent + created_resources에서 새 vN
- 동기화는 안전 동작이라 확인 모달 없이 실행 (스펙 §5-1)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
114
app/routes/sync.py
Normal file
114
app/routes/sync.py
Normal file
@@ -0,0 +1,114 @@
|
||||
"""동기화 실행 + 진행률 폴링 (화면 2).
|
||||
|
||||
POST /repos/{uuid}/sync → 동기화 시작, 진행률 바 조각 반환 (이후 3초 폴링)
|
||||
GET /repos/{uuid}/progress → 진행률 바 조각 (폴링 대상)
|
||||
|
||||
동기화는 외부 미러에서 새 스냅샷을 만들 뿐 운영망 배포가 아니므로 비교적 안전 →
|
||||
확인 모달 없이 바로 실행한다(스펙 §5-1). 폴링은 화면(HTMX)이 담당한다(스펙 §7).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import httpx
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from .. import task_store, views
|
||||
from ..deps import get_pulp_client, templates
|
||||
from ..pulp_client import PulpClient
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def _render(request: Request, uuid: str, progress: dict, status_code: int = 200):
|
||||
return templates.TemplateResponse(
|
||||
request,
|
||||
"partials/progress.html",
|
||||
{"uuid": uuid, "progress": progress},
|
||||
status_code=status_code,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/repos/{uuid}/sync", response_class=HTMLResponse)
|
||||
def start_sync(
|
||||
request: Request, uuid: str, pulp: PulpClient = Depends(get_pulp_client)
|
||||
) -> HTMLResponse:
|
||||
try:
|
||||
repo = pulp.get_repo(uuid)
|
||||
remote = repo.get("remote")
|
||||
if not remote:
|
||||
return _render(
|
||||
request,
|
||||
uuid,
|
||||
{
|
||||
"running": False,
|
||||
"completed": False,
|
||||
"failed": True,
|
||||
"error": "이 저장소에 remote(미러 소스)가 설정되어 있지 않습니다.",
|
||||
},
|
||||
status_code=400,
|
||||
)
|
||||
task_href = pulp.sync_repo(uuid, remote)
|
||||
except httpx.HTTPError as exc:
|
||||
return _render(
|
||||
request,
|
||||
uuid,
|
||||
{
|
||||
"running": False,
|
||||
"completed": False,
|
||||
"failed": True,
|
||||
"error": f"동기화 시작 실패: {exc}",
|
||||
},
|
||||
status_code=502,
|
||||
)
|
||||
|
||||
task_store.set_task(uuid, task_href)
|
||||
# 첫 조각: 폴링 시작 (아직 progress_reports 없음 → 준비 중)
|
||||
return _render(
|
||||
request,
|
||||
uuid,
|
||||
{
|
||||
"running": True,
|
||||
"completed": False,
|
||||
"failed": False,
|
||||
"state": "waiting",
|
||||
"done": 0,
|
||||
"total": 0,
|
||||
"percent": 0,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get("/repos/{uuid}/progress", response_class=HTMLResponse)
|
||||
def progress(
|
||||
request: Request, uuid: str, pulp: PulpClient = Depends(get_pulp_client)
|
||||
) -> HTMLResponse:
|
||||
task_href = task_store.get_task(uuid)
|
||||
if not task_href:
|
||||
# 추적 중인 sync 없음 → 폴링 중단(빈 조각)
|
||||
return _render(
|
||||
request,
|
||||
uuid,
|
||||
{"running": False, "completed": False, "failed": False, "idle": True},
|
||||
)
|
||||
|
||||
try:
|
||||
task = pulp.get_task(task_href)
|
||||
except httpx.HTTPError as exc:
|
||||
task_store.clear_task(uuid)
|
||||
return _render(
|
||||
request,
|
||||
uuid,
|
||||
{
|
||||
"running": False,
|
||||
"completed": False,
|
||||
"failed": True,
|
||||
"error": f"작업 상태 조회 실패: {exc}",
|
||||
},
|
||||
status_code=502,
|
||||
)
|
||||
|
||||
prog = views.task_progress(task)
|
||||
if prog["completed"] or prog["failed"]:
|
||||
task_store.clear_task(uuid) # 폴링 종료 + 동기화중 카운트에서 제외
|
||||
return _render(request, uuid, prog)
|
||||
Reference in New Issue
Block a user