public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Gabriel Goller <g.goller@proxmox.com>
To: Stefan Hanreich <s.hanreich@proxmox.com>
Cc: pve-devel@lists.proxmox.com
Subject: Re: [PATCH pve-network v2 6/9] sdn: adjust frr.conf.local merging to rust template types
Date: Wed, 4 Mar 2026 15:37:44 +0100	[thread overview]
Message-ID: <gbdpkbxkuyddh3snrsjaoqqn3urgi3aiv3jn34siitsn4xsorg@t5nt5xjfze3s> (raw)
In-Reply-To: <35bbc676-4261-4887-a5a4-9553d026a5bd@proxmox.com>

On 03.03.2026 16:19, Stefan Hanreich wrote:
> On 3/2/26 1:56 PM, Gabriel Goller wrote:
> > 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 | 202 ++++++++++++++++++++++++++++++-------
> >  1 file changed, 168 insertions(+), 34 deletions(-)
> > 
> > diff --git a/src/PVE/Network/SDN/Frr.pm b/src/PVE/Network/SDN/Frr.pm
> > index b572f4536004..af3074017ddf 100644
> > --- a/src/PVE/Network/SDN/Frr.pm
> > +++ b/src/PVE/Network/SDN/Frr.pm
> > @@ -271,70 +271,204 @@ sub append_local_config {
> >      return if !$local_config;
> >  
> >      my $section = \$frr_config->{""};
> > -    my $router = undef;
> > +    my $isis_router_name = undef;
> > +    my $bgp_router_asn = undef;
> > +    my $bgp_router_vrf = 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}->{""};
> 
> after this change merging now only works for isis and bgp routers? Did a
> quick check with other routing protocols in frr.conf.local and the
> routers seem to be missing.

Good catch, fixed this!

> > +        $line =~ s/\s+$//g;
> > +
> > +        if ($line =~ m/^router isis (.+)$/) {
> > +            $isis_router_name = $1;
> > +            if (defined $frr_config->{'frr'}->{'isis'}->{'router'}->{$isis_router_name}) {
> 
> calls to defined should use parentheses (several occurences below)

done!

> > +                $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';
> > +
> > +            my $config_line =
> > +                defined($2)
> > +                ? "router bgp $bgp_router_asn vrf $bgp_router_vrf"
> > +                : "router bgp $bgp_router_asn";
> > +
> 
> this is only required in the else branch?

moved it down.

> > +            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;
> > +                push(
> > +                    $frr_config->{'frr'}->{'custom_frr_config'}->@*, $config_line,
> > +                );
> > +                $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}) {
> 
> trying to override an existing IS-IS interface doesn't work for me, e.g.
> with the following IS-IS controller:
> 
> isis: isisberserker
>         isis-domain 1
>         isis-ifaces ens19,ens20
>         isis-net 49.0000.1234.0000.00
>         node berserker
> 
> and the following frr.conf.local:
> 
> interface ens20
>   isis circuit-type level-2-only
> exit
> !
> 
> I get an deserialization error:
> 
> error: invalid type: Option value, expected a sequence
> 
> 
> This seems to be because the generated $frr_config has
> 
>     'custom_frr_config' => undef
> 
> in its top-level.

fixed this.

> > +                $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;
> > +            if (defined $frr_config->{'frr'}->{'routemaps'}->{$routemap}) {
> > +                my $index = 0;
> > +                foreach my $single_routemap ($frr_config->{'frr'}->{'routemaps'}->{$routemap}->@*) {
> 
> for is preferred over foreach, see:
> https://pve.proxmox.com/wiki/Perl_Style_Guide#Perl_syntax_choices

done.

> > +                    if (
> > +                        $single_routemap->{'seq'} == $seq_number
> > +                        && $single_routemap->{'action'} eq $routemap_action
> > +                    ) {
> > +                        last;
> > +                    }
> > +                    $index++;
> > +                }
> > +                if ($index < scalar @{ $frr_config->{'frr'}->{'routemaps'}->{$routemap} }) {
> > +                    $section = \($frr_config->{'frr'}->{'routemaps'}->{$routemap}->[$index]
> > +                        ->{'custom_frr_config'} //= []);
> > +                } else {
> > +                    $new_block = 1;
> > +                    push(
> > +                        $frr_config->{'frr'}->{'custom_frr_config'}->@*,
> > +                        "route-map $routemap $routemap_action $seq_number",
> > +                    );
> > +                    $section = \$frr_config->{'frr'}->{'custom_frr_config'};
> > +                }
> > +            } else {
> > +                $new_block = 1;
> > +                push(
> > +                    $frr_config->{'frr'}->{'custom_frr_config'}->@*,
> > +                    "route-map $routemap $routemap_action $seq_number",
> > +                );
> > +                $section = \$frr_config->{'frr'}->{'custom_frr_config'};
> > +            }
> >              next;
> 
> adding entries for an existing route-map (i.e. MAP_VTEP_IN) like so:
> 
> route-map MAP_VTEP_IN deny 1
> exit
> !
> route-map MAP_VTEP_IN permit 1
> exit
> !
> route-map MAP_VTEP_IN permit 1
>  match community cm-prefmod-400
>  set local-preference 400
> exit
> !
> route-map MAP_VTEP_IN permit 1
>  match ip address 10
>  set local-preference 200
> exit
> !
> 
> merges the route-map configuration with this patch, if the verdict is
> the same:
> 
> route-map MAP_VTEP_IN permit 1
>  match community cm-prefmod-400
>  set local-preference 400
>  match ip address 10
>  set local-preference 200
> exit
> !
> 
> the section with the same seq nr, but different verdict gets
> additionally added:
> 
> route-map MAP_VTEP_IN deny 1
> exit
> !
> 
> ------------------------------------------
> 
> with the old merging logic, they were added as separate sections, with
> increasing numbers:
> 
> route-map MAP_VTEP_IN permit 1
> exit
> !
> route-map MAP_VTEP_IN deny 2
> exit
> !
> route-map MAP_VTEP_IN permit 3
> exit
> !
> route-map MAP_VTEP_IN permit 4
>  match community cm-prefmod-400
>  set local-preference 400
> exit
> !
> route-map MAP_VTEP_IN permit 5
>  match ip address 10
>  set local-preference 200
> exit
> !
> 
> Is this intentional? Imo this is quite the breaking change, particularly
> because route-maps are probably used relatively often in the frr.conf.local?

fixed this as well I hope.

> >          } 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->{''};
> > +        } elsif ($line =~ m/^exit/) {
> > +            if ($bgp_router_vrf || $vrf || $interface || $routemap || $isis_router_name) {
> > +                # this means we just added a new router/vrf/interface/routemap
> > +                if ($new_block) {
> > +                    push(@{$$section}, $line);
> > +                    push(@{$$section}, "!");
> > +                }
> > +                $section = \$frr_config->{'frr'}->{'custom_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;
> > +                $vrf = undef;
> > +                $interface = undef;
> >                  $routemap = undef;
> > -                $routemap_action = undef;
> > -                $routemap_config = ();
> > +            } else {
> > +                $section = \$frr_config->{'frr'}->{'custom_frr_config'};
> > +                push(@{$$section}, $line);
> > +                push(@{$$section}, "!");
> >              }
> > +            $new_block = 0;
> >              next;
> >          } elsif ($line =~ m/!/) {
> >              next;
> >          }
> >  
> >          next if !$section;
> > -        if ($routemap) {
> > -            push(@{$routemap_config}, $line);
> > -        } else {
> > -            push(@{$$section}, $line);
> > -        }
> > +        push(@{$$section}, $line);
> >      }
> >  }
> >  

