first add
This commit is contained in:
2
backend/app/services/__init__.py
Normal file
2
backend/app/services/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
|
||||
56
backend/app/services/docker_service.py
Normal file
56
backend/app/services/docker_service.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, List, Optional
|
||||
from pathlib import Path
|
||||
import docker
|
||||
|
||||
from ..core.config import settings
|
||||
|
||||
|
||||
class DockerRunner:
|
||||
"""通过 docker.sock 在宿主机运行容器的轻量封装。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# 依赖 /var/run/docker.sock 已在 docker-compose 挂载至 worker 容器
|
||||
self.client = docker.from_env()
|
||||
|
||||
def run(
|
||||
self,
|
||||
image: Optional[str],
|
||||
command: List[str],
|
||||
workdir: Path,
|
||||
mounts: Dict[Path, str],
|
||||
env: Optional[Dict[str, str]] = None,
|
||||
platform: Optional[str] = None,
|
||||
detach: bool = False,
|
||||
remove: bool = True,
|
||||
) -> str:
|
||||
"""
|
||||
运行容器并返回容器日志(非 detach)或容器 ID(detach)。
|
||||
|
||||
mounts: {host_path: container_path}
|
||||
"""
|
||||
image_to_use = image or settings.DOCKER_IMAGE
|
||||
platform_to_use = platform or settings.DOCKER_PLATFORM
|
||||
|
||||
volumes = {str(host): {"bind": container, "mode": "rw"} for host, container in mounts.items()}
|
||||
|
||||
container = self.client.containers.run(
|
||||
image=image_to_use,
|
||||
command=command,
|
||||
working_dir=str(workdir),
|
||||
volumes=volumes,
|
||||
environment=env or {},
|
||||
platform=platform_to_use,
|
||||
detach=detach,
|
||||
remove=remove if not detach else False,
|
||||
)
|
||||
|
||||
if detach:
|
||||
return container.id
|
||||
|
||||
# 同步模式:等待并返回日志
|
||||
result_bytes = container.logs(stream=False)
|
||||
return result_bytes.decode("utf-8", errors="ignore")
|
||||
|
||||
|
||||
45
backend/app/services/workspace_service.py
Normal file
45
backend/app/services/workspace_service.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from pathlib import Path
|
||||
from typing import List, Dict
|
||||
import shutil
|
||||
from fastapi import UploadFile
|
||||
from ..core.config import settings
|
||||
|
||||
|
||||
class WorkspaceManager:
|
||||
def __init__(self) -> None:
|
||||
self.base_path = Path(settings.WORKSPACE_BASE_PATH)
|
||||
self.base_path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def create_workspace(self, job_id: str) -> Dict[str, Path]:
|
||||
root = self.base_path / job_id
|
||||
input_dir = root / "inputs"
|
||||
logs_dir = root / "logs"
|
||||
results_dir = root / "results"
|
||||
for d in [root, input_dir, logs_dir, results_dir]:
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
return {"root": root, "inputs": input_dir, "logs": logs_dir, "results": results_dir}
|
||||
|
||||
def save_input_files(self, job_id: str, files: List[UploadFile]) -> List[dict]:
|
||||
ws = self.create_workspace(job_id)
|
||||
saved: List[dict] = []
|
||||
for f in files:
|
||||
dst = ws["inputs"] / f.filename
|
||||
with dst.open("wb") as out:
|
||||
out.write(f.file.read())
|
||||
saved.append({"filename": f.filename, "path": str(dst)})
|
||||
return saved
|
||||
|
||||
def cleanup_workspace(self, job_id: str, keep_results: bool = False) -> None:
|
||||
root = self.base_path / job_id
|
||||
if not root.exists():
|
||||
return
|
||||
if keep_results:
|
||||
# 仅清理 inputs 和 logs
|
||||
for name in ["inputs", "logs"]:
|
||||
p = root / name
|
||||
if p.exists():
|
||||
shutil.rmtree(p, ignore_errors=True)
|
||||
else:
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user