runtime-rs: wire guest_extension_images cold-plug

Add a serial_override field to BlockConfig and DeviceVirtioBlk so that
extension images get a deterministic QEMU serial (extension-<name>) instead of
the auto-generated image-<id>.  This lets the guest discover each extension
via /dev/disk/by-id/virtio-extension-<name>.

Wire up the full cold-plug path for guest_extension_images:

- sandbox.rs: iterate boot_info.guest_extension_images, create read-only
  BlockConfig entries with the correct serial_override, and push them
  as ResourceConfig::GuestExtensionImage.
- resource lib.rs / manager_inner.rs: handle the new GuestExtensionImage variant
  by delegating to the existing block device path.
- cmdline_generator.rs: use serial_override when present for the QEMU
  serial= parameter; append kata.extension.<name>.verity_params=... to the
  kernel command line for each extra image.
- inner.rs: propagate serial_override from BlockConfig to the cmdline
  generator's add_block_device().

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
Assisted-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Fabiano Fidêncio
2026-05-10 19:02:50 +02:00
committed by Fabiano Fidêncio
parent f657d3729a
commit e96b6c8b17
6 changed files with 94 additions and 4 deletions

View File

@@ -141,6 +141,12 @@ pub struct BlockConfig {
/// Physical sector size in bytes reported to the guest. 0 means use hypervisor default.
pub physical_sector_size: u32,
/// Override the QEMU virtio serial for this device.
/// When set, the device is discoverable in the guest via
/// `/dev/disk/by-id/virtio-<serial>`.
/// If empty, the default `image-{device_id}` serial is used.
pub serial_override: String,
}
#[derive(Debug, Clone, Default)]

View File

@@ -209,6 +209,18 @@ impl Kernel {
if config.disable_guest_selinux { 0 } else { 1 }
)));
// Emit one kata.extension.<name>.verity_params entry per configured
// extension. This is also the activation signal the guest-side systemd
// generator and unit key on, so it must be emitted even when
// verity_params is empty (e.g. an unmeasured extension on s390x): an
// empty value renders as a bare key, which still activates the mount.
for extra in &config.guest_extension_images {
kernel_params.append(&mut KernelParams::from_string(&format!(
"kata.extension.{}.verity_params={}",
extra.name, extra.verity_params
)));
}
Ok(Kernel {
path: config.boot_info.kernel.clone(),
initrd_path: config.boot_info.initrd.clone(),
@@ -1083,6 +1095,7 @@ struct DeviceVirtioBlk {
share_rw: bool,
discard: bool,
devno: Option<String>,
serial_override: Option<String>,
}
impl DeviceVirtioBlk {
@@ -1094,6 +1107,7 @@ impl DeviceVirtioBlk {
share_rw: true,
discard: false,
devno,
serial_override: None,
}
}
@@ -1114,6 +1128,11 @@ impl DeviceVirtioBlk {
self.discard = discard;
self
}
fn set_serial_override(&mut self, serial: String) -> &mut Self {
self.serial_override = Some(serial);
self
}
}
#[async_trait]
@@ -1135,7 +1154,11 @@ impl ToQemuParams for DeviceVirtioBlk {
if self.discard {
params.push("discard=on".to_owned());
}
params.push(format!("serial=image-{}", self.id));
let serial = match &self.serial_override {
Some(s) => s.clone(),
None => format!("image-{}", self.id),
};
params.push(format!("serial={serial}"));
if let Some(devno) = &self.devno {
params.push(format!("devno={devno}"));
}
@@ -2908,6 +2931,7 @@ impl<'a> QemuCmdLine<'a> {
is_direct: bool,
is_scsi: bool,
discard_unmap: bool,
serial_override: Option<&str>,
) -> Result<()> {
let mut backend = BlockBackend::new(device_id, path, is_direct);
backend.set_discard_unmap(discard_unmap);
@@ -2919,6 +2943,9 @@ impl<'a> QemuCmdLine<'a> {
} else {
let mut device = DeviceVirtioBlk::new(device_id, bus_type(), devno);
device.set_discard(discard_unmap);
if let Some(serial) = serial_override {
device.set_serial_override(serial.to_string());
}
self.devices.push(Box::new(device));
}

View File

@@ -141,8 +141,13 @@ impl QemuInner {
&block_dev.config.path_on_host,
block_dev.config.is_readonly,
)?,
KATA_CCW_DEV_TYPE | KATA_BLK_DEV_TYPE | KATA_SCSI_DEV_TYPE => cmdline
.add_block_device(
KATA_CCW_DEV_TYPE | KATA_BLK_DEV_TYPE | KATA_SCSI_DEV_TYPE => {
let serial = if block_dev.config.serial_override.is_empty() {
None
} else {
Some(block_dev.config.serial_override.as_str())
};
cmdline.add_block_device(
block_dev.device_id.as_str(),
&block_dev.config.path_on_host,
block_dev
@@ -151,7 +156,9 @@ impl QemuInner {
.unwrap_or(self.config.blockdev_info.block_device_cache_direct),
block_dev.config.driver_option.as_str() == KATA_SCSI_DEV_TYPE,
block_dev.config.discard_unmap,
)?,
serial,
)?
}
unsupported => {
info!(sl!(), "unsupported block device driver: {}", unsupported)
}

View File

@@ -37,6 +37,7 @@ pub enum ResourceConfig {
Network(NetworkConfig),
ShareFs(SharedFsInfo),
VmRootfs(BlockConfig),
GuestExtensionImage(BlockConfig),
HybridVsock(HybridVsockConfig),
Vsock(VsockConfig),
Protection(ProtectionDeviceConfig),

View File

@@ -189,6 +189,11 @@ impl ResourceManagerInner {
.await
.context("do handle device failed.")?;
}
ResourceConfig::GuestExtensionImage(r) => {
do_handle_device(&self.device_manager, &DeviceConfig::BlockCfg(r))
.await
.context("do handle extra image device failed.")?;
}
ResourceConfig::HybridVsock(hv) => {
do_handle_device(&self.device_manager, &DeviceConfig::HybridVsockCfg(hv))
.await

View File

@@ -56,6 +56,7 @@ use kata_sys_util::protection::{available_guest_protection, GuestProtection};
use kata_sys_util::spec::load_oci_spec;
use kata_types::capabilities::CapabilityBits;
use kata_types::config::hypervisor::Hypervisor as HypervisorConfig;
use kata_types::config::hypervisor::{VIRTIO_BLK_CCW, VIRTIO_BLK_PCI};
#[cfg(all(
feature = "cloud-hypervisor",
any(target_arch = "x86_64", target_arch = "aarch64")
@@ -250,6 +251,15 @@ impl VirtSandbox {
resource_configs.push(vm_rootfs);
}
// prepare extra extension image device configs (e.g. CoCo extension)
let extra_configs = self
.prepare_guest_extension_images_config()
.await
.context("failed to prepare extra images device config")?;
for block_config in extra_configs {
resource_configs.push(ResourceConfig::GuestExtensionImage(block_config));
}
// prepare protection device config
let init_data = if let Some(initdata) = self
.prepare_initdata_device_config(&self.hypervisor.hypervisor_config().await)
@@ -623,6 +633,40 @@ impl VirtSandbox {
}))
}
async fn prepare_guest_extension_images_config(&self) -> Result<Vec<BlockConfig>> {
let hv_config = self.hypervisor.hypervisor_config().await;
let mut configs = Vec::new();
// Extension images must be cold-plugged as virtio-blk, because the
// guest discovers each extension by its deterministic serial
// (extension-<name>), and only virtio-blk devices carry that serial.
// We therefore always enforce a virtio-blk transport here (the
// architecture's virtio-blk-ccw on s390x, virtio-blk-pci elsewhere)
// rather than reusing vm_rootfs_driver or block_device_driver: those
// may resolve to a non-virtio-blk transport such as virtio-pmem
// (NVDIMM, no serial) or virtio-scsi, which would leave the extension
// undiscoverable and its mount unit would fail closed.
let block_driver = if uses_native_ccw_bus() {
VIRTIO_BLK_CCW.to_string()
} else {
VIRTIO_BLK_PCI.to_string()
};
for extra in &hv_config.guest_extension_images {
if extra.path.is_empty() {
continue;
}
configs.push(BlockConfig {
path_on_host: extra.path.clone(),
is_readonly: true,
driver_option: block_driver.clone(),
serial_override: format!("extension-{}", extra.name),
..Default::default()
});
}
Ok(configs)
}
async fn set_agent_policy(&self) -> Result<()> {
// TODO: Exclude policy-related items from the annotations.
let toml_config = self.resource_manager.config().await;