DSCP QoS rule implementation

This patch adds the front end and back end implementation of QoS DSCP.

Associated patches that are dependent on this one:

* python-neutronclient: https://review.openstack.org/#/c/254280
* openstack-manuals: https://review.openstack.org/#/c/273638
* API Guide: https://review.openstack.org/#/c/275253
* Heat:
  * Spec: https://review.openstack.org/#/c/272173
  * QoSDscpMarkingRule resource: https://review.openstack.org/#/c/277567
* Fullstack tests: https://review.openstack.org/#/c/288392/

APIImpact - The API now supports marking traffic egressing from a VM's
            dscp field with a valid dscp value.

Co-Authored-By: Nate Johnston <nate_johnston@cable.comcast.com>
Co-Authored-By: Victor Howard <victor.r.howard@gmail.com>
Co-Authored-By: Margaret Frances <margaret_frances@cable.comcast.com>
Co-Authored-By: James Reeves <james.reeves5546@gmail.com>
Co-Authored-By: John Schwarz <jschwarz@redhat.com>
Needed-By: I25ad60c1b9a66e568276a772b8c496987d9f8299
Needed-By: I881b8f5bc9024c20275bc56062de72a1c70c8321
Needed-By: I48ead4b459183db795337ab729830a1b3c0022da
Needed-By: Ib92b172dce48276b90ec75ee5880ddd69040d7c8
Needed-By: I4eb21495e84feea46880caf3360759263e1e8f95
Needed-By: I0ab6a1a0d1430c5791fea1d5b54106c6cc93b937
Partial-Bug: #1468353

Change-Id: Ic3baefe176df05f049a2e06529c58fd65fe6b419
This commit is contained in:
David Shaughnessy 2016-03-01 18:55:56 +00:00 committed by Ihar Hrachyshka
parent 1e3f3fb266
commit a9a1943fde
34 changed files with 855 additions and 39 deletions

View File

@ -157,8 +157,13 @@ Base object class is defined in:
For QoS, new neutron objects were implemented:
* QosPolicy: directly maps to the conceptual policy resource, as defined above.
* QosBandwidthLimitRule: class that represents the only rule type supported by
initial QoS design.
* QosBandwidthLimitRule: defines the instance-egress bandwidth limit rule
type, characterized by a max kbps and a max burst kbits.
* QosDscpMarkingRule: defines the DSCP rule type, characterized by an even integer
between 0 and 56. These integers are the result of the bits in the DiffServ section
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.
Those are defined in:
@ -299,6 +304,18 @@ That approach is less flexible than linux-htb, Queues and OvS QoS profiles,
which we may explore in the future, but which will need to be used in
combination with openflow rules.
The Open vSwitch DSCP marking implementation relies on the recent addition
of the ovs_agent_extension_api OVSAgentExtensionAPI to request access to the
integration bridge functions:
* add_flow
* mod_flow
* delete_flows
* dump_flows_for
The DSCP markings are in fact configured on the port by means of
openflow rules.
SR-IOV
++++++

View File

@ -194,6 +194,10 @@
"create_policy_bandwidth_limit_rule": "rule:admin_only",
"delete_policy_bandwidth_limit_rule": "rule:admin_only",
"update_policy_bandwidth_limit_rule": "rule:admin_only",
"get_policy_dscp_marking_rule": "rule:regular_user",
"create_policy_dscp_marking_rule": "rule:admin_only",
"delete_policy_dscp_marking_rule": "rule:admin_only",
"update_policy_dscp_marking_rule": "rule:admin_only",
"get_rule_type": "rule:regular_user",
"restrict_wildcard": "(not field:rbac_policy:target_tenant=*) or rule:admin_only",

View File

@ -63,6 +63,15 @@ class QosAgentDriver(object):
"""
self._handle_update_create_rules('create', port, qos_policy)
def consume_api(self, agent_api):
"""Consume the AgentAPI instance from the QoSAgentExtension class
This allows QosAgentDrivers to gain access to resources limited to the
NeutronAgent when this method is overridden.
:param agent_api: An instance of an agent specific API
"""
def update(self, port, qos_policy):
"""Apply QoS rules on port.
@ -176,6 +185,7 @@ class QosAgentExtension(agent_extension.AgentCoreResourceExtension):
self.resource_rpc = resources_rpc.ResourcesPullRpcApi()
self.qos_driver = manager.NeutronManager.load_class_for_provider(
'neutron.qos.agent_drivers', driver_type)()
self.qos_driver.consume_api(self.agent_api)
self.qos_driver.initialize()
self.policy_map = PortPolicyMap()
@ -183,6 +193,9 @@ class QosAgentExtension(agent_extension.AgentCoreResourceExtension):
registry.subscribe(self._handle_notification, resources.QOS_POLICY)
self._register_rpc_consumers(connection)
def consume_api(self, agent_api):
self.agent_api = agent_api
def _register_rpc_consumers(self, connection):
endpoints = [resources_rpc.ResourcesPushRpcCallback()]
for resource_type in self.SUPPORTED_RESOURCES:

View File

@ -120,6 +120,9 @@ IP_PROTOCOL_MAP = {PROTO_NAME_AH: PROTO_NUM_AH,
PROTO_NAME_UDPLITE: PROTO_NUM_UDPLITE,
PROTO_NAME_VRRP: PROTO_NUM_VRRP}
VALID_DSCP_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34,
36, 38, 40, 46, 48, 56]
# List of ICMPv6 types that should be allowed by default:
# Multicast Listener Query (130),
# Multicast Listener Report (131),

View File

@ -1 +1 @@
0e66c5227a8a
45f8dd33480b

View File

