Merge "Use neutron port_list when filtering instance by ip"
This commit is contained in:
commit
ac60fda9e5
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue