-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7 from Apokleos/add-public-methods
Add public methods
- Loading branch information
Showing
6 changed files
with
271 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
} |