Files
infix/test/case/routing/route_pref_ospf/test.py
T
Joachim Wiberg 1bbd80d8c7 test: drop leading ietf/infix prefix from directories
Let's drop the leading IETF or Infix prefixes from tests.  Initially the
idea was to mimnic the YANG models, but it's difficult to navigate and
does not provide any real benefit to developers or end-users.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-11-20 20:23:23 +01:00

198 lines
7.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Route preference: OSPF vs Static
This test configures a device with both an OSPF-acquired route on a
dedicated interface and a static route to the same destination on
another interface. The static route has a higher preference value than
OSPF.
Initially, the device should prefer the OSPF route; if the OSPF route
becomes unavailable, the static route should take over.
"""
import infamy
import infamy.route as route
from infamy.util import until, parallel
from infamy.netns import TPMR
def configure_interface(name, ip, prefix_length, forwarding=True):
return {
"name": name,
"enabled": True,
"ipv4": {
"forwarding": forwarding,
"address": [{"ip": ip, "prefix-length": prefix_length}]
}
}
def config_target1(target, data, link, ospf):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [
configure_interface(data, "192.168.10.1", 24),
configure_interface(link, "192.168.50.1", 24),
configure_interface(ospf, "192.168.60.1", 24)
]
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [
{
"type": "infix-routing:ospfv2",
"name": "ospf-default",
"ospf": {
"redistribute": {
"redistribute": [{"protocol": "connected"}]
},
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces": {
"interface": [{
"name": ospf,
"hello-interval": 1,
"dead-interval": 3
}]
}
}]
}
}
},
{
"type": "infix-routing:static",
"name": "dot20",
"static-routes": {
"ipv4": {
"route": [{
"destination-prefix": "192.168.20.0/24",
"next-hop": {"next-hop-address": "192.168.50.2"},
"route-preference": 120
}]
}
}
}
]
}
}
}
})
def config_target2(target, data, link, ospf):
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [
configure_interface(data, "192.168.20.2", 24),
configure_interface(link, "192.168.50.2", 24),
configure_interface(ospf, "192.168.60.2", 24)
]
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [
{
"type": "infix-routing:ospfv2",
"name": "ospf-default",
"ospf": {
"redistribute": {
"redistribute": [{"protocol": "connected"}]
},
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces": {
"interface": [{
"name": ospf,
"hello-interval": 1,
"dead-interval": 3
}]
}
}]
}
}
},
{
"type": "infix-routing:static",
"name": "default",
"static-routes": {
"ipv4": {
"route": [{
"destination-prefix": "0.0.0.0/0",
"next-hop": {"next-hop-address": "192.168.50.1"}
}]
}
}
}
]
}
}
}
})
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env()
R1 = env.attach("R1", "mgmt")
R2 = env.attach("R2", "mgmt")
with test.step("Set up TPMR between R1ospf and R2ospf"):
ospf_breaker = TPMR(env.ltop.xlate("PC", "R1_ospf")[1], env.ltop.xlate("PC", "R2_ospf")[1]).start()
with test.step("Configure targets"):
_, R1data = env.ltop.xlate("R1", "data")
_, R1link = env.ltop.xlate("R1", "link")
_, R1ospf = env.ltop.xlate("R1", "ospf")
_, R2data = env.ltop.xlate("R2", "data")
_, R2link = env.ltop.xlate("R2", "link")
_, R2ospf = env.ltop.xlate("R2", "ospf")
parallel(config_target1(R1, R1data, R1link, R1ospf), config_target2(R2, R2data, R2link, R2ospf))
with test.step("Set up persistent MacVlan namespaces"):
_, hport_data1 = env.ltop.xlate("PC", "data1")
_, hport_data2 = env.ltop.xlate("PC", "data2")
ns1 = infamy.IsolatedMacVlan(hport_data1).start()
ns1.addip("192.168.10.11", prefix_length=24)
ns1.addroute("default", "192.168.10.1")
ns2 = infamy.IsolatedMacVlan(hport_data2).start()
ns2.addip("192.168.20.22", prefix_length=24)
ns2.addroute("default", "192.168.20.2")
with test.step("Wait for OSPF and static routes"):
print("Waiting for OSPF and static routes...")
until(lambda: route.ipv4_route_exist(R1, "192.168.20.0/24", proto="ietf-ospf:ospfv2"), attempts=200)
until(lambda: route.ipv4_route_exist(R1, "192.168.20.0/24", proto="ietf-routing:static"), attempts=200)
with test.step("Verify connectivity from PC:data1 to PC:data2 via OSPF"):
ns1.must_reach("192.168.20.22")
ospf_route_active = route.ipv4_route_exist(R1, "192.168.20.0/24", proto="ietf-ospf:ospfv2", active_check=True)
assert ospf_route_active, "OSPF route should be preferred when available."
hops = [row[1] for row in ns1.traceroute("192.168.20.22")]
assert "192.168.60.2" in hops, f"Path does not use expected OSPF route: {hops}"
with test.step("Simulate OSPF route loss by blocking OSPF interface"):
ospf_breaker.block()
until(lambda: not route.ipv4_route_exist(R1, "192.168.20.0/24", proto="ietf-ospf:ospfv2"), attempts=200)
with test.step("Verify connectivity via static route after OSPF failover"):
ns1.must_reach("192.168.20.22")
static_route_active = route.ipv4_route_exist(R1, "192.168.20.0/24", proto="ietf-routing:static", active_check=True)
assert static_route_active, "Static route should be preferred when OSPF route is unavailable."
hops = [row[1] for row in ns1.traceroute("192.168.20.22")]
assert "192.168.50.2" in hops, f"Path does not use expected static route: {hops}"
test.succeed()