From ec82d68a9fa1ccfaa819f4a759ccc6d5bd21fd5f Mon Sep 17 00:00:00 2001 From: Ahmed Karic Date: Tue, 24 Jun 2025 17:57:10 +0200 Subject: [PATCH] test: Verify auto-negotiated speed/duplex (copper) --- test/case/ietf_interfaces/Readme.adoc | 2 + .../case/ietf_interfaces/ietf_interfaces.yaml | 3 + .../speed_duplex_copper/Readme.adoc | 1 + .../speed_duplex_copper.adoc | 34 +++ .../speed_duplex_copper/test.py | 216 ++++++++++++++++++ .../speed_duplex_copper/topology.dot | 24 ++ .../speed_duplex_copper/topology.svg | 42 ++++ 7 files changed, 322 insertions(+) create mode 120000 test/case/ietf_interfaces/speed_duplex_copper/Readme.adoc create mode 100644 test/case/ietf_interfaces/speed_duplex_copper/speed_duplex_copper.adoc create mode 100755 test/case/ietf_interfaces/speed_duplex_copper/test.py create mode 100644 test/case/ietf_interfaces/speed_duplex_copper/topology.dot create mode 100644 test/case/ietf_interfaces/speed_duplex_copper/topology.svg diff --git a/test/case/ietf_interfaces/Readme.adoc b/test/case/ietf_interfaces/Readme.adoc index de861eb0..b9b01428 100644 --- a/test/case/ietf_interfaces/Readme.adoc +++ b/test/case/ietf_interfaces/Readme.adoc @@ -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[] diff --git a/test/case/ietf_interfaces/ietf_interfaces.yaml b/test/case/ietf_interfaces/ietf_interfaces.yaml index de0523fe..1ff61d90 100644 --- a/test/case/ietf_interfaces/ietf_interfaces.yaml +++ b/test/case/ietf_interfaces/ietf_interfaces.yaml @@ -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 diff --git a/test/case/ietf_interfaces/speed_duplex_copper/Readme.adoc b/test/case/ietf_interfaces/speed_duplex_copper/Readme.adoc new file mode 120000 index 00000000..22d678f6 --- /dev/null +++ b/test/case/ietf_interfaces/speed_duplex_copper/Readme.adoc @@ -0,0 +1 @@ +speed_duplex_copper.adoc \ No newline at end of file diff --git a/test/case/ietf_interfaces/speed_duplex_copper/speed_duplex_copper.adoc b/test/case/ietf_interfaces/speed_duplex_copper/speed_duplex_copper.adoc new file mode 100644 index 00000000..0be4f0cf --- /dev/null +++ b/test/case/ietf_interfaces/speed_duplex_copper/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 + + +<<< + diff --git a/test/case/ietf_interfaces/speed_duplex_copper/test.py b/test/case/ietf_interfaces/speed_duplex_copper/test.py new file mode 100755 index 00000000..121a2b63 --- /dev/null +++ b/test/case/ietf_interfaces/speed_duplex_copper/test.py @@ -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() \ No newline at end of file diff --git a/test/case/ietf_interfaces/speed_duplex_copper/topology.dot b/test/case/ietf_interfaces/speed_duplex_copper/topology.dot new file mode 100644 index 00000000..fe4821e8 --- /dev/null +++ b/test/case/ietf_interfaces/speed_duplex_copper/topology.dot @@ -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 | data }", + pos="0,12!", + requires="controller", + ]; + + target [ + label="{ mgmt | 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"] +} diff --git a/test/case/ietf_interfaces/speed_duplex_copper/topology.svg b/test/case/ietf_interfaces/speed_duplex_copper/topology.svg new file mode 100644 index 00000000..ff3d246b --- /dev/null +++ b/test/case/ietf_interfaces/speed_duplex_copper/topology.svg @@ -0,0 +1,42 @@ + + + + + + +1x2 + + + +host + +host + +mgmt + +data + + + +target + +mgmt + +data + +target + + + +host:mgmt--target:mgmt + + + + +host:data--target:data + + + +