diff --git a/nova/tests/functional/libvirt/__init__.py b/nova/tests/functional/libvirt/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/nova/tests/functional/libvirt/test_numa_servers.py b/nova/tests/functional/libvirt/test_numa_servers.py new file mode 100644 index 00000000000..da914c01adc --- /dev/null +++ b/nova/tests/functional/libvirt/test_numa_servers.py @@ -0,0 +1,166 @@ +# Copyright (C) 2015 Red Hat, Inc +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import contextlib +import mock + +import fixtures +from oslo_config import cfg +from oslo_log import log as logging + +from nova.tests.functional.test_servers import ServersTestBase +from nova.tests.unit import fake_network +from nova.tests.unit.virt.libvirt import fake_libvirt_utils +from nova.tests.unit.virt.libvirt import fakelibvirt + + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) + + +class NumaHostInfo(fakelibvirt.HostInfo): + def __init__(self, **kwargs): + super(NumaHostInfo, self).__init__(**kwargs) + self.numa_mempages_list = [] + + def get_numa_topology(self): + if self.numa_topology: + return self.numa_topology + + topology = self._gen_numa_topology(self.cpu_nodes, self.cpu_sockets, + self.cpu_cores, self.cpu_threads, + self.kB_mem) + self.numa_topology = topology + + # update number of active cpus + cpu_count = len(topology.cells) * len(topology.cells[0].cpus) + self.cpus = cpu_count - len(self.disabled_cpus_list) + return topology + + def set_custom_numa_toplogy(self, topology): + self.numa_topology = topology + + +class NUMAServersTest(ServersTestBase): + + def setUp(self): + super(NUMAServersTest, self).setUp() + + # Replace libvirt with fakelibvirt + self.useFixture(fixtures.MonkeyPatch( + 'nova.virt.libvirt.driver.libvirt_utils', + fake_libvirt_utils)) + self.useFixture(fixtures.MonkeyPatch( + 'nova.virt.libvirt.driver.libvirt', + fakelibvirt)) + self.useFixture(fixtures.MonkeyPatch( + 'nova.virt.libvirt.host.libvirt', + fakelibvirt)) + self.useFixture(fakelibvirt.FakeLibvirtFixture()) + + def _setup_compute_service(self): + pass + + def _setup_scheduler_service(self): + self.flags(compute_driver='nova.virt.libvirt.LibvirtDriver') + + self.flags(scheduler_driver='nova.scheduler.' + 'filter_scheduler.FilterScheduler') + self.flags(scheduler_default_filters=CONF.scheduler_default_filters + + ['NUMATopologyFilter']) + return self.start_service('scheduler') + + def _run_build_test(self, flavor_id, filter_mock, end_status='ACTIVE'): + + self.compute = self.start_service('compute', host='test_compute0') + fake_network.set_stub_network_methods(self.stubs) + + # Create server + good_server = self._build_server(flavor_id) + + post = {'server': good_server} + + created_server = self.api.post_server(post) + LOG.debug("created_server: %s" % created_server) + self.assertTrue(created_server['id']) + created_server_id = created_server['id'] + + # Validate that the server has been created + found_server = self.api.get_server(created_server_id) + self.assertEqual(created_server_id, found_server['id']) + + # It should also be in the all-servers list + servers = self.api.get_servers() + server_ids = [s['id'] for s in servers] + self.assertIn(created_server_id, server_ids) + + # Validate that NUMATopologyFilter has been called + self.assertTrue(filter_mock.called) + + found_server = self._wait_for_state_change(found_server, 'BUILD') + + self.assertEqual(end_status, found_server['status']) + self._delete_server(created_server_id) + + def _get_topology_filter_spy(self): + host_manager = self.scheduler.manager.driver.host_manager + numa_filter_class = host_manager.filter_cls_map['NUMATopologyFilter'] + host_pass_mock = mock.Mock(wraps=numa_filter_class().host_passes) + return host_pass_mock + + @mock.patch('nova.virt.libvirt.LibvirtDriver._create_image') + def test_create_server_with_numa_topology(self, img_mock): + + host_info = NumaHostInfo(cpu_nodes=2, cpu_sockets=1, cpu_cores=2, + cpu_threads=2, kB_mem=15740000) + fake_connection = fakelibvirt.Connection('qemu:///system', + version=1002007, + host_info=host_info) + + # Create a flavor + extra_spec = {'hw:numa_nodes': '2'} + flavor_id = self._create_flavor(extra_spec=extra_spec) + host_pass_mock = self._get_topology_filter_spy() + with contextlib.nested( + mock.patch('nova.virt.libvirt.host.Host.get_connection', + return_value=fake_connection), + mock.patch('nova.scheduler.filters' + '.numa_topology_filter.NUMATopologyFilter.host_passes', + side_effect=host_pass_mock)) as (conn_mock, + filter_mock): + self._run_build_test(flavor_id, filter_mock) + + @mock.patch('nova.virt.libvirt.LibvirtDriver._create_image') + def test_create_server_with_numa_fails(self, img_mock): + + host_info = NumaHostInfo(cpu_nodes=1, cpu_sockets=1, cpu_cores=2, + kB_mem=15740000) + fake_connection = fakelibvirt.Connection('qemu:///system', + version=1002007, + host_info=host_info) + + # Create a flavor + extra_spec = {'hw:numa_nodes': '2'} + flavor_id = self._create_flavor(extra_spec=extra_spec) + + host_pass_mock = self._get_topology_filter_spy() + with contextlib.nested( + mock.patch('nova.virt.libvirt.host.Host.get_connection', + return_value=fake_connection), + mock.patch('nova.scheduler.filters' + '.numa_topology_filter.NUMATopologyFilter.host_passes', + side_effect=host_pass_mock)) as (conn_mock, + filter_mock): + self._run_build_test(flavor_id, filter_mock, end_status='ERROR') diff --git a/nova/tests/unit/virt/libvirt/fakelibvirt.py b/nova/tests/unit/virt/libvirt/fakelibvirt.py index d2970442eb7..c4ee0faa60b 100644 --- a/nova/tests/unit/virt/libvirt/fakelibvirt.py +++ b/nova/tests/unit/virt/libvirt/fakelibvirt.py @@ -19,6 +19,7 @@ from lxml import etree from nova.compute import arch +from nova.virt.libvirt import config as vconfig # Allow passing None to the various connect methods # (i.e. allow the client to rely on default URLs) @@ -186,6 +187,46 @@ def __init__(self, arch=arch.X86_64, kB_mem=4096, self.numa_topology = numa_topology self.disabled_cpus_list = cpu_disabled or [] + @classmethod + def _gen_numa_topology(self, cpu_nodes, cpu_sockets, cpu_cores, + cpu_threads, kb_mem, numa_mempages_list=None): + + topology = vconfig.LibvirtConfigCapsNUMATopology() + + cpu_count = 0 + for cell_count in range(cpu_nodes): + cell = vconfig.LibvirtConfigCapsNUMACell() + cell.id = cell_count + cell.memory = kb_mem / cpu_nodes + for socket_count in range(cpu_sockets): + for cpu_num in range(cpu_cores * cpu_threads): + cpu = vconfig.LibvirtConfigCapsNUMACPU() + cpu.id = cpu_count + cpu.socket_id = cell_count + cpu.core_id = cpu_num // cpu_threads + cpu.siblings = set([cpu_threads * + (cpu_count // cpu_threads) + thread + for thread in range(cpu_threads)]) + cell.cpus.append(cpu) + + cpu_count += 1 + # Set mempages per numa cell. if numa_mempages_list is empty + # we will set only the default 4K pages. + if numa_mempages_list: + mempages = numa_mempages_list[cell_count] + else: + mempages = vconfig.LibvirtConfigCapsNUMAPages() + mempages.size = 4 + mempages.total = cell.memory / mempages.size + mempages = [mempages] + cell.mempages = mempages + topology.cells.append(cell) + + return topology + + def get_numa_topology(self): + return self.numa_topology + VIR_DOMAIN_JOB_NONE = 0 VIR_DOMAIN_JOB_BOUNDED = 1 @@ -868,6 +909,10 @@ def getCPUMap(self): def getCapabilities(self): """Return spoofed capabilities.""" + numa_topology = self.host_info.get_numa_topology() + if isinstance(numa_topology, vconfig.LibvirtConfigCapsNUMATopology): + numa_topology = numa_topology.to_xml() + return ''' cef19ce0-0ca2-11df-855d-b19fbce37686 @@ -1097,7 +1142,7 @@ def getCapabilities(self): ''' % {'sockets': self.host_info.cpu_sockets, 'cores': self.host_info.cpu_cores, 'threads': self.host_info.cpu_threads, - 'topology': self.host_info.numa_topology} + 'topology': numa_topology} def compareCPU(self, xml, flags): tree = etree.fromstring(xml) diff --git a/nova/tests/unit/virt/libvirt/test_fakelibvirt.py b/nova/tests/unit/virt/libvirt/test_fakelibvirt.py index c4e12d679b6..ee3bebaab57 100644 --- a/nova/tests/unit/virt/libvirt/test_fakelibvirt.py +++ b/nova/tests/unit/virt/libvirt/test_fakelibvirt.py @@ -384,3 +384,36 @@ def test_compareCPU_compatible_unspecified_model(self): conn.host_info.cpu_threads) self.assertEqual(conn.compareCPU(xml, 0), libvirt.VIR_CPU_COMPARE_IDENTICAL) + + def test_numa_topology_generation(self): + topology = """ + + + 7870000 + 1967500 + + + + + + + + + 7870000 + 1967500 + + + + + + + + + +""" + host_topology = libvirt.HostInfo._gen_numa_topology( + cpu_nodes=2, cpu_sockets=1, + cpu_cores=2, cpu_threads=2, + kb_mem=15740000) + self.assertEqual(host_topology.to_xml(), + topology)