[pve-devel] [PATCH network] sdn: factor out frr config generation and writing

Gabriel Goller g.goller at proxmox.com
Wed Feb 5 17:13:40 CET 2025


Previously the frr config generation and writing was only done in the
evpn plugin. This means that it was not possible to create a standalone
bgp and isis plugin without an evpn plugin in place. (The config would
just never be written.) To fix this, factor out the frr generation and
writing into a separate module and check if a frr-type-plugin is being
used. This also paves the way for the fabrics, which would get the
config from rust and then use this frr helper.

Signed-off-by: Gabriel Goller <g.goller at proxmox.com>
---
 src/PVE/Network/SDN/Controllers.pm            |  47 ++-
 src/PVE/Network/SDN/Controllers/BgpPlugin.pm  |  18 +-
 src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 289 +----------------
 src/PVE/Network/SDN/Controllers/Frr.pm        | 296 ++++++++++++++++++
 src/PVE/Network/SDN/Controllers/IsisPlugin.pm |  18 +-
 src/PVE/Network/SDN/Controllers/Makefile      |   2 +-
 src/PVE/Network/SDN/Zones/EvpnPlugin.pm       |  15 +
 7 files changed, 383 insertions(+), 302 deletions(-)
 create mode 100644 src/PVE/Network/SDN/Controllers/Frr.pm

diff --git a/src/PVE/Network/SDN/Controllers.pm b/src/PVE/Network/SDN/Controllers.pm
index fd7ad54ac38c..43f154b7338e 100644
--- a/src/PVE/Network/SDN/Controllers.pm
+++ b/src/PVE/Network/SDN/Controllers.pm
@@ -12,6 +12,7 @@ use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
 use PVE::Network::SDN::Vnets;
 use PVE::Network::SDN::Zones;
 
+use PVE::Network::SDN::Controllers::Frr;
 use PVE::Network::SDN::Controllers::EvpnPlugin;
 use PVE::Network::SDN::Controllers::BgpPlugin;
 use PVE::Network::SDN::Controllers::IsisPlugin;
@@ -148,10 +149,22 @@ sub reload_controller {
 
     return if !$controller_cfg;
 
+    my $frr_reload = 0;
+
     foreach my $id (keys %{$controller_cfg->{ids}}) {
 	my $plugin_config = $controller_cfg->{ids}->{$id};
-	my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
-	$plugin->reload_controller();
+	my $type = $plugin_config->{type};
+	my @frr_types = ("bgp", "isis", "evpn");
+	if (grep {$type} @frr_types) {
+	    $frr_reload = 1;
+	} else {
+	    my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($type);
+	    $plugin->reload_controller();
+	}
+    }
+
+    if ($frr_reload) {
+	PVE::Network::SDN::Controllers::Frr::reload_controller();
     }
 }
 
@@ -161,12 +174,22 @@ sub generate_controller_rawconfig {
     my $cfg = PVE::Network::SDN::running_config();
     my $controller_cfg = $cfg->{controllers};
     return if !$controller_cfg;
+    my $frr_generate = 0;
 
     my $rawconfig = "";
     foreach my $id (keys %{$controller_cfg->{ids}}) {
 	my $plugin_config = $controller_cfg->{ids}->{$id};
-	my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
-	$rawconfig .= $plugin->generate_controller_rawconfig($plugin_config, $config);
+	my $type = $plugin_config->{type};
+	my @frr_types = ("bgp", "isis", "evpn");
+	if (grep {$type} @frr_types) {
+	    $frr_generate = 1;
+	} else {
+	    my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($type);
+	    $rawconfig .= $plugin->generate_controller_rawconfig($plugin_config, $config);
+	}
+    }
+    if ($frr_generate) {
+        $rawconfig .= PVE::Network::SDN::Controllers::Frr::generate_controller_rawconfig($config);
     }
     return $rawconfig;
 }
@@ -178,10 +201,22 @@ sub write_controller_config {
     my $controller_cfg = $cfg->{controllers};
     return if !$controller_cfg;
 
+    my $frr_reload = 0;
+
     foreach my $id (keys %{$controller_cfg->{ids}}) {
 	my $plugin_config = $controller_cfg->{ids}->{$id};
-	my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($plugin_config->{type});
-	$plugin->write_controller_config($plugin_config, $config);
+	my $type = $plugin_config->{type};
+	my @frr_types = ("bgp", "isis", "evpn");
+	if (grep {$type} @frr_types) {
+	    $frr_reload = 1;
+	} else {
+	    my $plugin = PVE::Network::SDN::Controllers::Plugin->lookup($type);
+	    $plugin->write_controller_config($plugin_config, $config);
+	}
+    }
+    
+    if ($frr_reload) {
+	PVE::Network::SDN::Controllers::Frr::write_controller_config($config);
     }
 }
 
diff --git a/src/PVE/Network/SDN/Controllers/BgpPlugin.pm b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm
index 53963e5ad7f4..a4d3e9990647 100644
--- a/src/PVE/Network/SDN/Controllers/BgpPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/BgpPlugin.pm
@@ -7,6 +7,7 @@ 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::Frr;
 use PVE::Network::SDN::Controllers::Plugin;
 use PVE::Network::SDN::Zones::Plugin;
 use Net::IP;
@@ -164,19 +165,22 @@ sub on_update_hook {
     }
 }
 
+sub reload_controller {
+    my ($class) = @_;
+    #return PVE::Network::SDN::Controllers::Frr::reload_controller($class);
+    die "implemented in the Frr helper";
+}
+
 sub generate_controller_rawconfig {
     my ($class, $plugin_config, $config) = @_;
-    return "";
+    #return PVE::Network::SDN::Controllers::Frr::generate_controller_rawconfig($class, $plugin_config, $config);
+    die "implemented in the Frr helper";
 }
 
 sub write_controller_config {
     my ($class, $plugin_config, $config) = @_;
-    return;
-}
-
-sub reload_controller {
-    my ($class) = @_;
-    return;
+    #return PVE::Network::SDN::Controllers::Frr::write_controller_config($class, $plugin_config, $config);
+    die "implemented in the Frr helper";
 }
 
 1;
diff --git a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
index c245ea29cf90..6f875cb5dbf9 100644
--- a/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/EvpnPlugin.pm
@@ -9,6 +9,7 @@ use PVE::Tools qw(run_command file_set_contents file_get_contents);
 use PVE::RESTEnvironment qw(log_warn);
 
 use PVE::Network::SDN::Controllers::Plugin;
+use PVE::Network::SDN::Controllers::Frr;
 use PVE::Network::SDN::Zones::Plugin;
 use Net::IP;
 
