public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH pve-network 0/4] add ebgp-evpn support
@ 2020-11-24 13:29 Alexandre Derumier
  2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 1/4] controllers: improve bgp-evpn Alexandre Derumier
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Alexandre Derumier @ 2020-11-24 13:29 UTC (permalink / raw)
  To: pve-devel

This add support for a new controller plugin "bgp",
to manage specific bgp section by host.
This is allowing ebgp

(I'll send doc soon)

Also some fixes on pending parser

Alexandre Derumier (4):
  controllers: improve bgp-evpn
  zones: evpn : add support for loopback
  update test documentation
  sdn: fix : pending parser

 PVE/API2/Network/SDN/Controllers.pm         |   1 +
 PVE/API2/Network/SDN/Zones.pm               |  12 +-
 PVE/Network/SDN.pm                          |  45 +++-
 PVE/Network/SDN/Controllers.pm              |  12 +-
 PVE/Network/SDN/Controllers/BgpPlugin.pm    | 274 ++++++++++++++++++++
 PVE/Network/SDN/Controllers/EvpnPlugin.pm   | 122 ++++++---
 PVE/Network/SDN/Controllers/FaucetPlugin.pm |   4 +-
 PVE/Network/SDN/Controllers/Makefile        |   2 +-
 PVE/Network/SDN/Controllers/Plugin.pm       |   9 +-
 PVE/Network/SDN/Zones.pm                    |   2 +-
 PVE/Network/SDN/Zones/EvpnPlugin.pm         |  12 +-
 PVE/Network/SDN/Zones/Plugin.pm             |  39 +--
 PVE/Network/SDN/Zones/QinQPlugin.pm         |   2 +-
 PVE/Network/SDN/Zones/SimplePlugin.pm       |   2 +-
 PVE/Network/SDN/Zones/VlanPlugin.pm         |   2 +-
 PVE/Network/SDN/Zones/VxlanPlugin.pm        |   2 +-
 test/documentation.txt                      |  14 +-
 17 files changed, 457 insertions(+), 99 deletions(-)
 create mode 100644 PVE/Network/SDN/Controllers/BgpPlugin.pm

-- 
2.20.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH pve-network 1/4] controllers: improve bgp-evpn
  2020-11-24 13:29 [pve-devel] [PATCH pve-network 0/4] add ebgp-evpn support Alexandre Derumier
