Add QoS minimum bandwidth rule for instance egress traffic

This patch introduces the front end implementation for QoS
minimum bandwidth rule.

APIImpact: New type of parameter for QoS rule in neutron API
DocImpact

Change-Id: I6b619a96a2bfde164646c71409b671352bc6ce7d
Partial-Bug: #1560963
This commit is contained in:
Rodolfo Alonso Hernandez 2016-07-18 11:52:12 +01:00 committed by Ihar Hrachyshka
parent 79927038bc
commit 60325f4ae9
19 changed files with 509 additions and 64 deletions

View File

@ -184,6 +184,10 @@ For QoS, new neutron objects were implemented:
of the IP header, and only certain configurations are valid. As a result, the list of the IP header, and only certain configurations are valid. As a result, the list
of valid DSCP rule types is: 0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, of valid DSCP rule types is: 0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32,
34, 36, 38, 40, 46, 48, and 56. 34, 36, 38, 40, 46, 48, and 56.
* QosMinimumBandwidthRule: defines the minimum assured bandwidth rule type,
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.
Those are defined in: Those are defined in:

View File

@ -208,6 +208,10 @@
"delete_policy_dscp_marking_rule": "rule:admin_only", "delete_policy_dscp_marking_rule": "rule:admin_only",
"update_policy_dscp_marking_rule": "rule:admin_only", "update_policy_dscp_marking_rule": "rule:admin_only",
"get_rule_type": "rule:regular_user", "get_rule_type": "rule:regular_user",
"get_policy_minimum_bandwidth_rule": "rule:regular_user",
"create_policy_minimum_bandwidth_rule": "rule:admin_only",
"delete_policy_minimum_bandwidth_rule": "rule:admin_only",
"update_policy_minimum_bandwidth_rule": "rule:admin_only",
"restrict_wildcard": "(not field:rbac_policy:target_tenant=*) or rule:admin_only", "restrict_wildcard": "(not field:rbac_policy:target_tenant=*) or rule:admin_only",
"create_rbac_policy": "", "create_rbac_policy": "",

View File

@ -1 +1 @@
a5648cfeeadf 0f5bef0f87d4

View File

@ -0,0 +1,49 @@
# Copyright 2016 Intel Corporation.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
"""add_qos_minimum_bandwidth_rules
Revision ID: 0f5bef0f87d4
Revises: a5648cfeeadf
Create Date: 2016-07-29 14:33:37.243487
"""
# revision identifiers, used by Alembic.
revision = '0f5bef0f87d4'
down_revision = 'a5648cfeeadf'
from alembic import op
import sqlalchemy as sa
from neutron.common import constants
def upgrade():
op.create_table(
'qos_minimum_bandwidth_rules',
sa.Column('id', sa.String(length=36), primary_key=True),
sa.Column('qos_policy_id',
sa.String(length=36),
sa.ForeignKey('qos_policies.id', ondelete='CASCADE'),
nullable=False, index=True),
sa.Column('min_kbps', sa.Integer()),
sa.Column('direction', sa.Enum(constants.EGRESS_DIRECTION,
constants.INGRESS_DIRECTION,
name='directions'),
nullable=False, server_default=constants.EGRESS_DIRECTION),
sa.UniqueConstraint('qos_policy_id', 'direction',
name='qos_minimum_bandwidth_rules0qos_policy_id0direction')
)

View File

@ -16,6 +16,7 @@
import sqlalchemy as sa import sqlalchemy as sa
from neutron.api.v2 import attributes as attrs from neutron.api.v2 import attributes as attrs
from neutron.common import constants
from neutron.db import model_base from neutron.db import model_base
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.db import rbac_db_models from neutron.db import rbac_db_models
@ -87,3 +88,24 @@ class QosDscpMarkingRule(models_v2.HasId, model_base.BASEV2):
nullable=False, nullable=False,
unique=True) unique=True)
dscp_mark = sa.Column(sa.Integer) dscp_mark = sa.Column(sa.Integer)
class QosMinimumBandwidthRule(models_v2.HasId, model_base.BASEV2):
__tablename__ = 'qos_minimum_bandwidth_rules'
qos_policy_id = sa.Column(sa.String(36),
sa.ForeignKey('qos_policies.id',
ondelete='CASCADE'),
nullable=False,
index=True)
min_kbps = sa.Column(sa.Integer)
direction = sa.Column(sa.Enum(constants.EGRESS_DIRECTION,
constants.INGRESS_DIRECTION,
name='directions'),
nullable=False,
server_default=constants.EGRESS_DIRECTION)
__table_args__ = (
sa.UniqueConstraint(
qos_policy_id, direction,
name='qos_minimum_bandwidth_rules0qos_policy_id0direction'),
model_base.BASEV2.__table_args__
)

View File

