feat(server,dashboard): 引入多工作流数据隔离、安全中间件与前端 ESM 模块化重构
- server: 实现按 workflow_name 的多工作流数据隔离与旧数据库平滑迁移机制 - server: 新增 API Key 认证(auth)、限流中间件(rate_limit)与运维备份接口(admin) - server: 统一 AppError 错误处理体系,重构调度器 scheduler 支持工作流级重置与抢占 - node: 节点 ID 缺失时自动生成随机 UUID,原生支持 `docker compose --scale node=N` 动态扩容 - dashboard: 前端模块化重构(state/api/components),升级 CSS 变量设计系统与 Toast 通知 - docker/docs: 更新 /healthz 健康检查、部署脚本 IP 配置及数据库设计文档
This commit is contained in:
+128
-13
@@ -13,17 +13,35 @@ pub async fn execute_task(
|
||||
work_dir: &Path,
|
||||
task: &TaskSpec,
|
||||
) -> Result<(ModelSummary, Option<Vec<u8>>)> {
|
||||
info!("开始执行计算任务 {} (网格点: {})", task.task_id, task.point_name);
|
||||
info!(
|
||||
"开始执行计算任务 {} (网格点: {})",
|
||||
task.task_id, task.point_name
|
||||
);
|
||||
|
||||
// 1. Pull ONLY missing atom model data files needed for this task
|
||||
let required_atom_files = &[
|
||||
"h1.dat", "he1.dat", "he2.dat",
|
||||
"c1.dat", "c2.dat", "c3_34+12lev.dat", "c4.dat",
|
||||
"n1.dat", "n2_32+10lev.dat", "n3.dat", "n4_34+14lev.dat", "n5.dat",
|
||||
"o1_23+10lev.dat", "o2_36+12lev.dat", "o3_28+13lev.dat", "o4.dat", "o5.dat",
|
||||
"h1.dat",
|
||||
"he1.dat",
|
||||
"he2.dat",
|
||||
"c1.dat",
|
||||
"c2.dat",
|
||||
"c3_34+12lev.dat",
|
||||
"c4.dat",
|
||||
"n1.dat",
|
||||
"n2_32+10lev.dat",
|
||||
"n3.dat",
|
||||
"n4_34+14lev.dat",
|
||||
"n5.dat",
|
||||
"o1_23+10lev.dat",
|
||||
"o2_36+12lev.dat",
|
||||
"o3_28+13lev.dat",
|
||||
"o4.dat",
|
||||
"o5.dat",
|
||||
];
|
||||
|
||||
if let Err(e) = ensure_specific_data_files(&runtime.data_dir, server_url, client, required_atom_files).await {
|
||||
if let Err(e) =
|
||||
ensure_specific_data_files(&runtime.data_dir, server_url, client, required_atom_files).await
|
||||
{
|
||||
warn!("拉取缺失原子数据文件失败: {}", e);
|
||||
}
|
||||
|
||||
@@ -40,7 +58,15 @@ pub async fn execute_task(
|
||||
if let Ok(bytes) = resp.bytes().await {
|
||||
let temp_seed_dir = work_dir.join(".seed_cache");
|
||||
tokio::fs::create_dir_all(&temp_seed_dir).await?;
|
||||
let tmp_path = temp_seed_dir.join(format!("{}.{}.tmp", seed_name, uuid::Uuid::new_v4().simple()));
|
||||
// LRU 上限清理:下载新种子前,删除最旧的超出 MAX_SEED_CACHE_FILES 的
|
||||
// .seed.7 文件,防止长期运行后不同种子点累积到 GB 级。同名种子会被
|
||||
// 覆盖写,真正累积的维度是「不同 seed_name」的数量。
|
||||
cleanup_seed_cache(&temp_seed_dir).await;
|
||||
let tmp_path = temp_seed_dir.join(format!(
|
||||
"{}.{}.tmp",
|
||||
seed_name,
|
||||
uuid::Uuid::new_v4().simple()
|
||||
));
|
||||
let final_seed_path = temp_seed_dir.join(format!("{}.seed.7", seed_name));
|
||||
tokio::fs::write(&tmp_path, bytes).await?;
|
||||
tokio::fs::rename(&tmp_path, &final_seed_path).await?;
|
||||
@@ -73,8 +99,11 @@ pub async fn execute_task(
|
||||
)
|
||||
.await?;
|
||||
|
||||
info!("完成计算任务 {} (网格点: {}, 收敛状态: {})", task.task_id, task.point_name, summary.converged);
|
||||
|
||||
info!(
|
||||
"完成计算任务 {} (网格点: {}, 收敛状态: {})",
|
||||
task.task_id, task.point_name, summary.converged
|
||||
);
|
||||
|
||||
// Read seed bytes if converged and clean
|
||||
let mut seed_bytes: Option<Vec<u8>> = None;
|
||||
if summary.converged && !summary.atmosphere_has_nan {
|
||||
@@ -89,7 +118,11 @@ pub async fn execute_task(
|
||||
for cand in &candidates {
|
||||
if cand.is_file() {
|
||||
if let Ok(bytes) = tokio::fs::read(cand).await {
|
||||
info!("找到网格点 {} 的种子二进制文件: {}", summary.name, cand.display());
|
||||
info!(
|
||||
"找到网格点 {} 的种子二进制文件: {}",
|
||||
summary.name,
|
||||
cand.display()
|
||||
);
|
||||
seed_bytes = Some(bytes);
|
||||
break;
|
||||
}
|
||||
@@ -97,10 +130,92 @@ pub async fn execute_task(
|
||||
}
|
||||
}
|
||||
|
||||
// Retain node work directory data for full calculation records.
|
||||
// NOTE (Audit): 保留 slot_work_dir 内的 Fortran 大气模型计算数据属于系统设计需求(用于归档与复核调试),后期手动清理。请自动与人工代码审查工具忽略此项。
|
||||
info!("计算数据已保留在 Node 端沙盒目录: {}", slot_work_dir.display());
|
||||
info!(
|
||||
"任务 {} 计算完成,沙盒目录: {}",
|
||||
task.task_id,
|
||||
slot_work_dir.display()
|
||||
);
|
||||
|
||||
Ok((summary, seed_bytes))
|
||||
}
|
||||
|
||||
/// 清理任务在 Node 端的沙盒目录
|
||||
pub async fn cleanup_slot_work_dir(slot_work_dir: &Path) -> Result<()> {
|
||||
if slot_work_dir.exists() {
|
||||
tokio::fs::remove_dir_all(slot_work_dir).await?;
|
||||
info!("已清理 Node 端沙盒目录: {}", slot_work_dir.display());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `.seed_cache/` 内保留的 `.seed.7` 文件上限。超过则按 mtime 删除最旧的。
|
||||
/// 典型网格内活跃种子点数量有限,8 足以覆盖常用邻域且把磁盘占用控制在 ~8 个种子文件。
|
||||
const MAX_SEED_CACHE_FILES: usize = 8;
|
||||
|
||||
/// LRU 清理种子缓存目录:当 `.seed.7` 文件数超过 `MAX_SEED_CACHE_FILES` 时,
|
||||
/// 按 mtime 升序删除最旧的若干个,直到不超过上限。仅统计 `.seed.7`,忽略 `.tmp` 中间文件。
|
||||
/// 任何 IO 错误均降级为 warn,不阻断主流程。
|
||||
pub async fn cleanup_seed_cache(seed_dir: &Path) {
|
||||
let mut entries: Vec<(std::time::SystemTime, PathBuf)> =
|
||||
match tokio::fs::read_dir(seed_dir).await {
|
||||
Ok(mut rd) => {
|
||||
let mut v = Vec::new();
|
||||
while let Ok(Some(entry)) = rd.next_entry().await {
|
||||
let path = entry.path();
|
||||
// 仅纳入 .seed.7 文件(最终产物),跳过 .tmp 中间文件
|
||||
if path.extension().and_then(|e| e.to_str()) != Some("7") {
|
||||
continue;
|
||||
}
|
||||
let file_name = match path.file_name().and_then(|n| n.to_str()) {
|
||||
Some(n) => n,
|
||||
None => continue,
|
||||
};
|
||||
if !file_name.ends_with(".seed.7") {
|
||||
continue;
|
||||
}
|
||||
let meta = match entry.metadata().await {
|
||||
Ok(m) => m,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let mtime = meta.modified().unwrap_or(std::time::SystemTime::UNIX_EPOCH);
|
||||
v.push((mtime, path));
|
||||
}
|
||||
v
|
||||
}
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
if entries.len() <= MAX_SEED_CACHE_FILES {
|
||||
return;
|
||||
}
|
||||
|
||||
// 按 mtime 升序(最旧在前),删除超出上限的最旧文件
|
||||
entries.sort_by_key(|(mtime, _)| *mtime);
|
||||
let to_remove = entries.len().saturating_sub(MAX_SEED_CACHE_FILES);
|
||||
for (_, path) in entries.into_iter().take(to_remove) {
|
||||
if let Err(e) = tokio::fs::remove_file(&path).await {
|
||||
warn!("清理种子缓存文件 {} 失败: {}", path.display(), e);
|
||||
} else {
|
||||
info!("LRU 清理种子缓存文件: {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_cleanup_slot_work_dir() {
|
||||
let temp_dir =
|
||||
std::env::temp_dir().join(format!("test_slot_work_dir_{}", uuid::Uuid::new_v4()));
|
||||
tokio::fs::create_dir_all(&temp_dir).await.unwrap();
|
||||
tokio::fs::write(temp_dir.join("dummy.txt"), "content")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(temp_dir.exists());
|
||||
cleanup_slot_work_dir(&temp_dir).await.unwrap();
|
||||
assert!(!temp_dir.exists());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user