summaryrefslogtreecommitdiff
path: root/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'src/test')
-rw-r--r--src/test/Makefile17
-rw-r--r--src/test/debug/documentation.txt102
-rw-r--r--src/test/debug/generateconfig.pl24
-rw-r--r--src/test/debug/statuscheck.pl9
-rw-r--r--src/test/dns/powerdns/dns_config10
-rw-r--r--src/test/dns/powerdns/expected.add_a_multiple_record.ipv413
-rw-r--r--src/test/dns/powerdns/expected.add_a_multiple_record.ipv613
-rw-r--r--src/test/dns/powerdns/expected.add_a_record.ipv412
-rw-r--r--src/test/dns/powerdns/expected.add_a_record.ipv613
-rw-r--r--src/test/dns/powerdns/expected.add_ptr_record.ipv413
-rw-r--r--src/test/dns/powerdns/expected.add_ptr_record.ipv612
-rw-r--r--src/test/dns/powerdns/expected.del_a_multiple_record.ipv413
-rw-r--r--src/test/dns/powerdns/expected.del_a_multiple_record.ipv612
-rw-r--r--src/test/dns/powerdns/expected.del_a_record.ipv413
-rw-r--r--src/test/dns/powerdns/expected.del_a_record.ipv612
-rw-r--r--src/test/dns/powerdns/expected.del_ptr_record.ipv412
-rw-r--r--src/test/dns/powerdns/expected.del_ptr_record.ipv613
-rw-r--r--src/test/dns/powerdns/expected.verify_zone12
-rw-r--r--src/test/dns/powerdns/sdn_config20
-rw-r--r--src/test/ipams/netbox/expected.add_ip9
-rw-r--r--src/test/ipams/netbox/expected.add_ip_notgateway9
-rw-r--r--src/test/ipams/netbox/expected.add_next_freeip9
-rw-r--r--src/test/ipams/netbox/expected.add_subnet9
-rw-r--r--src/test/ipams/netbox/expected.del_ip9
-rw-r--r--src/test/ipams/netbox/expected.del_subnet9
-rw-r--r--src/test/ipams/netbox/expected.update_ip9
-rw-r--r--src/test/ipams/netbox/ipam_config18
-rw-r--r--src/test/ipams/netbox/sdn_config20
-rw-r--r--src/test/ipams/phpipam/expected.add_ip12
-rw-r--r--src/test/ipams/phpipam/expected.add_ip_notgateway12
-rw-r--r--src/test/ipams/phpipam/expected.add_next_freeip12
-rw-r--r--src/test/ipams/phpipam/expected.add_subnet12
-rw-r--r--src/test/ipams/phpipam/expected.del_ip12
-rw-r--r--src/test/ipams/phpipam/expected.del_subnet12
-rw-r--r--src/test/ipams/phpipam/expected.update_ip12
-rw-r--r--src/test/ipams/phpipam/ipam_config18
-rw-r--r--src/test/ipams/phpipam/sdn_config20
-rwxr-xr-xsrc/test/run_test_dns.pl271
-rwxr-xr-xsrc/test/run_test_ipams.pl197
-rwxr-xr-xsrc/test/run_test_subnets.pl305
-rwxr-xr-xsrc/test/run_test_vnets.pl355
-rwxr-xr-xsrc/test/run_test_zones.pl123
-rw-r--r--src/test/subnets/ipv4/ipam_config18
-rw-r--r--src/test/subnets/ipv4/sdn_config20
-rw-r--r--src/test/subnets/ipv6/ipam_config18
-rw-r--r--src/test/subnets/ipv6/sdn_config20
-rw-r--r--src/test/subnets/noipam/ipam_config18
-rw-r--r--src/test/subnets/noipam/sdn_config20
-rw-r--r--src/test/vnets/ipv4/ipam.db17
-rw-r--r--src/test/vnets/ipv4/ipam_config7
-rw-r--r--src/test/vnets/ipv4/sdn_config26
-rw-r--r--src/test/vnets/ipv4noipam/ipam.db17
-rw-r--r--src/test/vnets/ipv4noipam/ipam_config7
-rw-r--r--src/test/vnets/ipv4noipam/sdn_config26
-rw-r--r--src/test/vnets/ipv6/ipam.db16
-rw-r--r--src/test/vnets/ipv6/ipam_config7
-rw-r--r--src/test/vnets/ipv6/sdn_config26
-rw-r--r--src/test/zones/evpn/advertise_subnets/expected_controller_config54
-rw-r--r--src/test/zones/evpn/advertise_subnets/expected_sdn_interfaces42
-rw-r--r--src/test/zones/evpn/advertise_subnets/interfaces7
-rw-r--r--src/test/zones/evpn/advertise_subnets/sdn_config26
-rw-r--r--src/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config41
-rw-r--r--src/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces40
-rw-r--r--src/test/zones/evpn/disable_arp_nd_suppression/interfaces7
-rw-r--r--src/test/zones/evpn/disable_arp_nd_suppression/sdn_config26
-rw-r--r--src/test/zones/evpn/ebgp/expected_controller_config58
-rw-r--r--src/test/zones/evpn/ebgp/expected_sdn_interfaces41
-rw-r--r--src/test/zones/evpn/ebgp/interfaces7
-rw-r--r--src/test/zones/evpn/ebgp/sdn_config50
-rw-r--r--src/test/zones/evpn/ebgp_loopback/expected_controller_config69
-rw-r--r--src/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces41
-rw-r--r--src/test/zones/evpn/ebgp_loopback/interfaces13
-rw-r--r--src/test/zones/evpn/ebgp_loopback/sdn_config29
-rw-r--r--src/test/zones/evpn/exitnode/expected_controller_config66
-rw-r--r--src/test/zones/evpn/exitnode/expected_sdn_interfaces41
-rw-r--r--src/test/zones/evpn/exitnode/interfaces7
-rw-r--r--src/test/zones/evpn/exitnode/sdn_config26
-rw-r--r--src/test/zones/evpn/exitnode_local_routing/expected_controller_config51
-rw-r--r--src/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces56
-rw-r--r--src/test/zones/evpn/exitnode_local_routing/interfaces7
-rw-r--r--src/test/zones/evpn/exitnode_local_routing/sdn_config27
-rw-r--r--src/test/zones/evpn/exitnode_primary/expected_controller_config68
-rw-r--r--src/test/zones/evpn/exitnode_primary/expected_sdn_interfaces41
-rw-r--r--src/test/zones/evpn/exitnode_primary/interfaces7
-rw-r--r--src/test/zones/evpn/exitnode_primary/sdn_config26
-rw-r--r--src/test/zones/evpn/exitnode_snat/expected_controller_config66
-rw-r--r--src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces68
-rw-r--r--src/test/zones/evpn/exitnode_snat/interfaces7
-rw-r--r--src/test/zones/evpn/exitnode_snat/sdn_config35
-rw-r--r--src/test/zones/evpn/ipv4/expected_controller_config41
-rw-r--r--src/test/zones/evpn/ipv4/expected_sdn_interfaces42
-rw-r--r--src/test/zones/evpn/ipv4/interfaces7
-rw-r--r--src/test/zones/evpn/ipv4/sdn_config26
-rw-r--r--src/test/zones/evpn/ipv4ipv6/expected_controller_config41
-rw-r--r--src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces44
-rw-r--r--src/test/zones/evpn/ipv4ipv6/interfaces7
-rw-r--r--src/test/zones/evpn/ipv4ipv6/sdn_config32
-rw-r--r--src/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config41
-rw-r--r--src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces40
-rw-r--r--src/test/zones/evpn/ipv4ipv6nogateway/interfaces7
-rw-r--r--src/test/zones/evpn/ipv4ipv6nogateway/sdn_config30
-rw-r--r--src/test/zones/evpn/ipv6/expected_controller_config41
-rw-r--r--src/test/zones/evpn/ipv6/expected_sdn_interfaces42
-rw-r--r--src/test/zones/evpn/ipv6/interfaces7
-rw-r--r--src/test/zones/evpn/ipv6/sdn_config27
-rw-r--r--src/test/zones/evpn/multipath_relax/expected_controller_config53
-rw-r--r--src/test/zones/evpn/multipath_relax/expected_sdn_interfaces41
-rw-r--r--src/test/zones/evpn/multipath_relax/interfaces7
-rw-r--r--src/test/zones/evpn/multipath_relax/sdn_config49
-rw-r--r--src/test/zones/evpn/rt_import/expected_controller_config47
-rw-r--r--src/test/zones/evpn/rt_import/expected_sdn_interfaces42
-rw-r--r--src/test/zones/evpn/rt_import/interfaces7
-rw-r--r--src/test/zones/evpn/rt_import/sdn_config26
-rw-r--r--src/test/zones/qinq/bridge/expected_sdn_interfaces65
-rw-r--r--src/test/zones/qinq/bridge/interfaces5
-rw-r--r--src/test/zones/qinq/bridge/sdn_config16
-rw-r--r--src/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces36
-rw-r--r--src/test/zones/qinq/bridge_notagvnet/interfaces5
-rw-r--r--src/test/zones/qinq/bridge_notagvnet/sdn_config26
-rw-r--r--src/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces55
-rw-r--r--src/test/zones/qinq/bridge_vlanaware/interfaces7
-rw-r--r--src/test/zones/qinq/bridge_vlanaware/sdn_config16
-rw-r--r--src/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces27
-rw-r--r--src/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces7
-rw-r--r--src/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config11
-rw-r--r--src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces27
-rw-r--r--src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces7
-rw-r--r--src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config11
-rw-r--r--src/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces29
-rw-r--r--src/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces7
-rw-r--r--src/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config11
-rw-r--r--src/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces32
-rw-r--r--src/test/zones/qinq/bridge_vlanawarevnet/interfaces5
-rw-r--r--src/test/zones/qinq/bridge_vlanawarevnet/sdn_config11
-rw-r--r--src/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces31
-rw-r--r--src/test/zones/qinq/bridge_vlanprotocol/interfaces5
-rw-r--r--src/test/zones/qinq/bridge_vlanprotocol/sdn_config11
-rw-r--r--src/test/zones/qinq/ovs/expected_sdn_interfaces71
-rw-r--r--src/test/zones/qinq/ovs/interfaces9
-rw-r--r--src/test/zones/qinq/ovs/sdn_config16
-rw-r--r--src/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces37
-rw-r--r--src/test/zones/qinq/ovs_notagvnet/interfaces9
-rw-r--r--src/test/zones/qinq/ovs_notagvnet/sdn_config11
-rw-r--r--src/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces37
-rw-r--r--src/test/zones/qinq/ovs_vlanawarevnet/interfaces9
-rw-r--r--src/test/zones/qinq/ovs_vlanawarevnet/sdn_config11
-rw-r--r--src/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces35
-rw-r--r--src/test/zones/qinq/ovs_vlanprotocol/interfaces9
-rw-r--r--src/test/zones/qinq/ovs_vlanprotocol/sdn_config11
-rw-r--r--src/test/zones/simple/basic/expected_sdn_interfaces7
-rw-r--r--src/test/zones/simple/basic/interfaces5
-rw-r--r--src/test/zones/simple/basic/sdn_config11
-rw-r--r--src/test/zones/simple/hetzner/expected_sdn_interfaces19
-rw-r--r--src/test/zones/simple/hetzner/interfaces6
-rw-r--r--src/test/zones/simple/hetzner/sdn_config34
-rw-r--r--src/test/zones/simple/ipv4/expected_sdn_interfaces9
-rw-r--r--src/test/zones/simple/ipv4/interfaces5
-rw-r--r--src/test/zones/simple/ipv4/sdn_config22
-rw-r--r--src/test/zones/simple/ipv4snat/expected_sdn_interfaces13
-rw-r--r--src/test/zones/simple/ipv4snat/interfaces7
-rw-r--r--src/test/zones/simple/ipv4snat/sdn_config23
-rw-r--r--src/test/zones/simple/ipv4v6/expected_sdn_interfaces11
-rw-r--r--src/test/zones/simple/ipv4v6/interfaces5
-rw-r--r--src/test/zones/simple/ipv4v6/sdn_config27
-rw-r--r--src/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces7
-rw-r--r--src/test/zones/simple/ipv4v6nogateway/interfaces5
-rw-r--r--src/test/zones/simple/ipv4v6nogateway/sdn_config25
-rw-r--r--src/test/zones/simple/ipv6snat/expected_sdn_interfaces13
-rw-r--r--src/test/zones/simple/ipv6snat/interfaces7
-rw-r--r--src/test/zones/simple/ipv6snat/sdn_config24
-rw-r--r--src/test/zones/vlan/bridge/expected_sdn_interfaces23
-rw-r--r--src/test/zones/vlan/bridge/interfaces5
-rw-r--r--src/test/zones/vlan/bridge/sdn_config11
-rw-r--r--src/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces7
-rw-r--r--src/test/zones/vlan/bridge_vlanaware/interfaces7
-rw-r--r--src/test/zones/vlan/bridge_vlanaware/sdn_config11
-rw-r--r--src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces9
-rw-r--r--src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces7
-rw-r--r--src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config11
-rw-r--r--src/test/zones/vlan/ovs/expected_sdn_interfaces17
-rw-r--r--src/test/zones/vlan/ovs/interfaces9
-rw-r--r--src/test/zones/vlan/ovs/sdn_config11
-rw-r--r--src/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces19
-rw-r--r--src/test/zones/vlan/ovs_vlanware_vnet/interfaces9
-rw-r--r--src/test/zones/vlan/ovs_vlanware_vnet/sdn_config11
-rw-r--r--src/test/zones/vxlan/basic/expected_sdn_interfaces15
-rw-r--r--src/test/zones/vxlan/basic/interfaces7
-rw-r--r--src/test/zones/vxlan/basic/sdn_config11
-rw-r--r--src/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces17
-rw-r--r--src/test/zones/vxlan/vlanawarevnet/interfaces7
-rw-r--r--src/test/zones/vxlan/vlanawarevnet/sdn_config11
191 files changed, 5227 insertions, 0 deletions
diff --git a/src/test/Makefile b/src/test/Makefile
new file mode 100644
index 0000000..eb59d5f
--- /dev/null
+++ b/src/test/Makefile
@@ -0,0 +1,17 @@
+all: test
+
+test: test_zones test_ipams test_dns test_subnets
+
+test_zones: run_test_zones.pl
+ ./run_test_zones.pl
+
+test_ipams: run_test_ipams.pl
+ ./run_test_ipams.pl
+
+test_dns: run_test_dns.pl
+ ./run_test_dns.pl
+
+test_subnets: run_test_subnets.pl
+ ./run_test_subnets.pl
+
+clean:
diff --git a/src/test/debug/documentation.txt b/src/test/debug/documentation.txt
new file mode 100644
index 0000000..6ee8ee6
--- /dev/null
+++ b/src/test/debug/documentation.txt
@@ -0,0 +1,102 @@
+Here a sample of command with pvesh to manage the sdn.
+
+
+#create a vlan transportzone
+pvesh create /cluster/sdn/zones/ --zone vlanzone --type vlan --ipam pve --bridge vmbr0
+#create a vnet on vlanzone
+pvesh create /cluster/sdn/vnets/ --vnet vnet100 --type vnet --zone vlanzone --tag 100
+#create a subnet on vlanzone
+pvesh create /cluster/sdn/vnets/vnet100/subnets/ --type subnet --subnet 192.168.0.0/24 --gateway 192.168.0.1
+
+
+#create a layer2 vxlan unicast transportzone
+pvesh create /cluster/sdn/zones/ --zone vxlanunicastzone --type vxlan --ipam pve --peers 192.168.0.1,192.168.0.2,192.168.0.3
+
+#create an evpn controller
+pvesh create /cluster/sdn/controllers/ --controller evpn1 --type evpn --peers 192.168.0.1,192.168.0.2,192.168.0.3 --asn 1234
+
+#add a ebgp peer
+pvesh create /cluster/sdn/controllers/ --controller bgp1 --type bgp --peers 192.168.0.253,192.168.0.254 --asn 1234 --ebgp --node pxnode1
+
+#create a layer2 vxlan bgpevpn transportzone
+pvesh create /cluster/sdn/zones/ --zone layer2evpnzone --type evpn --ipam pve --controller evpn1
+
+#create a layer3 routable vxlan bgpevpn transportzone + exit-nodes
+pvesh create /cluster/sdn/zones/ --zone layer3evpnzone --type evpn --ipam pve --controller evpn1 --vrf-vxlan 4000 --exit-nodes pxnode1,pxnode2
+
+
+
+#create a vnet in the transportzone
+pvesh create /cluster/sdn/vnets/ --vnet vnet10 --type vnet --zone vlanzone --tag 10
+
+#create a vnet in the transportzone with subnets for evpn routing
+pvesh create /cluster/sdn/vnets/ --vnet vnet11 --type vnet --zone layer3evpnzone --tag 11 --mac c8:1f:66:f8:62:8d
+pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.0.0/24 --gateway 10.0.0.1
+pvesh create /cluster/sdn/vnets/ --vnet vnet12 --type vnet --zone layer3evpnzone --tag 12 --mac c8:1f:66:f8:62:8e
+pvesh create /cluster/sdn/vnets/vnet11/subnets/ --type subnet --subnet 10.0.1.0/24 --gateway 10.0.1.1
+
+#display running configuration
+pvesh get /cluster/sdn/vnets --running
+pvesh get /cluster/sdn/zones --running
+pvesh get /cluster/sdn/controllers --running
+pvesh get /cluster/sdn/vnets/vnetX/subnets --running
+
+
+#display pending configuration
+pvesh get /cluster/sdn/vnets --pending
+pvesh get /cluster/sdn/zones --pending
+pvesh get /cluster/sdn/controllers --pending
+pvesh get /cluster/sdn/vnets/vnetX/subnets --pending
+
+
+#apply changes from /etc/pve/sdn.cfg.new to /etc/pve/sdn.cfg
+pvesh set /cluster/sdn
+
+
+#generate local /etc/network/interfaces.d/sdn and reload (need to be called on each node)
+ pvesh set /nodes/<node>/network
+
+
+display transporzone status on all cluster nodes
+#pvesh get /cluster/resources
+┌────────────────────────────────────┬─────────┬───────┬───────────┬─────────┬───────┬────────┬─────────────┬────────────┬────────────┬───────────────┬──────┬───────────┬──────────────┬────────────────┐
+│ id │ type │ cpu │ disk │ hastate │ level │ maxcpu │ maxdisk │ maxmem │ mem │ node │ pool │ status │ storage │ uptime │
+│ sdn/node1/transportzone10 │ sdn │ │ │ │ │ │ │ │ │ kvmformation1 │ │ error │ │ │
+├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤
+│ sdn/node1/zone1 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │
+├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤
+│ sdn/node1/zone4 │ sdn │ │ │ │ │ │ │ │ │ node1 │ │ available │ │ │
+├────────────────────────────────────┼─────────┼───────┼───────────┼─────────┼───────┼────────┼─────────────┼────────────┼────────────┼───────────────┼──────┼───────────┼──────────────┼────────────────┤
+
+
+
+
+#list all transport zones of a node
+
+pvesh get /nodes/<node>/sdn/zones/
+ ┌─────────────────┬───────────┐
+ │ sdn │ status │
+ ├─────────────────┼───────────┤
+ │ transportzone10 │ error │
+ ├─────────────────┼───────────┤
+ │ zone1 │ available │
+ ├─────────────────┼───────────┤
+ │ zone4 │ available │
+ └─────────────────┴───────────┘
+
+
+#list all vnet status from a node transportzone
+
+pveset get /nodes/<node>/sdn/zones/<transportzone>/content
+
+ ┌─────────┬────────┐
+ │ vnet │ status │
+ ├─────────┼────────┤
+ │ vnet100 │ error │
+ ├─────────┼────────┤
+ │ vnet101 │ error │
+ └─────────┴────────┘
+
+
+
+
diff --git a/src/test/debug/generateconfig.pl b/src/test/debug/generateconfig.pl
new file mode 100644
index 0000000..250db43
--- /dev/null
+++ b/src/test/debug/generateconfig.pl
@@ -0,0 +1,24 @@
+use strict;
+use warnings;
+use File::Copy;
+use PVE::Cluster qw(cfs_read_file);
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use Data::Dumper;
+
+PVE::Network::SDN::commit_config();
+my $network_config = PVE::Network::SDN::Zones::generate_etc_network_config();
+
+PVE::Network::SDN::Zones::write_etc_network_config($network_config);
+print "/etc/network/interfaces.d/sdn\n";
+print $network_config;
+print "\n";
+
+my $controller_config = PVE::Network::SDN::Controllers::generate_controller_config();
+
+if ($controller_config) {
+ print Dumper($controller_config);
+ PVE::Network::SDN::Controllers::write_controller_config($controller_config);
+}
diff --git a/src/test/debug/statuscheck.pl b/src/test/debug/statuscheck.pl
new file mode 100644
index 0000000..e43003b
--- /dev/null
+++ b/src/test/debug/statuscheck.pl
@@ -0,0 +1,9 @@
+use strict;
+use warnings;
+use PVE::Network::SDN;
+use Data::Dumper;
+
+my ($transport_status, $vnet_status) = PVE::Network::SDN::status();
+
+print Dumper($vnet_status);
+print Dumper($transport_status);
diff --git a/src/test/dns/powerdns/dns_config b/src/test/dns/powerdns/dns_config
new file mode 100644
index 0000000..6052366
--- /dev/null
+++ b/src/test/dns/powerdns/dns_config
@@ -0,0 +1,10 @@
+{
+ 'ids' => {
+ 'powerdns' => {
+ 'url' => 'http://localhost:8881/api/v1/servers/localhost',
+ 'type' => 'powerdns',
+ 'key' => '1234',
+ 'ttl' => '3600'
+ },
+ },
+}
diff --git a/src/test/dns/powerdns/expected.add_a_multiple_record.ipv4 b/src/test/dns/powerdns/expected.add_a_multiple_record.ipv4
new file mode 100644
index 0000000..0e5539f
--- /dev/null
+++ b/src/test/dns/powerdns/expected.add_a_multiple_record.ipv4
@@ -0,0 +1,13 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"},{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
diff --git a/src/test/dns/powerdns/expected.add_a_multiple_record.ipv6 b/src/test/dns/powerdns/expected.add_a_multiple_record.ipv6
new file mode 100644
index 0000000..e432e7b
--- /dev/null
+++ b/src/test/dns/powerdns/expected.add_a_multiple_record.ipv6
@@ -0,0 +1,13 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"},{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
diff --git a/src/test/dns/powerdns/expected.add_a_record.ipv4 b/src/test/dns/powerdns/expected.add_a_record.ipv4
new file mode 100644
index 0000000..888d67f
--- /dev/null
+++ b/src/test/dns/powerdns/expected.add_a_record.ipv4
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"10.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' ); \ No newline at end of file
diff --git a/src/test/dns/powerdns/expected.add_a_record.ipv6 b/src/test/dns/powerdns/expected.add_a_record.ipv6
new file mode 100644
index 0000000..bfeeab7
--- /dev/null
+++ b/src/test/dns/powerdns/expected.add_a_record.ipv6
@@ -0,0 +1,13 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8888","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
diff --git a/src/test/dns/powerdns/expected.add_ptr_record.ipv4 b/src/test/dns/powerdns/expected.add_ptr_record.ipv4
new file mode 100644
index 0000000..6923971
--- /dev/null
+++ b/src/test/dns/powerdns/expected.add_ptr_record.ipv4
@@ -0,0 +1,13 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"1.0.0.10.in-addr.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"1.0.0.10.in-addr.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
diff --git a/src/test/dns/powerdns/expected.add_ptr_record.ipv6 b/src/test/dns/powerdns/expected.add_ptr_record.ipv6
new file mode 100644
index 0000000..1d8049f
--- /dev/null
+++ b/src/test/dns/powerdns/expected.add_ptr_record.ipv6
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[{"content":"myhostname.","disabled":false,"name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","priority":0,"type":"PTR"}],"ttl":"3600","type":"PTR"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/dns/powerdns/expected.del_a_multiple_record.ipv4 b/src/test/dns/powerdns/expected.del_a_multiple_record.ipv4
new file mode 100644
index 0000000..45d76c6
--- /dev/null
+++ b/src/test/dns/powerdns/expected.del_a_multiple_record.ipv4
@@ -0,0 +1,13 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"127.0.0.1","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"A"}],"ttl":"3600","type":"A"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
diff --git a/src/test/dns/powerdns/expected.del_a_multiple_record.ipv6 b/src/test/dns/powerdns/expected.del_a_multiple_record.ipv6
new file mode 100644
index 0000000..9b56abd
--- /dev/null
+++ b/src/test/dns/powerdns/expected.del_a_multiple_record.ipv6
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"REPLACE","name":"myhostname.domain.com.","records":[{"content":"2001:4860:4860::8844","disabled":false,"name":"myhostname.domain.com.","priority":0,"type":"AAAA"}],"ttl":"3600","type":"AAAA"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/dns/powerdns/expected.del_a_record.ipv4 b/src/test/dns/powerdns/expected.del_a_record.ipv4
new file mode 100644
index 0000000..7c0cf45
--- /dev/null
+++ b/src/test/dns/powerdns/expected.del_a_record.ipv4
@@ -0,0 +1,13 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"A"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
diff --git a/src/test/dns/powerdns/expected.del_a_record.ipv6 b/src/test/dns/powerdns/expected.del_a_record.ipv6
new file mode 100644
index 0000000..9494c83
--- /dev/null
+++ b/src/test/dns/powerdns/expected.del_a_record.ipv6
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"DELETE","name":"myhostname.domain.com.","records":[],"type":"AAAA"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/dns/powerdns/expected.del_ptr_record.ipv4 b/src/test/dns/powerdns/expected.del_ptr_record.ipv4
new file mode 100644
index 0000000..120485b
--- /dev/null
+++ b/src/test/dns/powerdns/expected.del_ptr_record.ipv4
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"DELETE","name":"1.0.0.10.in-addr.arpa.","records":[],"type":"PTR"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/dns/powerdns/expected.del_ptr_record.ipv6 b/src/test/dns/powerdns/expected.del_ptr_record.ipv6
new file mode 100644
index 0000000..7948e78
--- /dev/null
+++ b/src/test/dns/powerdns/expected.del_ptr_record.ipv6
@@ -0,0 +1,13 @@
+bless( {
+ '_content' => '{"rrsets":[{"changetype":"DELETE","name":"8.8.8.8.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.6.8.4.0.6.8.4.1.0.0.2.ip6.arpa.","records":[],"type":"PTR"}]}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com')}, 'URI::http' )
+ }, 'HTTP::Request' );
+
diff --git a/src/test/dns/powerdns/expected.verify_zone b/src/test/dns/powerdns/expected.verify_zone
new file mode 100644
index 0000000..b476875
--- /dev/null
+++ b/src/test/dns/powerdns/expected.verify_zone
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'x-api-key' => 'X-API-Key'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'x-api-key' => '1234'
+ }, 'HTTP::Headers' ),
+ '_method' => 'GET',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8881/api/v1/servers/localhost/zones/domain.com?rrsets=false')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/dns/powerdns/sdn_config b/src/test/dns/powerdns/sdn_config
new file mode 100644
index 0000000..2087729
--- /dev/null
+++ b/src/test/dns/powerdns/sdn_config
@@ -0,0 +1,20 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple", dns => "powerdns", reversedns => "powerdns", dnszone => "domain.com" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
diff --git a/src/test/ipams/netbox/expected.add_ip b/src/test/ipams/netbox/expected.add_ip
new file mode 100644
index 0000000..ae876f2
--- /dev/null
+++ b/src/test/ipams/netbox/expected.add_ip
@@ -0,0 +1,9 @@
+bless( {
+ '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/netbox/expected.add_ip_notgateway b/src/test/ipams/netbox/expected.add_ip_notgateway
new file mode 100644
index 0000000..ae876f2
--- /dev/null
+++ b/src/test/ipams/netbox/expected.add_ip_notgateway
@@ -0,0 +1,9 @@
+bless( {
+ '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/netbox/expected.add_next_freeip b/src/test/ipams/netbox/expected.add_next_freeip
new file mode 100644
index 0000000..7f80f4c
--- /dev/null
+++ b/src/test/ipams/netbox/expected.add_next_freeip
@@ -0,0 +1,9 @@
+bless( {
+ '_content' => '{"description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/1/available-ips/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/netbox/expected.add_subnet b/src/test/ipams/netbox/expected.add_subnet
new file mode 100644
index 0000000..62ca823
--- /dev/null
+++ b/src/test/ipams/netbox/expected.add_subnet
@@ -0,0 +1,9 @@
+bless( {
+ '_content' => '{"prefix":"10.0.0.0/24"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/prefixes/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/netbox/expected.del_ip b/src/test/ipams/netbox/expected.del_ip
new file mode 100644
index 0000000..3c41de4
--- /dev/null
+++ b/src/test/ipams/netbox/expected.del_ip
@@ -0,0 +1,9 @@
+bless( {
+ '_content' => '',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'DELETE',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/netbox/expected.del_subnet b/src/test/ipams/netbox/expected.del_subnet
new file mode 100644
index 0000000..bdadb71
--- /dev/null
+++ b/src/test/ipams/netbox/expected.del_subnet
@@ -0,0 +1,9 @@
+bless( {
+ '_content' => '{"address":"192.168.0.1/24","description":null,"dns_name":"toto"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/netbox/expected.update_ip b/src/test/ipams/netbox/expected.update_ip
new file mode 100644
index 0000000..a1202ad
--- /dev/null
+++ b/src/test/ipams/netbox/expected.update_ip
@@ -0,0 +1,9 @@
+bless( {
+ '_content' => '{"address":"10.0.0.1/24","description":"mydescription mac:da:65:8f:18:9b:6f","dns_name":"myhostname"}',
+ '_headers' => bless( {
+ 'authorization' => 'token 0123456789abcdef0123456789abcdef01234567',
+ 'content-type' => 'application/json; charset=UTF-8'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'http://localhost:8000/api/ipam/ip-addresses/1/')}, 'URI::http' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/netbox/ipam_config b/src/test/ipams/netbox/ipam_config
new file mode 100644
index 0000000..a33be30
--- /dev/null
+++ b/src/test/ipams/netbox/ipam_config
@@ -0,0 +1,18 @@
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
diff --git a/src/test/ipams/netbox/sdn_config b/src/test/ipams/netbox/sdn_config
new file mode 100644
index 0000000..c31847b
--- /dev/null
+++ b/src/test/ipams/netbox/sdn_config
@@ -0,0 +1,20 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "netbox" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
diff --git a/src/test/ipams/phpipam/expected.add_ip b/src/test/ipams/phpipam/expected.add_ip
new file mode 100644
index 0000000..50af460
--- /dev/null
+++ b/src/test/ipams/phpipam/expected.add_ip
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","is_gateway":1,"mac":"da:65:8f:18:9b:6f","subnetId":1}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/phpipam/expected.add_ip_notgateway b/src/test/ipams/phpipam/expected.add_ip_notgateway
new file mode 100644
index 0000000..7a91359
--- /dev/null
+++ b/src/test/ipams/phpipam/expected.add_ip_notgateway
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"description":"mydescription","hostname":"myhostname","ip":"10.0.0.1","mac":"da:65:8f:18:9b:6f","subnetId":1}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/phpipam/expected.add_next_freeip b/src/test/ipams/phpipam/expected.add_next_freeip
new file mode 100644
index 0000000..d72f94f
--- /dev/null
+++ b/src/test/ipams/phpipam/expected.add_next_freeip
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"description":"mydescription","hostname":"myhostname","mac":"da:65:8f:18:9b:6f"}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/first_free/1/')}, 'URI::https' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/phpipam/expected.add_subnet b/src/test/ipams/phpipam/expected.add_subnet
new file mode 100644
index 0000000..b10cc5a
--- /dev/null
+++ b/src/test/ipams/phpipam/expected.add_subnet
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"mask":"24","sectionId":1,"subnet":"10.0.0.0"}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/subnets/')}, 'URI::https' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/phpipam/expected.del_ip b/src/test/ipams/phpipam/expected.del_ip
new file mode 100644
index 0000000..72e83cb
--- /dev/null
+++ b/src/test/ipams/phpipam/expected.del_ip
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'DELETE',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/phpipam/expected.del_subnet b/src/test/ipams/phpipam/expected.del_subnet
new file mode 100644
index 0000000..349a34f
--- /dev/null
+++ b/src/test/ipams/phpipam/expected.del_subnet
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"description":null,"hostname":"toto","ip":"192.168.0.1","is_gateway":null,"subnetId":1}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'POST',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/')}, 'URI::https' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/phpipam/expected.update_ip b/src/test/ipams/phpipam/expected.update_ip
new file mode 100644
index 0000000..96c219b
--- /dev/null
+++ b/src/test/ipams/phpipam/expected.update_ip
@@ -0,0 +1,12 @@
+bless( {
+ '_content' => '{"description":"mydescription","hostname":"myhostname","is_gateway":1,"mac":"da:65:8f:18:9b:6f"}',
+ '_headers' => bless( {
+ '::std_case' => {
+ 'token' => 'Token'
+ },
+ 'content-type' => 'application/json; charset=UTF-8',
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ }, 'HTTP::Headers' ),
+ '_method' => 'PATCH',
+ '_uri' => bless( do{\(my $o = 'https://localhost/api/apiadmin/addresses/1')}, 'URI::https' )
+ }, 'HTTP::Request' );
diff --git a/src/test/ipams/phpipam/ipam_config b/src/test/ipams/phpipam/ipam_config
new file mode 100644
index 0000000..a33be30
--- /dev/null
+++ b/src/test/ipams/phpipam/ipam_config
@@ -0,0 +1,18 @@
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
diff --git a/src/test/ipams/phpipam/sdn_config b/src/test/ipams/phpipam/sdn_config
new file mode 100644
index 0000000..c774807
--- /dev/null
+++ b/src/test/ipams/phpipam/sdn_config
@@ -0,0 +1,20 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "phpipam" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
diff --git a/src/test/run_test_dns.pl b/src/test/run_test_dns.pl
new file mode 100755
index 0000000..87e011e
--- /dev/null
+++ b/src/test/run_test_dns.pl
@@ -0,0 +1,271 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+use Net::IP;
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use JSON;
+
+use Data::Dumper qw(Dumper);
+$Data::Dumper::Sortkeys = 1;
+
+sub read_sdn_config {
+ my ($file) = @_;
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+
+ return $sdn_config;
+}
+
+
+my @plugins = read_dir( './dns/', prefix => 1 ) ;
+
+foreach my $path (@plugins) {
+
+ my (undef, $dnsid) = split(/\//, $path);
+ my $sdn_config = read_sdn_config ("$path/sdn_config");
+
+
+ my $pve_sdn_dns;
+ $pve_sdn_dns = Test::MockModule->new('PVE::Network::SDN::Dns');
+ $pve_sdn_dns->mock(
+ config => sub {
+ my $dns_config = read_sdn_config ("$path/dns_config");
+ return $dns_config;
+ },
+ );
+
+ my $sdn_module = Test::MockModule->new("PVE::Network::SDN");
+ $sdn_module->mock(
+ config => sub {
+ return $sdn_config;
+ },
+ api_request => sub {
+ my ($method, $url, $headers, $data) = @_;
+
+ my $js = JSON->new;
+ $js->canonical(1);
+
+ my $encoded_data = $js->encode($data) if $data;
+ my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
+ die Dumper($req);
+ }
+ );
+
+
+
+ my $dns_cfg = PVE::Network::SDN::Dns::config();
+ my $plugin_config = $dns_cfg->{ids}->{$dnsid};
+ my $plugin = PVE::Network::SDN::Dns::Plugin->lookup($plugin_config->{type});
+
+ #test params;
+ my @ips = ("10.0.0.1", "2001:4860:4860::8888");
+ my $zone = "domain.com";
+ my $hostname = "myhostname";
+
+ foreach my $ip (@ips) {
+
+ my $ipversion = Net::IP::ip_is_ipv6($ip) ? "ipv6" : "ipv4";
+ my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A";
+ my $ip2 = $type eq 'AAAA' ? '2001:4860:4860::8844' : '127.0.0.1';
+ my $fqdn = $hostname.".".$zone.".";
+
+ my $sdn_dns_plugin = Test::MockModule->new($plugin);
+ $sdn_dns_plugin->mock(
+
+ get_zone_content => sub {
+ return undef;
+ },
+ get_zone_rrset => sub {
+ return undef;
+ }
+ );
+
+ ## add_a_record
+ my $test = "add_a_record";
+ my $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ my $name = "$dnsid $test";
+
+ $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## add_ptr_record
+ $test = "add_ptr_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->add_ptr_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+
+ ## del_ptr_record
+ $test = "del_ptr_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->del_ptr_record($plugin_config, $zone, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+
+ ## del_a_record
+
+ $sdn_dns_plugin->mock(
+
+ get_zone_content => sub {
+ return undef;
+ },
+ get_zone_rrset => sub {
+
+ my $type = Net::IP::ip_is_ipv6($ip) ? "AAAA" : "A";
+ my $fqdn = $hostname.".".$zone.".";
+ my $record = { content => $ip,
+ disabled => JSON::false,
+ name => $fqdn,
+ type => $type,
+ priority => 0 };
+
+ my $rrset = { name => $fqdn,
+ type => $type,
+ ttl => '3600',
+ records => [ $record ] };
+ return $rrset;
+ }
+ );
+
+ $test = "del_a_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## del_a_multiple_record
+
+ $sdn_dns_plugin->mock(
+
+ get_zone_content => sub {
+ return undef;
+ },
+ get_zone_rrset => sub {
+
+ my $record = { content => $ip,
+ disabled => JSON::false,
+ name => $fqdn,
+ type => $type,
+ priority => 0 };
+
+ my $record2 = { content => $ip2,
+ disabled => JSON::false,
+ name => $fqdn,
+ type => $type,
+ priority => 0 };
+
+ my $rrset = { name => $fqdn,
+ type => $type,
+ ttl => '3600',
+ records => [ $record, $record2 ] };
+ return $rrset;
+ }
+ );
+
+ $test = "del_a_multiple_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->del_a_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## add_a_multiple_record
+
+ $sdn_dns_plugin->mock(
+
+ get_zone_content => sub {
+ return undef;
+ },
+ get_zone_rrset => sub {
+
+ my $record2 = { content => $ip2,
+ disabled => JSON::false,
+ name => $fqdn,
+ type => $type,
+ priority => 0 };
+
+ my $rrset = { name => $fqdn,
+ type => $type,
+ ttl => '3600',
+ records => [ $record2 ] };
+ return $rrset;
+ }
+ );
+
+ $test = "add_a_multiple_record";
+ $expected = Dumper read_sdn_config("$path/expected.$test.$ipversion");
+ $name = "$dnsid $test";
+
+ $plugin->add_a_record($plugin_config, $zone, $hostname, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+ }
+
+ ## verify_zone
+ my $test = "verify_zone";
+ my $expected = Dumper read_sdn_config("$path/expected.$test");
+ my $name = "$dnsid $test";
+
+ $plugin->verify_zone($plugin_config, $zone, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+}
+
+done_testing();
+
+
diff --git a/src/test/run_test_ipams.pl b/src/test/run_test_ipams.pl
new file mode 100755
index 0000000..27bd441
--- /dev/null
+++ b/src/test/run_test_ipams.pl
@@ -0,0 +1,197 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::INotify;
+use JSON;
+
+use Data::Dumper qw(Dumper);
+$Data::Dumper::Sortkeys = 1;
+
+sub read_sdn_config {
+ my ($file) = @_;
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+
+ return $sdn_config;
+}
+
+
+#my @plugins = <./ipams/*>;
+my @plugins = read_dir( './ipams/', prefix => 1 ) ;
+
+foreach my $path (@plugins) {
+
+ my (undef, $ipamid) = split(/\//, $path);
+ my $sdn_config = read_sdn_config ("$path/sdn_config");
+
+
+ my $pve_sdn_subnets;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ );
+
+ my $pve_sdn_ipam;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Ipams');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ my $ipam_config = read_sdn_config ("$path/ipam_config");
+ return $ipam_config;
+ },
+ );
+
+ my $sdn_module = Test::MockModule->new("PVE::Network::SDN");
+ $sdn_module->mock(
+ config => sub {
+ return $sdn_config;
+ },
+ api_request => sub {
+ my ($method, $url, $headers, $data) = @_;
+
+ my $js = JSON->new;
+ $js->canonical(1);
+
+ my $encoded_data = $js->encode($data) if $data;
+ my $req = HTTP::Request->new($method,$url, $headers, $encoded_data);
+ die Dumper($req);
+ }
+ );
+
+
+
+ #test params;
+ my $subnetid = "myzone-10.0.0.0-24";
+ my $ip = "10.0.0.1";
+ my $hostname = "myhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $description = "mydescription";
+ my $is_gateway = 1;
+
+
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
+
+ my $ipam_cfg = PVE::Network::SDN::Ipams::config();
+ my $plugin_config = $ipam_cfg->{ids}->{$ipamid};
+ my $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($plugin_config->{type});
+ my $sdn_ipam_plugin = Test::MockModule->new($plugin);
+ $sdn_ipam_plugin->mock(
+ get_prefix_id => sub {
+ return 1;
+ },
+ get_ip_id => sub {
+ return 1;
+ },
+ is_ip_gateway => sub {
+ return 1;
+ }
+ );
+
+ ## add_ip
+ my $test = "add_ip";
+ my $expected = Dumper read_sdn_config("$path/expected.$test");
+ my $name = "$ipamid $test";
+
+ $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## add_next_freeip
+ $test = "add_next_freeip";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+
+ $plugin->add_next_freeip($plugin_config, $subnetid, $subnet, $hostname, $mac, $description, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+
+ ## del_ip
+ $test = "del_ip";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+
+ $plugin->del_ip($plugin_config, $subnetid, $subnet, $ip, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## update_ip
+ $test = "update_ip";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+ $plugin->update_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ ## add_ip_notgateway
+ $is_gateway = undef;
+ $test = "add_ip_notgateway";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+
+ $plugin->add_ip($plugin_config, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+ $sdn_ipam_plugin->mock(
+ get_prefix_id => sub {
+ return undef;
+ },
+ );
+
+ ## add_subnet
+ $test = "add_subnet";
+ $expected = Dumper read_sdn_config("$path/expected.$test");
+ $name = "$ipamid $test";
+
+ $plugin->add_subnet($plugin_config, $subnetid, $subnet, 1);
+
+ if ($@) {
+ is ($@, $expected, $name);
+ } else {
+ fail($name);
+ }
+
+}
+
+done_testing();
+
+
diff --git a/src/test/run_test_subnets.pl b/src/test/run_test_subnets.pl
new file mode 100755
index 0000000..f6564e1
--- /dev/null
+++ b/src/test/run_test_subnets.pl
@@ -0,0 +1,305 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::INotify;
+use JSON;
+
+use Data::Dumper qw(Dumper);
+$Data::Dumper::Sortkeys = 1;
+
+sub read_sdn_config {
+ my ($file) = @_;
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+
+ return $sdn_config;
+}
+
+
+my @plugins = read_dir( './subnets/', prefix => 1 ) ;
+
+foreach my $path (@plugins) {
+
+ my (undef, $testid) = split(/\//, $path);
+
+ print "test: $testid\n";
+ my $sdn_config = read_sdn_config ("$path/sdn_config");
+
+
+ my $pve_sdn_subnets;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ verify_dns_zone => sub {
+ return;
+ },
+ add_dns_record => sub {
+ return;
+ }
+ );
+
+
+ my $js = JSON->new;
+ $js->canonical(1);
+
+
+ #test params;
+ my $subnets = $sdn_config->{subnets}->{ids};
+ my $subnetid = (keys %{$subnets})[0];
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
+
+ my $subnet_cidr = $subnet->{cidr};
+ my $iplist = NetAddr::IP->new($subnet_cidr);
+ $iplist++ if Net::IP::ip_is_ipv4($iplist->canon()); #skip network address for ipv4
+ my $ip = $iplist->canon();
+ $iplist++;
+ my $ipnextfree = $iplist->canon();
+ $iplist++;
+ my $ip2 = $iplist->canon();
+
+ my $ip3 = undef;
+ my $hostname = "myhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $description = "mydescription";
+ my $is_gateway = 1;
+ my $ipamdb = {};
+
+ my $zone = $sdn_config->{zones}->{ids}->{"myzone"};
+ my $ipam = $zone->{ipam};
+
+ my $plugin;
+ my $sdn_ipam_plugin;
+ if($ipam) {
+ $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam);
+ $sdn_ipam_plugin = Test::MockModule->new($plugin);
+ $sdn_ipam_plugin->mock(
+ read_db => sub {
+ return $ipamdb;
+ },
+ write_db => sub {
+ my ($cfg) = @_;
+ $ipamdb = $cfg;
+ }
+ );
+ }
+
+ my $pve_sdn_ipams;
+ $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams');
+ $pve_sdn_ipams->mock(
+ config => sub {
+ my $ipam_config = read_sdn_config ("$path/ipam_config");
+ return $ipam_config;
+ },
+ );
+
+ ## add_subnet
+ my $test = "add_subnet $subnetid";
+ my $name = "$testid $test";
+ my $result = undef;
+ my $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::add_subnet($zone, $subnetid, $subnet);
+
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_ip
+ $test = "add_ip $ip";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1}}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description, $is_gateway);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+ if($ipam) {
+ ## add_already_exist_ip
+ $test = "add_already_exist_ip $ip";
+ $name = "$testid $test";
+
+ eval {
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } else {
+ fail("$name : $@");
+ }
+ }
+
+ ## add_second_ip
+ $test = "add_second_ip $ip2";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ip2.'":{}}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip2, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_next_free
+ $test = "find_next_freeip ($ipnextfree)";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ip.'":{"gateway":1},"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
+
+ eval {
+ $ip3 = PVE::Network::SDN::Subnets::next_free_ip($zone, $subnetid, $subnet, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ }
+
+ ## del_ip
+ $test = "del_ip $ip";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip, $hostname);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+ if($ipam){
+ ## del_subnet_not_empty
+ $test = "del_subnet_not_empty $subnetid";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet);
+ };
+
+ if ($@) {
+ is ($result, $expected, $name);
+ } else {
+ fail("$name : $@");
+ }
+ }
+
+
+ ## add_ip_rollback_failing_dns
+ $test = "add_ip_rollback_failing_dns";
+
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ verify_dns_zone => sub {
+ return;
+ },
+ add_dns_record => sub {
+ die "error add dns record";
+ return;
+ }
+ );
+
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{"'.$subnet_cidr.'":{"ips":{"'.$ipnextfree.'":{},"'.$ip2.'":{}}}}}}}';
+
+ eval {
+ PVE::Network::SDN::Subnets::add_ip($zone, $subnetid, $subnet, $ip, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ if($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+ } else {
+ fail("$name : $@");
+ }
+
+
+ ## del_empty_subnet
+ $test = "del_empty_subnet";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '{"zones":{"myzone":{"subnets":{}}}}';
+
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip2, $hostname);
+ PVE::Network::SDN::Subnets::del_ip($zone, $subnetid, $subnet, $ip3, $hostname);
+
+ eval {
+ PVE::Network::SDN::Subnets::del_subnet($zone, $subnetid, $subnet);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } elsif($ipam) {
+ $result = $js->encode($plugin->read_db());
+ is ($result, $expected, $name);
+ } else {
+ is (undef, undef, $name);
+ }
+
+}
+
+done_testing();
+
+
diff --git a/src/test/run_test_vnets.pl b/src/test/run_test_vnets.pl
new file mode 100755
index 0000000..5aeb676
--- /dev/null
+++ b/src/test/run_test_vnets.pl
@@ -0,0 +1,355 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+use NetAddr::IP qw(:lower);
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::INotify;
+use JSON;
+
+use Data::Dumper qw(Dumper);
+$Data::Dumper::Sortkeys = 1;
+
+sub read_sdn_config {
+ my ($file) = @_;
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+ return $sdn_config;
+}
+
+
+my @plugins = read_dir( './vnets/', prefix => 1 ) ;
+
+foreach my $path (@plugins) {
+
+ my (undef, $testid) = split(/\//, $path);
+
+ print "test: $testid\n";
+ my $sdn_config = read_sdn_config ("$path/sdn_config");
+
+ my $pve_sdn_zones;
+ $pve_sdn_zones = Test::MockModule->new('PVE::Network::SDN::Zones');
+ $pve_sdn_zones->mock(
+ config => sub {
+ return $sdn_config->{zones};
+ },
+ );
+
+ my $pve_sdn_vnets;
+ $pve_sdn_vnets = Test::MockModule->new('PVE::Network::SDN::Vnets');
+ $pve_sdn_vnets->mock(
+ config => sub {
+ return $sdn_config->{vnets};
+ },
+ );
+
+ my $pve_sdn_subnets;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ verify_dns_zone => sub {
+ return;
+ },
+ add_dns_record => sub {
+ return;
+ }
+ );
+
+ my $js = JSON->new;
+ $js->canonical(1);
+
+ #test params;
+ #test params;
+ my $subnets = $sdn_config->{subnets}->{ids};
+
+ my $subnetid = (sort keys %{$subnets})[0];
+ my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid, 1);
+ my $subnet_cidr = $subnet->{cidr};
+ my $iplist = NetAddr::IP->new($subnet_cidr);
+ my $mask = $iplist->masklen();
+ my $ipversion = undef;
+
+ if (Net::IP::ip_is_ipv4($iplist->canon())){
+ $iplist++; #skip network address for ipv4
+ $ipversion = 4;
+ } else {
+ $ipversion = 6;
+ }
+
+ my $cidr1 = $iplist->canon()."/$mask";
+ $iplist++;
+ my $cidr2 = $iplist->canon()."/$mask";
+ my $cidr_outofrange = '8.8.8.8/8';
+
+ my $subnetid2 = (sort keys %{$subnets})[1];
+ my $subnet2 = PVE::Network::SDN::Subnets::sdn_subnets_config($sdn_config->{subnets}, $subnetid2, 1);
+ my $subnet2_cidr = $subnet2->{cidr};
+ my $iplist2 = NetAddr::IP->new($subnet2_cidr);
+ $iplist2++;
+ my $cidr3 = $iplist2->canon()."/$mask";
+ $iplist2++;
+ my $cidr4 = $iplist2->canon()."/$mask";
+
+ my $hostname = "myhostname";
+ my $mac = "da:65:8f:18:9b:6f";
+ my $description = "mydescription";
+ my $ipamdb = read_sdn_config ("$path/ipam.db");
+
+ my $zone = $sdn_config->{zones}->{ids}->{"myzone"};
+ my $ipam = $zone->{ipam};
+
+ my $plugin;
+ my $sdn_ipam_plugin;
+ if($ipam) {
+ $plugin = PVE::Network::SDN::Ipams::Plugin->lookup($ipam);
+ $sdn_ipam_plugin = Test::MockModule->new($plugin);
+ $sdn_ipam_plugin->mock(
+ read_db => sub {
+ return $ipamdb;
+ },
+ write_db => sub {
+ my ($cfg) = @_;
+ $ipamdb = $cfg;
+ }
+ );
+ }
+
+ my $pve_sdn_ipams;
+ $pve_sdn_ipams = Test::MockModule->new('PVE::Network::SDN::Ipams');
+ $pve_sdn_ipams->mock(
+ config => sub {
+ my $ipam_config = read_sdn_config ("$path/ipam_config");
+ return $ipam_config;
+ },
+ );
+
+ my $vnetid = "myvnet";
+
+ ## add_ip
+ my $test = "add_cidr $cidr1";
+ my $name = "$testid $test";
+ my $result = undef;
+ my $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_ip
+ $test = "add_already_exist_cidr $cidr1";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr1, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } elsif($ipam) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_ip
+ $test = "add_cidr $cidr2";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr2, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ ## add_ip
+ $test = "add_ip_out_of_range_subnets $cidr_outofrange";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr_outofrange, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } else {
+ fail("$name : $@");
+ }
+
+ ## add_ip
+ $test = "add_cidr $cidr4";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = '';
+
+ eval {
+ PVE::Network::SDN::Vnets::add_cidr($vnetid, $cidr4, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+
+ $test = "find_next_free_cidr_in_second_subnet ($cidr3)";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = $ipam ? $cidr3 : undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is ($result, $expected, $name);
+ }
+
+
+ $test = "del_cidr $cidr1";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ $test = "del_cidr $cidr3";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr3, $hostname);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ $test = "del_cidr not exist $cidr1";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr1, $hostname);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } elsif($ipam) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ $test = "del_cidr outofrange $cidr_outofrange";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::del_cidr($vnetid, $cidr_outofrange, $hostname);
+ };
+
+ if ($@) {
+ is (undef, undef, $name);
+ } else {
+ fail("$name : $@");
+ }
+
+ $test = "find_next_free_cidr_in_first_subnet ($cidr1)";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = $ipam ? $cidr1 : undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::get_next_free_cidr($vnetid, $hostname, $mac, $description, $ipversion);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is ($result, $expected, $name);
+ }
+
+ $test = "update_cidr $cidr1";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+ $test = "update_cidr deleted $cidr3";
+ $name = "$testid $test";
+ $result = undef;
+ $expected = undef;
+
+ eval {
+ $result = PVE::Network::SDN::Vnets::update_cidr($vnetid, $cidr1, $hostname, $hostname, $mac, $description);
+ };
+
+ if ($@) {
+ fail("$name : $@");
+ } else {
+ is (undef, undef, $name);
+ }
+
+}
+
+done_testing();
+
+
diff --git a/src/test/run_test_zones.pl b/src/test/run_test_zones.pl
new file mode 100755
index 0000000..12e017a
--- /dev/null
+++ b/src/test/run_test_zones.pl
@@ -0,0 +1,123 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+use lib qw(..);
+use File::Slurp;
+
+use Test::More;
+use Test::MockModule;
+
+use PVE::Network::SDN;
+use PVE::Network::SDN::Zones;
+use PVE::Network::SDN::Controllers;
+use PVE::INotify;
+
+sub read_sdn_config {
+ my ($file) = @_;
+
+ # Read structure back in again
+ open my $in, '<', $file or die $!;
+ my $sdn_config;
+ {
+ local $/; # slurp mode
+ $sdn_config = eval <$in>;
+ }
+ close $in;
+
+ return $sdn_config;
+}
+
+
+my @tests = grep { -d } glob './zones/*/*';
+
+foreach my $test (@tests) {
+
+ my $sdn_config = read_sdn_config ("./$test/sdn_config");
+
+ open my $fh1, '<', "./$test/interfaces" or die "can't read interfaces file";
+ my $interfaces_config = PVE::INotify::__read_etc_network_interfaces($fh1, undef, undef);
+ close $fh1;
+
+ my $pve_common_inotify;
+ $pve_common_inotify = Test::MockModule->new('PVE::INotify');
+ $pve_common_inotify->mock(
+ nodename => sub {
+ return 'localhost';
+ },
+ read_file => sub {
+ return $interfaces_config;
+ },
+ );
+
+ my $pve_sdn_subnets;
+ $pve_sdn_subnets = Test::MockModule->new('PVE::Network::SDN::Subnets');
+ $pve_sdn_subnets->mock(
+ config => sub {
+ return $sdn_config->{subnets};
+ },
+ );
+
+ my $pve_sdn_zones_plugin;
+ $pve_sdn_zones_plugin = Test::MockModule->new('PVE::Network::SDN::Zones::Plugin');
+ $pve_sdn_zones_plugin->mock(
+ get_local_route_ip => sub {
+ my $outiface = "vmbr0";
+ my $outip = $interfaces_config->{ifaces}->{$outiface}->{address};
+ return ($outip, $outiface);
+ },
+ is_vlanaware => sub {
+ return $interfaces_config->{ifaces}->{vmbr0}->{'bridge_vlan_aware'};
+ },
+ is_ovs => sub {
+ return 1 if $interfaces_config->{ifaces}->{vmbr0}->{'type'} eq 'OVSBridge';
+ },
+ get_bridge_ifaces => sub {
+ return ('eth0');
+ },
+ find_bridge => sub {
+ return;
+ }
+ );
+
+ my $sdn_module = Test::MockModule->new("PVE::Network::SDN");
+ $sdn_module->mock(
+ running_config => sub {
+ return $sdn_config;
+ },
+ );
+
+ my $name = $test;
+ my $expected = read_file("./$test/expected_sdn_interfaces");
+
+ my $result = "";
+ eval {
+ $result = PVE::Network::SDN::Zones::generate_etc_network_config();
+ };
+
+ if (my $err = $@) {
+ fail($name);
+ } else {
+ is ($result, $expected, $name);
+ }
+
+ if ($sdn_config->{controllers}) {
+ my $expected = read_file("./$test/expected_controller_config");
+ my $controller_rawconfig = "";
+
+ eval {
+ my $config = PVE::Network::SDN::Controllers::generate_controller_config();
+ $controller_rawconfig = PVE::Network::SDN::Controllers::generate_controller_rawconfig($config);
+ };
+ if (my $err = $@) {
+ fail($name);
+ } else {
+ is ($controller_rawconfig, $expected, $name);
+ }
+ }
+}
+
+done_testing();
+
+
diff --git a/src/test/subnets/ipv4/ipam_config b/src/test/subnets/ipv4/ipam_config
new file mode 100644
index 0000000..a33be30
--- /dev/null
+++ b/src/test/subnets/ipv4/ipam_config
@@ -0,0 +1,18 @@
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
diff --git a/src/test/subnets/ipv4/sdn_config b/src/test/subnets/ipv4/sdn_config
new file mode 100644
index 0000000..72697d4
--- /dev/null
+++ b/src/test/subnets/ipv4/sdn_config
@@ -0,0 +1,20 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
diff --git a/src/test/subnets/ipv6/ipam_config b/src/test/subnets/ipv6/ipam_config
new file mode 100644
index 0000000..a33be30
--- /dev/null
+++ b/src/test/subnets/ipv6/ipam_config
@@ -0,0 +1,18 @@
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
diff --git a/src/test/subnets/ipv6/sdn_config b/src/test/subnets/ipv6/sdn_config
new file mode 100644
index 0000000..618f234
--- /dev/null
+++ b/src/test/subnets/ipv6/sdn_config
@@ -0,0 +1,20 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-2a0a:1580:2000::-56' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
diff --git a/src/test/subnets/noipam/ipam_config b/src/test/subnets/noipam/ipam_config
new file mode 100644
index 0000000..a33be30
--- /dev/null
+++ b/src/test/subnets/noipam/ipam_config
@@ -0,0 +1,18 @@
+{
+ 'ids' => {
+ 'phpipam' => {
+ 'url' => 'https://localhost/api/apiadmin',
+ 'type' => 'phpipam',
+ 'section' => 1,
+ 'token' => 'JPHkPSLB4O_XL-GQz4qtEFmNpx-99Htw'
+ },
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ 'netbox' => {
+ 'token' => '0123456789abcdef0123456789abcdef01234567',
+ 'type' => 'netbox',
+ 'url' => 'http://localhost:8000/api'
+ }
+ },
+}
diff --git a/src/test/subnets/noipam/sdn_config b/src/test/subnets/noipam/sdn_config
new file mode 100644
index 0000000..55107d6
--- /dev/null
+++ b/src/test/subnets/noipam/sdn_config
@@ -0,0 +1,20 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { type =>"simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
diff --git a/src/test/vnets/ipv4/ipam.db b/src/test/vnets/ipv4/ipam.db
new file mode 100644
index 0000000..ef3fa93
--- /dev/null
+++ b/src/test/vnets/ipv4/ipam.db
@@ -0,0 +1,17 @@
+{
+ "zones" => {
+ "myzone" => {
+ "subnets" => {
+ "192.168.0.0/30" => {
+ "ips" =>{
+ }
+ },
+ "192.168.1.0/30" => {
+ "ips" =>{
+ }
+ },
+ }
+ }
+ }
+}
+
diff --git a/src/test/vnets/ipv4/ipam_config b/src/test/vnets/ipv4/ipam_config
new file mode 100644
index 0000000..f5f36ad
--- /dev/null
+++ b/src/test/vnets/ipv4/ipam_config
@@ -0,0 +1,7 @@
+{
+ 'ids' => {
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ },
+}
diff --git a/src/test/vnets/ipv4/sdn_config b/src/test/vnets/ipv4/sdn_config
new file mode 100644
index 0000000..ee11fd1
--- /dev/null
+++ b/src/test/vnets/ipv4/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-192.168.0.0-30' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-192.168.1.0-30' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ }
+
+ }
+}
diff --git a/src/test/vnets/ipv4noipam/ipam.db b/src/test/vnets/ipv4noipam/ipam.db
new file mode 100644
index 0000000..ef3fa93
--- /dev/null
+++ b/src/test/vnets/ipv4noipam/ipam.db
@@ -0,0 +1,17 @@
+{
+ "zones" => {
+ "myzone" => {
+ "subnets" => {
+ "192.168.0.0/30" => {
+ "ips" =>{
+ }
+ },
+ "192.168.1.0/30" => {
+ "ips" =>{
+ }
+ },
+ }
+ }
+ }
+}
+
diff --git a/src/test/vnets/ipv4noipam/ipam_config b/src/test/vnets/ipv4noipam/ipam_config
new file mode 100644
index 0000000..f5f36ad
--- /dev/null
+++ b/src/test/vnets/ipv4noipam/ipam_config
@@ -0,0 +1,7 @@
+{
+ 'ids' => {
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ },
+}
diff --git a/src/test/vnets/ipv4noipam/sdn_config b/src/test/vnets/ipv4noipam/sdn_config
new file mode 100644
index 0000000..470c1ae
--- /dev/null
+++ b/src/test/vnets/ipv4noipam/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { type =>"simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-192.168.0.0-30' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-192.168.1.0-30' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ }
+
+ }
+}
diff --git a/src/test/vnets/ipv6/ipam.db b/src/test/vnets/ipv6/ipam.db
new file mode 100644
index 0000000..d3f2ce9
--- /dev/null
+++ b/src/test/vnets/ipv6/ipam.db
@@ -0,0 +1,16 @@
+{
+ "zones" => {
+ "myzone" => {
+ "subnets" => {
+ "2001:db8:85a3::8a2e:370:7334/127" => {
+ "ips" =>{
+ }
+ },
+ "2001:db8:85a3::8a2e:371:7334/127" => {
+ "ips" =>{
+ }
+ },
+ }
+ }
+ }
+}
diff --git a/src/test/vnets/ipv6/ipam_config b/src/test/vnets/ipv6/ipam_config
new file mode 100644
index 0000000..f5f36ad
--- /dev/null
+++ b/src/test/vnets/ipv6/ipam_config
@@ -0,0 +1,7 @@
+{
+ 'ids' => {
+ 'pve' => {
+ 'type' => 'pve'
+ },
+ },
+}
diff --git a/src/test/vnets/ipv6/sdn_config b/src/test/vnets/ipv6/sdn_config
new file mode 100644
index 0000000..231ca8a
--- /dev/null
+++ b/src/test/vnets/ipv6/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type =>"simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-2001:db8:85a3::8a2e:370:7334-127' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-2001:db8:85a3::8a2e:371:7334-127' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ }
+
+ }
+}
diff --git a/src/test/zones/evpn/advertise_subnets/expected_controller_config b/src/test/zones/evpn/advertise_subnets/expected_controller_config
new file mode 100644
index 0000000..82b06b4
--- /dev/null
+++ b/src/test/zones/evpn/advertise_subnets/expected_controller_config
@@ -0,0 +1,54 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family ipv4 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ advertise ipv4 unicast
+ advertise ipv6 unicast
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/advertise_subnets/expected_sdn_interfaces b/src/test/zones/evpn/advertise_subnets/expected_sdn_interfaces
new file mode 100644
index 0000000..9d1c64c
--- /dev/null
+++ b/src/test/zones/evpn/advertise_subnets/expected_sdn_interfaces
@@ -0,0 +1,42 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/advertise_subnets/interfaces b/src/test/zones/evpn/advertise_subnets/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/advertise_subnets/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/advertise_subnets/sdn_config b/src/test/zones/evpn/advertise_subnets/sdn_config
new file mode 100644
index 0000000..76f16a1
--- /dev/null
+++ b/src/test/zones/evpn/advertise_subnets/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'advertise-subnets' => 1 } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config b/src/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config
new file mode 100644
index 0000000..bd7830a
--- /dev/null
+++ b/src/test/zones/evpn/disable_arp_nd_suppression/expected_controller_config
@@ -0,0 +1,41 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces b/src/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces
new file mode 100644
index 0000000..bbde906
--- /dev/null
+++ b/src/test/zones/evpn/disable_arp_nd_suppression/expected_sdn_interfaces
@@ -0,0 +1,40 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ mtu 1450
diff --git a/src/test/zones/evpn/disable_arp_nd_suppression/interfaces b/src/test/zones/evpn/disable_arp_nd_suppression/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/disable_arp_nd_suppression/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/disable_arp_nd_suppression/sdn_config b/src/test/zones/evpn/disable_arp_nd_suppression/sdn_config
new file mode 100644
index 0000000..199596b
--- /dev/null
+++ b/src/test/zones/evpn/disable_arp_nd_suppression/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'disable-arp-nd-suppression' => 1 } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/ebgp/expected_controller_config b/src/test/zones/evpn/ebgp/expected_controller_config
new file mode 100644
index 0000000..ccc0b28
--- /dev/null
+++ b/src/test/zones/evpn/ebgp/expected_controller_config
@@ -0,0 +1,58 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65001
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as external
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ neighbor BGP peer-group
+ neighbor BGP remote-as external
+ neighbor BGP bfd
+ neighbor BGP ebgp-multihop 3
+ neighbor 192.168.0.252 peer-group BGP
+ neighbor 192.168.0.253 peer-group BGP
+ !
+ address-family ipv4 unicast
+ neighbor BGP activate
+ neighbor BGP soft-reconfiguration inbound
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ autort as 65000
+ exit-address-family
+exit
+!
+router bgp 65001 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family l2vpn evpn
+ route-target import 65000:1000
+ route-target export 65000:1000
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/ebgp/expected_sdn_interfaces b/src/test/zones/evpn/ebgp/expected_sdn_interfaces
new file mode 100644
index 0000000..4cf13e0
--- /dev/null
+++ b/src/test/zones/evpn/ebgp/expected_sdn_interfaces
@@ -0,0 +1,41 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/ebgp/interfaces b/src/test/zones/evpn/ebgp/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/ebgp/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/ebgp/sdn_config b/src/test/zones/evpn/ebgp/sdn_config
new file mode 100644
index 0000000..6e9d116
--- /dev/null
+++ b/src/test/zones/evpn/ebgp/sdn_config
@@ -0,0 +1,50 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => "100",
+ type => "vnet",
+ zone => "myzone",
+ },
+ },
+ },
+
+ zones => {
+ ids => {
+ myzone => {
+ ipam => "pve",
+ type => "evpn",
+ controller => "evpnctl",
+ 'vrf-vxlan' => 1000,
+ },
+ },
+ },
+ controllers => {
+ ids => {
+ evpnctl => {
+ type => "evpn",
+ 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
+ asn => "65000",
+ },
+ localhost => {
+ type => "bgp",
+ 'peers' => '192.168.0.252,192.168.0.253',
+ ebgp => "1",
+ 'ebgp-multihop' => '3',
+ asn => "65001",
+ node => "localhost",
+ },
+ },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ },
+ },
+}
diff --git a/src/test/zones/evpn/ebgp_loopback/expected_controller_config b/src/test/zones/evpn/ebgp_loopback/expected_controller_config
new file mode 100644
index 0000000..548d532
--- /dev/null
+++ b/src/test/zones/evpn/ebgp_loopback/expected_controller_config
@@ -0,0 +1,69 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+ip protocol bgp route-map correct_src
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65001
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as external
+ neighbor VTEP bfd
+ neighbor VTEP ebgp-multihop 10
+ neighbor VTEP update-source dummy1
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ bgp disable-ebgp-connected-route-check
+ neighbor BGP peer-group
+ neighbor BGP remote-as external
+ neighbor BGP bfd
+ neighbor 172.16.0.254 peer-group BGP
+ neighbor 172.17.0.254 peer-group BGP
+ !
+ address-family ipv4 unicast
+ network 192.168.0.1/32
+ neighbor BGP activate
+ neighbor BGP soft-reconfiguration inbound
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ autort as 65000
+ exit-address-family
+exit
+!
+router bgp 65001 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family l2vpn evpn
+ route-target import 65000:1000
+ route-target export 65000:1000
+ exit-address-family
+exit
+!
+ip prefix-list loopbacks_ips seq 10 permit 0.0.0.0/0 le 32
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+route-map correct_src permit 1
+ match ip address prefix-list loopbacks_ips
+ set src 192.168.0.1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces b/src/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces
new file mode 100644
index 0000000..4cf13e0
--- /dev/null
+++ b/src/test/zones/evpn/ebgp_loopback/expected_sdn_interfaces
@@ -0,0 +1,41 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/ebgp_loopback/interfaces b/src/test/zones/evpn/ebgp_loopback/interfaces
new file mode 100644
index 0000000..f6bc352
--- /dev/null
+++ b/src/test/zones/evpn/ebgp_loopback/interfaces
@@ -0,0 +1,13 @@
+auto eth0
+iface eth0 inet static
+ address 172.16.0.1/24
+
+auto eth1
+iface eth1 inet static
+ address 172.17.0.1/24
+
+auto dummy1
+iface dummy1 inet static
+ address 192.168.0.1/32
+ link-type dummy
+
diff --git a/src/test/zones/evpn/ebgp_loopback/sdn_config b/src/test/zones/evpn/ebgp_loopback/sdn_config
new file mode 100644
index 0000000..c8bc2e0
--- /dev/null
+++ b/src/test/zones/evpn/ebgp_loopback/sdn_config
@@ -0,0 +1,29 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000 } },
+ },
+ controllers => {
+ ids => {
+ evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" },
+ localhost => { type => "bgp", 'peers' => '172.16.0.254,172.17.0.254', ebgp => "1", asn => "65001", loopback => 'dummy1', node => "localhost" },
+ },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/exitnode/expected_controller_config b/src/test/zones/evpn/exitnode/expected_controller_config
new file mode 100644
index 0000000..48830a3
--- /dev/null
+++ b/src/test/zones/evpn/exitnode/expected_controller_config
@@ -0,0 +1,66 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family ipv4 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family ipv4 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ default-originate ipv4
+ default-originate ipv6
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN deny 1
+ match evpn route-type prefix
+exit
+!
+route-map MAP_VTEP_IN permit 2
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/exitnode/expected_sdn_interfaces b/src/test/zones/evpn/exitnode/expected_sdn_interfaces
new file mode 100644
index 0000000..5ab3084
--- /dev/null
+++ b/src/test/zones/evpn/exitnode/expected_sdn_interfaces
@@ -0,0 +1,41 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/exitnode/interfaces b/src/test/zones/evpn/exitnode/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/exitnode/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/exitnode/sdn_config b/src/test/zones/evpn/exitnode/sdn_config
new file mode 100644
index 0000000..fd81817
--- /dev/null
+++ b/src/test/zones/evpn/exitnode/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/exitnode_local_routing/expected_controller_config b/src/test/zones/evpn/exitnode_local_routing/expected_controller_config
new file mode 100644
index 0000000..f671b63
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_local_routing/expected_controller_config
@@ -0,0 +1,51 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+ip route 10.0.0.0/24 10.255.255.2 xvrf_myzone
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family l2vpn evpn
+ default-originate ipv4
+ default-originate ipv6
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN deny 1
+ match evpn route-type prefix
+exit
+!
+route-map MAP_VTEP_IN permit 2
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces b/src/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces
new file mode 100644
index 0000000..301f5b3
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_local_routing/expected_sdn_interfaces
@@ -0,0 +1,56 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto xvrf_myzone
+iface xvrf_myzone
+ link-type veth
+ address 10.255.255.1/30
+ veth-peer-name xvrfp_myzone
+ mtu 1500
+
+auto xvrfp_myzone
+iface xvrfp_myzone
+ link-type veth
+ address 10.255.255.2/30
+ veth-peer-name xvrf_myzone
+ vrf vrf_myzone
+ mtu 1500
diff --git a/src/test/zones/evpn/exitnode_local_routing/interfaces b/src/test/zones/evpn/exitnode_local_routing/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_local_routing/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/exitnode_local_routing/sdn_config b/src/test/zones/evpn/exitnode_local_routing/sdn_config
new file mode 100644
index 0000000..f5f7ca1
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_local_routing/sdn_config
@@ -0,0 +1,27 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 }, 'exitnodes-local-routing' => 1 },
+ },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/exitnode_primary/expected_controller_config b/src/test/zones/evpn/exitnode_primary/expected_controller_config
new file mode 100644
index 0000000..e45b22c
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_primary/expected_controller_config
@@ -0,0 +1,68 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family ipv4 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family ipv4 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ default-originate ipv4
+ default-originate ipv6
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+ match evpn vni 1000
+ match evpn route-type prefix
+ set metric 200
+exit
+!
+route-map MAP_VTEP_OUT permit 2
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/exitnode_primary/expected_sdn_interfaces b/src/test/zones/evpn/exitnode_primary/expected_sdn_interfaces
new file mode 100644
index 0000000..5ab3084
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_primary/expected_sdn_interfaces
@@ -0,0 +1,41 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/exitnode_primary/interfaces b/src/test/zones/evpn/exitnode_primary/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_primary/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/exitnode_primary/sdn_config b/src/test/zones/evpn/exitnode_primary/sdn_config
new file mode 100644
index 0000000..bfeafc5
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_primary/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'exitnodes-primary' => "othernode", exitnodes => { 'localhost' => 1 } } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/exitnode_snat/expected_controller_config b/src/test/zones/evpn/exitnode_snat/expected_controller_config
new file mode 100644
index 0000000..48830a3
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_snat/expected_controller_config
@@ -0,0 +1,66 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family ipv4 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ import vrf vrf_myzone
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family ipv4 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family ipv6 unicast
+ redistribute connected
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ default-originate ipv4
+ default-originate ipv6
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN deny 1
+ match evpn route-type prefix
+exit
+!
+route-map MAP_VTEP_IN permit 2
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces b/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces
new file mode 100644
index 0000000..47df77a
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_snat/expected_sdn_interfaces
@@ -0,0 +1,68 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
+ post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto myvnet2
+iface myvnet2
+ address 2a08:2142:302:3::1/64
+ post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
+ post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
+ bridge_ports vxlan_myvnet2
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip6-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route del vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet2
+iface vxlan_myvnet2
+ vxlan-id 200
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/exitnode_snat/interfaces b/src/test/zones/evpn/exitnode_snat/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_snat/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/exitnode_snat/sdn_config b/src/test/zones/evpn/exitnode_snat/sdn_config
new file mode 100644
index 0000000..35cdf5d
--- /dev/null
+++ b/src/test/zones/evpn/exitnode_snat/sdn_config
@@ -0,0 +1,35 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ myvnet2 => { tag => "200", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, exitnodes => { 'localhost' => 1 } } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ 'snat' => 1
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet2',
+ 'gateway' => '2a08:2142:302:3::1',
+ 'snat' => 1
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/ipv4/expected_controller_config b/src/test/zones/evpn/ipv4/expected_controller_config
new file mode 100644
index 0000000..bd7830a
--- /dev/null
+++ b/src/test/zones/evpn/ipv4/expected_controller_config
@@ -0,0 +1,41 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/ipv4/expected_sdn_interfaces b/src/test/zones/evpn/ipv4/expected_sdn_interfaces
new file mode 100644
index 0000000..9d1c64c
--- /dev/null
+++ b/src/test/zones/evpn/ipv4/expected_sdn_interfaces
@@ -0,0 +1,42 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/ipv4/interfaces b/src/test/zones/evpn/ipv4/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/ipv4/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/ipv4/sdn_config b/src/test/zones/evpn/ipv4/sdn_config
new file mode 100644
index 0000000..dd73b5c
--- /dev/null
+++ b/src/test/zones/evpn/ipv4/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/ipv4ipv6/expected_controller_config b/src/test/zones/evpn/ipv4ipv6/expected_controller_config
new file mode 100644
index 0000000..bd7830a
--- /dev/null
+++ b/src/test/zones/evpn/ipv4ipv6/expected_controller_config
@@ -0,0 +1,41 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces b/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces
new file mode 100644
index 0000000..7a5d741
--- /dev/null
+++ b/src/test/zones/evpn/ipv4ipv6/expected_sdn_interfaces
@@ -0,0 +1,44 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ address 2a08:2142:302:3::1/64
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ ip6-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/ipv4ipv6/interfaces b/src/test/zones/evpn/ipv4ipv6/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/ipv4ipv6/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/ipv4ipv6/sdn_config b/src/test/zones/evpn/ipv4ipv6/sdn_config
new file mode 100644
index 0000000..4583818
--- /dev/null
+++ b/src/test/zones/evpn/ipv4ipv6/sdn_config
@@ -0,0 +1,32 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '2a08:2142:302:3::1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config b/src/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config
new file mode 100644
index 0000000..bd7830a
--- /dev/null
+++ b/src/test/zones/evpn/ipv4ipv6nogateway/expected_controller_config
@@ -0,0 +1,41 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces b/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces
new file mode 100644
index 0000000..378fa77
--- /dev/null
+++ b/src/test/zones/evpn/ipv4ipv6nogateway/expected_sdn_interfaces
@@ -0,0 +1,40 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/ipv4ipv6nogateway/interfaces b/src/test/zones/evpn/ipv4ipv6nogateway/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/ipv4ipv6nogateway/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/ipv4ipv6nogateway/sdn_config b/src/test/zones/evpn/ipv4ipv6nogateway/sdn_config
new file mode 100644
index 0000000..ab2273f
--- /dev/null
+++ b/src/test/zones/evpn/ipv4ipv6nogateway/sdn_config
@@ -0,0 +1,30 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/ipv6/expected_controller_config b/src/test/zones/evpn/ipv6/expected_controller_config
new file mode 100644
index 0000000..bd7830a
--- /dev/null
+++ b/src/test/zones/evpn/ipv6/expected_controller_config
@@ -0,0 +1,41 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/ipv6/expected_sdn_interfaces b/src/test/zones/evpn/ipv6/expected_sdn_interfaces
new file mode 100644
index 0000000..b2bdbfe
--- /dev/null
+++ b/src/test/zones/evpn/ipv6/expected_sdn_interfaces
@@ -0,0 +1,42 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 2a08:2142:302:3::1/64
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip6-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/ipv6/interfaces b/src/test/zones/evpn/ipv6/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/ipv6/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/ipv6/sdn_config b/src/test/zones/evpn/ipv6/sdn_config
new file mode 100644
index 0000000..949e886
--- /dev/null
+++ b/src/test/zones/evpn/ipv6/sdn_config
@@ -0,0 +1,27 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '2a08:2142:302:3::1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/evpn/multipath_relax/expected_controller_config b/src/test/zones/evpn/multipath_relax/expected_controller_config
new file mode 100644
index 0000000..2d1ad44
--- /dev/null
+++ b/src/test/zones/evpn/multipath_relax/expected_controller_config
@@ -0,0 +1,53 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ bgp bestpath as-path multipath-relax
+ neighbor BGP peer-group
+ neighbor BGP remote-as 65000
+ neighbor BGP bfd
+ neighbor 192.168.0.1 peer-group BGP
+ neighbor 192.168.0.2 peer-group BGP
+ neighbor 192.168.0.3 peer-group BGP
+ !
+ address-family ipv4 unicast
+ neighbor BGP activate
+ neighbor BGP soft-reconfiguration inbound
+ exit-address-family
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/multipath_relax/expected_sdn_interfaces b/src/test/zones/evpn/multipath_relax/expected_sdn_interfaces
new file mode 100644
index 0000000..4cf13e0
--- /dev/null
+++ b/src/test/zones/evpn/multipath_relax/expected_sdn_interfaces
@@ -0,0 +1,41 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/multipath_relax/interfaces b/src/test/zones/evpn/multipath_relax/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/multipath_relax/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/multipath_relax/sdn_config b/src/test/zones/evpn/multipath_relax/sdn_config
new file mode 100644
index 0000000..5a1d8a7
--- /dev/null
+++ b/src/test/zones/evpn/multipath_relax/sdn_config
@@ -0,0 +1,49 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => "100",
+ type => "vnet",
+ zone => "myzone",
+ },
+ },
+ },
+
+ zones => {
+ ids => {
+ myzone => {
+ ipam => "pve",
+ type => "evpn",
+ controller => "evpnctl",
+ 'vrf-vxlan' => 1000,
+ },
+ },
+ },
+ controllers => {
+ ids => {
+ evpnctl => {
+ type => "evpn",
+ 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
+ asn => "65000",
+ },
+ localhost => {
+ type => "bgp",
+ 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3',
+ 'bgp-multipath-as-path-relax' => "1",
+ asn => "65000",
+ node => "localhost",
+ },
+ },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ },
+ },
+ },
+}
diff --git a/src/test/zones/evpn/rt_import/expected_controller_config b/src/test/zones/evpn/rt_import/expected_controller_config
new file mode 100644
index 0000000..f4f28dd
--- /dev/null
+++ b/src/test/zones/evpn/rt_import/expected_controller_config
@@ -0,0 +1,47 @@
+frr version 8.2.2
+frr defaults datacenter
+hostname localhost
+log syslog informational
+service integrated-vtysh-config
+!
+!
+vrf vrf_myzone
+ vni 1000
+exit-vrf
+!
+router bgp 65000
+ bgp router-id 192.168.0.1
+ no bgp default ipv4-unicast
+ coalesce-time 1000
+ neighbor VTEP peer-group
+ neighbor VTEP remote-as 65000
+ neighbor VTEP bfd
+ neighbor 192.168.0.2 peer-group VTEP
+ neighbor 192.168.0.3 peer-group VTEP
+ !
+ address-family l2vpn evpn
+ neighbor VTEP route-map MAP_VTEP_IN in
+ neighbor VTEP route-map MAP_VTEP_OUT out
+ neighbor VTEP activate
+ advertise-all-vni
+ exit-address-family
+exit
+!
+router bgp 65000 vrf vrf_myzone
+ bgp router-id 192.168.0.1
+ !
+ address-family l2vpn evpn
+ route-target import 65001:1000
+ route-target import 65002:1000
+ route-target import 65003:1000
+ exit-address-family
+exit
+!
+route-map MAP_VTEP_IN permit 1
+exit
+!
+route-map MAP_VTEP_OUT permit 1
+exit
+!
+line vty
+! \ No newline at end of file
diff --git a/src/test/zones/evpn/rt_import/expected_sdn_interfaces b/src/test/zones/evpn/rt_import/expected_sdn_interfaces
new file mode 100644
index 0000000..9d1c64c
--- /dev/null
+++ b/src/test/zones/evpn/rt_import/expected_sdn_interfaces
@@ -0,0 +1,42 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ hwaddress A2:1D:CB:1A:C0:8B
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ ip-forward on
+ arp-accept on
+ vrf vrf_myzone
+
+auto vrf_myzone
+iface vrf_myzone
+ vrf-table auto
+ post-up ip route add vrf vrf_myzone unreachable default metric 4278198272
+
+auto vrfbr_myzone
+iface vrfbr_myzone
+ bridge-ports vrfvx_myzone
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+ vrf vrf_myzone
+
+auto vrfvx_myzone
+iface vrfvx_myzone
+ vxlan-id 1000
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan-local-tunnelip 192.168.0.1
+ bridge-learning off
+ bridge-arp-nd-suppress on
+ mtu 1450
diff --git a/src/test/zones/evpn/rt_import/interfaces b/src/test/zones/evpn/rt_import/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/evpn/rt_import/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/evpn/rt_import/sdn_config b/src/test/zones/evpn/rt_import/sdn_config
new file mode 100644
index 0000000..b62bb2e
--- /dev/null
+++ b/src/test/zones/evpn/rt_import/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", zone => "myzone" },
+ },
+ },
+
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "evpn", controller => "evpnctl", 'vrf-vxlan' => 1000, 'mac' => 'A2:1D:CB:1A:C0:8B', 'rt-import' => '65001:1000,65002:1000,65003:1000' } },
+ },
+ controllers => {
+ ids => { evpnctl => { type => "evpn", 'peers' => '192.168.0.1,192.168.0.2,192.168.0.3', asn => "65000" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/qinq/bridge/expected_sdn_interfaces b/src/test/zones/qinq/bridge/expected_sdn_interfaces
new file mode 100644
index 0000000..58a0e23
--- /dev/null
+++ b/src/test/zones/qinq/bridge/expected_sdn_interfaces
@@ -0,0 +1,65 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto ln_myzone2
+iface ln_myzone2
+ link-type veth
+ veth-peer-name pr_myzone2
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet2
+iface myvnet2
+ bridge_ports z_myzone.101
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet3
+iface myvnet3
+ bridge_ports z_myzone2.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto pr_myzone2
+iface pr_myzone2
+ link-type veth
+ veth-peer-name ln_myzone2
+
+auto sv_myzone
+iface sv_myzone
+ vlan-raw-device eth0
+ vlan-id 10
+
+auto sv_myzone2
+iface sv_myzone2
+ vlan-raw-device eth0
+ vlan-id 20
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto z_myzone2
+iface z_myzone2
+ bridge-stp off
+ bridge-ports sv_myzone2 ln_myzone2
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/bridge/interfaces b/src/test/zones/qinq/bridge/interfaces
new file mode 100644
index 0000000..68b6a88
--- /dev/null
+++ b/src/test/zones/qinq/bridge/interfaces
@@ -0,0 +1,5 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/qinq/bridge/sdn_config b/src/test/zones/qinq/bridge/sdn_config
new file mode 100644
index 0000000..6321603
--- /dev/null
+++ b/src/test/zones/qinq/bridge/sdn_config
@@ -0,0 +1,16 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ myvnet2 => { tag => 101, type => "vnet", zone => "myzone" },
+ myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" },
+ },
+ },
+ zones => {
+ ids => {
+ myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" },
+ myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" },
+ },
+ },
+}
diff --git a/src/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces b/src/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces
new file mode 100644
index 0000000..cfa43a2
--- /dev/null
+++ b/src/test/zones/qinq/bridge_notagvnet/expected_sdn_interfaces
@@ -0,0 +1,36 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet2
+iface myvnet2
+ bridge_ports pr_myzone
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ vlan-raw-device eth0
+ vlan-id 10
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/bridge_notagvnet/interfaces b/src/test/zones/qinq/bridge_notagvnet/interfaces
new file mode 100644
index 0000000..68b6a88
--- /dev/null
+++ b/src/test/zones/qinq/bridge_notagvnet/interfaces
@@ -0,0 +1,5 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/qinq/bridge_notagvnet/sdn_config b/src/test/zones/qinq/bridge_notagvnet/sdn_config
new file mode 100644
index 0000000..1f40369
--- /dev/null
+++ b/src/test/zones/qinq/bridge_notagvnet/sdn_config
@@ -0,0 +1,26 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => {
+ tag => 100,
+ type => "vnet",
+ zone => "myzone"
+ },
+ myvnet2 => {
+ type => "vnet",
+ zone => "myzone"
+ },
+ },
+ },
+ zones => {
+ ids => {
+ myzone => {
+ bridge => "vmbr0",
+ tag => 10,
+ ipam => "pve",
+ type => "qinq",
+ },
+ },
+ },
+}
diff --git a/src/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces
new file mode 100644
index 0000000..c325dec
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware/expected_sdn_interfaces
@@ -0,0 +1,55 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto ln_myzone2
+iface ln_myzone2
+ link-type veth
+ veth-peer-name pr_myzone2
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet2
+iface myvnet2
+ bridge_ports z_myzone.101
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet3
+iface myvnet3
+ bridge_ports z_myzone2.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto pr_myzone2
+iface pr_myzone2
+ link-type veth
+ veth-peer-name ln_myzone2
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports vmbr0.10 ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto z_myzone2
+iface z_myzone2
+ bridge-stp off
+ bridge-ports vmbr0.20 ln_myzone2
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/bridge_vlanaware/interfaces b/src/test/zones/qinq/bridge_vlanaware/interfaces
new file mode 100644
index 0000000..cfdfafe
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
diff --git a/src/test/zones/qinq/bridge_vlanaware/sdn_config b/src/test/zones/qinq/bridge_vlanaware/sdn_config
new file mode 100644
index 0000000..6321603
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware/sdn_config
@@ -0,0 +1,16 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ myvnet2 => { tag => 101, type => "vnet", zone => "myzone" },
+ myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" },
+ },
+ },
+ zones => {
+ ids => {
+ myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" },
+ myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" },
+ },
+ },
+}
diff --git a/src/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces
new file mode 100644
index 0000000..cd87a3a
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware_notagvnet/expected_sdn_interfaces
@@ -0,0 +1,27 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports pr_myzone
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports vmbr0.10 ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces b/src/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces
new file mode 100644
index 0000000..cfdfafe
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware_notagvnet/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
diff --git a/src/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config b/src/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config
new file mode 100644
index 0000000..2382f4d
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware_notagvnet/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces
new file mode 100644
index 0000000..28d215b
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces
@@ -0,0 +1,27 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports vmbr0.10 ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces
new file mode 100644
index 0000000..cfdfafe
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config
new file mode 100644
index 0000000..c013176
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware_vlanawarevnet/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces
new file mode 100644
index 0000000..0bc301b
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/expected_sdn_interfaces
@@ -0,0 +1,29 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto vmbr0
+iface vmbr0
+ bridge-vlan-protocol 802.1ad
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports vmbr0.10 ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces
new file mode 100644
index 0000000..cfdfafe
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
diff --git a/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config
new file mode 100644
index 0000000..20a8a51
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanaware_vlanprotocol/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } },
+ },
+}
diff --git a/src/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces
new file mode 100644
index 0000000..bde23d9
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanawarevnet/expected_sdn_interfaces
@@ -0,0 +1,32 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ vlan-raw-device eth0
+ vlan-id 10
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/bridge_vlanawarevnet/interfaces b/src/test/zones/qinq/bridge_vlanawarevnet/interfaces
new file mode 100644
index 0000000..68b6a88
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanawarevnet/interfaces
@@ -0,0 +1,5 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/qinq/bridge_vlanawarevnet/sdn_config b/src/test/zones/qinq/bridge_vlanawarevnet/sdn_config
new file mode 100644
index 0000000..c013176
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanawarevnet/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
diff --git a/src/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces b/src/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces
new file mode 100644
index 0000000..6b59164
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanprotocol/expected_sdn_interfaces
@@ -0,0 +1,31 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ vlan-raw-device eth0
+ vlan-id 10
+ vlan-protocol 802.1ad
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/bridge_vlanprotocol/interfaces b/src/test/zones/qinq/bridge_vlanprotocol/interfaces
new file mode 100644
index 0000000..68b6a88
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanprotocol/interfaces
@@ -0,0 +1,5 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/qinq/bridge_vlanprotocol/sdn_config b/src/test/zones/qinq/bridge_vlanprotocol/sdn_config
new file mode 100644
index 0000000..20a8a51
--- /dev/null
+++ b/src/test/zones/qinq/bridge_vlanprotocol/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } },
+ },
+}
diff --git a/src/test/zones/qinq/ovs/expected_sdn_interfaces b/src/test/zones/qinq/ovs/expected_sdn_interfaces
new file mode 100644
index 0000000..d25b2a8
--- /dev/null
+++ b/src/test/zones/qinq/ovs/expected_sdn_interfaces
@@ -0,0 +1,71 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto ln_myzone2
+iface ln_myzone2
+ link-type veth
+ veth-peer-name pr_myzone2
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet2
+iface myvnet2
+ bridge_ports z_myzone.101
+ bridge_stp off
+ bridge_fd 0
+
+auto myvnet3
+iface myvnet3
+ bridge_ports z_myzone2.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto pr_myzone2
+iface pr_myzone2
+ link-type veth
+ veth-peer-name ln_myzone2
+
+auto sv_myzone
+iface sv_myzone
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q
+
+auto sv_myzone2
+iface sv_myzone2
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=20 other_config:qinq-ethtype=802.1q
+
+auto vmbr0
+iface vmbr0
+ ovs_ports sv_myzone sv_myzone2
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto z_myzone2
+iface z_myzone2
+ bridge-stp off
+ bridge-ports sv_myzone2 ln_myzone2
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/ovs/interfaces b/src/test/zones/qinq/ovs/interfaces
new file mode 100644
index 0000000..14d2f1e
--- /dev/null
+++ b/src/test/zones/qinq/ovs/interfaces
@@ -0,0 +1,9 @@
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
diff --git a/src/test/zones/qinq/ovs/sdn_config b/src/test/zones/qinq/ovs/sdn_config
new file mode 100644
index 0000000..6321603
--- /dev/null
+++ b/src/test/zones/qinq/ovs/sdn_config
@@ -0,0 +1,16 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ myvnet2 => { tag => 101, type => "vnet", zone => "myzone" },
+ myvnet3 => { tag => 100, type => "vnet", zone => "myzone2" },
+ },
+ },
+ zones => {
+ ids => {
+ myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" },
+ myzone2 => { bridge => "vmbr0", tag => 20, ipam => "pve", type => "qinq" },
+ },
+ },
+}
diff --git a/src/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces b/src/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces
new file mode 100644
index 0000000..5f47b28
--- /dev/null
+++ b/src/test/zones/qinq/ovs_notagvnet/expected_sdn_interfaces
@@ -0,0 +1,37 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports pr_myzone
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q
+
+auto vmbr0
+iface vmbr0
+ ovs_ports sv_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/ovs_notagvnet/interfaces b/src/test/zones/qinq/ovs_notagvnet/interfaces
new file mode 100644
index 0000000..14d2f1e
--- /dev/null
+++ b/src/test/zones/qinq/ovs_notagvnet/interfaces
@@ -0,0 +1,9 @@
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
diff --git a/src/test/zones/qinq/ovs_notagvnet/sdn_config b/src/test/zones/qinq/ovs_notagvnet/sdn_config
new file mode 100644
index 0000000..2382f4d
--- /dev/null
+++ b/src/test/zones/qinq/ovs_notagvnet/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
diff --git a/src/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces b/src/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces
new file mode 100644
index 0000000..d69d38c
--- /dev/null
+++ b/src/test/zones/qinq/ovs_vlanawarevnet/expected_sdn_interfaces
@@ -0,0 +1,37 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1q
+
+auto vmbr0
+iface vmbr0
+ ovs_ports sv_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/ovs_vlanawarevnet/interfaces b/src/test/zones/qinq/ovs_vlanawarevnet/interfaces
new file mode 100644
index 0000000..14d2f1e
--- /dev/null
+++ b/src/test/zones/qinq/ovs_vlanawarevnet/interfaces
@@ -0,0 +1,9 @@
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
diff --git a/src/test/zones/qinq/ovs_vlanawarevnet/sdn_config b/src/test/zones/qinq/ovs_vlanawarevnet/sdn_config
new file mode 100644
index 0000000..c013176
--- /dev/null
+++ b/src/test/zones/qinq/ovs_vlanawarevnet/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, ipam => "pve", type => "qinq" } },
+ },
+}
diff --git a/src/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces b/src/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces
new file mode 100644
index 0000000..aeefec9
--- /dev/null
+++ b/src/test/zones/qinq/ovs_vlanprotocol/expected_sdn_interfaces
@@ -0,0 +1,35 @@
+#version:1
+
+auto ln_myzone
+iface ln_myzone
+ link-type veth
+ veth-peer-name pr_myzone
+
+auto myvnet
+iface myvnet
+ bridge_ports z_myzone.100
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myzone
+iface pr_myzone
+ link-type veth
+ veth-peer-name ln_myzone
+
+auto sv_myzone
+iface sv_myzone
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel tag=10 other_config:qinq-ethtype=802.1ad
+
+auto vmbr0
+iface vmbr0
+ ovs_ports sv_myzone
+
+auto z_myzone
+iface z_myzone
+ bridge-stp off
+ bridge-ports sv_myzone ln_myzone
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/qinq/ovs_vlanprotocol/interfaces b/src/test/zones/qinq/ovs_vlanprotocol/interfaces
new file mode 100644
index 0000000..14d2f1e
--- /dev/null
+++ b/src/test/zones/qinq/ovs_vlanprotocol/interfaces
@@ -0,0 +1,9 @@
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
diff --git a/src/test/zones/qinq/ovs_vlanprotocol/sdn_config b/src/test/zones/qinq/ovs_vlanprotocol/sdn_config
new file mode 100644
index 0000000..20a8a51
--- /dev/null
+++ b/src/test/zones/qinq/ovs_vlanprotocol/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", tag => 10, 'vlan-protocol' => '802.1ad', ipam => "pve", type => "qinq" } },
+ },
+}
diff --git a/src/test/zones/simple/basic/expected_sdn_interfaces b/src/test/zones/simple/basic/expected_sdn_interfaces
new file mode 100644
index 0000000..1e0c2c7
--- /dev/null
+++ b/src/test/zones/simple/basic/expected_sdn_interfaces
@@ -0,0 +1,7 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
diff --git a/src/test/zones/simple/basic/interfaces b/src/test/zones/simple/basic/interfaces
new file mode 100644
index 0000000..68b6a88
--- /dev/null
+++ b/src/test/zones/simple/basic/interfaces
@@ -0,0 +1,5 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/simple/basic/sdn_config b/src/test/zones/simple/basic/sdn_config
new file mode 100644
index 0000000..527dcba
--- /dev/null
+++ b/src/test/zones/simple/basic/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+}
diff --git a/src/test/zones/simple/hetzner/expected_sdn_interfaces b/src/test/zones/simple/hetzner/expected_sdn_interfaces
new file mode 100644
index 0000000..f47ac53
--- /dev/null
+++ b/src/test/zones/simple/hetzner/expected_sdn_interfaces
@@ -0,0 +1,19 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 144.76.100.65/29
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
+
+auto myvnet2
+iface myvnet2
+ address 144.76.0.1/32
+ up ip route add 144.76.200.65/32 dev myvnet2
+ up ip route add 144.76.200.66/32 dev myvnet2
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
diff --git a/src/test/zones/simple/hetzner/interfaces b/src/test/zones/simple/hetzner/interfaces
new file mode 100644
index 0000000..5ab9635
--- /dev/null
+++ b/src/test/zones/simple/hetzner/interfaces
@@ -0,0 +1,6 @@
+auto eth0
+iface eth0 inet static
+ address 144.76.0.1
+ netmask 255.255.255.255
+ pointopoint 172.31.1.1
+ gateway 172.31.1.1 \ No newline at end of file
diff --git a/src/test/zones/simple/hetzner/sdn_config b/src/test/zones/simple/hetzner/sdn_config
new file mode 100644
index 0000000..30773ca
--- /dev/null
+++ b/src/test/zones/simple/hetzner/sdn_config
@@ -0,0 +1,34 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ myvnet2 => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-144.76.100.64-29' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '144.76.100.65',
+ },
+ 'myzone-144.76.200.65-32' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet2',
+ 'gateway' => '144.76.0.1',
+ },
+ 'myzone-144.76.200.66-32' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet2',
+ 'gateway' => '144.76.0.1',
+ },
+ }
+ }
+}
+
+
diff --git a/src/test/zones/simple/ipv4/expected_sdn_interfaces b/src/test/zones/simple/ipv4/expected_sdn_interfaces
new file mode 100644
index 0000000..06e43ad
--- /dev/null
+++ b/src/test/zones/simple/ipv4/expected_sdn_interfaces
@@ -0,0 +1,9 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 192.168.0.1/24
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
diff --git a/src/test/zones/simple/ipv4/interfaces b/src/test/zones/simple/ipv4/interfaces
new file mode 100644
index 0000000..68b6a88
--- /dev/null
+++ b/src/test/zones/simple/ipv4/interfaces
@@ -0,0 +1,5 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/simple/ipv4/sdn_config b/src/test/zones/simple/ipv4/sdn_config
new file mode 100644
index 0000000..dd77b75
--- /dev/null
+++ b/src/test/zones/simple/ipv4/sdn_config
@@ -0,0 +1,22 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-192.168.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '192.168.0.1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/simple/ipv4snat/expected_sdn_interfaces b/src/test/zones/simple/ipv4snat/expected_sdn_interfaces
new file mode 100644
index 0000000..69d7986
--- /dev/null
+++ b/src/test/zones/simple/ipv4snat/expected_sdn_interfaces
@@ -0,0 +1,13 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 10.0.0.1/24
+ post-up iptables -t nat -A POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-down iptables -t nat -D POSTROUTING -s '10.0.0.0/24' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
+ post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
diff --git a/src/test/zones/simple/ipv4snat/interfaces b/src/test/zones/simple/ipv4snat/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/simple/ipv4snat/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/simple/ipv4snat/sdn_config b/src/test/zones/simple/ipv4snat/sdn_config
new file mode 100644
index 0000000..5936d7d
--- /dev/null
+++ b/src/test/zones/simple/ipv4snat/sdn_config
@@ -0,0 +1,23 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+
+ subnets => {
+ ids => { 'myzone-10.0.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '10.0.0.1',
+ 'snat' => 1
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/simple/ipv4v6/expected_sdn_interfaces b/src/test/zones/simple/ipv4v6/expected_sdn_interfaces
new file mode 100644
index 0000000..34ed5db
--- /dev/null
+++ b/src/test/zones/simple/ipv4v6/expected_sdn_interfaces
@@ -0,0 +1,11 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 192.168.0.1/24
+ address 2a08:2142:302:3::1/64
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip-forward on
+ ip6-forward on
diff --git a/src/test/zones/simple/ipv4v6/interfaces b/src/test/zones/simple/ipv4v6/interfaces
new file mode 100644
index 0000000..68b6a88
--- /dev/null
+++ b/src/test/zones/simple/ipv4v6/interfaces
@@ -0,0 +1,5 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/simple/ipv4v6/sdn_config b/src/test/zones/simple/ipv4v6/sdn_config
new file mode 100644
index 0000000..b8ed848
--- /dev/null
+++ b/src/test/zones/simple/ipv4v6/sdn_config
@@ -0,0 +1,27 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+ subnets => {
+ ids => {
+ 'myzone-192.168.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '192.168.0.1',
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '2a08:2142:302:3::1',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces b/src/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces
new file mode 100644
index 0000000..1e0c2c7
--- /dev/null
+++ b/src/test/zones/simple/ipv4v6nogateway/expected_sdn_interfaces
@@ -0,0 +1,7 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
diff --git a/src/test/zones/simple/ipv4v6nogateway/interfaces b/src/test/zones/simple/ipv4v6nogateway/interfaces
new file mode 100644
index 0000000..68b6a88
--- /dev/null
+++ b/src/test/zones/simple/ipv4v6nogateway/interfaces
@@ -0,0 +1,5 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/simple/ipv4v6nogateway/sdn_config b/src/test/zones/simple/ipv4v6nogateway/sdn_config
new file mode 100644
index 0000000..dbd75c9
--- /dev/null
+++ b/src/test/zones/simple/ipv4v6nogateway/sdn_config
@@ -0,0 +1,25 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+ subnets => {
+ ids => {
+ 'myzone-192.168.0.0-24' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ },
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/simple/ipv6snat/expected_sdn_interfaces b/src/test/zones/simple/ipv6snat/expected_sdn_interfaces
new file mode 100644
index 0000000..d3adc24
--- /dev/null
+++ b/src/test/zones/simple/ipv6snat/expected_sdn_interfaces
@@ -0,0 +1,13 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ address 2a08:2142:302:3::1/64
+ post-up ip6tables -t nat -A POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-down ip6tables -t nat -D POSTROUTING -s '2a08:2142:302:3::/64' -o vmbr0 -j SNAT --to-source 192.168.0.1
+ post-up ip6tables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
+ post-down ip6tables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
+ bridge_ports none
+ bridge_stp off
+ bridge_fd 0
+ ip6-forward on
diff --git a/src/test/zones/simple/ipv6snat/interfaces b/src/test/zones/simple/ipv6snat/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/simple/ipv6snat/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/simple/ipv6snat/sdn_config b/src/test/zones/simple/ipv6snat/sdn_config
new file mode 100644
index 0000000..bc38527
--- /dev/null
+++ b/src/test/zones/simple/ipv6snat/sdn_config
@@ -0,0 +1,24 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "simple" } },
+ },
+
+ subnets => {
+ ids => {
+ 'myzone-2a08:2142:302:3::-64' => {
+ 'type' => 'subnet',
+ 'vnet' => 'myvnet',
+ 'gateway' => '2a08:2142:302:3::1',
+ 'snat' => 1
+ }
+ }
+ }
+}
+
+
diff --git a/src/test/zones/vlan/bridge/expected_sdn_interfaces b/src/test/zones/vlan/bridge/expected_sdn_interfaces
new file mode 100644
index 0000000..f9e96d1
--- /dev/null
+++ b/src/test/zones/vlan/bridge/expected_sdn_interfaces
@@ -0,0 +1,23 @@
+#version:1
+
+auto ln_myvnet
+iface ln_myvnet
+ link-type veth
+ veth-peer-name pr_myvnet
+
+auto myvnet
+iface myvnet
+ bridge_ports ln_myvnet
+ bridge_stp off
+ bridge_fd 0
+
+auto pr_myvnet
+iface pr_myvnet
+ link-type veth
+ veth-peer-name ln_myvnet
+
+auto vmbr0v100
+iface vmbr0v100
+ bridge_ports eth0.100 pr_myvnet
+ bridge_stp off
+ bridge_fd 0
diff --git a/src/test/zones/vlan/bridge/interfaces b/src/test/zones/vlan/bridge/interfaces
new file mode 100644
index 0000000..68b6a88
--- /dev/null
+++ b/src/test/zones/vlan/bridge/interfaces
@@ -0,0 +1,5 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/vlan/bridge/sdn_config b/src/test/zones/vlan/bridge/sdn_config
new file mode 100644
index 0000000..c6cfaaa
--- /dev/null
+++ b/src/test/zones/vlan/bridge/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
diff --git a/src/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces b/src/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces
new file mode 100644
index 0000000..a318c7a
--- /dev/null
+++ b/src/test/zones/vlan/bridge_vlanaware/expected_sdn_interfaces
@@ -0,0 +1,7 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports vmbr0.100
+ bridge_stp off
+ bridge_fd 0
diff --git a/src/test/zones/vlan/bridge_vlanaware/interfaces b/src/test/zones/vlan/bridge_vlanaware/interfaces
new file mode 100644
index 0000000..cfdfafe
--- /dev/null
+++ b/src/test/zones/vlan/bridge_vlanaware/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vids 2-4094
+ bridge-vlan-aware 1
diff --git a/src/test/zones/vlan/bridge_vlanaware/sdn_config b/src/test/zones/vlan/bridge_vlanaware/sdn_config
new file mode 100644
index 0000000..c6cfaaa
--- /dev/null
+++ b/src/test/zones/vlan/bridge_vlanaware/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
diff --git a/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces
new file mode 100644
index 0000000..ebf9d2e
--- /dev/null
+++ b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/expected_sdn_interfaces
@@ -0,0 +1,9 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports vmbr0.100
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
diff --git a/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces
new file mode 100644
index 0000000..64eec9e
--- /dev/null
+++ b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet manual
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4096
diff --git a/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config
new file mode 100644
index 0000000..67068f9
--- /dev/null
+++ b/src/test/zones/vlan/bridge_vlanaware_vlanawarevnet/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => "100", type => "vnet", vlanaware => 1, zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
diff --git a/src/test/zones/vlan/ovs/expected_sdn_interfaces b/src/test/zones/vlan/ovs/expected_sdn_interfaces
new file mode 100644
index 0000000..044559e
--- /dev/null
+++ b/src/test/zones/vlan/ovs/expected_sdn_interfaces
@@ -0,0 +1,17 @@
+#version:1
+
+auto ln_myvnet
+iface ln_myvnet
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options tag=100
+
+auto myvnet
+iface myvnet
+ bridge_ports ln_myvnet
+ bridge_stp off
+ bridge_fd 0
+
+auto vmbr0
+iface vmbr0
+ ovs_ports ln_myvnet
diff --git a/src/test/zones/vlan/ovs/interfaces b/src/test/zones/vlan/ovs/interfaces
new file mode 100644
index 0000000..14d2f1e
--- /dev/null
+++ b/src/test/zones/vlan/ovs/interfaces
@@ -0,0 +1,9 @@
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
diff --git a/src/test/zones/vlan/ovs/sdn_config b/src/test/zones/vlan/ovs/sdn_config
new file mode 100644
index 0000000..c6cfaaa
--- /dev/null
+++ b/src/test/zones/vlan/ovs/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
diff --git a/src/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces b/src/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces
new file mode 100644
index 0000000..7bb73b6
--- /dev/null
+++ b/src/test/zones/vlan/ovs_vlanware_vnet/expected_sdn_interfaces
@@ -0,0 +1,19 @@
+#version:1
+
+auto ln_myvnet
+iface ln_myvnet
+ ovs_type OVSIntPort
+ ovs_bridge vmbr0
+ ovs_options vlan_mode=dot1q-tunnel other_config:qinq-ethtype=802.1q tag=100
+
+auto myvnet
+iface myvnet
+ bridge_ports ln_myvnet
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+
+auto vmbr0
+iface vmbr0
+ ovs_ports ln_myvnet
diff --git a/src/test/zones/vlan/ovs_vlanware_vnet/interfaces b/src/test/zones/vlan/ovs_vlanware_vnet/interfaces
new file mode 100644
index 0000000..14d2f1e
--- /dev/null
+++ b/src/test/zones/vlan/ovs_vlanware_vnet/interfaces
@@ -0,0 +1,9 @@
+auto eth0
+iface eth0 inet manual
+ ovs_type OVSPort
+ ovs_bridge vmbr0
+
+auto vmbr0
+iface vmbr0 inet manual
+ ovs_type OVSBridge
+ ovs_ports eth0
diff --git a/src/test/zones/vlan/ovs_vlanware_vnet/sdn_config b/src/test/zones/vlan/ovs_vlanware_vnet/sdn_config
new file mode 100644
index 0000000..9cfdb52
--- /dev/null
+++ b/src/test/zones/vlan/ovs_vlanware_vnet/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { bridge => "vmbr0", ipam => "pve", type => "vlan" } },
+ },
+}
diff --git a/src/test/zones/vxlan/basic/expected_sdn_interfaces b/src/test/zones/vxlan/basic/expected_sdn_interfaces
new file mode 100644
index 0000000..7b73c3e
--- /dev/null
+++ b/src/test/zones/vxlan/basic/expected_sdn_interfaces
@@ -0,0 +1,15 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan_remoteip 192.168.0.2
+ vxlan_remoteip 192.168.0.3
+ mtu 1450
diff --git a/src/test/zones/vxlan/basic/interfaces b/src/test/zones/vxlan/basic/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/vxlan/basic/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/vxlan/basic/sdn_config b/src/test/zones/vxlan/basic/sdn_config
new file mode 100644
index 0000000..f929304
--- /dev/null
+++ b/src/test/zones/vxlan/basic/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } },
+ },
+}
diff --git a/src/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces b/src/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces
new file mode 100644
index 0000000..55cdf9c
--- /dev/null
+++ b/src/test/zones/vxlan/vlanawarevnet/expected_sdn_interfaces
@@ -0,0 +1,17 @@
+#version:1
+
+auto myvnet
+iface myvnet
+ bridge_ports vxlan_myvnet
+ bridge_stp off
+ bridge_fd 0
+ bridge-vlan-aware yes
+ bridge-vids 2-4094
+ mtu 1450
+
+auto vxlan_myvnet
+iface vxlan_myvnet
+ vxlan-id 100
+ vxlan_remoteip 192.168.0.2
+ vxlan_remoteip 192.168.0.3
+ mtu 1450
diff --git a/src/test/zones/vxlan/vlanawarevnet/interfaces b/src/test/zones/vxlan/vlanawarevnet/interfaces
new file mode 100644
index 0000000..66bb826
--- /dev/null
+++ b/src/test/zones/vxlan/vlanawarevnet/interfaces
@@ -0,0 +1,7 @@
+auto vmbr0
+iface vmbr0 inet static
+ address 192.168.0.1/24
+ gateway 192.168.0.254
+ bridge-ports eth0
+ bridge-stp off
+ bridge-fd 0
diff --git a/src/test/zones/vxlan/vlanawarevnet/sdn_config b/src/test/zones/vxlan/vlanawarevnet/sdn_config
new file mode 100644
index 0000000..23fb557
--- /dev/null
+++ b/src/test/zones/vxlan/vlanawarevnet/sdn_config
@@ -0,0 +1,11 @@
+{
+ version => 1,
+ vnets => {
+ ids => {
+ myvnet => { tag => 100, type => "vnet", vlanaware => "1", zone => "myzone" },
+ },
+ },
+ zones => {
+ ids => { myzone => { ipam => "pve", type => "vxlan", peers => "192.168.0.1,192.168.0.2,192.168.0.3" } },
+ },
+}