@ 2020-11-24 13:29 ` Alexandre Derumier
  2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 2/4] zones: evpn : add support for loopback Alexandre Derumier
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Alexandre Derumier @ 2020-11-24 13:29 UTC (permalink / raw)
  To: pve-devel

- add new bgp plugin
- add ebgp support
- add loopback support
- move gateway-nodes option to zone as 'exitnodes'
- move external-peers to bgp plugin

Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
---
 PVE/API2/Network/SDN/Controllers.pm         |   1 +
 PVE/Network/SDN/Controllers.pm              |  12 +-
 PVE/Network/SDN/Controllers/BgpPlugin.pm    | 274 ++++++++++++++++++++
 PVE/Network/SDN/Controllers/EvpnPlugin.pm   | 122 ++++++---
 PVE/Network/SDN/Controllers/FaucetPlugin.pm |   4 +-
 PVE/Network/SDN/Controllers/Makefile        |   2 +-
 PVE/Network/SDN/Controllers/Plugin.pm       |   9 +-
 PVE/Network/SDN/Zones/EvpnPlugin.pm         |  11 +-
 PVE/Network/SDN/Zones/Plugin.pm             |   9 +-
 9 files changed, 390 insertions(+), 54 deletions(-)
 create mode 100644 PVE/Network/SDN/Controllers/BgpPlugin.pm

diff --git a/PVE/API2/Network/SDN/Controllers.pm b/PVE/API2/Network/SDN/Controllers.pm
index 75beb6b..e761b6c 100644
--- a/PVE/API2/Network/SDN/Controllers.pm
+++ b/PVE/API2/Network/SDN/Controllers.pm
@@ -11,6 +11,7 @@ use PVE::Network::SDN::Zones;
 use PVE::Network::SDN::Controllers;
 use PVE::Network::SDN::Controllers::Plugin;
 use PVE::Network::SDN::Controllers::EvpnPlugin;
+use PVE::Network::SDN::Controllers::BgpPlugin;
 use PVE::Network::SDN::Controllers::FaucetPlugin;
 
 use Storable qw(dclone);
diff --git a/PVE/Network/SDN/Controllers.pm b/PVE/Network/SDN/Controllers.pm
index f652d7f..9937755 100644
--- a/PVE/Network/SDN/Controllers.pm
+++ b/PVE/Network/SDN/Controllers.pm
@@ -13,9 +13,11 @@ use PVE::Network::SDN::Vnets;
 use PVE::Network::SDN::Zones;
 
 use PVE::Network::SDN::Controllers::EvpnPlugin;
+use PVE::Network::SDN::Controllers::BgpPlugin;
 use PVE::Network::SDN::Controllers::FaucetPlugin;
 use PVE::Network::SDN::Controllers::Plugin;
 PVE::Network::SDN::Controllers::EvpnPlugin->register();
+PVE::Network::SDN::Controllers::BgpPlugin->register();
 PVE::Network::SDN::Controllers::FaucetPlugin->register();
 PVE::Network::SDN::Controllers::Plugin->init();
 
@@ -95,24 +97,24 @@ sub generate_controller_config {
     #generate configuration
     my $config = {};
 
-    foreach my $id (keys %{$controller_cfg->{ids}}) {
+    foreach my $id (sort keys %{$controller_cfg->{ids}}) {
 	my $plugin_config = $controller_cfg->{ids}->{$id};
 	my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
-	$plugin->generate_controller_config($plugin_config, $plugin_config, $id, $uplinks, $config);
+	$plugin->generate_controller_config($plugin_config, $controller_cfg, $id, $uplinks, $config);
     }
 
-    foreach my $id (keys %{$zone_cfg->{ids}}) {
+    foreach my $id (sort keys %{$zone_cfg->{ids}}) {
 	my $plugin_config = $zone_cfg->{ids}->{$id};
 	my $controllerid = $plugin_config->{controller};
 	next if !$controllerid;
 	my $controller = $controller_cfg->{ids}->{$controllerid};
 	if ($controller) {
 	    my $controller_plugin = PVE::Network::SDN::Controllers::Plugin->lookup($controller->{type});
-	    $controller_plugin->generate_controller_zone_config($plugin_config, $controller, $id, $uplinks, $config);
+	    $controller_plugin->generate_controller_zone_config($plugin_config, $controller, $controller_cfg, $id, $uplinks, $config);
 	}
     }
 
-    foreach my $id (keys %{$vnet_cfg->{ids}}) {
+    foreach my $id (sort keys %{$vnet_cfg->{ids}}) {
 	my $plugin_config = $vnet_cfg->{ids}->{$id};
 	my $zoneid = $plugin_config->{zone};
 	next if !$zoneid;
diff --git a/PVE/Network/SDN/Controllers/BgpPlugin.pm b/PVE/Network/SDN/Controllers/BgpPlugin.pm
new file mode 100644
index 0000000..149fd09
--- /dev/null
+++ b/PVE/Network/SDN/Controllers/BgpPlugin.pm
@@ -0,0 +1,274 @@
+package PVE::Network::SDN::Controllers::BgpPlugin;
+
+use strict;
+use warnings;
+
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Tools qw(run_command file_set_contents file_get_contents);
+
+use PVE::Network::SDN::Controllers::Plugin;
+use PVE::Network::SDN::Zones::Plugin;
+use Net::IP;
+
+use base('PVE::Network::SDN::Controllers::Plugin');
+
+sub type {
+    return 'bgp';
+}
+
+sub properties {
+    return {
+	ebgp => {
+	    type => 'boolean',
+	    optional => 1,
+	    description => "Enable ebgp. (remote-as external)",
+	},
+	loopback => {
+	    description => "source loopback interface.",
+	    type => 'string'
+	},
+        node => get_standard_option('pve-node'),
+    };
+}
+
+sub options {
+    return {
+	'node' => { optional => 0 },
+	'asn' => { optional => 0 },
+	'peers' => { optional => 0 },
+	'ebgp' => { optional => 1 },
+	'loopback' => { optional => 1 },
+    };
+}
+
+# Plugin implementation
+sub generate_controller_config {
+    my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
+
+    my @peers;
+    @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'};
+
+    my $asn = $plugin_config->{asn};
+    my $ebgp = $plugin_config->{ebgp};
+    my $loopback = $plugin_config->{loopback};
+
+    return if !$asn;
+
+    my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {};
+
+    my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
+
+    my $local_node = PVE::INotify::nodename();
+
+    my $remoteas = $ebgp ? "external" : $asn;
+
+    #global options
+    my @controller_config = (
+        "bgp router-id $ifaceip",
+        "no bgp default ipv4-unicast",
+        "coalesce-time 1000",
+        "bgp network import-check"
+    );
+
+    push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0;
+
+    @controller_config = ();
+    if($ebgp) {
+	push @controller_config, "no bgp ebgp-requires-policy";
+	push @controller_config, "bgp disable-ebgp-connected-route-check" if $loopback;
+    }
+
+    #BGP neighbors
+    if(@peers) {
+	push @controller_config, "neighbor BGP peer-group";
+	push @controller_config, "neighbor BGP remote-as $remoteas";
+	push @controller_config, "neighbor BGP bfd";
+    }
+
+    # BGP peers
+    foreach my $address (@peers) {
+	push @controller_config, "neighbor $address peer-group BGP";
+    }
+    push(@{$bgp->{""}}, @controller_config);
+
+    # address-family unicast
+    if (@peers) {
+	my $ipversion = Net::IP::ip_is_ipv6($ifaceip) ? "ipv6" : "ipv4";
+	my $mask = Net::IP::ip_is_ipv6($ifaceip) ? "/128" : "32";
+
+	push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "network $ifaceip/$mask") if $loopback;
+	push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP activate");
+	push(@{$bgp->{"address-family"}->{"$ipversion unicast"}}, "neighbor BGP soft-reconfiguration inbound");
+    }
+
+    return $config;
+}
+
+sub generate_controller_zone_config {
+    my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
+
+}
+
+sub on_delete_hook {
+    my ($class, $controllerid, $zone_cfg) = @_;
+
+    # verify that zone is associated to this controller
+    foreach my $id (keys %{$zone_cfg->{ids}}) {
+	my $zone = $zone_cfg->{ids}->{$id};
+	die "controller $controllerid is used by $id"
+	    if (defined($zone->{controller}) && $zone->{controller} eq $controllerid);
+    }
+}
+
+sub on_update_hook {
+    my ($class, $controllerid, $controller_cfg) = @_;
+
+    # we can only have 1 evpn controller / 1 asn by server
+
+    my $current_controller = $controller_cfg->{ids}->{$controllerid};
+
+    foreach my $id (keys %{$controller_cfg->{ids}}) {
+	next if $id eq $controllerid;
+	my $controller = $controller_cfg->{ids}->{$id};
+	next if $controller->{type} ne "evpn";
+	if(!$controller->{node} &&  !$current_controller->{node}) {
+	    die "only 1 global evpn controller can be defined";
+        } else {
+	    die "only 1 evpn controller can be defined for a specific node" if $controller->{node} eq $current_controller->{node};
+        }
+    }
+}
+
+sub sort_frr_config {
+    my $order = {};
+    $order->{''} = 0;
+    $order->{'vrf'} = 1;
+    $order->{'ipv4 unicast'} = 1;
+    $order->{'ipv6 unicast'} = 2;
+    $order->{'l2vpn evpn'} = 3;
+
+    my $a_val = 100;
+    my $b_val = 100;
+
+    $a_val = $order->{$a} if defined($order->{$a});
+    $b_val = $order->{$b} if defined($order->{$b});
+
+    if ($a =~ /bgp (\d+)$/) {
+	$a_val = 2;
+    }
+
+    if ($b =~ /bgp (\d+)$/) {
+	$b_val = 2;
+    }
+
+    return $a_val <=> $b_val;
+}
+
+sub generate_frr_recurse{
+   my ($final_config, $content, $parentkey, $level) = @_;
+
+   my $keylist = {};
+   $keylist->{vrf} = 1;
+   $keylist->{'address-family'} = 1;
+   $keylist->{router} = 1;
+
+   my $exitkeylist = {};
+   $exitkeylist->{vrf} = 1;
+   $exitkeylist->{'address-family'} = 1;
+
+   # FIXME: make this generic
+   my $paddinglevel = undef;
+   if ($level == 1 || $level == 2) {
+	$paddinglevel = $level - 1;
+   } elsif ($level == 3 || $level ==  4) {
+	$paddinglevel = $level - 2;
+   }
+
+   my $padding = "";
+   $padding = ' ' x ($paddinglevel) if $paddinglevel;
+
+   if (ref $content eq  'HASH') {
+	foreach my $key (sort sort_frr_config keys %$content) {
+	    if ($parentkey && defined($keylist->{$parentkey})) {
+		push @{$final_config}, $padding."!";
+		push @{$final_config}, $padding."$parentkey $key";
+	    } elsif ($key ne '' && !defined($keylist->{$key})) {
+		push @{$final_config}, $padding."$key";
+	    }
+
+	    my $option = $content->{$key};
+	    generate_frr_recurse($final_config, $option, $key, $level+1);
+
+	    push @{$final_config}, $padding."exit-$parentkey" if $parentkey && defined($exitkeylist->{$parentkey});
+	}
+    }
+
+    if (ref $content eq 'ARRAY') {
+	push @{$final_config}, map { $padding . "$_" } @$content;
+    }
+}
+
+sub write_controller_config {
+    my ($class, $plugin_config, $config) = @_;
+
+    my $nodename = PVE::INotify::nodename();
+
+    my $final_config = [];
+    push @{$final_config}, "log syslog informational";
+    push @{$final_config}, "ip forwarding";
+    push @{$final_config}, "ipv6 forwarding";
+    push @{$final_config}, "frr defaults traditional";
+    push @{$final_config}, "service integrated-vtysh-config";
+    push @{$final_config}, "hostname $nodename";
+    push @{$final_config}, "!";
+
+    if (-e "/etc/frr/frr.conf.local") {
+	generate_frr_recurse($final_config, $config->{frr}->{vrf}, "vrf", 1);
+	push @{$final_config}, "!";
+
+	my $local_conf = file_get_contents("/etc/frr/frr.conf.local");
+	chomp ($local_conf);
+	push @{$final_config}, $local_conf;
+    } else {
+	generate_frr_recurse($final_config, $config->{frr}, undef, 0);
+    }
+
+    push @{$final_config}, "!";
+    push @{$final_config}, "line vty";
+    push @{$final_config}, "!";
+
+    my $rawconfig = join("\n", @{$final_config});
+
+    return if !$rawconfig;
+    return if !-d "/etc/frr";
+
+    file_set_contents("/etc/frr/frr.conf", $rawconfig);
+}
+
+sub reload_controller {
+    my ($class) = @_;
+
+    my $conf_file = "/etc/frr/frr.conf";
+    my $bin_path = "/usr/lib/frr/frr-reload.py";
+
+    if (!-e $bin_path) {
+	warn "missing $bin_path. Please install frr-pythontools package";
+	return;
+    }
+
+    my $err = sub {
+	my $line = shift;
+	if ($line =~ /ERROR:/) {
+	    warn "$line \n";
+	}
+    };
+
+    if (-e $conf_file && -e $bin_path) {
+	run_command([$bin_path, '--stdout', '--reload', $conf_file], outfunc => {}, errfunc => $err);
+    }
+}
+
+1;
+
+
diff --git a/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/PVE/Network/SDN/Controllers/EvpnPlugin.pm
index d82de2a..e59c142 100644
--- a/PVE/Network/SDN/Controllers/EvpnPlugin.pm
+++ b/PVE/Network/SDN/Controllers/EvpnPlugin.pm
@@ -9,6 +9,7 @@ use PVE::Tools qw(run_command file_set_contents file_get_contents);
 
 use PVE::Network::SDN::Controllers::Plugin;
 use PVE::Network::SDN::Zones::Plugin;
+use Net::IP;
 
 use base('PVE::Network::SDN::Controllers::Plugin');
 
@@ -26,11 +27,6 @@ sub properties {
 	    description => "peers address list.",
 	    type => 'string', format => 'ip-list'
 	},
-	'gateway-nodes' => get_standard_option('pve-node-list'),
-	'gateway-external-peers' => {
-	    description => "upstream bgp peers address list.",
-	    type => 'string', format => 'ip-list'
-	},
     };
 }
 
@@ -38,80 +34,97 @@ sub options {
     return {
 	'asn' => { optional => 0 },
 	'peers' => { optional => 0 },
-	'gateway-nodes' => { optional => 1 },
-	'gateway-external-peers' => { optional => 1 },
     };
 }
 
 # Plugin implementation
 sub generate_controller_config {
-    my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
+    my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
 
     my @peers;
     @peers = PVE::Tools::split_list($plugin_config->{'peers'}) if $plugin_config->{'peers'};
 
+    my $local_node = PVE::INotify::nodename();
+
     my $asn = $plugin_config->{asn};
-    my $gatewaynodes = $plugin_config->{'gateway-nodes'};
-    my @gatewaypeers;
-    @gatewaypeers = PVE::Tools::split_list($plugin_config->{'gateway-external-peers'}) if $plugin_config->{'gateway-external-peers'};
+    my $ebgp = undef;
+    my $loopback = undef;
+    my $autortas = undef;
+    my $bgprouter = find_bgp_controller($local_node, $controller_cfg);
+    if($bgprouter) {
+	$ebgp = 1 if $plugin_config->{'asn'} ne $bgprouter->{asn};
+	$loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
+	$asn = $bgprouter->{asn} if $bgprouter->{asn};
+	$autortas = $plugin_config->{'asn'} if $ebgp;
+    }
 
     return if !$asn;
 
     my $bgp = $config->{frr}->{router}->{"bgp $asn"} //= {};
 
-    my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers);
+    my ($ifaceip, $interface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
 
-    my $is_gateway = undef;
-    my $local_node = PVE::INotify::nodename();
-
-    foreach my $gatewaynode (PVE::Tools::split_list($gatewaynodes)) {
-	$is_gateway = 1 if $gatewaynode eq $local_node;
-    }
+    my $remoteas = $ebgp ? "external" : $asn;
 
+    #global options
     my @controller_config = (
 	"bgp router-id $ifaceip",
 	"no bgp default ipv4-unicast",
 	"coalesce-time 1000",
     );
 
+    push(@{$bgp->{""}}, @controller_config) if keys %{$bgp} == 0;
+
+    @controller_config = ();
+    
+    #VTEP neighbors
+    push @controller_config, "neighbor VTEP peer-group";
+    push @controller_config, "neighbor VTEP remote-as $remoteas";
+    push @controller_config, "neighbor VTEP bfd";
+
+    if($ebgp && $loopback) {
+	push @controller_config, "neighbor VTEP ebgp-multihop 10";
+	push @controller_config, "neighbor VTEP update-source $loopback";
+    }
+
+    # VTEP peers
     foreach my $address (@peers) {
 	next if $address eq $ifaceip;
-	push @controller_config, "neighbor $address remote-as $asn";
+	push @controller_config, "neighbor $address peer-group VTEP";
     }
 
-    if ($is_gateway) {
-	foreach my $address (@gatewaypeers) {
-	    push @controller_config, "neighbor $address remote-as external";
-	}
-    }
     push(@{$bgp->{""}}, @controller_config);
 
+    # address-family l2vpn
     @controller_config = ();
-    foreach my $address (@peers) {
-	next if $address eq $ifaceip;
-	push @controller_config, "neighbor $address activate";
-    }
+    push @controller_config, "neighbor VTEP activate";
     push @controller_config, "advertise-all-vni";
+    push @controller_config, "autort as $autortas" if $autortas;
     push(@{$bgp->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
 
-    if ($is_gateway) {
-	# import /32 routes of evpn network from vrf1 to default vrf (for packet return)
-	@controller_config = map { "neighbor $_ activate" } @gatewaypeers;
-
-	push(@{$bgp->{"address-family"}->{"ipv4 unicast"}}, @controller_config);
-	push(@{$bgp->{"address-family"}->{"ipv6 unicast"}}, @controller_config);
-    }
-
     return $config;
 }
 
 sub generate_controller_zone_config {
-    my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
+    my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
+
+    my $local_node = PVE::INotify::nodename();
 
     my $vrf = "vrf_$id";
     my $vrfvxlan = $plugin_config->{'vrf-vxlan'};
+    my $exitnodes = $plugin_config->{'exitnodes'};
+
     my $asn = $controller->{asn};
-    my $gatewaynodes = $controller->{'gateway-nodes'};
+    my $ebgp = undef;
+    my $loopback = undef;
+    my $autortas = undef;
+    my $bgprouter = find_bgp_controller($local_node, $controller_cfg);
+    if($bgprouter) {
+        $ebgp = 1 if $controller->{'asn'} ne $bgprouter->{asn};
+	$loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
+	$asn = $bgprouter->{asn} if $bgprouter->{asn};
+	$autortas = $controller->{'asn'} if $ebgp;
+    }
 
     return if !$vrf || !$vrfvxlan || !$asn;
 
@@ -120,11 +133,18 @@ sub generate_controller_zone_config {
     push @controller_config, "vni $vrfvxlan";
     push(@{$config->{frr}->{vrf}->{"$vrf"}}, @controller_config);
 
-    push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{""}}, "!");
+    #main vrf router
+    @controller_config = ();
+    push @controller_config, "no bgp ebgp-requires-policy" if $ebgp;
+#    push @controller_config, "!";
+    push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{""}}, @controller_config);
 
-    my $local_node = PVE::INotify::nodename();
+    if ($autortas) {
+	push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target import $autortas:$vrfvxlan");
+	push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{"address-family"}->{"l2vpn evpn"}}, "route-target export $autortas:$vrfvxlan");
+    }
 
-    my $is_gateway = grep { $_ eq $local_node } PVE::Tools::split_list($gatewaynodes);
+    my $is_gateway = grep { $_ eq $local_node } PVE::Tools::split_list($exitnodes);
     if ($is_gateway) {
 
 	@controller_config = ();
@@ -165,13 +185,31 @@ sub on_update_hook {
 
     # we can only have 1 evpn controller / 1 asn by server
 
+    my $controllernb = 0;
     foreach my $id (keys %{$controller_cfg->{ids}}) {
 	next if $id eq $controllerid;
 	my $controller = $controller_cfg->{ids}->{$id};
-	die "only 1 evpn controller can be defined" if $controller->{type} eq "evpn";
+	next if $controller->{type} ne "evpn";
+	$controllernb++;
+	die "only 1 global evpn controller can be defined" if $controllernb > 1;
+    }
+}
+
+sub find_bgp_controller {
+    my ($nodename, $controller_cfg) = @_;
+
+    my $controller = undef;
+    foreach my $id  (keys %{$controller_cfg->{ids}}) {
+        $controller = $controller_cfg->{ids}->{$id};
+        next if $controller->{type} ne 'bgp';
+        next if $controller->{node} ne $nodename;
+	last;
     }
+
+    return $controller;
 }
 
+
 sub sort_frr_config {
     my $order = {};
     $order->{''} = 0;
diff --git a/PVE/Network/SDN/Controllers/FaucetPlugin.pm b/PVE/Network/SDN/Controllers/FaucetPlugin.pm
index dcac6eb..5742187 100644
--- a/PVE/Network/SDN/Controllers/FaucetPlugin.pm
+++ b/PVE/Network/SDN/Controllers/FaucetPlugin.pm
@@ -22,12 +22,12 @@ sub properties {
 
 # Plugin implementation
 sub generate_controller_config {
-    my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
+    my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
 
 }
 
 sub generate_controller_zone_config {
-    my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
+    my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
 
     my $dpid = $plugin_config->{'dp-id'};
     my $dphex = printf("%x",$dpid);
diff --git a/PVE/Network/SDN/Controllers/Makefile b/PVE/Network/SDN/Controllers/Makefile
index 3324125..11686a3 100644
--- a/PVE/Network/SDN/Controllers/Makefile
+++ b/PVE/Network/SDN/Controllers/Makefile
@@ -1,4 +1,4 @@
-SOURCES=Plugin.pm FaucetPlugin.pm EvpnPlugin.pm
+SOURCES=Plugin.pm FaucetPlugin.pm EvpnPlugin.pm BgpPlugin.pm
 
 
 PERL5DIR=${DESTDIR}/usr/share/perl5
diff --git a/PVE/Network/SDN/Controllers/Plugin.pm b/PVE/Network/SDN/Controllers/Plugin.pm
index 06cd576..0c92b17 100644
--- a/PVE/Network/SDN/Controllers/Plugin.pm
+++ b/PVE/Network/SDN/Controllers/Plugin.pm
@@ -70,7 +70,14 @@ sub generate_sdn_config {
 }
 
 sub generate_controller_config {
-    my ($class, $plugin_config, $controller, $id, $uplinks, $config) = @_;
+    my ($class, $plugin_config, $controller_cfg, $id, $uplinks, $config) = @_;
+
+    die "please implement inside plugin";
+}
+
+
+sub generate_controller_zone_config {
+    my ($class, $plugin_config, $controller, $controller_cfg, $id, $uplinks, $config) = @_;
 
     die "please implement inside plugin";
 }
diff --git a/PVE/Network/SDN/Zones/EvpnPlugin.pm b/PVE/Network/SDN/Zones/EvpnPlugin.pm
index 5338a1b..d50ddb9 100644
--- a/PVE/Network/SDN/Zones/EvpnPlugin.pm
+++ b/PVE/Network/SDN/Zones/EvpnPlugin.pm
@@ -4,6 +4,7 @@ use strict;
 use warnings;
 use PVE::Network::SDN::Zones::VxlanPlugin;
 use PVE::Exception qw(raise raise_param_exc);
+use PVE::JSONSchema qw(get_standard_option);
 use PVE::Tools qw($IPV4RE);
 use PVE::INotify;
 use PVE::Cluster;
@@ -27,6 +28,7 @@ sub properties {
 	    type => 'string',
 	    description => "Frr router name",
 	},
+	'exitnodes' => get_standard_option('pve-node-list'),
     };
 }
 
@@ -35,7 +37,8 @@ sub options {
     return {
         nodes => { optional => 1},
         'vrf-vxlan' => { optional => 0 },
-        'controller' => { optional => 0 },
+        controller => { optional => 0 },
+	exitnodes => { optional => 1 },
 	mtu => { optional => 1 },
 	dns => { optional => 1 },
 	reversedns => { optional => 1 },
@@ -59,10 +62,14 @@ sub generate_sdn_config {
     my $local_node = PVE::INotify::nodename();
 
     die "missing vxlan tag" if !$tag;
+    die "missing controller" if !$controller;
     warn "vlan-aware vnet can't be enabled with evpn plugin" if $vnet->{vlanaware};
 
     my @peers = PVE::Tools::split_list($controller->{'peers'});
-    my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers);
+#    my $bgprouter = PVE::Network::SDN::Controllers::EvpnController::find_bgp_controller($local_node, $controller_cfg);
+#    my $loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
+    my $loopback = undef;
+    my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
 
     my $mtu = 1450;
     $mtu = $interfaces_config->{$iface}->{mtu} - 50 if $interfaces_config->{$iface}->{mtu};
diff --git a/PVE/Network/SDN/Zones/Plugin.pm b/PVE/Network/SDN/Zones/Plugin.pm
index 6fc13eb..aa795a3 100644
--- a/PVE/Network/SDN/Zones/Plugin.pm
+++ b/PVE/Network/SDN/Zones/Plugin.pm
@@ -274,10 +274,17 @@ sub get_local_route_ip {
 
 
 sub find_local_ip_interface_peers {
-    my ($peers) = @_;
+    my ($peers, $iface) = @_;
 
     my $network_config = PVE::INotify::read_file('interfaces');
     my $ifaces = $network_config->{ifaces};
+    
+    #if iface is defined, return ip if exist (if not,try to find it on other ifaces)
+    if ($iface) {
+	my $ip = $ifaces->{$iface}->{address};
+	return ($ip,$iface) if $ip;
+    }
+
     #is a local ip member of peers list ?
     foreach my $address (@{$peers}) {
 	while (my $interface = each %$ifaces) {
-- 
2.20.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH pve-network 2/4] zones: evpn : add support for loopback
  2020-11-24 13:29 [pve-devel] [PATCH pve-network 0/4] add ebgp-evpn support Alexandre Derumier
  2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 1/4] controllers: improve bgp-evpn Alexandre Derumier
@ 2020-11-24 13:29 ` Alexandre Derumier
  2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 3/4] update test documentation Alexandre Derumier
  2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 4/4] sdn: fix : pending parser Alexandre Derumier
  3 siblings, 0 replies; 5+ messages in thread
From: Alexandre Derumier @ 2020-11-24 13:29 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
---
 PVE/Network/SDN/Zones.pm              | 2 +-
 PVE/Network/SDN/Zones/EvpnPlugin.pm   | 7 +++----
 PVE/Network/SDN/Zones/Plugin.pm       | 2 +-
 PVE/Network/SDN/Zones/QinQPlugin.pm   | 2 +-
 PVE/Network/SDN/Zones/SimplePlugin.pm | 2 +-
 PVE/Network/SDN/Zones/VlanPlugin.pm   | 2 +-
 PVE/Network/SDN/Zones/VxlanPlugin.pm  | 2 +-
 7 files changed, 9 insertions(+), 10 deletions(-)

diff --git a/PVE/Network/SDN/Zones.pm b/PVE/Network/SDN/Zones.pm
index 1f225dc..67d8f18 100644
--- a/PVE/Network/SDN/Zones.pm
+++ b/PVE/Network/SDN/Zones.pm
@@ -131,7 +131,7 @@ sub generate_etc_network_config {
 
 	my $plugin = PVE::Network::SDN::Zones::Plugin->lookup($plugin_config->{type});
 	eval {
-	    $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $subnet_cfg, $interfaces_config, $config);
+	    $plugin->generate_sdn_config($plugin_config, $zone, $id, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config);
 	};
 	if (my $err = $@) {
 	    warn "zone $zone : vnet $id : $err\n";
diff --git a/PVE/Network/SDN/Zones/EvpnPlugin.pm b/PVE/Network/SDN/Zones/EvpnPlugin.pm
index d50ddb9..14bbf56 100644
--- a/PVE/Network/SDN/Zones/EvpnPlugin.pm
+++ b/PVE/Network/SDN/Zones/EvpnPlugin.pm
@@ -49,7 +49,7 @@ sub options {
 
 # Plugin implementation
 sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $subnet_cfg, $interfaces_config, $config) = @_;
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
 
     my $tag = $vnet->{tag};
     my $alias = $vnet->{alias};
@@ -66,9 +66,8 @@ sub generate_sdn_config {
     warn "vlan-aware vnet can't be enabled with evpn plugin" if $vnet->{vlanaware};
 
     my @peers = PVE::Tools::split_list($controller->{'peers'});
-#    my $bgprouter = PVE::Network::SDN::Controllers::EvpnController::find_bgp_controller($local_node, $controller_cfg);
-#    my $loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
-    my $loopback = undef;
+    my $bgprouter = PVE::Network::SDN::Controllers::EvpnPlugin::find_bgp_controller($local_node, $controller_cfg);
+    my $loopback = $bgprouter->{loopback} if $bgprouter->{loopback};
     my ($ifaceip, $iface) = PVE::Network::SDN::Zones::Plugin::find_local_ip_interface_peers(\@peers, $loopback);
 
     my $mtu = 1450;
diff --git a/PVE/Network/SDN/Zones/Plugin.pm b/PVE/Network/SDN/Zones/Plugin.pm
index aa795a3..8592e3c 100644
--- a/PVE/Network/SDN/Zones/Plugin.pm
+++ b/PVE/Network/SDN/Zones/Plugin.pm
@@ -98,7 +98,7 @@ sub parse_section_header {
 }
 
 sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $subnet_cfg, $interfaces_config, $config) = @_;
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
 
     die "please implement inside plugin";
 }
diff --git a/PVE/Network/SDN/Zones/QinQPlugin.pm b/PVE/Network/SDN/Zones/QinQPlugin.pm
index 5d40db8..2bd60db 100644
--- a/PVE/Network/SDN/Zones/QinQPlugin.pm
+++ b/PVE/Network/SDN/Zones/QinQPlugin.pm
@@ -49,7 +49,7 @@ sub options {
 
 # Plugin implementation
 sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $subnet_cfg, $interfaces_config, $config) = @_;
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
 
     my $stag = $plugin_config->{tag};
     my $mtu = $plugin_config->{mtu};
diff --git a/PVE/Network/SDN/Zones/SimplePlugin.pm b/PVE/Network/SDN/Zones/SimplePlugin.pm
index c4f4475..ed41e62 100644
--- a/PVE/Network/SDN/Zones/SimplePlugin.pm
+++ b/PVE/Network/SDN/Zones/SimplePlugin.pm
@@ -43,7 +43,7 @@ sub options {
 
 # Plugin implementation
 sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $subnet_cfg, $interfaces_config, $config) = @_;
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
 
     return $config if$config->{$vnetid}; # nothing to do
 
diff --git a/PVE/Network/SDN/Zones/VlanPlugin.pm b/PVE/Network/SDN/Zones/VlanPlugin.pm
index 7af9b2c..ca6bd8f 100644
--- a/PVE/Network/SDN/Zones/VlanPlugin.pm
+++ b/PVE/Network/SDN/Zones/VlanPlugin.pm
@@ -43,7 +43,7 @@ sub options {
 
 # Plugin implementation
 sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $subnet_cfg, $interfaces_config, $config) = @_;
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
 
     my $bridge = $plugin_config->{bridge};
     die "can't find bridge $bridge" if !-d "/sys/class/net/$bridge";
diff --git a/PVE/Network/SDN/Zones/VxlanPlugin.pm b/PVE/Network/SDN/Zones/VxlanPlugin.pm
index 1fe16b8..c018d34 100644
--- a/PVE/Network/SDN/Zones/VxlanPlugin.pm
+++ b/PVE/Network/SDN/Zones/VxlanPlugin.pm
@@ -47,7 +47,7 @@ sub options {
 
 # Plugin implementation
 sub generate_sdn_config {
-    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $subnet_cfg, $interfaces_config, $config) = @_;
+    my ($class, $plugin_config, $zoneid, $vnetid, $vnet, $controller, $controller_cfg, $subnet_cfg, $interfaces_config, $config) = @_;
 
     my $tag = $vnet->{tag};
     my $alias = $vnet->{alias};
-- 
2.20.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH pve-network 3/4] update test documentation
  2020-11-24 13:29 [pve-devel] [PATCH pve-network 0/4] add ebgp-evpn support Alexandre Derumier
  2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 1/4] controllers: improve bgp-evpn Alexandre Derumier
  2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 2/4] zones: evpn : add support for loopback Alexandre Derumier
@ 2020-11-24 13:29 ` Alexandre Derumier
  2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 4/4] sdn: fix : pending parser Alexandre Derumier
  3 siblings, 0 replies; 5+ messages in thread
From: Alexandre Derumier @ 2020-11-24 13:29 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
---
 test/documentation.txt | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/test/documentation.txt b/test/documentation.txt
index 7886966..6ee8ee6 100644
--- a/test/documentation.txt
+++ b/test/documentation.txt
@@ -12,14 +12,18 @@ pvesh create /cluster/sdn/vnets/vnet100/subnets/ --type subnet --subnet 192.168.
 #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 controller
-pvesh create /cluster/sdn/controllers/ --controller frrrouter1 --type evpn --peers 192.168.0.1,192.168.0.2,192.168.0.3 --asn 1234 --gateway-nodes pxnode1,pxnode2 --gateway-external-peers 192.168.0.253,192.168.0.254
+#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 frrrouter1
+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 layer3 routable vxlan bgpevpn transportzone
-pvesh create /cluster/sdn/zones/ --zone layer3evpnzone --type evpn --ipam pve --controller frrrouter1 --vrf-vxlan 4000
 
 
 #create a vnet in the transportzone
-- 
2.20.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

* [pve-devel] [PATCH pve-network 4/4] sdn: fix : pending parser
  2020-11-24 13:29 [pve-devel] [PATCH pve-network 0/4] add ebgp-evpn support Alexandre Derumier
                   ` (2 preceding siblings ...)
  2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 3/4] update test documentation Alexandre Derumier
