From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 15C711FF14C for ; Fri, 26 Jun 2026 15:11:42 +0200 (CEST) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 05667119D2; Fri, 26 Jun 2026 15:11:35 +0200 (CEST) From: David Riley To: pve-devel@lists.proxmox.com Subject: [PATCH pve-manager v2 02/10] fix #7294: api: pool: add SDN VNets as pool members Date: Fri, 26 Jun 2026 15:10:27 +0200 Message-ID: <20260626131035.112374-3-d.riley@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260626131035.112374-1-d.riley@proxmox.com> References: <20260626131035.112374-1-d.riley@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1782479449875 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.133 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: ER4SAKTXYGXQ7PN3VZ4QMN7DZWZ56ZZM X-Message-ID-Hash: ER4SAKTXYGXQ7PN3VZ4QMN7DZWZ56ZZM X-MailFrom: d.riley@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Extend the pool API to accept SDN VNets and optional VLAN tags. Group VNets under the new 'network' property type in the pool configuration. 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. Link: https://bugzilla.proxmox.com/show_bug.cgi?id=7294 Signed-off-by: David Riley --- PVE/API2/Pool.pm | 135 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 129 insertions(+), 6 deletions(-) diff --git a/PVE/API2/Pool.pm b/PVE/API2/Pool.pm index 63aff5bb..6bd99abb 100644 --- a/PVE/API2/Pool.pm +++ b/PVE/API2/Pool.pm @@ -4,10 +4,12 @@ use strict; use warnings; use PVE::AccessControl; -use PVE::Cluster qw (cfs_read_file cfs_write_file); +use PVE::Cluster qw (cfs_read_file cfs_write_file assert_min_cluster_version); use PVE::Exception qw(raise_param_exc); use PVE::INotify; +use PVE::Network; use PVE::Storage; +use PVE::Tools; use PVE::SafeSyslog; @@ -16,6 +18,26 @@ use PVE::RESTHandler; use base qw(PVE::RESTHandler); +my $pool_network_format = { + zone => { + description => 'SDN Zone', + type => 'string', + format => 'pve-sdn-zone-id', + }, + vnet => { + description => 'VNet to add or remove from this pool.', + type => 'string', + format => 'pve-sdn-vnet-id', + }, + tag => { + description => "Specify a VLAN tag", + optional => 1, + type => 'integer', + minimum => 1, + maximum => 4094, + }, +}; + __PACKAGE__->register_method({ name => 'index', path => '', @@ -36,7 +58,7 @@ __PACKAGE__->register_method({ }, type => { type => 'string', - enum => ['qemu', 'lxc', 'storage'], + enum => ['qemu', 'lxc', 'storage', 'network'], optional => 1, requires => 'poolid', }, @@ -61,7 +83,7 @@ __PACKAGE__->register_method({ properties => { type => { type => 'string', - enum => ['qemu', 'lxc', 'openvz', 'storage'], + enum => ['qemu', 'lxc', 'openvz', 'storage', 'network'], }, id => { type => 'string', @@ -135,6 +157,29 @@ __PACKAGE__->register_method({ } } + if (!defined($param->{type}) || $param->{type} eq 'network') { + if ($pool_config->{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 +288,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 => 'zone=,vnet=[,tag=]', + format => $pool_network_format, + 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 +347,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 => 'zone=,vnet=[,tag=]', + format => $pool_network_format, + 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 +363,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 +432,62 @@ __PACKAGE__->register_method({ } } + if (defined($param->{network})) { + my $vnet_entry = PVE::JSONSchema::parse_property_string( + $pool_network_format, $param->{network}, + ); + + # gatekeep vnet as pool members + PVE::Cluster::assert_min_cluster_version(9, 2, 3); + + my ($zone, $vnetid, $tag) = $vnet_entry->@{qw(zone vnet tag)}; + + my $zones_cfg = PVE::Network::SDN::Zones::config(); + die "SDN Zone '$zone' does not exist\n" if !$zones_cfg->{ids}->{$zone}; + + my $vnets_cfg = PVE::Network::SDN::Vnets::config(); + + 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"; + + if (defined($tag)) { + if (!$vnet_data->{vlanaware}) { + die + "VNet '$vnetid' is not VLAN-aware, cannot assign a specific tag\n"; + } + + $network_key .= "/$tag"; + } + + $rpcenv->check_perm_modify( + $authuser, + "/sdn/zones/$vnet_zone/$vnetid", + ['SDN.Allocate'], + ); + + if ($param->{delete}) { + if (!$pool_config->{network}->{$network_key}) { + die "Network resource '$network_key' is not a pool member\n"; + } + + delete $pool_config->{network}->{$network_key}; + } else { + if ($pool_config->{network}->{$network_key}) { + die "Network resource '$network_key' is already a pool member\n"; + } + + $pool_config->{network}->{$network_key} = 1; + } + } + cfs_write_file("user.cfg", $usercfg); }, "update pools failed", @@ -400,7 +515,7 @@ __PACKAGE__->register_method({ }, type => { type => 'string', - enum => ['qemu', 'lxc', 'storage'], + enum => ['qemu', 'lxc', 'storage', 'network'], optional => 1, }, }, @@ -421,7 +536,7 @@ __PACKAGE__->register_method({ properties => { type => { type => 'string', - enum => ['qemu', 'lxc', 'openvz', 'storage'], + enum => ['qemu', 'lxc', 'openvz', 'storage', 'network'], }, id => { type => 'string', @@ -524,6 +639,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