]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Support for extensions in ML2
authorNader Lahouti <nlahouti@cisco.com>
Sat, 19 Apr 2014 23:01:05 +0000 (16:01 -0700)
committerNader Lahouti <nlahouti@cisco.com>
Sun, 31 Aug 2014 12:43:01 +0000 (05:43 -0700)
The current ML2 plugin supports only extensions defined in the plugin and it
does not support any extensions in the mechanism drivers. The changes in this
commit allows mechanism drivers to define new extensions.

Change-Id: I28da19fabf6de2e9f0d687f875aaaa24c8bbc4f0
Implements: blueprint extensions-in-ml2

etc/neutron/plugins/ml2/ml2_conf.ini
neutron/api/extensions.py
neutron/db/db_base_plugin_v2.py
neutron/plugins/ml2/config.py
neutron/plugins/ml2/driver_api.py
neutron/plugins/ml2/managers.py
neutron/plugins/ml2/plugin.py
neutron/tests/unit/ml2/extensions/__init__.py [new file with mode: 0644]
neutron/tests/unit/ml2/extensions/test_extension.py [new file with mode: 0644]
neutron/tests/unit/ml2/test_extension_driver_api.py [new file with mode: 0644]
setup.cfg

index 54722df91d84c78859eb8784abe36ff3cab3c44f..6325e714eee450c342079232aa2b75d6456c2036 100644 (file)
 # Example: mechanism_drivers = openvswitch,brocade
 # Example: mechanism_drivers = linuxbridge,brocade
 
+# (ListOpt) Ordered list of extension driver entrypoints
+# to be loaded from the neutron.ml2.extension_drivers namespace.
+# extension_drivers =
+# Example: extension_drivers = anewextensiondriver
+
 [ml2_type_flat]
 # (ListOpt) List of physical_network names with which flat networks
 # can be created. Use * to allow flat networks with arbitrary
index e67060339641ee86285eec9508ca45189df168cc..9c825cbd504a2b7949a24936b1d4ec5d2fe51480 100644 (file)
@@ -679,6 +679,6 @@ def get_extensions_path():
 
 
 def append_api_extensions_path(paths):
-    paths = [cfg.CONF.api_extensions_path] + paths
+    paths = list(set([cfg.CONF.api_extensions_path] + paths))
     cfg.CONF.set_override('api_extensions_path',
                           ':'.join([p for p in paths if p]))
index 9f8d7bf3fbf286f317ef8fd902bddd95dfad4e78..0b203094e02b126d2b66a36357b6f9adfd65f644 100644 (file)
@@ -807,6 +807,8 @@ class NeutronDbPluginV2(neutron_plugin_base_v2.NeutronPluginBaseV2,
                                for route in subnet['routes']],
                'shared': subnet['shared']
                }
+        # Call auxiliary extend functions, if any
+        self._apply_dict_extend_functions(attributes.SUBNETS, res, subnet)
         return self._fields(res, fields)
 
     def _make_port_dict(self, port, fields=None,
index afce63045e0294fdb876d4291aca244b0cacdbf6..9591e6b19e2b33c87f95d3177ce68bf5787e4fab 100644 (file)
@@ -30,6 +30,11 @@ ml2_opts = [
                 help=_("An ordered list of networking mechanism driver "
                        "entrypoints to be loaded from the "
                        "neutron.ml2.mechanism_drivers namespace.")),
+    cfg.ListOpt('extension_drivers',
+                default=[],
+                help=_("An ordered list of extension driver "
+                       "entrypoints to be loaded from the "
+                       "neutron.ml2.extension_drivers namespace.")),
 ]
 
 
