1
0
mirror of https://github.com/kata-containers/kata-containers.git synced 2025-05-10 01:16:42 +00:00

agent: Handle virtio-net-ccw devices separately

On s390x, a virtio-net device will use the CCW bus instead of PCI,
which impacts how its uevent should be handled. Take the respective
path accordingly.

Signed-off-by: Jakob Naucke <jakob.naucke@ibm.com>
This commit is contained in:
Jakob Naucke 2025-01-30 16:59:12 +01:00
parent a084b99324
commit c146980bcd
No known key found for this signature in database
GPG Key ID: D34F14412FB929FC
2 changed files with 128 additions and 27 deletions

View File

@ -3,35 +3,23 @@
//
// SPDX-License-Identifier: Apache-2.0
//
use crate::device::pcipath_to_sysfs;
#[cfg(target_arch = "s390x")]
use crate::ccw;
use crate::linux_abi::*;
use crate::pci;
use crate::sandbox::Sandbox;
use crate::uevent::{wait_for_uevent, Uevent, UeventMatcher};
#[cfg(not(target_arch = "s390x"))]
use crate::{device::pcipath_to_sysfs, pci};
use anyhow::{anyhow, Result};
use regex::Regex;
use std::fs;
use std::sync::Arc;
use tokio::sync::Mutex;
pub async fn wait_for_net_interface(
sandbox: &Arc<Mutex<Sandbox>>,
pcipath: &pci::Path,
) -> Result<()> {
let root_bus_sysfs = format!("{}{}", SYSFS_DIR, create_pci_root_bus_path());
let sysfs_rel_path = pcipath_to_sysfs(&root_bus_sysfs, pcipath)?;
let matcher = NetPciMatcher::new(&sysfs_rel_path);
fn check_existing(re: Regex) -> Result<bool> {
// Check if the interface is already added in case network is cold-plugged
// or the uevent loop is started before network is added.
// We check for the pci deive in the sysfs directory for network devices.
let pattern = format!(
r"[./]+{}/[a-z0-9/]*net/[a-z0-9/]*",
matcher.devpath.as_str()
);
let re = Regex::new(&pattern).expect("BUG: Failed to compile regex for NetPciMatcher");
// We check for the device in the sysfs directory for network devices.
for entry in fs::read_dir(SYSFS_NET_PATH)? {
let entry = entry?;
let path = entry.path();
@ -41,19 +29,41 @@ pub async fn wait_for_net_interface(
.ok_or_else(|| anyhow!("Expected symlink in dir {}", SYSFS_NET_PATH))?;
if re.is_match(target_path_str) {
return Ok(());
return Ok(true);
}
}
Ok(false)
}
#[cfg(not(target_arch = "s390x"))]
pub async fn wait_for_pci_net_interface(
sandbox: &Arc<Mutex<Sandbox>>,
pcipath: &pci::Path,
) -> Result<()> {
let root_bus_sysfs = format!("{}{}", SYSFS_DIR, create_pci_root_bus_path());
let sysfs_rel_path = pcipath_to_sysfs(&root_bus_sysfs, pcipath)?;
let matcher = NetPciMatcher::new(&sysfs_rel_path);
let pattern = format!(
r"[./]+{}/[a-z0-9/]*net/[a-z0-9/]*",
matcher.devpath.as_str()
);
let re = Regex::new(&pattern).expect("BUG: Failed to compile regex for NetPciMatcher");
if check_existing(re)? {
return Ok(());
}
let _uev = wait_for_uevent(sandbox, matcher).await?;
Ok(())
}
#[cfg(not(target_arch = "s390x"))]
#[derive(Debug)]
pub struct NetPciMatcher {
devpath: String,
}
#[cfg(not(target_arch = "s390x"))]
impl NetPciMatcher {
pub fn new(relpath: &str) -> NetPciMatcher {
let root_bus = create_pci_root_bus_path();
@ -64,6 +74,7 @@ impl NetPciMatcher {
}
}
#[cfg(not(target_arch = "s390x"))]
impl UeventMatcher for NetPciMatcher {
fn is_match(&self, uev: &Uevent) -> bool {
uev.devpath.starts_with(self.devpath.as_str())
@ -73,10 +84,53 @@ impl UeventMatcher for NetPciMatcher {
}
}
#[cfg(target_arch = "s390x")]
pub async fn wait_for_ccw_net_interface(
sandbox: &Arc<Mutex<Sandbox>>,
device: &ccw::Device,
) -> Result<()> {
let matcher = NetCcwMatcher::new(CCW_ROOT_BUS_PATH, device);
if check_existing(matcher.re.clone())? {
return Ok(());
}
let _uev = wait_for_uevent(sandbox, matcher).await?;
Ok(())
}
#[cfg(target_arch = "s390x")]
#[derive(Debug)]
struct NetCcwMatcher {
re: Regex,
}
#[cfg(target_arch = "s390x")]
impl NetCcwMatcher {
pub fn new(root_bus_path: &str, device: &ccw::Device) -> Self {
let re = format!(
r"{}/0\.[0-3]\.[0-9a-f]{{1,4}}/{}/virtio[0-9]+/net/",
root_bus_path, device
);
NetCcwMatcher {
re: Regex::new(&re).expect("BUG: failed to compile NetCCWMatcher regex"),
}
}
}
#[cfg(target_arch = "s390x")]
impl UeventMatcher for NetCcwMatcher {
fn is_match(&self, uev: &Uevent) -> bool {
self.re.is_match(&uev.devpath)
&& uev.subsystem == "net"
&& !uev.interface.is_empty()
&& uev.action == "add"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(not(target_arch = "s390x"))]
#[tokio::test]
#[allow(clippy::redundant_clone)]
async fn test_net_pci_matcher() {
@ -111,4 +165,34 @@ mod tests {
assert!(!matcher_a.is_match(&uev_c));
assert!(!matcher_b.is_match(&uev_c));
}
#[cfg(target_arch = "s390x")]
#[tokio::test]
async fn test_net_ccw_matcher() {
let dev_a = ccw::Device::new(0, 1).unwrap();
let dev_b = ccw::Device::new(1, 2).unwrap();
let mut uev_a = crate::uevent::Uevent::default();
uev_a.action = crate::linux_abi::U_EVENT_ACTION_ADD.to_string();
uev_a.subsystem = String::from("net");
uev_a.interface = String::from("eth0");
uev_a.devpath = format!(
"{}/0.0.0001/{}/virtio1/{}/{}",
CCW_ROOT_BUS_PATH, dev_a, uev_a.subsystem, uev_a.interface
);
let mut uev_b = uev_a.clone();
uev_b.devpath = format!(
"{}/0.0.0001/{}/virtio1/{}/{}",
CCW_ROOT_BUS_PATH, dev_b, uev_b.subsystem, uev_b.interface
);
let matcher_a = NetCcwMatcher::new(CCW_ROOT_BUS_PATH, &dev_a);
let matcher_b = NetCcwMatcher::new(CCW_ROOT_BUS_PATH, &dev_b);
assert!(matcher_a.is_match(&uev_a));
assert!(matcher_b.is_match(&uev_b));
assert!(!matcher_b.is_match(&uev_a));
assert!(!matcher_a.is_match(&uev_b));
}
}

View File

@ -55,9 +55,14 @@ use nix::sys::{stat, statfs};
use nix::unistd::{self, Pid};
use rustjail::process::ProcessOperations;
#[cfg(target_arch = "s390x")]
use crate::ccw;
use crate::cdh;
use crate::device::block_device_handler::get_virtio_blk_pci_device_name;
use crate::device::network_device_handler::wait_for_net_interface;
#[cfg(target_arch = "s390x")]
use crate::device::network_device_handler::wait_for_ccw_net_interface;
#[cfg(not(target_arch = "s390x"))]
use crate::device::network_device_handler::wait_for_pci_net_interface;
use crate::device::{add_devices, handle_cdi_devices, update_env_pci};
use crate::features::get_build_features;
#[cfg(feature = "guest-pull")]
@ -1000,15 +1005,27 @@ impl agent_ttrpc::AgentService for AgentService {
"empty update interface request",
)?;
// For network devices passed on the pci bus, check for the network interface
// For network devices passed, check for the network interface
// to be available first.
if !interface.devicePath.is_empty() {
let pcipath = pci::Path::from_str(&interface.devicePath)
.map_ttrpc_err(|e| format!("Unexpected pci-path for network interface: {:?}", e))?;
wait_for_net_interface(&self.sandbox, &pcipath)
.await
.map_ttrpc_err(|e| format!("interface not available: {:?}", e))?;
#[cfg(not(target_arch = "s390x"))]
{
let pcipath = pci::Path::from_str(&interface.devicePath).map_ttrpc_err(|e| {
format!("Unexpected pci-path for network interface: {:?}", e)
})?;
wait_for_pci_net_interface(&self.sandbox, &pcipath)
.await
.map_ttrpc_err(|e| format!("interface not available: {:?}", e))?;
}
#[cfg(target_arch = "s390x")]
{
let ccw_dev = ccw::Device::from_str(&interface.devicePath).map_ttrpc_err(|e| {
format!("Unexpected CCW path for network interface: {:?}", e)
})?;
wait_for_ccw_net_interface(&self.sandbox, &ccw_dev)
.await
.map_ttrpc_err(|e| format!("interface not available: {:?}", e))?;
}
}
self.sandbox