diff --git a/dstack/vmm/src/app.rs b/dstack/vmm/src/app.rs index df3e99281..732b3be97 100644 --- a/dstack/vmm/src/app.rs +++ b/dstack/vmm/src/app.rs @@ -209,6 +209,7 @@ impl GpuConfig { /// Round up a value to the nearest multiple of another value. /// If the value is already a multiple, it remains unchanged. +/// Left unchanged if the next multiple overflows, rather than wrapping to a tiny value. pub(crate) fn round_up(value: u32, multiple: u32) -> u32 { if multiple <= 1 { return value; @@ -219,7 +220,7 @@ pub(crate) fn round_up(value: u32, multiple: u32) -> u32 { return value; } - value + (multiple - remainder) + value.checked_add(multiple - remainder).unwrap_or(value) } /// Get the NUMA node associated with a PCI device. @@ -3263,6 +3264,11 @@ mod tests { Ok(config) } + #[test] + fn round_up_does_not_wrap() { + assert_eq!(round_up(u32::MAX, 2), u32::MAX); + } + #[test] fn effective_vcpu_count_clamps_zero_to_one() { assert_eq!(effective_vcpu_count(0, None), 1); diff --git a/dstack/vmm/src/app/qemu.rs b/dstack/vmm/src/app/qemu.rs index 74e3a7958..d32a2940d 100644 --- a/dstack/vmm/src/app/qemu.rs +++ b/dstack/vmm/src/app/qemu.rs @@ -896,7 +896,7 @@ impl QemuCommandBuilder<'_> { )); bus_number += device_count + 1; } - Ok((smp, memory_gib * 1024)) + Ok((smp, memory_gib.saturating_mul(1024))) } fn configure_gpus(&self, command: &mut Command) -> Result<()> { diff --git a/dstack/vmm/src/main_service.rs b/dstack/vmm/src/main_service.rs index ce29fc748..7177dd312 100644 --- a/dstack/vmm/src/main_service.rs +++ b/dstack/vmm/src/main_service.rs @@ -314,6 +314,16 @@ pub fn create_manifest_from_vm_config( cvm_config: &crate::config::CvmConfig, ) -> Result { validate_label(&request.name)?; + // Same checks as `validate_resize_request`. + if request.vcpu == 0 { + bail!("vcpu must be greater than zero"); + } + if request.memory == 0 { + bail!("memory must be greater than zero"); + } + if request.disk_size == 0 { + bail!("disk_size must be greater than zero"); + } let port_map = port_map_from_proto(&request.ports, &cvm_config.port_mapping, &[])?; let networks = networks_from_vm_config(&request, cvm_config)?; @@ -1749,6 +1759,13 @@ mod tests { assert!(!err.contains("storage_discard"), "{err}"); } + #[test] + fn deployment_rejects_zero_resources() { + let mut request = test_vm_configuration(); + request.memory = 0; + assert!(create_manifest_from_vm_config(request, &test_cvm_config()).is_err()); + } + #[test] fn resize_request_rejects_empty_zero_and_empty_image_updates() { let mut request = ResizeVmRequest {