From 2f944d310554f0248261fff875141bdf9698b35e Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 3 Feb 2022 00:04:02 +0000 Subject: [PATCH] Support filtering for QoS rule type list Added support for filtering the QoS rule type list command. Two new filter flags are added: - all_supported: if True, the listing call will print all QoS rule types supported by at least one loaded mechanism driver. - all_rules: if True, the listing call will print all QoS rule types supported by the Neutron server. Both filter flags are exclusive and not required. Depends-On: https://review.opendev.org/c/openstack/neutron-lib/+/827533 Closes-Bug: #1959749 Change-Id: I41eaab177e121316c3daec34b309c266e2f81979 --- .../internals/quality_of_service.rst | 2 +- neutron/common/ovn/extensions.py | 2 + neutron/extensions/qos_rule_type_filter.py | 20 ++++ neutron/objects/qos/rule_type.py | 9 +- neutron/services/qos/drivers/manager.py | 17 ++- neutron/services/qos/qos_plugin.py | 8 +- .../tests/unit/objects/qos/test_rule_type.py | 4 +- .../unit/services/qos/drivers/test_manager.py | 108 +++++++----------- .../unit/services/qos/test_qos_plugin.py | 4 +- ...qos-rule-type-filter-9c821e93b27fffe9.yaml | 11 ++ 10 files changed, 101 insertions(+), 84 deletions(-) create mode 100644 neutron/extensions/qos_rule_type_filter.py create mode 100644 releasenotes/notes/qos-rule-type-filter-9c821e93b27fffe9.yaml diff --git a/doc/source/contributor/internals/quality_of_service.rst b/doc/source/contributor/internals/quality_of_service.rst index 65ab290d53a..67a2f6525dc 100644 --- a/doc/source/contributor/internals/quality_of_service.rst +++ b/doc/source/contributor/internals/quality_of_service.rst @@ -98,7 +98,7 @@ NotImplemented. Supported QoS rule types ~~~~~~~~~~~~~~~~~~~~~~~~ -Each QoS driver has a property called supported_rule_types, where the driver +Each QoS driver has a member called ``supported_rules``, where the driver exposes the rules it's able to handle. For a list of all rule types, see: diff --git a/neutron/common/ovn/extensions.py b/neutron/common/ovn/extensions.py index 761f4326ef4..d99cf2b1303 100644 --- a/neutron/common/ovn/extensions.py +++ b/neutron/common/ovn/extensions.py @@ -52,6 +52,7 @@ from neutron_lib.api.definitions import qos from neutron_lib.api.definitions import qos_bw_limit_direction from neutron_lib.api.definitions import qos_default from neutron_lib.api.definitions import qos_rule_type_details +from neutron_lib.api.definitions import qos_rule_type_filter from neutron_lib.api.definitions import qos_rules_alias from neutron_lib.api.definitions import quota_check_limit from neutron_lib.api.definitions import rbac_address_scope @@ -124,6 +125,7 @@ ML2_SUPPORTED_API_EXTENSIONS = [ qos_bw_limit_direction.ALIAS, qos_default.ALIAS, qos_rule_type_details.ALIAS, + qos_rule_type_filter.ALIAS, qos_rules_alias.ALIAS, 'quotas', quota_check_limit.ALIAS, diff --git a/neutron/extensions/qos_rule_type_filter.py b/neutron/extensions/qos_rule_type_filter.py new file mode 100644 index 00000000000..a4c93573674 --- /dev/null +++ b/neutron/extensions/qos_rule_type_filter.py @@ -0,0 +1,20 @@ +# Copyright (c) 2022 Red Hat, Inc. +# +# 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.definitions import qos_rule_type_filter as apidef +from neutron_lib.api import extensions + + +class Qos_rule_type_filter(extensions.APIExtensionDescriptor): + api_definition = apidef diff --git a/neutron/objects/qos/rule_type.py b/neutron/objects/qos/rule_type.py index 6d8a5fc0de8..a4a90b98925 100644 --- a/neutron/objects/qos/rule_type.py +++ b/neutron/objects/qos/rule_type.py @@ -60,13 +60,14 @@ class QosRuleType(base.NeutronObject): # we don't receive context because we don't need db access at all @classmethod def get_objects(cls, validate_filters=True, **kwargs): + all_supported = kwargs.pop('all_supported', None) + all_rules = kwargs.pop('all_rules', None) if validate_filters: cls.validate_filters(**kwargs) - rule_types = ( - directory.get_plugin(alias=constants.QOS).supported_rule_types) - - # TODO(ihrachys): apply filters to returned result + rule_types = directory.get_plugin( + alias=constants.QOS).supported_rule_types( + all_supported=all_supported, all_rules=all_rules) return [cls(type=type_) for type_ in rule_types] # we don't receive context because we don't need db access at all diff --git a/neutron/services/qos/drivers/manager.py b/neutron/services/qos/drivers/manager.py index 13818065d59..248bb22ab77 100644 --- a/neutron/services/qos/drivers/manager.py +++ b/neutron/services/qos/drivers/manager.py @@ -163,15 +163,24 @@ class QosServiceDriverManager(object): return False - @property - def supported_rule_types(self): + def supported_rule_types(self, all_supported=None, all_rules=None): + rule_types = set(qos_consts.VALID_RULE_TYPES) + if all_rules: + LOG.debug('All supported QoS rule types: %s', rule_types) + return rule_types + if not self._drivers: return [] - rule_types = set(qos_consts.VALID_RULE_TYPES) - # Recalculate on every call to allow drivers determine supported rule # types dynamically + if all_supported: + rule_types = set.union(*[set(driver.supported_rules) for driver in + self._drivers]) + LOG.debug('All QoS rule types supported by at least one loaded ' + 'driver: %s', rule_types) + return rule_types + for driver in self._drivers: new_rule_types = rule_types & set(driver.supported_rules) dropped_rule_types = rule_types - new_rule_types diff --git a/neutron/services/qos/qos_plugin.py b/neutron/services/qos/qos_plugin.py index 21deb88b0d8..d2ddb95b7cf 100644 --- a/neutron/services/qos/qos_plugin.py +++ b/neutron/services/qos/qos_plugin.py @@ -31,6 +31,7 @@ from neutron_lib.api.definitions import qos_pps_minimum_rule from neutron_lib.api.definitions import qos_pps_minimum_rule_alias from neutron_lib.api.definitions import qos_pps_rule from neutron_lib.api.definitions import qos_rule_type_details +from neutron_lib.api.definitions import qos_rule_type_filter from neutron_lib.api.definitions import qos_rules_alias from neutron_lib.callbacks import events as callbacks_events from neutron_lib.callbacks import registry as callbacks_registry @@ -126,6 +127,7 @@ class QoSPlugin(qos.QoSPluginBase): qos_bw_limit_direction.ALIAS, qos_default.ALIAS, qos_rule_type_details.ALIAS, + qos_rule_type_filter.ALIAS, port_resource_request.ALIAS, port_resource_request_groups.ALIAS, qos_bw_minimum_ingress.ALIAS, @@ -882,9 +884,9 @@ class QoSPlugin(qos.QoSPluginBase): def supported_rule_type_details(self, rule_type_name): return self.driver_manager.supported_rule_type_details(rule_type_name) - @property - def supported_rule_types(self): - return self.driver_manager.supported_rule_types + def supported_rule_types(self, all_supported=None, all_rules=None): + return self.driver_manager.supported_rule_types( + all_supported=all_supported, all_rules=all_rules) @db_base_plugin_common.convert_result_to_dict def create_policy_rule(self, context, rule_cls, policy_id, rule_data): diff --git a/neutron/tests/unit/objects/qos/test_rule_type.py b/neutron/tests/unit/objects/qos/test_rule_type.py index ea7304618d3..50d7d573eff 100644 --- a/neutron/tests/unit/objects/qos/test_rule_type.py +++ b/neutron/tests/unit/objects/qos/test_rule_type.py @@ -77,10 +77,8 @@ class QosRuleTypeObjectTestCase(test_base.BaseTestCase): qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, rule_type_details.type) def test_get_objects(self): - rule_types_mock = mock.PropertyMock( - return_value=set(qos_consts.VALID_RULE_TYPES)) with mock.patch.object(qos_plugin.QoSPlugin, 'supported_rule_types', - new_callable=rule_types_mock): + return_value=set(qos_consts.VALID_RULE_TYPES)): types = rule_type.QosRuleType.get_objects() self.assertEqual(sorted(qos_consts.VALID_RULE_TYPES), sorted(type_['type'] for type_ in types)) diff --git a/neutron/tests/unit/services/qos/drivers/test_manager.py b/neutron/tests/unit/services/qos/drivers/test_manager.py index b4c2b8d7986..57e735862e2 100644 --- a/neutron/tests/unit/services/qos/drivers/test_manager.py +++ b/neutron/tests/unit/services/qos/drivers/test_manager.py @@ -10,9 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import itertools from unittest import mock from neutron_lib.api.definitions import portbindings +from neutron_lib.callbacks import registry from neutron_lib import constants as lib_consts from neutron_lib import context from neutron_lib import exceptions @@ -20,9 +22,14 @@ from neutron_lib.services.qos import base as qos_driver_base from neutron_lib.services.qos import constants as qos_consts from oslo_utils import uuidutils +from neutron.api.rpc.callbacks.producer import registry as rpc_registry from neutron.objects import ports as ports_object from neutron.objects.qos import rule as rule_object +from neutron.services.qos.drivers.linuxbridge import driver as lb_driver from neutron.services.qos.drivers import manager as driver_mgr +from neutron.services.qos.drivers.openvswitch import driver as ovs_driver +from neutron.services.qos.drivers.ovn import driver as ovn_driver +from neutron.services.qos.drivers.sriov import driver as sriov_driver from neutron.tests.unit.services.qos import base @@ -32,9 +39,14 @@ class TestQosDriversManagerBase(base.BaseQosTestCase): super(TestQosDriversManagerBase, self).setUp() self.config_parse() self.setup_coreplugin(load_plugins=False) + self._loaded_qos_drivers = [] - @staticmethod - def _create_manager_with_drivers(drivers_details): + def _delete_loaded_qos_drivers(self): + registry._get_callback_manager().clear() + for idx, _ in enumerate(self._loaded_qos_drivers): + del self._loaded_qos_drivers[idx] + + def _create_manager_with_drivers(self, drivers_details): for name, driver_details in drivers_details.items(): class QoSDriver(qos_driver_base.DriverBase): @@ -43,10 +55,11 @@ class TestQosDriversManagerBase(base.BaseQosTestCase): return driver_details['is_loaded'] # the new ad-hoc driver will register on the QOS_PLUGIN registry - QoSDriver(name, - driver_details.get('vif_types', []), - driver_details.get('vnic_types', []), - driver_details.get('rules', [])) + self._loaded_qos_drivers.append( + QoSDriver(name, + driver_details.get('vif_types', []), + driver_details.get('vnic_types', []), + driver_details.get('rules', []))) return driver_mgr.QosServiceDriverManager() @@ -169,67 +182,30 @@ class TestQoSDriversRulesValidations(TestQosDriversManagerBase): class TestQosDriversManagerRules(TestQosDriversManagerBase): """Test supported rules""" - def test_available_rules_one_in_common(self): - driver_manager = self._create_manager_with_drivers({ - 'driver-A': { - 'is_loaded': True, - 'rules': { - qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: { - "max_kbps": {'type:values': None}, - "max_burst_kbps": {'type:values': None} - }, - qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: { - "min_kbps": {'type:values': None}, - 'direction': { - 'type:values': lib_consts.VALID_DIRECTIONS} - } - } - }, - 'driver-B': { - 'is_loaded': True, - 'rules': { - qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: { - "min_kbps": {'type:values': None}, - 'direction': { - 'type:values': lib_consts.VALID_DIRECTIONS} - }, - qos_consts.RULE_TYPE_DSCP_MARKING: { - "dscp_mark": { - 'type:values': lib_consts.VALID_DSCP_MARKS} - } - } - } - }) - self.assertEqual(driver_manager.supported_rule_types, - set([qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH])) - def test_available_rules_no_rule_in_common(self): - driver_manager = self._create_manager_with_drivers({ - 'driver-A': { - 'is_loaded': True, - 'rules': { - qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: { - "max_kbps": {'type:values': None}, - "max_burst_kbps": {'type:values': None} - } - } - }, - 'driver-B': { - 'is_loaded': True, - 'rules': { - qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: { - "min_kbps": {'type:values': None}, - 'direction': { - 'type:values': lib_consts.VALID_DIRECTIONS} - }, - qos_consts.RULE_TYPE_DSCP_MARKING: { - "dscp_mark": { - 'type:values': lib_consts.VALID_DSCP_MARKS} - } - } - } - }) - self.assertEqual(driver_manager.supported_rule_types, set([])) + @mock.patch.object(rpc_registry, 'provide') + def test_available_rules(self, *args): + available_drivers = {'linuxbridge': lb_driver.SUPPORTED_RULES, + 'ovs': ovs_driver.SUPPORTED_RULES, + 'ovn': ovn_driver.SUPPORTED_RULES, + 'sriov': sriov_driver.SUPPORTED_RULES} + for drivers in itertools.combinations(available_drivers, 2): + rules_0 = set(available_drivers[drivers[0]]) + rules_1 = set(available_drivers[drivers[1]]) + driver_manager = self._create_manager_with_drivers( + {drivers[0]: {'is_loaded': True, 'rules': rules_0}, + drivers[1]: {'is_loaded': True, 'rules': rules_1}}) + self.assertEqual( + driver_manager.supported_rule_types(), + rules_0 & rules_1) + self.assertEqual( + driver_manager.supported_rule_types(all_supported=True), + rules_0 | rules_1) + self.assertEqual( + driver_manager.supported_rule_types(all_rules=True), + set(qos_consts.VALID_RULE_TYPES)) + + self._delete_loaded_qos_drivers() def test__parse_parameter_values(self): range_parameter = {'type:range': [0, 10]} diff --git a/neutron/tests/unit/services/qos/test_qos_plugin.py b/neutron/tests/unit/services/qos/test_qos_plugin.py index eb0cc83ee43..b9ef242e4a5 100644 --- a/neutron/tests/unit/services/qos/test_qos_plugin.py +++ b/neutron/tests/unit/services/qos/test_qos_plugin.py @@ -1310,11 +1310,9 @@ class TestQosPlugin(base.BaseQosTestCase): self.ctxt, qos_consts.RULE_TYPE_BANDWIDTH_LIMIT) def test_get_rule_types(self): - rule_types_mock = mock.PropertyMock( - return_value=qos_consts.VALID_RULE_TYPES) filters = {'type': 'type_id'} with mock.patch.object(qos_plugin.QoSPlugin, 'supported_rule_types', - new_callable=rule_types_mock): + return_value=qos_consts.VALID_RULE_TYPES): types = self.qos_plugin.get_rule_types(self.ctxt, filters=filters) self.assertEqual(sorted(qos_consts.VALID_RULE_TYPES), sorted(type_['type'] for type_ in types)) diff --git a/releasenotes/notes/qos-rule-type-filter-9c821e93b27fffe9.yaml b/releasenotes/notes/qos-rule-type-filter-9c821e93b27fffe9.yaml new file mode 100644 index 00000000000..dc1c54fc70d --- /dev/null +++ b/releasenotes/notes/qos-rule-type-filter-9c821e93b27fffe9.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + QoS rule type list accepts two filter flags: + + * ``all_supported``: if True, the listing call will print all QoS + rule types supported by at least one loaded mechanism driver. + * ``all_rules``: if True, the listing call will print all QoS rule + types supported by the Neutron server. + + Both filter flags are exclusive and not required.