diff --git a/nova/objects/image_meta.py b/nova/objects/image_meta.py index c098e3b56b6a..6bc151d7f72e 100644 --- a/nova/objects/image_meta.py +++ b/nova/objects/image_meta.py @@ -170,12 +170,15 @@ class ImageMetaProps(base.NovaObject): # Version 1.18: Pull signature properties from cursive library # Version 1.19: Added 'img_hide_hypervisor_id' type field # Version 1.20: Added 'traits_required' list field - VERSION = '1.20' + # Version 1.21: Added 'hw_time_hpet' field + VERSION = '1.21' def obj_make_compatible(self, primitive, target_version): super(ImageMetaProps, self).obj_make_compatible(primitive, target_version) target_version = versionutils.convert_version_to_tuple(target_version) + if target_version < (1, 21): + primitive.pop('hw_time_hpet', None) if target_version < (1, 20): primitive.pop('traits_required', None) if target_version < (1, 19): @@ -326,6 +329,9 @@ class ImageMetaProps(base.NovaObject): # name of the RNG device type eg virtio 'hw_rng_model': fields.RNGModelField(), + # boolean 'true' or 'false' to enable HPET + 'hw_time_hpet': fields.FlexibleBooleanField(), + # number of serial ports to create 'hw_serial_port_count': fields.IntegerField(), diff --git a/nova/tests/unit/objects/test_objects.py b/nova/tests/unit/objects/test_objects.py index 0c8b2a8524f7..b03ddf8b3a50 100644 --- a/nova/tests/unit/objects/test_objects.py +++ b/nova/tests/unit/objects/test_objects.py @@ -1093,7 +1093,7 @@ object_data = { 'HVSpec': '1.2-de06bcec472a2f04966b855a49c46b41', 'IDEDeviceBus': '1.0-29d4c9f27ac44197f01b6ac1b7e16502', 'ImageMeta': '1.8-642d1b2eb3e880a367f37d72dd76162d', - 'ImageMetaProps': '1.20-ffd686cde289814695d5f89522aa5aef', + 'ImageMetaProps': '1.21-f3721d8f744a9507a1966c81c386b532', 'Instance': '2.4-4437eb8b2737c3054ea579b8efe31dc5', 'InstanceAction': '1.1-f9f293e526b66fca0d05c3b3a2d13914', 'InstanceActionEvent': '1.2-b2f368b8a29d8d872b1f6ea841e820a0', diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 9e543465664b..c7e6f92b8275 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -3633,6 +3633,153 @@ class LibvirtConnTestCase(test.NoDBTestCase, else: self.assertEqual(2, len(cfg.clock.timers)) + def test_get_guest_config_clock_hpet_false(self): + self.flags(virt_type='kvm', group='libvirt') + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + instance_ref = objects.Instance(**self.test_instance) + image_meta = objects.ImageMeta.from_dict({ + "disk_format": "raw", + "properties": {"hw_time_hpet": "false"}}) + disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type, + instance_ref, + image_meta) + hpet_map = { + fields.Architecture.X86_64: True, + fields.Architecture.I686: True, + fields.Architecture.PPC: False, + fields.Architecture.PPC64: False, + fields.Architecture.ARMV7: False, + fields.Architecture.AARCH64: False, + } + + for guestarch, expect_hpet in hpet_map.items(): + with mock.patch.object(libvirt_driver.libvirt_utils, + 'get_arch', + return_value=guestarch): + cfg = drvr._get_guest_config(instance_ref, [], + image_meta, + disk_info) + self.assertIsInstance(cfg.clock, + vconfig.LibvirtConfigGuestClock) + self.assertEqual(cfg.clock.offset, "utc") + self.assertIsInstance(cfg.clock.timers[0], + vconfig.LibvirtConfigGuestTimer) + self.assertIsInstance(cfg.clock.timers[1], + vconfig.LibvirtConfigGuestTimer) + self.assertEqual(cfg.clock.timers[0].name, "pit") + self.assertEqual(cfg.clock.timers[0].tickpolicy, + "delay") + self.assertEqual(cfg.clock.timers[1].name, "rtc") + self.assertEqual(cfg.clock.timers[1].tickpolicy, + "catchup") + if expect_hpet: + self.assertEqual(3, len(cfg.clock.timers)) + self.assertIsInstance(cfg.clock.timers[2], + vconfig.LibvirtConfigGuestTimer) + self.assertEqual('hpet', cfg.clock.timers[2].name) + self.assertFalse(cfg.clock.timers[2].present) + else: + self.assertEqual(2, len(cfg.clock.timers)) + + def test_get_guest_config_clock_hpet_true(self): + self.flags(virt_type='kvm', group='libvirt') + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + instance_ref = objects.Instance(**self.test_instance) + image_meta = objects.ImageMeta.from_dict({ + "id": uuids.image_id, + "disk_format": "raw", + "properties": {"hw_time_hpet": "true"}}) + disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type, + instance_ref, + image_meta) + hpet_map = { + fields.Architecture.X86_64: True, + fields.Architecture.I686: True, + fields.Architecture.PPC: False, + fields.Architecture.PPC64: False, + fields.Architecture.ARMV7: False, + fields.Architecture.AARCH64: False, + } + + for guestarch, expect_hpet in hpet_map.items(): + with mock.patch.object(libvirt_driver.libvirt_utils, + 'get_arch', + return_value=guestarch): + cfg = drvr._get_guest_config(instance_ref, [], + image_meta, + disk_info) + self.assertIsInstance(cfg.clock, + vconfig.LibvirtConfigGuestClock) + self.assertEqual(cfg.clock.offset, "utc") + self.assertIsInstance(cfg.clock.timers[0], + vconfig.LibvirtConfigGuestTimer) + self.assertIsInstance(cfg.clock.timers[1], + vconfig.LibvirtConfigGuestTimer) + self.assertEqual(cfg.clock.timers[0].name, "pit") + self.assertEqual(cfg.clock.timers[0].tickpolicy, + "delay") + self.assertEqual(cfg.clock.timers[1].name, "rtc") + self.assertEqual(cfg.clock.timers[1].tickpolicy, + "catchup") + if expect_hpet: + self.assertEqual(3, len(cfg.clock.timers)) + self.assertIsInstance(cfg.clock.timers[2], + vconfig.LibvirtConfigGuestTimer) + self.assertEqual('hpet', cfg.clock.timers[2].name) + self.assertTrue(cfg.clock.timers[2].present) + else: + self.assertEqual(2, len(cfg.clock.timers)) + + def test_get_guest_config_clock_hpet_invalid(self): + self.flags(virt_type='kvm', group='libvirt') + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + instance_ref = objects.Instance(**self.test_instance) + image_meta = objects.ImageMeta.from_dict({ + "disk_format": "raw", + "properties": {"hw_time_hpet": "blah"}}) + disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type, + instance_ref, + image_meta) + hpet_map = { + fields.Architecture.X86_64: True, + fields.Architecture.I686: True, + fields.Architecture.PPC: False, + fields.Architecture.PPC64: False, + fields.Architecture.ARMV7: False, + fields.Architecture.AARCH64: False, + } + + for guestarch, expect_hpet in hpet_map.items(): + with mock.patch.object(libvirt_driver.libvirt_utils, + 'get_arch', + return_value=guestarch): + cfg = drvr._get_guest_config(instance_ref, [], + image_meta, + disk_info) + self.assertIsInstance(cfg.clock, + vconfig.LibvirtConfigGuestClock) + self.assertEqual(cfg.clock.offset, "utc") + self.assertIsInstance(cfg.clock.timers[0], + vconfig.LibvirtConfigGuestTimer) + self.assertIsInstance(cfg.clock.timers[1], + vconfig.LibvirtConfigGuestTimer) + self.assertEqual(cfg.clock.timers[0].name, "pit") + self.assertEqual(cfg.clock.timers[0].tickpolicy, + "delay") + self.assertEqual(cfg.clock.timers[1].name, "rtc") + self.assertEqual(cfg.clock.timers[1].tickpolicy, + "catchup") + if expect_hpet: + self.assertEqual(3, len(cfg.clock.timers)) + self.assertIsInstance(cfg.clock.timers[2], + vconfig.LibvirtConfigGuestTimer) + self.assertEqual('hpet', cfg.clock.timers[2].name) + # a non-boolean value of hw_time_hpet should be treated as + # False + self.assertFalse(cfg.clock.timers[2].present) + else: + self.assertEqual(2, len(cfg.clock.timers)) + @mock.patch.object(libvirt_utils, 'get_arch') def test_get_guest_config_windows_timer(self, mock_get_arch): mock_get_arch.return_value = fields.Architecture.I686 diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 194b56195289..67fb6f53e59d 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -4569,6 +4569,7 @@ class LibvirtDriver(driver.ComputeDriver): clk.add_timer(tmpit) clk.add_timer(tmrtc) + hpet = image_meta.properties.get('hw_time_hpet', False) guestarch = libvirt_utils.get_arch(image_meta) if guestarch in (fields.Architecture.I686, fields.Architecture.X86_64): @@ -4576,8 +4577,12 @@ class LibvirtDriver(driver.ComputeDriver): # qemu -no-hpet is not supported on non-x86 targets. tmhpet = vconfig.LibvirtConfigGuestTimer() tmhpet.name = "hpet" - tmhpet.present = False + tmhpet.present = hpet clk.add_timer(tmhpet) + else: + if hpet: + LOG.warning('HPET is not turned on for non-x86 guests in image' + ' %s.', image_meta.id) # Provide Windows guests with the paravirtualized hyperv timer source. # This is the windows equiv of kvm-clock, allowing Windows diff --git a/releasenotes/notes/bp-support-hpet-on-guest-2292b2b863c4d9ef.yaml b/releasenotes/notes/bp-support-hpet-on-guest-2292b2b863c4d9ef.yaml new file mode 100644 index 000000000000..ef4acc9918e1 --- /dev/null +++ b/releasenotes/notes/bp-support-hpet-on-guest-2292b2b863c4d9ef.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Added support for the High Precision Event Timer (HPET) for x86 guests + in the libvirt driver when image property ``hypervisor_type=qemu`` is set. + The timer can be set by setting a ``hw_time_hpet=True`` image property + key/value pair. By default HPET remains turned off. When it is turned on + the HPET is activated in libvirt.