diff --git a/etc/neutron.conf b/etc/neutron.conf old mode 100644 new mode 100755 index ee42954bfe9..5b58519d0ea --- a/etc/neutron.conf +++ b/etc/neutron.conf @@ -60,6 +60,14 @@ # core_plugin = # Example: core_plugin = ml2 +# (StrOpt) Neutron IPAM (IP address management) driver to be loaded from the +# neutron.ipam_drivers namespace. See setup.cfg for the entry point names. +# If ipam_driver is not set (default behavior), no ipam driver is used. +# Example: ipam_driver = +# In order to use the reference implementation of neutron ipam driver, use +# 'internal'. +# Example: ipam_driver = internal + # (ListOpt) List of service plugin entrypoints to be loaded from the # neutron.service_plugins namespace. See setup.cfg for the entrypoint names of # the plugins included in the neutron source distribution. For compatibility diff --git a/neutron/common/config.py b/neutron/common/config.py index 2837b4ca695..93f57159f3e 100644 --- a/neutron/common/config.py +++ b/neutron/common/config.py @@ -131,6 +131,8 @@ core_opts = [ help=_('If True, effort is made to advertise MTU settings ' 'to VMs via network methods (DHCP and RA MTU options) ' 'when the network\'s preferred MTU is known.')), + cfg.StrOpt('ipam_driver', default=None, + help=_('IPAM driver to use.')), cfg.BoolOpt('vlan_transparent', default=False, help=_('If True, then allow plugins that support it to ' 'create VLAN transparent networks.')), diff --git a/neutron/ipam/driver.py b/neutron/ipam/driver.py index ed40b5eee8d..0e54e8856da 100644 --- a/neutron/ipam/driver.py +++ b/neutron/ipam/driver.py @@ -12,9 +12,11 @@ import abc +from oslo_config import cfg +from oslo_log import log import six -from oslo_log import log +from neutron import manager LOG = log.getLogger(__name__) @@ -43,7 +45,12 @@ class Pool(object): :type subnet_pool: dict :returns: An instance of Driver for the given subnet pool """ - raise NotImplementedError + ipam_driver_name = cfg.CONF.ipam_driver + mgr = manager.NeutronManager + LOG.debug("Loading ipam driver: %s", ipam_driver_name) + driver_class = mgr.load_class_for_provider('neutron.ipam_drivers', + ipam_driver_name) + return driver_class(subnet_pool, context) @abc.abstractmethod def allocate_subnet(self, request): diff --git a/neutron/manager.py b/neutron/manager.py index 503d79448e7..19e50047ea3 100644 --- a/neutron/manager.py +++ b/neutron/manager.py @@ -127,7 +127,11 @@ class NeutronManager(object): self.service_plugins = {constants.CORE: self.plugin} self._load_service_plugins() - def _get_plugin_instance(self, namespace, plugin_provider): + @staticmethod + def load_class_for_provider(namespace, plugin_provider): + if not plugin_provider: + LOG.exception(_LE("Error, plugin is not set")) + raise ImportError(_("Plugin not found.")) try: # Try to resolve plugin by name mgr = driver.DriverManager(namespace, plugin_provider) @@ -140,6 +144,10 @@ class NeutronManager(object): LOG.exception(_LE("Error loading plugin by name, %s"), e1) LOG.exception(_LE("Error loading plugin by class, %s"), e2) raise ImportError(_("Plugin not found.")) + return plugin_class + + def _get_plugin_instance(self, namespace, plugin_provider): + plugin_class = self.load_class_for_provider(namespace, plugin_provider) return plugin_class() def _load_services_from_core_plugin(self): diff --git a/neutron/tests/unit/ipam/fake_driver.py b/neutron/tests/unit/ipam/fake_driver.py new file mode 100755 index 00000000000..3236a4c2e98 --- /dev/null +++ b/neutron/tests/unit/ipam/fake_driver.py @@ -0,0 +1,35 @@ +# Copyright (c) 2015 Infoblox 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.ipam import driver + + +class FakeDriver(driver.Pool): + """Fake IPAM driver for tests only + + Just implement IPAM Driver interface without any functionality inside + """ + + def allocate_subnet(self, subnet): + return driver.Subnet() + + def get_subnet(self, cidr): + return driver.Subnet() + + def update_subnet(self, request): + return driver.Subnet() + + def remove_subnet(self, cidr): + pass diff --git a/neutron/tests/unit/test_ipam.py b/neutron/tests/unit/test_ipam.py old mode 100644 new mode 100755 index aeec959a5da..bb8759f63d0 --- a/neutron/tests/unit/test_ipam.py +++ b/neutron/tests/unit/test_ipam.py @@ -10,14 +10,24 @@ # License for the specific language governing permissions and limitations # under the License. +import types + +import mock import netaddr +from oslo_config import cfg from neutron.common import constants from neutron.common import ipv6_utils +from neutron import context from neutron import ipam +from neutron.ipam import driver from neutron.ipam import exceptions as ipam_exc +from neutron import manager from neutron.openstack.common import uuidutils from neutron.tests import base +from neutron.tests.unit.ipam import fake_driver + +FAKE_IPAM_CLASS = 'neutron.tests.unit.ipam.fake_driver.FakeDriver' class IpamSubnetRequestTestCase(base.BaseTestCase): @@ -224,3 +234,54 @@ class TestAddressRequest(base.BaseTestCase): mac='meh', alien='et', prefix='meh') + + +class TestIpamDriverLoader(base.BaseTestCase): + + def setUp(self): + super(TestIpamDriverLoader, self).setUp() + self.ctx = context.get_admin_context() + + def _verify_fake_ipam_driver_is_loaded(self, driver_name): + mgr = manager.NeutronManager + ipam_driver = mgr.load_class_for_provider('neutron.ipam_drivers', + driver_name) + + self.assertEqual( + fake_driver.FakeDriver, ipam_driver, + "loaded ipam driver should be FakeDriver") + + def _verify_import_error_is_generated(self, driver_name): + mgr = manager.NeutronManager + self.assertRaises(ImportError, mgr.load_class_for_provider, + 'neutron.ipam_drivers', + driver_name) + + def test_ipam_driver_is_loaded_by_class(self): + self._verify_fake_ipam_driver_is_loaded(FAKE_IPAM_CLASS) + + def test_ipam_driver_is_loaded_by_name(self): + self._verify_fake_ipam_driver_is_loaded('fake') + + def test_ipam_driver_raises_import_error(self): + self._verify_import_error_is_generated( + 'neutron.tests.unit.ipam.SomeNonExistentClass') + + def test_ipam_driver_raises_import_error_for_none(self): + self._verify_import_error_is_generated(None) + + def _load_ipam_driver(self, driver_name, subnet_pool_id): + cfg.CONF.set_override("ipam_driver", driver_name) + return driver.Pool.get_instance(subnet_pool_id, self.ctx) + + def test_ipam_driver_is_loaded_from_ipam_driver_config_value(self): + ipam_driver = self._load_ipam_driver('fake', None) + self.assertIsInstance( + ipam_driver, (fake_driver.FakeDriver, types.ClassType), + "loaded ipam driver should be of type FakeDriver") + + @mock.patch(FAKE_IPAM_CLASS) + def test_ipam_driver_is_loaded_with_subnet_pool_id(self, ipam_mock): + subnet_pool_id = 'SomePoolID' + self._load_ipam_driver('fake', subnet_pool_id) + ipam_mock.assert_called_once_with(subnet_pool_id, self.ctx) diff --git a/setup.cfg b/setup.cfg old mode 100644 new mode 100755 index 3934f54a42a..593760c5c67 --- a/setup.cfg +++ b/setup.cfg @@ -198,6 +198,9 @@ neutron.ml2.extension_drivers = cisco_n1kv_ext = neutron.plugins.ml2.drivers.cisco.n1kv.n1kv_ext_driver:CiscoN1kvExtensionDriver neutron.openstack.common.cache.backends = memory = neutron.openstack.common.cache._backends.memory:MemoryBackend +neutron.ipam_drivers = + fake = neutron.tests.unit.ipam.fake_driver:FakeDriver + internal = neutron.ipam.drivers.neutrondb_ipam.driver:NeutronDbPool # These are for backwards compat with Icehouse notification_driver configuration values oslo.messaging.notify.drivers = neutron.openstack.common.notifier.log_notifier = oslo_messaging.notify._impl_log:LogDriver