mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 20:43:02 +02:00
@@ -8,3 +8,4 @@
|
||||
/test/.venv
|
||||
/test/.log
|
||||
/local.mk
|
||||
/test/spec/Readme.adoc
|
||||
|
||||
@@ -27,13 +27,13 @@ it maintenance-free. Configuration and data, e.g, containers, is stored
|
||||
on separate partitions to ensure complete separation from system files
|
||||
and allow for seamless backup, restore, and provisioning.
|
||||
|
||||
In itself Infix is perfectly suited for dedicated networking tasks and
|
||||
native support for Docker containers provides a versatile platform that
|
||||
can easily be adapted to any customer need. Be it legacy applications,
|
||||
network protocols, process monitoring, or edge data analysis, it can run
|
||||
close to end equipment. Either directly connected on dedicated Ethernet
|
||||
ports or indirectly using virtual network cables to exist on the same
|
||||
LAN as other connected equipment.
|
||||
In itself, Infix is perfectly suited for dedicated networking tasks, and
|
||||
with native support for Docker containers, the operating system provides
|
||||
a versatile platform that can easily be adapted to any customer need.
|
||||
Be it legacy applications, network protocols, process monitoring, or
|
||||
edge data analysis, it can run close to end equipment. Either directly
|
||||
connected on dedicated Ethernet ports or indirectly using virtual
|
||||
network cables to exist on the same LAN as other connected equipment.
|
||||
|
||||
The simple design of Infix provides complete control over both system
|
||||
and data, minimal cognitive burden, and makes it incredibly easy to get
|
||||
|
||||
@@ -33,6 +33,7 @@ CONFIG_KALLSYMS_ALL=y
|
||||
CONFIG_PROFILING=y
|
||||
CONFIG_SMP=y
|
||||
CONFIG_EFI=y
|
||||
CONFIG_KPROBES=y
|
||||
# CONFIG_GCC_PLUGINS is not set
|
||||
CONFIG_MODULES=y
|
||||
CONFIG_MODULE_UNLOAD=y
|
||||
@@ -264,5 +265,5 @@ CONFIG_PANIC_ON_OOPS=y
|
||||
CONFIG_PANIC_TIMEOUT=20
|
||||
CONFIG_DETECT_HUNG_TASK=y
|
||||
CONFIG_BOOTPARAM_HUNG_TASK_PANIC=y
|
||||
# CONFIG_FTRACE is not set
|
||||
CONFIG_FUNCTION_TRACER=y
|
||||
CONFIG_UNWINDER_FRAME_POINTER=y
|
||||
|
||||
+1
-1
Submodule buildroot updated: 475ea17ffe...897bb2cf43
@@ -66,8 +66,14 @@ int core_post_hook(sr_session_ctx_t *session, uint32_t sub_id, const char *modul
|
||||
num_changes = 0;
|
||||
return SR_ERR_OK;
|
||||
case SR_EV_DONE:
|
||||
if (num_changes <= 0) {
|
||||
ERROR("BUG in core_post_hook(): callback tracking out of sync");
|
||||
abort();
|
||||
}
|
||||
|
||||
if (--num_changes == 0)
|
||||
break;
|
||||
|
||||
return SR_ERR_OK;
|
||||
default:
|
||||
ERROR("core_post_hook() should not be called with event %s", ev2str(event));
|
||||
|
||||
@@ -492,10 +492,12 @@ def add_ospf(control_protocols):
|
||||
neighbor = {}
|
||||
neighbor["neighbor-router-id"] = neigh["neighborIp"]
|
||||
neighbor["address"] = neigh["ifaceAddress"]
|
||||
neighbor["dr-router-id"] = neigh["routerDesignatedId"]
|
||||
neighbor["bdr-router-id"] = neigh["routerDesignatedBackupId"]
|
||||
neighbor["dead-timer"] = neigh["routerDeadIntervalTimerDueMsec"]
|
||||
neighbor["state"] = frr_to_ietf_neighbor_state(neigh["nbrState"])
|
||||
if neigh.get("routerDesignatedId"):
|
||||
neighbor["dr-router-id"] = neigh["routerDesignatedId"]
|
||||
if neigh.get("routerDesignatedBackupId"):
|
||||
neighbor["bdr-router-id"] = neigh["routerDesignatedBackupId"]
|
||||
neighbors.append(neighbor)
|
||||
|
||||
interface["ietf-ospf:neighbors"] = {}
|
||||
|
||||
@@ -28,3 +28,6 @@
|
||||
|
||||
- name: infix-services
|
||||
suite: infix_services/infix_services.yaml
|
||||
|
||||
- name: use-cases
|
||||
suite: use_case/use_case.yml
|
||||
|
||||
@@ -4,16 +4,11 @@ Upgrade
|
||||
|
||||
Verify it is possible to upgrade.
|
||||
"""
|
||||
import concurrent.futures
|
||||
import functools
|
||||
import http.server
|
||||
import os
|
||||
import socket
|
||||
import time
|
||||
|
||||
import netifaces
|
||||
|
||||
import infamy
|
||||
import infamy.file_server as srv
|
||||
|
||||
SRVPORT = 8008
|
||||
|
||||
@@ -27,25 +22,6 @@ PKGPATH = os.path.join(
|
||||
"package"
|
||||
)
|
||||
|
||||
class FileServer(http.server.HTTPServer):
|
||||
class RequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def log_message(*args, **kwargs):
|
||||
pass
|
||||
|
||||
address_family = socket.AF_INET6
|
||||
|
||||
def __init__(self, server_address, directory):
|
||||
rh = functools.partial(FileServer.RequestHandler, directory=directory)
|
||||
self.__tp = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
||||
super().__init__(server_address, rh)
|
||||
|
||||
def __enter__(self):
|
||||
self.__tp.submit(self.serve_forever)
|
||||
|
||||
def __exit__(self, _, __, ___):
|
||||
self.shutdown()
|
||||
self.__tp.shutdown()
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to target DUT"):
|
||||
env = infamy.Env()
|
||||
@@ -66,7 +42,7 @@ with infamy.Test() as test:
|
||||
hip = netifaces.ifaddresses(hport)[netifaces.AF_INET6][0]["addr"]
|
||||
hip = hip.replace(f"%{hport}", f"%{tport}")
|
||||
|
||||
with FileServer(("::", SRVPORT), BUNDLEDIR):
|
||||
with srv.FileServer(("::", SRVPORT), BUNDLEDIR):
|
||||
|
||||
with test.step("Start installation of selected package"):
|
||||
print(f"Installing {os.path.basename(env.args.package)}")
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
:testgroup:
|
||||
|
||||
== Use case tests
|
||||
Contains test of multiple parts of Infix together.
|
||||
|
||||
<<<
|
||||
|
||||
include::ospf_container/Readme.adoc[]
|
||||
@@ -0,0 +1,98 @@
|
||||
=== OSPF Container
|
||||
==== Description
|
||||
This use-case test verifies connectivity in an OSPF network to services
|
||||
running as hosted containers inside each router.
|
||||
|
||||
NOTE: The _Controller_, _ABR_, and `data` connections are simulated by
|
||||
the test PC. The `ringN` ports are connected to other DUTs via the test
|
||||
PC, which can act as a link breaker.
|
||||
|
||||
.Use-case overview.
|
||||
[#img-overview]
|
||||
ifdef::topdoc[]
|
||||
image::../../test/case/use_case/ospf_container/overview.svg[]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::ospf_container/overview.svg[]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::overview.svg[]
|
||||
endif::testgroup[]
|
||||
endif::topdoc[]
|
||||
|
||||
The DUTs are connected in a routed topology inside their own OSPF area.
|
||||
A single area border router (ABR) is used to access the controller
|
||||
network in OSPF area 0. Each router also has "test point" connections
|
||||
where the controller can attach other than its connection in area 0.
|
||||
|
||||
- The ringN ports are intended to be connected to neighboring DUTs, but
|
||||
may at each end of the bus be used as test points
|
||||
- The data ports are intended to be test points for verifying
|
||||
connectivity with container B via br1
|
||||
- The uplink ports are for connecting to the ABR, at least one of the
|
||||
DUTs should not have a connection to the ABR, this to verify routing
|
||||
via another DUT
|
||||
- Area 1 is 10.1.Rn.0/16, and each router is assigned a /24
|
||||
|
||||
Each DUT hosts one application container and one system container, all
|
||||
have the same setup, with only different subnets assigned. A third
|
||||
container is used to manipulate the firewall of each DUT, providing port
|
||||
forwarding and masquerading.
|
||||
|
||||
Devices attached to the first bridge, `br0`, are supposed to be easily
|
||||
accesible using IPv4, so internally they use IPv4 too, and to avoid any
|
||||
risk of clashing with external IP subnets, IPv4 link-local addresses are
|
||||
employed: `br0` request 169.254.1.1, so the second container (B) always
|
||||
can reach it, the first container (A) reqquest 169.254.1.2 and the
|
||||
second (B) request 169.254.1.3. The network for devices attached to the
|
||||
second bridge, `br1`, only use IPv6 link-local addresses.
|
||||
|
||||
.Internal network setup, here router R1 on subnet 10.1.1.1/24.
|
||||
[#img-setup]
|
||||
ifdef::topdoc[]
|
||||
image::../../test/case/use_case/ospf_container/internal-network.svg[Internal networks]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::ospf_container/internal-network.svg[Internal networks]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::internal-network.svg[Internal networks]
|
||||
endif::testgroup[]
|
||||
endif::topdoc[]
|
||||
|
||||
- *Container A* runs a very basic web server, it runs on port 80 inside
|
||||
the container, and `br0`, but is accessible outside on port 8080.
|
||||
The controller connects to each of these servers from OSPF area 0.
|
||||
For the controller to be able to distinguish between the servers,
|
||||
they all serve slightly different content
|
||||
- *Container B* runs a complete system with an SSH server. During the
|
||||
test, the controller connects to this container using the `data` port
|
||||
to ensure the container can access all other parts of the network.
|
||||
To distinguish between the different container B's, each container
|
||||
will have a unique hostname derived from the chassis MAC address
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::../../test/case/use_case/ospf_container/topology.svg[OSPF Container topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::ospf_container/topology.svg[OSPF Container topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[OSPF Container topology]
|
||||
endif::testgroup[]
|
||||
endif::topdoc[]
|
||||
==== Test sequence
|
||||
. Set up topology and attach to target DUTs
|
||||
. Configure DUTs
|
||||
. Wait for all routers to peer
|
||||
. Verify ABR:data can access container A on R1 (10.1.1.101)
|
||||
. Verify ABR:data can access container A on R2 (10.1.2.101)
|
||||
. Verify ABR:data can access container A on R3 (10.1.3.101)
|
||||
|
||||
|
||||
<<<
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 720 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 406 KiB |
Executable
+644
@@ -0,0 +1,644 @@
|
||||
#!/usr/bin/env python3
|
||||
r"""OSPF Container
|
||||
|
||||
This use-case test verifies connectivity in an OSPF network to services
|
||||
running as hosted containers inside each router.
|
||||
|
||||
NOTE: The _Controller_, _ABR_, and `data` connections are simulated by
|
||||
the test PC. The `ringN` ports are connected to other DUTs via the test
|
||||
PC, which can act as a link breaker.
|
||||
|
||||
.Use-case overview.
|
||||
[#img-overview]
|
||||
image::overview.svg[]
|
||||
|
||||
The DUTs are connected in a routed topology inside their own OSPF area.
|
||||
A single area border router (ABR) is used to access the controller
|
||||
network in OSPF area 0. Each router also has "test point" connections
|
||||
where the controller can attach other than its connection in area 0.
|
||||
|
||||
- The ringN ports are intended to be connected to neighboring DUTs, but
|
||||
may at each end of the bus be used as test points
|
||||
- The data ports are intended to be test points for verifying
|
||||
connectivity with container B via br1
|
||||
- The uplink ports are for connecting to the ABR, at least one of the
|
||||
DUTs should not have a connection to the ABR, this to verify routing
|
||||
via another DUT
|
||||
- Area 1 is 10.1.Rn.0/16, and each router is assigned a /24
|
||||
|
||||
Each DUT hosts one application container and one system container, all
|
||||
have the same setup, with only different subnets assigned. A third
|
||||
container is used to manipulate the firewall of each DUT, providing port
|
||||
forwarding and masquerading.
|
||||
|
||||
Devices attached to the first bridge, `br0`, are supposed to be easily
|
||||
accesible using IPv4, so internally they use IPv4 too, and to avoid any
|
||||
risk of clashing with external IP subnets, IPv4 link-local addresses are
|
||||
employed: `br0` request 169.254.1.1, so the second container (B) always
|
||||
can reach it, the first container (A) reqquest 169.254.1.2 and the
|
||||
second (B) request 169.254.1.3. The network for devices attached to the
|
||||
second bridge, `br1`, only use IPv6 link-local addresses.
|
||||
|
||||
.Internal network setup, here router R1 on subnet 10.1.1.1/24.
|
||||
[#img-setup]
|
||||
image::internal-network.svg[Internal networks]
|
||||
|
||||
- *Container A* runs a very basic web server, it runs on port 80 inside
|
||||
the container, and `br0`, but is accessible outside on port 8080.
|
||||
The controller connects to each of these servers from OSPF area 0.
|
||||
For the controller to be able to distinguish between the servers,
|
||||
they all serve slightly different content
|
||||
- *Container B* runs a complete system with an SSH server. During the
|
||||
test, the controller connects to this container using the `data` port
|
||||
to ensure the container can access all other parts of the network.
|
||||
To distinguish between the different container B's, each container
|
||||
will have a unique hostname derived from the chassis MAC address
|
||||
|
||||
"""
|
||||
import infamy
|
||||
import infamy.util as util
|
||||
import infamy.route as route
|
||||
from infamy.furl import Furl
|
||||
|
||||
BODY = "<html><body><p>Router responding</p></body></html>"
|
||||
|
||||
def create_vlan_bridge(ns):
|
||||
return ns.runsh("""
|
||||
ip link add dev br0 type bridge
|
||||
ip link set dev br0 up
|
||||
ip link set dev iface1 up
|
||||
ip link set dev iface2 up
|
||||
ip link set dev iface1 master br0
|
||||
ip link set dev iface2 master br0
|
||||
ip link set dev br0 type bridge vlan_filtering 1
|
||||
bridge vlan del dev br0 vid 1 self
|
||||
bridge vlan del dev iface1 vid 1
|
||||
bridge vlan del dev iface2 vid 1
|
||||
bridge vlan add dev br0 vid 8 self
|
||||
bridge vlan add dev iface1 vid 8
|
||||
bridge vlan add dev iface2 vid 8
|
||||
""")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def config_generic(target, router, ring1, ring2, link):
|
||||
router_ip=f"10.1.{router}.1"
|
||||
link_ip=f"10.1.{router}.{101}"
|
||||
firewall_config = util.to_binary(f"""#!/usr/sbin/nft -f
|
||||
|
||||
flush ruleset
|
||||
|
||||
define UPLINK = "{link}"
|
||||
define WIP = 169.254.1.2
|
||||
"""
|
||||
"""
|
||||
table ip nat {
|
||||
chain prerouting {
|
||||
type nat hook prerouting priority filter; policy accept;
|
||||
iif $UPLINK tcp dport 8080 dnat to 169.254.1.2:91
|
||||
iif "br0" ip daddr 169.254.1.2 dnat to 169.254.1.1
|
||||
}
|
||||
|
||||
chain postrouting {
|
||||
type nat hook postrouting priority srcnat; policy accept;
|
||||
oif $UPLINK masquerade
|
||||
oif "br0" ip daddr $WIP snat to 169.254.1.1
|
||||
}
|
||||
}
|
||||
|
||||
""")
|
||||
data = util.to_binary(BODY)
|
||||
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": "lo",
|
||||
"type": "infix-if-type:loopback",
|
||||
"ietf-ip:ipv4": {
|
||||
"address": [
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"prefix-length": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
"ietf-ip:ipv6": {
|
||||
"address": [
|
||||
{
|
||||
"ip": "::1",
|
||||
"prefix-length": 128
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
"name": ring1,
|
||||
"ietf-ip:ipv6": {},
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": f"{ring1}.8",
|
||||
"type": "infix-if-type:vlan",
|
||||
"infix-interfaces:vlan": {
|
||||
"tag-type": "ieee802-dot1q-types:c-vlan",
|
||||
"id": 8,
|
||||
"lower-layer-if": ring1
|
||||
},
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": router_ip,
|
||||
"prefix-length": 32
|
||||
}]
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"name": ring2,
|
||||
"ietf-ip:ipv6": {},
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": f"{ring2}.8",
|
||||
"type": "infix-if-type:vlan",
|
||||
"infix-interfaces:vlan": {
|
||||
"tag-type": "ieee802-dot1q-types:c-vlan",
|
||||
"id": 8,
|
||||
"lower-layer-if": ring2
|
||||
},
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": router_ip,
|
||||
"prefix-length": 32
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": link,
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": link_ip,
|
||||
"prefix-length": 24
|
||||
}],
|
||||
"forwarding": True
|
||||
},
|
||||
"ietf-ip:ipv6": {}
|
||||
},
|
||||
{
|
||||
"name": "br0",
|
||||
"type": "infix-if-type:bridge",
|
||||
"ietf-ip:ipv4": {
|
||||
"enabled": True,
|
||||
"forwarding": True,
|
||||
"infix-ip:autoconf": {
|
||||
"enabled": True,
|
||||
"request-address": "169.254.1.1"
|
||||
}
|
||||
},
|
||||
"ietf-ip:ipv6": {
|
||||
"enabled": True
|
||||
},
|
||||
"infix-interfaces:bridge": {
|
||||
"ieee-group-forward": [
|
||||
"lldp"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "br1",
|
||||
"type": "infix-if-type:bridge",
|
||||
"infix-interfaces:bridge": {
|
||||
"vlans": {
|
||||
"vlan": [
|
||||
{
|
||||
"vid": 6,
|
||||
"untagged": [
|
||||
"veth1b"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "veth0a",
|
||||
"type": "infix-if-type:veth",
|
||||
"infix-interfaces:container-network": {
|
||||
"type": "host",
|
||||
},
|
||||
"ipv4": {
|
||||
"address": [
|
||||
{
|
||||
"ip": "169.254.1.2", # This only for test, use zeroconf in real-life
|
||||
"prefix-length": 16
|
||||
}
|
||||
]
|
||||
},
|
||||
"infix-interfaces:veth": {
|
||||
"peer": "veth0b"
|
||||
},
|
||||
"infix-interfaces:custom-phys-address": {
|
||||
"chassis": {
|
||||
"offset": "06:00:00:00:00:00"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "veth0b",
|
||||
"type": "infix-if-type:veth",
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
},
|
||||
"infix-interfaces:veth": {
|
||||
"peer": "veth0a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "veth1a",
|
||||
"type": "infix-if-type:veth",
|
||||
"infix-interfaces:container-network": {
|
||||
"type": "host"
|
||||
},
|
||||
"infix-interfaces:veth": {
|
||||
"peer": "veth1b"
|
||||
},
|
||||
"infix-interfaces:custom-phys-address": {
|
||||
"chassis": {
|
||||
"offset": "06:00:00:00:00:00"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "veth1b",
|
||||
"type": "infix-if-type:veth",
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br1",
|
||||
"pvid": 6
|
||||
},
|
||||
"infix-interfaces:veth": {
|
||||
"peer": "veth1a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "veth2a",
|
||||
"type": "infix-if-type:veth",
|
||||
"infix-interfaces:container-network": {
|
||||
"type": "host"
|
||||
},
|
||||
"infix-interfaces:veth": {
|
||||
"peer": "veth2b"
|
||||
},
|
||||
"infix-interfaces:custom-phys-address": {
|
||||
"chassis": {
|
||||
"offset": "06:00:00:00:00:00"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "veth2b",
|
||||
"type": "infix-if-type:veth",
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
},
|
||||
"infix-interfaces:veth": {
|
||||
"peer": "veth2a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "veth3a",
|
||||
"type": "infix-if-type:veth",
|
||||
"infix-interfaces:container-network": {
|
||||
"type": "host"
|
||||
},
|
||||
"infix-interfaces:veth": {
|
||||
"peer": "veth3b"
|
||||
},
|
||||
"infix-interfaces:custom-phys-address": {
|
||||
"chassis": {
|
||||
"offset": "06:00:00:00:00:00"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "veth3b",
|
||||
"type": "infix-if-type:veth",
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br1",
|
||||
"pvid": 6
|
||||
},
|
||||
"infix-interfaces:veth": {
|
||||
"peer": "veth3a"
|
||||
}
|
||||
}
|
||||
]}
|
||||
},
|
||||
"infix-containers": {
|
||||
"containers": {
|
||||
"container": [
|
||||
{
|
||||
"name": "container-A",
|
||||
"image": f"oci-archive:{infamy.Container.HTTPD_IMAGE}",
|
||||
"command": "/usr/sbin/httpd -f -v -p 91",
|
||||
"hostname": "web-container-%m",
|
||||
"restart-policy": "retry",
|
||||
"mount": [{
|
||||
"name": "index.html",
|
||||
"content": f"{data}",
|
||||
"target": "/var/www/index.html"
|
||||
}],
|
||||
"network": {
|
||||
"interface": [{
|
||||
"name": "veth0a",
|
||||
"option": [
|
||||
"interface_name=br0",
|
||||
]},
|
||||
{
|
||||
"name": "veth1a",
|
||||
"option": [
|
||||
"interface_name=br1"
|
||||
]}
|
||||
],
|
||||
"publish": [ "91" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "container-B",
|
||||
"image": f"oci-archive:{infamy.Container.HTTPD_IMAGE}",
|
||||
"hostname": "web-container-%m",
|
||||
"privileged": True,
|
||||
"restart-policy": "retry",
|
||||
"network": {
|
||||
"interface": [
|
||||
{
|
||||
"name": "veth2a",
|
||||
"option": ["ip=169.254.1.3"] # This only for test, use zeroconf in real-life
|
||||
},
|
||||
{
|
||||
"name": "veth3a"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "firewall",
|
||||
"image": f"oci-archive:{infamy.Container.NFTABLES_IMAGE}",
|
||||
"network": {
|
||||
"host": True
|
||||
},
|
||||
"mount": [{
|
||||
"name": "nftables.conf",
|
||||
"content": firewall_config,
|
||||
"target": "/etc/nftables.conf"
|
||||
}
|
||||
],
|
||||
"privileged": True
|
||||
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
},
|
||||
"ietf-routing": {
|
||||
"routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [{
|
||||
"type": "infix-routing:ospfv2",
|
||||
"name": "default",
|
||||
"ietf-ospf:ospf": {
|
||||
"explicit-router-id": router_ip,
|
||||
"areas": {
|
||||
"area": [{
|
||||
"area-id": "0.0.0.1",
|
||||
"area-type": "nssa-area",
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": f"{ring1}.8",
|
||||
"interface-type": "point-to-point",
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"name": f"{ring2}.8",
|
||||
"interface-type": "point-to-point",
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"name": link,
|
||||
"enabled": True
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
def config_abr(target, data, link1, link2, link3):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": "lo",
|
||||
"type": "infix-if-type:loopback",
|
||||
"ietf-ip:ipv4": {
|
||||
"address": [
|
||||
{
|
||||
"ip": "127.0.0.1",
|
||||
"prefix-length": 8
|
||||
}
|
||||
]
|
||||
},
|
||||
"ietf-ip:ipv6": {
|
||||
"address": [
|
||||
{
|
||||
"ip": "::1",
|
||||
"prefix-length": 128
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": data,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "192.168.100.1",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": link1,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.1.1.100",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": link2,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.1.2.100",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": link3,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.1.3.100",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
"ietf-routing": {
|
||||
"routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [
|
||||
{
|
||||
"type": "infix-routing:ospfv2",
|
||||
"name": "default",
|
||||
"ietf-ospf:ospf": {
|
||||
"areas": {
|
||||
"area": [
|
||||
{
|
||||
"area-id": "0.0.0.0",
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": data,
|
||||
"enabled": True
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"area-id": "0.0.0.1",
|
||||
"area-type": "nssa-area",
|
||||
"interfaces": {
|
||||
"interface":
|
||||
[
|
||||
{
|
||||
"name": link1,
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"name": link2,
|
||||
"enabled": True
|
||||
},
|
||||
{
|
||||
"name": link3,
|
||||
"enabled": True
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
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")
|
||||
R3 = env.attach("R3", "mgmt")
|
||||
ABR = env.attach("ABR", "mgmt")
|
||||
if not R1.has_model("infix-containers"):
|
||||
test.skip()
|
||||
if not R2.has_model("infix-containers"):
|
||||
test.skip()
|
||||
if not R3.has_model("infix-containers"):
|
||||
test.skip()
|
||||
with test.step("Configure DUTs"):
|
||||
_, R1ring1 = env.ltop.xlate("R1", "ring1")
|
||||
_, R1ring2 = env.ltop.xlate("R1", "ring2")
|
||||
_, R2ring1 = env.ltop.xlate("R2", "ring1")
|
||||
_, R2ring2 = env.ltop.xlate("R2", "ring2")
|
||||
_, R3ring1 = env.ltop.xlate("R3", "ring1")
|
||||
_, R3ring2 = env.ltop.xlate("R3", "ring2")
|
||||
|
||||
_, hostR1ring1 = env.ltop.xlate("host", "R1ring1")
|
||||
_, hostR1ring2 = env.ltop.xlate("host", "R1ring2")
|
||||
|
||||
_, hostR2ring1 = env.ltop.xlate("host", "R2ring1")
|
||||
_, hostR2ring2 = env.ltop.xlate("host", "R2ring2")
|
||||
_, hostR3ring1 = env.ltop.xlate("host", "R3ring1")
|
||||
_, hostR3ring2 = env.ltop.xlate("host", "R3ring2")
|
||||
|
||||
_, R1link = env.ltop.xlate("R1", "link")
|
||||
_, R2link = env.ltop.xlate("R2", "link")
|
||||
_, R3link = env.ltop.xlate("R3", "link")
|
||||
|
||||
_, R1data = env.ltop.xlate("R1", "data")
|
||||
_, R3data = env.ltop.xlate("R3", "data")
|
||||
_, ABRdata = env.ltop.xlate("ABR", "data")
|
||||
_, ABRlink1 = env.ltop.xlate("ABR", "link1")
|
||||
_, ABRlink2 = env.ltop.xlate("ABR", "link2")
|
||||
_, ABRlink3 = env.ltop.xlate("ABR", "link3")
|
||||
|
||||
util.parallel(lambda: config_generic(R1, 1, R1ring1, R1ring2, R1link),
|
||||
lambda: config_generic(R2, 2, R2ring1, R2ring2, R2link),
|
||||
lambda: config_generic(R3, 3, R3ring1, R3ring2, R3link),
|
||||
lambda: config_abr(ABR, ABRdata, ABRlink1, ABRlink2, ABRlink3))
|
||||
|
||||
with infamy.IsolatedMacVlans({hostR1ring1: "iface1", hostR2ring2: "iface2"}) as sw1,\
|
||||
infamy.IsolatedMacVlans({hostR2ring1: "iface1", hostR3ring2: "iface2"}) as sw2, \
|
||||
infamy.IsolatedMacVlans({hostR3ring1: "iface1", hostR1ring2: "iface2"}) as sw3:
|
||||
create_vlan_bridge(sw1)
|
||||
create_vlan_bridge(sw2)
|
||||
create_vlan_bridge(sw3)
|
||||
#breakpoint()
|
||||
_, hport0 = env.ltop.xlate("host", "data4")
|
||||
|
||||
with test.step("Wait for all routers to peer"):
|
||||
util.until(lambda: route.ospf_get_neighbor(R1, "0.0.0.1", f"{R1ring1}.8", "10.1.2.1"), attempts=200)
|
||||
util.until(lambda: route.ospf_get_neighbor(R1, "0.0.0.1", f"{R1ring2}.8", "10.1.3.1"), attempts=200)
|
||||
util.until(lambda: route.ospf_get_neighbor(R2, "0.0.0.1", f"{R2ring1}.8", "10.1.3.1"), attempts=200)
|
||||
util.until(lambda: route.ospf_get_neighbor(R2, "0.0.0.1", f"{R2ring2}.8", "10.1.1.1"), attempts=200)
|
||||
util.until(lambda: route.ospf_get_neighbor(R3, "0.0.0.1", f"{R3ring1}.8", "10.1.1.1"), attempts=200)
|
||||
util.until(lambda: route.ospf_get_neighbor(R3, "0.0.0.1", f"{R3ring2}.8", "10.1.2.1"), attempts=200)
|
||||
|
||||
util.until(lambda: route.ospf_get_neighbor(ABR, "0.0.0.1", ABRlink1, "10.1.1.1"), attempts=200)
|
||||
util.until(lambda: route.ospf_get_neighbor(ABR, "0.0.0.1", ABRlink2, "10.1.2.1"), attempts=200)
|
||||
util.until(lambda: route.ospf_get_neighbor(ABR, "0.0.0.1", ABRlink3, "10.1.3.1"), attempts=200)
|
||||
|
||||
with infamy.IsolatedMacVlan(hport0) as ns:
|
||||
ns.addip("192.168.100.2")
|
||||
ns.addroute("0.0.0.0/0", "192.168.100.1")
|
||||
#breakpoint()
|
||||
with test.step("Verify ABR:data can access container A on R1 (10.1.1.101)"):
|
||||
furl=Furl("http://10.1.1.101:8080")
|
||||
util.until(lambda: furl.nscheck(ns, BODY))
|
||||
with test.step("Verify ABR:data can access container A on R2 (10.1.2.101)"):
|
||||
furl=Furl("http://10.1.2.101:8080")
|
||||
util.until(lambda: furl.nscheck(ns, BODY))
|
||||
with test.step("Verify ABR:data can access container A on R3 (10.1.3.101)"):
|
||||
furl=Furl("http://10.1.3.101:8080")
|
||||
util.until(lambda: furl.nscheck(ns, BODY))
|
||||
test.succeed()
|
||||
@@ -0,0 +1,62 @@
|
||||
graph "ospf_containers" {
|
||||
layout="neato";
|
||||
overlap=false;
|
||||
esep="+20";
|
||||
splines=true;
|
||||
size=10;
|
||||
|
||||
node [ shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="black", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label=" { { <R1ring1> R1ring1 | <R1ring2> R1ring2 | <data1> data1 | <mgmt1> mgmt1 | <mgmt2> mgmt2 | <data2> data2 | <R2ring1> R2ring1 | <R2ring2> R2ring2 | <data3> data3 | <mgmt3> mgmt3 | <R3ring1> R3ring1 | <R3ring2> R3ring2 | <mgmt4> mgmt4 | <data4> data4 } | host } ",
|
||||
pos="0,5!"
|
||||
kind="controller",
|
||||
];
|
||||
|
||||
R1 [
|
||||
label="{{ <link> link } | R1 | {<ring1> ring1 | <ring2> ring2 | <data> data | <mgmt> mgmt }}"
|
||||
pos="-1,6!"
|
||||
kind="infix",
|
||||
];
|
||||
|
||||
R2 [
|
||||
label="{{ <link> link } | R2 | { <mgmt> mgmt | <data> data | <ring1> ring1 | <ring2> ring2 }}"
|
||||
pos="0,6!",
|
||||
kind="infix",
|
||||
];
|
||||
R3 [
|
||||
label="{ <link> link | R3 | { <data> data | <mgmt> mgmt | <ring1> ring1 | <ring2> ring2 }}"
|
||||
pos="1,6!"
|
||||
kind="infix",
|
||||
];
|
||||
|
||||
|
||||
ABR [
|
||||
label="{ ABR | {<link1> link1 | <link2> link2 |<link3> link3 | <mgmt> mgmt | <data> data }}"
|
||||
pos="2,6.5!"
|
||||
kind="infix",
|
||||
];
|
||||
|
||||
host:mgmt1 -- R1:mgmt [kind=mgmt, color="lightgray"]
|
||||
host:mgmt2 -- R2:mgmt [kind=mgmt, color="lightgray"]
|
||||
host:mgmt3 -- R3:mgmt [kind=mgmt, color="lightgray"]
|
||||
host:mgmt4 -- ABR:mgmt [kind=mgmt, color="lightgray"]
|
||||
|
||||
# host-Dut links
|
||||
host:data1 -- R1:data
|
||||
host:data2 -- R2:data
|
||||
host:data3 -- R3:data
|
||||
host:data4 -- ABR:data
|
||||
|
||||
host:R1ring1 -- R1:ring1
|
||||
host:R2ring1 -- R2:ring1
|
||||
host:R3ring1 -- R3:ring1
|
||||
|
||||
host:R1ring2 -- R1:ring2
|
||||
host:R2ring2 -- R2:ring2
|
||||
host:R3ring2 -- R3:ring2
|
||||
R1:link -- ABR:link1
|
||||
R2:link -- ABR:link2
|
||||
R3:link -- ABR:link3
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Title: ospf_containers Pages: 1 -->
|
||||
<svg width="720pt" height="291pt"
|
||||
viewBox="0.00 0.00 720.00 291.37" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(0.64 0.64) rotate(0) translate(4 453.72)">
|
||||
<title>ospf_containers</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-453.72 1127.05,-453.72 1127.05,4 -4,4"/>
|
||||
<!-- host -->
|
||||
<g id="node1" class="node">
|
||||
<title>host</title>
|
||||
<polygon fill="none" stroke="black" points="0,-0.5 0,-46.5 908,-46.5 908,-0.5 0,-0.5"/>
|
||||
<text text-anchor="middle" x="37" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1ring1</text>
|
||||
<polyline fill="none" stroke="black" points="74,-23.5 74,-46.5 "/>
|
||||
<text text-anchor="middle" x="111" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1ring2</text>
|
||||
<polyline fill="none" stroke="black" points="148,-23.5 148,-46.5 "/>
|
||||
<text text-anchor="middle" x="177" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<polyline fill="none" stroke="black" points="206,-23.5 206,-46.5 "/>
|
||||
<text text-anchor="middle" x="235" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
|
||||
<polyline fill="none" stroke="black" points="264,-23.5 264,-46.5 "/>
|
||||
<text text-anchor="middle" x="293" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
|
||||
<polyline fill="none" stroke="black" points="322,-23.5 322,-46.5 "/>
|
||||
<text text-anchor="middle" x="351" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||
<polyline fill="none" stroke="black" points="380,-23.5 380,-46.5 "/>
|
||||
<text text-anchor="middle" x="417" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2ring1</text>
|
||||
<polyline fill="none" stroke="black" points="454,-23.5 454,-46.5 "/>
|
||||
<text text-anchor="middle" x="491" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2ring2</text>
|
||||
<polyline fill="none" stroke="black" points="528,-23.5 528,-46.5 "/>
|
||||
<text text-anchor="middle" x="557" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data3</text>
|
||||
<polyline fill="none" stroke="black" points="586,-23.5 586,-46.5 "/>
|
||||
<text text-anchor="middle" x="615" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
|
||||
<polyline fill="none" stroke="black" points="644,-23.5 644,-46.5 "/>
|
||||
<text text-anchor="middle" x="681" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R3ring1</text>
|
||||
<polyline fill="none" stroke="black" points="718,-23.5 718,-46.5 "/>
|
||||
<text text-anchor="middle" x="755" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R3ring2</text>
|
||||
<polyline fill="none" stroke="black" points="792,-23.5 792,-46.5 "/>
|
||||
<text text-anchor="middle" x="821" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt4</text>
|
||||
<polyline fill="none" stroke="black" points="850,-23.5 850,-46.5 "/>
|
||||
<text text-anchor="middle" x="879" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data4</text>
|
||||
<polyline fill="none" stroke="black" points="0,-23.5 908,-23.5 "/>
|
||||
<text text-anchor="middle" x="454" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
|
||||
</g>
|
||||
<!-- R1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>R1</title>
|
||||
<polygon fill="none" stroke="black" points="79.97,-255.03 79.97,-324.03 295.97,-324.03 295.97,-255.03 79.97,-255.03"/>
|
||||
<text text-anchor="middle" x="187.97" y="-308.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||
<polyline fill="none" stroke="black" points="79.97,-301.03 295.97,-301.03 "/>
|
||||
<text text-anchor="middle" x="187.97" y="-285.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1</text>
|
||||
<polyline fill="none" stroke="black" points="79.97,-278.03 295.97,-278.03 "/>
|
||||
<text text-anchor="middle" x="108.97" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">ring1</text>
|
||||
<polyline fill="none" stroke="black" points="137.97,-255.03 137.97,-278.03 "/>
|
||||
<text text-anchor="middle" x="166.97" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">ring2</text>
|
||||
<polyline fill="none" stroke="black" points="195.97,-255.03 195.97,-278.03 "/>
|
||||
<text text-anchor="middle" x="220.97" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="245.97,-255.03 245.97,-278.03 "/>
|
||||
<text text-anchor="middle" x="270.97" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
</g>
|
||||
<!-- host--R1 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>host:mgmt1--R1:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M235,-46.5C235,-46.5 323.15,-144.12 315.97,-235.03 314.67,-251.57 296.04,-266.6 296.04,-266.6"/>
|
||||
</g>
|
||||
<!-- host--R1 -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>host:data1--R1:data</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M177,-46.5C177,-46.5 220.97,-254.53 220.97,-254.53"/>
|
||||
</g>
|
||||
<!-- host--R1 -->
|
||||
<g id="edge9" class="edge">
|
||||
<title>host:R1ring1--R1:ring1</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M37,-46.5C37,-46.5 108.97,-254.53 108.97,-254.53"/>
|
||||
</g>
|
||||
<!-- host--R1 -->
|
||||
<g id="edge12" class="edge">
|
||||
<title>host:R1ring2--R1:ring2</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M111,-46.5C111,-46.5 166.97,-254.53 166.97,-254.53"/>
|
||||
</g>
|
||||
<!-- R2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>R2</title>
|
||||
<polygon fill="none" stroke="black" points="346,-255.03 346,-324.03 562,-324.03 562,-255.03 346,-255.03"/>
|
||||
<text text-anchor="middle" x="454" y="-308.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||
<polyline fill="none" stroke="black" points="346,-301.03 562,-301.03 "/>
|
||||
<text text-anchor="middle" x="454" y="-285.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2</text>
|
||||
<polyline fill="none" stroke="black" points="346,-278.03 562,-278.03 "/>
|
||||
<text text-anchor="middle" x="371" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="396,-255.03 396,-278.03 "/>
|
||||
<text text-anchor="middle" x="421" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="446,-255.03 446,-278.03 "/>
|
||||
<text text-anchor="middle" x="475" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">ring1</text>
|
||||
<polyline fill="none" stroke="black" points="504,-255.03 504,-278.03 "/>
|
||||
<text text-anchor="middle" x="533" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">ring2</text>
|
||||
</g>
|
||||
<!-- host--R2 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>host:mgmt2--R2:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M293,-46.5C293,-46.5 371,-254.53 371,-254.53"/>
|
||||
</g>
|
||||
<!-- host--R2 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>host:data2--R2:data</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M351,-46.5C351,-46.5 421,-254.53 421,-254.53"/>
|
||||
</g>
|
||||
<!-- host--R2 -->
|
||||
<g id="edge10" class="edge">
|
||||
<title>host:R2ring1--R2:ring1</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M417,-46.5C417,-46.5 475,-254.53 475,-254.53"/>
|
||||
</g>
|
||||
<!-- host--R2 -->
|
||||
<g id="edge13" class="edge">
|
||||
<title>host:R2ring2--R2:ring2</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M491,-46.5C491,-46.5 533,-254.53 533,-254.53"/>
|
||||
</g>
|
||||
<!-- R3 -->
|
||||
<g id="node4" class="node">
|
||||
<title>R3</title>
|
||||
<polygon fill="none" stroke="black" points="612.03,-255.03 612.03,-324.03 828.03,-324.03 828.03,-255.03 612.03,-255.03"/>
|
||||
<text text-anchor="middle" x="720.03" y="-308.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||
<polyline fill="none" stroke="black" points="612.03,-301.03 828.03,-301.03 "/>
|
||||
<text text-anchor="middle" x="720.03" y="-285.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">R3</text>
|
||||
<polyline fill="none" stroke="black" points="612.03,-278.03 828.03,-278.03 "/>
|
||||
<text text-anchor="middle" x="637.03" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="662.03,-255.03 662.03,-278.03 "/>
|
||||
<text text-anchor="middle" x="687.03" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="712.03,-255.03 712.03,-278.03 "/>
|
||||
<text text-anchor="middle" x="741.03" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">ring1</text>
|
||||
<polyline fill="none" stroke="black" points="770.03,-255.03 770.03,-278.03 "/>
|
||||
<text text-anchor="middle" x="799.03" y="-262.83" font-family="DejaVu Sans Mono, Book" font-size="14.00">ring2</text>
|
||||
</g>
|
||||
<!-- host--R3 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>host:mgmt3--R3:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M615,-46.5C615,-46.5 687.03,-254.53 687.03,-254.53"/>
|
||||
</g>
|
||||
<!-- host--R3 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>host:data3--R3:data</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M557,-46.5C557,-46.5 560.94,-155.68 592.03,-235.03 598.08,-250.48 611.96,-266.6 611.96,-266.6"/>
|
||||
</g>
|
||||
<!-- host--R3 -->
|
||||
<g id="edge11" class="edge">
|
||||
<title>host:R3ring1--R3:ring1</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M681,-46.5C681,-46.5 741.03,-254.53 741.03,-254.53"/>
|
||||
</g>
|
||||
<!-- host--R3 -->
|
||||
<g id="edge14" class="edge">
|
||||
<title>host:R3ring2--R3:ring2</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M755,-46.5C755,-46.5 799.03,-254.53 799.03,-254.53"/>
|
||||
</g>
|
||||
<!-- ABR -->
|
||||
<g id="node5" class="node">
|
||||
<title>ABR</title>
|
||||
<polygon fill="none" stroke="black" points="849.05,-399.54 849.05,-445.54 1123.05,-445.54 1123.05,-399.54 849.05,-399.54"/>
|
||||
<text text-anchor="middle" x="986.05" y="-430.34" font-family="DejaVu Sans Mono, Book" font-size="14.00">ABR</text>
|
||||
<polyline fill="none" stroke="black" points="849.05,-422.54 1123.05,-422.54 "/>
|
||||
<text text-anchor="middle" x="878.05" y="-407.34" font-family="DejaVu Sans Mono, Book" font-size="14.00">link1</text>
|
||||
<polyline fill="none" stroke="black" points="907.05,-399.54 907.05,-422.54 "/>
|
||||
<text text-anchor="middle" x="936.05" y="-407.34" font-family="DejaVu Sans Mono, Book" font-size="14.00">link2</text>
|
||||
<polyline fill="none" stroke="black" points="965.05,-399.54 965.05,-422.54 "/>
|
||||
<text text-anchor="middle" x="994.05" y="-407.34" font-family="DejaVu Sans Mono, Book" font-size="14.00">link3</text>
|
||||
<polyline fill="none" stroke="black" points="1023.05,-399.54 1023.05,-422.54 "/>
|
||||
<text text-anchor="middle" x="1048.05" y="-407.34" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="1073.05,-399.54 1073.05,-422.54 "/>
|
||||
<text text-anchor="middle" x="1098.05" y="-407.34" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
</g>
|
||||
<!-- host--ABR -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>host:mgmt4--ABR:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M821,-46.5C821,-46.5 1048.05,-399.54 1048.05,-399.54"/>
|
||||
</g>
|
||||
<!-- host--ABR -->
|
||||
<g id="edge8" class="edge">
|
||||
<title>host:data4--ABR:data</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M879,-46.5C879,-46.5 1098.05,-399.54 1098.05,-399.54"/>
|
||||
</g>
|
||||
<!-- R1--ABR -->
|
||||
<g id="edge15" class="edge">
|
||||
<title>R1:link--ABR:link1</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M295.97,-312.53C295.97,-312.53 317.37,-339.66 326,-344.03 535.15,-449.72 849.05,-410.54 849.05,-410.54"/>
|
||||
</g>
|
||||
<!-- R2--ABR -->
|
||||
<g id="edge16" class="edge">
|
||||
<title>R2:link--ABR:link2</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M562,-312.53C562,-312.53 583.44,-339.57 592.03,-344.03 686.56,-393.12 724.01,-361.89 829.05,-379.54 876.8,-387.56 936.15,-399.5 936.15,-399.5"/>
|
||||
</g>
|
||||
<!-- R3--ABR -->
|
||||
<g id="edge17" class="edge">
|
||||
<title>R3:link--ABR:link3</title>
|
||||
<path fill="none" stroke="black" stroke-width="2" d="M828.03,-312.53C828.03,-312.53 994.05,-399.54 994.05,-399.54"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,3 @@
|
||||
---
|
||||
- name: ospf_container
|
||||
case: ospf_container/test.py
|
||||
@@ -3,7 +3,7 @@ import os
|
||||
from .container import Container
|
||||
from .env import Env
|
||||
from .furl import Furl
|
||||
from .netns import IsolatedMacVlan
|
||||
from .netns import IsolatedMacVlan,IsolatedMacVlans
|
||||
from .sniffer import Sniffer
|
||||
from .tap import Test
|
||||
from .util import parallel, until
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
"""
|
||||
Basic file server over HTTP
|
||||
"""
|
||||
import concurrent.futures
|
||||
import functools
|
||||
import http.server
|
||||
import socket
|
||||
|
||||
|
||||
class FileServer(http.server.HTTPServer):
|
||||
"""Open web server on (address, port) serving files from directory"""
|
||||
class RequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def log_message(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
address_family = socket.AF_INET6
|
||||
|
||||
def __init__(self, server_address, directory):
|
||||
rh = functools.partial(FileServer.RequestHandler, directory=directory)
|
||||
self.__tp = concurrent.futures.ThreadPoolExecutor(max_workers=1)
|
||||
super().__init__(server_address, rh)
|
||||
|
||||
def __enter__(self):
|
||||
self.__tp.submit(self.serve_forever)
|
||||
|
||||
def __exit__(self, _, __, ___):
|
||||
self.shutdown()
|
||||
self.__tp.shutdown()
|
||||
@@ -70,9 +70,9 @@ def _get_ospf_status(target):
|
||||
rib = protos.get("control-plane-protocol", {})
|
||||
for p in rib:
|
||||
if p["type"] == "infix-routing:ospfv2":
|
||||
return p.get("ospf") or p.get("ietf-ospf:ospf")
|
||||
return p.get("ospf") or p.get("ietf-ospf:ospf", {})
|
||||
|
||||
return []
|
||||
return {}
|
||||
|
||||
|
||||
def _get_ospf_status_area(target, area_id):
|
||||
|
||||
@@ -3,8 +3,8 @@ graph "1x3" {
|
||||
overlap="false";
|
||||
esep="+80";
|
||||
|
||||
node [shape=record, fontname="monospace"];
|
||||
edge [color="cornflowerblue", penwidth="2"];
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <tgt> tgt | <data0> data0 | <data1> data1 }",
|
||||
|
||||
@@ -3,8 +3,8 @@ graph "1x4" {
|
||||
overlap="false";
|
||||
esep="+80";
|
||||
|
||||
node [shape=record, fontname="monospace"];
|
||||
edge [color="cornflowerblue", penwidth="2"];
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <tgt> tgt | <data0> data0 | <data1> data1 | <data2> data2 }",
|
||||
|
||||
@@ -3,8 +3,8 @@ graph "2x2" {
|
||||
overlap="false";
|
||||
esep="+40";
|
||||
|
||||
node [shape=record, fontname="monospace"];
|
||||
edge [color="cornflowerblue", penwidth="2"];
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt1> mgmt1 | <data1> data1 | <mgmt2> mgmt2 | <data2> data2 }",
|
||||
|
||||
@@ -3,8 +3,8 @@ graph "2x4" {
|
||||
overlap="false";
|
||||
esep="+40";
|
||||
|
||||
node [shape=record, fontname="monospace"];
|
||||
edge [color="cornflowerblue", penwidth="2"];
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label="host | { <mgmt1> mgmt1 | <data10> data10 | <data11> data11 | <> \n\n\n\n\n\n | <mgmt2> mgmt2 | <data20> data20 | <data21> data21 }",
|
||||
|
||||
@@ -4,9 +4,9 @@ graph "ring-4-duts" {
|
||||
esep="+20";
|
||||
splines=true;
|
||||
size=10;
|
||||
|
||||
node [shape=record, fontname="monospace"];
|
||||
edge [color="cornflowerblue", penwidth="2"];
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
host [
|
||||
label=" { host | { <mgmt4> mgmt4 | <data4> data4 | <mgmt1> mgmt1 | <data1> data1 | <data2> data2 | <mgmt2> mgmt2 | <data3> data3 | <mgmt3> mgmt3 } }",
|
||||
@@ -36,7 +36,7 @@ graph "ring-4-duts" {
|
||||
label="{ <ring2> ring2 | <cross> cross | <ring1> ring1 } | { { <data> data | <mgmt> mgmt } | \n R3 \n10.0.0.3/32 \n(lo) }",
|
||||
pos="250,50!"
|
||||
kind="infix",
|
||||
];
|
||||
];
|
||||
|
||||
host:mgmt1 -- R1:mgmt [kind=mgmt, color="lightgray"]
|
||||
host:mgmt2 -- R2:mgmt [kind=mgmt, color="lightgray"]
|
||||
@@ -51,7 +51,7 @@ graph "ring-4-duts" {
|
||||
|
||||
# Ring
|
||||
R1:ring1 -- R2:ring2 [color="blue",headlabel=".2", label="10.0.12.1/30", taillabel=".1", labeldistance=1, fontcolor="blue"]
|
||||
R2:ring1 -- R3:ring2 [color="blue",headlabel=".2", label="10.0.23.0/30", taillabel=".1", labeldistance=1, fontcolor="blue"]
|
||||
R2:ring1 -- R3:ring2 [color="blue",headlabel=".2", label="10.0.23.0/30", taillabel=".1", labeldistance=1, fontcolor="blue"]
|
||||
R3:ring1 -- R4:ring2 [color="blue",headlabel=".2", label="192.168.4.0/24", taillabel=".1", labeldistance=1 fontcolor="blue"]
|
||||
R4:ring1 -- R1:ring2 [color="blue",headlabel=".2", label="10.0.41.0/30", taillabel=".1", labeldistance=1, fontcolor="blue"]
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
:topdoc:
|
||||
|
||||
= Test specification
|
||||
v24.10.2-2-g032a51b9-dirty
|
||||
:title-page:
|
||||
:toc:
|
||||
:toclevels: 2
|
||||
:sectnums:
|
||||
:sectnumlevels: 2
|
||||
|
||||
<<<
|
||||
include::../case/misc/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::../case/ietf_system/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::../case/ietf_syslog/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::../case/ietf_interfaces/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::../case/ietf_routing/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::../case/infix_containers/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::../case/infix_dhcp/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::../case/ietf_hardware/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::../case/infix_services/Readme.adoc[]
|
||||
@@ -42,3 +42,7 @@ include::../case/ietf_hardware/Readme.adoc[]
|
||||
<<<
|
||||
|
||||
include::../case/infix_services/Readme.adoc[]
|
||||
|
||||
<<<
|
||||
|
||||
include::../case/use_case/Readme.adoc[]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#!/bin/env python3
|
||||
import os
|
||||
import ast
|
||||
import graphviz
|
||||
import argparse
|
||||
import io
|
||||
import sys
|
||||
@@ -9,15 +8,47 @@ import re
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Define a custom NodeVisitor to extract the arguments
|
||||
import graphviz
|
||||
|
||||
def replace_image_tag(text, test_dir):
|
||||
"""
|
||||
Convert images added in the description and replace with the required ifdefs to work
|
||||
generating the test specifcation as well.
|
||||
"""
|
||||
|
||||
pattern = r"image::(?P<image_name>\S+)\[(?P<label>[^\]]*)\]"
|
||||
|
||||
def repl(match):
|
||||
image_name = match.group("image_name")
|
||||
label = match.group("label")
|
||||
|
||||
return f"""ifdef::topdoc[]
|
||||
image::../../{test_dir}/{image_name}[{label}]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::{'/'.join(Path(test_dir).parts[3:])}/{image_name}[{label}]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::{image_name}[{label}]
|
||||
endif::testgroup[]
|
||||
endif::topdoc[]"""
|
||||
|
||||
return re.sub(pattern, repl, text)
|
||||
|
||||
class TestStepVisitor(ast.NodeVisitor):
|
||||
"""
|
||||
A custom test step visitor to grab the test description (docstring)
|
||||
and the test steps test.step(.....) for the test case.
|
||||
|
||||
"""
|
||||
def __init__(self):
|
||||
self.test_steps=[]
|
||||
self.name = ""
|
||||
self.description = ""
|
||||
|
||||
def visit_Module(self, node):
|
||||
"""Extract docstring from a module."""
|
||||
"""Extract docstring from a test."""
|
||||
docstring = ast.get_docstring(node)
|
||||
if docstring:
|
||||
lines = docstring.splitlines()
|
||||
@@ -27,7 +58,7 @@ class TestStepVisitor(ast.NodeVisitor):
|
||||
for line in lines:
|
||||
if self.name == "":
|
||||
self.name=line.strip()
|
||||
elif newline_parsed == False and line == "":
|
||||
elif newline_parsed is False and line == "":
|
||||
newline_parsed = True
|
||||
continue # Skip mandatory newline
|
||||
else:
|
||||
@@ -37,6 +68,7 @@ class TestStepVisitor(ast.NodeVisitor):
|
||||
|
||||
# Check for test.step() for the actual test steps
|
||||
def visit_Call(self, node):
|
||||
"""Extract test.step from test"""
|
||||
if isinstance(node.func, ast.Attribute) and node.func.attr == 'step':
|
||||
if isinstance(node.func.value, ast.Name) and node.func.value.id == 'test':
|
||||
if node.args and isinstance(node.args[0], ast.Constant):
|
||||
@@ -44,6 +76,7 @@ class TestStepVisitor(ast.NodeVisitor):
|
||||
self.generic_visit(node) # Continue visiting other nodes
|
||||
|
||||
class TestCase:
|
||||
"""All test specifcation resources for a test case"""
|
||||
def __init__(self, directory, rootdir=None):
|
||||
self.test_dir=Path(directory)
|
||||
if rootdir:
|
||||
@@ -63,6 +96,7 @@ class TestCase:
|
||||
self.test_steps=visitor.test_steps
|
||||
|
||||
def generate_topology(self):
|
||||
"""Generate SVG file from the topology.dot file"""
|
||||
with open(self.topology_dot, 'r') as dot_file:
|
||||
dot_graph = dot_file.read()
|
||||
graph = graphviz.Source(dot_graph)
|
||||
@@ -75,8 +109,11 @@ class TestCase:
|
||||
with open(f"{self.topology_image}.svg", 'w') as f:
|
||||
f.write(mod_content)
|
||||
|
||||
|
||||
def generate_specification(self):
|
||||
"""Generate a Readme.adoc for the test case"""
|
||||
self.generate_topology()
|
||||
self.description = replace_image_tag(self.description, self.test_dir)
|
||||
with open(self.specification, "w") as spec:
|
||||
spec.write(f"=== {self.name}\n")
|
||||
spec.write("==== Description\n")
|
||||
@@ -98,6 +135,10 @@ class TestCase:
|
||||
spec.write("\n\n<<<\n\n") # need empty lines to pagebreak
|
||||
|
||||
def parse_directory_tree(directory):
|
||||
"""P
|
||||
arse a directory for subdirectories with a test.py
|
||||
and a topology.dot files
|
||||
"""
|
||||
directories=[]
|
||||
for dirpath, dirnames, filenames in os.walk(directory):
|
||||
testscript = False
|
||||
@@ -126,6 +167,8 @@ sys.stderr = output_capture
|
||||
directories = parse_directory_tree(args.directory)
|
||||
error_string = ""
|
||||
for directory in directories:
|
||||
# This is hacky, graphviz output error only to stdout and return successful(always).
|
||||
# If everything goes well, output shall be empty, fail on any output
|
||||
output_capture.truncate(0)
|
||||
output_capture.seek(0)
|
||||
test_case = TestCase(directory, args.root_dir)
|
||||
|
||||
Reference in New Issue
Block a user