diff --git a/lisa/microsoft/testsuites/performance/common.py b/lisa/microsoft/testsuites/performance/common.py index 1680ee475d..24e7eef5b8 100644 --- a/lisa/microsoft/testsuites/performance/common.py +++ b/lisa/microsoft/testsuites/performance/common.py @@ -251,12 +251,16 @@ def perf_tcp_latency(test_result: TestResult) -> List[NetworkLatencyPerformanceM server = cast(RemoteNode, environment.nodes[1]) client_lagscope = client.tools[Lagscope] server_lagscope = server.tools[Lagscope] + # lagscope builds IPv4-style CLI args (-s/-r) with no IPv6 bracket + # handling. On dual-stack (use_ipv6) environments server.internal_address is + # IPv6, so resolve the server's internal IPv4 to bind/connect over instead. + server_comm_address = server.nics.get_internal_ipv4_address() try: for lagscope in [client_lagscope, server_lagscope]: lagscope.set_busy_poll() - server_lagscope.run_as_server_async(ip=server.internal_address) + server_lagscope.run_as_server_async(ip=server_comm_address) latency_perf_messages = client_lagscope.create_latency_performance_messages( - client_lagscope.run_as_client(server_ip=server.internal_address), + client_lagscope.run_as_client(server_ip=server_comm_address), inspect.stack()[1][3], test_result, ) @@ -303,17 +307,21 @@ def perf_tcp_pps( ] ) + # netperf builds IPv4-style CLI args with no IPv6 bracket handling. On a + # dual-stack (use_ipv6) environment internal_address is IPv6, so resolve + # the internal IPv4 to bind/target instead. + server_comm_ip = server_node.nics.get_internal_ipv4_address() server_interface_ip: str = "" client_interface_ip: str = "" if use_internal_address: - assert_that(server_node.internal_address).described_as( + server_interface_ip = server_comm_ip + client_interface_ip = client_node.nics.get_internal_ipv4_address() + assert_that(server_interface_ip).described_as( "Server Node: internal address is not set" ).is_not_empty() - assert_that(client_node.internal_address).described_as( + assert_that(client_interface_ip).described_as( "Client Node: internal address is not set" ).is_not_empty() - server_interface_ip = server_node.internal_address - client_interface_ip = client_node.internal_address cpu = client_node.tools[Lscpu] thread_count = cpu.get_thread_count() @@ -326,10 +334,10 @@ def perf_tcp_pps( for port in ports: server_netperf.run_as_server(port, interface_ip=server_interface_ip) for port in ports: - # Use server.internal_address as target since netperf client needs + # Use the server's internal IPv4 as target since netperf client needs # the server's IP (which may differ from the interface it binds to) client_netperf.run_as_client_async( - server_ip=server_node.internal_address, + server_ip=server_comm_ip, core_count=thread_count, port=port, interface_ip=client_interface_ip, @@ -381,6 +389,11 @@ def perf_ntttcp( # noqa: C901 # if it's not filled, assume it's called by case directly. test_case_name = inspect.stack()[1][3] + # ntttcp and lagscope run over IPv4. On dual-stack (use_ipv6) environments + # server.internal_address is IPv6, so resolve the server's internal IPv4 to + # use as the ntttcp/lagscope target. + server_comm_address = server.nics.get_internal_ipv4_address() + if connections is None: if udp_mode: connections = NTTTCP_UDP_CONCURRENCY @@ -535,7 +548,7 @@ def perf_ntttcp( # noqa: C901 ip=( lagscope_server_ip if lagscope_server_ip is not None - else server.internal_address + else server_comm_address ), no_debug_log=True, ) @@ -545,9 +558,7 @@ def perf_ntttcp( # noqa: C901 server_result = server_ntttcp.run_as_server_async( server_nic_name, server_ip=( - server.internal_address - if isinstance(server.os, BSD) - else "" + server_comm_address if isinstance(server.os, BSD) else "" ), ports_count=num_threads_p, buffer_size=buffer_size, @@ -562,7 +573,7 @@ def perf_ntttcp( # noqa: C901 # Start lagscope client to measure latency during the # ntttcp test client_lagscope_process = client_lagscope.run_as_client_async( - server_ip=server.internal_address, + server_ip=server_comm_address, ping_count=0, run_time_seconds=( run_time_seconds @@ -582,7 +593,7 @@ def perf_ntttcp( # noqa: C901 # Use daemon mode to run in background, then monitor process client_ntttcp_result = client_ntttcp.run_as_client( client_nic_name, - server.internal_address, + server_comm_address, buffer_size=buffer_size, threads_count=num_threads_n, ports_count=num_threads_p, @@ -781,11 +792,15 @@ def perf_iperf( ) test_case_name = inspect.stack()[1][3] iperf3_messages_list: List[Any] = [] + # iperf3 is invoked with ip_version="4"; on a dual-stack (use_ipv6) + # environment internal_address is IPv6, so resolve the server's internal + # IPv4 to use as the client target / bind interface. + server_comm_ip = server.nics.get_internal_ipv4_address() server_interface_ip = "" client_interface_ip = "" if run_with_internal_address: - server_interface_ip = server.internal_address - client_interface_ip = client.internal_address + server_interface_ip = server_comm_ip + client_interface_ip = client.nics.get_internal_ipv4_address() assert server_interface_ip, "Server Node: internal address is not set" assert client_interface_ip, "Client Node: internal address is not set" @@ -831,7 +846,7 @@ def perf_iperf( current_client_iperf_instances += 1 client_iperf3_process_list.append( client_iperf3.run_as_client_async( - server.internal_address, + server_comm_ip, output_json=True, report_periodic=1, report_unit="g", @@ -905,7 +920,9 @@ def perf_sockperf( "sockperf: Warmup stage", timeout=30, ) - client_output = client.tools[Sockperf].run_client(mode, server.internal_address) + client_output = client.tools[Sockperf].run_client( + mode, server.nics.get_internal_ipv4_address() + ) client.tools[Sockperf].create_latency_performance_message( client_output, test_case_name, test_result ) diff --git a/lisa/nic.py b/lisa/nic.py index 6412ffc9b5..0cae78a031 100644 --- a/lisa/nic.py +++ b/lisa/nic.py @@ -274,6 +274,33 @@ def get_nic_names(self) -> List[str]: def get_primary_nic(self) -> NicInfo: return self.get_nic_by_index(0) + def get_internal_ipv4_address(self) -> str: + # Return an IPv4 address usable for intra-VM communication. Prefer the + # node's internal_address when it is a valid IPv4. On a dual-stack + # (use_ipv6) environment it is IPv6, and some node types (e.g. + # LocalNode) don't define internal_address at all -- in both cases fall + # back to the primary NIC's internal IPv4, since tools that bind/connect + # over IPv4 only (ntttcp/lagscope/netperf/sockperf) need an IPv4 target. + address: str = getattr(self._node, "internal_address", "") or "" + try: + is_ipv4 = ipaddress.ip_address(address).version == 4 + except ValueError: + is_ipv4 = False + if is_ipv4: + return address + ipv4_address: str = self.get_primary_nic().ip_addr + if not ipv4_address: + raise LisaException( + f"internal address '{address}' is not a usable IPv4 and the " + f"primary NIC '{self.get_primary_nic().name}' has no IPv4 " + f"address to fall back to." + ) + self._node.log.debug( + f"internal address '{address}' is not IPv4; using the node's " + f"internal IPv4 {ipv4_address} instead." + ) + return ipv4_address + def get_secondary_nic(self) -> NicInfo: # get a nic which isn't servicing the SSH connection with lisa. # will assert if none is present.