Hi.
Originally I was thinking about versioned CPU models support as a good
opportunity for redoing the way they are defined in libvirt and the way
we check their compatibility with the current host setup. In other
words, I wanted each new CPU model definition to be probed from QEMU and
drop check="partial" for these new models and rely on CPU runnability
reported by QEMU. But this would require additional support from QEMU,
for which there were some patches sent to the list, but nothing was
really merged in the end.
So I guess we need to accept the situation and add versioned CPU models
the old way without waiting for changes in QEMU that nobody is working
on anyway.
But I wonder if we should still try to change the way we check guest CPU
ABI a bit (for the newly added models) and rely more on QEMU not
changing their CPU models. I believe they will never change an existing
versioned model and introduce a new one instead, is that correct?
On Mon, Nov 06, 2023 at 16:21:05 -0600, Jonathon Jongsma wrote:
Assuming that we want to offer all versioned CPUs like this, there
are two
approaches to naming. I chose to maintain the existing names (e.g. EPYC-IBPB)
as the primary name where available, and use the versioned name (EPYC-v2) as
the alias. However, some CPU models don't have an alias, so their versioned
name would be their primary name. So we have the following set of 'EPYC' CPU
models:
- EPYC (alias = EPYC-v1)
- EPYC-IBPB (alias = EPYC-v2)
- EPYC-v3 (no alias)
- EPYC-v4 (no alias)
An alternative approach is something more like:
- EPYC-v1 (alias = EPYC)
- EPYC-v2 (alias = EPYC-IBPB)
- EPYC-v3 (no alias)
- EPYC-v4 (no alias)
Hmm, I don't think creating aliases is a good idea, at least on the
public API level. We could perhaps use them internally if needed (and if
QEMU doesn't provide that info) when implementing a transformation of
non-versioned CPUs to versioned ones. But providing the same CPU model
under several different names doesn't seem like a good idea.
The naming of the second set is more consistent, but it could result
in slight
changes to behavior. For example, any call to cpuDecode() that returned
EPYC-IBPB in the past might now return EPYC-v2. These two CPUs are just two
different names for the same model, so I'm not sure it would result in any
issues. But in this patch series I went with the first approach since it
maintained stability and resulted in less churn in the test output.
Well the tests are mostly there to avoid such churn because it's indeed
an issue if the same CPU suddenly starts to be recognized as a different
model. Sometimes the change is correct, but most of the time it's not.
So using your example, having EPYC-v2 in domain XML instead of EPYC-IBPB
means you cannot migrate to an libvirt version that doesn't know about
EPYC-v2 despite it being exactly the same as the supported EPYC-IBPB CPU
model. Which is why introducing aliases in public APIs is not a good
idea.
That said, if we want to implement the translation from non-versioned to
versioned CPU models we need to solve migration if Model is translated
as Model-v2, which the other side does not support. I'm afraid we would
need to do some kind of reverse translation here.
Note also that there are a couple of patches that update existing CPU
models by
re-running this script against the current qemu source code. For example, the
patch "cpu_map: Update EPYC cpu definitions from qemu" results in some minor
changes to the existing EPYC CPUs by adding a couple of feature flags. In
theory, it seems like a good idea for our libvirt models to match how the model
is defined in qemu, but I admit that I don't have a great understanding of
whether this will result in undesirable side-effects. I'm hoping those of you
with deeper knowledge will tell me why this is or is not a good idea.
I've said in my other emails that this is not a good idea so I'll
explain why in detail here. Taking an example from your patch that
updates several EPYC models, let's suppose we have an EPYC CPU model
defined without "npt" and we decide to add "npt" to the model
definition. On the source with new libvirt with this change we
start a domain with the following CPU definition (simplified):
<cpu>
<model>EPYC</model>
</cpu>
Now if the source host cannot provide npt, we're good as the CPU
definition will be translated to something like
<cpu>
<model>EPYC</model>
<feature name="npt" policy="disable"/>
</cpu>
because we detected that QEMU removed the npt feature even though we
asked for it by using EPYC. But if the host can provide npt the live XML
will not be updated.
Note, in real world CPUs missing a feature we're adding might not
actually exist and everything would be fine, but once we're in
nested environment, everything is possible. Also there are features
that can be available on a host CPU based on its configuration or
even microcode revision.
Once we try to migrate such domain to a destination host with older
libvirt without npt in EPYC, it will ask QEMU to run a domain with EPYC
CPU model. In case the destination host cannot provide npt (in contrast
to the source host), the guest will see npt disabled and we won't detect
it as an issue because we didn't know we were asking for it by using
EPYC. Actually, we might still detect it as a list of features QEMU
disabled would not be empty (in case it was new enough to have npt in
EPYC).
And when we want to remove a feature from a model, new libvirt and QEMU
on the source will not provide the removed feature, but older libvirt on
the destination host will require it because the feature is part our the
CPU model definition there.
All this, while using a specific example, was meant rather generally.
And I didn't cover all possible combinations of what can happen on each
side. There might be cases when we can add or drop CPU features from
existing models, but each case needs to be considered separately and be
accompanied with detailed description why such change would not cause
migration issues. And doing so is usually not very pleasant mental
exercise as you can see above :-)
Jirka