test: Verify auto-negotiated speed/duplex (copper)

This commit is contained in:
Ahmed Karic
2025-06-25 08:54:33 +02:00
parent 20d5426935
commit ec82d68a9f
7 changed files with 322 additions and 0 deletions
+2
View File
@@ -29,6 +29,8 @@ include::bridge_veth/Readme.adoc[]
include::bridge_vlan/Readme.adoc[]
include::speed_duplex_copper/Readme.adoc[]
include::bridge_vlan_separation/Readme.adoc[]
include::dual_bridge/Readme.adoc[]
@@ -20,6 +20,9 @@
- name: vlan_qos
case: vlan_qos/test.py
- name: speed_duplex_copper
case: speed_duplex_copper/test.py
- name: verify_all_interface_types
case: verify_all_interface_types/test.py
@@ -0,0 +1 @@
speed_duplex_copper.adoc
@@ -0,0 +1,34 @@
=== Interface Speed Duplex (Copper)
==== Description
Verify that auto-negotiation results in expected speed/duplex mode.
==== Topology
ifdef::topdoc[]
image::{topdoc}../../test/case/ietf_interfaces/speed_duplex_copper/topology.svg[Interface Speed Duplex (Copper) topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::speed_duplex_copper/topology.svg[Interface Speed Duplex (Copper) topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[Interface Speed Duplex (Copper) topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUT
. Enable target interface
. Set fixed 10/full
. Set fixed 10/half
. Set fixed 100/full
. Set fixed 100/half
. Switch to auto-negotiation mode for target and host
. Configure host to advertise 10/Full only
. Configure host to advertise 10/Half only
. Configure host to advertise 100/Full only
. Configure host to advertise 100/Half only
. Configure host to advertise 10/half + 10/full + 100/half
. Configure host to advertise 10/half + 10/full + 100/half + 100/full + 1000/full
<<<
+216
View File
@@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""
Interface Speed Duplex (Copper)
Verify that auto-negotiation results in expected speed/duplex mode.
"""
import infamy
import subprocess
from infamy.util import until
ADVERTISE_MODES = {
# Values from ethtool's ETHTOOL_LINK_MODE bit positions
# See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/ethtool.h
"10half": 0x0001,
"10full": 0x0002,
"100half": 0x0004,
"100full": 0x0008,
"1000full": 0x0020
}
def advertise_host_modes(interface, modes):
mask = 0
for mode in modes:
mask |= ADVERTISE_MODES[mode]
try:
subprocess.run([
"ethtool", "-s", interface,
"advertise", hex(mask)
], check=True)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to advertise modes via ethtool: {e}")
def get_target_speed_duplex(target, interface):
data = target.get_data(f"/ietf-interfaces:interfaces/interface[name='{interface}']") \
["interfaces"]["interface"][interface]
eth = data.get("ethernet", {})
return eth.get("speed"), eth.get("duplex")
def set_target_speed_duplex(target, interface, speed, duplex):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": interface,
"ethernet": {
"auto-negotiation": {
"enable": False
},
"speed": speed / 1000,
"duplex": duplex
}
}]
}
}
})
def set_host_speed_duplex(interface, speed, duplex):
try:
subprocess.run([
"ethtool", "-s", interface,
"speed", str(speed),
"duplex", duplex,
"autoneg", "off"
], check=True)
except subprocess.CalledProcessError as e:
raise RuntimeError(f"Failed to set speed/duplex via ethtool: {e}")
def verify_speed_duplex(target, ns, interface, exp_speed, exp_duplex):
until(lambda: speed_duplex_present(target, interface))
act_speed, act_duplex = get_target_speed_duplex(target, interface)
if act_speed is None or act_duplex is None:
print(f"Could not fetch speed or duplex from target for interface {interface}")
test.fail()
exp_speed_gbps = exp_speed / 1000
if float(act_speed) != exp_speed_gbps:
print(f"act_speed: {act_speed}, exp_speed: {exp_speed_gbps}")
test.fail()
if act_duplex.lower() != exp_duplex.lower():
print(f"act_duplex: {act_duplex}, exp_duplex: {exp_duplex}")
test.fail()
ns.must_reach("10.0.0.2")
print(f"Verified: {interface} is operating at {act_speed} Gbps, {act_duplex} duplex")
def speed_duplex_present(target, interface):
speed, duplex = get_target_speed_duplex(target, interface)
return speed is not None and duplex is not None
def enable_target_interface(target, interface):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": interface,
"enabled": True,
"ipv4": {
"address": [
{
"ip": "10.0.0.2",
"prefix-length": 24
}
]
}
}]
}
}
})
def enable_target_autoneg(target, interface):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": interface,
"ethernet": {
"auto-negotiation": {
"enable": True
}
}
}]
}
}
})
def enable_host_autoneg(interface):
subprocess.run(["ethtool", "-s", interface, "autoneg", "on"], check=True)
def cleanup(target, hdata, tdata):
"""
Restore both host and target interfaces to autonegotiation mode
to ensure clean state for future tests.
"""
print("Restoring interfaces to default (autoneg on)")
try:
enable_host_autoneg(hdata)
except Exception as e:
print(f"Host autoneg restore failed: {e}")
try:
enable_target_interface(target, tdata)
enable_target_autoneg(target, tdata)
except Exception as e:
print(f"Target autoneg restore failed: {e}")
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUT"):
env = infamy.Env()
target = env.attach("target", "mgmt")
_, hdata = env.ltop.xlate("host", "data")
_, tdata = env.ltop.xlate("target", "data")
# Append a test cleanup function
test.push_test_cleanup(lambda: cleanup(target, hdata, tdata))
with test.step("Enable target interface"):
enable_target_interface(target, tdata)
with infamy.IsolatedMacVlan(hdata) as ns:
ns.addip("10.0.0.1")
# Fixed mode tests
with test.step("Set fixed 10/full"):
set_host_speed_duplex(hdata, 10, "full")
set_target_speed_duplex(target, tdata, 10, "full")
verify_speed_duplex(target, ns, tdata, 10, "full")
with test.step("Set fixed 10/half"):
set_host_speed_duplex(hdata, 10, "half")
set_target_speed_duplex(target, tdata, 10, "half")
verify_speed_duplex(target, ns, tdata, 10, "half")
with test.step("Set fixed 100/full"):
set_host_speed_duplex(hdata, 100, "full")
set_target_speed_duplex(target, tdata, 100, "full")
verify_speed_duplex(target, ns, tdata, 100, "full")
with test.step("Set fixed 100/half"):
set_host_speed_duplex(hdata, 100, "half")
set_target_speed_duplex(target, tdata, 100, "half")
verify_speed_duplex(target, ns, tdata, 100, "half")
# Auto-negotiation tests: host advertises, Infix negotiates
with test.step("Switch to auto-negotiation mode for target and host"):
enable_host_autoneg(hdata)
enable_target_autoneg(target, tdata)
with test.step("Configure host to advertise 10/Full only"):
advertise_host_modes(hdata, ["10full"])
verify_speed_duplex(target, ns, tdata, 10, "full")
with test.step("Configure host to advertise 10/Half only"):
advertise_host_modes(hdata, ["10half"])
verify_speed_duplex(target, ns, tdata, 10, "half")
with test.step("Configure host to advertise 100/Full only"):
advertise_host_modes(hdata, ["100full"])
verify_speed_duplex(target, ns, tdata, 100, "full")
with test.step("Configure host to advertise 100/Half only"):
advertise_host_modes(hdata, ["100half"])
verify_speed_duplex(target, ns, tdata, 100, "half")
with test.step("Configure host to advertise 10/half + 10/full + 100/half"):
advertise_host_modes(hdata, ["10half", "10full", "100half"])
verify_speed_duplex(target, ns, tdata, 100, "half")
with test.step("Configure host to advertise 10/half + 10/full + 100/half + 100/full + 1000/full"):
advertise_host_modes(hdata, ["10half", "10full", "100half", "100full", "1000full"])
verify_speed_duplex(target, ns, tdata, 1000, "full")
test.succeed()
@@ -0,0 +1,24 @@
graph "1x2" {
layout="neato";
overlap="false";
esep="+80";
node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="host | { <mgmt> mgmt | <data> data }",
pos="0,12!",
requires="controller",
];
target [
label="{ <mgmt> mgmt | <data> data } | target",
pos="10,12!",
requires="infix",
];
host:mgmt -- target:mgmt [requires="mgmt", color=lightgrey]
host:data -- target:data [color=black, requires="link-ctrl copper"]
}
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 1x2 Pages: 1 -->
<svg width="424pt" height="55pt"
viewBox="0.00 0.00 424.03 55.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 51)">
<title>1x2</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-51 420.03,-51 420.03,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-46.5 100,-46.5 100,-0.5 0,-0.5"/>
<text text-anchor="middle" x="25" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-0.5 50,-46.5 "/>
<text text-anchor="middle" x="75" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-23.5 100,-23.5 "/>
<text text-anchor="middle" x="75" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
</g>
<!-- target -->
<g id="node2" class="node">
<title>target</title>
<polygon fill="none" stroke="black" points="300.03,-0.5 300.03,-46.5 416.03,-46.5 416.03,-0.5 300.03,-0.5"/>
<text text-anchor="middle" x="325.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="300.03,-23.5 350.03,-23.5 "/>
<text text-anchor="middle" x="325.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
<polyline fill="none" stroke="black" points="350.03,-0.5 350.03,-46.5 "/>
<text text-anchor="middle" x="383.03" y="-19.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
</g>
<!-- host&#45;&#45;target -->
<g id="edge1" class="edge">
<title>host:mgmt&#45;&#45;target:mgmt</title>
<path fill="none" stroke="lightgrey" stroke-width="2" d="M100,-35.5C100,-35.5 300.03,-35.5 300.03,-35.5"/>
</g>
<!-- host&#45;&#45;target -->
<g id="edge2" class="edge">
<title>host:data&#45;&#45;target:data</title>
<path fill="none" stroke="black" stroke-width="2" d="M100,-11.5C100,-11.5 300.03,-11.5 300.03,-11.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB