From 133132db7f3472effd0edf5c1b8f532f34f26e56 Mon Sep 17 00:00:00 2001 From: Aditya Nagesh Date: Mon, 29 Sep 2025 04:39:02 +0000 Subject: [PATCH 1/4] Remove use_ipv6 from RemoteNode The field was intended to be set via connection info mechanism. The connection mechanism was removed in commit 43e6a3d95 --- lisa/schema.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lisa/schema.py b/lisa/schema.py index bc308e056f..dcc481a385 100644 --- a/lisa/schema.py +++ b/lisa/schema.py @@ -1314,7 +1314,6 @@ class RemoteNode(Node): username: str = constants.DEFAULT_USER_NAME password: str = "" private_key_file: str = "" - use_ipv6: bool = False def __post_init__(self, *args: Any, **kwargs: Any) -> None: add_secret(self.username, PATTERN_HEADTAIL) From 0bc66a80deae16075df844de4b2660c0645e30b7 Mon Sep 17 00:00:00 2001 From: Aditya Nagesh Date: Mon, 29 Sep 2025 04:45:13 +0000 Subject: [PATCH 2/4] IP Tool: Add parameter to get ipv6 address --- lisa/tools/ip.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lisa/tools/ip.py b/lisa/tools/ip.py index 6c0f4f9d64..3c03f93745 100644 --- a/lisa/tools/ip.py +++ b/lisa/tools/ip.py @@ -12,7 +12,7 @@ from lisa.tools import Cat from lisa.tools.start_configuration import StartConfiguration from lisa.tools.whoami import Whoami -from lisa.util import LisaException, find_patterns_in_lines +from lisa.util import LisaException, find_patterns_in_lines, get_matched_str class IpInfo: @@ -340,11 +340,19 @@ def setup_tap(self, name: str, bridge: str) -> None: # start interface self.up(name) - def get_ip_address(self, nic_name: str) -> str: - result = self.run(f"addr show {nic_name}", force_run=True, sudo=True) - matched = self._get_matched_dict(result.stdout) - assert "ip_addr" in matched, f"not find ip address for nic {nic_name}" - return matched["ip_addr"] + def get_ip_address(self, nic_name: str, ipv6: bool = False) -> str: + if ipv6: + result = self.run(f"-6 addr show {nic_name}", force_run=True, sudo=True) + # Regex to match IPv6 addresses with global scope + # Example: inet6 2001:db8::5/128 scope global dynamic noprefixroute + ipv6_pattern = re.compile(r"inet6\s+([0-9a-fA-F:]+)\/\d+\s+scope\s+global") + ipv6_address = get_matched_str(result.stdout, ipv6_pattern) + return ipv6_address + else: + result = self.run(f"addr show {nic_name}", force_run=True, sudo=True) + matched = self._get_matched_dict(result.stdout) + assert "ip_addr" in matched, f"not find ip address for nic {nic_name}" + return matched["ip_addr"] def get_default_route_info(self) -> tuple[str, str]: result = self.run("route", force_run=True, sudo=True) @@ -462,7 +470,10 @@ def get_mac(self, nic_name: str) -> str: assert_that(matched).described_as("could not find mac address").is_length(1) return str(matched[0][0]) - def get_ip_address(self, nic_name: str) -> str: + def get_ip_address(self, nic_name: str, ipv6: bool = False) -> str: + if ipv6: + raise NotImplementedError("IPv6 support not implemented for FreeBSD") + output = self.run( nic_name, force_run=True, From 86c103f6a216bd0e51401beddfbe82ee185e50a7 Mon Sep 17 00:00:00 2001 From: Aditya Nagesh Date: Mon, 29 Sep 2025 05:14:45 +0000 Subject: [PATCH 3/4] Add Ipv6 support for lagscope and ntttcp --- lisa/tools/lagscope.py | 10 +++++++++- lisa/tools/ntttcp.py | 18 +++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lisa/tools/lagscope.py b/lisa/tools/lagscope.py index d69d4078ef..05918004ff 100644 --- a/lisa/tools/lagscope.py +++ b/lisa/tools/lagscope.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +import ipaddress import re from decimal import Decimal from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, cast @@ -134,8 +135,11 @@ def restore_busy_poll(self) -> None: def run_as_server_async(self, ip: str = "") -> Process: # -r: run as a receiver # -rip: run as server mode with specified ip address + # -6: use IPv6 cmd = "" if ip: + if ipaddress.ip_address(ip).version == 6: + cmd += " -6 " cmd += f" -r{ip}" else: cmd += " -r" @@ -171,7 +175,11 @@ def run_as_client_async( # -c: count of histogram intervals # -R: dumps raw latencies into csv file # -D: run as daemon - cmd = f"{self.command} -s{server_ip} " + # -6: use IPv6 + cmd = f"{self.command}" + if ipaddress.ip_address(server_ip).version == 6: + cmd += " -6 " + cmd += f" -s{server_ip} " if run_time_seconds: cmd += f" -t{run_time_seconds} " if count_of_histogram_intervals: diff --git a/lisa/tools/ntttcp.py b/lisa/tools/ntttcp.py index 0118c8967c..b29011eb8c 100644 --- a/lisa/tools/ntttcp.py +++ b/lisa/tools/ntttcp.py @@ -1,6 +1,7 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +import ipaddress import re import time from decimal import Decimal @@ -216,6 +217,8 @@ def run_as_server_async( ) -> Process: cmd = "" if server_ip: + if ipaddress.ip_address(server_ip).version == 6: + cmd += " -6 " cmd += f" -r{server_ip} " cmd += ( f" -P {ports_count} -t {run_time_seconds} -W {warm_up_time_seconds} " @@ -326,7 +329,10 @@ def run_as_client( # the devices specified by the differentiator # Examples for differentiator: Hyper-V PCIe MSI, mlx4, Hypervisor callback # interrupts - cmd = ( + cmd = "" + if ipaddress.ip_address(server_ip).version == 6: + cmd += " -6 " + cmd += ( f" -s{server_ip} -P {ports_count} -n {threads_count} -t {run_time_seconds} " f"-W {warm_up_time_seconds} -C {cool_down_time_seconds} -b {buffer_size}k " f"--show-nic-packets {nic_name} " @@ -779,7 +785,10 @@ def run_as_server_async( ) # Setup command - cmd = ( + cmd = "" + if ipaddress.ip_address(server_ip).version == 6: + cmd += " -6 " + cmd += ( f" -r{server_ip} -P {ports_count} -t {run_time_seconds} -b {buffer_size}k " ) if run_as_daemon: @@ -815,7 +824,10 @@ def run_as_client( "Paramers nic_name, cool_down_time_seconds, warm_up_time_seconds, " "use_epoll and dev_differentiator are not supported in FreeBSD" ) - cmd = ( + cmd = "" + if ipaddress.ip_address(server_ip).version == 6: + cmd += " -6 " + cmd += ( f" -s{server_ip} -P {ports_count} -n {threads_count}" f" -t {run_time_seconds} -b {buffer_size}k " ) From 289f6eb67d801c7acc30b60a535b039efc0dd078 Mon Sep 17 00:00:00 2001 From: Aditya Nagesh Date: Mon, 29 Sep 2025 14:36:04 +0000 Subject: [PATCH 4/4] Add ipv6_internal and ipv6_public --- lisa/features/network_interface.py | 10 +++++ lisa/schema.py | 20 ++++++++++ .../sut_orchestrator/azure/arm_template.bicep | 16 ++++++-- .../azure/autogen_arm_template.json | 40 +++++++++++++++---- lisa/sut_orchestrator/azure/common.py | 15 +++---- .../azure/nested_nodes_nics.bicep | 6 ++- lisa/sut_orchestrator/azure/platform_.py | 27 +++++++++++++ microsoft/testsuites/network/sriov.py | 24 +++++++++++ microsoft/testsuites/network/synthetic.py | 29 ++++++++++++++ 9 files changed, 167 insertions(+), 20 deletions(-) diff --git a/lisa/features/network_interface.py b/lisa/features/network_interface.py index a488e7fcbd..2a8454b687 100644 --- a/lisa/features/network_interface.py +++ b/lisa/features/network_interface.py @@ -70,3 +70,13 @@ def _initialize(self, *args: Any, **kwargs: Any) -> None: Synthetic = partial( NetworkInterfaceOptionSettings, data_path=schema.NetworkDataPath.Synthetic ) +SriovIpv6Internal = partial( + NetworkInterfaceOptionSettings, + data_path=schema.NetworkDataPath.Sriov, + ip_version=schema.IpProtocol.ipv6, +) +SyntheticIpv6Internal = partial( + NetworkInterfaceOptionSettings, + data_path=schema.NetworkDataPath.Synthetic, + ip_version=schema.IpProtocol.ipv6, +) diff --git a/lisa/schema.py b/lisa/schema.py index dcc481a385..4b7fbbe38f 100644 --- a/lisa/schema.py +++ b/lisa/schema.py @@ -709,6 +709,11 @@ class NetworkDataPath(str, Enum): Sriov = "Sriov" +class IpProtocol(str, Enum): + ipv4 = "IPv4" + ipv6 = "IPv6" + + _network_data_path_priority: List[NetworkDataPath] = [ NetworkDataPath.Sriov, NetworkDataPath.Synthetic, @@ -753,6 +758,21 @@ class NetworkInterfaceOptionSettings(FeatureSettings): allow_none=True, decoder=search_space.decode_count_space ), ) + ip_version: Optional[Union[search_space.SetSpace[IpProtocol], IpProtocol]] = field( + default_factory=partial( + search_space.SetSpace, + items=[IpProtocol.ipv4, IpProtocol.ipv6], + ), + metadata=field_metadata( + decoder=partial( + search_space.decode_nullable_set_space, + base_type=IpProtocol, + default_values=[IpProtocol.ipv4, IpProtocol.ipv6], + ), + required=False, + allow_none=True, + ), + ) def __eq__(self, o: object) -> bool: if not super().__eq__(o): diff --git a/lisa/sut_orchestrator/azure/arm_template.bicep b/lisa/sut_orchestrator/azure/arm_template.bicep index dbdd9d6b4b..7cef1d612e 100644 --- a/lisa/sut_orchestrator/azure/arm_template.bicep +++ b/lisa/sut_orchestrator/azure/arm_template.bicep @@ -49,9 +49,15 @@ param is_ultradisk bool = false @description('IP Service Tags') param ip_service_tags object -@description('whether to use ipv6') +@description('whether to use ipv6 (legacy parameter for backward compatibility)') param use_ipv6 bool = false +@description('whether to use ipv6 for public addresses') +param use_ipv6_public bool = false + +@description('whether to use ipv6 for internal/private addresses') +param use_ipv6_internal bool = false + @description('whether to enable network outbound access') param enable_vm_nat bool @@ -233,6 +239,8 @@ module nodes_nics './nested_nodes_nics.bicep' = [for i in range(0, node_count): enable_sriov: nodes[i].enable_sriov tags: tags use_ipv6: use_ipv6 + use_ipv6_public: use_ipv6_public + use_ipv6_internal: use_ipv6_internal create_public_address: create_public_address } dependsOn: [ @@ -249,7 +257,7 @@ resource virtual_network_name_resource 'Microsoft.Network/virtualNetworks@2024-0 addressSpace: { addressPrefixes: concat( ['10.0.0.0/16'], - use_ipv6 ? ['2001:db8::/32'] : [] + (use_ipv6 || use_ipv6_internal) ? ['2001:db8::/32'] : [] ) } subnets: [for j in range(0, subnet_count): { @@ -257,7 +265,7 @@ resource virtual_network_name_resource 'Microsoft.Network/virtualNetworks@2024-0 properties: { addressPrefixes: concat( ['10.0.${j}.0/24'], - use_ipv6 ? ['2001:db8:${j}::/64'] : [] + (use_ipv6 || use_ipv6_internal) ? ['2001:db8:${j}::/64'] : [] ) defaultOutboundAccess: enable_vm_nat networkSecurityGroup: { @@ -345,7 +353,7 @@ resource nodes_public_ip 'Microsoft.Network/publicIPAddresses@2020-05-01' = [for zones: (use_availability_zones ? availability_zones : null) }] -resource nodes_public_ip_ipv6 'Microsoft.Network/publicIPAddresses@2020-05-01' = [for i in range(0, node_count): if (use_ipv6 && create_public_address) { +resource nodes_public_ip_ipv6 'Microsoft.Network/publicIPAddresses@2020-05-01' = [for i in range(0, node_count): if ((use_ipv6 || use_ipv6_public) && create_public_address) { name: '${nodes[i].name}-public-ipv6' location: location tags: tags diff --git a/lisa/sut_orchestrator/azure/autogen_arm_template.json b/lisa/sut_orchestrator/azure/autogen_arm_template.json index 6ca0881abb..10b1e17e5b 100644 --- a/lisa/sut_orchestrator/azure/autogen_arm_template.json +++ b/lisa/sut_orchestrator/azure/autogen_arm_template.json @@ -6,7 +6,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "16291477761159155605" + "templateHash": "7375950074177020167" } }, "functions": [ @@ -522,7 +522,21 @@ "type": "bool", "defaultValue": false, "metadata": { - "description": "whether to use ipv6" + "description": "whether to use ipv6 (legacy parameter for backward compatibility)" + } + }, + "use_ipv6_public": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "whether to use ipv6 for public IP addresses" + } + }, + "use_ipv6_internal": { + "type": "bool", + "defaultValue": false, + "metadata": { + "description": "whether to use ipv6 for internal IP addresses" } }, "enable_vm_nat": { @@ -585,7 +599,7 @@ "input": { "name": "[format('{0}{1}', parameters('subnet_prefix'), range(0, parameters('subnet_count'))[copyIndex('subnets')])]", "properties": { - "addressPrefixes": "[concat(createArray(format('10.0.{0}.0/24', range(0, parameters('subnet_count'))[copyIndex('subnets')])), if(parameters('use_ipv6'), createArray(format('2001:db8:{0}::/64', range(0, parameters('subnet_count'))[copyIndex('subnets')])), createArray()))]", + "addressPrefixes": "[concat(createArray(format('10.0.{0}.0/24', range(0, parameters('subnet_count'))[copyIndex('subnets')])), if(or(parameters('use_ipv6'), parameters('use_ipv6_internal')), createArray(format('2001:db8:{0}::/64', range(0, parameters('subnet_count'))[copyIndex('subnets')])), createArray()))]", "defaultOutboundAccess": "[parameters('enable_vm_nat')]", "networkSecurityGroup": { "id": "[resourceId('Microsoft.Network/networkSecurityGroups', format('{0}-nsg', toLower(parameters('virtual_network_name'))))]" @@ -595,7 +609,7 @@ } ], "addressSpace": { - "addressPrefixes": "[concat(createArray('10.0.0.0/16'), if(parameters('use_ipv6'), createArray('2001:db8::/32'), createArray()))]" + "addressPrefixes": "[concat(createArray('10.0.0.0/16'), if(or(parameters('use_ipv6'), parameters('use_ipv6_internal')), createArray('2001:db8::/32'), createArray()))]" } }, "dependsOn": [ @@ -690,7 +704,7 @@ "name": "nodes_public_ip_ipv6", "count": "[length(range(0, variables('node_count')))]" }, - "condition": "[and(parameters('use_ipv6'), parameters('create_public_address'))]", + "condition": "[and(or(parameters('use_ipv6'), parameters('use_ipv6_public')), parameters('create_public_address'))]", "type": "Microsoft.Network/publicIPAddresses", "apiVersion": "2020-05-01", "name": "[format('{0}-public-ipv6', parameters('nodes')[range(0, variables('node_count'))[copyIndex()]].name)]", @@ -887,6 +901,12 @@ "use_ipv6": { "value": "[parameters('use_ipv6')]" }, + "use_ipv6_public": { + "value": "[parameters('use_ipv6_public')]" + }, + "use_ipv6_internal": { + "value": "[parameters('use_ipv6_internal')]" + }, "create_public_address": { "value": "[parameters('create_public_address')]" } @@ -899,7 +919,7 @@ "_generator": { "name": "bicep", "version": "0.37.4.10188", - "templateHash": "17932923534203698710" + "templateHash": "797006272337831175" } }, "functions": [ @@ -955,6 +975,12 @@ "use_ipv6": { "type": "bool" }, + "use_ipv6_public": { + "type": "bool" + }, + "use_ipv6_internal": { + "type": "bool" + }, "create_public_address": { "type": "bool" } @@ -975,7 +1001,7 @@ "location": "[parameters('location')]", "tags": "[parameters('tags')]", "properties": { - "ipConfigurations": "[concat(createArray(createObject('name', 'IPv4Config', 'properties', createObject('privateIPAddressVersion', 'IPv4', 'publicIPAddress', if(and(equals(0, range(0, parameters('nic_count'))[copyIndex()]), parameters('create_public_address')), variables('publicIpAddress'), null()), 'subnet', createObject('id', if(not(empty(parameters('existing_subnet_ref'))), parameters('existing_subnet_ref'), format('{0}/subnets/{1}{2}', parameters('vnet_id'), parameters('subnet_prefix'), range(0, parameters('nic_count'))[copyIndex()]))), 'privateIPAllocationMethod', 'Dynamic'))), if(parameters('use_ipv6'), createArray(createObject('name', 'IPv6Config', 'properties', createObject('privateIPAddressVersion', 'IPv6', 'publicIPAddress', if(and(equals(0, range(0, parameters('nic_count'))[copyIndex()]), parameters('create_public_address')), variables('publicIpAddressV6'), null()), 'subnet', createObject('id', if(not(empty(parameters('existing_subnet_ref'))), parameters('existing_subnet_ref'), format('{0}/subnets/{1}{2}', parameters('vnet_id'), parameters('subnet_prefix'), range(0, parameters('nic_count'))[copyIndex()]))), 'privateIPAllocationMethod', 'Dynamic'))), createArray()))]", + "ipConfigurations": "[concat(createArray(createObject('name', 'IPv4Config', 'properties', createObject('privateIPAddressVersion', 'IPv4', 'publicIPAddress', if(and(equals(0, range(0, parameters('nic_count'))[copyIndex()]), parameters('create_public_address')), variables('publicIpAddress'), null()), 'subnet', createObject('id', if(not(empty(parameters('existing_subnet_ref'))), parameters('existing_subnet_ref'), format('{0}/subnets/{1}{2}', parameters('vnet_id'), parameters('subnet_prefix'), range(0, parameters('nic_count'))[copyIndex()]))), 'privateIPAllocationMethod', 'Dynamic'))), if(or(parameters('use_ipv6'), parameters('use_ipv6_internal')), createArray(createObject('name', 'IPv6Config', 'properties', createObject('privateIPAddressVersion', 'IPv6', 'publicIPAddress', if(and(and(equals(0, range(0, parameters('nic_count'))[copyIndex()]), parameters('create_public_address')), or(parameters('use_ipv6'), parameters('use_ipv6_public'))), variables('publicIpAddressV6'), null()), 'subnet', createObject('id', if(not(empty(parameters('existing_subnet_ref'))), parameters('existing_subnet_ref'), format('{0}/subnets/{1}{2}', parameters('vnet_id'), parameters('subnet_prefix'), range(0, parameters('nic_count'))[copyIndex()]))), 'privateIPAllocationMethod', 'Dynamic'))), createArray()))]", "enableAcceleratedNetworking": "[parameters('enable_sriov')]" } } diff --git a/lisa/sut_orchestrator/azure/common.py b/lisa/sut_orchestrator/azure/common.py index 0e13b3c01e..73363ab8f4 100644 --- a/lisa/sut_orchestrator/azure/common.py +++ b/lisa/sut_orchestrator/azure/common.py @@ -1126,11 +1126,6 @@ class DiskPlacementType(str, Enum): NVME = "NvmeDisk" -class IpProtocol(str, Enum): - ipv4 = "IPv4" - ipv6 = "IPv6" - - def get_disk_placement_priority() -> List[DiskPlacementType]: return [ DiskPlacementType.NVME, @@ -1214,6 +1209,8 @@ class AzureArmParameter: subnet_prefix: str = AZURE_SUBNET_PREFIX is_ultradisk: bool = False use_ipv6: bool = False + use_ipv6_public: bool = False + use_ipv6_internal: bool = False enable_vm_nat: bool = False create_public_address: bool = True source_address_prefixes: List[str] = field(default_factory=list) @@ -2274,7 +2271,10 @@ def get_primary_ip_addresses( nic_index = 1 ip_config = nic.ip_configurations[nic_index] - if use_ipv6 and ip_config.private_ip_address_version != IpProtocol.ipv6: + if ( + use_ipv6 + and ip_config.private_ip_address_version != schema.IpProtocol.ipv6 + ): raise LisaException(f"private address is not IPv6 in nic {nic.name}") private_ip = ip_config.private_ip_address @@ -2291,7 +2291,8 @@ def get_primary_ip_addresses( ) if ( use_ipv6 - and public_ip_address.public_ip_address_version != IpProtocol.ipv6 + and public_ip_address.public_ip_address_version + != schema.IpProtocol.ipv6 ): raise LisaException(f"public address is not IPv6 in nic {nic.name}") diff --git a/lisa/sut_orchestrator/azure/nested_nodes_nics.bicep b/lisa/sut_orchestrator/azure/nested_nodes_nics.bicep index 8f283ca78b..56c53570ae 100644 --- a/lisa/sut_orchestrator/azure/nested_nodes_nics.bicep +++ b/lisa/sut_orchestrator/azure/nested_nodes_nics.bicep @@ -7,6 +7,8 @@ param existing_subnet_ref string param enable_sriov bool param tags object param use_ipv6 bool +param use_ipv6_public bool = false +param use_ipv6_internal bool = false param create_public_address bool func getPublicIpAddress(vmName string, publicIpName string) object => { @@ -35,12 +37,12 @@ resource vm_nics 'Microsoft.Network/networkInterfaces@2023-06-01' = [for i in ra } } ], - use_ipv6 ? [ + (use_ipv6 || use_ipv6_internal) ? [ { name: 'IPv6Config' properties: { privateIPAddressVersion: 'IPv6' - publicIPAddress: ((0 == i && create_public_address) ? publicIpAddressV6 : null) + publicIPAddress: ((0 == i && create_public_address && (use_ipv6 || use_ipv6_public)) ? publicIpAddressV6 : null) subnet: { id: ((!empty(existing_subnet_ref)) ? existing_subnet_ref : '${vnet_id}/subnets/${subnet_prefix}${i}') } diff --git a/lisa/sut_orchestrator/azure/platform_.py b/lisa/sut_orchestrator/azure/platform_.py index b52f957fd9..9e91dfd16d 100644 --- a/lisa/sut_orchestrator/azure/platform_.py +++ b/lisa/sut_orchestrator/azure/platform_.py @@ -312,7 +312,11 @@ class AzurePlatformSchema: tags: Optional[Dict[str, Any]] = field(default=None) use_public_address: bool = field(default=True) create_public_address: bool = field(default=True) + # use_ipv6 should be deprecated, use use_ipv6_internal + # and use_ipv6_public instead use_ipv6: bool = field(default=False) + use_ipv6_internal: bool = field(default=False) + use_ipv6_public: bool = field(default=False) ip_service_tags: Optional[Dict[str, str]] = field(default=None) # Default outbound access is disabled for better security and control. # As of September 30, 2025, default outbound access for new deployments @@ -376,6 +380,8 @@ def __post_init__(self, *args: Any, **kwargs: Any) -> None: "subnet_prefix", "use_public_address", "use_ipv6", + "use_ipv6_public", + "use_ipv6_internal", "enable_vm_nat", "source_address_prefixes", "create_public_address", @@ -1211,6 +1217,8 @@ def _create_deployment_parameters( self._azure_runbook.virtual_network_name or AZURE_VIRTUAL_NETWORK_NAME ) arm_parameters.use_ipv6 = self._azure_runbook.use_ipv6 + arm_parameters.use_ipv6_public = self._azure_runbook.use_ipv6_public + arm_parameters.use_ipv6_internal = self._azure_runbook.use_ipv6_internal is_windows: bool = False arm_parameters.admin_username = self.runbook.admin_username @@ -1307,6 +1315,25 @@ def _create_deployment_parameters( if f.type not in features_settings: features_settings[f.type] = f + # Check for IPv6 network interface requirements and set deployment flags + if ( + node.capability.network_interface + and hasattr(node.capability.network_interface, 'ip_version') + and node.capability.network_interface.ip_version is not None + ): + # Handle both single value and SetSpace cases + ip_version = node.capability.network_interface.ip_version + if hasattr(ip_version, 'items'): + # SetSpace case - check if IPv6 is in the set + ipv6_required = schema.IpProtocol.ipv6 in ip_version.items + else: + # Single value case + ipv6_required = ip_version == schema.IpProtocol.ipv6 + + if ipv6_required: + log.info("IPv6 network interface requirement detected, enabling IPv6 internal") + arm_parameters.use_ipv6_internal = True + log.info(f"vm setting: {azure_node_runbook}") if is_windows: diff --git a/microsoft/testsuites/network/sriov.py b/microsoft/testsuites/network/sriov.py index bb218dc259..07ec73b2f5 100644 --- a/microsoft/testsuites/network/sriov.py +++ b/microsoft/testsuites/network/sriov.py @@ -25,6 +25,7 @@ ) from lisa.base_tools import Systemctl from lisa.features import NetworkInterface, SerialConsole, StartStop +from lisa.features.network_interface import SriovIpv6Internal from lisa.nic import NicInfo from lisa.operating_system import BSD, Posix, Windows from lisa.sut_orchestrator import AZURE, HYPERV @@ -33,6 +34,7 @@ Ethtool, Firewall, InterruptInspector, + Ip, Iperf3, Journalctl, Lscpu, @@ -161,6 +163,28 @@ def verify_sriov_single_vf_connection(self, environment: Environment) -> None: sriov_basic_test(environment) sriov_vf_connection_test(environment, vm_nics) + @TestCaseMetadata( + description=""" + Verify IPv6 networking functionality with SR-IOV + 1. Create an Azure VM with AN Enabled and IPv6 enabled + 2. Verify that the NIC has an IPv6 address + """, + priority=2, + use_new_environment=True, + requirement=simple_requirement( + network_interface=SriovIpv6Internal(), + ), + ) + def verify_sriov_ipv6_basic(self, node: Node, log: Logger) -> None: + ip = node.tools[Ip] + # for each nic, verify that ipv6 exists using nic name + for nic in node.nics.nics.keys(): + nic_ipv6 = ip.get_ip_address(nic, ipv6=True) + assert_that( + nic_ipv6, + f"Expected IPv6 address but found none on nic {nic}", + ).is_not_empty() + @TestCaseMetadata( description=""" This case needs 2 nodes and 64 Vcpus. And it verifies module of sriov network diff --git a/microsoft/testsuites/network/synthetic.py b/microsoft/testsuites/network/synthetic.py index 3503d73cc5..f60c8f7fad 100644 --- a/microsoft/testsuites/network/synthetic.py +++ b/microsoft/testsuites/network/synthetic.py @@ -1,7 +1,11 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT license. +from assertpy import assert_that + from lisa import ( Environment, + Logger, + Node, TestCaseMetadata, TestSuite, TestSuiteMetadata, @@ -9,6 +13,8 @@ simple_requirement, ) from lisa.features import NetworkInterface, StartStop +from lisa.features.network_interface import SyntheticIpv6Internal +from lisa.tools import Ip from .common import initialize_nic_info, remove_extra_nics, restore_extra_nics @@ -191,3 +197,26 @@ def verify_synthetic_add_max_nics_one_by_one_after_provision( initialize_nic_info(environment, is_sriov=False) finally: restore_extra_nics(environment) + + @TestCaseMetadata( + description=""" + Verify IPv6 networking functionality with synthetic network interfaces + 1. Create an Azure VM with IPv6 enabled + 2. Verify that each NIC has an IPv6 address + """, + priority=2, + use_new_environment=True, + requirement=simple_requirement( + network_interface=SyntheticIpv6Internal(), + ), + ) + def verify_synthetic_ipv6_basic(self, node: Node, log: Logger) -> None: + ip = node.tools[Ip] + # for each nic, verify that ipv6 exists using nic name + for nic in node.nics.nics.keys(): + nic_ipv6 = ip.get_ip_address(nic, ipv6=True) + assert_that( + nic_ipv6, + f"Expected IPv6 address but found none on nic {nic}", + ).is_not_empty() +