Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion test/.env
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# shellcheck disable=SC2034,SC2154

# Current container image
INFIX_TEST=ghcr.io/kernelkit/infix-test:2.2
INFIX_TEST=ghcr.io/kernelkit/infix-test:2.3

ixdir=$(readlink -f "$testdir/..")
logdir=$(readlink -f "$testdir/.log")
Expand Down
2 changes: 2 additions & 0 deletions test/case/ietf_interfaces/Readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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[]
Expand Down
3 changes: 3 additions & 0 deletions test/case/ietf_interfaces/ietf_interfaces.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions test/case/ietf_interfaces/speed_duplex_copper/Readme.adoc
Original file line number Diff line number Diff line change
@@ -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 changes: 216 additions & 0 deletions test/case/ietf_interfaces/speed_duplex_copper/test.py
Original file line number Diff line number Diff line change
@@ -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()
24 changes: 24 additions & 0 deletions test/case/ietf_interfaces/speed_duplex_copper/topology.dot
Original file line number Diff line number Diff line change
@@ -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"]
}
42 changes: 42 additions & 0 deletions test/case/ietf_interfaces/speed_duplex_copper/topology.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion test/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ RUN apk add --no-cache \
openssl \
curl \
e2tools \
make
make \
ethtool

ARG MTOOL_VERSION="3.0"
RUN wget https://github.com/troglobit/mtools/releases/download/v3.0/mtools-$MTOOL_VERSION.tar.gz -O /tmp/mtools-$MTOOL_VERSION.tar.gz
Expand Down