Handle neutron without the fip-port-details extension
The 'fip-port-details' API extension was added to neutron in Rocky [1] and is optional. As a result, we cannot rely on the 'port_details' field being present in API responses. If it is not, we need to make a second query for all ports and build 'port_details' using the 'port_id' field. [1] https://docs.openstack.org/releasenotes/neutron-lib/rocky.html#relnotes-1-14-0-stable-rocky-new-features Change-Id: Ifb96f31f471cc0a25c1dfce2161a669b97a384ae Signed-off-by: Stephen Finucane <sfinucan@redhat.com> Closes-bug: #1861876
This commit is contained in:
parent
7601efa5e3
commit
eef658bf53
nova
network
tests
@ -18,6 +18,7 @@ NET_EXTERNAL = 'router:external'
|
||||
VNIC_INDEX_EXT = 'VNIC Index'
|
||||
DNS_INTEGRATION = 'DNS Integration'
|
||||
MULTI_NET_EXT = 'Multi Provider Network'
|
||||
FIP_PORT_DETAILS = 'Floating IP Port Details Extension'
|
||||
SUBSTR_PORT_FILTERING = 'IP address substring filtering'
|
||||
PORT_BINDING_EXTENDED = 'Port Bindings Extended'
|
||||
LIVE_MIGRATION = 'live-migration'
|
||||
|
@ -1265,6 +1265,10 @@ class API(base.Base):
|
||||
self._refresh_neutron_extensions_cache(context, neutron=neutron)
|
||||
return constants.QOS_QUEUE in self.extensions
|
||||
|
||||
def _has_fip_port_details_extension(self, context, neutron=None):
|
||||
self._refresh_neutron_extensions_cache(context, neutron=neutron)
|
||||
return constants.FIP_PORT_DETAILS in self.extensions
|
||||
|
||||
def has_substr_port_filtering_extension(self, context):
|
||||
self._refresh_neutron_extensions_cache(context)
|
||||
return constants.SUBSTR_PORT_FILTERING in self.extensions
|
||||
@ -2599,13 +2603,18 @@ class API(base.Base):
|
||||
except neutron_client_exc.NetworkNotFoundClient:
|
||||
raise exception.NetworkNotFound(network_id=network_uuid)
|
||||
|
||||
return fip
|
||||
# ...and retrieve the port details for the same reason, but only if
|
||||
# they're not already there because the fip-port-details extension is
|
||||
# present
|
||||
if not self._has_fip_port_details_extension(context, client):
|
||||
port_id = fip['port_id']
|
||||
try:
|
||||
fip['port_details'] = client.show_port(
|
||||
port_id)['port']
|
||||
except neutron_client_exc.PortNotFoundClient:
|
||||
raise exception.PortNotFound(port_id=port_id)
|
||||
|
||||
def get_floating_ip_pools(self, context):
|
||||
"""Return floating IP pools a.k.a. external networks."""
|
||||
client = get_client(context)
|
||||
data = client.list_networks(**{constants.NET_EXTERNAL: True})
|
||||
return data['networks']
|
||||
return fip
|
||||
|
||||
def get_floating_ip_by_address(self, context, address):
|
||||
"""Return a floating IP given an address."""
|
||||
@ -2621,12 +2630,31 @@ class API(base.Base):
|
||||
except neutron_client_exc.NetworkNotFoundClient:
|
||||
raise exception.NetworkNotFound(network_id=network_uuid)
|
||||
|
||||
# ...and retrieve the port details for the same reason, but only if
|
||||
# they're not already there because the fip-port-details extension is
|
||||
# present
|
||||
if not self._has_fip_port_details_extension(context, client):
|
||||
port_id = fip['port_id']
|
||||
try:
|
||||
fip['port_details'] = client.show_port(
|
||||
port_id)['port']
|
||||
except neutron_client_exc.PortNotFoundClient:
|
||||
raise exception.PortNotFound(port_id=port_id)
|
||||
|
||||
return fip
|
||||
|
||||
def get_floating_ip_pools(self, context):
|
||||
"""Return floating IP pools a.k.a. external networks."""
|
||||
client = get_client(context)
|
||||
data = client.list_networks(**{constants.NET_EXTERNAL: True})
|
||||
return data['networks']
|
||||
|
||||
def get_floating_ips_by_project(self, context):
|
||||
client = get_client(context)
|
||||
project_id = context.project_id
|
||||
fips = self._safe_get_floating_ips(client, tenant_id=project_id)
|
||||
if not fips:
|
||||
return fips
|
||||
|
||||
# retrieve and cache the network details now since many callers need
|
||||
# the network name which isn't present in the response from neutron
|
||||
@ -2643,6 +2671,19 @@ class API(base.Base):
|
||||
|
||||
fip['network_details'] = networks[network_uuid]
|
||||
|
||||
# ...and retrieve the port details for the same reason, but only if
|
||||
# they're not already there because the fip-port-details extension is
|
||||
# present
|
||||
if not self._has_fip_port_details_extension(context, client):
|
||||
ports = {port['id']: port for port in client.list_ports(
|
||||
**{'tenant_id': project_id})['ports']}
|
||||
for fip in fips:
|
||||
port_id = fip['port_id']
|
||||
if port_id not in ports:
|
||||
raise exception.PortNotFound(port_id=port_id)
|
||||
|
||||
fip['port_details'] = ports[port_id]
|
||||
|
||||
return fips
|
||||
|
||||
def get_instance_id_by_floating_address(self, context, address):
|
||||
|
@ -15,6 +15,7 @@
|
||||
import copy
|
||||
|
||||
import nova.conf
|
||||
from nova.network import constants
|
||||
from nova.tests import fixtures
|
||||
from nova.tests.functional.api_sample_tests import api_sample_base
|
||||
|
||||
@ -160,6 +161,21 @@ class NeutronFixture(fixtures.NeutronFixture):
|
||||
def list_floatingips(self, retrieve_all=True, **_params):
|
||||
return {'floatingips': copy.deepcopy(list(self._floatingips.values()))}
|
||||
|
||||
def list_extensions(self, *args, **kwargs):
|
||||
extensions = super().list_extensions(*args, **kwargs)
|
||||
extensions['extensions'].append(
|
||||
{
|
||||
# Copied from neutron-lib fip_port_details.py
|
||||
'updated': '2018-04-09T10:00:00-00:00',
|
||||
'name': constants.FIP_PORT_DETAILS,
|
||||
'links': [],
|
||||
'alias': 'fip-port-details',
|
||||
'description': 'Add port_details attribute to Floating IP '
|
||||
'resource',
|
||||
},
|
||||
)
|
||||
return extensions
|
||||
|
||||
|
||||
class FloatingIpsTest(api_sample_base.ApiSampleTestBaseV21):
|
||||
sample_dir = "os-floating-ips"
|
||||
|
@ -2345,6 +2345,51 @@ class TestAPI(TestAPIBase):
|
||||
mock_get_client.assert_called_once_with(self.context)
|
||||
mocked_client.show_floatingip.assert_called_once_with(floating_ip_id)
|
||||
|
||||
@mock.patch.object(neutronapi.API, '_refresh_neutron_extensions_cache')
|
||||
@mock.patch.object(neutronapi, 'get_client')
|
||||
def _test_get_floating_ip(
|
||||
self, fip_ext_enabled, mock_ntrn, mock_refresh):
|
||||
mock_nc = mock.Mock()
|
||||
mock_ntrn.return_value = mock_nc
|
||||
# NOTE(stephenfin): These are clearly not full responses
|
||||
mock_nc.show_floatingip.return_value = {
|
||||
'floatingip': {
|
||||
'id': uuids.fip_id,
|
||||
'floating_network_id': uuids.fip_net_id,
|
||||
'port_id': uuids.fip_port_id,
|
||||
}
|
||||
}
|
||||
mock_nc.show_network.return_value = {
|
||||
'network': {
|
||||
'id': uuids.fip_net_id,
|
||||
},
|
||||
}
|
||||
mock_nc.show_port.return_value = {
|
||||
'port': {
|
||||
'id': uuids.fip_port_id,
|
||||
},
|
||||
}
|
||||
|
||||
if fip_ext_enabled:
|
||||
self.api.extensions = [constants.FIP_PORT_DETAILS]
|
||||
else:
|
||||
self.api.extensions = []
|
||||
|
||||
fip = self.api.get_floating_ip(self.context, uuids.fip_id)
|
||||
|
||||
if fip_ext_enabled:
|
||||
mock_nc.show_port.assert_not_called()
|
||||
self.assertNotIn('port_details', fip)
|
||||
else:
|
||||
mock_nc.show_port.assert_called_once_with(uuids.fip_port_id)
|
||||
self.assertIn('port_details', fip)
|
||||
|
||||
def test_get_floating_ip_with_fip_port_details_ext(self):
|
||||
self._test_get_floating_ip(True)
|
||||
|
||||
def test_get_floating_ip_without_fip_port_details_ext(self):
|
||||
self._test_get_floating_ip(False)
|
||||
|
||||
@mock.patch.object(neutronapi, 'get_client')
|
||||
def test_get_floating_ip_by_address_multiple_found(self, mock_get_client):
|
||||
mocked_client = mock.create_autospec(client.Client)
|
||||
@ -2359,6 +2404,53 @@ class TestAPI(TestAPIBase):
|
||||
mocked_client.list_floatingips.assert_called_once_with(
|
||||
floating_ip_address=address)
|
||||
|
||||
@mock.patch.object(neutronapi.API, '_refresh_neutron_extensions_cache')
|
||||
@mock.patch.object(neutronapi, 'get_client')
|
||||
def _test_get_floating_ip_by_address(
|
||||
self, fip_ext_enabled, mock_ntrn, mock_refresh):
|
||||
mock_nc = mock.Mock()
|
||||
mock_ntrn.return_value = mock_nc
|
||||
# NOTE(stephenfin): These are clearly not full responses
|
||||
mock_nc.list_floatingips.return_value = {
|
||||
'floatingips': [
|
||||
{
|
||||
'id': uuids.fip_id,
|
||||
'floating_network_id': uuids.fip_net_id,
|
||||
'port_id': uuids.fip_port_id,
|
||||
},
|
||||
]
|
||||
}
|
||||
mock_nc.show_network.return_value = {
|
||||
'network': {
|
||||
'id': uuids.fip_net_id,
|
||||
},
|
||||
}
|
||||
mock_nc.show_port.return_value = {
|
||||
'port': {
|
||||
'id': uuids.fip_port_id,
|
||||
},
|
||||
}
|
||||
|
||||
if fip_ext_enabled:
|
||||
self.api.extensions = [constants.FIP_PORT_DETAILS]
|
||||
else:
|
||||
self.api.extensions = []
|
||||
|
||||
fip = self.api.get_floating_ip_by_address(self.context, '172.1.2.3')
|
||||
|
||||
if fip_ext_enabled:
|
||||
mock_nc.show_port.assert_not_called()
|
||||
self.assertNotIn('port_details', fip)
|
||||
else:
|
||||
mock_nc.show_port.assert_called_once_with(uuids.fip_port_id)
|
||||
self.assertIn('port_details', fip)
|
||||
|
||||
def test_get_floating_ip_by_address_with_fip_port_details_ext(self):
|
||||
self._test_get_floating_ip_by_address(True)
|
||||
|
||||
def test_get_floating_ip_by_address_without_fip_port_details_ext(self):
|
||||
self._test_get_floating_ip_by_address(False)
|
||||
|
||||
@mock.patch.object(neutronapi, 'get_client')
|
||||
def _test_get_instance_id_by_floating_address(self, fip_data,
|
||||
mock_get_client,
|
||||
@ -5304,6 +5396,58 @@ class TestAPI(TestAPIBase):
|
||||
self.api.get_floating_ips_by_project,
|
||||
self.context)
|
||||
|
||||
@mock.patch.object(neutronapi.API, '_refresh_neutron_extensions_cache')
|
||||
@mock.patch.object(neutronapi, 'get_client')
|
||||
def _test_get_floating_ips_by_project(
|
||||
self, fip_ext_enabled, mock_ntrn, mock_refresh):
|
||||
mock_nc = mock.Mock()
|
||||
mock_ntrn.return_value = mock_nc
|
||||
# NOTE(stephenfin): These are clearly not full responses
|
||||
mock_nc.list_floatingips.return_value = {
|
||||
'floatingips': [
|
||||
{
|
||||
'id': uuids.fip_id,
|
||||
'floating_network_id': uuids.fip_net_id,
|
||||
'port_id': uuids.fip_port_id,
|
||||
}
|
||||
]
|
||||
}
|
||||
mock_nc.show_network.return_value = {
|
||||
'network': {
|
||||
'id': uuids.fip_net_id,
|
||||
},
|
||||
}
|
||||
mock_nc.list_ports.return_value = {
|
||||
'ports': [
|
||||
{
|
||||
'id': uuids.fip_port_id,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
if fip_ext_enabled:
|
||||
self.api.extensions = [constants.FIP_PORT_DETAILS]
|
||||
else:
|
||||
self.api.extensions = []
|
||||
|
||||
fips = self.api.get_floating_ips_by_project(self.context)
|
||||
|
||||
self.assertEqual(1, len(fips))
|
||||
|
||||
if fip_ext_enabled:
|
||||
mock_nc.list_ports.assert_not_called()
|
||||
self.assertNotIn('port_details', fips[0])
|
||||
else:
|
||||
mock_nc.list_ports.assert_called_once_with(
|
||||
tenant_id=self.context.project_id)
|
||||
self.assertIn('port_details', fips[0])
|
||||
|
||||
def test_get_floating_ips_by_project_with_fip_port_details_ext(self):
|
||||
self._test_get_floating_ips_by_project(True)
|
||||
|
||||
def test_get_floating_ips_by_project_without_fip_port_details_ext(self):
|
||||
self._test_get_floating_ips_by_project(False)
|
||||
|
||||
@mock.patch('nova.network.neutron.API._show_port')
|
||||
def test_unbind_ports_reset_dns_name_by_admin(self, mock_show):
|
||||
neutron = mock.Mock()
|
||||
|
Loading…
x
Reference in New Issue
Block a user