From a6ef502aa06599102ae9a5a86067f3bc4b6440b2 Mon Sep 17 00:00:00 2001 From: Kevin Zhao Date: Mon, 23 Nov 2020 22:10:42 -0500 Subject: [PATCH] Support Cpu Compararion on Aarch64 Platform MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The general use case of libvirt's compareCPU() API is to allow comparing a guest CPU to a host CPU. However, for AArch64, the _only_ use case libvirt supports is to allow comparing host CPU with a destination CPU which is accomplished with the help of this patch[1]. So what we're talking about here, in context of AArch64, is comparing source _host_ CPU to destination _host_ CPU, so as to prevent a guest from migrating to a completely different host. For AArch64, in the past we skipped[2] _guest_ CPU to host CPU comparison (the "traditional" use case) check for AArch64 for two main reasons, and these reasons are still valid: 1. There are lots of vendors making different AArch64 CPUs, and they are all not easily comparable because they all differ in various ways. 2. QEMU AArch64 developers themselves recommend that using 'host- passthrough' is the way to run KVM guests on AArch64. In sum, for AArch64, (a) guests should use 'host-passthrough'; and (b) src host to dest host CPU comparison only makes sense to know about _host_ CPU compatibility, but you will not get any CPU config that you can give to a guest. For more detail discussion, please check link [3] and discussion [4]. [1] https://listman.redhat.com/archives/libvir-list/2020-September/msg01391.html — Modify virCPUarmCompare to perform compare actions [2] https://review.opendev.org/c/openstack/nova/+/589769/1/nova/virt/libvirt/driver.py#6892 [3] https://www.redhat.com/archives/libvir-list/2020-September/msg00262.html [4] https://www.redhat.com/archives/libvir-list/2020-September/msg00328.html, Change-Id: I9026f2ec39660408e4ac66fc0a85223e4b25e4d1 Signed-off-by: Kevin Zhao --- nova/tests/unit/virt/libvirt/test_driver.py | 41 +++++++++++++++++---- nova/tests/unit/virt/libvirt/test_host.py | 17 +++++++++ nova/virt/libvirt/driver.py | 20 ++++++++-- nova/virt/libvirt/host.py | 18 +++++---- 4 files changed, 78 insertions(+), 18 deletions(-) diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 2722f4c75f87..767bb0afa2a0 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -11020,22 +11020,49 @@ class LibvirtConnTestCase(test.NoDBTestCase, instance) self.assertIsNone(ret) - @mock.patch.object(host.Host, 'compare_cpu') + @mock.patch.object(fakelibvirt.Connection, 'getLibVersion', + return_value=versionutils.convert_version_to_int( + libvirt_driver.MIN_LIBVIRT_AARCH64_CPU_COMPARE) - 1 + ) @mock.patch.object(nova.virt.libvirt, 'config') def test_compare_cpu_aarch64_skip_comparison(self, mock_vconfig, - mock_compare): + mock_get_libversion): instance = objects.Instance(**self.test_instance) - skip_comparison_exc = fakelibvirt.make_libvirtError( - fakelibvirt.libvirtError, - 'Host CPU compatibility check does not make ' - 'sense on AArch64; skip CPU comparison') - mock_compare.side_effect = skip_comparison_exc + self.mock_uname.return_value = fakelibvirt.os_uname( + 'Linux', '', '5.4.0-0-generic', '', fields.Architecture.AARCH64) conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) ret = conn._compare_cpu(None, jsonutils.dumps(_fake_cpu_info_aarch64), instance) self.assertIsNone(ret) + @mock.patch.object(host.Host, 'get_capabilities') + @mock.patch.object(fakelibvirt.Connection, 'getLibVersion', + return_value=versionutils.convert_version_to_int( + libvirt_driver.MIN_LIBVIRT_AARCH64_CPU_COMPARE)) + @mock.patch.object(host.Host, 'compare_cpu') + def test_compare_cpu_host_aarch64(self, + mock_compare, + mock_get_libversion, + mock_caps): + instance = objects.Instance(**self.test_instance) + mock_compare.return_value = 6 + caps = vconfig.LibvirtConfigCaps() + caps.host = vconfig.LibvirtConfigCapsHost() + caps.host.cpu = vconfig.LibvirtConfigCPU() + caps.host.cpu.arch = fields.Architecture.AARCH64 + caps.host.topology = fakelibvirt.NUMATopology() + + mock_caps.return_value = caps + self.mock_uname.return_value = fakelibvirt.os_uname( + 'Linux', '', '5.4.0-0-generic', '', fields.Architecture.AARCH64) + conn = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + ret = conn._compare_cpu(None, + jsonutils.dumps(_fake_cpu_info_aarch64), + instance) + mock_compare.assert_called_once_with(caps.host.cpu.to_xml()) + self.assertIsNone(ret) + @mock.patch.object(host.Host, 'compare_cpu') @mock.patch.object(nova.virt.libvirt.LibvirtDriver, '_vcpu_model_to_cpu_config') diff --git a/nova/tests/unit/virt/libvirt/test_host.py b/nova/tests/unit/virt/libvirt/test_host.py index 761b73c3302e..192909d72194 100644 --- a/nova/tests/unit/virt/libvirt/test_host.py +++ b/nova/tests/unit/virt/libvirt/test_host.py @@ -689,6 +689,23 @@ class HostTestCase(test.NoDBTestCase): self.assertIsNone(caps.host.cpu.model) self.assertEqual(0, len(caps.host.cpu.features)) + def test_get_capabilities_on_aarch64(self): + """Tests that cpu features are not retrieved on aarch64 platform. + """ + fake_caps_xml = ''' + + + cef19ce0-0ca2-11df-855d-b193fbce7686 + + aarch64 + + +''' + with mock.patch.object(fakelibvirt.virConnect, 'getCapabilities', + return_value=fake_caps_xml): + caps = self.host.get_capabilities() + self.assertEqual(0, len(caps.host.cpu.features)) + def test__get_machine_types(self): expected = [ # NOTE(aspiers): in the real world, i686 would probably diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 729254e8bdee..66d1929aeb89 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -219,6 +219,8 @@ MIN_QEMU_VERSION = (4, 2, 0) NEXT_MIN_LIBVIRT_VERSION = (7, 0, 0) NEXT_MIN_QEMU_VERSION = (5, 2, 0) +MIN_LIBVIRT_AARCH64_CPU_COMPARE = (6, 9, 0) + # Virtuozzo driver support MIN_VIRTUOZZO_VERSION = (7, 0, 0) @@ -9350,6 +9352,20 @@ class LibvirtDriver(driver.ComputeDriver): else: cpu = self._vcpu_model_to_cpu_config(guest_cpu) + host_cpu = self._host.get_capabilities().host.cpu + if host_cpu.arch == fields.Architecture.AARCH64: + if self._host.has_min_version(MIN_LIBVIRT_AARCH64_CPU_COMPARE): + LOG.debug("On AArch64 hosts, source and destination host " + "CPUs are compared to check if they're compatible" + "(the only use-case supported by libvirt for " + "Arm64/AArch64)") + cpu = host_cpu + else: + LOG.debug("You need %s libvirt version to be able to compare " + "source host CPU with destination host CPU; skip " + "CPU comparison", MIN_LIBVIRT_AARCH64_CPU_COMPARE) + return + u = ("http://libvirt.org/html/libvirt-libvirt-host.html#" "virCPUCompareResult") m = _("CPU doesn't have compatibility.\n\n%(ret)s\n\nRefer to %(u)s") @@ -9359,10 +9375,6 @@ class LibvirtDriver(driver.ComputeDriver): LOG.debug("cpu compare xml: %s", cpu_xml, instance=instance) ret = self._host.compare_cpu(cpu_xml) except libvirt.libvirtError as e: - if cpu.arch == fields.Architecture.AARCH64: - LOG.debug("Host CPU compatibility check does not make " - "sense on AArch64; skip CPU comparison") - return error_code = e.get_error_code() if error_code == libvirt.VIR_ERR_NO_SUPPORT: LOG.debug("URI %(uri)s does not support cpu comparison. " diff --git a/nova/virt/libvirt/host.py b/nova/virt/libvirt/host.py index ade1ef620031..5c39dd320fec 100644 --- a/nova/virt/libvirt/host.py +++ b/nova/virt/libvirt/host.py @@ -793,13 +793,17 @@ class Host(object): xml_str = self._caps.host.cpu.to_xml() if isinstance(xml_str, bytes): xml_str = xml_str.decode('utf-8') - features = self.get_connection().baselineCPU( - [xml_str], - libvirt.VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES) - if features: - cpu = vconfig.LibvirtConfigCPU() - cpu.parse_str(features) - self._caps.host.cpu.features = cpu.features + # NOTE(kevinz): The baseline CPU info on Aarch64 will not + # include any features. So on Aarch64, we use the original + # features from LibvirtConfigCaps. + if self._caps.host.cpu.arch != fields.Architecture.AARCH64: + features = self.get_connection().baselineCPU( + [xml_str], + libvirt.VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES) + if features: + cpu = vconfig.LibvirtConfigCPU() + cpu.parse_str(features) + self._caps.host.cpu.features = cpu.features except libvirt.libvirtError as ex: error_code = ex.get_error_code() if error_code == libvirt.VIR_ERR_NO_SUPPORT: