From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) by lore.proxmox.com (Postfix) with ESMTPS id 6A6FC1FF13F for ; Thu, 12 Mar 2026 09:41:30 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 400749D81; Thu, 12 Mar 2026 09:41:03 +0100 (CET) From: Arthur Bied-Charreton To: pve-devel@lists.proxmox.com Subject: [PATCH qemu-server 8/8] api: qemu: Add CRUD handlers for custom CPU models Date: Thu, 12 Mar 2026 09:40:21 +0100 Message-ID: <20260312084021.124465-9-a.bied-charreton@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260312084021.124465-1-a.bied-charreton@proxmox.com> References: <20260312084021.124465-1-a.bied-charreton@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL -0.099 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods RDNS_NONE 0.793 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: D47UEPRWPQYKFJR2ECVPXWPU747NQEPB X-Message-ID-Hash: D47UEPRWPQYKFJR2ECVPXWPU747NQEPB X-MailFrom: abied-charreton@jett.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: Stefan Reiter X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add GET handlers for both all custom CPU models and specific ones, POST handler for creating custom CPU models, PUT handler for updating them, and DELETE handler for deleting them. Original patches: https://lore.proxmox.com/pve-devel/20211028114150.3245864-4-s.reiter@proxmox.com/ https://lore.proxmox.com/pve-devel/20211028114150.3245864-5-s.reiter@proxmox.com/ Originally-by: Stefan Reiter Signed-off-by: Arthur Bied-Charreton --- src/PVE/API2/Qemu/CPU.pm | 236 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 235 insertions(+), 1 deletion(-) diff --git a/src/PVE/API2/Qemu/CPU.pm b/src/PVE/API2/Qemu/CPU.pm index f8a7e11d..9d89504d 100644 --- a/src/PVE/API2/Qemu/CPU.pm +++ b/src/PVE/API2/Qemu/CPU.pm @@ -52,7 +52,7 @@ __PACKAGE__->register_method({ }, }, }, - links => [{ rel => 'child', href => '{name}' }], + links => [{ rel => 'child', href => 'model/{cputype}' }], }, code => sub { my ($param) = @_; @@ -67,4 +67,238 @@ __PACKAGE__->register_method({ }, }); +__PACKAGE__->register_method({ + name => 'config', + path => 'model', + method => 'GET', + description => 'Read all custom CPU models definitions', + permissions => { + check => ['perm', '/nodes', ['Sys.Audit']], + }, + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + }, + }, + returns => { + type => 'array', + items => { + type => 'object', + properties => get_standard_option('pve-qemu-cpu'), + }, + }, + code => sub { + my $conf = PVE::QemuServer::CPUConfig::load_custom_model_conf(); + delete $conf->{order}; + my $ids = []; + foreach my $id (keys %{ $conf->{ids} }) { + delete $conf->{ids}->{$id}->{type}; + push @$ids, $conf->{ids}->{$id}; + } + return $ids; + }, +}); + +__PACKAGE__->register_method({ + name => 'create', + path => 'model', + method => 'POST', + protected => 1, + description => 'Add a custom CPU model definition', + permissions => { + check => ['perm', '/nodes', ['Sys.Console']], + }, + parameters => { + additionalProperties => 0, + properties => PVE::QemuServer::CPUConfig::add_cpu_json_properties({ + digest => get_standard_option('pve-config-digest'), + node => get_standard_option('pve-node'), + }), + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + delete $param->{node}; + + my $digest = extract_param($param, 'digest'); + + my $name = $param->{cputype}; + die "custom cpu models 'cputype' must start with 'custom-' prefix\n" + if $name !~ m/^custom-/; + (my $name_no_prefix = $name) =~ s/^custom-//; + + PVE::QemuServer::CPUConfig::lock_cpu_config(sub { + my $conf = PVE::QemuServer::CPUConfig::load_custom_model_conf(); + PVE::SectionConfig::assert_if_modified($conf, $digest); + + die "custom CPU model '$name' already exists\n" + if defined($conf->{ids}->{$name_no_prefix}); + $conf->{ids}->{$name_no_prefix} = $param; + + PVE::QemuServer::CPUConfig::write_custom_model_conf($conf); + }); + }, +}); + +__PACKAGE__->register_method({ + name => 'delete', + path => 'model/{cputype}', + method => 'DELETE', + protected => 1, + description => 'Delete a custom CPU model definition', + permissions => { + check => ['perm', '/nodes', ['Sys.Console']], + }, + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + digest => get_standard_option('pve-config-digest'), + cputype => { + type => 'string', + description => "The custom model to delete. Must be prefixed with 'custom-'", + }, + }, + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + + my $digest = extract_param($param, 'digest'); + + my $name = $param->{cputype}; + die "'cputype' must start with 'custom-' prefix\n" if $name !~ m/^custom-/; + (my $name_no_prefix = $name) =~ s/^custom-//; + + PVE::QemuServer::CPUConfig::lock_cpu_config(sub { + my $conf = PVE::QemuServer::CPUConfig::load_custom_model_conf(); + PVE::SectionConfig::assert_if_modified($conf, $digest); + + die "custom CPU model '$name_no_prefix' does not exist\n" + if !defined($conf->{ids}->{$name_no_prefix}); + delete $conf->{ids}->{$name_no_prefix}; + + PVE::QemuServer::CPUConfig::write_custom_model_conf($conf); + }); + + }, +}); + +__PACKAGE__->register_method({ + name => 'update', + path => 'model/{cputype}', + method => 'PUT', + protected => 1, + description => "Update a custom CPU model definition.", + permissions => { + check => ['perm', '/nodes', ['Sys.Console']], + }, + parameters => { + additionalProperties => 0, + properties => PVE::QemuServer::CPUConfig::add_cpu_json_properties({ + node => get_standard_option('pve-node'), + digest => get_standard_option('pve-config-digest'), + delete => { + type => 'string', + format => 'pve-configid-list', + description => "A list of properties to delete.", + optional => 1, + }, + }), + }, + returns => { type => 'null' }, + code => sub { + my ($param) = @_; + delete $param->{node}; + + my $digest = extract_param($param, 'digest'); + my $delete = extract_param($param, 'delete') // ''; + my %delete_hash = map { $_ => 1 } PVE::Tools::split_list($delete); + + my $name = $param->{cputype}; + die "custom CPU model's 'cputype' must start with 'custom-' prefix\n" + if $name !~ m/^custom-/; + + (my $name_no_prefix = $name) =~ s/^custom-//; + + PVE::QemuServer::CPUConfig::lock_cpu_config(sub { + my $conf = PVE::QemuServer::CPUConfig::load_custom_model_conf(); + + PVE::SectionConfig::assert_if_modified($conf, $digest); + + my $model = $conf->{ids}->{$name_no_prefix}; + die "custom CPU model '$name_no_prefix' does not exist\n" if !defined($model); + + my $props = PVE::QemuServer::CPUConfig::add_cpu_json_properties({}); + for my $p (keys %$props) { + if ($delete_hash{$p}) { + die "cannot delete 'cputype' property\n" if $p eq 'cputype'; + die "cannot set and delete property '$p' at once\n" if $param->{$p}; + delete $model->{$p}; + } elsif (defined($param->{$p})) { + $model->{$p} = $param->{$p}; + } + } + + PVE::QemuServer::CPUConfig::write_custom_model_conf($conf); + }); + }, +}); + +__PACKAGE__->register_method({ + name => 'info', + path => 'model/{cputype}', + method => 'GET', + description => 'Retrieve details about a specific CPU model', + permissions => { + check => ['perm', '/nodes', ['Sys.Audit']], + }, + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + cputype => { + type => 'string', + description => + "Name of the CPU model to query. Prefix with 'custom-' for custom models.", + }, + }, + }, + returns => { + type => 'object', + properties => PVE::QemuServer::CPUConfig::add_cpu_json_properties({ + vendor => { + type => 'string', + description => 'The CPU vendor reported to the guest OS. Only' + . ' relevant for default models.', + optional => 1, + }, + digest => get_standard_option('pve-config-digest'), + }), + }, + code => sub { + my ($param) = @_; + my $cputype = $param->{cputype}; + + if ($cputype =~ m/^custom-/) { + my $conf = PVE::QemuServer::CPUConfig::load_custom_model_conf(); + my $digest = $conf->{digest}; + my $retval = PVE::QemuServer::CPUConfig::get_custom_model($cputype); + $retval->{digest} = $digest; + return $retval; + } + + # this correctly errors in case $cputype is unknown + my $vendor = PVE::QemuServer::CPUConfig::get_cpu_vendor($cputype); + + my $retval = { + cputype => $cputype, + vendor => $vendor, + }; + + return $retval; + }, +}); + 1; -- 2.47.3