From 7284c02d31cb4cd3d8b8898e112da72b059cbcb4 Mon Sep 17 00:00:00 2001 From: Muminul Islam Date: Sat, 21 Mar 2026 18:37:48 -0700 Subject: [PATCH] tests: extract _test_net_hotplug to tests_wrappers Extract test logic from _test_net_hotplug into a shared wrapper function in tests_wrappers.rs. Update both test_net_hotplug and test_net_multi_segment_hotplug to use the basic_regular_guest macro. The wrapper uses default_kernel_cmdline() for kernel/cmdline setup. Signed-off-by: Muminul Islam --- .../tests/common/tests_wrappers.rs | 172 ++++++++++++++++ cloud-hypervisor/tests/integration.rs | 190 ++---------------- 2 files changed, 185 insertions(+), 177 deletions(-) diff --git a/cloud-hypervisor/tests/common/tests_wrappers.rs b/cloud-hypervisor/tests/common/tests_wrappers.rs index 4c39043aa..6f002fee7 100644 --- a/cloud-hypervisor/tests/common/tests_wrappers.rs +++ b/cloud-hypervisor/tests/common/tests_wrappers.rs @@ -2868,3 +2868,175 @@ pub(crate) fn _test_virtio_block_topology(guest: &Guest, loop_dev: &str) { handle_child_output(r, &output); } + +pub(crate) fn _test_net_hotplug( + guest: &Guest, + max_num_pci_segments: u16, + pci_segment: Option, +) { + let api_socket = temp_api_path(&guest.tmp_dir); + + // Boot without network + let mut cmd = GuestCommand::new(guest); + + cmd.args(["--api-socket", &api_socket]) + .default_cpus() + .default_memory() + .default_net() + .default_disks() + .capture_output(); + + if pci_segment.is_some() { + cmd.default_kernel_cmdline_with_platform(Some(&format!( + "num_pci_segments={max_num_pci_segments}" + ))); + } else { + cmd.default_kernel_cmdline(); + } + + let mut child = cmd.spawn().unwrap(); + + guest.wait_vm_boot().unwrap(); + + let r = std::panic::catch_unwind(|| { + // Add network + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test0,tap=,mac={},ip={},mask=255.255.255.128{}", + guest.network.guest_mac1, + guest.network.host_ip1, + if let Some(pci_segment) = pci_segment { + format!(",pci_segment={pci_segment}") + } else { + String::new() + } + ) + .as_str(), + ), + ); + assert!(cmd_success); + + if let Some(pci_segment) = pci_segment { + assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( + "{{\"id\":\"test0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" + ))); + } else { + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") + ); + } + + thread::sleep(std::time::Duration::new(5, 0)); + + // 2 network interfaces + default localhost ==> 3 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + // Test the same using the added network interface's IP + assert_eq!( + ssh_command_ip( + "ip -o link | wc -l", + &guest.network.guest_ip1, + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT + ) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + // Remove network + assert!(remote_command(&api_socket, "remove-device", Some("test0"),)); + thread::sleep(std::time::Duration::new(5, 0)); + + // Add network + let (cmd_success, cmd_output) = remote_command_w_output( + &api_socket, + "add-net", + Some( + format!( + "id=test1,tap=,mac={},ip={},mask=255.255.255.128{}", + guest.network.guest_mac1, + guest.network.host_ip1, + if let Some(pci_segment) = pci_segment { + format!(",pci_segment={pci_segment}") + } else { + String::new() + } + ) + .as_str(), + ), + ); + assert!(cmd_success); + + if let Some(pci_segment) = pci_segment { + assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( + "{{\"id\":\"test1\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" + ))); + } else { + assert!( + String::from_utf8_lossy(&cmd_output) + .contains("{\"id\":\"test1\",\"bdf\":\"0000:00:06.0\"}") + ); + } + + thread::sleep(std::time::Duration::new(5, 0)); + + // 2 network interfaces + default localhost ==> 3 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + guest.reboot_linux(0); + + // 2 network interfaces + default localhost ==> 3 interfaces + assert_eq!( + guest + .ssh_command("ip -o link | wc -l") + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + + // Test the same using the added network interface's IP + assert_eq!( + ssh_command_ip( + "ip -o link | wc -l", + &guest.network.guest_ip1, + DEFAULT_SSH_RETRIES, + DEFAULT_SSH_TIMEOUT + ) + .unwrap() + .trim() + .parse::() + .unwrap_or_default(), + 3 + ); + }); + + kill_child(&mut child); + let output = child.wait_with_output().unwrap(); + + handle_child_output(r, &output); +} diff --git a/cloud-hypervisor/tests/integration.rs b/cloud-hypervisor/tests/integration.rs index d67570ee2..770313eee 100644 --- a/cloud-hypervisor/tests/integration.rs +++ b/cloud-hypervisor/tests/integration.rs @@ -4936,189 +4936,25 @@ mod common_parallel { #[test] fn test_net_hotplug() { - _test_net_hotplug(None); - } - - #[test] - fn test_net_multi_segment_hotplug() { - _test_net_hotplug(Some(15)); - } - - fn _test_net_hotplug(pci_segment: Option) { - let disk_config = UbuntuDiskConfig::new(JAMMY_IMAGE_NAME.to_string()); - let guest = Guest::new(Box::new(disk_config)); - #[cfg(target_arch = "x86_64")] let kernel_path = direct_kernel_boot_path(); #[cfg(target_arch = "aarch64")] let kernel_path = edk2_path(); + let guest = + basic_regular_guest!(JAMMY_IMAGE_NAME).with_kernel_path(kernel_path.to_str().unwrap()); - let api_socket = temp_api_path(&guest.tmp_dir); + _test_net_hotplug(&guest, MAX_NUM_PCI_SEGMENTS, None); + } - // Boot without network - let mut cmd = GuestCommand::new(&guest); - - cmd.args(["--api-socket", &api_socket]) - .default_cpus() - .default_memory() - .args(["--kernel", kernel_path.to_str().unwrap()]) - .args(["--cmdline", DIRECT_KERNEL_BOOT_CMDLINE]) - .default_net() - .default_disks() - .capture_output(); - - if pci_segment.is_some() { - cmd.args([ - "--platform", - &format!("num_pci_segments={MAX_NUM_PCI_SEGMENTS}"), - ]); - } - - let mut child = cmd.spawn().unwrap(); - - guest.wait_vm_boot().unwrap(); - - let r = std::panic::catch_unwind(|| { - // Add network - let (cmd_success, cmd_output) = remote_command_w_output( - &api_socket, - "add-net", - Some( - format!( - "id=test0,tap=,mac={},ip={},mask=255.255.255.128{}", - guest.network.guest_mac1, - guest.network.host_ip1, - if let Some(pci_segment) = pci_segment { - format!(",pci_segment={pci_segment}") - } else { - String::new() - } - ) - .as_str(), - ), - ); - assert!(cmd_success); - - if let Some(pci_segment) = pci_segment { - assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( - "{{\"id\":\"test0\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" - ))); - } else { - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test0\",\"bdf\":\"0000:00:06.0\"}") - ); - } - - thread::sleep(std::time::Duration::new(5, 0)); - - // 2 network interfaces + default localhost ==> 3 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - // Test the same using the added network interface's IP - assert_eq!( - ssh_command_ip( - "ip -o link | wc -l", - &guest.network.guest_ip1, - DEFAULT_SSH_RETRIES, - DEFAULT_SSH_TIMEOUT - ) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - // Remove network - assert!(remote_command(&api_socket, "remove-device", Some("test0"),)); - thread::sleep(std::time::Duration::new(5, 0)); - - // Add network - let (cmd_success, cmd_output) = remote_command_w_output( - &api_socket, - "add-net", - Some( - format!( - "id=test1,tap=,mac={},ip={},mask=255.255.255.128{}", - guest.network.guest_mac1, - guest.network.host_ip1, - if let Some(pci_segment) = pci_segment { - format!(",pci_segment={pci_segment}") - } else { - String::new() - } - ) - .as_str(), - ), - ); - assert!(cmd_success); - - if let Some(pci_segment) = pci_segment { - assert!(String::from_utf8_lossy(&cmd_output).contains(&format!( - "{{\"id\":\"test1\",\"bdf\":\"{pci_segment:04x}:00:01.0\"}}" - ))); - } else { - assert!( - String::from_utf8_lossy(&cmd_output) - .contains("{\"id\":\"test1\",\"bdf\":\"0000:00:06.0\"}") - ); - } - - thread::sleep(std::time::Duration::new(5, 0)); - - // 2 network interfaces + default localhost ==> 3 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - guest.reboot_linux(0); - - // 2 network interfaces + default localhost ==> 3 interfaces - assert_eq!( - guest - .ssh_command("ip -o link | wc -l") - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - - // Test the same using the added network interface's IP - assert_eq!( - ssh_command_ip( - "ip -o link | wc -l", - &guest.network.guest_ip1, - DEFAULT_SSH_RETRIES, - DEFAULT_SSH_TIMEOUT - ) - .unwrap() - .trim() - .parse::() - .unwrap_or_default(), - 3 - ); - }); - - kill_child(&mut child); - let output = child.wait_with_output().unwrap(); - - handle_child_output(r, &output); + #[test] + fn test_net_multi_segment_hotplug() { + #[cfg(target_arch = "x86_64")] + let kernel_path = direct_kernel_boot_path(); + #[cfg(target_arch = "aarch64")] + let kernel_path = edk2_path(); + let guest = + basic_regular_guest!(JAMMY_IMAGE_NAME).with_kernel_path(kernel_path.to_str().unwrap()); + _test_net_hotplug(&guest, MAX_NUM_PCI_SEGMENTS, Some(15)); } #[test]