public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: David Riley <d.riley@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-manager v3 10/12] fix #7294: api: pool: add SDN VNets as pool members
Date: Fri, 24 Jul 2026 16:25:26 +0200	[thread overview]
Message-ID: <20260724142528.109453-11-d.riley@proxmox.com> (raw)
In-Reply-To: <20260724142528.109453-1-d.riley@proxmox.com>

Extend the pool API to accept SDN VNets as pool members under the
'network' parameter.

Accept comma-separated lists of network resources for VNets. These can
include optional VLAN tags or a wildcard (*) to propagate access to
all untagged and tagged traffic.

Unlike VMs or containers which strictly belong to a single pool, VNets
are shared similar to storage. A single VNet can be assigned to
multiple pools simultaneously, allowing cross-team usage without
management conflicts.

Enforce a cluster-wide version check before allowing network
assignments. This prevents older nodes from accidentally overwriting
the newly structured pool configurations.

Suggested-by: Daniel Kral <d.kral@proxmox.com>
Signed-off-by: David Riley <d.riley@proxmox.com>

Link: https://bugzilla.proxmox.com/show_bug.cgi?id=7294
---
 PVE/API2/Pool.pm | 152 +++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 147 insertions(+), 5 deletions(-)

diff --git a/PVE/API2/Pool.pm b/PVE/API2/Pool.pm
index 63aff5bb..f34ca609 100644
--- a/PVE/API2/Pool.pm
+++ b/PVE/API2/Pool.pm
@@ -7,7 +7,12 @@ use PVE::AccessControl;
 use PVE::Cluster qw (cfs_read_file cfs_write_file);
 use PVE::Exception qw(raise_param_exc);
 use PVE::INotify;
+use PVE::JSONSchema;
+use PVE::Network;
+use PVE::Network::SDN::Vnets;
+use PVE::Network::SDN::Zones;
 use PVE::Storage;
+use PVE::Tools;
 
 use PVE::SafeSyslog;
 
@@ -16,6 +21,42 @@ use PVE::RESTHandler;
 
 use base qw(PVE::RESTHandler);
 
