container_device_interface/generate/
generator.rs1use std::cmp::Ordering;
2use std::path::PathBuf;
3
4use oci_spec::runtime::{
5 Hook, LinuxDevice, LinuxDeviceCgroup, LinuxDeviceType, LinuxIntelRdt, LinuxNetDevice, Mount,
6};
7
8use super::config::Generator;
9
10impl Generator {
11 pub fn remove_device(&mut self, path: &str) {
13 self.init_config_linux();
14 if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
15 if let Some(devices) = linux.devices_mut() {
16 if let Some(index) = devices
17 .iter()
18 .position(|device| device.path() == &PathBuf::from(path))
19 {
20 devices.remove(index);
21 }
22 }
23 }
24 }
25
26 pub fn add_device(&mut self, device: LinuxDevice) {
28 self.init_config_linux();
29
30 if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
31 if let Some(devices) = linux.devices_mut() {
32 if let Some(index) = devices.iter().position(|dev| dev.path() == device.path()) {
33 devices[index] = device;
34 } else {
35 devices.push(device);
36 }
37 } else {
38 linux.set_devices(Some(vec![device]));
39 }
40 }
41 }
42
43 pub fn add_linux_resources_device(
45 &mut self,
46 allow: bool,
47 dev_type: LinuxDeviceType,
48 major: Option<i64>,
49 minor: Option<i64>,
50 access: Option<String>,
51 ) {
52 self.init_config_linux_resources_devices();
53 if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
54 if let Some(resource) = linux.resources_mut() {
55 if let Some(devices) = resource.devices_mut() {
56 let mut device = LinuxDeviceCgroup::default();
57 device.set_allow(allow);
58 device.set_typ(Some(dev_type));
59 device.set_major(major);
60 device.set_minor(minor);
61 device.set_access(access);
62
63 devices.push(device);
64 }
65 }
66 }
67 }
68
69 pub fn add_linux_net_device(
70 &mut self,
71 host_interface_name: String,
72 net_device: LinuxNetDevice,
73 ) {
74 self.init_config_linux_net_devices();
75 if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
76 if let Some(net_devices) = linux.net_devices_mut() {
77 net_devices.insert(host_interface_name, net_device);
78 }
79 }
80 }
81
82 pub fn set_linux_intel_rdt(&mut self, intel_rdt: LinuxIntelRdt) {
83 self.init_config_linux();
84 if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
85 linux.set_intel_rdt(Some(intel_rdt));
86 }
87 }
88
89 pub fn set_linux_intel_rdt_clos_id(&mut self, clos_id: String) {
91 self.init_config_linux_intel_rdt();
92 if let Some(linux) = self.config.as_mut().unwrap().linux_mut() {
93 if let Some(intel_rdt) = linux.intel_rdt_mut() {
94 intel_rdt.set_clos_id(Some(clos_id));
95 }
96 }
97 }
98
99 pub fn add_process_additional_gid(&mut self, gid: u32) {
101 self.init_config_process();
102 if let Some(process) = self.config.as_mut().unwrap().process_mut() {
103 let mut gids = process.user().additional_gids().clone().unwrap_or_default();
104 if !gids.contains(&gid) {
105 gids.push(gid);
106 }
107 process.user_mut().set_additional_gids(Some(gids));
108 }
109 }
110
111 pub fn add_multiple_process_env(&mut self, envs: &[String]) {
112 self.init_config_process();
113
114 if let Some(process) = self.config.as_mut().unwrap().process_mut() {
115 let mut env_vec: Vec<String> = process.env_mut().get_or_insert_with(Vec::new).to_vec();
116 for env in envs {
117 let split: Vec<&str> = env.splitn(2, '=').collect();
118 let key = split[0].to_string();
119 let idx = self.env_map.entry(key.clone()).or_insert(env_vec.len());
120
121 if let Some(elem) = env_vec.get_mut(*idx) {
122 elem.clone_from(env);
123 } else {
124 env_vec.push(env.clone());
125 self.env_map.insert(key, env_vec.len() - 1);
126 }
127 }
128 process.set_env(Some(env_vec));
129 }
130 }
131
132 pub fn add_prestart_hook(&mut self, hook: Hook) {
134 self.init_config_hooks();
135 if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
136 if let Some(prestart_hooks) = hooks.prestart_mut() {
137 prestart_hooks.push(hook);
138 } else {
139 hooks.set_prestart(Some(vec![hook]));
140 }
141 }
142 }
143
144 pub fn add_poststop_hook(&mut self, hook: Hook) {
146 self.init_config_hooks();
147 if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
148 if let Some(poststop_hooks) = hooks.poststop_mut() {
149 poststop_hooks.push(hook);
150 } else {
151 hooks.set_poststop(Some(vec![hook]));
152 }
153 }
154 }
155
156 pub fn add_poststart_hook(&mut self, hook: Hook) {
158 self.init_config_hooks();
159 if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
160 if let Some(poststart_hooks) = hooks.poststart_mut() {
161 poststart_hooks.push(hook);
162 } else {
163 hooks.set_poststart(Some(vec![hook]));
164 }
165 }
166 }
167
168 pub fn add_createruntime_hook(&mut self, hook: Hook) {
170 self.init_config_hooks();
171 if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
172 if let Some(create_runtime) = hooks.create_runtime_mut() {
173 create_runtime.push(hook);
174 } else {
175 hooks.set_create_runtime(Some(vec![hook]));
176 }
177 }
178 }
179
180 pub fn add_createcontainer_hook(&mut self, hook: Hook) {
182 self.init_config_hooks();
183 if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
184 if let Some(create_container) = hooks.create_container_mut() {
185 create_container.push(hook);
186 } else {
187 hooks.set_create_container(Some(vec![hook]));
188 }
189 }
190 }
191
192 pub fn add_startcontainer_hook(&mut self, hook: Hook) {
194 self.init_config_hooks();
195 if let Some(hooks) = self.config.as_mut().unwrap().hooks_mut() {
196 if let Some(start_container) = hooks.start_container_mut() {
197 start_container.push(hook);
198 } else {
199 hooks.set_start_container(Some(vec![hook]));
200 }
201 }
202 }
203
204 pub fn remove_mount(&mut self, dest: &str) {
206 if let Some(mounts) = self.config.as_mut().unwrap().mounts_mut() {
207 if let Some(index) = mounts
208 .iter()
209 .position(|m| m.destination() == &PathBuf::from(dest))
210 {
211 mounts.remove(index);
212 }
213 }
214 }
215
216 pub fn add_mount(&mut self, mount: Mount) {
218 self.init_config_mounts();
219
220 if let Some(mounts) = self.config.as_mut().unwrap().mounts_mut() {
221 mounts.push(mount);
222 }
223 }
224
225 pub fn sort_mounts(&mut self) {
227 if let Some(ref mut mounts) = self.config.as_mut().unwrap().mounts_mut() {
228 mounts.sort_by(|a, b| a.destination().cmp(b.destination()));
229 }
230 }
231
232 pub fn list_mounts(&self) -> Option<&Vec<Mount>> {
234 self.config.as_ref().and_then(|spec| spec.mounts().as_ref())
235 }
236
237 pub fn clear_mounts(&mut self) {
239 if let Some(spec) = self.config.as_mut() {
240 spec.set_mounts(None);
241 }
242 }
243}
244
245struct OrderedMounts(Vec<Mount>);
251
252#[allow(dead_code)]
253impl OrderedMounts {
254 fn new(mounts: Vec<Mount>) -> Self {
255 OrderedMounts(mounts)
256 }
257
258 fn parts(&self, i: usize) -> usize {
260 self.0[i].destination().components().count()
261 }
262}
263
264impl Ord for OrderedMounts {
265 fn cmp(&self, other: &Self) -> Ordering {
266 let self_parts = self.parts(0);
267 let other_parts = other.parts(0);
268 self_parts
269 .cmp(&other_parts)
270 .then_with(|| self.0[0].destination().cmp(other.0[0].destination()))
271 }
272}
273
274impl PartialOrd for OrderedMounts {
275 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
276 Some(self.cmp(other))
277 }
278}
279
280impl PartialEq for OrderedMounts {
281 fn eq(&self, other: &Self) -> bool {
282 self.parts(0) == other.parts(0) && self.0[0].destination() == other.0[0].destination()
283 }
284}
285
286impl Eq for OrderedMounts {}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use oci_spec::runtime::{LinuxNetDevice, Spec};
292
293 fn gen() -> Generator {
294 Generator::spec_gen(Some(Spec::default()))
295 }
296
297 #[test]
298 fn add_device_inserts_replaces_and_remove_deletes() {
299 let mut g = gen();
300 let mut dev = LinuxDevice::default();
301 dev.set_path(PathBuf::from("/dev/x"));
302 g.add_device(dev.clone());
303
304 let mut replacement = LinuxDevice::default();
306 replacement.set_path(PathBuf::from("/dev/x"));
307 replacement.set_major(7);
308 g.add_device(replacement);
309
310 let devices = |g: &Generator| {
311 g.config
312 .as_ref()
313 .unwrap()
314 .linux()
315 .as_ref()
316 .unwrap()
317 .devices()
318 .clone()
319 .unwrap_or_default()
320 };
321 assert_eq!(devices(&g).len(), 1);
322 assert_eq!(devices(&g)[0].major(), 7);
323
324 g.remove_device("/dev/x");
325 assert!(devices(&g).is_empty());
326 g.remove_device("/dev/x");
328 }
329
330 #[test]
331 fn add_linux_resources_device_records_cgroup_entry() {
332 let mut g = gen();
333 g.add_linux_resources_device(
334 true,
335 LinuxDeviceType::C,
336 Some(1),
337 Some(3),
338 Some("rwm".to_string()),
339 );
340
341 let binding = g.config.unwrap();
342 let devices = binding
343 .linux()
344 .as_ref()
345 .unwrap()
346 .resources()
347 .as_ref()
348 .unwrap()
349 .devices()
350 .as_ref()
351 .unwrap();
352 assert_eq!(devices.len(), 1);
353 assert!(devices[0].allow());
354 assert_eq!(devices[0].typ(), Some(LinuxDeviceType::C));
355 assert_eq!(devices[0].major(), Some(1));
356 assert_eq!(devices[0].minor(), Some(3));
357 assert_eq!(devices[0].access().as_deref(), Some("rwm"));
358 }
359
360 #[test]
361 fn intel_rdt_set_and_clos_id_update() {
362 let mut g = gen();
363 let mut rdt = LinuxIntelRdt::default();
364 rdt.set_clos_id(Some("initial".to_string()));
365 g.set_linux_intel_rdt(rdt);
366 g.set_linux_intel_rdt_clos_id("updated".to_string());
367
368 let binding = g.config.unwrap();
369 let rdt = binding
370 .linux()
371 .as_ref()
372 .unwrap()
373 .intel_rdt()
374 .as_ref()
375 .unwrap();
376 assert_eq!(rdt.clos_id().as_deref(), Some("updated"));
377 }
378
379 #[test]
380 fn additional_gids_deduplicate() {
381 let mut g = gen();
382 g.add_process_additional_gid(1000);
383 g.add_process_additional_gid(1000);
384 g.add_process_additional_gid(2000);
385
386 let binding = g.config.unwrap();
387 let gids = binding
388 .process()
389 .as_ref()
390 .unwrap()
391 .user()
392 .additional_gids()
393 .clone()
394 .unwrap();
395 assert_eq!(gids, vec![1000, 2000]);
396 }
397
398 #[test]
399 fn env_updates_existing_keys_and_appends_new_ones() {
400 let mut g = gen();
401 g.add_multiple_process_env(&["A=1".to_string(), "B=2".to_string()]);
402 g.add_multiple_process_env(&["A=3".to_string(), "C=4".to_string()]);
403
404 let binding = g.config.unwrap();
405 let env = binding.process().as_ref().unwrap().env().clone().unwrap();
406 assert_eq!(env.iter().filter(|e| e.starts_with("A=")).count(), 1);
408 let tail = &env[env.len() - 3..];
409 assert_eq!(tail, ["A=3", "B=2", "C=4"]);
410 }
411
412 #[test]
413 fn hooks_initialize_then_append() {
414 let mut g = gen();
415 for _ in 0..2 {
416 g.add_prestart_hook(Hook::default());
417 g.add_poststart_hook(Hook::default());
418 g.add_poststop_hook(Hook::default());
419 g.add_createruntime_hook(Hook::default());
420 g.add_createcontainer_hook(Hook::default());
421 g.add_startcontainer_hook(Hook::default());
422 }
423
424 let binding = g.config.unwrap();
425 let hooks = binding.hooks().as_ref().unwrap();
426 assert_eq!(hooks.prestart().as_ref().unwrap().len(), 2);
427 assert_eq!(hooks.poststart().as_ref().unwrap().len(), 2);
428 assert_eq!(hooks.poststop().as_ref().unwrap().len(), 2);
429 assert_eq!(hooks.create_runtime().as_ref().unwrap().len(), 2);
430 assert_eq!(hooks.create_container().as_ref().unwrap().len(), 2);
431 assert_eq!(hooks.start_container().as_ref().unwrap().len(), 2);
432 }
433
434 #[test]
435 fn mounts_add_sort_list_remove_clear() {
436 let mut g = gen();
437 let mount = |dest: &str| {
438 let mut m = Mount::default();
439 m.set_destination(PathBuf::from(dest));
440 m
441 };
442 g.add_mount(mount("/z"));
443 g.add_mount(mount("/a"));
444
445 g.sort_mounts();
446 let dests: Vec<_> = g
447 .list_mounts()
448 .unwrap()
449 .iter()
450 .map(|m| m.destination().clone())
451 .collect();
452 assert!(dests.windows(2).all(|w| w[0] <= w[1]));
453
454 g.remove_mount("/z");
455 assert!(!g
456 .list_mounts()
457 .unwrap()
458 .iter()
459 .any(|m| m.destination() == &PathBuf::from("/z")));
460
461 g.clear_mounts();
462 assert!(g.list_mounts().is_none());
463 }
464
465 #[test]
466 fn add_process_additional_gid_initializes_empty_gid_list() {
467 let mut generator = Generator::spec_gen(Some(oci_spec::runtime::Spec::default()));
468
469 generator.add_process_additional_gid(1000);
470
471 let gids = generator
472 .config
473 .as_ref()
474 .unwrap()
475 .process()
476 .as_ref()
477 .unwrap()
478 .user()
479 .additional_gids()
480 .as_ref()
481 .unwrap();
482 assert_eq!(gids, &vec![1000]);
483 }
484
485 #[test]
486 fn add_linux_net_device_initializes_map_and_sets_entry() {
487 let mut generator = Generator::spec_gen(Some(oci_spec::runtime::Spec::default()));
488 let mut net_device = LinuxNetDevice::default();
489 net_device.set_name(Some("container_eth0".to_string()));
490
491 generator.add_linux_net_device("eth0".to_string(), net_device);
492
493 let net_devices = generator
494 .config
495 .as_ref()
496 .unwrap()
497 .linux()
498 .as_ref()
499 .unwrap()
500 .net_devices()
501 .as_ref()
502 .unwrap();
503 assert_eq!(
504 net_devices.get("eth0").unwrap().name().as_ref().unwrap(),
505 "container_eth0"
506 );
507 }
508
509 fn empty_gen() -> Generator {
512 let mut bare = Spec::default();
513 bare.set_process(None)
514 .set_linux(None)
515 .set_hooks(None)
516 .set_mounts(None);
517 Generator::spec_gen(Some(bare))
518 }
519
520 #[test]
521 fn helpers_create_missing_layers_on_empty_specs() {
522 let mut g = empty_gen();
523 let mut dev = LinuxDevice::default();
524 dev.set_path(PathBuf::from("/dev/x"));
525 g.remove_device("/dev/x"); g.add_device(dev);
527 g.add_linux_resources_device(true, LinuxDeviceType::C, Some(1), Some(3), None);
528 let mut net_device = LinuxNetDevice::default();
529 net_device.set_name(Some("c".to_string()));
530 g.add_linux_net_device("eth0".to_string(), net_device);
531 g.set_linux_intel_rdt_clos_id("clos".to_string());
532 g.add_process_additional_gid(9);
533 g.add_multiple_process_env(&["K=V".to_string()]);
534 g.add_prestart_hook(Hook::default());
535 g.add_poststart_hook(Hook::default());
536 g.add_poststop_hook(Hook::default());
537 g.add_createruntime_hook(Hook::default());
538 g.add_createcontainer_hook(Hook::default());
539 g.add_startcontainer_hook(Hook::default());
540 g.add_mount(Mount::default());
541 g.sort_mounts();
542 g.clear_mounts();
543 assert!(g.list_mounts().is_none());
544
545 let binding = g.config.unwrap();
546 assert_eq!(
547 binding
548 .linux()
549 .as_ref()
550 .unwrap()
551 .devices()
552 .as_ref()
553 .unwrap()
554 .len(),
555 1
556 );
557 assert!(binding.hooks().is_some());
558 }
559
560 #[test]
561 fn ordered_mounts_compare_by_depth_then_path() {
562 let mount = |dest: &str| {
563 let mut m = Mount::default();
564 m.set_destination(PathBuf::from(dest));
565 m
566 };
567 let shallow = OrderedMounts::new(vec![mount("/a")]);
568 let deep = OrderedMounts::new(vec![mount("/a/b")]);
569 let deep_b = OrderedMounts::new(vec![mount("/a/c")]);
570
571 assert!(shallow < deep);
572 assert!(deep < deep_b);
573 assert_eq!(deep.partial_cmp(&deep_b), Some(Ordering::Less));
574 assert_eq!(shallow.parts(0), 2);
575 assert!(shallow == OrderedMounts::new(vec![mount("/a")]));
576 }
577}