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(
39 endpoint: &crate::varlink::endpoint::VarlinkEndpoint,
40) -> anyhow::Result<ManagerRuntime> {
41 let mut conn = endpoint.connect().await?;
42 match conn.describe().await? {
43 Ok(output) => output
44 .runtime
45 .ok_or_else(|| anyhow::anyhow!("io.systemd.Manager.Describe has no runtime")),
46 Err(e) => Err(anyhow::anyhow!("io.systemd.Manager.Describe error: {}", e)),
47 }
48}
49
50pub fn shared_describe(endpoint: crate::varlink::endpoint::VarlinkEndpoint) -> SharedDescribe {
55 async move { describe_runtime(&endpoint).await.map_err(Arc::new) }
56 .boxed()
57 .shared()
58}
59
60async fn describe(shared: SharedDescribe) -> anyhow::Result<ManagerRuntime> {
61 shared
62 .await
63 .map_err(|err| anyhow::anyhow!("{:#}", err))
64 .map_err(|err| err.context("io.systemd.Manager.Describe failed"))
65}
66
67pub async fn get_system_state(shared: SharedDescribe) -> anyhow::Result<SystemdSystemState> {
68 let runtime = describe(shared).await?;
69 let system_state = runtime
70 .system_state
71 .ok_or_else(|| anyhow::anyhow!("io.systemd.Manager.Describe has no SystemState"))?;
72 Ok(parse_system_state(&system_state))
73}
74
75pub async fn get_version(shared: SharedDescribe) -> anyhow::Result<SystemdVersion> {
76 let runtime = describe(shared).await?;
77 let version = runtime
78 .version
79 .ok_or_else(|| anyhow::anyhow!("io.systemd.Manager.Describe has no Version"))?;
80 Ok(version.try_into()?)
81}
82
83pub async fn update_system_stats(
89 shared: SharedDescribe,
90 locked_machine_stats: Arc<RwLock<MachineStats>>,
91) -> anyhow::Result<()> {
92 let system_state = get_system_state(shared).await?;
93 let mut machine_stats = locked_machine_stats.write().await;
94 machine_stats.system_state = system_state;
95 Ok(())
96}
97
98pub async fn update_version(
100 shared: SharedDescribe,
101 locked_machine_stats: Arc<RwLock<MachineStats>>,
102) -> anyhow::Result<()> {
103 let version = get_version(shared).await?;
104 let mut machine_stats = locked_machine_stats.write().await;
105 machine_stats.version = version;
106 Ok(())
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use crate::varlink::manager::DescribeOutput;
113
114 #[test]
115 fn test_describe_output_ignores_unconsumed_fields() {
116 let reply = r#"{
120 "context": {"ShowStatus": true, "LogTarget": "journal"},
121 "runtime": {
122 "Version": "262~rc3-1.fc46",
123 "Architecture": "arm64",
124 "Virtualization": "docker",
125 "SystemState": "running",
126 "NNames": 295
127 }
128 }"#;
129 let output: DescribeOutput =
130 serde_json::from_str(reply).expect("Describe reply should deserialize");
131 let runtime = output.runtime.expect("reply should have a runtime section");
132 assert_eq!(runtime.version.as_deref(), Some("262~rc3-1.fc46"));
133 assert_eq!(runtime.system_state.as_deref(), Some("running"));
134 }
135
136 #[test]
137 fn test_version_string_parses_like_the_dbus_property() {
138 let version: SystemdVersion = "262~rc3-1.fc46"
140 .to_string()
141 .try_into()
142 .expect("version should parse");
143 assert_eq!(
144 version,
145 SystemdVersion::new(262, "rc3-1".to_string(), None, "fc46".to_string())
146 );
147 }
148
149 #[tokio::test]
150 async fn test_shared_describe_resolves_for_every_awaiter() {
151 let shared = shared_describe("/nonexistent/io.systemd.Manager".into());
154 let (version, system_state) =
155 tokio::join!(get_version(shared.clone()), get_system_state(shared));
156 assert!(version.is_err());
157 assert!(system_state.is_err());
158 }
159
160 #[test]
161 fn test_runtime_without_our_fields() {
162 let output: DescribeOutput =
164 serde_json::from_str(r#"{"runtime": {}}"#).expect("Describe reply should deserialize");
165 let runtime = output.runtime.expect("reply should have a runtime section");
166 assert!(runtime.version.is_none());
167 assert!(runtime.system_state.is_none());
168 }
169}