@ -0,0 +1,40 @@
# Copyright 2015 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 dscp db addition
Revision ID: 45f8dd33480b
Revises: 0e66c5227a8a
Create Date: 2015-12-03 07:16:24.742290
"""
# revision identifiers, used by Alembic.
revision = '45f8dd33480b'
down_revision = '0e66c5227a8a'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'qos_dscp_marking_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, unique=True),
sa.Column('dscp_mark', sa.Integer()))

View File

@ -77,3 +77,13 @@ class QosBandwidthLimitRule(model_base.HasId, model_base.BASEV2):
unique=True)
max_kbps = sa.Column(sa.Integer)
max_burst_kbps = sa.Column(sa.Integer)
class QosDscpMarkingRule(models_v2.HasId, model_base.BASEV2):
__tablename__ = 'qos_dscp_marking_rules'
qos_policy_id = sa.Column(sa.String(36),
sa.ForeignKey('qos_policies.id',
ondelete='CASCADE'),
nullable=False,
unique=True)
dscp_mark = sa.Column(sa.Integer)

View File

@ -22,6 +22,7 @@ from neutron.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import base
from neutron.api.v2 import resource_helper
from neutron.common import constants as common_constants
from neutron import manager
from neutron.plugins.common import constants
from neutron.services.qos import qos_consts
@ -78,6 +79,17 @@ SUB_RESOURCE_ATTRIBUTE_MAP = {
'allow_post': True, 'allow_put': True,
'is_visible': True, 'default': 0,
'validate': {'type:non_negative': None}}})
},
'dscp_marking_rules': {
'parent': {'collection_name': 'policies',
'member_name': 'policy'},
'parameters': dict(QOS_RULE_COMMON_FIELDS,
**{'dscp_mark': {
'allow_post': True, 'allow_put': True,
'convert_to': attr.convert_to_int,
'is_visible': True, 'default': None,
'validate': {'type:values': common_constants.
VALID_DSCP_MARKS}}})
}
}
@ -229,6 +241,32 @@ class QoSPluginBase(service_base.ServicePluginBase):
def delete_policy_bandwidth_limit_rule(self, context, rule_id, policy_id):
pass
@abc.abstractmethod
def get_policy_dscp_marking_rule(self, context, rule_id,
policy_id, fields=None):
pass
@abc.abstractmethod
def get_policy_dscp_marking_rules(self, context, policy_id,
filters=None, fields=None,
sorts=None, limit=None,
marker=None, page_reverse=False):
pass
@abc.abstractmethod
def create_policy_dscp_marking_rule(self, context, policy_id,
dscp_marking_rule):
pass
@abc.abstractmethod
def update_policy_dscp_marking_rule(self, context, rule_id, policy_id,
dscp_marking_rule):
pass
@abc.abstractmethod
def delete_policy_dscp_marking_rule(self, context, rule_id, policy_id):
pass
@abc.abstractmethod
def get_rule_types(self, context, filters=None, fields=None,
sorts=None, limit=None,

View File

@ -12,7 +12,9 @@
# under the License.
from oslo_versionedobjects import fields as obj_fields
import six
from neutron._i18n import _
from neutron.common import constants
@ -27,3 +29,46 @@ class IPV6ModeEnumField(obj_fields.BaseEnumField):
def __init__(self, **kwargs):
self.AUTO_TYPE = IPV6ModeEnum()
super(IPV6ModeEnumField, self).__init__(**kwargs)
class IntegerEnum(obj_fields.Integer):
def __init__(self, valid_values=None, **kwargs):
if not valid_values:
msg = _("No possible values specified")
raise ValueError(msg)
for value in valid_values:
if not isinstance(value, six.integer_types):
msg = _("Possible value %s is not an integer") % value
raise ValueError(msg)
self._valid_values = valid_values
super(IntegerEnum, self).__init__(**kwargs)
def _validate_value(self, value):
if not isinstance(value, six.integer_types):
msg = _("Field value %s is not an integer") % value
raise ValueError(msg)
if value not in self._valid_values:
msg = (
_("Field value %(value)s is not in the list "
"of valid values: %(values)s") %
{'value': value, 'values': self._valid_values}
)
raise ValueError(msg)
def coerce(self, obj, attr, value):
self._validate_value(value)
return super(IntegerEnum, self).coerce(obj, attr, value)
def stringify(self, value):
self._validate_value(value)
return super(IntegerEnum, self).stringify(value)
class DscpMark(IntegerEnum):
def __init__(self, valid_values=None, **kwargs):
super(DscpMark, self).__init__(
valid_values=constants.VALID_DSCP_MARKS)
class DscpMarkField(obj_fields.AutoTypedField):
AUTO_TYPE = DscpMark()

View File

@ -15,6 +15,7 @@
import itertools
from oslo_utils import versionutils
from oslo_versionedobjects import base as obj_base
from oslo_versionedobjects import fields as obj_fields
from six import add_metaclass
@ -36,7 +37,8 @@ from neutron.objects import rbac_db
@add_metaclass(rbac_db.RbacNeutronMetaclass)
class QosPolicy(base.NeutronDbObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: QosDscpMarkingRule introduced
VERSION = '1.1'
# required by RbacNeutronMetaclass
rbac_db_model = QosPolicyRBAC
@ -206,3 +208,13 @@ class QosPolicy(base.NeutronDbObject):
cls._get_bound_tenant_ids(context.session, qosport, port,
qosport.port_id, policy_id))
return set(bound_tenants)
def obj_make_compatible(self, primitive, target_version):
_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'])

View File

@ -25,8 +25,11 @@ from neutron.common import utils
from neutron.db import api as db_api
from neutron.db.qos import models as qos_db_model
from neutron.objects import base
from neutron.objects import common_types
from neutron.services.qos import qos_consts
DSCP_MARK = 'dscp_mark'
def get_rules(context, qos_policy_id):
all_rules = []
@ -42,6 +45,13 @@ def get_rules(context, qos_policy_id):
@six.add_metaclass(abc.ABCMeta)
class QosRule(base.NeutronDbObject):
# Version 1.0: Initial version, only BandwidthLimitRule
# 1.1: Added DscpMarkingRule
#
#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'
fields = {
'id': obj_fields.UUIDField(),
@ -77,8 +87,6 @@ class QosRule(base.NeutronDbObject):
@obj_base.VersionedObjectRegistry.register
class QosBandwidthLimitRule(QosRule):
# Version 1.0: Initial version
VERSION = '1.0'
db_model = qos_db_model.QosBandwidthLimitRule
@ -88,3 +96,15 @@ class QosBandwidthLimitRule(QosRule):
}
rule_type = qos_consts.RULE_TYPE_BANDWIDTH_LIMIT
@obj_base.VersionedObjectRegistry.register
class QosDscpMarkingRule(QosRule):
db_model = qos_db_model.QosDscpMarkingRule
fields = {
DSCP_MARK: common_types.DscpMarkField(),
}
rule_type = qos_consts.RULE_TYPE_DSCP_MARK

View File

@ -29,8 +29,10 @@ class RuleTypeField(obj_fields.BaseEnumField):
@obj_base.VersionedObjectRegistry.register
class QosRuleType(base.NeutronObject):
# Version 1.0: Initial version
VERSION = '1.0'
# Version 1.1: Added DscpMarkingRule
VERSION = '1.1'
#TODO(davidsha) add obj_make_compatible and associated tests.
fields = {
'type': RuleTypeField(),
}

View File

@ -14,7 +14,6 @@
from oslo_config import cfg
from neutron.agent.common import ovs_lib
from neutron.agent.l2.extensions import qos
from neutron.plugins.ml2.drivers.openvswitch.mech_driver import (
mech_openvswitch)
@ -29,9 +28,14 @@ class QosOVSAgentDriver(qos.QosAgentDriver):
super(QosOVSAgentDriver, self).__init__()
self.br_int_name = cfg.CONF.OVS.integration_bridge
self.br_int = None
self.agent_api = None
def consume_api(self, agent_api):
self.agent_api = agent_api
def initialize(self):
self.br_int = ovs_lib.OVSBridge(self.br_int_name)
self.br_int = self.agent_api.request_int_br()
self.cookie = self.br_int.default_cookie
def create_bandwidth_limit(self, port, rule):
self.update_bandwidth_limit(port, rule)
@ -48,3 +52,45 @@ class QosOVSAgentDriver(qos.QosAgentDriver):
def delete_bandwidth_limit(self, port):
port_name = port['vif_port'].port_name
self.br_int.delete_egress_bw_limit_for_port(port_name)
def create_dscp_marking(self, port, rule):
self.update_dscp_marking(port, rule)
def update_dscp_marking(self, port, rule):
port_name = port['vif_port'].port_name
port = self.br_int.get_port_ofport(port_name)
mark = rule.dscp_mark
#mark needs to be bit shifted 2 left to not overwrite the
#lower 2 bits of type of service packet header.
#source: man ovs-ofctl (/mod_nw_tos)
mark = str(mark << 2)
# reg2 is a metadata field that does not alter packets.
# By loading a value into this field and checking if the value is
# altered it allows the packet to be resubmitted and go through
# the flow table again to be identified by other flows.
flows = self.br_int.dump_flows_for(cookie=self.cookie, table=0,
in_port=port, reg2=0)
if not flows:
actions = ("mod_nw_tos:" + mark + ",load:55->NXM_NX_REG2[0..5]," +
"resubmit(,0)")
self.br_int.add_flow(in_port=port, table=0, priority=65535,
reg2=0, actions=actions)
else:
for flow in flows:
actions = str(flow).partition("actions=")[2]
acts = actions.split(',')
# mod_nw_tos = modify type of service header
# This is the second byte of the IPv4 packet header.
# DSCP makes up the upper 6 bits of this header field.
actions = "mod_nw_tos:" + mark + ","
actions += ','.join([act for act in acts
if "mod_nw_tos:" not in act])
self.br_int.mod_flows(reg2=0, in_port=port, table=0,
actions=actions)
def delete_dscp_marking(self, port):
port_name = port['vif_port'].port_name
port = self.br_int.get_port_ofport(port_name)
self.br_int.delete_flows(in_port=port, table=0, reg2=0)

View File

@ -41,7 +41,8 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
network.
"""
supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT]
supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
qos_consts.RULE_TYPE_DSCP_MARK]
def __init__(self):
sg_enabled = securitygroups_rpc.is_firewall_enabled()

View File

@ -14,6 +14,7 @@
# under the License.
RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth_limit'
VALID_RULE_TYPES = [RULE_TYPE_BANDWIDTH_LIMIT]
RULE_TYPE_DSCP_MARK = 'dscp_marking'
VALID_RULE_TYPES = [RULE_TYPE_BANDWIDTH_LIMIT, RULE_TYPE_DSCP_MARK]
QOS_POLICY_ID = 'qos_policy_id'

View File

@ -81,7 +81,7 @@ class QoSPlugin(qos.QoSPluginBase):
page_reverse=False):
return policy_object.QosPolicy.get_objects(context, **filters)
#TODO(QoS): Consider adding a proxy catch-all for rules, so
#TODO(mangelajo): need to add a proxy catch-all for rules, so
# we capture the API function call, and just pass
# the rule type as a parameter removing lots of
# future code duplication when we have more rules.
@ -159,6 +159,78 @@ class QoSPlugin(qos.QoSPluginBase):
return rule_object.QosBandwidthLimitRule.get_objects(context,
**filters)
@db_base_plugin_common.convert_result_to_dict
def create_policy_dscp_marking_rule(self, context, policy_id,
dscp_marking_rule):
with db_api.autonested_transaction(context.session):
# first, validate that we have access to the policy
policy = self._get_policy_obj(context, policy_id)
rule = rule_object.QosDscpMarkingRule(
context, qos_policy_id=policy_id,
**dscp_marking_rule['dscp_marking_rule'])
rule.create()
policy.reload_rules()
self.notification_driver_manager.update_policy(context, policy)
return rule
@db_base_plugin_common.convert_result_to_dict
def update_policy_dscp_marking_rule(self, context, rule_id, policy_id,
dscp_marking_rule):
with db_api.autonested_transaction(context.session):
# first, validate that we have access to the policy
policy = self._get_policy_obj(context, policy_id)
# check if the rule belong to the policy
policy.get_rule_by_id(rule_id)
rule = rule_object.QosDscpMarkingRule(
context, id=rule_id)
rule.obj_reset_changes()
for k, v in dscp_marking_rule['dscp_marking_rule'].items():
if k != 'id':
setattr(rule, k, v)
rule.update()
policy.reload_rules()
self.notification_driver_manager.update_policy(context, policy)
return rule
def delete_policy_dscp_marking_rule(self, context, rule_id, policy_id):
# make sure we will have a policy object to push resource update
with db_api.autonested_transaction(context.session):
# first, validate that we have access to the policy
policy = self._get_policy_obj(context, policy_id)
rule = policy.get_rule_by_id(rule_id)
rule.delete()
policy.reload_rules()
self.notification_driver_manager.update_policy(context, policy)
@db_base_plugin_common.filter_fields
@db_base_plugin_common.convert_result_to_dict
def get_policy_dscp_marking_rule(self, context, rule_id,
policy_id, fields=None):
# make sure we have access to the policy when fetching the rule
with db_api.autonested_transaction(context.session):
# first, validate that we have access to the policy
self._get_policy_obj(context, policy_id)
rule = rule_object.QosDscpMarkingRule.get_object(
context, id=rule_id)
if not rule:
raise n_exc.QosRuleNotFound(policy_id=policy_id, rule_id=rule_id)
return rule
@db_base_plugin_common.filter_fields
@db_base_plugin_common.convert_result_to_dict
def get_policy_dscp_marking_rules(self, context, policy_id,
filters=None, fields=None,
sorts=None, limit=None,
marker=None, page_reverse=False):
# make sure we have access to the policy when fetching rules
with db_api.autonested_transaction(context.session):
# first, validate that we have access to the policy
self._get_policy_obj(context, policy_id)
filters = filters or dict()
filters[qos_consts.QOS_POLICY_ID] = policy_id
return rule_object.QosDscpMarkingRule.get_objects(context,
**filters)
# TODO(QoS): enforce rule types when accessing rule objects
@db_base_plugin_common.filter_fields
@db_base_plugin_common.convert_result_to_dict

