From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id 225401FF0E6 for ; Fri, 07 Aug 2026 11:12:50 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id DF56C21586; Fri, 07 Aug 2026 11:12:41 +0200 (CEST) From: Erik Fastermann To: pve-devel@lists.proxmox.com Subject: [PATCH qemu-server v2 3/5] remote migration: pull in checks from qm Date: Fri, 7 Aug 2026 11:12:25 +0200 Message-ID: <20260807091227.73614-4-e.fastermann@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260807091227.73614-1-e.fastermann@proxmox.com> References: <20260807091227.73614-1-e.fastermann@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 1 AWL -0.546 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 1.274 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Message-ID-Hash: YJNCUGHZMDJP6RPC4PNOO7FMQLQJTLHV X-Message-ID-Hash: YJNCUGHZMDJP6RPC4PNOO7FMQLQJTLHV X-MailFrom: efastermann@ruth.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 CC: Erik Fastermann X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Pull in the checks that previously lived only in the qm CLI into the endpoint. Direct API callers such as the web UI now run them too, so fewer migrations start only to fail partway. The logic is mostly identical. Error messages are reworded slightly and errors are collected instead of dying on the first one. Signed-off-by: Erik Fastermann --- src/PVE/API2/Qemu.pm | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/PVE/API2/Qemu.pm b/src/PVE/API2/Qemu.pm index b16a1365..1d94babd 100644 --- a/src/PVE/API2/Qemu.pm +++ b/src/PVE/API2/Qemu.pm @@ -1136,6 +1136,47 @@ my sub validate_remote_migrate_preconditions { return; } + my $resources = eval { $api_client->get("/cluster/resources", { type => 'vm' }) }; + if (my $err = $@) { + $add_error->('remote-query', $err); + } elsif (grep { defined($_->{vmid}) && $_->{vmid} eq $target_vmid } @$resources) { + $add_error->('target-vmid-exists', "remote: guest with ID '$target_vmid' already exists"); + } + + my $storages = eval { $api_client->get("/nodes/localhost/storage", { enabled => 1 }) }; + if (my $err = $@) { + $add_error->('remote-query', $err); + } else { + my $check_remote_storage = sub { + my ($storage) = @_; + my $found = [grep { $_->{storage} eq $storage } @$storages]; + + if (!@$found) { + $add_error->( + 'storage-missing', + "remote: storage '$storage' does not exist (or missing permission)", + storage => $storage, + ); + return; + } + + $found = $found->[0]; + my $content_types = [PVE::Tools::split_list($found->{content})]; + $add_error->( + 'storage-no-images', + "remote: storage '$storage' cannot store images", + storage => $storage, + ) if !grep { $_ eq 'images' } @$content_types; + }; + + for my $target_sid (values $storagemap->{entries}->%*) { + $check_remote_storage->($target_sid); + } + + $check_remote_storage->($storagemap->{default}) + if $storagemap->{default}; + } + my $repl_conf = PVE::ReplicationConfig->new(); my $is_replicated = $repl_conf->check_for_existing_jobs($source_vmid, 1); $add_error->('vm-replicated', "cannot remote-migrate replicated VM") -- 2.47.3