Skip to content

Commit b6daa1a

Browse files
author
Ahmed Karic
committed
test: Verify auto-negotiated speed/duplex (copper)
1 parent 25fd431 commit b6daa1a

File tree

7 files changed

+304
-0
lines changed

7 files changed

+304
-0
lines changed

test/case/ietf_interfaces/Readme.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ include::bridge_veth/Readme.adoc[]
2929

3030
include::bridge_vlan/Readme.adoc[]
3131

32+
include::speed_duplex_copper/Readme.adoc[]
33+
3234
include::bridge_vlan_separation/Readme.adoc[]
3335

3436
include::dual_bridge/Readme.adoc[]

test/case/ietf_interfaces/ietf_interfaces.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
- name: vlan_qos
2121
case: vlan_qos/test.py
2222

23+
- name: speed_duplex_copper
24+
case: speed_duplex_copper/test.py
25+
2326
- name: verify_all_interface_types
2427
case: verify_all_interface_types/test.py
2528

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
speed_duplex_copper.adoc
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
=== Interface Speed Duplex (Copper)
2+
==== Description
3+
Verify that auto-negotiation results in expected speed/duplex mode.
4+
5+
==== Topology
6+
ifdef::topdoc[]
7+
image::{topdoc}../../test/case/ietf_interfaces/speed_duplex_copper/topology.svg[Interface Speed Duplex (Copper) topology]
8+
endif::topdoc[]
9+
ifndef::topdoc[]
10+
ifdef::testgroup[]
11+
image::speed_duplex_copper/topology.svg[Interface Speed Duplex (Copper) topology]
12+
endif::testgroup[]
13+
ifndef::testgroup[]
14+
image::topology.svg[Interface Speed Duplex (Copper) topology]
15+
endif::testgroup[]
16+
endif::topdoc[]
17+
==== Test sequence
18+
. Set up topology and attach to target DUT
19+
. Enable target interface and autonegotiation
20+
. Advertise 10/Full only
21+
. Advertise 10/Half only
22+
. Advertise 100/Full only
23+
. Advertise 100/Half only
24+
. Advertise 10/half + 10/full + 100/half
25+
. Advertise 10/half + 10/full + 100/half + 100/full + 1000/full
26+
27+
28+
<<<
29+
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Interface Speed Duplex (Copper)
4+
5+
Verify that auto-negotiation results in expected speed/duplex mode.
6+
"""
7+
8+
import infamy
9+
import subprocess
10+
from infamy.util import until
11+
12+
ADVERTISE_MODES = {
13+
# Values from ethtool's ETHTOOL_LINK_MODE bit positions
14+
# See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/ethtool.h
15+
"10half": 0x0001,
16+
"10full": 0x0002,
17+
"100half": 0x0004,
18+
"100full": 0x0008,
19+
"1000full": 0x0020
20+
}
21+
22+
def advertise_host_modes(interface, modes):
23+
mask = 0
24+
for mode in modes:
25+
mask |= ADVERTISE_MODES[mode]
26+
try:
27+
subprocess.run([
28+
"ethtool", "-s", interface,
29+
"advertise", hex(mask)
30+
], check=True)
31+
except subprocess.CalledProcessError as e:
32+
raise RuntimeError(f"Failed to advertise modes via ethtool: {e}")
33+
34+
def get_target_speed_duplex(target, interface):
35+
data = target.get_data(f"/ietf-interfaces:interfaces/interface[name='{interface}']") \
36+
["interfaces"]["interface"][interface]
37+
eth = data.get("ethernet", {})
38+
39+
return eth.get("speed"), eth.get("duplex")
40+
41+
def set_target_speed_duplex(target, interface, speed, duplex):
42+
target.put_config_dicts({
43+
"ietf-interfaces": {
44+
"interfaces": {
45+
"interface": [{
46+
"name": interface,
47+
"ethernet": {
48+
"auto-negotiation": {
49+
"enable": False
50+
},
51+
"speed": speed / 1000,
52+
"duplex": duplex
53+
}
54+
}]
55+
}
56+
}
57+
})
58+
59+
def set_host_speed_duplex(interface, speed, duplex):
60+
try:
61+
subprocess.run([
62+
"ethtool", "-s", interface,
63+
"speed", str(speed),
64+
"duplex", duplex,
65+
"autoneg", "off"
66+
], check=True)
67+
except subprocess.CalledProcessError as e:
68+
raise RuntimeError(f"Failed to set speed/duplex via ethtool: {e}")
69+
70+
def verify_speed_duplex(target, interface, exp_speed, exp_duplex):
71+
until(lambda: speed_duplex_present(target, interface))
72+
act_speed, act_duplex = get_target_speed_duplex(target, interface)
73+
if act_speed is None or act_duplex is None:
74+
print(f"Could not fetch speed or duplex from target for interface {interface}")
75+
test.fail()
76+
77+
exp_speed_gbps = exp_speed / 1000
78+
if float(act_speed) != exp_speed_gbps:
79+
print(f"act_speed: {act_speed}, exp_speed: {exp_speed_gbps}")
80+
test.fail()
81+
82+
if act_duplex.lower() != exp_duplex.lower():
83+
print(f"act_duplex: {act_duplex}, exp_duplex: {exp_duplex}")
84+
test.fail()
85+
86+
print(f"Verified: {interface} is operating at {act_speed} Gbps, {act_duplex} duplex")
87+
88+
def speed_duplex_present(target, interface):
89+
speed, duplex = get_target_speed_duplex(target, interface)
90+
return speed is not None and duplex is not None
91+
92+
def enable_target_interface(target, interface):
93+
target.put_config_dicts({
94+
"ietf-interfaces": {
95+
"interfaces": {
96+
"interface": [{
97+
"name": interface,
98+
"enabled": True
99+
}]
100+
}
101+
}
102+
})
103+
104+
def enable_target_autoneg(target, interface):
105+
target.put_config_dicts({
106+
"ietf-interfaces": {
107+
"interfaces": {
108+
"interface": [{
109+
"name": interface,
110+
"ethernet": {
111+
"auto-negotiation": {
112+
"enable": True
113+
}
114+
}
115+
}]
116+
}
117+
}
118+
})
119+
120+
def enable_host_autoneg(interface):
121+
subprocess.run(["ethtool", "-s", interface, "autoneg", "on"], check=True)
122+
123+
def cleanup(target, hdata, tdata):
124+
"""
125+
Restore both host and target interfaces to autonegotiation mode
126+
to ensure clean state for future tests.
127+
"""
128+
129+
print("Restoring interfaces to default (autoneg on)")
130+
try:
131+
enable_host_autoneg(hdata)
132+
except Exception as e:
133+
print(f"Host autoneg restore failed: {e}")
134+
try:
135+
enable_target_interface(target, tdata)
136+
enable_target_autoneg(target, tdata)
137+
except Exception as e:
138+
print(f"Target autoneg restore failed: {e}")
139+
140+
with infamy.Test() as test:
141+
with test.step("Set up topology and attach to target DUT"):
142+
env = infamy.Env()
143+
target = env.attach("target", "mgmt")
144+
_, hdata = env.ltop.xlate("host", "data")
145+
_, tdata = env.ltop.xlate("target", "data")
146+
147+
# Append a test cleanup function
148+
test.push_test_cleanup(lambda: cleanup(target, hdata, tdata))
149+
150+
with test.step("Enable target interface"):
151+
enable_target_interface(target, tdata)
152+
153+
# Fixed mode tests
154+
with test.step("Set fixed 10/full"):
155+
set_host_speed_duplex(hdata, 10, "full")
156+
set_target_speed_duplex(target, tdata, 10, "full")
157+
verify_speed_duplex(target, tdata, 10, "full")
158+
159+
with test.step("Set fixed 10/half"):
160+
set_host_speed_duplex(hdata, 10, "half")
161+
set_target_speed_duplex(target, tdata, 10, "half")
162+
verify_speed_duplex(target, tdata, 10, "half")
163+
164+
with test.step("Set fixed 100/full"):
165+
set_host_speed_duplex(hdata, 100, "full")
166+
set_target_speed_duplex(target, tdata, 100, "full")
167+
verify_speed_duplex(target, tdata, 100, "full")
168+
169+
with test.step("Set fixed 100/half"):
170+
set_host_speed_duplex(hdata, 100, "half")
171+
set_target_speed_duplex(target, tdata, 100, "half")
172+
verify_speed_duplex(target, tdata, 100, "half")
173+
174+
# Auto-negotiation tests: host advertises, Infix negotiates
175+
with test.step("Switch to auto-negotiation mode for target and host"):
176+
enable_host_autoneg(hdata)
177+
enable_target_autoneg(target, tdata)
178+
179+
with test.step("Configure host to advertise 10/Full only"):
180+
advertise_host_modes(hdata, ["10full"])
181+
verify_speed_duplex(target, tdata, 10, "full")
182+
183+
with test.step("Configure host to advertise 10/Half only"):
184+
advertise_host_modes(hdata, ["10half"])
185+
verify_speed_duplex(target, tdata, 10, "half")
186+
187+
with test.step("Configure host to advertise 100/Full only"):
188+
advertise_host_modes(hdata, ["100full"])
189+
verify_speed_duplex(target, tdata, 100, "full")
190+
191+
with test.step("Configure host to advertise 100/Half only"):
192+
advertise_host_modes(hdata, ["100half"])
193+
verify_speed_duplex(target, tdata, 100, "half")
194+
195+
with test.step("Configure host to advertise 10/half + 10/full + 100/half"):
196+
advertise_host_modes(hdata, ["10half", "10full", "100half"])
197+
verify_speed_duplex(target, tdata, 100, "half")
198+
199+
with test.step("Configure host to advertise 10/half + 10/full + 100/half + 100/full + 1000/full"):
200+
advertise_host_modes(hdata, ["10half", "10full", "100half", "100full", "1000full"])
201+
verify_speed_duplex(target, tdata, 1000, "full")
202+
203+
test.succeed()
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
graph "1x2" {
2+
layout="neato";
3+
overlap="false";
4+
esep="+80";
5+
6+
node [shape=record, fontname="DejaVu Sans Mono, Book"];
7+
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
8+
9+
host [
10+
label="host | { <mgmt> mgmt | <data> data }",
11+
pos="0,12!",
12+
requires="controller",
13+
];
14+
15+
target [
16+
label="{ <mgmt> mgmt | <data> data } | target",
17+
pos="10,12!",
18+
19+
requires="infix",
20+
];
21+
22+
host:mgmt -- target:mgmt [requires="mgmt", color=lightgrey]
23+
host:data -- target:data [color=black, requires="link-ctrl copper"]
24+
}
Lines changed: 42 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)