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)