public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Gabriel Goller <g.goller@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-network v3 7/9] sdn: adjust frr.conf.local merging to rust template types
Date: Thu,  5 Mar 2026 11:03:23 +0100	[thread overview]
Message-ID: <20260305100331.80741-17-g.goller@proxmox.com> (raw)
In-Reply-To: <20260305100331.80741-1-g.goller@proxmox.com>

The frr object in perl which stores the whole frr config is now also
modeled in rust, so it changed a bit. Adjust the frr.conf.local merging
code so that the frr.conf.local is still merged correctly. This makes
use of the `custom_frr_config` properties scattered in many rust types.
So if we encounter a line in frr.conf.local that we need to merge, we
just throw it into this string vec and render it as-is.

Co-authored-by: Stefan Hanreich <s.hanreich@proxmox.com>
Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
---
 src/PVE/Network/SDN/Frr.pm | 221 +++++++++++++++++++++++++++++++------
 1 file changed, 186 insertions(+), 35 deletions(-)

diff --git a/src/PVE/Network/SDN/Frr.pm b/src/PVE/Network/SDN/Frr.pm
index 69b58345f0ec..14a13d1b7276 100644
--- a/src/PVE/Network/SDN/Frr.pm
+++ b/src/PVE/Network/SDN/Frr.pm
@@ -274,70 +274,221 @@ sub append_local_config {
         return;
     }
 
-    my $section = \$frr_config->{""};
-    my $router = undef;
+    $frr_config->{'frr'}->{'custom_frr_config'} //= [];
+    my $section = \$frr_config->{''};
+    my $isis_router_name = undef;
+    my $bgp_router_asn = undef;
+    my $bgp_router_vrf = undef;
+    my $custom_router_name = undef;
     my $routemap = undef;
-    my $routemap_config = ();
-    my $routemap_action = undef;
+    my $interface = undef;
+    my $vrf = undef;
+    my $new_block = 0;
+    my $new_af_block = 0;
 