@@ -109,6 +110,7 @@ sub generate_controller_config {
     push @controller_config, "neighbor VTEP route-map MAP_VTEP_IN in";
     push @controller_config, "neighbor VTEP route-map MAP_VTEP_OUT out";
     push @controller_config, "advertise-all-vni";
+    # https://datatracker.ietf.org/doc/html/rfc8365#section-5.1.2.1
     push @controller_config, "autort as $autortas" if $autortas;
     push(@{$bgp->{"address-family"}->{"l2vpn evpn"}}, @controller_config);
 
@@ -195,7 +197,7 @@ sub generate_controller_zone_config {
     push @controller_config, "no bgp hard-administrative-reset";
     push @controller_config, "no bgp graceful-restart notification";
 
-#    push @controller_config, "!";
+    #push @controller_config, "!";
     push(@{$config->{frr}->{router}->{"bgp $asn vrf $vrf"}->{""}}, @controller_config);
 
     if ($autortas) {
@@ -204,7 +206,6 @@ sub generate_controller_zone_config {
     }
 
     if ($is_gateway) {
-
 	$config->{frr_prefix_list}->{'only_default'}->{1} = "permit 0.0.0.0/0";
 	$config->{frr_prefix_list_v6}->{'only_default_v6'}->{1} = "permit ::/0";
 
@@ -356,291 +357,17 @@ sub find_isis_controller {
     return $res;
 }
 
-sub generate_frr_recurse{
-   my ($final_config, $content, $parentkey, $level) = @_;
-
-   my $keylist = {};
-   $keylist->{'address-family'} = 1;
-   $keylist->{router} = 1;
-
-   my $exitkeylist = {};
-   $exitkeylist->{'address-family'} = 1;
-
-   my $simple_exitkeylist = {};
-   $simple_exitkeylist->{router} = 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 keys %$content) {
-	    next if $key eq 'vrf';
-	    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});
-	    push @{$final_config}, $padding."exit" if $parentkey && defined($simple_exitkeylist->{$parentkey});
-	}
-    }
-
-    if (ref $content eq 'ARRAY') {
-	push @{$final_config}, map { $padding . "$_" } @$content;
-    }
-}
-
-sub generate_frr_vrf {
-   my ($final_config, $vrfs) = @_;
-
-   return if !$vrfs;
-
-   my @config = ();
-
-   foreach my $id (sort keys %$vrfs) {
-	my $vrf = $vrfs->{$id};
-	push @config, "!";
-	push @config, "vrf $id";
-	foreach my $rule (@$vrf) {
-	    push @config, " $rule";
-
-	}
-	push @config, "exit-vrf";
-    }
-
-    push @{$final_config}, @config;
-}
-
-sub generate_frr_simple_list {
-   my ($final_config, $rules) = @_;
-
-   return if !$rules;
-
-   my @config = ();
-   push @{$final_config}, "!";
-   foreach my $rule (sort @$rules) {
-	push @{$final_config}, $rule;
-   }
-}
-
-sub generate_frr_interfaces {
-   my ($final_config, $interfaces) = @_;
-
-   foreach my $k (sort keys %$interfaces) {
-	my $iface = $interfaces->{$k};
-	push @{$final_config}, "!";
-	push @{$final_config}, "interface $k";
-	foreach my $rule (sort @$iface) {
-	    push @{$final_config}, " $rule";
-	}
-   }
-}
-
-sub generate_frr_routemap {
-   my ($final_config, $routemaps) = @_;
-
-   foreach my $id (sort keys %$routemaps) {
-
-	my $routemap = $routemaps->{$id};
-	my $order = 0;
-	foreach my $seq (@$routemap) {
-		$order++;
-		next if !defined($seq->{action});
-		my @config = ();
-		push @config, "!";
-		push @config, "route-map $id $seq->{action} $order";
-		my $rule = $seq->{rule};
-		push @config, map { " $_" } @$rule;
-		push @{$final_config}, @config;
-		push @{$final_config}, "exit";
-	}
-   }
-}
-
-sub generate_frr_list {
-    my ($final_config, $lists, $type) = @_;
-
-    my $config = [];
-
-    for my $id (sort keys %$lists) {
-	my $list = $lists->{$id};
-
-	for my $seq (sort keys %$list) {
-	    my $rule = $list->{$seq};
-	    push @$config, "$type $id seq $seq $rule";
-	}
-    }
-
-    if (@$config > 0) {
-	push @{$final_config}, "!", @$config;
-    }
-}
-
-sub read_local_frr_config {
-    if (-e "/etc/frr/frr.conf.local") {
-	return file_get_contents("/etc/frr/frr.conf.local");
-    }
-};
-
 sub generate_controller_rawconfig {
     my ($class, $plugin_config, $config) = @_;
-
-    my $nodename = PVE::INotify::nodename();
-
-    my $final_config = [];
-    push @{$final_config}, "frr version 8.5.2";
-    push @{$final_config}, "frr defaults datacenter";
-    push @{$final_config}, "hostname $nodename";
-    push @{$final_config}, "log syslog informational";
-    push @{$final_config}, "service integrated-vtysh-config";
-    push @{$final_config}, "!";
-
-    my $local_conf = read_local_frr_config();
-    if ($local_conf) {
-	parse_merge_frr_local_config($config, $local_conf);
-    }
-
-    generate_frr_vrf($final_config, $config->{frr}->{vrf});
-    generate_frr_interfaces($final_config, $config->{frr_interfaces});
-    generate_frr_recurse($final_config, $config->{frr}, undef, 0);
-    generate_frr_list($final_config, $config->{frr_access_list}, "access-list");
-    generate_frr_list($final_config, $config->{frr_prefix_list}, "ip prefix-list");
-    generate_frr_list($final_config, $config->{frr_prefix_list_v6}, "ipv6 prefix-list");
-    generate_frr_simple_list($final_config, $config->{frr_bgp_community_list});
-    generate_frr_routemap($final_config, $config->{frr_routemap});
-    generate_frr_simple_list($final_config, $config->{frr_ip_protocol});
-
-    push @{$final_config}, "!";
-    push @{$final_config}, "line vty";
-    push @{$final_config}, "!";
-
-    my $rawconfig = join("\n", @{$final_config});
-
-    return if !$rawconfig;
-    return $rawconfig;
-}
-
-sub parse_merge_frr_local_config {
-    my ($config, $local_conf) = @_;
-
-    my $section = \$config->{""};
-    my $router = undef;
-    my $routemap = undef;
-    my $routemap_config = ();
-    my $routemap_action = undef;
-
-    while ($local_conf =~ /^\s*(.+?)\s*$/gm) {
-        my $line = $1;
-	$line =~ s/^\s+|\s+$//g;
-
-	if ($line =~ m/^router (.+)$/) {
-	    $router = $1;
-	    $section = \$config->{'frr'}->{'router'}->{$router}->{""};
-	    next;
-	} elsif ($line =~ m/^vrf (.+)$/) {
-	    $section = \$config->{'frr'}->{'vrf'}->{$1};
-	    next;
-	} elsif ($line =~ m/^interface (.+)$/) {
-	    $section = \$config->{'frr_interfaces'}->{$1};
-	    next;
-	} elsif ($line =~ m/^bgp community-list (.+)$/) {
-	    push(@{$config->{'frr_bgp_community_list'}}, $line);
-	    next;
-	} elsif ($line =~ m/address-family (.+)$/) {
-	    $section = \$config->{'frr'}->{'router'}->{$router}->{'address-family'}->{$1};
-	    next;
-	} elsif ($line =~ m/^route-map (.+) (permit|deny) (\d+)/) {
-	    $routemap = $1;
-	    $routemap_config = ();
-	    $routemap_action = $2;
-	    $section = \$config->{'frr_routemap'}->{$routemap};
-	    next;
-	} elsif ($line =~ m/^access-list (.+) seq (\d+) (.+)$/) {
-	    $config->{'frr_access_list'}->{$1}->{$2} = $3;
-	    next;
-	} elsif ($line =~ m/^ip prefix-list (.+) seq (\d+) (.*)$/) {
-	    $config->{'frr_prefix_list'}->{$1}->{$2} = $3;
-	    next;
-	} elsif ($line =~ m/^ipv6 prefix-list (.+) seq (\d+) (.*)$/) {
-	    $config->{'frr_prefix_list_v6'}->{$1}->{$2} = $3;
-	    next;
-	} elsif($line =~ m/^exit-address-family$/) {
-	    next;
-	} elsif($line =~ m/^exit$/) {
-	    if($router) {
-		$section = \$config->{''};
-		$router = undef;
-	    } elsif($routemap) {
-		push(@{$$section}, { rule => $routemap_config, action => $routemap_action });
-		$section = \$config->{''};
-		$routemap = undef;
-		$routemap_action = undef;
-		$routemap_config = ();
-	    }
-	    next;
-	} elsif($line =~ m/!/) {
-	    next;
-	}
-
-	next if !$section;
-	if($routemap) {
-	    push(@{$routemap_config}, $line);
-	} else {
-	    push(@{$$section}, $line);
-	}
-    }
+    #return PVE::Network::SDN::Controllers::Frr::generate_controller_rawconfig($class, $plugin_config, $config);
+    die "implemented in the Frr helper";
 }
 
 sub write_controller_config {
     my ($class, $plugin_config, $config) = @_;
-
-    my $rawconfig = $class->generate_controller_rawconfig($plugin_config, $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) {
-	log_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) {
-	eval {
-	    run_command([$bin_path, '--stdout', '--reload', $conf_file], outfunc => {}, errfunc => $err);
-	};
-	if ($@) {
-	    warn "frr reload command fail. Restarting frr.";
-	    eval { run_command(['systemctl', 'restart', 'frr']); };
-	}
-    }
+    
+    #return PVE::Network::SDN::Controllers::Frr::write_controller_config($class, $plugin_config, $config);
+    die "implemented in the Frr helper";
 }
 
 1;
diff --git a/src/PVE/Network/SDN/Controllers/Frr.pm b/src/PVE/Network/SDN/Controllers/Frr.pm
new file mode 100644
index 000000000000..386dcae543e8
--- /dev/null
+++ b/src/PVE/Network/SDN/Controllers/Frr.pm
@@ -0,0 +1,296 @@
+package PVE::Network::SDN::Controllers::Frr;
+
+use strict;
+use warnings;
+
+use PVE::RESTEnvironment qw(log_warn);
+use PVE::Tools qw(file_get_contents file_set_contents);
+
+sub read_local_frr_config {
+    if (-e "/etc/frr/frr.conf.local") {
+	return file_get_contents("/etc/frr/frr.conf.local");
+    }
+};
+
+sub reload_controller {
+    my $conf_file = "/etc/frr/frr.conf";
+    my $bin_path = "/usr/lib/frr/frr-reload.py";
+
+    if (!-e $bin_path) {
+	log_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) {
+	eval {
+	    run_command([$bin_path, '--stdout', '--reload', $conf_file], outfunc => {}, errfunc => $err);
+	};
+	if ($@) {
+	    warn "frr reload command fail. Restarting frr.";
+	    eval { run_command(['systemctl', 'restart', 'frr']); };
+	}
+    }
+}
+
+sub generate_controller_rawconfig {
+    my ($config) = @_;
+
+    my $nodename = PVE::INotify::nodename();
+
+    my $final_config = [];
+    push @{$final_config}, "frr version 8.5.2";
+    push @{$final_config}, "frr defaults datacenter";
+    push @{$final_config}, "hostname $nodename";
+    push @{$final_config}, "log syslog informational";
+    push @{$final_config}, "service integrated-vtysh-config";
+    push @{$final_config}, "!";
+
+    my $local_conf = read_local_frr_config();
+    if ($local_conf) {
+	parse_merge_frr_local_config($config, $local_conf);
+    }
+
+    generate_frr_vrf($final_config, $config->{frr}->{vrf});
+    generate_frr_interfaces($final_config, $config->{frr_interfaces});
+    generate_frr_recurse($final_config, $config->{frr}, undef, 0);
+    generate_frr_list($final_config, $config->{frr_access_list}, "access-list");
+    generate_frr_list($final_config, $config->{frr_prefix_list}, "ip prefix-list");
+    generate_frr_list($final_config, $config->{frr_prefix_list_v6}, "ipv6 prefix-list");
+    generate_frr_simple_list($final_config, $config->{frr_bgp_community_list});
+    generate_frr_routemap($final_config, $config->{frr_routemap});
+    generate_frr_simple_list($final_config, $config->{frr_ip_protocol});
+
+    push @{$final_config}, "!";
+    push @{$final_config}, "line vty";
+    push @{$final_config}, "!";
+
+    my $rawconfig = join("\n", @{$final_config});
+
+    return if !$rawconfig;
+    return $rawconfig;
+}
+
+sub parse_merge_frr_local_config {
+    my ($config, $local_conf) = @_;
+
+    my $section = \$config->{""};
+    my $router = undef;
+    my $routemap = undef;
+    my $routemap_config = ();
+    my $routemap_action = undef;
+
+    while ($local_conf =~ /^\s*(.+?)\s*$/gm) {
+        my $line = $1;
+	$line =~ s/^\s+|\s+$//g;
+
+	if ($line =~ m/^router (.+)$/) {
+	    $router = $1;
+	    $section = \$config->{'frr'}->{'router'}->{$router}->{""};
+	    next;
+	} elsif ($line =~ m/^vrf (.+)$/) {
+	    $section = \$config->{'frr'}->{'vrf'}->{$1};
+	    next;
+	} elsif ($line =~ m/^interface (.+)$/) {
+	    $section = \$config->{'frr_interfaces'}->{$1};
+	    next;
+	} elsif ($line =~ m/^bgp community-list (.+)$/) {
+	    push(@{$config->{'frr_bgp_community_list'}}, $line);
+	    next;
+	} elsif ($line =~ m/address-family (.+)$/) {
+	    $section = \$config->{'frr'}->{'router'}->{$router}->{'address-family'}->{$1};
+	    next;
+	} elsif ($line =~ m/^route-map (.+) (permit|deny) (\d+)/) {
+	    $routemap = $1;
+	    $routemap_config = ();
+	    $routemap_action = $2;
+	    $section = \$config->{'frr_routemap'}->{$routemap};
+	    next;
+	} elsif ($line =~ m/^access-list (.+) seq (\d+) (.+)$/) {
+	    $config->{'frr_access_list'}->{$1}->{$2} = $3;
+	    next;
+	} elsif ($line =~ m/^ip prefix-list (.+) seq (\d+) (.*)$/) {
+	    $config->{'frr_prefix_list'}->{$1}->{$2} = $3;
+	    next;
+	} elsif ($line =~ m/^ipv6 prefix-list (.+) seq (\d+) (.*)$/) {
+	    $config->{'frr_prefix_list_v6'}->{$1}->{$2} = $3;
+	    next;
+	} elsif($line =~ m/^exit-address-family$/) {
+	    next;
+	} elsif($line =~ m/^exit$/) {
+	    if($router) {
+		$section = \$config->{''};
+		$router = undef;
+	    } elsif($routemap) {
+		push(@{$$section}, { rule => $routemap_config, action => $routemap_action });
+		$section = \$config->{''};
+		$routemap = undef;
+		$routemap_action = undef;
+		$routemap_config = ();
+	    }
+	    next;
+	} elsif($line =~ m/!/) {
+	    next;
+	}
+
+	next if !$section;
+	if($routemap) {
+	    push(@{$routemap_config}, $line);
+	} else {
+	    push(@{$$section}, $line);
+	}
+    }
+}
+
+sub write_controller_config {
+    my ($config) = @_;
+
+    my $rawconfig = generate_controller_rawconfig($config);
+    return if !$rawconfig;
+    return if !-d "/etc/frr";
+
+    file_set_contents("/etc/frr/frr.conf", $rawconfig);
+}
+
+
+sub generate_frr_recurse{
+   my ($final_config, $content, $parentkey, $level) = @_;
+
+   my $keylist = {};
+   $keylist->{'address-family'} = 1;
+   $keylist->{router} = 1;
+
+   my $exitkeylist = {};
+   $exitkeylist->{'address-family'} = 1;
+
+   my $simple_exitkeylist = {};
+   $simple_exitkeylist->{router} = 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 keys %$content) {
+	    next if $key eq 'vrf';
+	    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});
+	    push @{$final_config}, $padding."exit" if $parentkey && defined($simple_exitkeylist->{$parentkey});
+	}
+    }
+
+    if (ref $content eq 'ARRAY') {
+	push @{$final_config}, map { $padding . "$_" } @$content;
+    }
+}
+
+sub generate_frr_vrf {
+   my ($final_config, $vrfs) = @_;
+
+   return if !$vrfs;
+
+   my @config = ();
+
+   foreach my $id (sort keys %$vrfs) {
+	my $vrf = $vrfs->{$id};
+	push @config, "!";
+	push @config, "vrf $id";
+	foreach my $rule (@$vrf) {
+	    push @config, " $rule";
+
+	}
+	push @config, "exit-vrf";
+    }
+
+    push @{$final_config}, @config;
+}
+
+sub generate_frr_simple_list {
+   my ($final_config, $rules) = @_;
+
+   return if !$rules;
+
+   my @config = ();
+   push @{$final_config}, "!";
+   foreach my $rule (sort @$rules) {
+	push @{$final_config}, $rule;
+   }
+}
+
+sub generate_frr_list {
+    my ($final_config, $lists, $type) = @_;
+
+    my $config = [];
+
+    for my $id (sort keys %$lists) {
+	my $list = $lists->{$id};
+
+	for my $seq (sort keys %$list) {
+	    my $rule = $list->{$seq};
+	    push @$config, "$type $id seq $seq $rule";
+	}
+    }
+
+    if (@$config > 0) {
+	push @{$final_config}, "!", @$config;
+    }
+}
+
+
+sub generate_frr_interfaces {
+   my ($final_config, $interfaces) = @_;
+
+   foreach my $k (sort keys %$interfaces) {
+	my $iface = $interfaces->{$k};
+	push @{$final_config}, "!";
+	push @{$final_config}, "interface $k";
+	foreach my $rule (sort @$iface) {
+	    push @{$final_config}, " $rule";
+	}
+   }
+}
+
+sub generate_frr_routemap {
+   my ($final_config, $routemaps) = @_;
+
+   foreach my $id (sort keys %$routemaps) {
+
+	my $routemap = $routemaps->{$id};
+	my $order = 0;
+	foreach my $seq (@$routemap) {
+		$order++;
+		next if !defined($seq->{action});
+		my @config = ();
+		push @config, "!";
+		push @config, "route-map $id $seq->{action} $order";
+		my $rule = $seq->{rule};
+		push @config, map { " $_" } @$rule;
+		push @{$final_config}, @config;
+		push @{$final_config}, "exit";
+	}
+   }
+}
+
+1;
diff --git a/src/PVE/Network/SDN/Controllers/IsisPlugin.pm b/src/PVE/Network/SDN/Controllers/IsisPlugin.pm
index 97c6876db303..50a11742fff6 100644
--- a/src/PVE/Network/SDN/Controllers/IsisPlugin.pm
+++ b/src/PVE/Network/SDN/Controllers/IsisPlugin.pm
@@ -7,6 +7,7 @@ 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::Frr;
 use PVE::Network::SDN::Controllers::Plugin;
 use PVE::Network::SDN::Zones::Plugin;
 use Net::IP;
