Skip to main content

container_device_interface/
default_cache.rs

1use anyhow::Result;
2use std::collections::HashMap;
3use std::error::Error;
4use std::sync::{Arc, Mutex};
5
6use oci_spec::runtime::Spec;
7use once_cell::sync::OnceCell;
8
9use crate::cache::{new_cache, with_auto_refresh, Cache, CdiOption};
10
11// Process-wide: configure() and the query helpers must observe the same
12// instance, and auto-refresh state has to survive across calls.
13static DEFAULT_CACHE: OnceCell<Arc<Mutex<Cache>>> = OnceCell::new();
14
15fn get_or_create_default_cache() -> Arc<Mutex<Cache>> {
16    DEFAULT_CACHE
17        .get_or_init(|| new_cache(vec![with_auto_refresh(true)]))
18        .clone()
19}
20
21pub fn get_default_cache() -> Arc<Mutex<Cache>> {
22    get_or_create_default_cache()
23}
24
25pub fn configure(options: Vec<CdiOption>) -> Result<()> {
26    let cache = get_or_create_default_cache();
27    let mut cache = cache.lock().unwrap();
28    if options.is_empty() {
29        return Ok(());
30    }
31    cache.configure(options);
32    Ok(())
33}
34
35pub fn refresh() -> Result<(), Box<dyn Error>> {
36    let cache = get_default_cache();
37    let mut cache = cache.lock().unwrap();
38    cache.refresh()
39}
40
41pub fn inject_devices(
42    oci_spec: &mut Spec,
43    devices: Vec<String>,
44) -> Result<Vec<String>, Box<dyn Error + Send + Sync + 'static>> {
45    let cache = get_default_cache();
46    let mut cache = cache.lock().unwrap();
47    cache.inject_devices(Some(oci_spec), devices)
48}
49
50pub fn list_devices() -> Vec<String> {
51    let cache = get_default_cache();
52    let mut cache = cache.lock().unwrap();
53    cache.list_devices()
54}
55
56pub fn get_errors() -> HashMap<String, Vec<anyhow::Error>> {
57    let cache = get_default_cache();
58    let cache = cache.lock().unwrap();
59    cache.get_errors()
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use crate::spec_dirs::with_spec_dirs;
66    use std::fs;
67
68    // One test covers the whole lifecycle: the cache is a process-wide
69    // singleton, so independent tests would race each other's state.
70    #[test]
71    fn default_cache_is_a_singleton_and_configurable() {
72        let first = get_default_cache();
73        let second = get_default_cache();
74        assert!(
75            Arc::ptr_eq(&first, &second),
76            "get_default_cache must return the same instance"
77        );
78
79        let dir = tempfile::tempdir().unwrap();
80        fs::write(
81            dir.path().join("vendor.yaml"),
82            r#"cdiVersion: "0.6.0"
83kind: "vendor.com/device"
84devices:
85  - name: "gpu0"
86    containerEdits:
87      env:
88        - "VENDOR=1"
89"#,
90        )
91        .unwrap();
92
93        configure(vec![with_spec_dirs(&[dir.path().to_str().unwrap()])]).unwrap();
94        assert_eq!(
95            first.lock().unwrap().spec_dirs,
96            vec![dir.path().to_str().unwrap().to_string()],
97            "configure must act on the singleton"
98        );
99
100        // Empty configure is a no-op, not an error.
101        configure(vec![]).unwrap();
102
103        refresh().unwrap();
104        assert_eq!(list_devices(), vec!["vendor.com/device=gpu0".to_string()]);
105        assert!(get_errors().is_empty());
106
107        let mut oci_spec = Spec::default();
108        inject_devices(&mut oci_spec, vec!["vendor.com/device=gpu0".to_string()]).unwrap();
109        let env = oci_spec.process().as_ref().unwrap().env().as_ref().unwrap();
110        assert!(env.contains(&"VENDOR=1".to_string()));
111    }
112}