* [PATCH pve-manager 1/8] ui: VMCPUFlagSelector: Fix unknownFlags behaviour
2026-03-12 8:40 [PATCH manager/qemu-server 0/8] Add API and UI for custom CPU models Arthur Bied-Charreton
@ 2026-03-12 8:40 ` Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH pve-manager 2/8] ui: CPUModelSelector: Fix dirty state on default Arthur Bied-Charreton
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Arthur Bied-Charreton @ 2026-03-12 8:40 UTC (permalink / raw)
To: pve-devel; +Cc: Stefan Reiter
...and typo in name.
Original patch:
https://lore.proxmox.com/pve-devel/20211028114150.3245864-6-s.reiter@proxmox.com/
Originally-by: Stefan Reiter <s.reiter@proxmox.com>
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
www/manager6/form/VMCPUFlagSelector.js | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/www/manager6/form/VMCPUFlagSelector.js b/www/manager6/form/VMCPUFlagSelector.js
index 922fcfa6..74b1a2c4 100644
--- a/www/manager6/form/VMCPUFlagSelector.js
+++ b/www/manager6/form/VMCPUFlagSelector.js
@@ -14,7 +14,7 @@ Ext.define('PVE.form.VMCPUFlagSelector', {
scrollable: 'y',
height: 200,
- unkownFlags: [],
+ unknownFlags: [],
store: {
type: 'store',
@@ -62,32 +62,32 @@ Ext.define('PVE.form.VMCPUFlagSelector', {
}
});
- flags += me.unkownFlags.join(';');
+ flags += (me.unknownFlags.length > 0 ? ';' : '') + me.unknownFlags.join(';');
return flags;
},
- // Adjusts the store for the current value and determines the unkown flags based on what the
+ // Adjusts the store for the current value and determines the unknown flags based on what the
// store does not know.
adjustStoreForValue: function () {
let me = this;
let store = me.getStore();
let value = me.value;
- me.unkownFlags = [];
+ me.unknownFlags = [];
store.getData().each((rec) => rec.set('state', '='));
let flags = value ? value.split(';') : [];
flags.forEach(function (flag) {
let sign = flag.substr(0, 1);
- flag = flag.substr(1);
+ let flagName = flag.substr(1);
- let rec = store.findRecord('name', flag, 0, false, true, true);
+ let rec = store.findRecord('name', flagName, 0, false, true, true);
if (rec !== null) {
rec.set('state', sign);
} else {
- me.unkownFlags.push(flag);
+ me.unknownFlags.push(flag);
}
});
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH pve-manager 2/8] ui: CPUModelSelector: Fix dirty state on default
2026-03-12 8:40 [PATCH manager/qemu-server 0/8] Add API and UI for custom CPU models Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH pve-manager 1/8] ui: VMCPUFlagSelector: Fix unknownFlags behaviour Arthur Bied-Charreton
@ 2026-03-12 8:40 ` Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH pve-manager 3/8] ui: CPUModelSelector: Allow filtering out custom models Arthur Bied-Charreton
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Arthur Bied-Charreton @ 2026-03-12 8:40 UTC (permalink / raw)
To: pve-devel; +Cc: Stefan Reiter
'originalValue' is set to null in case it's the default, but getValue
returns an empty string. This means that when editing a VM's CPU config
when the model is 'default', the form would always be marked dirty.
Original patch:
https://lore.proxmox.com/pve-devel/20211028114150.3245864-7-s.reiter@proxmox.com/
Originally-by: Stefan Reiter <s.reiter@proxmox.com>
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
www/manager6/form/CPUModelSelector.js | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/www/manager6/form/CPUModelSelector.js b/www/manager6/form/CPUModelSelector.js
index 2ebd08eb..737b6ff4 100644
--- a/www/manager6/form/CPUModelSelector.js
+++ b/www/manager6/form/CPUModelSelector.js
@@ -39,7 +39,11 @@ Ext.define('PVE.form.CPUModelSelector', {
],
width: 360,
},
-
+ getValue: function () {
+ let me = this;
+ let val = me.callParent();
+ return val === '' ? null : val;
+ },
store: {
autoLoad: true,
model: 'PVE.data.CPUModel',
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH pve-manager 3/8] ui: CPUModelSelector: Allow filtering out custom models
2026-03-12 8:40 [PATCH manager/qemu-server 0/8] Add API and UI for custom CPU models Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH pve-manager 1/8] ui: VMCPUFlagSelector: Fix unknownFlags behaviour Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH pve-manager 2/8] ui: CPUModelSelector: Fix dirty state on default Arthur Bied-Charreton
@ 2026-03-12 8:40 ` Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH pve-manager 4/8] ui: Add basic custom CPU model editor Arthur Bied-Charreton
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Arthur Bied-Charreton @ 2026-03-12 8:40 UTC (permalink / raw)
To: pve-devel; +Cc: Stefan Reiter
Add 'allowCustom' configuration parameter. When set to false, only
default CPU models will be shown.
Original patch:
https://lore.proxmox.com/pve-devel/20211028114150.3245864-8-s.reiter@proxmox.com/
Originally-by: Stefan Reiter <s.reiter@proxmox.com>
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
www/manager6/form/CPUModelSelector.js | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/www/manager6/form/CPUModelSelector.js b/www/manager6/form/CPUModelSelector.js
index 737b6ff4..2154ff46 100644
--- a/www/manager6/form/CPUModelSelector.js
+++ b/www/manager6/form/CPUModelSelector.js
@@ -19,7 +19,9 @@ Ext.define('PVE.form.CPUModelSelector', {
autoSelect: false,
deleteEmpty: true,
-
+ config: {
+ allowCustom: true,
+ },
listConfig: {
columns: [
{
@@ -100,4 +102,11 @@ Ext.define('PVE.form.CPUModelSelector', {
},
},
},
+ initComponent: function () {
+ let me = this;
+ me.callParent();
+ if (!me.allowCustom) {
+ me.getStore().addFilter({ filterFn: (rec) => !rec.data.custom });
+ }
+ },
});
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH pve-manager 4/8] ui: Add basic custom CPU model editor
2026-03-12 8:40 [PATCH manager/qemu-server 0/8] Add API and UI for custom CPU models Arthur Bied-Charreton
` (2 preceding siblings ...)
2026-03-12 8:40 ` [PATCH pve-manager 3/8] ui: CPUModelSelector: Allow filtering out custom models Arthur Bied-Charreton
@ 2026-03-12 8:40 ` Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH pve-manager 5/8] ui: Add CPU flag editor for custom models Arthur Bied-Charreton
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Arthur Bied-Charreton @ 2026-03-12 8:40 UTC (permalink / raw)
To: pve-devel; +Cc: Stefan Reiter
Add components for creating/updating custom CPU models, allowing to set
the values that would normally have to be set manually in
`/etc/pve/virtual-guest/cpu-models.conf` [0].
Original patch:
https://lore.proxmox.com/pve-devel/20211028114150.3245864-9-s.reiter@proxmox.com/
[0] https://pve.proxmox.com/wiki/Manual:_cpu-models.conf
Originally-by: Stefan Reiter <s.reiter@proxmox.com>
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
www/manager6/Makefile | 3 +
www/manager6/dc/CPUTypeEdit.js | 90 +++++++++++++++
www/manager6/dc/CPUTypeView.js | 139 +++++++++++++++++++++++
www/manager6/dc/Config.js | 6 +
www/manager6/form/PhysBitsSelector.js | 153 ++++++++++++++++++++++++++
5 files changed, 391 insertions(+)
create mode 100644 www/manager6/dc/CPUTypeEdit.js
create mode 100644 www/manager6/dc/CPUTypeView.js
create mode 100644 www/manager6/form/PhysBitsSelector.js
diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 4558d53e..fbde2327 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -58,6 +58,7 @@ JSSRC= \
form/PCISelector.js \
form/PCIMapSelector.js \
form/PermPathSelector.js \
+ form/PhysBitsSelector.js \
form/PoolSelector.js \
form/PreallocationSelector.js \
form/PrivilegesSelector.js \
@@ -170,6 +171,8 @@ JSSRC= \
dc/CmdMenu.js \
dc/Config.js \
dc/CorosyncLinkEdit.js \
+ dc/CPUTypeEdit.js \
+ dc/CPUTypeView.js \
dc/GroupEdit.js \
dc/GroupView.js \
dc/Guests.js \
diff --git a/www/manager6/dc/CPUTypeEdit.js b/www/manager6/dc/CPUTypeEdit.js
new file mode 100644
index 00000000..8cf508b4
--- /dev/null
+++ b/www/manager6/dc/CPUTypeEdit.js
@@ -0,0 +1,90 @@
+Ext.define('PVE.dc.CPUTypeEdit', {
+ extend: 'Proxmox.window.Edit',
+ alias: ['widget.pveCpuTypeEdit'],
+ mixins: ['Proxmox.Mixin.CBind'],
+
+ subject: gettext('CPU Type'),
+
+ // Avoid default-focusing the reported model dropdown while still
+ // focusing the name textfield if it is editable
+ defaultFocus: 'textfield',
+
+ height: 600,
+ width: 800,
+
+ cbindData: {
+ cputype: '',
+ isCreate: (cfg) => !cfg.cputype,
+ },
+
+ cbind: {
+ autoLoad: (get) => !get('isCreate'),
+ url: (get) => `/api2/extjs/nodes/localhost/capabilities/qemu/cpu/model/${get('cputype')}`,
+ method: (get) => (get('isCreate') ? 'POST' : 'PUT'),
+ isCreate: (get) => get('isCreate'),
+ },
+
+ getValues: function () {
+ let me = this;
+ let values = me.callParent();
+
+ PVE.Utils.delete_if_default(values, 'reported-model', '', me.isCreate);
+ PVE.Utils.delete_if_default(values, 'hv-vendor-id', '', me.isCreate);
+ PVE.Utils.delete_if_default(values, 'phys-bits', '', me.isCreate);
+ PVE.Utils.delete_if_default(values, 'flags', '', me.isCreate);
+
+ if (me.isCreate && !values.cputype.match(/^custom-/)) {
+ values.cputype = 'custom-' + values.cputype;
+ }
+
+ return values;
+ },
+
+ items: [
+ {
+ xtype: 'inputpanel',
+ column1: [
+ {
+ xtype: 'pmxDisplayEditField',
+ fieldLabel: gettext('Name'),
+ cbind: {
+ editable: '{isCreate}',
+ value: '{cputype}',
+ },
+ name: 'cputype',
+ renderer: (val) => val.replace(/^custom-/, ''),
+ allowBlank: false,
+ },
+ {
+ xtype: 'CPUModelSelector',
+ fieldLabel: gettext('Reported Model'),
+ allowCustom: false,
+ name: 'reported-model',
+ },
+ {
+ xtype: 'textfield',
+ fieldLabel: gettext('Hyper-V Vendor'),
+ name: 'hv-vendor-id',
+ allowBlank: true,
+ emptyText: gettext('None'),
+ maxLength: 12,
+ },
+ ],
+ column2: [
+ {
+ xtype: 'checkbox',
+ fieldLabel: gettext('Hidden'),
+ name: 'hidden',
+ inputValue: 1,
+ uncheckedValue: 0,
+ },
+ {
+ xtype: 'PhysBitsSelector',
+ fieldLabel: gettext('Phys-Bits'),
+ name: 'phys-bits',
+ },
+ ],
+
+ },
+ ],
+});
diff --git a/www/manager6/dc/CPUTypeView.js b/www/manager6/dc/CPUTypeView.js
new file mode 100644
index 00000000..c79ce690
--- /dev/null
+++ b/www/manager6/dc/CPUTypeView.js
@@ -0,0 +1,139 @@
+Ext.define('pve-custom-cpu-type', {
+ extend: 'Ext.data.Model',
+ fields: [
+ 'cputype',
+ 'reported-model',
+ 'hv-vendor-id',
+ 'flags',
+ 'phys-bits',
+ { name: 'hidden', type: 'boolean' },
+ ],
+});
+
+Ext.define('PVE.dc.CPUTypeView', {
+ extend: 'Ext.grid.GridPanel',
+ alias: ['widget.pveCPUTypeView'],
+
+ onlineHelp: 'qm_cpu',
+
+ store: {
+ model: 'pve-custom-cpu-type',
+ proxy: {
+ type: 'proxmox',
+ url: '/api2/json/nodes/localhost/capabilities/qemu/cpu/model',
+ },
+ autoLoad: true,
+ sorters: ['cputype'],
+ },
+
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ getSelection: function () {
+ let me = this;
+ let grid = me.getView();
+ let selection = grid.getSelection();
+ if (selection.length === 1) {
+ return selection[0].data;
+ }
+ return null;
+ },
+
+ showEditor: function (cputype) {
+ let me = this;
+ let param = cputype ? { cputype } : {};
+ let win = Ext.create('PVE.dc.CPUTypeEdit', param);
+ win.on('destroy', () => me.reload());
+ win.show();
+ },
+
+ onAdd: function () {
+ let me = this;
+ me.showEditor();
+ },
+
+ onEdit: function () {
+ let me = this;
+ let selection = me.getSelection();
+ me.showEditor(selection.cputype);
+ },
+
+ reload: function () {
+ let me = this;
+ me.getView().getStore().reload();
+ },
+ },
+
+ columns: [
+ {
+ header: gettext('Name'),
+ flex: 1,
+ dataIndex: 'cputype',
+ renderer: (val) => val.replace(/^custom-/, ''),
+ },
+ {
+ header: gettext('Reported Model'),
+ flex: 1,
+ dataIndex: 'reported-model',
+ },
+ {
+ header: gettext('Phys-Bits'),
+ flex: 1,
+ dataIndex: 'phys-bits',
+ },
+ {
+ header: gettext('Hidden'),
+ flex: 1,
+ dataIndex: 'hidden',
+ },
+ {
+ header: gettext('HyperV-Vendor'),
+ flex: 1,
+ dataIndex: 'hv-vendor-id',
+ },
+ {
+ header: gettext('Flags'),
+ flex: 2,
+ dataIndex: 'flags',
+ },
+ ],
+
+ tbar: [
+ {
+ text: gettext('Add'),
+ handler: 'onAdd',
+ },
+ '-',
+ {
+ xtype: 'proxmoxStdRemoveButton',
+ baseurl: '/api2/extjs/nodes/localhost/capabilities/qemu/cpu/model/',
+ getRecordName: (rec) => rec.data.cputype,
+ getUrl: function (rec) {
+ let me = this;
+ return me.baseurl + rec.data.cputype;
+ },
+ callback: 'reload',
+ },
+ {
+ text: gettext('Edit'),
+ handler: 'onEdit',
+ },
+ ],
+
+ selModel: {
+ xtype: 'rowmodel',
+ },
+
+ listeners: {
+ itemdblclick: function (_, rec) {
+ let me = this;
+ me.getController().showEditor(rec.data.cputype);
+ },
+ },
+
+ initComponent: function () {
+ let me = this;
+ me.callParent();
+ Proxmox.Utils.monStoreErrors(me, me.store);
+ },
+});
diff --git a/www/manager6/dc/Config.js b/www/manager6/dc/Config.js
index b5e27a21..629e4fc8 100644
--- a/www/manager6/dc/Config.js
+++ b/www/manager6/dc/Config.js
@@ -146,6 +146,12 @@ Ext.define('PVE.dc.Config', {
title: gettext('Replication'),
itemId: 'replication',
},
+ {
+ xtype: 'pveCPUTypeView',
+ iconCls: 'fa fa-microchip',
+ title: gettext('Custom CPU models'),
+ itemId: 'cputypes',
+ },
{
xtype: 'pveACLView',
title: gettext('Permissions'),
diff --git a/www/manager6/form/PhysBitsSelector.js b/www/manager6/form/PhysBitsSelector.js
new file mode 100644
index 00000000..78f932a6
--- /dev/null
+++ b/www/manager6/form/PhysBitsSelector.js
@@ -0,0 +1,153 @@
+Ext.define('PVE.form.PhysBitsSelector', {
+ extend: 'Ext.form.FieldContainer',
+ alias: 'widget.PhysBitsSelector',
+ mixins: ['Ext.form.field.Field'],
+
+ layout: 'vbox',
+ originalValue: '',
+
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ onRadioChange: function (radio, value) {
+ let me = this;
+ if (value === undefined) {
+ return;
+ }
+
+ ['modeDefault', 'modeHost', 'modeCustom'].forEach(function (ref) {
+ let r = me.lookupReference(ref);
+ if (r !== radio) {
+ r.suspendEvents();
+ r.setValue(false);
+ r.resumeEvents();
+ }
+ });
+
+ me.updateNumberField();
+ },
+
+ updateNumberField: function () {
+ let me = this;
+ let modeCustom = me.lookupReference('modeCustom');
+ let customNum = me.lookupReference('customNum');
+
+ customNum.setDisabled(!modeCustom.getValue());
+ me.getView().validate();
+ },
+
+ listen: {
+ component: {
+ '*': {
+ change: function () {
+ let me = this;
+ me.getView().checkChange();
+ },
+ },
+ },
+ },
+ },
+
+ getValue: function () {
+ let me = this;
+ let ctrl = me.getController();
+ if (ctrl.lookupReference('modeDefault').getValue()) {
+ return '';
+ } else if (ctrl.lookupReference('modeHost').getValue()) {
+ return 'host';
+ } else if (ctrl.lookupReference('modeCustom').getValue()) {
+ return ctrl.lookupReference('customNum').getValue();
+ }
+ return ''; // shouldn't happen
+ },
+
+ setValue: function (value) {
+ let me = this;
+ let ctrl = me.getController();
+ let modeField;
+
+ if (!value) {
+ modeField = ctrl.lookupReference('modeDefault');
+ } else if (value === 'host') {
+ modeField = ctrl.lookupReference('modeHost');
+ } else {
+ let customNum = ctrl.lookupReference('customNum');
+ customNum.setValue(value);
+ modeField = ctrl.lookupReference('modeCustom');
+ }
+
+ modeField.setValue(true);
+ me.checkChange();
+
+ return value;
+ },
+
+ getErrors: function () {
+ let me = this;
+ let ctrl = me.getController();
+ if (ctrl.lookupReference('modeCustom').getValue()) {
+ return ctrl.lookupReference('customNum').getErrors();
+ }
+ return [];
+ },
+
+ isValid: function () {
+ let me = this;
+ let ctrl = me.getController();
+ if (ctrl.lookupReference('modeCustom').getValue()) {
+ return ctrl.lookupReference('customNum').isValid();
+ }
+ return true;
+ },
+
+ items: [
+ {
+ xtype: 'radiofield',
+ boxLabel: gettext('Default'),
+ inputValue: 'default',
+ checked: true,
+ reference: 'modeDefault',
+ listeners: {
+ change: 'onRadioChange',
+ },
+ isFormField: false,
+ },
+ {
+ xtype: 'radiofield',
+ boxLabel: gettext('Host'),
+ inputValue: 'host',
+ reference: 'modeHost',
+ listeners: {
+ change: 'onRadioChange',
+ },
+ isFormField: false,
+ },
+ {
+ xtype: 'fieldcontainer',
+ layout: 'hbox',
+ items: [
+ {
+ xtype: 'radiofield',
+ boxLabel: gettext('Custom'),
+ inputValue: 'custom',
+ listeners: {
+ change: 'onRadioChange',
+ },
+ reference: 'modeCustom',
+ isFormField: false,
+ },
+ {
+ xtype: 'numberfield',
+ width: 60,
+ margin: '0 0 0 10px',
+ minValue: 8,
+ maxValue: 64,
+ reference: 'customNum',
+ allowBlank: false,
+ isFormField: false,
+ disabled: true,
+ },
+ ],
+ },
+ ],
+});
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH pve-manager 5/8] ui: Add CPU flag editor for custom models
2026-03-12 8:40 [PATCH manager/qemu-server 0/8] Add API and UI for custom CPU models Arthur Bied-Charreton
` (3 preceding siblings ...)
2026-03-12 8:40 ` [PATCH pve-manager 4/8] ui: Add basic custom CPU model editor Arthur Bied-Charreton
@ 2026-03-12 8:40 ` Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH qemu-server 6/8] qemu: Add helpers for new custom models endpoints Arthur Bied-Charreton
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Arthur Bied-Charreton @ 2026-03-12 8:40 UTC (permalink / raw)
To: pve-devel; +Cc: Stefan Reiter
Add CPU flag editor to the CPUTypeEdit component, using the VMCPUFlagSelector
also used in the VM creation flow. By default, only show the CPU flags that
are currently meant to be shown in the VM creation window, see [0]. When in
CPUTypeEdit, show all available flags.
For each flag in VMCPUFlagSelector, also display which node(s) it is available
on to limit misconfigurations.
Original patch:
https://lore.proxmox.com/pve-devel/20211028114150.3245864-10-s.reiter@proxmox.com
[0] https://git.proxmox.com/?p=qemu-server.git;a=blob;f=src/PVE/QemuServer/CPUConfig.pm;h=32ec495422791422f20caa928d6b632b3de4fcd9;hb=refs/heads/master#l349
Originally-by: Stefan Reiter <s.reiter@proxmox.com>
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
www/manager6/dc/CPUTypeEdit.js | 11 ++-
www/manager6/form/CPUModelSelector.js | 1 +
www/manager6/form/VMCPUFlagSelector.js | 121 ++++++++++++++++++++++---
3 files changed, 117 insertions(+), 16 deletions(-)
diff --git a/www/manager6/dc/CPUTypeEdit.js b/www/manager6/dc/CPUTypeEdit.js
index 8cf508b4..d6e06601 100644
--- a/www/manager6/dc/CPUTypeEdit.js
+++ b/www/manager6/dc/CPUTypeEdit.js
@@ -84,7 +84,16 @@ Ext.define('PVE.dc.CPUTypeEdit', {
name: 'phys-bits',
},
],
-
+ columnB: [
+ {
+ xtype: 'vmcpuflagselector',
+ fieldLabel: gettext('Extra CPU flags'),
+ name: 'flags',
+ restrictToVMFlags: false,
+ height: 380,
+ hideHeaders: false,
+ },
+ ],
},
],
});
diff --git a/www/manager6/form/CPUModelSelector.js b/www/manager6/form/CPUModelSelector.js
index 2154ff46..683fa469 100644
--- a/www/manager6/form/CPUModelSelector.js
+++ b/www/manager6/form/CPUModelSelector.js
@@ -17,6 +17,7 @@ Ext.define('PVE.form.CPUModelSelector', {
anyMatch: true,
forceSelection: true,
autoSelect: false,
+ triggerAction: 'query',
deleteEmpty: true,
config: {
diff --git a/www/manager6/form/VMCPUFlagSelector.js b/www/manager6/form/VMCPUFlagSelector.js
index 74b1a2c4..06c9d9f1 100644
--- a/www/manager6/form/VMCPUFlagSelector.js
+++ b/www/manager6/form/VMCPUFlagSelector.js
@@ -1,3 +1,19 @@
+const VM_CPU_FLAGS_SUBSET = {
+ aes: true,
+ 'amd-no-ssb': true,
+ 'amd-ssbd': true,
+ 'hv-evmcs': true,
+ 'hv-tlbflush': true,
+ ibpb: true,
+ 'md-clear': true,
+ 'nested-virt': true,
+ pcid: true,
+ pdpe1gb: true,
+ 'spec-ctrl': true,
+ ssbd: true,
+ 'virt-ssbd': true,
+};
+
Ext.define('PVE.form.VMCPUFlagSelector', {
extend: 'Ext.grid.Panel',
alias: 'widget.vmcpuflagselector',
@@ -6,6 +22,10 @@ Ext.define('PVE.form.VMCPUFlagSelector', {
field: 'Ext.form.field.Field',
},
+ config: {
+ restrictToVMFlags: true,
+ },
+
disableSelection: true,
columnLines: false,
selectable: false,
@@ -17,27 +37,18 @@ Ext.define('PVE.form.VMCPUFlagSelector', {
unknownFlags: [],
store: {
- type: 'store',
- fields: ['name', { name: 'state', defaultValue: '=' }, 'description'],
- autoLoad: true,
+ fields: ['name', { name: 'state', defaultValue: '=' }, 'description', 'supported-on'],
+ autoLoad: false,
proxy: {
type: 'proxmox',
url: '/api2/json/nodes/localhost/capabilities/qemu/cpu-flags',
+ extraParams: { accel: 'kvm' },
},
listeners: {
update: function () {
this.commitChanges();
},
- refresh: function (store, eOpts) {
- let me = this;
- let view = me.view;
-
- if (store.adjustedForValue !== view.value) {
- view.adjustStoreForValue();
- }
- },
},
- adjustedForValue: undefined,
},
getValue: function () {
@@ -86,14 +97,18 @@ Ext.define('PVE.form.VMCPUFlagSelector', {
let rec = store.findRecord('name', flagName, 0, false, true, true);
if (rec !== null) {
rec.set('state', sign);
+ rec.commit();
} else {
me.unknownFlags.push(flag);
}
});
- store.adjustedForValue = value;
+ me.getView().refresh();
+ },
+ isDirty: function () {
+ let me = this;
+ return me.originalValue !== me.getValue();
},
-
setValue: function (value) {
let me = this;
@@ -109,6 +124,7 @@ Ext.define('PVE.form.VMCPUFlagSelector', {
},
columns: [
{
+ text: gettext('State'),
dataIndex: 'state',
renderer: function (v) {
switch (v) {
@@ -125,6 +141,7 @@ Ext.define('PVE.form.VMCPUFlagSelector', {
width: 65,
},
{
+ text: gettext('Set'),
xtype: 'widgetcolumn',
dataIndex: 'state',
width: 95,
@@ -171,22 +188,96 @@ Ext.define('PVE.form.VMCPUFlagSelector', {
},
},
{
+ text: gettext('Flag'),
dataIndex: 'name',
width: 100,
},
{
+ text: gettext('Description'),
dataIndex: 'description',
+ sortable: false,
+ cellWrap: true,
+ flex: 3,
+ },
+ {
+ text: gettext('Supported On'),
+ dataIndex: 'supported-on',
cellWrap: true,
flex: 1,
+ renderer: (v) => (Array.isArray(v) ? v.join(', ') : ''),
},
],
initComponent: function () {
let me = this;
+ me.unknownFlags = [];
me.value = me.originalValue = '';
- me.store.view = me;
+
+ me.dockedItems = [
+ {
+ xtype: 'toolbar',
+ dock: 'top',
+ items: [
+ {
+ xtype: 'tbtext',
+ text: gettext('Acceleration:'),
+ autoEl: {
+ tag: 'span',
+ 'data-qtip': gettext(
+ 'A custom CPU model using acceleration-specific flags should only be assigned to VMs configured with the matching acceleration type, i.e., `kvm: off` for TCG, or `kvm: on` for KVM.',
+ ),
+ },
+ },
+ {
+ xtype: 'radiogroup',
+ layout: 'hbox',
+ validateOnChange: false,
+ items: [
+ {
+ boxLabel: 'KVM',
+ inputValue: 'kvm',
+ name: 'accel',
+ checked: true,
+ isFormField: false,
+ },
+ {
+ boxLabel: 'TCG',
+ inputValue: 'tcg',
+ name: 'accel',
+ margin: '0 0 0 10',
+ isFormField: false,
+ },
+ ],
+ listeners: {
+ change: function (_, value) {
+ let view = this.up('grid');
+ let proxy = view.getStore().getProxy();
+ let accel = value.accel;
+ if (accel) {
+ proxy.setExtraParam('accel', accel);
+ } else {
+ delete proxy.extraParams.accel;
+ }
+ view.getStore().load();
+ },
+ },
+ },
+ ],
+ },
+ ];
me.callParent(arguments);
+
+ me.getStore().on('load', function (store, _, success) {
+ if (success) {
+ if (me.restrictToVMFlags) {
+ store.filterBy((rec) => VM_CPU_FLAGS_SUBSET[rec.get('name')] === true);
+ }
+ me.adjustStoreForValue();
+ me.checkDirty();
+ }
+ });
+ me.getStore().load();
},
});
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH qemu-server 6/8] qemu: Add helpers for new custom models endpoints
2026-03-12 8:40 [PATCH manager/qemu-server 0/8] Add API and UI for custom CPU models Arthur Bied-Charreton
` (4 preceding siblings ...)
2026-03-12 8:40 ` [PATCH pve-manager 5/8] ui: Add CPU flag editor for custom models Arthur Bied-Charreton
@ 2026-03-12 8:40 ` Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH qemu-server 7/8] api: qemu: Extend cpu-flags endpoint to return actually supported flags Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH qemu-server 8/8] api: qemu: Add CRUD handlers for custom CPU models Arthur Bied-Charreton
7 siblings, 0 replies; 9+ messages in thread
From: Arthur Bied-Charreton @ 2026-03-12 8:40 UTC (permalink / raw)
To: pve-devel; +Cc: Stefan Reiter
Add functionality to lock & write the custom CPU config and some other
helpers that will be needed in custom CPU models CRUD endpoints.
Original patch:
https://lore.proxmox.com/pve-devel/20211028114150.3245864-5-s.reiter@proxmox.com/
Originally-by: Stefan Reiter <s.reiter@proxmox.com>
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
src/PVE/QemuServer/CPUConfig.pm | 35 ++++++++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 3 deletions(-)
diff --git a/src/PVE/QemuServer/CPUConfig.pm b/src/PVE/QemuServer/CPUConfig.pm
index 32ec4954..2b6665a7 100644
--- a/src/PVE/QemuServer/CPUConfig.pm
+++ b/src/PVE/QemuServer/CPUConfig.pm
@@ -6,10 +6,10 @@ use warnings;
use JSON;
use PVE::JSONSchema qw(json_bool);
-use PVE::Cluster qw(cfs_register_file cfs_read_file);
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
use PVE::ProcFSTools;
use PVE::RESTEnvironment qw(log_warn);
-use PVE::Tools qw(run_command);
+use PVE::Tools qw(run_command lock_file);
use PVE::QemuServer::Helpers qw(min_version);
@@ -51,6 +51,19 @@ sub load_custom_model_conf {
return cfs_read_file($default_filename);
}
+sub write_custom_model_conf {
+ my ($conf) = @_;
+ cfs_write_file($default_filename, $conf);
+}
+
+sub lock_cpu_config {
+ my ($code) = @_;
+ cfs_lock_file($default_filename, undef, $code);
+ if (my $err = $@) {
+ die $err;
+ }
+}
+
#builtin models : reported-model is mandatory
my $builtin_models_by_arch = {
x86_64 => {
@@ -298,6 +311,19 @@ sub get_supported_cpu_flags {
return $supported_cpu_flags_by_arch->{$arch};
}
+sub description_by_flag {
+ my ($arch) = @_;
+ return { map { $_->{name} => $_->{description} } get_supported_cpu_flags($arch)->@* };
+}
+
+sub get_cpu_vendor {
+ my ($cputype, $arch) = @_;
+ $arch = $host_arch if !defined($arch);
+ my $retval = $cpu_models_by_arch->{$arch}->{$cputype}
+ or die "Built-in CPU type '$cputype' does not exist";
+ return $retval;
+}
+
my $all_supported_cpu_flags = {};
for my $arch ($supported_cpu_flags_by_arch->%*) {
for my $flag ($supported_cpu_flags_by_arch->{$arch}->@*) {
@@ -375,6 +401,7 @@ my $cpu_fmt = {
optional => 1,
},
};
+PVE::JSONSchema::register_standard_option('pve-qemu-cpu', $cpu_fmt);
my $sev_fmt = {
type => {
@@ -564,6 +591,7 @@ sub write_config {
# saved in section header
delete $model_conf->{cputype};
+ $model_conf->{type} = $class->type();
}
$class->SUPER::write_config($filename, $cfg);
@@ -612,7 +640,8 @@ sub get_cpu_models {
my $conf = load_custom_model_conf();
for my $custom_model (keys %{ $conf->{ids} }) {
- my $reported_model = $conf->{ids}->{$custom_model}->{'reported-model'};
+ my $model = $conf->{ids}->{$custom_model};
+ my $reported_model = $model->{'reported-model'};
$reported_model //= $cpu_fmt->{'reported-model'}->{default};
my $vendor = $all_cpu_models->{$reported_model};
push @$models,
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH qemu-server 7/8] api: qemu: Extend cpu-flags endpoint to return actually supported flags
2026-03-12 8:40 [PATCH manager/qemu-server 0/8] Add API and UI for custom CPU models Arthur Bied-Charreton
` (5 preceding siblings ...)
2026-03-12 8:40 ` [PATCH qemu-server 6/8] qemu: Add helpers for new custom models endpoints Arthur Bied-Charreton
@ 2026-03-12 8:40 ` Arthur Bied-Charreton
2026-03-12 8:40 ` [PATCH qemu-server 8/8] api: qemu: Add CRUD handlers for custom CPU models Arthur Bied-Charreton
7 siblings, 0 replies; 9+ messages in thread
From: Arthur Bied-Charreton @ 2026-03-12 8:40 UTC (permalink / raw)
To: pve-devel; +Cc: Stefan Reiter
Previously the endpoint returned a hardcoded list of flags. It now
returns flags that are both recognized by QEMU, and supported by at
least one cluster node to give a somewhat accurate picture of what flags
can actually be used on the current cluster.
The 'nested-virt` entry is prepended. An optional 'accel' parameter
filters results by virtualization type (kvm or tcg) to help avoid
misconfigurations when assigning flags to VMs with a specific
acceleration setting.
This deviates from the original patch [0] by adding the functionality to
the `cpu-flags` endpoint instead of adding new endpoints. This is
because we never need the understood/supported flags alone in the
frontend, only their intersection. This also improves the VM CPU flag
selector by letting users select from all possible flags in their
cluster.
When passed `aarch64` as argument for `arch`, the index returns an empty
list, which is consistent with the behavior from before this patch.
[0]
https://lore.proxmox.com/pve-devel/20211028114150.3245864-3-s.reiter@proxmox.com/
Originally-by: Stefan Reiter <s.reiter@proxmox.com>
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
src/PVE/API2/Qemu/CPUFlags.pm | 108 +++++++++++++++++++++++++++++++++-
1 file changed, 107 insertions(+), 1 deletion(-)
diff --git a/src/PVE/API2/Qemu/CPUFlags.pm b/src/PVE/API2/Qemu/CPUFlags.pm
index 672bd2d2..9baf6c3e 100644
--- a/src/PVE/API2/Qemu/CPUFlags.pm
+++ b/src/PVE/API2/Qemu/CPUFlags.pm
@@ -10,6 +10,9 @@ use PVE::QemuServer::CPUConfig;
use base qw(PVE::RESTHandler);
+# vmx/svm are already represented by the nested-virt synthetic entry
+my %NESTED_VIRT_ALIASES = map { $_ => 1 } qw(vmx svm);
+
__PACKAGE__->register_method({
name => 'index',
path => '',
@@ -21,6 +24,13 @@ __PACKAGE__->register_method({
properties => {
node => get_standard_option('pve-node'),
arch => get_standard_option('pve-qm-cpu-arch', { optional => 1 }),
+ accel => {
+ description =>
+ 'Virtualization type to filter flags by. If not provided, return all flags.',
+ type => 'string',
+ enum => [qw(kvm tcg)],
+ optional => 1,
+ },
},
},
returns => {
@@ -35,6 +45,13 @@ __PACKAGE__->register_method({
description => {
type => 'string',
description => "Description of the CPU flag.",
+ optional => 1,
+ },
+ 'supported-on' => {
+ description => 'List of nodes supporting the flag.',
+ type => 'array',
+ items => get_standard_option('pve-node'),
+ optional => 1,
},
},
},
@@ -42,10 +59,99 @@ __PACKAGE__->register_method({
code => sub {
my ($param) = @_;
+ my $accel = extract_param($param, 'accel');
my $arch = extract_param($param, 'arch');
- return PVE::QemuServer::CPUConfig::get_supported_cpu_flags($arch);
+ if (defined($arch) && $arch eq 'aarch64') {
+ return [];
+ }
+
+ my $descriptions = PVE::QemuServer::CPUConfig::description_by_flag($arch);
+
+ my $nested_virt = {
+ name => 'nested-virt',
+ description => $descriptions->{'nested-virt'},
+ };
+
+ return [$nested_virt, @{ extract_flags($descriptions, $accel) }];
},
});
+# As described here [0], in order to get an accurate picture of which flags can actually be used, we need
+# to intersect:
+#
+# 1. The understood CPU flags, i.e., all flags QEMU accepts in theory, but that may not be actually
+# supported by the host CPU, and
+# 2. The supported CPU flags, which returns some settings/flags that cannot be used as `-cpu` arguments.
+#
+# [0] https://git.proxmox.com/?p=qemu-server.git;a=blob;f=src/PVE/QemuServer.pm;h=09e7a19b2f11ef48d2cfc11837b70338c306817c;hb=refs/heads/master#l2916
+sub extract_flags($descriptions, $accel = undef) {
+ my $recognized = extract_understood($descriptions);
+ my $supported = extract_supported($descriptions, $accel);
+
+ my %recognized_set = map { $_->{name} => 1 } @$recognized;
+
+ return [
+ map { {
+ name => $_->{name},
+ 'supported-on' => $_->{'supported-on'},
+ (defined($_->{description}) ? (description => $_->{description}) : ()),
+ } } grep {
+ $recognized_set{ $_->{name} }
+ } @$supported
+ ];
+}
+
+sub extract_understood($descriptions) {
+ my $understood_cpu_flags = PVE::QemuServer::query_understood_cpu_flags();
+
+ return [
+ map {
+ my $entry = { name => $_ };
+ $entry->{description} = $descriptions->{$_} if $descriptions->{$_};
+ $entry;
+ } grep {
+ !$NESTED_VIRT_ALIASES{$_}
+ } @$understood_cpu_flags
+ ];
+}
+
+# We do not use `PVE::QemuServer::CPUConfig::query_supported_cpu_flags`, which is quite expensive since
+# it needs to spawn QEMU instances in order to check which flags are supported. Rather, we use its cached
+# output, which is stored by `pvestatd` [0].
+#
+# [0] https://git.proxmox.com/?p=pve-manager.git;a=blob;f=PVE/Service/pvestatd.pm;h=d0719446e3b9a5f1bd3c861dbe768432cb3d7a0e;hb=refs/heads/master#l87
+sub extract_supported($descriptions, $accel = undef) {
+ my %hash;
+
+ my sub add_flags($flags_by_node) {
+ for my $node (keys %$flags_by_node) {
+ # This depends on `pvestatd` storing the flags in space-separated format, which is the case
+ # at the time of this commit.
+ for (split(' ', $flags_by_node->{$node})) {
+ if ($hash{$_}) {
+ $hash{$_}->{'supported-on'}->{$node} = 1;
+ } else {
+ $hash{$_} = { 'supported-on' => { $node => 1 }, name => $_ };
+ }
+ }
+ }
+ }
+
+ add_flags(PVE::Cluster::get_node_kv('cpuflags-kvm')) if !defined($accel) || $accel eq 'kvm';
+ add_flags(PVE::Cluster::get_node_kv('cpuflags-tcg')) if !defined($accel) || $accel eq 'tcg';
+
+ return [
+ map {
+ my $entry = { %$_, 'supported-on' => [sort keys %{ $_->{'supported-on'} }] };
+ $entry->{description} = $descriptions->{ $_->{name} }
+ if $descriptions->{ $_->{name} };
+ $entry;
+ } sort {
+ $a->{name} cmp $b->{name}
+ } grep {
+ !$NESTED_VIRT_ALIASES{ $_->{name} }
+ } values %hash
+ ];
+}
1;
--
2.47.3
^ permalink raw reply [flat|nested] 9+ messages in thread* [PATCH qemu-server 8/8] api: qemu: Add CRUD handlers for custom CPU models
2026-03-12 8:40 [PATCH manager/qemu-server 0/8] Add API and UI for custom CPU models Arthur Bied-Charreton
` (6 preceding siblings ...)
2026-03-12 8:40 ` [PATCH qemu-server 7/8] api: qemu: Extend cpu-flags endpoint to return actually supported flags Arthur Bied-Charreton
@ 2026-03-12 8:40 ` Arthur Bied-Charreton
7 siblings, 0 replies; 9+ messages in thread
From: Arthur Bied-Charreton @ 2026-03-12 8:40 UTC (permalink / raw)
To: pve-devel; +Cc: Stefan Reiter
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 <s.reiter@proxmox.com>
Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
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
^ permalink raw reply [flat|nested] 9+ messages in thread