From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [IPv6:2a0f:8001:1:32::40]) by lore.proxmox.com (Postfix) with ESMTPS id 986BE1FF0BC for ; Thu, 15 Oct 2026 14:54:31 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 29A4E2172B; Thu, 15 Oct 2026 14:52:31 +0200 (CEST) From: Fiona Ebner To: pve-devel@lists.proxmox.com Subject: [PATCH ha-manager v4 14/27] api: resources: migration: support additional migration options Date: Thu, 15 Oct 2026 14:50:48 +0200 Message-ID: <20261015125112.174954-15-f.ebner@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20261015125112.174954-1-f.ebner@proxmox.com> References: <20261015125112.174954-1-f.ebner@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1792068677004 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.399 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) RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: G763TT6UYB3NVQRXMQMLAZNXZVXWRSVX X-Message-ID-Hash: G763TT6UYB3NVQRXMQMLAZNXZVXWRSVX X-MailFrom: f.ebner@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 X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Additional migration options can now be passed from the guest API endpoints to the HA stack. From the HA resource API endpoint, the options are passed all the way to the resource plugins. Check that all nodes are recent enough to support the new-style JSON CRM command, otherwise produce a warning and fall-back to the previous behavior ignoring the additional migration options. The migration API endpoints for containers and VMs use different parameters. For example, the endpoint for containers does not have the 'with-conntrack-state' option. To keep the schema for the more abstract endpoint for HA resources clean, separate by type using a oneOf schema and let the resource plugins declare additional parameters via a migrate_json_properties() method. Initially, this will be used to fix #7053 and pass along the 'with-conntrack-state' migration option. Signed-off-by: Fiona Ebner --- Dependency bump for pve-cluster needed! Changes in v4: * Add cfs_update() call. * Import raise_param_exc(). * Ensure error messages are also visible on the CLI. * Only use JSON-style command if required to avoid fallout in scenarios with old manager that fails to report its version properly * Document why continuing when the version-info for a node cannot be parsed is better than aborting. src/PVE/API2/HA/Resources.pm | 110 ++++++++++++++++++++++++++++++----- src/PVE/HA/Resources.pm | 1 + 2 files changed, 97 insertions(+), 14 deletions(-) diff --git a/src/PVE/API2/HA/Resources.pm b/src/PVE/API2/HA/Resources.pm index b96572b..6a97b2f 100644 --- a/src/PVE/API2/HA/Resources.pm +++ b/src/PVE/API2/HA/Resources.pm @@ -3,15 +3,20 @@ package PVE::API2::HA::Resources; use strict; use warnings; +use JSON qw(); + +use PVE::Exception qw(raise_param_exc); use PVE::SafeSyslog; use PVE::Tools qw(extract_param); use PVE::Cluster; use PVE::HA::Config; +use PVE::HA::Env::PVE2; use PVE::HA::Resources; use HTTP::Status qw(:constants); use Storable qw(dclone); use PVE::JSONSchema qw(get_standard_option); use PVE::RPCEnvironment; +use PVE::RESTEnvironment qw(log_warn); use PVE::RESTHandler; @@ -34,6 +39,49 @@ my $api_copy_config = sub { return $scfg; }; +my sub migrate_json_properties { + my ($type) = @_; + my $plugin = PVE::HA::Resources->lookup($type); + return $plugin->migrate_json_properties(); +} + +my sub json_crm_command_is_supported { + PVE::Cluster::cfs_update(); + my $version_info = PVE::Cluster::get_node_kv('version-info'); + for my $node (sort keys $version_info->%*) { + my $node_info = eval { JSON::decode_json($version_info->{$node}); }; + if (my $err = $@) { + # An updated cluster is the usual situation. An error here should not affect the usual + # situation. The unusual situation, where the master is too old and unable to report its + # version properly is rare. Submitting a JSON CRM to such a master will lead to warnings + # and the command being ignored. Note that migration without extra parameters also still + # works. + log_warn("failed check for migration options support - cannot parse version info for" + . " node '$node' - $err"); + } elsif (!PVE::Cluster::pvecfg_min_version($node_info->{version}, 9, 2, 22)) { + return 0; + } + } + + return 1; +} + +my sub json_crm_command_is_applicable { + my ($sid, $req_node, $param) = @_; + + # FIXME: MAJOR VERSION: use JSON variant even if there are no extra params. + if (scalar(keys $param->%*) > 0) { + if (json_crm_command_is_supported()) { + return 1; + } else { + log_warn("$sid migration to $req_node: ignoring additional migration options - not" + . " supported by CRM on all nodes"); + } + } + + return 0; +} + sub check_service_state { my ($sid, $req_state) = @_; @@ -336,20 +384,32 @@ __PACKAGE__->register_method({ check => ['perm', '/', ['Sys.Console']], }, parameters => { - additionalProperties => 0, - properties => { - sid => get_standard_option( - 'pve-ha-resource-or-vm-id', - { completion => \&PVE::HA::Tools::complete_sid }, - ), - node => get_standard_option( - 'pve-node', - { - completion => \&PVE::Cluster::complete_migration_target, - description => "Target node.", - }, - ), + 'type-property' => 'resource-type', + 'type-property-schema' => { + type => 'string', + description => 'The resource type. Automatically determined from the service ID.', + enum => $resource_type_enum, }, + oneOf => [ + { + 'instance-type' => 'ct', + additionalProperties => 0, + properties => migrate_json_properties('ct'), + }, + { + 'instance-type' => 'vm', + additionalProperties => 0, + properties => migrate_json_properties('vm'), + }, + ], + }, + resolve_type => sub { + my ($param) = @_; + my $type = (PVE::HA::Config::parse_sid($param->{sid}))[1]; + if (!($type eq 'ct' || $type eq 'vm')) { + die "Unknown service type '$type' for 'migrate' endpoint schema.\n"; + } + return $type; }, returns => { type => 'object', @@ -403,12 +463,34 @@ __PACKAGE__->register_method({ my ($sid, $type, $name) = PVE::HA::Config::parse_sid(extract_param($param, 'sid')); my $req_node = extract_param($param, 'node'); + my $resource_type = extract_param($param, 'resource-type'); + if ($resource_type ne $type) { + raise_param_exc({ + 'resource-type' => "value '$resource_type' does not match type of '$sid'" }); + } + + # The rest of the parameters in $param are migration options passed along to the plugin. PVE::HA::Config::service_is_ha_managed($sid); check_service_state($sid); - PVE::HA::Config::queue_crm_commands("migrate $sid $req_node"); + my $crm_command; + if (json_crm_command_is_applicable($sid, $req_node, $param)) { + $crm_command = JSON::to_json( + { + kind => 'migrate', + sid => $sid, + node => $req_node, + options => $param, + }, + { canonical => 1 }, + ); + } else { + $crm_command = "migrate $sid $req_node"; + } + + PVE::HA::Config::queue_crm_commands($crm_command); $result->{sid} = $sid; $result->{'requested-node'} = $req_node; diff --git a/src/PVE/HA/Resources.pm b/src/PVE/HA/Resources.pm index 57fde00..ad603dc 100644 --- a/src/PVE/HA/Resources.pm +++ b/src/PVE/HA/Resources.pm @@ -3,6 +3,7 @@ package PVE::HA::Resources; use strict; use warnings; +use PVE::Cluster; use PVE::JSONSchema qw(get_standard_option); use PVE::SectionConfig; use PVE::HA::Tools; -- 2.47.3