From mboxrd@z Thu Jan  1 00:00:00 1970
Return-Path: <pve-devel-bounces@lists.proxmox.com>
Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9])
	by lore.proxmox.com (Postfix) with ESMTPS id A7A491FF165
	for <inbox@lore.proxmox.com>; Wed, 12 Feb 2025 12:18:11 +0100 (CET)
Received: from firstgate.proxmox.com (localhost [127.0.0.1])
	by firstgate.proxmox.com (Proxmox) with ESMTP id EBC0512BE5;
	Wed, 12 Feb 2025 12:18:06 +0100 (CET)
Message-ID: <ebe83020-28b3-4e02-a3d1-4de9e76ebf8e@proxmox.com>
Date: Wed, 12 Feb 2025 12:17:58 +0100
MIME-Version: 1.0
User-Agent: Mozilla Thunderbird
To: Proxmox VE development discussion <pve-devel@lists.proxmox.com>,
 Gabriel Goller <g.goller@proxmox.com>
References: <20250205161340.740740-1-g.goller@proxmox.com>
Content-Language: en-US
From: Stefan Hanreich <s.hanreich@proxmox.com>
In-Reply-To: <20250205161340.740740-1-g.goller@proxmox.com>
X-SPAM-LEVEL: Spam detection results:  0
 AWL 0.507 Adjusted score from AWL reputation of From: address
 BAYES_00                 -1.9 Bayes spam probability is 0 to 1%
 DMARC_MISSING             0.1 Missing DMARC policy
 KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment
 POISEN_SPAM_PILL          0.1 Meta: its spam
 POISEN_SPAM_PILL_1        0.1 random spam to be learned in bayes
 POISEN_SPAM_PILL_3        0.1 random spam to be learned in bayes
 SPF_HELO_NONE           0.001 SPF: HELO does not publish an SPF Record
 SPF_PASS               -0.001 SPF: sender matches SPF record
Subject: Re: [pve-devel] [PATCH network] sdn: factor out frr config
 generation and writing
X-BeenThere: pve-devel@lists.proxmox.com
X-Mailman-Version: 2.1.29
Precedence: list
List-Id: Proxmox VE development discussion <pve-devel.lists.proxmox.com>
List-Unsubscribe: <https://lists.proxmox.com/cgi-bin/mailman/options/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=unsubscribe>
List-Archive: <http://lists.proxmox.com/pipermail/pve-devel/>
List-Post: <mailto:pve-devel@lists.proxmox.com>
List-Help: <mailto:pve-devel-request@lists.proxmox.com?subject=help>
List-Subscribe: <https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel>, 
 <mailto:pve-devel-request@lists.proxmox.com?subject=subscribe>
Reply-To: Proxmox VE development discussion <pve-devel@lists.proxmox.com>
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit
Errors-To: pve-devel-bounces@lists.proxmox.com
Sender: "pve-devel" <pve-devel-bounces@lists.proxmox.com>

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@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?



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel