container_device_interface/
device.rs1use std::collections::BTreeMap;
2
3use anyhow::{anyhow, Context, Result};
4use oci_spec::runtime as oci;
5
6use crate::{
7 container_edits::{ContainerEdits, Validate},
8 internal::validation::validate::validate_spec_annotations,
9 parser::{qualified_name, validate_device_name},
10 spec::Spec,
11 specs::config::Device as CDIDevice,
12};
13
14#[derive(Clone, Debug, Eq, PartialEq, Hash)]
16pub struct Device {
17 pub cdi_device: CDIDevice,
18 cdi_spec: Spec,
19}
20
21impl Default for Device {
22 fn default() -> Self {
23 Self::new()
24 }
25}
26
27pub fn new_device(spec: &Spec, device: &CDIDevice) -> Result<Device> {
29 let device = Device {
30 cdi_device: device.clone(),
31 cdi_spec: spec.clone(),
32 };
33
34 if let Err(e) = device.validate() {
35 return Err(anyhow!(
36 "device validated failed with error: {:?}",
37 e.to_string()
38 ));
39 }
40
41 Ok(device)
42}
43
44impl Device {
45 pub fn new() -> Self {
47 Self {
48 cdi_device: Default::default(),
49 cdi_spec: Default::default(),
50 }
51 }
52
53 pub fn get_spec(&self) -> Spec {
55 self.cdi_spec.clone()
56 }
57
58 pub fn get_qualified_name(&self) -> String {
60 qualified_name(
61 &self.cdi_spec.get_vendor(),
62 &self.cdi_spec.get_class(),
63 &self.cdi_device.name,
64 )
65 }
66
67 pub fn edits(&self) -> ContainerEdits {
69 ContainerEdits {
70 container_edits: self.cdi_device.container_edits.clone(),
71 }
72 }
73 pub fn apply_edits(&mut self, oci_spec: &mut oci::Spec) -> Result<()> {
75 self.edits().apply(oci_spec)?;
76 Ok(())
77 }
78
79 pub fn validate(&self) -> Result<()> {
81 validate_device_name(&self.cdi_device.name).context("validate device name failed")?;
82 let name = self.get_qualified_name();
83
84 let annotations: &BTreeMap<String, String> =
85 &<BTreeMap<String, String> as Clone>::clone(&self.cdi_device.annotations)
86 .into_iter()
87 .collect();
88 if let Err(e) = validate_spec_annotations(&name, annotations) {
89 return Err(anyhow!(
90 "validate spec annotations failed with error: {:?}",
91 e
92 ));
93 }
94
95 let edits = self.edits();
96 if edits.is_empty() {
97 return Err(anyhow!("invalid device, empty device edits"));
98 }
99 edits
100 .validate()
101 .context(format!("invalid device {:?} ", self.cdi_device.name))?;
102
103 Ok(())
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use crate::{
111 spec::new_spec,
112 specs::config::{
113 ContainerEdits as CDIContainerEdits, Device as CDIDeviceSpec, Spec as CDISpec,
114 },
115 };
116 use std::path::PathBuf;
117
118 fn spec_with(device: CDIDeviceSpec) -> Spec {
119 let raw = CDISpec {
120 version: "0.6.0".to_string(),
121 kind: "vendor.com/class".to_string(),
122 devices: vec![device.clone()],
123 ..Default::default()
124 };
125 new_spec(&raw, &PathBuf::from("/tmp/spec.yaml"), 0).unwrap()
126 }
127
128 fn cdi_device(name: &str) -> CDIDeviceSpec {
129 CDIDeviceSpec {
130 name: name.to_string(),
131 container_edits: CDIContainerEdits {
132 env: Some(vec!["X=1".to_string()]),
133 ..Default::default()
134 },
135 ..Default::default()
136 }
137 }
138
139 #[test]
140 fn new_device_builds_and_applies_edits() {
141 let raw = cdi_device("gpu0");
142 let spec = spec_with(raw.clone());
143 let mut device = new_device(&spec, &raw).unwrap();
144
145 assert_eq!(device.get_qualified_name(), "vendor.com/class=gpu0");
146 assert_eq!(device.get_spec().get_vendor(), "vendor.com");
147
148 let mut oci_spec = oci::Spec::default();
149 device.apply_edits(&mut oci_spec).unwrap();
150 let env = oci_spec.process().as_ref().unwrap().env().as_ref().unwrap();
151 assert!(env.contains(&"X=1".to_string()));
152 }
153
154 #[test]
155 fn new_device_rejects_invalid_names_and_empty_edits() {
156 let bad_name = cdi_device("not a name");
157 let spec = spec_with(cdi_device("gpu0"));
158 let err = new_device(&spec, &bad_name).unwrap_err();
159 assert!(err.to_string().contains("validate device name failed"));
160
161 let empty_edits = CDIDeviceSpec {
162 name: "gpu1".to_string(),
163 ..Default::default()
164 };
165 let err = new_device(&spec, &empty_edits).unwrap_err();
166 assert!(err.to_string().contains("empty device edits"));
167 }
168
169 #[test]
170 fn default_device_is_constructible() {
171 let device = Device::default();
172 assert_eq!(device.cdi_device.name, "");
173 }
174}