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:
+72
-16
@@ -6,11 +6,11 @@ use crate::gen_input5::make_input5;
|
||||
use crate::models::{GridPointParams, ModelSummary, StageSummary, TaskType};
|
||||
use crate::nst_writer::generate_nst_content;
|
||||
use anyhow::Result;
|
||||
use tokio::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Stdio;
|
||||
use tokio::process::Command as AsyncCommand;
|
||||
use std::time::Instant;
|
||||
use tokio::fs::File;
|
||||
use tokio::process::Command as AsyncCommand;
|
||||
use tracing::{info, warn};
|
||||
|
||||
pub fn default_cold_chain() -> Vec<StageConfig> {
|
||||
@@ -130,7 +130,15 @@ impl<'a> ExecutionRunner<'a> {
|
||||
seed_atmos: Option<&Path>,
|
||||
synspec_cfg: Option<&SynspecConfig>,
|
||||
) -> Result<ModelSummary> {
|
||||
self.run_model_with_timeout(params, task_type, custom_chain, seed_atmos, synspec_cfg, 7200).await
|
||||
self.run_model_with_timeout(
|
||||
params,
|
||||
task_type,
|
||||
custom_chain,
|
||||
seed_atmos,
|
||||
synspec_cfg,
|
||||
7200,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn run_model_with_timeout(
|
||||
@@ -157,7 +165,9 @@ impl<'a> ExecutionRunner<'a> {
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let abs_data_dir = tokio::fs::canonicalize(&self.runtime.data_dir).await.unwrap_or_else(|_| self.runtime.data_dir.clone());
|
||||
let abs_data_dir = tokio::fs::canonicalize(&self.runtime.data_dir)
|
||||
.await
|
||||
.unwrap_or_else(|_| self.runtime.data_dir.clone());
|
||||
if let Err(e) = std::os::unix::fs::symlink(&abs_data_dir, &link_data) {
|
||||
warn!("构建 data 数据集软链时发生提示性告警: {}", e);
|
||||
}
|
||||
@@ -171,7 +181,10 @@ impl<'a> ExecutionRunner<'a> {
|
||||
if let Some(seed_path) = seed_atmos {
|
||||
if seed_path.is_file() {
|
||||
if let Err(e) = tokio::fs::copy(seed_path, &fort8).await {
|
||||
warn!("向工作沙盒引导填载首期收敛模型种子 fort.8 发生复制错误: {}", e);
|
||||
warn!(
|
||||
"向工作沙盒引导填载首期收敛模型种子 fort.8 发生复制错误: {}",
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,15 +232,24 @@ impl<'a> ExecutionRunner<'a> {
|
||||
} else if let Some(ref s_path) = current_seed {
|
||||
if s_path.is_file() {
|
||||
if let Err(e) = tokio::fs::copy(s_path, &fort8).await {
|
||||
warn!("阶段 {} 重载候选近邻推算种子模型期间发生文件复制异常: {}", stage_def.label, e);
|
||||
warn!(
|
||||
"阶段 {} 重载候选近邻推算种子模型期间发生文件复制异常: {}",
|
||||
stage_def.label, e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Run tlusty.exe
|
||||
let fin = File::open(&input5_path).await?.into_std().await;
|
||||
let fout = File::create(model_dir.join(format!("{}.6", name))).await?.into_std().await;
|
||||
let ferr = File::create(model_dir.join(format!("{}.err", name))).await?.into_std().await;
|
||||
let fout = File::create(model_dir.join(format!("{}.6", name)))
|
||||
.await?
|
||||
.into_std()
|
||||
.await;
|
||||
let ferr = File::create(model_dir.join(format!("{}.err", name)))
|
||||
.await?
|
||||
.into_std()
|
||||
.await;
|
||||
|
||||
let child = AsyncCommand::new(&self.runtime.tlusty_exe)
|
||||
.current_dir(&model_dir)
|
||||
@@ -246,7 +268,6 @@ impl<'a> ExecutionRunner<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let fort9 = model_dir.join("fort.9");
|
||||
let fort7 = model_dir.join("fort.7");
|
||||
|
||||
@@ -293,7 +314,10 @@ impl<'a> ExecutionRunner<'a> {
|
||||
stage_summaries.push(stage_summary);
|
||||
|
||||
if !final_converged && stage_def.require_converged {
|
||||
warn!("阶段 {} 要求收敛但未达标,中止后续收敛链阶段", stage_def.label);
|
||||
warn!(
|
||||
"阶段 {} 要求收敛但未达标,中止后续收敛链阶段",
|
||||
stage_def.label
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -344,14 +368,19 @@ impl<'a> ExecutionRunner<'a> {
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let abs_linelist = tokio::fs::canonicalize(&self.runtime.linelist).await.unwrap_or_else(|_| self.runtime.linelist.clone());
|
||||
let abs_linelist = tokio::fs::canonicalize(&self.runtime.linelist)
|
||||
.await
|
||||
.unwrap_or_else(|_| self.runtime.linelist.clone());
|
||||
let _ = std::os::unix::fs::symlink(&abs_linelist, &fort19_path);
|
||||
}
|
||||
|
||||
let input5_path = model_dir.join(format!("{}.5", name));
|
||||
if input5_path.is_file() {
|
||||
let fin = File::open(&input5_path).await?.into_std().await;
|
||||
let fout = File::create(model_dir.join(format!("{}.log", name))).await?.into_std().await;
|
||||
let fout = File::create(model_dir.join(format!("{}.log", name)))
|
||||
.await?
|
||||
.into_std()
|
||||
.await;
|
||||
|
||||
let child = AsyncCommand::new(&self.runtime.synspec_exe)
|
||||
.current_dir(&model_dir)
|
||||
@@ -361,7 +390,8 @@ impl<'a> ExecutionRunner<'a> {
|
||||
.kill_on_drop(true)
|
||||
.spawn()?;
|
||||
|
||||
let status_res = run_child_async_with_timeout(child, timeout_sec).await;
|
||||
let synspec_timeout_sec = 600_u64.min(timeout_sec);
|
||||
let status_res = run_child_async_with_timeout(child, synspec_timeout_sec).await;
|
||||
let rc = match status_res {
|
||||
Ok(st) => st.code().unwrap_or(-1),
|
||||
Err(e) => {
|
||||
@@ -374,13 +404,25 @@ impl<'a> ExecutionRunner<'a> {
|
||||
|
||||
// Copy/move outputs: fort.7 (Synspec spectrum) -> .spec, fort.17 -> .cont, fort.12 -> .iden
|
||||
if model_dir.join("fort.7").is_file() {
|
||||
let _ = tokio::fs::rename(model_dir.join("fort.7"), model_dir.join(format!("{}.spec", name))).await;
|
||||
let _ = tokio::fs::rename(
|
||||
model_dir.join("fort.7"),
|
||||
model_dir.join(format!("{}.spec", name)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if model_dir.join("fort.17").is_file() {
|
||||
let _ = tokio::fs::copy(model_dir.join("fort.17"), model_dir.join(format!("{}.cont", name))).await;
|
||||
let _ = tokio::fs::copy(
|
||||
model_dir.join("fort.17"),
|
||||
model_dir.join(format!("{}.cont", name)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
if model_dir.join("fort.12").is_file() {
|
||||
let _ = tokio::fs::copy(model_dir.join("fort.12"), model_dir.join(format!("{}.iden", name))).await;
|
||||
let _ = tokio::fs::copy(
|
||||
model_dir.join("fort.12"),
|
||||
model_dir.join(format!("{}.iden", name)),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -415,3 +457,17 @@ impl<'a> ExecutionRunner<'a> {
|
||||
Ok(summary)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_synspec_timeout_calculation() {
|
||||
let long_tlusty_timeout: u64 = 7200;
|
||||
let synspec_timeout = 600_u64.min(long_tlusty_timeout);
|
||||
assert_eq!(synspec_timeout, 600);
|
||||
|
||||
let short_tlusty_timeout: u64 = 300;
|
||||
let synspec_timeout_short = 600_u64.min(short_tlusty_timeout);
|
||||
assert_eq!(synspec_timeout_short, 300);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user