index 779ebdf92d2efe070690332d4a71085f9ae9286d..1fe5f8381fb458b2c0086dd05aa01c9561dececf 100644 (file)
@@ -649,3 +649,158 @@ class MechanismDriver(object):
         that such state changes are eventually cleaned up.
         """
         pass
+
+
+@six.add_metaclass(abc.ABCMeta)
+class ExtensionDriver(object):
+    """Define stable abstract interface for ML2 extension drivers.
+
+    An extension driver extends the core resources implemented by the
+    ML2 plugin with additional attributes. Methods that process create
+    and update operations for these resources validate and persist
+    values for extended attributes supplied through the API. Other
+    methods extend the resource dictionaries returned from the API
+    operations with the values of the extended attributes.
+    """
+
+    @abc.abstractmethod
+    def initialize(self):
+        """Perform driver initialization.
+
+        Called after all drivers have been loaded and the database has
+        been initialized. No abstract methods defined below will be
+        called prior to this method being called.
+        """
+        pass
+
+    @abc.abstractproperty
+    def extension_alias(self):
+        """Supported extension alias.
+
+        Return the alias identifying the core API extension supported
+        by this driver.
+        """
+        pass
+
+    def process_create_network(self, session, data, result):
+        """Process extended attributes for create network.
+
+        :param session: database session
+        :param data: dictionary of incoming network data
+        :param result: network dictionary to extend
+
+        Called inside transaction context on session to validate and
+        persist any extended network attributes defined by this
+        driver. Extended attribute values must also be added to
+        result.
+        """
+        pass
+
+    def process_create_subnet(self, session, data, result):
+        """Process extended attributes for create subnet.
+
+        :param session: database session
+        :param data: dictionary of incoming subnet data
+        :param result: subnet dictionary to extend
+
+        Called inside transaction context on session to validate and
+        persist any extended subnet attributes defined by this
+        driver. Extended attribute values must also be added to
+        result.
+        """
+        pass
+
+    def process_create_port(self, session, data, result):
+        """Process extended attributes for create port.
+
+        :param session: database session
+        :param data: dictionary of incoming port data
+        :param result: port dictionary to extend
+
+        Called inside transaction context on session to validate and
+        persist any extended port attributes defined by this
+        driver. Extended attribute values must also be added to
+        result.
+        """
+        pass
+
+    def process_update_network(self, session, data, result):
+        """Process extended attributes for update network.
+
+        :param session: database session
+        :param data: dictionary of incoming network data
+        :param result: network dictionary to extend
+
+        Called inside transaction context on session to validate and
+        update any extended network attributes defined by this
+        driver. Extended attribute values, whether updated or not,
+        must also be added to result.
+        """
+        pass
+
+    def process_update_subnet(self, session, data, result):
+        """Process extended attributes for update subnet.
+
+        :param session: database session
+        :param data: dictionary of incoming subnet data
+        :param result: subnet dictionary to extend
+
+        Called inside transaction context on session to validate and
+        update any extended subnet attributes defined by this
+        driver. Extended attribute values, whether updated or not,
+        must also be added to result.
+        """
+        pass
+
+    def process_update_port(self, session, data, result):
+        """Process extended attributes for update port.
+
+        :param session: database session
+        :param data: dictionary of incoming port data
+        :param result: port dictionary to extend
+
+        Called inside transaction context on session to validate and
+        update any extended port attributes defined by this
+        driver. Extended attribute values, whether updated or not,
+        must also be added to result.
+        """
+        pass
+
+    def extend_network_dict(self, session, result):
+        """Add extended attributes to network dictionary.
+
+        :param session: database session
+        :param result: network dictionary to extend
+
+        Called inside transaction context on session to add any
+        extended attributes defined by this driver to a network
+        dictionary to be used for mechanism driver calls and/or
+        returned as the result of a network operation.
+        """
+        pass
+
+    def extend_subnet_dict(self, session, result):
+        """Add extended attributes to subnet dictionary.
+
+        :param session: database session
+        :param result: subnet dictionary to extend
+
+        Called inside transaction context on session to add any
+        extended attributes defined by this driver to a subnet
+        dictionary to be used for mechanism driver calls and/or
+        returned as the result of a subnet operation.
+        """
+        pass
+
+    def extend_port_dict(self, session, result):
+        """Add extended attributes to port dictionary.
+
+        :param session: database session
+        :param result: port dictionary to extend
+
+        Called inside transaction context on session to add any
+        extended attributes defined by this driver to a port
+        dictionary to be used for mechanism driver calls and/or
+        returned as the result of a port operation.
+        """
+        pass
index d679e7dd5b21166be8f90d14f20698de10b4c0fd..084829e583711c49dfe8378aafe3b07020bd3c59 100644 (file)
@@ -573,3 +573,107 @@ class MechanismManager(stevedore.named.NamedExtensionManager):
         LOG.warning(_("Failed to bind port %(port)s on host %(host)s"),
                     {'port': context._port['id'],
                      'host': binding.host})
+
+
+class ExtensionManager(stevedore.named.NamedExtensionManager):
+    """Manage extension drivers using drivers."""
+
+    def __init__(self):
+        # Ordered list of extension drivers, defining
+        # the order in which the drivers are called.
+        self.ordered_ext_drivers = []
+
+        LOG.info(_("Configured extension driver names: %s"),
+                 cfg.CONF.ml2.extension_drivers)
+        super(ExtensionManager, self).__init__('neutron.ml2.extension_drivers',
+                                               cfg.CONF.ml2.extension_drivers,
+                                               invoke_on_load=True,
+                                               name_order=True)
+        LOG.info(_("Loaded extension driver names: %s"), self.names())
+        self._register_drivers()
+
+    def _register_drivers(self):
+        """Register all extension drivers.
+
+        This method should only be called once in the ExtensionManager
+        constructor.
+        """
+        for ext in self:
+            self.ordered_ext_drivers.append(ext)
+        LOG.info(_("Registered extension drivers: %s"),
+                 [driver.name for driver in self.ordered_ext_drivers])
+
+    def initialize(self):
+        # Initialize each driver in the list.
+        for driver in self.ordered_ext_drivers:
+            LOG.info(_("Initializing extension driver '%s'"), driver.name)
+            driver.obj.initialize()
+
+    def extension_aliases(self):
+        exts = []
+        for driver in self.ordered_ext_drivers:
+            alias = driver.obj.extension_alias
+            exts.append(alias)
+            LOG.info(_("Got %(alias)s extension from driver '%(drv)s'"),
+                     {'alias': alias, 'drv': driver.name})
+        return exts
+
+    def _call_on_ext_drivers(self, method_name, session, data, result):
+        """Helper method for calling a method across all extension drivers."""
+        for driver in self.ordered_ext_drivers:
+            try:
+                getattr(driver.obj, method_name)(session, data, result)
+            except Exception:
+                LOG.exception(
+                    _("Extension driver '%(name)s' failed in %(method)s"),
+                    {'name': driver.name, 'method': method_name}
+                )
+
+    def process_create_network(self, session, data, result):
+        """Notify all extension drivers during network creation."""
+        self._call_on_ext_drivers("process_create_network", session, data,
+                                  result)
+
+    def process_update_network(self, session, data, result):
+        """Notify all extension drivers during network update."""
+        self._call_on_ext_drivers("process_update_network", session, data,
+                                  result)
+
+    def process_create_subnet(self, session, data, result):
+        """Notify all extension drivers during subnet creation."""
+        self._call_on_ext_drivers("process_create_subnet", session, data,
+                                  result)
+
+    def process_update_subnet(self, session, data, result):
+        """Notify all extension drivers during subnet update."""
+        self._call_on_ext_drivers("process_update_subnet", session, data,
+                                  result)
+
+    def process_create_port(self, session, data, result):
+        """Notify all extension drivers during port creation."""
+        self._call_on_ext_drivers("process_create_port", session, data, result)
+
+    def process_update_port(self, session, data, result):
+        """Notify all extension drivers during port update."""
+        self._call_on_ext_drivers("process_update_port", session, data, result)
+
+    def extend_network_dict(self, session, result):
+        """Notify all extension drivers to extend network dictionary."""
+        for driver in self.ordered_ext_drivers:
+            driver.obj.extend_network_dict(session, result)
+            LOG.info(_("Extended network dict for driver '%(drv)s'"),
+                     {'drv': driver.name})
+
+    def extend_subnet_dict(self, session, result):
+        """Notify all extension drivers to extend subnet dictionary."""
+        for driver in self.ordered_ext_drivers:
+            driver.obj.extend_subnet_dict(session, result)
+            LOG.info(_("Extended subnet dict for driver '%(drv)s'"),
+                     {'drv': driver.name})
+
+    def extend_port_dict(self, session, result):
+        """Notify all extension drivers to extend port dictionary."""
+        for driver in self.ordered_ext_drivers:
+            driver.obj.extend_port_dict(session, result)
+            LOG.info(_("Extended port dict for driver '%(drv)s'"),
+                     {'drv': driver.name})
index 7f60ba5ce6602fb66ba043fb366f79425c29bd19..f25c5124104d1f8159245374c03ae6b6e13a0f08 100644 (file)
@@ -35,6 +35,7 @@ from neutron.common import utils
 from neutron.db import agents_db
 from neutron.db import agentschedulers_db
 from neutron.db import allowedaddresspairs_db as addr_pair_db
+from neutron.db import api as db_api
 from neutron.db import db_base_plugin_v2
 from neutron.db import dvr_mac_db
 from neutron.db import external_net_db
@@ -110,6 +111,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
     def supported_extension_aliases(self):
         if not hasattr(self, '_aliases'):
             aliases = self._supported_extension_aliases[:]
+            aliases += self.extension_manager.extension_aliases()
             sg_rpc.disable_security_group_extension_by_config(aliases)
             self._aliases = aliases
         return self._aliases
@@ -117,9 +119,11 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
     def __init__(self):
         # First load drivers, then initialize DB, then initialize drivers
         self.type_manager = managers.TypeManager()
+        self.extension_manager = managers.ExtensionManager()
         self.mechanism_manager = managers.MechanismManager()
         super(Ml2Plugin, self).__init__()
         self.type_manager.initialize()
+        self.extension_manager.initialize()
         self.mechanism_manager.initialize()
         # bulk support depends on the underlying drivers
         self.__native_bulk_support = self.mechanism_manager.native_bulk_support
@@ -411,6 +415,31 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
     db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
         attributes.PORTS, ['_ml2_extend_port_dict_binding'])
 
+    # Register extend dict methods for network and port resources.
+    # Each mechanism driver that supports extend attribute for the resources
+    # can add those attribute to the result.
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+               attributes.NETWORKS, ['_ml2_md_extend_network_dict'])
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+               attributes.PORTS, ['_ml2_md_extend_port_dict'])
+    db_base_plugin_v2.NeutronDbPluginV2.register_dict_extend_funcs(
+               attributes.SUBNETS, ['_ml2_md_extend_subnet_dict'])
+
+    def _ml2_md_extend_network_dict(self, result, netdb):
+        session = db_api.get_session()
+        with session.begin(subtransactions=True):
+            self.extension_manager.extend_network_dict(session, result)
+
+    def _ml2_md_extend_port_dict(self, result, portdb):
+        session = db_api.get_session()
+        with session.begin(subtransactions=True):
+            self.extension_manager.extend_port_dict(session, result)
+
+    def _ml2_md_extend_subnet_dict(self, result, subnetdb):
+        session = db_api.get_session()
+        with session.begin(subtransactions=True):
+            self.extension_manager.extend_subnet_dict(session, result)
+
     # Note - The following hook methods have "ml2" in their names so
     # that they are not called twice during unit tests due to global
     # registration of hooks in portbindings_db.py used by other
@@ -460,6 +489,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
         with session.begin(subtransactions=True):
             self._ensure_default_security_group(context, tenant_id)
             result = super(Ml2Plugin, self).create_network(context, network)
+            self.extension_manager.process_create_network(session, net_data,
+                                                          result)
             self._process_l3_create(context, result, net_data)
             net_data['id'] = result['id']
             self.type_manager.create_network_segments(context, net_data,
@@ -487,6 +518,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
             updated_network = super(Ml2Plugin, self).update_network(context,
                                                                     id,
                                                                     network)
+            self.extension_manager.process_update_network(session, network,
+                                                          original_network)
             self._process_l3_update(context, updated_network,
                                     network['network'])
             self.type_manager._extend_network_dict_provider(context,
@@ -627,6 +660,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
         session = context.session
         with session.begin(subtransactions=True):
             result = super(Ml2Plugin, self).create_subnet(context, subnet)
+            self.extension_manager.process_create_subnet(session, subnet,
+                                                         result)
             mech_context = driver_context.SubnetContext(self, context, result)
             self.mechanism_manager.create_subnet_precommit(mech_context)
 
@@ -645,6 +680,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
             original_subnet = super(Ml2Plugin, self).get_subnet(context, id)
             updated_subnet = super(Ml2Plugin, self).update_subnet(
                 context, id, subnet)
+            self.extension_manager.process_update_subnet(session, subnet,
+                                                         original_subnet)
             mech_context = driver_context.SubnetContext(
                 self, context, updated_subnet, original_subnet=original_subnet)
             self.mechanism_manager.update_subnet_precommit(mech_context)
@@ -737,6 +774,7 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
             sgids = self._get_security_groups_on_port(context, port)
             dhcp_opts = port['port'].get(edo_ext.EXTRADHCPOPTS, [])
             result = super(Ml2Plugin, self).create_port(context, port)
+            self.extension_manager.process_create_port(session, attrs, result)
             self._process_port_create_security_group(context, result, sgids)
             network = self.get_network(context, result['network_id'])
             binding = db.add_port_binding(session, result['id'])
@@ -791,6 +829,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2,
             original_port = self._make_port_dict(port_db)
             updated_port = super(Ml2Plugin, self).update_port(context, id,
                                                               port)
+            self.extension_manager.process_update_port(session, attrs,
+                                                       original_port)
             if addr_pair.ADDRESS_PAIRS in port['port']:
                 need_port_update_notify |= (
                     self.update_address_pairs_on_port(context, id, port,
diff --git a/neutron/tests/unit/ml2/extensions/__init__.py b/neutron/tests/unit/ml2/extensions/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/unit/ml2/extensions/test_extension.py b/neutron/tests/unit/ml2/extensions/test_extension.py
new file mode 100644 (file)
index 0000000..c48c0e7
--- /dev/null
@@ -0,0 +1,69 @@
+#    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.api import extensions
+from neutron.api.v2 import attributes as attr
+
+
+EXTENDED_ATTRIBUTES_2_0 = {
+    'networks': {
+        'network_extension': {'allow_post': True,
+                              'allow_put': True,
+                              'default': attr.ATTR_NOT_SPECIFIED,
+                              'is_visible': True,
+                              'enforce_policy': True},
+    },
+    'subnets': {
+        'subnet_extension': {'allow_post': True,
+                             'allow_put': True,
+                             'default': attr.ATTR_NOT_SPECIFIED,
+                             'is_visible': True,
+                             'enforce_policy': True},
+    },
+    'ports': {
+        'port_extension': {'allow_post': True,
+                           'allow_put': True,
+                           'default': attr.ATTR_NOT_SPECIFIED,
+                           'is_visible': True,
+                           'enforce_policy': True},
+    },
+}
+
+
+class Test_extension(extensions.ExtensionDescriptor):
+
+    @classmethod
+    def get_name(cls):
+        return "ML2 test extension"
+
+    @classmethod
+    def get_alias(cls):
+        return "test_extension"
+
+    @classmethod
+    def get_description(cls):
+        return _("Adds test attributes to core resources.")
+
+    @classmethod
+    def get_namespace(cls):
+        return ("http://docs.openstack.org/ext/neutron/ml2/test/"
+                "test_extension/api/v1.0")
+
+    @classmethod
+    def get_updated(cls):
+        return "2014-07-16T10:00:00-00:00"
+
+    def get_extended_resources(self, version):
+        if version == "2.0":
+            return EXTENDED_ATTRIBUTES_2_0
+        else:
+            return {}
diff --git a/neutron/tests/unit/ml2/test_extension_driver_api.py b/neutron/tests/unit/ml2/test_extension_driver_api.py
new file mode 100644 (file)
index 0000000..f2367b4
--- /dev/null
@@ -0,0 +1,66 @@
+#    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.api import extensions
+from neutron.plugins.ml2 import config
+from neutron.plugins.ml2 import driver_api as api
+from neutron.tests.unit.ml2 import extensions as test_extensions
+from neutron.tests.unit.ml2 import test_ml2_plugin
+
+
+class ExtensionDriverTestCase(test_ml2_plugin.Ml2PluginV2TestCase):
+
+    _extension_drivers = ['test']
+
+    def setUp(self):
+        config.cfg.CONF.set_override('extension_drivers',
+                                     self._extension_drivers,
+                                     group='ml2')
+        super(ExtensionDriverTestCase, self).setUp()
+
+    def test_network_attr(self):
+        with self.network() as network:
+            ent = network['network'].get('network_extension')
+            self.assertIsNotNone(ent)
+
+    def test_subnet_attr(self):
+        with self.subnet() as subnet:
+            ent = subnet['subnet'].get('subnet_extension')
+            self.assertIsNotNone(ent)
+
+    def test_port_attr(self):
+        with self.port() as port:
+            ent = port['port'].get('port_extension')
+            self.assertIsNotNone(ent)
+
+
+class TestExtensionDriver(api.ExtensionDriver):
+    _supported_extension_alias = 'test_extension'
+
+    def initialize(self):
+        self.network_extension = 'Test_Network_Extension'
+        self.subnet_extension = 'Test_Subnet_Extension'
+        self.port_extension = 'Test_Port_Extension'
+        extensions.append_api_extensions_path(test_extensions.__path__)
+
+    @property
+    def extension_alias(self):
+        return self._supported_extension_alias
+
+    def process_create_network(self, session, data, result):
+        result['network_extension'] = self.network_extension
+
+    def process_create_subnet(self, session, data, result):
+        result['subnet_extension'] = self.subnet_extension
+
+    def process_create_port(self, session, data, result):
+        result['port_extension'] = self.port_extension
index d006b09a6f6024e5fb58e902d36ca3a51ce5efa2..707545704a9c3b30d5e21aa156bfc2313550b517 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -176,6 +176,8 @@ neutron.ml2.mechanism_drivers =
     fslsdn = neutron.plugins.ml2.drivers.mechanism_fslsdn:FslsdnMechanismDriver
     sriovnicswitch = neutron.plugins.ml2.drivers.mech_sriov.mech_driver:SriovNicSwitchMechanismDriver
     nuage = neutron.plugins.ml2.drivers.mech_nuage.driver:NuageMechanismDriver
+neutron.ml2.extension_drivers =
+    test = neutron.tests.unit.ml2.test_extension_driver_api:TestExtensionDriver
 neutron.openstack.common.cache.backends =
     memory = neutron.openstack.common.cache._backends.memory:MemoryBackend
 # These are for backwards compat with Icehouse notification_driver configuration values
@@ -186,7 +188,6 @@ oslo.messaging.notify.drivers =
     neutron.openstack.common.notifier.rpc_notifier = oslo.messaging.notify._impl_messaging:MessagingDriver
     neutron.openstack.common.notifier.test_notifier = oslo.messaging.notify._impl_test:TestDriver
 
-
 [build_sphinx]
 all_files = 1
 build-dir = doc/build