[pve-devel] [PATCH pve-network v4 14/21] api: fabrics: add root-level module

Stefan Hanreich s.hanreich at proxmox.com
Fri Jul 4 16:32:27 CEST 2025



On 7/2/25 16:50, Gabriel Goller wrote:
> From: Stefan Hanreich <s.hanreich at proxmox.com>
> 
> There is one endpoint (/all) at the top-level that fetches both types
> of fabric entities (fabrics & nodes) and lists them separately. This
> is used for the main view, in order to avoid having to do two API
> calls. It works analogous to the existing root-level SDN API calls
> with the running / pending parameters.
> 
> Also, since the interfaces key is used in the node sections, we need
> to add it to the function encoding the values so they are compared and
> returned from the API properly, when the pending parameter is set.
> 
> Co-authored-by: Gabriel Goller <g.goller at proxmox.com>
> Signed-off-by: Stefan Hanreich <s.hanreich at proxmox.com>
> ---
>  src/PVE/API2/Network/SDN.pm         |   7 ++
>  src/PVE/API2/Network/SDN/Fabrics.pm | 165 ++++++++++++++++++++++++++++
>  src/PVE/API2/Network/SDN/Makefile   |   3 +-
>  src/PVE/Network/SDN.pm              |  10 +-
>  4 files changed, 177 insertions(+), 8 deletions(-)
>  create mode 100644 src/PVE/API2/Network/SDN/Fabrics.pm
> 
> diff --git a/src/PVE/API2/Network/SDN.pm b/src/PVE/API2/Network/SDN.pm
> index 0824410f43cb..6645f28b5de1 100644
> --- a/src/PVE/API2/Network/SDN.pm
> +++ b/src/PVE/API2/Network/SDN.pm
> @@ -17,6 +17,7 @@ use PVE::API2::Network::SDN::Vnets;
>  use PVE::API2::Network::SDN::Zones;
>  use PVE::API2::Network::SDN::Ipams;
>  use PVE::API2::Network::SDN::Dns;
> +use PVE::API2::Network::SDN::Fabrics;
>  
>  use base qw(PVE::RESTHandler);
>  
> @@ -45,6 +46,11 @@ __PACKAGE__->register_method({
>      path => 'dns',
>  });
>  
> +__PACKAGE__->register_method({
> +    subclass => "PVE::API2::Network::SDN::Fabrics",
> +    path => 'fabrics',
> +});
> +
>  __PACKAGE__->register_method({
>      name => 'index',
>      path => '',
> @@ -76,6 +82,7 @@ __PACKAGE__->register_method({
>              { id => 'controllers' },
>              { id => 'ipams' },
>              { id => 'dns' },
> +            { id => 'fabrics' },
>          ];
>  
>          return $res;
> diff --git a/src/PVE/API2/Network/SDN/Fabrics.pm b/src/PVE/API2/Network/SDN/Fabrics.pm
> new file mode 100644
> index 000000000000..a4a972d65cc2
> --- /dev/null
> +++ b/src/PVE/API2/Network/SDN/Fabrics.pm
> @@ -0,0 +1,165 @@
> +package PVE::API2::Network::SDN::Fabrics;
> +
> +use strict;
> +use warnings;
> +
> +use PVE::Tools qw(extract_param);
> +
> +use PVE::Network::SDN;
> +use PVE::Network::SDN::Fabrics;
> +
> +use PVE::RESTHandler;
> +use base qw(PVE::RESTHandler);
> +
> +__PACKAGE__->register_method({
> +    name => 'index',
> +    path => '',
> +    method => 'GET',
> +    permissions => {
> +        check => ['perm', '/sdn/fabrics', ['SDN.Audit']],
> +    },
> +    description => "SDN Fabrics Index",
> +    parameters => {
> +        properties => {},
> +    },
> +    returns => {
> +        type => 'array',
> +        items => {
> +            type => "object",
> +            properties => {
> +                subdir => { type => 'string' },
> +            },
> +        },
> +        links => [{ rel => 'child', href => "{subdir}" }],
> +    },
> +    code => sub {
> +        my ($param) = @_;
> +
> +        my $res = [
> +            { subdir => 'all' },
> +        ];
> +
> +        return $res;
> +    },
> +});
> +
> +__PACKAGE__->register_method({
> +    name => 'list_all',
> +    path => 'all',
> +    method => 'GET',
> +    permissions => {
> +        description =>
> +            "Only list fabrics where you have 'SDN.Audit' or 'SDN.Allocate' permissions on\n"
> +            . "'/sdn/fabrics/<fabric>', only list nodes where you have 'Sys.Audit' or 'Sys.Modify' on /nodes/<node_id>",
> +        user => 'all',
> +    },
> +    description => "SDN Fabrics Index",
> +    parameters => {
> +        properties => {
> +            running => {
> +                type => 'boolean',
> +                optional => 1,
> +                description => "Display running config.",
> +            },
> +            pending => {
> +                type => 'boolean',
> +                optional => 1,
> +                description => "Display pending config.",
> +            },
> +        },
> +    },
> +    returns => {
> +        type => 'object',
> +        properties => {
> +            fabrics => {
> +                type => 'array',
> +                items => {
> +                    type => "object",
> +                    properties => PVE::Network::SDN::Fabrics::fabric_properties(0),
> +                },
> +            },
> +            nodes => {
> +                type => 'array',
> +                items => {
> +                    type => "object",
> +                    properties => PVE::Network::SDN::Fabrics::node_properties(0),
> +                },
> +            },
> +        },
> +    },
> +    code => sub {
> +        my ($param) = @_;
> +
> +        my $pending = extract_param($param, 'pending');
> +        my $running = extract_param($param, 'running');
> +
> +        my $digest;
> +        my $fabrics;
> +        my $nodes;
> +
> +        if ($pending) {
> +            my $current_config = PVE::Network::SDN::Fabrics::config();
> +            my $running_config = PVE::Network::SDN::Fabrics::config(1);
> +
> +            my ($running_fabrics, $running_nodes) = $running_config->list_all();
> +
> +            my ($current_fabrics, $current_nodes) = $current_config->list_all();
> +
> +            my $pending_fabrics = PVE::Network::SDN::pending_config(
> +                { fabrics => { ids => $running_fabrics } },
> +                { ids => $current_fabrics },
> +                'fabrics',
> +            );
> +
> +            my $pending_nodes = PVE::Network::SDN::pending_config(
> +                { nodes => { ids => $running_nodes } },
> +                { ids => $current_nodes },
> +                'nodes',
> +            );
> +
> +            $digest = $current_config->digest();
> +            $fabrics = $pending_fabrics->{ids};
> +            $nodes = $pending_nodes->{ids};
> +        } elsif ($running) {
> +            ($fabrics, $nodes) = PVE::Network::SDN::Fabrics::config(1)->list_all();
> +        } else {
> +            my $current_config = PVE::Network::SDN::Fabrics::config();
> +
> +            ($fabrics, $nodes) = $current_config->list_all();
> +            $digest = $current_config->digest();
> +        }
> +
> +        my $rpcenv = PVE::RPCEnvironment::get();
> +        my $authuser = $rpcenv->get_user();
> +        my $fabric_privs = ['SDN.Audit', 'SDN.Allocate'];
> +        my $node_privs = ['Sys.Audit', 'Sys.Modify'];
> +
> +        my @res_fabrics;
> +        for my $id (keys %$fabrics) {
> +            next if !$rpcenv->check_any($authuser, "/sdn/fabrics/$id", $fabric_privs, 1);
> +
> +            $fabrics->{$id}->{digest} = $digest if $digest;
> +            push @res_fabrics, $fabrics->{$id};
> +        }
> +
> +        my @res_nodes;
> +        for my $node_id (keys %$nodes) {
> +            my $node = $nodes->{$node_id};
> +            my $fabric_id = $node->{fabric_id} // $node->{pending}->{fabric_id};
> +
> +            next if !$rpcenv->check_any($authuser, "/sdn/fabrics/$fabric_id", $fabric_privs, 1);
> +            next if !$rpcenv->check_any($authuser, "/nodes/$node_id", $node_privs, 1);
> +
> +            $node->{digest} = $digest if $digest;
> +
> +            push @res_nodes, $node;
> +        }
> +
> +        return {
> +            fabrics => \@res_fabrics,
> +            nodes => \@res_nodes,
> +        };
> +    },
> +});
> +
> +1;
> diff --git a/src/PVE/API2/Network/SDN/Makefile b/src/PVE/API2/Network/SDN/Makefile
> index abd1bfae020e..08bec7535530 100644
> --- a/src/PVE/API2/Network/SDN/Makefile
> +++ b/src/PVE/API2/Network/SDN/Makefile
> @@ -1,4 +1,4 @@
> -SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm Ips.pm
> +SOURCES=Vnets.pm Zones.pm Controllers.pm Subnets.pm Ipams.pm Dns.pm Ips.pm Fabrics.pm
>  
>  
>  PERL5DIR=${DESTDIR}/usr/share/perl5
> @@ -7,4 +7,5 @@ PERL5DIR=${DESTDIR}/usr/share/perl5
>  install:
>  	for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/API2/Network/SDN/$$i; done
>  	make -C Zones install
> +	make -C Fabrics install
>  
> diff --git a/src/PVE/Network/SDN.pm b/src/PVE/Network/SDN.pm
> index 1a0bc769a252..c6324f1ac5d2 100644
> --- a/src/PVE/Network/SDN.pm
> +++ b/src/PVE/Network/SDN.pm
> @@ -414,15 +414,11 @@ sub encode_value {
>          $type, $key, $value,
>      ) = @_;
>  
> -    if ($key eq 'nodes' || $key eq 'exitnodes' || $key eq 'dhcp-range') {
> +    if ($key eq 'nodes' || $key eq 'exitnodes' || $key eq 'dhcp-range' || $key eq 'interfaces') {
>          if (ref($value) eq 'HASH') {
> -            return join(
> -                ',', sort keys(%$value),
> -            );
> +            return join(',', sort keys(%$value));
>          } elsif (ref($value) eq 'ARRAY') {
> -            return join(
> -                ',', sort @$value,
> -            );
> +            return join(',', sort @$value);

some additional formatting changes here as well?

>          } else {
>              return $value;
>          }





More information about the pve-devel mailing list