View File

@ -704,3 +704,144 @@ class RbacSharedQosPoliciesTest(base.BaseAdminNetworkTest):
# make sure the rbac-policy is invisible to the tenant for which it's
# being shared
self.assertFalse(self.client.list_rbac_policies()['rbac_policies'])
class QosDscpMarkingRuleTestJSON(base.BaseAdminNetworkTest):
VALID_DSCP_MARK1 = 56
VALID_DSCP_MARK2 = 48
@classmethod
def resource_setup(cls):
super(QosDscpMarkingRuleTestJSON, cls).resource_setup()
if not test.is_extension_enabled('qos', 'network'):
msg = "qos extension not enabled."
raise cls.skipException(msg)
@test.attr(type='smoke')
@test.idempotent_id('8a59b00b-3e9c-4787-92f8-93a5cdf5e378')
def test_rule_create(self):
policy = self.create_qos_policy(name='test-policy',
description='test policy',
shared=False)
rule = self.admin_client.create_dscp_marking_rule(
policy['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
# Test 'show rule'
retrieved_rule = self.admin_client.show_dscp_marking_rule(
policy['id'], rule['id'])
retrieved_rule = retrieved_rule['dscp_marking_rule']
self.assertEqual(rule['id'], retrieved_rule['id'])
self.assertEqual(self.VALID_DSCP_MARK1, retrieved_rule['dscp_mark'])
# Test 'list rules'
rules = self.admin_client.list_dscp_marking_rules(policy['id'])
rules = rules['dscp_marking_rules']
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_DSCP_MARK,
policy_rules[0]['type'])
@test.attr(type='smoke')
@test.idempotent_id('8a59b00b-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_dscp_marking_rule(
policy['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
self.assertRaises(exceptions.Conflict,
self.admin_client.create_dscp_marking_rule,
policy_id=policy['id'],
dscp_mark=self.VALID_DSCP_MARK2)
@test.attr(type='smoke')
@test.idempotent_id('149a6988-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_dscp_marking_rule(
policy['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
self.admin_client.update_dscp_marking_rule(
policy['id'], rule['id'], dscp_mark=self.VALID_DSCP_MARK2)
retrieved_policy = self.admin_client.show_dscp_marking_rule(
policy['id'], rule['id'])
retrieved_policy = retrieved_policy['dscp_marking_rule']
self.assertEqual(self.VALID_DSCP_MARK2, retrieved_policy['dscp_mark'])
@test.attr(type='smoke')
@test.idempotent_id('67ee6efd-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_dscp_marking_rule(
policy['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
retrieved_policy = self.admin_client.show_dscp_marking_rule(
policy['id'], rule['id'])
retrieved_policy = retrieved_policy['dscp_marking_rule']
self.assertEqual(rule['id'], retrieved_policy['id'])
self.admin_client.delete_dscp_marking_rule(policy['id'], rule['id'])
self.assertRaises(exceptions.NotFound,
self.admin_client.show_dscp_marking_rule,
policy['id'], rule['id'])
@test.attr(type='smoke')
@test.idempotent_id('f211222c-5808-46cb-a961-983bbab6b852')
def test_rule_create_rule_nonexistent_policy(self):
self.assertRaises(
exceptions.NotFound,
self.admin_client.create_dscp_marking_rule,
'policy', self.VALID_DSCP_MARK1)
@test.attr(type='smoke')
@test.idempotent_id('a4a2e7ad-786f-4927-a85a-e545a93bd274')
def test_rule_create_forbidden_for_regular_tenants(self):
self.assertRaises(
exceptions.Forbidden,
self.client.create_dscp_marking_rule,
'policy', self.VALID_DSCP_MARK1)
@test.attr(type='smoke')
@test.idempotent_id('33646b08-4f05-4493-a48a-bde768a18533')
def test_invalid_rule_create(self):
policy = self.create_qos_policy(name='test-policy',
description='test policy',
shared=False)
self.assertRaises(
exceptions.BadRequest,
self.admin_client.create_dscp_marking_rule,
policy['id'], 58)
@test.attr(type='smoke')
@test.idempotent_id('ce0bd0c2-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_dscp_marking_rule(
policy1['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
policy2 = self.create_qos_policy(name='test-policy2',
description='test policy2',
shared=False)
rule2 = self.admin_client.create_dscp_marking_rule(
policy2['id'], self.VALID_DSCP_MARK2)['dscp_marking_rule']
# Test 'list rules'
rules = self.admin_client.list_dscp_marking_rules(policy1['id'])
rules = rules['dscp_marking_rules']
rules_ids = [r['id'] for r in rules]
self.assertIn(rule1['id'], rules_ids)
self.assertNotIn(rule2['id'], rules_ids)

View File

@ -16,6 +16,18 @@
from neutron.agent.linux import utils as agent_utils
def extract_mod_nw_tos_action(flows):
tos_mark = None
if flows:
flow_list = flows.splitlines()
for flow in flow_list:
if 'mod_nw_tos' in flow:
actions = flow.partition('actions=')[2]
after_mod = actions.partition('mod_nw_tos:')[2]
tos_mark = int(after_mod.partition(',')[0])
return tos_mark
def wait_until_bandwidth_limit_rule_applied(bridge, port_vif, rule):
def _bandwidth_limit_rule_applied():
bw_rule = bridge.get_egress_bw_limit_for_port(port_vif)
@ -25,3 +37,18 @@ def wait_until_bandwidth_limit_rule_applied(bridge, port_vif, rule):
return bw_rule == expected
agent_utils.wait_until_true(_bandwidth_limit_rule_applied)
def wait_until_dscp_marking_rule_applied(bridge, port_vif, rule):
def _dscp_marking_rule_applied():
port_num = bridge.get_port_ofport(port_vif)
flows = bridge.dump_flows_for(table='0', in_port=str(port_num))
dscp_mark = extract_mod_nw_tos_action(flows)
expected = None
if rule:
expected = rule
return dscp_mark == expected
agent_utils.wait_until_true(_dscp_marking_rule_applied)

View File

@ -194,6 +194,10 @@
"create_policy_bandwidth_limit_rule": "rule:admin_only",
"delete_policy_bandwidth_limit_rule": "rule:admin_only",
"update_policy_bandwidth_limit_rule": "rule:admin_only",
"get_policy_dscp_marking_rule": "rule:regular_user",
"create_policy_dscp_marking_rule": "rule:admin_only",
"delete_policy_dscp_marking_rule": "rule:admin_only",
"update_policy_dscp_marking_rule": "rule:admin_only",
"get_rule_type": "rule:regular_user",
"restrict_wildcard": "(not field:rbac_policy:target_tenant=*) or rule:admin_only",

View File

@ -114,7 +114,7 @@ class ML2ConfigFixture(ConfigFixture):
super(ML2ConfigFixture, self).__init__(
env_desc, host_desc, temp_dir, base_filename='ml2_conf.ini')
mechanism_drivers = 'openvswitch,linuxbridge'
mechanism_drivers = self.env_desc.mech_drivers
if self.env_desc.l2_pop:
mechanism_drivers += ',l2population'

View File

@ -35,11 +35,13 @@ class EnvironmentDescription(object):
Does the setup, as a whole, support tunneling? How about l2pop?
"""
def __init__(self, network_type='vxlan', l2_pop=True, qos=False):
def __init__(self, network_type='vxlan', l2_pop=True, qos=False,
mech_drivers='openvswitch,linuxbridge'):
self.network_type = network_type
self.l2_pop = l2_pop
self.qos = qos
self.network_range = None
self.mech_drivers = mech_drivers
@property
def tunneling_enabled(self):

View File

@ -163,8 +163,16 @@ class TestQoSWithL2Agent(base.BaseFullStackTestCase):
class TestQoSWithL2Population(base.BaseFullStackTestCase):
def setUp(self):
# We limit this test to using the openvswitch mech driver, because DSCP
# is presently not implemented for Linux Bridge. The 'rule_types' API
# call only returns rule types that are supported by all configured
# mech drivers. So in a fullstack scenario, where both the OVS and the
# Linux Bridge mech drivers are configured, the DSCP rule type will be
# unavailable since it is not implemented in Linux Bridge.
mech_driver = 'openvswitch'
host_desc = [] # No need to register agents for this test case
env_desc = environment.EnvironmentDescription(qos=True, l2_pop=True)
env_desc = environment.EnvironmentDescription(qos=True, l2_pop=True,
mech_drivers=mech_driver)
env = environment.Environment(env_desc, host_desc)
super(TestQoSWithL2Population, self).setUp(env)

View File

@ -29,6 +29,18 @@ from neutron.tests.functional.agent.l2 import base
TEST_POLICY_ID1 = "a2d72369-4246-4f19-bd3c-af51ec8d70cd"
TEST_POLICY_ID2 = "46ebaec0-0570-43ac-82f6-60d2b03168c5"
TEST_DSCP_MARK_1 = 14
TEST_DSCP_MARK_2 = 30
TEST_DSCP_MARKING_RULE_1 = rule.QosDscpMarkingRule(
context=None,
qos_policy_id=TEST_POLICY_ID1,
id="9f126d84-551a-4dcf-bb01-0e9c0df0c793",
dscp_mark=TEST_DSCP_MARK_1)
TEST_DSCP_MARKING_RULE_2 = rule.QosDscpMarkingRule(
context=None,
qos_policy_id=TEST_POLICY_ID2,
id="7f126d84-551a-4dcf-bb01-0e9c0df0c793",
dscp_mark=TEST_DSCP_MARK_2)
TEST_BW_LIMIT_RULE_1 = rule.QosBandwidthLimitRule(
context=None,
qos_policy_id=TEST_POLICY_ID1,
@ -48,8 +60,12 @@ class OVSAgentQoSExtensionTestFramework(base.OVSAgentTestFramework):
super(OVSAgentQoSExtensionTestFramework, self).setUp()
self.config.set_override('extensions', ['qos'], 'agent')
self._set_pull_mock()
self.set_test_qos_rules(TEST_POLICY_ID1, [TEST_BW_LIMIT_RULE_1])
self.set_test_qos_rules(TEST_POLICY_ID2, [TEST_BW_LIMIT_RULE_2])
self.set_test_qos_rules(TEST_POLICY_ID1,
[TEST_BW_LIMIT_RULE_1,
TEST_DSCP_MARKING_RULE_1])
self.set_test_qos_rules(TEST_POLICY_ID2,
[TEST_BW_LIMIT_RULE_2,
TEST_DSCP_MARKING_RULE_2])
def _set_pull_mock(self):
@ -107,6 +123,27 @@ class OVSAgentQoSExtensionTestFramework(base.OVSAgentTestFramework):
l2_extensions.wait_until_bandwidth_limit_rule_applied(
self.agent.int_br, port['vif_name'], rule)
def _assert_dscp_marking_rule_is_set(self, port, dscp_rule):
port_num = self.agent.int_br._get_port_ofport(port['vif_name'])
flows = self.agent.int_br.dump_flows_for(table='0',
in_port=str(port_num))
tos_mark = l2_extensions.extract_mod_nw_tos_action(flows)
self.assertEqual(dscp_rule.dscp_mark << 2, tos_mark)
def _assert_dscp_marking_rule_not_set(self, port):
port_num = self.agent.int_br._get_port_ofport(port['vif_name'])
flows = self.agent.int_br.dump_flows_for(table='0',
in_port=str(port_num))
tos_mark = l2_extensions.extract_mod_nw_tos_action(flows)
self.assertIsNone(tos_mark)
def wait_until_dscp_marking_rule_applied(self, port, dscp_mark):
l2_extensions.wait_until_dscp_marking_rule_applied(
self.agent.int_br, port['vif_name'], dscp_mark)
def _create_port_with_qos(self):
port_dict = self._create_test_port_dict()
port_dict['qos_policy_id'] = TEST_POLICY_ID1
@ -150,6 +187,37 @@ class TestOVSAgentQosExtension(OVSAgentQoSExtensionTestFramework):
self._assert_bandwidth_limit_rule_not_set(self.ports[2])
def test_port_creation_with_dscp_marking(self):
"""Make sure dscp marking rules are set in low level to ports."""
self.setup_agent_and_ports(
port_dicts=self.create_test_ports(amount=1,
policy_id=TEST_POLICY_ID1))
self.wait_until_ports_state(self.ports, up=True)
for port in self.ports:
self._assert_dscp_marking_rule_is_set(
port, TEST_DSCP_MARKING_RULE_1)
def test_port_creation_with_different_dscp_markings(self):
"""Make sure different types of policies end on the right ports."""
port_dicts = self.create_test_ports(amount=3)
port_dicts[0]['qos_policy_id'] = TEST_POLICY_ID1
port_dicts[1]['qos_policy_id'] = TEST_POLICY_ID2
self.setup_agent_and_ports(port_dicts)
self.wait_until_ports_state(self.ports, up=True)
self._assert_dscp_marking_rule_is_set(self.ports[0],
TEST_DSCP_MARKING_RULE_1)
self._assert_dscp_marking_rule_is_set(self.ports[1],
TEST_DSCP_MARKING_RULE_2)
self._assert_dscp_marking_rule_not_set(self.ports[2])
def test_simple_port_policy_update(self):
self.setup_agent_and_ports(
port_dicts=self.create_test_ports(amount=1,

View File

@ -699,6 +699,50 @@ class NetworkClientJSON(service_client.RestClient):
self.expected_success(204, resp.status)
return service_client.ResponseBody(resp, body)
def create_dscp_marking_rule(self, policy_id, dscp_mark):
uri = '%s/qos/policies/%s/dscp_marking_rules' % (
self.uri_prefix, policy_id)
post_data = self.serialize(
{'dscp_marking_rule': {
'dscp_mark': dscp_mark}
})
resp, body = self.post(uri, post_data)
self.expected_success(201, resp.status)
body = json.loads(body)
return service_client.ResponseBody(resp, body)
def list_dscp_marking_rules(self, policy_id):
uri = '%s/qos/policies/%s/dscp_marking_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_dscp_marking_rule(self, policy_id, rule_id):
uri = '%s/qos/policies/%s/dscp_marking_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_dscp_marking_rule(self, policy_id, rule_id, **kwargs):
uri = '%s/qos/policies/%s/dscp_marking_rules/%s' % (
self.uri_prefix, policy_id, rule_id)
post_data = {'dscp_marking_rule': kwargs}
resp, body = self.put(uri, json.dumps(post_data))
body = self.deserialize_single(body)
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
def delete_dscp_marking_rule(self, policy_id, rule_id):
uri = '%s/qos/policies/%s/dscp_marking_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)

View File

@ -25,7 +25,11 @@ from neutron.common import exceptions
from neutron import context
from neutron.objects.qos import policy
from neutron.objects.qos import rule
from neutron.plugins.ml2.drivers.openvswitch.agent import (
ovs_agent_extension_api as ovs_ext_api)
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.ovs_ofctl import (
ovs_bridge)
from neutron.services.qos import qos_consts
from neutron.tests import base
@ -128,6 +132,10 @@ class QosExtensionBaseTestCase(base.BaseTestCase):
self.qos_ext = qos.QosAgentExtension()
self.context = context.get_admin_context()
self.connection = mock.Mock()
self.agent_api = ovs_ext_api.OVSAgentExtensionAPI(
ovs_bridge.OVSAgentBridge('br-int'),
ovs_bridge.OVSAgentBridge('br-tun'))
self.qos_ext.consume_api(self.agent_api)
# Don't rely on used driver
mock.patch(

View File

@ -31,12 +31,17 @@ class QosPolicyObjectTestCase(test_base.BaseObjectIfaceTestCase):
self.get_random_fields(rule.QosBandwidthLimitRule)
for _ in range(3)]
self.db_qos_dscp_rules = [
self.get_random_fields(rule.QosDscpMarkingRule)
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.QosBandwidthLimitRule.db_model: self.db_qos_bandwidth_rules,
rule.QosDscpMarkingRule.db_model: self.db_qos_dscp_rules}
self.get_object = mock.patch.object(
db_api, 'get_object', side_effect=self.fake_get_object).start()
@ -121,7 +126,7 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj.create()
return policy_obj
def _create_test_policy_with_rule(self):
def _create_test_policy_with_bwrule(self):
policy_obj = self._create_test_policy()
rule_fields = self.get_random_fields(
@ -227,21 +232,22 @@ 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_rule()
policy_obj, rule_obj = self._create_test_policy_with_bwrule()
policy_obj = policy.QosPolicy.get_object(self.context,
id=policy_obj.id)
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_rule()
policy_obj, rule_obj = self._create_test_policy_with_bwrule()
policy_obj = policy.QosPolicy.get_object(self.context,
id=policy_obj.id)
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_rule()
policy_obj, rule_obj = self._create_test_policy_with_bwrule()
policy_obj = policy.QosPolicy.get_object(self.context,
id=policy_obj.id)
@ -278,7 +284,7 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
obj.delete()
def test_reload_rules_reloads_rules(self):
policy_obj, rule_obj = self._create_test_policy_with_rule()
policy_obj, rule_obj = self._create_test_policy_with_bwrule()
self.assertEqual([], policy_obj.rules)
policy_obj.reload_rules()
@ -293,3 +299,42 @@ class QosPolicyDbObjectTestCase(test_base.BaseDbObjectTestCase,
obj.detach_port(self._port['id'])
obj.delete()
@staticmethod
def _policy_through_version(obj, version):
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_v1_1 = self._policy_through_version(policy_obj, '1.1')
self.assertIn(rule_obj_band, policy_obj_v1_1.rules)
self.assertIn(rule_obj_dscp, policy_obj_v1_1.rules)
self.assertEqual(policy_obj.VERSION, '1.1')
#TODO(davidsha) add testing for object version incrementation
def test_object_version_degradation_1_1_to_1_0(self):
policy_obj, rule_obj_band, rule_obj_dscp = (
self._create_test_policy_with_bw_and_dscp())
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)
#NOTE(mangelajo): we should not check .VERSION, since that's the
# local version on the class definition

View File

@ -84,3 +84,22 @@ class QosBandwidthLimitRuleDbObjectTestCase(test_base.BaseDbObjectTestCase,
policy_obj = policy.QosPolicy(self.context,
id=generated_qos_policy_id)
policy_obj.create()
class QosDscpMarkingRuleObjectTestCase(test_base.BaseObjectIfaceTestCase):
_test_class = rule.QosDscpMarkingRule
class QosDscpMarkingRuleDbObjectTestCase(test_base.BaseDbObjectTestCase,
testlib_api.SqlTestCase):
_test_class = rule.QosDscpMarkingRule
def setUp(self):
super(QosDscpMarkingRuleDbObjectTestCase, self).setUp()
# Prepare policy to be able to insert a rule
generated_qos_policy_id = self.db_obj['qos_policy_id']
policy_obj = policy.QosPolicy(self.context,
id=generated_qos_policy_id)
policy_obj.create()

View File

@ -11,6 +11,7 @@
# under the License.
import copy
import random
import mock
from oslo_db import exception as obj_exc
@ -19,11 +20,13 @@ from oslo_versionedobjects import base as obj_base
from oslo_versionedobjects import fields as obj_fields
from oslo_versionedobjects import fixture
from neutron.common import constants
from neutron.common import exceptions as n_exc
from neutron.common import utils as common_utils
from neutron import context
from neutron.db import models_v2
from neutron.objects import base
from neutron.objects import common_types
from neutron.objects.db import api as obj_db_api
from neutron.tests import base as test_base
from neutron.tests import tools
@ -182,13 +185,18 @@ class FakeNeutronObjectCompositePrimaryKeyWithId(base.NeutronDbObject):
synthetic_fields = ['obj_field']
def get_random_dscp_mark():
return random.choice(constants.VALID_DSCP_MARKS)
FIELD_TYPE_VALUE_GENERATOR_MAP = {
obj_fields.BooleanField: tools.get_random_boolean,
obj_fields.IntegerField: tools.get_random_integer,
obj_fields.StringField: tools.get_random_string,
obj_fields.UUIDField: uuidutils.generate_uuid,
obj_fields.ObjectField: lambda: None,
obj_fields.ListOfObjectsField: lambda: []
obj_fields.ListOfObjectsField: lambda: [],
common_types.DscpMarkField: get_random_dscp_mark,
}

View File

@ -11,6 +11,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import abc
from neutron.common import constants
from neutron.objects import common_types
from neutron.tests import base as test_base
@ -40,9 +42,9 @@ class TestField(object):
self.assertEqual(out_val, self.field.from_primitive(
ObjectLikeThing, 'attr', prim_val))
@abc.abstractmethod
def test_stringify(self):
for in_val, out_val in self.coerce_good_values:
self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))
'''This test should validate stringify() format for new field types.'''
def test_stringify_invalid(self):
for in_val in self.coerce_bad_values:
@ -58,3 +60,22 @@ class IPV6ModeEnumFieldTest(test_base.BaseTestCase, TestField):
self.coerce_bad_values = ['6', 4, 'type', 'slaacc']
self.to_primitive_values = self.coerce_good_values
self.from_primitive_values = self.coerce_good_values
def test_stringify(self):
for in_val, out_val in self.coerce_good_values:
self.assertEqual("'%s'" % in_val, self.field.stringify(in_val))
class DscpMarkFieldTest(test_base.BaseTestCase, TestField):
def setUp(self):
super(DscpMarkFieldTest, self).setUp()
self.field = common_types.DscpMarkField()
self.coerce_good_values = [(val, val)
for val in constants.VALID_DSCP_MARKS]
self.coerce_bad_values = ['6', 'str', [], {}, object()]
self.to_primitive_values = self.coerce_good_values
self.from_primitive_values = self.coerce_good_values
def test_stringify(self):
for in_val, out_val in self.coerce_good_values:
self.assertEqual("%s" % in_val, self.field.stringify(in_val))

View File

@ -26,9 +26,10 @@ from neutron.tests import tools
# NOTE: The hashes in this list should only be changed if they come with a
# corresponding version bump in the affected objects.
object_data = {
'QosBandwidthLimitRule': '1.0-4e44a8f5c2895ab1278399f87b40a13d',
'QosRuleType': '1.0-d0df298d49eeffab91af18d1a4cf7eaf',
'QosPolicy': '1.0-721fa60ea8f0e8f15d456d6e917dfe59',
'QosBandwidthLimitRule': '1.1-4e44a8f5c2895ab1278399f87b40a13d',
'QosDscpMarkingRule': '1.1-0313c6554b34fd10c753cb63d638256c',
'QosRuleType': '1.1-8a53fef4c6a43839d477a85b787d22ce',
'QosPolicy': '1.1-721fa60ea8f0e8f15d456d6e917dfe59',
}

View File

@ -16,8 +16,12 @@ from oslo_utils import uuidutils
from neutron import context
from neutron.objects.qos import policy
from neutron.objects.qos import rule
from neutron.plugins.ml2.drivers.openvswitch.agent import (
ovs_agent_extension_api as ovs_ext_api)
from neutron.plugins.ml2.drivers.openvswitch.agent.extension_drivers import (
qos_driver)
from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.ovs_ofctl import (
ovs_bridge)
from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent import (
ovs_test_base)
@ -28,6 +32,10 @@ class QosOVSAgentDriverTestCase(ovs_test_base.OVSAgentConfigTestBase):
super(QosOVSAgentDriverTestCase, self).setUp()
self.context = context.get_admin_context()
self.qos_driver = qos_driver.QosOVSAgentDriver()
self.agent_api = ovs_ext_api.OVSAgentExtensionAPI(
ovs_bridge.OVSAgentBridge('br-int'),
ovs_bridge.OVSAgentBridge('br-tun'))
self.qos_driver.consume_api(self.agent_api)
self.qos_driver.initialize()
self.qos_driver.br_int = mock.Mock()
self.qos_driver.br_int.get_egress_bw_limit_for_port = mock.Mock(

View File

@ -172,9 +172,10 @@ class TestMl2SupportedQosRuleTypes(Ml2PluginV2TestCase):
# make sure both plugins have the same supported qos rule types
for mock_ in mocks:
mock_.return_value = qos_consts.VALID_RULE_TYPES
self.assertEqual(
qos_consts.VALID_RULE_TYPES,
self.driver.mechanism_manager.supported_qos_rule_types)
for rule in qos_consts.VALID_RULE_TYPES:
self.assertIn(
rule,
self.driver.mechanism_manager.supported_qos_rule_types)
@mock.patch.object(mech_test.TestMechanismDriver,
'supported_qos_rule_types',
@ -187,9 +188,10 @@ class TestMl2SupportedQosRuleTypes(Ml2PluginV2TestCase):
return_value=False)
def test_rule_types_with_driver_that_does_not_implement_binding(self,
*mocks):
self.assertEqual(
qos_consts.VALID_RULE_TYPES,
self.driver.mechanism_manager.supported_qos_rule_types)
for rule in qos_consts.VALID_RULE_TYPES:
self.assertIn(
rule,
self.driver.mechanism_manager.supported_qos_rule_types)
class TestMl2BasicGet(test_plugin.TestBasicGet,

View File

@ -50,18 +50,19 @@ class TestQosPlugin(base.BaseQosTestCase):
self.qos_plugin.notification_driver_manager = mock.Mock()
self.ctxt = context.Context('fake_user', 'fake_tenant')
policy_id = uuidutils.generate_uuid()
self.policy_data = {
'policy': {'id': policy_id,
'policy': {'id': uuidutils.generate_uuid(),
'tenant_id': uuidutils.generate_uuid(),
'name': 'test-policy',
'description': 'Test policy description',
'shared': True}}
self.rule_data = {
'bandwidth_limit_rule': {'id': policy_id,
'bandwidth_limit_rule': {'id': uuidutils.generate_uuid(),
'max_kbps': 100,
'max_burst_kbps': 150}}
'max_burst_kbps': 150},
'dscp_marking_rule': {'id': uuidutils.generate_uuid(),
'dscp_mark': 16}}
self.policy = policy_object.QosPolicy(
self.ctxt, **self.policy_data['policy'])
@ -69,6 +70,9 @@ class TestQosPlugin(base.BaseQosTestCase):
self.rule = rule_object.QosBandwidthLimitRule(
self.ctxt, **self.rule_data['bandwidth_limit_rule'])
self.dscp_rule = rule_object.QosDscpMarkingRule(
self.ctxt, **self.rule_data['dscp_marking_rule'])
def _validate_notif_driver_params(self, method_name):
method = getattr(self.qos_plugin.notification_driver_manager,
method_name)
@ -197,6 +201,77 @@ class TestQosPlugin(base.BaseQosTestCase):
self.qos_plugin.get_policy_bandwidth_limit_rules,
self.ctxt, self.policy.id)
def test_create_policy_dscp_marking_rule(self):
_policy = policy_object.QosPolicy(
self.ctxt, **self.policy_data['policy'])
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
return_value=_policy):
setattr(_policy, "rules", [self.dscp_rule])
self.qos_plugin.create_policy_dscp_marking_rule(
self.ctxt, self.policy.id, self.rule_data)
self._validate_notif_driver_params('update_policy')
def test_update_policy_dscp_marking_rule(self):
_policy = policy_object.QosPolicy(
self.ctxt, **self.policy_data['policy'])
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
return_value=_policy):
setattr(_policy, "rules", [self.dscp_rule])
self.qos_plugin.update_policy_dscp_marking_rule(
self.ctxt, self.dscp_rule.id, self.policy.id, self.rule_data)
self._validate_notif_driver_params('update_policy')
def test_delete_policy_dscp_marking_rule(self):
_policy = policy_object.QosPolicy(
self.ctxt, **self.policy_data['policy'])
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
return_value=_policy):
setattr(_policy, "rules", [self.dscp_rule])
self.qos_plugin.delete_policy_dscp_marking_rule(
self.ctxt, self.dscp_rule.id, self.policy.id)
self._validate_notif_driver_params('update_policy')
def test_get_policy_dscp_marking_rules(self):
with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object',
return_value=self.policy):
with mock.patch('neutron.objects.qos.rule.'
'QosDscpMarkingRule.'
'get_objects') as get_object_mock:
self.qos_plugin.get_policy_dscp_marking_rules(
self.ctxt, self.policy.id)
get_object_mock.assert_called_once_with(
self.ctxt, qos_policy_id=self.policy.id)
def test_get_policy_dscp_marking_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.'
'QosDscpMarkingRule.'
'get_objects') as get_object_mock:
filters = {'filter': 'filter_id'}
self.qos_plugin.get_policy_dscp_marking_rules(
self.ctxt, self.policy.id, filters=filters)
get_object_mock.assert_called_once_with(
self.ctxt, qos_policy_id=self.policy.id,
filter='filter_id')
def test_get_policy_dscp_marking_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_dscp_marking_rule,
self.ctxt, self.dscp_rule.id, self.policy.id)
def test_get_policy_dscp_marking_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_dscp_marking_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):

View File

@ -0,0 +1,11 @@
---
prelude: >
A new rule has been added to the API that allows for tagging
traffic with DSCP values. This is currently supported by the
Open vSwitch QoS driver.
features:
- Neutron can apply a QoS rule to ports that mark outgoing
traffic's type of service packet header field.
- The Open vSwitch Neutron agent has been extended to mark the type of
service packet header field of packets egressing from the VM when the
QoS rule has been applied.