@@ -113,19 +114,22 @@ sub on_update_hook {
     }
 }
 
+sub reload_controller {
+    my ($class) = @_;
+    #return PVE::Network::SDN::Controllers::Frr::reload_controller($class);
+    die "implemented in the Frr helper";
+}
+
 sub generate_controller_rawconfig {
     my ($class, $plugin_config, $config) = @_;
-    return "";
+    #return PVE::Network::SDN::Controllers::Frr::generate_controller_rawconfig($class, $plugin_config, $config);
+    die "implemented in the Frr helper";
 }
 
 sub write_controller_config {
     my ($class, $plugin_config, $config) = @_;
-    return;
-}
-
-sub reload_controller {
-    my ($class) = @_;
-    return;
+    #return PVE::Network::SDN::Controllers::Frr::write_controller_config($class, $plugin_config, $config);
+    die "implemented in the Frr helper";
 }
 
 1;
diff --git a/src/PVE/Network/SDN/Controllers/Makefile b/src/PVE/Network/SDN/Controllers/Makefile
index fd9f881a0ad2..3b0387913cdc 100644
--- a/src/PVE/Network/SDN/Controllers/Makefile
+++ b/src/PVE/Network/SDN/Controllers/Makefile
@@ -1,4 +1,4 @@
-SOURCES=Plugin.pm FaucetPlugin.pm EvpnPlugin.pm BgpPlugin.pm IsisPlugin.pm
+SOURCES=Plugin.pm FaucetPlugin.pm EvpnPlugin.pm BgpPlugin.pm IsisPlugin.pm Frr.pm
 
 
 PERL5DIR=${DESTDIR}/usr/share/perl5
diff --git a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm
index 4843756a75bd..6212ba0a02d9 100644
--- a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm
+++ b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm
@@ -323,6 +323,21 @@ sub vnet_update_hook {
     }
 }
 
+sub reload_controller {
+    my ($class) = @_;
+    die "implemented in the Frr helper";
+}
+
+sub generate_controller_rawconfig {
+    my ($class, $config) = @_;
+    die "implemented in the Frr helper";
+}
+
+sub write_controller_config {
+    my ($class, $config) = @_;
+    die "implemented in the Frr helper";
+}
+
 1;
 
 
-- 
2.39.5





More information about the pve-devel mailing list