[pve-devel] [PATCH network] sdn: factor out frr config generation and writing
Stefan Hanreich
s.hanreich at proxmox.com
Wed Feb 12 12:17:58 CET 2025
This still has some issues (see below), maybe we can look at it together
next week (will be gone after today) and see if we can make some
additional structural improvements to the whole controller / frr logic?
There were also some regressions/bugs with FRR config generation while
testing, so this series needs some work still.
On 2/5/25 17:13, Gabriel Goller wrote:
> 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) {
this doesn't work. you need:
grep {$_ eq $type} @frr_types
(or what thomas said is even better - just for future reference)
> + $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 {
This actually never gets called (except in the tests, ..). It works, but
still takes the old code path without going through the new FRR helper.
Probably not the intention?
> 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) {
grep issue again
> + $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) {
grep issue again
> + $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;
Those methods are only needed in the Controllers, not Zones - they serve
no purpose here afaict?
More information about the pve-devel
mailing list