network: Always retrieve network information if available

We're going to need to consume information about the physnet and tunnel
status of networks. While we don't currently retrieve the latter, we do
retrieve the former but only for SR-IOV ports. Modify this so physnet
information is retrieved for any non-auto and non-null network requests.
This is stored in a new object, 'NetworkMetadata', which defines fields
for both physnets and tunneled networks. The latter attribute of this
object is unset for now, but this is not an issue as the value returned
from this function is not yet used.

Part of blueprint numa-aware-vswitches

Change-Id: I3dde6074d69e299f2844675ef968c8f949722395
This commit is contained in:
Stephen Finucane 2018-06-26 10:55:55 +01:00
parent ee7c39e441
commit de72526791
7 changed files with 124 additions and 13 deletions

View File

@ -280,6 +280,9 @@ class NetworkAPI(base.Base):
: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.
"""
raise NotImplementedError()

View File

@ -1618,26 +1618,43 @@ 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.RequestedNetworkList
: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.
"""
if not requested_networks or requested_networks.no_allocate:
return
return None
physnets = set()
tunneled = False
neutron = get_client(context, admin=True)
for request_net in requested_networks:
physnet = None
trusted = None
vnic_type = network_model.VNIC_TYPE_NORMAL
pci_request_id = None
if request_net.port_id:
vnic_type, trusted, network_id = self._get_port_vnic_info(
context, neutron, request_net.port_id)
physnet = self._get_physnet_info(
context, neutron, network_id)
LOG.debug("Creating PCI device request for port_id=%s, "
"vnic_type=%s, phynet_name=%s, trusted=%s",
request_net.port_id, vnic_type, physnet,
trusted)
pci_request_id = None
elif request_net.network_id and not request_net.auto_allocate:
network_id = request_net.network_id
physnet = self._get_physnet_info(
context, neutron, network_id)
if physnet:
physnets.add(physnet)
if vnic_type in network_model.VNIC_TYPES_SRIOV:
# TODO(moshele): To differentiate between the SR-IOV legacy
# and SR-IOV ovs hardware offload we will leverage the nic
@ -1665,6 +1682,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)
def _can_auto_allocate_network(self, context, neutron):
"""Helper method to determine if we can auto-allocate networks

View File

@ -57,6 +57,7 @@ def register_all():
__import__('nova.objects.migration_context')
__import__('nova.objects.monitor_metric')
__import__('nova.objects.network')
__import__('nova.objects.network_metadata')
__import__('nova.objects.network_request')
__import__('nova.objects.numa')
__import__('nova.objects.pci_device')

View File

@ -77,6 +77,7 @@ PCIAddressField = fields.PCIAddressField
Enum = fields.Enum
Field = fields.Field
FieldType = fields.FieldType
String = fields.String
Set = fields.Set
Dict = fields.Dict
List = fields.List
@ -89,6 +90,10 @@ IPV4Network = fields.IPV4Network
IPV6Network = fields.IPV6Network
class SetOfStringsField(AutoTypedField):
AUTO_TYPE = Set(String())
class BaseNovaEnum(Enum):
def __init__(self, **kwargs):
super(BaseNovaEnum, self).__init__(valid_values=self.__class__.ALL)

View File

@ -0,0 +1,46 @@
# Copyright 2018 Red Hat, Inc.
#
# 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.
from nova.objects import base
from nova.objects import fields
@base.NovaObjectRegistry.register
class NetworkMetadata(base.NovaObject):
"""Hold aggregate metadata for a collection on networks.
This object holds aggregate information for a collection of neutron
networks. There are two types of network collections we care about and use
this for: the collection of networks configured or requested for a guest
and the collection of networks available to a host. We want this
information to allow us to map a given neutron network to the logical NICs
it does or will use (or, rather, to identify the NUMA affinity of those
NICs and therefore the networks). Given that there are potentially tens of
thousands of neutron networks accessible from a given host and tens or
hundreds of networks configured for an instance, we need a way to group
networks by some common attribute that would identify the logical NIC it
would use. For L2 networks, this is the physnet attribute (e.g.
``provider:physical_network=provider1``), which is an arbitrary string used
to distinguish between multiple physical (in the sense of physical wiring)
networks. For L3 (tunneled) networks, this is merely the fact that they are
L3 networks (e.g. ``provider:network_type=vxlan``) because, in neutron,
*all* L3 networks must use the same logical NIC.
"""
# Version 1.0: Initial version
VERSION = '1.0'
fields = {
'physnets': fields.SetOfStringsField(),
'tunneled': fields.BooleanField(),
}

View File

@ -4925,18 +4925,42 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
self.assertTrue(mock_log.called)
@mock.patch.object(neutronapi, 'get_client')
def test_create_resource_requests_no_allocate(self, getclient):
"""Tests that create_resource_requests is a noop if
networks are specifically requested to not be allocated.
def test_create_resource_requests_no_allocate(self, mock_get_client):
"""Ensure physnet info is not retrieved when networks are not to be
allocated.
"""
requested_networks = objects.NetworkRequestList(objects=[
objects.NetworkRequest(network_id=net_req_obj.NETWORK_ID_NONE)
])
pci_requests = objects.InstancePCIRequests()
api = neutronapi.API()
api.create_resource_requests(
network_metadata = api.create_resource_requests(
self.context, requested_networks, pci_requests)
self.assertFalse(getclient.called)
self.assertFalse(mock_get_client.called)
self.assertIsNone(network_metadata)
@mock.patch.object(neutronapi.API, '_get_physnet_info')
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
def test_create_resource_requests_auto_allocated(self, mock_get_client,
mock_get_physnet_info):
"""Ensure physnet info is not retrieved for auto-allocated networks.
This isn't possible so we shouldn't attempt to do it.
"""
requested_networks = objects.NetworkRequestList(objects=[
objects.NetworkRequest(network_id=net_req_obj.NETWORK_ID_AUTO)
])
pci_requests = objects.InstancePCIRequests()
api = neutronapi.API()
network_metadata = api.create_resource_requests(
self.context, requested_networks, pci_requests)
mock_get_physnet_info.assert_not_called()
self.assertEqual(set(), network_metadata.physnets)
self.assertFalse(network_metadata.tunneled)
@mock.patch.object(neutronapi.API, '_get_physnet_info')
@mock.patch.object(neutronapi.API, "_get_port_vnic_info")
@ -4953,6 +4977,8 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
objects.NetworkRequest(port_id=uuids.portid_5),
objects.NetworkRequest(port_id=uuids.trusted_port)])
pci_requests = objects.InstancePCIRequests(requests=[])
# _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'),
@ -4961,12 +4987,15 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
(model.VNIC_TYPE_DIRECT_PHYSICAL, None, 'netN'),
(model.VNIC_TYPE_DIRECT, True, 'netN'),
]
# _get_physnet_info should be called for every NetworkRequest (so seven
# times)
mock_get_physnet_info.side_effect = [
'physnet1', '', 'physnet1', 'physnet2', 'physnet3', 'physnet4',
'physnet1', 'physnet1', '', 'physnet1', 'physnet2', 'physnet3',
'physnet4',
]
api = neutronapi.API()
api.create_resource_requests(
network_metadata = api.create_resource_requests(
self.context, requested_networks, pci_requests)
self.assertEqual(5, len(pci_requests.requests))
@ -4986,6 +5015,13 @@ class TestNeutronv2WithMock(_TestNeutronv2Common):
else:
self.assertNotIn(pci_request.PCI_TRUSTED_TAG, spec)
self.assertItemsEqual(
['physnet1', 'physnet2', 'physnet3', 'physnet4'],
network_metadata.physnets)
# TODO(stephenfin): Expand this to be a positive check when we start
# retrieving this information
self.assertFalse(network_metadata.tunneled)
@mock.patch.object(neutronapi, 'get_client')
def test_associate_floating_ip_conflict(self, mock_get_client):
"""Tests that if Neutron raises a Conflict we handle it and re-raise

View File

@ -1131,6 +1131,7 @@ object_data = {
'Network': '1.2-a977ab383aa462a479b2fae8211a5dde',
'NetworkInterfaceMetadata': '1.2-6f3d480b40fe339067b1c0dd4d656716',
'NetworkList': '1.2-69eca910d8fa035dfecd8ba10877ee59',
'NetworkMetadata': '1.0-2cb8d21b34f87b0261d3e1d1ae5cf218',
'NetworkRequest': '1.2-af1ff2d986999fbb79377712794d82aa',
'NetworkRequestList': '1.1-15ecf022a68ddbb8c2a6739cfc9f8f5e',
'PciDevice': '1.6-2a2612baaa1786679e52084e82ca7e66',