#!/usr/bin/env python3 r"""LACP Aggregate w/ Degraded Link Verify communication over an LACP link aggregate when individual member links stop forwarding traffic, without carrier loss. .Logical network setup, link breakers (lb1 & lb2) here managed by host PC image::lag-failure.svg[align=center, scaledwidth=75%] The host verifies connectivity with dut2 via dut1 over the aggregate for each failure mode step using the `mon` interface. """ from time import time import infamy import infamy.lag from infamy.netns import TPMR from infamy.util import parallel IPH = "192.168.2.1" IP1 = "192.168.2.41" IP2 = "192.168.2.42" class LinkBreaker: """Encapsulates TPMR based link-breakers.""" def __init__(self, sys, netns): self.net = netns self.lb1 = TPMR(sys.ltop.xlate("host", "lb1a")[1], sys.ltop.xlate("host", "lb1b")[1]).start() self.lb2 = TPMR(sys.ltop.xlate("host", "lb2a")[1], sys.ltop.xlate("host", "lb2b")[1]).start() def forward(self, lb1, lb2): """Set link breakers in forwarding or blocking state.""" getattr(self.lb1, lb1)() getattr(self.lb2, lb2)() def fail_check(self, peer): """Verify connectivity with a given peer during failure.""" sequence = [ ("forward", "forward"), ("forward", "block"), ("block", "forward"), ("forward", "forward") ] total_start = time() print(f"{'LB1':<8} | {'LB2':<8} | {'Status':<8}") print("---------|----------|---------") for lb1, lb2 in sequence: state_start = time() try: print(f"{lb1:<8} | {lb2:<8} | {'...':<8}", end="\r# ") self.forward(lb1, lb2) self.net.must_reach(peer, timeout=30) print(f"{lb1:<8} | {lb2:<8} | {'OK':<8} in " f"{time() - state_start:.2f}s") except Exception as e: print(f"{lb1:<8} | {lb2:<8} | {'FAIL':<8} after " f"{time() - state_start:.2f}s") print(f"\nError encountered: {e}") print(f"Link breakers were in state: LB1='{lb1}', LB2='{lb2}'") raise print(f"Total time: {time() - total_start:.2f}s") 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", "ipv4": { "address": [{"ip": addr, "prefix-length": 24}] } }, { "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, addr, peer): """Configure each DUT specific according to LAG mode and peer""" net = net_init(dut["mon"], addr) dut.put_config_dict("ietf-interfaces", { "interfaces": { "interface": [{ "name": "lag0", "type": "infix-if-type:lag", "lag": { "mode": "lacp", "lacp": {"rate": "fast"}, "link-monitor": {"interval": 100} } }, { "name": dut["link1"], "lag-port": {"lag": "lag0"} }, { "name": dut["link2"], "lag-port": {"lag": "lag0"} }] + net } }) with infamy.Test() as test: with test.step("Set up topology and attach to target DUTs"): env = infamy.Env(edge_mappings=infamy.lag.edge_mappings) dut1 = env.attach("dut1") dut2 = env.attach("dut2") _, mon = env.ltop.xlate("host", "mon") with infamy.IsolatedMacVlan(mon) as ns: lb = LinkBreaker(env, ns) ns.addip(IPH) print(f"Setting up lag0 in LACP mode between {dut1} and {dut2}") with test.step("Set up link aggregate, lag0, between dut1 and dut2"): parallel(lambda: dut_init(dut1, IP1, IP2), lambda: dut_init(dut2, IP2, IP1)) with test.step("Initial connectivity check ..."): ns.must_reach(IP2, timeout=30) with test.step("Verify failure modes"): lb.fail_check(IP2) test.succeed()