@ 2020-11-24 13:29 ` Alexandre Derumier
  3 siblings, 0 replies; 5+ messages in thread
From: Alexandre Derumier @ 2020-11-24 13:29 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Alexandre Derumier <aderumier@odiso.com>
---
 PVE/API2/Network/SDN/Zones.pm   | 12 +++++++--
 PVE/Network/SDN.pm              | 45 ++++++++++++++++++++++++++++++---
 PVE/Network/SDN/Zones/Plugin.pm | 28 --------------------
 3 files changed, 52 insertions(+), 33 deletions(-)

diff --git a/PVE/API2/Network/SDN/Zones.pm b/PVE/API2/Network/SDN/Zones.pm
index 5ae577b..5bbdd36 100644
--- a/PVE/API2/Network/SDN/Zones.pm
+++ b/PVE/API2/Network/SDN/Zones.pm
@@ -38,12 +38,20 @@ my $api_sdn_zones_config = sub {
     $scfg->{digest} = $cfg->{digest};
 
     if ($scfg->{nodes}) {
-        $scfg->{nodes} = PVE::Network::SDN::Zones::Plugin->encode_value($scfg->{type}, 'nodes', $scfg->{nodes});
+        $scfg->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $scfg->{nodes});
+    }
+
+    if ($scfg->{exitnodes}) {
+        $scfg->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $scfg->{exitnodes});
     }
 
     my $pending = $scfg->{pending};
     if ($pending->{nodes}) {
-        $pending->{nodes} = PVE::Network::SDN::Zones::Plugin->encode_value($scfg->{type}, 'nodes', $pending->{nodes});
+        $pending->{nodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'nodes', $pending->{nodes});
+    }
+
+    if ($pending->{exitnodes}) {
+        $pending->{exitnodes} = PVE::Network::SDN::encode_value($scfg->{type}, 'exitnodes', $pending->{exitnodes});
     }
 
     return $scfg;
diff --git a/PVE/Network/SDN.pm b/PVE/Network/SDN.pm
index 3cd73ff..c0c5672 100644
--- a/PVE/Network/SDN.pm
+++ b/PVE/Network/SDN.pm
@@ -6,6 +6,8 @@ use warnings;
 use Data::Dumper;
 use JSON;
 
+use PVE::JSONSchema;
+
 use PVE::Network::SDN::Vnets;
 use PVE::Network::SDN::Zones;
 use PVE::Network::SDN::Controllers;
@@ -96,7 +98,11 @@ sub pending_config {
 	    $pending->{$id}->{$key} = $running_object->{$key};
 	    if(!keys %{$config_object}) {
 		$pending->{$id}->{state} = "deleted";
-	    } elsif ($running_object->{$key} ne $config_object->{$key}) {
+	    } elsif (!defined($config_object->{$key})) {
+		$pending->{$id}->{"pending"}->{$key} = 'deleted';
+		$pending->{$id}->{state} = "changed";
+	    } elsif (PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key})
+			 ne PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key})) {
 		$pending->{$id}->{state} = "changed";
 	    }
 	}
@@ -107,8 +113,8 @@ sub pending_config {
 	my $config_object = $config_objects->{$id};
 
 	foreach my $key (sort keys %{$config_object}) {
-	    my $config_value = $config_object->{$key} if $config_object->{$key};
-	    my $running_value = $running_object->{$key} if $running_object->{$key};
+	    my $config_value = PVE::Network::SDN::encode_value(undef, $key, $config_object->{$key}) if $config_object->{$key};
+	    my $running_value = PVE::Network::SDN::encode_value(undef, $key, $running_object->{$key}) if $running_object->{$key};
 	    if($key eq 'type' || $key eq 'vnet') {
 		$pending->{$id}->{$key} = $config_value;
 	    } else {
@@ -210,5 +216,38 @@ sub generate_controller_config {
     PVE::Network::SDN::Controllers::reload_controller() if $reload;
 }
 
+
+sub decode_value {
+    my ($type, $key, $value) = @_;
+
+    if ($key eq 'nodes') {
+        my $res = {};
+
+        foreach my $node (PVE::Tools::split_list($value)) {
+            if (PVE::JSONSchema::pve_verify_node_name($node)) {
+                $res->{$node} = 1;
+            }
+        }
+
+        return $res;
+    }
+
+   return $value;
+}
+
+sub encode_value {
+    my ($type, $key, $value) = @_;
+
+    if ($key eq 'nodes' || $key eq 'exitnodes') {
+        if(ref($value) eq 'HASH') {
+            return join(',', sort keys(%$value));
+        } else {
+            return $value;
+        }
+    }
+
+    return $value;
+}
+
 1;
 
diff --git a/PVE/Network/SDN/Zones/Plugin.pm b/PVE/Network/SDN/Zones/Plugin.pm
index 8592e3c..ebb5c7e 100644
--- a/PVE/Network/SDN/Zones/Plugin.pm
+++ b/PVE/Network/SDN/Zones/Plugin.pm
@@ -55,34 +55,6 @@ sub private {
     return $defaultData;
 }
 
-sub decode_value {
-    my ($class, $type, $key, $value) = @_;
-
-    if ($key eq 'nodes') {
-        my $res = {};
-
-        foreach my $node (PVE::Tools::split_list($value)) {
-            if (PVE::JSONSchema::pve_verify_node_name($node)) {
-                $res->{$node} = 1;
-            }
-        }
-
-        return $res;
-    }
-
-   return $value;
-}
-
-sub encode_value {
-    my ($class, $type, $key, $value) = @_;
-
-    if ($key eq 'nodes') {
-        return join(',', keys(%$value));
-    }
-
-    return $value;
-}
-
 sub parse_section_header {
     my ($class, $line) = @_;
 
-- 
2.20.1




^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2020-11-24 13:30 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-11-24 13:29 [pve-devel] [PATCH pve-network 0/4] add ebgp-evpn support Alexandre Derumier
2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 1/4] controllers: improve bgp-evpn Alexandre Derumier
2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 2/4] zones: evpn : add support for loopback Alexandre Derumier
2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 3/4] update test documentation Alexandre Derumier
2020-11-24 13:29 ` [pve-devel] [PATCH pve-network 4/4] sdn: fix : pending parser Alexandre Derumier

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal