runtime-rs: Introduce queues for DanConfig to make it configurable

As there's no field to map the configuration's network_queues item
in the `DanConfig`, this commit introduces a network_queues to do this.

And accordingly, we also make it passed down from sandbox layer to
Dan network configurations.

To make it work well, it make it more robust for queues and queue_size
settings with checking logics. And related UT is added.

Signed-off-by: Alex Lyn <alex.lyn@antgroup.com>
This commit is contained in:
Alex Lyn
2026-07-03 10:48:40 +08:00
parent 69f35bc73e
commit 69d4064c87
3 changed files with 127 additions and 36 deletions

View File

@@ -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<dyn Endpoint> = 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,

View File

@@ -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));
}
}

View File

@@ -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() {