public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: "Michael Köppl" <m.koeppl@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH guest-common v6 04/18] add module to track previously used guest IDs
Date: Thu, 24 Sep 2026 18:14:56 +0200	[thread overview]
Message-ID: <20260924161510.847362-5-m.koeppl@proxmox.com> (raw)
In-Reply-To: <20260924161510.847362-1-m.koeppl@proxmox.com>

The /cluster/nextid API endpoint always suggests the lowest free
guest ID, so IDs of destroyed guests get handed out again. Offering
an opt-out requires recording every ID that has ever been in use
somewhere both the endpoint and the guest create and destroy paths
can reach.

Store them in /etc/pve/virtual-guest/used-guest-ids and register it as
a cfs file, so the list is kept in sync cluster-wide. IDs are stored as
"<start>-<end>" ranges and are not expanded per ID since cfs_read_file()
hands out a deep copy of the parsed data on every call, which gets
expensive on clusters that churn through many guests. Adjacent ranges
are merged on write to keep the file small. Entries that do not parse,
ranges with reversed bounds included, are skipped with a warning instead
of failing the whole read.

get_next_unused_id() returns the lowest ID at or above a given one that
is neither in use nor recorded as having been used previously.

Recording failures are non-critical and will only warn while 'unique' is
off.

Originally-by: Daniel Krambrock <krambrock@hrz.uni-marburg.de>
Originally-by: Severen Redwood <severen.redwood@sitehost.co.nz>
Signed-off-by: Michael Köppl <m.koeppl@proxmox.com>
---
 src/Makefile       |   1 +
 src/PVE/GuestID.pm | 132 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 133 insertions(+)
 create mode 100644 src/PVE/GuestID.pm

diff --git a/src/Makefile b/src/Makefile
index 030e7f7..ceba738 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -14,6 +14,7 @@ install: PVE
 	install -m 0644 PVE/Replication.pm ${PERL5DIR}/PVE/
 	install -m 0644 PVE/StorageTunnel.pm ${PERL5DIR}/PVE/
 	install -m 0644 PVE/Tunnel.pm ${PERL5DIR}/PVE/
+	install -m 0644 PVE/GuestID.pm ${PERL5DIR}/PVE/
 	install -d ${PERL5DIR}/PVE/Mapping
 	install -m 0644 PVE/Mapping/Dir.pm ${PERL5DIR}/PVE/Mapping/
 	install -m 0644 PVE/Mapping/PCI.pm ${PERL5DIR}/PVE/Mapping/
diff --git a/src/PVE/GuestID.pm b/src/PVE/GuestID.pm
new file mode 100644
index 0000000..862d92b
--- /dev/null
+++ b/src/PVE/GuestID.pm
@@ -0,0 +1,132 @@
+package PVE::GuestID;
+
+use v5.36;
+
+use PVE::Cluster qw(
+    cfs_lock_file
+    cfs_read_file
+    cfs_register_file
+    cfs_write_file
+);
+
+my $FILENAME = 'virtual-guest/used-guest-ids';
+
+my sub parse_id_list($filename, $raw) {
+    my $ranges = [];
+
+    return $ranges if !defined($raw);
+
+    for my $line (split(/\n/, $raw)) {
+        next if $line =~ m/^\s*$/;
+
+        if ($line =~ m/^(\d+)$/) {
+            push $ranges->@*, [$1, $1];
+        } elsif ($line =~ m/^(\d+)-(\d+)$/) {
+            my ($start, $end) = ($1, $2);
+            if ($start > $end) {
+                warn "skipping reversed range in $filename: $line\n";
+                next;
+            }
+            push $ranges->@*, [$start, $end];
+        } else {
+            warn "skipping invalid entry in $filename: $line\n";
+        }
+    }
+
+    # lookup and insertion rely on the ranges being ordered by start,
+    # merging adjacent and overlapping ones is done when writing the
+    # id list.
+    return [sort { $a->[0] <=> $b->[0] } $ranges->@*];
+}
+
+my sub next_unused($ranges, $id) {
+    my $next = $id;
+    for my $range ($ranges->@*) {
+        my ($start, $end) = $range->@*;
+        next if $end < $next;
+        last if $next < $start;
+        $next = $end + 1;
+    }
+    return $next;
+}
+
+my sub format_entry($start, $last) {
+    return $start == $last ? "$start\n" : "$start-$last\n";
+}
+
+my sub write_id_list($filename, $ranges) {
+    my $output = '';
+    my ($start, $last);
+
+    for my $range ($ranges->@*) {
+        my ($curr_start, $curr_end) = $range->@*;
+
+        if (!defined($start)) {
+            ($start, $last) = ($curr_start, $curr_end);
+        } elsif ($curr_start <= $last + 1) {
+            $last = $curr_end if $curr_end > $last;
+        } else {
+            $output .= format_entry($start, $last);
+            ($start, $last) = ($curr_start, $curr_end);
+        }
+    }
+
+    $output .= format_entry($start, $last) if defined($start);
+
+    return $output;
+}
+
+# Returns the lowest ID at or above $id that is neither taken by an
+# existing guest nor recorded as used before.
+sub get_next_unused_id($id) {
+    my $ranges = cfs_read_file($FILENAME);
+    my $vmlist = PVE::Cluster::get_vmlist() // {};
+    my $existing = $vmlist->{ids} // {};
+
+    $id = next_unused($ranges, $id);
+    while (defined($existing->{$id})) {
+        $id = next_unused($ranges, $id + 1);
+    }
+
+    return $id;
+}
+
+my sub insert_id($ranges, $id) {
+    my $i = 0;
+
+    while ($i < @$ranges && $ranges->[$i]->[1] < $id) {
+        $i++;
+    }
+
+    return 0 if $i < @$ranges && $ranges->[$i]->[0] <= $id;
+
+    splice(@$ranges, $i, 0, [$id, $id]);
+
+    return 1;
+}
+
+sub register_used_id($id) {
+    cfs_lock_file(
+        $FILENAME,
+        10,
+        sub {
+            my $ranges = cfs_read_file($FILENAME);
+
+            return if !insert_id($ranges, $id);
+
+            cfs_write_file($FILENAME, $ranges);
+        },
+    );
+
+    if (my $err = $@) {
+        my $dc_conf = cfs_read_file('datacenter.cfg');
+
+        my $emsg = "unable to record guest ID $id as used";
+        die "$emsg - $err" if $dc_conf->{'next-id'}->{unique};
+        warn "$emsg - $err";
+    }
+}
+
+cfs_register_file($FILENAME, \&parse_id_list, \&write_id_list);
+
+1;
-- 
2.47.3





  parent reply	other threads:[~2026-09-24 16:17 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-24 16:14 [PATCH many v6 00/18] add option to prevent suggesting previously used VMIDs Michael Köppl
2026-09-24 16:14 ` [PATCH cluster v6 01/18] cluster files: add virtual-guest/used-guest-ids Michael Köppl
2026-09-24 16:14 ` [PATCH cluster v6 02/18] datacenter config: add unique subproperty to next-id Michael Köppl
2026-09-24 16:14 ` [PATCH cluster v6 03/18] datacenter config: next-id: add enforce subproperty Michael Köppl
2026-09-24 16:14 ` Michael Köppl [this message]
2026-09-24 16:14 ` [PATCH guest-common v6 05/18] tests: add tests for used guest ID tracking Michael Köppl
2026-09-24 16:14 ` [PATCH guest-common v6 06/18] abstract config: register used guest ID when creating config Michael Köppl
2026-09-24 16:14 ` [PATCH guest-common v6 07/18] guest id: keep used ID list below the pmxcfs file size limit Michael Köppl
2026-09-24 16:15 ` [PATCH guest-common v6 08/18] tests: add tests for used-guest-ids max file size handling Michael Köppl
2026-09-24 16:15 ` [PATCH guest-common v6 09/18] guest id: optionally enforce the next-id range and uniqueness Michael Köppl
2026-09-24 16:15 ` [PATCH qemu-server v6 10/18] api: record VM ID as used on destruction and remote migration Michael Köppl
2026-09-24 16:15 ` [PATCH qemu-server v6 11/18] api, remote migrate: exempt existing VMs from next-id enforcement Michael Köppl
2026-09-24 16:15 ` [PATCH container v6 12/18] api: record CT ID as used on destruction and remote migration Michael Köppl
2026-09-24 16:15 ` [PATCH container v6 13/18] api, migrate: exempt existing CTs from next-id enforcement Michael Köppl
2026-09-24 16:15 ` [PATCH manager v6 14/18] fix #4369: api: optionally only suggest unique IDs Michael Köppl
2026-09-24 16:15 ` [PATCH manager v6 15/18] ui: dc options: rename VMID to guest ID Michael Köppl
2026-09-24 16:15 ` [PATCH manager v6 16/18] fix #4369: ui: dc options: add option for unique VM/CT IDs Michael Köppl
2026-09-24 16:15 ` [PATCH manager v6 17/18] api: nextid: reject IDs forbidden by next-id enforcement Michael Köppl
2026-09-24 16:15 ` [PATCH manager v6 18/18] ui: dc options: add option to enforce next free guest ID settings Michael Köppl

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=20260924161510.847362-5-m.koeppl@proxmox.com \
    --to=m.koeppl@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