From aadf2f30f84dff3d85f380a7ff4e16dbbb0c6bb0 Mon Sep 17 00:00:00 2001 From: armando-migliaccio Date: Thu, 28 Jan 2016 01:39:00 -0800 Subject: [PATCH] Add the ability to load a set of service plugins on startup Service plugins are a great way of adding functionality in a cohesive way. Some plugins (e.g. network ip availability or auto_allocate) extend the capabilities of the Neutron server by being completely orthogonal to the core plugin, and yet may be considered an integral part of functionality available in any Neutron deployment. For this reason, it makes sense to include them seamlessly in the service plugin loading process. This patch, in particular, introduces the 'auto_allocate' service plugin for default loading, as we'd want this feature to be enabled for Nova to use irrespective of the chosen underlying core plugin. The feature requires subnetpools, external_net and router, while the first is part of the core, the others can be plugin specific and they must be explicitly advertised. That said, they all are features that any deployment can hardly live without. DocImpact: The "get-me-a-network" feature simplifies the process for launching an instance with basic network connectivity (via an externally connected private tenant network). Once leveraged by Nova, a tenant/admin is no longer required to provision networking resources ahead of boot process in order to successfully launch an instance. Implements: blueprint get-me-a-network Change-Id: Ia35e8a946bf0ac0bb085cde46b675d17b0bb2f51 --- neutron/api/extensions.py | 18 +++++++++++++----- neutron/manager.py | 5 +++++ neutron/plugins/common/constants.py | 5 +++++ neutron/tests/base.py | 6 ++++++ neutron/tests/unit/api/test_extensions.py | 10 +++++++++- neutron/tests/unit/test_manager.py | 9 +++++++++ .../add-get-me-a-network-56321aeef5389001.yaml | 9 +++++++++ setup.cfg | 1 + 8 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/add-get-me-a-network-56321aeef5389001.yaml diff --git a/neutron/api/extensions.py b/neutron/api/extensions.py index 2009211fdb1..2f3c2491818 100644 --- a/neutron/api/extensions.py +++ b/neutron/api/extensions.py @@ -30,6 +30,7 @@ from neutron._i18n import _, _LE, _LI, _LW from neutron.common import exceptions import neutron.extensions from neutron import manager +from neutron.plugins.common import constants as const from neutron.services import provider_configuration from neutron import wsgi @@ -440,11 +441,18 @@ class ExtensionManager(object): # Exit loop as no progress was made break if exts_to_process: - LOG.error(_LE("It was impossible to process the following " - "extensions: %s because of missing requirements."), - ','.join(exts_to_process.keys())) - raise exceptions.ExtensionsNotFound( - extensions=list(exts_to_process.keys())) + unloadable_extensions = set(exts_to_process.keys()) + LOG.error(_LE("Unable to process extensions (%s) because " + "the configured plugins do not satisfy " + "their requirements. Some features will not " + "work as expected."), + ', '.join(unloadable_extensions)) + # Fail gracefully for default extensions, just in case some out + # of tree plugins are not entirely up to speed + default_extensions = set(const.DEFAULT_SERVICE_PLUGINS.values()) + if not unloadable_extensions <= default_extensions: + raise exceptions.ExtensionsNotFound( + extensions=list(unloadable_extensions)) # Extending extensions' attributes map. for ext in processed_exts.values(): diff --git a/neutron/manager.py b/neutron/manager.py index fcbf78ac2ca..0b3c4b84946 100644 --- a/neutron/manager.py +++ b/neutron/manager.py @@ -161,6 +161,10 @@ class NeutronManager(object): LOG.info(_LI("Service %s is supported by the core plugin"), service_type) + def _get_default_service_plugins(self): + """Get default service plugins to be loaded.""" + return constants.DEFAULT_SERVICE_PLUGINS.keys() + def _load_service_plugins(self): """Loads service plugins. @@ -171,6 +175,7 @@ class NeutronManager(object): self._load_services_from_core_plugin() plugin_providers = cfg.CONF.service_plugins + plugin_providers.extend(self._get_default_service_plugins()) LOG.debug("Loading service plugins: %s", plugin_providers) for provider in plugin_providers: if provider == '': diff --git a/neutron/plugins/common/constants.py b/neutron/plugins/common/constants.py index aa2d23af2b3..efc982e97fd 100644 --- a/neutron/plugins/common/constants.py +++ b/neutron/plugins/common/constants.py @@ -38,6 +38,11 @@ EXT_TO_SERVICE_MAPPING = { 'qos': QOS, } +# Maps default service plugins entry points to their extension aliases +DEFAULT_SERVICE_PLUGINS = { + 'auto_allocate': 'auto-allocated-topology', +} + # Service operation status constants ACTIVE = "ACTIVE" DOWN = "DOWN" diff --git a/neutron/tests/base.py b/neutron/tests/base.py index 6fe73f692a9..a9d434c83c6 100644 --- a/neutron/tests/base.py +++ b/neutron/tests/base.py @@ -350,6 +350,7 @@ class BaseTestCase(DietTestCase): cp = PluginFixture(core_plugin) self.useFixture(cp) self.patched_dhcp_periodic = cp.patched_dhcp_periodic + self.patched_default_svc_plugins = cp.patched_default_svc_plugins def setup_notification_driver(self, notification_driver=None): self.addCleanup(fake_notifier.reset) @@ -365,6 +366,11 @@ class PluginFixture(fixtures.Fixture): self.core_plugin = core_plugin def _setUp(self): + # Do not load default service plugins in the testing framework + # as all the mocking involved can cause havoc. + self.default_svc_plugins_p = mock.patch( + 'neutron.manager.NeutronManager._get_default_service_plugins') + self.patched_default_svc_plugins = self.default_svc_plugins_p.start() self.dhcp_periodic_p = mock.patch( 'neutron.db.agentschedulers_db.DhcpAgentSchedulerDbMixin.' 'start_periodic_dhcp_agent_status_check') diff --git a/neutron/tests/unit/api/test_extensions.py b/neutron/tests/unit/api/test_extensions.py index 73257e8c689..1a3e2d00fe0 100644 --- a/neutron/tests/unit/api/test_extensions.py +++ b/neutron/tests/unit/api/test_extensions.py @@ -502,13 +502,21 @@ class RequestExtensionTest(base.BaseTestCase): class ExtensionManagerTest(base.BaseTestCase): - def test_missing_required_extensions(self): + def test_missing_required_extensions_raise_error(self): ext_mgr = extensions.ExtensionManager('') attr_map = {} ext_mgr.add_extension(ext_stubs.StubExtensionWithReqs('foo_alias')) self.assertRaises(exceptions.ExtensionsNotFound, ext_mgr.extend_resources, "2.0", attr_map) + def test_missing_required_extensions_gracefully_error(self): + ext_mgr = extensions.ExtensionManager('') + attr_map = {} + default_ext = list(constants.DEFAULT_SERVICE_PLUGINS.values())[0] + ext_mgr.add_extension(ext_stubs.StubExtensionWithReqs(default_ext)) + ext_mgr.extend_resources("2.0", attr_map) + self.assertIn(default_ext, ext_mgr.extensions) + def test_invalid_extensions_are_not_registered(self): class InvalidExtension(object): diff --git a/neutron/tests/unit/test_manager.py b/neutron/tests/unit/test_manager.py index c90d0405132..3ee3b956fa4 100644 --- a/neutron/tests/unit/test_manager.py +++ b/neutron/tests/unit/test_manager.py @@ -111,6 +111,15 @@ class NeutronManagerTestCase(base.BaseTestCase): self.assertIn(constants.LOADBALANCER, svc_plugins.keys()) self.assertIn(constants.DUMMY, svc_plugins.keys()) + def test_load_default_service_plugins(self): + self.patched_default_svc_plugins.return_value = { + 'neutron.tests.unit.dummy_plugin.DummyServicePlugin': 'DUMMY' + } + cfg.CONF.set_override("core_plugin", DB_PLUGIN_KLASS) + mgr = manager.NeutronManager.get_instance() + svc_plugins = mgr.get_service_plugins() + self.assertIn('DUMMY', svc_plugins) + def test_post_plugin_validation(self): cfg.CONF.import_opt('dhcp_agents_per_network', 'neutron.db.agentschedulers_db') diff --git a/releasenotes/notes/add-get-me-a-network-56321aeef5389001.yaml b/releasenotes/notes/add-get-me-a-network-56321aeef5389001.yaml new file mode 100644 index 00000000000..efdc8220a56 --- /dev/null +++ b/releasenotes/notes/add-get-me-a-network-56321aeef5389001.yaml @@ -0,0 +1,9 @@ +--- +prelude: > + The "get-me-a-network" feature simplifies the process + for launching an instance with basic network connectivity + (via an externally connected private tenant network). +features: + - Once Nova takes advantage of this feature, a user can + launch an instance without explicitly provisioning + network resources. diff --git a/setup.cfg b/setup.cfg index 021a88c3e89..f1873b4f26e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -76,6 +76,7 @@ neutron.service_plugins = neutron.services.vpn.plugin.VPNDriverPlugin = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin qos = neutron.services.qos.qos_plugin:QoSPlugin flavors = neutron.services.flavors.flavors_plugin:FlavorsPlugin + auto_allocate = neutron.services.auto_allocate.plugin:Plugin neutron.qos.notification_drivers = message_queue = neutron.services.qos.notification_drivers.message_queue:RpcQosServiceNotificationDriver neutron.ml2.type_drivers =