Merge pull request #12807 from PiotrProkop/blk-sector-rust

runtime-rs: allow specifying logical/physical sector size for block devices
This commit is contained in:
Fabiano Fidêncio
2026-04-11 00:42:45 +02:00
committed by GitHub
15 changed files with 366 additions and 2 deletions

View File

@@ -284,6 +284,20 @@ pub const KATA_ANNO_CFG_HYPERVISOR_DEFAULT_GPUS: &str =
pub const KATA_ANNO_CFG_HYPERVISOR_DEFAULT_GPU_MODEL: &str =
"io.katacontainers.config.hypervisor.default_gpu_model";
/// A sandbox annotation that specifies the logical sector size reported by block devices to the
/// guest, in bytes. Common values are 512 and 4096. Set to 0 to use the hypervisor default.
/// NOTE: the annotation key uses "blk_logical_sector_size" rather than
/// "block_device_logical_sector_size" because Kubernetes enforces a 63-character limit on
/// annotation name segments.
pub const KATA_ANNO_CFG_HYPERVISOR_BLK_LOGICAL_SECTOR_SIZE: &str =
"io.katacontainers.config.hypervisor.blk_logical_sector_size";
/// A sandbox annotation that specifies the physical sector size reported by block devices to the
/// guest, in bytes. Common values are 512 and 4096. Set to 0 to use the hypervisor default.
/// NOTE: the annotation key uses "blk_physical_sector_size" rather than
/// "block_device_physical_sector_size" because Kubernetes enforces a 63-character limit on
/// annotation name segments.
pub const KATA_ANNO_CFG_HYPERVISOR_BLK_PHYSICAL_SECTOR_SIZE: &str =
"io.katacontainers.config.hypervisor.blk_physical_sector_size";
/// Block device specific annotation for num_queues
pub const KATA_ANNO_CFG_HYPERVISOR_BLOCK_DEV_NUM_QUEUES: &str =
"io.katacontainers.config.hypervisor.block_device_num_queues";
@@ -973,6 +987,48 @@ impl Annotation {
hv.shared_fs.virtio_fs_extra_args.push(arg.to_string());
}
}
KATA_ANNO_CFG_HYPERVISOR_BLK_LOGICAL_SECTOR_SIZE => {
match self.get_value::<u32>(key) {
Ok(v) => {
let size = v.unwrap_or_default();
if let Err(e) =
crate::config::hypervisor::validate_block_device_sector_size(
size,
)
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
e.to_string(),
));
}
hv.blockdev_info.block_device_logical_sector_size = size;
}
Err(_e) => {
return Err(u32_err);
}
}
}
KATA_ANNO_CFG_HYPERVISOR_BLK_PHYSICAL_SECTOR_SIZE => {
match self.get_value::<u32>(key) {
Ok(v) => {
let size = v.unwrap_or_default();
if let Err(e) =
crate::config::hypervisor::validate_block_device_sector_size(
size,
)
{
return Err(io::Error::new(
io::ErrorKind::InvalidData,
e.to_string(),
));
}
hv.blockdev_info.block_device_physical_sector_size = size;
}
Err(_e) => {
return Err(u32_err);
}
}
}
KATA_ANNO_CFG_HYPERVISOR_BLOCK_DEV_NUM_QUEUES => {
match self.get_value::<usize>(key) {
Ok(v) => {
@@ -1123,6 +1179,18 @@ impl Annotation {
}
}
// Validate cross-field constraint: logical sector size must not exceed physical.
// Individual sizes are validated inside the loop, but the cross-field check must
// run after both annotations have been applied.
let logical = hv.blockdev_info.block_device_logical_sector_size;
let physical = hv.blockdev_info.block_device_physical_sector_size;
if logical != 0 && physical != 0 && logical > physical {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
format!("invalid sector sizes: logical ({logical}) must not be larger than physical ({physical})"),
));
}
config.adjust_config()?;
Ok(())

View File

