Read port resource request from Neutron

This patch collects the resource requests from each neutron port
involved in a server create request. Converts each request to
a RequestGroup object.

blueprint bandwidth-resource-provider

Change-Id: I4473cb192447b5bfa9d1dfcc0dd5216c536caf73
This commit is contained in:
Balazs Gibizer 2019-01-09 16:17:59 +01:00
parent a5d6833d77
commit 9ba910bb53
7 changed files with 123 additions and 36 deletions

View File

@ -809,8 +809,9 @@ class API(base.Base):
# InstancePCIRequests object
pci_request_info = pci_request.get_pci_requests_from_flavor(
instance_type)
network_metadata = self.network_api.create_resource_requests(
result = self.network_api.create_resource_requests(
context, requested_networks, pci_request_info)
network_metadata, port_resource_requests = result
base_options = {
'reservation_id': reservation_id,

View File

@ -390,10 +390,22 @@ class API(base_api.NetworkAPI):
pci_requests=None):
"""Retrieve all information for the networks passed at the time of
creating the server.
:param context: The request context.
:param requested_networks: The networks requested for the server.
:type requested_networks: nova.objects.NetworkRequestList
:param pci_requests: The list of PCI requests to which additional PCI
requests created here will be added.
:type pci_requests: nova.objects.InstancePCIRequests
:returns: A tuple with an instance of ``objects.NetworkMetadata`` for
use by the scheduler or None and a list of RequestGroup
objects representing the resource needs of each requested
port
"""
# This is NOOP for Nova network since it doesn't support SR-IOV or
# NUMA-aware vSwitch functionality.
pass
return None, []
def get_dns_domains(self, context):
"""Returns a list of available dns domains.

View File

@ -273,19 +273,21 @@ class NetworkAPI(base.Base):
"""
raise NotImplementedError()
def create_resource_requests(self, context, requested_networks):
def create_resource_requests(self, context, requested_networks,
pci_requests=None):
"""Retrieve all information for the networks passed at the time of
creating the server.
:param context: The request context.
:param requested_networks: The networks requested for the server.
:type requested_networks: nova.objects.RequestedNetworkList
:type requested_networks: nova.objects.NetworkRequestList
:param pci_requests: The list of PCI requests to which additional PCI
requests created here will be added.
requests created here will be added.
:type pci_requests: nova.objects.InstancePCIRequests
:returns: An instance of ``objects.NetworkMetadata`` for use by the
scheduler or None.
:returns: A tuple with an instance of ``objects.NetworkMetadata`` for
use by the scheduler or None and a list of RequestGroup
objects representing the resource needs of each request ports
"""
raise NotImplementedError()

View File

@ -1876,13 +1876,13 @@ class API(base_api.NetworkAPI):
:param neutron: The Neutron client
:param port_id: The id of port to be queried
:return: A tuple of vNIC type, trusted status and network ID. Trusted
status only affects SR-IOV ports and will always be None for other
port types.
:return: A tuple of vNIC type, trusted status, network ID and resource
request of the port if any. Trusted status only affects SR-IOV
ports and will always be None for other port types.
"""
port = self._show_port(context, port_id, neutron_client=neutron,
fields=['binding:vnic_type', BINDING_PROFILE,
'network_id'])
'network_id', 'resource_request'])
network_id = port.get('network_id')
trusted = None
vnic_type = port.get('binding:vnic_type',
@ -1890,7 +1890,12 @@ class API(base_api.NetworkAPI):
if vnic_type in network_model.VNIC_TYPES_SRIOV:
trusted = self._get_trusted_mode_from_port(port)
return vnic_type, trusted, network_id
# NOTE(gibi): Get the port resource_request which may or may not be
# set depending on neutron configuration, e.g. if QoS rules are
# applied to the port/network and the resource_request API extension is
# enabled.
resource_request = port.get('resource_request', None)
return vnic_type, trusted, network_id, resource_request
def create_resource_requests(self, context, requested_networks,
pci_requests=None):
@ -1899,21 +1904,25 @@ class API(base_api.NetworkAPI):
:param context: The request context.
:param requested_networks: The networks requested for the server.
:type requested_networks: nova.objects.RequestedNetworkList
:type requested_networks: nova.objects.NetworkRequestList
:param pci_requests: The list of PCI requests to which additional PCI
requests created here will be added.
:type pci_requests: nova.objects.InstancePCIRequests
:returns: An instance of ``objects.NetworkMetadata`` for use by the
scheduler or None.
:returns: A tuple with an instance of ``objects.NetworkMetadata`` for
use by the scheduler or None and a list of RequestGroup
objects representing the resource needs of each requested
port
"""
if not requested_networks or requested_networks.no_allocate:
return None
return None, []
physnets = set()
tunneled = False
neutron = get_client(context, admin=True)
resource_requests = []
for request_net in requested_networks:
physnet = None
trusted = None
@ -1922,10 +1931,20 @@ class API(base_api.NetworkAPI):
pci_request_id = None
if request_net.port_id:
vnic_type, trusted, network_id = self._get_port_vnic_info(
result = self._get_port_vnic_info(
context, neutron, request_net.port_id)
vnic_type, trusted, network_id, resource_request = result
physnet, tunneled_ = self._get_physnet_tunneled_info(
context, neutron, network_id)
if resource_request:
# NOTE(gibi): explicitly orphan the RequestGroup as we
# never intended to save it to the DB.
resource_requests.append(
objects.RequestGroup.from_port_request(
context=None,
port_resource_request=resource_request))
elif request_net.network_id and not request_net.auto_allocate:
network_id = request_net.network_id
physnet, tunneled_ = self._get_physnet_tunneled_info(
@ -1969,7 +1988,8 @@ class API(base_api.NetworkAPI):
# Add pci_request_id into the requested network
request_net.pci_request_id = pci_request_id
return objects.NetworkMetadata(physnets=physnets, tunneled=tunneled)
return (objects.NetworkMetadata(physnets=physnets, tunneled=tunneled),
resource_requests)
def _can_auto_allocate_network(self, context, neutron):
"""Helper method to determine if we can auto-allocate networks

View File

@ -203,7 +203,7 @@ def stub_out_nw_api(test, cls=None, private=None, publics=None):
def create_resource_requests(self, context, requested_networks,
pci_requests):
pass
return None, []
if cls is None:
cls = Fake

View File

@ -219,7 +219,8 @@ class _ComputeAPIUnitTestMixIn(object):
port_id=port)])
with mock.patch.object(self.compute_api.network_api,
'create_resource_requests'):
'create_resource_requests',
return_value=(None, [])):
self.compute_api.create(self.context, instance_type, 'image_id',
requested_networks=requested_networks,
max_count=None)

View File

@ -3015,7 +3015,8 @@ class TestNeutronv2(TestNeutronv2Base):
def _test_get_port_vnic_info(self, mock_get_client,
binding_vnic_type,
expected_vnic_type):
expected_vnic_type,
port_resource_request=None):
api = neutronapi.API()
self.mox.ResetAll()
test_port = {
@ -3026,19 +3027,24 @@ class TestNeutronv2(TestNeutronv2Base):
if binding_vnic_type:
test_port['port']['binding:vnic_type'] = binding_vnic_type
if port_resource_request:
test_port['port']['resource_request'] = port_resource_request
mock_get_client.reset_mock()
mock_client = mock_get_client()
mock_client.show_port.return_value = test_port
vnic_type, trusted, network_id = api._get_port_vnic_info(
result = api._get_port_vnic_info(
self.context, mock_client, test_port['port']['id'])
vnic_type, trusted, network_id, resource_request = result
mock_client.show_port.assert_called_once_with(test_port['port']['id'],
fields=['binding:vnic_type', 'binding:profile', 'network_id'])
fields=['binding:vnic_type', 'binding:profile', 'network_id',
'resource_request'])
self.assertEqual(expected_vnic_type, vnic_type)
self.assertEqual('net-id', network_id)
self.assertIsNone(trusted)
self.assertEqual(port_resource_request, resource_request)
@mock.patch.object(neutronapi, 'get_client', return_value=mock.MagicMock())
def test_get_port_vnic_info_1(self, mock_get_client):
@ -3055,6 +3061,22 @@ class TestNeutronv2(TestNeutronv2Base):
self._test_get_port_vnic_info(mock_get_client, None,
model.VNIC_TYPE_NORMAL)
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
def test_get_port_vnic_info_requested_resources(self, mock_get_client):
self._test_get_port_vnic_info(
mock_get_client, None, model.VNIC_TYPE_NORMAL,
port_resource_request={
"resources": {
"NET_BW_EGR_KILOBIT_PER_SEC": 6000,
"NET_BW_IGR_KILOBIT_PER_SEC": 6000,
},
"required": [
"CUSTOM_PHYSNET_PHYSNET0",
"CUSTOM_VNIC_TYPE_NORMAL"
]
}
)
class TestNeutronv2WithMock(_TestNeutronv2Common):
"""Used to test Neutron V2 API with mock."""
@ -3401,14 +3423,17 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
mock_client = mock_get_client()
mock_client.show_port.return_value = test_port
mock_client.list_extensions.return_value = test_ext_list
vnic_type, trusted, network_id = self.api._get_port_vnic_info(
result = self.api._get_port_vnic_info(
self.context, mock_client, test_port['port']['id'])
vnic_type, trusted, network_id, resource_requests = result
mock_client.show_port.assert_called_once_with(test_port['port']['id'],
fields=['binding:vnic_type', 'binding:profile', 'network_id'])
fields=['binding:vnic_type', 'binding:profile', 'network_id',
'resource_request'])
self.assertEqual(model.VNIC_TYPE_DIRECT, vnic_type)
self.assertEqual('net-id', network_id)
self.assertTrue(trusted)
self.assertIsNone(resource_requests)
@mock.patch('nova.network.neutronv2.api.API._show_port')
def test_deferred_ip_port_immediate_allocation(self, mock_show):
@ -5154,11 +5179,13 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
pci_requests = objects.InstancePCIRequests()
api = neutronapi.API()
network_metadata = api.create_resource_requests(
result = api.create_resource_requests(
self.context, requested_networks, pci_requests)
network_metadata, port_resource_requests = result
self.assertFalse(mock_get_client.called)
self.assertIsNone(network_metadata)
self.assertEqual([], port_resource_requests)
@mock.patch.object(neutronapi.API, '_get_physnet_tunneled_info')
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
@ -5174,18 +5201,22 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
pci_requests = objects.InstancePCIRequests()
api = neutronapi.API()
network_metadata = api.create_resource_requests(
result = api.create_resource_requests(
self.context, requested_networks, pci_requests)
network_metadata, port_resource_requests = result
mock_get_physnet_tunneled_info.assert_not_called()
self.assertEqual(set(), network_metadata.physnets)
self.assertFalse(network_metadata.tunneled)
self.assertEqual([], port_resource_requests)
@mock.patch('nova.objects.request_spec.RequestGroup.from_port_request')
@mock.patch.object(neutronapi.API, '_get_physnet_tunneled_info')
@mock.patch.object(neutronapi.API, "_get_port_vnic_info")
@mock.patch.object(neutronapi, 'get_client')
def test_create_resource_requests(self, getclient,
mock_get_port_vnic_info, mock_get_physnet_tunneled_info):
mock_get_port_vnic_info, mock_get_physnet_tunneled_info,
mock_request_spec):
requested_networks = objects.NetworkRequestList(
objects = [
objects.NetworkRequest(port_id=uuids.portid_1),
@ -5199,12 +5230,14 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
# _get_port_vnic_info should be called for every NetworkRequest with a
# port_id attribute (so six times)
mock_get_port_vnic_info.side_effect = [
(model.VNIC_TYPE_DIRECT, None, 'netN'),
(model.VNIC_TYPE_NORMAL, None, 'netN'),
(model.VNIC_TYPE_MACVTAP, None, 'netN'),
(model.VNIC_TYPE_MACVTAP, None, 'netN'),
(model.VNIC_TYPE_DIRECT_PHYSICAL, None, 'netN'),
(model.VNIC_TYPE_DIRECT, True, 'netN'),
(model.VNIC_TYPE_DIRECT, None, 'netN', None),
(model.VNIC_TYPE_NORMAL, None, 'netN',
mock.sentinel.resource_request1),
(model.VNIC_TYPE_MACVTAP, None, 'netN', None),
(model.VNIC_TYPE_MACVTAP, None, 'netN', None),
(model.VNIC_TYPE_DIRECT_PHYSICAL, None, 'netN', None),
(model.VNIC_TYPE_DIRECT, True, 'netN',
mock.sentinel.resource_request2),
]
# _get_physnet_tunneled_info should be called for every NetworkRequest
# (so seven times)
@ -5215,9 +5248,19 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
]
api = neutronapi.API()
network_metadata = api.create_resource_requests(
self.context, requested_networks, pci_requests)
mock_request_spec.side_effect = [
mock.sentinel.request_group1,
mock.sentinel.request_group2,
]
result = api.create_resource_requests(
self.context, requested_networks, pci_requests)
network_metadata, port_resource_requests = result
self.assertEqual([
mock.sentinel.request_group1,
mock.sentinel.request_group2],
port_resource_requests)
self.assertEqual(5, len(pci_requests.requests))
has_pci_request_id = [net.pci_request_id is not None for net in
requested_networks.objects]
@ -5239,6 +5282,14 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
['physnet1', 'physnet2', 'physnet3', 'physnet4'],
network_metadata.physnets)
self.assertTrue(network_metadata.tunneled)
mock_request_spec.assert_has_calls([
mock.call(
context=None,
port_resource_request=mock.sentinel.resource_request1),
mock.call(
context=None,
port_resource_request=mock.sentinel.resource_request2),
])
@mock.patch.object(neutronapi, 'get_client')
def test_associate_floating_ip_conflict(self, mock_get_client):