* [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration
@ 2026-02-25 15:18 Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 01/14] d/control: bump versioned build dependency for libpve-common-perl to 9.0.12 Fiona Ebner
` (13 more replies)
0 siblings, 14 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
Changes in v2:
* add patches for container
* log when skipping strict configuration check
For remote migration, we already check that the config can be parsed
on the target. Do the same for intra-cluster migration, to avoid
issues like [0] for future new settings, with settings being
unexpectedly ignored on the target, and in the case of qemu-server
even relatively silently dropped (there are warnings in the target's
system logs). In case of containers, an example is migrating a
container with a mountpoint with 'keepattrs' to a node with a too old
pve-container version, resulting in the mountpoint not being mounted
on the target.
The first few patches for each repository are cleanups/tiny
improvements that would be nice to have in any case.
Unfortunately, before patch "qm: mtunnel: reply when a command is
unknown", when a command is unknown, mtunnel did not reply at all.
Therefore, this delays VM backwards migrations to qemu-server versions
less than the next bumped version (at the time of this writing
expected to be 9.1.5) by 3 seconds.
I opted for 3 seconds, since config parsing should be very quick and
5 seconds would still be very noticeable for a bulk migration of VMs
on a shared storage with a fast network. Right now, the option won't
help anyways, only once we add a new config option (at which point
we could bump it to 5 seconds).
For containers, an 'mtunnel' endpoint is added and changes to stderr
handling for the tunnel are useful, see guest-common patch "tunnel:
redirect stderr to log function".
[0]: https://bugzilla.proxmox.com/show_bug.cgi?id=7341
Dependency bump pve-container -> guest-common needed!
qemu-server:
Fiona Ebner (6):
d/control: bump versioned build dependency for libpve-common-perl to
9.0.12
tests: migration: get rid of mocking for removed
PVE::QemuMigrate::read_tunnel()
qm: mtunnel: avoid using deprecated check_running() helper
mtunnel: add 'conf' command to do strict configuration parsing
qm: mtunnel: reply when a command is unknown
migration: intra-cluster: check config can be parsed on target node
debian/control | 2 +-
src/PVE/API2/Qemu.pm | 4 +++-
src/PVE/CLI/qm.pm | 21 +++++++++++++++---
src/PVE/QemuMigrate.pm | 27 +++++++++++++++++++++++
src/test/MigrationTest/QemuMigrateMock.pm | 14 ++++++++----
5 files changed, 59 insertions(+), 9 deletions(-)
guest-common:
Fiona Ebner (3):
tunnel: add missing IO::File module import
tunnel: end module with true value as recommended by perlcritic
tunnel: redirect stderr to log function
src/PVE/Tunnel.pm | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
container:
Fiona Ebner (5):
pct: add missing module imports and group according to style guide
migrate: add missing module imports
pct: introduce mtunnel command
d/control: bump versioned build dependency for libpve-common-perl to
9.0.12
migration: intra-cluster: check config can be parsed on target node
debian/control | 2 +-
src/PVE/API2/LXC.pm | 11 ++++++++
src/PVE/CLI/pct.pm | 64 +++++++++++++++++++++++++++++++++++++++++-
src/PVE/LXC/Migrate.pm | 41 +++++++++++++++++++++++++++
4 files changed, 116 insertions(+), 2 deletions(-)
Summary over all repositories:
10 files changed, 192 insertions(+), 14 deletions(-)
--
Generated by git-murpp 0.5.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH qemu-server v2 01/14] d/control: bump versioned build dependency for libpve-common-perl to 9.0.12
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 02/14] tests: migration: get rid of mocking for removed PVE::QemuMigrate::read_tunnel() Fiona Ebner
` (12 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
The config2command test script uses PVE::File which was introduced
with libpve-common-perl=9.0.12.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
debian/control | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/debian/control b/debian/control
index 7f87060a..0c8ea812 100644
--- a/debian/control
+++ b/debian/control
@@ -10,7 +10,7 @@ Build-Depends: debhelper-compat (= 13),
libnet-dbus-perl,
libpve-apiclient-perl,
libpve-cluster-perl,
- libpve-common-perl (>= 9.0.3),
+ libpve-common-perl (>= 9.0.12),
libpve-guest-common-perl (>= 5.2.2),
libpve-network-perl,
libpve-storage-perl (>= 9.0.16),
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH qemu-server v2 02/14] tests: migration: get rid of mocking for removed PVE::QemuMigrate::read_tunnel()
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 01/14] d/control: bump versioned build dependency for libpve-common-perl to 9.0.12 Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 03/14] qm: mtunnel: avoid using deprecated check_running() helper Fiona Ebner
` (11 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
The PVE::QemuMigrate::read_tunnel() function was removed by commit
e594231b ("migrate: move tunnel-helpers to pve-guest-common").
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/test/MigrationTest/QemuMigrateMock.pm | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/test/MigrationTest/QemuMigrateMock.pm b/src/test/MigrationTest/QemuMigrateMock.pm
index 8cd2da12..df8b575a 100644
--- a/src/test/MigrationTest/QemuMigrateMock.pm
+++ b/src/test/MigrationTest/QemuMigrateMock.pm
@@ -75,9 +75,6 @@ $qemu_migrate_module->mock(
fork_tunnel => sub {
die "fork_tunnel (mocked) - implement me\n"; # currently no call should lead here
},
- read_tunnel => sub {
- die "read_tunnel (mocked) - implement me\n"; # currently no call should lead here
- },
start_remote_tunnel => sub {
my ($self, $raddr, $rport, $ruri, $unix_socket_info) = @_;
$expected_calls->{'finish_tunnel'} = 1;
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH qemu-server v2 03/14] qm: mtunnel: avoid using deprecated check_running() helper
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 01/14] d/control: bump versioned build dependency for libpve-common-perl to 9.0.12 Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 02/14] tests: migration: get rid of mocking for removed PVE::QemuMigrate::read_tunnel() Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 04/14] mtunnel: add 'conf' command to do strict configuration parsing Fiona Ebner
` (10 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
Calling check_running() with $nocheck=1 is equivalent to using
vm_running_locally().
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/CLI/qm.pm | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/PVE/CLI/qm.pm b/src/PVE/CLI/qm.pm
index bdae9641..2b42c3b4 100755
--- a/src/PVE/CLI/qm.pm
+++ b/src/PVE/CLI/qm.pm
@@ -465,9 +465,9 @@ __PACKAGE__->register_method({
last;
} elsif ($line =~ /^resume (\d+)$/) {
my $vmid = $1;
- # check_running and vm_resume with nocheck, since local node
- # might not have processed config move/rename yet
- if (PVE::QemuServer::check_running($vmid, 1)) {
+ if (PVE::QemuServer::Helpers::vm_running_locally($vmid)) {
+ # vm_resume with nocheck, since local node might not have processed config
+ # move/rename yet
eval { PVE::QemuServer::RunState::vm_resume($vmid, 1, 1); };
if ($@) {
$tunnel_write->("ERR: resume failed - $@");
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH qemu-server v2 04/14] mtunnel: add 'conf' command to do strict configuration parsing
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (2 preceding siblings ...)
2026-02-25 15:18 ` [PATCH qemu-server v2 03/14] qm: mtunnel: avoid using deprecated check_running() helper Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 05/14] qm: mtunnel: reply when a command is unknown Fiona Ebner
` (9 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
Will be requested by the source of the migration before the
configuration is moved, so there is a parameter for the node where
the configuration resides.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/CLI/qm.pm | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/src/PVE/CLI/qm.pm b/src/PVE/CLI/qm.pm
index 2b42c3b4..8498d0c5 100755
--- a/src/PVE/CLI/qm.pm
+++ b/src/PVE/CLI/qm.pm
@@ -18,6 +18,7 @@ use URI::Escape;
use PVE::APIClient::LWP;
use PVE::Cluster;
use PVE::Exception qw(raise_param_exc);
+use PVE::File;
use PVE::GuestHelpers;
use PVE::GuestImport::OVF;
use PVE::INotify;
@@ -477,6 +478,18 @@ __PACKAGE__->register_method({
} else {
$tunnel_write->("ERR: resume failed - VM $vmid not running");
}
+ } elsif ($line =~ /^config (\d+) (\S+)$/) {
+ my ($vmid, $node) = ($1, $2);
+ eval {
+ my $conf_fn = PVE::QemuConfig->config_file($vmid, $node);
+ my $raw = PVE::File::file_get_contents($conf_fn);
+ PVE::QemuServer::parse_vm_config($conf_fn, $raw, 1);
+ };
+ if (my $err = $@) {
+ $tunnel_write->("ERR: strict config check for target node failed - $err");
+ } else {
+ $tunnel_write->("OK");
+ }
}
}
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH qemu-server v2 05/14] qm: mtunnel: reply when a command is unknown
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (3 preceding siblings ...)
2026-02-25 15:18 ` [PATCH qemu-server v2 04/14] mtunnel: add 'conf' command to do strict configuration parsing Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 06/14] migration: intra-cluster: check config can be parsed on target node Fiona Ebner
` (8 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
Otherwise, the other endpoint cannot distinguish between an unknown
command and a command which takes a long time.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
src/PVE/CLI/qm.pm | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/PVE/CLI/qm.pm b/src/PVE/CLI/qm.pm
index 8498d0c5..9b7e5ab8 100755
--- a/src/PVE/CLI/qm.pm
+++ b/src/PVE/CLI/qm.pm
@@ -490,6 +490,8 @@ __PACKAGE__->register_method({
} else {
$tunnel_write->("OK");
}
+ } else {
+ $tunnel_write->("ERR: unknown command '$line'");
}
}
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH qemu-server v2 06/14] migration: intra-cluster: check config can be parsed on target node
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (4 preceding siblings ...)
2026-02-25 15:18 ` [PATCH qemu-server v2 05/14] qm: mtunnel: reply when a command is unknown Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH guest-common v2 07/14] tunnel: add missing IO::File module import Fiona Ebner
` (7 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
For remote migration, we already check that the config can be parsed
on the target. Do the same for intra-cluster migration, to avoid
issues like [0] for future new settings, with lines being unexpectedly
and relatively silently dropped (there are warnings in the target's
system logs).
Unfortunately, before commit "qm: mtunnel: reply when a command is
unknown", which is part of the same patch series, when a command is
unknown, mtunnel did not reply at all. Therefore, this delays
backwards migrations to qemu-server versions less than the next bumped
version (at the time of this writing expected to be 9.1.5) by 3
seconds.
[0]: https://bugzilla.proxmox.com/show_bug.cgi?id=7341
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
Changes in v2:
* log when skipping config check
For easier testing:
# commented out so 'git am' does not apply it O;)
# diff --git a/src/PVE/QemuServer.pm b/src/PVE/QemuServer.pm
# index 545758dc..b914314e 100644
# --- a/src/PVE/QemuServer.pm
# +++ b/src/PVE/QemuServer.pm
# @@ -236,6 +236,13 @@ my $spice_enhancements_fmt = {
# };
#
# my $confdesc = {
#+ 'shiny-new' => {
#+ type => 'string',
#+ enum => ['shiny', 'new'],
#+ default => 'shiny',
#+ optional => 1,
#+ description => "you know you want it",
#+ },
# onboot => {
# optional => 1,
# type => 'boolean',
src/PVE/API2/Qemu.pm | 4 +++-
src/PVE/QemuMigrate.pm | 27 +++++++++++++++++++++++
src/test/MigrationTest/QemuMigrateMock.pm | 11 ++++++++-
3 files changed, 40 insertions(+), 2 deletions(-)
diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm
index 1f0864f5..47466513 100644
--- a/src/PVE/API2/Qemu.pm
+++ b/src/PVE/API2/Qemu.pm
@@ -5399,7 +5399,9 @@ __PACKAGE__->register_method({
force => {
type => 'boolean',
description =>
- "Allow to migrate VMs which use local devices. Only root may use this option.",
+ "Allow to migrate VMs which use local devices and for intra-cluster migration,"
+ . " configuration options not understood by the target. Only root may use this"
+ . " option.",
optional => 1,
},
migration_type => {
diff --git a/src/PVE/QemuMigrate.pm b/src/PVE/QemuMigrate.pm
index f7ec3227..901fe96d 100644
--- a/src/PVE/QemuMigrate.pm
+++ b/src/PVE/QemuMigrate.pm
@@ -355,6 +355,33 @@ sub prepare {
my $cmd = [@{ $self->{rem_ssh} }, '/bin/true'];
eval { $self->cmd_quiet($cmd); };
die "Can't connect to destination address using public key\n" if $@;
+
+ if (!$self->{opts}->{force}) {
+ # Fork a short-lived tunnel for checking the config. Later, the proper tunnel with SSH
+ # forwaring info is forked.
+ my $tunnel = $self->fork_tunnel();
+ # Compared to remote migration, which also does volume activation, this only strictly
+ # parses the config, so no large timeout is needed. Unfortunately, mtunnel did not
+ # indicate that a command is unknown, but not reply at all, so the timeout must be very
+ # low right now.
+ # FIXME PVE 10 - bump timeout, the trade-off between delaying backwards migration and
+ # giving config check more time should now be in favor of config checking
+ eval {
+ my $nodename = PVE::INotify::nodename();
+ PVE::Tunnel::write_tunnel($tunnel, 3, "config $vmid $nodename");
+ };
+ if (my $err = $@) {
+ chomp($err);
+ # if there is no reply, assume target did not know the command yet
+ if ($err =~ m/^no reply to command/) {
+ $self->log('info', "skipping strict configuration check (target too old?)");
+ } else {
+ die "$err - use --force to migrate regardless\n";
+ }
+ }
+ eval { PVE::Tunnel::finish_tunnel($tunnel); };
+ $self->log('warn', "failed to finish tunnel in prepare() - $@") if $@;
+ }
}
return $running;
diff --git a/src/test/MigrationTest/QemuMigrateMock.pm b/src/test/MigrationTest/QemuMigrateMock.pm
index df8b575a..170634de 100644
--- a/src/test/MigrationTest/QemuMigrateMock.pm
+++ b/src/test/MigrationTest/QemuMigrateMock.pm
@@ -65,6 +65,10 @@ $tunnel_module->mock(
my $vmid = $1;
die "resuming wrong VM '$vmid'\n" if $vmid ne $test_vmid;
return;
+ } elsif ($command =~ m/^config (\d+) (\S+)$/) {
+ my ($vmid, $node) = ($1, $2);
+ die "check config for wrong VM '$vmid'\n" if $vmid ne $test_vmid;
+ return;
}
die "write_tunnel (mocked) - implement me: $command\n";
},
@@ -73,7 +77,12 @@ $tunnel_module->mock(
my $qemu_migrate_module = Test::MockModule->new("PVE::QemuMigrate");
$qemu_migrate_module->mock(
fork_tunnel => sub {
- die "fork_tunnel (mocked) - implement me\n"; # currently no call should lead here
+ return {
+ writer => "mocked",
+ reader => "mocked",
+ pid => 123456,
+ version => 1,
+ };
},
start_remote_tunnel => sub {
my ($self, $raddr, $rport, $ruri, $unix_socket_info) = @_;
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH guest-common v2 07/14] tunnel: add missing IO::File module import
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (5 preceding siblings ...)
2026-02-25 15:18 ` [PATCH qemu-server v2 06/14] migration: intra-cluster: check config can be parsed on target node Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH guest-common v2 08/14] tunnel: end module with true value as recommended by perlcritic Fiona Ebner
` (6 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
New in v2.
src/PVE/Tunnel.pm | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/PVE/Tunnel.pm b/src/PVE/Tunnel.pm
index 791b465..66d98ea 100644
--- a/src/PVE/Tunnel.pm
+++ b/src/PVE/Tunnel.pm
@@ -3,6 +3,7 @@ package PVE::Tunnel;
use strict;
use warnings;
+use IO::File;
use IO::Pipe;
use IPC::Open2;
use JSON qw(encode_json decode_json);
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH guest-common v2 08/14] tunnel: end module with true value as recommended by perlcritic
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (6 preceding siblings ...)
2026-02-25 15:18 ` [PATCH guest-common v2 07/14] tunnel: add missing IO::File module import Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH guest-common v2 09/14] tunnel: redirect stderr to log function Fiona Ebner
` (5 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
New in v2.
src/PVE/Tunnel.pm | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/PVE/Tunnel.pm b/src/PVE/Tunnel.pm
index 66d98ea..25e03b6 100644
--- a/src/PVE/Tunnel.pm
+++ b/src/PVE/Tunnel.pm
@@ -372,3 +372,5 @@ sub finish_tunnel {
die $err if $err;
}
+
+1;
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH guest-common v2 09/14] tunnel: redirect stderr to log function
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (7 preceding siblings ...)
2026-02-25 15:18 ` [PATCH guest-common v2 08/14] tunnel: end module with true value as recommended by perlcritic Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 10/14] pct: add missing module imports and group according to style guide Fiona Ebner
` (4 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
With open2(), the stderr from the child process would just be printed
directly making it impossible for the call site of the tunnel to
filter the output.
This is in preparation to add a 'pct mtunnel' command. When forking a
tunnel to a node which does not support that command yet, the whole
'pct' usage documentation would be printed to the migration log and
the call site would only see 'can't open tunnel - no reply' as an
error. By capturing the stderr output too, the call site can detect
that the failure happens because the target node is too old and avoid
dumping the whole usage information.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
New in v2.
src/PVE/Tunnel.pm | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)
diff --git a/src/PVE/Tunnel.pm b/src/PVE/Tunnel.pm
index 25e03b6..148a8e2 100644
--- a/src/PVE/Tunnel.pm
+++ b/src/PVE/Tunnel.pm
@@ -5,7 +5,7 @@ use warnings;
use IO::File;
use IO::Pipe;
-use IPC::Open2;
+use IPC::Open3 qw(open3);
use JSON qw(encode_json decode_json);
use POSIX qw( WNOHANG );
use Storable qw(dclone);
@@ -66,12 +66,19 @@ sub read_tunnel {
$timeout = 60 if !defined($timeout);
my $reader = $tunnel->{reader};
+ my $reader_stderr = $tunnel->{reader_stderr};
my $output;
eval {
PVE::Tools::run_with_timeout($timeout, sub { $output = <$reader>; });
};
- die "reading from tunnel failed: $@\n" if $@;
+ my $err = $@;
+
+ while (my $line = <$reader_stderr>) { # $reader_stderr is set up as non-blocking
+ $tunnel->{log}->('warn', $line);
+ }
+
+ die "reading from tunnel failed: $err\n" if $err;
chomp $output if defined($output);
@@ -145,13 +152,16 @@ sub fork_ssh_tunnel {
my $full_cmd = [@$rem_ssh, '-o ExitOnForwardFailure=yes', @localtunnelinfo, @$cmd];
my $reader = IO::File->new();
+ my $reader_stderr = IO::File->new();
my $writer = IO::File->new();
my $orig_pid = $$;
my $cpid;
- eval { $cpid = open2($reader, $writer, @$full_cmd); };
+ eval { $cpid = open3($writer, $reader, $reader_stderr, @$full_cmd); };
+ # Only read_tunnel() uses $reader_stderr and it never wants to wait.
+ $reader_stderr->blocking(0);
my $err = $@;
@@ -167,6 +177,7 @@ sub fork_ssh_tunnel {
my $tunnel = {
writer => $writer,
reader => $reader,
+ reader_stderr => $reader_stderr,
pid => $cpid,
rem_ssh => $rem_ssh,
log => $log,
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH container v2 10/14] pct: add missing module imports and group according to style guide
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (8 preceding siblings ...)
2026-02-25 15:18 ` [PATCH guest-common v2 09/14] tunnel: redirect stderr to log function Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 11/14] migrate: add missing module imports Fiona Ebner
` (3 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
New in v2.
src/PVE/CLI/pct.pm | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/PVE/CLI/pct.pm b/src/PVE/CLI/pct.pm
index e2f3c90..df8f4c3 100755
--- a/src/PVE/CLI/pct.pm
+++ b/src/PVE/CLI/pct.pm
@@ -7,6 +7,7 @@ use Fcntl;
use File::Copy 'copy';
use POSIX;
+use PVE::APIClient::LWP;
use PVE::CLIHandler;
use PVE::Cluster;
use PVE::CpuSet;
@@ -14,11 +15,15 @@ use PVE::Exception qw(raise_param_exc);
use PVE::GuestHelpers;
use PVE::INotify;
use PVE::JSONSchema qw(get_standard_option);
-use PVE::LXC::CGroup;
use PVE::RPCEnvironment;
use PVE::SafeSyslog;
+use PVE::Storage;
use PVE::Tools qw(extract_param);
+use PVE::LXC;
+use PVE::LXC::Config;
+use PVE::LXC::CGroup;
+
use PVE::API2::LXC::Config;
use PVE::API2::LXC::Snapshot;
use PVE::API2::LXC::Status;
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH container v2 11/14] migrate: add missing module imports
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (9 preceding siblings ...)
2026-02-25 15:18 ` [PATCH container v2 10/14] pct: add missing module imports and group according to style guide Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 12/14] pct: introduce mtunnel command Fiona Ebner
` (2 subsequent siblings)
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
New in v2.
src/PVE/LXC/Migrate.pm | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/PVE/LXC/Migrate.pm b/src/PVE/LXC/Migrate.pm
index fe588ba..d243d90 100644
--- a/src/PVE/LXC/Migrate.pm
+++ b/src/PVE/LXC/Migrate.pm
@@ -8,11 +8,15 @@ use File::Copy; # fixme: remove
use PVE::Cluster;
use PVE::INotify;
+use PVE::JSONSchema;
use PVE::Replication;
use PVE::ReplicationConfig;
use PVE::ReplicationState;
+use PVE::Storage::Plugin;
use PVE::Storage;
+use PVE::StorageTunnel;
use PVE::Tools;
+use PVE::Tunnel;
use PVE::LXC::Config;
use PVE::LXC;
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH container v2 12/14] pct: introduce mtunnel command
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (10 preceding siblings ...)
2026-02-25 15:18 ` [PATCH container v2 11/14] migrate: add missing module imports Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 13/14] d/control: bump versioned build dependency for libpve-common-perl to 9.0.12 Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 14/14] migration: intra-cluster: check config can be parsed on target node Fiona Ebner
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
Similar to what we already have in qemu-server. There is a 'config'
command used for checking that the configuration can be understood by
the migration target.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
New in v2.
src/PVE/CLI/pct.pm | 57 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/src/PVE/CLI/pct.pm b/src/PVE/CLI/pct.pm
index df8f4c3..74dfc92 100755
--- a/src/PVE/CLI/pct.pm
+++ b/src/PVE/CLI/pct.pm
@@ -12,6 +12,7 @@ use PVE::CLIHandler;
use PVE::Cluster;
use PVE::CpuSet;
use PVE::Exception qw(raise_param_exc);
+use PVE::File;
use PVE::GuestHelpers;
use PVE::INotify;
use PVE::JSONSchema qw(get_standard_option);
@@ -1051,6 +1052,60 @@ __PACKAGE__->register_method({
},
});
+__PACKAGE__->register_method({
+ name => 'mtunnel',
+ path => 'mtunnel',
+ method => 'POST',
+ description => "For internal use by intra-cluster migration only.",
+ parameters => {
+ additionalProperties => 0,
+ properties => {},
+ },
+ returns => { type => 'null' },
+ code => sub {
+ my ($param) = @_;
+
+ if (!PVE::Cluster::check_cfs_quorum(1)) {
+ print "no quorum\n";
+ return;
+ }
+
+ my $tunnel_write = sub {
+ my $text = shift;
+ chomp $text;
+ print "$text\n";
+ *STDOUT->flush();
+ };
+
+ $tunnel_write->("tunnel online");
+ $tunnel_write->("ver 1");
+
+ while (my $line = <STDIN>) {
+ chomp $line;
+ if ($line =~ /^quit$/) {
+ $tunnel_write->("OK");
+ last;
+ } elsif ($line =~ /^config (\d+) (\S+)$/) {
+ my ($vmid, $node) = ($1, $2);
+ eval {
+ my $conf_fn = PVE::LXC::Config->config_file($vmid, $node);
+ my $raw = PVE::File::file_get_contents($conf_fn);
+ PVE::LXC::Config::parse_pct_config($conf_fn, $raw, 1);
+ };
+ if (my $err = $@) {
+ $tunnel_write->("ERR: strict config check for target node failed - $err");
+ } else {
+ $tunnel_write->("OK");
+ }
+ } else {
+ $tunnel_write->("ERR: unknown command '$line'");
+ }
+ }
+
+ return;
+ },
+});
+
our $cmddef = {
list => [
'PVE::API2::LXC',
@@ -1191,6 +1246,8 @@ our $cmddef = {
rescan => [__PACKAGE__, 'rescan', []],
cpusets => [__PACKAGE__, 'cpusets', []],
fstrim => [__PACKAGE__, 'fstrim', ['vmid']],
+
+ mtunnel => [__PACKAGE__, 'mtunnel', []],
};
1;
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH container v2 13/14] d/control: bump versioned build dependency for libpve-common-perl to 9.0.12
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (11 preceding siblings ...)
2026-02-25 15:18 ` [PATCH container v2 12/14] pct: introduce mtunnel command Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 14/14] migration: intra-cluster: check config can be parsed on target node Fiona Ebner
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
The PVE::File module was introduced with libpve-common-perl=9.0.12.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
New in v2.
debian/control | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/debian/control b/debian/control
index 771c8eb..ce99e3d 100644
--- a/debian/control
+++ b/debian/control
@@ -6,7 +6,7 @@ Build-Depends: debhelper-compat (= 13),
dh-apparmor,
libpve-access-control (>= 8.0.0~),
libpve-cluster-perl,
- libpve-common-perl (>= 8.1.0),
+ libpve-common-perl (>= 9.0.12),
libpve-guest-common-perl (>= 5.1.0),
libpve-rs-perl (>= 0.11~),
libpve-storage-perl,
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH container v2 14/14] migration: intra-cluster: check config can be parsed on target node
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
` (12 preceding siblings ...)
2026-02-25 15:18 ` [PATCH container v2 13/14] d/control: bump versioned build dependency for libpve-common-perl to 9.0.12 Fiona Ebner
@ 2026-02-25 15:18 ` Fiona Ebner
13 siblings, 0 replies; 15+ messages in thread
From: Fiona Ebner @ 2026-02-25 15:18 UTC (permalink / raw)
To: pve-devel
For remote migration, we already check that the config can be parsed
on the target. Do the same for intra-cluster migration, to avoid
issues with future new settings unexpectedly being ignored if the
target is too old. For example, migrating a container with a
mountpoint with 'keepattrs' to a node with a too old pve-container
version, results in the mountpoint not being mounted on the target.
Signed-off-by: Fiona Ebner <f.ebner@proxmox.com>
---
Dependency bump for guest-common needed!
New in v2.
src/PVE/API2/LXC.pm | 11 +++++++++++
src/PVE/LXC/Migrate.pm | 37 +++++++++++++++++++++++++++++++++++++
2 files changed, 48 insertions(+)
diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm
index 7841215..ec0b815 100644
--- a/src/PVE/API2/LXC.pm
+++ b/src/PVE/API2/LXC.pm
@@ -1618,6 +1618,14 @@ __PACKAGE__->register_method({
minimum => '0',
default => 'migrate limit from datacenter or storage config',
},
+ force => {
+ type => 'boolean',
+ description =>
+ "For intra-cluster migration: allow migration even if there are configuration"
+ . " options not understood by the target. Only root may use this option.",
+ optional => 1,
+ default => 0,
+ },
},
},
returns => {
@@ -1644,6 +1652,9 @@ __PACKAGE__->register_method({
my $vmid = extract_param($param, 'vmid');
+ raise_param_exc({ force => "Only root may use this option." })
+ if $param->{force} && $authuser ne 'root@pam';
+
# test if VM exists
PVE::LXC::Config->load_config($vmid);
diff --git a/src/PVE/LXC/Migrate.pm b/src/PVE/LXC/Migrate.pm
index d243d90..1786e15 100644
--- a/src/PVE/LXC/Migrate.pm
+++ b/src/PVE/LXC/Migrate.pm
@@ -159,6 +159,43 @@ sub prepare {
my $cmd = [@{ $self->{rem_ssh} }, '/bin/true'];
eval { $self->cmd_quiet($cmd); };
die "Can't connect to destination address using public key\n" if $@;
+
+ if (!$self->{opts}->{force}) {
+ my $skip;
+
+ # TODO PVE 10 - simplify by assuming that 'pct mtunnel' command is supported
+ my $log_and_check_supported = sub {
+ my ($level, $msg) = @_;
+ # Skip if the target is too old to support 'pct mtunnel' and avoid printing the
+ # whole 'pct' usage output
+ return if $skip;
+ if ($msg =~ m/unknown command 'pct mtunnel'/) {
+ $skip = 1;
+ return;
+ }
+ $self->log($level, $msg);
+ };
+
+ my $tunnel = eval {
+ PVE::Tunnel::fork_ssh_tunnel(
+ $self->{rem_ssh}, ['pct', 'mtunnel'], undef, $log_and_check_supported);
+ };
+ my $err = $@;
+ if ($skip) {
+ $self->log('info', "skipping strict configuration check - target too old");
+ } else {
+ die $err if $err;
+
+ my $nodename = PVE::INotify::nodename();
+ eval { PVE::Tunnel::write_tunnel($tunnel, 10, "config $vmid $nodename"); };
+ if (my $err = $@) {
+ chomp($err);
+ die "$err - use --force to migrate regardless\n";
+ }
+ eval { PVE::Tunnel::finish_tunnel($tunnel); };
+ $self->log('warn', "failed to finish tunnel in prepare() - $@") if $@;
+ }
+ }
}
# in restart mode, we shutdown the container before migrating
--
2.47.3
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-02-25 15:20 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-25 15:18 [PATCH-SERIES qemu-server/guest-common/container v2 00/14] migration: strict config check for intra-cluster migration Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 01/14] d/control: bump versioned build dependency for libpve-common-perl to 9.0.12 Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 02/14] tests: migration: get rid of mocking for removed PVE::QemuMigrate::read_tunnel() Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 03/14] qm: mtunnel: avoid using deprecated check_running() helper Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 04/14] mtunnel: add 'conf' command to do strict configuration parsing Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 05/14] qm: mtunnel: reply when a command is unknown Fiona Ebner
2026-02-25 15:18 ` [PATCH qemu-server v2 06/14] migration: intra-cluster: check config can be parsed on target node Fiona Ebner
2026-02-25 15:18 ` [PATCH guest-common v2 07/14] tunnel: add missing IO::File module import Fiona Ebner
2026-02-25 15:18 ` [PATCH guest-common v2 08/14] tunnel: end module with true value as recommended by perlcritic Fiona Ebner
2026-02-25 15:18 ` [PATCH guest-common v2 09/14] tunnel: redirect stderr to log function Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 10/14] pct: add missing module imports and group according to style guide Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 11/14] migrate: add missing module imports Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 12/14] pct: introduce mtunnel command Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 13/14] d/control: bump versioned build dependency for libpve-common-perl to 9.0.12 Fiona Ebner
2026-02-25 15:18 ` [PATCH container v2 14/14] migration: intra-cluster: check config can be parsed on target node Fiona Ebner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox