diff --git a/docs/windows.md b/docs/windows.md index 7264d20ea..85d75d4a2 100644 --- a/docs/windows.md +++ b/docs/windows.md @@ -184,6 +184,10 @@ Set-Service -Name sshd -StartupType ‘Automatic’ This allows for SSH login from a remote machine, for example through the `administrator` user: `ssh administrator@192.168.249.2`. For a more detailed OpenSSH guide, please follow the MSDN article from the [links](#links) section. +## Hotplug capability + +CPU hotplug is supported. The VM operating system needs to support hotplug and be appropriately licensed. SKU limitations like constraints on the number of cores are to be taken into consideration. + ## Debugging The Windows guest debugging process relies heavily on QEMU and [socat](http://www.dest-unreach.org/socat/). The procedure requires two Windows VMs: diff --git a/tests/integration.rs b/tests/integration.rs index 8bb2d9851..852c27ca2 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -5565,6 +5565,30 @@ mod tests { } } + fn get_cpu_count_windows(auth: &PasswordAuth) -> u8 { + return ssh_command_ip_with_auth( + "powershell -Command \"(Get-CimInstance win32_computersystem).NumberOfLogicalProcessors\"", + &auth, + "192.168.249.2", + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT, + ) + .unwrap() + .trim() + .parse::() + .unwrap_or(0); + } + + fn get_vcpu_threads_count(pid: u32) -> u8 { + // ps -T -p 12345 | grep vcpu | wc -l + let out = Command::new("ps") + .args(&["-T", "-p", format!("{}", pid).as_str()]) + .output() + .expect("ps command failed") + .stdout; + return String::from_utf8_lossy(&out).matches("vcpu").count() as u8; + } + #[test] fn test_windows_guest() { let mut windows = WindowsDiskConfig::new(WINDOWS_IMAGE_NAME.to_string()); @@ -5712,6 +5736,75 @@ mod tests { handle_child_output(r, &output); } + + #[test] + fn test_windows_guest_cpu_hotplug() { + let mut windows = WindowsDiskConfig::new(WINDOWS_IMAGE_NAME.to_string()); + let guest = Guest::new(&mut windows); + + let tmp_dir = TempDir::new_with_prefix("/tmp/ch").unwrap(); + let mut workload_path = dirs::home_dir().unwrap(); + workload_path.push("workloads"); + + let mut ovmf_path = workload_path.clone(); + ovmf_path.push(OVMF_NAME); + + let mut osdisk_path = workload_path; + osdisk_path.push(WINDOWS_IMAGE_NAME.to_string()); + + let api_socket = temp_api_path(&tmp_dir); + + let mut child = GuestCommand::new(&guest) + .args(&["--api-socket", &api_socket]) + .args(&["--cpus", "boot=2,max=8,kvm_hyperv=on"]) + .args(&["--memory", "size=4G"]) + .args(&["--kernel", ovmf_path.to_str().unwrap()]) + .default_disks() + .args(&["--serial", "tty"]) + .args(&["--console", "off"]) + .args(&["--net", "tap="]) + .capture_output() + .spawn() + .unwrap(); + + // Wait to make sure Windows boots up + thread::sleep(std::time::Duration::new(60, 0)); + + let r = std::panic::catch_unwind(|| { + let auth = windows_auth(); + + let vcpu_num = 2; + // Check the initial number of CPUs the guest sees + assert_eq!(get_cpu_count_windows(&auth), vcpu_num); + // Check the initial number of vcpu threads in the CH process + assert_eq!(get_vcpu_threads_count(child.id()), vcpu_num); + + let vcpu_num = 6; + // Hotplug some CPUs + resize_command(&api_socket, Some(vcpu_num), None, None); + // Wait to make sure CPUs are added + thread::sleep(std::time::Duration::new(10, 0)); + // Check the guest sees the correct number + assert_eq!(get_cpu_count_windows(&auth), vcpu_num); + // Check the CH process has the correct number of vcpu threads + assert_eq!(get_vcpu_threads_count(child.id()), vcpu_num); + + ssh_command_ip_with_auth( + "shutdown /s", + &auth, + "192.168.249.2", + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT, + ) + .unwrap(); + }); + + let _ = child.wait_timeout(std::time::Duration::from_secs(60)); + let _ = child.kill(); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); + } } #[cfg(target_arch = "x86_64")]