1use diesel::dsl::exists;
20use diesel::{
21 delete, insert_or_ignore_into, select, update, CombineDsl, ExpressionMethods,
22 NullableExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection,
23};
24use edgehog_store::conversions::SqlUuid;
25use edgehog_store::db::HandleError;
26use edgehog_store::models::containers::container::{
27 Container, ContainerDeviceMapping, ContainerNetwork, ContainerVolume,
28};
29use edgehog_store::models::containers::deployment::{
30 Deployment, DeploymentContainer, DeploymentMissingContainer, DeploymentStatus,
31};
32use edgehog_store::models::QueryModel;
33use edgehog_store::schema::containers::{
34 container_device_mappings, container_missing_images, container_missing_networks,
35 container_missing_volumes, container_networks, container_volumes, containers,
36 deployment_containers, deployment_missing_containers, deployments,
37};
38use itertools::Itertools;
39use tracing::{debug, instrument};
40use uuid::Uuid;
41
42use crate::requests::deployment::{CreateDeployment, DeploymentUpdate};
43use crate::resource::deployment::Deployment as DeploymentResource;
44
45use super::{Result, StateStore};
46
47impl StateStore {
48 #[instrument(skip_all, fields(%deployment.id))]
50 pub(crate) async fn create_deployment(&self, deployment: CreateDeployment) -> Result<()> {
51 let containers = deployment.containers.iter().map(SqlUuid::new).collect_vec();
52 let deployment = Deployment::from(deployment);
53
54 self.handle
55 .for_write(move |writer| {
56 insert_or_ignore_into(deployments::table)
57 .values(&deployment)
58 .execute(writer)?;
59
60 for container_id in containers {
61 let exists: bool = Container::exists(&container_id).get_result(writer)?;
62
63 if !exists {
64 insert_or_ignore_into(deployment_missing_containers::table)
65 .values(DeploymentMissingContainer {
66 deployment_id: deployment.id,
67 container_id,
68 })
69 .execute(writer)?;
70
71 continue;
72 }
73
74 insert_or_ignore_into(deployment_containers::table)
75 .values(DeploymentContainer {
76 deployment_id: deployment.id,
77 container_id,
78 })
79 .execute(writer)?;
80 }
81
82 Ok(())
83 })
84 .await?;
85
86 Ok(())
87 }
88
89 #[instrument(skip(self))]
91 pub(crate) async fn update_deployment_status(
92 &self,
93 id: Uuid,
94 status: DeploymentStatus,
95 ) -> Result<()> {
96 self.handle
97 .for_write(move |writer| {
98 let updated = update(Deployment::find_id(&SqlUuid::new(id)))
99 .set(deployments::status.eq(status))
100 .execute(writer)?;
101
102 HandleError::check_modified(updated, 1)?;
103
104 Ok(())
105 })
106 .await?;
107
108 Ok(())
109 }
110
111 #[instrument(skip(self))]
112 pub(crate) async fn delete_deployment(&self, id: Uuid) -> Result<()> {
113 self.handle
114 .for_write(move |writer| {
115 let updated = delete(Deployment::find_id(&SqlUuid::new(id))).execute(writer)?;
116
117 HandleError::check_modified(updated, 1)?;
118
119 Ok(())
120 })
121 .await?;
122
123 Ok(())
124 }
125
126 #[instrument(skip(self))]
128 pub(crate) async fn deployment_update(&self, from: Uuid, to: Uuid) -> Result<()> {
129 self.handle
130 .for_write(move |writer| {
131 let status: DeploymentStatus = Deployment::find_id(&SqlUuid::new(from))
132 .select(deployments::status)
133 .first(writer)?;
134
135 match status {
137 DeploymentStatus::Started => {
138 let updated = update(Deployment::find_id(&SqlUuid::new(from)))
139 .set(deployments::status.eq(DeploymentStatus::Stopped))
140 .execute(writer)?;
141
142 HandleError::check_modified(updated, 1)?;
143
144 debug!("deployment to update set to stopped")
145 }
146 DeploymentStatus::Received
147 | DeploymentStatus::Stopped
148 | DeploymentStatus::Deleted => {
149 debug!("deployment to update is in state {status}, not setting to stopped")
150 }
151 }
152
153 let updated = update(Deployment::find_id(&SqlUuid::new(to)))
154 .set(deployments::status.eq(DeploymentStatus::Started))
155 .execute(writer)?;
156
157 HandleError::check_modified(updated, 1)?;
158
159 Ok(())
160 })
161 .await?;
162
163 Ok(())
164 }
165
166 #[instrument(skip(self))]
167 pub(crate) async fn load_deployments_in(
168 &self,
169 status: DeploymentStatus,
170 ) -> Result<Vec<SqlUuid>> {
171 let deployment = self
172 .handle
173 .for_read(move |reader| {
174 let deployments = deployments::table
175 .filter(deployments::status.eq(status))
176 .select(deployments::id)
177 .load::<SqlUuid>(reader)?;
178
179 Ok(deployments)
180 })
181 .await?;
182
183 Ok(deployment)
184 }
185
186 #[instrument(skip(self))]
188 pub(crate) async fn load_deployment_containers(
189 &self,
190 id: Uuid,
191 ) -> Result<Option<Vec<SqlUuid>>> {
192 let containers = self
193 .handle
194 .for_read(move |reader| {
195 let id = SqlUuid::new(id);
196 if !Deployment::exists(&id).get_result(reader)? {
197 return Ok(None);
198 }
199
200 let containers = deployment_containers::table
201 .select(deployment_containers::container_id)
202 .filter(deployment_containers::deployment_id.eq(id))
203 .load::<SqlUuid>(reader)?;
204
205 Ok(Some(containers))
206 })
207 .await?;
208
209 Ok(containers)
210 }
211
212 pub(crate) async fn find_complete_deployment(
213 &self,
214 id: Uuid,
215 ) -> Result<Option<DeploymentResource>> {
216 let deployment = self
217 .handle
218 .for_read(move |reader| {
219 let id = SqlUuid::new(id);
220
221 if !Deployment::exists(&id).get_result(reader)? {
222 return Ok(None);
223 }
224
225 if !is_deployment_complete(reader, &id)? {
226 return Ok(None);
227 }
228
229 let rows = Deployment::join_resources()
230 .filter(deployment_containers::deployment_id.eq(id))
231 .select((
232 deployment_containers::container_id,
233 containers::image_id.assume_not_null(),
234 Option::<ContainerNetwork>::as_select(),
235 Option::<ContainerVolume>::as_select(),
236 Option::<ContainerDeviceMapping>::as_select(),
237 ))
238 .load::<(
239 SqlUuid,
240 SqlUuid,
241 Option<ContainerNetwork>,
242 Option<ContainerVolume>,
243 Option<ContainerDeviceMapping>,
244 )>(reader)?;
245
246 Ok(Some(DeploymentResource::from(rows)))
247 })
248 .await?;
249
250 Ok(deployment)
251 }
252
253 pub(crate) async fn find_deployment_for_delete(
254 &self,
255 id: Uuid,
256 ) -> Result<Option<DeploymentResource>> {
257 let deployment = self
258 .handle
259 .for_read(move |reader| {
260 let id = SqlUuid::new(id);
261
262 if !Deployment::exists(&id).get_result(reader)? {
263 return Ok(None);
264 }
265
266 let containers = Deployment::join_resources()
268 .filter(deployment_containers::deployment_id.eq(id))
269 .select(deployment_containers::container_id)
270 .except(
271 Deployment::join_resources()
272 .filter(deployment_containers::deployment_id.ne(id))
273 .select(deployment_containers::container_id),
274 )
275 .load::<SqlUuid>(reader)?
276 .into_iter()
277 .map(Uuid::from)
278 .collect();
279
280 let images = Deployment::join_resources()
281 .filter(deployment_containers::deployment_id.eq(id))
282 .select(containers::image_id.assume_not_null())
283 .except(
284 Deployment::join_resources()
285 .filter(deployment_containers::deployment_id.ne(id))
286 .select(containers::image_id.assume_not_null()),
287 )
288 .load::<SqlUuid>(reader)?
289 .into_iter()
290 .map(Uuid::from)
291 .collect();
292
293 let volumes = Deployment::join_resources()
294 .filter(deployment_containers::deployment_id.eq(id))
295 .select(container_volumes::volume_id.nullable())
296 .except(
297 Deployment::join_resources()
298 .filter(deployment_containers::deployment_id.ne(id))
299 .select(container_volumes::volume_id.nullable()),
300 )
301 .load::<Option<SqlUuid>>(reader)?
302 .into_iter()
303 .filter_map(|container_volume| container_volume.map(Uuid::from))
304 .collect();
305
306 let networks = Deployment::join_resources()
307 .filter(deployment_containers::deployment_id.eq(id))
308 .select(container_networks::network_id.nullable())
309 .except(
310 Deployment::join_resources()
311 .filter(deployment_containers::deployment_id.ne(id))
312 .select(container_networks::network_id.nullable()),
313 )
314 .load::<Option<SqlUuid>>(reader)?
315 .into_iter()
316 .filter_map(|container_network| container_network.map(Uuid::from))
317 .collect();
318
319 let device_mapping = Deployment::join_resources()
320 .filter(deployment_containers::deployment_id.eq(id))
321 .select(container_device_mappings::device_mapping_id.nullable())
322 .except(
323 Deployment::join_resources()
324 .filter(deployment_containers::deployment_id.ne(id))
325 .select(container_device_mappings::device_mapping_id.nullable()),
326 )
327 .load::<Option<SqlUuid>>(reader)?
328 .into_iter()
329 .filter_map(|container_device_mapping| container_device_mapping.map(Uuid::from))
330 .collect();
331
332 Ok(Some(DeploymentResource {
333 containers,
334 images,
335 volumes,
336 networks,
337 device_mapping,
338 }))
339 })
340 .await?;
341
342 Ok(deployment)
343 }
344
345 #[instrument(skip(self))]
347 pub(crate) async fn load_deployment_containers_update_from(
348 &self,
349 DeploymentUpdate { from, to }: DeploymentUpdate,
350 ) -> Result<Option<Vec<SqlUuid>>> {
351 let containers = self
352 .handle
353 .for_read(move |reader| {
354 let from = SqlUuid::new(from);
355 if !Deployment::exists(&from).get_result(reader)? {
356 return Ok(None);
357 }
358
359 let to = SqlUuid::new(to);
360
361 let containers = deployment_containers::table
362 .select(deployment_containers::container_id)
363 .filter(deployment_containers::deployment_id.eq(from))
364 .except(
366 deployment_containers::table
367 .select(deployment_containers::container_id)
368 .filter(deployment_containers::deployment_id.eq(to)),
369 )
370 .load::<SqlUuid>(reader)?;
371
372 Ok(Some(containers))
373 })
374 .await?;
375
376 Ok(containers)
377 }
378}
379
380fn is_deployment_complete(
383 reader: &mut SqliteConnection,
384 id: &SqlUuid,
385) -> std::result::Result<bool, HandleError> {
386 select(exists(
387 deployments::table
388 .left_join(deployment_missing_containers::table)
389 .inner_join(
390 deployment_containers::table.inner_join(
391 containers::table
392 .left_join(container_missing_images::table)
393 .left_join(container_missing_networks::table)
394 .left_join(container_missing_volumes::table),
395 ),
396 )
397 .filter(deployments::id.eq(id))
398 .filter(deployment_missing_containers::deployment_id.is_null())
399 .filter(container_missing_images::container_id.is_null())
400 .filter(container_missing_networks::container_id.is_null())
401 .filter(container_missing_volumes::container_id.is_null()),
402 ))
403 .first::<bool>(reader)
404 .map_err(HandleError::Query)
405}
406
407impl From<CreateDeployment> for Deployment {
408 fn from(CreateDeployment { id, containers: _ }: CreateDeployment) -> Self {
409 Self {
410 id: SqlUuid::new(id),
411 status: DeploymentStatus::default(),
412 }
413 }
414}
415
416#[cfg(test)]
417mod tests {
418 use std::collections::HashSet;
419
420 use diesel::OptionalExtension;
421 use edgehog_store::db;
422 use pretty_assertions::assert_eq;
423 use tempfile::TempDir;
424
425 use crate::requests::device_mapping::CreateDeviceMapping;
426 use crate::requests::OptString;
427 use crate::requests::{
428 container::CreateContainer, image::CreateImage, network::CreateNetwork,
429 volume::CreateVolume, ReqUuid, VecReqUuid,
430 };
431
432 use super::*;
433
434 pub(crate) async fn find_deployment(store: &StateStore, id: Uuid) -> Option<Deployment> {
435 store
436 .handle
437 .for_read(move |reader| {
438 Deployment::find_id(&SqlUuid::new(id))
439 .first::<Deployment>(reader)
440 .optional()
441 .map_err(HandleError::Query)
442 })
443 .await
444 .unwrap()
445 }
446
447 #[tokio::test]
448 async fn should_create() {
449 let tmp = TempDir::with_prefix("create_full_deployment").unwrap();
450 let db_file = tmp.path().join("state.db");
451 let db_file = db_file.to_str().unwrap();
452
453 let handle = db::Handle::open(db_file).await.unwrap();
454 let store = StateStore::new(handle);
455
456 let deployment_id = Uuid::new_v4();
457
458 let image_id = Uuid::new_v4();
459 let image = CreateImage {
460 id: ReqUuid(image_id),
461 deployment_id: ReqUuid(deployment_id),
462 reference: "postgres:15".to_string(),
463 registry_auth: String::new(),
464 };
465 store.create_image(image).await.unwrap();
466
467 let volume_id = ReqUuid(Uuid::new_v4());
468 let volume = CreateVolume {
469 id: volume_id,
470 deployment_id: ReqUuid(deployment_id),
471 driver: "local".to_string(),
472 options: ["device=tmpfs", "o=size=100m,uid=1000", "type=tmpfs"]
473 .map(str::to_string)
474 .to_vec(),
475 };
476 store.create_volume(volume).await.unwrap();
477
478 let network_id = ReqUuid(Uuid::new_v4());
479 let network = CreateNetwork {
480 id: network_id,
481 deployment_id: ReqUuid(deployment_id),
482 driver: "bridge".to_string(),
483 internal: true,
484 enable_ipv6: false,
485 options: vec!["isolate=true".to_string()],
486 };
487 store.create_network(network).await.unwrap();
488
489 let device_mapping_id = ReqUuid(Uuid::new_v4());
490 let device_mapping = CreateDeviceMapping {
491 id: device_mapping_id,
492 deployment_id: ReqUuid(deployment_id),
493 path_on_host: "/dev/tty12".to_string(),
494 path_in_container: "dev/tty12".to_string(),
495 c_group_permissions: OptString::from("msv".to_string()),
496 };
497 store.create_device_mapping(device_mapping).await.unwrap();
498
499 let container_id = Uuid::new_v4();
500 let container = CreateContainer {
501 id: ReqUuid(container_id),
502 deployment_id: ReqUuid(deployment_id),
503 image_id: ReqUuid(image_id),
504 network_ids: VecReqUuid(vec![network_id]),
505 volume_ids: VecReqUuid(vec![volume_id]),
506 device_mapping_ids: VecReqUuid(vec![device_mapping_id]),
507 hostname: "database".to_string(),
508 restart_policy: "unless-stopped".to_string(),
509 env: ["POSTGRES_USER=user", "POSTGRES_PASSWORD=password"]
510 .map(str::to_string)
511 .to_vec(),
512 binds: vec!["/var/lib/postgres".to_string()],
513 network_mode: "bridge".to_string(),
514 port_bindings: vec!["5432:5432".to_string()],
515 extra_hosts: vec!["host.docker.internal:host-gateway".to_string()],
516 cap_add: vec!["CAP_CHOWN".to_string()],
517 cap_drop: vec!["CAP_KILL".to_string()],
518 cpu_period: 1000,
519 cpu_quota: 100,
520 cpu_realtime_period: 1000,
521 cpu_realtime_runtime: 100,
522 memory: 4096,
523 memory_reservation: 1024,
524 memory_swap: 8192,
525 memory_swappiness: 50,
526 volume_driver: "local".to_string().into(),
527 storage_opt: vec!["size=1024k".to_string()],
528 read_only_rootfs: true,
529 tmpfs: vec!["/run=rw,noexec,nosuid,size=65536k".to_string()],
530 privileged: false,
531 };
532 store.create_container(Box::new(container)).await.unwrap();
533
534 let deployment_id = Uuid::new_v4();
535 let deployment = CreateDeployment {
536 id: ReqUuid(deployment_id),
537 containers: VecReqUuid(vec![ReqUuid(container_id)]),
538 };
539 store.create_deployment(deployment).await.unwrap();
540
541 let deployment = find_deployment(&store, deployment_id).await.unwrap();
542 let exp = Deployment {
543 id: SqlUuid::new(deployment_id),
544 status: DeploymentStatus::Received,
545 };
546 assert_eq!(deployment, exp);
547
548 let containers = store
549 .load_deployment_containers(deployment_id)
550 .await
551 .unwrap()
552 .unwrap();
553 let exp = vec![SqlUuid::new(container_id)];
554 assert_eq!(containers, exp);
555 }
556
557 #[tokio::test]
558 async fn update_status() {
559 let tmp = TempDir::with_prefix("create_full_deployment").unwrap();
560 let db_file = tmp.path().join("state.db");
561 let db_file = db_file.to_str().unwrap();
562
563 let handle = db::Handle::open(db_file).await.unwrap();
564 let store = StateStore::new(handle);
565
566 let container_id = Uuid::new_v4();
567 let deployment_id = Uuid::new_v4();
568 let deployment = CreateDeployment {
569 id: ReqUuid(deployment_id),
570 containers: VecReqUuid(vec![ReqUuid(container_id)]),
571 };
572 store.create_deployment(deployment).await.unwrap();
573
574 store
575 .update_deployment_status(deployment_id, DeploymentStatus::Stopped)
576 .await
577 .unwrap();
578
579 let deployment = find_deployment(&store, deployment_id).await.unwrap();
580 let exp = Deployment {
581 id: SqlUuid::new(deployment_id),
582 status: DeploymentStatus::Stopped,
583 };
584 assert_eq!(deployment, exp);
585 }
586
587 #[tokio::test]
588 async fn find_complete_deployment() {
589 let tmp = TempDir::with_prefix("create_full_deployment").unwrap();
590 let db_file = tmp.path().join("state.db");
591 let db_file = db_file.to_str().unwrap();
592
593 let handle = db::Handle::open(db_file).await.unwrap();
594 let store = StateStore::new(handle);
595
596 let deployment_id = Uuid::new_v4();
597
598 let image_id = Uuid::new_v4();
599 let image = CreateImage {
600 id: ReqUuid(image_id),
601 deployment_id: ReqUuid(deployment_id),
602 reference: "postgres:15".to_string(),
603 registry_auth: String::new(),
604 };
605 store.create_image(image).await.unwrap();
606
607 let volume_id = ReqUuid(Uuid::new_v4());
608 let volume = CreateVolume {
609 id: volume_id,
610 deployment_id: ReqUuid(deployment_id),
611 driver: "local".to_string(),
612 options: ["device=tmpfs", "o=size=100m,uid=1000", "type=tmpfs"]
613 .map(str::to_string)
614 .to_vec(),
615 };
616 store.create_volume(volume).await.unwrap();
617
618 let network_id = ReqUuid(Uuid::new_v4());
619 let network = CreateNetwork {
620 id: network_id,
621 deployment_id: ReqUuid(deployment_id),
622 driver: "bridge".to_string(),
623 internal: true,
624 enable_ipv6: false,
625 options: vec!["isolate=true".to_string()],
626 };
627 store.create_network(network).await.unwrap();
628
629 let device_mapping_id = ReqUuid(Uuid::new_v4());
630 let device_mapping = CreateDeviceMapping {
631 id: device_mapping_id,
632 deployment_id: ReqUuid(deployment_id),
633 path_on_host: "/dev/tty12".to_string(),
634 path_in_container: "dev/tty12".to_string(),
635 c_group_permissions: OptString::from("msv".to_string()),
636 };
637 store.create_device_mapping(device_mapping).await.unwrap();
638
639 let container_id = Uuid::new_v4();
640 let container = CreateContainer {
641 id: ReqUuid(container_id),
642 deployment_id: ReqUuid(deployment_id),
643 image_id: ReqUuid(image_id),
644 network_ids: VecReqUuid(vec![network_id]),
645 volume_ids: VecReqUuid(vec![volume_id]),
646 device_mapping_ids: VecReqUuid(vec![device_mapping_id]),
647 hostname: "database".to_string(),
648 restart_policy: "unless-stopped".to_string(),
649 env: ["POSTGRES_USER=user", "POSTGRES_PASSWORD=password"]
650 .map(str::to_string)
651 .to_vec(),
652 binds: vec!["/var/lib/postgres".to_string()],
653 network_mode: "bridge".to_string(),
654 port_bindings: vec!["5432:5432".to_string()],
655 extra_hosts: vec!["host.docker.internal:host-gateway".to_string()],
656 cap_add: vec!["CAP_CHOWN".to_string()],
657 cap_drop: vec!["CAP_KILL".to_string()],
658 cpu_period: 1000,
659 cpu_quota: 100,
660 cpu_realtime_period: 1000,
661 cpu_realtime_runtime: 100,
662 memory: 4096,
663 memory_reservation: 1024,
664 memory_swap: 8192,
665 memory_swappiness: 50,
666 volume_driver: "local".to_string().into(),
667 storage_opt: vec!["size=1024k".to_string()],
668 read_only_rootfs: true,
669 tmpfs: vec!["/run=rw,noexec,nosuid,size=65536k".to_string()],
670 privileged: false,
671 };
672 store.create_container(Box::new(container)).await.unwrap();
673
674 let deployment_id = Uuid::new_v4();
675 let deployment = CreateDeployment {
676 id: ReqUuid(deployment_id),
677 containers: VecReqUuid(vec![ReqUuid(container_id)]),
678 };
679 store.create_deployment(deployment).await.unwrap();
680
681 let deployment = store
682 .find_complete_deployment(deployment_id)
683 .await
684 .unwrap()
685 .unwrap();
686 let exp = DeploymentResource {
687 containers: HashSet::from_iter([container_id]),
688 images: HashSet::from_iter([image_id]),
689 volumes: HashSet::from_iter([volume_id.0]),
690 networks: HashSet::from_iter([network_id.0]),
691 device_mapping: HashSet::from_iter([device_mapping_id.0]),
692 };
693
694 assert_eq!(deployment, exp);
695 }
696
697 #[tokio::test]
698 async fn shared_resources_delete() {
699 let tmp = TempDir::with_prefix("create_full_deployment").unwrap();
700 let db_file = tmp.path().join("state.db");
701 let db_file = db_file.to_str().unwrap();
702
703 let handle = db::Handle::open(db_file).await.unwrap();
704 let store = StateStore::new(handle);
705
706 let deployment_id_1 = Uuid::new_v4();
707
708 let image_id = Uuid::new_v4();
709 let image = CreateImage {
710 id: ReqUuid(image_id),
711 deployment_id: ReqUuid(deployment_id_1),
712 reference: "postgres:15".to_string(),
713 registry_auth: String::new(),
714 };
715 store.create_image(image).await.unwrap();
716
717 let volume_id = ReqUuid(Uuid::new_v4());
718 let volume = CreateVolume {
719 id: volume_id,
720 deployment_id: ReqUuid(deployment_id_1),
721 driver: "local".to_string(),
722 options: ["device=tmpfs", "o=size=100m,uid=1000", "type=tmpfs"]
723 .map(str::to_string)
724 .to_vec(),
725 };
726 store.create_volume(volume).await.unwrap();
727
728 let network_id = ReqUuid(Uuid::new_v4());
729 let network = CreateNetwork {
730 id: network_id,
731 deployment_id: ReqUuid(deployment_id_1),
732 driver: "bridge".to_string(),
733 internal: true,
734 enable_ipv6: false,
735 options: vec!["isolate=true".to_string()],
736 };
737 store.create_network(network).await.unwrap();
738
739 let device_mapping_id = ReqUuid(Uuid::new_v4());
740 let device_mapping = CreateDeviceMapping {
741 id: device_mapping_id,
742 deployment_id: ReqUuid(deployment_id_1),
743 path_on_host: "/dev/tty12".to_string(),
744 path_in_container: "dev/tty12".to_string(),
745 c_group_permissions: OptString::from("msv".to_string()),
746 };
747 store.create_device_mapping(device_mapping).await.unwrap();
748
749 let container_id_1 = Uuid::new_v4();
750 let container_1 = CreateContainer {
751 id: ReqUuid(container_id_1),
752 deployment_id: ReqUuid(deployment_id_1),
753 image_id: ReqUuid(image_id),
754 network_ids: VecReqUuid(vec![network_id]),
755 volume_ids: VecReqUuid(vec![volume_id]),
756 device_mapping_ids: VecReqUuid(vec![device_mapping_id]),
757 hostname: "database".to_string(),
758 restart_policy: "unless-stopped".to_string(),
759 env: ["POSTGRES_USER=user", "POSTGRES_PASSWORD=password"]
760 .map(str::to_string)
761 .to_vec(),
762 binds: vec!["/var/lib/postgres".to_string()],
763 network_mode: "bridge".to_string(),
764 port_bindings: vec!["5432:5432".to_string()],
765 extra_hosts: vec!["host.docker.internal:host-gateway".to_string()],
766 cap_add: vec!["CAP_CHOWN".to_string()],
767 cap_drop: vec!["CAP_KILL".to_string()],
768 cpu_period: 1000,
769 cpu_quota: 100,
770 cpu_realtime_period: 1000,
771 cpu_realtime_runtime: 100,
772 memory: 4096,
773 memory_reservation: 1024,
774 memory_swap: 8192,
775 memory_swappiness: 50,
776 volume_driver: "local".to_string().into(),
777 storage_opt: vec!["size=1024k".to_string()],
778 read_only_rootfs: true,
779 tmpfs: vec!["/run=rw,noexec,nosuid,size=65536k".to_string()],
780 privileged: false,
781 };
782 store.create_container(Box::new(container_1)).await.unwrap();
783
784 let deployment_1 = CreateDeployment {
785 id: ReqUuid(deployment_id_1),
786 containers: VecReqUuid(vec![ReqUuid(container_id_1)]),
787 };
788 store.create_deployment(deployment_1).await.unwrap();
789
790 let deployment_id_2 = Uuid::new_v4();
791 let container_id_2 = Uuid::new_v4();
792 let container_2 = CreateContainer {
793 id: ReqUuid(container_id_2),
794 deployment_id: ReqUuid(deployment_id_2),
795 image_id: ReqUuid(image_id),
796 network_ids: VecReqUuid(vec![network_id]),
797 volume_ids: VecReqUuid(vec![volume_id]),
798 device_mapping_ids: VecReqUuid(vec![device_mapping_id]),
799 hostname: "database".to_string(),
800 restart_policy: "unless-stopped".to_string(),
801 env: ["POSTGRES_USER=user", "POSTGRES_PASSWORD=password"]
802 .map(str::to_string)
803 .to_vec(),
804 binds: vec!["/var/lib/postgres".to_string()],
805 network_mode: "bridge".to_string(),
806 port_bindings: vec!["5432:5432".to_string()],
807 extra_hosts: vec!["host.docker.internal:host-gateway".to_string()],
808 cap_add: vec!["CAP_CHOWN".to_string()],
809 cap_drop: vec!["CAP_KILL".to_string()],
810 cpu_period: 1000,
811 cpu_quota: 100,
812 cpu_realtime_period: 1000,
813 cpu_realtime_runtime: 100,
814 memory: 4096,
815 memory_reservation: 1024,
816 memory_swap: 8192,
817 memory_swappiness: 50,
818 volume_driver: "local".to_string().into(),
819 storage_opt: vec!["size=1024k".to_string()],
820 read_only_rootfs: true,
821 tmpfs: vec!["/run=rw,noexec,nosuid,size=65536k".to_string()],
822 privileged: false,
823 };
824 store.create_container(Box::new(container_2)).await.unwrap();
825
826 let deployment_2 = CreateDeployment {
827 id: ReqUuid(deployment_id_2),
828 containers: VecReqUuid(vec![ReqUuid(container_id_2)]),
829 };
830 store.create_deployment(deployment_2).await.unwrap();
831
832 let res = store
833 .find_deployment_for_delete(deployment_id_1)
834 .await
835 .unwrap()
836 .unwrap();
837
838 let exp = DeploymentResource {
839 containers: HashSet::from_iter([container_id_1]),
840 images: HashSet::new(),
841 volumes: HashSet::new(),
842 networks: HashSet::new(),
843 device_mapping: HashSet::new(),
844 };
845
846 assert_eq!(res, exp);
847 }
848}