@ -95,6 +95,21 @@ SUB_RESOURCE_ATTRIBUTE_MAP = {
'is_visible': True, 'default': None, 'is_visible': True, 'default': None,
'validate': {'type:values': common_constants. 'validate': {'type:values': common_constants.
VALID_DSCP_MARKS}}}) VALID_DSCP_MARKS}}})
},
'minimum_bandwidth_rules': {
'parent': {'collection_name': 'policies',
'member_name': 'policy'},
'parameters': dict(QOS_RULE_COMMON_FIELDS,
**{'min_kbps': {
'allow_post': True, 'allow_put': True,
'is_visible': True, 'default': None,
'validate': {'type:range': [0,
common_constants.DB_INTEGER_MAX_VALUE]}},
'direction': {
'allow_post': True, 'allow_put': True,
'is_visible': True, 'default': 'egress',
'validate': {'type:values':
[common_constants.EGRESS_DIRECTION]}}})
} }
} }
@ -194,7 +209,8 @@ class QoSPluginBase(service_base.ServicePluginBase):
# The rule object type to use for each incoming rule-related request. # The rule object type to use for each incoming rule-related request.
rule_objects = {'bandwidth_limit': rule_object.QosBandwidthLimitRule, rule_objects = {'bandwidth_limit': rule_object.QosBandwidthLimitRule,
'dscp_marking': rule_object.QosDscpMarkingRule} 'dscp_marking': rule_object.QosDscpMarkingRule,
'minimum_bandwidth': rule_object.QosMinimumBandwidthRule}
# Patterns used to call method proxies for all policy-rule-specific # Patterns used to call method proxies for all policy-rule-specific
# method calls (see __getattr__ docstring, below). # method calls (see __getattr__ docstring, below).

View File

@ -38,7 +38,8 @@ from neutron.objects import rbac_db
class QosPolicy(base.NeutronDbObject): class QosPolicy(base.NeutronDbObject):
# Version 1.0: Initial version # Version 1.0: Initial version
# Version 1.1: QosDscpMarkingRule introduced # Version 1.1: QosDscpMarkingRule introduced
VERSION = '1.1' # Version 1.2: Added QosMinimumBandwidthRule
VERSION = '1.2'
# required by RbacNeutronMetaclass # required by RbacNeutronMetaclass
rbac_db_model = QosPolicyRBAC rbac_db_model = QosPolicyRBAC
@ -212,11 +213,15 @@ class QosPolicy(base.NeutronDbObject):
return set(bound_tenants) return set(bound_tenants)
def obj_make_compatible(self, primitive, target_version): def obj_make_compatible(self, primitive, target_version):
def filter_rules(obj_names, rules):
return filter(lambda rule:
(rule['versioned_object.name'] in obj_names), rules)
_target_version = versionutils.convert_version_to_tuple(target_version) _target_version = versionutils.convert_version_to_tuple(target_version)
if _target_version < (1, 1): names = []
if 'rules' in primitive: if _target_version >= (1, 0):
bw_obj_name = rule_obj_impl.QosBandwidthLimitRule.obj_name() names.append(rule_obj_impl.QosBandwidthLimitRule.obj_name())
primitive['rules'] = filter( if _target_version >= (1, 1):
lambda rule: (rule['versioned_object.name'] == names.append(rule_obj_impl.QosDscpMarkingRule.obj_name())
bw_obj_name), if 'rules' in primitive and names:
primitive['rules']) primitive['rules'] = filter_rules(names, primitive['rules'])

View File

