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
next prev 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