Add QoS bandwidth limit for instance ingress traffic

This patch introduces the new parameter "direction" to
the QoS bandwidth limit rule. It will allow the creation
of bandwidth limit rules for either ingress or egress
traffic. For backwards compatibility the default direction
will be egress.

DocImpact: Ingress bandwidth limit available for QoS
APIImpact: New type of parameter for QoS rule in neutron API

Change-Id: Ia13568879c2b6f80fb190ccafe7e19ca05b0c6a8
Partial-Bug: #1560961
This commit is contained in:
Sławek Kapłoński 2017-03-24 22:04:53 +00:00
parent 39c05533a3
commit c29f3aaa7c
18 changed files with 384 additions and 50 deletions

View File

@ -1 +1 @@
804a3c76314c
2b42d90729da

View File

@ -0,0 +1,81 @@
# Copyright 2017 OpenStack Foundation
#
# 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.
#
"""qos add direction to bw_limit_rule table
Revision ID: 2b42d90729da
Revises: 804a3c76314c
Create Date: 2017-04-03 20:56:00.169599
"""
# revision identifiers, used by Alembic.
revision = '2b42d90729da'
down_revision = '804a3c76314c'
from alembic import op
import sqlalchemy as sa
from neutron.common import constants
from neutron.db import migration
policies_table_name = "qos_policies"
bw_limit_table_name = "qos_bandwidth_limit_rules"
direction_enum = sa.Enum(
constants.EGRESS_DIRECTION, constants.INGRESS_DIRECTION,
name="directions"
)
def upgrade():
if op.get_context().bind.dialect.name == 'postgresql':
direction_enum.create(op.get_bind(), checkfirst=True)
with migration.remove_fks_from_table(bw_limit_table_name,
remove_unique_constraints=True):
op.add_column(bw_limit_table_name,
sa.Column("direction", direction_enum,
server_default=constants.EGRESS_DIRECTION,
nullable=False))
op.create_unique_constraint(
op.f('qos_bandwidth_rules0qos_policy_id0direction'),
bw_limit_table_name,
['qos_policy_id', 'direction'])
def expand_drop_exceptions():
"""
Drop the existing QoS policy foreign key uniq constraint and then replace
it with new unique constraint for pair (policy_id, direction).
As names of constraints are different in MySQL and PGSQL there is need to
add both variants to drop exceptions.
"""
# TODO(slaweq): replace hardcoded constaints names with names get directly
# from database model after bug
# https://bugs.launchpad.net/neutron/+bug/1685352 will be closed
return {
sa.ForeignKeyConstraint: [
"qos_bandwidth_limit_rules_ibfk_1", # MySQL name
"qos_bandwidth_limit_rules_qos_policy_id_fkey" # PGSQL name
],
sa.UniqueConstraint: [
"qos_policy_id", # MySQL name
"qos_bandwidth_limit_rules_qos_policy_id_key" # PGSQL name
]
}

View File

@ -78,12 +78,23 @@ class QosBandwidthLimitRule(model_base.HasId, model_base.BASEV2):
qos_policy_id = sa.Column(sa.String(36),
sa.ForeignKey('qos_policies.id',
ondelete='CASCADE'),
nullable=False,
unique=True)
nullable=False)
max_kbps = sa.Column(sa.Integer)
max_burst_kbps = 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="directions"),
default=constants.EGRESS_DIRECTION,
server_default=constants.EGRESS_DIRECTION,
nullable=False)
__table_args__ = (
sa.UniqueConstraint(
qos_policy_id, direction,
name="qos_bandwidth_rules0qos_policy_id0direction"),
model_base.BASEV2.__table_args__
)
class QosDscpMarkingRule(model_base.HasId, model_base.BASEV2):

View File