@ -49,11 +49,12 @@ def get_rules(context, qos_policy_id):
class QosRule(base.NeutronDbObject): 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
# #
#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.1' VERSION = '1.2'
fields = { fields = {
'id': obj_fields.UUIDField(), 'id': obj_fields.UUIDField(),
@ -117,3 +118,23 @@ class QosDscpMarkingRule(QosRule):
raise exception.IncompatibleObjectVersion( raise exception.IncompatibleObjectVersion(
objver=target_version, objver=target_version,
objname="QosDscpMarkingRule") objname="QosDscpMarkingRule")
@obj_base.VersionedObjectRegistry.register
class QosMinimumBandwidthRule(QosRule):
db_model = qos_db_model.QosMinimumBandwidthRule
fields = {
'min_kbps': obj_fields.IntegerField(nullable=True),
'direction': common_types.FlowDirectionEnumField(),
}
rule_type = qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH
def obj_make_compatible(self, primitive, target_version):
_target_version = versionutils.convert_version_to_tuple(target_version)
if _target_version < (1, 2):
raise exception.IncompatibleObjectVersion(
objver=target_version,
objname="QosMinimumBandwidthRule")

View File

@ -29,8 +29,9 @@ class RuleTypeField(obj_fields.BaseEnumField):
@obj_base.VersionedObjectRegistry.register @obj_base.VersionedObjectRegistry.register
class QosRuleType(base.NeutronObject): class QosRuleType(base.NeutronObject):
# Version 1.0: Initial version # Version 1.0: Initial version
# Version 1.1: Added DscpMarkingRule # Version 1.1: Added QosDscpMarkingRule
VERSION = '1.1' # Version 1.2: Added QosMinimumBandwidthRule
VERSION = '1.2'
fields = { fields = {
'type': RuleTypeField(), 'type': RuleTypeField(),

View File

@ -15,7 +15,11 @@
RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth_limit' RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth_limit'
RULE_TYPE_DSCP_MARKING = 'dscp_marking' RULE_TYPE_DSCP_MARKING = 'dscp_marking'
VALID_RULE_TYPES = [RULE_TYPE_BANDWIDTH_LIMIT, RULE_TYPE_DSCP_MARKING] RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum_bandwidth'
VALID_RULE_TYPES = [RULE_TYPE_BANDWIDTH_LIMIT,
RULE_TYPE_DSCP_MARKING,
RULE_TYPE_MINIMUM_BANDWIDTH,
]
QOS_POLICY_ID = 'qos_policy_id' QOS_POLICY_ID = 'qos_policy_id'

View File

@ -208,6 +208,10 @@
"delete_policy_dscp_marking_rule": "rule:admin_only", "delete_policy_dscp_marking_rule": "rule:admin_only",
"update_policy_dscp_marking_rule": "rule:admin_only", "update_policy_dscp_marking_rule": "rule:admin_only",
"get_rule_type": "rule:regular_user", "get_rule_type": "rule:regular_user",
"get_policy_minimum_bandwidth_rule": "rule:regular_user",
"create_policy_minimum_bandwidth_rule": "rule:admin_only",
"delete_policy_minimum_bandwidth_rule": "rule:admin_only",
"update_policy_minimum_bandwidth_rule": "rule:admin_only",
"restrict_wildcard": "(not field:rbac_policy:target_tenant=*) or rule:admin_only", "restrict_wildcard": "(not field:rbac_policy:target_tenant=*) or rule:admin_only",
"create_rbac_policy": "", "create_rbac_policy": "",

View File

@ -337,6 +337,13 @@ class QosTestJSON(base.BaseAdminNetworkTest):
obtained_policy = self.client.show_qos_policy(policy['id'])['policy'] obtained_policy = self.client.show_qos_policy(policy['id'])['policy']
self.assertEqual(obtained_policy, policy) self.assertEqual(obtained_policy, policy)
@test.idempotent_id('aed8e2a6-22da-421b-89b9-935a2c1a1b50')
def test_policy_create_forbidden_for_regular_tenants(self):
self.assertRaises(
exceptions.Forbidden,
self.client.create_qos_policy,
'test-policy', 'test policy', False)
class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest): class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
@classmethod @classmethod
@ -434,13 +441,6 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
self.create_qos_bandwidth_limit_rule, self.create_qos_bandwidth_limit_rule,
'policy', 200, 1337) 'policy', 200, 1337)
@test.idempotent_id('eed8e2a6-22da-421b-89b9-935a2c1a1b50')
def test_policy_create_forbidden_for_regular_tenants(self):
self.assertRaises(
exceptions.Forbidden,
self.client.create_qos_policy,
'test-policy', 'test policy', False)
@test.idempotent_id('a4a2e7ad-786f-4927-a85a-e545a93bd274') @test.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(
@ -883,6 +883,147 @@ class QosDscpMarkingRuleTestJSON(base.BaseAdminNetworkTest):
self.assertNotIn(rule2['id'], rules_ids) self.assertNotIn(rule2['id'], rules_ids)
class QosMinimumBandwidthRuleTestJSON(base.BaseAdminNetworkTest):
DIRECTION_EGRESS = "egress"
DIRECTION_INGRESS = "ingress"
RULE_NAME = qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH + "_rule"
RULES_NAME = RULE_NAME + "s"
@classmethod
@test.requires_ext(extension="qos", service="network")
def resource_setup(cls):
super(QosMinimumBandwidthRuleTestJSON, cls).resource_setup()
@test.idempotent_id('aa59b00b-3e9c-4787-92f8-93a5cdf5e378')
def test_rule_create(self):
policy = self.create_qos_policy(name='test-policy',
description='test policy',
shared=False)
rule = self.admin_client.create_minimum_bandwidth_rule(
policy_id=policy['id'], min_kbps=1138,
direction=self.DIRECTION_EGRESS)[self.RULE_NAME]
# Test 'show rule'
retrieved_rule = self.admin_client.show_minimum_bandwidth_rule(
policy['id'], rule['id'])
retrieved_rule = retrieved_rule[self.RULE_NAME]
self.assertEqual(rule['id'], retrieved_rule['id'])
self.assertEqual(1138, retrieved_rule['min_kbps'])
self.assertEqual(self.DIRECTION_EGRESS, retrieved_rule['direction'])
# Test 'list rules'
rules = self.admin_client.list_minimum_bandwidth_rules(policy['id'])
rules = rules[self.RULES_NAME]
rules_ids = [r['id'] for r in rules]
self.assertIn(rule['id'], rules_ids)
# Test 'show policy'
retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
policy_rules = retrieved_policy['policy']['rules']
self.assertEqual(1, len(policy_rules))
self.assertEqual(rule['id'], policy_rules[0]['id'])
self.assertEqual(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH,
policy_rules[0]['type'])
@test.idempotent_id('aa59b00b-ab01-4787-92f8-93a5cdf5e378')
def test_rule_create_fail_for_the_same_type(self):
policy = self.create_qos_policy(name='test-policy',
description='test policy',
shared=False)
self.admin_client.create_minimum_bandwidth_rule(
policy_id=policy['id'], min_kbps=200,
direction=self.DIRECTION_EGRESS)
self.assertRaises(exceptions.Conflict,
self.admin_client.create_minimum_bandwidth_rule,
policy_id=policy['id'],
min_kbps=201, direction=self.DIRECTION_EGRESS)
@test.idempotent_id('d6fce764-e511-4fa6-9f86-f4b41cf142cf')
def test_rule_create_fail_for_direction_ingress(self):
policy = self.create_qos_policy(name='test-policy',
description='test policy',
shared=False)
self.assertRaises(exceptions.BadRequest,
self.admin_client.create_minimum_bandwidth_rule,
policy_id=policy['id'],
min_kbps=201, direction=self.DIRECTION_INGRESS)
@test.idempotent_id('a49a6988-2568-47d2-931e-2dbc858943b3')
def test_rule_update(self):
policy = self.create_qos_policy(name='test-policy',
description='test policy',
shared=False)
rule = self.admin_client.create_minimum_bandwidth_rule(
policy_id=policy['id'], min_kbps=300,
direction=self.DIRECTION_EGRESS)[self.RULE_NAME]
self.admin_client.update_minimum_bandwidth_rule(policy['id'],
rule['id'], min_kbps=350, direction=self.DIRECTION_EGRESS)
retrieved_policy = self.admin_client.show_minimum_bandwidth_rule(
policy['id'], rule['id'])
retrieved_policy = retrieved_policy[self.RULE_NAME]
self.assertEqual(350, retrieved_policy['min_kbps'])
self.assertEqual(self.DIRECTION_EGRESS, retrieved_policy['direction'])
@test.idempotent_id('a7ee6efd-7b33-4a68-927d-275b4f8ba958')
def test_rule_delete(self):
policy = self.create_qos_policy(name='test-policy',
description='test policy',
shared=False)
rule = self.admin_client.create_minimum_bandwidth_rule(
policy['id'], 200, self.DIRECTION_EGRESS)[self.RULE_NAME]
retrieved_policy = self.admin_client.show_minimum_bandwidth_rule(
policy['id'], rule['id'])
retrieved_policy = retrieved_policy[self.RULE_NAME]
self.assertEqual(rule['id'], retrieved_policy['id'])
self.admin_client.delete_minimum_bandwidth_rule(policy['id'],
rule['id'])
self.assertRaises(exceptions.NotFound,
self.admin_client.show_minimum_bandwidth_rule,
policy['id'], rule['id'])
@test.idempotent_id('a211222c-5808-46cb-a961-983bbab6b852')
def test_rule_create_rule_nonexistent_policy(self):
self.assertRaises(
exceptions.NotFound,
self.admin_client.create_minimum_bandwidth_rule,
'policy', 200, self.DIRECTION_EGRESS)
@test.idempotent_id('b4a2e7ad-786f-4927-a85a-e545a93bd274')
def test_rule_create_forbidden_for_regular_tenants(self):
self.assertRaises(
exceptions.Forbidden,
self.client.create_minimum_bandwidth_rule,
'policy', 300, self.DIRECTION_EGRESS)
@test.idempotent_id('de0bd0c2-54d9-4e29-85f1-cfb36ac3ebe2')
def test_get_rules_by_policy(self):
policy1 = self.create_qos_policy(name='test-policy1',
description='test policy1',
shared=False)
rule1 = self.admin_client.create_minimum_bandwidth_rule(
policy_id=policy1['id'], min_kbps=200,
direction=self.DIRECTION_EGRESS)[self.RULE_NAME]
policy2 = self.create_qos_policy(name='test-policy2',
description='test policy2',
shared=False)
rule2 = self.admin_client.create_minimum_bandwidth_rule(
policy_id=policy2['id'], min_kbps=5000,
direction=self.DIRECTION_EGRESS)[self.RULE_NAME]
# Test 'list rules'
rules = self.admin_client.list_minimum_bandwidth_rules(policy1['id'])
rules = rules[self.RULES_NAME]
rules_ids = [r['id'] for r in rules]
self.assertIn(rule1['id'], rules_ids)
self.assertNotIn(rule2['id'], rules_ids)
class QosSearchCriteriaTest(base.BaseSearchCriteriaTest, class QosSearchCriteriaTest(base.BaseSearchCriteriaTest,
base.BaseAdminNetworkTest): base.BaseAdminNetworkTest):

View File

@ -55,6 +55,7 @@ class NetworkClientJSON(service_client.RestClient):
'metering_label_rules': 'metering', 'metering_label_rules': 'metering',
'policies': 'qos', 'policies': 'qos',
'bandwidth_limit_rules': 'qos', 'bandwidth_limit_rules': 'qos',
'minimum_bandwidth_rules': 'qos',
'rule_types': 'qos', 'rule_types': 'qos',
'rbac-policies': '', 'rbac-policies': '',
} }
@ -652,6 +653,52 @@ class NetworkClientJSON(service_client.RestClient):
self.expected_success(204, resp.status) self.expected_success(204, resp.status)
return service_client.ResponseBody(resp, body) return service_client.ResponseBody(resp, body)
def create_minimum_bandwidth_rule(self, policy_id, min_kbps, direction):
uri = '%s/qos/policies/%s/minimum_bandwidth_rules' % (
self.uri_prefix, policy_id)
post_data = self.serialize({
'minimum_bandwidth_rule': {
'min_kbps': min_kbps,
'direction': direction
}
})
resp, body = self.post(uri, post_data)
self.expected_success(201, resp.status)
body = jsonutils.loads(body)
return service_client.ResponseBody(resp, body)
def list_minimum_bandwidth_rules(self, policy_id):
uri = '%s/qos/policies/%s/minimum_bandwidth_rules' % (
self.uri_prefix, policy_id)
resp, body = self.get(uri)
body = self.deserialize_single(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
def show_minimum_bandwidth_rule(self, policy_id, rule_id):
uri = '%s/qos/policies/%s/minimum_bandwidth_rules/%s' % (
self.uri_prefix, policy_id, rule_id)
resp, body = self.get(uri)
body = self.deserialize_single(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
def update_minimum_bandwidth_rule(self, policy_id, rule_id, **kwargs):
uri = '%s/qos/policies/%s/minimum_bandwidth_rules/%s' % (
self.uri_prefix, policy_id, rule_id)
post_data = {'minimum_bandwidth_rule': kwargs}
resp, body = self.put(uri, jsonutils.dumps(post_data))
body = self.deserialize_single(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
def delete_minimum_bandwidth_rule(self, policy_id, rule_id):
uri = '%s/qos/policies/%s/minimum_bandwidth_rules/%s' % (
self.uri_prefix, policy_id, rule_id)
resp, body = self.delete(uri)
self.expected_success(204, resp.status)
return service_client.ResponseBody(resp, body)
def list_qos_rule_types(self): def list_qos_rule_types(self):
uri = '%s/qos/rule-types' % self.uri_prefix uri = '%s/qos/rule-types' % self.uri_prefix
resp, body = self.get(uri) resp, body = self.get(uri)

View File

@ -17,10 +17,18 @@ from neutron.db import models_v2
from neutron.objects.db import api as db_api from neutron.objects.db import api as db_api
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.tests.unit.objects import test_base from neutron.tests.unit.objects import test_base
from neutron.tests.unit import testlib_api from neutron.tests.unit import testlib_api
RULE_OBJ_CLS = {
qos_consts.RULE_TYPE_BANDWIDTH_LIMIT: rule.QosBandwidthLimitRule,
qos_consts.RULE_TYPE_DSCP_MARKING: rule.QosDscpMarkingRule,
qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH: rule.QosMinimumBandwidthRule,
}
class QosPolicyObjectTestCase(test_base.BaseObjectIfaceTestCase): class QosPolicyObjectTestCase(test_base.BaseObjectIfaceTestCase):
_test_class = policy.QosPolicy _test_class = policy.QosPolicy
@ -36,13 +44,19 @@ class QosPolicyObjectTestCase(test_base.BaseObjectIfaceTestCase):
self.get_random_fields(rule.QosDscpMarkingRule) self.get_random_fields(rule.QosDscpMarkingRule)
for _ in range(3)] for _ in range(3)]
self.db_qos_minimum_bandwidth_rules = [
self.get_random_fields(rule.QosMinimumBandwidthRule)
for _ in range(3)]
self.model_map = { self.model_map = {
self._test_class.db_model: self.db_objs, self._test_class.db_model: self.db_objs,
self._test_class.rbac_db_model: [], self._test_class.rbac_db_model: [],
self._test_class.port_binding_model: [], self._test_class.port_binding_model: [],
self._test_class.network_binding_model: [], self._test_class.network_binding_model: [],
rule.QosBandwidthLimitRule.db_model: self.db_qos_bandwidth_rules, rule.QosBandwidthLimitRule.db_model: self.db_qos_bandwidth_rules,
rule.QosDscpMarkingRule.db_model: self.db_qos_dscp_rules} rule.QosDscpMarkingRule.db_model: self.db_qos_dscp_rules,
rule.QosMinimumBandwidthRule.db_model:
self.db_qos_minimum_bandwidth_rules}
self.get_object = mock.patch.object( self.get_object = mock.patch.object(
db_api, 'get_object', side_effect=self.fake_get_object).start() db_api, 'get_object', side_effect=self.fake_get_object).start()
@ -127,17 +141,20 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj.create() policy_obj.create()
return policy_obj return policy_obj
def _create_test_policy_with_bwrule(self): def _create_test_policy_with_rules(self, rule_type, reload_rules=False):
policy_obj = self._create_test_policy() 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_fields(obj_cls=obj_cls)
rule_fields['qos_policy_id'] = policy_obj.id
rule_obj = obj_cls(self.context, **rule_fields)
rule_obj.create()
rules.append(rule_obj)
rule_fields = self.get_random_fields( if reload_rules:
obj_cls=rule.QosBandwidthLimitRule) policy_obj.reload_rules()
rule_fields['qos_policy_id'] = policy_obj.id return policy_obj, rules
rule_obj = rule.QosBandwidthLimitRule(self.context, **rule_fields)
rule_obj.create()
return policy_obj, rule_obj
def test_attach_network_get_network_policy(self): def test_attach_network_get_network_policy(self):
@ -291,27 +308,30 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj.detach_network, self._network['id']) policy_obj.detach_network, self._network['id'])
def test_synthetic_rule_fields(self): def test_synthetic_rule_fields(self):
policy_obj, rule_obj = self._create_test_policy_with_bwrule() policy_obj, rule_obj = self._create_test_policy_with_rules(
[qos_consts.RULE_TYPE_BANDWIDTH_LIMIT])
policy_obj = policy.QosPolicy.get_object(self.context, policy_obj = policy.QosPolicy.get_object(self.context,
id=policy_obj.id) id=policy_obj.id)
self.assertEqual([rule_obj], policy_obj.rules) self.assertEqual(rule_obj, policy_obj.rules)
def test_get_object_fetches_rules_non_lazily(self): def test_get_object_fetches_rules_non_lazily(self):
policy_obj, rule_obj = self._create_test_policy_with_bwrule() policy_obj, rule_obj = self._create_test_policy_with_rules(
[qos_consts.RULE_TYPE_BANDWIDTH_LIMIT])
policy_obj = policy.QosPolicy.get_object(self.context, policy_obj = policy.QosPolicy.get_object(self.context,
id=policy_obj.id) id=policy_obj.id)
self.assertEqual([rule_obj], policy_obj.rules) self.assertEqual(rule_obj, policy_obj.rules)
primitive = policy_obj.obj_to_primitive() primitive = policy_obj.obj_to_primitive()
self.assertNotEqual([], (primitive['versioned_object.data']['rules'])) self.assertNotEqual([], (primitive['versioned_object.data']['rules']))
def test_to_dict_returns_rules_as_dicts(self): def test_to_dict_returns_rules_as_dicts(self):
policy_obj, rule_obj = self._create_test_policy_with_bwrule() policy_obj, rule_obj = self._create_test_policy_with_rules(
[qos_consts.RULE_TYPE_BANDWIDTH_LIMIT])
policy_obj = policy.QosPolicy.get_object(self.context, policy_obj = policy.QosPolicy.get_object(self.context,
id=policy_obj.id) id=policy_obj.id)
obj_dict = policy_obj.to_dict() obj_dict = policy_obj.to_dict()
rule_dict = rule_obj.to_dict() rule_dict = rule_obj[0].to_dict()
# first make sure that to_dict() is still sane and does not return # first make sure that to_dict() is still sane and does not return
# objects # objects
@ -343,11 +363,12 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
obj.delete() obj.delete()
def test_reload_rules_reloads_rules(self): def test_reload_rules_reloads_rules(self):
policy_obj, rule_obj = self._create_test_policy_with_bwrule() policy_obj, rule_obj = self._create_test_policy_with_rules(
[qos_consts.RULE_TYPE_BANDWIDTH_LIMIT])
self.assertEqual([], policy_obj.rules) self.assertEqual([], policy_obj.rules)
policy_obj.reload_rules() policy_obj.reload_rules()
self.assertEqual([rule_obj], policy_obj.rules) self.assertEqual(rule_obj, policy_obj.rules)
def test_get_bound_tenant_ids_returns_set_of_tenant_ids(self): def test_get_bound_tenant_ids_returns_set_of_tenant_ids(self):
obj = self._create_test_policy() obj = self._create_test_policy()
@ -364,37 +385,40 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
primitive = obj.obj_to_primitive(target_version=version) primitive = obj.obj_to_primitive(target_version=version)
return policy.QosPolicy.clean_obj_from_primitive(primitive) return policy.QosPolicy.clean_obj_from_primitive(primitive)
def _create_test_policy_with_bw_and_dscp(self):
policy_obj, rule_obj_band = self._create_test_policy_with_bwrule()
rule_fields = self.get_random_fields(obj_cls=rule.QosDscpMarkingRule)
rule_fields['qos_policy_id'] = policy_obj.id
rule_obj_dscp = rule.QosDscpMarkingRule(self.context, **rule_fields)
rule_obj_dscp.create()
policy_obj.reload_rules()
return policy_obj, rule_obj_band, rule_obj_dscp
def test_object_version(self): def test_object_version(self):
policy_obj, rule_obj_band, rule_obj_dscp = ( policy_obj, rule_objs = self._create_test_policy_with_rules(
self._create_test_policy_with_bw_and_dscp()) RULE_OBJ_CLS.keys(), reload_rules=True)
policy_obj_v1_1 = self._policy_through_version(policy_obj, '1.1') policy_obj_v1_2 = self._policy_through_version(policy_obj, '1.2')
self.assertIn(rule_obj_band, policy_obj_v1_1.rules) for rule_obj in rule_objs:
self.assertIn(rule_obj_dscp, policy_obj_v1_1.rules) self.assertIn(rule_obj, policy_obj_v1_2.rules)
def test_object_version_degradation_1_1_to_1_0(self): def test_object_version_degradation_1_1_to_1_0(self):
#NOTE(mangelajo): we should not check .VERSION, since that's the #NOTE(mangelajo): we should not check .VERSION, since that's the
# local version on the class definition # local version on the class definition
policy_obj, rule_obj_band, rule_obj_dscp = ( policy_obj, rule_objs = self._create_test_policy_with_rules(
self._create_test_policy_with_bw_and_dscp()) [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
qos_consts.RULE_TYPE_DSCP_MARKING], reload_rules=True)
policy_obj_v1_0 = self._policy_through_version(policy_obj, '1.0') policy_obj_v1_0 = self._policy_through_version(policy_obj, '1.0')
self.assertIn(rule_obj_band, policy_obj_v1_0.rules) self.assertIn(rule_objs[0], policy_obj_v1_0.rules)
self.assertNotIn(rule_obj_dscp, policy_obj_v1_0.rules) self.assertNotIn(rule_objs[1], policy_obj_v1_0.rules)
def test_object_version_degradation_1_2_to_1_1(self):
#NOTE(mangelajo): we should not check .VERSION, since that's the
# local version on the class definition
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)
policy_obj_v1_1 = self._policy_through_version(policy_obj, '1.1')
self.assertIn(rule_objs[0], policy_obj_v1_1.rules)
self.assertIn(rule_objs[1], policy_obj_v1_1.rules)
self.assertNotIn(rule_objs[2], policy_obj_v1_1.rules)
def test_filter_by_shared(self): def test_filter_by_shared(self):
policy_obj = policy.QosPolicy( policy_obj = policy.QosPolicy(

View File

@ -121,3 +121,37 @@ class QosDscpMarkingRuleDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj = policy.QosPolicy(self.context, policy_obj = policy.QosPolicy(self.context,
id=generated_qos_policy_id) id=generated_qos_policy_id)
policy_obj.create() policy_obj.create()
class QosMinimumBandwidthRuleObjectTestCase(test_base.BaseObjectIfaceTestCase):
_test_class = rule.QosMinimumBandwidthRule
def test_min_bw_object_version_degradation(self):
min_bw_rule = rule.QosMinimumBandwidthRule()
for version in ['1.0', '1.1']:
self.assertRaises(exception.IncompatibleObjectVersion,
min_bw_rule.obj_to_primitive, version)
def test_min_bw_object_version(self):
min_bw_rule = rule.QosMinimumBandwidthRule()
prim = min_bw_rule.obj_to_primitive('1.2')
self.assertTrue(prim)
class QosMinimumBandwidthRuleDbObjectTestCase(test_base.BaseDbObjectTestCase,
testlib_api.SqlTestCase):
_test_class = rule.QosMinimumBandwidthRule
def setUp(self):
super(QosMinimumBandwidthRuleDbObjectTestCase, 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)
policy_obj.create()

View File

@ -343,6 +343,10 @@ def get_random_dscp_mark():
return random.choice(constants.VALID_DSCP_MARKS) return random.choice(constants.VALID_DSCP_MARKS)
def get_random_direction():
return random.choice(constants.VALID_DIRECTIONS)
def get_list_of_random_networks(num=10): def get_list_of_random_networks(num=10):
for i in range(5): for i in range(5):
res = [tools.get_random_ip_network() for i in range(num)] res = [tools.get_random_ip_network() for i in range(num)]
@ -360,6 +364,7 @@ FIELD_TYPE_VALUE_GENERATOR_MAP = {
obj_fields.ObjectField: lambda: None, obj_fields.ObjectField: lambda: None,
obj_fields.ListOfObjectsField: lambda: [], obj_fields.ListOfObjectsField: lambda: [],
common_types.DscpMarkField: get_random_dscp_mark, common_types.DscpMarkField: get_random_dscp_mark,
common_types.FlowDirectionEnumField: get_random_direction,
obj_fields.IPNetworkField: tools.get_random_ip_network, obj_fields.IPNetworkField: tools.get_random_ip_network,
common_types.IPNetworkField: tools.get_random_ip_network, common_types.IPNetworkField: tools.get_random_ip_network,
common_types.IPNetworkPrefixLenField: tools.get_random_prefixlen, common_types.IPNetworkPrefixLenField: tools.get_random_prefixlen,

View File

@ -35,10 +35,11 @@ object_data = {
'NetworkSegment': '1.0-865567a6f70eb85cf33fb7a5575a4eab', 'NetworkSegment': '1.0-865567a6f70eb85cf33fb7a5575a4eab',
'PortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3', 'PortSecurity': '1.0-b30802391a87945ee9c07582b4ff95e3',
'AllowedAddressPair': '1.0-9f9186b6f952fbf31d257b0458b852c0', 'AllowedAddressPair': '1.0-9f9186b6f952fbf31d257b0458b852c0',
'QosBandwidthLimitRule': '1.1-4e44a8f5c2895ab1278399f87b40a13d', 'QosBandwidthLimitRule': '1.2-4e44a8f5c2895ab1278399f87b40a13d',
'QosDscpMarkingRule': '1.1-0313c6554b34fd10c753cb63d638256c', 'QosDscpMarkingRule': '1.2-0313c6554b34fd10c753cb63d638256c',
'QosRuleType': '1.1-8a53fef4c6a43839d477a85b787d22ce', 'QosMinimumBandwidthRule': '1.2-314c3419f4799067cc31cc319080adff',
'QosPolicy': '1.1-7c5659e1c1f64395223592d3d3293e22', 'QosRuleType': '1.2-e6fd08fcca152c339cbd5e9b94b1b8e7',
'QosPolicy': '1.2-7c5659e1c1f64395223592d3d3293e22',
'Route': '1.0-a9883a63b416126f9e345523ec09483b', 'Route': '1.0-a9883a63b416126f9e345523ec09483b',
'SecurityGroup': '1.0-e26b90c409b31fd2e3c6fcec402ac0b9', 'SecurityGroup': '1.0-e26b90c409b31fd2e3c6fcec402ac0b9',
'SecurityGroupRule': '1.0-e9b8dace9d48b936c62ad40fe1f339d5', 'SecurityGroupRule': '1.0-e9b8dace9d48b936c62ad40fe1f339d5',

View File

@ -290,6 +290,59 @@ class TestQosPlugin(base.BaseQosTestCase):
self.qos_plugin.get_policy_dscp_marking_rules, self.qos_plugin.get_policy_dscp_marking_rules,
self.ctxt, self.policy.id) self.ctxt, self.policy.id)
def test_get_policy_minimum_bandwidth_rule(self):
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
return_value=self.policy):
with mock.patch('neutron.objects.qos.rule.'
'QosMinimumBandwidthRule.'
'get_object') as get_object_mock:
self.qos_plugin.get_policy_minimum_bandwidth_rule(
self.ctxt, self.rule.id, self.policy.id)
get_object_mock.assert_called_once_with(self.ctxt,
id=self.rule.id)
def test_get_policy_minimum_bandwidth_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.'
'QosMinimumBandwidthRule.'
'get_objects') as get_objects_mock:
self.qos_plugin.get_policy_minimum_bandwidth_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_minimum_bandwidth_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.'
'QosMinimumBandwidthRule.'
'get_objects') as get_objects_mock:
filters = {'filter': 'filter_id'}
self.qos_plugin.get_policy_minimum_bandwidth_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_minimum_bandwidth_rule_for_nonexistent_policy(self):
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
return_value=None):
self.assertRaises(
n_exc.QosPolicyNotFound,
self.qos_plugin.get_policy_minimum_bandwidth_rule,
self.ctxt, self.rule.id, self.policy.id)
def test_get_policy_minimum_bandwidth_rules_for_nonexistent_policy(self):
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
return_value=None):
self.assertRaises(
n_exc.QosPolicyNotFound,
self.qos_plugin.get_policy_minimum_bandwidth_rules,
self.ctxt, self.policy.id)
def test_create_policy_rule_for_nonexistent_policy(self): def test_create_policy_rule_for_nonexistent_policy(self):
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
return_value=None): return_value=None):

View File

@ -0,0 +1,10 @@
---
features:
- Users can now apply a QoS rule to a port or network to
setup the minimum egress bandwidth per queue and port.
The minimum egress bandwidth rule is applied to each port
individually.
other:
- At the time of writing, Neutron bandwidth booking is not
integrated with Compute scheduler, which means that minimal
bandwidth is not guaranteed but provided as best effort.