[pve-devel] [PATCH v6 qemu-server 6/6] qm: add remote-migrate command
DERUMIER, Alexandre
Alexandre.DERUMIER at groupe-cyllene.com
Mon Oct 17 16:40:27 CEST 2022
Hi Fabian,
> an example invocation:
>
> $ qm remote-migrate 1234 4321
'host=123.123.123.123,apitoken=pveapitoken=user at pve!incoming=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee,fingerprint=aa:bb:cc:dd:ee:ff:aa:bb:cc:dd:ee:ff:aa:bb:cc:dd:ee:ff:aa:bb:cc:dd:ee:ff:aa:bb:cc:dd:ee:ff:aa:bb'
--target-bridge vmbr0 --target-storage zfs-a:rbd-b,nfs-c:dir-d,zfs-e
--online
Maybe it could be better (optionnaly) to store the long
"'host=123.123.123.123,apitoken=pveapitoken=user at pve!incoming=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee,fingerprint=aa:bb:cc:dd:ee:ff:aa:bb:cc:dd:ee:ff:aa:bb:cc:dd:ee:ff:aa:bb:cc:dd:ee:ff:aa:bb:cc:dd:ee:ff:aa:bb'
"
in a config file in /etc/pve/priv/<targethost>.conf ?
Like this, this should avoid to have the api token in the bash history.
maybe something like:
qm remote-migration 1234 4321 <targethost> ....
?
Le 28/09/22 à 14:50, Fabian Grünbichler a écrit :
> which wraps the remote_migrate_vm API endpoint, but does the
> precondition checks that can be done up front itself.
>
> this now just leaves the FP retrieval and target node name lookup to the
> sync part of the API endpoint, which should be do-able in <30s ..
>
>
> will migrate the local VM 1234 to the host 123.123.1232.123 using the
> given API token, mapping the VMID to 4321 on the target cluster, all its
> virtual NICs to the target vm bridge 'vmbr0', any volumes on storage
> zfs-a to storage rbd-b, any on storage nfs-c to storage dir-d, and any
> other volumes to storage zfs-e. the source VM will be stopped but remain
> on the source node/cluster after the migration has finished.
>
> Signed-off-by: Fabian Grünbichler <f.gruenbichler at proxmox.com>
> ---
>
> Notes:
> v6:
> - mark as experimental
> - drop `with-local-disks` parameter from API, always set to true
> - add example invocation to commit message
>
> v5: rename to 'remote-migrate'
>
> PVE/API2/Qemu.pm | 31 -------------
> PVE/CLI/qm.pm | 113 +++++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 113 insertions(+), 31 deletions(-)
>
> diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
> index fa31e973..57083601 100644
> --- a/PVE/API2/Qemu.pm
> +++ b/PVE/API2/Qemu.pm
> @@ -4416,17 +4416,6 @@ __PACKAGE__->register_method({
> $param->{online} = 0;
> }
>
> - # FIXME: fork worker hear to avoid timeout? or poll these periodically
> - # in pvestatd and access cached info here? all of the below is actually
> - # checked at the remote end anyway once we call the mtunnel endpoint,
> - # we could also punt it to the client and not do it here at all..
> - 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 = extract_param($param, 'target-storage');
> my $storagemap = eval { PVE::JSONSchema::parse_idmap($target_storage, 'pve-storage-id') };
> @@ -4438,26 +4427,6 @@ __PACKAGE__->register_method({
> raise_param_exc({ 'target-bridge' => "failed to parse bridge map: $@" })
> if $@;
>
> - my $check_remote_storage = sub {
> - my ($storage) = @_;
> - my $found = [ grep { $_->{storage} eq $storage } @$storages ];
> - die "remote: storage '$storage' does not exist!\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};
> -
> die "remote migration requires explicit storage mapping!\n"
> if $storagemap->{identity};
>
> diff --git a/PVE/CLI/qm.pm b/PVE/CLI/qm.pm
> index ca5d25fc..a6a63566 100755
> --- a/PVE/CLI/qm.pm
> +++ b/PVE/CLI/qm.pm
> @@ -15,6 +15,7 @@ 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;
> @@ -158,6 +159,117 @@ __PACKAGE__->register_method ({
> return;
> }});
>
> +
> +__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!\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',
> @@ -900,6 +1012,7 @@ our $cmddef = {
> clone => [ "PVE::API2::Qemu", 'clone_vm', ['vmid', 'newid'], { node => $nodename }, $upid_exit ],
>
> migrate => [ "PVE::API2::Qemu", 'migrate_vm', ['vmid', 'target'], { node => $nodename }, $upid_exit ],
> + 'remote-migrate' => [ __PACKAGE__, 'remote_migrate_vm', ['vmid', 'target-vmid', 'target-endpoint'], { node => $nodename }, $upid_exit ],
>
> set => [ "PVE::API2::Qemu", 'update_vm', ['vmid'], { node => $nodename } ],
>
More information about the pve-devel
mailing list