Allow server-side filtering of Neutron floating IPs
Neutron API allows to filter floating IPs based on some fields. This enables server-side (efficient) filtering that Shade should leverage when it can. Unfortunately Nova-net (which is deprecated) doesn't support server-side filtering when it comes to FIP, so we have to distinguish the two at several places. This patch also updates the `search_floating_ips` method to use server-side filtering when appropriate. Change-Id: I28c960091c1679ca588c0cfb3bed1881cd6b03d2
This commit is contained in:
parent
5296d06ae1
commit
958f972552
@ -1383,14 +1383,18 @@ class OpenStackCloud(_normalize.Normalizer):
|
|||||||
pools = self.list_floating_ip_pools()
|
pools = self.list_floating_ip_pools()
|
||||||
return _utils._filter_list(pools, name, filters)
|
return _utils._filter_list(pools, name, filters)
|
||||||
|
|
||||||
# Note (dguerri): when using Neutron, this can be optimized using
|
# With Neutron, there are some cases in which full server side filtering is
|
||||||
# server-side search.
|
# not possible (e.g. nested attributes or list of objects) so we also need
|
||||||
# There are some cases in which such optimization is not possible (e.g.
|
# to use the client-side filtering
|
||||||
# nested attributes or list of objects) so we need to use the client-side
|
|
||||||
# filtering when we can't do otherwise.
|
|
||||||
# The same goes for all neutron-related search/get methods!
|
# The same goes for all neutron-related search/get methods!
|
||||||
def search_floating_ips(self, id=None, filters=None):
|
def search_floating_ips(self, id=None, filters=None):
|
||||||
floating_ips = self.list_floating_ips()
|
# `filters` could be a jmespath expression which Neutron server doesn't
|
||||||
|
# understand, obviously.
|
||||||
|
if self._use_neutron_floating() and isinstance(filters, dict):
|
||||||
|
kwargs = {'filters': filters}
|
||||||
|
else:
|
||||||
|
kwargs = {}
|
||||||
|
floating_ips = self.list_floating_ips(**kwargs)
|
||||||
return _utils._filter_list(floating_ips, id, filters)
|
return _utils._filter_list(floating_ips, id, filters)
|
||||||
|
|
||||||
def search_stacks(self, name_or_id=None, filters=None):
|
def search_stacks(self, name_or_id=None, filters=None):
|
||||||
@ -1700,26 +1704,47 @@ class OpenStackCloud(_normalize.Normalizer):
|
|||||||
with _utils.shade_exceptions("Error fetching floating IP pool list"):
|
with _utils.shade_exceptions("Error fetching floating IP pool list"):
|
||||||
return self.manager.submit_task(_tasks.FloatingIPPoolList())
|
return self.manager.submit_task(_tasks.FloatingIPPoolList())
|
||||||
|
|
||||||
def _list_floating_ips(self):
|
def _list_floating_ips(self, filters=None):
|
||||||
if self._use_neutron_floating():
|
if self._use_neutron_floating():
|
||||||
try:
|
try:
|
||||||
return self._normalize_floating_ips(
|
return self._normalize_floating_ips(
|
||||||
self._neutron_list_floating_ips())
|
self._neutron_list_floating_ips(filters))
|
||||||
except OpenStackCloudURINotFound as e:
|
except OpenStackCloudURINotFound as e:
|
||||||
|
# Nova-network don't support server-side floating ips
|
||||||
|
# filtering, so it's safer to die hard than to fallback to Nova
|
||||||
|
# which may return more results that expected.
|
||||||
|
if filters:
|
||||||
|
self.log.error(
|
||||||
|
"Something went wrong talking to neutron API. Can't "
|
||||||
|
"fallback to Nova since it doesn't support server-side"
|
||||||
|
" filtering when listing floating ips."
|
||||||
|
)
|
||||||
|
raise
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
"Something went wrong talking to neutron API: "
|
"Something went wrong talking to neutron API: "
|
||||||
"'%(msg)s'. Trying with Nova.", {'msg': str(e)})
|
"'%(msg)s'. Trying with Nova.", {'msg': str(e)})
|
||||||
# Fall-through, trying with Nova
|
# Fall-through, trying with Nova
|
||||||
|
else:
|
||||||
|
if filters:
|
||||||
|
raise ValueError(
|
||||||
|
"Nova-network don't support server-side floating ips "
|
||||||
|
"filtering. Use the search_floatting_ips method instead"
|
||||||
|
)
|
||||||
|
|
||||||
floating_ips = self._nova_list_floating_ips()
|
floating_ips = self._nova_list_floating_ips()
|
||||||
return self._normalize_floating_ips(floating_ips)
|
return self._normalize_floating_ips(floating_ips)
|
||||||
|
|
||||||
def list_floating_ips(self):
|
def list_floating_ips(self, filters=None):
|
||||||
"""List all available floating IPs.
|
"""List all available floating IPs.
|
||||||
|
|
||||||
|
:param filters: (optional) dict of filter conditions to push down
|
||||||
:returns: A list of floating IP ``munch.Munch``.
|
:returns: A list of floating IP ``munch.Munch``.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
# If pushdown filters are specified, bypass local caching.
|
||||||
|
if filters:
|
||||||
|
return self._list_floating_ips(filters)
|
||||||
|
|
||||||
if (time.time() - self._floating_ips_time) >= self._FLOAT_AGE:
|
if (time.time() - self._floating_ips_time) >= self._FLOAT_AGE:
|
||||||
# Since we're using cached data anyway, we don't need to
|
# Since we're using cached data anyway, we don't need to
|
||||||
# have more than one thread actually submit the list
|
# have more than one thread actually submit the list
|
||||||
@ -1738,10 +1763,12 @@ class OpenStackCloud(_normalize.Normalizer):
|
|||||||
self._floating_ips_lock.release()
|
self._floating_ips_lock.release()
|
||||||
return self._floating_ips
|
return self._floating_ips
|
||||||
|
|
||||||
def _neutron_list_floating_ips(self):
|
def _neutron_list_floating_ips(self, filters=None):
|
||||||
|
if not filters:
|
||||||
|
filters = {}
|
||||||
with _utils.neutron_exceptions("error fetching floating IPs list"):
|
with _utils.neutron_exceptions("error fetching floating IPs list"):
|
||||||
return self.manager.submit_task(
|
return self.manager.submit_task(
|
||||||
_tasks.NeutronFloatingIPList())['floatingips']
|
_tasks.NeutronFloatingIPList(**filters))['floatingips']
|
||||||
|
|
||||||
def _nova_list_floating_ips(self):
|
def _nova_list_floating_ips(self):
|
||||||
with _utils.shade_exceptions("Error fetching floating IPs list"):
|
with _utils.shade_exceptions("Error fetching floating IPs list"):
|
||||||
|
@ -243,3 +243,52 @@ class TestFloatingIP(base.BaseFunctionalTestCase):
|
|||||||
id=None, filters={'floating_ip_address': ip})
|
id=None, filters={'floating_ip_address': ip})
|
||||||
self.demo_cloud.detach_ip_from_server(
|
self.demo_cloud.detach_ip_from_server(
|
||||||
server_id=new_server.id, floating_ip_id=f_ip['id'])
|
server_id=new_server.id, floating_ip_id=f_ip['id'])
|
||||||
|
|
||||||
|
def test_list_floating_ips(self):
|
||||||
|
fip_admin = self.operator_cloud.create_floating_ip()
|
||||||
|
self.addCleanup(self.operator_cloud.delete_floating_ip, fip_admin.id)
|
||||||
|
fip_user = self.demo_cloud.create_floating_ip()
|
||||||
|
self.addCleanup(self.demo_cloud.delete_floating_ip, fip_user.id)
|
||||||
|
|
||||||
|
# Get all the floating ips.
|
||||||
|
fip_id_list = [
|
||||||
|
fip.id for fip in self.operator_cloud.list_floating_ips()
|
||||||
|
]
|
||||||
|
if self.demo_cloud.has_service('network'):
|
||||||
|
# Neutron returns all FIP for all projects by default
|
||||||
|
self.assertIn(fip_admin.id, fip_id_list)
|
||||||
|
self.assertIn(fip_user.id, fip_id_list)
|
||||||
|
|
||||||
|
# Ask Neutron for only a subset of all the FIPs.
|
||||||
|
filtered_fip_id_list = [
|
||||||
|
fip.id for fip in self.operator_cloud.list_floating_ips(
|
||||||
|
{'tenant_id': self.demo_cloud.current_project_id}
|
||||||
|
)
|
||||||
|
]
|
||||||
|
self.assertNotIn(fip_admin.id, filtered_fip_id_list)
|
||||||
|
self.assertIn(fip_user.id, filtered_fip_id_list)
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.assertIn(fip_admin.id, fip_id_list)
|
||||||
|
# By default, Nova returns only the FIPs that belong to the
|
||||||
|
# project which made the listing request.
|
||||||
|
self.assertNotIn(fip_user.id, fip_id_list)
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
ValueError, "Nova-network don't support server-side.*",
|
||||||
|
self.operator_cloud.list_floating_ips, filters={'foo': 'bar'}
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_search_floating_ips(self):
|
||||||
|
fip_user = self.demo_cloud.create_floating_ip()
|
||||||
|
self.addCleanup(self.demo_cloud.delete_floating_ip, fip_user.id)
|
||||||
|
|
||||||
|
self.assertIn(
|
||||||
|
fip_user['id'],
|
||||||
|
[fip.id for fip in self.demo_cloud.search_floating_ips(
|
||||||
|
filters={"attached": False})]
|
||||||
|
)
|
||||||
|
self.assertNotIn(
|
||||||
|
fip_user['id'],
|
||||||
|
[fip.id for fip in self.demo_cloud.search_floating_ips(
|
||||||
|
filters={"attached": True})]
|
||||||
|
)
|
||||||
|
@ -166,9 +166,8 @@ class TestFloatingIP(base.TestCase):
|
|||||||
self.assertEqual('UNKNOWN', normalized[0]['status'])
|
self.assertEqual('UNKNOWN', normalized[0]['status'])
|
||||||
|
|
||||||
@patch.object(OpenStackCloud, 'neutron_client')
|
@patch.object(OpenStackCloud, 'neutron_client')
|
||||||
@patch.object(OpenStackCloud, 'has_service')
|
@patch.object(OpenStackCloud, 'has_service', return_value=True)
|
||||||
def test_list_floating_ips(self, mock_has_service, mock_neutron_client):
|
def test_list_floating_ips(self, mock_has_service, mock_neutron_client):
|
||||||
mock_has_service.return_value = True
|
|
||||||
mock_neutron_client.list_floatingips.return_value = \
|
mock_neutron_client.list_floatingips.return_value = \
|
||||||
self.mock_floating_ip_list_rep
|
self.mock_floating_ip_list_rep
|
||||||
|
|
||||||
@ -179,6 +178,21 @@ class TestFloatingIP(base.TestCase):
|
|||||||
self.assertAreInstances(floating_ips, dict)
|
self.assertAreInstances(floating_ips, dict)
|
||||||
self.assertEqual(2, len(floating_ips))
|
self.assertEqual(2, len(floating_ips))
|
||||||
|
|
||||||
|
@patch.object(OpenStackCloud, 'neutron_client')
|
||||||
|
@patch.object(OpenStackCloud, 'has_service', return_value=True)
|
||||||
|
def test_list_floating_ips_with_filters(self, mock_has_service,
|
||||||
|
mock_neutron_client):
|
||||||
|
mock_neutron_client.list_floatingips.side_effect = \
|
||||||
|
exc.OpenStackCloudURINotFound("")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.cloud.list_floating_ips(filters={'Foo': 42})
|
||||||
|
except exc.OpenStackCloudException as e:
|
||||||
|
self.assertIsInstance(
|
||||||
|
e.inner_exception[1], exc.OpenStackCloudURINotFound)
|
||||||
|
|
||||||
|
mock_neutron_client.list_floatingips.assert_called_once_with(Foo=42)
|
||||||
|
|
||||||
@patch.object(OpenStackCloud, 'neutron_client')
|
@patch.object(OpenStackCloud, 'neutron_client')
|
||||||
@patch.object(OpenStackCloud, 'has_service')
|
@patch.object(OpenStackCloud, 'has_service')
|
||||||
def test_search_floating_ips(self, mock_has_service, mock_neutron_client):
|
def test_search_floating_ips(self, mock_has_service, mock_neutron_client):
|
||||||
@ -189,7 +203,7 @@ class TestFloatingIP(base.TestCase):
|
|||||||
floating_ips = self.cloud.search_floating_ips(
|
floating_ips = self.cloud.search_floating_ips(
|
||||||
filters={'attached': False})
|
filters={'attached': False})
|
||||||
|
|
||||||
mock_neutron_client.list_floatingips.assert_called_with()
|
mock_neutron_client.list_floatingips.assert_called_with(attached=False)
|
||||||
self.assertIsInstance(floating_ips, list)
|
self.assertIsInstance(floating_ips, list)
|
||||||
self.assertAreInstances(floating_ips, dict)
|
self.assertAreInstances(floating_ips, dict)
|
||||||
self.assertEqual(1, len(floating_ips))
|
self.assertEqual(1, len(floating_ips))
|
||||||
@ -311,7 +325,7 @@ class TestFloatingIP(base.TestCase):
|
|||||||
|
|
||||||
mock_keystone_session.get_project_id.assert_called_once_with()
|
mock_keystone_session.get_project_id.assert_called_once_with()
|
||||||
mock_get_ext_nets.assert_called_once_with()
|
mock_get_ext_nets.assert_called_once_with()
|
||||||
mock__neutron_list_fips.assert_called_once_with()
|
mock__neutron_list_fips.assert_called_once_with(None)
|
||||||
mock__filter_list.assert_called_once_with(
|
mock__filter_list.assert_called_once_with(
|
||||||
[], name_or_id=None,
|
[], name_or_id=None,
|
||||||
filters={'port_id': None,
|
filters={'port_id': None,
|
||||||
@ -349,7 +363,7 @@ class TestFloatingIP(base.TestCase):
|
|||||||
|
|
||||||
mock_keystone_session.get_project_id.assert_called_once_with()
|
mock_keystone_session.get_project_id.assert_called_once_with()
|
||||||
mock_get_ext_nets.assert_called_once_with()
|
mock_get_ext_nets.assert_called_once_with()
|
||||||
mock__neutron_list_fips.assert_called_once_with()
|
mock__neutron_list_fips.assert_called_once_with(None)
|
||||||
mock__filter_list.assert_called_once_with(
|
mock__filter_list.assert_called_once_with(
|
||||||
[], name_or_id=None,
|
[], name_or_id=None,
|
||||||
filters={'port_id': None,
|
filters={'port_id': None,
|
||||||
|
@ -99,6 +99,17 @@ class TestFloatingIP(base.TestCase):
|
|||||||
self.assertEqual(3, len(floating_ips))
|
self.assertEqual(3, len(floating_ips))
|
||||||
self.assertAreInstances(floating_ips, dict)
|
self.assertAreInstances(floating_ips, dict)
|
||||||
|
|
||||||
|
@patch.object(OpenStackCloud, 'nova_client')
|
||||||
|
@patch.object(OpenStackCloud, 'has_service')
|
||||||
|
def test_list_floating_ips_with_filters(self, mock_has_service,
|
||||||
|
mock_nova_client):
|
||||||
|
mock_has_service.side_effect = has_service_side_effect
|
||||||
|
|
||||||
|
self.assertRaisesRegex(
|
||||||
|
ValueError, "Nova-network don't support server-side",
|
||||||
|
self.cloud.list_floating_ips, filters={'Foo': 42}
|
||||||
|
)
|
||||||
|
|
||||||
@patch.object(OpenStackCloud, 'nova_client')
|
@patch.object(OpenStackCloud, 'nova_client')
|
||||||
@patch.object(OpenStackCloud, 'has_service')
|
@patch.object(OpenStackCloud, 'has_service')
|
||||||
def test_search_floating_ips(self, mock_has_service, mock_nova_client):
|
def test_search_floating_ips(self, mock_has_service, mock_nova_client):
|
||||||
|
Loading…
Reference in New Issue
Block a user