monitord/
varlink_system.rs1use std::sync::Arc;
17
18use futures_util::future::{BoxFuture, FutureExt, Shared};
19use tokio::sync::RwLock;
20
21use crate::system::{parse_system_state, SystemdSystemState, SystemdVersion};
22use crate::varlink::manager::{Manager, ManagerRuntime};
23use crate::MachineStats;
24
25pub use crate::varlink::manager::MANAGER_SOCKET_PATH;
26
27pub type SharedDescribe = Shared<BoxFuture<'static, Result<ManagerRuntime, Arc<anyhow::Error>>>>;
32
33async fn describe_runtime(socket_path: &str) -> anyhow::Result<ManagerRuntime> {
39 let mut conn = zlink::unix::connect(socket_path).await?;
40 match conn.describe().await? {
41 Ok(output) => output
42 .runtime
43 .ok_or_else(|| anyhow::anyhow!("io.systemd.Manager.Describe has no runtime")),
44 Err(e) => Err(anyhow::anyhow!("io.systemd.Manager.Describe error: {}", e)),
45 }
46}
47
48pub fn shared_describe(socket_path: String) -> SharedDescribe {
53 async move { describe_runtime(&socket_path).await.map_err(Arc::new) }
54 .boxed()
55 .shared()
56}
57
58async fn describe(shared: SharedDescribe) -> anyhow::Result<ManagerRuntime> {
59 shared
60 .await
61 .map_err(|err| anyhow::anyhow!("{:#}", err))
62 .map_err(|err| err.context("io.systemd.Manager.Describe failed"))
63}
64
65pub async fn get_system_state(shared: SharedDescribe) -> anyhow::Result<SystemdSystemState> {
66 let runtime = describe(shared).await?;
67 let system_state = runtime
68 .system_state
69 .ok_or_else(|| anyhow::anyhow!("io.systemd.Manager.Describe has no SystemState"))?;
70 Ok(parse_system_state(&system_state))
71}
72
73pub async fn get_version(shared: SharedDescribe) -> anyhow::Result<SystemdVersion> {
74 let runtime = describe(shared).await?;
75 let version = runtime
76 .version
77 .ok_or_else(|| anyhow::anyhow!("io.systemd.Manager.Describe has no Version"))?;
78 Ok(version.try_into()?)
79}
80
81pub async fn update_system_stats(
87 shared: SharedDescribe,
88 locked_machine_stats: Arc<RwLock<MachineStats>>,
89) -> anyhow::Result<()> {
90 let system_state = get_system_state(shared).await?;
91 let mut machine_stats = locked_machine_stats.write().await;
92 machine_stats.system_state = system_state;
93 Ok(())
94}
95
96pub async fn update_version(
98 shared: SharedDescribe,
99 locked_machine_stats: Arc<RwLock<MachineStats>>,
100) -> anyhow::Result<()> {
101 let version = get_version(shared).await?;
102 let mut machine_stats = locked_machine_stats.write().await;
103 machine_stats.version = version;
104 Ok(())
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::varlink::manager::DescribeOutput;
111
112 #[test]
113 fn test_describe_output_ignores_unconsumed_fields() {
114 let reply = r#"{
118 "context": {"ShowStatus": true, "LogTarget": "journal"},
119 "runtime": {
120 "Version": "262~rc3-1.fc46",
121 "Architecture": "arm64",
122 "Virtualization": "docker",
123 "SystemState": "running",
124 "NNames": 295
125 }
126 }"#;
127 let output: DescribeOutput =
128 serde_json::from_str(reply).expect("Describe reply should deserialize");
129 let runtime = output.runtime.expect("reply should have a runtime section");
130 assert_eq!(runtime.version.as_deref(), Some("262~rc3-1.fc46"));
131 assert_eq!(runtime.system_state.as_deref(), Some("running"));
132 }
133
134 #[test]
135 fn test_version_string_parses_like_the_dbus_property() {
136 let version: SystemdVersion = "262~rc3-1.fc46"
138 .to_string()
139 .try_into()
140 .expect("version should parse");
141 assert_eq!(
142 version,
143 SystemdVersion::new(262, "rc3-1".to_string(), None, "fc46".to_string())
144 );
145 }
146
147 #[tokio::test]
148 async fn test_shared_describe_resolves_for_every_awaiter() {
149 let shared = shared_describe("/nonexistent/io.systemd.Manager".to_string());
152 let (version, system_state) =
153 tokio::join!(get_version(shared.clone()), get_system_state(shared));
154 assert!(version.is_err());
155 assert!(system_state.is_err());
156 }
157
158 #[test]
159 fn test_runtime_without_our_fields() {
160 let output: DescribeOutput =
162 serde_json::from_str(r#"{"runtime": {}}"#).expect("Describe reply should deserialize");
163 let runtime = output.runtime.expect("reply should have a runtime section");
164 assert!(runtime.version.is_none());
165 assert!(runtime.system_state.is_none());
166 }
167}