mirror of
https://github.com/kernelkit/infix.git
synced 2026-08-07 07:53:18 +02:00
+2
-1
@@ -17,8 +17,9 @@ All notable changes to the project are documented in this file.
|
||||
RPC and add boot order to operational datastore.
|
||||
- SSH Server is now configurable, issue #441
|
||||
SSH Server and NETCONF Server now uses the same SSH hostkey in factory-config
|
||||
- Add support for GRE/GRETAP tunnels
|
||||
- Support for GRE/GRETAP tunnels
|
||||
- Support for STP/RSTP on bridges
|
||||
- Support for VXLAN tunnels
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -14,3 +14,15 @@ admin@example:/config/interface/gre1/> set gre local 192.168.3.1 remote 192.168.
|
||||
admin@example:/config/interface/gre1/> leave
|
||||
admin@example:/>
|
||||
```
|
||||
|
||||
## Virtual eXtensible Local Area Network (VXLAN)
|
||||
|
||||
The support for VXLAN tunnels includes IPv4 and IPv6.
|
||||
|
||||
```
|
||||
admin@example:/config/> edit interface vxlan100
|
||||
admin@example:/config/interface/vxlan100/> set vxlan local 192.168.3.1
|
||||
admin@example:/config/interface/vxlan100/> set vxlan remote 192.168.3.2
|
||||
admin@example:/config/interface/vxlan100/> set vxlan vni 100
|
||||
admin@example:/config/interface/vxlan100/> leave
|
||||
```
|
||||
|
||||
@@ -34,7 +34,8 @@ confd_plugin_la_SOURCES = \
|
||||
infix-if-bridge-port.c \
|
||||
infix-if-veth.c \
|
||||
infix-if-vlan.c \
|
||||
infix-if-gre.c \
|
||||
infix-if-gre.c \
|
||||
infix-if-vxlan.c \
|
||||
ietf-keystore.c \
|
||||
ietf-system.c \
|
||||
ietf-syslog.c \
|
||||
|
||||
@@ -91,7 +91,8 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path)
|
||||
inferred.data.string_val = "infix-if-type:gre";
|
||||
else if (!fnmatch("gretap+([0-9])", ifname, FNM_EXTMATCH))
|
||||
inferred.data.string_val = "infix-if-type:gretap";
|
||||
|
||||
else if (!fnmatch("vxlan+([0-9])", ifname, FNM_EXTMATCH))
|
||||
inferred.data.string_val = "infix-if-type:vxlan";
|
||||
free(ifname);
|
||||
|
||||
if (inferred.data.string_val)
|
||||
@@ -401,6 +402,8 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net,
|
||||
return -ENOENT;
|
||||
} else if (!strcmp(iftype, "infix-if-type:gre") || !strcmp(iftype, "infix-if-type:gretap")) {
|
||||
err = gre_gen(net, NULL, cif, ip);
|
||||
} else if (!strcmp(iftype, "infix-if-type:vxlan")) {
|
||||
err = vxlan_gen(net, NULL, cif, ip);
|
||||
} else {
|
||||
sr_session_set_error_message(net->session, "%s: unsupported interface type \"%s\"", ifname, iftype);
|
||||
return -ENOSYS;
|
||||
@@ -428,7 +431,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
|
||||
return 0;
|
||||
if (!strcmp(iftype, "infix-if-type:gretap"))
|
||||
return 0;
|
||||
|
||||
if (!strcmp(iftype, "infix-if-type:vxlan"))
|
||||
return 0;
|
||||
ERROR("%s: unsupported interface type \"%s\"", ifname, iftype);
|
||||
return -ENOSYS;
|
||||
}
|
||||
@@ -462,6 +466,9 @@ static bool netdag_must_del(struct lyd_node *dif, struct lyd_node *cif)
|
||||
} else if (!strcmp(iftype, "infix-if-type:gre") || !strcmp(iftype, "infix-if-type:gretap")) {
|
||||
if (lydx_get_descendant(lyd_child(dif), "gre", NULL))
|
||||
return true;
|
||||
} else if (!strcmp(iftype, "infix-if-type:vxlan")) {
|
||||
if (lydx_get_descendant(lyd_child(dif), "vxlan", NULL))
|
||||
return true;
|
||||
/*
|
||||
} else if (!strcmp(iftype, "infix-if-type:lag")) {
|
||||
if (is_phys_addr_deleted(dif))
|
||||
|
||||
@@ -76,6 +76,10 @@ int netdag_gen_vlan(struct dagger *net, struct lyd_node *dif,
|
||||
|
||||
/* infix-if-gre.c */
|
||||
int gre_gen(struct dagger *net, struct lyd_node *dif,
|
||||
struct lyd_node *cif, FILE *ip);
|
||||
struct lyd_node *cif, FILE *ip);
|
||||
|
||||
/* infix-if-vxlan.c */
|
||||
int vxlan_gen(struct dagger *net, struct lyd_node *dif,
|
||||
struct lyd_node *cif, FILE *ip);
|
||||
|
||||
#endif /* CONFD_IETF_INTERFACES_H_ */
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "ietf-interfaces.h"
|
||||
|
||||
int gre_gen(struct dagger *net, struct lyd_node *dif,
|
||||
struct lyd_node *cif, FILE *ip)
|
||||
struct lyd_node *cif, FILE *ip)
|
||||
{
|
||||
const char *ifname, *iftype, *local, *remote, *mac = NULL;
|
||||
struct lyd_node *node = NULL;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
#include <srx/lyx.h>
|
||||
|
||||
#include "ietf-interfaces.h"
|
||||
|
||||
int vxlan_gen(struct dagger *net, struct lyd_node *dif,
|
||||
struct lyd_node *cif, FILE *ip)
|
||||
{
|
||||
const char *ifname, *local, *remote, *mac = NULL;
|
||||
const char *vni, *remote_port;
|
||||
struct lyd_node *node = NULL;
|
||||
|
||||
ifname = lydx_get_cattr(cif, "name");
|
||||
node = lydx_get_descendant(lyd_child(cif), "vxlan", NULL);
|
||||
if (!node)
|
||||
return -EINVAL;
|
||||
|
||||
local = lydx_get_cattr(node, "local");
|
||||
remote = lydx_get_cattr(node, "remote");
|
||||
vni = lydx_get_cattr(node, "vni");
|
||||
remote_port = lydx_get_cattr(node, "remote-port");
|
||||
fprintf(ip, "link add name %s type vxlan id %s local %s remote %s dstport %s", ifname, vni, local, remote, remote_port);
|
||||
if (mac)
|
||||
fprintf(ip, "address %s\n", mac);
|
||||
else
|
||||
fprintf(ip, "\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -714,7 +714,8 @@ submodule infix-if-bridge {
|
||||
"derived-from-or-self(if:type,'ianaift:ieee8023adLag') or "+
|
||||
"derived-from-or-self(if:type,'ianaift:l2vlan') or "+
|
||||
"derived-from-or-self(if:type,'ianaift:ilan') or "+
|
||||
"derived-from-or-self(if:type,'infix-ift:gretap')" {
|
||||
"derived-from-or-self(if:type,'infix-ift:gretap') or "+
|
||||
"derived-from-or-self(if:type,'infix-ift:vxlan')" {
|
||||
description "Applies when a Bridge interface exists.";
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,9 @@ module infix-if-type {
|
||||
identity gretap {
|
||||
base infix-interface-type;
|
||||
}
|
||||
identity vxlan {
|
||||
base infix-interface-type;
|
||||
}
|
||||
identity lag {
|
||||
base infix-interface-type;
|
||||
base ianaift:ieee8023adLag;
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
submodule infix-if-vxlan {
|
||||
yang-version 1.1;
|
||||
belongs-to infix-interfaces {
|
||||
prefix infix-if;
|
||||
}
|
||||
|
||||
import ietf-interfaces {
|
||||
prefix if;
|
||||
}
|
||||
|
||||
import ietf-inet-types {
|
||||
prefix inet-types;
|
||||
}
|
||||
|
||||
import infix-if-type {
|
||||
prefix infixift;
|
||||
}
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
description "VXLAN tunnel extension for ietf-interfaces";
|
||||
|
||||
revision 2025-01-13 {
|
||||
description "Initial revision.";
|
||||
reference "internal";
|
||||
}
|
||||
|
||||
typedef vni {
|
||||
type uint32 {
|
||||
range "0..16777215";
|
||||
}
|
||||
}
|
||||
augment "/if:interfaces/if:interface" {
|
||||
when "derived-from-or-self(if:type, 'infixift:vxlan')" {
|
||||
description "Only shown for if:type infixift:vxlan";
|
||||
}
|
||||
description "Augments the interface model with VXLAN tunnels.";
|
||||
container vxlan {
|
||||
uses local-remote;
|
||||
leaf remote-port {
|
||||
type inet-types:port-number;
|
||||
default 4789;
|
||||
description
|
||||
"VXLAN destination UDP port. Valid range: 0..65535. Default is 4789 (IANA-assigned VXLAN UDP port).";
|
||||
}
|
||||
leaf vni {
|
||||
type vni;
|
||||
mandatory true;
|
||||
description
|
||||
"VXLAN Network Identifier (VNI), valid values are 0 to 16777215.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
infix-if-vxlan.yang
|
||||
@@ -22,6 +22,7 @@ module infix-interfaces {
|
||||
include infix-if-veth;
|
||||
include infix-if-vlan;
|
||||
include infix-if-gre;
|
||||
include infix-if-vxlan;
|
||||
|
||||
organization "KernelKit";
|
||||
contact "kernelkit@googlegroups.com";
|
||||
@@ -106,6 +107,7 @@ module infix-interfaces {
|
||||
mandatory true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Data Nodes
|
||||
*/
|
||||
|
||||
@@ -49,6 +49,6 @@ include::veth_delete/Readme.adoc[]
|
||||
|
||||
include::vlan_iface_termination/Readme.adoc[]
|
||||
|
||||
include::gre_basic/Readme.adoc[]
|
||||
include::tunnel_basic/Readme.adoc[]
|
||||
|
||||
include::gretap_bridged/Readme.adoc[]
|
||||
include::tunnel_bridged/Readme.adoc[]
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
gretap_bridged.adoc
|
||||
@@ -1,24 +0,0 @@
|
||||
=== GRETAP interface bridged with physical
|
||||
==== Description
|
||||
Test that GRETAP works as it should and that it possible to bridge it.
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::../../test/case/ietf_interfaces/gretap_bridged/topology.svg[GRETAP interface bridged with physical topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::gretap_bridged/topology.svg[GRETAP interface bridged with physical topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[GRETAP interface bridged with physical topology]
|
||||
endif::testgroup[]
|
||||
endif::topdoc[]
|
||||
==== Test sequence
|
||||
. Set up topology and attach to target DUTs
|
||||
. Configure DUTs
|
||||
. Test connectivity host:data to right:gre0 at 192.168.10.2
|
||||
|
||||
|
||||
<<<
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
- name: vlan_iface_termination
|
||||
case: vlan_iface_termination/test.py
|
||||
|
||||
- name: gre_basic
|
||||
suite: gre_basic/test.yaml
|
||||
- name: tunnel_basic
|
||||
suite: tunnel_basic/test.yaml
|
||||
|
||||
- name: gretap_bridged
|
||||
case: gretap_bridged/test.py
|
||||
- name: tunnel_bridged
|
||||
suite: tunnel_bridged/test.yaml
|
||||
|
||||
+2
@@ -2,3 +2,5 @@ include::gre_basic.adoc[]
|
||||
|
||||
include::gretap_basic.adoc[]
|
||||
|
||||
include::vxlan_basic.adoc[]
|
||||
|
||||
+4
-3
@@ -1,15 +1,15 @@
|
||||
=== GRE point-to-point
|
||||
==== Description
|
||||
Test setting up IP GRE tunnels using IPv4 and IPv6,
|
||||
Test setting up gre tunnels using IPv4 and IPv6,
|
||||
and ends with a connectivity test.
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::../../test/case/ietf_interfaces/gre_basic/topology.svg[GRE point-to-point topology]
|
||||
image::../../test/case/ietf_interfaces/tunnel_basic/topology.svg[GRE point-to-point topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::gre_basic/topology.svg[GRE point-to-point topology]
|
||||
image::tunnel_basic/topology.svg[GRE point-to-point topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[GRE point-to-point topology]
|
||||
@@ -18,6 +18,7 @@ endif::topdoc[]
|
||||
==== Test sequence
|
||||
. Set up topology and attach to target DUTs
|
||||
. Configure DUTs
|
||||
. Verify connectivity host:data to 10.0.0.2
|
||||
. Verify connectivity host:data to 2001:db8::c0a8:0a02
|
||||
|
||||
|
||||
+4
-3
@@ -1,15 +1,15 @@
|
||||
=== GRETAP point-to-point
|
||||
==== Description
|
||||
Test setting up IP GRE tunnels using IPv4 and IPv6,
|
||||
Test setting up gretap tunnels using IPv4 and IPv6,
|
||||
and ends with a connectivity test.
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::../../test/case/ietf_interfaces/gre_basic/topology.svg[GRETAP point-to-point topology]
|
||||
image::../../test/case/ietf_interfaces/tunnel_basic/topology.svg[GRETAP point-to-point topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::gre_basic/topology.svg[GRETAP point-to-point topology]
|
||||
image::tunnel_basic/topology.svg[GRETAP point-to-point topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[GRETAP point-to-point topology]
|
||||
@@ -18,6 +18,7 @@ endif::topdoc[]
|
||||
==== Test sequence
|
||||
. Set up topology and attach to target DUTs
|
||||
. Configure DUTs
|
||||
. Verify connectivity host:data to 10.0.0.2
|
||||
. Verify connectivity host:data to 2001:db8::c0a8:0a02
|
||||
|
||||
|
||||
+45
-25
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Basic GRE connectivity test
|
||||
Basic tunnel connectivity test
|
||||
|
||||
Test setting up IP GRE tunnels using IPv4 and IPv6,
|
||||
Test setting up {type} tunnels using IPv4 and IPv6,
|
||||
and ends with a connectivity test.
|
||||
"""
|
||||
|
||||
@@ -21,8 +21,41 @@ with infamy.Test() as test:
|
||||
left = env.attach("left", "mgmt")
|
||||
right = env.attach("right", "mgmt")
|
||||
|
||||
|
||||
with test.step("Configure DUTs"):
|
||||
container_left4 = {
|
||||
"local": "192.168.50.1",
|
||||
"remote": "192.168.50.2"
|
||||
}
|
||||
container_left6 = {
|
||||
"local": "2001:db8:3c4d:50::1",
|
||||
"remote": "2001:db8:3c4d:50::2",
|
||||
}
|
||||
container_right4 = {
|
||||
"local": "192.168.50.2",
|
||||
"remote": "192.168.50.1"
|
||||
}
|
||||
container_right6 = {
|
||||
"local": "2001:db8:3c4d:50::2",
|
||||
"remote": "2001:db8:3c4d:50::1",
|
||||
}
|
||||
if type == "gretap" or type == "gre":
|
||||
container_name = "gre"
|
||||
if type == "vxlan":
|
||||
container_name = "vxlan"
|
||||
container_left4.update({
|
||||
"vni": 4
|
||||
})
|
||||
container_left6.update({
|
||||
"vni": 6
|
||||
})
|
||||
container_right4.update({
|
||||
"vni": 4
|
||||
})
|
||||
container_right6.update({
|
||||
"vni": 6
|
||||
})
|
||||
|
||||
|
||||
left.put_config_dicts({ "ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
@@ -61,7 +94,7 @@ with infamy.Test() as test:
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gre0",
|
||||
"name": f"{type}4",
|
||||
"type": f"infix-if-type:{type}",
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
@@ -70,14 +103,10 @@ with infamy.Test() as test:
|
||||
}],
|
||||
"forwarding": True
|
||||
},
|
||||
"gre": {
|
||||
"local": "192.168.50.1",
|
||||
"remote": "192.168.50.2"
|
||||
}
|
||||
|
||||
container_name: container_left4
|
||||
},
|
||||
{
|
||||
"name": "gre6",
|
||||
"name": f"{type}6",
|
||||
"type": f"infix-if-type:{type}",
|
||||
"ipv6": {
|
||||
"address": [{
|
||||
@@ -85,10 +114,7 @@ with infamy.Test() as test:
|
||||
"prefix-length": 64
|
||||
}]
|
||||
},
|
||||
"gre": {
|
||||
"local": "2001:db8:3c4d:50::1",
|
||||
"remote": "2001:db8:3c4d:50::2",
|
||||
}
|
||||
container_name: container_left6
|
||||
}]
|
||||
}
|
||||
}
|
||||
@@ -115,7 +141,7 @@ with infamy.Test() as test:
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gre1",
|
||||
"name": f"{type}4",
|
||||
"type": f"infix-if-type:{type}",
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
@@ -124,13 +150,10 @@ with infamy.Test() as test:
|
||||
}],
|
||||
"forwarding": True
|
||||
},
|
||||
"gre": {
|
||||
"local": "192.168.50.2",
|
||||
"remote": "192.168.50.1"
|
||||
}
|
||||
container_name: container_right4
|
||||
},
|
||||
{
|
||||
"name": "gre6",
|
||||
"name": f"{type}",
|
||||
"type": f"infix-if-type:{type}",
|
||||
"ipv6": {
|
||||
"address": [{
|
||||
@@ -138,10 +161,7 @@ with infamy.Test() as test:
|
||||
"prefix-length": 64
|
||||
}]
|
||||
},
|
||||
"gre": {
|
||||
"local": "2001:db8:3c4d:50::2",
|
||||
"remote": "2001:db8:3c4d:50::1",
|
||||
}
|
||||
container_name: container_right6
|
||||
}]
|
||||
}
|
||||
},
|
||||
@@ -175,7 +195,7 @@ with infamy.Test() as test:
|
||||
}
|
||||
})
|
||||
_, hport = env.ltop.xlate("host", "data")
|
||||
with test.step(f"Verify connectivity host:data to 10.0.0.2"):
|
||||
with test.step("Verify connectivity host:data to 10.0.0.2"):
|
||||
with infamy.IsolatedMacVlan(hport) as ns0:
|
||||
ns0.addip("192.168.10.2")
|
||||
ns0.addroute("192.168.30.0/24", "192.168.10.1")
|
||||
+6
@@ -10,3 +10,9 @@
|
||||
opts: ["--type", "gretap"]
|
||||
infamy:
|
||||
title: GRETAP point-to-point
|
||||
|
||||
- name: vxlan_basic
|
||||
case: test.py
|
||||
opts: ["--type", "vxlan"]
|
||||
infamy:
|
||||
title: VXLAN point-to-point
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,26 @@
|
||||
=== VXLAN point-to-point
|
||||
==== Description
|
||||
Test setting up vxlan tunnels using IPv4 and IPv6,
|
||||
and ends with a connectivity test.
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::../../test/case/ietf_interfaces/tunnel_basic/topology.svg[VXLAN point-to-point topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::tunnel_basic/topology.svg[VXLAN point-to-point topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[VXLAN point-to-point topology]
|
||||
endif::testgroup[]
|
||||
endif::topdoc[]
|
||||
==== Test sequence
|
||||
. Set up topology and attach to target DUTs
|
||||
. Configure DUTs
|
||||
. Verify connectivity host:data to 10.0.0.2
|
||||
. Verify connectivity host:data to 2001:db8::c0a8:0a02
|
||||
|
||||
|
||||
<<<
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
include::gretap_bridged.adoc[]
|
||||
|
||||
include::vxlan_bridged.adoc[]
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
=== GRETAP bridged with physical interface
|
||||
==== Description
|
||||
Undefined
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::../../test/case/ietf_interfaces/tunnel_bridged/topology.svg[GRETAP bridged with physical interface topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::tunnel_bridged/topology.svg[GRETAP bridged with physical interface topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[GRETAP bridged with physical interface topology]
|
||||
endif::testgroup[]
|
||||
endif::topdoc[]
|
||||
==== Test sequence
|
||||
. Set up topology and attach to target DUTs
|
||||
. Configure DUTs
|
||||
. Test connectivity host:data to right:gretap0 at 192.168.10.2
|
||||
|
||||
|
||||
<<<
|
||||
|
||||
+41
-25
@@ -1,29 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
GRETAP interface bridged with physical
|
||||
Tunnel interface bridged with physical
|
||||
|
||||
Test that GRETAP works as it should and that it possible to bridge it.
|
||||
Test that {type} works as it should and that it possible to bridge it.
|
||||
|
||||
"""
|
||||
|
||||
import infamy
|
||||
|
||||
class ArgumentParser(infamy.ArgumentParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.add_argument("--type")
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to target DUTs"):
|
||||
env = infamy.Env()
|
||||
left = env.attach("left", "mgmt")
|
||||
right = env.attach("right", "mgmt")
|
||||
_, leftlink = env.ltop.xlate("left", "link")
|
||||
_, leftdata = env.ltop.xlate("left", "data")
|
||||
_, rightlink = env.ltop.xlate("right", "link")
|
||||
|
||||
args=ArgumentParser()
|
||||
env = infamy.Env(args=args)
|
||||
left = env.attach("left", "mgmt")
|
||||
right = env.attach("right", "mgmt")
|
||||
type = env.args.type
|
||||
|
||||
with test.step("Configure DUTs"):
|
||||
if type == "gretap":
|
||||
container_name = "gre"
|
||||
else:
|
||||
container_name = type
|
||||
|
||||
container_left = {
|
||||
"local": "192.168.50.1",
|
||||
"remote": "192.168.50.2"
|
||||
}
|
||||
container_right = {
|
||||
"local": "192.168.50.2",
|
||||
"remote": "192.168.50.1"
|
||||
}
|
||||
container_left.update({
|
||||
"vni": 4
|
||||
})
|
||||
container_right.update({
|
||||
"vni": 4
|
||||
})
|
||||
left.put_config_dicts({ "ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": leftlink,
|
||||
"name": left["link"],
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": "192.168.50.1",
|
||||
@@ -33,7 +55,7 @@ with infamy.Test() as test:
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": leftdata,
|
||||
"name": left["data"],
|
||||
"bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
@@ -43,12 +65,9 @@ with infamy.Test() as test:
|
||||
"type": "infix-if-type:bridge"
|
||||
},
|
||||
{
|
||||
"name": "gre0",
|
||||
"type": "infix-if-type:gretap",
|
||||
"gre": {
|
||||
"local": "192.168.50.1",
|
||||
"remote": "192.168.50.2"
|
||||
},
|
||||
"name": f"{type}0",
|
||||
"type": f"infix-if-type:{type}",
|
||||
container_name: container_left,
|
||||
"bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
@@ -64,7 +83,7 @@ with infamy.Test() as test:
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": rightlink,
|
||||
"name": right["link"],
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": "192.168.50.2",
|
||||
@@ -80,8 +99,8 @@ with infamy.Test() as test:
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "gre0",
|
||||
"type": "infix-if-type:gretap",
|
||||
"name": f"{type}0",
|
||||
"type": f"infix-if-type:{type}",
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": "192.168.10.2",
|
||||
@@ -89,16 +108,13 @@ with infamy.Test() as test:
|
||||
}],
|
||||
"forwarding": True
|
||||
},
|
||||
"gre": {
|
||||
"local": "192.168.50.2",
|
||||
"remote": "192.168.50.1"
|
||||
}
|
||||
container_name: container_right,
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
_, hport = env.ltop.xlate("host", "data")
|
||||
with test.step("Test connectivity host:data to right:gre0 at 192.168.10.2"):
|
||||
with test.step(f"Test connectivity host:data to right:{type}0 at 192.168.10.2"):
|
||||
with infamy.IsolatedMacVlan(hport) as ns0:
|
||||
ns0.addip("192.168.10.1")
|
||||
ns0.must_reach("192.168.10.2")
|
||||
@@ -0,0 +1,12 @@
|
||||
---
|
||||
- name: gretap_bridged
|
||||
case: test.py
|
||||
opts: ["--type", "gretap"]
|
||||
infamy:
|
||||
title: GRETAP bridged with physical interface
|
||||
|
||||
- name: vxlan_bridged
|
||||
case: test.py
|
||||
opts: ["--type", "vxlan"]
|
||||
infamy:
|
||||
title: VXLAN bridged with physical interface
|
||||
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,24 @@
|
||||
=== VXLAN bridged with physical interface
|
||||
==== Description
|
||||
Undefined
|
||||
|
||||
==== Topology
|
||||
ifdef::topdoc[]
|
||||
image::../../test/case/ietf_interfaces/tunnel_bridged/topology.svg[VXLAN bridged with physical interface topology]
|
||||
endif::topdoc[]
|
||||
ifndef::topdoc[]
|
||||
ifdef::testgroup[]
|
||||
image::tunnel_bridged/topology.svg[VXLAN bridged with physical interface topology]
|
||||
endif::testgroup[]
|
||||
ifndef::testgroup[]
|
||||
image::topology.svg[VXLAN bridged with physical interface topology]
|
||||
endif::testgroup[]
|
||||
endif::topdoc[]
|
||||
==== Test sequence
|
||||
. Set up topology and attach to target DUTs
|
||||
. Configure DUTs
|
||||
. Test connectivity host:data to right:vxlan0 at 192.168.10.2
|
||||
|
||||
|
||||
<<<
|
||||
|
||||
+62
-18
@@ -67,15 +67,42 @@ class TestStepVisitor(ast.NodeVisitor):
|
||||
self.description=self.description.strip()
|
||||
self.generic_visit(node)
|
||||
|
||||
def _rebuild_fstring(self, node: ast.JoinedStr) -> str:
|
||||
"""
|
||||
Rebuild an f-string (JoinedStr) by concatenating literal pieces and placeholders.
|
||||
"""
|
||||
parts = []
|
||||
for value in node.values:
|
||||
if isinstance(value, ast.Constant):
|
||||
parts.append(value.value)
|
||||
elif isinstance(value, ast.FormattedValue):
|
||||
expression = ast.unparse(value.value) if hasattr(ast, "unparse") else ast.dump(value.value)
|
||||
parts.append(f"{{{expression}}}")
|
||||
return "".join(parts)
|
||||
|
||||
# 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':
|
||||
arg = node.args[0]
|
||||
if isinstance(node.func.value, ast.Name) and node.func.value.id == 'test':
|
||||
if node.args and isinstance(node.args[0], ast.Constant):
|
||||
self.test_steps.append(node.args[0].value)
|
||||
if node.args:
|
||||
if isinstance(arg, ast.Constant):
|
||||
self.test_steps.append(node.args[0].value)
|
||||
elif isinstance(arg, ast.JoinedStr):
|
||||
fstring_value = self._rebuild_fstring(arg)
|
||||
self.test_steps.append(fstring_value)
|
||||
self.generic_visit(node) # Continue visiting other nodes
|
||||
|
||||
def replace_in_steps(steps, variables):
|
||||
result = []
|
||||
for step in steps:
|
||||
new_step = step
|
||||
for k, v in variables.items():
|
||||
new_step = new_step.replace("{" + k + "}", v)
|
||||
result.append(new_step)
|
||||
return result
|
||||
|
||||
class TestCase:
|
||||
"""All test specifcation resources for a test case"""
|
||||
def __init__(self, directory, rootdir=None):
|
||||
@@ -103,7 +130,7 @@ class TestCase:
|
||||
f.write(mod_content)
|
||||
|
||||
|
||||
def generate_specification(self, name, case_path, spec_path):
|
||||
def generate_specification(self, name, case_path, spec_path, variables):
|
||||
"""Generate a ASCIIDOC specification for the test case"""
|
||||
with open(case_path, 'r') as file:
|
||||
script_content = file.read()
|
||||
@@ -113,6 +140,12 @@ class TestCase:
|
||||
visitor.visit(parsed_script)
|
||||
description=visitor.description if visitor.description != "" else "Undefined"
|
||||
test_steps=visitor.test_steps
|
||||
if variables is not None:
|
||||
for k,v in variables.items():
|
||||
if "{" + k + "}" in description:
|
||||
description = description.replace("{" + k + "}", v)
|
||||
|
||||
test_steps=replace_in_steps(test_steps, variables)
|
||||
if name is None:
|
||||
name = visitor.name
|
||||
self.generate_topology()
|
||||
@@ -135,22 +168,24 @@ class TestCase:
|
||||
spec.write("endif::topdoc[]\n")
|
||||
spec.write("==== Test sequence\n")
|
||||
spec.writelines([f". {step}\n" for step in test_steps])
|
||||
spec.write("\n\n<<<\n\n") # need empty lines to pagebreak
|
||||
spec.write("\n\n<<<\n\n") # need empty lines to pagebreak
|
||||
|
||||
|
||||
def parse_suite(directory, root, suitefile):
|
||||
"""Parse 9pm .yaml suite file"""
|
||||
test_spec = None
|
||||
readme = None
|
||||
num_tests = 0
|
||||
|
||||
with open(suitefile, "r") as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
for entry in data:
|
||||
if entry.get("suite"):
|
||||
path = os.path.dirname(entry["suite"])
|
||||
parse_suite(f"{directory}/{path}", root, f"{directory}/{entry['suite']}")
|
||||
elif entry.get("case"):
|
||||
variables=None
|
||||
if not entry.get("infamy"):
|
||||
test_title = None # Set from the docstring in the test case
|
||||
test_title = None # Set from the docstring in the test case
|
||||
else:
|
||||
specification = entry["infamy"].get("specification", True);
|
||||
if not specification:
|
||||
@@ -160,39 +195,48 @@ def parse_suite(directory, root, suitefile):
|
||||
if readme is None:
|
||||
readme = open(f"{directory}/{path}/Readme.adoc.tmp", "w")
|
||||
test_case_id = entry["name"]
|
||||
|
||||
if "opts" in entry:
|
||||
variables = dict((k[2:], v) for k, v in zip(entry["opts"][::2], entry["opts"][1::2]))
|
||||
|
||||
test_file = f"{directory}/{entry['case']}"
|
||||
test_spec = f"{directory}/{path}/{test_case_id}.adoc"
|
||||
test_case = TestCase(f"{directory}/{path}", root)
|
||||
test_case.generate_specification(test_title, test_file, test_spec)
|
||||
test_case.generate_specification(test_title, test_file, test_spec, variables)
|
||||
readme.write(f"include::{path}{test_case_id}.adoc[]\n\n")
|
||||
if path != "":
|
||||
readme.close()
|
||||
os.unlink(f"{directory}/{path}/Readme.adoc.tmp")
|
||||
readme = None
|
||||
os.unlink(f"{directory}/{path}/Readme.adoc")
|
||||
os.symlink(f"{test_case_id}.adoc", f"{directory}/{path}/Readme.adoc")
|
||||
if not readme is None:
|
||||
if readme is not None:
|
||||
readme.close()
|
||||
os.rename(f"{directory}/Readme.adoc.tmp", f"{directory}/Readme.adoc")
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description="Generate a test specification for a subtree.")
|
||||
parser.add_argument("-s", "--suite", required=True, help="The suite file to parse")
|
||||
parser.add_argument("-r", "--root-dir", required=True, help="Path that all paths should be relative to")
|
||||
parser.add_argument("-d", "--debug", help="Show debug output", action="store_true")
|
||||
args=parser.parse_args()
|
||||
|
||||
error_string = ""
|
||||
output_capture = io.StringIO()
|
||||
sys.stderr = output_capture
|
||||
|
||||
# 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)
|
||||
|
||||
if not args.debug:
|
||||
output_capture = io.StringIO()
|
||||
sys.stderr = output_capture
|
||||
output_capture.truncate(0)
|
||||
output_capture.seek(0)
|
||||
parse_suite(os.path.dirname(args.suite), args.root_dir, args.suite)
|
||||
|
||||
sys.stdout = sys.__stdout__
|
||||
if not args.debug:
|
||||
sys.stdout = sys.__stdout__
|
||||
|
||||
if len(error_string) > 0:
|
||||
print(error_string)
|
||||
exit(1)
|
||||
if len(error_string) > 0:
|
||||
print(error_string)
|
||||
exit(1)
|
||||
|
||||
exit(0)
|
||||
|
||||
Reference in New Issue
Block a user