diff --git a/neutron/api/extensions.py b/neutron/api/extensions.py index 5919d83965..751c062adf 100644 --- a/neutron/api/extensions.py +++ b/neutron/api/extensions.py @@ -18,6 +18,7 @@ from abc import ABCMeta import imp +import itertools import os from oslo.config import cfg @@ -578,6 +579,7 @@ class PluginAwareExtensionManager(ExtensionManager): def __init__(self, path, plugins): self.plugins = plugins super(PluginAwareExtensionManager, self).__init__(path) + self.check_if_plugin_extensions_loaded() def _check_extension(self, extension): """Check if an extension is supported by any plugin.""" @@ -616,6 +618,16 @@ class PluginAwareExtensionManager(ExtensionManager): NeutronManager.get_service_plugins()) return cls._instance + def check_if_plugin_extensions_loaded(self): + """Check if an extension supported by a plugin has been loaded.""" + plugin_extensions = set(itertools.chain.from_iterable([ + getattr(plugin, "supported_extension_aliases", []) + for plugin in self.plugins.values()])) + missing_aliases = plugin_extensions - set(self.extensions) + if missing_aliases: + raise exceptions.ExtensionsNotFound( + extensions=list(missing_aliases)) + class RequestExtension(object): """Extend requests and responses of core Neutron OpenStack API controllers. @@ -662,3 +674,9 @@ def get_extensions_path(): paths = ':'.join([cfg.CONF.api_extensions_path, paths]) return paths + + +def append_api_extensions_path(paths): + paths = [cfg.CONF.api_extensions_path] + paths + cfg.CONF.set_override('api_extensions_path', + ':'.join([p for p in paths if p])) diff --git a/neutron/common/exceptions.py b/neutron/common/exceptions.py index df49df8580..a46e17dfcd 100644 --- a/neutron/common/exceptions.py +++ b/neutron/common/exceptions.py @@ -261,6 +261,10 @@ class InvalidExtensionEnv(BadRequest): message = _("Invalid extension environment: %(reason)s") +class ExtensionsNotFound(NotFound): + message = _("Extensions not found: %(extensions)s") + + class InvalidContentType(NeutronException): message = _("Invalid content type %(content_type)s") diff --git a/neutron/plugins/bigswitch/plugin.py b/neutron/plugins/bigswitch/plugin.py index b5e272e731..ab2e7bf7c8 100644 --- a/neutron/plugins/bigswitch/plugin.py +++ b/neutron/plugins/bigswitch/plugin.py @@ -48,11 +48,11 @@ import base64 import copy import httplib import json -import os import socket from oslo.config import cfg +from neutron.api import extensions as neutron_extensions from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.common import constants as const from neutron.common import exceptions @@ -77,17 +77,12 @@ from neutron.openstack.common import importutils from neutron.openstack.common import log as logging from neutron.openstack.common import rpc from neutron.plugins.bigswitch.db import porttracker_db +from neutron.plugins.bigswitch import extensions from neutron.plugins.bigswitch import routerrule_db from neutron.plugins.bigswitch.version import version_string_with_vcs LOG = logging.getLogger(__name__) -# Include the BigSwitch Extensions path in the api_extensions -EXTENSIONS_PATH = os.path.join(os.path.dirname(__file__), 'extensions') -if not cfg.CONF.api_extensions_path: - cfg.CONF.set_override('api_extensions_path', - EXTENSIONS_PATH) - restproxy_opts = [ cfg.StrOpt('servers', default='localhost:8800', help=_("A comma separated list of BigSwitch or Floodlight " @@ -450,6 +445,9 @@ class NeutronRestProxyV2(db_base_plugin_v2.NeutronDbPluginV2, # init DB, proxy's persistent store defaults to in-memory sql-lite DB db.configure_db() + # Include the BigSwitch Extensions path in the api_extensions + neutron_extensions.append_api_extensions_path(extensions.__path__) + # 'servers' is the list of network controller REST end-points # (used in order specified till one suceeds, and it is sticky # till next failure). Use 'server_auth' to encode api-key diff --git a/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py b/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py index 6a21d13150..1e695912df 100644 --- a/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py +++ b/neutron/plugins/cisco/n1kv/n1kv_neutron_plugin.py @@ -21,8 +21,6 @@ import eventlet -from oslo.config import cfg as q_conf - from neutron.agent import securitygroups_rpc as sg_rpc from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.api.rpc.agentnotifiers import l3_rpc_agent_api @@ -148,8 +146,6 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, # bulk operations. __native_bulk_support = False supported_extension_aliases = ["provider", "agent", - "policy_profile_binding", - "network_profile_binding", "n1kv_profile", "network_profile", "policy_profile", "external-net", "router", "credential"] @@ -164,11 +160,6 @@ class N1kvNeutronPluginV2(db_base_plugin_v2.NeutronDbPluginV2, n1kv_db_v2.initialize() c_cred.Store.initialize() self._initialize_network_ranges() - # If no api_extensions_path is provided set the following - if not q_conf.CONF.api_extensions_path: - q_conf.CONF.set_override( - 'api_extensions_path', - 'extensions:neutron/plugins/cisco/extensions') self._setup_vsm() self._setup_rpc() diff --git a/neutron/plugins/cisco/network_plugin.py b/neutron/plugins/cisco/network_plugin.py index f64822f51d..c7b0ea57f3 100644 --- a/neutron/plugins/cisco/network_plugin.py +++ b/neutron/plugins/cisco/network_plugin.py @@ -22,6 +22,7 @@ import logging from sqlalchemy import orm import webob.exc as wexc +from neutron.api import extensions as neutron_extensions from neutron.api.v2 import base from neutron.common import exceptions as exc from neutron.db import db_base_plugin_v2 @@ -31,13 +32,14 @@ from neutron.plugins.cisco.common import cisco_constants as const from neutron.plugins.cisco.common import cisco_exceptions as cexc from neutron.plugins.cisco.common import config from neutron.plugins.cisco.db import network_db_v2 as cdb +from neutron.plugins.cisco import extensions LOG = logging.getLogger(__name__) class PluginV2(db_base_plugin_v2.NeutronDbPluginV2): """Meta-Plugin with v2 API support for multiple sub-plugins.""" - supported_extension_aliases = ["Cisco Credential", "Cisco qos"] + supported_extension_aliases = ["credential", "Cisco qos"] _methods_to_delegate = ['create_network', 'delete_network', 'update_network', 'get_network', 'get_networks', @@ -81,6 +83,8 @@ class PluginV2(db_base_plugin_v2.NeutronDbPluginV2): self.supported_extension_aliases.extend( self._model.supported_extension_aliases) + neutron_extensions.append_api_extensions_path(extensions.__path__) + # Extend the fault map self._extend_fault_map() diff --git a/neutron/plugins/metaplugin/meta_neutron_plugin.py b/neutron/plugins/metaplugin/meta_neutron_plugin.py index 6731d204bf..fb4ad88776 100644 --- a/neutron/plugins/metaplugin/meta_neutron_plugin.py +++ b/neutron/plugins/metaplugin/meta_neutron_plugin.py @@ -51,11 +51,12 @@ class MetaPluginV2(db_base_plugin_v2.NeutronDbPluginV2, def __init__(self, configfile=None): LOG.debug(_("Start initializing metaplugin")) - self.supported_extension_aliases = \ - cfg.CONF.META.supported_extension_aliases.split(',') - self.supported_extension_aliases += ['flavor', 'external-net', - 'router', 'ext-gw-mode', - 'extraroute'] + self.supported_extension_aliases = ['flavor', 'external-net', + 'router', 'ext-gw-mode', + 'extraroute'] + if cfg.CONF.META.supported_extension_aliases: + cfg_aliases = cfg.CONF.META.supported_extension_aliases.split(',') + self.supported_extension_aliases += cfg_aliases # Ignore config option overapping def _is_opt_registered(opts, opt): diff --git a/neutron/plugins/midonet/plugin.py b/neutron/plugins/midonet/plugin.py index ca2cb9c186..230fe41cf5 100644 --- a/neutron/plugins/midonet/plugin.py +++ b/neutron/plugins/midonet/plugin.py @@ -204,7 +204,7 @@ class MidonetPluginV2(db_base_plugin_v2.NeutronDbPluginV2, securitygroups_db.SecurityGroupDbMixin): supported_extension_aliases = ['external-net', 'router', 'security-group', - 'agent' 'dhcp_agent_scheduler', 'binding'] + 'agent', 'dhcp_agent_scheduler', 'binding'] __native_bulk_support = False def __init__(self): diff --git a/neutron/plugins/nec/extensions/packetfilter.py b/neutron/plugins/nec/extensions/packetfilter.py index 52591a64b1..b487dc965c 100644 --- a/neutron/plugins/nec/extensions/packetfilter.py +++ b/neutron/plugins/nec/extensions/packetfilter.py @@ -122,7 +122,6 @@ PACKET_FILTER_ATTR_MAP = {COLLECTION: PACKET_FILTER_ATTR_PARAMS} class Packetfilter(extensions.ExtensionDescriptor): - @classmethod def get_name(cls): return ALIAS @@ -157,10 +156,6 @@ class Packetfilter(extensions.ExtensionDescriptor): COLLECTION, resource, attr_map=PACKET_FILTER_ATTR_PARAMS) return [pf_ext] - def update_attributes_map(self, attributes): - super(Packetfilter, self).update_attributes_map( - attributes, extension_attrs_map=PACKET_FILTER_ATTR_MAP) - def get_extended_resources(self, version): if version == "2.0": return PACKET_FILTER_ATTR_MAP diff --git a/neutron/plugins/nec/extensions/router_provider.py b/neutron/plugins/nec/extensions/router_provider.py index d893a4c188..102e232184 100644 --- a/neutron/plugins/nec/extensions/router_provider.py +++ b/neutron/plugins/nec/extensions/router_provider.py @@ -14,7 +14,6 @@ # License for the specific language governing permissions and limitations # under the License. -from neutron.api import extensions from neutron.api.v2 import attributes from neutron.openstack.common import log as logging @@ -33,7 +32,7 @@ ROUTER_PROVIDER_ATTRIBUTE = { } -class Router_provider(extensions.ExtensionDescriptor): +class Router_provider(object): @classmethod def get_name(cls): return "Router Provider" diff --git a/neutron/plugins/nec/nec_plugin.py b/neutron/plugins/nec/nec_plugin.py index 5d38a485f3..8fbc59c95b 100644 --- a/neutron/plugins/nec/nec_plugin.py +++ b/neutron/plugins/nec/nec_plugin.py @@ -17,6 +17,7 @@ # @author: Akihiro MOTOKI from neutron.agent import securitygroups_rpc as sg_rpc +from neutron.api import extensions as neutron_extensions from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api from neutron.api.v2 import attributes as attrs from neutron.common import constants as const @@ -46,6 +47,7 @@ from neutron.plugins.nec.common import config from neutron.plugins.nec.common import exceptions as nexc from neutron.plugins.nec.db import api as ndb from neutron.plugins.nec.db import router as rdb +from neutron.plugins.nec import extensions from neutron.plugins.nec import nec_router from neutron.plugins.nec import ofc_manager from neutron.plugins.nec import packet_filter @@ -104,11 +106,8 @@ class NECPluginV2(db_base_plugin_v2.NeutronDbPluginV2, self.ofc = ofc_manager.OFCManager() self.base_binding_dict = self._get_base_binding_dict() portbindings_base.register_port_dict_function() - # Set the plugin default extension path - # if no api_extensions_path is specified. - if not config.CONF.api_extensions_path: - config.CONF.set_override('api_extensions_path', - 'neutron/plugins/nec/extensions') + + neutron_extensions.append_api_extensions_path(extensions.__path__) self.setup_rpc() self.l3_rpc_notifier = nec_router.L3AgentNotifyAPI() diff --git a/neutron/plugins/nicira/NeutronPlugin.py b/neutron/plugins/nicira/NeutronPlugin.py index ab3319bc4b..05081318ac 100644 --- a/neutron/plugins/nicira/NeutronPlugin.py +++ b/neutron/plugins/nicira/NeutronPlugin.py @@ -27,6 +27,7 @@ from oslo.config import cfg from sqlalchemy.orm import exc as sa_exc import webob.exc +from neutron.api import extensions as neutron_extensions from neutron.api.v2 import attributes as attr from neutron.api.v2 import base from neutron.common import constants @@ -181,9 +182,7 @@ class NvpPluginV2(addr_pair_db.AllowedAddressPairsMixin, 'default': self._nvp_delete_port} } - # If no api_extensions_path is provided set the following - if not cfg.CONF.api_extensions_path: - cfg.CONF.set_override('api_extensions_path', NVP_EXT_PATH) + neutron_extensions.append_api_extensions_path([NVP_EXT_PATH]) self.nvp_opts = cfg.CONF.NVP self.nvp_sync_opts = cfg.CONF.NVP_SYNC self.cluster = create_nvp_cluster(cfg.CONF, diff --git a/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py b/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py index 59b2a1f999..298074c7df 100644 --- a/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py +++ b/neutron/tests/unit/cisco/n1kv/test_n1kv_plugin.py @@ -18,9 +18,9 @@ # @author: Abhishek Raut, Cisco Systems Inc. from mock import patch -import os from oslo.config import cfg +from neutron.api import extensions as neutron_extensions from neutron.api.v2 import attributes from neutron.common.test_lib import test_config from neutron import context @@ -204,8 +204,7 @@ class N1kvPluginTestCase(test_plugin.NeutronDbPluginV2TestCase): n1kv_neutron_plugin.N1kvNeutronPluginV2._setup_vsm = _fake_setup_vsm test_config['plugin_name_v2'] = self._plugin_name - cfg.CONF.set_override('api_extensions_path', - os.path.dirname(extensions.__file__)) + neutron_extensions.append_api_extensions_path(extensions.__path__) self.addCleanup(cfg.CONF.reset) ext_mgr = NetworkProfileTestExtensionManager() test_config['extension_manager'] = ext_mgr diff --git a/neutron/tests/unit/nicira/test_networkgw.py b/neutron/tests/unit/nicira/test_networkgw.py index e8fcd32a0f..db6fda6c7a 100644 --- a/neutron/tests/unit/nicira/test_networkgw.py +++ b/neutron/tests/unit/nicira/test_networkgw.py @@ -32,6 +32,7 @@ from neutron.db import db_base_plugin_v2 from neutron import manager from neutron.plugins.nicira.dbexts import nicira_networkgw_db from neutron.plugins.nicira.extensions import nvp_networkgw as networkgw +from neutron.plugins.nicira.NeutronPlugin import NVP_EXT_PATH from neutron import quota from neutron.tests import base from neutron.tests.unit import test_api_v2 @@ -630,6 +631,10 @@ class TestNetworkGatewayPlugin(db_base_plugin_v2.NeutronDbPluginV2, supported_extension_aliases = ["network-gateway"] + def __init__(self, **args): + super(TestNetworkGatewayPlugin, self).__init__(**args) + extensions.append_api_extensions_path([NVP_EXT_PATH]) + def delete_port(self, context, id, nw_gw_port_check=True): if nw_gw_port_check: port = self._get_port(context, id) diff --git a/neutron/tests/unit/test_extensions.py b/neutron/tests/unit/test_extensions.py index b2ae1ec3b5..4b0c639a63 100644 --- a/neutron/tests/unit/test_extensions.py +++ b/neutron/tests/unit/test_extensions.py @@ -17,12 +17,14 @@ import os +import mock import routes import webob import webtest from neutron.api import extensions from neutron.common import config +from neutron.common import exceptions from neutron.db import db_base_plugin_v2 from neutron.openstack.common import jsonutils from neutron.openstack.common import log as logging @@ -447,15 +449,17 @@ class PluginAwareExtensionManagerTest(base.BaseTestCase): def test_unsupported_extensions_are_not_loaded(self): stub_plugin = ext_stubs.StubPlugin(supported_extensions=["e1", "e3"]) plugin_info = {constants.CORE: stub_plugin} - ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) + with mock.patch("neutron.api.extensions.PluginAwareExtensionManager." + "check_if_plugin_extensions_loaded"): + ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) - ext_mgr.add_extension(ext_stubs.StubExtension("e1")) - ext_mgr.add_extension(ext_stubs.StubExtension("e2")) - ext_mgr.add_extension(ext_stubs.StubExtension("e3")) + ext_mgr.add_extension(ext_stubs.StubExtension("e1")) + ext_mgr.add_extension(ext_stubs.StubExtension("e2")) + ext_mgr.add_extension(ext_stubs.StubExtension("e3")) - self.assertIn("e1", ext_mgr.extensions) - self.assertNotIn("e2", ext_mgr.extensions) - self.assertIn("e3", ext_mgr.extensions) + self.assertIn("e1", ext_mgr.extensions) + self.assertNotIn("e2", ext_mgr.extensions) + self.assertIn("e3", ext_mgr.extensions) def test_extensions_are_not_loaded_for_plugins_unaware_of_extensions(self): class ExtensionUnawarePlugin(object): @@ -478,11 +482,13 @@ class PluginAwareExtensionManagerTest(base.BaseTestCase): supported_extension_aliases = ["supported_extension"] plugin_info = {constants.CORE: PluginWithoutExpectedIface()} - ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) - ext_mgr.add_extension( - ext_stubs.ExtensionExpectingPluginInterface("supported_extension")) + with mock.patch("neutron.api.extensions.PluginAwareExtensionManager." + "check_if_plugin_extensions_loaded"): + ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) + ext_mgr.add_extension(ext_stubs.ExtensionExpectingPluginInterface( + "supported_extension")) - self.assertNotIn("e1", ext_mgr.extensions) + self.assertNotIn("e1", ext_mgr.extensions) def test_extensions_are_loaded_for_plugin_with_expected_interface(self): @@ -494,11 +500,13 @@ class PluginAwareExtensionManagerTest(base.BaseTestCase): pass plugin_info = {constants.CORE: PluginWithExpectedInterface()} - ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) - ext_mgr.add_extension( - ext_stubs.ExtensionExpectingPluginInterface("supported_extension")) + with mock.patch("neutron.api.extensions.PluginAwareExtensionManager." + "check_if_plugin_extensions_loaded"): + ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) + ext_mgr.add_extension(ext_stubs.ExtensionExpectingPluginInterface( + "supported_extension")) - self.assertIn("supported_extension", ext_mgr.extensions) + self.assertIn("supported_extension", ext_mgr.extensions) def test_extensions_expecting_neutron_plugin_interface_are_loaded(self): class ExtensionForQuamtumPluginInterface(ext_stubs.StubExtension): @@ -509,10 +517,13 @@ class PluginAwareExtensionManagerTest(base.BaseTestCase): pass stub_plugin = ext_stubs.StubPlugin(supported_extensions=["e1"]) plugin_info = {constants.CORE: stub_plugin} - ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) - ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1")) - self.assertIn("e1", ext_mgr.extensions) + with mock.patch("neutron.api.extensions.PluginAwareExtensionManager." + "check_if_plugin_extensions_loaded"): + ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) + ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1")) + + self.assertIn("e1", ext_mgr.extensions) def test_extensions_without_need_for__plugin_interface_are_loaded(self): class ExtensionWithNoNeedForPluginInterface(ext_stubs.StubExtension): @@ -525,10 +536,12 @@ class PluginAwareExtensionManagerTest(base.BaseTestCase): stub_plugin = ext_stubs.StubPlugin(supported_extensions=["e1"]) plugin_info = {constants.CORE: stub_plugin} - ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) - ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1")) + with mock.patch("neutron.api.extensions.PluginAwareExtensionManager." + "check_if_plugin_extensions_loaded"): + ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) + ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1")) - self.assertIn("e1", ext_mgr.extensions) + self.assertIn("e1", ext_mgr.extensions) def test_extension_loaded_for_non_core_plugin(self): class NonCorePluginExtenstion(ext_stubs.StubExtension): @@ -537,10 +550,20 @@ class PluginAwareExtensionManagerTest(base.BaseTestCase): stub_plugin = ext_stubs.StubPlugin(supported_extensions=["e1"]) plugin_info = {constants.DUMMY: stub_plugin} - ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) - ext_mgr.add_extension(NonCorePluginExtenstion("e1")) + with mock.patch("neutron.api.extensions.PluginAwareExtensionManager." + "check_if_plugin_extensions_loaded"): + ext_mgr = extensions.PluginAwareExtensionManager('', plugin_info) + ext_mgr.add_extension(NonCorePluginExtenstion("e1")) - self.assertIn("e1", ext_mgr.extensions) + self.assertIn("e1", ext_mgr.extensions) + + def test_unloaded_supported_extensions_raises_exception(self): + stub_plugin = ext_stubs.StubPlugin( + supported_extensions=["unloaded_extension"]) + plugin_info = {constants.CORE: stub_plugin} + self.assertRaises(exceptions.ExtensionsNotFound, + extensions.PluginAwareExtensionManager, + '', plugin_info) class ExtensionControllerTest(testlib_api.WebTestCase):