Merge "Add QoS bandwidth limit for instance ingress traffic"

This commit is contained in:
Jenkins 2017-05-01 11:20:39 +00:00 committed by Gerrit Code Review
commit 90b01bb6ca
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), qos_policy_id = sa.Column(sa.String(36),
sa.ForeignKey('qos_policies.id', sa.ForeignKey('qos_policies.id',
ondelete='CASCADE'), ondelete='CASCADE'),
nullable=False, nullable=False)
unique=True)
max_kbps = sa.Column(sa.Integer) max_kbps = sa.Column(sa.Integer)
max_burst_kbps = sa.Column(sa.Integer) max_burst_kbps = sa.Column(sa.Integer)
revises_on_change = ('qos_policy', ) revises_on_change = ('qos_policy', )
qos_policy = sa.orm.relationship(QosPolicy, load_on_pending=True) 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): 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.plugins.common import constants
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
ALIAS = "qos"
QOS_PREFIX = "/qos" QOS_PREFIX = "/qos"
# Attribute Map # Attribute Map
@ -74,8 +75,10 @@ RESOURCE_ATTRIBUTE_MAP = {
} }
} }
BANDWIDTH_LIMIT_RULES = "bandwidth_limit_rules"
SUB_RESOURCE_ATTRIBUTE_MAP = { SUB_RESOURCE_ATTRIBUTE_MAP = {
'bandwidth_limit_rules': { BANDWIDTH_LIMIT_RULES: {
'parent': {'collection_name': 'policies', 'parent': {'collection_name': 'policies',
'member_name': 'policy'}, 'member_name': 'policy'},
'parameters': dict(QOS_RULE_COMMON_FIELDS, 'parameters': dict(QOS_RULE_COMMON_FIELDS,
@ -88,7 +91,7 @@ SUB_RESOURCE_ATTRIBUTE_MAP = {
'allow_post': True, 'allow_put': True, 'allow_post': True, 'allow_put': True,
'is_visible': True, 'default': 0, 'is_visible': True, 'default': 0,
'validate': {'type:range': [0, 'validate': {'type:range': [0,
common_constants.DB_INTEGER_MAX_VALUE]}}}) common_constants.DB_INTEGER_MAX_VALUE]}}}),
}, },
'dscp_marking_rules': { 'dscp_marking_rules': {
'parent': {'collection_name': 'policies', 'parent': {'collection_name': 'policies',
@ -196,12 +199,15 @@ class Qos(api_extensions.ExtensionDescriptor):
def update_attributes_map(self, attributes, extension_attrs_map=None): def update_attributes_map(self, attributes, extension_attrs_map=None):
super(Qos, self).update_attributes_map( 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): def get_extended_resources(self, version):
if version == "2.0": if version == "2.0":
return dict(list(EXTENDED_ATTRIBUTES_2_0.items()) + 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: else:
return {} 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 oslo_versionedobjects import fields as obj_fields
from neutron._i18n import _ from neutron._i18n import _
from neutron.common import constants as n_const
from neutron.common import exceptions from neutron.common import exceptions
from neutron.db import api as db_api from neutron.db import api as db_api
from neutron.db import models_v2 from neutron.db import models_v2
@ -40,7 +41,8 @@ class QosPolicy(rbac_db.NeutronRbacObject):
# Version 1.2: Added QosMinimumBandwidthRule # Version 1.2: Added QosMinimumBandwidthRule
# Version 1.3: Added standard attributes (created_at, revision, etc) # Version 1.3: Added standard attributes (created_at, revision, etc)
# Version 1.4: Changed tenant_id to project_id # 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 # required by RbacNeutronMetaclass
rbac_db_model = QosPolicyRBAC rbac_db_model = QosPolicyRBAC
@ -222,6 +224,19 @@ class QosPolicy(rbac_db.NeutronRbacObject):
return [rule for rule in rules if return [rule for rule in rules if
rule['versioned_object.name'] in obj_names] 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) _target_version = versionutils.convert_version_to_tuple(target_version)
names = [] names = []
if _target_version >= (1, 0): if _target_version >= (1, 0):
@ -244,3 +259,8 @@ class QosPolicy(rbac_db.NeutronRbacObject):
if _target_version < (1, 4): if _target_version < (1, 4):
primitive['tenant_id'] = primitive.pop('project_id') 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 from oslo_versionedobjects import fields as obj_fields
import six import six
from neutron.common import constants as n_const
from neutron.db import api as db_api from neutron.db import api as db_api
from neutron.db.qos import models as qos_db_model from neutron.db.qos import models as qos_db_model
from neutron.objects import base from neutron.objects import base
@ -50,11 +51,12 @@ class QosRule(base.NeutronDbObject):
# Version 1.0: Initial version, only BandwidthLimitRule # Version 1.0: Initial version, only BandwidthLimitRule
# 1.1: Added DscpMarkingRule # 1.1: Added DscpMarkingRule
# 1.2: Added QosMinimumBandwidthRule # 1.2: Added QosMinimumBandwidthRule
# 1.3: Added direction for BandwidthLimitRule
# #
#NOTE(mangelajo): versions need to be handled from the top QosRule object #NOTE(mangelajo): versions need to be handled from the top QosRule object
# because it's the only reference QosPolicy can make # because it's the only reference QosPolicy can make
# to them via obj_relationships version map # to them via obj_relationships version map
VERSION = '1.2' VERSION = '1.3'
fields = { fields = {
'id': common_types.UUIDField(), 'id': common_types.UUIDField(),
@ -106,11 +108,22 @@ class QosBandwidthLimitRule(QosRule):
fields = { fields = {
'max_kbps': obj_fields.IntegerField(nullable=True), '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 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 @obj_base.VersionedObjectRegistry.register
class QosDscpMarkingRule(QosRule): class QosDscpMarkingRule(QosRule):

View File

@ -29,7 +29,9 @@ SUPPORTED_RULES = {
qos_consts.MAX_KBPS: { qos_consts.MAX_KBPS: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]}, 'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.MAX_BURST: { 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.RULE_TYPE_DSCP_MARKING: {
qos_consts.DSCP_MARK: {'type:values': constants.VALID_DSCP_MARKS} qos_consts.DSCP_MARK: {'type:values': constants.VALID_DSCP_MARKS}

View File

@ -29,7 +29,9 @@ SUPPORTED_RULES = {
qos_consts.MAX_KBPS: { qos_consts.MAX_KBPS: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]}, 'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.MAX_BURST: { 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.RULE_TYPE_DSCP_MARKING: {
qos_consts.DSCP_MARK: {'type:values': constants.VALID_DSCP_MARKS} qos_consts.DSCP_MARK: {'type:values': constants.VALID_DSCP_MARKS}

View File

@ -29,7 +29,9 @@ SUPPORTED_RULES = {
qos_consts.MAX_KBPS: { qos_consts.MAX_KBPS: {
'type:range': [0, constants.DB_INTEGER_MAX_VALUE]}, 'type:range': [0, constants.DB_INTEGER_MAX_VALUE]},
qos_consts.MAX_BURST: { 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.RULE_TYPE_MINIMUM_BANDWIDTH: {
qos_consts.MIN_KBPS: { qos_consts.MIN_KBPS: {

View File

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

View File

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

View File

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

View File

@ -577,16 +577,19 @@ class NetworkClientJSON(service_client.RestClient):
self.expected_success(200, resp.status) self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body) 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' % ( uri = '%s/qos/policies/%s/bandwidth_limit_rules' % (
self.uri_prefix, policy_id) self.uri_prefix, policy_id)
post_data = self.serialize({ post_data = {
'bandwidth_limit_rule': { 'bandwidth_limit_rule': {
'max_kbps': max_kbps, 'max_kbps': max_kbps,
'max_burst_kbps': max_burst_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) self.expected_success(201, resp.status)
body = jsonutils.loads(body) body = jsonutils.loads(body)
return service_client.ResponseBody(resp, 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): def update_bandwidth_limit_rule(self, policy_id, rule_id, **kwargs):
uri = '%s/qos/policies/%s/bandwidth_limit_rules/%s' % ( uri = '%s/qos/policies/%s/bandwidth_limit_rules/%s' % (
self.uri_prefix, policy_id, rule_id) 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} post_data = {'bandwidth_limit_rule': kwargs}
resp, body = self.put(uri, jsonutils.dumps(post_data)) resp, body = self.put(uri, jsonutils.dumps(post_data))
body = self.deserialize_single(body) body = self.deserialize_single(body)

View File

@ -14,6 +14,7 @@ import mock
from oslo_versionedobjects import exception from oslo_versionedobjects import exception
import testtools import testtools
from neutron.common import constants as n_const
from neutron.common import exceptions as n_exc from neutron.common import exceptions as n_exc
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.objects.db import api as db_api from neutron.objects.db import api as db_api
@ -132,13 +133,17 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
self.objs[0].create() self.objs[0].create()
return self.objs[0] 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() policy_obj = self._create_test_policy()
rules = [] rules = []
for obj_cls in (RULE_OBJ_CLS.get(rule_type) for obj_cls in (RULE_OBJ_CLS.get(rule_type)
for rule_type in rule_type): for rule_type in rule_type):
rule_fields = self.get_random_object_fields(obj_cls=obj_cls) rule_fields = self.get_random_object_fields(obj_cls=obj_cls)
rule_fields['qos_policy_id'] = policy_obj.id 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 = obj_cls(self.context, **rule_fields)
rule_obj.create() rule_obj.create()
rules.append(rule_obj) rules.append(rule_obj)
@ -380,11 +385,11 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj, rule_objs = self._create_test_policy_with_rules( policy_obj, rule_objs = self._create_test_policy_with_rules(
RULE_OBJ_CLS.keys(), reload_rules=True) 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) policy_obj, policy.QosPolicy.VERSION)
for rule_obj in rule_objs: 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): def test_object_version_degradation_1_3_to_1_2_null_description(self):
policy_obj = self._create_test_policy() 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( policy_obj, rule_objs = self._create_test_policy_with_rules(
[qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
qos_consts.RULE_TYPE_DSCP_MARKING, 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') 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( policy_obj, rule_objs = self._create_test_policy_with_rules(
[qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
qos_consts.RULE_TYPE_DSCP_MARKING, 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') 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( policy_obj, rule_objs = self._create_test_policy_with_rules(
[qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
qos_consts.RULE_TYPE_DSCP_MARKING, 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') policy_obj_v1_2 = self._policy_through_version(policy_obj, '1.2')
for rule_obj in rule_objs: self.assertIn(rule_objs[0], policy_obj_v1_2.rules)
self.assertIn(rule_obj, 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): def test_v1_4_to_v1_3_drops_project_id(self):
policy_new = self._create_test_policy() 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.assertNotIn('project_id', policy_v1_3['versioned_object.data'])
self.assertIn('tenant_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): def test_filter_by_shared(self):
policy_obj = policy.QosPolicy( policy_obj = policy.QosPolicy(
self.context, name='shared-policy', shared=True) self.context, name='shared-policy', shared=True)

View File

@ -14,6 +14,7 @@ from neutron_lib import constants
from oslo_versionedobjects import exception from oslo_versionedobjects import exception
from neutron.common import constants as n_const
from neutron.objects.qos import policy from neutron.objects.qos import policy
from neutron.objects.qos import rule from neutron.objects.qos import rule
from neutron.services.qos import qos_consts from neutron.services.qos import qos_consts
@ -117,6 +118,25 @@ class QosBandwidthLimitRuleObjectTestCase(test_base.BaseObjectIfaceTestCase):
dict_ = obj.to_dict() dict_ = obj.to_dict()
self.assertEqual(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, dict_['type']) 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, class QosBandwidthLimitRuleDbObjectTestCase(test_base.BaseDbObjectTestCase,
testlib_api.SqlTestCase): testlib_api.SqlTestCase):

View File

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