diff --git a/nova/tests/unit/virt/libvirt/fakelibvirt.py b/nova/tests/unit/virt/libvirt/fakelibvirt.py index 99e2ed03775e..a03de3d98e77 100644 --- a/nova/tests/unit/virt/libvirt/fakelibvirt.py +++ b/nova/tests/unit/virt/libvirt/fakelibvirt.py @@ -21,6 +21,7 @@ import typing as ty import fixtures from lxml import etree +import mock from oslo_log import log as logging from oslo_utils.fixture import uuidsentinel as uuids from oslo_utils import versionutils @@ -1963,13 +1964,60 @@ class FakeLibvirtFixture(fixtures.Fixture): self.useFixture( fixtures.MockPatch('os.uname', return_value=fake_uname)) - # Ensure UEFI checks don't actually check the host - def fake_has_uefi_support(): - return os.uname().machine == obj_fields.Architecture.AARCH64 - - self.useFixture(fixtures.MockPatch( - 'nova.virt.libvirt.driver.LibvirtDriver._has_uefi_support', - side_effect=fake_has_uefi_support)) + # ...and on all machine types + fake_loaders = [ + { + 'description': 'UEFI firmware for x86_64', + 'interface-types': ['uefi'], + 'mapping': { + 'device': 'flash', + 'executable': { + 'filename': '/usr/share/OVMF/OVMF_CODE.fd', + 'format': 'raw', + }, + 'nvram-template': { + 'filename': '/usr/share/OVMF/OVMF_VARS.fd', + 'format': 'raw', + }, + }, + 'targets': [ + { + 'architecture': 'x86_64', + 'machines': ['pc-i440fx-*', 'pc-q35-*'], + }, + ], + 'features': ['acpi-s3', 'amd-sev', 'verbose-dynamic'], + 'tags': [], + }, + { + 'description': 'UEFI firmware for aarch64', + 'interface-types': ['uefi'], + 'mapping': { + 'device': 'flash', + 'executable': { + 'filename': '/usr/share/AAVMF/AAVMF_CODE.fd', + 'format': 'raw', + }, + 'nvram-template': { + 'filename': '/usr/share/AAVMF/AAVMF_VARS.fd', + 'format': 'raw', + } + }, + 'targets': [ + { + 'architecture': 'aarch64', + 'machines': ['virt-*'], + } + ], + 'features': ['verbose-static'], + "tags": [], + } + ] + self.useFixture( + fixtures.MockPatch( + 'nova.virt.libvirt.host.Host.loaders', + new_callable=mock.PropertyMock, + return_value=fake_loaders)) disable_event_thread(self) diff --git a/nova/tests/unit/virt/libvirt/test_config.py b/nova/tests/unit/virt/libvirt/test_config.py index 7a21e908cbda..122c303cf502 100644 --- a/nova/tests/unit/virt/libvirt/test_config.py +++ b/nova/tests/unit/virt/libvirt/test_config.py @@ -2459,7 +2459,6 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest): xml = obj.to_xml() self.assertXmlEqual( - xml, """ f01cf68d-515c-4daf-b85f-ef1424d93bfc @@ -2471,6 +2470,7 @@ class LibvirtConfigGuestTest(LibvirtConfigBaseTest): /tmp/OVMF_CODE.secboot.fd """, # noqa: E501 + xml, ) def _test_config_uefi_autoconfigure(self, secure): diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 5f34695ff177..d67cb467221e 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -3466,6 +3466,9 @@ class LibvirtConnTestCase(test.NoDBTestCase, domain_caps = vconfig.LibvirtConfigDomainCaps() domain_caps._features = vconfig.LibvirtConfigDomainCapsFeatures() domain_caps._features.features = [sev_feature] + domain_caps._os = vconfig.LibvirtConfigDomainCapsOS() + domain_caps._os.loader_paths = ['foo'] + fake_domain_caps.return_value = collections.defaultdict( dict, {'x86_64': {'q35': domain_caps}}) @@ -3491,10 +3494,9 @@ class LibvirtConnTestCase(test.NoDBTestCase, self.assertEqual(47, feature.cbitpos) self.assertEqual(1, feature.reduced_phys_bits) - @mock.patch.object(libvirt_driver.LibvirtDriver, - "_has_uefi_support", new=mock.Mock(return_value=True)) def _setup_sev_guest(self, extra_image_properties=None): drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + drvr._host._supports_uefi = True drvr._host._supports_amd_sev = True ctxt = context.RequestContext(project_id=123, @@ -5260,21 +5262,18 @@ class LibvirtConnTestCase(test.NoDBTestCase, "properties": {"hw_firmware_type": "uefi"}}) instance_ref = objects.Instance(**self.test_instance) - disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type, - instance_ref, - image_meta) - with mock.patch.object(drvr, "_has_uefi_support", - return_value=True) as mock_support: - cfg = drvr._get_guest_config(instance_ref, [], - image_meta, disk_info) - mock_support.assert_called_once_with() - self.assertEqual(cfg.os_loader_type, "pflash") + disk_info = blockinfo.get_disk_info( + CONF.libvirt.virt_type, instance_ref, image_meta) + cfg = drvr._get_guest_config( + instance_ref, [], image_meta, disk_info) + # these values are derived from the FakeLibvirtFixture + self.assertEqual('/usr/share/OVMF/OVMF_CODE.fd', cfg.os_loader) + self.assertEqual('/usr/share/OVMF/OVMF_VARS.fd', cfg.os_nvram_template) def test_check_uefi_support_aarch64(self): self.mock_uname.return_value = fakelibvirt.os_uname( 'Linux', '', '5.4.0-0-generic', '', fields.Architecture.AARCH64) drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) - drvr._has_uefi_support = mock.Mock(return_value=True) self.assertTrue(drvr._check_uefi_support(None)) def test_get_guest_config_with_block_device(self): @@ -6229,8 +6228,6 @@ class LibvirtConnTestCase(test.NoDBTestCase, self.mock_uname.return_value = fakelibvirt.os_uname( 'Linux', '', '5.4.0-0-generic', '', fields.Architecture.S390X) - self._stub_host_capabilities_cpu_arch(fields.Architecture.S390X) - instance_ref = objects.Instance(**self.test_instance) cfg = self._get_guest_config_via_fake_api(instance_ref) @@ -6246,19 +6243,6 @@ class LibvirtConnTestCase(test.NoDBTestCase, self.assertEqual("pty", terminal_device.type) self.assertEqual("s390-ccw-virtio", cfg.os_mach_type) - def _stub_host_capabilities_cpu_arch(self, cpu_arch): - def get_host_capabilities_stub(self): - cpu = vconfig.LibvirtConfigGuestCPU() - cpu.arch = cpu_arch - - caps = vconfig.LibvirtConfigCaps() - caps.host = vconfig.LibvirtConfigCapsHost() - caps.host.cpu = cpu - return caps - - self.stub_out('nova.virt.libvirt.host.Host.get_capabilities', - get_host_capabilities_stub) - def _get_guest_config_via_fake_api(self, instance): drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) image_meta = objects.ImageMeta.from_dict(self.test_image_meta) @@ -6644,7 +6628,8 @@ class LibvirtConnTestCase(test.NoDBTestCase, @mock.patch.object(host.Host, 'get_domain_capabilities') @mock.patch.object(designer, 'set_driver_iommu_for_sev') def test_get_guest_config_with_qga_through_image_meta_with_sev( - self, mock_designer, fake_domain_caps): + self, mock_designer, fake_domain_caps, + ): self._setup_fake_domain_caps(fake_domain_caps) extra_properties = {"hw_qemu_guest_agent": "yes"} cfg = self._setup_sev_guest(extra_properties) @@ -7678,9 +7663,6 @@ class LibvirtConnTestCase(test.NoDBTestCase, _fake_network_info(self), image_meta, disk_info) self.assertTrue(mock_path_exists.called) - expected = mock.call(libvirt_driver. - DEFAULT_UEFI_LOADER_PATH['aarch64'][0]) - self.assertIn(expected, mock_path_exists.mock_calls) self.assertEqual(cfg.os_mach_type, "virt") num_ports = 0 @@ -7711,9 +7693,6 @@ class LibvirtConnTestCase(test.NoDBTestCase, cfg = self._get_guest_config_with_graphics() self.assertTrue(mock_path_exists.called) - expected = mock.call(libvirt_driver. - DEFAULT_UEFI_LOADER_PATH['aarch64'][0]) - self.assertIn(expected, mock_path_exists.mock_calls) self.assertEqual(cfg.os_mach_type, "virt") usbhost_exists = False @@ -7972,27 +7951,7 @@ class LibvirtConnTestCase(test.NoDBTestCase, image_meta, disk_info) self.assertIsNone(conf.cpu) - def test_get_guest_cpu_config_host_passthrough(self): - 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", 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), - image_meta, disk_info) - self.assertIsInstance(conf.cpu, - vconfig.LibvirtConfigGuestCPU) - self.assertEqual(conf.cpu.mode, "host-passthrough") - self.assertIsNone(conf.cpu.model) - self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus) - self.assertEqual(conf.cpu.cores, 1) - self.assertEqual(conf.cpu.threads, 1) - - def test_get_guest_cpu_config_host_passthrough_aarch64(self): + def test_get_guest_cpu_config_automatic(self): expected = { fields.Architecture.X86_64: "host-model", fields.Architecture.I686: "host-model", @@ -8016,34 +7975,32 @@ class LibvirtConnTestCase(test.NoDBTestCase, self.assertIsInstance(conf.cpu, vconfig.LibvirtConfigGuestCPU) self.assertEqual(conf.cpu.mode, expect_mode) - def test_get_guest_cpu_config_host_model(self): - drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) - instance_ref = objects.Instance(**self.test_instance) - image_meta = objects.ImageMeta.from_dict(self.test_image_meta) + def test_get_guest_cpu_config_manual(self): + for mode in ('host-passthrough', 'host-model'): + self.flags(cpu_mode=mode, group='libvirt') - self.flags(cpu_mode="host-model", 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), - image_meta, disk_info) - self.assertIsInstance(conf.cpu, - vconfig.LibvirtConfigGuestCPU) - self.assertEqual(conf.cpu.mode, "host-model") - self.assertIsNone(conf.cpu.model) - self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus) - self.assertEqual(conf.cpu.cores, 1) - self.assertEqual(conf.cpu.threads, 1) + drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) + instance_ref = objects.Instance(**self.test_instance) + image_meta = objects.ImageMeta.from_dict(self.test_image_meta) + + 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), image_meta, disk_info) + self.assertIsInstance(conf.cpu, vconfig.LibvirtConfigGuestCPU) + self.assertEqual(conf.cpu.mode, mode) + self.assertIsNone(conf.cpu.model) + self.assertEqual(conf.cpu.sockets, instance_ref.flavor.vcpus) + self.assertEqual(conf.cpu.cores, 1) + self.assertEqual(conf.cpu.threads, 1) def test_get_guest_cpu_config_custom(self): + self.flags(cpu_mode="custom", cpu_models=["Penryn"], group='libvirt') + 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_models=["Penryn"], - group='libvirt') disk_info = blockinfo.get_disk_info(CONF.libvirt.virt_type, instance_ref, image_meta) @@ -16556,7 +16513,6 @@ class LibvirtConnTestCase(test.NoDBTestCase, drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) drvr._host._get_domain = mock.Mock(return_value=mock_domain) - drvr._has_uefi_support = mock.Mock(return_value=False) drvr.delete_instance_files = mock.Mock(return_value=None) drvr.get_info = mock.Mock(return_value= hardware.InstanceInfo(state=power_state.SHUTDOWN, internal_id=-1) @@ -16579,7 +16535,6 @@ class LibvirtConnTestCase(test.NoDBTestCase, drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) drvr._host._get_domain = mock.Mock(return_value=mock_domain) - drvr._has_uefi_support = mock.Mock(return_value=False) drvr.delete_instance_files = mock.Mock(return_value=None) drvr.get_info = mock.Mock(return_value= hardware.InstanceInfo(state=power_state.SHUTDOWN, internal_id=-1) @@ -16605,7 +16560,6 @@ class LibvirtConnTestCase(test.NoDBTestCase, drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) drvr._host._get_domain = mock.Mock(return_value=mock_domain) - drvr._has_uefi_support = mock.Mock(return_value=False) drvr.delete_instance_files = mock.Mock(return_value=None) drvr.get_info = mock.Mock(return_value= hardware.InstanceInfo(state=power_state.SHUTDOWN, internal_id=-1) @@ -16631,7 +16585,6 @@ class LibvirtConnTestCase(test.NoDBTestCase, drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) drvr._host._get_domain = mock.Mock(return_value=mock_domain) - drvr._has_uefi_support = mock.Mock(return_value=True) drvr.delete_instance_files = mock.Mock(return_value=None) drvr.get_info = mock.Mock(return_value=hardware.InstanceInfo( state=power_state.SHUTDOWN, internal_id=-1)) @@ -16660,7 +16613,6 @@ class LibvirtConnTestCase(test.NoDBTestCase, drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) drvr._host._get_domain = mock.Mock(return_value=mock_domain) - drvr._has_uefi_support = mock.Mock(return_value=True) drvr.delete_instance_files = mock.Mock(return_value=None) drvr.get_info = mock.Mock(return_value=hardware.InstanceInfo( state=power_state.SHUTDOWN, internal_id=-1)) diff --git a/nova/virt/libvirt/config.py b/nova/virt/libvirt/config.py index 0671562ffa8b..521341015880 100644 --- a/nova/virt/libvirt/config.py +++ b/nova/virt/libvirt/config.py @@ -84,6 +84,9 @@ class LibvirtConfigObject(object): pretty_print=pretty_print) return xml_str + def __repr__(self): + return self.to_xml(pretty_print=False) + class LibvirtConfigCaps(LibvirtConfigObject): @@ -2784,6 +2787,8 @@ class LibvirtConfigGuest(LibvirtConfigObject): self.os_firmware = None self.os_loader_type = None self.os_loader_secure = None + self.os_nvram = None + self.os_nvram_template = None self.os_kernel = None self.os_initrd = None self.os_cmdline = None @@ -2841,20 +2846,28 @@ class LibvirtConfigGuest(LibvirtConfigObject): if self.os_kernel is not None: os.append(self._text_node("kernel", self.os_kernel)) - # Generate XML nodes for UEFI boot. if ( + self.os_loader is not None or self.os_loader_type is not None or self.os_loader_secure is not None ): loader = self._text_node("loader", self.os_loader) if self.os_loader_type is not None: - loader.set("type", "pflash") + loader.set("type", self.os_loader_type) loader.set("readonly", "yes") if self.os_loader_secure is not None: loader.set( "secure", self.get_yes_no_str(self.os_loader_secure)) os.append(loader) + if ( + self.os_nvram is not None or + self.os_nvram_template is not None + ): + nvram = self._text_node("nvram", self.os_nvram) + nvram.set("template", self.os_nvram_template) + os.append(nvram) + if self.os_initrd is not None: os.append(self._text_node("initrd", self.os_initrd)) if self.os_cmdline is not None: diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 4bd9ab8828ca..8cafb4318d27 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -140,14 +140,6 @@ LOG = logging.getLogger(__name__) CONF = nova.conf.CONF -DEFAULT_UEFI_LOADER_PATH = { - "x86_64": ['/usr/share/OVMF/OVMF_CODE.fd', - '/usr/share/OVMF/OVMF_CODE.secboot.fd', - '/usr/share/qemu/ovmf-x86_64-code.bin'], - "aarch64": ['/usr/share/AAVMF/AAVMF_CODE.fd', - '/usr/share/qemu/aavmf-aarch64-code.bin'] -} - MAX_CONSOLE_BYTES = 100 * units.Ki VALID_DISK_CACHEMODES = [ "default", "none", "writethrough", "writeback", "directsync", "unsafe", @@ -5819,40 +5811,12 @@ class LibvirtDriver(driver.ComputeDriver): return flavor return instance.flavor - def _has_uefi_support(self): - # This means that the host can support UEFI booting for guests - supported_archs = [fields.Architecture.X86_64, - fields.Architecture.AARCH64] - caps = self._host.get_capabilities() - # TODO(dmllr, kchamart): Get rid of probing the OVMF binary file - # paths, it is not robust, because nothing but the binary's - # filename is reported, which means you have to detect its - # architecture and features by other means. To solve this, - # query the libvirt's getDomainCapabilities() to get the - # firmware paths (as reported in the 'loader' value). Nova now - # has a wrapper method for this, get_domain_capabilities(). - # This is a more reliable way to detect UEFI boot support. - # - # Further, with libvirt 5.3 onwards, support for UEFI boot is - # much more simplified by the "firmware auto-selection" feature. - # When using this, Nova doesn't need to query OVMF file paths at - # all; libvirt will take care of it. This is done by taking - # advantage of the so-called firmware "descriptor files" -- - # small JSON files (which will be shipped by Linux - # distributions) that describe a UEFI firmware binary's - # "characteristics", such as the binary's file path, its - # features, architecture, supported machine type, NVRAM template - # and so forth. - - return ((caps.host.cpu.arch in supported_archs) and - any((os.path.exists(p) - for p in DEFAULT_UEFI_LOADER_PATH[caps.host.cpu.arch]))) - def _check_uefi_support(self, hw_firmware_type): caps = self._host.get_capabilities() - return (self._has_uefi_support() and - (hw_firmware_type == fields.FirmwareType.UEFI or - caps.host.cpu.arch == fields.Architecture.AARCH64)) + return self._host.supports_uefi and ( + hw_firmware_type == fields.FirmwareType.UEFI or + caps.host.cpu.arch == fields.Architecture.AARCH64 + ) def _get_supported_perf_events(self): if not len(CONF.libvirt.enabled_perf_events): @@ -5891,6 +5855,9 @@ class LibvirtDriver(driver.ComputeDriver): guest.sysinfo = self._get_guest_config_sysinfo(instance) guest.os_smbios = vconfig.LibvirtConfigGuestSMBIOS() + mach_type = libvirt_utils.get_machine_type(image_meta) + guest.os_mach_type = mach_type + hw_firmware_type = image_meta.properties.get('hw_firmware_type') if arch == fields.Architecture.AARCH64: @@ -5898,22 +5865,30 @@ class LibvirtDriver(driver.ComputeDriver): hw_firmware_type = fields.FirmwareType.UEFI if hw_firmware_type == fields.FirmwareType.UEFI: - if self._has_uefi_support(): - global uefi_logged - if not uefi_logged: - LOG.warning("uefi support is without some kind of " - "functional testing and therefore " - "considered experimental.") - uefi_logged = True - for lpath in DEFAULT_UEFI_LOADER_PATH[arch]: - if os.path.exists(lpath): - guest.os_loader = lpath - guest.os_loader_type = "pflash" - else: + global uefi_logged + if not uefi_logged: + LOG.warning("uefi support is without some kind of " + "functional testing and therefore " + "considered experimental.") + uefi_logged = True + + if not self._host.supports_uefi: raise exception.UEFINotSupported() - mtype = libvirt_utils.get_machine_type(image_meta) - guest.os_mach_type = mtype + # TODO(stephenfin): Drop this when we drop support for legacy + # architectures + if not mach_type: + # loaders are specific to arch and machine type - if we + # don't have a machine type here, we're on a legacy + # architecture that we have no default machine type for + raise exception.UEFINotSupported() + + loader, nvram_template = self._host.get_loader( + arch, mach_type, has_secure_boot=False) + + guest.os_loader = loader + guest.os_loader_type = 'pflash' + guest.os_nvram_template = nvram_template # NOTE(lyarwood): If the machine type isn't recorded in the stashed # image metadata then record it through the system metadata table. @@ -5925,7 +5900,7 @@ class LibvirtDriver(driver.ComputeDriver): # nova.objects.ImageMeta.from_instance and the # nova.utils.get_image_from_system_metadata function. if image_meta.properties.get('hw_machine_type') is None: - instance.system_metadata['image_hw_machine_type'] = mtype + instance.system_metadata['image_hw_machine_type'] = mach_type if image_meta.properties.get('hw_boot_menu') is None: guest.os_bootmenu = strutils.bool_from_string(