From 56044db26d97c65c7baec1a16623eff3a1614949 Mon Sep 17 00:00:00 2001 From: Przemyslaw Szczerbik Date: Mon, 12 Jul 2021 09:02:02 +0200 Subject: [PATCH] Add API extension for QoS minimum pps rule This patch implements support for CRUD operations for QoS minimum packet rate, for example: DELETE /qos/policies/$POLICY_ID/minimum_packet_rate_rules/$RULE_ID Placement or dataplane enforcement is not implemented yet. Partial-Bug: #1922237 See-Also: https://review.opendev.org/785236 Change-Id: Ie994bdab62bab33737f25287e568519c782dea9a --- doc/source/admin/config-qos.rst | 39 +- .../internals/quality_of_service.rst | 5 + neutron/api/v2/resource_helper.py | 13 +- neutron/conf/policies/qos.py | 55 +++ .../alembic_migrations/versions/EXPAND_HEAD | 2 +- ...1bb1d89e4_qos_minimum_packet_rate_rules.py | 56 +++ neutron/db/qos/models.py | 24 ++ neutron/extensions/qos.py | 1 + neutron/extensions/qos_pps_minimum_rule.py | 35 ++ neutron/objects/qos/policy.py | 5 +- neutron/objects/qos/qos_policy_validator.py | 33 ++ neutron/objects/qos/rule.py | 18 +- neutron/objects/qos/rule_type.py | 3 +- neutron/services/qos/constants.py | 3 + neutron/services/qos/qos_plugin.py | 25 +- neutron/tests/tools.py | 4 + neutron/tests/unit/conf/policies/test_qos.py | 169 ++++++++ neutron/tests/unit/objects/qos/test_policy.py | 28 +- neutron/tests/unit/objects/qos/test_rule.py | 51 +++ neutron/tests/unit/objects/test_base.py | 2 + neutron/tests/unit/objects/test_objects.py | 13 +- .../unit/services/qos/test_qos_plugin.py | 373 ++++++++++++++++-- ...os_rule_type_min_pps-0cc3fe5b0ee5d596.yaml | 5 + setup.cfg | 1 + 24 files changed, 906 insertions(+), 57 deletions(-) create mode 100644 neutron/db/migration/alembic_migrations/versions/yoga/expand/c181bb1d89e4_qos_minimum_packet_rate_rules.py create mode 100644 neutron/extensions/qos_pps_minimum_rule.py create mode 100644 releasenotes/notes/qos_rule_type_min_pps-0cc3fe5b0ee5d596.yaml diff --git a/doc/source/admin/config-qos.rst b/doc/source/admin/config-qos.rst index dcdb7f90423..4f1dc09a951 100644 --- a/doc/source/admin/config-qos.rst +++ b/doc/source/admin/config-qos.rst @@ -38,24 +38,28 @@ QoS supported rule types are now available as ``VALID_RULE_TYPES`` in `QoS rule * minimum_bandwidth: Minimum bandwidth constraints on certain types of traffic. +* minimum_packet_rate: Minimum packet rate constraints on certain types of traffic. + Any QoS driver can claim support for some QoS rule types by providing a driver property called ``supported_rules``, the QoS driver manager will recalculate rule types -dynamically that the QoS driver supports. +dynamically that the QoS driver supports. In the most simple case, the +property can be represented by a simple Python list defined on the class. The following table shows the Networking back ends, QoS supported rules, and traffic directions (from the VM point of view). .. table:: **Networking back ends, supported rules, and traffic direction** - ==================== ======================= ======================= =================== =================== - Rule \\ back end Open vSwitch SR-IOV Linux bridge OVN - ==================== ======================= ======================= =================== =================== - Bandwidth limit Egress \\ Ingress Egress (1) Egress \\ Ingress Egress \\ Ingress - Minimum bandwidth Egress \\ Ingress (2) Egress \\ Ingress (2) - - - DSCP marking Egress - Egress Egress - ==================== ======================= ======================= =================== =================== + ==================== ============================= ======================= =================== =================== + Rule \\ back end Open vSwitch SR-IOV Linux bridge OVN + ==================== ============================= ======================= =================== =================== + Bandwidth limit Egress \\ Ingress Egress (1) Egress \\ Ingress Egress \\ Ingress + Minimum bandwidth Egress \\ Ingress (2) Egress \\ Ingress (2) - - + Minimum packet rate - - - - + DSCP marking Egress - Egress Egress + ==================== ============================= ======================= =================== =================== .. note:: @@ -78,8 +82,14 @@ traffic directions (from the VM point of view). (1) Since Newton (2) Since Stein -In the most simple case, the property can be represented by a simple Python -list defined on the class. +.. table:: **Neutron backends, supported directions and enforcement types for Minimum Packet Rate rule** + + ============================ ==================== ==================== ============== ===== + Enforcement type \ Backend Open vSwitch SR-IOV Linux Bridge OVN + ============================ ==================== ==================== ============== ===== + Dataplane - - - - + Placement - - - - + ============================ ==================== ==================== ============== ===== For an ml2 plug-in, the list of supported QoS rule types and parameters is defined as a common subset of rules supported by all active mechanism drivers. @@ -328,6 +338,15 @@ To enable minimum bandwidth rule: "delete_policy_minimum_bandwidth_rule": "rule:regular_user", "update_policy_minimum_bandwidth_rule": "rule:regular_user", +To enable minimum packet rate rule: + +.. code-block:: none + + "get_policy_minimum_packet_rate_rule": "rule:regular_user", + "create_policy_minimum_packet_rate_rule": "rule:regular_user", + "delete_policy_minimum_packet_rate_rule": "rule:regular_user", + "update_policy_minimum_packet_rate_rule": "rule:regular_user", + User workflow ~~~~~~~~~~~~~ diff --git a/doc/source/contributor/internals/quality_of_service.rst b/doc/source/contributor/internals/quality_of_service.rst index b17039cbb55..65ab290d53a 100644 --- a/doc/source/contributor/internals/quality_of_service.rst +++ b/doc/source/contributor/internals/quality_of_service.rst @@ -151,6 +151,8 @@ From database point of view, following objects are defined in schema: bits for egress traffic. * QosMinimumBandwidthRule: defines the rule that creates a minimum bandwidth constraint. +* QosMinimumPacketRateRule: defines the rule that creates a minimum packet rate + constraint. All database models are defined under: @@ -176,6 +178,9 @@ For QoS, the following neutron objects are implemented: characterized by a min_kbps parameter. This rule has also a direction parameter to set the traffic direction, from the instance point of view. The only direction now implemented is egress. +* QosMinimumPacketRateRule: defines the minimum assured packet rate rule type, + characterized by a min_kpps parameter. This rule has also a direction + parameter to set the traffic direction, from the instance point of view. Those are defined in: diff --git a/neutron/api/v2/resource_helper.py b/neutron/api/v2/resource_helper.py index 0643f134b1a..53e2516f204 100644 --- a/neutron/api/v2/resource_helper.py +++ b/neutron/api/v2/resource_helper.py @@ -79,6 +79,13 @@ def build_resource_info(plural_mappings, resource_map, which_service, for collection_name in resource_map: resource_name = plural_mappings[collection_name] params = resource_map.get(collection_name, {}) + # If SUB_RESOURCE_ATTRIBUTE_MAP was passed in as a resource_map, we + # need special handling for it. SUB_RESOURCE_ATTRIBUTE_MAP must have + # a 'parent' and 'parameters' keys. 'parameters' key is going to + # contain sub-resources that are being extended. + parent = params.get('parent') + params = params['parameters'] if params.get('parameters') else params + if translate_name: collection_name = collection_name.replace('_', '-') if register_quota: @@ -89,12 +96,14 @@ def build_resource_info(plural_mappings, resource_map, which_service, member_actions=member_actions, allow_bulk=allow_bulk, allow_pagination=True, - allow_sorting=True) + allow_sorting=True, + parent=parent) resource = extensions.ResourceExtension( collection_name, controller, path_prefix=path_prefix, member_actions=member_actions, - attr_map=params) + attr_map=params, + parent=parent) resources.append(resource) return resources diff --git a/neutron/conf/policies/qos.py b/neutron/conf/policies/qos.py index 5b2d975a673..528abbe08bc 100644 --- a/neutron/conf/policies/qos.py +++ b/neutron/conf/policies/qos.py @@ -343,6 +343,61 @@ rules = [ deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY) ), + policy.DocumentedRuleDefault( + name='get_policy_minimum_packet_rate_rule', + check_str=base.SYSTEM_OR_PROJECT_READER, + scope_types=['system', 'project'], + description='Get a QoS minimum packet rate rule', + operations=[ + { + 'method': 'GET', + 'path': '/qos/policies/{policy_id}/minimum_packet_rate_rules', + }, + { + 'method': 'GET', + 'path': ('/qos/policies/{policy_id}/' + 'minimum_packet_rate_rules/{rule_id}'), + }, + ], + ), + policy.DocumentedRuleDefault( + name='create_policy_minimum_packet_rate_rule', + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Create a QoS minimum packet rate rule', + operations=[ + { + 'method': 'POST', + 'path': '/qos/policies/{policy_id}/minimum_packet_rate_rules', + }, + ], + ), + policy.DocumentedRuleDefault( + name='update_policy_minimum_packet_rate_rule', + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Update a QoS minimum packet rate rule', + operations=[ + { + 'method': 'PUT', + 'path': ('/qos/policies/{policy_id}/' + 'minimum_packet_rate_rules/{rule_id}'), + }, + ], + ), + policy.DocumentedRuleDefault( + name='delete_policy_minimum_packet_rate_rule', + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Delete a QoS minimum packet rate rule', + operations=[ + { + 'method': 'DELETE', + 'path': ('/qos/policies/{policy_id}/' + 'minimum_packet_rate_rules/{rule_id}'), + }, + ], + ), policy.DocumentedRuleDefault( name='get_alias_bandwidth_limit_rule', check_str='rule:get_policy_bandwidth_limit_rule', diff --git a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD index c31bc14c105..1fabf4a11aa 100644 --- a/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD +++ b/neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD @@ -1 +1 @@ -1bb3393de75d +c181bb1d89e4 diff --git a/neutron/db/migration/alembic_migrations/versions/yoga/expand/c181bb1d89e4_qos_minimum_packet_rate_rules.py b/neutron/db/migration/alembic_migrations/versions/yoga/expand/c181bb1d89e4_qos_minimum_packet_rate_rules.py new file mode 100644 index 00000000000..69d491c934a --- /dev/null +++ b/neutron/db/migration/alembic_migrations/versions/yoga/expand/c181bb1d89e4_qos_minimum_packet_rate_rules.py @@ -0,0 +1,56 @@ +# Copyright (c) 2021 Ericsson Software Technology +# +# 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 alembic import op +from neutron_lib import constants as n_const +from neutron_lib.db import constants as db_const +import sqlalchemy as sa + +from neutron.db import migration + + +"""qos_minimum_packet_rate_rules + +Revision ID: c181bb1d89e4 +Revises: 1bb3393de75d +Create Date: 2021-07-09 15:47:46.826903 + +""" + +# revision identifiers, used by Alembic. +revision = 'c181bb1d89e4' +down_revision = '1bb3393de75d' + +# milestone identifier, used by neutron-db-manage +neutron_milestone = [migration.YOGA] + + +def upgrade(): + op.create_table( + 'qos_minimum_packet_rate_rules', + sa.Column('id', sa.String(db_const.UUID_FIELD_SIZE), + primary_key=True), + sa.Column('qos_policy_id', sa.String(db_const.UUID_FIELD_SIZE), + sa.ForeignKey('qos_policies.id', ondelete='CASCADE'), + index=True), + sa.Column('min_kpps', sa.Integer(), nullable=False), + sa.Column('direction', + sa.Enum(*n_const.VALID_DIRECTIONS_AND_ANY, + name="qos_minimum_packet_rate_rules_directions"), + nullable=False, + server_default=n_const.EGRESS_DIRECTION), + sa.UniqueConstraint('qos_policy_id', 'direction', + name='qos_minimum_packet_rate_rules0qos_policy_id0direction') + ) diff --git a/neutron/db/qos/models.py b/neutron/db/qos/models.py index 8d55878c03c..9f63877b9e8 100644 --- a/neutron/db/qos/models.py +++ b/neutron/db/qos/models.py @@ -218,3 +218,27 @@ class QosPacketRateLimitRule(model_base.HasId, model_base.BASEV2): name="qos_packet_rate_limit_rules0qos_policy_id0direction"), model_base.BASEV2.__table_args__ ) + + +class QosMinimumPacketRateRule(model_base.HasId, model_base.BASEV2): + __tablename__ = 'qos_minimum_packet_rate_rules' + qos_policy_id = sa.Column(sa.String(db_const.UUID_FIELD_SIZE), + sa.ForeignKey('qos_policies.id', + ondelete='CASCADE'), + index=True) + min_kpps = sa.Column(sa.Integer(), nullable=False) + direction = sa.Column( + sa.Enum(*constants.VALID_DIRECTIONS_AND_ANY, + name='qos_minimum_packet_rate_rules_directions'), + nullable=False, + default=constants.EGRESS_DIRECTION, + server_default=constants.EGRESS_DIRECTION) + revises_on_change = ('qos_policy', ) + qos_policy = sa.orm.relationship(QosPolicy, load_on_pending=True) + + __table_args__ = ( + sa.UniqueConstraint( + qos_policy_id, direction, + name='qos_minimum_packet_rate_rules0qos_policy_id0direction'), + model_base.BASEV2.__table_args__ + ) diff --git a/neutron/extensions/qos.py b/neutron/extensions/qos.py index 82f2f15d0db..843f686d84f 100644 --- a/neutron/extensions/qos.py +++ b/neutron/extensions/qos.py @@ -88,6 +88,7 @@ class QoSPluginBase(service_base.ServicePluginBase, metaclass=abc.ABCMeta): 'bandwidth_limit': rule_object.QosBandwidthLimitRule, 'dscp_marking': rule_object.QosDscpMarkingRule, 'minimum_bandwidth': rule_object.QosMinimumBandwidthRule, + 'minimum_packet_rate': rule_object.QosMinimumPacketRateRule, 'packet_rate_limit': rule_object.QosPacketRateLimitRule, } diff --git a/neutron/extensions/qos_pps_minimum_rule.py b/neutron/extensions/qos_pps_minimum_rule.py new file mode 100644 index 00000000000..ae74bd49600 --- /dev/null +++ b/neutron/extensions/qos_pps_minimum_rule.py @@ -0,0 +1,35 @@ +# Copyright (c) 2021 Ericsson Software Technology +# +# 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_pps_minimum_rule as apidef +from neutron_lib.api import extensions as api_extensions +from neutron_lib.plugins import constants as nl_pl_const + +from neutron.api.v2 import resource_helper + + +class Qos_pps_minimum_rule(api_extensions.APIExtensionDescriptor): + api_definition = apidef + + @classmethod + def get_resources(cls): + plural_mappings = resource_helper.build_plural_mappings( + {}, apidef.SUB_RESOURCE_ATTRIBUTE_MAP) + + return resource_helper.build_resource_info( + plural_mappings, + apidef.SUB_RESOURCE_ATTRIBUTE_MAP, + nl_pl_const.QOS, + translate_name=True, + allow_bulk=True) diff --git a/neutron/objects/qos/policy.py b/neutron/objects/qos/policy.py index bedcd2af85c..fe491381a1f 100644 --- a/neutron/objects/qos/policy.py +++ b/neutron/objects/qos/policy.py @@ -67,7 +67,8 @@ class QosPolicy(rbac_db.NeutronRbacObject): # Version 1.7: Added floating IP bindings # Version 1.8: Added router gateway QoS policy bindings # Version 1.9: Added QosPacketRateLimitRule - VERSION = '1.9' + # Version 1.10: Added QosMinimumPacketRateRule + VERSION = '1.10' # required by RbacNeutronMetaclass rbac_db_cls = QosPolicyRBAC @@ -391,6 +392,8 @@ class QosPolicy(rbac_db.NeutronRbacObject): ] if _target_version >= (1, 9): names.append(rule_obj_impl.QosPacketRateLimitRule.obj_name()) + if _target_version >= (1, 10): + names.append(rule_obj_impl.QosMinimumPacketRuleRule.obj_name()) if 'rules' in primitive and names: primitive['rules'] = filter_rules(names, primitive['rules']) diff --git a/neutron/objects/qos/qos_policy_validator.py b/neutron/objects/qos/qos_policy_validator.py index 1c4cebc762d..a59cf30de62 100644 --- a/neutron/objects/qos/qos_policy_validator.py +++ b/neutron/objects/qos/qos_policy_validator.py @@ -13,9 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron_lib import constants as n_consts from neutron_lib.exceptions import qos as qos_exc from neutron_lib.services.qos import constants as qos_consts +from neutron.services.qos import constants as qos_constants + def check_bandwidth_rule_conflict(policy, rule_data): """Implementation of the QoS Rule checker. @@ -65,3 +68,33 @@ def check_rules_conflict(policy, rule_obj): new_rule_type=rule_obj.rule_type, rule_id=rule.id, policy_id=policy.id) + + +def check_min_pps_rule_conflict(policy, rule_obj): + """Implementation of the QoS Rule checker. + + This function checks if the new QoS minimum packet rate rule to be + associated with the policy doesn't conflict with the existing rules. + Raises an exception if conflict is identified. + """ + if (getattr(rule_obj, "rule_type", None) != + qos_constants.RULE_TYPE_MINIMUM_PACKET_RATE): + return + for rule in policy.rules: + if rule.rule_type == qos_constants.RULE_TYPE_MINIMUM_PACKET_RATE: + # Just like in check_rules_conflict(), we need to avoid raising + # exception when compared rules have got same ID. + if rule.id == getattr(rule_obj, "id", None): + continue + # Check if we are mixing directionless and direction-oriented QoS + # minimum packet rate rules + if getattr(rule_obj, "direction", None) and ( + (rule_obj.direction == n_consts.ANY_DIRECTION and + rule.direction in n_consts.VALID_DIRECTIONS) or + (rule_obj.direction in n_consts.VALID_DIRECTIONS and + rule.direction == n_consts.ANY_DIRECTION)): + raise qos_exc.QoSRuleParameterConflict( + rule_value=rule_obj.direction, + policy_id=policy["id"], + existing_rule=rule.rule_type, + existing_value=rule.direction) diff --git a/neutron/objects/qos/rule.py b/neutron/objects/qos/rule.py index 049820217fd..74d9789e7a6 100644 --- a/neutron/objects/qos/rule.py +++ b/neutron/objects/qos/rule.py @@ -52,11 +52,12 @@ class QosRule(base.NeutronDbObject, metaclass=abc.ABCMeta): # 1.2: Added QosMinimumBandwidthRule # 1.3: Added direction for BandwidthLimitRule # 1.4: Added PacketRateLimitRule + # 1.5: Added QosMinimumPacketRateRule # # 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.4' + VERSION = '1.5' fields = { 'id': common_types.UUIDField(), @@ -186,3 +187,18 @@ class QosPacketRateLimitRule(QosRule): duplicates_compare_fields = ['direction'] rule_type = qos_constants.RULE_TYPE_PACKET_RATE_LIMIT + + +@base.NeutronObjectRegistry.register +class QosMinimumPacketRateRule(QosRule): + + db_model = qos_db_model.QosMinimumPacketRateRule + + fields = { + 'min_kpps': obj_fields.IntegerField(nullable=False), + 'direction': common_types.FlowDirectionAndAnyEnumField(), + } + + duplicates_compare_fields = ['direction'] + + rule_type = qos_constants.RULE_TYPE_MINIMUM_PACKET_RATE diff --git a/neutron/objects/qos/rule_type.py b/neutron/objects/qos/rule_type.py index ced31370cdd..957e1a0a64e 100644 --- a/neutron/objects/qos/rule_type.py +++ b/neutron/objects/qos/rule_type.py @@ -34,7 +34,8 @@ class QosRuleType(base.NeutronObject): # Version 1.2: Added QosMinimumBandwidthRule # Version 1.3: Added drivers field # Version 1.4: Added QosPacketRateLimitRule - VERSION = '1.4' + # Version 1.5: Added QosMinimumPacketRateRule + VERSION = '1.5' fields = { 'type': RuleTypeField(), diff --git a/neutron/services/qos/constants.py b/neutron/services/qos/constants.py index 2f3afea6c0b..014a648e25f 100644 --- a/neutron/services/qos/constants.py +++ b/neutron/services/qos/constants.py @@ -19,6 +19,7 @@ from neutron_lib.services.qos import constants as qos_consts # to neutron-lib after neutron has the new rule. # Add qos rule packet rate limit RULE_TYPE_PACKET_RATE_LIMIT = 'packet_rate_limit' +RULE_TYPE_MINIMUM_PACKET_RATE = 'minimum_packet_rate' # NOTE(przszc): Ensure that there are no duplicates in the list. Order of the # items in the list must be stable, as QosRuleType OVO hash value depends on # it. @@ -26,5 +27,7 @@ RULE_TYPE_PACKET_RATE_LIMIT = 'packet_rate_limit' # from the list below. VALID_RULE_TYPES = (qos_consts.VALID_RULE_TYPES + ([RULE_TYPE_PACKET_RATE_LIMIT] if RULE_TYPE_PACKET_RATE_LIMIT not in + qos_consts.VALID_RULE_TYPES else []) + + ([RULE_TYPE_MINIMUM_PACKET_RATE] if RULE_TYPE_MINIMUM_PACKET_RATE not in qos_consts.VALID_RULE_TYPES else []) ) diff --git a/neutron/services/qos/qos_plugin.py b/neutron/services/qos/qos_plugin.py index 7c844b7997b..516249fe153 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_minimum_rule 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 @@ -51,6 +52,7 @@ from neutron.objects.qos import policy as policy_object from neutron.objects.qos import qos_policy_validator as checker from neutron.objects.qos import rule as rule_object from neutron.objects.qos import rule_type as rule_type_object +from neutron.services.qos import constants as qos_constants from neutron.services.qos.drivers import manager @@ -75,6 +77,7 @@ class QoSPlugin(qos.QoSPluginBase): qos_rules_alias.ALIAS, qos_port_network_policy.ALIAS, qos_pps_rule.ALIAS, + qos_pps_minimum_rule.ALIAS, ] __native_pagination_support = True @@ -410,7 +413,7 @@ class QoSPlugin(qos.QoSPluginBase): raise qos_exc.QosRuleNotSupportedByNetwork( rule_type=rule.rule_type, network_id=network_id) - def reject_min_bw_rule_updates(self, context, policy): + def reject_rule_update_for_bound_port(self, context, policy): ports = self._get_ports_with_policy(context, policy) for port in ports: # NOTE(bence romsics): In some cases the presence of @@ -582,12 +585,15 @@ class QoSPlugin(qos.QoSPluginBase): policy = policy_object.QosPolicy.get_policy_obj(context, policy_id) checker.check_bandwidth_rule_conflict(policy, rule_data) rule = rule_cls(context, qos_policy_id=policy_id, **rule_data) + checker.check_min_pps_rule_conflict(policy, rule) checker.check_rules_conflict(policy, rule) rule.create() policy.obj_load_attr('rules') self.validate_policy(context, policy) - if rule.rule_type == qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: - self.reject_min_bw_rule_updates(context, policy) + if rule.rule_type in ( + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, + qos_constants.RULE_TYPE_MINIMUM_PACKET_RATE): + self.reject_rule_update_for_bound_port(context, policy) self.driver_manager.call(qos_consts.UPDATE_POLICY_PRECOMMIT, context, policy) @@ -623,12 +629,15 @@ class QoSPlugin(qos.QoSPluginBase): checker.check_bandwidth_rule_conflict(policy, rule_data) rule = policy.get_rule_by_id(rule_id) rule.update_fields(rule_data, reset_changes=True) + checker.check_min_pps_rule_conflict(policy, rule) checker.check_rules_conflict(policy, rule) rule.update() policy.obj_load_attr('rules') self.validate_policy(context, policy) - if rule.rule_type == qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: - self.reject_min_bw_rule_updates(context, policy) + if rule.rule_type in ( + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, + qos_constants.RULE_TYPE_MINIMUM_PACKET_RATE): + self.reject_rule_update_for_bound_port(context, policy) self.driver_manager.call(qos_consts.UPDATE_POLICY_PRECOMMIT, context, policy) @@ -687,8 +696,10 @@ class QoSPlugin(qos.QoSPluginBase): rule = policy.get_rule_by_id(rule_id) rule.delete() policy.obj_load_attr('rules') - if rule.rule_type == qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: - self.reject_min_bw_rule_updates(context, policy) + if rule.rule_type in ( + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, + qos_constants.RULE_TYPE_MINIMUM_PACKET_RATE): + self.reject_rule_update_for_bound_port(context, policy) self.driver_manager.call(qos_consts.UPDATE_POLICY_PRECOMMIT, context, policy) diff --git a/neutron/tests/tools.py b/neutron/tests/tools.py index d92874b1bab..72b797cc53d 100644 --- a/neutron/tests/tools.py +++ b/neutron/tests/tools.py @@ -233,6 +233,10 @@ def get_random_flow_direction(): return random.choice(constants.VALID_DIRECTIONS) +def get_random_flow_direction_or_any(): + return random.choice(constants.VALID_DIRECTIONS_AND_ANY) + + def get_random_ha_states(): return random.choice(constants.VALID_HA_STATES) diff --git a/neutron/tests/unit/conf/policies/test_qos.py b/neutron/tests/unit/conf/policies/test_qos.py index 3c79f56246e..c26e41f470e 100644 --- a/neutron/tests/unit/conf/policies/test_qos.py +++ b/neutron/tests/unit/conf/policies/test_qos.py @@ -1002,3 +1002,172 @@ class ProjectReaderQosMinimumBandwidthRuleTests( def setUp(self): super(ProjectReaderQosMinimumBandwidthRuleTests, self).setUp() self.context = self.project_reader_ctx + + +class SystemAdminQosMinimumPacketRateRuleTests(QosRulesAPITestCase): + + def setUp(self): + super(SystemAdminQosMinimumPacketRateRuleTests, self).setUp() + self.context = self.system_admin_ctx + + def test_get_policy_minimum_packet_rate_rule(self): + self.assertTrue( + policy.enforce(self.context, + 'get_policy_minimum_packet_rate_rule', + self.target)) + self.assertTrue( + policy.enforce(self.context, + 'get_policy_minimum_packet_rate_rule', + self.alt_target)) + + def test_create_policy_minimum_packet_rate_rule(self): + self.assertTrue( + policy.enforce(self.context, + 'create_policy_minimum_packet_rate_rule', + self.target)) + self.assertTrue( + policy.enforce(self.context, + 'create_policy_minimum_packet_rate_rule', + self.alt_target)) + + def test_update_policy_minimum_packet_rate_rule(self): + self.assertTrue( + policy.enforce(self.context, + 'update_policy_minimum_packet_rate_rule', + self.target)) + self.assertTrue( + policy.enforce(self.context, + 'update_policy_minimum_packet_rate_rule', + self.alt_target)) + + def test_delete_policy_minimum_packet_rate_rule(self): + self.assertTrue( + policy.enforce(self.context, + 'delete_policy_minimum_packet_rate_rule', + self.target)) + self.assertTrue( + policy.enforce(self.context, + 'delete_policy_minimum_packet_rate_rule', + self.alt_target)) + + +class SystemMemberQosMinimumPacketRateRuleTests( + SystemAdminQosMinimumPacketRateRuleTests): + + def setUp(self): + super(SystemMemberQosMinimumPacketRateRuleTests, self).setUp() + self.context = self.system_member_ctx + + def test_create_policy_minimum_packet_rate_rule(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_policy_minimum_packet_rate_rule', + self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_policy_minimum_packet_rate_rule', + self.alt_target) + + def test_update_policy_minimum_packet_rate_rule(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'update_policy_minimum_packet_rate_rule', + self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'update_policy_minimum_packet_rate_rule', + self.alt_target) + + def test_delete_policy_minimum_packet_rate_rule(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'delete_policy_minimum_packet_rate_rule', + self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'delete_policy_minimum_packet_rate_rule', + self.alt_target) + + +class SystemReaderQosMinimumPacketRateRuleTests( + SystemMemberQosMinimumPacketRateRuleTests): + + def setUp(self): + super(SystemReaderQosMinimumPacketRateRuleTests, self).setUp() + self.context = self.system_reader_ctx + + +class ProjectAdminQosMinimumPacketRateRuleTests(QosRulesAPITestCase): + + def setUp(self): + super(ProjectAdminQosMinimumPacketRateRuleTests, self).setUp() + self.context = self.project_admin_ctx + + def test_get_policy_minimum_packet_rate_rule(self): + self.assertTrue( + policy.enforce(self.context, + 'get_policy_minimum_packet_rate_rule', + self.target)) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'get_policy_minimum_packet_rate_rule', + self.alt_target) + + def test_create_policy_minimum_packet_rate_rule(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_policy_minimum_packet_rate_rule', + self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'create_policy_minimum_packet_rate_rule', + self.alt_target) + + def test_update_policy_minimum_packet_rate_rule(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'update_policy_minimum_packet_rate_rule', + self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'update_policy_minimum_packet_rate_rule', + self.alt_target) + + def test_delete_policy_minimum_packet_rate_rule(self): + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'delete_policy_minimum_packet_rate_rule', + self.target) + self.assertRaises( + base_policy.PolicyNotAuthorized, + policy.enforce, + self.context, 'delete_policy_minimum_packet_rate_rule', + self.alt_target) + + +class ProjectMemberQosMinimumPacketRateRuleTests( + ProjectAdminQosMinimumPacketRateRuleTests): + + def setUp(self): + super(ProjectMemberQosMinimumPacketRateRuleTests, self).setUp() + self.context = self.project_member_ctx + + +class ProjectReaderQosMinimumPacketRateRuleTests( + ProjectMemberQosMinimumPacketRateRuleTests): + + def setUp(self): + super(ProjectReaderQosMinimumPacketRateRuleTests, self).setUp() + self.context = self.project_reader_ctx diff --git a/neutron/tests/unit/objects/qos/test_policy.py b/neutron/tests/unit/objects/qos/test_policy.py index 8e1b7fb4ade..1d287ffc2a9 100644 --- a/neutron/tests/unit/objects/qos/test_policy.py +++ b/neutron/tests/unit/objects/qos/test_policy.py @@ -35,6 +35,7 @@ RULE_OBJ_CLS = { 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, + q_consts.RULE_TYPE_MINIMUM_PACKET_RATE: rule.QosMinimumPacketRateRule, } @@ -101,6 +102,10 @@ class QosPolicyObjectTestCase(test_base.BaseObjectIfaceTestCase): self.get_random_db_fields(rule.QosMinimumBandwidthRule) for _ in range(3)] + self.db_qos_minimum_packet_rate_rules = [ + self.get_random_db_fields(rule.QosMinimumPacketRateRule) + for _ in range(3)] + self.model_map.update({ self._test_class.db_model: self.db_objs, binding.QosPolicyPortBinding.db_model: [], @@ -108,7 +113,9 @@ class QosPolicyObjectTestCase(test_base.BaseObjectIfaceTestCase): rule.QosBandwidthLimitRule.db_model: self.db_qos_bandwidth_rules, rule.QosDscpMarkingRule.db_model: self.db_qos_dscp_rules, rule.QosMinimumBandwidthRule.db_model: - self.db_qos_minimum_bandwidth_rules}) + self.db_qos_minimum_bandwidth_rules, + rule.QosMinimumPacketRateRule: + self.db_qos_minimum_packet_rate_rules}) # TODO(ihrachys): stop overriding those test cases, instead base test cases # should be expanded if there are missing bits there to support QoS objects @@ -179,7 +186,8 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, 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 + q_consts.RULE_TYPE_PACKET_RATE_LIMIT, + q_consts.RULE_TYPE_MINIMUM_PACKET_RATE] and bwlimit_direction is not None): rule_fields['direction'] = bwlimit_direction rule_obj = obj_cls(self.context, **rule_fields) @@ -481,6 +489,22 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase, self.assertIn(rule_objs[2], policy_obj_v1_8.rules) self.assertNotIn(rule_objs[3], policy_obj_v1_8.rules) + def test_object_version_degradation_less_than_1_10(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, + q_consts.RULE_TYPE_MINIMUM_PACKET_RATE], reload_rules=True, + bwlimit_direction=lib_consts.INGRESS_DIRECTION) + policy_obj_v1_9 = self._policy_through_version(policy_obj, '1.9') + + self.assertIn(rule_objs[0], policy_obj_v1_9.rules) + self.assertIn(rule_objs[1], policy_obj_v1_9.rules) + self.assertIn(rule_objs[2], policy_obj_v1_9.rules) + self.assertIn(rule_objs[3], policy_obj_v1_9.rules) + self.assertNotIn(rule_objs[4], policy_obj_v1_9.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 e225a9a3356..b42bc5a11e0 100644 --- a/neutron/tests/unit/objects/qos/test_rule.py +++ b/neutron/tests/unit/objects/qos/test_rule.py @@ -290,3 +290,54 @@ class QosPacketRateLimitRuleDbObjectTestCase(test_base.BaseDbObjectTestCase, id=generated_qos_policy_id, project_id=uuidutils.generate_uuid()) policy_obj.create() + + +class QosMinimumPacketRateRuleObjectTestCase( + test_base.BaseObjectIfaceTestCase): + + _test_class = rule.QosMinimumPacketRateRule + + def test_to_dict_returns_type(self): + obj = rule.QosMinimumPacketRateRule(self.context, **self.db_objs[0]) + dict_ = obj.to_dict() + self.assertEqual(qos_constants.RULE_TYPE_MINIMUM_PACKET_RATE, + dict_['type']) + + def test_duplicate_rules(self): + policy_id = uuidutils.generate_uuid() + ingress_rule_1 = rule.QosMinimumPacketRateRule( + self.context, qos_policy_id=policy_id, + min_kpps=1000, direction=constants.INGRESS_DIRECTION) + ingress_rule_2 = rule.QosMinimumPacketRateRule( + self.context, qos_policy_id=policy_id, + min_kpps=2000, direction=constants.INGRESS_DIRECTION) + egress_rule = rule.QosMinimumPacketRateRule( + self.context, qos_policy_id=policy_id, + min_kpps=1000, direction=constants.EGRESS_DIRECTION) + directionless_rule = rule.QosMinimumPacketRateRule( + self.context, qos_policy_id=policy_id, + min_kpps=1000, direction=constants.ANY_DIRECTION) + min_bw_rule = rule.QosMinimumBandwidthRule( + self.context, qos_policy_id=policy_id, + min_kbps=1000, direction=constants.INGRESS_DIRECTION) + self.assertTrue(ingress_rule_1.duplicates(ingress_rule_2)) + self.assertFalse(ingress_rule_1.duplicates(egress_rule)) + self.assertFalse(ingress_rule_1.duplicates(directionless_rule)) + self.assertFalse(ingress_rule_1.duplicates(min_bw_rule)) + + +class QosMinimumPacketRateRuleDbObjectTestCase(test_base.BaseDbObjectTestCase, + testlib_api.SqlTestCase): + + _test_class = rule.QosMinimumPacketRateRule + + def setUp(self): + super(QosMinimumPacketRateRuleDbObjectTestCase, 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/test_base.py b/neutron/tests/unit/objects/test_base.py index 9ce30234884..0846548b4dd 100644 --- a/neutron/tests/unit/objects/test_base.py +++ b/neutron/tests/unit/objects/test_base.py @@ -511,6 +511,8 @@ FIELD_TYPE_VALUE_GENERATOR_MAP = { common_types.EtherTypeEnumField: tools.get_random_ether_type, common_types.FloatingIPStatusEnumField: tools.get_random_floatingip_status, common_types.FlowDirectionEnumField: tools.get_random_flow_direction, + common_types.FlowDirectionAndAnyEnumField: + tools.get_random_flow_direction_or_any, common_types.HARouterEnumField: tools.get_random_ha_states, common_types.IpamAllocationStatusEnumField: tools.get_random_ipam_status, common_types.IPNetworkField: lib_test_tools.get_random_ip_network, diff --git a/neutron/tests/unit/objects/test_objects.py b/neutron/tests/unit/objects/test_objects.py index 511b18e3447..3ac9d8bfad0 100644 --- a/neutron/tests/unit/objects/test_objects.py +++ b/neutron/tests/unit/objects/test_objects.py @@ -82,14 +82,15 @@ object_data = { 'PortUplinkStatusPropagation': '1.1-f0a4ca451a941910376c33616dea5de2', 'ProviderResourceAssociation': '1.0-05ab2d5a3017e5ce9dd381328f285f34', 'ProvisioningBlock': '1.0-c19d6d05bfa8143533471c1296066125', - 'QosBandwidthLimitRule': '1.4-51b662b12a8d1dfa89288d826c6d26d3', - 'QosDscpMarkingRule': '1.4-0313c6554b34fd10c753cb63d638256c', - 'QosMinimumBandwidthRule': '1.4-314c3419f4799067cc31cc319080adff', - 'QosPacketRateLimitRule': '1.4-18411fa95f54602b8c8a5da2d3194b31', + 'QosBandwidthLimitRule': '1.5-51b662b12a8d1dfa89288d826c6d26d3', + 'QosDscpMarkingRule': '1.5-0313c6554b34fd10c753cb63d638256c', + 'QosMinimumBandwidthRule': '1.5-314c3419f4799067cc31cc319080adff', + 'QosMinimumPacketRateRule': '1.5-d0516c55aa2f310a2646c7d243cb8620', + 'QosPacketRateLimitRule': '1.5-18411fa95f54602b8c8a5da2d3194b31', 'QosPolicyRBAC': '1.1-192845c5ed0718e1c54fac36936fcd7d', - 'QosRuleType': '1.4-a5b870dfa6f510a91f5cb0216873064e', + 'QosRuleType': '1.5-56b25ec81e27aa5c8238b8c43e88aed6', 'QosRuleTypeDriver': '1.0-7d8cb9f0ef661ac03700eae97118e3db', - 'QosPolicy': '1.9-4adb0cde3102c10d8970ec9487fd7fe7', + 'QosPolicy': '1.10-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 4ae2af3e1f5..02dc993d933 100644 --- a/neutron/tests/unit/services/qos/test_qos_plugin.py +++ b/neutron/tests/unit/services/qos/test_qos_plugin.py @@ -103,7 +103,12 @@ class TestQosPlugin(base.BaseQosTestCase): 'packet_rate_limit_rule': { 'id': uuidutils.generate_uuid(), 'max_kpps': 20, - 'max_burst_kpps': 130}} + 'max_burst_kpps': 130}, + 'minimum_packet_rate_rule': { + 'id': uuidutils.generate_uuid(), + 'min_kpps': 10, + 'direction': 'any'}, + } self.policy = policy_object.QosPolicy( self.ctxt, **self.policy_data['policy']) @@ -114,12 +119,15 @@ class TestQosPlugin(base.BaseQosTestCase): self.dscp_rule = rule_object.QosDscpMarkingRule( self.ctxt, **self.rule_data['dscp_marking_rule']) - self.min_rule = rule_object.QosMinimumBandwidthRule( + self.min_bw_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']) + self.min_pps_rule = rule_object.QosMinimumPacketRateRule( + self.ctxt, **self.rule_data['minimum_packet_rate_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) @@ -158,8 +166,8 @@ class TestQosPlugin(base.BaseQosTestCase): port_res, self.port) def test__extend_port_resource_request_min_bw_rule(self): - self.min_rule.direction = lib_constants.EGRESS_DIRECTION - port = self._create_and_extend_port([self.min_rule]) + self.min_bw_rule.direction = lib_constants.EGRESS_DIRECTION + port = self._create_and_extend_port([self.min_bw_rule]) self.assertEqual( ['CUSTOM_PHYSNET_PUBLIC', 'CUSTOM_VNIC_TYPE_NORMAL'], @@ -171,16 +179,17 @@ class TestQosPlugin(base.BaseQosTestCase): ) def test__extend_port_resource_request_mixed_rules(self): - self.min_rule.direction = lib_constants.EGRESS_DIRECTION + self.min_bw_rule.direction = lib_constants.EGRESS_DIRECTION - min_rule_ingress_data = { + min_bw_rule_ingress_data = { 'id': uuidutils.generate_uuid(), 'min_kbps': 20, 'direction': lib_constants.INGRESS_DIRECTION} - min_rule_ingress = rule_object.QosMinimumBandwidthRule( - self.ctxt, **min_rule_ingress_data) + min_bw_rule_ingress = rule_object.QosMinimumBandwidthRule( + self.ctxt, **min_bw_rule_ingress_data) - port = self._create_and_extend_port([self.min_rule, min_rule_ingress]) + port = self._create_and_extend_port( + [self.min_bw_rule, min_bw_rule_ingress]) self.assertEqual( ['CUSTOM_PHYSNET_PUBLIC', 'CUSTOM_VNIC_TYPE_NORMAL'], port['resource_request']['required'] @@ -199,9 +208,9 @@ class TestQosPlugin(base.BaseQosTestCase): self.assertIsNone(port.get('resource_request')) def test__extend_port_resource_request_non_provider_net(self): - self.min_rule.direction = lib_constants.EGRESS_DIRECTION + self.min_bw_rule.direction = lib_constants.EGRESS_DIRECTION - port = self._create_and_extend_port([self.min_rule], + port = self._create_and_extend_port([self.min_bw_rule], physical_network=None) self.assertIsNone(port.get('resource_request')) @@ -211,10 +220,10 @@ class TestQosPlugin(base.BaseQosTestCase): self.assertIsNone(port.get('resource_request')) def test__extend_port_resource_request_inherited_policy(self): - self.min_rule.direction = lib_constants.EGRESS_DIRECTION - self.min_rule.qos_policy_id = self.policy.id + self.min_bw_rule.direction = lib_constants.EGRESS_DIRECTION + self.min_bw_rule.qos_policy_id = self.policy.id - port = self._create_and_extend_port([self.min_rule], + port = self._create_and_extend_port([self.min_bw_rule], has_net_qos_policy=True) self.assertEqual( ['CUSTOM_PHYSNET_PUBLIC', 'CUSTOM_VNIC_TYPE_NORMAL'], @@ -413,7 +422,7 @@ class TestQosPlugin(base.BaseQosTestCase): def test_create_min_bw_rule_on_bound_port(self): policy = self._get_policy() - policy.rules = [self.min_rule] + policy.rules = [self.min_bw_rule] segment = network_object.NetworkSegment( physical_network='fake physnet') net = network_object.Network( @@ -441,7 +450,7 @@ class TestQosPlugin(base.BaseQosTestCase): def test_create_min_bw_rule_on_unbound_port(self): policy = self._get_policy() - policy.rules = [self.min_rule] + policy.rules = [self.min_bw_rule] segment = network_object.NetworkSegment( physical_network='fake physnet') net = network_object.Network( @@ -574,9 +583,12 @@ class TestQosPlugin(base.BaseQosTestCase): mock_manager.attach_mock(mock_qos_rule_create, 'create') mock_manager.attach_mock(self.qos_plugin.driver_manager, 'driver') mock_manager.reset_mock() - with mock.patch( - 'neutron.objects.qos.qos_policy_validator' - '.check_bandwidth_rule_conflict', return_value=None): + with mock.patch('neutron.objects.qos.qos_policy_validator' + '.check_bandwidth_rule_conflict', + return_value=None), \ + mock.patch( + 'neutron.objects.qos.qos_policy_validator' + '.check_min_pps_rule_conflict', return_value=None): self.qos_plugin.create_policy_bandwidth_limit_rule( self.ctxt, self.policy.id, self.rule_data) self._validate_driver_params('update_policy', self.ctxt) @@ -603,7 +615,7 @@ class TestQosPlugin(base.BaseQosTestCase): def test_create_policy_rule_check_rule_max_more_than_min(self): _policy = self._get_policy() - setattr(_policy, "rules", [self.min_rule]) + setattr(_policy, "rules", [self.min_bw_rule]) with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', return_value=_policy) as mock_qos_get_obj: self.qos_plugin.create_policy_bandwidth_limit_rule( @@ -615,7 +627,7 @@ class TestQosPlugin(base.BaseQosTestCase): def test_create_policy_rule_check_rule_bwlimit_less_than_minbw(self): _policy = self._get_policy() self.rule_data['bandwidth_limit_rule']['max_kbps'] = 1 - setattr(_policy, "rules", [self.min_rule]) + setattr(_policy, "rules", [self.min_bw_rule]) with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', return_value=_policy) as mock_qos_get_obj: self.assertRaises(qos_exc.QoSRuleParameterConflict, @@ -690,13 +702,13 @@ class TestQosPlugin(base.BaseQosTestCase): self.mock_qos_load_attr.assert_called_once_with('rules') self._validate_driver_params('update_policy', self.ctxt) - rules = [self.rule, self.min_rule] + rules = [self.rule, self.min_bw_rule] setattr(_policy, "rules", rules) self.mock_qos_load_attr.reset_mock() with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', return_value=_policy): self.qos_plugin.update_policy_minimum_bandwidth_rule( - self.ctxt, self.min_rule.id, + self.ctxt, self.min_bw_rule.id, self.policy.id, self.rule_data) self.mock_qos_load_attr.assert_called_once_with('rules') self._validate_driver_params('update_policy', self.ctxt) @@ -716,16 +728,17 @@ class TestQosPlugin(base.BaseQosTestCase): self.assertRaises( qos_exc.QoSRuleParameterConflict, self.qos_plugin.update_policy_minimum_bandwidth_rule, - self.ctxt, self.min_rule.id, + self.ctxt, self.min_bw_rule.id, self.policy.id, self.rule_data) def test_update_policy_rule_check_rule_minbw_gr_than_bwlimit(self): _policy = self._get_policy() - setattr(_policy, "rules", [self.min_rule]) + setattr(_policy, "rules", [self.min_bw_rule]) with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', return_value=_policy): self.qos_plugin.update_policy_minimum_bandwidth_rule( - self.ctxt, self.min_rule.id, self.policy.id, self.rule_data) + self.ctxt, self.min_bw_rule.id, self.policy.id, + self.rule_data) self.mock_qos_load_attr.assert_called_once_with('rules') self._validate_driver_params('update_policy', self.ctxt) self.rule_data['bandwidth_limit_rule']['max_kbps'] = 1 @@ -1260,6 +1273,314 @@ class TestQosPlugin(base.BaseQosTestCase): self.qos_plugin.get_rule_type, self.ctxt, qos_constants.RULE_TYPE_PACKET_RATE_LIMIT) + def test_create_min_pps_rule_on_bound_port(self): + _policy = self._get_policy() + setattr(_policy, "rules", [self.min_pps_rule]) + segment = network_object.NetworkSegment( + physical_network='fake physnet') + net = network_object.Network( + self.ctxt, + segments=[segment]) + port = ports_object.Port( + self.ctxt, + id=uuidutils.generate_uuid(), + network_id=uuidutils.generate_uuid(), + device_owner='compute:fake-zone') + with mock.patch( + 'neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy), \ + mock.patch( + 'neutron.objects.network.Network.get_object', + return_value=net), \ + mock.patch.object( + self.qos_plugin, + '_get_ports_with_policy', + return_value=[port]): + self.assertRaises( + NotImplementedError, + self.qos_plugin.create_policy_minimum_packet_rate_rule, + self.ctxt, _policy.id, self.rule_data) + + def test_create_min_pps_rule_on_unbound_port(self): + _policy = self._get_policy() + setattr(_policy, "rules", [self.min_pps_rule]) + segment = network_object.NetworkSegment( + physical_network='fake physnet') + net = network_object.Network( + self.ctxt, + segments=[segment]) + port = ports_object.Port( + self.ctxt, + id=uuidutils.generate_uuid(), + network_id=uuidutils.generate_uuid(), + device_owner='') + with mock.patch( + 'neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy), \ + mock.patch( + 'neutron.objects.network.Network.get_object', + return_value=net), \ + mock.patch.object( + self.qos_plugin, + '_get_ports_with_policy', + return_value=[port]): + try: + self.qos_plugin.create_policy_minimum_packet_rate_rule( + self.ctxt, _policy.id, self.rule_data) + except NotImplementedError: + self.fail() + + def test_create_policy_rule_check_rule_min_pps_direction_conflict(self): + _policy = self._get_policy() + self.rule_data['minimum_packet_rate_rule']['direction'] = 'any' + setattr(_policy, "rules", [self.min_pps_rule]) + rules = [ + { + 'minimum_packet_rate_rule': { + 'id': uuidutils.generate_uuid(), + 'min_kpps': 10, + 'direction': 'ingress' + } + }, + { + 'minimum_packet_rate_rule': { + 'id': uuidutils.generate_uuid(), + 'min_kpps': 10, + 'direction': 'egress' + } + }, + ] + for new_rule_data in rules: + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy) as mock_qos_get_obj: + self.assertRaises(qos_exc.QoSRuleParameterConflict, + self.qos_plugin.create_policy_minimum_packet_rate_rule, + self.ctxt, self.policy.id, new_rule_data) + mock_qos_get_obj.assert_called_once_with(self.ctxt, + id=_policy.id) + + for rule_data in rules: + min_pps_rule = rule_object.QosMinimumPacketRateRule( + self.ctxt, **rule_data['minimum_packet_rate_rule']) + setattr(_policy, "rules", [min_pps_rule]) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy) as mock_qos_get_obj: + self.assertRaises(qos_exc.QoSRuleParameterConflict, + self.qos_plugin.create_policy_minimum_packet_rate_rule, + self.ctxt, self.policy.id, self.rule_data) + mock_qos_get_obj.assert_called_once_with(self.ctxt, + id=_policy.id) + + def test_create_policy_min_pps_rule(self): + _policy = self._get_policy() + setattr(_policy, "rules", [self.min_pps_rule]) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + self.qos_plugin.create_policy_minimum_packet_rate_rule( + self.ctxt, self.policy.id, self.rule_data) + self._validate_driver_params('update_policy', self.ctxt) + + def test_create_policy_min_pps_rule_duplicates(self): + _policy = self._get_policy() + setattr(_policy, "rules", [self.min_pps_rule]) + new_rule_data = { + 'minimum_packet_rate_rule': { + 'id': uuidutils.generate_uuid(), + 'min_kpps': 1234, + 'direction': self.min_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_minimum_packet_rate_rule, + self.ctxt, _policy.id, new_rule_data) + mock_qos_get_obj.assert_called_once_with(self.ctxt, id=_policy.id) + + def test_create_policy_min_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_minimum_packet_rate_rule, + self.ctxt, self.policy.id, self.rule_data) + + def test_update_policy_min_pps_rule(self): + _policy = self._get_policy() + setattr(_policy, "rules", [self.min_pps_rule]) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + self.qos_plugin.update_policy_minimum_packet_rate_rule( + self.ctxt, self.min_pps_rule.id, self.policy.id, + self.rule_data) + self._validate_driver_params('update_policy', self.ctxt) + + def test_update_policy_rule_check_rule_min_pps_direction_conflict(self): + _policy = self._get_policy() + rules_data = [ + { + 'minimum_packet_rate_rule': { + 'id': uuidutils.generate_uuid(), + 'min_kpps': 10, + 'direction': 'ingress' + } + }, + { + 'minimum_packet_rate_rule': { + 'id': uuidutils.generate_uuid(), + 'min_kpps': 10, + 'direction': 'egress' + } + }, + ] + + self.rule_data['minimum_packet_rate_rule']['direction'] = 'any' + for rule_data in rules_data: + rules = [ + rule_object.QosMinimumPacketRateRule( + self.ctxt, **rules_data[0]['minimum_packet_rate_rule']), + rule_object.QosMinimumPacketRateRule( + self.ctxt, **rules_data[1]['minimum_packet_rate_rule']), + ] + setattr(_policy, 'rules', rules) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy) as mock_qos_get_obj: + self.assertRaises(qos_exc.QoSRuleParameterConflict, + self.qos_plugin.update_policy_minimum_packet_rate_rule, + self.ctxt, rule_data['minimum_packet_rate_rule']['id'], + self.policy.id, self.rule_data) + mock_qos_get_obj.assert_called_once_with(self.ctxt, + id=_policy.id) + + def test_update_policy_min_pps_rule_bad_policy(self): + _policy = self._get_policy() + setattr(_policy, "rules", []) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + self.assertRaises( + qos_exc.QosRuleNotFound, + self.qos_plugin.update_policy_minimum_packet_rate_rule, + self.ctxt, self.min_pps_rule.id, self.policy.id, + self.rule_data) + + def test_update_policy_min_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_minimum_packet_rate_rule, + self.ctxt, self.min_pps_rule.id, self.policy.id, + self.rule_data) + + def test_delete_policy_min_pps_rule(self): + _policy = self._get_policy() + setattr(_policy, "rules", [self.min_pps_rule]) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + self.qos_plugin.delete_policy_minimum_packet_rate_rule( + self.ctxt, self.min_pps_rule.id, self.policy.id) + self._validate_driver_params('update_policy', self.ctxt) + + def test_delete_policy_min_pps_rule_bad_policy(self): + _policy = self._get_policy() + setattr(_policy, "rules", []) + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=_policy): + self.assertRaises( + qos_exc.QosRuleNotFound, + self.qos_plugin.delete_policy_minimum_packet_rate_rule, + self.ctxt, self.min_pps_rule.id, _policy.id) + + def test_delete_policy_min_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_minimum_packet_rate_rule, + self.ctxt, self.min_pps_rule.id, self.policy.id) + + def test_get_policy_min_pps_rule(self): + with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', + return_value=self.policy): + with mock.patch('neutron.objects.qos.rule.' + 'QosMinimumPacketRateRule.' + 'get_object') as get_object_mock: + self.qos_plugin.get_policy_minimum_packet_rate_rule( + self.ctxt, self.min_pps_rule.id, self.policy.id) + get_object_mock.assert_called_once_with( + self.ctxt, id=self.min_pps_rule.id) + + def test_get_policy_min_pps_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.' + 'QosMinimumPacketRateRule.' + 'get_objects') as get_objects_mock: + self.qos_plugin.get_policy_minimum_packet_rate_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_min_pps_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.' + 'QosMinimumPacketRateRule.' + 'get_objects') as get_objects_mock: + filters = {'filter': 'filter_id'} + self.qos_plugin.get_policy_minimum_packet_rate_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_min_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.get_policy_minimum_packet_rate_rule, + self.ctxt, self.min_pps_rule.id, self.policy.id) + + def test_get_policy_min_pps_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_minimum_packet_rate_rules, + self.ctxt, self.policy.id) + + def test_get_min_pps_rule_type(self): + admin_ctxt = context.get_admin_context() + drivers_details = [{ + 'name': 'fake-driver', + 'supported_parameters': [{ + 'parameter_name': 'min_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_MINIMUM_PACKET_RATE) + self.assertEqual( + qos_constants.RULE_TYPE_MINIMUM_PACKET_RATE, + rule_type_details['type']) + self.assertEqual( + drivers_details, rule_type_details['drivers']) + + def test_get_min_pps_rule_type_as_user(self): + self.assertRaises( + lib_exc.NotAuthorized, + self.qos_plugin.get_rule_type, + self.ctxt, qos_constants.RULE_TYPE_MINIMUM_PACKET_RATE) + class QoSRuleAliasTestExtensionManager(object): diff --git a/releasenotes/notes/qos_rule_type_min_pps-0cc3fe5b0ee5d596.yaml b/releasenotes/notes/qos_rule_type_min_pps-0cc3fe5b0ee5d596.yaml new file mode 100644 index 00000000000..905924a6b60 --- /dev/null +++ b/releasenotes/notes/qos_rule_type_min_pps-0cc3fe5b0ee5d596.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added new API extension to QoS service plugin to support CRUD operations + for minimum packet rate rule in Neutron server. diff --git a/setup.cfg b/setup.cfg index e4fc9327797..a7e6c1ac752 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 + QosMinimumPacketRateRule = neutron.objects.qos.rule:QosMinimumPacketRateRule QosPacketRateLimitRule = neutron.objects.qos.rule:QosPacketRateLimitRule QosPolicy = neutron.objects.qos.policy:QosPolicy QosPolicyDefault = neutron.objects.qos.policy:QosPolicyDefault