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 5eade153418..e4fc9327797 100644 --- a/setup.cfg +++ b/setup.cfg @@ -235,6 +235,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