diff --git a/nova/objects/instance_pci_requests.py b/nova/objects/instance_pci_requests.py index 7e3a920c5a9e..d1accd48333f 100644 --- a/nova/objects/instance_pci_requests.py +++ b/nova/objects/instance_pci_requests.py @@ -90,6 +90,8 @@ class InstancePCIRequests(base.NovaObject, request_obj = InstancePCIRequest( count=request['count'], spec=request['spec'], alias_name=request['alias_name'], is_new=False, + numa_policy=request.get('numa_policy', + fields.PCINUMAAffinityPolicy.LEGACY), request_id=request['request_id'], requester_id=request.get('requester_id')) request_obj.obj_reset_changes() @@ -142,6 +144,7 @@ class InstancePCIRequests(base.NovaObject, 'spec': x.spec, 'alias_name': x.alias_name, 'is_new': x.is_new, + 'numa_policy': x.numa_policy, 'request_id': x.request_id, 'requester_id': x.requester_id} for x in self.requests] return jsonutils.dumps(blob) diff --git a/nova/tests/functional/libvirt/test_pci_sriov_servers.py b/nova/tests/functional/libvirt/test_pci_sriov_servers.py index 78ec391fb835..813bfb7fd11e 100644 --- a/nova/tests/functional/libvirt/test_pci_sriov_servers.py +++ b/nova/tests/functional/libvirt/test_pci_sriov_servers.py @@ -26,27 +26,32 @@ CONF = cfg.CONF LOG = logging.getLogger(__name__) -class SRIOVServersTest(base.ServersTestBase): +class _PCIServersTestBase(base.ServersTestBase): vfs_alias_name = 'vfs' pfs_alias_name = 'pfs' + pci_passthrough_whitelist = [ + '{"vendor_id":"8086", "product_id":"1528"}', + '{"vendor_id":"8086", "product_id":"1515"}', + ] + # PFs will be removed from pools unless they are specifically + # requested, so we explicitly request them with the 'device_type' + # attribute + pci_alias = [ + '{"vendor_id":"8086", "product_id":"1528", "name":"%s", ' + '"device_type":"%s"}' % ( + pfs_alias_name, fields.PciDeviceType.SRIOV_PF), + '{"vendor_id":"8086", "product_id":"1515", "name":"%s"}' % ( + vfs_alias_name), + ] + def setUp(self): - white_list = ['{"vendor_id":"8086","product_id":"1528"}', - '{"vendor_id":"8086","product_id":"1515"}'] - self.flags(passthrough_whitelist=white_list, group='pci') + self.flags(passthrough_whitelist=self.pci_passthrough_whitelist, + alias=self.pci_alias, + group='pci') - # PFs will be removed from pools, unless these has been specifically - # requested. This is especially needed in cases where PFs and VFs have - # the same vendor/product id - pci_alias = ['{"vendor_id":"8086", "product_id":"1528", "name":"%s",' - ' "device_type":"%s"}' % (self.pfs_alias_name, - fields.PciDeviceType.SRIOV_PF), - '{"vendor_id":"8086", "product_id":"1515", "name":"%s"}' % - self.vfs_alias_name] - self.flags(alias=pci_alias, group='pci') - - super(SRIOVServersTest, self).setUp() + super(_PCIServersTestBase, self).setUp() self.compute_started = False @@ -63,10 +68,12 @@ class SRIOVServersTest(base.ServersTestBase): def _setup_scheduler_service(self): # Enable the 'NUMATopologyFilter', 'PciPassthroughFilter' + enabled_filters = CONF.filter_scheduler.enabled_filters + [ + 'NUMATopologyFilter', 'PciPassthroughFilter'] + self.flags(driver='filter_scheduler', group='scheduler') - self.flags(enabled_filters=CONF.filter_scheduler.enabled_filters - + ['NUMATopologyFilter', 'PciPassthroughFilter'], - group='filter_scheduler') + self.flags(enabled_filters=enabled_filters, group='filter_scheduler') + return self.start_service('scheduler') def _run_build_test(self, flavor_id, end_status='ACTIVE'): @@ -103,6 +110,9 @@ class SRIOVServersTest(base.ServersTestBase): self.addCleanup(self._delete_server, created_server_id) return created_server + +class SRIOVServersTest(_PCIServersTestBase): + @mock.patch('nova.virt.libvirt.LibvirtDriver._create_image') def test_create_server_with_VF(self, img_mock): @@ -177,6 +187,9 @@ class SRIOVServersTest(base.ServersTestBase): self._run_build_test(flavor_id_vfs) self._run_build_test(flavor_id_pfs, end_status='ERROR') + +class PCIServersTest(_PCIServersTestBase): + @mock.patch('nova.virt.libvirt.LibvirtDriver._create_image') def test_create_server_with_pci_dev_and_numa(self, img_mock): """Verifies that an instance can be booted with cpu pinning and with an @@ -190,11 +203,11 @@ class SRIOVServersTest(base.ServersTestBase): fake_connection = self._get_connection(host_info, pci_info) self.mock_conn.return_value = fake_connection - # Create a flavor - extra_spec = {"pci_passthrough:alias": "%s:1" % self.pfs_alias_name, - 'hw:numa_nodes': '1', - 'hw:cpu_policy': 'dedicated', - 'hw:cpu_thread_policy': 'prefer'} + # create a flavor + extra_spec = { + 'hw:cpu_policy': 'dedicated', + 'pci_passthrough:alias': '%s:1' % self.pfs_alias_name, + } flavor_id = self._create_flavor(extra_spec=extra_spec) self._run_build_test(flavor_id) @@ -212,15 +225,64 @@ class SRIOVServersTest(base.ServersTestBase): fake_connection = self._get_connection(host_info, pci_info) self.mock_conn.return_value = fake_connection - # Create a flavor - extra_spec_vm = {'hw:cpu_policy': 'dedicated', - 'hw:numa_node': '1'} - extra_spec = {'pci_passthrough:alias': '%s:1' % self.pfs_alias_name, - 'hw:numa_nodes': '1', - 'hw:cpu_policy': 'dedicated', - 'hw:cpu_thread_policy': 'prefer'} - vm_flavor_id = self._create_flavor(vcpu=4, extra_spec=extra_spec_vm) - pf_flavor_id = self._create_flavor(extra_spec=extra_spec) + # boot one instance with no PCI device to "fill up" NUMA node 0 + extra_spec = { + 'hw:cpu_policy': 'dedicated', + } + flavor_id = self._create_flavor(vcpu=4, extra_spec=extra_spec) - self._run_build_test(vm_flavor_id) - self._run_build_test(pf_flavor_id, end_status='ERROR') + self._run_build_test(flavor_id) + + # now boot one with a PCI device, which should fail to boot + extra_spec['pci_passthrough:alias'] = '%s:1' % self.pfs_alias_name + flavor_id = self._create_flavor(extra_spec=extra_spec) + + self._run_build_test(flavor_id, end_status='ERROR') + + +class PCIServersWithNUMAPoliciesTest(_PCIServersTestBase): + + # PFs will be removed from pools unless they are specifically + # requested, so we explicitly request them with the 'device_type' + # attribute + pci_alias = [ + '{"vendor_id":"8086", "product_id":"1528", "name":"%s", ' + '"device_type":"%s", "numa_policy":"%s"}' % ( + _PCIServersTestBase.pfs_alias_name, + fields.PciDeviceType.SRIOV_PF, + fields.PCINUMAAffinityPolicy.PREFERRED + ), + '{"vendor_id":"8086", "product_id":"1515", "name":"%s"}' % ( + _PCIServersTestBase.vfs_alias_name), + ] + + @mock.patch('nova.virt.libvirt.LibvirtDriver._create_image') + def test_create_server_with_pci_dev_and_numa(self, img_mock): + """Validate behavior of 'preferred' PCI NUMA policy. + + This test ensures that it *is* possible to allocate CPU and memory + resources from one NUMA node and a PCI device from another *if* PCI + NUMA policies are in use. + """ + + host_info = fakelibvirt.NUMAHostInfo(cpu_nodes=2, cpu_sockets=1, + cpu_cores=2, cpu_threads=2, + kB_mem=15740000) + pci_info = fakelibvirt.HostPciSRIOVDevicesInfo(num_pfs=1, numa_node=0) + fake_connection = self._get_connection(host_info, pci_info) + self.mock_conn.return_value = fake_connection + + # boot one instance with no PCI device to "fill up" NUMA node 0 + extra_spec = { + 'hw:cpu_policy': 'dedicated', + } + flavor_id = self._create_flavor(vcpu=4, extra_spec=extra_spec) + + self._run_build_test(flavor_id) + + # now boot one with a PCI device, which should succeed thanks to the + # use of the PCI policy + extra_spec['pci_passthrough:alias'] = '%s:1' % self.pfs_alias_name + flavor_id = self._create_flavor(extra_spec=extra_spec) + + self._run_build_test(flavor_id) diff --git a/nova/tests/unit/objects/test_instance.py b/nova/tests/unit/objects/test_instance.py index 4b301ee280c9..81a0db93a239 100644 --- a/nova/tests/unit/objects/test_instance.py +++ b/nova/tests/unit/objects/test_instance.py @@ -574,7 +574,7 @@ class _TestInstanceObject(object): expected_json = ('[{"count": 1, "alias_name": null, "is_new": false,' '"request_id": null, "requester_id": null,' '"spec": [{"vendor_id": "8086", ' - '"product_id": "1502"}]}]') + '"product_id": "1502"}], "numa_policy": null}]') inst = objects.Instance() inst = objects.Instance._from_db_object(self.context, inst, diff --git a/nova/tests/unit/objects/test_instance_pci_requests.py b/nova/tests/unit/objects/test_instance_pci_requests.py index 98f5255cf03a..c10ceacda808 100644 --- a/nova/tests/unit/objects/test_instance_pci_requests.py +++ b/nova/tests/unit/objects/test_instance_pci_requests.py @@ -32,12 +32,14 @@ fake_pci_requests = [ 'device_id': '1502'}], 'alias_name': 'alias_1', 'is_new': False, + 'numa_policy': 'preferred', 'request_id': FAKE_REQUEST_UUID}, {'count': 2, 'spec': [{'vendor_id': '6502', 'device_id': '07B5'}], 'alias_name': 'alias_2', 'is_new': True, + 'numa_policy': 'preferred', 'request_id': FAKE_REQUEST_UUID, 'requester_id': uuids.requester_id}, ] @@ -71,6 +73,8 @@ class _TestInstancePCIRequests(object): request.count) self.assertEqual(fake_pci_requests[index]['spec'], [dict(x.items()) for x in request.spec]) + self.assertEqual(fake_pci_requests[index]['numa_policy'], + request.numa_policy) @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid') def test_get_by_instance_current(self, mock_get): @@ -118,6 +122,7 @@ class _TestInstancePCIRequests(object): self.assertEqual(FAKE_UUID, req.instance_uuid) self.assertEqual(2, len(req.requests)) self.assertEqual('alias_1', req.requests[0].alias_name) + self.assertEqual('preferred', req.requests[0].numa_policy) self.assertIsNone(None, req.requests[0].requester_id) self.assertEqual(uuids.requester_id, req.requests[1].requester_id)