Skip to content

Commit ca54343

Browse files
Oblynxmriedem
authored andcommitted
Hide hypervisor id on windows guests
Blueprints hide-hypervisor-id-flavor-extra-spec [1] and add-kvm-hidden-feature [2] allow hiding KVM's signature for guests, which is necessary for Nvidia drivers to work in VMs with passthrough GPUs. While this works well for linux guests on KVM, it doesn't work for Windows guests. For them, KVM emulates some HyperV features. With the current implementation, KVM's signature is hidden, but HyperV's is not, and Nvidia drivers don't work in Windows VMs. This change generates an extra element in the libvirt xml for Windows guests on KVM which obfuscates HyperV's signature too, controlled by the existing image and flavor parameters (img_hide_hypervisor_id and hide_hypervisor_id correspondingly). The extra xml element is <vendor_id state='on' value='1234567890ab'/> in features/hyperv. [1] https://blueprints.launchpad.net/nova/+spec/hide-hypervisor-id-flavor-extra-spec [2] https://blueprints.launchpad.net/nova/+spec/add-kvm-hidden-feature Change-Id: Iaaeae9281301f14f4ae9b43f4a06de58b699fd68 Closes-Bug: 1779845
1 parent 809799e commit ca54343

File tree

5 files changed

+97
-10
lines changed

5 files changed

+97
-10
lines changed

nova/tests/unit/virt/libvirt/test_config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2182,13 +2182,15 @@ def test_feature_hyperv_all(self):
21822182
obj.relaxed = True
21832183
obj.vapic = True
21842184
obj.spinlocks = True
2185+
obj.vendorid_spoof = True
21852186

21862187
xml = obj.to_xml()
21872188
self.assertXmlEqual(xml, """
21882189
<hyperv>
21892190
<relaxed state="on"/>
21902191
<vapic state="on"/>
21912192
<spinlocks state="on" retries="4095"/>
2193+
<vendor_id state="on" value="1234567890ab"/>
21922194
</hyperv>""")
21932195

21942196

nova/tests/unit/virt/libvirt/test_driver.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3813,13 +3813,17 @@ def test_get_guest_config_windows_timer(self, mock_get_arch):
38133813
self.assertIsInstance(cfg.features[2],
38143814
vconfig.LibvirtConfigGuestFeatureHyperV)
38153815

3816-
@mock.patch.object(host.Host, 'has_min_version')
3817-
def test_get_guest_config_windows_hyperv_feature2(self, mock_version):
3818-
mock_version.return_value = True
3816+
@mock.patch.object(host.Host, 'has_min_version',
3817+
new=mock.Mock(return_value=True))
3818+
def _test_get_guest_config_windows_hyperv(
3819+
self, flavor=None, image_meta=None, hvid_hidden=False):
38193820
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
38203821
instance_ref = objects.Instance(**self.test_instance)
38213822
instance_ref['os_type'] = 'windows'
3822-
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
3823+
if flavor is not None:
3824+
instance_ref.flavor = flavor
3825+
if image_meta is None:
3826+
image_meta = objects.ImageMeta.from_dict(self.test_image_meta)
38233827

38243828
disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type,
38253829
instance_ref,
@@ -3832,18 +3836,67 @@ def test_get_guest_config_windows_hyperv_feature2(self, mock_version):
38323836
vconfig.LibvirtConfigGuestClock)
38333837
self.assertEqual(cfg.clock.offset, "localtime")
38343838

3835-
self.assertEqual(3, len(cfg.features))
3839+
num_features = 4 if hvid_hidden else 3
3840+
self.assertEqual(num_features, len(cfg.features))
38363841
self.assertIsInstance(cfg.features[0],
38373842
vconfig.LibvirtConfigGuestFeatureACPI)
38383843
self.assertIsInstance(cfg.features[1],
38393844
vconfig.LibvirtConfigGuestFeatureAPIC)
38403845
self.assertIsInstance(cfg.features[2],
38413846
vconfig.LibvirtConfigGuestFeatureHyperV)
3847+
if hvid_hidden:
3848+
self.assertIsInstance(cfg.features[3],
3849+
vconfig.LibvirtConfigGuestFeatureKvmHidden)
38423850

38433851
self.assertTrue(cfg.features[2].relaxed)
38443852
self.assertTrue(cfg.features[2].spinlocks)
38453853
self.assertEqual(8191, cfg.features[2].spinlock_retries)
38463854
self.assertTrue(cfg.features[2].vapic)
3855+
self.assertEqual(hvid_hidden, cfg.features[2].vendorid_spoof)
3856+
3857+
def test_get_guest_config_windows_hyperv_feature2(self):
3858+
self._test_get_guest_config_windows_hyperv()
3859+
3860+
def test_get_guest_config_windows_hyperv_all_hide_flv(self):
3861+
# Similar to test_get_guest_config_windows_hyperv_feature2
3862+
# but also test hiding the HyperV signature with the flavor
3863+
# extra_spec "hide_hypervisor_id"
3864+
flavor_hide_id = fake_flavor.fake_flavor_obj(self.context,
3865+
extra_specs={"hide_hypervisor_id": "true"},
3866+
expected_attrs={"extra_specs"})
3867+
# this works for kvm (the default, tested below) and qemu
3868+
self.flags(virt_type='qemu', group='libvirt')
3869+
3870+
self._test_get_guest_config_windows_hyperv(
3871+
flavor=flavor_hide_id, hvid_hidden=True)
3872+
3873+
def test_get_guest_config_windows_hyperv_all_hide_img(self):
3874+
# Similar to test_get_guest_config_windows_hyperv_feature2
3875+
# but also test hiding the HyperV signature with the image
3876+
# property "img_hide_hypervisor_id"
3877+
image_meta = objects.ImageMeta.from_dict({
3878+
"disk_format": "raw",
3879+
"properties": {"img_hide_hypervisor_id": "true"}})
3880+
3881+
self._test_get_guest_config_windows_hyperv(
3882+
image_meta=image_meta, hvid_hidden=True)
3883+
3884+
def test_get_guest_config_windows_hyperv_all_hide_flv_img(self):
3885+
# Similar to test_get_guest_config_windows_hyperv_feature2
3886+
# but also test hiding the HyperV signature with both the flavor
3887+
# extra_spec "hide_hypervisor_id" and the image property
3888+
# "img_hide_hypervisor_id"
3889+
flavor_hide_id = fake_flavor.fake_flavor_obj(self.context,
3890+
extra_specs={"hide_hypervisor_id": "true"},
3891+
expected_attrs={"extra_specs"})
3892+
self.flags(virt_type='qemu', group='libvirt')
3893+
3894+
image_meta = objects.ImageMeta.from_dict({
3895+
"disk_format": "raw",
3896+
"properties": {"img_hide_hypervisor_id": "true"}})
3897+
3898+
self._test_get_guest_config_windows_hyperv(
3899+
flavor=flavor_hide_id, image_meta=image_meta, hvid_hidden=True)
38473900