-    while ($local_config =~ /^\s*(.+?)\s*$/gm) {
+    while ($local_config =~ /^(.+?)\s*$/gm) {
         my $line = $1;
-        $line =~ s/^\s+|\s+$//g;
-
-        if ($line =~ m/^router (.+)$/) {
-            $router = $1;
-            $section = \$frr_config->{'frr'}->{'router'}->{$router}->{""};
+        $line =~ s/\s+$//g;
+
+        if ($line =~ m/^router isis (.+)$/) {
+            $isis_router_name = $1;
+            if (defined($frr_config->{'frr'}->{'isis'}->{'router'}->{$isis_router_name})) {
+                $section =
+                    \($frr_config->{'frr'}->{'isis'}->{'router'}->{$isis_router_name}
+                        ->{'custom_frr_config'} //= []);
+            } else {
+                $new_block = 1;
+                push(
+                    $frr_config->{'frr'}->{'custom_frr_config'}->@*,
+                    "router isis $isis_router_name",
+                );
+                $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+            }
+            next;
+        } elsif ($line =~ m/^router bgp (\S+)(?: vrf (.+))?$/) {
+            $bgp_router_asn = $1;
+            $bgp_router_vrf = $2 // 'default';
+
+            if (
+                defined($frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf})
+                and $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}->{'asn'}
+                eq $bgp_router_asn
+            ) {
+                $section =
+                    \($frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+                        ->{'custom_frr_config'} //= []);
+            } else {
+                $new_block = 1;
+
+                my $config_line =
+                    defined($2)
+                    ? "router bgp $bgp_router_asn vrf $bgp_router_vrf"
+                    : "router bgp $bgp_router_asn";
+
+                push(
+                    $frr_config->{'frr'}->{'custom_frr_config'}->@*, $config_line,
+                );
+                $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+            }
+            next;
+        } elsif ($line =~ m/^router (.+)$/) {
+            $custom_router_name = $1;
+            $new_block = 1;
+            push(
+                $frr_config->{'frr'}->{'custom_frr_config'}->@*, "router $custom_router_name",
+            );
+            $section = \$frr_config->{'frr'}->{'custom_frr_config'};
             next;
         } elsif ($line =~ m/^vrf (.+)$/) {
-            $section = \$frr_config->{'frr'}->{'vrf'}->{$1};
+            $vrf = $1;
+            if (defined($frr_config->{'frr'}->{'bgp'}->{'vrfs'}->{$vrf})) {
+                $section = \$frr_config->{'frr'}->{'bgp'}->{'vrfs'}->{$vrf}->{'custom_frr_config'};
+            } else {
+                $new_block = 1;
+                push($frr_config->{'frr'}->{'custom_frr_config'}->@*, "vrf $vrf");
+                $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+            }
             next;
         } elsif ($line =~ m/^interface (.+)$/) {
-            $section = \$frr_config->{'frr_interfaces'}->{$1};
+            $interface = $1;
+            if (defined($frr_config->{'frr'}->{'isis'}->{'interfaces'}->{$interface})) {
+                $section = \($frr_config->{'frr'}->{'isis'}->{'interfaces'}->{$interface}
+                    ->{'custom_frr_config'} //= []);
+            } else {
+                $new_block = 1;
+                push(
+                    $frr_config->{'frr'}->{'custom_frr_config'}->@*, "interface $interface",
+                );
+                $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+            }
             next;
         } elsif ($line =~ m/^bgp community-list (.+)$/) {
-            push(@{ $frr_config->{'frr_bgp_community_list'} }, $line);
+            push(@{ $frr_config->{'frr'}->{'custom_frr_config'} }, $line);
             next;
         } elsif ($line =~ m/address-family (.+)$/) {
-            $section = \$frr_config->{'frr'}->{'router'}->{$router}->{'address-family'}->{$1};
+            # convert the address family from frr (e.g. l2vpn evpn) into the rust property (e.g. l2vpn_evpn)
+            my $address_family_unchanged = $1;
+            my $address_family = $1 =~ s/ /_/gr;
+
+            if (
+                defined($frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf})
+                and $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}->{'asn'}
+                eq $bgp_router_asn
+            ) {
+                if (defined(
+                    $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+                        ->{'address_families'}->{$address_family}
+                )) {
+                    $section =
+                        \($frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+                            ->{'address_families'}->{$address_family}->{'custom_frr_config'} //=
+                            []);
+                } else {
+                    $new_af_block = 1;
+                    push(
+                        $frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+                            ->{'custom_frr_config'}->@*,
+                        " address-family $address_family_unchanged",
+                    );
+                    $section = \$frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+                        ->{'custom_frr_config'};
+                }
+            } else {
+                $new_af_block = 1;
+                push(
+                    $frr_config->{'frr'}->{'custom_frr_config'}->@*,
+                    " address-family $address_family_unchanged",
+                );
+                $section = \$frr_config->{'frr'}->{'custom_frr_config'};
+            }
             next;
         } elsif ($line =~ m/^route-map (.+) (permit|deny) (\d+)/) {
             $routemap = $1;
-            $routemap_config = ();
-            $routemap_action = $2;
-            $section = \$frr_config->{'frr_routemap'}->{$routemap};
+            my $routemap_action = $2;
+            my $seq_number = $3;
+            # NEVER merge the route-maps, we always just add them to the
+            # custom_routemaps map so that we can push them at the very end.
+            $new_block = 1;
+            push(
+                $custom_routemaps->{$routemap}->@*,
+                "route-map $routemap $routemap_action $seq_number",
+            );
+            $section = \$custom_routemaps->{$routemap};
             next;
         } elsif ($line =~ m/^access-list (.+) seq (\d+) (.+)$/) {
-            $frr_config->{'frr_access_list'}->{$1}->{$2} = $3;
+            push($frr_config->{'frr'}->{'custom_frr_config'}->@*, $line);
             next;
         } elsif ($line =~ m/^ip prefix-list (.+) seq (\d+) (.*)$/) {
-            $frr_config->{'frr_prefix_list'}->{$1}->{$2} = $3;
+            push($frr_config->{'frr'}->{'custom_frr_config'}->@*, $line);
             next;
         } elsif ($line =~ m/^ipv6 prefix-list (.+) seq (\d+) (.*)$/) {
-            $frr_config->{'frr_prefix_list_v6'}->{$1}->{$2} = $3;
+            push($frr_config->{'frr'}->{'custom_frr_config'}->@*, $line);
             next;
-        } elsif ($line =~ m/^exit-address-family$/) {
+        } elsif ($line =~ m/exit-address-family$/) {
+            if ($new_af_block) {
+                push(@{$$section}, $line);
+                $section = \$frr_config->{'frr'}->{'bgp'}->{'custom_frr_config'};
+            } else {
+                $section =
+                    \($frr_config->{'frr'}->{'bgp'}->{'vrf_router'}->{$bgp_router_vrf}
+                        ->{'custom_frr_config'} //= []);
+            }
+            $new_af_block = 0;
             next;
-        } elsif ($line =~ m/^exit$/) {
-            if ($router) {
-                $section = \$frr_config->{''};
-                $router = undef;
-            } elsif ($routemap) {
-                push(@{$$section}, { rule => $routemap_config, action => $routemap_action });
-                $section = \$frr_config->{''};
-                $routemap = undef;
-                $routemap_action = undef;
-                $routemap_config = ();
+        } elsif ($line =~ m/^exit/) {
+            # this means we just added a new router/vrf/interface/routemap
+            if ($new_block) {
+                push(@{$$section}, $line);
+                push(@{$$section}, "!");
             }
+            $section = \$frr_config->{''};
+            # we can't stack these, so exit out of all of them (technically we can have a vrf inside of a router bgp block, but we don't support that)
+            $isis_router_name = undef;
+            $bgp_router_vrf = undef;
+            $bgp_router_asn = undef;
+            $custom_router_name = undef;
+            $vrf = undef;
+            $interface = undef;
+            $routemap = undef;
+            $new_block = 0;
             next;
         } elsif ($line =~ m/!/) {
             next;
         }
 
         next if !$section;
-        if ($routemap) {
-            push(@{$routemap_config}, $line);
-        } else {
-            push(@{$$section}, $line);
+        push(@{$$section}, $line);
+    }
+
+    # go through custom_routemaps, which holds generated and override routemaps.
+    # We need to sort by name and then give each rule in the route-map name a
+    # ascending seq number. If we have a routemap generated by the perl code, we
+    # get an object and need to change the seq property (this is rendered using
+    # the templates). If we have a override route-map (from frr.conf.local), then
+    # we just get the strings of the lines and we need to parse it again and write
+    # with the correct seq.
+    for my $rm (sort keys %{$custom_routemaps}) {
+        my $seq = 1;
+        my $entry = $custom_routemaps->{$rm};
+        for my $rm_line (@{$entry}) {
+            if (!ref($rm_line)) {
+                if ($rm_line =~ m/^route-map (.+) (permit|deny) (\d+)/) {
+                    my $name = $1;
+                    my $action = $2;
+                    push(
+                        $frr_config->{'frr'}->{'custom_frr_config'}->@*,
+                        "route-map $name $action $seq",
+                    );
+                    $seq++;
+                } else {
+                    push($frr_config->{'frr'}->{'custom_frr_config'}->@*, $rm_line);
+                }
+            } else {
+                for my $rm_obj_entry (@{$$rm_line}) {
+                    $rm_obj_entry->{seq} = $seq;
+                    $seq++;
+                }
+            }
         }
     }
 }
-- 
2.47.3





  parent reply	other threads:[~2026-03-05 10:03 UTC|newest]

Thread overview: 20+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-05 10:03 [PATCH manager/network/proxmox{-ve-rs,-perl-rs} v3 00/19] Generate frr config using jinja templates and rust types Gabriel Goller
2026-03-05 10:03 ` [PATCH proxmox-ve-rs v3 1/8] ve-config: firewall: cargo fmt Gabriel Goller
2026-03-05 10:03 ` [PATCH proxmox-ve-rs v3 2/8] frr: add proxmox-frr-templates package that contains templates Gabriel Goller
2026-03-05 10:03 ` [PATCH proxmox-ve-rs v3 3/8] ve-config: remove FrrConfigBuilder struct Gabriel Goller
2026-03-05 10:03 ` [PATCH proxmox-ve-rs v3 4/8] sdn-types: support variable-length NET identifier Gabriel Goller
2026-03-05 10:03 ` [PATCH proxmox-ve-rs v3 5/8] frr: add template serializer and serialize fabrics using templates Gabriel Goller
2026-03-05 10:03 ` [PATCH proxmox-ve-rs v3 6/8] frr: add isis configuration and templates Gabriel Goller
2026-03-05 10:03 ` [PATCH proxmox-ve-rs v3 7/8] frr: support custom frr configuration lines Gabriel Goller
2026-03-05 10:03 ` [PATCH proxmox-ve-rs v3 8/8] frr: add bgp support with templates and serialization Gabriel Goller
2026-03-05 10:03 ` [PATCH proxmox-perl-rs v3 1/1] sdn: add function to generate the frr config for all daemons Gabriel Goller
2026-03-05 10:03 ` [PATCH pve-network v3 1/9] tests: use Test::Differences to make test assertions Gabriel Goller
2026-03-05 10:03 ` [PATCH pve-network v3 2/9] test: add test for frr.conf.local merging Gabriel Goller
2026-03-05 10:03 ` [PATCH pve-network v3 3/9] test: bgp: add some various integration tests Gabriel Goller
2026-03-05 10:03 ` [PATCH pve-network v3 4/9] sdn: write structured frr config that can be rendered using templates Gabriel Goller
2026-03-05 10:03 ` [PATCH pve-network v3 5/9] sdn: remove duplicate comment line '!' in frr config Gabriel Goller
2026-03-05 10:03 ` [PATCH pve-network v3 6/9] tests: rearrange some statements in the " Gabriel Goller
2026-03-05 10:03 ` Gabriel Goller [this message]
2026-03-05 10:03 ` [PATCH pve-network v3 8/9] test: adjust frr_local_merge test for new template generation Gabriel Goller
2026-03-05 10:03 ` [PATCH pve-network v3 9/9] api: add dry-run endpoint for sdn apply to preview changes Gabriel Goller
2026-03-05 10:03 ` [PATCH pve-manager v3 1/1] sdn: add dry-run diff view for sdn apply Gabriel Goller

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260305100331.80741-17-g.goller@proxmox.com \
    --to=g.goller@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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