diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index 6ae0e4f6232a..7148e22ede37 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -1018,6 +1018,10 @@ class API(base_api.NetworkAPI): self._refresh_neutron_extensions_cache(context, neutron=neutron) 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): dev_spec = self.pci_whitelist.get_devspec(pci_dev) if dev_spec: @@ -1401,6 +1405,37 @@ class API(base_api.NetworkAPI): raise exception.FixedIpNotFoundForSpecificInstance( 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): """Retrieve port vnic info @@ -1414,9 +1449,7 @@ class API(base_api.NetworkAPI): network_model.VNIC_TYPE_NORMAL) if vnic_type in network_model.VNIC_TYPES_SRIOV: net_id = port['network_id'] - net = neutron.show_network(net_id, - fields='provider:physical_network').get('network') - phynet_name = net.get('provider:physical_network') + phynet_name = self._get_phynet_info(context, neutron, net_id) return vnic_type, phynet_name def create_pci_requests_for_sriov_ports(self, context, pci_requests, diff --git a/nova/network/neutronv2/constants.py b/nova/network/neutronv2/constants.py index a59de3de7fba..200b6233f1bc 100644 --- a/nova/network/neutronv2/constants.py +++ b/nova/network/neutronv2/constants.py @@ -19,3 +19,4 @@ PORTBINDING_EXT = 'Port Binding' VNIC_INDEX_EXT = 'VNIC Index' DNS_INTEGRATION = 'DNS Integration' AUTO_ALLOCATE_TOPO_EXT = 'Auto Allocated Topology Services' +MULTI_NET_EXT = 'Multi Provider Network' diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 9bd37d36fd7f..f4b303865cc1 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -3138,6 +3138,98 @@ class TestNeutronv2(TestNeutronv2Base): self.assertEqual(0, len(networks)) @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): api = neutronapi.API() self.mox.ResetAll() diff --git a/releasenotes/notes/retrieve_physical_network_from_multi-segment-eec5a490c1ed8739.yaml b/releasenotes/notes/retrieve_physical_network_from_multi-segment-eec5a490c1ed8739.yaml new file mode 100644 index 000000000000..69a6cf2ca894 --- /dev/null +++ b/releasenotes/notes/retrieve_physical_network_from_multi-segment-eec5a490c1ed8739.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - Physical network name will be retrieved from a multi-segment network. + The current implementation will retrieve the physical network name for the + first segment that provides it. This is mostly intended to support a + combination of vxlan and vlan segments. Additional work will be required to + support a case of multiple vlan segments associated with different + physical networks.