From 43d3e88a07b4275ad814c6875fa037efd94223bb Mon Sep 17 00:00:00 2001 From: Ahmed Zaid Date: Wed, 17 Jan 2018 09:32:59 -0500 Subject: [PATCH] Filter port-list based on security_groups. This patch allows users to filter ports depending on security groups. In addition to that I added a unit test to verify this change. TODO: move security_groups_port_filtering_lib.py into neutron-lib. Closes-Bug: 1405057 Change-Id: I528719d895fc1d89f74e2ee85b9ddc470323c601 --- .../security_groups_port_filtering.py | 23 ++++++++ .../security_groups_port_filtering_lib.py | 59 +++++++++++++++++++ neutron/plugins/ml2/plugin.py | 14 ++++- .../tests/contrib/hooks/api_all_extensions | 1 + neutron/tests/unit/plugins/ml2/test_plugin.py | 40 +++++++++++++ ...roups-port-filtering-69d36ac7db90c9e0.yaml | 6 ++ 6 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 neutron/extensions/security_groups_port_filtering.py create mode 100644 neutron/extensions/security_groups_port_filtering_lib.py create mode 100644 releasenotes/notes/security-groups-port-filtering-69d36ac7db90c9e0.yaml diff --git a/neutron/extensions/security_groups_port_filtering.py b/neutron/extensions/security_groups_port_filtering.py new file mode 100644 index 00000000000..99f45c2bdbe --- /dev/null +++ b/neutron/extensions/security_groups_port_filtering.py @@ -0,0 +1,23 @@ +# Copyright (c) 2018 Nokia. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from neutron_lib.api import extensions + +from neutron.extensions import security_groups_port_filtering_lib as apidef + + +class Security_groups_port_filtering(extensions.APIExtensionDescriptor): + """Extension class supporting filtering port depend on security group.""" + + api_definition = apidef diff --git a/neutron/extensions/security_groups_port_filtering_lib.py b/neutron/extensions/security_groups_port_filtering_lib.py new file mode 100644 index 00000000000..9a200b630b2 --- /dev/null +++ b/neutron/extensions/security_groups_port_filtering_lib.py @@ -0,0 +1,59 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# The alias of the extension. +ALIAS = 'port-security-groups-filtering' + +# Whether or not this extension is simply signaling behavior to the user +# or it actively modifies the attribute map. +IS_SHIM_EXTENSION = True + +# Whether the extension is marking the adoption of standardattr model for +# legacy resources, or introducing new standardattr attributes. False or +# None if the standardattr model is adopted since the introduction of +# resource extension. +# If this is True, the alias for the extension should be prefixed with +# 'standard-attr-'. +IS_STANDARD_ATTR_EXTENSION = False + +# The name of the extension. +NAME = 'Port filtering on security groups' + +# The description of the extension. +DESCRIPTION = "Provides security groups filtering when listing ports" + +# A timestamp of when the extension was introduced. +UPDATED_TIMESTAMP = "2018-01-09T09:00:00-00:00" + +# The resource attribute map for the extension. +RESOURCE_ATTRIBUTE_MAP = { +} + +# The subresource attribute map for the extension. +SUB_RESOURCE_ATTRIBUTE_MAP = { +} + +# The action map. +ACTION_MAP = { +} + +# The action status. +ACTION_STATUS = { +} + +# The list of required extensions. +REQUIRED_EXTENSIONS = [ +] + +# The list of optional extensions. +OPTIONAL_EXTENSIONS = [ +] diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index 6599b84e527..3cc8f5ea20a 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -155,7 +155,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, "network_availability_zone", "default-subnetpools", "subnet-service-types", - "ip-substring-filtering"] + "ip-substring-filtering", + "port-security-groups-filtering"] @property def supported_extension_aliases(self): @@ -1853,6 +1854,17 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, def _get_ports_query(self, context, filters=None, *args, **kwargs): filters = filters or {} + security_groups = filters.pop("security_groups", None) + if security_groups: + port_bindings = self._get_port_security_group_bindings( + context, filters={'security_group_id': + security_groups}) + if 'id' in filters: + filters['id'] = [entry['port_id'] for + entry in port_bindings + if entry['port_id'] in filters['id']] + else: + filters['id'] = [entry['port_id'] for entry in port_bindings] fixed_ips = filters.get('fixed_ips', {}) ip_addresses_s = fixed_ips.get('ip_address_substr') query = super(Ml2Plugin, self)._get_ports_query(context, filters, diff --git a/neutron/tests/contrib/hooks/api_all_extensions b/neutron/tests/contrib/hooks/api_all_extensions index a82e3f9581e..47c19becf34 100644 --- a/neutron/tests/contrib/hooks/api_all_extensions +++ b/neutron/tests/contrib/hooks/api_all_extensions @@ -37,6 +37,7 @@ NETWORK_API_EXTENSIONS+=",rbac-policies" NETWORK_API_EXTENSIONS+=",router" NETWORK_API_EXTENSIONS+=",router_availability_zone" NETWORK_API_EXTENSIONS+=",security-group" +NETWORK_API_EXTENSIONS+=",port-security-groups-filtering" NETWORK_API_EXTENSIONS+=",segment" NETWORK_API_EXTENSIONS+=",service-type" NETWORK_API_EXTENSIONS+=",sorting" diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index ce74e8b312f..57fceb14455 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -44,6 +44,7 @@ from neutron.db import agents_db from neutron.db import api as db_api from neutron.db import models_v2 from neutron.db import provisioning_blocks +from neutron.db import securitygroups_db as sg_db from neutron.db import segments_db from neutron.extensions import multiprovidernet as mpnet from neutron.objects import base as base_obj @@ -1278,6 +1279,45 @@ fixed_ips=ip_address_substr%%3D%s&fixed_ips=ip_address%%3D%s self._delete('ports', port1['port']['id']) self._delete('ports', port2['port']['id']) + def test_list_ports_filtered_by_security_groups(self): + # for this test we need to enable overlapping ips + cfg.CONF.set_default('allow_overlapping_ips', True) + ctx = context.get_admin_context() + with self.port() as port1, self.port() as port2: + query_params = "security_groups=%s" % ( + port1['port']['security_groups'][0]) + ports_data = self._list('ports', query_params=query_params) + self.assertEqual(set([port1['port']['id'], port2['port']['id']]), + set([port['id'] for port in ports_data['ports']])) + self.assertEqual(2, len(ports_data['ports'])) + query_params = "security_groups=%s&id=%s" % ( + port1['port']['security_groups'][0], + port1['port']['id']) + ports_data = self._list('ports', query_params=query_params) + self.assertEqual(port1['port']['id'], ports_data['ports'][0]['id']) + self.assertEqual(1, len(ports_data['ports'])) + temp_sg = {'security_group': {'tenant_id': 'some_tenant', + 'name': '', 'description': 's'}} + sg_dbMixin = sg_db.SecurityGroupDbMixin() + sg = sg_dbMixin.create_security_group(ctx, temp_sg) + sg_dbMixin._delete_port_security_group_bindings( + ctx, port2['port']['id']) + sg_dbMixin._create_port_security_group_binding( + ctx, port2['port']['id'], sg['id']) + port2['port']['security_groups'][0] = sg['id'] + query_params = "security_groups=%s&id=%s" % ( + port1['port']['security_groups'][0], + port1['port']['id']) + ports_data = self._list('ports', query_params=query_params) + self.assertEqual(port1['port']['id'], ports_data['ports'][0]['id']) + self.assertEqual(1, len(ports_data['ports'])) + query_params = "security_groups=%s&id=%s" % ( + (port2['port']['security_groups'][0], + port2['port']['id'])) + ports_data = self._list('ports', query_params=query_params) + self.assertEqual(port2['port']['id'], ports_data['ports'][0]['id']) + self.assertEqual(1, len(ports_data['ports'])) + class TestMl2PortsV2WithRevisionPlugin(Ml2PluginV2TestCase): diff --git a/releasenotes/notes/security-groups-port-filtering-69d36ac7db90c9e0.yaml b/releasenotes/notes/security-groups-port-filtering-69d36ac7db90c9e0.yaml new file mode 100644 index 00000000000..3be1d7502ea --- /dev/null +++ b/releasenotes/notes/security-groups-port-filtering-69d36ac7db90c9e0.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Support port filtering on security group IDs. + The feature can be used if 'port-security-group-filtering' extension is available. +