Introduce mechanism to determine supported qos rule types for a plugin

Every plugin that supports some of QoS rules will define a property
called supported_qos_rule_types of list type.

For ml2, determine supported qos rule types as a subset of rule types
supported by all drivers. (In the future, we may expand the list to
include all types supported by at least one of enabled drivers. This
would require synchronized work with nova scheduler though.)

For ml2, tests are limited, and should be expanded to check that common
subset of qos rules is calculated properly when intersection != the list
of each plugins. For now, it's enough since we don't have more than one
rule type planned for Liberty.

Added API test for the resource.

Partially-Implements: blueprint ml2-qos
Co-Authored-By: Irena Berezovsky <irenab.dev@gmail.com>
Co-Authored-By: John Schwarz <jschwarz@redhat.com>
Change-Id: I0d18ae256877a129e203110003fcadd1d63590b4
This commit is contained in:
Ihar Hrachyshka 2015-06-11 08:11:08 +02:00 committed by John Schwarz
parent b9996c08a2
commit 12f7abd398
19 changed files with 270 additions and 23 deletions

View File

@ -31,6 +31,21 @@ Service side design
notifications to any interested agent, using `RPC callbacks <rpc_callbacks.html>`_.
Supported QoS rule types
------------------------
Any plugin or Ml2 mechanism driver can claim support for some QoS rule types by
providing a plugin/driver class property called 'supported_qos_rule_types' that
should return a list of strings that correspond to QoS rule types (for the list
of all rule types, see: neutron.extensions.qos.VALID_RULE_TYPES).
In the most simple case, the property can be represented by a simple Python
list defined on the class.
For Ml2 plugin, the list of supported QoS rule types is defined as a common
subset of rules supported by all active mechanism drivers.
QoS resources
-------------
@ -253,4 +268,3 @@ in terms of how those objects are implemented. Specific test classes can
obviously extend the set of test cases as they see needed (f.e. you need to
define new test cases for those additional methods that you may add to your
object implementations on top of base semantics common to all neutron objects).

View File

@ -28,9 +28,6 @@ from neutron.services import service_base
QOS_PREFIX = "/qos"
RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth_limit'
VALID_RULE_TYPES = [RULE_TYPE_BANDWIDTH_LIMIT]
# Attribute Map
QOS_RULE_COMMON_FIELDS = {
'id': {'allow_post': False, 'allow_put': False,

View File

@ -26,6 +26,31 @@ class NeutronObject(obj_base.VersionedObject,
obj_base.VersionedObjectDictCompat,
obj_base.ComparableVersionedObject):
# TODO(QoS): this should be revisited on how we plan to work with dicts
def to_dict(self):
return dict(self.items())
@classmethod
def get_by_id(cls, context, id):
raise NotImplementedError()
@classmethod
@abc.abstractmethod
def get_objects(cls, context, **kwargs):
raise NotImplementedError()
def create(self):
raise NotImplementedError()
def update(self):
raise NotImplementedError()
def delete(self):
raise NotImplementedError()
class NeutronDbObject(NeutronObject):
# should be overridden for all persistent objects
db_model = None
@ -42,10 +67,6 @@ class NeutronObject(obj_base.VersionedObject,
break
self.obj_reset_changes()
# TODO(QoS): this should be revisited on how we plan to work with dicts
def to_dict(self):
return dict(self.items())
@classmethod
def get_by_id(cls, context, id):
db_obj = db_api.get_object(context, cls.db_model, id=id)

View File

@ -24,9 +24,9 @@ from neutron.common import utils
from neutron.db import api as db_api
from neutron.db.qos import api as qos_db_api
from neutron.db.qos import models as qos_db_model
from neutron.extensions import qos as qos_extension
from neutron.objects import base
from neutron.objects.qos import rule as rule_obj_impl
from neutron.services.qos import qos_consts
class QosRulesExtenderMeta(abc.ABCMeta):
@ -35,7 +35,7 @@ class QosRulesExtenderMeta(abc.ABCMeta):
cls = super(QosRulesExtenderMeta, mcs).__new__(mcs, name, bases, dct)
cls.rule_fields = {}
for rule in qos_extension.VALID_RULE_TYPES:
for rule in qos_consts.VALID_RULE_TYPES:
rule_cls_name = 'Qos%sRule' % utils.camelize(rule)
field = '%s_rules' % rule
cls.fields[field] = obj_fields.ListOfObjectsField(rule_cls_name)
@ -48,7 +48,7 @@ class QosRulesExtenderMeta(abc.ABCMeta):
@obj_base.VersionedObjectRegistry.register
@six.add_metaclass(QosRulesExtenderMeta)
class QosPolicy(base.NeutronObject):
class QosPolicy(base.NeutronDbObject):
db_model = qos_db_model.QosPolicy

View File

@ -21,12 +21,12 @@ import six
from neutron.db import api as db_api
from neutron.db.qos import models as qos_db_model
from neutron.extensions import qos as qos_extension
from neutron.objects import base
from neutron.services.qos import qos_consts
@six.add_metaclass(abc.ABCMeta)
class QosRule(base.NeutronObject):
class QosRule(base.NeutronDbObject):
base_db_model = qos_db_model.QosRule
@ -155,7 +155,7 @@ class QosBandwidthLimitRule(QosRule):
db_model = qos_db_model.QosBandwidthLimitRule
rule_type = qos_extension.RULE_TYPE_BANDWIDTH_LIMIT
rule_type = qos_consts.RULE_TYPE_BANDWIDTH_LIMIT
fields = {
'max_kbps': obj_fields.IntegerField(nullable=True),

View File

@ -0,0 +1,41 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_versionedobjects import base as obj_base
from oslo_versionedobjects import fields as obj_fields
from neutron import manager
from neutron.objects import base
from neutron.services.qos import qos_consts
class RuleTypeField(obj_fields.BaseEnumField):
def __init__(self, **kwargs):
self.AUTO_TYPE = obj_fields.Enum(
valid_values=qos_consts.VALID_RULE_TYPES)
super(RuleTypeField, self).__init__(**kwargs)
@obj_base.VersionedObjectRegistry.register
class QosRuleType(base.NeutronObject):
fields = {
'type': RuleTypeField(),
}
# we don't receive context because we don't need db access at all
@classmethod
def get_objects(cls, **kwargs):
core_plugin = manager.NeutronManager.get_plugin()
return [cls(type=type_)
for type_ in core_plugin.supported_qos_rule_types]

View File

@ -20,6 +20,7 @@ from neutron.common import constants
from neutron.extensions import portbindings
from neutron.plugins.common import constants as p_constants
from neutron.plugins.ml2.drivers import mech_agent
from neutron.services.qos import qos_consts
LOG = log.getLogger(__name__)
@ -34,6 +35,12 @@ class LinuxbridgeMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
network.
"""
# TODO(QoS): really, there is no support for QoS in the driver. Leaving it
# here since API tests are executed against both ovs and lb drivers, and it
# effectively makes ml2 plugin return an empty list for supported rule
# types
supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT]
def __init__(self):
sg_enabled = securitygroups_rpc.is_firewall_enabled()
super(LinuxbridgeMechanismDriver, self).__init__(

View File

@ -17,7 +17,7 @@ from oslo_log import log as logging
from neutron.agent.common import ovs_lib
from neutron.agent.l2.extensions import qos_agent
from neutron.extensions import qos
from neutron.services.qos import qos_consts
LOG = logging.getLogger(__name__)
@ -33,11 +33,11 @@ class QosOVSAgentDriver(qos_agent.QosAgentDriver):
self.handlers = {}
def initialize(self):
self.handlers[('update', qos.RULE_TYPE_BANDWIDTH_LIMIT)] = (
self.handlers[('update', qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)] = (
self._update_bw_limit_rule)
self.handlers[('create', qos.RULE_TYPE_BANDWIDTH_LIMIT)] = (
self.handlers[('create', qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)] = (
self._update_bw_limit_rule)
self.handlers[('delete', qos.RULE_TYPE_BANDWIDTH_LIMIT)] = (
self.handlers[('delete', qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)] = (
self._delete_bw_limit_rule)
self.br_int = ovs_lib.OVSBridge(self.br_int_name)

View File

@ -20,6 +20,7 @@ from neutron.common import constants
from neutron.extensions import portbindings
from neutron.plugins.common import constants as p_constants
from neutron.plugins.ml2.drivers import mech_agent
from neutron.services.qos import qos_consts
LOG = log.getLogger(__name__)
@ -34,6 +35,8 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
network.
"""
supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT]
def __init__(self):
sg_enabled = securitygroups_rpc.is_firewall_enabled()
vif_details = {portbindings.CAP_PORT_FILTER: sg_enabled,

View File

@ -25,11 +25,12 @@ from neutron.extensions import multiprovidernet as mpnet
from neutron.extensions import portbindings
from neutron.extensions import providernet as provider
from neutron.extensions import vlantransparent
from neutron.i18n import _LE, _LI
from neutron.i18n import _LE, _LI, _LW
from neutron.plugins.ml2.common import exceptions as ml2_exc
from neutron.plugins.ml2 import db
from neutron.plugins.ml2 import driver_api as api
from neutron.plugins.ml2 import models
from neutron.services.qos import qos_consts
LOG = log.getLogger(__name__)
@ -312,6 +313,40 @@ class MechanismManager(stevedore.named.NamedExtensionManager):
LOG.info(_LI("Registered mechanism drivers: %s"),
[driver.name for driver in self.ordered_mech_drivers])
@property
def supported_qos_rule_types(self):
if not self.ordered_mech_drivers:
return []
rule_types = set(qos_consts.VALID_RULE_TYPES)
# Recalculate on every call to allow drivers determine supported rule
# types dynamically
for driver in self.ordered_mech_drivers:
if hasattr(driver.obj, 'supported_qos_rule_types'):
new_rule_types = \
rule_types & set(driver.obj.supported_qos_rule_types)
dropped_rule_types = new_rule_types - rule_types
if dropped_rule_types:
LOG.info(
_LI("%(rule_types)s rule types disabled for ml2 "
"because %(driver)s does not support them"),
{'rule_types': ', '.join(dropped_rule_types),
'driver': driver.name})
rule_types = new_rule_types
else:
# at least one of drivers does not support QoS, meaning there
# are no rule types supported by all of them
LOG.warn(
_LW("%s does not support QoS; no rule types available"),
driver.name)
return []
rule_types = list(rule_types)
LOG.debug("Supported QoS rule types "
"(common subset for all mech drivers): %s", rule_types)
return rule_types
def initialize(self):
for driver in self.ordered_mech_drivers:
LOG.info(_LI("Initializing mechanism driver '%s'"), driver.name)

View File

@ -165,6 +165,10 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
)
self.start_periodic_dhcp_agent_status_check()
@property
def supported_qos_rule_types(self):
return self.mechanism_manager.supported_qos_rule_types
@log_helpers.log_method_call
def start_rpc_listeners(self):
"""Start the RPC loop to let the plugin communicate with agents."""

View File

@ -0,0 +1,17 @@
# Copyright (c) 2015 Red Hat Inc.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth_limit'
VALID_RULE_TYPES = [RULE_TYPE_BANDWIDTH_LIMIT]

View File

@ -22,6 +22,7 @@ from neutron.extensions import qos
from neutron.i18n import _LW
from neutron.objects.qos import policy as policy_object
from neutron.objects.qos import rule as rule_object
from neutron.objects.qos import rule_type as rule_type_object
from neutron.plugins.common import constants
from oslo_log import log as logging
@ -140,4 +141,5 @@ class QoSPlugin(qos.QoSPluginBase):
def get_rule_types(self, context, filters=None, fields=None,
sorts=None, limit=None,
marker=None, page_reverse=False):
pass
return [rule_type_obj.to_dict() for rule_type_obj in
rule_type_object.QosRuleType.get_objects()]

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from neutron.services.qos import qos_consts
from neutron.tests.api import base
from neutron.tests.tempest import config
from neutron.tests.tempest import test
@ -70,6 +71,25 @@ class QosTestJSON(base.BaseAdminNetworkTest):
rules_ids = [r['id'] for r in rules]
self.assertIn(rule['id'], rules_ids)
@test.attr(type='smoke')
@test.idempotent_id('cf776f77-8d3d-49f2-8572-12d6a1557224')
def test_list_rule_types(self):
# List supported rule types
expected_rule_types = qos_consts.VALID_RULE_TYPES
expected_rule_details = ['type']
rule_types = self.admin_client.list_qos_rule_types()
actual_list_rule_types = rule_types['rule_types']
actual_rule_types = [rule['type'] for rule in actual_list_rule_types]
# Verify that only required fields present in rule details
for rule in actual_list_rule_types:
self.assertEqual(tuple(rule.keys()), tuple(expected_rule_details))
# Verify if expected rules are present in the actual rules list
for rule in expected_rule_types:
self.assertIn(rule, actual_rule_types)
#TODO(QoS): policy update (name)
#TODO(QoS): create several bandwidth-limit rules (not sure it makes sense,
# but to test more than one rule)

View File

@ -68,6 +68,7 @@ class NetworkClientJSON(service_client.ServiceClient):
'firewalls': 'fw',
'policies': 'qos',
'bandwidth_limit_rules': 'qos',
'rule_types': 'qos',
}
service_prefix = service_resource_prefix_map.get(
plural_name)
@ -692,3 +693,10 @@ class NetworkClientJSON(service_client.ServiceClient):
resp, body = self.put(uri, json.dumps(post_data))
self.expected_success(200, 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)
self.expected_success(200, resp.status)
body = json.loads(body)
return service_client.ResponseBody(resp, body)

View File

@ -0,0 +1,46 @@
# 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.
# rule types are so different from other objects that we don't base the test
# class on the common base class for all objects
import mock
from neutron import manager
from neutron.objects.qos import rule_type
from neutron.services.qos import qos_consts
from neutron.tests import base as test_base
DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
class QosRuleTypeObjectTestCase(test_base.BaseTestCase):
def setUp(self):
self.config_parse()
self.setup_coreplugin(DB_PLUGIN_KLASS)
super(QosRuleTypeObjectTestCase, self).setUp()
def test_get_objects(self):
core_plugin = manager.NeutronManager.get_plugin()
rule_types_mock = mock.PropertyMock(
return_value=qos_consts.VALID_RULE_TYPES)
with mock.patch.object(core_plugin, 'supported_qos_rule_types',
new_callable=rule_types_mock,
create=True):
types = rule_type.QosRuleType.get_objects()
self.assertEqual(sorted(qos_consts.VALID_RULE_TYPES),
sorted(type_['type'] for type_ in types))
def test_wrong_type(self):
self.assertRaises(ValueError, rule_type.QosRuleType, type='bad_type')

View File

@ -29,7 +29,7 @@ class FakeModel(object):
@obj_base.VersionedObjectRegistry.register
class FakeNeutronObject(base.NeutronObject):
class FakeNeutronObject(base.NeutronDbObject):
db_model = FakeModel

View File

@ -12,9 +12,9 @@
import mock
from neutron.extensions import qos
from neutron.plugins.ml2.drivers.openvswitch.agent.extension_drivers import (
qos_driver)
from neutron.services.qos import qos_consts
from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent import (
ovs_test_base)
@ -37,7 +37,7 @@ class OVSQoSAgentDriverBwLimitRule(ovs_test_base.OVSAgentConfigTestBase):
self.port = self._create_fake_port()
def _create_bw_limit_rule(self):
return {'type': qos.RULE_TYPE_BANDWIDTH_LIMIT,
return {'type': qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
'max_kbps': '200',
'max_burst_kbps': '2'}

View File

@ -49,6 +49,7 @@ from neutron.plugins.ml2 import driver_context
from neutron.plugins.ml2.drivers import type_vlan
from neutron.plugins.ml2 import models
from neutron.plugins.ml2 import plugin as ml2_plugin
from neutron.services.qos import qos_consts
from neutron.tests import base
from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit.agent import test_securitygroups_rpc as test_sg_rpc
@ -139,6 +140,37 @@ class TestMl2BulkToggleWithoutBulkless(Ml2PluginV2TestCase):
self.assertFalse(self._skip_native_bulk)
class TestMl2SupportedQosRuleTypes(Ml2PluginV2TestCase):
def test_empty_driver_list(self, *mocks):
mech_drivers_mock = mock.PropertyMock(return_value=[])
with mock.patch.object(self.driver.mechanism_manager,
'ordered_mech_drivers',
new_callable=mech_drivers_mock):
self.assertEqual(
[], self.driver.mechanism_manager.supported_qos_rule_types)
def test_no_rule_types_in_common(self):
self.assertEqual(
[], self.driver.mechanism_manager.supported_qos_rule_types)
@mock.patch.object(mech_logger.LoggerMechanismDriver,
'supported_qos_rule_types',
new_callable=mock.PropertyMock,
create=True)
@mock.patch.object(mech_test.TestMechanismDriver,
'supported_qos_rule_types',
new_callable=mock.PropertyMock,
create=True)
def test_rule_type_in_common(self, *mocks):
# 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)
class TestMl2BasicGet(test_plugin.TestBasicGet,
Ml2PluginV2TestCase):
pass