Browse Source

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
tags/9.0.0.0b3
Rodolfo Alonso Hernandez 3 years ago
parent
commit
60325f4ae9
19 changed files with 510 additions and 65 deletions
  1. +4
    -0
      doc/source/devref/quality_of_service.rst
  2. +4
    -0
      etc/policy.json
  3. +1
    -1
      neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD
  4. +49
    -0
      neutron/db/migration/alembic_migrations/versions/newton/expand/0f5bef0f87d4_add_qos_minimum_bandwidth_rules.py
  5. +22
    -0
      neutron/db/qos/models.py
  6. +17
    -1
      neutron/extensions/qos.py
  7. +13
    -8
      neutron/objects/qos/policy.py
  8. +22
    -1
      neutron/objects/qos/rule.py
  9. +3
    -2
      neutron/objects/qos/rule_type.py
  10. +5
    -1
      neutron/services/qos/qos_consts.py
  11. +4
    -0
      neutron/tests/etc/policy.json
  12. +148
    -7
      neutron/tests/tempest/api/test_qos.py
  13. +47
    -0
      neutron/tests/tempest/services/network/json/network_client.py
  14. +64
    -40
      neutron/tests/unit/objects/qos/test_policy.py
  15. +34
    -0
      neutron/tests/unit/objects/qos/test_rule.py
  16. +5
    -0
      neutron/tests/unit/objects/test_base.py
  17. +5
    -4
      neutron/tests/unit/objects/test_objects.py
  18. +53
    -0
      neutron/tests/unit/services/qos/test_qos_plugin.py
  19. +10
    -0
      releasenotes/notes/qos-min-egress-bw-rule-b1c80f5675a4c1c3.yaml

+ 4
- 0
doc/source/devref/quality_of_service.rst 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 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.
* 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:


+ 4
- 0
etc/policy.json View File

@@ -208,6 +208,10 @@
"delete_policy_dscp_marking_rule": "rule:admin_only",
"update_policy_dscp_marking_rule": "rule:admin_only",
"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",
"create_rbac_policy": "",

+ 1
- 1
neutron/db/migration/alembic_migrations/versions/EXPAND_HEAD View File

@@ -1 +1 @@
a5648cfeeadf
0f5bef0f87d4

+ 49
- 0
neutron/db/migration/alembic_migrations/versions/newton/expand/0f5bef0f87d4_add_qos_minimum_bandwidth_rules.py 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')
)

+ 22
- 0
neutron/db/qos/models.py View File

@@ -16,6 +16,7 @@
import sqlalchemy as sa

from neutron.api.v2 import attributes as attrs
from neutron.common import constants
from neutron.db import model_base
from neutron.db import models_v2
from neutron.db import rbac_db_models
@@ -87,3 +88,24 @@ class QosDscpMarkingRule(models_v2.HasId, model_base.BASEV2):
nullable=False,
unique=True)
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__
)

+ 17
- 1
neutron/extensions/qos.py View File

@@ -95,6 +95,21 @@ SUB_RESOURCE_ATTRIBUTE_MAP = {
'is_visible': True, 'default': None,
'validate': {'type:values': common_constants.
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.
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
# method calls (see __getattr__ docstring, below).

+ 13
- 8
neutron/objects/qos/policy.py View File

@@ -38,7 +38,8 @@ from neutron.objects import rbac_db
class QosPolicy(base.NeutronDbObject):
# Version 1.0: Initial version
# Version 1.1: QosDscpMarkingRule introduced
VERSION = '1.1'
# Version 1.2: Added QosMinimumBandwidthRule
VERSION = '1.2'

# required by RbacNeutronMetaclass
rbac_db_model = QosPolicyRBAC
@@ -212,11 +213,15 @@ class QosPolicy(base.NeutronDbObject):
return set(bound_tenants)

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)
if _target_version < (1, 1):
if 'rules' in primitive:
bw_obj_name = rule_obj_impl.QosBandwidthLimitRule.obj_name()
primitive['rules'] = filter(
lambda rule: (rule['versioned_object.name'] ==
bw_obj_name),
primitive['rules'])
names = []
if _target_version >= (1, 0):
names.append(rule_obj_impl.QosBandwidthLimitRule.obj_name())
if _target_version >= (1, 1):
names.append(rule_obj_impl.QosDscpMarkingRule.obj_name())
if 'rules' in primitive and names:
primitive['rules'] = filter_rules(names, primitive['rules'])

+ 22
- 1
neutron/objects/qos/rule.py View File

@@ -49,11 +49,12 @@ def get_rules(context, qos_policy_id):
class QosRule(base.NeutronDbObject):
# Version 1.0: Initial version, only BandwidthLimitRule
# 1.1: Added DscpMarkingRule
# 1.2: Added QosMinimumBandwidthRule
#
#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.1'
VERSION = '1.2'

fields = {
'id': obj_fields.UUIDField(),
@@ -117,3 +118,23 @@ class QosDscpMarkingRule(QosRule):
raise exception.IncompatibleObjectVersion(
objver=target_version,
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")

+ 3
- 2
neutron/objects/qos/rule_type.py View File

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

fields = {
'type': RuleTypeField(),

+ 5
- 1
neutron/services/qos/qos_consts.py View File

@@ -15,7 +15,11 @@

RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth_limit'
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'


+ 4
- 0
neutron/tests/etc/policy.json View File

@@ -208,6 +208,10 @@
"delete_policy_dscp_marking_rule": "rule:admin_only",
"update_policy_dscp_marking_rule": "rule:admin_only",
"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",
"create_rbac_policy": "",

+ 148
- 7
neutron/tests/tempest/api/test_qos.py View File

@@ -337,6 +337,13 @@ class QosTestJSON(base.BaseAdminNetworkTest):
obtained_policy = self.client.show_qos_policy(policy['id'])['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):
@classmethod
@@ -434,13 +441,6 @@ class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
self.create_qos_bandwidth_limit_rule,
'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')
def test_rule_create_forbidden_for_regular_tenants(self):
self.assertRaises(
@@ -883,6 +883,147 @@ class QosDscpMarkingRuleTestJSON(base.BaseAdminNetworkTest):
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,
base.BaseAdminNetworkTest):


+ 47
- 0
neutron/tests/tempest/services/network/json/network_client.py View File

@@ -55,6 +55,7 @@ class NetworkClientJSON(service_client.RestClient):
'metering_label_rules': 'metering',
'policies': 'qos',
'bandwidth_limit_rules': 'qos',
'minimum_bandwidth_rules': 'qos',
'rule_types': 'qos',
'rbac-policies': '',
}
@@ -652,6 +653,52 @@ class NetworkClientJSON(service_client.RestClient):
self.expected_success(204, resp.status)
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):
uri = '%s/qos/rule-types' % self.uri_prefix
resp, body = self.get(uri)

+ 64
- 40
neutron/tests/unit/objects/qos/test_policy.py View File

@@ -17,10 +17,18 @@ from neutron.db import models_v2
from neutron.objects.db import api as db_api
from neutron.objects.qos import policy
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 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):

_test_class = policy.QosPolicy
@@ -36,13 +44,19 @@ class QosPolicyObjectTestCase(test_base.BaseObjectIfaceTestCase):
self.get_random_fields(rule.QosDscpMarkingRule)
for _ in range(3)]

