Skip to content

Commit

Permalink
Merge pull request #7 from Apokleos/add-public-methods
Browse files Browse the repository at this point in the history
Add public methods
  • Loading branch information
zvonkok authored May 24, 2024
2 parents 57eee5a + 489617b commit c268f08
Show file tree
Hide file tree
Showing 6 changed files with 271 additions and 6 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,6 @@ notify = "4.0.15"
serde = "1.0.131"
serde_derive = "1.0.131"
libc = "0.2.112"

[dev-dependencies]
tempfile = "3.1.0"
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use cache::CacheOption;

pub mod specs;
pub mod annotations;
pub mod cache;
pub mod device;
pub mod parser;
pub mod registry;
pub mod schema;
pub mod spec;
pub mod specs;
pub mod utils;
pub mod validations;

//pub mod watch;
use crate::registry::RegistryCache;
use crate::registry::RegistrySpecDB;
Expand Down
4 changes: 2 additions & 2 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ pub(crate) fn parse_qualifier(kind: &str) -> (&str, &str) {
// - underscore, dash, and dot ('_', '-', and '.')
pub(crate) fn validate_vendor_name(vendor: &str) -> Result<()> {
if let Err(e) = validate_vendor_or_class_name(vendor) {
return Err(anyhow!("invalid vendor. {}", e))
return Err(anyhow!("invalid vendor. {}", e));
}

Ok(())
Expand All @@ -118,7 +118,7 @@ pub(crate) fn validate_vendor_name(vendor: &str) -> Result<()> {
// - digits ('0'-'9')
// - underscore, dash, and dot ('_', '-', and '.')
pub(crate) fn validate_class_name(class: &str) -> Result<()> {
if let Err(e) = validate_vendor_or_class_name(class) {
if let Err(e) = validate_vendor_or_class_name(class) {
return Err(anyhow!("invalid class. {}", e));
}

Expand Down
6 changes: 3 additions & 3 deletions src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ use crate::spec;
use anyhow::{Error, Result};
use once_cell::sync::OnceCell;

use oci_spec::runtime as oci;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use oci_spec::runtime as oci;

// Registry keeps a cache of all CDI Specs installed or generated on
// the host. Registry is the primary interface clients should use to
Expand Down Expand Up @@ -170,10 +170,10 @@ pub struct Registry {
pub fn get_registry(options: Vec<Box<dyn cache::CacheOption>>) -> Option<Registry> {
let mut registry: OnceCell<Registry> = OnceCell::new();
registry.get_or_init(|| Registry {
cache: cache::Cache::new()
cache: cache::Cache::new(),
});
registry.get_mut().unwrap().configure(options);
let _ = registry.get_mut().unwrap().refresh();

registry.take()
}
}
148 changes: 148 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use std::{
ffi::OsStr,
fs::rename,
io::{Error, ErrorKind},
path::Path,
};

use anyhow::Result;

pub fn merge<T>(v1: &mut Option<Vec<T>>, v2: &Option<Vec<T>>) -> Option<Vec<T>>
where
T: Clone,
{
let mut result = v1.clone().map(|mut vec| {
if let Some(ref other) = v2 {
vec.extend(other.iter().cloned());
}
vec
});

if result.is_none() {
result = v2.clone();
}

result
}

// rename src to dst, both relative to the directory dir. If dst already exists
// refuse renaming with an error unless overwrite is explicitly asked for.
pub fn rename_in<P: AsRef<Path>, Q: AsRef<Path>>(
dir: P,
src: Q,
dst: Q,
overwrite: bool,
) -> Result<()> {
let src_path = dir.as_ref().join(src);
let dst_path = dir.as_ref().join(dst);

if !overwrite && dst_path.exists() {
return Err(Error::new(ErrorKind::AlreadyExists, "destination already exists").into());
}

rename(src_path, &dst_path)?;

Ok(())
}

pub fn is_cdi_spec(path: &Path) -> bool {
path.extension()
.and_then(OsStr::to_str)
.map(|ext| ext.eq_ignore_ascii_case("json") || ext.eq_ignore_ascii_case("yaml"))
.unwrap_or(false)
}

#[cfg(test)]
mod tests {
use super::*;
use std::fs::{self, File};
use std::io::Write;
use std::path::Path;
use tempfile::TempDir;

#[test]
fn test_merge_none_none() {
let mut v1: Option<Vec<i32>> = None;
let v2: Option<Vec<i32>> = None;

let result = merge(&mut v1, &v2);
assert!(result.is_none());
}

#[test]
fn test_merge_some_none() {
let mut v1 = Some(vec![1, 2, 3]);
let v2: Option<Vec<i32>> = None;

let result = merge(&mut v1, &v2);
assert_eq!(result, Some(vec![1, 2, 3]));
}

#[test]
fn test_merge_none_some() {
let mut v1: Option<Vec<i32>> = None;
let v2 = Some(vec![4, 5, 6]);

let result = merge(&mut v1, &v2);
assert_eq!(result, Some(vec![4, 5, 6]));
}

#[test]
fn test_merge_some_some() {
let mut v1 = Some(vec![1, 2, 3]);
let v2 = Some(vec![4, 5, 6]);

let result = merge(&mut v1, &v2);
assert_eq!(result, Some(vec![1, 2, 3, 4, 5, 6]));
}

#[test]
fn test_rename_in_success() {
let tmp_dir = TempDir::new().unwrap();
let dir_path = tmp_dir.path().to_path_buf();
let mut src_file = File::create(dir_path.join("src.txt")).unwrap();
let dst_file = dir_path.join("dst.txt");

let _ = src_file.write_all(b"Hello, CDI-rs!");

rename_in(&dir_path, "src.txt", "dst.txt", false).unwrap();
assert!(dst_file.exists());
assert!(!Path::new(&dir_path).join("src.txt").exists());
}

#[test]
fn test_rename_in_overwrite() {
let tmp_dir = TempDir::new().unwrap();
let dir_path = tmp_dir.path().to_path_buf();

let mut src_file = File::create(dir_path.join("src.txt")).unwrap();
let mut dst_file = File::create(dir_path.join("dst.txt")).unwrap();

let _ = src_file.write_all(b"Hello, CDI-rs!");
let _ = dst_file.write_all(b"Goodbye, CDI-rs!");

rename_in(&dir_path, "src.txt", "dst.txt", true).unwrap();
assert_eq!(
fs::read_to_string(dir_path.join("dst.txt")).unwrap(),
"Hello, CDI-rs!"
);
assert!(!Path::new(&dir_path).join("src.txt").exists());
}

#[test]
fn test_rename_in_no_overwrite() {
let tmp_dir = TempDir::new().unwrap();
let dir_path = tmp_dir.path().to_path_buf();

let mut src_file = File::create(dir_path.join("src.txt")).unwrap();
let mut dst_file = File::create(dir_path.join("dst.txt")).unwrap();

let _ = src_file.write_all(b"Hello, CDI-rs!");
let _ = dst_file.write_all(b"Goodbye, CDI-rs!");

let result = rename_in(&dir_path, "src.txt", "dst.txt", false);
assert!(result.is_err());
assert!(Path::new(&dir_path).join("src.txt").exists());
assert!(Path::new(&dir_path).join("dst.txt").exists());
}
}
111 changes: 111 additions & 0 deletions src/validations.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use std::collections::HashMap;

use anyhow::Result;

use crate::parser::parse_qualified_name;

const TOTAL_ANNOTATION_SIZE_LIMIT: usize = 256 * 1024; // 256 kB

#[allow(dead_code)]
pub fn validate_annotations(annotations: &HashMap<String, String>) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
let total_size: usize = annotations.iter().map(|(k, v)| k.len() + v.len()).sum();

if total_size > TOTAL_ANNOTATION_SIZE_LIMIT {
errors.push(format!(
"annotations size {} is larger than limit {}",
total_size, TOTAL_ANNOTATION_SIZE_LIMIT
));
}

for (key, value) in annotations {
if let Err(msg) = parse_qualified_name(value) {
errors.push(format!("{}:{} is invalid: {}", key, value, msg));
}
}

if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}

#[allow(dead_code)]
pub fn validate_spec_annotations(
name: &str,
annotations: &HashMap<String, String>,
) -> Result<(), Vec<String>> {
let path = if name.is_empty() {
"annotations".to_string()
} else {
format!("{}.annotations", name)
};

validate_annotations(annotations).map_err(|mut errors| {
errors.iter_mut().for_each(|error| {
error.insert_str(0, &format!("{}: ", path));
});
errors
})
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_validate_annotations() {
let mut annotations = HashMap::new();
annotations.insert(
"cdi.k8s.io/vfio17".to_string(),
"nvidia.com/gpu=0".to_string(),
);
annotations.insert(
"cdi.k8s.io/vfio18".to_string(),
"nvidia.com/gpu=1".to_string(),
);
annotations.insert(
"cdi.k8s.io/vfio19".to_string(),
"nvidia.com/gpu=all".to_string(),
);
assert!(validate_annotations(&annotations).is_ok());

let mut large_annotations = HashMap::new();
let long_value = "CDI".repeat(TOTAL_ANNOTATION_SIZE_LIMIT + 1);
large_annotations.insert("CDIKEY".to_string(), long_value);
assert!(validate_annotations(&large_annotations).is_err());

let mut invalid_annotations = HashMap::new();
invalid_annotations.insert("invalid_CDIKEY".to_string(), "invalied_CDIVAL".to_string());
assert!(validate_annotations(&invalid_annotations).is_err());
}

#[test]
fn test_validate_spec_annotations() {
let mut annotations = HashMap::new();
annotations.insert(
"cdi.k8s.io/vfio17".to_string(),
"nvidia.com/gpu=0".to_string(),
);
annotations.insert(
"cdi.k8s.io/vfio18".to_string(),
"nvidia.com/gpu=1".to_string(),
);
annotations.insert(
"cdi.k8s.io/vfio19".to_string(),
"nvidia.com/gpu=all".to_string(),
);
assert!(validate_spec_annotations("", &annotations).is_ok());
assert!(validate_spec_annotations("CDITEST", &annotations).is_ok());

let mut large_annotations = HashMap::new();
let long_value = "CDI".repeat(TOTAL_ANNOTATION_SIZE_LIMIT + 1);
large_annotations.insert("CDIKEY".to_string(), long_value);
assert!(validate_spec_annotations("", &large_annotations).is_err());

let mut invalid_annotations = HashMap::new();
invalid_annotations.insert("invalid_CDIKEY".to_string(), "invalied_CDIVAL".to_string());
assert!(validate_spec_annotations("CDITEST", &invalid_annotations).is_err());
}
}

0 comments on commit c268f08

Please sign in to comment.