38483901
def test_get_guest_config_with_two_nics(self):
38493902
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)

nova/virt/libvirt/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2334,6 +2334,8 @@ class LibvirtConfigGuestFeatureHyperV(LibvirtConfigGuestFeature):
23342334

23352335
# QEMU requires at least this value to be set
23362336
MIN_SPINLOCK_RETRIES = 4095
2337+
# The spoofed vendor_id can be any alphanumeric string
2338+
SPOOFED_VENDOR_ID = "1234567890ab"
23372339

23382340
def __init__(self, **kwargs):
23392341
super(LibvirtConfigGuestFeatureHyperV, self).__init__("hyperv",
@@ -2343,6 +2345,8 @@ def __init__(self, **kwargs):
23432345
self.vapic = False
23442346
self.spinlocks = False
23452347
self.spinlock_retries = self.MIN_SPINLOCK_RETRIES
2348+
self.vendorid_spoof = False
2349+
self.vendorid = self.SPOOFED_VENDOR_ID
23462350

23472351
def format_dom(self):
23482352
root = super(LibvirtConfigGuestFeatureHyperV, self).format_dom()
@@ -2354,6 +2358,9 @@ def format_dom(self):
23542358
if self.spinlocks:
23552359
root.append(etree.Element("spinlocks", state="on",
23562360
retries=str(self.spinlock_retries)))
2361+
if self.vendorid_spoof:
2362+
root.append(etree.Element("vendor_id", state="on",
2363+
value=self.vendorid))
23572364

23582365
return root
23592366

nova/virt/libvirt/driver.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4737,6 +4737,10 @@ def _set_kvm_timers(self, clk, os_type, image_meta):
47374737

47384738
def _set_features(self, guest, os_type, caps, virt_type, image_meta,
47394739
flavor):
4740+
hide_hypervisor_id = (strutils.bool_from_string(
4741+
flavor.extra_specs.get('hide_hypervisor_id')) or
4742+
image_meta.properties.get('img_hide_hypervisor_id'))
4743+
47404744
if virt_type == "xen":
47414745
# PAE only makes sense in X86
47424746
if caps.host.cpu.arch in (fields.Architecture.I686,
@@ -4759,13 +4763,23 @@ def _set_features(self, guest, os_type, caps, virt_type, image_meta,
47594763
# with Microsoft
47604764
hv.spinlock_retries = 8191
47614765
hv.vapic = True
4766+
4767+
# NOTE(kosamara): Spoofing the vendor_id aims to allow the nvidia
4768+
# driver to work on windows VMs. At the moment, the nvidia driver
4769+
# checks for the hyperv vendorid, and if it doesn't find that, it
4770+
# works. In the future, its behaviour could become more strict,
4771+
# checking for the presence of other hyperv feature flags to
4772+
# determine that it's loaded in a VM. If that happens, this
4773+
# workaround will not be enough, and we'll need to drop the whole
4774+
# hyperv element.
4775+
# That would disable some optimizations, reducing the guest's
4776+
# performance.
4777+
if hide_hypervisor_id:
4778+
hv.vendorid_spoof = True
4779+
47624780
guest.features.append(hv)
47634781

4764-
flavor_hide_kvm = strutils.bool_from_string(
4765-
flavor.get('extra_specs', {}).get('hide_hypervisor_id'))
4766-
if (virt_type in ("qemu", "kvm") and
4767-
(image_meta.properties.get('img_hide_hypervisor_id') or
4768-
flavor_hide_kvm)):
4782+
if (virt_type in ("qemu", "kvm") and hide_hypervisor_id):
47694783
guest.features.append(vconfig.LibvirtConfigGuestFeatureKvmHidden())
47704784

47714785
def _check_number_of_serial_console(self, num_ports):
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
fixes:
3+
- |
4+
Blueprints `hide-hypervisor-id-flavor-extra-spec`_ and
5+
`add-kvm-hidden-feature`_ enabled NVIDIA drivers in Linux guests using KVM
6+
and QEMU, but support was not included for Windows guests. This is now
7+
fixed. See `bug 1779845`_ for details.
8+
9+
.. _hide-hypervisor-id-flavor-extra-spec: https://blueprints.launchpad.net/nova/+spec/hide-hypervisor-id-flavor-extra-spec
10+
.. _add-kvm-hidden-feature: https://blueprints.launchpad.net/nova/+spec/add-kvm-hidden-feature
11+
.. _bug 1779845: https://bugs.launchpad.net/nova/+bug/1779845

0 commit comments

Comments
 (0)