]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Santhosh/Rajaram|latest merge from quantum and made extensions use options to load...
authorRajaram Mallya <rajarammallya@gmail.com>
Mon, 25 Jul 2011 08:58:52 +0000 (14:28 +0530)
committerRajaram Mallya <rajarammallya@gmail.com>
Mon, 25 Jul 2011 08:58:52 +0000 (14:28 +0530)
1  2 
etc/quantum.conf.test
quantum/api/__init__.py
quantum/common/extensions.py
quantum/manager.py
quantum/plugins/SamplePlugin.py
tests/unit/test_extensions.py
tools/pip-requires

index 5e1e3412bcc57ce6060a36a286ff57038d2b913a,b1c266246af654711f2a53af58812afe4e7ec553..f3199cd888f88b9eb9f92403ac2da88738e6f58b
@@@ -10,19 -13,3 +10,32 @@@ bind_host = 0.0.0.
  
  # Port the bind the API server to
  bind_port = 9696
- [app:quantum]
- paste.app_factory = quantum.l2Network.service:app_factory
 +
 +# Path to the extensions
 +api_extensions_path = unit/extensions
 +
 +[pipeline:extensions_app_with_filter]
 +pipeline = extensions extensions_test_app
 +
 +[filter:extensions]
 +paste.filter_factory = quantum.common.extensions:ExtensionMiddleware.factory
 +
 +[app:extensions_test_app]
 +paste.app_factory = tests.unit.test_extensions:app_factory
 +
++[composite:quantum]
++use = egg:Paste#urlmap
++/: quantumversions
++/v0.1: quantumapi
 +