Will send a new version soon!




  reply	other threads:[~2026-03-04 14:37 UTC|newest]

Thread overview: 22+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-02 12:55 [PATCH manager/network/proxmox{-ve-rs,-perl-rs} v2 00/19] Generate frr config using jinja templates and rust types Gabriel Goller
2026-03-02 12:55 ` [PATCH proxmox-ve-rs v2 1/8] ve-config: firewall: cargo fmt Gabriel Goller
2026-03-02 12:55 ` [PATCH proxmox-ve-rs v2 2/8] frr: add proxmox-frr-templates package that contains templates Gabriel Goller
2026-03-02 12:55 ` [PATCH proxmox-ve-rs v2 3/8] ve-config: remove FrrConfigBuilder struct Gabriel Goller
2026-03-02 12:55 ` [PATCH proxmox-ve-rs v2 4/8] sdn-types: support variable-length NET identifier Gabriel Goller
2026-03-02 12:55 ` [PATCH proxmox-ve-rs v2 5/8] frr: add template serializer and serialize fabrics using templates Gabriel Goller
2026-03-02 12:55 ` [PATCH proxmox-ve-rs v2 6/8] frr: add isis configuration and templates Gabriel Goller
2026-03-02 12:55 ` [PATCH proxmox-ve-rs v2 7/8] frr: support custom frr configuration lines Gabriel Goller
2026-03-02 12:55 ` [PATCH proxmox-ve-rs v2 8/8] frr: add bgp support with templates and serialization Gabriel Goller
2026-03-02 12:55 ` [PATCH proxmox-perl-rs v2 1/1] sdn: add function to generate the frr config for all daemons Gabriel Goller
2026-03-02 12:55 ` [PATCH pve-network v2 1/9] sdn: remove duplicate comment line '!' in frr config Gabriel Goller
2026-03-02 12:55 ` [PATCH pve-network v2 2/9] sdn: tests: add missing comment " Gabriel Goller
2026-03-02 12:55 ` [PATCH pve-network v2 3/9] tests: use Test::Differences to make test assertions Gabriel Goller
2026-03-02 12:55 ` [PATCH pve-network v2 4/9] sdn: write structured frr config that can be rendered using templates Gabriel Goller
2026-03-02 12:55 ` [PATCH pve-network v2 5/9] tests: rearrange some statements in the frr config Gabriel Goller
2026-03-02 12:55 ` [PATCH pve-network v2 6/9] sdn: adjust frr.conf.local merging to rust template types Gabriel Goller
2026-03-03 15:19   ` Stefan Hanreich
2026-03-04 14:37     ` Gabriel Goller [this message]
2026-03-02 12:55 ` [PATCH pve-network v2 7/9] api: add dry-run endpoint for sdn apply to preview changes Gabriel Goller
2026-03-02 12:55 ` [PATCH pve-network v2 8/9] test: add test for frr.conf.local merging Gabriel Goller
2026-03-02 12:55 ` [PATCH pve-network v2 9/9] test: bgp: add some various integration tests Gabriel Goller
2026-03-02 12:55 ` [PATCH pve-manager v2 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=gbdpkbxkuyddh3snrsjaoqqn3urgi3aiv3jn34siitsn4xsorg@t5nt5xjfze3s \
    --to=g.goller@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    --cc=s.hanreich@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