@@ -271,6 +271,18 @@ pub struct BlockDeviceInfo {
#[serde(default)]
pub block_device_cache_noflush: bool,
/// Specifies the logical sector size, in bytes, reported by block devices to the guest.
/// Common values are 512 and 4096. Set to 0 to use the hypervisor default.
/// Must be 0 or a power of 2 between 512 and 65536.
#[serde(default)]
pub block_device_logical_sector_size: u32,
/// Specifies the physical sector size, in bytes, reported by block devices to the guest.
/// Common values are 512 and 4096. Set to 0 to use the hypervisor default.
/// Must be 0 or a power of 2 between 512 and 65536.
#[serde(default)]
pub block_device_physical_sector_size: u32,
/// If false and nvdimm is supported, use nvdimm device to plug guest image.
#[serde(default)]
pub disable_image_nvdimm: bool,
@@ -400,6 +412,16 @@ impl BlockDeviceInfo {
"Invalid vhost-user-store-path {}: {}"
)?;
validate_block_device_sector_size(self.block_device_logical_sector_size)?;
validate_block_device_sector_size(self.block_device_physical_sector_size)?;
let logical = self.block_device_logical_sector_size;
let physical = self.block_device_physical_sector_size;
if logical != 0 && physical != 0 && logical > physical {
return Err(std::io::Error::other(format!(
"invalid sector sizes: logical ({logical}) must not be larger than physical ({physical})"
)));
}
Ok(())
}
@@ -409,6 +431,19 @@ impl BlockDeviceInfo {
}
}
/// Validate that a block device sector size is 0 or a power of 2 in [512, 65536].
pub fn validate_block_device_sector_size(size: u32) -> Result<()> {
if size == 0 {
return Ok(());
}
if !(512..=65536).contains(&size) || (size & (size - 1)) != 0 {
return Err(std::io::Error::other(format!(
"invalid sector size {size}: must be 0 or a power of 2 between 512 and 65536"
)));
}
Ok(())
}
/// Guest kernel boot information.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct BootInfo {
@@ -2072,4 +2107,83 @@ mod tests {
expected_error_msg
);
}
#[test]
fn test_validate_block_device_sector_size_valid() {
for size in [0, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536] {
assert!(
validate_block_device_sector_size(size).is_ok(),
"expected size {} to be accepted",
size
);
}
}
#[test]
fn test_validate_block_device_sector_size_not_power_of_two() {
for size in [3, 100, 1000, 3000, 5000] {
assert!(
validate_block_device_sector_size(size).is_err(),
"expected non-power-of-2 size {} to be rejected",
size
);
}
}
#[test]
fn test_validate_block_device_sector_size_below_minimum() {
for size in [1, 256] {
assert!(
validate_block_device_sector_size(size).is_err(),
"expected below-minimum size {} to be rejected",
size
);
}
}
#[test]
fn test_validate_block_device_sector_size_above_maximum() {
for size in [131072, 1048576] {
assert!(
validate_block_device_sector_size(size).is_err(),
"expected above-maximum size {} to be rejected",
size
);
}
}
fn blockdev_info_with_sectors(logical: u32, physical: u32) -> BlockDeviceInfo {
BlockDeviceInfo {
block_device_driver: VIRTIO_BLK_PCI.to_string(),
block_device_logical_sector_size: logical,
block_device_physical_sector_size: physical,
..Default::default()
}
}
#[test]
fn test_validate_block_device_sector_sizes_valid() {
assert!(blockdev_info_with_sectors(0, 0).validate().is_ok());
assert!(blockdev_info_with_sectors(512, 0).validate().is_ok());
assert!(blockdev_info_with_sectors(0, 4096).validate().is_ok());
assert!(blockdev_info_with_sectors(512, 4096).validate().is_ok());
assert!(blockdev_info_with_sectors(4096, 4096).validate().is_ok());
assert!(blockdev_info_with_sectors(512, 512).validate().is_ok());
}
#[test]
fn test_validate_block_device_sector_sizes_logical_exceeds_physical() {
assert!(
blockdev_info_with_sectors(4096, 512).validate().is_err(),
"logical > physical should be rejected"
);
assert!(
blockdev_info_with_sectors(4096, 1024).validate().is_err(),
"logical > physical should be rejected"
);
assert!(
blockdev_info_with_sectors(65536, 512).validate().is_err(),
"logical > physical should be rejected"
);
}
}

