neutron: retrieve physical network name from a multi-provider network

Multi-provider networks are a combination of vxlan and vlan networks.
such a network has a special "segments" field that holds a list
of networks. Each element of this list has the same structure as the 'provider
network attributes'.
These attributes are:
- provider:network_type
- provider:physical_network
- provider:segmentation_id

This patch will retrieve the relevant physical network name from a
multi-provider network

Change-Id: Icb9a546e6fcbf399fcef3e9ea686b35ec3817cd5
Closes-Bug: #1659467
This commit is contained in:
Vladik Romanovsky 2017-02-06 22:17:31 -05:00
parent 35eaa27a04
commit b9d9d96a40
4 changed files with 137 additions and 3 deletions

View File

@ -1019,6 +1019,10 @@ class API(base_api.NetworkAPI):
self._refresh_neutron_extensions_cache(context, neutron=neutron) self._refresh_neutron_extensions_cache(context, neutron=neutron)
return constants.AUTO_ALLOCATE_TOPO_EXT in self.extensions return constants.AUTO_ALLOCATE_TOPO_EXT in self.extensions
def _has_multi_provider_extension(self, context, neutron=None):
self._refresh_neutron_extensions_cache(context, neutron=neutron)
return constants.MULTI_NET_EXT in self.extensions
def _get_pci_device_profile(self, pci_dev): def _get_pci_device_profile(self, pci_dev):
dev_spec = self.pci_whitelist.get_devspec(pci_dev) dev_spec = self.pci_whitelist.get_devspec(pci_dev)
if dev_spec: if dev_spec:
@ -1402,6 +1406,37 @@ class API(base_api.NetworkAPI):
raise exception.FixedIpNotFoundForSpecificInstance( raise exception.FixedIpNotFoundForSpecificInstance(
instance_uuid=instance.uuid, ip=address) instance_uuid=instance.uuid, ip=address)
def _get_phynet_info(self, context, neutron, net_id):
phynet_name = None
if self._has_multi_provider_extension(context, neutron=neutron):
network = neutron.show_network(net_id,
fields='segments').get('network')
segments = network.get('segments', {})
for net in segments:
# NOTE(vladikr): In general, "multi-segments" network is a
# combination of L2 segments. The current implementation
# contains a vxlan and vlan(s) segments, where only a vlan
# network will have a physical_network specified, but may
# change in the future. The purpose of this method
# is to find a first segment that provides a physical network.
# TODO(vladikr): Additional work will be required to handle the
# case of multiple vlan segments associated with different
# physical networks.
phynet_name = net.get('provider:physical_network')
if phynet_name:
return phynet_name
# Raising here as at least one segment should
# have a physical network provided.
if segments:
msg = (_("None of the segments of network %s provides a "
"physical_network") % net_id)
raise exception.NovaException(message=msg)
net = neutron.show_network(net_id,
fields='provider:physical_network').get('network')
phynet_name = net.get('provider:physical_network')
return phynet_name
def _get_port_vnic_info(self, context, neutron, port_id): def _get_port_vnic_info(self, context, neutron, port_id):
"""Retrieve port vnic info """Retrieve port vnic info
@ -1415,9 +1450,7 @@ class API(base_api.NetworkAPI):
network_model.VNIC_TYPE_NORMAL) network_model.VNIC_TYPE_NORMAL)
if vnic_type in network_model.VNIC_TYPES_SRIOV: if vnic_type in network_model.VNIC_TYPES_SRIOV:
net_id = port['network_id'] net_id = port['network_id']
net = neutron.show_network(net_id, phynet_name = self._get_phynet_info(context, neutron, net_id)
fields='provider:physical_network').get('network')
phynet_name = net.get('provider:physical_network')
return vnic_type, phynet_name return vnic_type, phynet_name
def create_pci_requests_for_sriov_ports(self, context, pci_requests, def create_pci_requests_for_sriov_ports(self, context, pci_requests,

View File

@ -19,3 +19,4 @@ PORTBINDING_EXT = 'Port Binding'
VNIC_INDEX_EXT = 'VNIC Index' VNIC_INDEX_EXT = 'VNIC Index'
DNS_INTEGRATION = 'DNS Integration' DNS_INTEGRATION = 'DNS Integration'
AUTO_ALLOCATE_TOPO_EXT = 'Auto Allocated Topology Services' AUTO_ALLOCATE_TOPO_EXT = 'Auto Allocated Topology Services'
MULTI_NET_EXT = 'Multi Provider Network'

View File

@ -3139,6 +3139,98 @@ class TestNeutronv2(TestNeutronv2Base):
self.assertEqual(0, len(networks)) self.assertEqual(0, len(networks))
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock()) @mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
def test_get_port_vnic_info_multi_segment(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': {'segments':
[{'provider:physical_network': 'phynet10',
'provider:segmentation_id': 1000,
'provider:network_type': 'vlan'},
{'provider:physical_network': None,
'provider:segmentation_id': 153,
'provider:network_type': 'vxlan'}]}}
test_ext_list = {'extensions':
[{'name': 'Multi Provider Network',
'alias': 'multi-segments'}]}
mock_client = mock_get_client()
mock_client.show_port.return_value = test_port
mock_client.list_extensions.return_value = test_ext_list
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_network.assert_called_once_with(
test_port['port']['network_id'],
fields='segments')
self.assertEqual('phynet10', phynet_name)
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
def test_get_port_vnic_info_vlan_with_multi_segment_ext(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': 'phynet10',
'provider:segmentation_id': 1000,
'provider:network_type': 'vlan'}}
test_ext_list = {'extensions':
[{'name': 'Multi Provider Network',
'alias': 'multi-segments'}]}
mock_client = mock_get_client()
mock_client.show_port.return_value = test_port
mock_client.list_extensions.return_value = test_ext_list
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_network.assert_called_with(
test_port['port']['network_id'],
fields='provider:physical_network')
self.assertEqual('phynet10', phynet_name)
@mock.patch.object(neutronapi, 'get_client', return_value=mock.Mock())
def test_get_port_vnic_info_multi_segment_no_phynet(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': {'segments':
[{'provider:physical_network': None,
'provider:segmentation_id': 1000,
'provider:network_type': 'vlan'},
{'provider:physical_network': None,
'provider:segmentation_id': 153,
'provider:network_type': 'vlan'}]}}
test_ext_list = {'extensions':
[{'name': 'Multi Provider Network',
'alias': 'multi-segments'}]}
mock_client = mock_get_client()
mock_client.show_port.return_value = test_port
mock_client.list_extensions.return_value = test_ext_list
mock_client.show_network.return_value = test_net
self.assertRaises(exception.NovaException,
api._get_port_vnic_info,
self.context, mock_client, test_port['port']['id'])
@mock.patch.object(neutronapi, 'get_client', return_value=mock.MagicMock())
def test_get_port_vnic_info_1(self, mock_get_client): def test_get_port_vnic_info_1(self, mock_get_client):
api = neutronapi.API() api = neutronapi.API()
self.mox.ResetAll() self.mox.ResetAll()

View File

@ -0,0 +1,8 @@
---
fixes:
- Physical network name will be retrieved from a multi-segement network.
The current implementation will retrieve the physical network name for the
first segment that provides it. This is mostly intended to support a
combinatin of vxlan and vlan segments. Additional work will be required to
support a case of multiple vlan segments associated with different
physical networks.