++[pipeline:quantumapi]
++pipeline = extensions quantumapiapp
++
++[filter:extensions]
++paste.filter_factory = quantum.common.extensions:ExtensionMiddleware.factory
++
++[app:quantumversions]
++paste.app_factory = quantum.api.versions:Versions.factory
++
++[app:quantumapiapp]
++paste.app_factory = quantum.api:APIRouterV01.factory
index 87df7673f06cf60bcb1ab60d738b9e3b87e39899,40dd058babb40f385a06882c355f775eb244e9e8..e0f0d0101d05e6ec1bae5480c01aed5f7c276102
@@@ -41,14 -41,14 +41,15 @@@ class APIRouterV01(wsgi.Router)
      Routes requests on the Quantum API to the appropriate controller
      """
  
-     def __init__(self, ext_mgr=None):
+     def __init__(self, options=None):
          mapper = routes.Mapper()
-         self._setup_routes(mapper)
+         self._setup_routes(mapper, options)
          super(APIRouterV01, self).__init__(mapper)
  
-     def _setup_routes(self, mapper):
+     def _setup_routes(self, mapper, options):
          # Loads the quantum plugin
-         plugin = manager.QuantumManager.get_plugin()
+         plugin = manager.QuantumManager(options).get_plugin()
++
          uri_prefix = '/tenants/{tenant_id}/'
          mapper.resource('network', 'networks',
                          controller=networks.Controller(plugin),
index 80d43fd085244141680ce96f2906b203a75fd86d,0000000000000000000000000000000000000000..521543a40d3de6eca6106682d255dd09c583ef3b
mode 100644,000000..100644
--- /dev/null
@@@ -1,497 -1,0 +1,508 @@@
-                         or PluginAwareExtensionManager(
 +
 +# vim: tabstop=4 shiftwidth=4 softtabstop=4
 +
 +# Copyright 2011 OpenStack LLC.
 +# Copyright 2011 Justin Santa Barbara
 +# 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.
 +
 +import imp
 +import os
 +import routes
 +import logging
 +import webob.dec
 +import webob.exc
 +from gettext import gettext as _
 +from abc import ABCMeta, abstractmethod
 +
 +from quantum.manager import QuantumManager
 +from quantum.common import exceptions
 +from quantum.common import wsgi
 +
 +LOG = logging.getLogger('quantum.common.extensions')
 +
 +
 +class PluginInterface(object):
 +    __metaclass__ = ABCMeta
 +
 +    @classmethod
 +    def __subclasshook__(cls, klass):
 +        """
 +        The __subclasshook__ method is a class method
 +        that will be called everytime a class is tested
 +        using issubclass(klass, PluginInterface).
 +        In that case, it will check that every method
 +        marked with the abstractmethod decorator is
 +        provided by the plugin class.
 +        """
 +        for method in cls.__abstractmethods__:
 +            if any(method in base.__dict__ for base in klass.__mro__):
 +                continue
 +            return NotImplemented
 +        return True
 +
 +
 +class ExtensionDescriptor(object):
 +    """Base class that defines the contract for extensions.
 +
 +    Note that you don't have to derive from this class to have a valid
 +    extension; it is purely a convenience.
 +
 +    """
 +
 +    def get_name(self):
 +        """The name of the extension.
 +
 +        e.g. 'Fox In Socks'
 +
 +        """
 +        raise NotImplementedError()
 +
 +    def get_alias(self):
 +        """The alias for the extension.
 +
 +        e.g. 'FOXNSOX'
 +
 +        """
 +        raise NotImplementedError()
 +
 +    def get_description(self):
 +        """Friendly description for the extension.
 +
 +        e.g. 'The Fox In Socks Extension'
 +
 +        """
 +        raise NotImplementedError()
 +
 +    def get_namespace(self):
 +        """The XML namespace for the extension.
 +
 +        e.g. 'http://www.fox.in.socks/api/ext/pie/v1.0'
 +
 +        """
 +        raise NotImplementedError()
 +
 +    def get_updated(self):
 +        """The timestamp when the extension was last updated.
 +
 +        e.g. '2011-01-22T13:25:27-06:00'
 +
 +        """
 +        # NOTE(justinsb): Not sure of the purpose of this is, vs the XML NS
 +        raise NotImplementedError()
 +
 +    def get_resources(self):
 +        """List of extensions.ResourceExtension extension objects.
 +
 +        Resources define new nouns, and are accessible through URLs.
 +
 +        """
 +        resources = []
 +        return resources
 +
 +    def get_actions(self):
 +        """List of extensions.ActionExtension extension objects.
 +
 +        Actions are verbs callable from the API.
 +
 +        """
 +        actions = []
 +        return actions
 +
 +    def get_request_extensions(self):
 +        """List of extensions.RequestException extension objects.
 +
 +        Request extensions are used to handle custom request data.
 +
 +        """
 +        request_exts = []
 +        return request_exts
 +
 +    def get_plugin_interface(self):
 +        """
 +        Returns an abstract class which defines contract for the plugin.
 +        The abstract class should inherit from extesnions.PluginInterface,
 +        Methods in this abstract class  should be decorated as abstractmethod
 +        """
 +        return None
 +
 +
 +class ActionExtensionController(wsgi.Controller):
 +
 +    def __init__(self, application):
 +
 +        self.application = application
 +        self.action_handlers = {}
 +
 +    def add_action(self, action_name, handler):
 +        self.action_handlers[action_name] = handler
 +
 +    def action(self, request, id):
 +
 +        input_dict = self._deserialize(request.body,
 +                                       request.get_content_type())
 +        for action_name, handler in self.action_handlers.iteritems():
 +            if action_name in input_dict:
 +                return handler(input_dict, request, id)
 +        # no action handler found (bump to downstream application)
 +        response = self.application
 +        return response
 +
 +
 +class RequestExtensionController(wsgi.Controller):
 +
 +    def __init__(self, application):
 +        self.application = application
 +        self.handlers = []
 +
 +    def add_handler(self, handler):
 +        self.handlers.append(handler)
 +
 +    def process(self, request, *args, **kwargs):
 +        res = request.get_response(self.application)
 +        # currently request handlers are un-ordered
 +        for handler in self.handlers:
 +            response = handler(request, res)
 +        return response
 +
 +
 +class ExtensionController(wsgi.Controller):
 +
 +    def __init__(self, extension_manager):
 +        self.extension_manager = extension_manager
 +
 +    def _translate(self, ext):
 +        ext_data = {}
 +        ext_data['name'] = ext.get_name()
 +        ext_data['alias'] = ext.get_alias()
 +        ext_data['description'] = ext.get_description()
 +        ext_data['namespace'] = ext.get_namespace()
 +        ext_data['updated'] = ext.get_updated()
 +        ext_data['links'] = []  # TODO(dprince): implement extension links
 +        return ext_data
 +
 +    def index(self, request):
 +        extensions = []
 +        for _alias, ext in self.extension_manager.extensions.iteritems():
 +            extensions.append(self._translate(ext))
 +        return dict(extensions=extensions)
 +
 +    def show(self, request, id):
 +        # NOTE(dprince): the extensions alias is used as the 'id' for show
 +        ext = self.extension_manager.extensions[id]
 +        return self._translate(ext)
 +
 +    def delete(self, request, id):
 +        raise webob.exc.HTTPNotFound()
 +
 +    def create(self, request):
 +        raise webob.exc.HTTPNotFound()
 +
 +
 +class ExtensionMiddleware(wsgi.Middleware):
 +    """Extensions middleware for WSGI."""
 +    def __init__(self, application, config_params,
 +                 ext_mgr=None):
 +
 +        self.ext_mgr = (ext_mgr
++                        or ExtensionManager(
 +                config_params.get('api_extensions_path', '')))
-     def __init__(self, path):
-         self.plugin = QuantumManager.get_plugin()
 +        mapper = routes.Mapper()
 +
 +        # extended resources
 +        for resource in self.ext_mgr.get_resources():
 +            LOG.debug(_('Extended resource: %s'),
 +                        resource.collection)
 +            mapper.resource(resource.collection, resource.collection,
 +                            controller=resource.controller,
 +                            collection=resource.collection_actions,
 +                            member=resource.member_actions,
 +                            parent_resource=resource.parent)
 +
 +        # extended actions
 +        action_controllers = self._action_ext_controllers(application,
 +                                                          self.ext_mgr, mapper)
 +        for action in self.ext_mgr.get_actions():
 +            LOG.debug(_('Extended action: %s'), action.action_name)
 +            controller = action_controllers[action.collection]
 +            controller.add_action(action.action_name, action.handler)
 +
 +        # extended requests
 +        req_controllers = self._request_ext_controllers(application,
 +                                                        self.ext_mgr, mapper)
 +        for request_ext in self.ext_mgr.get_request_extensions():
 +            LOG.debug(_('Extended request: %s'), request_ext.key)
 +            controller = req_controllers[request_ext.key]
 +            controller.add_handler(request_ext.handler)
 +
 +        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
 +                                                          mapper)
 +
 +        super(ExtensionMiddleware, self).__init__(application)
 +
 +    @classmethod
 +    def factory(cls, global_config, **local_config):
 +        """Paste factory."""
 +        def _factory(app):
 +            return cls(app, global_config, **local_config)
 +        return _factory
 +
 +    def _action_ext_controllers(self, application, ext_mgr, mapper):
 +        """Return a dict of ActionExtensionController-s by collection."""
 +        action_controllers = {}
 +        for action in ext_mgr.get_actions():
 +            if not action.collection in action_controllers.keys():
 +                controller = ActionExtensionController(application)
 +                mapper.connect("/%s/:(id)/action.:(format)" %
 +                                action.collection,
 +                                action='action',
 +                                controller=controller,
 +                                conditions=dict(method=['POST']))
 +                mapper.connect("/%s/:(id)/action" % action.collection,
 +                                action='action',
 +                                controller=controller,
 +                                conditions=dict(method=['POST']))
 +                action_controllers[action.collection] = controller
 +
 +        return action_controllers
 +
 +    def _request_ext_controllers(self, application, ext_mgr, mapper):
 +        """Returns a dict of RequestExtensionController-s by collection."""
 +        request_ext_controllers = {}
 +        for req_ext in ext_mgr.get_request_extensions():
 +            if not req_ext.key in request_ext_controllers.keys():
 +                controller = RequestExtensionController(application)
 +                mapper.connect(req_ext.url_route + '.:(format)',
 +                                action='process',
 +                                controller=controller,
 +                                conditions=req_ext.conditions)
 +
 +                mapper.connect(req_ext.url_route,
 +                                action='process',
 +                                controller=controller,
 +                                conditions=req_ext.conditions)
 +                request_ext_controllers[req_ext.key] = controller
 +
 +        return request_ext_controllers
 +
 +    @webob.dec.wsgify(RequestClass=wsgi.Request)
 +    def __call__(self, req):
 +        """Route the incoming request with router."""
 +        req.environ['extended.app'] = self.application
 +        return self._router
 +
 +    @staticmethod
 +    @webob.dec.wsgify(RequestClass=wsgi.Request)
 +    def _dispatch(req):
 +        """Dispatch the request.
 +
 +        Returns the routed WSGI app's response or defers to the extended
 +        application.
 +
 +        """
 +        match = req.environ['wsgiorg.routing_args'][1]
 +        if not match:
 +            return req.environ['extended.app']
 +        app = match['controller']
 +        return app
 +
 +
++class PluginAwareExtensionMiddleware(ExtensionMiddleware):
++
++    def __init__(self, application, config_params, ext_mgr=None,
++                 plugin_options=None):
++        plugin_aware_extension_mgr = PluginAwareExtensionManager(
++                      config_params.get('api_extensions_path', ''),
++                      plugin_options)
++        ext_mgr = (ext_mgr or plugin_aware_extension_mgr)
++        super(PluginAwareExtensionMiddleware, self).__init__(
++            application, config_params, ext_mgr)
++
++
 +class ExtensionManager(object):
 +    """Load extensions from the configured extension path.
 +
 +    See tests/unit/extensions/foxinsocks.py for an
 +    example extension implementation.
 +
 +    """
 +    def __init__(self, path):
 +        LOG.info(_('Initializing extension manager.'))
 +        self.path = path
 +        self.extensions = {}
 +        self._load_all_extensions()
 +
 +    def get_resources(self):
 +        """Returns a list of ResourceExtension objects."""
 +        resources = []
 +        resources.append(ResourceExtension('extensions',
 +                                            ExtensionController(self)))
 +        for alias, ext in self.extensions.iteritems():
 +            try:
 +                resources.extend(ext.get_resources())
 +            except AttributeError:
 +                # NOTE(dprince): Extension aren't required to have resource
 +                # extensions
 +                pass
 +        return resources
 +
 +    def get_actions(self):
 +        """Returns a list of ActionExtension objects."""
 +        actions = []
 +        for alias, ext in self.extensions.iteritems():
 +            try:
 +                actions.extend(ext.get_actions())
 +            except AttributeError:
 +                # NOTE(dprince): Extension aren't required to have action
 +                # extensions
 +                pass
 +        return actions
 +
 +    def get_request_extensions(self):
 +        """Returns a list of RequestExtension objects."""
 +        request_exts = []
 +        for alias, ext in self.extensions.iteritems():
 +            try:
 +                request_exts.extend(ext.get_request_extensions())
 +            except AttributeError:
 +                # NOTE(dprince): Extension aren't required to have request
 +                # extensions
 +                pass
 +        return request_exts
 +
 +    def _check_extension(self, extension):
 +        """Checks for required methods in extension objects."""
 +        try:
 +            LOG.debug(_('Ext name: %s'), extension.get_name())
 +            LOG.debug(_('Ext alias: %s'), extension.get_alias())
 +            LOG.debug(_('Ext description: %s'), extension.get_description())
 +            LOG.debug(_('Ext namespace: %s'), extension.get_namespace())
 +            LOG.debug(_('Ext updated: %s'), extension.get_updated())
 +        except AttributeError as ex:
 +            LOG.exception(_("Exception loading extension: %s"), unicode(ex))
 +            return False
 +        return True
 +
 +    def _load_all_extensions(self):
 +        """Load extensions from the configured path.
 +
 +        Load extensions from the configured path. The extension name is
 +        constructed from the module_name. If your extension module was named
 +        widgets.py the extension class within that module should be
 +        'Widgets'.
 +
 +        In addition, extensions are loaded from the 'contrib' directory.
 +
 +        See tests/unit/extensions/foxinsocks.py for an example
 +        extension implementation.
 +
 +        """
 +        if os.path.exists(self.path):
 +            self._load_all_extensions_from_path(self.path)
 +
 +        contrib_path = os.path.join(os.path.dirname(__file__), "contrib")
 +        if os.path.exists(contrib_path):
 +            self._load_all_extensions_from_path(contrib_path)
 +
 +    def _load_all_extensions_from_path(self, path):
 +        for f in os.listdir(path):
 +            LOG.info(_('Loading extension file: %s'), f)
 +            mod_name, file_ext = os.path.splitext(os.path.split(f)[-1])
 +            ext_path = os.path.join(path, f)
 +            if file_ext.lower() == '.py' and not mod_name.startswith('_'):
 +                mod = imp.load_source(mod_name, ext_path)
 +                ext_name = mod_name[0].upper() + mod_name[1:]
 +                new_ext_class = getattr(mod, ext_name, None)
 +                if not new_ext_class:
 +                    LOG.warn(_('Did not find expected name '
 +                               '"%(ext_name)s" in %(file)s'),
 +                             {'ext_name': ext_name,
 +                              'file': ext_path})
 +                    continue
 +                new_ext = new_ext_class()
 +                self.add_extension(new_ext)
 +
 +    def add_extension(self, ext):
 +        # Do nothing if the extension doesn't check out
 +        if not self._check_extension(ext):
 +            return
 +
 +        alias = ext.get_alias()
 +        LOG.warn(_('Loaded extension: %s'), alias)
 +
 +        if alias in self.extensions:
 +            raise exceptions.Error("Found duplicate extension: %s"
 +                                         % alias)
 +        self.extensions[alias] = ext
 +
 +
 +class PluginAwareExtensionManager(ExtensionManager):
 +
++    def __init__(self, path, plugin_options=None):
++        self.plugin = QuantumManager(plugin_options).get_plugin()
 +        super(PluginAwareExtensionManager, self).__init__(path)
 +
 +    def _check_extension(self, extension):
 +        """Checks if plugin supports extension and implements the contract."""
 +        extension_is_valid = super(PluginAwareExtensionManager,
 +                                self)._check_extension(extension)
 +        return (extension_is_valid and
 +                self._plugin_supports(extension) and
 +                self._plugin_implements_interface(extension))
 +
 +    def _plugin_supports(self, extension):
 +        alias = extension.get_alias()
 +        return (hasattr(self.plugin, "supported_extension_aliases") and
 +                alias in self.plugin.supported_extension_aliases)
 +
 +    def _plugin_implements_interface(self, extension):
 +        if(not hasattr(extension, "get_plugin_interface") or
 +           extension.get_plugin_interface() is None):
 +            return True
 +        return isinstance(self.plugin, extension.get_plugin_interface())
 +
 +
 +class RequestExtension(object):
 +    """Extend requests and responses of core Quantum OpenStack API controllers.
 +
 +    Provide a way to add data to responses and handle custom request data
 +    that is sent to core Quantum OpenStack API controllers.
 +
 +    """
 +    def __init__(self, method, url_route, handler):
 +        self.url_route = url_route
 +        self.handler = handler
 +        self.conditions = dict(method=[method])
 +        self.key = "%s-%s" % (method, url_route)
 +
 +
 +class ActionExtension(object):
 +    """Add custom actions to core Quantum OpenStack API controllers."""
 +
 +    def __init__(self, collection, action_name, handler):
 +        self.collection = collection
 +        self.action_name = action_name
 +        self.handler = handler
 +
 +
 +class ResourceExtension(object):
 +    """Add top level resources to the OpenStack API in Quantum."""
 +
 +    def __init__(self, collection, controller, parent=None,
 +                 collection_actions={}, member_actions={}):
 +        self.collection = collection
 +        self.controller = controller
 +        self.parent = parent
 +        self.collection_actions = collection_actions
 +        self.member_actions = member_actions
index 33eb77e59048a0cc2e969547ea03332feb405e7f,2a2383b3e2a6718703382529de455d92a8c3a743..4c890d7f750729c6b2faa32030afdf7dadd73f19
@@@ -25,15 -25,16 +25,17 @@@ class
  The caller should make sure that QuantumManager is a singleton.
  """
  import gettext
