[pve-devel] applied: [PATCH manager v2] gui: vm: add CPU flag selector with tri-state awareness

Thomas Lamprecht t.lamprecht at proxmox.com
Fri Jun 7 17:44:01 CEST 2019


This allows to select the tri-state (enforce on, enforce off, default
from QEMU+CPU Model) for each CPU flag independently.

For this a grid with a widgetcolumn is used hosting tree radio
buttons for each state. They're marked '+' for enforce on, '-' for
enforce off and the default has no label, as it isn't easy to add in
such a way that it does not confuses people and does not looks
completely ugly.. But, to help people which have a hard time figuring
out what the states mean, a fake column was added showing the current
selected state's outcome in words.

For show casing the new nice interface add all currently supported
flags from out API-
It could be worth to add some selected CPU model awareness, so that
flags are only enabled if they can make sense with the selected
model. But one should be able to add this relative easily with this
as base.

The hardcoded flag lists is not ideal, we should try to generate this
in the future, but here already qemu-server is lacking and this is
rather independent of the fact and can be done later one just fine
too.

Note that this /is/ an *advanced* feature so not visible for all
directly, while I try to document in short what a flag does it surely
isn't perfect and to short to explain all nuances, they should give
enough pointers to know if it's relevant at all (amd / intel cpu) and
for what one should research

Signed-off-by: Thomas Lamprecht <t.lamprecht at proxmox.com>
---

changes v1 -> v2:
* fix form reset behaviour
* s/amd-no-ssbd/amd-no-ssb/

 www/manager6/Makefile                  |   1 +
 www/manager6/form/VMCPUFlagSelector.js | 174 +++++++++++++++++++++++++
 www/manager6/qemu/ProcessorEdit.js     |  50 +++----
 3 files changed, 195 insertions(+), 30 deletions(-)
 create mode 100644 www/manager6/form/VMCPUFlagSelector.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 853fcb4f..b9a6dd14 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -61,6 +61,7 @@ JSSRC= 				                 	\
 	form/GlobalSearchField.js			\
 	form/QemuBiosSelector.js			\
 	form/VMSelector.js			\
+	form/VMCPUFlagSelector.js			\
 	form/USBSelector.js				\
 	form/CalendarEvent.js				\
 	form/CephPoolSelector.js				\
diff --git a/www/manager6/form/VMCPUFlagSelector.js b/www/manager6/form/VMCPUFlagSelector.js
new file mode 100644
index 00000000..92bb3198
--- /dev/null
+++ b/www/manager6/form/VMCPUFlagSelector.js
@@ -0,0 +1,174 @@
+/*jslint confusion: true*/
+Ext.define('PVE.form.VMCPUFlagSelector', {
+    extend: 'Ext.grid.Panel',
+    alias: 'widget.vmcpuflagselector',
+
+    mixins: {
+	field: 'Ext.form.field.Field'
+    },
+
+    disableSelection: true,
+    columnLines: false,
+    selectable: false,
+    hideHeaders: true,
+
+    scrollable: 'y',
+    height: 200,
+
+    unkownFlags: [],
+
+    store: {
+	type: 'store',
+	fields: ['flag', { name: 'state', defaultValue: '=' }, 'desc'],
+	data: [
+	    // FIXME: let qemu-server host this and autogenerate or get from API call??
+	    { flag: 'md-clear', desc: 'Required to let the guest OS know if MDS is mitigated correctly' },
+	    { flag: 'pcid', desc: 'Meltdown fix cost reduction on Westmere, Sandy-, and IvyBridge Intel CPUs' },
+	    { flag: 'spec-ctrl', desc: 'Allows improved Spectre mitigation with Intel CPUs' },
+	    { flag: 'ssbd', desc: 'Protection for "Speculative Store Bypass" for Intel models' },
+	    { flag: 'ibpb', desc: 'Allows improved Spectre mitigation with AMD CPUs' },
+	    { flag: 'virt-ssbd', desc: 'Basis for "Speculative Store Bypass" protection for AMD models' },
+	    { flag: 'amd-ssbd', desc: 'Improves Spectre mitigation performance with AMD CPUs, best used with "virt-ssbd"' },
+	    { flag: 'amd-no-ssb', desc: 'Notifies guest OS that host is not vulnerable for Spectre on AMD CPUs' },
+	    { flag: 'pdpe1gb', desc: 'Allow guest OS to use 1GB size pages, if host HW supports it' }
+	],
+	listeners: {
+	    update: function() {
+		this.commitChanges();
+	    }
+	}
+    },
+
+    getValue: function() {
+	var me = this;
+	var store = me.getStore();
+	var flags = '';
+
+	// ExtJS does not has a nice getAllRecords interface for stores :/
+	store.queryBy(Ext.returnTrue).each(function(rec) {
+	    var s = rec.get('state');
+	    if (s && s !== '=') {
+		var f = rec.get('flag');
+		if (flags === '') {
+		    flags = s + f;
+		} else {
+		    flags += ';' + s + f;
+		}
+	    }
+	});
+
+	flags += me.unkownFlags.join(';');
+
+	return flags;
+    },
+
+    setValue: function(value) {
+	var me = this;
+	var store = me.getStore();
+
+	me.value = value || '';
+
+	me.unkownFlags = [];
+
+	me.getStore().queryBy(Ext.returnTrue).each(function(rec) {
+	    rec.set('state', '=');
+	});
+
+	var flags = value ? value.split(';') : [];
+	flags.forEach(function(flag) {
+	    var sign = flag.substr(0, 1);
+	    flag = flag.substr(1);
+
+	    var rec = store.findRecord('flag', flag);
+	    if (rec !== null) {
+		rec.set('state', sign);
+	    } else {
+		me.unkownFlags.push(flag);
+	    }
+	});
+	store.reload();
+
+	var res = me.mixins.field.setValue.call(me, value);
+
+	return res;
+    },
+    columns: [
+	{
+	    dataIndex: 'state',
+	    renderer: function(v) {
+		switch(v) {
+		    case '=': return 'Default';
+		    case '-': return 'Off';
+		    case '+': return 'On';
+		    default: return 'Unknown';
+		}
+	    },
+	    width: 65
+	},
+	{
+	    xtype: 'widgetcolumn',
+	    dataIndex: 'state',
+	    width: 95,
+	    onWidgetAttach: function (column, widget, record) {
+		var val = record.get('state') || '=';
+		widget.down('[inputValue=' + val + ']').setValue(true);
+		// TODO: disable if selected CPU model and flag are incompatible
+	    },
+	    widget: {
+		xtype: 'radiogroup',
+		hideLabel: true,
+		layout: 'hbox',
+		validateOnChange: false,
+		value: '=',
+		listeners: {
+		    change: function(f, value) {
+			var v = Object.values(value)[0];
+			f.getWidgetRecord().set('state', v);
+
+			var view = this.up('grid');
+			view.dirty = view.getValue() !== view.originalValue;
+			view.checkDirty();
+			//view.checkChange();
+		    }
+		},
+		items: [
+		    {
+			boxLabel: '-',
+			boxLabelAlign: 'before',
+			inputValue: '-'
+		    },
+		    {
+			checked: true,
+			inputValue: '='
+		    },
+		    {
+			boxLabel: '+',
+			inputValue: '+'
+		    }
+		]
+	    }
+	},
+	{
+	    dataIndex: 'flag',
+	    width: 100
+	},
+	{
+	    dataIndex: 'desc',
+	    cellWrap: true,
+	    flex: 1
+	}
+    ],
+
+    initComponent: function() {
+	var me = this;
+
+	// static class store, thus gets not recreated, so ensure defaults are set!
+	me.getStore().data.forEach(function(v) {
+	    v.state = '=';
+	});
+
+	me.value = me.originalValue = '';
+
+	me.callParent(arguments);
+    }
+});
diff --git a/www/manager6/qemu/ProcessorEdit.js b/www/manager6/qemu/ProcessorEdit.js
index 4c0524eb..c62dc734 100644
--- a/www/manager6/qemu/ProcessorEdit.js
+++ b/www/manager6/qemu/ProcessorEdit.js
@@ -42,16 +42,11 @@ Ext.define('PVE.qemu.ProcessorInputPanel', {
 	// build the cpu options:
 	me.cpu.cputype = values.cputype;
 
-	var flags = [];
-
-	['pcid', 'spec-ctrl'].forEach(function(flag) {
-	    if (values[flag]) {
-		flags.push('+' + flag.toString());
-	    }
-	    delete values[flag];
-	});
-
-	me.cpu.flags = flags.length ? flags.join(';') : undefined;
+	if (values.flags) {
+	    me.cpu.flags = values.flags;
+	} else {
+	    delete me.cpu.flags;
+	}
 
 	delete values.cputype;
 	delete values.flags;
@@ -141,7 +136,10 @@ Ext.define('PVE.qemu.ProcessorInputPanel', {
 	    fieldLabel: gettext('CPU limit'),
 	    allowBlank: true,
 	    emptyText: gettext('unlimited')
-	},
+	}
+    ],
+
+    advancedColumn2: [
 	{
 	    xtype: 'proxmoxintegerfield',
 	    name: 'cpuunits',
@@ -151,27 +149,22 @@ Ext.define('PVE.qemu.ProcessorInputPanel', {
 	    value: '1024',
 	    deleteEmpty: true,
 	    allowBlank: true
-	}
-    ],
-
-    advancedColumn2: [
+	},
 	{
 	    xtype: 'proxmoxcheckbox',
 	    fieldLabel: gettext('Enable NUMA'),
 	    name: 'numa',
 	    uncheckedValue: 0
-	},
+	}
+    ],
+    advancedColumnB: [
 	{
-	    xtype: 'proxmoxcheckbox',
-	    fieldLabel: 'PCID',
-	    name: 'pcid',
-	    uncheckedValue: 0
+	    xtype: 'label',
+	    text: 'Extra CPU Flags:'
 	},
 	{
-	    xtype: 'proxmoxcheckbox',
-	    fieldLabel: 'SPEC-CTRL',
-	    name: 'spec-ctrl',
-	    uncheckedValue: 0
+	    xtype: 'vmcpuflagselector',
+	    name: 'flags'
 	}
     ]
 });
@@ -179,6 +172,8 @@ Ext.define('PVE.qemu.ProcessorInputPanel', {
 Ext.define('PVE.qemu.ProcessorEdit', {
     extend: 'Proxmox.window.Edit',
 
+    width: 700,
+
     initComponent : function() {
 	var me = this;
 
@@ -200,12 +195,7 @@ Ext.define('PVE.qemu.ProcessorEdit', {
 		    ipanel.cpu = cpu;
 		    data.cputype = cpu.cputype;
 		    if (cpu.flags) {
-			var flags = cpu.flags.split(';');
-			flags.forEach(function(flag) {
-			    var sign = flag.substr(0,1);
-			    flag = flag.substr(1);
-			    data[flag] = (sign === '+');
-			});
+			data.flags = cpu.flags;
 		    }
 		}
 		me.setValues(data);
-- 
2.20.1





More information about the pve-devel mailing list