[WIP] Daemon & CLI

This commit is contained in:
Alex-Emmet 2026-07-23 23:13:02 +02:00
commit 8b158108bb
100 changed files with 6519 additions and 1596 deletions

View file

@ -0,0 +1,52 @@
use async_trait::async_trait;
use iota_daemon_lib::{CommandRouter, DaemonRuntime, DaemonServices};
use iota_ipc::{LocalRequest, ResponseResult};
use mtp::codec::CommunicationValue;
use omikron_connector::{OmikronClient, OmikronError};
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
use std::time::Duration;
struct FakeOmikron {
reconnects: AtomicUsize,
}
#[async_trait]
impl OmikronClient for FakeOmikron {
async fn send_message(&self, _: &CommunicationValue) -> Result<(), OmikronError> {
Ok(())
}
async fn await_response(
&self,
_: &CommunicationValue,
_: Duration,
) -> Result<CommunicationValue, OmikronError> {
Err(OmikronError::Disconnected("fake".into()))
}
async fn reconnect(&self) -> Result<(), OmikronError> {
self.reconnects.fetch_add(1, Ordering::SeqCst);
Ok(())
}
async fn is_connected(&self) -> bool {
true
}
}
#[tokio::test]
async fn reconnect_uses_the_injected_client() {
let fake = Arc::new(FakeOmikron {
reconnects: AtomicUsize::new(0),
});
let services = Arc::new(DaemonServices {
omikron: fake.clone(),
users: Default::default(),
config: Default::default(),
});
let router = CommandRouter::new(Arc::new(DaemonRuntime::new()), services);
assert!(matches!(
router.route(1, LocalRequest::ReconnectOmikron).await.result,
ResponseResult::Ok(_)
));
assert_eq!(fake.reconnects.load(Ordering::SeqCst), 1);
}

View file

@ -0,0 +1,29 @@
use iota_daemon_lib::{DaemonRuntime, StartupPhase};
use iota_ipc::{ComponentId, HealthStatus, LifecyclePhase};
#[test]
fn component_failures_are_independent_and_recovery_is_scoped() {
let runtime = DaemonRuntime::new();
runtime.set_component_degraded(ComponentId::Omikron, "offline".into());
runtime.set_component_failed(ComponentId::Web, "bind failed".into());
runtime.set_startup_phase(StartupPhase::Ready);
let snapshot = runtime.snapshot();
assert_eq!(snapshot.lifecycle, LifecyclePhase::Ready);
assert_eq!(snapshot.overall_health, HealthStatus::Degraded);
assert_eq!(
snapshot.components[&ComponentId::Omikron].status,
HealthStatus::Degraded
);
runtime.set_component_healthy(ComponentId::Web, None);
assert_eq!(
runtime.snapshot().components[&ComponentId::Omikron].status,
HealthStatus::Degraded
);
}
#[test]
fn critical_failure_is_failed_but_optional_degradation_is_not() {
let runtime = DaemonRuntime::new();
runtime.set_component_failed(ComponentId::Storage, "database unavailable".into());
assert_eq!(runtime.snapshot().overall_health, HealthStatus::Failed);
}

View file

@ -0,0 +1,40 @@
use iota_daemon_lib::{DaemonRuntime, ShutdownReason};
use std::time::Duration;
#[tokio::test]
async fn shutdown_reason_is_first_write_wins_and_tasks_join() {
let runtime = DaemonRuntime::new();
runtime.shutdown(ShutdownReason::Fatal("first".into()));
runtime.shutdown(ShutdownReason::Restart);
assert_eq!(
runtime.shutdown_reason(),
Some(ShutdownReason::Fatal("first".into()))
);
runtime.tasks.spawn_tracked("quick", async { Ok(()) }).await;
assert!(
runtime
.tasks
.join_with_timeout(Duration::from_millis(100))
.await
.is_empty()
);
}
#[tokio::test]
async fn long_task_is_aborted_at_join_timeout() {
let runtime = DaemonRuntime::new();
runtime
.tasks
.spawn_tracked("slow", async {
tokio::time::sleep(Duration::from_secs(10)).await;
Ok(())
})
.await;
assert!(
runtime
.tasks
.join_with_timeout(Duration::from_millis(10))
.await
.is_empty()
);
}