diff --git a/src/runtime-rs/crates/resource/src/network/dan.rs b/src/runtime-rs/crates/resource/src/network/dan.rs index e17a557886..f739fb7ff8 100644 --- a/src/runtime-rs/crates/resource/src/network/dan.rs +++ b/src/runtime-rs/crates/resource/src/network/dan.rs @@ -68,8 +68,8 @@ impl DanInner { let json_str = fs::read_to_string(&config.dan_conf_path) .await .context("Read DAN config from file")?; - let config: DanConfig = serde_json::from_str(&json_str).context("Invalid DAN config")?; - info!(sl!(), "Dan config is loaded = {:?}", config); + let dan_config: DanConfig = serde_json::from_str(&json_str).context("Invalid DAN config")?; + info!(sl!(), "Dan config is loaded = {:?}", dan_config); let (connection, handle, _) = rtnetlink::new_connection().context("New connection")?; let thread_handler = tokio::spawn(connection); @@ -77,43 +77,45 @@ impl DanInner { thread_handler.abort(); }); - let mut entity_list = Vec::with_capacity(config.devices.len()); - for (idx, device) in config.devices.iter().enumerate() { + let mut entity_list = Vec::with_capacity(dan_config.devices.len()); + for (idx, device) in dan_config.devices.iter().enumerate() { let name = format!("eth{idx}"); + // The `network_queues` is a queue *pair* count. + // Keep `queue_num` as a pair count and the hypervisor backend converts pairs into the actual virtqueue count. + // A JSON-provided non-zero `queue_num` (also a pair count) with a higher priority always wins. + let (qnum, qsize) = device + .device + .get_effective_queues(config.network_queues); let endpoint: Arc = match &device.device { - Device::VhostUser { - path, - queue_num, - queue_size, - } => Arc::new( - VhostUserEndpoint::new( - dev_mgr, - &name, - &device.guest_mac, - path, - *queue_num, - *queue_size, + Device::VhostUser { path, .. } => { + Arc::new( + VhostUserEndpoint::new( + dev_mgr, + &name, + &device.guest_mac, + path, + qnum, + qsize, + ) + .await + .with_context(|| format!("create a vhost user endpoint, path: {path}"))?, ) - .await - .with_context(|| format!("create a vhost user endpoint, path: {path}"))?, - ), - Device::HostTap { - tap_name, - queue_num, - queue_size, - } => Arc::new( - TapEndpoint::new( - &handle, - &name, - tap_name, - &device.guest_mac, - *queue_num, - *queue_size, - dev_mgr, + } + Device::HostTap { tap_name, .. } => { + Arc::new( + TapEndpoint::new( + &handle, + &name, + tap_name, + &device.guest_mac, + qnum, + qsize, + dev_mgr, + ) + .await + .with_context(|| format!("create a {tap_name} tap endpoint"))?, ) - .await - .with_context(|| format!("create a {tap_name} tap endpoint"))?, - ), + } }; let network_info = Arc::new( @@ -129,7 +131,7 @@ impl DanInner { } Ok(Self { - netns: config.netns, + netns: dan_config.netns, entity_list, }) } @@ -211,6 +213,9 @@ impl Network for Dan { #[derive(Debug)] pub struct DanNetworkConfig { pub dan_conf_path: PathBuf, + /// Number of virtio queue pairs (each pair = 1 RX + 1 TX). + /// Derived from `network_queues` in the hypervisor TOML config. + pub network_queues: usize, } /// Directly attachable network config written by CNI plugins @@ -259,6 +264,34 @@ pub(crate) enum Device { }, } +impl Device { + /// get the effective queue-pair count and queue size. + pub(crate) fn get_effective_queues(&self, network_queues: usize) -> (usize, usize) { + // The `network_queues` comes from hypervisor configurations, and we need to ensure that it is at least 1, + // otherwise the network device will not work. + let network_queues = network_queues.max(1); + let (queue_num, queue_size) = match self { + Device::VhostUser { + queue_num, + queue_size, + .. + } + | Device::HostTap { + queue_num, + queue_size, + .. + } => (queue_num, queue_size), + }; + let qnum = if *queue_num == 0 { + network_queues + } else { + *queue_num + }; + let qsize = if *queue_size == 0 { 256 } else { *queue_size }; + (qnum, qsize) + } +} + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub(crate) struct NetworkInfo { pub(crate) interface: Interface, diff --git a/src/runtime-rs/crates/resource/src/network/endpoint/endpoints_test.rs b/src/runtime-rs/crates/resource/src/network/endpoint/endpoints_test.rs index 753b88e70d..df3b356654 100644 --- a/src/runtime-rs/crates/resource/src/network/endpoint/endpoints_test.rs +++ b/src/runtime-rs/crates/resource/src/network/endpoint/endpoints_test.rs @@ -17,6 +17,7 @@ mod tests { use tokio::sync::RwLock; use crate::network::{ + dan::Device, endpoint::{IPVlanEndpoint, MacVlanEndpoint, VlanEndpoint}, network_model::{ self, @@ -410,4 +411,55 @@ mod tests { } } } + + // DAN regression test for the minimum-of-1 requirement. + #[test] + fn test_dan_device_get_effective_queues_min_one() { + // Test `network_queues` of 0 to the minimum of 1. + let default_network_queues = 0_usize; + + // VhostUser effective pair count falls back to default (1), + // queue size falls back to 256. + let vhost = Device::VhostUser { + path: "/tmp/test".to_owned(), + queue_num: 0, + queue_size: 0, + }; + assert_eq!( + vhost.get_effective_queues(default_network_queues), + (1, 256) + ); + + // HostTap fallback default behaviour. + let tap = Device::HostTap { + tap_name: "tap0".to_owned(), + queue_num: 0, + queue_size: 0, + }; + assert_eq!(tap.get_effective_queues(default_network_queues), (1, 256)); + + // This catches a regression that would always return 1. + let vhost_default4 = Device::VhostUser { + path: "/tmp/test".to_owned(), + queue_num: 0, + queue_size: 0, + }; + assert_eq!(vhost_default4.get_effective_queues(4), (4, 256)); + + // A non-zero `queue_num` from the JSON always wins over the default, + // even when it equals the minimum — and is never reported as 0. + let vhost_nonzero = Device::VhostUser { + path: "/tmp/test".to_owned(), + queue_num: 1, + queue_size: 0, + }; + assert_eq!(vhost_nonzero.get_effective_queues(default_network_queues), (1, 256)); + + let tap_explicit = Device::HostTap { + tap_name: "tap0".to_owned(), + queue_num: 7, + queue_size: 512, + }; + assert_eq!(tap_explicit.get_effective_queues(default_network_queues), (7, 512)); + } } diff --git a/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs b/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs index 853cfd4b85..fb9b3dd710 100644 --- a/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs +++ b/src/runtime-rs/crates/runtimes/virt_container/src/sandbox.rs @@ -549,6 +549,12 @@ impl VirtSandbox { Some(ResourceConfig::Network(NetworkConfig::Dan( DanNetworkConfig { dan_conf_path: dan_path, + network_queues: self + .hypervisor + .hypervisor_config() + .await + .network_info + .network_queues as usize, }, ))) } else if let Some(netns_path) = network_env.netns.as_ref() {