public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Erik Fastermann <e.fastermann@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Erik Fastermann <e.fastermann@proxmox.com>
Subject: [RFC qemu-server 3/4] qm: remote-migrate: call API endpoint directly
Date: Tue, 21 Jul 2026 13:58:26 +0200	[thread overview]
Message-ID: <20260721115827.163442-4-e.fastermann@proxmox.com> (raw)
In-Reply-To: <20260721115827.163442-1-e.fastermann@proxmox.com>

The command wrapper only ran a subset of the precondition checks the
endpoint now performs itself. Register the 'remote-migrate' command
against PVE::API2::Qemu directly, as done for 'migrate' and the other
commands, and drop the wrapper.

Also remove the now unused PVE::APIClient::LWP import.

Signed-off-by: Erik Fastermann <e.fastermann@proxmox.com>
---
 src/PVE/CLI/qm.pm | 128 +---------------------------------------------
 1 file changed, 1 insertion(+), 127 deletions(-)

diff --git a/src/PVE/CLI/qm.pm b/src/PVE/CLI/qm.pm
index b903c1f1..ec87972c 100755
--- a/src/PVE/CLI/qm.pm
+++ b/src/PVE/CLI/qm.pm
@@ -15,7 +15,6 @@ use POSIX qw(strftime);
 use Term::ReadLine;
 use URI::Escape;
 
-use PVE::APIClient::LWP;
 use PVE::Cluster;
 use PVE::Exception qw(raise_param_exc);
 use PVE::GuestHelpers;
@@ -172,131 +171,6 @@ __PACKAGE__->register_method({
     },
 });
 
-__PACKAGE__->register_method({
-    name => 'remote_migrate_vm',
-    path => 'remote_migrate_vm',
-    method => 'POST',
-    description =>
-        "Migrate virtual machine to a remote cluster. Creates a new migration task. EXPERIMENTAL feature!",
-    permissions => {
-        check => ['perm', '/vms/{vmid}', ['VM.Migrate']],
-    },
-    parameters => {
-        additionalProperties => 0,
-        properties => {
-            node => get_standard_option('pve-node'),
-            vmid =>
-                get_standard_option('pve-vmid', { completion => \&PVE::QemuServer::complete_vmid }),
-            'target-vmid' => get_standard_option('pve-vmid', { optional => 1 }),
-            'target-endpoint' => get_standard_option('proxmox-remote', {
-                    description => "Remote target endpoint",
-            }),
-            online => {
-                type => 'boolean',
-                description =>
-                    "Use online/live migration if VM is running. Ignored if VM is stopped.",
-                optional => 1,
-            },
-            delete => {
-                type => 'boolean',
-                description =>
-                    "Delete the original VM and related data after successful migration. By default the original VM is kept on the source cluster in a stopped state.",
-                optional => 1,
-                default => 0,
-            },
-            'target-storage' => get_standard_option(
-                'pve-targetstorage',
-                {
-                    completion => \&PVE::QemuServer::complete_migration_storage,
-                    optional => 0,
-                },
-            ),
-            'target-bridge' => {
-                type => 'string',
-                description =>
-                    "Mapping from source to target bridges. Providing only a single bridge ID maps all source bridges to that bridge. Providing the special value '1' will map each source bridge to itself.",
-                format => 'bridge-pair-list',
-            },
-            bwlimit => {
-                description => "Override I/O bandwidth limit (in KiB/s).",
-                optional => 1,
-                type => 'integer',
-                minimum => '0',
-                default => 'migrate limit from datacenter or storage config',
-            },
-        },
-    },
-    returns => {
-        type => 'string',
-        description => "the task ID.",
-    },
-    code => sub {
-        my ($param) = @_;
-
-        my $rpcenv = PVE::RPCEnvironment::get();
-        my $authuser = $rpcenv->get_user();
-
-        my $source_vmid = $param->{vmid};
-        my $target_endpoint = $param->{'target-endpoint'};
-        my $target_vmid = $param->{'target-vmid'} // $source_vmid;
-
-        my $remote = PVE::JSONSchema::parse_property_string('proxmox-remote', $target_endpoint);
-
-        # TODO: move this as helper somewhere appropriate?
-        my $conn_args = {
-            protocol => 'https',
-            host => $remote->{host},
-            port => $remote->{port} // 8006,
-            apitoken => $remote->{apitoken},
-        };
-
-        $conn_args->{cached_fingerprints} = { uc($remote->{fingerprint}) => 1 }
-            if defined($remote->{fingerprint});
-
-        my $api_client = PVE::APIClient::LWP->new(%$conn_args);
-        my $resources = $api_client->get("/cluster/resources", { type => 'vm' });
-        if (grep { defined($_->{vmid}) && $_->{vmid} eq $target_vmid } @$resources) {
-            raise_param_exc(
-                {
-                    target_vmid =>
-                        "Guest with ID '$target_vmid' already exists on remote cluster",
-                },
-            );
-        }
-
-        my $storages = $api_client->get("/nodes/localhost/storage", { enabled => 1 });
-
-        my $storecfg = PVE::Storage::config();
-        my $target_storage = $param->{'target-storage'};
-        my $storagemap =
-            eval { PVE::JSONSchema::parse_idmap($target_storage, 'pve-storage-id') };
-        raise_param_exc({ 'target-storage' => "failed to parse storage map: $@" })
-            if $@;
-
-        my $check_remote_storage = sub {
-            my ($storage) = @_;
-            my $found = [grep { $_->{storage} eq $storage } @$storages];
-            die "remote: storage '$storage' does not exist (or missing permission)!\n"
-                if !@$found;
-
-            $found = @$found[0];
-
-            my $content_types = [PVE::Tools::split_list($found->{content})];
-            die "remote: storage '$storage' cannot store images\n"
-                if !grep { $_ eq 'images' } @$content_types;
-        };
-
-        foreach my $target_sid (values %{ $storagemap->{entries} }) {
-            $check_remote_storage->($target_sid);
-        }
-
-        $check_remote_storage->($storagemap->{default})
-            if $storagemap->{default};
-
-        return PVE::API2::Qemu->remote_migrate_vm($param);
-    },
-});
-
 __PACKAGE__->register_method({
     name => 'status',
     path => 'status',
@@ -1361,7 +1235,7 @@ our $cmddef = {
 
     migrate => ["PVE::API2::Qemu", 'migrate_vm', ['vmid', 'target'], {%node}, $upid_exit],
     'remote-migrate' => [
-        __PACKAGE__,
+        "PVE::API2::Qemu",
         'remote_migrate_vm',
         ['vmid', 'target-vmid', 'target-endpoint'],
         {%node},
-- 
2.47.3




  parent reply	other threads:[~2026-07-21 11:58 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-21 11:58 [RFC qemu-server 0/4] remote migrate: extract preconditions and add check endpoint Erik Fastermann
2026-07-21 11:58 ` [RFC qemu-server 1/4] remote migrate: drop ineffective fingerprint auto-detection Erik Fastermann
2026-07-21 11:58 ` [RFC qemu-server 2/4] remote migrate: collect preconditions as structured findings Erik Fastermann
2026-07-21 11:58 ` Erik Fastermann [this message]
2026-07-21 11:58 ` [RFC qemu-server 4/4] remote migrate: add precondition check endpoint Erik Fastermann

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=20260721115827.163442-4-e.fastermann@proxmox.com \
    --to=e.fastermann@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