diff --git a/nova/conf/libvirt.py b/nova/conf/libvirt.py
index 7e6a5425d065..5053d0a1668e 100644
--- a/nova/conf/libvirt.py
+++ b/nova/conf/libvirt.py
@@ -23,6 +23,8 @@ import itertools
from oslo_config import cfg
+from oslo_config import types
+
from nova.conf import paths
@@ -524,6 +526,58 @@ configure (via ``cpu_model``) a specific named CPU model. Otherwise, it
would result in an error and the instance launch will fail.
* ``virt_type``: Only the virtualization types ``kvm`` and ``qemu`` use this.
+"""),
+ cfg.ListOpt(
+ 'cpu_model_extra_flags',
+ item_type=types.String(
+ choices=['pcid'],
+ ignore_case=True,
+ ),
+ default=[],
+ help="""
+This allows specifying granular CPU feature flags when specifying CPU
+models. For example, to explicitly specify the ``pcid``
+(Process-Context ID, an Intel processor feature) flag to the "IvyBridge"
+virtual CPU model::
+
+ [libvirt]
+ cpu_mode = custom
+ cpu_model = IvyBridge
+ cpu_model_extra_flags = pcid
+
+Currently, the choice is restricted to only one option: ``pcid`` (the
+option is case-insensitive, so ``PCID`` is also valid). This flag is
+now required to address the guest performance degradation as a result of
+applying the "Meltdown" CVE fixes on certain Intel CPU models.
+
+Note that when using this config attribute to set the 'PCID' CPU flag,
+not all virtual (i.e. libvirt / QEMU) CPU models need it:
+
+* The only virtual CPU models that include the 'PCID' capability are
+ Intel "Haswell", "Broadwell", and "Skylake" variants.
+
+* The libvirt / QEMU CPU models "Nehalem", "Westmere", "SandyBridge",
+ and "IvyBridge" will _not_ expose the 'PCID' capability by default,
+ even if the host CPUs by the same name include it. I.e. 'PCID' needs
+ to be explicitly specified when using the said virtual CPU models.
+
+For now, the ``cpu_model_extra_flags`` config attribute is valid only in
+combination with ``cpu_mode`` + ``cpu_model`` options.
+
+Besides ``custom``, the libvirt driver has two other CPU modes: The
+default, ``host-model``, tells it to do the right thing with respect to
+handling 'PCID' CPU flag for the guest -- *assuming* you are running
+updated processor microcode, host and guest kernel, libvirt, and QEMU.
+The other mode, ``host-passthrough``, checks if 'PCID' is available in
+the hardware, and if so directly passes it through to the Nova guests.
+Thus, in context of 'PCID', with either of these CPU modes
+(``host-model`` or ``host-passthrough``), there is no need to use the
+``cpu_model_extra_flags``.
+
+Related options:
+
+* cpu_mode
+* cpu_model
"""),
cfg.StrOpt('snapshots_directory',
default='$instances_path/snapshots',
diff --git a/nova/tests/unit/virt/libvirt/test_config.py b/nova/tests/unit/virt/libvirt/test_config.py
index ac59d0732571..358e68608147 100644
--- a/nova/tests/unit/virt/libvirt/test_config.py
+++ b/nova/tests/unit/virt/libvirt/test_config.py
@@ -270,6 +270,15 @@ class LibvirtConfigGuestCPUFeatureTest(LibvirtConfigBaseTest):
""")
+ def test_config_simple_pcid(self):
+ obj = config.LibvirtConfigGuestCPUFeature("pcid")
+ obj.policy = "require"
+
+ xml = obj.to_xml()
+ self.assertXmlEqual(xml, """
+
+ """)
+
class LibvirtConfigGuestCPUNUMATest(LibvirtConfigBaseTest):
diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py
index 30d7ed55e2ac..283b1f35ec86 100644
--- a/nova/tests/unit/virt/libvirt/test_driver.py
+++ b/nova/tests/unit/virt/libvirt/test_driver.py
@@ -6107,6 +6107,83 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.assertEqual(conf.cpu.cores, 1)
self.assertEqual(conf.cpu.threads, 1)
+ @mock.patch.object(libvirt_driver.LOG, 'warning')
+ def test_get_guest_cpu_config_custom_with_extra_flags(self,
+ mock_warn):
+ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
+ instance_ref = objects.Instance(**self.test_instance)
+ image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
+
+ self.flags(cpu_mode="custom",
+ cpu_model="IvyBridge",
+ cpu_model_extra_flags="pcid",
+ group='libvirt')
+ disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
+ instance_ref,
+ image_meta)
+ conf = drvr._get_guest_config(instance_ref,
+ _fake_network_info(self, 1),
+ image_meta, disk_info)
+ self.assertIsInstance(conf.cpu,
+ vconfig.LibvirtConfigGuestCPU)
+ self.assertEqual(conf.cpu.mode, "custom")
+ self.assertEqual(conf.cpu.model, "IvyBridge")
+ self.assertIn(conf.cpu.features.pop().name, "pcid")
+ self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus)
+ self.assertEqual(conf.cpu.cores, 1)
+ self.assertEqual(conf.cpu.threads, 1)
+ self.assertFalse(mock_warn.called)
+
+ @mock.patch.object(libvirt_driver.LOG, 'warning')
+ def test_get_guest_cpu_config_host_model_with_extra_flags(self,
+ mock_warn):
+ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
+ instance_ref = objects.Instance(**self.test_instance)
+ image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
+
+ self.flags(cpu_mode="host-model",
+ cpu_model_extra_flags="pcid",
+ group='libvirt')
+ disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
+ instance_ref,
+ image_meta)
+ conf = drvr._get_guest_config(instance_ref,
+ _fake_network_info(self, 1),
+ image_meta, disk_info)
+ self.assertIsInstance(conf.cpu,
+ vconfig.LibvirtConfigGuestCPU)
+ self.assertEqual(conf.cpu.mode, "host-model")
+ self.assertEqual(len(conf.cpu.features), 0)
+ self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus)
+ self.assertEqual(conf.cpu.cores, 1)
+ self.assertEqual(conf.cpu.threads, 1)
+ self.assertTrue(mock_warn.called)
+
+ @mock.patch.object(libvirt_driver.LOG, 'warning')
+ def test_get_guest_cpu_config_host_passthrough_with_extra_flags(self,
+ mock_warn):
+ drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
+ instance_ref = objects.Instance(**self.test_instance)
+ image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
+
+ self.flags(cpu_mode="host-passthrough",
+ cpu_model_extra_flags="pcid",
+ group='libvirt')
+ disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
+ instance_ref,
+ image_meta)
+ conf = drvr._get_guest_config(instance_ref,
+ _fake_network_info(self, 1),
+ image_meta, disk_info)
+ self.assertIsInstance(conf.cpu,
+ vconfig.LibvirtConfigGuestCPU)
+ self.assertEqual(conf.cpu.mode, "host-passthrough")
+ self.assertEqual(len(conf.cpu.features), 0)
+ self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus)
+ self.assertEqual(conf.cpu.cores, 1)
+ self.assertEqual(conf.cpu.threads, 1)
+ self.assertTrue(mock_warn.called)
+
def test_get_guest_cpu_topology(self):
instance_ref = objects.Instance(**self.test_instance)
instance_ref.flavor.vcpus = 8
diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py
index c524c2168036..4384956fb3f9 100644
--- a/nova/virt/libvirt/driver.py
+++ b/nova/virt/libvirt/driver.py
@@ -3809,6 +3809,7 @@ class LibvirtDriver(driver.ComputeDriver):
def _get_guest_cpu_model_config(self):
mode = CONF.libvirt.cpu_mode
model = CONF.libvirt.cpu_model
+ extra_flags = CONF.libvirt.cpu_model_extra_flags
if (CONF.libvirt.virt_type == "kvm" or
CONF.libvirt.virt_type == "qemu"):
@@ -3851,14 +3852,49 @@ class LibvirtDriver(driver.ComputeDriver):
msg = _("A CPU model name should not be set when a "
"host CPU model is requested")
raise exception.Invalid(msg)
+ # FIXME (kchamart): We're intentionally restricting the choices
+ # (in the conf/libvirt.py) for 'extra_flags` to just 'PCID', to
+ # address the immediate guest performance degradation caused by
+ # "Meltdown" CVE fixes on certain Intel CPU models. In a future
+ # patch, we will:
+ # (a) Remove the restriction of choices for 'extra_flags',
+ # allowing to add / remove additional CPU flags, as it will
+ # make way for other useful features.
+ # (b) Remove the below check for "host-model", as it is a
+ # valid configuration to supply additional CPU flags to it.
+ # (c) Revisit and fix the warnings / exception handling for
+ # different combinations of CPU modes and 'extra_flags'.
+ elif ((mode == "host-model" or mode == "host-passthrough") and
+ extra_flags):
+ extra_flags = []
+ LOG.warning("Setting extra CPU flags is only valid in "
+ "combination with a custom CPU model. Refer "
+ "to the 'nova.conf' documentation for "
+ "'[libvirt]/cpu_model_extra_flags'")
- LOG.debug("CPU mode '%(mode)s' model '%(model)s' was chosen",
- {'mode': mode, 'model': (model or "")})
+ LOG.debug("CPU mode '%(mode)s' model '%(model)s' was chosen, "
+ "with extra flags: '%(extra_flags)s'",
+ {'mode': mode,
+ 'model': (model or ""),
+ 'extra_flags': (extra_flags or "")})
cpu = vconfig.LibvirtConfigGuestCPU()
cpu.mode = mode
cpu.model = model
+ # NOTE (kchamart): Currently there's no existing way to ask if a
+ # given CPU model + CPU flags combination is supported by KVM &
+ # a specific QEMU binary. However, libvirt runs the 'CPUID'
+ # command upfront -- before even a Nova instance (a QEMU
+ # process) is launched -- to construct CPU models and check
+ # their validity; so we are good there. In the long-term,
+ # upstream libvirt intends to add an additional new API that can
+ # do fine-grained validation of a certain CPU model + CPU flags
+ # against a specific QEMU binary (the libvirt RFE bug for that:
+ # https://bugzilla.redhat.com/show_bug.cgi?id=1559832).
+ for flag in extra_flags:
+ cpu.add_feature(vconfig.LibvirtConfigGuestCPUFeature(flag))
+
return cpu
def _get_guest_cpu_config(self, flavor, image_meta,
diff --git a/releasenotes/notes/libvirt-cpu-model-extra-flags-a23085f58bd22d27.yaml b/releasenotes/notes/libvirt-cpu-model-extra-flags-a23085f58bd22d27.yaml
new file mode 100644
index 000000000000..14156df964e6
--- /dev/null
+++ b/releasenotes/notes/libvirt-cpu-model-extra-flags-a23085f58bd22d27.yaml
@@ -0,0 +1,21 @@
+---
+features:
+ - |
+ The libvirt driver now allows specifying individual CPU feature
+ flags for guests, via a new configuration attribute
+ ``[libvirt]/cpu_model_extra_flags`` -- only with ``custom`` as the
+ ``[libvirt]/cpu_model``. Refer to its documentation in
+ ``nova.conf`` for usage details.
+
+ One of the motivations for this is to alleviate the performance
+ degradation (caused as a result of applying the "Meltdown" CVE
+ fixes) for guests running with certain Intel-based virtual CPU
+ models. This guest performance impact is reduced by exposing the
+ CPU feature flag 'PCID' ("Process-Context ID") to the *guest* CPU,
+ assuming that it is available in the physical hardware itself.
+
+ Note that besides ``custom``, Nova's libvirt driver has two other
+ CPU modes: ``host-model`` (which is the default), and
+ ``host-passthrough``. Refer to the
+ ``[libvirt]/cpu_model_extra_flags`` documentation for what to do
+ when you are using either of those CPU modes in context of 'PCID'.