self.db_qos_minimum_bandwidth_rules = [
self.get_random_fields(rule.QosMinimumBandwidthRule)
for _ in range(3)]

self.model_map = {
self._test_class.db_model: self.db_objs,
self._test_class.rbac_db_model: [],
self._test_class.port_binding_model: [],
self._test_class.network_binding_model: [],
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(
db_api, 'get_object', side_effect=self.fake_get_object).start()
@@ -127,17 +141,20 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj.create()
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()

rule_fields = self.get_random_fields(
obj_cls=rule.QosBandwidthLimitRule)
rule_fields['qos_policy_id'] = policy_obj.id

rule_obj = rule.QosBandwidthLimitRule(self.context, **rule_fields)
rule_obj.create()

return policy_obj, rule_obj
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)

if reload_rules:
policy_obj.reload_rules()
return policy_obj, rules

def test_attach_network_get_network_policy(self):

@@ -291,27 +308,30 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj.detach_network, self._network['id'])

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,
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):
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,
id=policy_obj.id)
self.assertEqual([rule_obj], policy_obj.rules)
self.assertEqual(rule_obj, policy_obj.rules)

primitive = policy_obj.obj_to_primitive()
self.assertNotEqual([], (primitive['versioned_object.data']['rules']))

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,
id=policy_obj.id)

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
# objects
@@ -343,11 +363,12 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
obj.delete()

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)

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):
obj = self._create_test_policy()
@@ -364,37 +385,40 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
primitive = obj.obj_to_primitive(target_version=version)
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):
policy_obj, rule_obj_band, rule_obj_dscp = (
self._create_test_policy_with_bw_and_dscp())
policy_obj, rule_objs = self._create_test_policy_with_rules(
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)
self.assertIn(rule_obj_dscp, policy_obj_v1_1.rules)
for rule_obj in rule_objs:
self.assertIn(rule_obj, policy_obj_v1_2.rules)

def test_object_version_degradation_1_1_to_1_0(self):
#NOTE(mangelajo): we should not check .VERSION, since that's the
# local version on the class definition
policy_obj, rule_obj_band, rule_obj_dscp = (
self._create_test_policy_with_bw_and_dscp())
policy_obj, rule_objs = self._create_test_policy_with_rules(
[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')

self.assertIn(rule_obj_band, policy_obj_v1_0.rules)
self.assertNotIn(rule_obj_dscp, policy_obj_v1_0.rules)
self.assertIn(rule_objs[0], 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):
policy_obj = policy.QosPolicy(

+ 34
- 0
neutron/tests/unit/objects/qos/test_rule.py View File

@@ -121,3 +121,37 @@ class QosDscpMarkingRuleDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj = policy.QosPolicy(self.context,
id=generated_qos_policy_id)
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()

+ 5
- 0
neutron/tests/unit/objects/test_base.py View File

@@ -343,6 +343,10 @@ def get_random_dscp_mark():
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):
for i in range(5):
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.ListOfObjectsField: lambda: [],
common_types.DscpMarkField: get_random_dscp_mark,
common_types.FlowDirectionEnumField: get_random_direction,
obj_fields.IPNetworkField: tools.get_random_ip_network,
common_types.IPNetworkField: tools.get_random_ip_network,
common_types.IPNetworkPrefixLenField: tools.get_random_prefixlen,

+ 5
- 4
neutron/tests/unit/objects/test_objects.py View File

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

+ 53
- 0
neutron/tests/unit/services/qos/test_qos_plugin.py View File

@@ -290,6 +290,59 @@ class TestQosPlugin(base.BaseQosTestCase):
self.qos_plugin.get_policy_dscp_marking_rules,
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):
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
return_value=None):

+ 10
- 0
releasenotes/notes/qos-min-egress-bw-rule-b1c80f5675a4c1c3.yaml 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.

Loading…
Cancel
Save