Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/statd/python/yanger/ietf_ospf.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ def add_areas(control_protocols):

add_routes(control_protocol["ietf-ospf:ospf"])
control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"]["ietf-ospf:area"] = areas
insert(control_protocols, "control-plane-protocol", [control_protocol])
control_protocols.setdefault("control-plane-protocol", []).append(control_protocol)


def add_areas6(control_protocols):
Expand Down Expand Up @@ -391,7 +391,7 @@ def add_areas6(control_protocols):

add_routes(control_protocol["ietf-ospf:ospf"], ipv6=True)
control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"]["ietf-ospf:area"] = areas
insert(control_protocols, "control-plane-protocol", [control_protocol])
control_protocols.setdefault("control-plane-protocol", []).append(control_protocol)


def operational():
Expand Down
5 changes: 5 additions & 0 deletions test/case/routing/Readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Tests verifying standard routing protocols and configuration:
- RIP passive interface configuration
- RIP route redistribution (connected, static, and OSPF)
- RIP multi-hop routing with multiple neighbors
- OSPF and RIP for IPv4 and IPv6 at the same time
- Route preference handling for OSPF routes
- Route preference handling for DHCP-learned routes
- Administrative distance configuration (route preference 255)
Expand Down Expand Up @@ -72,6 +73,10 @@ include::rip_multihop/Readme.adoc[]

<<<

include::dual_stack/Readme.adoc[]

<<<

include::route_pref_ospf/Readme.adoc[]

<<<
Expand Down
3 changes: 3 additions & 0 deletions test/case/routing/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@

- name: RIP Multi-hop
suite: rip_multihop/test.yaml

- name: Dual Stack Routing
suite: dual_stack/test.yaml
6 changes: 6 additions & 0 deletions test/case/routing/dual_stack/Readme.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
include::ospf.adoc[]

<<<

include::rip.adoc[]

28 changes: 28 additions & 0 deletions test/case/routing/dual_stack/ospf.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=== OSPF Dual Stack

ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/dual_stack]

==== Description

The status of each OSPF instance must stay visible in the operational
datastore when the IPv4 and IPv6 versions run at the same time.

R1 and R2 have an IPv4 and an IPv6 address on the link between them, with
OSPF for both address families on that link. The test waits until the
operational datastore shows the neighbor in each instance, a full
adjacency for OSPF and a known peer for RIP. A RIP router does not send
the link prefix back on the link, so each router also redistributes a
loopback address to have something to advertise.

==== Topology

image::topology.svg[OSPF Dual Stack topology, align=center, scaledwidth=75%]

==== Sequence

. Set up topology and attach to target DUTs
. Configure OSPF for IPv4 and IPv6 on R1 and R2
. Wait for the IPv4 OSPF neighbor on R1 and R2
. Wait for the IPv6 OSPF neighbor on R1 and R2


1 change: 1 addition & 0 deletions test/case/routing/dual_stack/ospf.py
28 changes: 28 additions & 0 deletions test/case/routing/dual_stack/rip.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
=== RIP Dual Stack

ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/dual_stack]

==== Description

The status of each RIP instance must stay visible in the operational
datastore when the IPv4 and IPv6 versions run at the same time.

R1 and R2 have an IPv4 and an IPv6 address on the link between them, with
RIP for both address families on that link. The test waits until the
operational datastore shows the neighbor in each instance, a full
adjacency for OSPF and a known peer for RIP. A RIP router does not send
the link prefix back on the link, so each router also redistributes a
loopback address to have something to advertise.

==== Topology

image::topology.svg[RIP Dual Stack topology, align=center, scaledwidth=75%]

==== Sequence

. Set up topology and attach to target DUTs
. Configure RIP for IPv4 and IPv6 on R1 and R2
. Wait for the IPv4 RIP neighbor on R1 and R2
. Wait for the IPv6 RIP neighbor on R1 and R2


1 change: 1 addition & 0 deletions test/case/routing/dual_stack/rip.py
171 changes: 171 additions & 0 deletions test/case/routing/dual_stack/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
#!/usr/bin/env python3
"""{proto} Dual Stack

The status of each {proto} instance must stay visible in the operational
datastore when the IPv4 and IPv6 versions run at the same time.

R1 and R2 have an IPv4 and an IPv6 address on the link between them, with
{proto} for both address families on that link. The test waits until the
operational datastore shows the neighbor in each instance, a full
adjacency for OSPF and a known peer for RIP. A RIP router does not send
the link prefix back on the link, so each router also redistributes a
loopback address to have something to advertise.
"""

import infamy
import infamy.route as route
from infamy.util import until, parallel


class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.add_argument("--proto", type=str.lower, choices=["ospf", "rip"])


ROUTER_ID = {"R1": "1.1.1.1", "R2": "2.2.2.2"}
IPV4 = {"R1": "192.168.50.1", "R2": "192.168.50.2"}
IPV6 = {"R1": "2001:db8:50::1", "R2": "2001:db8:50::2"}
LO4 = {"R1": "192.168.100.1", "R2": "192.168.200.1"}
LO6 = {"R1": "2001:db8:100::1", "R2": "2001:db8:200::1"}

