修改测试案例

This commit is contained in:
2025-10-13 22:10:36 +08:00
parent 675bce4b5a
commit 50a901c167

View File

@@ -7,6 +7,7 @@ import logging
import time import time
from pathlib import Path from pathlib import Path
from typing import Dict, Any, Optional, List from typing import Dict, Any, Optional, List
import sys
try: try:
import docker # type: ignore import docker # type: ignore
@@ -76,6 +77,14 @@ class DockerContainerManager:
remove: bool = True, remove: bool = True,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""在容器中执行命令,返回执行结果。""" """在容器中执行命令,返回执行结果。"""
# 确保挂载目录存在且可写
for host_path, spec in volumes.items():
p = Path(host_path)
p.mkdir(parents=True, exist_ok=True)
try:
p.chmod(0o777)
except Exception:
pass
if self._engine == "docker-sdk" and self._client is not None: if self._engine == "docker-sdk" and self._client is not None:
return self._run_with_docker_sdk( return self._run_with_docker_sdk(
command, volumes, environment, working_dir, name, detach, remove command, volumes, environment, working_dir, name, detach, remove
@@ -144,12 +153,36 @@ class DockerContainerManager:
threads: int = 4, threads: int = 4,
**kwargs: Any, **kwargs: Any,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
"""在容器中运行 BtToxin_Digger 主分析(工作目录挂载到 /workspace)。""" """在容器中运行 BtToxin_Digger 主分析(单目录方案)。"""
command: List[str] = [
# 1) 在宿主输出目录下准备 input_files并复制输入文件
work_input_dir = (output_dir / "input_files").resolve()
work_input_dir.mkdir(parents=True, exist_ok=True)
import shutil
if sequence_type == "nucl":
pattern = f"*{scaf_suffix}"
elif sequence_type == "orfs":
pattern = f"*{kwargs.get('orfs_suffix', '.ffn')}"
elif sequence_type == "prot":
pattern = f"*{kwargs.get('prot_suffix', '.faa')}"
elif sequence_type == "reads":
pattern = "*"
else:
pattern = "*"
copied_files = 0
for f in input_dir.glob(pattern):
if f.is_file():
shutil.copy2(f, work_input_dir / f.name)
copied_files += 1
logger.info(f"已复制 {copied_files} 个输入文件到 {work_input_dir}")
base_cmd: List[str] = [
"/usr/local/env-execute", "/usr/local/env-execute",
"BtToxin_Digger", "BtToxin_Digger",
"--SeqPath", "--SeqPath",
"/data/input", "/workspace/input_files",
"--SequenceType", "--SequenceType",
sequence_type, sequence_type,
"--threads", "--threads",
@@ -157,32 +190,32 @@ class DockerContainerManager:
] ]
if sequence_type == "nucl": if sequence_type == "nucl":
command += ["--Scaf_suffix", scaf_suffix] base_cmd += ["--Scaf_suffix", scaf_suffix]
elif sequence_type == "orfs": elif sequence_type == "orfs":
command += ["--orfs_suffix", kwargs.get("orfs_suffix", ".ffn")] base_cmd += ["--orfs_suffix", kwargs.get("orfs_suffix", ".ffn")]
elif sequence_type == "prot": elif sequence_type == "prot":
command += ["--prot_suffix", kwargs.get("prot_suffix", ".faa")] base_cmd += ["--prot_suffix", kwargs.get("prot_suffix", ".faa")]
elif sequence_type == "reads": elif sequence_type == "reads":
platform = kwargs.get("platform", "illumina") platform = kwargs.get("platform", "illumina")
command += ["--platform", platform] base_cmd += ["--platform", platform]
if platform == "illumina": if platform == "illumina":
r1 = kwargs.get("reads1_suffix", "_R1.fastq.gz") r1 = kwargs.get("reads1_suffix", "_R1.fastq.gz")
r2 = kwargs.get("reads2_suffix", "_R2.fastq.gz") r2 = kwargs.get("reads2_suffix", "_R2.fastq.gz")
sfx = kwargs.get("suffix_len") or len(r1) sfx = kwargs.get("suffix_len") or len(r1)
v = self.validate_reads_filenames(input_dir, platform, r1, r2, sfx) v = self.validate_reads_filenames(work_input_dir, platform, r1, r2, sfx)
if not v.get("valid"): if not v.get("valid"):
raise ValueError(f"Reads 文件验证失败: {v.get('error')}") raise ValueError(f"Reads 文件验证失败: {v.get('error')}")
sfx = v.get("suggested_suffix_len", sfx) sfx = v.get("suggested_suffix_len", sfx)
command += ["--reads1", r1, "--reads2", r2, "--suffix_len", str(sfx)] base_cmd += ["--reads1", r1, "--reads2", r2, "--suffix_len", str(sfx)]
elif platform in ("pacbio", "oxford"): elif platform in ("pacbio", "oxford"):
r = kwargs.get("reads1_suffix", ".fastq.gz") r = kwargs.get("reads1_suffix", ".fastq.gz")
gsize = kwargs.get("genome_size", "6.07m") gsize = kwargs.get("genome_size", "6.07m")
sfx = kwargs.get("suffix_len") or len(r) sfx = kwargs.get("suffix_len") or len(r)
v = self.validate_reads_filenames(input_dir, platform, r, None, sfx) v = self.validate_reads_filenames(work_input_dir, platform, r, None, sfx)
if not v.get("valid"): if not v.get("valid"):
raise ValueError(f"Reads 文件验证失败: {v.get('error')}") raise ValueError(f"Reads 文件验证失败: {v.get('error')}")
sfx = v.get("suggested_suffix_len", sfx) sfx = v.get("suggested_suffix_len", sfx)
command += ["--reads1", r, "--genomeSize", gsize, "--suffix_len", str(sfx)] base_cmd += ["--reads1", r, "--genomeSize", gsize, "--suffix_len", str(sfx)]
elif platform == "hybrid": elif platform == "hybrid":
short1 = kwargs.get("short1") short1 = kwargs.get("short1")
short2 = kwargs.get("short2") short2 = kwargs.get("short2")
@@ -190,9 +223,9 @@ class DockerContainerManager:
if not all([short1, short2, long]): if not all([short1, short2, long]):
raise ValueError("hybrid 需要 short1/short2/long 三个完整文件名") raise ValueError("hybrid 需要 short1/short2/long 三个完整文件名")
for fn in (short1, short2, long): for fn in (short1, short2, long):
if not (input_dir / fn).exists(): if not (work_input_dir / fn).exists():
raise ValueError(f"文件不存在: {fn}") raise ValueError(f"文件不存在: {fn}")
command += [ base_cmd += [
"--short1", "--short1",
short1, short1,
"--short2", "--short2",
@@ -204,19 +237,23 @@ class DockerContainerManager:
] ]
if kwargs.get("assemble_only"): if kwargs.get("assemble_only"):
command.append("--assemble_only") base_cmd.append("--assemble_only")
# 2) 只挂载输出目录(含 input_files与日志目录
volumes = { volumes = {
str(input_dir.resolve()): {"bind": "/data/input", "mode": "ro"},
str(output_dir.resolve()): {"bind": "/workspace", "mode": "rw"}, str(output_dir.resolve()): {"bind": "/workspace", "mode": "rw"},
str(log_dir.resolve()): {"bind": "/data/logs", "mode": "rw"}, str(log_dir.resolve()): {"bind": "/data/logs", "mode": "rw"},
} }
logger.info("开始 BtToxin_Digger 分析...") logger.info("开始 BtToxin_Digger 分析...")
final_cmd = base_cmd
working_dir = "/workspace"
result = self.run_command_in_container( result = self.run_command_in_container(
command=command, command=final_cmd,
volumes=volumes, volumes=volumes,
working_dir="/workspace", working_dir=working_dir,
name=f"bttoxin_digger_{int(time.time())}", name=f"bttoxin_digger_{int(time.time())}",
) )
@@ -270,6 +307,8 @@ class DockerContainerManager:
) -> Dict[str, Any]: ) -> Dict[str, Any]:
assert self._client is not None assert self._client is not None
try: try:
# 注意docker SDK 在 detach=False 时返回的是日志字节串,而非容器对象。
# 这里统一以 detach=True 运行,然后等待并抓取日志,最后按需删除容器。
container = self._client.containers.run( container = self._client.containers.run(
image=self.image, image=self.image,
command=command, command=command,
@@ -278,13 +317,12 @@ class DockerContainerManager:
working_dir=working_dir, working_dir=working_dir,
platform=self.platform, platform=self.platform,
name=name, name=name,
detach=detach, user="0:0", # 以 root 运行,避免挂载目录权限问题
remove=False, # 等获取日志后再删 detach=True,
remove=False, # 获取日志后再删
stdout=True, stdout=True,
stderr=True, stderr=True,
) )
if detach:
return {"success": True, "container_id": container.id, "status": "running"}
exit_info = container.wait() exit_info = container.wait()
code = exit_info.get("StatusCode", 1) code = exit_info.get("StatusCode", 1)
logs = container.logs().decode("utf-8", errors="ignore") logs = container.logs().decode("utf-8", errors="ignore")
@@ -312,12 +350,18 @@ class DockerContainerManager:
cmd: List[str] = [cli, "run", "--rm" if remove and not detach else ""] cmd: List[str] = [cli, "run", "--rm" if remove and not detach else ""]
cmd = [c for c in cmd if c] cmd = [c for c in cmd if c]
cmd += ["--platform", self.platform] cmd += ["--platform", self.platform]
# 以 root 运行,避免权限问题
cmd += ["--user", "0:0"]
if name: if name:
cmd += ["--name", name] cmd += ["--name", name]
for host, spec in volumes.items(): for host, spec in volumes.items():
bind = spec.get("bind") bind = spec.get("bind")
mode = spec.get("mode", "rw") mode = spec.get("mode", "rw")
cmd += ["-v", f"{host}:{bind}:{mode}"] # Podman(Linux) 下附加 :Z 处理 SELinux 标注;其他平台保持不变
mount_mode = mode
if self._engine == "podman-cli" and os.name == "posix" and sys.platform.startswith("linux"):
mount_mode = f"{mode},Z"
cmd += ["-v", f"{host}:{bind}:{mount_mode}"]
for k, v in (environment or {}).items(): for k, v in (environment or {}).items():
cmd += ["-e", f"{k}={v}"] cmd += ["-e", f"{k}={v}"]
cmd += ["-w", working_dir, self.image] cmd += ["-w", working_dir, self.image]