From 4260d2414341a04258b40287dd183015cc406cef Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Mon, 16 Dec 2024 01:32:31 +0100 Subject: [PATCH] test: new test, verify basic lag setup Verify connectivity from host to the second DUT via the first, over a link aggregate. The lag starts in static mode and then changes to an LACP aggregate. This verifies not just basic aggregate functionality, but also changing mode, which is quite tricky to get right. Signed-off-by: Joachim Wiberg --- test/case/ietf_interfaces/Readme.adoc | 2 + .../case/ietf_interfaces/ietf_interfaces.yaml | 3 + .../ietf_interfaces/lag_basic/Readme.adoc | 1 + .../ietf_interfaces/lag_basic/lag-basic.svg | 4 + .../ietf_interfaces/lag_basic/lag_basic.adoc | 43 +++++ test/case/ietf_interfaces/lag_basic/test.py | 165 ++++++++++++++++++ .../ietf_interfaces/lag_basic/topology.dot | 33 ++++ .../ietf_interfaces/lag_basic/topology.svg | 76 ++++++++ 8 files changed, 327 insertions(+) create mode 120000 test/case/ietf_interfaces/lag_basic/Readme.adoc create mode 100644 test/case/ietf_interfaces/lag_basic/lag-basic.svg create mode 100644 test/case/ietf_interfaces/lag_basic/lag_basic.adoc create mode 100755 test/case/ietf_interfaces/lag_basic/test.py create mode 100644 test/case/ietf_interfaces/lag_basic/topology.dot create mode 100644 test/case/ietf_interfaces/lag_basic/topology.svg diff --git a/test/case/ietf_interfaces/Readme.adoc b/test/case/ietf_interfaces/Readme.adoc index ff5f893e..42f6061e 100644 --- a/test/case/ietf_interfaces/Readme.adoc +++ b/test/case/ietf_interfaces/Readme.adoc @@ -33,6 +33,8 @@ include::bridge_vlan_separation/Readme.adoc[] include::dual_bridge/Readme.adoc[] +include::lag_basic/Readme.adoc[] + include::igmp_basic/Readme.adoc[] include::igmp_vlan/Readme.adoc[] diff --git a/test/case/ietf_interfaces/ietf_interfaces.yaml b/test/case/ietf_interfaces/ietf_interfaces.yaml index 2f2bbe8e..4709465b 100644 --- a/test/case/ietf_interfaces/ietf_interfaces.yaml +++ b/test/case/ietf_interfaces/ietf_interfaces.yaml @@ -35,6 +35,9 @@ - name: ipv4_autoconf case: ipv4_autoconf/test.py +- name: lag_basic + case: lag_basic/test.py + - name: bridge_fwd_sgl_dut case: bridge_fwd_sgl_dut/test.py diff --git a/test/case/ietf_interfaces/lag_basic/Readme.adoc b/test/case/ietf_interfaces/lag_basic/Readme.adoc new file mode 120000 index 00000000..ae7efc4b --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/Readme.adoc @@ -0,0 +1 @@ +lag_basic.adoc \ No newline at end of file diff --git a/test/case/ietf_interfaces/lag_basic/lag-basic.svg b/test/case/ietf_interfaces/lag_basic/lag-basic.svg new file mode 100644 index 00000000..b25aa63b --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/lag-basic.svg @@ -0,0 +1,4 @@ + + + +
eth
eth
eth
bridge
lag
dut1
eth
eth
lag
ip
pc
eth
ip
dut2
\ No newline at end of file diff --git a/test/case/ietf_interfaces/lag_basic/lag_basic.adoc b/test/case/ietf_interfaces/lag_basic/lag_basic.adoc new file mode 100644 index 00000000..32fec881 --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/lag_basic.adoc @@ -0,0 +1,43 @@ +=== Ling Aggregation Basic +==== Description +Verify communication over a link aggregate in static and LACP operating +modes during basic failure scenarios. + +.Internal network setup, PC verifies connectivity with dut2 via dut1 +ifdef::topdoc[] +image::../../test/case/ietf_interfaces/lag_basic/lag-basic.svg[Internal networks] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::lag_basic/lag-basic.svg[Internal networks] +endif::testgroup[] +ifndef::testgroup[] +image::lag-basic.svg[Internal networks] +endif::testgroup[] +endif::topdoc[] + +The host verifies connectivity with dut2 via dut1 over the aggregate for +each test step using the `mon` interface. + +==== Topology +ifdef::topdoc[] +image::{topdoc}../../test/case/ietf_interfaces/lag_basic/topology.svg[Ling Aggregation Basic topology] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::lag_basic/topology.svg[Ling Aggregation Basic topology] +endif::testgroup[] +ifndef::testgroup[] +image::topology.svg[Ling Aggregation Basic topology] +endif::testgroup[] +endif::topdoc[] +==== Test sequence +. Set up topology and attach to target DUTs +. Set up LACP link aggregate, lag0, on dut1 and dut2 +. Verify failure modes for lacp mode +. Set up static link aggregate, lag0, on dut1 and dut2 +. Verify failure modes for static mode + + +<<< + diff --git a/test/case/ietf_interfaces/lag_basic/test.py b/test/case/ietf_interfaces/lag_basic/test.py new file mode 100755 index 00000000..9cbd0d25 --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/test.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +r"""Ling Aggregation Basic + +Verify communication over a link aggregate in static and LACP operating +modes during basic failure scenarios. + +.Internal network setup, PC verifies connectivity with dut2 via dut1 +image::lag-basic.svg[Internal networks] + +The host verifies connectivity with dut2 via dut1 over the aggregate for +each test step using the `mon` interface. + +""" +from time import sleep, time +from datetime import datetime +import infamy +from infamy.util import parallel, until + + +class DumbLinkBreaker: + """Encapsulates basic, dumb link-breaking ops over SSH.""" + + def __init__(self, sys, dut, netns): + self.env = sys + self.dut = dut + self.net = netns + self.tgt = {} + for i, (name, _) in dut.items(): + self.tgt[i] = env.attach(name, "mgmt", "ssh") + + def set_link(self, link, updown): + """Set link up or down, verify before returning.""" + def set_and_verify(i): + name, dut = self.dut[i] + + cmd = self.tgt[i].runsh(f"sudo ip link set {dut[link]} {updown}") + if cmd.returncode: + for out in [cmd.stdout, cmd.stderr]: + if out: + print(f"{name}: {out.rstrip()}") + raise RuntimeError(f"{name}: failed setting {link} {updown}") + + for _ in range(10): + sleep(0.1) + check = self.tgt[i].runsh(f"ip link show {dut[link]}") + if f"state {updown.upper()}" in check.stdout: + break + else: + raise RuntimeError(f"{name}: {dut[link]} did not go {updown}") + + parallel(*[lambda i=i: set_and_verify(i) for i in self.dut]) + + def fail_check(self, peer): + """Verify connectivity with peer during link failure.""" + sequence = [ + [("link1", "up"), ("link2", "up")], + [("link1", "down"), ("link2", "up")], + [("link1", "up"), ("link2", "down")], + [("link1", "up"), ("link2", "up")] + ] + + total_start = time() + for state in sequence: + state_start = time() + print(f"{datetime.now().strftime('%H:%M:%S.%f')[:-3]} {state}") + + for link, updown in state: + self.set_link(link, updown) + self.net.must_reach(peer, timeout=10) + + print(f"Completed in {time() - state_start:.2f}s") + + print(f"Total time: {time() - total_start:.2f}s") + + +def lag_init(mode): + """Set up mode specific attributes for the LAG""" + if mode == "lacp": + lag = [{ + "name": "lag0", + "lag": {"lacp": {"rate": "fast"}} + }] + else: + lag = [] + return lag + + +def net_init(host, addr): + """Set up DUT network, dut1 bridges host port with lag0""" + if host: + net = [{ + "name": "br0", + "type": "infix-if-type:bridge", + }, { + "name": host, + "bridge-port": {"bridge": "br0"} + }, { + "name": "lag0", + "bridge-port": {"bridge": "br0"} + }] + else: + net = [{ + "name": "lag0", + "ipv4": { + "address": [{"ip": addr, "prefix-length": 24}] + } + }] + return net + + +def dut_init(dut, mode, addr): + """Set up link aggregate on dut""" + net = net_init(dut["mon"], addr) + lag = lag_init(mode) + + dut.put_config_dict("ietf-interfaces", { + "interfaces": { + "interface": [{ + "name": "lag0", + "type": "infix-if-type:lag", + "lag": { + "mode": mode, + "link-monitor": {"interval": 100} + } + }, { + "name": dut["link1"], + "lag-port": {"lag": "lag0"} + }, { + "name": dut["link2"], + "lag-port": {"lag": "lag0"} + }] + net + lag + } + }) + + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUTs"): + env = infamy.Env() + dut1 = env.attach("dut1", "mgmt") + dut2 = env.attach("dut2", "mgmt") + + _, mon = env.ltop.xlate("host", "mon") + with infamy.IsolatedMacVlan(mon) as ns: + dm = { + '1': ("dut1", dut1), + '2': ("dut2", dut2) + } + lb = DumbLinkBreaker(env, dm, ns) + ns.addip("192.168.2.1") + + with test.step("Set up LACP link aggregate, lag0, on dut1 and dut2"): + parallel(lambda: dut_init(dut1, "lacp", None), + lambda: dut_init(dut2, "lacp", "192.168.2.42")) + + with test.step("Verify failure modes for lacp mode"): + lb.fail_check("192.168.2.42") + + with test.step("Set up static link aggregate, lag0, on dut1 and dut2"): + parallel(lambda: dut_init(dut1, "static", None), + lambda: dut_init(dut2, "static", "192.168.2.42")) + + with test.step("Verify failure modes for static mode"): + lb.fail_check("192.168.2.42") + + test.succeed() diff --git a/test/case/ietf_interfaces/lag_basic/topology.dot b/test/case/ietf_interfaces/lag_basic/topology.dot new file mode 100644 index 00000000..9715914e --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/topology.dot @@ -0,0 +1,33 @@ +graph "lag" { + layout="neato"; + overlap="false"; + esep="+23"; + + node [shape=record, fontsize=12, fontname="DejaVu Sans Mono, Book"]; + edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; + + host [ + label="host | { mgmt1 | mon | \n\n\n\n | mgmt2 }", + pos="0,15!", + requires="controller", + ]; + + dut1 [ + label="{ mgmt | mon } | { dut1\r | { link1 | link2 } }", + pos="2,15.25!", + requires="infix", + ]; + + dut2 [ + label=" mgmt | { { link1 | link2 } | dut2\r }", + pos="2,14.75!", + requires="infix", + ]; + + host:mgmt1 -- dut1:mgmt [requires="mgmt", color=lightgray] + host:mon -- dut1:mon // Monitor connection to dut2 via dut1 + host:mgmt2 -- dut2:mgmt [requires="mgmt", color=lightgrey] + + dut1:link1 -- dut2:link1 [color=black, fontcolor=black, penwidth=3] + dut1:link2 -- dut2:link2 [color=black, fontcolor=black, penwidth=3] +} diff --git a/test/case/ietf_interfaces/lag_basic/topology.svg b/test/case/ietf_interfaces/lag_basic/topology.svg new file mode 100644 index 00000000..d346d430 --- /dev/null +++ b/test/case/ietf_interfaces/lag_basic/topology.svg @@ -0,0 +1,76 @@ + + + + + + +lag + + + +host + +host + +mgmt1 + +mon + + +mgmt2 + + + +dut1 + +mgmt + +mon + +dut1 + +link1 + +link2 + + + +host:mgmt1--dut1:mgmt + + + + +host:mon--dut1:mon + + + + +dut2 + +mgmt + +link1 + +link2 + +dut2 + + + +host:mgmt2--dut2:mgmt + + + + +dut1:link1--dut2:link1 + + + + +dut1:link2--dut2:link2 + + + +