View File

@@ -7,7 +7,9 @@ mod tests {
use kata_types::annotations::{
Annotation, KATA_ANNO_CFG_AGENT_CONTAINER_PIPE_SIZE, KATA_ANNO_CFG_AGENT_TRACE,
KATA_ANNO_CFG_DISABLE_GUEST_SECCOMP, KATA_ANNO_CFG_ENABLE_PPROF,
KATA_ANNO_CFG_EXPERIMENTAL, KATA_ANNO_CFG_HYPERVISOR_BLOCK_DEV_CACHE_NOFLUSH,
KATA_ANNO_CFG_EXPERIMENTAL, KATA_ANNO_CFG_HYPERVISOR_BLK_LOGICAL_SECTOR_SIZE,
KATA_ANNO_CFG_HYPERVISOR_BLK_PHYSICAL_SECTOR_SIZE,
KATA_ANNO_CFG_HYPERVISOR_BLOCK_DEV_CACHE_NOFLUSH,
KATA_ANNO_CFG_HYPERVISOR_BLOCK_DEV_DRIVER, KATA_ANNO_CFG_HYPERVISOR_DEFAULT_MEMORY,
KATA_ANNO_CFG_HYPERVISOR_DEFAULT_VCPUS, KATA_ANNO_CFG_HYPERVISOR_ENABLE_GUEST_SWAP,
KATA_ANNO_CFG_HYPERVISOR_ENABLE_HUGEPAGES, KATA_ANNO_CFG_HYPERVISOR_ENABLE_IO_THREADS,
@@ -479,4 +481,111 @@ mod tests {
let mut config = TomlConfig::load(content).unwrap();
assert!(anno.update_config_by_annotation(&mut config).is_err());
}
#[test]
fn test_block_device_sector_size_annotations_valid() {
let content = include_str!("texture/configuration-anno-0.toml");
let qemu = QemuConfig::new();
qemu.register();
// Valid: 512 logical, 4096 physical
let config = TomlConfig::load(content).unwrap();
KataConfig::set_active_config(Some(config), "qemu", "agent0");
let mut anno_hash = HashMap::new();
anno_hash.insert(
KATA_ANNO_CFG_HYPERVISOR_BLK_LOGICAL_SECTOR_SIZE.to_string(),
"512".to_string(),
);
anno_hash.insert(
KATA_ANNO_CFG_HYPERVISOR_BLK_PHYSICAL_SECTOR_SIZE.to_string(),
"4096".to_string(),
);
let anno = Annotation::new(anno_hash);
let mut config = TomlConfig::load(content).unwrap();
assert!(anno.update_config_by_annotation(&mut config).is_ok());
if let Some(hv) = config.hypervisor.get("qemu") {
assert_eq!(hv.blockdev_info.block_device_logical_sector_size, 512);
assert_eq!(hv.blockdev_info.block_device_physical_sector_size, 4096);
}
// Valid: 0 means hypervisor default
let mut anno_hash = HashMap::new();
anno_hash.insert(
KATA_ANNO_CFG_HYPERVISOR_BLK_LOGICAL_SECTOR_SIZE.to_string(),
"0".to_string(),
);
anno_hash.insert(
KATA_ANNO_CFG_HYPERVISOR_BLK_PHYSICAL_SECTOR_SIZE.to_string(),
"0".to_string(),
);
let anno = Annotation::new(anno_hash);
let mut config = TomlConfig::load(content).unwrap();
assert!(anno.update_config_by_annotation(&mut config).is_ok());
if let Some(hv) = config.hypervisor.get("qemu") {
assert_eq!(hv.blockdev_info.block_device_logical_sector_size, 0);
assert_eq!(hv.blockdev_info.block_device_physical_sector_size, 0);
}
}
#[test]
fn test_block_device_sector_size_annotation_invalid_not_power_of_two() {
let content = include_str!("texture/configuration-anno-0.toml");
let qemu = QemuConfig::new();
qemu.register();
let config = TomlConfig::load(content).unwrap();
KataConfig::set_active_config(Some(config), "qemu", "agent0");
let mut anno_hash = HashMap::new();
anno_hash.insert(
KATA_ANNO_CFG_HYPERVISOR_BLK_LOGICAL_SECTOR_SIZE.to_string(),
"1000".to_string(),
);
let anno = Annotation::new(anno_hash);
let mut config = TomlConfig::load(content).unwrap();
assert!(anno.update_config_by_annotation(&mut config).is_err());
}
#[test]
fn test_block_device_sector_size_annotation_invalid_below_minimum() {
let content = include_str!("texture/configuration-anno-0.toml");
let qemu = QemuConfig::new();
qemu.register();
let config = TomlConfig::load(content).unwrap();
KataConfig::set_active_config(Some(config), "qemu", "agent0");
let mut anno_hash = HashMap::new();
anno_hash.insert(
KATA_ANNO_CFG_HYPERVISOR_BLK_PHYSICAL_SECTOR_SIZE.to_string(),
"256".to_string(),
);
let anno = Annotation::new(anno_hash);
let mut config = TomlConfig::load(content).unwrap();
assert!(anno.update_config_by_annotation(&mut config).is_err());
}
#[test]
fn test_block_device_sector_size_annotation_invalid_above_maximum() {
let content = include_str!("texture/configuration-anno-0.toml");
let qemu = QemuConfig::new();
qemu.register();
let config = TomlConfig::load(content).unwrap();
KataConfig::set_active_config(Some(config), "qemu", "agent0");
let mut anno_hash = HashMap::new();
anno_hash.insert(
KATA_ANNO_CFG_HYPERVISOR_BLK_LOGICAL_SECTOR_SIZE.to_string(),
"131072".to_string(),
);
let anno = Annotation::new(anno_hash);
let mut config = TomlConfig::load(content).unwrap();
assert!(anno.update_config_by_annotation(&mut config).is_err());
}
}

View File

@@ -19,7 +19,7 @@ default_maxvcpus = 64
machine_type = "q35"
confidential_guest = true
rootless = true
enable_annotations = ["shared_fs","path", "ctlpath","jailer_path","enable_iothreads","default_memory","memory_slots","enable_mem_prealloc","enable_hugepages","file_mem_backend","enable_virtio_mem","enable_guest_swap","default_vcpus","virtio_fs_extra_args","block_device_driver","vhost_user_store_path","kernel","guest_hook_path","block_device_cache_noflush","virtio_fs_daemon"]
enable_annotations = ["shared_fs","path", "ctlpath","jailer_path","enable_iothreads","default_memory","memory_slots","enable_mem_prealloc","enable_hugepages","file_mem_backend","enable_virtio_mem","enable_guest_swap","default_vcpus","virtio_fs_extra_args","block_device_driver","vhost_user_store_path","kernel","guest_hook_path","block_device_cache_noflush","virtio_fs_daemon","blk_logical_sector_size","blk_physical_sector_size"]
machine_accelerators="noapic"
default_bridges = 2
default_memory = 128

View File

@@ -255,6 +255,16 @@ block_device_cache_direct = false
# Default false
block_device_cache_noflush = false
# Specifies the logical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_logical_sector_size = 0
# Specifies the physical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_physical_sector_size = 0
# Enable iothreads (data-plane) to be used. This causes IO to be
# handled in a separate IO thread. This is currently only implemented
# for SCSI.

View File

@@ -245,6 +245,16 @@ block_device_cache_direct = false
# Default false
block_device_cache_noflush = false
# Specifies the logical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_logical_sector_size = 0
# Specifies the physical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_physical_sector_size = 0
# Enable iothreads (data-plane) to be used. This causes IO to be
# handled in a separate IO thread. This is currently only implemented
# for SCSI.

View File

@@ -244,6 +244,16 @@ block_device_cache_direct = false
# Default false
block_device_cache_noflush = false
# Specifies the logical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_logical_sector_size = 0
# Specifies the physical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_physical_sector_size = 0
# Enable iothreads (data-plane) to be used. This causes IO to be
# handled in a separate IO thread. This is currently only implemented
# for SCSI.

View File

@@ -281,6 +281,16 @@ block_device_cache_direct = false
# Default false
block_device_cache_noflush = false
# Specifies the logical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_logical_sector_size = 0
# Specifies the physical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_physical_sector_size = 0
# Enable iothreads (data-plane) to be used. This causes IO to be
# handled in a separate IO thread. This is currently only implemented
# for SCSI.

View File

@@ -256,6 +256,16 @@ block_device_cache_direct = false
# Default false
block_device_cache_noflush = false
# Specifies the logical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_logical_sector_size = 0
# Specifies the physical sector size, in bytes, reported by block devices to the guest.
# Common values are 512 and 4096. Set to 0 to use the QEMU/hypervisor default.
# Default 0
block_device_physical_sector_size = 0
# Enable iothreads (data-plane) to be used. This causes IO to be
# handled in a separate IO thread. This is currently implemented
# for virtio-scsi and virtio-blk.

View File

@@ -112,6 +112,12 @@ pub struct BlockConfig {
/// block device multi-queue
pub num_queues: usize,
/// Logical sector size in bytes reported to the guest. 0 means use hypervisor default.
pub logical_sector_size: u32,
/// Physical sector size in bytes reported to the guest. 0 means use hypervisor default.
pub physical_sector_size: u32,
}
#[derive(Debug, Clone, Default)]

View File

@@ -866,6 +866,8 @@ impl QemuInner {
),
block_device.config.is_readonly,
block_device.config.no_drop,
block_device.config.logical_sector_size,
block_device.config.physical_sector_size,
)
.context("hotplug block device")?;

View File

@@ -642,6 +642,8 @@ impl Qmp {
is_direct: Option<bool>,
is_readonly: bool,
no_drop: bool,
logical_block_size: u32,
physical_block_size: u32,
) -> Result<(Option<PciPath>, Option<String>)> {
// `blockdev-add`
let node_name = format!("drive-{index}");
@@ -719,6 +721,13 @@ impl Qmp {
let mut blkdev_add_args = Dictionary::new();
blkdev_add_args.insert("drive".to_owned(), node_name.clone().into());
if logical_block_size > 0 {
blkdev_add_args.insert("logical_block_size".to_owned(), logical_block_size.into());
}
if physical_block_size > 0 {
blkdev_add_args.insert("physical_block_size".to_owned(), physical_block_size.into());
}
if block_driver == VIRTIO_SCSI {
// Helper closure to decode a flattened u16 SCSI index into an (ID, LUN) pair.
let get_scsi_id_lun = |index_u16: u16| -> Result<(u8, u8)> {

View File

@@ -422,6 +422,8 @@ impl ResourceManagerInner {
blkdev_aio: BlockDeviceAio::new(&blkdev_info.block_device_aio),
num_queues: blkdev_info.num_queues,
queue_size: blkdev_info.queue_size,
logical_sector_size: blkdev_info.block_device_logical_sector_size,
physical_sector_size: blkdev_info.block_device_physical_sector_size,
..Default::default()
});

View File

@@ -49,6 +49,8 @@ impl BlockVolume {
blkdev_aio: BlockDeviceAio::new(&blkdev_info.block_device_aio),
num_queues: blkdev_info.num_queues,
queue_size: blkdev_info.queue_size,
logical_sector_size: blkdev_info.block_device_logical_sector_size,
physical_sector_size: blkdev_info.block_device_physical_sector_size,
..Default::default()
};

View File

@@ -64,6 +64,8 @@ impl RawblockVolume {
blkdev_aio: BlockDeviceAio::new(&blkdev_info.block_device_aio),
num_queues: blkdev_info.num_queues,
queue_size: blkdev_info.queue_size,
logical_sector_size: blkdev_info.block_device_logical_sector_size,
physical_sector_size: blkdev_info.block_device_physical_sector_size,
..Default::default()
};