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 + + + +