+use constant NETWORK_VNET_REGEX => qr!^vnet/([^/]+)/([^/]+)(?:/([0-9]+|\*))?$!;
+
+sub pve_verify_pool_network {
+    my ($value, $noerr) = @_;
+
+    if ($value =~ NETWORK_VNET_REGEX) {
+        my ($zone, $vnet, $tag) = ($1, $2, $3);
+
+        eval { PVE::JSONSchema::check_format('pve-sdn-zone-id', $zone) };
+        if ($@) {
+            return undef if $noerr;
+            die "invalid SDN zone ID '$zone'\n";
+        }
+
+        eval { PVE::JSONSchema::check_format('pve-sdn-vnet-id', $vnet) };
+        if ($@) {
+            return undef if $noerr;
+            die "invalid SDN vnet ID '$vnet'\n";
+        }
+
+        if (defined($tag) && $tag ne '*') {
+            if ($tag < 1 || $tag > 4094) {
+                return undef if $noerr;
+                die "vlan tag '$tag' out of range (1-4094)\n";
+            }
+        }
+
+        return $value;
+    }
+
+    return undef if $noerr;
+    die "value '$value' is not a valid pool network vnet string (<type>/<zone>/<vnet>[/<tag>|*])\n";
+}
+
+PVE::JSONSchema::register_format('pve-pool-network', \&pve_verify_pool_network);
+
 __PACKAGE__->register_method({
     name => 'index',
     path => '',
@@ -36,7 +77,7 @@ __PACKAGE__->register_method({
             },
             type => {
                 type => 'string',
-                enum => ['qemu', 'lxc', 'storage'],
+                enum => ['qemu', 'lxc', 'storage', 'network'],
                 optional => 1,
                 requires => 'poolid',
             },
@@ -61,7 +102,7 @@ __PACKAGE__->register_method({
                         properties => {
                             type => {
                                 type => 'string',
-                                enum => ['qemu', 'lxc', 'openvz', 'storage'],
+                                enum => ['qemu', 'lxc', 'openvz', 'storage', 'network'],
                             },
                             id => {
                                 type => 'string',
@@ -135,6 +176,27 @@ __PACKAGE__->register_method({
                 }
             }
 
+            if (!defined($param->{type}) || $param->{type} eq 'network') {
+                for my $net_key (sort keys $pool_config->{network}->%*) {
+                    my ($type, @path) = split('/', $net_key);
+
+                    if ($type eq 'vnet') {
+                        my ($zoneid, $vnet, $tag) = @path;
+
+                        my $description = "$vnet ($zoneid)";
+                        $description = "$vnet.$tag ($zoneid)" if defined($tag);
+
+                        push @$members,
+                            {
+                                type => 'network',
+                                'network-type' => $type,
+                                id => $net_key,
+                                text => $description,
+                            };
+                    }
+                }
+            }
+
             my $pool_info = {
                 members => $members,
             };
@@ -243,6 +305,13 @@ __PACKAGE__->register_method({
                 format => 'pve-storage-id-list',
                 optional => 1,
             },
+            network => {
+                description => 'Network resource to add or remove from this pool.',
+                type => 'string',
+                typetext => '<type>/<zone>/<vnet>[/<tag>|*]',
+                format => 'pve-pool-network-list',
+                optional => 1,
+            },
             'allow-move' => {
                 description => 'Allow adding a guest even if already in another pool.'
                     . ' The guest will be removed from its current pool and added to this one.',
@@ -295,6 +364,13 @@ __PACKAGE__->register_method({
                 format => 'pve-storage-id-list',
                 optional => 1,
             },
+            network => {
+                description => 'Network resource to add or remove from this pool.',
+                type => 'string',
+                typetext => '<type>/<zone>/<vnet>[/<tag>|*]',
+                format => 'pve-pool-network-list',
+                optional => 1,
+            },
             'allow-move' => {
                 description => 'Allow adding a guest even if already in another pool.'
                     . ' The guest will be removed from its current pool and added to this one.',
@@ -304,7 +380,7 @@ __PACKAGE__->register_method({
             },
             delete => {
                 description =>
-                    'Remove the passed VMIDs and/or storage IDs instead of adding them.',
+                    'Remove the passed VMIDs, storage IDs and/or network resource instead of adding them.',
                 type => 'boolean',
                 optional => 1,
                 default => 0,
@@ -373,6 +449,64 @@ __PACKAGE__->register_method({
                     }
                 }
 
+                if (defined($param->{network})) {
+                    # FIXME: MAJOR VERSION: 10.0 remove gatekeeper
+                    # gatekeep vnet as pool members
+                    PVE::Cluster::assert_min_cluster_version(9, 2, 5);
+
+                    my $network_param = $param->{network};
+                    my $zones_cfg = PVE::Network::SDN::Zones::config();
+                    my $vnets_cfg = PVE::Network::SDN::Vnets::config();
+
+                    for my $network (PVE::Tools::split_list($network_param)) {
+                        if ($network =~ NETWORK_VNET_REGEX) {
+                            my ($zone, $vnetid, $tag) = ($1, $2, $3);
+
+                            die "SDN Zone '$zone' does not exist\n"
+                                if !$zones_cfg->{ids}->{$zone};
+
+                            my $vnet_data = $vnets_cfg->{ids}->{$vnetid}
+                                or die "VNet '$vnetid' does not exist\n";
+
+                            my $vnet_zone = $vnet_data->{zone};
+                            if ($zone ne $vnet_zone) {
+                                die
+                                    "VNet '$vnetid' does not belong to zone '$zone' (it belongs to '$vnet_zone')\n";
+                            }
+
+                            my $network_key = "vnet/$vnet_zone/$vnetid";
+                            my $acl_path = "/sdn/zones/$vnet_zone/$vnetid";
+
+                            if (defined($tag)) {
+                                if (!$vnet_data->{vlanaware}) {
+                                    die
+                                        "VNet '$vnetid' is not VLAN-aware, cannot assign a specific tag\n";
+                                }
+                                $network_key .= "/$tag";
+                                $acl_path .= "/$tag" if $tag ne "*";
+                            }
+
+                            $rpcenv->check_perm_modify(
+                                $authuser, $acl_path, ['SDN.Allocate'],
+                            );
+
+                            if ($param->{delete}) {
+                                die "Network resource '$network_key' is not a pool member\n"
+                                    if !$pool_config->{network}->{$network_key};
+
+                                delete $pool_config->{network}->{$network_key};
+                            } else {
+                                die
+                                    "Network resource '$network_key' is already a pool member\n"
+                                    if ($pool_config->{network}->{$network_key});
+
+                                $pool_config->{network}->{$network_key} = 1;
+                            }
+
+                        }
+                    }
+                }
+
                 cfs_write_file("user.cfg", $usercfg);
             },
             "update pools failed",
@@ -400,7 +534,7 @@ __PACKAGE__->register_method({
             },
             type => {
                 type => 'string',
-                enum => ['qemu', 'lxc', 'storage'],
+                enum => ['qemu', 'lxc', 'storage', 'network'],
                 optional => 1,
             },
         },
@@ -421,7 +555,7 @@ __PACKAGE__->register_method({
                     properties => {
                         type => {
                             type => 'string',
-                            enum => ['qemu', 'lxc', 'openvz', 'storage'],
+                            enum => ['qemu', 'lxc', 'openvz', 'storage', 'network'],
                         },
                         id => {
                             type => 'string',
@@ -524,6 +658,14 @@ __PACKAGE__->register_method({
                     die "pool '$pool' is not empty (contains storage '$storeid')\n";
                 }
 
+                for my $netid (sort keys $pool_config->{network}->%*) {
+                    my ($type, $id) = split('/', $netid, 2);
+                    $type = 'network' if !defined($type);
+                    $id = $netid if !defined($id);
+
+                    die "pool '$pool' is not empty (contains $type '$id')\n";
+                }
+
                 delete($usercfg->{pools}->{$pool});
                 PVE::AccessControl::delete_pool_acl($pool, $usercfg);
 
-- 
2.47.3





  parent reply	other threads:[~2026-07-24 14:33 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-24 14:25 [PATCH access-control/cluster/common/manager/network/proxmox-widget-toolkit/qemu-server v3 00/12] fix #7294: pool: add SDN VNets as pool members David Riley
2026-07-24 14:25 ` [PATCH pve-common v3 01/12] tools: add helpers for version comparison David Riley
2026-07-24 14:25 ` [PATCH qemu-server v3 02/12] helpers: drop local version helpers in favor of pve-common David Riley
2026-07-24 14:25 ` [PATCH pve-cluster v3 03/12] cluster: helpers: add cluster-wide version assertion David Riley
2026-07-24 14:25 ` [PATCH pve-access-control v3 04/12] fix #7294: acl: pool: add SDN VNets as pool members David Riley
2026-07-24 14:25 ` [PATCH pve-access-control v3 05/12] fix #7294: acl: pool: add helpers to remove and migrate pool VNets David Riley
2026-07-24 14:25 ` [PATCH pve-access-control v3 06/12] readme: document SDN VNets as pool members David Riley
2026-07-24 14:25 ` [PATCH pve-network v3 07/12] sdn: register api formats for zones and vnets David Riley
2026-07-24 14:25 ` [PATCH pve-network v3 08/12] fix #7294: sdn: vnet: update pool members on vnet migration and deletion David Riley
2026-07-24 14:25 ` [PATCH pve-manager v3 09/12] ui: replace var with let to match style guide for variable declaration David Riley
2026-07-24 14:25 ` David Riley [this message]
2026-07-24 14:25 ` [PATCH pve-manager v3 11/12] fix #7294: ui: pool: add SDN VNets as pool members David Riley
2026-07-24 14:25 ` [PATCH proxmox-widget-toolkit v3 12/12] fix #7294: css: theme: add opacity override for pool VNet icon David Riley

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=20260724142528.109453-11-d.riley@proxmox.com \
    --to=d.riley@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