On Fri, Mar 20, 2026 at 06:20:40PM +0100, Fiona Ebner wrote:
> Similar comment regarding the 'qemu' prefix as in patch 6/8
>
> Note that I'm not finished with the review and will continue next week
> with patch 8/8 and a quick look at the UI patches. Still sending this
> already for discussion.
>
> Am 12.03.26 um 9:40 AM schrieb Arthur Bied-Charreton:
> > 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/[email protected]/
> >
> > Originally-by: Stefan Reiter <[email protected]>
>
> I don't think there is enough left of the original patch/approach in
> this case to keep that trailer.
>
Noted
> > Signed-off-by: Arthur Bied-Charreton <[email protected]>
> > ---
> > 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;
>
> Missing use statements for PVE::QemuServer and PVE::Cluster
>
Argh, thanks!
> >
> > 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);
>
> I'd prefer a short helper function with a bit more generic name, like
> flag_is_aliased(). And/or maybe have a filter_cpu_flags() function.
>
> > +
> > __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);
>
> NAK: this endpoint is used for the VM-specific selection right now and
> you can only configure flags that are allow-listed for a VM-specific CPU
> model, i.e. the get_supported_cpu_flags() flags, not all flags. Some
> flags do have security implications for the host too, that's why
> creating custom models with arbitrary flags must be a more privileged
> operation. I also think it would be a bit overkill to have all flags
> there, rather than the ones we deem most useful to expose.
>
> So the API call should not start providing other flags by default,
> because the non-allow-listed flags cannot be set anyways for a
> VM-specific model. We can add a new API parameter for this, maybe
> "kind/type/something-else?" being an enum with values 'all|vm-specific'
> and by default having 'vm-specific'.
>
Yes, the UI part of this series filters the flags in the UI, which is
admittedly not very pretty. I like the enum idea, will introduce this in
v2, this will simplify the UI code quite a bit as well. Thanks!
> I don't think there ever is a case where we want all flags regardless of
> accelerator, or is there? If not, we should make 'accel' have a default
> value to avoid the case where it's not provided at all being treated
> differently. I'd opt for 'kvm' as that's the more common use case.
>
I don't think so either, will add the paramter in v2. I also think `kvm`
is a good default.
> > + if (defined($arch) && $arch eq 'aarch64') {
> > + return [];
>
> We should document this in the description and add a TODO comment for
> the 'all flags' case. Not having any VM-specific flags is intentional
> right now. Not having any usable flags is not. It's just that we don't
> ship a list yet, as it's more difficult to obtain during QEMU build. For
> x86_64, kvm -cpu help will list the flags.
>
Makes sense, will add a TODO comment and update the endpoint description.
> > + }
> > +
> > + my $descriptions =
> > PVE::QemuServer::CPUConfig::description_by_flag($arch);
>
> We could avoid passing around the descriptions, by having a function
> get_flag_description() and call that where needed. We can also have
> CPUConfig cache it with a hash to avoid re-grepping every time. That
> would feel a bit cleaner to me.
>
Yes, the reason why I passed it around was to avoid re-grepping, having
it cached in CPUConfig does sound better though.
> > +
> > + my $nested_virt = {
> > + name => 'nested-virt',
> > + description => $descriptions->{'nested-virt'},
>
> Should we check which hosts do support it? I.e. check for svm|vmx and
> declare support if either is present on a host.
>
Good call, I made the wrong assumption that they would always be
available - will add a check in v2.
> > + };
> > +
> > + 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
>
> I'd prefer having "See the documentation of query_supported_cpu_flags()
> and query_understood_cpu_flags() for more information." rather than
> putting a web link. Then the reference stays up-to-date with any changes
> there.
>
Noted, will update the doc comment
> > +sub extract_flags($descriptions, $accel = undef) {
>
> To me, the 'extract' in the function names here and for the others below
> is rather confusing. The functions take the hash of supported flags
> (currently called $descriptions) and adds more information/other flags.
> The only thing it's extracting is the descriptions.
>
I will think about better naming, I agree that these are not very
good.
> > + 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
>
> Again, not a fan of putting a web reference here. I think it's enough to
> mention that there are entries in the node kv store (regularly updated
> by pvestatd).
>
Noted
> > +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
> > => $_ };
> > + }
> > + }
> > + }
> > + }
>
> I didn't know you could do nested subs directly and not just by
> assigning a closure to a local variable. I'm not sure we have this
> anywhere else in the code base and if there are any gotchas when doing
> it like this.
>
> @Wolfgang: anything to be aware of with this construct?
>
> > +
> > + 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
>
> Style nit: we usually write such things in a more mixed way rather than
> purely functional. One advantage is that one doesn't have to read
> bottom-to-top. Opinionated preference, but I think when you have
> multiple instructions inside a map expression, it is a good sign that
> it's better written as a loop.
>
Noted, will change that
> > + ];
> > +}
> > 1;
>