runtime-rs: Fix initial vCPU / memory with static_sandbox_resource_mgmt

InitialSizeManager::setup_config() is responsible for applying the
sandbox workload sizing (computed from containerd/CRI-O sandbox
annotations) to the hypervisor configuration before VM creation.

Previously, the workload vCPU count was only logged but never actually
added to default_vcpus, so the VM was always created with only the base
vCPUs from the configuration/annotations. This caused the
k8s-sandbox-vcpus-allocation test to fail with qemu-snp-runtime-rs:
a pod with default_vcpus=0.75 and a container CPU limit of 1.2 should
see ceil(0.75 + 1.2) = 2 vCPUs, but only got 1.

Additionally, the workload memory was being added to default_memory
unconditionally, diverging from the Go runtime which only applies both
CPU and memory additions when static_sandbox_resource_mgmt is enabled.
In the non-static path, adding workload resources here would cause
double-counting: once from setup_config() at sandbox creation, and
again from update_cpu_resources()/update_mem_resources() when
individual containers are added.

Guard both additions behind static_sandbox_resource_mgmt, matching the
Go runtime's behavior in src/runtime/pkg/oci/utils.go:

    if sandboxConfig.StaticResourceMgmt {
        sandboxConfig.HypervisorConfig.NumVCPUsF += sandboxConfig.SandboxResources.WorkloadCPUs
        sandboxConfig.HypervisorConfig.MemorySize += sandboxConfig.SandboxResources.WorkloadMemMB
    }

Fixes: k8s-sandbox-vcpus-allocation test failure on qemu-snp-runtime-rs

Signed-off-by: Fabiano Fidêncio <ffidencio@nvidia.com>
Made-with: Cursor
This commit is contained in:
Fabiano Fidêncio
2026-04-07 15:30:15 +02:00
parent 2148afe243
commit 328383adc3

View File

@@ -142,15 +142,23 @@ impl InitialSizeManager {
if self.resource.vcpu > 0.0 {
info!(sl!(), "resource with vcpu {}", self.resource.vcpu);
if config.runtime.static_sandbox_resource_mgmt {
hv.cpu_info.default_vcpus += self.resource.vcpu;
let new_vcpus_ceil = hv.cpu_info.default_vcpus.ceil() as u32;
if hv.cpu_info.default_maxvcpus < new_vcpus_ceil {
hv.cpu_info.default_maxvcpus = new_vcpus_ceil;
}
}
}
self.resource.orig_toml_default_mem = hv.memory_info.default_memory;
if self.resource.mem_mb > 0 {
// since the memory overhead introduced by kata-agent and system components
// will really affect the amount of memory the user can use, so we choose to
// plus the default_memory here, instead of overriding it.
// (if we override the default_memory here, and user apllications still
// use memory as they orignally expected, it would be easy to OOM.)
hv.memory_info.default_memory += self.resource.mem_mb;
info!(sl!(), "resource with memory {}", self.resource.mem_mb);
if config.runtime.static_sandbox_resource_mgmt {
hv.memory_info.default_memory += self.resource.mem_mb;
if hv.memory_info.default_maxmemory < hv.memory_info.default_memory {
hv.memory_info.default_maxmemory = hv.memory_info.default_memory;
}
}
}
Ok(())
}
@@ -366,4 +374,114 @@ mod tests {
);
}
}
fn make_config(
default_vcpus: f32,
default_maxvcpus: u32,
default_memory: u32,
default_maxmemory: u32,
static_sandbox_resource_mgmt: bool,
) -> TomlConfig {
use kata_types::config::Hypervisor;
let mut config = TomlConfig::default();
config
.hypervisor
.insert("qemu".to_owned(), Hypervisor::default());
config
.hypervisor
.entry("qemu".to_owned())
.and_modify(|hv| {
hv.cpu_info.default_vcpus = default_vcpus;
hv.cpu_info.default_maxvcpus = default_maxvcpus;
hv.memory_info.default_memory = default_memory;
hv.memory_info.default_maxmemory = default_maxmemory;
});
config.runtime.hypervisor_name = "qemu".to_owned();
config.runtime.static_sandbox_resource_mgmt = static_sandbox_resource_mgmt;
config
}
#[test]
fn test_setup_config_static_applies_vcpu_and_memory() {
let mut config = make_config(1.0, 4, 256, 4096, true);
let mut mgr = InitialSizeManager {
resource: InitialSize {
vcpu: 1.2,
mem_mb: 512,
orig_toml_default_mem: 0,
},
};
mgr.setup_config(&mut config).unwrap();
let hv = config.hypervisor.get("qemu").unwrap();
assert_eq!(hv.cpu_info.default_vcpus, 2.2);
assert_eq!(hv.memory_info.default_memory, 768);
}
#[test]
fn test_setup_config_non_static_does_not_apply() {
let mut config = make_config(1.0, 4, 256, 4096, false);
let mut mgr = InitialSizeManager {
resource: InitialSize {
vcpu: 1.2,
mem_mb: 512,
orig_toml_default_mem: 0,
},
};
mgr.setup_config(&mut config).unwrap();
let hv = config.hypervisor.get("qemu").unwrap();
assert_eq!(hv.cpu_info.default_vcpus, 1.0);
assert_eq!(hv.memory_info.default_memory, 256);
}
#[test]
fn test_setup_config_clamps_maxvcpus() {
let mut config = make_config(1.0, 2, 256, 4096, true);
let mut mgr = InitialSizeManager {
resource: InitialSize {
vcpu: 2.5,
mem_mb: 0,
orig_toml_default_mem: 0,
},
};
mgr.setup_config(&mut config).unwrap();
let hv = config.hypervisor.get("qemu").unwrap();
assert_eq!(hv.cpu_info.default_vcpus, 3.5);
assert_eq!(hv.cpu_info.default_maxvcpus, 4);
}
#[test]
fn test_setup_config_clamps_maxmemory() {
let mut config = make_config(1.0, 4, 256, 300, true);
let mut mgr = InitialSizeManager {
resource: InitialSize {
vcpu: 0.0,
mem_mb: 512,
orig_toml_default_mem: 0,
},
};
mgr.setup_config(&mut config).unwrap();
let hv = config.hypervisor.get("qemu").unwrap();
assert_eq!(hv.memory_info.default_memory, 768);
assert_eq!(hv.memory_info.default_maxmemory, 768);
}
#[test]
fn test_setup_config_preserves_orig_toml_default_mem() {
let mut config = make_config(1.0, 4, 256, 4096, true);
let mut mgr = InitialSizeManager {
resource: InitialSize {
vcpu: 0.0,
mem_mb: 128,
orig_toml_default_mem: 0,
},
};
mgr.setup_config(&mut config).unwrap();
assert_eq!(mgr.get_orig_toml_default_mem(), 256);
}
}