From 7f4d7e0b67521344e32790e4b59c42073182a2cf Mon Sep 17 00:00:00 2001 From: Chris Friesen Date: Sun, 5 Apr 2015 22:04:36 -0600 Subject: [PATCH] libvirt: check qemu version for NUMA & hugepage support In commit 945ab28 the libvirt driver was modified to validate the libvirt version before enabling NUMA and hugepage support. While good, it's not sufficient. We also need to check the qemu version since NUMA hugepage support wasn't added until version 2.1. (See http://wiki.qemu.org/ChangeLog/2.1#x86, specifically the memory-backend-* objects.) We know that qemu on the s390x doesn't support NUMA or hugepages. Support for ppc64 is possible but not yet determined...we can update the supported list once we find out for sure. Change-Id: Ib31c4f806e30a8b33669d5a15bd1578050c7e352 Closes-Bug: #1422775 --- .../functional/libvirt/test_numa_servers.py | 1 + nova/tests/unit/virt/libvirt/fakelibvirt.py | 5 +- nova/tests/unit/virt/libvirt/test_driver.py | 111 +++++++++++++++--- nova/virt/libvirt/driver.py | 35 +++--- 4 files changed, 120 insertions(+), 32 deletions(-) diff --git a/nova/tests/functional/libvirt/test_numa_servers.py b/nova/tests/functional/libvirt/test_numa_servers.py index da914c01adc0..b266efb9e981 100644 --- a/nova/tests/functional/libvirt/test_numa_servers.py +++ b/nova/tests/functional/libvirt/test_numa_servers.py @@ -127,6 +127,7 @@ class NUMAServersTest(ServersTestBase): cpu_threads=2, kB_mem=15740000) fake_connection = fakelibvirt.Connection('qemu:///system', version=1002007, + hv_version=2001000, host_info=host_info) # Create a flavor diff --git a/nova/tests/unit/virt/libvirt/fakelibvirt.py b/nova/tests/unit/virt/libvirt/fakelibvirt.py index c4ee0faa60b8..b2f266fbea8b 100644 --- a/nova/tests/unit/virt/libvirt/fakelibvirt.py +++ b/nova/tests/unit/virt/libvirt/fakelibvirt.py @@ -743,7 +743,8 @@ class DomainSnapshot(object): class Connection(object): - def __init__(self, uri=None, readonly=False, version=9011, host_info=None): + def __init__(self, uri=None, readonly=False, version=9011, + hv_version=1001000, host_info=None): if not uri or uri == '': if allow_default_uri_connection: uri = 'qemu:///session' @@ -775,7 +776,7 @@ class Connection(object): self._nodedevs = {} self._event_callbacks = {} self.fakeLibVersion = version - self.fakeVersion = version + self.fakeVersion = hv_version self.host_info = host_info or HostInfo() def _add_filter(self, nwfilter): diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index 88d72bd53e47..10f11f2ee18f 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -1338,13 +1338,17 @@ class LibvirtConnTestCase(test.NoDBTestCase): self.assertEqual(0, len(cfg.cputune.vcpupin)) self.assertIsNone(cfg.cpu.numa) + @mock.patch.object(fakelibvirt.Connection, 'getType') + @mock.patch.object(fakelibvirt.Connection, 'getVersion') @mock.patch.object(fakelibvirt.Connection, 'getLibVersion') @mock.patch.object(host.Host, 'get_capabilities') @mock.patch.object(libvirt_driver.LibvirtDriver, '_set_host_enabled') - def _test_get_guest_config_numa_unsupported(self, fake_version, - exception_class, pagesize, - mock_host, - mock_caps, mock_version): + def _test_get_guest_config_numa_unsupported(self, fake_lib_version, + fake_version, fake_type, + fake_arch, exception_class, + pagesize, mock_host, + mock_caps, mock_lib_version, + mock_version, mock_type): instance_topology = objects.InstanceNUMATopology( cells=[objects.InstanceNUMACell( id=0, cpuset=set([0]), @@ -1360,10 +1364,12 @@ class LibvirtConnTestCase(test.NoDBTestCase): caps = vconfig.LibvirtConfigCaps() caps.host = vconfig.LibvirtConfigCapsHost() caps.host.cpu = vconfig.LibvirtConfigCPU() - caps.host.cpu.arch = "x86_64" + caps.host.cpu.arch = fake_arch caps.host.topology = self._fake_caps_numa_topology() + mock_type.return_value = fake_type mock_version.return_value = fake_version + mock_lib_version.return_value = fake_lib_version mock_caps.return_value = caps drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) @@ -1375,12 +1381,42 @@ class LibvirtConnTestCase(test.NoDBTestCase): drvr._get_guest_config, instance_ref, [], {}, disk_info) - def test_get_guest_config_numa_old_version(self): + def test_get_guest_config_numa_old_version_libvirt(self): self.flags(virt_type='kvm', group='libvirt') self._test_get_guest_config_numa_unsupported( utils.convert_version_to_int( libvirt_driver.MIN_LIBVIRT_NUMA_VERSION) - 1, + utils.convert_version_to_int( + libvirt_driver.MIN_QEMU_NUMA_HUGEPAGE_VERSION), + libvirt_driver.REQ_HYPERVISOR_NUMA_HUGEPAGE, + arch.X86_64, + exception.NUMATopologyUnsupported, + None) + + def test_get_guest_config_numa_old_version_qemu(self): + self.flags(virt_type='kvm', group='libvirt') + + self._test_get_guest_config_numa_unsupported( + utils.convert_version_to_int( + libvirt_driver.MIN_LIBVIRT_NUMA_VERSION), + utils.convert_version_to_int( + libvirt_driver.MIN_QEMU_NUMA_HUGEPAGE_VERSION) - 1, + libvirt_driver.REQ_HYPERVISOR_NUMA_HUGEPAGE, + arch.X86_64, + exception.NUMATopologyUnsupported, + None) + + def test_get_guest_config_numa_other_arch_qemu(self): + self.flags(virt_type='kvm', group='libvirt') + + self._test_get_guest_config_numa_unsupported( + utils.convert_version_to_int( + libvirt_driver.MIN_LIBVIRT_NUMA_VERSION), + utils.convert_version_to_int( + libvirt_driver.MIN_QEMU_NUMA_HUGEPAGE_VERSION), + libvirt_driver.REQ_HYPERVISOR_NUMA_HUGEPAGE, + arch.PPC64, exception.NUMATopologyUnsupported, None) @@ -1390,18 +1426,38 @@ class LibvirtConnTestCase(test.NoDBTestCase): self._test_get_guest_config_numa_unsupported( utils.convert_version_to_int( libvirt_driver.MIN_LIBVIRT_NUMA_VERSION), + utils.convert_version_to_int((4, 5, 0)), + 'XEN', + arch.X86_64, exception.NUMATopologyUnsupported, None) - def test_get_guest_config_numa_old_pages(self): + def test_get_guest_config_numa_old_pages_libvirt(self): self.flags(virt_type='kvm', group='libvirt') self._test_get_guest_config_numa_unsupported( utils.convert_version_to_int( libvirt_driver.MIN_LIBVIRT_HUGEPAGE_VERSION) - 1, + utils.convert_version_to_int( + libvirt_driver.MIN_QEMU_NUMA_HUGEPAGE_VERSION), + libvirt_driver.REQ_HYPERVISOR_NUMA_HUGEPAGE, + arch.X86_64, exception.MemoryPagesUnsupported, 2048) + def test_get_guest_config_numa_old_pages_qemu(self): + self.flags(virt_type='kvm', group='libvirt') + + self._test_get_guest_config_numa_unsupported( + utils.convert_version_to_int( + libvirt_driver.MIN_LIBVIRT_HUGEPAGE_VERSION), + utils.convert_version_to_int( + libvirt_driver.MIN_QEMU_NUMA_HUGEPAGE_VERSION) - 1, + libvirt_driver.REQ_HYPERVISOR_NUMA_HUGEPAGE, + arch.X86_64, + exception.NUMATopologyUnsupported, + 2048) + def test_get_guest_config_numa_host_instance_fit_w_cpu_pinset(self): instance_ref = objects.Instance(**self.test_instance) image_meta = {} @@ -8816,6 +8872,8 @@ class LibvirtConnTestCase(test.NoDBTestCase): def _test_get_host_numa_topology(self, mempages): caps = vconfig.LibvirtConfigCaps() caps.host = vconfig.LibvirtConfigCapsHost() + caps.host.cpu = vconfig.LibvirtConfigCPU() + caps.host.cpu.arch = arch.X86_64 caps.host.topology = self._fake_caps_numa_topology() drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) @@ -8873,17 +8931,24 @@ class LibvirtConnTestCase(test.NoDBTestCase): def test_get_host_numa_topology(self, mock_version): self._test_get_host_numa_topology(mempages=True) + @mock.patch.object(fakelibvirt.Connection, 'getType') + @mock.patch.object(fakelibvirt.Connection, 'getVersion') @mock.patch.object(fakelibvirt.Connection, 'getLibVersion') - def test_get_host_numa_topology_no_mempages(self, mock_version): + def test_get_host_numa_topology_no_mempages(self, mock_lib_version, + mock_version, mock_type): self.flags(virt_type='kvm', group='libvirt') - - mock_version.return_value = utils.convert_version_to_int( + mock_lib_version.return_value = utils.convert_version_to_int( libvirt_driver.MIN_LIBVIRT_HUGEPAGE_VERSION) - 1 + mock_version.return_value = utils.convert_version_to_int( + libvirt_driver.MIN_QEMU_NUMA_HUGEPAGE_VERSION) + mock_type.return_value = libvirt_driver.REQ_HYPERVISOR_NUMA_HUGEPAGE self._test_get_host_numa_topology(mempages=False) def test_get_host_numa_topology_empty(self): caps = vconfig.LibvirtConfigCaps() caps.host = vconfig.LibvirtConfigCapsHost() + caps.host.cpu = vconfig.LibvirtConfigCPU() + caps.host.cpu.arch = arch.X86_64 caps.host.topology = None drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) @@ -8893,22 +8958,36 @@ class LibvirtConnTestCase(test.NoDBTestCase): return_value=caps) ) as (has_min_version, get_caps): self.assertIsNone(drvr._get_host_numa_topology()) - get_caps.assert_called_once_with() + self.assertEqual(2, get_caps.call_count) + @mock.patch.object(fakelibvirt.Connection, 'getType') + @mock.patch.object(fakelibvirt.Connection, 'getVersion') @mock.patch.object(fakelibvirt.Connection, 'getLibVersion') - def test_get_host_numa_topology_old_version(self, mock_version): + def test_get_host_numa_topology_old_version(self, mock_lib_version, + mock_version, mock_type): self.flags(virt_type='kvm', group='libvirt') drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) - mock_version.side_effect = utils.convert_version_to_int( + mock_lib_version.return_value = utils.convert_version_to_int( libvirt_driver.MIN_LIBVIRT_NUMA_VERSION) - 1 + mock_version.return_value = utils.convert_version_to_int( + libvirt_driver.MIN_QEMU_NUMA_HUGEPAGE_VERSION) + mock_type.return_value = libvirt_driver.REQ_HYPERVISOR_NUMA_HUGEPAGE self.assertIsNone(drvr._get_host_numa_topology()) - @mock.patch.object(host.Host, 'has_min_version', return_value=True) - def test_get_host_numa_topology_xen(self, mock_version): + @mock.patch.object(fakelibvirt.Connection, 'getType') + @mock.patch.object(fakelibvirt.Connection, 'getVersion') + @mock.patch.object(fakelibvirt.Connection, 'getLibVersion') + def test_get_host_numa_topology_xen(self, mock_lib_version, + mock_version, mock_type): self.flags(virt_type='xen', group='libvirt') drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), False) + mock_lib_version.return_value = utils.convert_version_to_int( + libvirt_driver.MIN_LIBVIRT_NUMA_VERSION) + mock_version.return_value = utils.convert_version_to_int( + libvirt_driver.MIN_QEMU_NUMA_HUGEPAGE_VERSION) + mock_type.return_value = 'xen' self.assertIsNone(drvr._get_host_numa_topology()) def test_diagnostic_vcpus_exception(self): @@ -11160,7 +11239,7 @@ class HostStateTestCase(test.NoDBTestCase): self.assertEqual(stats["memory_mb_used"], 88) self.assertEqual(stats["local_gb_used"], 20) self.assertEqual(stats["hypervisor_type"], 'QEMU') - self.assertEqual(stats["hypervisor_version"], 9011) + self.assertEqual(stats["hypervisor_version"], 1001000) self.assertEqual(stats["hypervisor_hostname"], 'compute1') cpu_info = jsonutils.loads(stats["cpu_info"]) self.assertEqual(cpu_info, diff --git a/nova/virt/libvirt/driver.py b/nova/virt/libvirt/driver.py index 370ee6cbf462..434242ed9914 100644 --- a/nova/virt/libvirt/driver.py +++ b/nova/virt/libvirt/driver.py @@ -364,6 +364,11 @@ MIN_LIBVIRT_NUMA_VERSION = (1, 2, 7) MIN_LIBVIRT_HUGEPAGE_VERSION = (1, 2, 8) # missing libvirt cpu pinning support BAD_LIBVIRT_CPU_POLICY_VERSIONS = [(1, 2, 9, 2), (1, 2, 10)] +# qemu 2.1 introduces support for pinning memory on host +# NUMA nodes, along with the ability to specify hugepage +# sizes per guest NUMA node +MIN_QEMU_NUMA_HUGEPAGE_VERSION = (2, 1, 0) +REQ_HYPERVISOR_NUMA_HUGEPAGE = "QEMU" # fsFreeze/fsThaw requirement MIN_LIBVIRT_FSFREEZE_VERSION = (1, 2, 5) @@ -4741,22 +4746,24 @@ class LibvirtDriver(driver.ComputeDriver): return jsonutils.dumps(pci_info) def _has_numa_support(self): - if not self._host.has_min_version(MIN_LIBVIRT_NUMA_VERSION): - return False - - if CONF.libvirt.virt_type not in ['qemu', 'kvm']: - return False - - return True + # This means that the host can support LibvirtConfigGuestNUMATune + # and the nodeset field in LibvirtConfigGuestMemoryBackingPage + supported_archs = [arch.I686, arch.X86_64] + caps = self._host.get_capabilities() + return ((caps.host.cpu.arch in supported_archs) and + self._host.has_min_version(MIN_LIBVIRT_NUMA_VERSION, + MIN_QEMU_NUMA_HUGEPAGE_VERSION, + REQ_HYPERVISOR_NUMA_HUGEPAGE)) def _has_hugepage_support(self): - if not self._host.has_min_version(MIN_LIBVIRT_HUGEPAGE_VERSION): - return False - - if CONF.libvirt.virt_type not in ['qemu', 'kvm']: - return False - - return True + # This means that the host can support multiple values for the size + # field in LibvirtConfigGuestMemoryBackingPage + supported_archs = [arch.I686, arch.X86_64] + caps = self._host.get_capabilities() + return ((caps.host.cpu.arch in supported_archs) and + self._host.has_min_version(MIN_LIBVIRT_HUGEPAGE_VERSION, + MIN_QEMU_NUMA_HUGEPAGE_VERSION, + REQ_HYPERVISOR_NUMA_HUGEPAGE)) def _get_host_numa_topology(self): if not self._has_numa_support():