From 655bf1fc5cc0bce2ca83f323938d06f78e459826 Mon Sep 17 00:00:00 2001 From: Tobias Waldekranz Date: Tue, 10 Dec 2024 18:51:31 +0100 Subject: [PATCH] test: bridge_stp_basic: Add --- test/case/ietf_interfaces/Readme.adoc | 3 +- .../bridge_stp_basic/Readme.adoc | 31 ++++ .../ietf_interfaces/bridge_stp_basic/test.py | 89 ++++++++++ .../bridge_stp_basic/topology.dot | 55 ++++++ .../bridge_stp_basic/topology.svg | 168 ++++++++++++++++++ .../case/ietf_interfaces/ietf_interfaces.yaml | 3 + 6 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 test/case/ietf_interfaces/bridge_stp_basic/Readme.adoc create mode 100755 test/case/ietf_interfaces/bridge_stp_basic/test.py create mode 100644 test/case/ietf_interfaces/bridge_stp_basic/topology.dot create mode 100644 test/case/ietf_interfaces/bridge_stp_basic/topology.svg diff --git a/test/case/ietf_interfaces/Readme.adoc b/test/case/ietf_interfaces/Readme.adoc index 712d95733..707e2902d 100644 --- a/test/case/ietf_interfaces/Readme.adoc +++ b/test/case/ietf_interfaces/Readme.adoc @@ -23,6 +23,8 @@ include::bridge_fwd_dual_dut/Readme.adoc[] include::bridge_fwd_sgl_dut/Readme.adoc[] +include::bridge_stp_basic/Readme.adoc[] + include::bridge_veth/Readme.adoc[] include::bridge_vlan/Readme.adoc[] @@ -46,4 +48,3 @@ include::iface_enable_disable/Readme.adoc[] include::veth_delete/Readme.adoc[] include::vlan_iface_termination/Readme.adoc[] - diff --git a/test/case/ietf_interfaces/bridge_stp_basic/Readme.adoc b/test/case/ietf_interfaces/bridge_stp_basic/Readme.adoc new file mode 100644 index 000000000..e017893f1 --- /dev/null +++ b/test/case/ietf_interfaces/bridge_stp_basic/Readme.adoc @@ -0,0 +1,31 @@ +=== Bridge STP Basic +==== Description +Verify that a fully connected mesh of 4 DUTs is pruned to a spanning +tree. + +Since the mesh contains 3 redundant paths, can infer that a spanning +tree has been created if all host interfaces can reach each other +while exactly three links are in the blocking state. + +==== Topology +ifdef::topdoc[] +image::../../test/case/ietf_interfaces/bridge_stp_basic/topology.svg[Bridge STP Basic topology] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::bridge_stp_basic/topology.svg[Bridge STP Basic topology] +endif::testgroup[] +ifndef::testgroup[] +image::topology.svg[Bridge STP Basic topology] +endif::testgroup[] +endif::topdoc[] +==== Test sequence +. Set up topology and attach to target DUT +. Configure a bridge with spanning tree eneabled on dut a, b, c, and d +. Add an IP address to each host interface in the 10.0.0.0/24 subnet +. Verify that exactly three links are blocking +. Verify that host:a can reach host:{b,c,d} + + +<<< + diff --git a/test/case/ietf_interfaces/bridge_stp_basic/test.py b/test/case/ietf_interfaces/bridge_stp_basic/test.py new file mode 100755 index 000000000..f4a74e9cd --- /dev/null +++ b/test/case/ietf_interfaces/bridge_stp_basic/test.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +r"""Bridge STP Basic + +Verify that a fully connected mesh of 4 DUTs is pruned to a spanning +tree. + +Since the mesh contains 3 redundant paths, can infer that a spanning +tree has been created if all host interfaces can reach each other +while exactly three links are in the blocking state. + +""" +import infamy +from infamy.util import parallel, until + +def addbr(dut): + ip = { + "A": "10.0.0.101", + "B": "10.0.0.102", + "C": "10.0.0.103", + "D": "10.0.0.104" + }[dut.name] + + brports = [ + { + "name": dut[n], + "infix-interfaces:bridge-port": { + "bridge": "br0", + } + } for n in ("a", "b", "c", "d", "h") if n != dut.name.lower() + ] + + dut.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [ + { + "name": "br0", + "type": "infix-if-type:bridge", + "enabled": True, + "bridge": { + "stp": {}, + }, + "ipv4": { + "address": [ + { + "ip": ip, + "prefix-length": 24, + } + ] + }, + } + ] + brports, + } + } + }) + +def num_blocking(dut): + num = 0 + for iface in dut.get_data("/ietf-interfaces:interfaces")["interfaces"]["interface"]: + if iface.get("bridge-port", {}).get("stp-state") == "blocking": + num += 1 + + return num + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUT"): + env = infamy.Env() + a, b, c, d = parallel(lambda: env.attach("A"), lambda: env.attach("B"), + lambda: env.attach("C"), lambda: env.attach("D")) + + host = { p: env.ltop.xlate("host", p)[1] for p in ("a", "b", "c", "d") } + + with test.step("Configure a bridge with spanning tree eneabled on dut a, b, c, and d"): + parallel(lambda: addbr(a), lambda: addbr(b), lambda: addbr(c), lambda: addbr(d)) + + with test.step("Add an IP address to each host interface in the 10.0.0.0/24 subnet"): + ns = { p: infamy.IsolatedMacVlan(host[p]).start() for p in ("a", "b", "c", "d") } + parallel(lambda: ns["a"].addip("10.0.0.1"), lambda: ns["b"].addip("10.0.0.2"), + lambda: ns["c"].addip("10.0.0.3"), lambda: ns["d"].addip("10.0.0.4")) + + with test.step("Verify that exactly three links are blocking"): + until(lambda: sum(map(num_blocking, (a, b, c, d))) == 3, 60) + + with test.step("Verify that host:a can reach host:{b,c,d}"): + parallel(lambda: ns["a"].must_reach("10.0.0.2"), + lambda: ns["a"].must_reach("10.0.0.3"), + lambda: ns["a"].must_reach("10.0.0.4")) + + test.succeed() diff --git a/test/case/ietf_interfaces/bridge_stp_basic/topology.dot b/test/case/ietf_interfaces/bridge_stp_basic/topology.dot new file mode 100644 index 000000000..b467d13c0 --- /dev/null +++ b/test/case/ietf_interfaces/bridge_stp_basic/topology.dot @@ -0,0 +1,55 @@ +graph "stp" { + layout="neato"; + overlap="false"; + esep="+80"; + + node [shape=record, fontname="DejaVu Sans Mono, Book"]; + edge [penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="{ { mgmtd | d | mgmta | a | b | mgmtb | c | mgmtc } | host }", + color="grey",fontcolor="grey",pos="9,0!", + kind="controller", + ]; + + A [ + label="{ A | { mgmt | h } } | { b | c | d }", + pos="6,6!", + kind="infix", + ]; + B [ + label="{ a | d | c } | { B | { h | mgmt } }", + pos="12,6!", + kind="infix", + ]; + C [ + label="{ b | a | d } | { C | { h | mgmt } }", + pos="12,3!", + kind="infix", + ]; + D [ + label="{ D | { mgmt | h } } | { a | b | c }", + pos="6,3!", + kind="infix", + ]; + + host:mgmta -- A:mgmt [kind=mgmt, color="lightgrey"] + host:mgmtb -- B:mgmt [kind=mgmt, color="lightgrey"] + host:mgmtc -- C:mgmt [kind=mgmt, color="lightgrey"] + host:mgmtd -- D:mgmt [kind=mgmt, color="lightgrey"] + + host:a -- A:h [color="cornflowerblue"] + host:b -- B:h [color="cornflowerblue"] + host:c -- C:h [color="cornflowerblue"] + host:d -- D:h [color="cornflowerblue"] + + # Ring + A:b -- B:a + B:c -- C:b + C:d -- D:c + D:a -- A:d + + # Cross-links + A:c -- C:a + B:d -- D:b +} diff --git a/test/case/ietf_interfaces/bridge_stp_basic/topology.svg b/test/case/ietf_interfaces/bridge_stp_basic/topology.svg new file mode 100644 index 000000000..aa67633c8 --- /dev/null +++ b/test/case/ietf_interfaces/bridge_stp_basic/topology.svg @@ -0,0 +1,168 @@ + + + + + + +stp + + + +host + +mgmtd + +d + +mgmta + +a + +b + +mgmtb + +c + +mgmtc + +host + + + +A + +A + +mgmt + +h + +b + +c + +d + + + +host:mgmta--A:mgmt + + + + +host:a--A:h + + + + +B + +a + +d + +c + +B + +h + +mgmt + + + +host:mgmtb--B:mgmt + + + + +host:b--B:h + + + + +C + +b + +a + +d + +C + +h + +mgmt + + + +host:mgmtc--C:mgmt + + + + +host:c--C:h + + + + +D + +D + +mgmt + +h + +a + +b + +c + + + +host:mgmtd--D:mgmt + + + + +host:d--D:h + + + + +A:b--B:a + + + + +A:c--C:a + + + + +B:c--C:b + + + + +B:d--D:b + + + + +C:d--D:c + + + + +D:a--A:d + + + + diff --git a/test/case/ietf_interfaces/ietf_interfaces.yaml b/test/case/ietf_interfaces/ietf_interfaces.yaml index b1418d10b..55c7829ac 100644 --- a/test/case/ietf_interfaces/ietf_interfaces.yaml +++ b/test/case/ietf_interfaces/ietf_interfaces.yaml @@ -41,6 +41,9 @@ - name: bridge_fwd_dual_dut case: bridge_fwd_dual_dut/test.py +- name: bridge_stp_basic + case: bridge_stp_basic/test.py + - name: bridge_vlan_separation case: bridge_vlan_separation/test.py