@ -32,6 +32,7 @@ from neutron.objects.qos import rule as rule_object
from neutron.plugins.common import constants
from neutron.services.qos import qos_consts
ALIAS = "qos"
QOS_PREFIX = "/qos"
# Attribute Map
@ -74,8 +75,10 @@ RESOURCE_ATTRIBUTE_MAP = {
}
}
BANDWIDTH_LIMIT_RULES = "bandwidth_limit_rules"
SUB_RESOURCE_ATTRIBUTE_MAP = {
'bandwidth_limit_rules': {
BANDWIDTH_LIMIT_RULES: {
'parent': {'collection_name': 'policies',
'member_name': 'policy'},
'parameters': dict(QOS_RULE_COMMON_FIELDS,
@ -88,7 +91,7 @@ SUB_RESOURCE_ATTRIBUTE_MAP = {
'allow_post': True, 'allow_put': True,
'is_visible': True, 'default': 0,
'validate': {'type:range': [0,
common_constants.DB_INTEGER_MAX_VALUE]}}})
common_constants.DB_INTEGER_MAX_VALUE]}}}),
},
'dscp_marking_rules': {
'parent': {'collection_name': 'policies',
@ -196,12 +199,15 @@ class Qos(api_extensions.ExtensionDescriptor):
def update_attributes_map(self, attributes, extension_attrs_map=None):
super(Qos, self).update_attributes_map(
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
attributes,
extension_attrs_map=dict(list(RESOURCE_ATTRIBUTE_MAP.items()) +
list(SUB_RESOURCE_ATTRIBUTE_MAP.items())))
def get_extended_resources(self, version):
if version == "2.0":
return dict(list(EXTENDED_ATTRIBUTES_2_0.items()) +
list(RESOURCE_ATTRIBUTE_MAP.items()))
list(RESOURCE_ATTRIBUTE_MAP.items()) +
list(SUB_RESOURCE_ATTRIBUTE_MAP.items()))
else:
return {}

View File

@ -0,0 +1,84 @@
# Copyright (c) 2017 OVH SAS
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron_lib.api import extensions as api_extensions
from neutron.common import constants as common_constants
from neutron.extensions import qos
# The name of the extension.
NAME = "Direction for QoS bandwidth limit rule"
# The alias of the extension.
ALIAS = "qos-bw-limit-direction"
# The description of the extension.
DESCRIPTION = ("Allow to configure QoS bandwidth limit rule with specific "
"direction: ingress or egress")
# The list of required extensions.
REQUIRED_EXTENSIONS = [qos.ALIAS]
# The list of optional extensions.
OPTIONAL_EXTENSIONS = None
# The resource attribute map for the extension.
SUB_RESOURCE_ATTRIBUTE_MAP = {
qos.BANDWIDTH_LIMIT_RULES: {
'parameters': dict(
qos.SUB_RESOURCE_ATTRIBUTE_MAP[
qos.BANDWIDTH_LIMIT_RULES]['parameters'],
**{'direction': {
'allow_post': True,
'allow_put': True,
'is_visible': True,
'default': common_constants.EGRESS_DIRECTION,
'validate': {
'type:values': common_constants.VALID_DIRECTIONS}}}
)
}
}
class Qos_bw_limit_direction(api_extensions.ExtensionDescriptor):
@classmethod
def get_name(cls):
return NAME
@classmethod
def get_alias(cls):
return ALIAS
@classmethod
def get_description(cls):
return DESCRIPTION
@classmethod
def get_updated(cls):
return "2017-04-10T10:00:00-00:00"
def get_required_extensions(self):
return REQUIRED_EXTENSIONS or []
def get_optional_extensions(self):
return OPTIONAL_EXTENSIONS or []
def get_extended_resources(self, version):
if version == "2.0":
return SUB_RESOURCE_ATTRIBUTE_MAP
else:
return {}

View File

@ -21,6 +21,7 @@ from oslo_versionedobjects import exception
from oslo_versionedobjects import fields as obj_fields
from neutron._i18n import _
from neutron.common import constants as n_const
from neutron.common import exceptions
from neutron.db import api as db_api
from neutron.db import models_v2
@ -40,7 +41,8 @@ class QosPolicy(rbac_db.NeutronRbacObject):
# Version 1.2: Added QosMinimumBandwidthRule
# Version 1.3: Added standard attributes (created_at, revision, etc)
# Version 1.4: Changed tenant_id to project_id
VERSION = '1.4'
# Version 1.5: Direction for bandwidth limit rule added
VERSION = '1.5'
# required by RbacNeutronMetaclass
rbac_db_model = QosPolicyRBAC
@ -222,6 +224,19 @@ class QosPolicy(rbac_db.NeutronRbacObject):
return [rule for rule in rules if
rule['versioned_object.name'] in obj_names]
def filter_ingress_bandwidth_limit_rules(rules):
bwlimit_obj_name = rule_obj_impl.QosBandwidthLimitRule.obj_name()
filtered_rules = []
for rule in rules:
if rule['versioned_object.name'] == bwlimit_obj_name:
direction = rule['versioned_object.data'].get("direction")
if direction == n_const.EGRESS_DIRECTION:
rule['versioned_object.data'].pop('direction')
filtered_rules.append(rule)
else:
filtered_rules.append(rule)
return filtered_rules
_target_version = versionutils.convert_version_to_tuple(target_version)
names = []
if _target_version >= (1, 0):
@ -244,3 +259,8 @@ class QosPolicy(rbac_db.NeutronRbacObject):
if _target_version < (1, 4):
primitive['tenant_id'] = primitive.pop('project_id')
if _target_version < (1, 5):
if 'rules' in primitive:
primitive['rules'] = filter_ingress_bandwidth_limit_rules(
primitive['rules'])

View File

@ -24,6 +24,7 @@ from oslo_versionedobjects import exception
from oslo_versionedobjects import fields as obj_fields
import six
from neutron.common import constants as n_const
from neutron.db import api as db_api
from neutron.db.qos import models as qos_db_model
from neutron.objects import base
@ -50,11 +51,12 @@ class QosRule(base.NeutronDbObject):
# Version 1.0: Initial version, only BandwidthLimitRule
# 1.1: Added DscpMarkingRule
# 1.2: Added QosMinimumBandwidthRule
# 1.3: Added direction for BandwidthLimitRule
#
#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.2'
VERSION = '1.3'
fields = {
'id': common_types.UUIDField(),
@ -106,11 +108,22 @@ class QosBandwidthLimitRule(QosRule):
fields = {
'max_kbps': obj_fields.IntegerField(nullable=True),
'max_burst_kbps': obj_fields.IntegerField(nullable=True)
'max_burst_kbps': obj_fields.IntegerField(nullable=True),
'direction': common_types.FlowDirectionEnumField(
default=n_const.EGRESS_DIRECTION)
}
rule_type = qos_consts.RULE_TYPE_BANDWIDTH_LIMIT
def obj_make_compatible(self, primitive, target_version):
_target_version = versionutils.convert_version_to_tuple(target_version)
if _target_version < (1, 3) and 'direction' in primitive:
direction = primitive.pop('direction')
if direction == n_const.INGRESS_DIRECTION:
raise exception.IncompatibleObjectVersion(
objver=target_version,
objtype="QosBandwidthLimitRule")
@obj_base.VersionedObjectRegistry.register
class QosDscpMarkingRule(QosRule):

View File

@ -29,7 +29,9 @@ SUPPORTED_RULES = {
qos_consts.MAX_KBPS: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.MAX_BURST: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]}
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.DIRECTION: {
'type:values': [constants.EGRESS_DIRECTION]}
},
qos_consts.RULE_TYPE_DSCP_MARKING: {
qos_consts.DSCP_MARK: {'type:values': constants.VALID_DSCP_MARKS}

View File

@ -29,7 +29,9 @@ SUPPORTED_RULES = {
qos_consts.MAX_KBPS: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.MAX_BURST: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]}
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.DIRECTION: {
'type:values': [constants.EGRESS_DIRECTION]}
},
qos_consts.RULE_TYPE_DSCP_MARKING: {
qos_consts.DSCP_MARK: {'type:values': constants.VALID_DSCP_MARKS}

View File

@ -29,7 +29,9 @@ SUPPORTED_RULES = {
qos_consts.MAX_KBPS: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.MAX_BURST: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]}
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.DIRECTION: {
'type:values': [constants.EGRESS_DIRECTION]}
},
qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: {
qos_consts.MIN_KBPS: {

View File

@ -36,7 +36,7 @@ class QoSPlugin(qos.QoSPluginBase):
service parameters over ports and networks.
"""
supported_extension_aliases = ['qos']
supported_extension_aliases = ['qos', 'qos-bw-limit-direction']
__native_pagination_support = True
__native_sorting_support = True

View File

@ -374,11 +374,12 @@ class BaseNetworkTest(test.BaseTestCase):
return qos_policy
@classmethod
def create_qos_bandwidth_limit_rule(cls, policy_id,
max_kbps, max_burst_kbps):
def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
max_burst_kbps,
direction=constants.EGRESS_DIRECTION):
"""Wrapper utility that returns a test QoS bandwidth limit rule."""
body = cls.admin_client.create_bandwidth_limit_rule(
policy_id, max_kbps, max_burst_kbps)
policy_id, max_kbps, max_burst_kbps, direction)
qos_rule = body['bandwidth_limit_rule']
cls.qos_rules.append(qos_rule)
return qos_rule

View File

@ -17,12 +17,16 @@ from tempest.lib import decorators
from tempest.lib import exceptions
from tempest import test
import testscenarios
import testtools
from neutron.services.qos import qos_consts
from neutron.tests.tempest.api import base
load_tests = testscenarios.load_tests_apply_scenarios
class QosTestJSON(base.BaseAdminNetworkTest):
@classmethod
@test.requires_ext(extension="qos", service="network")
@ -360,20 +364,34 @@ class QosTestJSON(base.BaseAdminNetworkTest):
class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
direction = None
@classmethod
@test.requires_ext(extension="qos", service="network")
@base.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
def resource_setup(cls):
super(QosBandwidthLimitRuleTestJSON, cls).resource_setup()
@property
def opposite_direction(self):
if self.direction == "ingress":
return "egress"
elif self.direction == "egress":
return "ingress"
else:
return None
@decorators.idempotent_id('8a59b00b-3e9c-4787-92f8-93a5cdf5e378')
def test_rule_create(self):
policy = self.create_qos_policy(name='test-policy',
description='test policy',
shared=False)
rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
max_kbps=200,
max_burst_kbps=1337)
rule = self.create_qos_bandwidth_limit_rule(
policy_id=policy['id'],
max_kbps=200,
max_burst_kbps=1337,
direction=self.direction)
# Test 'show rule'
retrieved_rule = self.admin_client.show_bandwidth_limit_rule(
@ -382,6 +400,8 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
self.assertEqual(rule['id'], retrieved_rule['id'])
self.assertEqual(200, retrieved_rule['max_kbps'])
self.assertEqual(1337, retrieved_rule['max_burst_kbps'])
if self.direction:
self.assertEqual(self.direction, retrieved_rule['direction'])
# Test 'list rules'
rules = self.admin_client.list_bandwidth_limit_rules(policy['id'])
@ -404,12 +424,14 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
shared=False)
self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
max_kbps=200,
max_burst_kbps=1337)
max_burst_kbps=1337,
direction=self.direction)
self.assertRaises(exceptions.Conflict,
self.create_qos_bandwidth_limit_rule,
policy_id=policy['id'],
max_kbps=201, max_burst_kbps=1338)
max_kbps=201, max_burst_kbps=1338,
direction=self.direction)
@decorators.idempotent_id('149a6988-2568-47d2-931e-2dbc858943b3')
def test_rule_update(self):
@ -418,18 +440,24 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
shared=False)
rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
max_kbps=1,
max_burst_kbps=1)
max_burst_kbps=1,
direction=self.direction)
self.admin_client.update_bandwidth_limit_rule(policy['id'],
rule['id'],
max_kbps=200,
max_burst_kbps=1337)
self.admin_client.update_bandwidth_limit_rule(
policy['id'],
rule['id'],
max_kbps=200,
max_burst_kbps=1337,
direction=self.opposite_direction)
retrieved_policy = self.admin_client.show_bandwidth_limit_rule(
policy['id'], rule['id'])
retrieved_policy = retrieved_policy['bandwidth_limit_rule']
self.assertEqual(200, retrieved_policy['max_kbps'])
self.assertEqual(1337, retrieved_policy['max_burst_kbps'])
if self.opposite_direction:
self.assertEqual(self.opposite_direction,
retrieved_policy['direction'])
@decorators.idempotent_id('67ee6efd-7b33-4a68-927d-275b4f8ba958')
def test_rule_delete(self):
@ -437,7 +465,7 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
description='test policy',
shared=False)
rule = self.admin_client.create_bandwidth_limit_rule(
policy['id'], 200, 1337)['bandwidth_limit_rule']
policy['id'], 200, 1337, self.direction)['bandwidth_limit_rule']
retrieved_policy = self.admin_client.show_bandwidth_limit_rule(
policy['id'], rule['id'])
@ -454,14 +482,14 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
self.assertRaises(
exceptions.NotFound,
self.create_qos_bandwidth_limit_rule,
'policy', 200, 1337)
'policy', 200, 1337, self.direction)
@decorators.idempotent_id('a4a2e7ad-786f-4927-a85a-e545a93bd274')
def test_rule_create_forbidden_for_regular_tenants(self):
self.assertRaises(
exceptions.Forbidden,
self.client.create_bandwidth_limit_rule,
'policy', 1, 2)
'policy', 1, 2, self.direction)
@decorators.idempotent_id('1bfc55d9-6fd8-4293-ab3a-b1d69bf7cd2e')
def test_rule_update_forbidden_for_regular_tenants_own_policy(self):
@ -471,7 +499,8 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
tenant_id=self.client.tenant_id)
rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
max_kbps=1,
max_burst_kbps=1)
max_burst_kbps=1,
direction=self.direction)
self.assertRaises(
exceptions.Forbidden,
self.client.update_bandwidth_limit_rule,
@ -485,7 +514,8 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
tenant_id=self.admin_client.tenant_id)
rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
max_kbps=1,
max_burst_kbps=1)
max_burst_kbps=1,
direction=self.direction)
self.assertRaises(
exceptions.NotFound,
self.client.update_bandwidth_limit_rule,
@ -498,14 +528,16 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
shared=False)
rule1 = self.create_qos_bandwidth_limit_rule(policy_id=policy1['id'],
max_kbps=200,
max_burst_kbps=1337)
max_burst_kbps=1337,
direction=self.direction)
policy2 = self.create_qos_policy(name='test-policy2',
description='test policy2',
shared=False)
rule2 = self.create_qos_bandwidth_limit_rule(policy_id=policy2['id'],
max_kbps=5000,
max_burst_kbps=2523)
max_burst_kbps=2523,
direction=self.direction)
# Test 'list rules'
rules = self.admin_client.list_bandwidth_limit_rules(policy1['id'])
@ -515,6 +547,20 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
self.assertNotIn(rule2['id'], rules_ids)
class QosBandwidthLimitRuleWithDirectionTestJSON(
QosBandwidthLimitRuleTestJSON):
scenarios = [
('ingress', {'direction': 'ingress'}),
('egress', {'direction': 'egress'}),
]
@classmethod
@test.requires_ext(extension="qos-bw-limit-direction", service="network")
def resource_setup(cls):
super(QosBandwidthLimitRuleWithDirectionTestJSON, cls).resource_setup()
class RbacSharedQosPoliciesTest(base.BaseAdminNetworkTest):
force_tenant_isolation = True

View File

@ -577,16 +577,19 @@ class NetworkClientJSON(service_client.RestClient):
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
def create_bandwidth_limit_rule(self, policy_id, max_kbps, max_burst_kbps):
def create_bandwidth_limit_rule(self, policy_id, max_kbps,
max_burst_kbps, direction=None):
uri = '%s/qos/policies/%s/bandwidth_limit_rules' % (
self.uri_prefix, policy_id)
post_data = self.serialize({
post_data = {
'bandwidth_limit_rule': {
'max_kbps': max_kbps,
'max_burst_kbps': max_burst_kbps
}
})
resp, body = self.post(uri, post_data)
}
if direction:
post_data['bandwidth_limit_rule']['direction'] = direction
resp, body = self.post(uri, self.serialize(post_data))
self.expected_success(201, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
@ -610,6 +613,8 @@ class NetworkClientJSON(service_client.RestClient):
def update_bandwidth_limit_rule(self, policy_id, rule_id, **kwargs):
uri = '%s/qos/policies/%s/bandwidth_limit_rules/%s' % (
self.uri_prefix, policy_id, rule_id)
if "direction" in kwargs and kwargs['direction'] is None:
kwargs.pop('direction')
post_data = {'bandwidth_limit_rule': kwargs}
resp, body = self.put(uri, jsonutils.dumps(post_data))
body = self.deserialize_single(body)

View File

@ -14,6 +14,7 @@ import mock
from oslo_versionedobjects import exception
import testtools
from neutron.common import constants as n_const
from neutron.common import exceptions as n_exc
from neutron.db import models_v2
from neutron.objects.db import api as db_api
@ -132,13 +133,17 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
self.objs[0].create()
return self.objs[0]
def _create_test_policy_with_rules(self, rule_type, reload_rules=False):
def _create_test_policy_with_rules(self, rule_type, reload_rules=False,
bwlimit_direction=None):
policy_obj = self._create_test_policy()
rules = []
for obj_cls in (RULE_OBJ_CLS.get(rule_type)
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
bwlimit_direction is not None):
rule_fields['direction'] = bwlimit_direction
rule_obj = obj_cls(self.context, **rule_fields)
rule_obj.create()
rules.append(rule_obj)
@ -380,11 +385,11 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj, rule_objs = self._create_test_policy_with_rules(
RULE_OBJ_CLS.keys(), reload_rules=True)
policy_obj_v1_2 = self._policy_through_version(
policy_obj_v1_5 = self._policy_through_version(
policy_obj, policy.QosPolicy.VERSION)
for rule_obj in rule_objs:
self.assertIn(rule_obj, policy_obj_v1_2.rules)
self.assertIn(rule_obj, policy_obj_v1_5.rules)
def test_object_version_degradation_1_3_to_1_2_null_description(self):
policy_obj = self._create_test_policy()
@ -398,7 +403,8 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
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], reload_rules=True)
qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH],
reload_rules=True, bwlimit_direction=n_const.EGRESS_DIRECTION)
policy_obj_v1_0 = self._policy_through_version(policy_obj, '1.0')
@ -412,7 +418,8 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
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], reload_rules=True)
qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH],
reload_rules=True, bwlimit_direction=n_const.EGRESS_DIRECTION)
policy_obj_v1_1 = self._policy_through_version(policy_obj, '1.1')
@ -426,12 +433,14 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
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], reload_rules=True)
qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH],
reload_rules=True, bwlimit_direction=n_const.EGRESS_DIRECTION)
policy_obj_v1_2 = self._policy_through_version(policy_obj, '1.2')
for rule_obj in rule_objs:
self.assertIn(rule_obj, policy_obj_v1_2.rules)
self.assertIn(rule_objs[0], policy_obj_v1_2.rules)
self.assertIn(rule_objs[1], policy_obj_v1_2.rules)
self.assertIn(rule_objs[2], policy_obj_v1_2.rules)
def test_v1_4_to_v1_3_drops_project_id(self):
policy_new = self._create_test_policy()
@ -440,6 +449,32 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
self.assertNotIn('project_id', policy_v1_3['versioned_object.data'])
self.assertIn('tenant_id', policy_v1_3['versioned_object.data'])
def test_object_version_degradation_1_5_to_1_4_ingress_direction(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],
reload_rules=True, bwlimit_direction=n_const.INGRESS_DIRECTION)
policy_obj_v1_4 = self._policy_through_version(policy_obj, '1.4')
self.assertNotIn(rule_objs[0], policy_obj_v1_4.rules)
self.assertIn(rule_objs[1], policy_obj_v1_4.rules)
self.assertIn(rule_objs[2], policy_obj_v1_4.rules)
def test_object_version_degradation_1_5_to_1_4_egress_direction(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],
reload_rules=True, bwlimit_direction=n_const.EGRESS_DIRECTION)
policy_obj_v1_4 = self._policy_through_version(policy_obj, '1.4')
self.assertIn(rule_objs[0], policy_obj_v1_4.rules)
self.assertIn(rule_objs[1], policy_obj_v1_4.rules)
self.assertIn(rule_objs[2], policy_obj_v1_4.rules)
def test_filter_by_shared(self):
policy_obj = policy.QosPolicy(
self.context, name='shared-policy', shared=True)

View File

@ -14,6 +14,7 @@ from neutron_lib import constants
from oslo_versionedobjects import exception
from neutron.common import constants as n_const
from neutron.objects.qos import policy
from neutron.objects.qos import rule
from neutron.services.qos import qos_consts
@ -117,6 +118,25 @@ class QosBandwidthLimitRuleObjectTestCase(test_base.BaseObjectIfaceTestCase):
dict_ = obj.to_dict()
self.assertEqual(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, dict_['type'])
def test_bandwidth_limit_object_version_degradation(self):
self.db_objs[0]['direction'] = n_const.EGRESS_DIRECTION
rule_obj = rule.QosBandwidthLimitRule(self.context, **self.db_objs[0])
primitive_rule = rule_obj.obj_to_primitive('1.2')
self.assertNotIn(
"direction", primitive_rule['versioned_object.data'].keys())
self.assertEqual(
self.db_objs[0]['max_kbps'],
primitive_rule['versioned_object.data']['max_kbps'])
self.assertEqual(
self.db_objs[0]['max_burst_kbps'],
primitive_rule['versioned_object.data']['max_burst_kbps'])
self.db_objs[0]['direction'] = n_const.INGRESS_DIRECTION
rule_obj = rule.QosBandwidthLimitRule(self.context, **self.db_objs[0])
self.assertRaises(
exception.IncompatibleObjectVersion,
rule_obj.obj_to_primitive, '1.2')
class QosBandwidthLimitRuleDbObjectTestCase(test_base.BaseDbObjectTestCase,
testlib_api.SqlTestCase):

View File

@ -60,11 +60,11 @@ object_data = {
'PortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
'ProviderResourceAssociation': '1.0-05ab2d5a3017e5ce9dd381328f285f34',
'ProvisioningBlock': '1.0-c19d6d05bfa8143533471c1296066125',
'QosBandwidthLimitRule': '1.2-4e44a8f5c2895ab1278399f87b40a13d',
'QosDscpMarkingRule': '1.2-0313c6554b34fd10c753cb63d638256c',
'QosMinimumBandwidthRule': '1.2-314c3419f4799067cc31cc319080adff',
'QosBandwidthLimitRule': '1.3-51b662b12a8d1dfa89288d826c6d26d3',
'QosDscpMarkingRule': '1.3-0313c6554b34fd10c753cb63d638256c',
'QosMinimumBandwidthRule': '1.3-314c3419f4799067cc31cc319080adff',
'QosRuleType': '1.2-e6fd08fcca152c339cbd5e9b94b1b8e7',
'QosPolicy': '1.4-50460f619c34428ec5651916e938e5a0',
'QosPolicy': '1.5-50460f619c34428ec5651916e938e5a0',
'Quota': '1.0-6bb6a0f1bd5d66a2134ffa1a61873097',
'QuotaUsage': '1.0-6fbf820368681aac7c5d664662605cf9',
'Reservation': '1.0-49929fef8e82051660342eed51b48f2a',

View File

@ -0,0 +1,6 @@
---
features:
- The QoS service plugin now supports new attribute in
``qos_bandwidth_limit_rule``. This new parameter is called
``direction`` and allows to specify direction of traffic
for which the limit should be applied.