Merge "Use neutron port_list when filtering instance by ip"

This commit is contained in:
Zuul 2018-01-26 09:53:30 +00:00 committed by Gerrit Code Review
commit ac60fda9e5
7 changed files with 186 additions and 4 deletions

View File

@ -2347,9 +2347,36 @@ class API(base.Base):
# limit so that it can be applied after the IP filter.
filter_ip = 'ip6' in filters or 'ip' in filters
orig_limit = limit
if filter_ip and limit:
LOG.debug('Removing limit for DB query due to IP filter')
limit = None
if filter_ip:
if self.network_api.has_substr_port_filtering_extension(context):
# We're going to filter by IP using Neutron so set filter_ip
# to False so we don't attempt post-DB query filtering in
# memory below.
filter_ip = False
instance_uuids = self._ip_filter_using_neutron(context,
filters)
if instance_uuids:
# Note that 'uuid' is not in the 2.1 GET /servers query
# parameter schema, however, we allow additionalProperties
# so someone could filter instances by uuid, which doesn't
# make a lot of sense but we have to account for it.
if 'uuid' in filters and filters['uuid']:
filter_uuids = filters['uuid']
if isinstance(filter_uuids, list):
instance_uuids.extend(filter_uuids)
else:
# Assume a string. If it's a dict or tuple or
# something, well...that's too bad. This is why
# we have query parameter schema definitions.
if filter_uuids not in instance_uuids:
instance_uuids.append(filter_uuids)
filters['uuid'] = instance_uuids
else:
# No matches on the ip filter(s), return an empty list.
return objects.InstanceList()
elif limit:
LOG.debug('Removing limit for DB query due to IP filter')
limit = None
# The ordering of instances will be
# [sorted instances with no host] + [sorted instances with host].
@ -2484,6 +2511,26 @@ class API(base.Base):
break
return objects.InstanceList(objects=result_objs)
def _ip_filter_using_neutron(self, context, filters):
ip4_address = filters.get('ip')
ip6_address = filters.get('ip6')
addresses = [ip4_address, ip6_address]
uuids = []
for address in addresses:
if address:
try:
ports = self.network_api.list_ports(
context, fixed_ips='ip_address_substr=' + address,
fields=['device_id'])['ports']
for port in ports:
uuids.append(port['device_id'])
except Exception as e:
LOG.error('An error occurred while listing ports '
'with an ip_address filter value of "%s". '
'Error: %s',
address, six.text_type(e))
return uuids
def _get_instances_by_filters(self, context, filters,
limit=None, marker=None, fields=None,
sort_keys=None, sort_dirs=None):

View File

@ -367,3 +367,6 @@ class NetworkAPI(base.Base):
:param index: The index on the instance for the VIF.
"""
pass
def has_substr_port_filtering_extension(self, context):
return False

View File

@ -1112,6 +1112,10 @@ class API(base_api.NetworkAPI):
self._refresh_neutron_extensions_cache(context, neutron=neutron)
return constants.QOS_QUEUE 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
def _get_pci_device_profile(self, pci_dev):
dev_spec = self.pci_whitelist.get_devspec(pci_dev)
if dev_spec:

View File

@ -18,3 +18,4 @@ NET_EXTERNAL = 'router:external'
VNIC_INDEX_EXT = 'VNIC Index'
DNS_INTEGRATION = 'DNS Integration'
MULTI_NET_EXT = 'Multi Provider Network'
SUBSTR_PORT_FILTERING = 'IP address substring filtering'

View File

@ -11403,7 +11403,10 @@ class ComputeAPIIpFilterTestCase(test.NoDBTestCase):
@mock.patch.object(objects.BuildRequestList, 'get_by_filters')
@mock.patch.object(objects.CellMapping, 'get_by_uuid',
side_effect=exception.CellMappingNotFound(uuid='fake'))
def test_ip_filtering_no_limit_to_db(self, _mock_cell_map_get,
@mock.patch('nova.network.neutronv2.api.API.'
'has_substr_port_filtering_extension', return_value=False)
def test_ip_filtering_no_limit_to_db(self, mock_has_port_filter_ext,
_mock_cell_map_get,
mock_buildreq_get):
c = context.get_admin_context()
# Limit is not supplied to the DB when using an IP filter

View File

@ -38,6 +38,7 @@ from nova import conductor
from nova import context
from nova import db
from nova import exception
from nova.network.neutronv2 import api as neutron_api
from nova import objects
from nova.objects import base as obj_base
from nova.objects import block_device as block_device_obj
@ -5630,6 +5631,123 @@ class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
self.compute_api.attach_volume,
self.context, instance, uuids.volumeid)
@mock.patch.object(neutron_api.API, 'has_substr_port_filtering_extension')
@mock.patch.object(neutron_api.API, 'list_ports')
@mock.patch.object(objects.BuildRequestList, 'get_by_filters')
def test_get_all_ip_filter_use_neutron(self, mock_buildreq_get,
mock_list_port, mock_check_ext):
mock_check_ext.return_value = True
cell_instances = self._list_of_instances(2)
mock_list_port.return_value = {
'ports': [{'device_id': 'fake_device_id'}]}
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances)
self.compute_api.get_all(
self.context, search_opts={'ip': 'fake'},
limit=None, marker='fake-marker', sort_keys=['baz'],
sort_dirs=['desc'])
mock_list_port.assert_called_once_with(
self.context, fixed_ips='ip_address_substr=fake',
fields=['device_id'])
mock_buildreq_get.assert_called_once_with(
self.context, {'ip': 'fake', 'uuid': ['fake_device_id']},
limit=None, marker='fake-marker',
sort_keys=['baz'], sort_dirs=['desc'])
fields = ['metadata', 'info_cache', 'security_groups']
mock_inst_get.assert_called_once_with(
self.context, {'ip': 'fake', 'uuid': ['fake_device_id']},
None, None, fields, ['baz'], ['desc'])
@mock.patch.object(neutron_api.API, 'has_substr_port_filtering_extension')
@mock.patch.object(neutron_api.API, 'list_ports')
@mock.patch.object(objects.BuildRequestList, 'get_by_filters')
def test_get_all_ip6_filter_use_neutron(self, mock_buildreq_get,
mock_list_port, mock_check_ext):
mock_check_ext.return_value = True
cell_instances = self._list_of_instances(2)
mock_list_port.return_value = {
'ports': [{'device_id': 'fake_device_id'}]}
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances)
self.compute_api.get_all(
self.context, search_opts={'ip6': 'fake'},
limit=None, marker='fake-marker', sort_keys=['baz'],
sort_dirs=['desc'])
mock_list_port.assert_called_once_with(
self.context, fixed_ips='ip_address_substr=fake',
fields=['device_id'])
mock_buildreq_get.assert_called_once_with(
self.context, {'ip6': 'fake', 'uuid': ['fake_device_id']},
limit=None, marker='fake-marker',
sort_keys=['baz'], sort_dirs=['desc'])
fields = ['metadata', 'info_cache', 'security_groups']
mock_inst_get.assert_called_once_with(
self.context, {'ip6': 'fake', 'uuid': ['fake_device_id']},
None, None, fields, ['baz'], ['desc'])
@mock.patch.object(neutron_api.API, 'has_substr_port_filtering_extension')
@mock.patch.object(neutron_api.API, 'list_ports')
@mock.patch.object(objects.BuildRequestList, 'get_by_filters')
def test_get_all_ip_and_ip6_filter_use_neutron(self, mock_buildreq_get,
mock_list_port,
mock_check_ext):
mock_check_ext.return_value = True
cell_instances = self._list_of_instances(2)
mock_list_port.return_value = {
'ports': [{'device_id': 'fake_device_id'}]}
with mock.patch('nova.compute.instance_list.'
'get_instance_objects_sorted') as mock_inst_get:
mock_inst_get.return_value = objects.InstanceList(
self.context, objects=cell_instances)
self.compute_api.get_all(
self.context, search_opts={'ip': 'fake1', 'ip6': 'fake2'},
limit=None, marker='fake-marker', sort_keys=['baz'],
sort_dirs=['desc'])
mock_list_port.assert_has_calls([
mock.call(
self.context, fixed_ips='ip_address_substr=fake1',
fields=['device_id']),
mock.call(
self.context, fixed_ips='ip_address_substr=fake2',
fields=['device_id'])
])
mock_buildreq_get.assert_called_once_with(
self.context, {'ip': 'fake1', 'ip6': 'fake2',
'uuid': ['fake_device_id', 'fake_device_id']},
limit=None, marker='fake-marker',
sort_keys=['baz'], sort_dirs=['desc'])
fields = ['metadata', 'info_cache', 'security_groups']
mock_inst_get.assert_called_once_with(
self.context, {'ip': 'fake1', 'ip6': 'fake2',
'uuid': ['fake_device_id', 'fake_device_id']},
None, None, fields, ['baz'], ['desc'])
@mock.patch.object(neutron_api.API, 'has_substr_port_filtering_extension')
@mock.patch.object(neutron_api.API, 'list_ports')
def test_get_all_ip6_filter_use_neutron_exc(self, mock_list_port,
mock_check_ext):
mock_check_ext.return_value = True
mock_list_port.side_effect = exception.InternalError('fake')
instances = self.compute_api.get_all(
self.context, search_opts={'ip6': 'fake'},
limit=None, marker='fake-marker', sort_keys=['baz'],
sort_dirs=['desc'])
mock_list_port.assert_called_once_with(
self.context, fixed_ips='ip_address_substr=fake',
fields=['device_id'])
self.assertEqual([], instances.objects)
class Cellsv1DeprecatedTestMixIn(object):
@mock.patch.object(objects.BuildRequestList, 'get_by_filters')

View File

@ -0,0 +1,6 @@
---
features:
- When ``IP address substring filtering`` extension
is available in Neutron, Nova will proxy the instance
list with ``ip`` or ``ip6`` filter to Neutron for
better performance.