traffic_core: продакшн-фиксы runtime и валидации

This commit is contained in:
2026-05-10 21:29:52 +05:00
parent a4bb7adb4f
commit d4cd6a4e43
9 changed files with 353 additions and 52 deletions
+50 -19
View File
@@ -6,17 +6,21 @@ use std::{
time::{Duration, Instant},
};
use crate::{config::Config, docker, firewall, iface, netns::build_transport, reboot, traffic};
use crate::{config::Config, docker, firewall, iface, netns::build_transport, traffic};
use tracing::{error, info};
#[derive(Debug, Clone, Default)]
struct LastActionState {
last_ok_at: Option<String>,
last_ok: Option<String>,
last_error_at: Option<String>,
last_error: Option<String>,
}
const LAST_STATE_FILE: &str = "/run/awg2-harness/last_action.state";
const EVENTS_FILE: &str = "/run/awg2-harness/events.log";
const EVENTS_MAX_LINES: usize = 1000;
const EVENTS_MAX_BYTES: usize = 256 * 1024;
pub fn run(cfg: Config) -> anyhow::Result<()> {
info!("starting daemon");
@@ -36,22 +40,22 @@ pub fn run(cfg: Config) -> anyhow::Result<()> {
set_ok(&state, "startup firewall reconcile ok");
append_event("startup_firewall ok");
}
let _ = reboot::maybe_schedule_reboot(&cfg);
let reboot_cfg = cfg.clone();
thread::spawn(move || {
if let Err(e) = reboot::reboot_scheduler_loop(reboot_cfg) {
error!(error = %e, "reboot scheduler loop error");
}
});
let events_cfg = cfg.clone();
let events_state = state.clone();
thread::spawn(move || {
loop {
if let Err(e) = docker_events_loop(&events_cfg, &events_state) {
set_err(&events_state, format!("docker events loop crashed: {e}"));
thread::sleep(Duration::from_secs(10));
match docker_events_loop(&events_cfg, &events_state) {
Ok(()) => {
set_err(
&events_state,
"docker events stream ended, restarting".to_string(),
);
}
Err(e) => {
set_err(&events_state, format!("docker events loop crashed: {e}"));
}
}
thread::sleep(Duration::from_secs(10));
}
});
@@ -144,13 +148,16 @@ fn wait_for_interface(cfg: &Config, container_name: &str, timeout_secs: u64) ->
fn set_ok(state: &Arc<Mutex<LastActionState>>, msg: &str) {
if let Ok(mut s) = state.lock() {
s.last_ok_at = Some(now_rfc3339());
s.last_ok = Some(msg.to_string());
s.last_error = None;
persist_state(&s);
}
}
fn set_err(state: &Arc<Mutex<LastActionState>>, msg: String) {
if let Ok(mut s) = state.lock() {
s.last_error_at = Some(now_rfc3339());
s.last_error = Some(msg);
persist_state(&s);
}
@@ -159,8 +166,10 @@ fn set_err(state: &Arc<Mutex<LastActionState>>, msg: String) {
fn persist_state(state: &LastActionState) {
let _ = fs::create_dir_all("/run/awg2-harness");
let content = format!(
"last_ok={}\nlast_error={}\n",
"last_ok_at={}\nlast_ok={}\nlast_error_at={}\nlast_error={}\n",
state.last_ok_at.clone().unwrap_or_default(),
state.last_ok.clone().unwrap_or_default(),
state.last_error_at.clone().unwrap_or_default(),
state.last_error.clone().unwrap_or_default()
);
let _ = fs::write(LAST_STATE_FILE, content);
@@ -168,12 +177,34 @@ fn persist_state(state: &LastActionState) {
fn append_event(event: &str) {
let _ = fs::create_dir_all("/run/awg2-harness");
let ts = chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
if let Ok(mut f) = fs::OpenOptions::new()
.create(true)
.append(true)
.open(EVENTS_FILE)
let ts = now_rfc3339();
let mut lines: Vec<String> = fs::read_to_string(EVENTS_FILE)
.ok()
.map(|s| s.lines().map(|v| v.to_string()).collect())
.unwrap_or_default();
lines.push(format!("{ts} {event}"));
while lines.len() > EVENTS_MAX_LINES {
lines.remove(0);
}
let mut content = lines.join("\n");
if !content.is_empty() {
content.push('\n');
}
while content.len() > EVENTS_MAX_BYTES {
if let Some(pos) = content.find('\n') {
content.drain(..=pos);
} else {
break;
}
}
if let Ok(mut f) = fs::OpenOptions::new().create(true).write(true).truncate(true).open(EVENTS_FILE)
{
let _ = writeln!(f, "{ts} {event}");
let _ = f.write_all(content.as_bytes());
}
}
fn now_rfc3339() -> String {
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
}