edgehog_device_runtime_containers/store/
device_mapping.rs1use diesel::query_dsl::methods::{FilterDsl, SelectDsl};
20use diesel::{delete, insert_or_ignore_into, update, ExpressionMethods, RunQueryDsl};
21use edgehog_store::conversions::SqlUuid;
22use edgehog_store::db::HandleError;
23use edgehog_store::models::containers::container::ContainerMissingDeviceMapping;
24use edgehog_store::models::containers::device_mapping::DeviceMapping;
25use edgehog_store::models::containers::device_mapping::DeviceMappingStatus;
26use edgehog_store::models::QueryModel;
27use edgehog_store::schema::containers::{container_device_mappings, device_mappings};
28use tracing::instrument;
29use uuid::Uuid;
30
31use crate::requests::device_mapping::CreateDeviceMapping;
32
33use super::{Result, StateStore};
34
35impl StateStore {
36 #[instrument(skip_all, fields(%create_device_mapping.id))]
38 pub(crate) async fn create_device_mapping(
39 &self,
40 create_device_mapping: CreateDeviceMapping,
41 ) -> Result<()> {
42 let dm_value = DeviceMapping::from(create_device_mapping);
43
44 self.handle
45 .for_write(move |writer| {
46 insert_or_ignore_into(device_mappings::table)
47 .values(&dm_value)
48 .execute(writer)?;
49
50 insert_or_ignore_into(container_device_mappings::table)
51 .values(ContainerMissingDeviceMapping::find_by_device_mapping(
52 &dm_value.id,
53 ))
54 .execute(writer)?;
55
56 delete(ContainerMissingDeviceMapping::find_by_device_mapping(
57 &dm_value.id,
58 ))
59 .execute(writer)?;
60
61 Ok(())
62 })
63 .await?;
64
65 Ok(())
66 }
67
68 #[instrument(skip(self))]
70 pub(crate) async fn update_device_mapping_status(
71 &self,
72 device_mapping_id: Uuid,
73 status: DeviceMappingStatus,
74 ) -> Result<()> {
75 self.handle
76 .for_write(move |writer| {
77 let updated = update(DeviceMapping::find_id(&SqlUuid::new(device_mapping_id)))
78 .set(device_mappings::status.eq(status))
79 .execute(writer)?;
80
81 HandleError::check_modified(updated, 1)?;
82
83 Ok(())
84 })
85 .await?;
86
87 Ok(())
88 }
89
90 #[instrument(skip(self))]
92 pub(crate) async fn delete_device_mapping(&self, device_mapping_id: Uuid) -> Result<()> {
93 self.handle
94 .for_write(move |writer| {
95 let updated = delete(DeviceMapping::find_id(&SqlUuid::new(device_mapping_id)))
96 .execute(writer)?;
97
98 HandleError::check_modified(updated, 1)?;
99
100 Ok(())
101 })
102 .await?;
103
104 Ok(())
105 }
106
107 #[instrument(skip(self))]
108 pub(crate) async fn load_device_mappings_to_publish(&self) -> Result<Vec<SqlUuid>> {
109 let device_mappings = self
110 .handle
111 .for_read(move |reader| {
112 let device_mappings = device_mappings::table
113 .select(device_mappings::id)
114 .filter(device_mappings::status.eq(DeviceMappingStatus::Received))
115 .load::<SqlUuid>(reader)?;
116
117 Ok(device_mappings)
118 })
119 .await?;
120
121 Ok(device_mappings)
122 }
123}
124
125impl From<CreateDeviceMapping> for DeviceMapping {
126 fn from(
127 CreateDeviceMapping {
128 id,
129 deployment_id: _,
130 path_on_host,
131 path_in_container,
132 c_group_permissions,
133 }: CreateDeviceMapping,
134 ) -> Self {
135 Self {
136 id: SqlUuid::new(id),
137 status: DeviceMappingStatus::default(),
138 path_on_host,
139 path_in_container,
140 cgroup_permissions: c_group_permissions.into(),
141 }
142 }
143}
144
145impl From<DeviceMapping> for crate::docker::container::DeviceMapping {
146 fn from(
147 DeviceMapping {
148 id: _,
149 status: _,
150 path_on_host,
151 path_in_container,
152 cgroup_permissions,
153 }: DeviceMapping,
154 ) -> Self {
155 Self {
156 path_on_host,
157 path_in_container,
158 cgroup_permissions,
159 }
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use crate::requests::{OptString, ReqUuid};
166
167 use super::*;
168
169 use diesel::OptionalExtension;
170 use edgehog_store::db;
171 use pretty_assertions::assert_eq;
172 use tempfile::TempDir;
173
174 async fn find_device_mapping(store: &StateStore, id: Uuid) -> Option<DeviceMapping> {
175 store
176 .handle
177 .for_read(move |reader| {
178 DeviceMapping::find_id(&SqlUuid::new(id))
179 .first::<DeviceMapping>(reader)
180 .optional()
181 .map_err(HandleError::Query)
182 })
183 .await
184 .unwrap()
185 }
186
187 #[tokio::test]
188 async fn should_store() {
189 let tmp = TempDir::with_prefix("store_device_mapping").unwrap();
190 let db_file = tmp.path().join("state.db");
191 let db_file = db_file.to_str().unwrap();
192
193 let handle = db::Handle::open(db_file).await.unwrap();
194 let store = StateStore::new(handle);
195
196 let device_mapping_id = Uuid::new_v4();
197 let deployment_id = Uuid::new_v4();
198 let device_mapping = CreateDeviceMapping {
199 id: ReqUuid(device_mapping_id),
200 deployment_id: ReqUuid(deployment_id),
201 path_on_host: "/dev/tty12".to_string(),
202 path_in_container: "/dev/tty12".to_string(),
203 c_group_permissions: OptString::from("mvr".to_string()),
204 };
205 store.create_device_mapping(device_mapping).await.unwrap();
206
207 let res = find_device_mapping(&store, device_mapping_id)
208 .await
209 .unwrap();
210
211 let exp = DeviceMapping {
212 id: SqlUuid::new(device_mapping_id),
213 status: DeviceMappingStatus::Received,
214 path_on_host: "/dev/tty12".to_string(),
215 path_in_container: "/dev/tty12".to_string(),
216 cgroup_permissions: Some("mvr".to_string()),
217 };
218
219 assert_eq!(res, exp);
220 }
221
222 #[tokio::test]
223 async fn should_update() {
224 let tmp = TempDir::with_prefix("update_device_mapping").unwrap();
225 let db_file = tmp.path().join("state.db");
226 let db_file = db_file.to_str().unwrap();
227
228 let handle = db::Handle::open(db_file).await.unwrap();
229 let store = StateStore::new(handle);
230
231 let device_mapping_id = Uuid::new_v4();
232 let deployment_id = Uuid::new_v4();
233 let device_mapping = CreateDeviceMapping {
234 id: ReqUuid(device_mapping_id),
235 deployment_id: ReqUuid(deployment_id),
236 path_on_host: "/dev/tty12".to_string(),
237 path_in_container: "/dev/tty12".to_string(),
238 c_group_permissions: OptString::from("mvr".to_string()),
239 };
240 store.create_device_mapping(device_mapping).await.unwrap();
241
242 store
243 .update_device_mapping_status(device_mapping_id, DeviceMappingStatus::Published)
244 .await
245 .unwrap();
246
247 let res = find_device_mapping(&store, device_mapping_id)
248 .await
249 .unwrap();
250
251 let exp = DeviceMapping {
252 id: SqlUuid::new(device_mapping_id),
253 status: DeviceMappingStatus::Published,
254 path_on_host: "/dev/tty12".to_string(),
255 path_in_container: "/dev/tty12".to_string(),
256 cgroup_permissions: Some("mvr".to_string()),
257 };
258
259 assert_eq!(res, exp);
260 }
261}