diff --git a/src/runtime-rs/crates/agent/src/types.rs b/src/runtime-rs/crates/agent/src/types.rs index 3ac22715be..c89fdc675f 100644 --- a/src/runtime-rs/crates/agent/src/types.rs +++ b/src/runtime-rs/crates/agent/src/types.rs @@ -42,7 +42,7 @@ pub struct StringUser { pub additional_gids: Vec, } -#[derive(PartialEq, Clone, Default)] +#[derive(PartialEq, Clone, Debug, Default)] pub struct Device { pub id: String, pub field_type: String, diff --git a/src/runtime-rs/crates/resource/src/cdi_devices/container_device.rs b/src/runtime-rs/crates/resource/src/cdi_devices/container_device.rs index 867d02f15a..0e3fb750dd 100644 --- a/src/runtime-rs/crates/resource/src/cdi_devices/container_device.rs +++ b/src/runtime-rs/crates/resource/src/cdi_devices/container_device.rs @@ -106,3 +106,370 @@ pub fn annotate_container_devices( Ok(devices_agent) } + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use crate::cdi_devices::DeviceInfo; + use agent::types::Device; + use oci_spec::runtime::SpecBuilder; + + use super::*; + + #[test] + fn test_sort_devices_by_guest_pcipath() { + let mut devices = vec![ + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0xffff".to_string(), + class_id: "0x030x".to_string(), + host_path: PathBuf::from("/dev/device3"), + }), + device: Device { + options: vec!["pci_host_path03=BB:DD03.F03".to_string()], + ..Default::default() + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0xffff".to_string(), + class_id: "0x030x".to_string(), + host_path: PathBuf::from("/dev/device1"), + }), + device: Device { + options: vec!["pci_host_path01=BB:DD01.F01".to_string()], + ..Default::default() + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0xffff".to_string(), + class_id: "0x030x".to_string(), + host_path: PathBuf::from("/dev/device2"), + }), + device: Device { + options: vec!["pci_host_path02=BB:DD02.F02".to_string()], + ..Default::default() + }, + }, + ]; + + sort_devices_by_guest_pcipath(&mut devices); + + let expected_devices_order = vec![ + "/dev/device1".to_string(), + "/dev/device2".to_string(), + "/dev/device3".to_string(), + ]; + let actual_devices_order: Vec = devices + .iter() + .map(|cd| { + cd.device_info + .as_ref() + .unwrap() + .host_path + .display() + .to_string() + }) + .collect(); + + assert_eq!(actual_devices_order, expected_devices_order); + } + + #[test] + fn test_sort_devices_with_empty_options() { + let mut devices = vec![ + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0xffff".to_string(), + class_id: "0x030x".to_string(), + host_path: PathBuf::from("/dev/device1"), + }), + device: Device { + options: vec![], // empty + ..Default::default() + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0xffff".to_string(), + class_id: "0x030x".to_string(), + host_path: PathBuf::from("/dev/device2"), + }), + device: Device { + options: vec!["pci_host_path02=BB:DD02.F02".to_string()], + ..Default::default() + }, + }, + ]; + + sort_devices_by_guest_pcipath(&mut devices); + + // As the first device has no options, ignore it. + let expected_devices_order = vec!["BB:DD02.F02".to_string()]; + + let actual_devices_order: Vec = devices + .iter() + .filter_map(|d| d.device.options.first()) + .map(|option| option.split('=').nth(1).unwrap_or("").to_string()) + .collect(); + + assert_eq!(actual_devices_order, expected_devices_order); + } + + #[test] + fn test_annotate_container_devices() { + let devices = vec![ + ContainerDevice { + device_info: None, + device: Device { + id: "test0000x".to_string(), + container_path: "/dev/xvdx".to_string(), + field_type: "virtio-blk".to_string(), + vm_path: "/dev/vdx".to_string(), + options: vec![], + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0x1002".to_string(), + class_id: "0x0302".to_string(), + host_path: PathBuf::from("/dev/device2"), + }), + device: Device { + container_path: "/dev/device2".to_string(), + options: vec!["pci_host_path02=BB:DD02.F02".to_string()], + ..Default::default() + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0x1002".to_string(), + class_id: "0x0302".to_string(), + host_path: PathBuf::from("/dev/device3"), + }), + device: Device { + container_path: "/dev/device3".to_string(), + options: vec!["pci_host_path03=BB:DD03.F03".to_string()], + ..Default::default() + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0x1002".to_string(), + class_id: "0x0302".to_string(), + host_path: PathBuf::from("/dev/device1"), + }), + device: Device { + container_path: "/dev/device1".to_string(), + options: vec!["pci_host_path01=BB:DD01.F01".to_string()], + ..Default::default() + }, + }, + ContainerDevice { + device_info: None, + device: Device { + id: "test0000yx".to_string(), + container_path: "/dev/xvdyx".to_string(), + field_type: "virtio-blk".to_string(), + vm_path: "/dev/vdyx".to_string(), + options: vec![], + }, + }, + ]; + + let annotations = HashMap::new(); + let mut spec = SpecBuilder::default() + .annotations(annotations) + .build() + .unwrap(); + + // do annotate container devices + let _devices = annotate_container_devices(&mut spec, devices); + + let expected_annotations: HashMap = vec![ + ( + "cdi.k8s.io/vfiodevice3.0".to_owned(), + "amd.com/gpu=2".to_owned(), + ), + ( + "cdi.k8s.io/vfiodevice1.0".to_owned(), + "amd.com/gpu=0".to_owned(), + ), + ( + "cdi.k8s.io/vfiodevice2.0".to_owned(), + "amd.com/gpu=1".to_owned(), + ), + ] + .into_iter() + .collect(); + + assert_eq!(Some(expected_annotations), spec.annotations().clone()); + } + + #[test] + fn test_annotate_container_multi_vendor_devices() { + let devices = vec![ + ContainerDevice { + device_info: None, + device: Device { + id: "test0000x".to_string(), + container_path: "/dev/xvdx".to_string(), + field_type: "virtio-blk".to_string(), + vm_path: "/dev/vdx".to_string(), + options: vec![], + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0x10de".to_string(), + class_id: "0x0302".to_string(), + host_path: PathBuf::from("/dev/device2"), + }), + device: Device { + container_path: "/dev/device2".to_string(), + options: vec!["pci_host_path02=BB:DD02.F02".to_string()], + ..Default::default() + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0x10de".to_string(), + class_id: "0x0302".to_string(), + host_path: PathBuf::from("/dev/device3"), + }), + device: Device { + container_path: "/dev/device3".to_string(), + options: vec!["pci_host_path03=BB:DD03.F03".to_string()], + ..Default::default() + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0x8086".to_string(), + class_id: "0x0302".to_string(), + host_path: PathBuf::from("/dev/device1"), + }), + device: Device { + container_path: "/dev/device1".to_string(), + options: vec!["pci_host_path01=BB:DD01.F01".to_string()], + ..Default::default() + }, + }, + ContainerDevice { + device_info: Some(DeviceInfo { + vendor_id: "0x8086".to_string(), + class_id: "0x0302".to_string(), + host_path: PathBuf::from("/dev/device4"), + }), + device: Device { + container_path: "/dev/device4".to_string(), + options: vec!["pci_host_path04=BB:DD01.F04".to_string()], + ..Default::default() + }, + }, + ContainerDevice { + device_info: None, + device: Device { + id: "test0000yx".to_string(), + container_path: "/dev/xvdyx".to_string(), + field_type: "virtio-blk".to_string(), + vm_path: "/dev/vdyx".to_string(), + options: vec![], + }, + }, + ]; + + let annotations = HashMap::new(); + let mut spec = SpecBuilder::default() + .annotations(annotations) + .build() + .unwrap(); + + let _devices = annotate_container_devices(&mut spec, devices); + + let expected_annotations: HashMap = vec![ + ( + "cdi.k8s.io/vfiodevice1.0".to_owned(), + "intel.com/gpu=0".to_owned(), + ), + ( + "cdi.k8s.io/vfiodevice2.0".to_owned(), + "nvidia.com/gpu=0".to_owned(), + ), + ( + "cdi.k8s.io/vfiodevice3.0".to_owned(), + "nvidia.com/gpu=1".to_owned(), + ), + ( + "cdi.k8s.io/vfiodevice4.0".to_owned(), + "intel.com/gpu=1".to_owned(), + ), + ] + .into_iter() + .collect(); + + assert_eq!(Some(expected_annotations), spec.annotations().clone()); + } + + #[test] + fn test_annotate_container_without_vfio_devices() { + let devices = vec![ + ContainerDevice { + device_info: None, + device: Device { + id: "test0000x".to_string(), + container_path: "/dev/xvdx".to_string(), + field_type: "virtio-blk".to_string(), + vm_path: "/dev/vdx".to_string(), + options: vec![], + }, + }, + ContainerDevice { + device_info: None, + device: Device { + id: "test0000y".to_string(), + container_path: "/dev/yvdy".to_string(), + field_type: "virtio-blk".to_string(), + vm_path: "/dev/vdy".to_string(), + options: vec![], + }, + }, + ContainerDevice { + device_info: None, + device: Device { + id: "test0000z".to_string(), + container_path: "/dev/zvdz".to_string(), + field_type: "virtio-blk".to_string(), + vm_path: "/dev/zvdz".to_string(), + options: vec![], + }, + }, + ]; + + let annotations = HashMap::from([( + "cdi.k8s.io/vfiodeviceX".to_owned(), + "katacontainer.com/device=Y".to_owned(), + )]); + let mut spec = SpecBuilder::default() + .annotations(annotations) + .build() + .unwrap(); + + // do annotate container devices + let annotated_devices = annotate_container_devices(&mut spec, devices.clone()).unwrap(); + + let actual_devices = devices + .iter() + .map(|d| d.device.clone()) + .collect::>(); + let expected_annotations: HashMap = HashMap::from([( + "cdi.k8s.io/vfiodeviceX".to_owned(), + "katacontainer.com/device=Y".to_owned(), + )]); + + assert_eq!(Some(expected_annotations), spec.annotations().clone()); + assert_eq!(annotated_devices, actual_devices); + } +}