From 38c1812015b6977f8212723d082222cefb0926e6 Mon Sep 17 00:00:00 2001 From: Miguel Angel Ajo Date: Thu, 17 Nov 2016 09:17:29 -0500 Subject: [PATCH] Transition qos notification driver into qos driver This will deprecate the notification_driver config setting, and no config setting will be needed. Also it lays down the foundation for a more decoupled interaction with mechanism drivers. Closes-Bug: #1657379 Related-Bug: #1627749 DocImpact Change-Id: I2f166a43f0b980ad22617f8a3f7b4cc7f4786c48 --- doc/source/devref/quality_of_service.rst | 50 ++++---- neutron/conf/services/qos_driver_manager.py | 4 +- neutron/objects/qos/rule_type.py | 26 +++- .../agent/extension_drivers/qos_driver.py | 7 +- .../mech_driver/mech_linuxbridge.py | 7 +- .../agent/extension_drivers/qos_driver.py | 6 +- .../mech_sriov/mech_driver/mech_driver.py | 8 +- .../agent/extension_drivers/qos_driver.py | 6 +- .../mech_driver/mech_openvswitch.py | 7 +- neutron/plugins/ml2/managers.py | 43 ------- neutron/plugins/ml2/plugin.py | 4 - neutron/services/qos/drivers/__init__.py | 0 neutron/services/qos/drivers/base.py | 100 +++++++++++++++ .../qos/drivers/linuxbridge/__init__.py | 0 .../qos/drivers/linuxbridge/driver.py | 48 +++++++ neutron/services/qos/drivers/manager.py | 117 ++++++++++++++++++ .../qos/drivers/openvswitch/__init__.py | 0 .../qos/drivers/openvswitch/driver.py | 48 +++++++ .../services/qos/drivers/sriov/__init__.py | 0 neutron/services/qos/drivers/sriov/driver.py | 48 +++++++ .../qos/notification_drivers/manager.py | 18 ++- .../qos/notification_drivers/message_queue.py | 28 +---- neutron/services/qos/qos_consts.py | 2 + neutron/services/qos/qos_plugin.py | 42 +++++++ neutron/tests/base.py | 2 + neutron/tests/fullstack/test_qos.py | 6 +- neutron/tests/unit/plugins/ml2/test_plugin.py | 49 -------- .../unit/services/qos/drivers/__init__.py | 0 .../unit/services/qos/drivers/test_manager.py | 94 ++++++++++++++ .../qos/notification_drivers/test_manager.py | 32 ----- .../test_message_queue.py | 69 ----------- .../unit/services/qos/test_qos_plugin.py | 45 +++++-- .../revisions/test_revision_plugin.py | 3 - ...qos-drivers-refactor-16ece9984958f8a4.yaml | 12 ++ 34 files changed, 629 insertions(+), 302 deletions(-) create mode 100644 neutron/services/qos/drivers/__init__.py create mode 100644 neutron/services/qos/drivers/base.py create mode 100644 neutron/services/qos/drivers/linuxbridge/__init__.py create mode 100644 neutron/services/qos/drivers/linuxbridge/driver.py create mode 100644 neutron/services/qos/drivers/manager.py create mode 100644 neutron/services/qos/drivers/openvswitch/__init__.py create mode 100644 neutron/services/qos/drivers/openvswitch/driver.py create mode 100644 neutron/services/qos/drivers/sriov/__init__.py create mode 100644 neutron/services/qos/drivers/sriov/driver.py create mode 100644 neutron/tests/unit/services/qos/drivers/__init__.py create mode 100644 neutron/tests/unit/services/qos/drivers/test_manager.py delete mode 100644 neutron/tests/unit/services/qos/notification_drivers/test_message_queue.py create mode 100644 releasenotes/notes/qos-drivers-refactor-16ece9984958f8a4.yaml diff --git a/doc/source/devref/quality_of_service.rst b/doc/source/devref/quality_of_service.rst index 788e05e8b02..104a8b45f27 100644 --- a/doc/source/devref/quality_of_service.rst +++ b/doc/source/devref/quality_of_service.rst @@ -45,18 +45,15 @@ Service side design QoSPlugin, service plugin that implements 'qos' extension, receiving and handling API calls to create/modify policies and rules. -* neutron.services.qos.notification_drivers.manager: - the manager that passes object notifications down to every enabled - notification driver. +* neutron.services.qos.drivers.manager: + the manager that passes object actions down to every enabled QoS driver and + issues RPC calls when any of the drivers require RPC push notifications. -* neutron.services.qos.notification_drivers.qos_base: - the interface class for pluggable notification drivers that are used to - update backends about new {create, update, delete} events on any rule or - policy change. - -* neutron.services.qos.notification_drivers.message_queue: - MQ-based reference notification driver which updates agents via messaging - bus, using `RPC callbacks `_. +* neutron.services.qos.drivers.base: + the interface class for pluggable QoS drivers that are used to update + backends about new {create, update, delete} events on any rule or policy + change. The drivers also declare which QoS rules, VIF drivers and VNIC + types are supported. * neutron.core_extensions.base: Contains an interface class to implement core resource (port/network) @@ -96,21 +93,19 @@ NotImplemented. 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.services.qos.qos_consts.VALID_RULE_TYPES). +Each QoS driver has a property called supported_rule_types, where the driver +exposes the rules it's able to handle. -In the most simple case, the property can be represented by a simple Python -list defined on the class. +For a list of all rule types, see: +neutron.services.qos.qos_consts.VALID_RULE_TYPES. -For Ml2 plugin, the list of supported QoS rule types is defined as a common -subset of rules supported by all active mechanism drivers. +The list of supported QoS rule types exposed by neutron is calculated as +the common subset of rules supported by all active QoS drivers. Note: the list of supported rule types reported by core plugin is not enforced when accessing QoS rule resources. This is mostly because then we would not be -able to create any rules while at least one ml2 driver in gate lacks support -for QoS (at the moment of writing, linuxbridge is such a driver). +able to create rules while at least one of the QoS driver in gate lacks +support for the rules we're trying to test. Database models @@ -401,15 +396,15 @@ Traffic in the tap port is redirected (mirrored) to the IFB using a Traffic Control filter (`Filter Actions `_). -Notification driver design --------------------------- +QoS driver design +----------------- QoS framework is flexible enough to support any third-party vendor. To integrate a third party driver (that just wants to be aware of the QoS create/update/delete API -calls), one needs to implement 'neutron.services.qos.notification_drivers.qos_base', -register its specific driver information to the 'notification_drivers' stevedore -namespace in the setup.cfg and finally set the 'notification_drivers' parameter in -the [qos] section of the neutron config file. +calls), one needs to implement 'neutron.services.qos.drivers.base', and register +the driver during the core plugin or mechanism driver load, see + +neutron.services.qos.drivers.openvswitch.driver register method for an example. .. note:: All the functionality MUST be implemented by the vendor, neutron's QoS framework @@ -424,7 +419,6 @@ To enable the service, the following steps should be followed: On server side: * enable qos service in service_plugins; -* set the needed notification_drivers in [qos] section (message_queue is the default); * for ml2, add 'qos' to extension_drivers in [ml2] section. On agent side (OVS): diff --git a/neutron/conf/services/qos_driver_manager.py b/neutron/conf/services/qos_driver_manager.py index 57ea1f5f4f5..198dbece034 100644 --- a/neutron/conf/services/qos_driver_manager.py +++ b/neutron/conf/services/qos_driver_manager.py @@ -16,7 +16,9 @@ from neutron._i18n import _ QOS_PLUGIN_OPTS = [ cfg.ListOpt('notification_drivers', default=['message_queue'], - help=_('Drivers list to use to send the update notification')), + help=_("Drivers list to use to send the update notification. " + "This option will be unused in Pike."), + deprecated_for_removal=True), ] diff --git a/neutron/objects/qos/rule_type.py b/neutron/objects/qos/rule_type.py index fd5d9c16580..5a24ff677af 100644 --- a/neutron/objects/qos/rule_type.py +++ b/neutron/objects/qos/rule_type.py @@ -10,13 +10,18 @@ # License for the specific language governing permissions and limitations # under the License. +from neutron.plugins.common import constants from neutron_lib.plugins import directory +from oslo_log import log as logging from oslo_versionedobjects import base as obj_base from oslo_versionedobjects import fields as obj_fields +from neutron._i18n import _LW from neutron.objects import base from neutron.services.qos import qos_consts +LOG = logging.getLogger(__name__) + class RuleTypeField(obj_fields.BaseEnumField): @@ -42,7 +47,22 @@ class QosRuleType(base.NeutronObject): def get_objects(cls, validate_filters=True, **kwargs): if validate_filters: cls.validate_filters(**kwargs) - core_plugin = directory.get_plugin() + + #TODO(mangelajo): remove in backwards compatible available rule + # inspection in Pike + + core_plugin_supported_rules = getattr( + directory.get_plugin(), 'supported_qos_rule_types', None) + + rule_types = ( + core_plugin_supported_rules or + directory.get_plugin(alias=constants.QOS).supported_rule_types) + + if core_plugin_supported_rules: + LOG.warning(_LW( + "Your core plugin defines supported_qos_rule_types which is " + "deprecated and shall be implemented through a QoS driver." + )) + # TODO(ihrachys): apply filters to returned result - return [cls(type=type_) - for type_ in core_plugin.supported_qos_rule_types] + return [cls(type=type_) for type_ in rule_types] diff --git a/neutron/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/qos_driver.py b/neutron/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/qos_driver.py index 7eb48a6ee70..d9d8dc8a818 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/qos_driver.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/agent/extension_drivers/qos_driver.py @@ -22,8 +22,7 @@ from neutron.agent.l2.extensions import qos_linux as qos from neutron.agent.linux import iptables_manager from neutron.agent.linux import tc_lib import neutron.common.constants as const -from neutron.plugins.ml2.drivers.linuxbridge.mech_driver import ( - mech_linuxbridge) +from neutron.services.qos.drivers.linuxbridge import driver from neutron.services.qos import qos_consts LOG = log.getLogger(__name__) @@ -36,9 +35,7 @@ class QosLinuxbridgeAgentDriver(qos.QosLinuxAgentDriver): # the delete function, to have the 'direction' parameter. This QoS # extension modification is going to be implemented in # https://review.openstack.org/#/c/341186/ - SUPPORTED_RULES = ( - mech_linuxbridge.LinuxbridgeMechanismDriver.supported_qos_rule_types - ) + SUPPORTED_RULES = driver.SUPPORTED_RULES IPTABLES_DIRECTION = {const.INGRESS_DIRECTION: 'physdev-out', const.EGRESS_DIRECTION: 'physdev-in'} diff --git a/neutron/plugins/ml2/drivers/linuxbridge/mech_driver/mech_linuxbridge.py b/neutron/plugins/ml2/drivers/linuxbridge/mech_driver/mech_linuxbridge.py index 1eaac071bc7..4d60a186461 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/mech_driver/mech_linuxbridge.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/mech_driver/mech_linuxbridge.py @@ -19,7 +19,7 @@ from neutron.agent import securitygroups_rpc 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 +from neutron.services.qos.drivers.linuxbridge import driver as lb_qos_driver class LinuxbridgeMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): @@ -32,16 +32,13 @@ class LinuxbridgeMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): network. """ - supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, - qos_consts.RULE_TYPE_DSCP_MARKING, - qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH] - def __init__(self): sg_enabled = securitygroups_rpc.is_firewall_enabled() super(LinuxbridgeMechanismDriver, self).__init__( constants.AGENT_TYPE_LINUXBRIDGE, portbindings.VIF_TYPE_BRIDGE, {portbindings.CAP_PORT_FILTER: sg_enabled}) + lb_qos_driver.register() def get_allowed_network_types(self, agent): return (agent['configurations'].get('tunnel_types', []) + diff --git a/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py b/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py index b7f53a6295a..e0be5514a2f 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/agent/extension_drivers/qos_driver.py @@ -19,16 +19,14 @@ from neutron.agent.l2.extensions import qos_linux as qos from neutron.plugins.ml2.drivers.mech_sriov.agent.common import ( exceptions as exc) from neutron.plugins.ml2.drivers.mech_sriov.agent import eswitch_manager as esm -from neutron.plugins.ml2.drivers.mech_sriov.mech_driver import ( - mech_driver) +from neutron.services.qos.drivers.sriov import driver LOG = logging.getLogger(__name__) class QosSRIOVAgentDriver(qos.QosLinuxAgentDriver): - SUPPORTED_RULES = ( - mech_driver.SriovNicSwitchMechanismDriver.supported_qos_rule_types) + SUPPORTED_RULES = driver.SUPPORTED_RULES def __init__(self): super(QosSRIOVAgentDriver, self).__init__() diff --git a/neutron/plugins/ml2/drivers/mech_sriov/mech_driver/mech_driver.py b/neutron/plugins/ml2/drivers/mech_sriov/mech_driver/mech_driver.py index 2acce410923..61b78d041f5 100644 --- a/neutron/plugins/ml2/drivers/mech_sriov/mech_driver/mech_driver.py +++ b/neutron/plugins/ml2/drivers/mech_sriov/mech_driver/mech_driver.py @@ -23,7 +23,7 @@ from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2.drivers import mech_agent from neutron.plugins.ml2.drivers.mech_sriov.mech_driver \ import exceptions as exc -from neutron.services.qos import qos_consts +from neutron.services.qos.drivers.sriov import driver as sriov_qos_driver LOG = log.getLogger(__name__) @@ -43,11 +43,6 @@ class SriovNicSwitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): L2 Agent presents in order to manage port update events. """ - supported_qos_rule_types = [ - qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, - qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, - ] - def __init__(self, agent_type=constants.AGENT_TYPE_NIC_SWITCH, vif_details={portbindings.CAP_PORT_FILTER: False}, @@ -69,6 +64,7 @@ class SriovNicSwitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): else portbindings.VIF_TYPE_HW_VEB for vtype in self.supported_vnic_types}) self.vif_details = vif_details + sriov_qos_driver.register() def get_allowed_network_types(self, agent): return (p_const.TYPE_FLAT, p_const.TYPE_VLAN) diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/extension_drivers/qos_driver.py b/neutron/plugins/ml2/drivers/openvswitch/agent/extension_drivers/qos_driver.py index 66745991dd6..c84b33e35b8 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/extension_drivers/qos_driver.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/extension_drivers/qos_driver.py @@ -18,8 +18,7 @@ from oslo_config import cfg from oslo_log import log as logging from neutron.agent.l2.extensions import qos_linux as qos -from neutron.plugins.ml2.drivers.openvswitch.mech_driver import ( - mech_openvswitch) +from neutron.services.qos.drivers.openvswitch import driver from neutron.services.qos import qos_consts @@ -28,8 +27,7 @@ LOG = logging.getLogger(__name__) class QosOVSAgentDriver(qos.QosLinuxAgentDriver): - SUPPORTED_RULES = ( - mech_openvswitch.OpenvswitchMechanismDriver.supported_qos_rule_types) + SUPPORTED_RULES = driver.SUPPORTED_RULES def __init__(self): super(QosOVSAgentDriver, self).__init__() diff --git a/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py b/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py index 494cbb54bb4..7d0cc433727 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py +++ b/neutron/plugins/ml2/drivers/openvswitch/mech_driver/mech_openvswitch.py @@ -27,7 +27,8 @@ from neutron.plugins.ml2 import driver_api as api from neutron.plugins.ml2.drivers import mech_agent from neutron.plugins.ml2.drivers.openvswitch.agent.common \ import constants as a_const -from neutron.services.qos import qos_consts +from neutron.services.qos.drivers.openvswitch import driver as ovs_qos_driver + IPTABLES_FW_DRIVER_FULL = ("neutron.agent.linux.iptables_firewall." "OVSHybridIptablesFirewallDriver") @@ -43,9 +44,6 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): network. """ - supported_qos_rule_types = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, - qos_consts.RULE_TYPE_DSCP_MARKING] - def __init__(self): sg_enabled = securitygroups_rpc.is_firewall_enabled() hybrid_plug_required = (not cfg.CONF.SECURITYGROUP.firewall_driver or @@ -57,6 +55,7 @@ class OpenvswitchMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase): constants.AGENT_TYPE_OVS, portbindings.VIF_TYPE_OVS, vif_details) + ovs_qos_driver.register() def get_allowed_network_types(self, agent): return (agent['configurations'].get('tunnel_types', []) + diff --git a/neutron/plugins/ml2/managers.py b/neutron/plugins/ml2/managers.py index 487ae3e36be..18e7152f64a 100644 --- a/neutron/plugins/ml2/managers.py +++ b/neutron/plugins/ml2/managers.py @@ -33,7 +33,6 @@ from neutron.extensions import vlantransparent from neutron.plugins.ml2.common import exceptions as ml2_exc 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__) @@ -369,48 +368,6 @@ 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) - binding_driver_found = False - - # Recalculate on every call to allow drivers determine supported rule - # types dynamically - for driver in self.ordered_mech_drivers: - driver_obj = driver.obj - if driver_obj._supports_port_binding: - binding_driver_found = True - 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.warning( - _LW("%s does not support QoS; " - "no rule types available"), - driver.name) - return [] - - if binding_driver_found: - rule_types = list(rule_types) - else: - 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) diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index f00c147db62..e1916cf231c 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -236,10 +236,6 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, return self.update_port_status(context, port_id, const.PORT_STATUS_ACTIVE) - @property - def supported_qos_rule_types(self): - return self.mechanism_manager.supported_qos_rule_types - @log_helpers.log_method_call def _start_rpc_notifiers(self): """Initialize RPC notifiers for agents.""" diff --git a/neutron/services/qos/drivers/__init__.py b/neutron/services/qos/drivers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/services/qos/drivers/base.py b/neutron/services/qos/drivers/base.py new file mode 100644 index 00000000000..040a1128b79 --- /dev/null +++ b/neutron/services/qos/drivers/base.py @@ -0,0 +1,100 @@ +# Copyright 2016 Hewlett Packard Enterprise Development Company, LP +# Copyright 2016 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. + +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.services.qos import qos_consts + + +class DriverBase(object): + + def __init__(self, name, vif_types, vnic_types, + supported_rules, + requires_rpc_notifications=False): + """Instantiate a qos driver. + + :param name: driver name. + :param vif_types: list of interfaces (VIFs) supported. + :param vnic_types: list of vnic types supported. + :param supported_rules: list of supported rules. + :param requires_rpc_notifications: indicates if this driver + expects rpc push notifications to be sent from the driver. + """ + + self.name = name + self.vif_types = vif_types + self.vnic_types = vnic_types + self.supported_rules = supported_rules + self.requires_rpc_notifications = requires_rpc_notifications + registry.subscribe(self._register, + qos_consts.QOS_PLUGIN, + events.AFTER_INIT) + + def _register(self, resource, event, trigger, **kwargs): + if self.is_loaded: + # trigger is the QosServiceDriverManager + trigger.register_driver(self) + + def is_loaded(self): + """True if the driver is active for the Neutron Server. + + Implement this property to determine if your driver is actively + configured for this Neutron Server deployment. + """ + return True + + def is_vif_type_compatible(self, vif_type): + """True if the driver is compatible with the VIF type.""" + return vif_type in self.vif_types + + def is_vnic_compatible(self, vnic_type): + """True if the driver is compatible with the specific VNIC type.""" + return vnic_type in self.vnic_types + + def is_rule_supported(self, rule): + return rule.rule_type in self.supported_rules + + def create_policy(self, context, policy): + """Create policy invocation. + + This method can be implemented by the specific driver subclass + to update the backend where necessary with the specific policy + information. + + :param context: current running context information + :param policy: a QoSPolicy object being created, which will have no + rules. + """ + + def update_policy(self, context, policy): + """Update policy invocation. + + This method can be implemented by the specific driver subclass + to update the backend where necessary. + + :param context: current running context information + :param policy: a QoSPolicy object being updated. + """ + + def delete_policy(self, context, policy): + """Delete policy invocation. + + This method can be implemented by the specific driver subclass + to delete the backend policy where necessary. + + :param context: current running context information + :param policy: a QoSPolicy object being deleted + """ diff --git a/neutron/services/qos/drivers/linuxbridge/__init__.py b/neutron/services/qos/drivers/linuxbridge/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/services/qos/drivers/linuxbridge/driver.py b/neutron/services/qos/drivers/linuxbridge/driver.py new file mode 100644 index 00000000000..cc60157fe74 --- /dev/null +++ b/neutron/services/qos/drivers/linuxbridge/driver.py @@ -0,0 +1,48 @@ +# Copyright (c) 2016 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. + +from oslo_log import log as logging + +from neutron.extensions import portbindings +from neutron.services.qos.drivers import base +from neutron.services.qos import qos_consts + +LOG = logging.getLogger(__name__) + +DRIVER = None + +SUPPORTED_RULES = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, + qos_consts.RULE_TYPE_DSCP_MARKING, + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH] + + +class LinuxBridgeDriver(base.DriverBase): + + @staticmethod + def create(): + return LinuxBridgeDriver( + name='linuxbridge', + vif_types=[portbindings.VIF_TYPE_BRIDGE], + vnic_types=[portbindings.VNIC_NORMAL], + supported_rules=SUPPORTED_RULES, + requires_rpc_notifications=True) + + +def register(): + """Register the driver.""" + global DRIVER + if not DRIVER: + DRIVER = LinuxBridgeDriver.create() + LOG.debug('Linuxbridge QoS driver registered') diff --git a/neutron/services/qos/drivers/manager.py b/neutron/services/qos/drivers/manager.py new file mode 100644 index 00000000000..e3f4ad240ce --- /dev/null +++ b/neutron/services/qos/drivers/manager.py @@ -0,0 +1,117 @@ +# 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_log import log as logging +from oslo_utils import excutils + + +from neutron._i18n import _LE, _LW +from neutron.api.rpc.callbacks import events as rpc_events +from neutron.api.rpc.callbacks.producer import registry as rpc_registry +from neutron.api.rpc.callbacks import resources +from neutron.api.rpc.handlers import resources_rpc +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.conf.services import qos_driver_manager as qos_mgr +from neutron.objects.qos import policy as policy_object +from neutron.services.qos import qos_consts + +qos_mgr.register_qos_plugin_opts() + +LOG = logging.getLogger(__name__) + + +class QosServiceDriverManager(object): + + def __init__(self, enable_rpc=False): + self._drivers = [] + self.notification_api = resources_rpc.ResourcesPushRpcApi() + #TODO(mangelajo): remove the enable_rpc parameter in Pike since + # we only use it when a message_queue derived driver + # is found in the notification_drivers + self.rpc_notifications_required = enable_rpc + rpc_registry.provide(self._get_qos_policy_cb, resources.QOS_POLICY) + # notify any registered QoS driver that we're ready, those will + # call the driver manager back with register_driver if they + # are enabled + registry.notify(qos_consts.QOS_PLUGIN, events.AFTER_INIT, self) + + if self.rpc_notifications_required: + self.push_api = resources_rpc.ResourcesPushRpcApi() + + @staticmethod + def _get_qos_policy_cb(resource, policy_id, **kwargs): + context = kwargs.get('context') + if context is None: + LOG.warning(_LW( + 'Received %(resource)s %(policy_id)s without context'), + {'resource': resource, 'policy_id': policy_id} + ) + return + + policy = policy_object.QosPolicy.get_object(context, id=policy_id) + return policy + + def call(self, method_name, *args, **kwargs): + """Helper method for calling a method across all extension drivers.""" + for driver in self._drivers: + try: + getattr(driver, method_name)(*args, **kwargs) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.exception(_LE("Extension driver '%(name)s' failed in " + "%(method)s"), + {'name': driver.name, 'method': method_name}) + + if self.rpc_notifications_required: + context = kwargs.get('context') or args[0] + policy_obj = kwargs.get('policy_obj') or args[1] + + # we don't push create_policy events since policies are empty + # on creation, they only become of any use when rules get + # attached to them. + if method_name == 'update_policy': + self.push_api.push(context, [policy_obj], rpc_events.UPDATED) + + elif method_name == 'delete_policy': + self.push_api.push(context, [policy_obj], rpc_events.DELETED) + + def register_driver(self, driver): + """Register driver with qos plugin. + + This method is called from drivers on INIT event. + """ + self._drivers.append(driver) + self.rpc_notifications_required |= driver.requires_rpc_notifications + + @property + def supported_rule_types(self): + if not self._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._drivers: + new_rule_types = rule_types & set(driver.supported_rules) + dropped_rule_types = rule_types - new_rule_types + if dropped_rule_types: + LOG.debug("%(rule_types)s rule types disabled " + "because enabled %(driver)s does not support them", + {'rule_types': ', '.join(dropped_rule_types), + 'driver': driver.name}) + rule_types = new_rule_types + + LOG.debug("Supported QoS rule types " + "(common subset for all loaded QoS drivers): %s", rule_types) + return rule_types diff --git a/neutron/services/qos/drivers/openvswitch/__init__.py b/neutron/services/qos/drivers/openvswitch/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/services/qos/drivers/openvswitch/driver.py b/neutron/services/qos/drivers/openvswitch/driver.py new file mode 100644 index 00000000000..469eb542f49 --- /dev/null +++ b/neutron/services/qos/drivers/openvswitch/driver.py @@ -0,0 +1,48 @@ +# Copyright (c) 2016 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. + +from oslo_log import log as logging + +from neutron.extensions import portbindings +from neutron.services.qos.drivers import base +from neutron.services.qos import qos_consts + +LOG = logging.getLogger(__name__) + +DRIVER = None + +SUPPORTED_RULES = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, + qos_consts.RULE_TYPE_DSCP_MARKING] + + +class OVSDriver(base.DriverBase): + + @staticmethod + def create(): + return OVSDriver( + name='openvswitch', + vif_types=[portbindings.VIF_TYPE_OVS, + portbindings.VIF_TYPE_VHOST_USER], + vnic_types=[portbindings.VNIC_NORMAL], + supported_rules=SUPPORTED_RULES, + requires_rpc_notifications=True) + + +def register(): + """Register the driver.""" + global DRIVER + if not DRIVER: + DRIVER = OVSDriver.create() + LOG.debug('Open vSwitch QoS driver registered') diff --git a/neutron/services/qos/drivers/sriov/__init__.py b/neutron/services/qos/drivers/sriov/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/services/qos/drivers/sriov/driver.py b/neutron/services/qos/drivers/sriov/driver.py new file mode 100644 index 00000000000..ac5418c7a24 --- /dev/null +++ b/neutron/services/qos/drivers/sriov/driver.py @@ -0,0 +1,48 @@ +# Copyright (c) 2016 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. + +from oslo_log import log as logging + +from neutron.extensions import portbindings +from neutron.services.qos.drivers import base +from neutron.services.qos import qos_consts + +LOG = logging.getLogger(__name__) + +DRIVER = None + +SUPPORTED_RULES = [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH] + + +class SRIOVNICSwitchDriver(base.DriverBase): + + @staticmethod + def create(): + return SRIOVNICSwitchDriver( + name='sriovnicswitch', + vif_types=[portbindings.VIF_TYPE_HW_VEB], + vnic_types=[portbindings.VNIC_DIRECT, + portbindings.VNIC_MACVTAP], + supported_rules=SUPPORTED_RULES, + requires_rpc_notifications=True) + + +def register(): + """Register the driver.""" + global DRIVER + if not DRIVER: + DRIVER = SRIOVNICSwitchDriver.create() + LOG.debug('SR-IOV NIC Switch QoS driver registered') diff --git a/neutron/services/qos/notification_drivers/manager.py b/neutron/services/qos/notification_drivers/manager.py index 7328791e953..1b2a96ef69c 100644 --- a/neutron/services/qos/notification_drivers/manager.py +++ b/neutron/services/qos/notification_drivers/manager.py @@ -12,9 +12,10 @@ from oslo_config import cfg from oslo_log import log as logging +from neutron._i18n import _LI from neutron.conf.services import qos_driver_manager as qos_mgr -from neutron._i18n import _, _LI from neutron import manager +from neutron.services.qos.notification_drivers import message_queue QOS_DRIVER_NAMESPACE = 'neutron.qos.notification_drivers' qos_mgr.register_qos_plugin_opts() @@ -40,14 +41,23 @@ class QosServiceNotificationDriverManager(object): for driver in self.notification_drivers: driver.create_policy(context, qos_policy) + @property + def has_message_queue_driver(self): + """Determine if we have any message_queue derived driver in the + notifications drivers, so the QoS plugin will forcefully enable + the rpc notifications for Pike, since our message_queue driver + is a dummy which doesn't send any messages. + """ + #TODO(mangelajo): remove this in Pike + return any( + isinstance(driver, message_queue.RpcQosServiceNotificationDriver) + for driver in self.notification_drivers) + def _load_drivers(self, notification_drivers): """Load all the instances of the configured QoS notification drivers :param notification_drivers: comma separated string """ - if not notification_drivers: - raise SystemExit(_('A QoS driver must be specified')) - LOG.debug("Loading QoS notification drivers: %s", notification_drivers) for notification_driver in notification_drivers: driver_ins = self._load_driver_instance(notification_driver) self.notification_drivers.append(driver_ins) diff --git a/neutron/services/qos/notification_drivers/message_queue.py b/neutron/services/qos/notification_drivers/message_queue.py index 5cfcb7a9117..d27c32fb085 100644 --- a/neutron/services/qos/notification_drivers/message_queue.py +++ b/neutron/services/qos/notification_drivers/message_queue.py @@ -13,47 +13,29 @@ from oslo_log import log as logging from neutron._i18n import _LW -from neutron.api.rpc.callbacks import events -from neutron.api.rpc.callbacks.producer import registry -from neutron.api.rpc.callbacks import resources -from neutron.api.rpc.handlers import resources_rpc -from neutron.objects.qos import policy as policy_object from neutron.services.qos.notification_drivers import qos_base LOG = logging.getLogger(__name__) -def _get_qos_policy_cb(resource, policy_id, **kwargs): - context = kwargs.get('context') - if context is None: - LOG.warning(_LW( - 'Received %(resource)s %(policy_id)s without context'), - {'resource': resource, 'policy_id': policy_id} - ) - return - - policy = policy_object.QosPolicy.get_object(context, id=policy_id) - return policy - - class RpcQosServiceNotificationDriver( qos_base.QosServiceNotificationDriverBase): """RPC message queue service notification driver for QoS.""" def __init__(self): - self.notification_api = resources_rpc.ResourcesPushRpcApi() - registry.provide(_get_qos_policy_cb, resources.QOS_POLICY) + LOG.warning(_LW("The QoS message_queue notification driver " + "has been ignored, since rpc push is implemented " + "for any QoS driver that requests it.")) def get_description(self): return "Message queue updates" def create_policy(self, context, policy): - #No need to update agents on create pass def update_policy(self, context, policy): - self.notification_api.push(context, [policy], events.UPDATED) + pass def delete_policy(self, context, policy): - self.notification_api.push(context, [policy], events.DELETED) + pass diff --git a/neutron/services/qos/qos_consts.py b/neutron/services/qos/qos_consts.py index 0e526bf12de..de8d301e89b 100644 --- a/neutron/services/qos/qos_consts.py +++ b/neutron/services/qos/qos_consts.py @@ -23,6 +23,8 @@ VALID_RULE_TYPES = [RULE_TYPE_BANDWIDTH_LIMIT, QOS_POLICY_ID = 'qos_policy_id' +QOS_PLUGIN = 'qos_plugin' + # NOTE(slaweq): Value used to calculate burst value for egress bandwidth limit # if burst is not given by user. In such case burst value will be calculated # as 80% of bw_limit to ensure that at least limits for TCP traffic will work diff --git a/neutron/services/qos/qos_plugin.py b/neutron/services/qos/qos_plugin.py index 49766dabaf5..ccdbac6eb06 100644 --- a/neutron/services/qos/qos_plugin.py +++ b/neutron/services/qos/qos_plugin.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +from oslo_log import log + from neutron.common import exceptions as n_exc from neutron.db import api as db_api from neutron.db import db_base_plugin_common @@ -20,9 +22,12 @@ from neutron.extensions import qos from neutron.objects import base as base_obj from neutron.objects.qos import policy as policy_object from neutron.objects.qos import rule_type as rule_type_object +from neutron.services.qos.drivers import manager from neutron.services.qos.notification_drivers import manager as driver_mgr from neutron.services.qos import qos_consts +LOG = log.getLogger(__name__) + class QoSPlugin(qos.QoSPluginBase): """Implementation of the Neutron QoS Service Plugin. @@ -38,9 +43,14 @@ class QoSPlugin(qos.QoSPluginBase): def __init__(self): super(QoSPlugin, self).__init__() + + # TODO(mangelajo): remove notification_driver_manager in Pike self.notification_driver_manager = ( driver_mgr.QosServiceNotificationDriverManager()) + self.driver_manager = manager.QosServiceDriverManager(enable_rpc=( + self.notification_driver_manager.has_message_queue_driver)) + @db_base_plugin_common.convert_result_to_dict def create_policy(self, context, policy): """Create a QoS policy. @@ -61,7 +71,12 @@ class QoSPlugin(qos.QoSPluginBase): policy_obj = policy_object.QosPolicy(context, **policy['policy']) policy_obj.create() + + self.driver_manager.call('create_policy', context, policy_obj) + + #TODO(majopela): remove notification_driver_manager call in Pike self.notification_driver_manager.create_policy(context, policy_obj) + return policy_obj @db_base_plugin_common.convert_result_to_dict @@ -81,7 +96,12 @@ class QoSPlugin(qos.QoSPluginBase): policy_obj = policy_object.QosPolicy(context, id=policy_id) policy_obj.update_fields(policy_data, reset_changes=True) policy_obj.update() + + self.driver_manager.call('update_policy', context, policy_obj) + + #TODO(majopela): remove notification_driver_manager call in Pike self.notification_driver_manager.update_policy(context, policy_obj) + return policy_obj def delete_policy(self, context, policy_id): @@ -97,6 +117,10 @@ class QoSPlugin(qos.QoSPluginBase): policy = policy_object.QosPolicy(context) policy.id = policy_id policy.delete() + + self.driver_manager.call('delete_policy', context, policy) + + #TODO(majopela): remove notification_driver_manager call in Pike self.notification_driver_manager.delete_policy(context, policy) def _get_policy_obj(self, context, policy_id): @@ -155,6 +179,10 @@ class QoSPlugin(qos.QoSPluginBase): filters = {} return rule_type_object.QosRuleType.get_objects(**filters) + @property + def supported_rule_types(self): + return self.driver_manager.supported_rule_types + @db_base_plugin_common.convert_result_to_dict def create_policy_rule(self, context, rule_cls, policy_id, rule_data): """Create a QoS policy rule. @@ -179,7 +207,12 @@ class QoSPlugin(qos.QoSPluginBase): rule = rule_cls(context, qos_policy_id=policy_id, **rule_data) rule.create() policy.reload_rules() + + self.driver_manager.call('update_policy', context, policy) + + #TODO(majopela): remove notification_driver_manager call in Pike self.notification_driver_manager.update_policy(context, policy) + return rule @db_base_plugin_common.convert_result_to_dict @@ -212,7 +245,12 @@ class QoSPlugin(qos.QoSPluginBase): rule.update_fields(rule_data, reset_changes=True) rule.update() policy.reload_rules() + + self.driver_manager.call('update_policy', context, policy) + + #TODO(majopela): remove notification_driver_manager call in Pike self.notification_driver_manager.update_policy(context, policy) + return rule def delete_policy_rule(self, context, rule_cls, rule_id, policy_id): @@ -235,6 +273,10 @@ class QoSPlugin(qos.QoSPluginBase): rule = policy.get_rule_by_id(rule_id) rule.delete() policy.reload_rules() + + self.driver_manager.call('update_policy', context, policy) + + #TODO(majopela): remove notification_driver_manager call in Pike self.notification_driver_manager.update_policy(context, policy) @db_base_plugin_common.filter_fields diff --git a/neutron/tests/base.py b/neutron/tests/base.py index ea126b7c976..6e00cbdecd8 100644 --- a/neutron/tests/base.py +++ b/neutron/tests/base.py @@ -42,6 +42,7 @@ import testtools from neutron._i18n import _ from neutron.agent.linux import external_process from neutron.api.rpc.callbacks.consumer import registry as rpc_consumer_reg +from neutron.api.rpc.callbacks.producer import registry as rpc_producer_reg from neutron.callbacks import manager as registry_manager from neutron.callbacks import registry from neutron.common import config @@ -298,6 +299,7 @@ class BaseTestCase(DietTestCase): self.addCleanup(resource_registry.unregister_all_resources) self.addCleanup(db_api.sqla_remove_all) self.addCleanup(rpc_consumer_reg.clear) + self.addCleanup(rpc_producer_reg.clear) def get_new_temp_dir(self): """Create a new temporary directory. diff --git a/neutron/tests/fullstack/test_qos.py b/neutron/tests/fullstack/test_qos.py index 5f894a317c2..101fe325783 100644 --- a/neutron/tests/fullstack/test_qos.py +++ b/neutron/tests/fullstack/test_qos.py @@ -30,8 +30,7 @@ from neutron.tests.unit import testlib_api from neutron.plugins.ml2.drivers.linuxbridge.agent import \ linuxbridge_neutron_agent as linuxbridge_agent -from neutron.plugins.ml2.drivers.openvswitch.mech_driver import \ - mech_openvswitch as mech_ovs +from neutron.services.qos.drivers.openvswitch import driver as ovs_drv load_tests = testlib_api.module_load_tests @@ -351,6 +350,5 @@ class TestQoSWithL2Population(base.BaseFullStackTestCase): def test_supported_qos_rule_types(self): res = self.client.list_qos_rule_types() rule_types = {t['type'] for t in res['rule_types']} - expected_rules = ( - set(mech_ovs.OpenvswitchMechanismDriver.supported_qos_rule_types)) + expected_rules = set(ovs_drv.SUPPORTED_RULES) self.assertEqual(expected_rules, rule_types) diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index 36d451fe55d..4b846de3fad 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -58,7 +58,6 @@ from neutron.plugins.ml2 import managers from neutron.plugins.ml2 import models from neutron.plugins.ml2 import plugin as ml2_plugin from neutron.services.l3_router import l3_router_plugin -from neutron.services.qos import qos_consts from neutron.services.revisions import revision_plugin from neutron.services.segments import db as segments_plugin_db from neutron.services.segments import plugin as segments_plugin @@ -160,54 +159,6 @@ 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 - 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', - new_callable=mock.PropertyMock, - return_value=qos_consts.VALID_RULE_TYPES, - create=True) - @mock.patch.object(mech_logger.LoggerMechanismDriver, - '_supports_port_binding', - new_callable=mock.PropertyMock, - return_value=False) - def test_rule_types_with_driver_that_does_not_implement_binding(self, - *mocks): - for rule in qos_consts.VALID_RULE_TYPES: - self.assertIn( - rule, - self.driver.mechanism_manager.supported_qos_rule_types) - - class TestMl2BasicGet(test_plugin.TestBasicGet, Ml2PluginV2TestCase): pass diff --git a/neutron/tests/unit/services/qos/drivers/__init__.py b/neutron/tests/unit/services/qos/drivers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/unit/services/qos/drivers/test_manager.py b/neutron/tests/unit/services/qos/drivers/test_manager.py new file mode 100644 index 00000000000..2e16c8dd38f --- /dev/null +++ b/neutron/tests/unit/services/qos/drivers/test_manager.py @@ -0,0 +1,94 @@ +# 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_config import cfg + +from neutron.conf.services import qos_driver_manager as notif_driver_mgr_config +from neutron.services.qos.drivers import base as qos_driver_base +from neutron.services.qos.drivers import manager as driver_mgr +from neutron.services.qos import qos_consts +from neutron.tests.unit.services.qos import base + + +class TestQosDriversManagerBase(base.BaseQosTestCase): + + def setUp(self): + super(TestQosDriversManagerBase, self).setUp() + self.config_parse() + self.setup_coreplugin(load_plugins=False) + config = cfg.ConfigOpts() + notif_driver_mgr_config.register_qos_plugin_opts(config) + + @staticmethod + def _create_manager_with_drivers(drivers_details): + for name, driver_details in drivers_details.items(): + + class QoSDriver(qos_driver_base.DriverBase): + @property + def is_loaded(self): + return driver_details['is_loaded'] + + # the new ad-hoc driver will register on the QOS_PLUGIN registry + QoSDriver(name, + driver_details.get('vif_types', []), + driver_details.get('vnic_types', []), + driver_details.get('rules', [])) + + return driver_mgr.QosServiceDriverManager() + + +class TestQosDriversManagerMulti(TestQosDriversManagerBase): + """Test calls happen to all drivers""" + def test_driver_manager_empty_with_no_drivers(self): + driver_manager = self._create_manager_with_drivers({}) + self.assertEqual(len(driver_manager._drivers), 0) + + def test_driver_manager_empty_with_no_loaded_drivers(self): + driver_manager = self._create_manager_with_drivers( + {'driver-A': {'is_loaded': False}}) + self.assertEqual(len(driver_manager._drivers), 0) + + def test_driver_manager_with_one_loaded_driver(self): + driver_manager = self._create_manager_with_drivers( + {'driver-A': {'is_loaded': True}}) + self.assertEqual(len(driver_manager._drivers), 1) + + def test_driver_manager_with_two_loaded_drivers(self): + driver_manager = self._create_manager_with_drivers( + {'driver-A': {'is_loaded': True}, + 'driver-B': {'is_loaded': True}}) + self.assertEqual(len(driver_manager._drivers), 2) + + +class TestQosDriversManagerRules(TestQosDriversManagerBase): + """Test supported rules""" + def test_available_rules_one_in_common(self): + driver_manager = self._create_manager_with_drivers( + {'driver-A': {'is_loaded': True, + 'rules': [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT, + qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH]}, + 'driver-B': {'is_loaded': True, + 'rules': [qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, + qos_consts.RULE_TYPE_DSCP_MARKING]} + }) + self.assertEqual(driver_manager.supported_rule_types, + set([qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH])) + + def test_available_rules_no_rule_in_common(self): + driver_manager = self._create_manager_with_drivers( + {'driver-A': {'is_loaded': True, + 'rules': [qos_consts.RULE_TYPE_BANDWIDTH_LIMIT]}, + 'driver-B': {'is_loaded': True, + 'rules': [qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH, + qos_consts.RULE_TYPE_DSCP_MARKING]} + }) + self.assertEqual(driver_manager.supported_rule_types, set([])) diff --git a/neutron/tests/unit/services/qos/notification_drivers/test_manager.py b/neutron/tests/unit/services/qos/notification_drivers/test_manager.py index bf716e11553..1c8ad8cd598 100644 --- a/neutron/tests/unit/services/qos/notification_drivers/test_manager.py +++ b/neutron/tests/unit/services/qos/notification_drivers/test_manager.py @@ -14,7 +14,6 @@ import mock from oslo_config import cfg from oslo_utils import uuidutils -from neutron.api.rpc.callbacks import events from neutron.conf.services import qos_driver_manager as driver_mgr_config from neutron import context from neutron.objects.qos import policy as policy_object @@ -55,37 +54,6 @@ class TestQosDriversManagerBase(base.BaseQosTestCase): self.kwargs = {'context': ctxt} -class TestQosDriversManager(TestQosDriversManagerBase): - - def setUp(self): - super(TestQosDriversManager, self).setUp() - #TODO(Qos): Fix this unittest to test manager and not message_queue - # notification driver - rpc_api_cls = mock.patch('neutron.api.rpc.handlers.resources_rpc' - '.ResourcesPushRpcApi').start() - self.rpc_api = rpc_api_cls.return_value - self.driver_manager = driver_mgr.QosServiceNotificationDriverManager() - - def _validate_registry_params(self, event_type, policy): - self.rpc_api.push.assert_called_with(self.context, [policy], - event_type) - - def test_create_policy_default_configuration(self): - #RPC driver should be loaded by default - self.driver_manager.create_policy(self.context, self.policy) - self.assertFalse(self.rpc_api.push.called) - - def test_update_policy_default_configuration(self): - #RPC driver should be loaded by default - self.driver_manager.update_policy(self.context, self.policy) - self._validate_registry_params(events.UPDATED, self.policy) - - def test_delete_policy_default_configuration(self): - #RPC driver should be loaded by default - self.driver_manager.delete_policy(self.context, self.policy) - self._validate_registry_params(events.DELETED, self.policy) - - class TestQosDriversManagerMulti(TestQosDriversManagerBase): def _test_multi_drivers_configuration_op(self, op): diff --git a/neutron/tests/unit/services/qos/notification_drivers/test_message_queue.py b/neutron/tests/unit/services/qos/notification_drivers/test_message_queue.py deleted file mode 100644 index 457b48b94cc..00000000000 --- a/neutron/tests/unit/services/qos/notification_drivers/test_message_queue.py +++ /dev/null @@ -1,69 +0,0 @@ -# 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. - -import mock -from oslo_utils import uuidutils - -from neutron.api.rpc.callbacks import events -from neutron import context -from neutron.objects.qos import policy as policy_object -from neutron.objects.qos import rule as rule_object -from neutron.services.qos.notification_drivers import message_queue -from neutron.tests.unit.services.qos import base - -DB_PLUGIN_KLASS = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2' - - -class TestQosRpcNotificationDriver(base.BaseQosTestCase): - - def setUp(self): - super(TestQosRpcNotificationDriver, self).setUp() - rpc_api_cls = mock.patch('neutron.api.rpc.handlers.resources_rpc' - '.ResourcesPushRpcApi').start() - self.rpc_api = rpc_api_cls.return_value - self.driver = message_queue.RpcQosServiceNotificationDriver() - policy_id = uuidutils.generate_uuid() - self.policy_data = {'policy': { - 'id': policy_id, - 'project_id': uuidutils.generate_uuid(), - 'name': 'testi-policy', - 'description': 'test policyi description', - 'shared': True}} - - self.rule_data = {'bandwidth_limit_rule': { - 'id': policy_id, - 'max_kbps': 100, - 'max_burst_kbps': 150}} - - self.context = context.get_admin_context() - self.policy = policy_object.QosPolicy(self.context, - **self.policy_data['policy']) - - self.rule = rule_object.QosBandwidthLimitRule( - self.context, - **self.rule_data['bandwidth_limit_rule']) - - def _validate_push_params(self, event_type, policy): - self.rpc_api.push.assert_called_once_with(self.context, [policy], - event_type) - - def test_create_policy(self): - self.driver.create_policy(self.context, self.policy) - self.assertFalse(self.rpc_api.push.called) - - def test_update_policy(self): - self.driver.update_policy(self.context, self.policy) - self._validate_push_params(events.UPDATED, self.policy) - - def test_delete_policy(self): - self.driver.delete_policy(self.context, self.policy) - self._validate_push_params(events.DELETED, self.policy) diff --git a/neutron/tests/unit/services/qos/test_qos_plugin.py b/neutron/tests/unit/services/qos/test_qos_plugin.py index 3ba44a04794..82326b4b0d0 100644 --- a/neutron/tests/unit/services/qos/test_qos_plugin.py +++ b/neutron/tests/unit/services/qos/test_qos_plugin.py @@ -51,7 +51,13 @@ class TestQosPlugin(base.BaseQosTestCase): manager.init() self.qos_plugin = directory.get_plugin(constants.QOS) + + #TODO(mangelajo): Remove notification_driver_manager mock in Pike self.qos_plugin.notification_driver_manager = mock.Mock() + self.qos_plugin.driver_manager = mock.Mock() + + self.rpc_push = mock.patch('neutron.api.rpc.handlers.resources_rpc' + '.ResourcesPushRpcApi.push').start() self.ctxt = context.Context('fake_user', 'fake_tenant') mock.patch.object(self.ctxt.session, 'refresh').start() @@ -80,19 +86,27 @@ class TestQosPlugin(base.BaseQosTestCase): self.dscp_rule = rule_object.QosDscpMarkingRule( self.ctxt, **self.rule_data['dscp_marking_rule']) - def _validate_notif_driver_params(self, method_name): + def _validate_driver_params(self, method_name): method = getattr(self.qos_plugin.notification_driver_manager, method_name) self.assertTrue(method.called) self.assertIsInstance( method.call_args[0][1], policy_object.QosPolicy) + self.assertTrue(self.qos_plugin.driver_manager.call.called) + self.assertEqual(self.qos_plugin.driver_manager.call.call_args[0][0], + method_name) + self.assertIsInstance( + self.qos_plugin.driver_manager.call.call_args[0][2], + policy_object.QosPolicy + ) + @mock.patch( 'neutron.objects.rbac_db.RbacNeutronDbObjectMixin' '.create_rbac_policy') def test_add_policy(self, *mocks): self.qos_plugin.create_policy(self.ctxt, self.policy_data) - self._validate_notif_driver_params('create_policy') + self._validate_driver_params('create_policy') def test_add_policy_with_extra_tenant_keyword(self, *mocks): policy_id = uuidutils.generate_uuid() @@ -124,19 +138,19 @@ class TestQosPlugin(base.BaseQosTestCase): policy_object.QosPolicy, self.policy_data['policy']) self.qos_plugin.update_policy( self.ctxt, self.policy.id, {'policy': fields}) - self._validate_notif_driver_params('update_policy') + self._validate_driver_params('update_policy') @mock.patch('neutron.objects.db.api.get_object', return_value=None) def test_delete_policy(self, *mocks): self.qos_plugin.delete_policy(self.ctxt, self.policy.id) - self._validate_notif_driver_params('delete_policy') + self._validate_driver_params('delete_policy') def test_create_policy_rule(self): with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', return_value=self.policy): self.qos_plugin.create_policy_bandwidth_limit_rule( self.ctxt, self.policy.id, self.rule_data) - self._validate_notif_driver_params('update_policy') + self._validate_driver_params('update_policy') def test_update_policy_rule(self): _policy = policy_object.QosPolicy( @@ -146,7 +160,7 @@ class TestQosPlugin(base.BaseQosTestCase): setattr(_policy, "rules", [self.rule]) self.qos_plugin.update_policy_bandwidth_limit_rule( self.ctxt, self.rule.id, self.policy.id, self.rule_data) - self._validate_notif_driver_params('update_policy') + self._validate_driver_params('update_policy') def test_update_policy_rule_bad_policy(self): _policy = policy_object.QosPolicy( @@ -168,7 +182,7 @@ class TestQosPlugin(base.BaseQosTestCase): setattr(_policy, "rules", [self.rule]) self.qos_plugin.delete_policy_bandwidth_limit_rule( self.ctxt, self.rule.id, _policy.id) - self._validate_notif_driver_params('update_policy') + self._validate_driver_params('update_policy') def test_delete_policy_rule_bad_policy(self): _policy = policy_object.QosPolicy( @@ -250,7 +264,7 @@ class TestQosPlugin(base.BaseQosTestCase): 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') + self._validate_driver_params('update_policy') def test_update_policy_dscp_marking_rule(self): _policy = policy_object.QosPolicy( @@ -260,7 +274,7 @@ class TestQosPlugin(base.BaseQosTestCase): 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') + self._validate_driver_params('update_policy') def test_delete_policy_dscp_marking_rule(self): _policy = policy_object.QosPolicy( @@ -270,7 +284,7 @@ class TestQosPlugin(base.BaseQosTestCase): 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') + self._validate_driver_params('update_policy') def test_get_policy_dscp_marking_rules(self): with mock.patch('neutron.objects.qos.policy.QosPolicy.get_object', @@ -436,7 +450,7 @@ class TestQosPlugin(base.BaseQosTestCase): mock_manager.mock_calls.index(notify_mock_call)) @mock.patch('neutron.objects.qos.policy.QosPolicy') - def test_rule_notification_ordering(self, qos_policy_mock): + def test_rule_notification_and_driver_ordering(self, qos_policy_mock): rule_cls_mock = mock.Mock() rule_cls_mock.rule_type = 'fake' @@ -448,6 +462,8 @@ class TestQosPlugin(base.BaseQosTestCase): 'delete': [self.ctxt, rule_cls_mock, self.rule.id, self.policy.id]} + # TODO(mangelajo): Remove notification_driver_manager checks in Pike + # and rename this test self.qos_plugin.notification_driver_manager = mock.Mock() mock_manager = mock.Mock() @@ -455,6 +471,7 @@ class TestQosPlugin(base.BaseQosTestCase): mock_manager.attach_mock(rule_cls_mock, 'RuleCls') mock_manager.attach_mock(self.qos_plugin.notification_driver_manager, 'notification_driver') + mock_manager.attach_mock(self.qos_plugin.driver_manager, 'driver') for action, arguments in rule_actions.items(): mock_manager.reset_mock() @@ -470,6 +487,9 @@ class TestQosPlugin(base.BaseQosTestCase): notify_mock_call = mock.call.notification_driver.update_policy( self.ctxt, mock.ANY) + driver_mock_call = mock.call.driver.call('update_policy', + self.ctxt, mock.ANY) + if rule_mock_call in mock_manager.mock_calls: action_index = mock_manager.mock_calls.index(rule_mock_call) else: @@ -478,3 +498,6 @@ class TestQosPlugin(base.BaseQosTestCase): self.assertTrue( action_index < mock_manager.mock_calls.index(notify_mock_call)) + + self.assertTrue( + action_index < mock_manager.mock_calls.index(driver_mock_call)) diff --git a/neutron/tests/unit/services/revisions/test_revision_plugin.py b/neutron/tests/unit/services/revisions/test_revision_plugin.py index d68e75bcfcf..6857fd2b24e 100644 --- a/neutron/tests/unit/services/revisions/test_revision_plugin.py +++ b/neutron/tests/unit/services/revisions/test_revision_plugin.py @@ -13,7 +13,6 @@ # under the License. # -import mock import netaddr from neutron_lib import constants from neutron_lib.plugins import directory @@ -43,8 +42,6 @@ class TestRevisionPlugin(test_plugin.Ml2PluginV2TestCase): config.cfg.CONF.set_override('extension_drivers', self._extension_drivers, group='ml2') - mock.patch('neutron.services.qos.notification_drivers.message_queue' - '.RpcQosServiceNotificationDriver').start() super(TestRevisionPlugin, self).setUp() self.cp = directory.get_plugin() self.l3p = directory.get_plugin(constants.L3) diff --git a/releasenotes/notes/qos-drivers-refactor-16ece9984958f8a4.yaml b/releasenotes/notes/qos-drivers-refactor-16ece9984958f8a4.yaml new file mode 100644 index 00000000000..49ab49d256b --- /dev/null +++ b/releasenotes/notes/qos-drivers-refactor-16ece9984958f8a4.yaml @@ -0,0 +1,12 @@ +--- +features: + - The QoS driver architecture has been refactored to overcome several + previous limitations, the main one was the coupling of QoS details + into the mechanism drivers, and the next one was the need of + configuration knobs to enable each specific notification driver, + that will be handled automatically from now on. + +deprecations: + - | + notification_drivers from [qos] section has been deprecated. It will be + removed in a future release.