# IPv4 and IPv6 instance type for each protocol
TYPES = {
"ospf": ["infix-routing:ospfv2", "infix-routing:ospfv3"],
"rip": ["infix-routing:ripv2", "infix-routing:ripng"],
}


def ospf_instance(proto, name, link):
return {
"type": proto,
"name": "default",
"ospf": {
"explicit-router-id": ROUTER_ID[name],
"areas": {
"area": [{
"area-id": "0.0.0.0",
"interfaces": {
"interface": [{
"enabled": True,
"name": link,
"hello-interval": 1,
"dead-interval": 3
}]
}
}]
}
}
}


def rip_instance(proto, name, link):
return {
"type": proto,
"name": "default",
"rip": {
"timers": {
"update-interval": 5,
"invalid-interval": 15,
"flush-interval": 20
},
"redistribute": {
"redistribute": [{"protocol": "connected"}]
},
"interfaces": {
"interface": [{
"interface": link
}]
}
}
}


def config_target(target, name, link, proto):
instance = ospf_instance if proto == "ospf" else rip_instance
target.put_config_dicts({
"ietf-interfaces": {
"interfaces": {
"interface": [{
"name": link,
"enabled": True,
"ipv4": {
"forwarding": True,
"address": [{
"ip": IPV4[name],
"prefix-length": 24
}]
},
"ipv6": {
"forwarding": True,
"address": [{
"ip": IPV6[name],
"prefix-length": 64
}]
}
}, {
"name": "lo",
"ipv4": {
"address": [{
"ip": LO4[name],
"prefix-length": 32
}]
},
"ipv6": {
"address": [{
"ip": LO6[name],
"prefix-length": 128
}]
}
}]
}
},
"ietf-routing": {
"routing": {
"control-plane-protocols": {
"control-plane-protocol": [instance(t, name, link) for t in TYPES[proto]]
}
}
}
})


def has_neighbor(target, link, neighbor, proto, version):
if proto == "ospf":
return route.ospf_get_neighbor(target, "0.0.0.0", link, ROUTER_ID[neighbor],
proto=version)

# RIPng peers are known by their link-local address only
return route.rip_has_neighbors(target, proto=version)


def wait_neighbor(target, link, neighbor, proto, version):
until(lambda: has_neighbor(target, link, neighbor, proto, version), attempts=200)


with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env(args=ArgumentParser())
proto = env.args.proto
ipv4, ipv6 = TYPES[proto]

R1, R2 = parallel(lambda: env.attach("R1", "mgmt"),
lambda: env.attach("R2", "mgmt"))
route.skip_unless_supported(test, proto, R1, R2)

_, R1link = env.ltop.xlate("R1", "link")
_, R2link = env.ltop.xlate("R2", "link")

with test.step(f"Configure {proto} for IPv4 and IPv6 on R1 and R2"):
parallel(lambda: config_target(R1, "R1", R1link, proto),
lambda: config_target(R2, "R2", R2link, proto))

with test.step(f"Wait for the IPv4 {proto} neighbor on R1 and R2"):
parallel(lambda: wait_neighbor(R1, R1link, "R2", proto, ipv4),
lambda: wait_neighbor(R2, R2link, "R1", proto, ipv4))

with test.step(f"Wait for the IPv6 {proto} neighbor on R1 and R2"):
parallel(lambda: wait_neighbor(R1, R1link, "R2", proto, ipv6),
lambda: wait_neighbor(R2, R2link, "R1", proto, ipv6))

test.succeed()
11 changes: 11 additions & 0 deletions test/case/routing/dual_stack/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
- settings:
test-spec: <case>.adoc

- name: OSPF Dual Stack
case: ospf.py
opts: ["--proto", "OSPF"]

- name: RIP Dual Stack
case: rip.py
opts: ["--proto", "RIP"]
32 changes: 32 additions & 0 deletions test/case/routing/dual_stack/topology.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
graph "dual_stack" {
layout="neato";
overlap="false";
esep="+20";
size=10

node [shape=record, fontname="DejaVu Sans Mono, Book"];
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];

PC [
label="PC | { <mgmt1> mgmt1 | <mgmt2> mgmt2 }",
pos="-20,130!",
requires="controller",
];

R1 [
label="{ <mgmt> mgmt | <link> link } | R1 \n router-id 1.1.1.1",
pos="160,230!",

requires="infix",
];
R2 [
label="{ <link> link | <mgmt> mgmt } | R2 \n router-id 2.2.2.2",
pos="160,30!",

requires="infix",
];

PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"]
PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"]
R1:link -- R2:link [label="192.168.50.0/24\n2001:db8:50::/64", headlabel=".2\n::2", taillabel=".1\n::1", labeldistance=2, fontcolor="black", color="black"]
}
65 changes: 65 additions & 0 deletions test/case/routing/dual_stack/topology.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading