From 8e30639452312019de17f3e1e8364f579752944e Mon Sep 17 00:00:00 2001 From: LIU Yulong Date: Wed, 2 Jun 2021 15:38:02 +0800 Subject: [PATCH] [QoS] Add rule type packet per second (pps) This patch adds new API extension to QoS service plugin to allow CURD actions for packet rate limit (packet per second) rule in Neutron server side. NOTE: This patch will NOT implement the real functionality in L2/L3 backend to limit the pps. Co-Authored-By: NANALI Closes-bug: #1912460 Change-Id: Icc88accb88d9cec40c960c56f032c3c27317b42e --- .../alembic_migrations/versions/EXPAND_HEAD | 2 +- .../expand/1bb3393de75d_add_qos_pps_rule.py | 55 +++++ neutron/db/qos/models.py | 26 +++ neutron/extensions/qos.py | 9 +- neutron/extensions/qos_pps_rule.py | 60 ++++++ neutron/objects/qos/policy.py | 15 +- neutron/objects/qos/rule.py | 23 ++- neutron/objects/qos/rule_type.py | 7 +- neutron/services/qos/constants.py | 22 ++ neutron/services/qos/drivers/manager.py | 3 +- neutron/services/qos/qos_plugin.py | 20 +- neutron/tests/unit/objects/qos/test_policy.py | 27 ++- neutron/tests/unit/objects/qos/test_rule.py | 49 +++++ .../tests/unit/objects/qos/test_rule_type.py | 5 +- neutron/tests/unit/objects/test_objects.py | 11 +- .../unit/services/qos/test_qos_plugin.py | 188 +++++++++++++++++- .../qos_rule_type_pps-27254b90f26c10b6.yaml | 6 + setup.cfg | 1 + 18 files changed, 499 insertions(+), 30 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/xena/expand/1bb3393de75d_add_qos_pps_rule.py create mode 100644 neutron/extensions/qos_pps_rule.py create mode 100644 neutron/services/qos/constants.py create mode 100644 releasenotes/notes/qos_rule_type_pps-27254b90f26c10b6.yaml diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index d13d55f4767..c31bc14c105 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -8df53b0d2c0e +1bb3393de75d diff --git a/neutron/db/migration/alembic_migrations/versions/xena/expand/1bb3393de75d_add_qos_pps_rule.py b/neutron/db/migration/alembic_migrations/versions/xena/expand/1bb3393de75d_add_qos_pps_rule.py new file mode 100644 index 00000000000..f84d7adc589 --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/xena/expand/1bb3393de75d_add_qos_pps_rule.py @@ -0,0 +1,55 @@ +# Copyright (c) 2021 China Unicom Cloud Data Co.,Ltd. +# 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. +# + +"""add qos policy rule Packet Rate Limit + +Revision ID: 1bb3393de75d +Revises: 8df53b0d2c0e +Create Date: 2021-01-22 17:00:03.085196 + +""" + +from alembic import op +import sqlalchemy as sa + +from neutron_lib import constants + +# revision identifiers, used by Alembic. +revision = '1bb3393de75d' +down_revision = '8df53b0d2c0e' + +direction_enum = sa.Enum( + constants.EGRESS_DIRECTION, constants.INGRESS_DIRECTION, + name='qos_packet_rate_limit_rules_directions' +) + + +def upgrade(): + op.create_table( + 'qos_packet_rate_limit_rules', + sa.Column('id', sa.String(36), primary_key=True), + sa.Column('qos_policy_id', + sa.String(length=36), + sa.ForeignKey('qos_policies.id', ondelete='CASCADE'), + nullable=False, index=True), + sa.Column('max_kpps', sa.Integer()), + sa.Column('max_burst_kpps', sa.Integer()), + sa.Column('direction', direction_enum, + nullable=False, + server_default=constants.EGRESS_DIRECTION), + sa.UniqueConstraint('qos_policy_id', 'direction', + name='qos_packet_rate_limit_rules0qos_policy_id0direction') + ) diff --git a/neutron/db/qos/models.py b/neutron/db/qos/models.py index 8a64e6cd0b4..7d2345fc116 100644 --- a/neutron/db/qos/models.py +++ b/neutron/db/qos/models.py @@ -192,3 +192,29 @@ class QosMinimumBandwidthRule(model_base.HasId, model_base.BASEV2): name='qos_minimum_bandwidth_rules0qos_policy_id0direction'), model_base.BASEV2.__table_args__ ) + + +class QosPacketRateLimitRule(model_base.HasId, model_base.BASEV2): + __tablename__ = 'qos_packet_rate_limit_rules' + qos_policy_id = sa.Column(sa.String(36), + sa.ForeignKey('qos_policies.id', + ondelete='CASCADE'), + nullable=False, + index=True) + max_kpps = sa.Column(sa.Integer) + max_burst_kpps = sa.Column(sa.Integer) + revises_on_change = ('qos_policy',) + qos_policy = sa.orm.relationship(QosPolicy, load_on_pending=True) + direction = sa.Column( + sa.Enum(constants.EGRESS_DIRECTION, + constants.INGRESS_DIRECTION, + name="qos_packet_rate_limit_rules_directions"), + default=constants.EGRESS_DIRECTION, + server_default=constants.EGRESS_DIRECTION, + nullable=False) + __table_args__ = ( + sa.UniqueConstraint( + qos_policy_id, direction, + name="qos_packet_rate_limit_rules0qos_policy_id0direction"), + model_base.BASEV2.__table_args__ + ) diff --git a/neutron/extensions/qos.py b/neutron/extensions/qos.py index 7764f6615d7..82f2f15d0db 100644 --- a/neutron/extensions/qos.py +++ b/neutron/extensions/qos.py @@ -84,9 +84,12 @@ class QoSPluginBase(service_base.ServicePluginBase, metaclass=abc.ABCMeta): path_prefix = apidef.API_PREFIX # The rule object type to use for each incoming rule-related request. - rule_objects = {'bandwidth_limit': rule_object.QosBandwidthLimitRule, - 'dscp_marking': rule_object.QosDscpMarkingRule, - 'minimum_bandwidth': rule_object.QosMinimumBandwidthRule} + rule_objects = { + 'bandwidth_limit': rule_object.QosBandwidthLimitRule, + 'dscp_marking': rule_object.QosDscpMarkingRule, + 'minimum_bandwidth': rule_object.QosMinimumBandwidthRule, + 'packet_rate_limit': rule_object.QosPacketRateLimitRule, + } # Patterns used to call method proxies for all policy-rule-specific # method calls (see __getattr__ docstring, below). diff --git a/neutron/extensions/qos_pps_rule.py b/neutron/extensions/qos_pps_rule.py new file mode 100644 index 00000000000..03ef37b0947 --- /dev/null +++ b/neutron/extensions/qos_pps_rule.py @@ -0,0 +1,60 @@ +# Copyright (c) 2021 China Unicom Cloud Data Co.,Ltd. +# 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.definitions import qos_bw_minimum_ingress +from neutron_lib.api.definitions import qos_pps_rule as apidef +from neutron_lib.api import extensions as api_extensions +from neutron_lib.plugins import constants +from neutron_lib.plugins import directory + +from neutron.api import extensions +from neutron.api.v2 import base + +COLLECTION_NAME = 'packet_rate_limit_rules' +RESOURCE_NAME = 'packet_rate_limit_rule' + +# A quick align for subresource minimum bandwidth ingress direction. +# TODO(liuyulong): Move to neutron-lib +apidef.SUB_RESOURCE_ATTRIBUTE_MAP.update( + qos_bw_minimum_ingress.SUB_RESOURCE_ATTRIBUTE_MAP) + + +class Qos_pps_rule(api_extensions.APIExtensionDescriptor): + + api_definition = apidef + + @classmethod + def get_resources(cls): + plugin = directory.get_plugin(constants.QOS) + params = apidef.SUB_RESOURCE_ATTRIBUTE_MAP[ + COLLECTION_NAME]['parameters'] + parent = apidef.SUB_RESOURCE_ATTRIBUTE_MAP[ + COLLECTION_NAME]['parent'] + controller = base.create_resource( + COLLECTION_NAME, + RESOURCE_NAME, + plugin, + params, + parent=parent, + allow_pagination=True, + allow_sorting=True) + exts = [ + extensions.ResourceExtension( + COLLECTION_NAME, + controller, + parent, + attr_map=params) + ] + return exts diff --git a/neutron/objects/qos/policy.py b/neutron/objects/qos/policy.py index bb805873a05..bedcd2af85c 100644 --- a/neutron/objects/qos/policy.py +++ b/neutron/objects/qos/policy.py @@ -66,7 +66,8 @@ class QosPolicy(rbac_db.NeutronRbacObject): # Version 1.6: Added "is_default" field # Version 1.7: Added floating IP bindings # Version 1.8: Added router gateway QoS policy bindings - VERSION = '1.8' + # Version 1.9: Added QosPacketRateLimitRule + VERSION = '1.9' # required by RbacNeutronMetaclass rbac_db_cls = QosPolicyRBAC @@ -376,10 +377,22 @@ class QosPolicy(rbac_db.NeutronRbacObject): return set(bound_tenants) def obj_make_compatible(self, primitive, target_version): + def filter_rules(obj_names, rules): + return [rule for rule in rules if + rule['versioned_object.name'] in obj_names] _target_version = versionutils.convert_version_to_tuple(target_version) if _target_version < (1, 8): raise exception.IncompatibleObjectVersion( objver=target_version, objname=self.__class__.__name__) + names = [ + rule_obj_impl.QosBandwidthLimitRule.obj_name(), + rule_obj_impl.QosDscpMarkingRule.obj_name(), + rule_obj_impl.QosMinimumBandwidthRule.obj_name(), + ] + if _target_version >= (1, 9): + names.append(rule_obj_impl.QosPacketRateLimitRule.obj_name()) + if 'rules' in primitive and names: + primitive['rules'] = filter_rules(names, primitive['rules']) @base_db.NeutronObjectRegistry.register diff --git a/neutron/objects/qos/rule.py b/neutron/objects/qos/rule.py index 165c0901000..049820217fd 100644 --- a/neutron/objects/qos/rule.py +++ b/neutron/objects/qos/rule.py @@ -26,6 +26,7 @@ from oslo_versionedobjects import fields as obj_fields from neutron.db.qos import models as qos_db_model from neutron.objects import base +from neutron.services.qos import constants as qos_constants DSCP_MARK = 'dscp_mark' @@ -36,7 +37,7 @@ def get_rules(obj_cls, context, qos_policy_id): return all_rules with obj_cls.db_context_reader(context): - for rule_type in qos_consts.VALID_RULE_TYPES: + for rule_type in qos_constants.VALID_RULE_TYPES: rule_cls_name = 'Qos%sRule' % helpers.camelize(rule_type) rule_cls = getattr(sys.modules[__name__], rule_cls_name) @@ -50,11 +51,12 @@ class QosRule(base.NeutronDbObject, metaclass=abc.ABCMeta): # 1.1: Added DscpMarkingRule # 1.2: Added QosMinimumBandwidthRule # 1.3: Added direction for BandwidthLimitRule + # 1.4: Added PacketRateLimitRule # # NOTE(mangelajo): versions need to be handled from the top QosRule object # because it's the only reference QosPolicy can make # to them via obj_relationships version map - VERSION = '1.3' + VERSION = '1.4' fields = { 'id': common_types.UUIDField(), @@ -167,3 +169,20 @@ class QosMinimumBandwidthRule(QosRule): duplicates_compare_fields = ['direction'] rule_type = qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH + + +@base.NeutronObjectRegistry.register +class QosPacketRateLimitRule(QosRule): + + db_model = qos_db_model.QosPacketRateLimitRule + + fields = { + 'max_kpps': obj_fields.IntegerField(nullable=True), + 'max_burst_kpps': obj_fields.IntegerField(nullable=True), + 'direction': common_types.FlowDirectionEnumField( + default=constants.EGRESS_DIRECTION) + } + + duplicates_compare_fields = ['direction'] + + rule_type = qos_constants.RULE_TYPE_PACKET_RATE_LIMIT diff --git a/neutron/objects/qos/rule_type.py b/neutron/objects/qos/rule_type.py index 1a94fff09cf..ced31370cdd 100644 --- a/neutron/objects/qos/rule_type.py +++ b/neutron/objects/qos/rule_type.py @@ -13,17 +13,17 @@ from neutron_lib.objects import common_types from neutron_lib.plugins import constants from neutron_lib.plugins import directory -from neutron_lib.services.qos import constants as qos_consts from oslo_versionedobjects import fields as obj_fields from neutron.objects import base +from neutron.services.qos import constants as qos_constants class RuleTypeField(obj_fields.BaseEnumField): def __init__(self, **kwargs): self.AUTO_TYPE = obj_fields.Enum( - valid_values=qos_consts.VALID_RULE_TYPES) + valid_values=qos_constants.VALID_RULE_TYPES) super(RuleTypeField, self).__init__(**kwargs) @@ -33,7 +33,8 @@ class QosRuleType(base.NeutronObject): # Version 1.1: Added QosDscpMarkingRule # Version 1.2: Added QosMinimumBandwidthRule # Version 1.3: Added drivers field - VERSION = '1.3' + # Version 1.4: Added QosPacketRateLimitRule + VERSION = '1.4' fields = { 'type': RuleTypeField(), diff --git a/neutron/services/qos/constants.py b/neutron/services/qos/constants.py new file mode 100644 index 00000000000..576c3f54412 --- /dev/null +++ b/neutron/services/qos/constants.py @@ -0,0 +1,22 @@ +# +# 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.services.qos import constants as qos_consts + +# TODO(liuyulong): Because of the development sequence, the rule must +# be implemented in Neutron first. Then the following can be moved +# to neutron-lib after neutron has the new rule. +# Add qos rule packet rate limit +RULE_TYPE_PACKET_RATE_LIMIT = 'packet_rate_limit' +VALID_RULE_TYPES = qos_consts.VALID_RULE_TYPES + [RULE_TYPE_PACKET_RATE_LIMIT] diff --git a/neutron/services/qos/drivers/manager.py b/neutron/services/qos/drivers/manager.py index 13818065d59..494cc9b0936 100644 --- a/neutron/services/qos/drivers/manager.py +++ b/neutron/services/qos/drivers/manager.py @@ -24,6 +24,7 @@ from neutron.api.rpc.callbacks.producer import registry as rpc_registry from neutron.api.rpc.callbacks import resources from neutron.api.rpc.handlers import resources_rpc from neutron.objects.qos import policy as policy_object +from neutron.services.qos import constants as qos_constants LOG = logging.getLogger(__name__) @@ -168,7 +169,7 @@ class QosServiceDriverManager(object): if not self._drivers: return [] - rule_types = set(qos_consts.VALID_RULE_TYPES) + rule_types = set(qos_constants.VALID_RULE_TYPES) # Recalculate on every call to allow drivers determine supported rule # types dynamically diff --git a/neutron/services/qos/qos_plugin.py b/neutron/services/qos/qos_plugin.py index 3ac3e992725..7c844b7997b 100644 --- a/neutron/services/qos/qos_plugin.py +++ b/neutron/services/qos/qos_plugin.py @@ -22,6 +22,7 @@ from neutron_lib.api.definitions import qos_bw_limit_direction from neutron_lib.api.definitions import qos_bw_minimum_ingress from neutron_lib.api.definitions import qos_default from neutron_lib.api.definitions import qos_port_network_policy +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_rules_alias from neutron_lib.callbacks import events as callbacks_events @@ -64,14 +65,17 @@ class QoSPlugin(qos.QoSPluginBase): service parameters over ports and networks. """ - supported_extension_aliases = [qos_apidef.ALIAS, - qos_bw_limit_direction.ALIAS, - qos_default.ALIAS, - qos_rule_type_details.ALIAS, - port_resource_request.ALIAS, - qos_bw_minimum_ingress.ALIAS, - qos_rules_alias.ALIAS, - qos_port_network_policy.ALIAS] + supported_extension_aliases = [ + qos_apidef.ALIAS, + qos_bw_limit_direction.ALIAS, + qos_default.ALIAS, + qos_rule_type_details.ALIAS, + port_resource_request.ALIAS, + qos_bw_minimum_ingress.ALIAS, + qos_rules_alias.ALIAS, + qos_port_network_policy.ALIAS, + qos_pps_rule.ALIAS, + ] __native_pagination_support = True __native_sorting_support = True diff --git a/neutron/tests/unit/objects/qos/test_policy.py b/neutron/tests/unit/objects/qos/test_policy.py index d4490bd5d12..8e1b7fb4ade 100644 --- a/neutron/tests/unit/objects/qos/test_policy.py +++ b/neutron/tests/unit/objects/qos/test_policy.py @@ -13,6 +13,7 @@ import random from unittest import mock +from neutron_lib import constants as lib_consts from neutron_lib.exceptions import qos as qos_exc from neutron_lib.services.qos import constants as qos_consts from oslo_utils import uuidutils @@ -24,6 +25,7 @@ from neutron.objects import ports as port_obj from neutron.objects.qos import binding from neutron.objects.qos import policy from neutron.objects.qos import rule +from neutron.services.qos import constants as q_consts from neutron.tests.unit.objects import test_base from neutron.tests.unit import testlib_api @@ -32,6 +34,7 @@ RULE_OBJ_CLS = { qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: rule.QosBandwidthLimitRule, qos_consts.RULE_TYPE_DSCP_MARKING: rule.QosDscpMarkingRule, qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: rule.QosMinimumBandwidthRule, + q_consts.RULE_TYPE_PACKET_RATE_LIMIT: rule.QosPacketRateLimitRule, } @@ -173,7 +176,10 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, for rule_type in rule_type): rule_fields = self.get_random_object_fields(obj_cls=obj_cls) rule_fields['qos_policy_id'] = policy_obj.id - if (obj_cls.rule_type == qos_consts.RULE_TYPE_BANDWIDTH_LIMIT and + if (obj_cls.rule_type in [ + qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, + q_consts.RULE_TYPE_PACKET_RATE_LIMIT] and bwlimit_direction is not None): rule_fields['direction'] = bwlimit_direction rule_obj = obj_cls(self.context, **rule_fields) @@ -184,6 +190,11 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, policy_obj.obj_load_attr('rules') return policy_obj, rules + @staticmethod + def _policy_through_version(obj, version): + primitive = obj.obj_to_primitive(target_version=version) + return policy.QosPolicy.clean_obj_from_primitive(primitive) + def test_attach_network_get_network_policy(self): obj = self._create_test_policy() @@ -456,6 +467,20 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, self.assertRaises(exception.IncompatibleObjectVersion, policy_obj.obj_to_primitive, '1.7') + def test_object_version_degradation_less_than_1_9(self): + policy_obj, rule_objs = self._create_test_policy_with_rules( + [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, + qos_consts.RULE_TYPE_DSCP_MARKING, + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, + q_consts.RULE_TYPE_PACKET_RATE_LIMIT], reload_rules=True, + bwlimit_direction=lib_consts.INGRESS_DIRECTION) + policy_obj_v1_8 = self._policy_through_version(policy_obj, '1.8') + + self.assertIn(rule_objs[0], policy_obj_v1_8.rules) + self.assertIn(rule_objs[1], policy_obj_v1_8.rules) + self.assertIn(rule_objs[2], policy_obj_v1_8.rules) + self.assertNotIn(rule_objs[3], policy_obj_v1_8.rules) + @mock.patch.object(policy.QosPolicy, 'unset_default') def test_filter_by_shared(self, *mocks): project_id = uuidutils.generate_uuid() diff --git a/neutron/tests/unit/objects/qos/test_rule.py b/neutron/tests/unit/objects/qos/test_rule.py index a8064137dee..e225a9a3356 100644 --- a/neutron/tests/unit/objects/qos/test_rule.py +++ b/neutron/tests/unit/objects/qos/test_rule.py @@ -18,6 +18,7 @@ from oslo_versionedobjects import exception from neutron.objects.qos import policy from neutron.objects.qos import rule +from neutron.services.qos import constants as qos_constants from neutron.tests import base as neutron_test_base from neutron.tests.unit.objects import test_base from neutron.tests.unit import testlib_api @@ -241,3 +242,51 @@ class QosMinimumBandwidthRuleDbObjectTestCase(test_base.BaseDbObjectTestCase, id=generated_qos_policy_id, project_id=uuidutils.generate_uuid()) policy_obj.create() + + +class QosPacketRateLimitRuleObjectTestCase(test_base.BaseObjectIfaceTestCase): + + _test_class = rule.QosPacketRateLimitRule + + def test_to_dict_returns_type(self): + obj = rule.QosPacketRateLimitRule(self.context, **self.db_objs[0]) + dict_ = obj.to_dict() + self.assertEqual(qos_constants.RULE_TYPE_PACKET_RATE_LIMIT, + dict_['type']) + + def test_duplicate_rules(self): + policy_id = uuidutils.generate_uuid() + ingress_rule_1 = rule.QosPacketRateLimitRule( + self.context, qos_policy_id=policy_id, + max_kpps=2000, max_burst=800, + direction=constants.INGRESS_DIRECTION) + ingress_rule_2 = rule.QosPacketRateLimitRule( + self.context, qos_policy_id=policy_id, + max_kpps=3000, max_burst=200, + direction=constants.INGRESS_DIRECTION) + egress_rule = rule.QosPacketRateLimitRule( + self.context, qos_policy_id=policy_id, + max_kpps=1000, max_burst=500, + direction=constants.EGRESS_DIRECTION) + dscp_rule = rule.QosDscpMarkingRule( + self.context, qos_policy_id=policy_id, dscp_mark=16) + self.assertTrue(ingress_rule_1.duplicates(ingress_rule_2)) + self.assertFalse(ingress_rule_1.duplicates(egress_rule)) + self.assertFalse(ingress_rule_1.duplicates(dscp_rule)) + + +class QosPacketRateLimitRuleDbObjectTestCase(test_base.BaseDbObjectTestCase, + testlib_api.SqlTestCase): + + _test_class = rule.QosPacketRateLimitRule + + def setUp(self): + super(QosPacketRateLimitRuleDbObjectTestCase, self).setUp() + + # Prepare policy to be able to insert a rule + for obj in self.db_objs: + generated_qos_policy_id = obj['qos_policy_id'] + policy_obj = policy.QosPolicy(self.context, + id=generated_qos_policy_id, + project_id=uuidutils.generate_uuid()) + policy_obj.create() diff --git a/neutron/tests/unit/objects/qos/test_rule_type.py b/neutron/tests/unit/objects/qos/test_rule_type.py index ea7304618d3..43d8807f082 100644 --- a/neutron/tests/unit/objects/qos/test_rule_type.py +++ b/neutron/tests/unit/objects/qos/test_rule_type.py @@ -22,6 +22,7 @@ from oslo_config import cfg from neutron import manager from neutron.objects.qos import rule_type +from neutron.services.qos import constants as qos_constants from neutron.services.qos import qos_plugin from neutron.tests import base as test_base @@ -78,11 +79,11 @@ class QosRuleTypeObjectTestCase(test_base.BaseTestCase): def test_get_objects(self): rule_types_mock = mock.PropertyMock( - return_value=set(qos_consts.VALID_RULE_TYPES)) + return_value=set(qos_constants.VALID_RULE_TYPES)) with mock.patch.object(qos_plugin.QoSPlugin, 'supported_rule_types', new_callable=rule_types_mock): types = rule_type.QosRuleType.get_objects() - self.assertEqual(sorted(qos_consts.VALID_RULE_TYPES), + self.assertEqual(sorted(qos_constants.VALID_RULE_TYPES), sorted(type_['type'] for type_ in types)) def test_wrong_type(self): diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index 0c7e975b9f4..511b18e3447 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -82,13 +82,14 @@ object_data = { 'PortUplinkStatusPropagation': '1.1-f0a4ca451a941910376c33616dea5de2', 'ProviderResourceAssociation': '1.0-05ab2d5a3017e5ce9dd381328f285f34', 'ProvisioningBlock': '1.0-c19d6d05bfa8143533471c1296066125', - 'QosBandwidthLimitRule': '1.3-51b662b12a8d1dfa89288d826c6d26d3', - 'QosDscpMarkingRule': '1.3-0313c6554b34fd10c753cb63d638256c', - 'QosMinimumBandwidthRule': '1.3-314c3419f4799067cc31cc319080adff', + 'QosBandwidthLimitRule': '1.4-51b662b12a8d1dfa89288d826c6d26d3', + 'QosDscpMarkingRule': '1.4-0313c6554b34fd10c753cb63d638256c', + 'QosMinimumBandwidthRule': '1.4-314c3419f4799067cc31cc319080adff', + 'QosPacketRateLimitRule': '1.4-18411fa95f54602b8c8a5da2d3194b31', 'QosPolicyRBAC': '1.1-192845c5ed0718e1c54fac36936fcd7d', - 'QosRuleType': '1.3-7286188edeb3a0386f9cf7979b9700fc', + 'QosRuleType': '1.4-a5b870dfa6f510a91f5cb0216873064e', 'QosRuleTypeDriver': '1.0-7d8cb9f0ef661ac03700eae97118e3db', - 'QosPolicy': '1.8-4adb0cde3102c10d8970ec9487fd7fe7', + 'QosPolicy': '1.9-4adb0cde3102c10d8970ec9487fd7fe7', 'QosPolicyDefault': '1.0-59e5060eedb1f06dd0935a244d27d11c', 'QosPolicyFloatingIPBinding': '1.0-5625df4205a18778cd6aa40f99be024e', 'QosPolicyRouterGatewayIPBinding': '1.0-da064fbfe5ee18c950b905b483bf59e3', diff --git a/neutron/tests/unit/services/qos/test_qos_plugin.py b/neutron/tests/unit/services/qos/test_qos_plugin.py index 65b745bc4bc..4ae2af3e1f5 100644 --- a/neutron/tests/unit/services/qos/test_qos_plugin.py +++ b/neutron/tests/unit/services/qos/test_qos_plugin.py @@ -38,6 +38,7 @@ from neutron.objects import network as network_object from neutron.objects import ports as ports_object from neutron.objects.qos import policy as policy_object from neutron.objects.qos import rule as rule_object +from neutron.services.qos import constants as qos_constants from neutron.services.qos import qos_plugin from neutron.tests.unit.db import test_db_base_plugin_v2 from neutron.tests.unit.services.qos import base @@ -98,7 +99,11 @@ class TestQosPlugin(base.BaseQosTestCase): 'dscp_mark': 16}, 'minimum_bandwidth_rule': { 'id': uuidutils.generate_uuid(), - 'min_kbps': 10}} + 'min_kbps': 10}, + 'packet_rate_limit_rule': { + 'id': uuidutils.generate_uuid(), + 'max_kpps': 20, + 'max_burst_kpps': 130}} self.policy = policy_object.QosPolicy( self.ctxt, **self.policy_data['policy']) @@ -112,6 +117,9 @@ class TestQosPlugin(base.BaseQosTestCase): self.min_rule = rule_object.QosMinimumBandwidthRule( self.ctxt, **self.rule_data['minimum_bandwidth_rule']) + self.pps_rule = rule_object.QosPacketRateLimitRule( + self.ctxt, **self.rule_data['packet_rate_limit_rule']) + def _validate_driver_params(self, method_name, ctxt): call_args = self.qos_plugin.driver_manager.call.call_args[0] self.assertTrue(self.qos_plugin.driver_manager.call.called) @@ -1025,12 +1033,12 @@ class TestQosPlugin(base.BaseQosTestCase): def test_get_rule_types(self): rule_types_mock = mock.PropertyMock( - return_value=qos_consts.VALID_RULE_TYPES) + return_value=qos_constants.VALID_RULE_TYPES) filters = {'type': 'type_id'} with mock.patch.object(qos_plugin.QoSPlugin, 'supported_rule_types', new_callable=rule_types_mock): types = self.qos_plugin.get_rule_types(self.ctxt, filters=filters) - self.assertEqual(sorted(qos_consts.VALID_RULE_TYPES), + self.assertEqual(sorted(qos_constants.VALID_RULE_TYPES), sorted(type_['type'] for type_ in types)) @mock.patch('neutron.objects.ports.Port') @@ -1078,6 +1086,180 @@ class TestQosPlugin(base.BaseQosTestCase): self.assertLess( action_index, mock_manager.mock_calls.index(driver_mock_call)) + def test_create_policy_packet_rate_limit_rule(self): + _policy = policy_object.QosPolicy( + self.ctxt, **self.policy_data['policy']) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + setattr(_policy, "rules", [self.pps_rule]) + self.qos_plugin.create_policy_packet_rate_limit_rule( + self.ctxt, self.policy.id, self.rule_data) + self._validate_driver_params('update_policy', self.ctxt) + + def test_create_policy_pps_rule_duplicates(self): + _policy = self._get_policy() + setattr(_policy, "rules", [self.pps_rule]) + new_rule_data = { + 'packet_rate_limit_rule': { + 'max_kpps': 400, + 'direction': self.pps_rule.direction + } + } + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy) as mock_qos_get_obj: + self.assertRaises( + qos_exc.QoSRulesConflict, + self.qos_plugin.create_policy_packet_rate_limit_rule, + self.ctxt, _policy.id, new_rule_data) + mock_qos_get_obj.assert_called_once_with(self.ctxt, id=_policy.id) + + def test_update_policy_packet_rate_limit_rule(self): + _policy = policy_object.QosPolicy( + self.ctxt, **self.policy_data['policy']) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + setattr(_policy, "rules", [self.pps_rule]) + self.qos_plugin.update_policy_packet_rate_limit_rule( + self.ctxt, self.pps_rule.id, self.policy.id, self.rule_data) + self._validate_driver_params('update_policy', self.ctxt) + + def test_update_policy_pps_rule_bad_policy(self): + _policy = policy_object.QosPolicy( + self.ctxt, **self.policy_data['policy']) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + setattr(_policy, "rules", []) + self.assertRaises( + qos_exc.QosRuleNotFound, + self.qos_plugin.update_policy_packet_rate_limit_rule, + self.ctxt, self.pps_rule.id, self.policy.id, + self.rule_data) + + def test_delete_policy_packet_rate_limit_rule(self): + _policy = policy_object.QosPolicy( + self.ctxt, **self.policy_data['policy']) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + setattr(_policy, "rules", [self.pps_rule]) + self.qos_plugin.delete_policy_packet_rate_limit_rule( + self.ctxt, self.pps_rule.id, self.policy.id) + self._validate_driver_params('update_policy', self.ctxt) + + def test_delete_policy_pps_rule_bad_policy(self): + _policy = policy_object.QosPolicy( + self.ctxt, **self.policy_data['policy']) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + setattr(_policy, "rules", []) + self.assertRaises( + qos_exc.QosRuleNotFound, + self.qos_plugin.delete_policy_packet_rate_limit_rule, + self.ctxt, self.pps_rule.id, _policy.id) + + def test_get_policy_packet_rate_limit_rule(self): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=self.policy): + with mock.patch('neutron.objects.qos.rule.' + 'QosPacketRateLimitRule.' + 'get_object') as get_object_mock: + self.qos_plugin.get_policy_packet_rate_limit_rule( + self.ctxt, self.pps_rule.id, self.policy.id) + get_object_mock.assert_called_once_with(self.ctxt, + id=self.pps_rule.id) + + def test_get_policy_packet_rate_limit_rules_for_policy(self): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=self.policy): + with mock.patch('neutron.objects.qos.rule.' + 'QosPacketRateLimitRule.' + 'get_objects') as get_objects_mock: + self.qos_plugin.get_policy_packet_rate_limit_rules( + self.ctxt, self.policy.id) + get_objects_mock.assert_called_once_with( + self.ctxt, _pager=mock.ANY, qos_policy_id=self.policy.id) + + def test_get_policy_packet_rate_limit_rules_for_policy_with_filters(self): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=self.policy): + with mock.patch('neutron.objects.qos.rule.' + 'QosPacketRateLimitRule.' + 'get_objects') as get_objects_mock: + filters = {'filter': 'filter_id'} + self.qos_plugin.get_policy_packet_rate_limit_rules( + self.ctxt, self.policy.id, filters=filters) + get_objects_mock.assert_called_once_with( + self.ctxt, _pager=mock.ANY, + qos_policy_id=self.policy.id, + filter='filter_id') + + def test_get_policy_packet_rate_limit_rule_for_nonexistent_policy(self): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=None): + self.assertRaises( + qos_exc.QosPolicyNotFound, + self.qos_plugin.get_policy_packet_rate_limit_rule, + self.ctxt, self.pps_rule.id, self.policy.id) + + def test_get_policy_packet_rate_limit_rules_for_nonexistent_policy(self): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=None): + self.assertRaises( + qos_exc.QosPolicyNotFound, + self.qos_plugin.get_policy_packet_rate_limit_rules, + self.ctxt, self.policy.id) + + def test_create_policy_pps_rule_for_nonexistent_policy(self): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=None): + self.assertRaises( + qos_exc.QosPolicyNotFound, + self.qos_plugin.create_policy_packet_rate_limit_rule, + self.ctxt, self.policy.id, self.rule_data) + + def test_update_policy_pps_rule_for_nonexistent_policy(self): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=None): + self.assertRaises( + qos_exc.QosPolicyNotFound, + self.qos_plugin.update_policy_packet_rate_limit_rule, + self.ctxt, self.pps_rule.id, self.policy.id, self.rule_data) + + def test_delete_policy_pps_rule_for_nonexistent_policy(self): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=None): + self.assertRaises( + qos_exc.QosPolicyNotFound, + self.qos_plugin.delete_policy_packet_rate_limit_rule, + self.ctxt, self.pps_rule.id, self.policy.id) + + def test_get_pps_rule_type(self): + admin_ctxt = context.get_admin_context() + drivers_details = [{ + 'name': 'fake-driver', + 'supported_parameters': [{ + 'parameter_name': 'max_kpps', + 'parameter_type': lib_constants.VALUES_TYPE_RANGE, + 'parameter_range': {'start': 0, 'end': 100} + }] + }] + with mock.patch.object( + qos_plugin.QoSPlugin, "supported_rule_type_details", + return_value=drivers_details + ): + rule_type_details = self.qos_plugin.get_rule_type( + admin_ctxt, qos_constants.RULE_TYPE_PACKET_RATE_LIMIT) + self.assertEqual( + qos_constants.RULE_TYPE_PACKET_RATE_LIMIT, + rule_type_details['type']) + self.assertEqual( + drivers_details, rule_type_details['drivers']) + + def test_get_pps_rule_type_as_user(self): + self.assertRaises( + lib_exc.NotAuthorized, + self.qos_plugin.get_rule_type, + self.ctxt, qos_constants.RULE_TYPE_PACKET_RATE_LIMIT) + class QoSRuleAliasTestExtensionManager(object): diff --git a/releasenotes/notes/qos_rule_type_pps-27254b90f26c10b6.yaml b/releasenotes/notes/qos_rule_type_pps-27254b90f26c10b6.yaml new file mode 100644 index 00000000000..ffc68e69968 --- /dev/null +++ b/releasenotes/notes/qos_rule_type_pps-27254b90f26c10b6.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added new API extension to QoS service plugin + to support CRUD actions for packet rate limit (packet per + second) rule in Neutron server side. diff --git a/setup.cfg b/setup.cfg index e958fc5d7aa..ef7256b9a5a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -234,6 +234,7 @@ neutron.objects = QosBandwidthLimitRule = neutron.objects.qos.rule:QosBandwidthLimitRule QosDscpMarkingRule = neutron.objects.qos.rule:QosDscpMarkingRule QosMinimumBandwidthRule = neutron.objects.qos.rule:QosMinimumBandwidthRule + QosPacketRateLimitRule = neutron.objects.qos.rule:QosPacketRateLimitRule QosPolicy = neutron.objects.qos.policy:QosPolicy QosPolicyDefault = neutron.objects.qos.policy:QosPolicyDefault QosPolicyFloatingIPBinding = neutron.objects.qos.binding:QosPolicyFloatingIPBinding