Skip to content

Commit e35839a

Browse files
committed
Add IPv6 Feature and Netperf test
1 parent 116bfaa commit e35839a

File tree

11 files changed

+185
-13
lines changed

11 files changed

+185
-13
lines changed

lisa/features/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@
2121
from .infiniband import Infiniband
2222
from .isolated_resource import IsolatedResource
2323
from .nested_virtualization import NestedVirtualization
24-
from .network_interface import NetworkInterface, Sriov, Synthetic
24+
from .network_interface import (
25+
NetworkInterface,
26+
Sriov,
27+
SriovIPv6,
28+
Synthetic,
29+
SyntheticIPv6,
30+
)
2531
from .nfs import Nfs
2632
from .nvme import Nvme, NvmeSettings
2733
from .password_extension import PasswordExtension
@@ -69,6 +75,8 @@
6975
"SecurityProfileSettings",
7076
"SecurityProfileType",
7177
"Sriov",
78+
"SriovIPv6",
79+
"SyntheticIPv6",
7280
"StopState",
7381
"VMStatus",
7482
"Synthetic",

lisa/features/network_interface.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,3 +70,13 @@ def _initialize(self, *args: Any, **kwargs: Any) -> None:
7070
Synthetic = partial(
7171
NetworkInterfaceOptionSettings, data_path=schema.NetworkDataPath.Synthetic
7272
)
73+
SriovIPv6 = partial(
74+
NetworkInterfaceOptionSettings,
75+
data_path=schema.NetworkDataPath.Sriov,
76+
ip_version=schema.IPVersion.IPv6,
77+
)
78+
SyntheticIPv6 = partial(
79+
NetworkInterfaceOptionSettings,
80+
data_path=schema.NetworkDataPath.Synthetic,
81+
ip_version=schema.IPVersion.IPv6,
82+
)

lisa/schema.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,11 @@ class NetworkDataPath(str, Enum):
709709
Sriov = "Sriov"
710710

711711

712+
class IPVersion(str, Enum):
713+
IPv4 = "IPv4"
714+
IPv6 = "IPv6"
715+
716+
712717
_network_data_path_priority: List[NetworkDataPath] = [
713718
NetworkDataPath.Sriov,
714719
NetworkDataPath.Synthetic,
@@ -740,6 +745,21 @@ class NetworkInterfaceOptionSettings(FeatureSettings):
740745
)
741746
),
742747
)
748+
ip_version: Optional[Union[search_space.SetSpace[IPVersion], IPVersion]] = (
749+
field( # type: ignore
750+
default_factory=partial(
751+
search_space.SetSpace,
752+
items=[IPVersion.IPv4, IPVersion.IPv6],
753+
),
754+
metadata=field_metadata(
755+
decoder=partial(
756+
search_space.decode_nullable_set_space,
757+
base_type=IPVersion,
758+
default_values=[IPVersion.IPv4, IPVersion.IPv6],
759+
)
760+
),
761+
)
762+
)
743763
# nic_count is used for specifying associated nic count during provisioning vm
744764
nic_count: search_space.CountSpace = field(
745765
default_factory=partial(search_space.IntRange, min=1),
@@ -762,14 +782,15 @@ def __eq__(self, o: object) -> bool:
762782
return (
763783
self.type == o.type
764784
and self.data_path == o.data_path
785+
and self.ip_version == o.ip_version
765786
and self.nic_count == o.nic_count
766787
and self.max_nic_count == o.max_nic_count
767788
)
768789

769790
def __repr__(self) -> str:
770791
return (
771-
f"data_path:{self.data_path}, nic_count:{self.nic_count},"
772-
f" max_nic_count:{self.max_nic_count}"
792+
f"data_path:{self.data_path}, ip_version:{self.ip_version}, "
793+
f"nic_count:{self.nic_count}, max_nic_count:{self.max_nic_count}"
773794
)
774795

775796
def __str__(self) -> str:
@@ -806,6 +827,11 @@ def check(self, capability: Any) -> search_space.ResultReason:
806827
"data_path",
807828
)
808829

830+
result.merge(
831+
search_space.check_setspace(self.ip_version, capability.ip_version),
832+
"ip_version",
833+
)
834+
809835
result.merge(
810836
search_space.check_countspace(self.max_nic_count, capability.max_nic_count),
811837
"max_nic_count",
@@ -839,6 +865,7 @@ def _call_requirement_method(
839865
value.data_path = getattr(search_space, f"{method.value}_setspace_by_priority")(
840866
self.data_path, capability.data_path, _network_data_path_priority
841867
)
868+
842869
return value
843870

844871

lisa/sut_orchestrator/azure/features.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -783,9 +783,32 @@ class NetworkInterface(AzureFeatureMixin, features.NetworkInterface):
783783
def settings_type(cls) -> Type[schema.FeatureSettings]:
784784
return schema.NetworkInterfaceOptionSettings
785785

786+
@classmethod
787+
def create_setting(
788+
cls, *args: Any, **kwargs: Any
789+
) -> Optional[schema.FeatureSettings]:
790+
# All Azure VMs support synthetic and SRIOV networking
791+
# All Azure VMs can support both IPv4 and IPv6 via ARM template configuration
792+
return schema.NetworkInterfaceOptionSettings(
793+
data_path=search_space.SetSpace(
794+
items=[
795+
schema.NetworkDataPath.Synthetic,
796+
schema.NetworkDataPath.Sriov,
797+
]
798+
),
799+
ip_version=search_space.SetSpace(
800+
is_allow_set=True,
801+
items=[
802+
schema.IPVersion.IPv4,
803+
schema.IPVersion.IPv6,
804+
],
805+
),
806+
)
807+
786808
def _initialize(self, *args: Any, **kwargs: Any) -> None:
787809
super()._initialize(*args, **kwargs)
788810
self._initialize_information(self._node)
811+
789812
all_nics = self._get_all_nics()
790813
# store extra synthetic and sriov nics count
791814
# in order to restore nics status after testing which needs change nics

lisa/sut_orchestrator/azure/platform_.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1199,6 +1199,12 @@ def _create_deployment_parameters(
11991199
)
12001200
arm_parameters.use_ipv6 = self._azure_runbook.use_ipv6
12011201

1202+
# Override IPv6 setting if environment specifically requires IPv6
1203+
if not arm_parameters.use_ipv6: # Only check if not already enabled
1204+
ipv6_required = self._check_ipv6_requirements(environment)
1205+
if ipv6_required:
1206+
arm_parameters.use_ipv6 = True
1207+
12021208
is_windows: bool = False
12031209
arm_parameters.admin_username = self.runbook.admin_username
12041210
# if no key or password specified, generate the key pair
@@ -2242,6 +2248,11 @@ def _generate_max_capability(self, vm_size: str, location: str) -> AzureCapabili
22422248
](is_allow_set=True, items=[])
22432249
node_space.network_interface.data_path.add(schema.NetworkDataPath.Synthetic)
22442250
node_space.network_interface.data_path.add(schema.NetworkDataPath.Sriov)
2251+
node_space.network_interface.ip_version = search_space.SetSpace[
2252+
schema.IPVersion
2253+
](is_allow_set=True, items=[])
2254+
node_space.network_interface.ip_version.add(schema.IPVersion.IPv4)
2255+
node_space.network_interface.ip_version.add(schema.IPVersion.IPv6)
22452256
node_space.network_interface.nic_count = search_space.IntRange(min=1)
22462257
# till now, the max nic number supported in Azure is 8
22472258
node_space.network_interface.max_nic_count = 8
@@ -3054,6 +3065,29 @@ def _is_byoip_feature_registered(self) -> bool:
30543065
self._cached_byoip_registered = False
30553066
return self._cached_byoip_registered
30563067

3068+
def _check_ipv6_requirements(self, environment: Environment) -> bool:
3069+
"""Check if IPv6 is specifically required by analyzing environment requirements."""
3070+
ipv6_required = False
3071+
3072+
# Check environment runbook node requirements for IPv6 specifications
3073+
if environment.runbook and environment.runbook.nodes_requirement:
3074+
for node_requirement in environment.runbook.nodes_requirement:
3075+
if (
3076+
node_requirement.network_interface
3077+
and node_requirement.network_interface.ip_version is not None
3078+
):
3079+
ip_version = node_requirement.network_interface.ip_version
3080+
if isinstance(ip_version, search_space.SetSpace):
3081+
if schema.IPVersion.IPv6 in ip_version.items:
3082+
ipv6_required = True
3083+
break
3084+
elif isinstance(ip_version, schema.IPVersion):
3085+
if ip_version == schema.IPVersion.IPv6:
3086+
ipv6_required = True
3087+
break
3088+
3089+
return ipv6_required
3090+
30573091

30583092
def _get_allowed_locations(nodes_requirement: List[schema.NodeSpace]) -> List[str]:
30593093
existing_locations_str: str = ""

lisa/tools/ip.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from lisa.tools import Cat
1313
from lisa.tools.start_configuration import StartConfiguration
1414
from lisa.tools.whoami import Whoami
15-
from lisa.util import LisaException, find_patterns_in_lines
15+
from lisa.util import LisaException, find_patterns_in_lines, get_matched_str
1616

1717

1818
class IpInfo:
@@ -346,6 +346,17 @@ def get_ip_address(self, nic_name: str) -> str:
346346
assert "ip_addr" in matched, f"not find ip address for nic {nic_name}"
347347
return matched["ip_addr"]
348348

349+
def get_ipv6_address(self, nic_name: str) -> str:
350+
"""Get the global IPv6 address for a network interface."""
351+
result = self.run(f"-6 addr show {nic_name}", force_run=True, sudo=True)
352+
353+
# Regex to match IPv6 addresses with global scope
354+
# Example: inet6 2001:db8::5/128 scope global dynamic noprefixroute
355+
ipv6_pattern = re.compile(r"inet6\s+([0-9a-fA-F:]+)\/\d+\s+scope\s+global")
356+
357+
ipv6_address = get_matched_str(result.stdout, ipv6_pattern)
358+
return ipv6_address
359+
349360
def get_default_route_info(self) -> tuple[str, str]:
350361
result = self.run("route", force_run=True, sudo=True)
351362
result.assert_exit_code()

lisa/tools/lagscope.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,17 @@ def restore_busy_poll(self) -> None:
131131
for key in self._busy_pool_keys:
132132
sysctl.write(key, self._original_settings[key])
133133

134-
def run_as_server_async(self, ip: str = "") -> Process:
134+
def run_as_server_async(self, ip: str = "", use_ipv6: bool = False) -> Process:
135135
# -r: run as a receiver
136136
# -rip: run as server mode with specified ip address
137137
cmd = ""
138+
if use_ipv6:
139+
cmd += " -6"
138140
if ip:
139141
cmd += f" -r{ip}"
140142
else:
141143
cmd += " -r"
144+
142145
process = self.run_async(cmd, sudo=True, shell=True, force_run=True)
143146
if not process.is_running():
144147
raise LisaException("lagscope server failed to start")
@@ -159,6 +162,7 @@ def run_as_client_async(
159162
count_of_histogram_intervals: int = 30,
160163
dump_csv: bool = True,
161164
daemon: bool = False,
165+
use_ipv6: bool = False,
162166
) -> Process:
163167
# -s: run as a sender
164168
# -i: test interval
@@ -172,6 +176,8 @@ def run_as_client_async(
172176
# -R: dumps raw latencies into csv file
173177
# -D: run as daemon
174178
cmd = f"{self.command} -s{server_ip} "
179+
if use_ipv6:
180+
cmd += " -6 "
175181
if run_time_seconds:
176182
cmd += f" -t{run_time_seconds} "
177183
if count_of_histogram_intervals:
@@ -391,7 +397,7 @@ def restore_busy_poll(self) -> None:
391397
# This is not supported on FreeBSD.
392398
return
393399

394-
def run_as_server_async(self, ip: str = "") -> Process:
400+
def run_as_server_async(self, ip: str = "", use_ipv6: bool = False) -> Process:
395401
return self.node.tools[Sockperf].start_server_async("tcp")
396402

397403
def run_as_client_async(
@@ -407,6 +413,7 @@ def run_as_client_async(
407413
count_of_histogram_intervals: int = 30,
408414
dump_csv: bool = True,
409415
daemon: bool = False,
416+
use_ipv6: bool = False,
410417
) -> Process:
411418
return self.node.tools[Sockperf].run_client_async("tcp", server_ip)
412419

lisa/tools/ntttcp.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,8 +213,11 @@ def run_as_server_async(
213213
dev_differentiator: str = "Hypervisor callback interrupts",
214214
run_as_daemon: bool = False,
215215
udp_mode: bool = False,
216+
use_ipv6: bool = False,
216217
) -> Process:
217218
cmd = ""
219+
if use_ipv6:
220+
cmd += " -6 "
218221
if server_ip:
219222
cmd += f" -r{server_ip} "
220223
cmd += (
@@ -308,6 +311,7 @@ def run_as_client(
308311
dev_differentiator: str = "Hypervisor callback interrupts",
309312
run_as_daemon: bool = False,
310313
udp_mode: bool = False,
314+
use_ipv6: bool = False,
311315
) -> ExecutableResult:
312316
# -sserver_ip: run as a sender with server ip address
313317
# -P: Number of ports listening on receiver side [default: 16] [max: 512]
@@ -326,11 +330,15 @@ def run_as_client(
326330
# the devices specified by the differentiator
327331
# Examples for differentiator: Hyper-V PCIe MSI, mlx4, Hypervisor callback
328332
# interrupts
329-
cmd = (
333+
cmd = ""
334+
if use_ipv6:
335+
cmd += " -6 "
336+
cmd += (
330337
f" -s{server_ip} -P {ports_count} -n {threads_count} -t {run_time_seconds} "
331338
f"-W {warm_up_time_seconds} -C {cool_down_time_seconds} -b {buffer_size}k "
332339
f"--show-nic-packets {nic_name} "
333340
)
341+
334342
if udp_mode:
335343
cmd += " -u "
336344
if dev_differentiator:

microsoft/testsuites/network/sriov.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
Lscpu,
3939
Service,
4040
)
41+
from lisa.tools.ip import Ip
4142
from lisa.util import (
4243
LisaException,
4344
LisaTimeoutException,
@@ -877,6 +878,30 @@ def verify_sriov_interrupts_change(self, environment: Environment) -> None:
877878
"interrupt count!"
878879
).is_greater_than(unused_cpu)
879880

881+
@TestCaseMetadata(
882+
description="""
883+
Verify IPv6 networking functionality with IPv6
884+
1. Create an Azure VM with AN Enabled and IPv6 enabled
885+
2. Verify that the NIC has an IPv6 address
886+
""",
887+
priority=2,
888+
requirement=node_requirement(
889+
node=schema.NodeSpace(
890+
network_interface=features.SriovIPv6(),
891+
)
892+
),
893+
)
894+
def verify_sriov_ipv6_basic(self, node: Node, log: Logger) -> None:
895+
896+
ip = node.tools[Ip]
897+
# for each nic, verify that ipv6 exists using nic name
898+
for nic in node.nics.nics.keys():
899+
nic_ipv6 = ip.get_ipv6_address(nic)
900+
assert_that(
901+
nic_ipv6,
902+
f"Expected IPv6 address but found none on nic {nic}",
903+
).is_not_empty()
904+
880905
def after_case(self, log: Logger, **kwargs: Any) -> None:
881906
environment: Environment = kwargs.pop("environment")
882907
cleanup_iperf3(environment)

0 commit comments

Comments
 (0)