+ import logging
  import os
 -
 +import logging
  gettext.install('quantum', unicode=1)
  
  from common import utils
  from quantum_plugin_base import QuantumPluginBase
  
 +LOG = logging.getLogger('quantum.manager')
  CONFIG_FILE = "plugins.ini"
+ LOG = logging.getLogger('quantum.manager')
  
  
  def find_config(basepath):
  
  
  class QuantumManager(object):
-     _instance = None
-     def __init__(self, config=None):
-         if config == None:
 +
+     def __init__(self, options=None, config_file=None):
+         if config_file == None:
              self.configuration_file = find_config(
                  os.path.abspath(os.path.dirname(__file__)))
          else:
index 5b4e9c12655e8901c6c82283ea79d4f50887ec05,b1486bf7c3f1395ba49a0078d666d7c17644c27f..ff08bb370100c094ecfa6e20e50e7cef5bec7aee
@@@ -227,40 -233,14 +233,16 @@@ class FakePlugin(object)
      client/cli/api development
      """
  
-     #static data for networks and ports
-     _port_dict_1 = {
-                    1: {'port-id': 1,
-                         'port-state': 'DOWN',
-                         'attachment': None},
-                    2: {'port-id': 2,
-                         'port-state': 'UP',
-                         'attachment': None}}
-     _port_dict_2 = {
-                    1: {'port-id': 1,
-                         'port-state': 'UP',
-                         'attachment': 'SomeFormOfVIFID'},
-                    2: {'port-id': 2,
-                         'port-state': 'DOWN',
-                         'attachment': None}}
-     _networks = {'001':
-                     {
-                     'net-id': '001',
-                     'net-name': 'pippotest',
-                     'net-ports': _port_dict_1},
-                     '002':
-                     {
-                     'net-id': '002',
-                     'net-name': 'cicciotest',
-                     'net-ports': _port_dict_2}}
+     def __init__(self):
+         db.configure_db({'sql_connection': 'sqlite:///:memory:'})
+         FakePlugin._net_counter = 0
  
-     def __init__(self):
-         FakePlugin._net_counter = len(FakePlugin._networks)
 +    supported_extension_aliases = ["FOXNSOX"]
 +
      def _get_network(self, tenant_id, network_id):
-         network = FakePlugin._networks.get(network_id)
-         if not network:
+         try:
+             network = db.network_get(network_id)
+         except:
              raise exc.NetworkNotFound(net_id=network_id)
          return network
  
index 8511a13a144a93a41147377fa6f03389e7cdf53b,0000000000000000000000000000000000000000..e9f01e3ebca33471d11adfb917a2ec0a161acd8f
mode 100644,000000..100644
--- /dev/null
@@@ -1,451 -1,0 +1,455 @@@
-                                        PluginAwareExtensionManager)
 +# vim: tabstop=4 shiftwidth=4 softtabstop=4
 +# Copyright 2011 OpenStack LLC.
 +# 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.
 +import json
 +import unittest
 +import routes
 +import os.path
 +from tests.unit import BaseTest
 +from abc import  abstractmethod
 +
 +from webtest import TestApp
 +from quantum.common import extensions
 +from quantum.common import wsgi
 +from quantum.common import config
 +from quantum.common.extensions import (ExtensionManager,
-         self.ext_mgr = PluginAwareExtensionManager('')
++                                       PluginAwareExtensionManager,
++                                       PluginAwareExtensionMiddleware)
 +
 +test_conf_file = os.path.join(os.path.dirname(__file__), os.pardir,
 +                              os.pardir, 'etc', 'quantum.conf.test')
 +
++plugin_options = {'plugin_provider': "quantum.plugins.SamplePlugin.FakePlugin"}
++
 +
 +class StubExtension(object):
 +
 +    def __init__(self, alias="stub_extension"):
 +        self.alias = alias
 +
 +    def get_name(self):
 +        return "Stub Extension"
 +
 +    def get_alias(self):
 +        return self.alias
 +
 +    def get_description(self):
 +        return ""
 +
 +    def get_namespace(self):
 +        return ""
 +
 +    def get_updated(self):
 +        return ""
 +
 +
 +class StubPlugin(object):
 +
 +    def __init__(self, supported_extensions=[]):
 +        self.supported_extension_aliases = supported_extensions
 +
 +
 +class ExtensionExpectingPluginInterface(StubExtension):
 +    """
 +    This extension expects plugin to implement all the methods defined
 +    in StubPluginInterface
 +    """
 +
 +    def get_plugin_interface(self):
 +        return StubPluginInterface
 +
 +
 +class StubPluginInterface(extensions.PluginInterface):
 +
 +    @abstractmethod
 +    def get_foo(self, bar=None):
 +        pass
 +
 +
 +class StubBaseAppController(wsgi.Controller):
 +
 +    def index(self, request):
 +        return "base app index"
 +
 +    def show(self, request, id):
 +        return {'fort': 'knox'}
 +
 +    def update(self, request, id):
 +        return {'uneditable': 'original_value'}
 +
 +
 +class ExtensionsTestApp(wsgi.Router):
 +
 +    def __init__(self, options={}):
 +        mapper = routes.Mapper()
 +        controller = StubBaseAppController()
 +        mapper.resource("dummy_resource", "/dummy_resources",
 +                        controller=controller)
 +        super(ExtensionsTestApp, self).__init__(mapper)
 +
 +
 +class ResourceExtensionTest(unittest.TestCase):
 +
 +    class ResourceExtensionController(wsgi.Controller):
 +
 +        def index(self, request):
 +            return "resource index"
 +
 +        def show(self, request, id):
 +            return {'data': {'id': id}}
 +
 +        def custom_member_action(self, request, id):
 +            return {'member_action': 'value'}
 +
 +        def custom_collection_action(self, request):
 +            return {'collection': 'value'}
 +
 +    def test_resource_can_be_added_as_extension(self):
 +        res_ext = extensions.ResourceExtension('tweedles',
 +                                            self.ResourceExtensionController())
 +        test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext))
 +
 +        index_response = test_app.get("/tweedles")
 +        self.assertEqual(200, index_response.status_int)
 +        self.assertEqual("resource index", index_response.body)
 +
 +        show_response = test_app.get("/tweedles/25266")
 +        self.assertEqual({'data': {'id': "25266"}}, show_response.json)
 +
 +    def test_resource_extension_with_custom_member_action(self):
 +        controller = self.ResourceExtensionController()
 +        member = {'custom_member_action': "GET"}
 +        res_ext = extensions.ResourceExtension('tweedles', controller,
 +                                               member_actions=member)
 +        test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext))
 +
 +        response = test_app.get("/tweedles/some_id/custom_member_action")
 +        self.assertEqual(200, response.status_int)
 +        self.assertEqual(json.loads(response.body)['member_action'], "value")
 +
 +    def test_resource_extension_with_custom_collection_action(self):
 +        controller = self.ResourceExtensionController()
 +        collections = {'custom_collection_action': "GET"}
 +        res_ext = extensions.ResourceExtension('tweedles', controller,
 +                                               collection_actions=collections)
 +        test_app = setup_extensions_test_app(SimpleExtensionManager(res_ext))
 +
 +        response = test_app.get("/tweedles/custom_collection_action")
 +        self.assertEqual(200, response.status_int)
 +        self.assertEqual(json.loads(response.body)['collection'], "value")
 +
 +    def test_returns_404_for_non_existant_extension(self):
 +        test_app = setup_extensions_test_app(SimpleExtensionManager(None))
 +
 +        response = test_app.get("/non_extistant_extension", status='*')
 +
 +        self.assertEqual(404, response.status_int)
 +
 +
 +class ActionExtensionTest(unittest.TestCase):
 +
 +    def setUp(self):
 +        super(ActionExtensionTest, self).setUp()
 +        self.extension_app = setup_extensions_test_app()
 +
 +    def test_extended_action_for_adding_extra_data(self):
 +        action_name = 'add_tweedle'
 +        action_params = dict(name='Beetle')
 +        req_body = json.dumps({action_name: action_params})
 +        response = self.extension_app.post('/dummy_resources/1/action',
 +                                     req_body, content_type='application/json')
 +        self.assertEqual("Tweedle Beetle Added.", response.body)
 +
 +    def test_extended_action_for_deleting_extra_data(self):
 +        action_name = 'delete_tweedle'
 +        action_params = dict(name='Bailey')
 +        req_body = json.dumps({action_name: action_params})
 +        response = self.extension_app.post("/dummy_resources/1/action",
 +                                     req_body, content_type='application/json')
 +        self.assertEqual("Tweedle Bailey Deleted.", response.body)
 +
 +    def test_returns_404_for_non_existant_action(self):
 +        non_existant_action = 'blah_action'
 +        action_params = dict(name="test")
 +        req_body = json.dumps({non_existant_action: action_params})
 +
 +        response = self.extension_app.post("/dummy_resources/1/action",
 +                                     req_body, content_type='application/json',
 +                                     status='*')
 +
 +        self.assertEqual(404, response.status_int)
 +
 +    def test_returns_404_for_non_existant_resource(self):
 +        action_name = 'add_tweedle'
 +        action_params = dict(name='Beetle')
 +        req_body = json.dumps({action_name: action_params})
 +
 +        response = self.extension_app.post("/asdf/1/action", req_body,
 +                                   content_type='application/json', status='*')
 +        self.assertEqual(404, response.status_int)
 +
 +
 +class RequestExtensionTest(BaseTest):
 +
 +    def test_headers_can_be_extended(self):
 +        def extend_headers(req, res):
 +            assert req.headers['X-NEW-REQUEST-HEADER'] == "sox"
 +            res.headers['X-NEW-RESPONSE-HEADER'] = "response_header_data"
 +            return res
 +
 +        app = self._setup_app_with_request_handler(extend_headers, 'GET')
 +        response = app.get("/dummy_resources/1",
 +                           headers={'X-NEW-REQUEST-HEADER': "sox"})
 +
 +        self.assertEqual(response.headers['X-NEW-RESPONSE-HEADER'],
 +                                                   "response_header_data")
 +
 +    def test_extend_get_resource_response(self):
 +        def extend_response_data(req, res):
 +            data = json.loads(res.body)
 +            data['extended_key'] = req.GET.get('extended_key')
 +            res.body = json.dumps(data)
 +            return res
 +
 +        app = self._setup_app_with_request_handler(extend_response_data, 'GET')
 +        response = app.get("/dummy_resources/1?extended_key=extended_data")
 +
 +        self.assertEqual(200, response.status_int)
 +        response_data = json.loads(response.body)
 +        self.assertEqual('extended_data', response_data['extended_key'])
 +        self.assertEqual('knox', response_data['fort'])
 +
 +    def test_get_resources(self):
 +        app = setup_extensions_test_app()
 +
 +        response = app.get("/dummy_resources/1?chewing=newblue")
 +
 +        response_data = json.loads(response.body)
 +        self.assertEqual('newblue', response_data['googoose'])
 +        self.assertEqual("Pig Bands!", response_data['big_bands'])
 +
 +    def test_edit_previously_uneditable_field(self):
 +
 +        def _update_handler(req, res):
 +            data = json.loads(res.body)
 +            data['uneditable'] = req.params['uneditable']
 +            res.body = json.dumps(data)
 +            return res
 +
 +        base_app = TestApp(setup_base_app())
 +        response = base_app.put("/dummy_resources/1",
 +                                {'uneditable': "new_value"})
 +        self.assertEqual(response.json['uneditable'], "original_value")
 +
 +        ext_app = self._setup_app_with_request_handler(_update_handler,
 +                                                            'PUT')
 +        ext_response = ext_app.put("/dummy_resources/1",
 +                                    {'uneditable': "new_value"})
 +        self.assertEqual(ext_response.json['uneditable'], "new_value")
 +
 +    def _setup_app_with_request_handler(self, handler, verb):
 +        req_ext = extensions.RequestExtension(verb,
 +                                   '/dummy_resources/:(id)', handler)
 +        manager = SimpleExtensionManager(None, None, req_ext)
 +        return setup_extensions_test_app(manager)
 +
 +
 +class ExtensionManagerTest(unittest.TestCase):
 +
 +    def test_invalid_extensions_are_not_registered(self):
 +
 +        class InvalidExtension(object):
 +            """
 +            This Extension doesn't implement extension methods :
 +            get_name, get_description, get_namespace and get_updated
 +            """
 +            def get_alias(self):
 +                return "invalid_extension"
 +
 +        ext_mgr = ExtensionManager('')
 +        ext_mgr.add_extension(InvalidExtension())
 +        ext_mgr.add_extension(StubExtension("valid_extension"))
 +
 +        self.assertTrue('valid_extension' in ext_mgr.extensions)
 +        self.assertFalse('invalid_extension' in ext_mgr.extensions)
 +
 +
 +class PluginAwareExtensionManagerTest(unittest.TestCase):
 +
 +    def setUp(self):
-     return extensions.ExtensionMiddleware(app, conf, extension_manager)
++        self.ext_mgr = PluginAwareExtensionManager('', plugin_options)
 +
 +    def test_unsupported_extensions_are_not_loaded(self):
 +        self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1", "e3"])
 +
 +        self.ext_mgr.add_extension(StubExtension("e1"))
 +        self.ext_mgr.add_extension(StubExtension("e2"))
 +        self.ext_mgr.add_extension(StubExtension("e3"))
 +
 +        self.assertTrue("e1" in self.ext_mgr.extensions)
 +        self.assertFalse("e2" in self.ext_mgr.extensions)
 +        self.assertTrue("e3" in self.ext_mgr.extensions)
 +
 +    def test_extensions_are_not_loaded_for_plugins_unaware_of_extensions(self):
 +        class ExtensionUnawarePlugin(object):
 +            """
 +            This plugin does not implement supports_extension method.
 +            Extensions will not be loaded when this plugin is used.
 +            """
 +            pass
 +
 +        self.ext_mgr.plugin = ExtensionUnawarePlugin()
 +        self.ext_mgr.add_extension(StubExtension("e1"))
 +
 +        self.assertFalse("e1" in self.ext_mgr.extensions)
 +
 +    def test_extensions_not_loaded_for_plugin_without_expected_interface(self):
 +
 +        class PluginWithoutExpectedInterface(object):
 +            """
 +            Plugin does not implement get_foo method as expected by extension
 +            """
 +            supported_extension_aliases = ["supported_extension"]
 +
 +        self.ext_mgr.plugin = PluginWithoutExpectedInterface()
 +        self.ext_mgr.add_extension(
 +            ExtensionExpectingPluginInterface("supported_extension"))
 +
 +        self.assertFalse("e1" in self.ext_mgr.extensions)
 +
 +    def test_extensions_are_loaded_for_plugin_with_expected_interface(self):
 +
 +        class PluginWithExpectedInterface(object):
 +            """
 +            This Plugin implements get_foo method as expected by extension
 +            """
 +            supported_extension_aliases = ["supported_extension"]
 +
 +            def get_foo(self, bar=None):
 +                pass
 +
 +        self.ext_mgr.plugin = PluginWithExpectedInterface()
 +        self.ext_mgr.add_extension(
 +                ExtensionExpectingPluginInterface("supported_extension"))
 +
 +        self.assertTrue("supported_extension" in self.ext_mgr.extensions)
 +
 +    def test_extensions_expecting_quantum_plugin_interface_are_loaded(self):
 +        class ExtensionForQuamtumPluginInterface(StubExtension):
 +            """
 +            This Extension does not implement get_plugin_interface method.
 +            This will work with any plugin implementing QuantumPluginBase
 +            """
 +            pass
 +
 +        self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"])
 +        self.ext_mgr.add_extension(ExtensionForQuamtumPluginInterface("e1"))
 +
 +        self.assertTrue("e1" in self.ext_mgr.extensions)
 +
 +    def test_extensions_without_need_for__plugin_interface_are_loaded(self):
 +        class ExtensionWithNoNeedForPluginInterface(StubExtension):
 +            """
 +            This Extension does not need any plugin interface.
 +            This will work with any plugin implementing QuantumPluginBase
 +            """
 +            def get_plugin_interface(self):
 +                return None
 +
 +        self.ext_mgr.plugin = StubPlugin(supported_extensions=["e1"])
 +        self.ext_mgr.add_extension(ExtensionWithNoNeedForPluginInterface("e1"))
 +
 +        self.assertTrue("e1" in self.ext_mgr.extensions)
 +
 +
 +class ExtensionControllerTest(unittest.TestCase):
 +
 +    def setUp(self):
 +        super(ExtensionControllerTest, self).setUp()
 +        self.test_app = setup_extensions_test_app()
 +
 +    def test_index_gets_all_registerd_extensions(self):
 +        response = self.test_app.get("/extensions")
 +        foxnsox = response.json["extensions"][0]
 +
 +        self.assertEqual(foxnsox["alias"], "FOXNSOX")
 +        self.assertEqual(foxnsox["namespace"],
 +                         "http://www.fox.in.socks/api/ext/pie/v1.0")
 +
 +    def test_extension_can_be_accessed_by_alias(self):
 +        foxnsox_extension = self.test_app.get("/extensions/FOXNSOX").json
 +
 +        self.assertEqual(foxnsox_extension["alias"], "FOXNSOX")
 +        self.assertEqual(foxnsox_extension["namespace"],
 +                         "http://www.fox.in.socks/api/ext/pie/v1.0")
 +
 +
 +class TestExtensionMiddlewareFactory(unittest.TestCase):
 +
 +    def test_app_configured_with_extensions_as_filter(self):
 +        conf, quantum_app = config.load_paste_app('extensions_app_with_filter',
 +                                        {"config_file": test_conf_file}, None)
 +
 +        response = TestApp(quantum_app).get("/extensions")
 +        self.assertEqual(response.status_int, 200)
 +
 +
 +def app_factory(global_conf, **local_conf):
 +    conf = global_conf.copy()
 +    conf.update(local_conf)
 +    return ExtensionsTestApp(conf)
 +
 +
 +def setup_base_app():
 +    options = {'config_file': test_conf_file}
 +    conf, app = config.load_paste_app('extensions_test_app', options, None)
 +    return app
 +
 +
 +def setup_extensions_middleware(extension_manager=None):
 +    options = {'config_file': test_conf_file}
 +    conf, app = config.load_paste_app('extensions_test_app', options, None)
++    return PluginAwareExtensionMiddleware(app, conf, ext_mgr=extension_manager,
++                                          plugin_options=plugin_options)
 +
 +
 +def setup_extensions_test_app(extension_manager=None):
 +    return TestApp(setup_extensions_middleware(extension_manager))
 +
 +
 +class SimpleExtensionManager(object):
 +
 +    def __init__(self, resource_ext=None, action_ext=None, request_ext=None):
 +        self.resource_ext = resource_ext
 +        self.action_ext = action_ext
 +        self.request_ext = request_ext
 +
 +    def get_resources(self):
 +        resource_exts = []
 +        if self.resource_ext:
 +            resource_exts.append(self.resource_ext)
 +        return resource_exts
 +
 +    def get_actions(self):
 +        action_exts = []
 +        if self.action_ext:
 +            action_exts.append(self.action_ext)
 +        return action_exts
 +
 +    def get_request_extensions(self):
 +        request_extensions = []
 +        if self.request_ext:
 +            request_extensions.append(self.request_ext)
 +        return request_extensions
index 8dbfd85d317f20300326447a0f58095ea6ffacd0,729f950b3d2437b87648aed937a98d9a9c59088a..baf065da290ef136d92ef18158e13f9d9a56f363
@@@ -4,7 -5,7 +5,7 @@@ Past
  PasteDeploy
  pep8==0.5.0
  python-gflags
--routes
  simplejson
++sqlalchemy
  webob
  webtest