From a8a5d44c8aca218f00649232c2b8a46aee59b77e Mon Sep 17 00:00:00 2001 From: Robert Li Date: Wed, 10 Sep 2014 16:55:51 -0400 Subject: [PATCH] Support SR-IOV networking in nova compute api and nova neutronv2 Add another field into the NetworkRequest object for the request_id. For a SR-IOV port, Create a PCI request and add the pci-request-id into the tuple. For a non SR-IOV port, the pci-request-id is None. Add new fields 'profile' and 'vnic_type' in VIF. Enhance nova neutronv2 api module to: -- retrieve a port's vnic-type and the physical network it is attached to -- populate binding:profile before updating a neutron port -- populate VIF with newly added fields. Partially Implements: blueprint pci-passthrough-sriov Change-Id: I4aab1cd43960d5b1b54a67a05c191f295dfc46fb --- nova/compute/api.py | 16 +- nova/network/api.py | 11 + nova/network/base_api.py | 10 + nova/network/model.py | 27 ++- nova/network/neutronv2/api.py | 91 +++++++- nova/objects/network_request.py | 14 +- nova/pci/pci_request.py | 2 + nova/scheduler/filter_scheduler.py | 5 - .../contrib/test_neutron_security_groups.py | 7 +- .../compute/plugins/v3/test_servers.py | 12 +- .../api/openstack/compute/test_servers.py | 12 +- nova/tests/api/openstack/fakes.py | 5 + nova/tests/compute/test_compute_cells.py | 2 +- nova/tests/network/test_network_info.py | 8 + nova/tests/network/test_neutronv2.py | 218 +++++++++++++++--- nova/tests/objects/test_network_request.py | 9 +- nova/tests/pci/test_pci_manager.py | 2 + nova/tests/scheduler/test_filter_scheduler.py | 17 -- 18 files changed, 383 insertions(+), 85 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 0867d1bc9217..bf79793dff72 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -784,8 +784,16 @@ class API(base.Base): system_metadata = flavors.save_flavor_info( dict(), instance_type) + # PCI requests come from two sources: instance flavor and + # requested_networks. The first call in below returns an + # InstancePCIRequests object which is a list of InstancePCIRequest + # objects. The second call in below creates an InstancePCIRequest + # object for each SR-IOV port, and append it to the list in the + # InstancePCIRequests object pci_request_info = pci_request.get_pci_requests_from_flavor( instance_type) + self.network_api.create_pci_requests_for_sriov_ports(context, + pci_request_info, requested_networks) base_options = { 'reservation_id': reservation_id, @@ -827,13 +835,15 @@ class API(base.Base): return base_options, max_network_count def _build_filter_properties(self, context, scheduler_hints, forced_host, - forced_node, instance_type): + forced_node, instance_type, pci_request_info): filter_properties = dict(scheduler_hints=scheduler_hints) filter_properties['instance_type'] = instance_type if forced_host: filter_properties['force_hosts'] = [forced_host] if forced_node: filter_properties['force_nodes'] = [forced_node] + if pci_request_info and pci_request_info.requests: + filter_properties['pci_requests'] = pci_request_info return filter_properties def _provision_instances(self, context, instance_type, min_count, @@ -1036,7 +1046,9 @@ class API(base.Base): block_device_mapping, shutdown_terminate) filter_properties = self._build_filter_properties(context, - scheduler_hints, forced_host, forced_node, instance_type) + scheduler_hints, forced_host, + forced_node, instance_type, + base_options.get('pci_request_info')) self._update_instance_group(context, instances, scheduler_hints) diff --git a/nova/network/api.py b/nova/network/api.py index feaf217dcdbe..c8bbb411d9a5 100644 --- a/nova/network/api.py +++ b/nova/network/api.py @@ -406,6 +406,17 @@ class API(base_api.NetworkAPI): # the requested number in this case. return num_instances + def create_pci_requests_for_sriov_ports(self, context, + pci_requests, + requested_networks): + """Check requested networks for any SR-IOV port request. + + Create a PCI request object for each SR-IOV port, and add it to the + pci_requests object that contains a list of PCI request object. + """ + # This is NOOP for Nova network since it doesn't support SR-IOV. + pass + @wrap_check_policy def get_instance_uuids_by_ip_filter(self, context, filters): """Returns a list of dicts in the form of diff --git a/nova/network/base_api.py b/nova/network/base_api.py index e92cb9b703ad..5bee95e4cce1 100644 --- a/nova/network/base_api.py +++ b/nova/network/base_api.py @@ -237,6 +237,16 @@ class NetworkAPI(base.Base): """Returns all network info related to an instance.""" raise NotImplementedError() + def create_pci_requests_for_sriov_ports(self, context, + pci_requests, + requested_networks): + """Check requested networks for any SR-IOV port request. + + Create a PCI request object for each SR-IOV port, and add it to the + pci_requests object that contains a list of PCI request object. + """ + raise NotImplementedError() + def validate_networks(self, context, requested_networks, num_instances): """validate the networks passed at the time of creating the server. diff --git a/nova/network/model.py b/nova/network/model.py index 2829d7bda856..f811f8d71b47 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -36,16 +36,29 @@ VIF_TYPE_IOVISOR = 'iovisor' VIF_TYPE_BRIDGE = 'bridge' VIF_TYPE_802_QBG = '802.1qbg' VIF_TYPE_802_QBH = '802.1qbh' +VIF_TYPE_HW_VEB = 'hw_veb' VIF_TYPE_MLNX_DIRECT = 'mlnx_direct' VIF_TYPE_MIDONET = 'midonet' VIF_TYPE_OTHER = 'other' # Constants for dictionary keys in the 'vif_details' field in the VIF # class -VIF_DETAIL_PORT_FILTER = 'port_filter' -VIF_DETAIL_OVS_HYBRID_PLUG = 'ovs_hybrid_plug' +VIF_DETAILS_PORT_FILTER = 'port_filter' +VIF_DETAILS_OVS_HYBRID_PLUG = 'ovs_hybrid_plug' VIF_DETAILS_PHYSICAL_NETWORK = 'physical_network' +# The following two constants define the SR-IOV related fields in the +# 'vif_details'. 'profileid' should be used for VIF_TYPE_802_QBH, +# 'vlan' for VIF_TYPE_HW_VEB +VIF_DETAILS_PROFILEID = 'profileid' +VIF_DETAILS_VLAN = 'vlan' + +# Define supported virtual NIC types. VNIC_TYPE_DIRECT and VNIC_TYPE_MACVTAP +# are used for SR-IOV ports +VNIC_TYPE_NORMAL = 'normal' +VNIC_TYPE_DIRECT = 'direct' +VNIC_TYPE_MACVTAP = 'macvtap' + # Constants for the 'vif_model' values VIF_MODEL_VIRTIO = 'virtio' VIF_MODEL_NE2K_PCI = 'ne2k_pci' @@ -273,6 +286,7 @@ class VIF(Model): def __init__(self, id=None, address=None, network=None, type=None, details=None, devname=None, ovs_interfaceid=None, qbh_params=None, qbg_params=None, active=False, + vnic_type=VNIC_TYPE_NORMAL, profile=None, **kwargs): super(VIF, self).__init__() @@ -287,11 +301,14 @@ class VIF(Model): self['qbh_params'] = qbh_params self['qbg_params'] = qbg_params self['active'] = active + self['vnic_type'] = vnic_type + self['profile'] = profile self._set_meta(kwargs) def __eq__(self, other): - keys = ['id', 'address', 'network', 'type', 'details', 'devname', + keys = ['id', 'address', 'network', 'vnic_type', + 'type', 'profile', 'details', 'devname', 'ovs_interfaceid', 'qbh_params', 'qbg_params', 'active'] return all(self[k] == other[k] for k in keys) @@ -342,10 +359,10 @@ class VIF(Model): return [] def is_hybrid_plug_enabled(self): - return self['details'].get(VIF_DETAIL_OVS_HYBRID_PLUG, False) + return self['details'].get(VIF_DETAILS_OVS_HYBRID_PLUG, False) def is_neutron_filtering_enabled(self): - return self['details'].get(VIF_DETAIL_PORT_FILTER, False) + return self['details'].get(VIF_DETAILS_PORT_FILTER, False) def get_physical_network(self): phy_network = self['network']['meta'].get('physical_network') diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 5ab60889bbce..1e6990fc7db7 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -16,6 +16,7 @@ # import time +import uuid from neutronclient.common import exceptions as neutron_client_exc from oslo.config import cfg @@ -35,6 +36,9 @@ from nova import objects from nova.openstack.common import excutils from nova.openstack.common import log as logging from nova.openstack.common import uuidutils +from nova.pci import pci_manager +from nova.pci import pci_request +from nova.pci import pci_whitelist neutron_opts = [ cfg.StrOpt('url', @@ -310,7 +314,8 @@ class API(base_api.NetworkAPI): # if this function is directly called without a requested_network param # or if it is indirectly called through allocate_port_for_instance() - # with None params=(network_id=None, requested_ip=None, port_id=None): + # with None params=(network_id=None, requested_ip=None, port_id=None, + # pci_request_id=None): if (not requested_networks or requested_networks.is_single_unspecified): # bug/1267723 - if no network is requested and more @@ -390,7 +395,9 @@ class API(base_api.NetworkAPI): port_req_body = {'port': {'device_id': instance.uuid, 'device_owner': zone}} try: - self._populate_neutron_extension_values(context, instance, + self._populate_neutron_extension_values(context, + instance, + request.pci_request_id, port_req_body) # Requires admin creds to set port bindings port_client = (neutron if not @@ -461,8 +468,27 @@ class API(base_api.NetworkAPI): self._refresh_neutron_extensions_cache(context) return constants.PORTBINDING_EXT in self.extensions + @staticmethod + def _populate_neutron_binding_profile(instance, pci_request_id, + port_req_body): + """Populate neutron binding:profile. + + Populate it with SR-IOV related information + """ + if pci_request_id: + pci_dev = pci_manager.get_instance_pci_devs( + instance, pci_request_id).pop() + devspec = pci_whitelist.get_pci_device_devspec(pci_dev) + profile = {'pci_vendor_info': "%s:%s" % (pci_dev.vendor_id, + pci_dev.product_id), + 'pci_slot': pci_dev.address, + 'physical_network': + devspec.get_tags().get('physical_network') + } + port_req_body['port']['binding:profile'] = profile + def _populate_neutron_extension_values(self, context, instance, - port_req_body): + pci_request_id, port_req_body): """Populate neutron extension values for the instance. If the extensions loaded contain QOS_QUEUE then pass the rxtx_factor. @@ -474,6 +500,9 @@ class API(base_api.NetworkAPI): port_req_body['port']['rxtx_factor'] = rxtx_factor if self._has_port_binding_extension(context): port_req_body['port']['binding:host_id'] = instance.get('host') + self._populate_neutron_binding_profile(instance, + pci_request_id, + port_req_body) def deallocate_for_instance(self, context, instance, **kwargs): """Deallocate all network resources related to the instance.""" @@ -487,7 +516,8 @@ class API(base_api.NetworkAPI): # NOTE(danms): Temporary and transitional if isinstance(requested_networks, objects.NetworkRequestList): requested_networks = requested_networks.as_tuples() - ports_to_skip = [port_id for nets, fips, port_id in requested_networks] + ports_to_skip = [port_id for nets, fips, port_id, pci_request_id + in requested_networks] ports = set(ports) - set(ports_to_skip) # Reset device_id and device_owner for the ports that are skipped for port in ports_to_skip: @@ -523,7 +553,8 @@ class API(base_api.NetworkAPI): requested_networks = objects.NetworkRequestList( objects=[objects.NetworkRequest(network_id=network_id, address=requested_ip, - port_id=port_id)]) + port_id=port_id, + pci_request_id=None)]) return self.allocate_for_instance(context, instance, requested_networks=requested_networks) @@ -670,6 +701,54 @@ class API(base_api.NetworkAPI): raise exception.FixedIpNotFoundForSpecificInstance( instance_uuid=instance['uuid'], ip=address) + def _get_port_vnic_info(self, context, neutron, port_id): + """Retrieve port vnic info + + Invoked with a valid port_id. + Return vnic type and the attached physical network name. + """ + phynet_name = None + vnic_type = None + port = neutron.show_port(port_id, + fields=['binding:vnic_type', 'network_id']).get('port') + vnic_type = port['binding:vnic_type'] + if vnic_type != network_model.VNIC_TYPE_NORMAL: + net_id = port['network_id'] + net = neutron.show_network(net_id, + fields='provider:physical_network').get('network') + phynet_name = net.get('provider:physical_network') + return vnic_type, phynet_name + + def create_pci_requests_for_sriov_ports(self, context, pci_requests, + requested_networks): + """Check requested networks for any SR-IOV port request. + + Create a PCI request object for each SR-IOV port, and add it to the + pci_requests object that contains a list of PCI request object. + """ + if not requested_networks: + return + + neutron = neutronv2.get_client(context, admin=True) + for request_net in requested_networks: + phynet_name = None + vnic_type = network_model.VNIC_TYPE_NORMAL + + if request_net.port_id: + vnic_type, phynet_name = self._get_port_vnic_info( + context, neutron, request_net.port_id) + pci_request_id = None + if vnic_type != network_model.VNIC_TYPE_NORMAL: + request = objects.InstancePCIRequest( + count=1, + spec=[{pci_request.PCI_NET_TAG: phynet_name}], + request_id=str(uuid.uuid4())) + pci_requests.requests.append(request) + pci_request_id = request.request_id + + # Add pci_request_id into the requested network + request_net.pci_request_id = pci_request_id + def validate_networks(self, context, requested_networks, num_instances): """Validate that the tenant can use the requested networks. @@ -1297,7 +1376,9 @@ class API(base_api.NetworkAPI): id=current_neutron_port['id'], address=current_neutron_port['mac_address'], network=network, + vnic_type=current_neutron_port['binding:vnic_type'], type=current_neutron_port.get('binding:vif_type'), + profile=current_neutron_port.get('binding:profile'), details=current_neutron_port.get('binding:vif_details'), ovs_interfaceid=ovs_interfaceid, devname=devname, diff --git a/nova/objects/network_request.py b/nova/objects/network_request.py index 86ca23a4e372..1b128a609f77 100644 --- a/nova/objects/network_request.py +++ b/nova/objects/network_request.py @@ -19,11 +19,13 @@ from nova import utils class NetworkRequest(obj_base.NovaObject): # Version 1.0: Initial version + # Version 1.1: Added pci_request_id VERSION = '1.0' fields = { 'network_id': fields.StringField(nullable=True), 'address': fields.IPAddressField(nullable=True), 'port_id': fields.UUIDField(nullable=True), + 'pci_request_id': fields.UUIDField(nullable=True), } def obj_load_attr(self, attr): @@ -32,15 +34,16 @@ class NetworkRequest(obj_base.NovaObject): def to_tuple(self): address = str(self.address) if self.address is not None else None if utils.is_neutron(): - return self.network_id, address, self.port_id + return self.network_id, address, self.port_id, self.pci_request_id else: return self.network_id, address @classmethod def from_tuple(cls, net_tuple): - if len(net_tuple) == 3: - network_id, address, port_id = net_tuple - return cls(network_id=network_id, address=address, port_id=port_id) + if len(net_tuple) == 4: + network_id, address, port_id, pci_request_id = net_tuple + return cls(network_id=network_id, address=address, + port_id=port_id, pci_request_id=pci_request_id) else: network_id, address = net_tuple return cls(network_id=network_id, address=address) @@ -53,8 +56,9 @@ class NetworkRequestList(obj_base.ObjectListBase, obj_base.NovaObject): child_versions = { '1.0': '1.0', + '1.1': '1.1', } - VERSION = '1.0' + VERSION = '1.1' def as_tuples(self): return [x.to_tuple() for x in self.objects] diff --git a/nova/pci/pci_request.py b/nova/pci/pci_request.py index 512ef5974dc5..268fc14a95a0 100644 --- a/nova/pci/pci_request.py +++ b/nova/pci/pci_request.py @@ -66,6 +66,8 @@ pci_alias_opts = [ ) ] +PCI_NET_TAG = 'physical_network' + CONF = cfg.CONF CONF.register_opts(pci_alias_opts) diff --git a/nova/scheduler/filter_scheduler.py b/nova/scheduler/filter_scheduler.py index ba6d010b1989..3876c247602f 100644 --- a/nova/scheduler/filter_scheduler.py +++ b/nova/scheduler/filter_scheduler.py @@ -28,7 +28,6 @@ from nova import exception from nova.i18n import _ from nova import objects from nova.openstack.common import log as logging -from nova.pci import pci_request from nova import rpc from nova.scheduler import driver from nova.scheduler import scheduler_options @@ -207,10 +206,6 @@ class FilterScheduler(driver.Scheduler): os_type = request_spec['instance_properties']['os_type'] filter_properties['project_id'] = project_id filter_properties['os_type'] = os_type - pci_requests = pci_request.get_pci_requests_from_flavor( - request_spec.get('instance_type') or {}) - if pci_requests: - filter_properties['pci_requests'] = pci_requests def _setup_instance_group(self, context, filter_properties): update_group_hosts = False diff --git a/nova/tests/api/openstack/compute/contrib/test_neutron_security_groups.py b/nova/tests/api/openstack/compute/contrib/test_neutron_security_groups.py index 82203a23a040..71674c3fddf1 100644 --- a/nova/tests/api/openstack/compute/contrib/test_neutron_security_groups.py +++ b/nova/tests/api/openstack/compute/contrib/test_neutron_security_groups.py @@ -27,6 +27,7 @@ from nova import compute from nova import context import nova.db from nova import exception +from nova.network import model from nova.network import neutronv2 from nova.network.neutronv2 import api as neutron_api from nova.network.security_group import neutron_driver @@ -69,7 +70,7 @@ class TestNeutronSecurityGroups( return net def _create_port(self, **kwargs): - body = {'port': {}} + body = {'port': {'binding:vnic_type': model.VNIC_TYPE_NORMAL}} fields = ['security_groups', 'device_id', 'network_id', 'port_security_enabled'] for field in fields: @@ -711,7 +712,9 @@ class MockClient(object): 'device_id': p.get('device_id', str(uuid.uuid4())), 'admin_state_up': p.get('admin_state_up', True), 'security_groups': p.get('security_groups', []), - 'network_id': p.get('network_id')} + 'network_id': p.get('network_id'), + 'binding:vnic_type': + p.get('binding:vnic_type') or model.VNIC_TYPE_NORMAL} network = self._fake_networks[p['network_id']] if 'port_security_enabled' in p: diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_servers.py b/nova/tests/api/openstack/compute/plugins/v3/test_servers.py index 2818b38c76db..87c1aeaa8a05 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_servers.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_servers.py @@ -210,14 +210,14 @@ class ServersControllerTest(ControllerTest): port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' requested_networks = [{'port': port}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(None, None, port)], res.as_tuples()) + self.assertEqual([(None, None, port, None)], res.as_tuples()) def test_requested_networks_neutronv2_enabled_with_network(self): self.flags(network_api_class='nova.network.neutronv2.api.API') network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' requested_networks = [{'uuid': network}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(network, None, None)], res.as_tuples()) + self.assertEqual([(network, None, None, None)], res.as_tuples()) def test_requested_networks_neutronv2_enabled_with_network_and_port(self): self.flags(network_api_class='nova.network.neutronv2.api.API') @@ -225,7 +225,7 @@ class ServersControllerTest(ControllerTest): port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' requested_networks = [{'uuid': network, 'port': port}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(None, None, port)], res.as_tuples()) + self.assertEqual([(None, None, port, None)], res.as_tuples()) def test_requested_networks_neutronv2_enabled_conflict_on_fixed_ip(self): self.flags(network_api_class='nova.network.neutronv2.api.API') @@ -254,7 +254,7 @@ class ServersControllerTest(ControllerTest): port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' requested_networks = [{'uuid': network, 'port': port}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(None, None, port)], res.as_tuples()) + self.assertEqual([(None, None, port, None)], res.as_tuples()) def test_requested_networks_neutronv2_subclass_with_port(self): cls = 'nova.tests.api.openstack.compute.test_servers.NeutronV2Subclass' @@ -262,7 +262,7 @@ class ServersControllerTest(ControllerTest): port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' requested_networks = [{'port': port}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(None, None, port)], res.as_tuples()) + self.assertEqual([(None, None, port, None)], res.as_tuples()) def test_get_server_by_uuid(self): req = fakes.HTTPRequestV3.blank('/servers/%s' % FAKE_UUID) @@ -2167,7 +2167,7 @@ class ServersControllerCreateTest(test.TestCase): def create(*args, **kwargs): result = [('76fa36fc-c930-4bf3-8c8a-ea2a2420deb6', None, - None)] + None, None)] self.assertEqual(result, kwargs['requested_networks'].as_tuples()) return old_create(*args, **kwargs) diff --git a/nova/tests/api/openstack/compute/test_servers.py b/nova/tests/api/openstack/compute/test_servers.py index 5fd58546118b..2d6d61ad669b 100644 --- a/nova/tests/api/openstack/compute/test_servers.py +++ b/nova/tests/api/openstack/compute/test_servers.py @@ -212,14 +212,14 @@ class ServersControllerTest(ControllerTest): port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' requested_networks = [{'port': port}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(None, None, port)], res.as_tuples()) + self.assertEqual([(None, None, port, None)], res.as_tuples()) def test_requested_networks_neutronv2_enabled_with_network(self): self.flags(network_api_class='nova.network.neutronv2.api.API') network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' requested_networks = [{'uuid': network}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(network, None, None)], res.as_tuples()) + self.assertEqual([(network, None, None, None)], res.as_tuples()) def test_requested_networks_neutronv2_enabled_with_network_and_port(self): self.flags(network_api_class='nova.network.neutronv2.api.API') @@ -227,7 +227,7 @@ class ServersControllerTest(ControllerTest): port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' requested_networks = [{'uuid': network, 'port': port}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(None, None, port)], res.as_tuples()) + self.assertEqual([(None, None, port, None)], res.as_tuples()) def test_requested_networks_neutronv2_disabled_with_port(self): port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' @@ -243,7 +243,7 @@ class ServersControllerTest(ControllerTest): port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' requested_networks = [{'uuid': network, 'port': port}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(None, None, port)], res.as_tuples()) + self.assertEqual([(None, None, port, None)], res.as_tuples()) def test_requested_networks_neutronv2_subclass_with_port(self): cls = 'nova.tests.api.openstack.compute.test_servers.NeutronV2Subclass' @@ -251,7 +251,7 @@ class ServersControllerTest(ControllerTest): port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' requested_networks = [{'port': port}] res = self.controller._get_requested_networks(requested_networks) - self.assertEqual([(None, None, port)], res.as_tuples()) + self.assertEqual([(None, None, port, None)], res.as_tuples()) def test_get_server_by_uuid(self): req = fakes.HTTPRequest.blank('/fake/servers/%s' % FAKE_UUID) @@ -2854,7 +2854,7 @@ class ServersControllerCreateTest(test.TestCase): def create(*args, **kwargs): result = [('76fa36fc-c930-4bf3-8c8a-ea2a2420deb6', None, - None)] + None, None)] self.assertEqual(result, kwargs['requested_networks'].as_tuples()) return old_create(*args, **kwargs) diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index 01328fdb2f48..dcd06fb704af 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -263,6 +263,11 @@ def stub_out_nw_api(stubs, cls=None, private=None, publics=None): def validate_networks(self, context, networks, max_count): return max_count + def create_pci_requests_for_sriov_ports(self, context, + system_metadata, + requested_networks): + pass + if cls is None: cls = Fake stubs.Set(network_api, 'API', cls) diff --git a/nova/tests/compute/test_compute_cells.py b/nova/tests/compute/test_compute_cells.py index 2bf6c7a5c82b..fdca06c973bd 100644 --- a/nova/tests/compute/test_compute_cells.py +++ b/nova/tests/compute/test_compute_cells.py @@ -230,7 +230,7 @@ class CellsConductorAPIRPCRedirect(test.NoDBTestCase): def test_build_instances(self, _validate, _get_image, _check_bdm, _provision, _record_action_start): _get_image.return_value = (None, 'fake-image') - _validate.return_value = (None, 1) + _validate.return_value = ({}, 1) _check_bdm.return_value = 'bdms' _provision.return_value = 'instances' diff --git a/nova/tests/network/test_network_info.py b/nova/tests/network/test_network_info.py index 468640fca6da..aa5413efcbb0 100644 --- a/nova/tests/network/test_network_info.py +++ b/nova/tests/network/test_network_info.py @@ -382,6 +382,14 @@ class VIFTests(test.NoDBTestCase): vif2 = model.VIF(active=False) self.assertNotEqual(vif1, vif2) + vif1 = model.VIF(vnic_type=model.VNIC_TYPE_NORMAL) + vif2 = model.VIF(vnic_type=model.VNIC_TYPE_DIRECT) + self.assertNotEqual(vif1, vif2) + + vif1 = model.VIF(profile={'pci_slot': '0000:0a:00.1'}) + vif2 = model.VIF(profile={'pci_slot': '0000:0a:00.2'}) + self.assertNotEqual(vif1, vif2) + def test_create_vif_with_type(self): vif_dict = dict( id=1, diff --git a/nova/tests/network/test_neutronv2.py b/nova/tests/network/test_neutronv2.py index 721ed02a6722..85246f79c826 100644 --- a/nova/tests/network/test_neutronv2.py +++ b/nova/tests/network/test_neutronv2.py @@ -14,6 +14,7 @@ # under the License. # +import collections import contextlib import copy import uuid @@ -35,6 +36,8 @@ from nova.network.neutronv2 import constants from nova import objects from nova.openstack.common import jsonutils from nova.openstack.common import policy as common_policy +from nova.pci import pci_manager +from nova.pci import pci_whitelist from nova import policy from nova import test from nova.tests import fake_instance @@ -250,6 +253,7 @@ class TestNeutronv2Base(test.TestCase): 'device_id': self.instance2['uuid'], 'device_owner': 'compute:nova', 'id': 'my_portid1', + 'binding:vnic_type': model.VNIC_TYPE_NORMAL, 'status': 'DOWN', 'admin_state_up': True, 'fixed_ips': [{'ip_address': self.port_address, @@ -271,6 +275,7 @@ class TestNeutronv2Base(test.TestCase): 'status': 'ACTIVE', 'device_owner': 'compute:nova', 'id': 'my_portid2', + 'binding:vnic_type': model.VNIC_TYPE_NORMAL, 'fixed_ips': [{'ip_address': self.port_address2, 'subnet_id': 'my_subid2'}], @@ -286,6 +291,7 @@ class TestNeutronv2Base(test.TestCase): 'admin_state_up': True, 'device_owner': 'compute:nova', 'id': 'my_portid3', + 'binding:vnic_type': model.VNIC_TYPE_NORMAL, 'fixed_ips': [], # no fixed ip 'mac_address': 'my_mac3', }] self.subnet_data1 = [{'id': 'my_subid1', @@ -467,7 +473,8 @@ class TestNeutronv2Base(test.TestCase): self.instance.get('host')) if not has_portbinding: api._populate_neutron_extension_values(mox.IgnoreArg(), - self.instance, mox.IgnoreArg()).AndReturn(None) + self.instance, mox.IgnoreArg(), + mox.IgnoreArg()).AndReturn(None) else: # since _populate_neutron_extension_values() will call # _has_port_binding_extension() @@ -834,7 +841,7 @@ class TestNeutronv2(TestNeutronv2Base): instance = {'system_metadata': sys_meta} port_req_body = {'port': {}} api._populate_neutron_extension_values(self.context, instance, - port_req_body) + None, port_req_body) self.assertEqual(port_req_body['port']['rxtx_factor'], 1) def test_allocate_for_instance_1(self): @@ -1017,7 +1024,7 @@ class TestNeutronv2(TestNeutronv2Base): port = {'id': 'portid_' + network['id']} api._populate_neutron_extension_values(self.context, - self.instance, binding_port_req_body).AndReturn(None) + self.instance, None, binding_port_req_body).AndReturn(None) if index == 0: self.moxed_client.create_port( MyComparator(port_req_body)).AndReturn({'port': port}) @@ -1067,7 +1074,7 @@ class TestNeutronv2(TestNeutronv2Base): }, } api._populate_neutron_extension_values(self.context, - self.instance, binding_port_req_body).AndReturn(None) + self.instance, None, binding_port_req_body).AndReturn(None) self.moxed_client.create_port( MyComparator(port_req_body)).AndRaise( Exception("fail to create port")) @@ -1128,7 +1135,7 @@ class TestNeutronv2(TestNeutronv2Base): # NOTE(danms): Temporary and transitional with mock.patch('nova.utils.is_neutron', return_value=True): requested_networks = requested_networks.as_tuples() - for net, fip, port in requested_networks: + for net, fip, port, request_id in requested_networks: ret_data.append({'network_id': net, 'device_id': self.instance.uuid, 'device_owner': 'compute:nova', @@ -1141,7 +1148,7 @@ class TestNeutronv2(TestNeutronv2Base): device_id=self.instance.uuid).AndReturn( {'ports': ret_data}) if requested_networks: - for net, fip, port in requested_networks: + for net, fip, port, request_id in requested_networks: self.moxed_client.update_port(port) for port in reversed(port_data): self.moxed_client.delete_port(port['id']) @@ -1263,8 +1270,8 @@ class TestNeutronv2(TestNeutronv2Base): neutronapi.API().show_port(self.context, 'foo') def test_validate_networks(self): - requested_networks = [('my_netid1', None, None), - ('my_netid2', None, None)] + requested_networks = [('my_netid1', None, None, None), + ('my_netid2', None, None, None)] ids = ['my_netid1', 'my_netid2'] self.moxed_client.list_networks( id=mox.SameElementsAs(ids)).AndReturn( @@ -1279,8 +1286,8 @@ class TestNeutronv2(TestNeutronv2Base): api.validate_networks(self.context, requested_networks, 1) def test_validate_networks_without_port_quota_on_network_side(self): - requested_networks = [('my_netid1', None, None), - ('my_netid2', None, None)] + requested_networks = [('my_netid1', None, None, None), + ('my_netid2', None, None, None)] ids = ['my_netid1', 'my_netid2'] self.moxed_client.list_networks( id=mox.SameElementsAs(ids)).AndReturn( @@ -1295,7 +1302,7 @@ class TestNeutronv2(TestNeutronv2Base): api.validate_networks(self.context, requested_networks, 1) def test_validate_networks_ex_1(self): - requested_networks = [('my_netid1', None, None)] + requested_networks = [('my_netid1', None, None, None)] self.moxed_client.list_networks( id=mox.SameElementsAs(['my_netid1'])).AndReturn( {'networks': self.nets1}) @@ -1312,9 +1319,9 @@ class TestNeutronv2(TestNeutronv2Base): self.assertIn("my_netid2", six.text_type(ex)) def test_validate_networks_ex_2(self): - requested_networks = [('my_netid1', None, None), - ('my_netid2', None, None), - ('my_netid3', None, None)] + requested_networks = [('my_netid1', None, None, None), + ('my_netid2', None, None, None), + ('my_netid3', None, None, None)] ids = ['my_netid1', 'my_netid2', 'my_netid3'] self.moxed_client.list_networks( id=mox.SameElementsAs(ids)).AndReturn( @@ -1331,8 +1338,8 @@ class TestNeutronv2(TestNeutronv2Base): network ids are passed to validate_networks, when nova config flag allow_duplicate_networks is set to its default value: False """ - requested_networks = [('my_netid1', None, None), - ('my_netid1', None, None)] + requested_networks = [('my_netid1', None, None, None), + ('my_netid1', None, None, None)] self.mox.ReplayAll() # Expected call from setUp. neutronv2.get_client(None) @@ -2330,6 +2337,8 @@ class TestNeutronv2(TestNeutronv2Base): 'fixed_ips': [{'ip_address': '1.1.1.1'}], 'mac_address': 'de:ad:be:ef:00:01', 'binding:vif_type': model.VIF_TYPE_BRIDGE, + 'binding:vnic_type': model.VNIC_TYPE_NORMAL, + 'binding:vif_details': {}, }, # admin_state_up=False and status='DOWN' thus vif.active=True {'id': 'port2', @@ -2339,6 +2348,8 @@ class TestNeutronv2(TestNeutronv2Base): 'fixed_ips': [{'ip_address': '1.1.1.1'}], 'mac_address': 'de:ad:be:ef:00:02', 'binding:vif_type': model.VIF_TYPE_BRIDGE, + 'binding:vnic_type': model.VNIC_TYPE_NORMAL, + 'binding:vif_details': {}, }, # admin_state_up=True and status='DOWN' thus vif.active=False {'id': 'port0', @@ -2348,13 +2359,44 @@ class TestNeutronv2(TestNeutronv2Base): 'fixed_ips': [{'ip_address': '1.1.1.1'}], 'mac_address': 'de:ad:be:ef:00:03', 'binding:vif_type': model.VIF_TYPE_BRIDGE, + 'binding:vnic_type': model.VNIC_TYPE_NORMAL, + 'binding:vif_details': {}, + }, + # admin_state_up=True and status='ACTIVE' thus vif.active=True + {'id': 'port3', + 'network_id': 'net-id', + 'admin_state_up': True, + 'status': 'ACTIVE', + 'fixed_ips': [{'ip_address': '1.1.1.1'}], + 'mac_address': 'de:ad:be:ef:00:04', + 'binding:vif_type': model.VIF_TYPE_HW_VEB, + 'binding:vnic_type': model.VNIC_TYPE_DIRECT, + 'binding:profile': {'pci_vendor_info': '1137:0047', + 'pci_slot': '0000:0a:00.1', + 'physical_network': 'phynet1'}, + 'binding:vif_details': {model.VIF_DETAILS_PROFILEID: 'pfid'}, + }, + # admin_state_up=True and status='ACTIVE' thus vif.active=True + {'id': 'port4', + 'network_id': 'net-id', + 'admin_state_up': True, + 'status': 'ACTIVE', + 'fixed_ips': [{'ip_address': '1.1.1.1'}], + 'mac_address': 'de:ad:be:ef:00:05', + 'binding:vif_type': model.VIF_TYPE_802_QBH, + 'binding:vnic_type': model.VNIC_TYPE_MACVTAP, + 'binding:profile': {'pci_vendor_info': '1137:0047', + 'pci_slot': '0000:0a:00.2', + 'physical_network': 'phynet1'}, + 'binding:vif_details': {model.VIF_DETAILS_PROFILEID: 'pfid'}, }, # This does not match the networks we provide below, # so it should be ignored (and is here to verify that) - {'id': 'port3', + {'id': 'port5', 'network_id': 'other-net-id', 'admin_state_up': True, 'status': 'DOWN', + 'binding:vnic_type': model.VNIC_TYPE_NORMAL, }, ] fake_subnets = [model.Subnet(cidr='1.0.0.0/8')] @@ -2372,7 +2414,8 @@ class TestNeutronv2(TestNeutronv2Base): self.mox.StubOutWithMock(api, '_get_floating_ips_by_fixed_and_port') self.mox.StubOutWithMock(api, '_get_subnets_from_port') - requested_ports = [fake_ports[2], fake_ports[0], fake_ports[1]] + requested_ports = [fake_ports[2], fake_ports[0], fake_ports[1], + fake_ports[3], fake_ports[4]] for requested_port in requested_ports: api._get_floating_ips_by_fixed_and_port( self.moxed_client, '1.1.1.1', requested_port['id']).AndReturn( @@ -2387,25 +2430,39 @@ class TestNeutronv2(TestNeutronv2Base): fake_nets, [fake_ports[2]['id'], fake_ports[0]['id'], - fake_ports[1]['id']]) - self.assertEqual(len(nw_infos), 3) + fake_ports[1]['id'], + fake_ports[3]['id'], + fake_ports[4]['id']]) + self.assertEqual(len(nw_infos), 5) index = 0 for nw_info in nw_infos: self.assertEqual(nw_info['address'], requested_ports[index]['mac_address']) self.assertEqual(nw_info['devname'], 'tapport' + str(index)) self.assertIsNone(nw_info['ovs_interfaceid']) - self.assertEqual(nw_info['type'], model.VIF_TYPE_BRIDGE) - self.assertEqual(nw_info['network']['bridge'], 'brqnet-id') + self.assertEqual(nw_info['type'], + requested_ports[index]['binding:vif_type']) + if nw_info['type'] == model.VIF_TYPE_BRIDGE: + self.assertEqual(nw_info['network']['bridge'], 'brqnet-id') + self.assertEqual(nw_info['vnic_type'], + requested_ports[index]['binding:vnic_type']) + self.assertEqual(nw_info.get('details'), + requested_ports[index].get('binding:vif_details')) + self.assertEqual(nw_info.get('profile'), + requested_ports[index].get('binding:profile')) index += 1 self.assertEqual(nw_infos[0]['active'], False) self.assertEqual(nw_infos[1]['active'], True) self.assertEqual(nw_infos[2]['active'], True) + self.assertEqual(nw_infos[3]['active'], True) + self.assertEqual(nw_infos[4]['active'], True) self.assertEqual(nw_infos[0]['id'], 'port0') self.assertEqual(nw_infos[1]['id'], 'port1') self.assertEqual(nw_infos[2]['id'], 'port2') + self.assertEqual(nw_infos[3]['id'], 'port3') + self.assertEqual(nw_infos[4]['id'], 'port4') def test_get_subnets_from_port(self): api = neutronapi.API() @@ -2450,6 +2507,81 @@ class TestNeutronv2(TestNeutronv2Base): api.get_floating_ips_by_fixed_address, self.context, fake_fixed) + @mock.patch.object(neutronv2, 'get_client', return_value=mock.Mock()) + def test_get_port_vnic_info_1(self, mock_get_client): + api = neutronapi.API() + self.mox.ResetAll() + test_port = { + 'port': {'id': 'my_port_id1', + 'network_id': 'net-id', + 'binding:vnic_type': model.VNIC_TYPE_DIRECT, + }, + } + test_net = {'network': {'provider:physical_network': 'phynet1'}} + + mock_client = mock_get_client() + mock_client.show_port.return_value = test_port + mock_client.show_network.return_value = test_net + vnic_type, phynet_name = api._get_port_vnic_info( + self.context, mock_client, test_port['port']['id']) + + mock_client.show_port.assert_called_once_with(test_port['port']['id'], + fields=['binding:vnic_type', 'network_id']) + mock_client.show_network.assert_called_once_with( + test_port['port']['network_id'], + fields='provider:physical_network') + self.assertEqual(model.VNIC_TYPE_DIRECT, vnic_type) + self.assertEqual(phynet_name, 'phynet1') + + @mock.patch.object(neutronv2, 'get_client', return_value=mock.Mock()) + def test_get_port_vnic_info_2(self, mock_get_client): + api = neutronapi.API() + self.mox.ResetAll() + test_port = { + 'port': {'id': 'my_port_id2', + 'network_id': 'net-id', + 'binding:vnic_type': model.VNIC_TYPE_NORMAL, + }, + } + + mock_client = mock_get_client() + mock_client.show_port.return_value = test_port + vnic_type, phynet_name = api._get_port_vnic_info( + self.context, mock_client, test_port['port']['id']) + + mock_client.show_port.assert_called_once_with(test_port['port']['id'], + fields=['binding:vnic_type', 'network_id']) + self.assertEqual(model.VNIC_TYPE_NORMAL, vnic_type) + self.assertFalse(phynet_name) + + @mock.patch.object(neutronapi.API, "_get_port_vnic_info") + @mock.patch.object(neutronv2, 'get_client', return_value=mock.Mock()) + def test_create_pci_requests_for_sriov_ports(self, mock_get_client, + mock_get_port_vnic_info): + api = neutronapi.API() + self.mox.ResetAll() + requested_networks = objects.NetworkRequestList( + objects = [ + objects.NetworkRequest(port_id='my_portid1'), + objects.NetworkRequest(network_id='net1'), + objects.NetworkRequest(port_id='my_portid2'), + objects.NetworkRequest(port_id='my_portid3'), + objects.NetworkRequest(port_id='my_portid4')]) + pci_requests = objects.InstancePCIRequests(requests=[]) + mock_get_port_vnic_info.side_effect = [ + (model.VNIC_TYPE_DIRECT, 'phynet1'), + (model.VNIC_TYPE_NORMAL, ''), + (model.VNIC_TYPE_MACVTAP, 'phynet1'), + (model.VNIC_TYPE_MACVTAP, 'phynet2') + ] + api.create_pci_requests_for_sriov_ports( + None, pci_requests, requested_networks) + self.assertEqual(3, len(pci_requests.requests)) + has_pci_request_id = [net.pci_request_id is not None for net in + requested_networks.objects] + expected_results = [True, False, False, True, True] + self.assertEqual(expected_results, has_pci_request_id) + class TestNeutronv2WithMock(test.TestCase): """Used to test Neutron V2 API with mock.""" @@ -2495,7 +2627,7 @@ class TestNeutronv2WithMock(test.TestCase): 'subnets': ['mysubnid1'], 'tenant_id': 'fake-project'}] - requested_networks = [('my_netid1', '10.0.1.2', None)] + requested_networks = [('my_netid1', '10.0.1.2', None, None)] ids = ['my_netid1'] list_port_values = [({'network_id': 'my_netid1', 'fixed_ips': 'ip_address=10.0.1.2', @@ -2519,8 +2651,8 @@ class TestNeutronv2WithMock(test.TestCase): 'subnets': ['mysubnid2'], 'tenant_id': 'fake-project'}] - requested_networks = [('my_netid1', '10.0.1.2', None), - ('my_netid2', '10.0.1.3', None)] + requested_networks = [('my_netid1', '10.0.1.2', None, None), + ('my_netid2', '10.0.1.3', None, None)] ids = ['my_netid1', 'my_netid2'] list_port_values = [({'network_id': 'my_netid1', 'fixed_ips': 'ip_address=10.0.1.2', @@ -2541,7 +2673,7 @@ class TestNeutronv2WithMock(test.TestCase): # Test validation for a request for a network with a # fixed ip that is already in use - requested_networks = [('my_netid1', '10.0.1.2', None)] + requested_networks = [('my_netid1', '10.0.1.2', None, None)] list_port_mock_params = {'network_id': 'my_netid1', 'fixed_ips': 'ip_address=10.0.1.2', 'fields': 'device_id'} @@ -2691,8 +2823,40 @@ class TestNeutronv2Portbinding(TestNeutronv2Base): instance = {'host': host_id} port_req_body = {'port': {}} api._populate_neutron_extension_values(self.context, instance, - port_req_body) + None, port_req_body) self.assertEqual(port_req_body['port']['binding:host_id'], host_id) + self.assertFalse(port_req_body['port'].get('binding:profile')) + + @mock.patch.object(pci_whitelist, 'get_pci_device_devspec') + @mock.patch.object(pci_manager, 'get_instance_pci_devs') + def test_populate_neutron_extension_values_binding_sriov(self, + mock_get_instance_pci_devs, + mock_get_pci_device_devspec): + api = neutronapi.API() + host_id = 'my_host_id' + instance = {'host': host_id} + port_req_body = {'port': {}} + pci_req_id = 'my_req_id' + pci_dev = {'vendor_id': '1377', + 'product_id': '0047', + 'address': '0000:0a:00.1', + } + PciDevice = collections.namedtuple('PciDevice', + ['vendor_id', 'product_id', 'address']) + mydev = PciDevice(**pci_dev) + profile = {'pci_vendor_info': '1377:0047', + 'pci_slot': '0000:0a:00.1', + 'physical_network': 'phynet1', + } + + mock_get_instance_pci_devs.return_value = [mydev] + devspec = mock.Mock() + devspec.get_tags.return_value = {'physical_network': 'phynet1'} + mock_get_pci_device_devspec.return_value = devspec + api._populate_neutron_binding_profile(instance, + pci_req_id, port_req_body) + + self.assertEqual(port_req_body['port']['binding:profile'], profile) def test_migrate_instance_finish_binding_false(self): api = neutronapi.API() diff --git a/nova/tests/objects/test_network_request.py b/nova/tests/objects/test_network_request.py index 86728e1651d1..7c2736d23b04 100644 --- a/nova/tests/objects/test_network_request.py +++ b/nova/tests/objects/test_network_request.py @@ -38,7 +38,7 @@ class _TestNetworkRequestObject(object): port_id=FAKE_UUID, ) with mock.patch('nova.utils.is_neutron', return_value=True): - self.assertEqual(('123', '1.2.3.4', FAKE_UUID), + self.assertEqual(('123', '1.2.3.4', FAKE_UUID, None), request.to_tuple()) def test_to_tuple_nova(self): @@ -51,7 +51,7 @@ class _TestNetworkRequestObject(object): def test_from_tuple_neutron(self): request = objects.NetworkRequest.from_tuple( - ('123', '1.2.3.4', FAKE_UUID)) + ('123', '1.2.3.4', FAKE_UUID, None)) self.assertEqual('123', request.network_id) self.assertEqual('1.2.3.4', str(request.address)) self.assertEqual(FAKE_UUID, request.port_id) @@ -68,8 +68,9 @@ class _TestNetworkRequestObject(object): requests = objects.NetworkRequestList( objects=[objects.NetworkRequest(network_id='123'), objects.NetworkRequest(network_id='456')]) - self.assertEqual([('123', None, None), ('456', None, None)], - requests.as_tuples()) + self.assertEqual( + [('123', None, None, None), ('456', None, None, None)], + requests.as_tuples()) def test_is_single_unspecified(self): requests = objects.NetworkRequestList( diff --git a/nova/tests/pci/test_pci_manager.py b/nova/tests/pci/test_pci_manager.py index 27f9f00622db..0b60fe7cb2c9 100644 --- a/nova/tests/pci/test_pci_manager.py +++ b/nova/tests/pci/test_pci_manager.py @@ -106,6 +106,8 @@ class PciDevTrackerTestCase(test.TestCase): super(PciDevTrackerTestCase, self).setUp() self.stubs.Set(db, 'pci_device_get_all_by_node', self._fake_get_pci_devices) + # The fake_pci_whitelist must be called before creating the fake + # devices patcher = pci_fakes.fake_pci_whitelist() self.addCleanup(patcher.stop) self._create_fake_instance() diff --git a/nova/tests/scheduler/test_filter_scheduler.py b/nova/tests/scheduler/test_filter_scheduler.py index 24b93f2be433..81686b24f11c 100644 --- a/nova/tests/scheduler/test_filter_scheduler.py +++ b/nova/tests/scheduler/test_filter_scheduler.py @@ -28,7 +28,6 @@ from nova import context from nova import db from nova import exception from nova import objects -from nova.pci import pci_request from nova.scheduler import driver from nova.scheduler import filter_scheduler from nova.scheduler import host_manager @@ -676,19 +675,3 @@ class FilterSchedulerTestCase(test_scheduler.SchedulerTestCase): sched._provision_resource(fake_context, weighted_host, request_spec, filter_properties, None, None, None, None) - - def test_pci_request_in_filter_properties(self): - instance_type = {} - request_spec = {'instance_type': instance_type, - 'instance_properties': {'project_id': 1, - 'os_type': 'Linux'}} - filter_properties = {} - requests = [{'count': 1, 'spec': [{'vendor_id': '8086'}]}] - self.mox.StubOutWithMock(pci_request, 'get_pci_requests_from_flavor') - pci_request.get_pci_requests_from_flavor( - instance_type).AndReturn(requests) - self.mox.ReplayAll() - self.driver.populate_filter_properties( - request_spec, filter_properties) - self.assertEqual(filter_properties.get('pci_requests'), - requests)