From 6b95c5133452ae26da2cb7f08267aa4cb056e7af Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 10 Mar 2011 15:05:04 -0500 Subject: Initial support fo extension resources. Tests. --- nova/api/openstack/__init__.py | 8 ++- nova/api/openstack/extensions.py | 29 ++++++++++ nova/tests/api/openstack/test_extensions.py | 83 +++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 nova/api/openstack/extensions.py create mode 100644 nova/tests/api/openstack/test_extensions.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index ab9dbb780..28e2a1691 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -30,6 +30,7 @@ from nova import wsgi from nova.api.openstack import faults from nova.api.openstack import backup_schedules from nova.api.openstack import consoles +from nova.api.openstack import extensions from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import servers @@ -68,7 +69,7 @@ class APIRouter(wsgi.Router): """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one""" return cls() - def __init__(self): + def __init__(self, ext_manager=None): mapper = routes.Mapper() server_members = {'action': 'POST'} @@ -111,6 +112,11 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}, controller=shared_ip_groups.Controller()) + if ext_manager is None: + ext_manager = extensions.ExtensionManager() + for resource in ext_manager.get_resources(): + resource.add_routes(mapper) + super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py new file mode 100644 index 000000000..1c539c500 --- /dev/null +++ b/nova/api/openstack/extensions.py @@ -0,0 +1,29 @@ +# 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. + +class ExtensionManager(object): + + def get_resources(self): + """ + returns a list of ExtensionResource objects + """ + return [] + +class ExtensionResource(object): + + def add_routes(self, mapper): + pass diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py new file mode 100644 index 000000000..f5332c84a --- /dev/null +++ b/nova/tests/api/openstack/test_extensions.py @@ -0,0 +1,83 @@ +# 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 unittest + +import webob + +from nova.api import openstack +import nova.wsgi + +class StubController(nova.wsgi.Controller): + + def __init__(self, body): + self.body = body + + def index(self, req): + return self.body + +class StubExtensionManager(object): + + def __init__(self, resources): + self.resources = resources + + def get_resources(self): + return self.resources + +class WidgetExtensionResource(object): + + def __init__(self, name, collection, wsgi_app): + self.name = name + self.collection = collection + self.wsgi_app = wsgi_app + + def add_routes(self, mapper): + mapper.resource(self.name, self.collection, controller=self.wsgi_app) + +class ExtensionTest(unittest.TestCase): + + def test_no_extension_present(self): + manager = StubExtensionManager([]) + router = openstack.APIRouter(manager) + request = webob.Request.blank("/widgets") + response = request.get_response(router) + self.assertEqual(404, response.status_int) + + def test_get_resources(self): + response_body = "Buy more widgets!" + response = webob.Response() + response.body = response_body + resource1 = WidgetExtensionResource("widget", "widgets", response) + manager = StubExtensionManager([resource1]) + router = openstack.APIRouter(manager) + request = webob.Request.blank("/widgets") + response = request.get_response(router) + self.assertEqual(200, response.status_int) + self.assertEqual(response_body, response.body) + + def test_get_resources_with_controller(self): + response_body = "Buy more widgets!" + controller = StubController(response_body) + resource1 = WidgetExtensionResource("widget", "widgets", controller) + manager = StubExtensionManager([resource1]) + router = openstack.APIRouter(manager) + request = webob.Request.blank("/widgets") + response = request.get_response(router) + self.assertEqual(200, response.status_int) + self.assertEqual(response_body, response.body) + + -- cgit From 7d4eae131f2f844f368aa5ff79c68191756775b6 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Fri, 11 Mar 2011 14:33:12 -0500 Subject: Add config for osapi_extensions_path. Update the ExtensionManager so that it loads extensions in the osapi_extensions_path. --- nova/api/openstack/__init__.py | 9 ++++---- nova/api/openstack/extensions.py | 32 +++++++++++++++++++++++++- nova/flags.py | 2 ++ nova/tests/api/openstack/extensions/widgets.py | 27 ++++++++++++++++++++++ nova/tests/api/openstack/test_extensions.py | 19 +++++++++++++++ 5 files changed, 84 insertions(+), 5 deletions(-) create mode 100644 nova/tests/api/openstack/extensions/widgets.py (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 28e2a1691..8f6076511 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -69,7 +69,7 @@ class APIRouter(wsgi.Router): """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one""" return cls() - def __init__(self, ext_manager=None): + def __init__(self, ext_mgr=None): mapper = routes.Mapper() server_members = {'action': 'POST'} @@ -112,9 +112,10 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}, controller=shared_ip_groups.Controller()) - if ext_manager is None: - ext_manager = extensions.ExtensionManager() - for resource in ext_manager.get_resources(): + if ext_mgr is None: + ext_mgr = extensions.ExtensionManager(FLAGS.osapi_extensions_path) + for resource in ext_mgr.get_resources(): + print resource resource.add_routes(mapper) super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 1c539c500..24846d9cd 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -15,15 +15,45 @@ # License for the specific language governing permissions and limitations # under the License. +import imp +import os +import sys + + class ExtensionManager(object): + def __init__(self, path): + + self.path = path + self.extensions = [] + self._load_extensions() + def get_resources(self): """ returns a list of ExtensionResource objects """ - return [] + resources = [] + for ext in self.extensions: + resources.append(ext.get_resources()) + return resources + + def _load_extensions(self): + if not os.path.exists(self.path): + return + + for f in os.listdir(self.path): + mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) + ext_path = os.path.join(self.path, f) + if file_ext.lower() == '.py': + mod = imp.load_source(mod_name, ext_path) + self.extensions.append(getattr(mod, 'get_extension')()) + class ExtensionResource(object): + """ + Example ExtensionResource object. All ExtensionResource objects should + adhere to this interface. + """ def add_routes(self, mapper): pass diff --git a/nova/flags.py b/nova/flags.py index 9123e9ac7..63e4cb5b6 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -298,6 +298,8 @@ DEFINE_string('ec2_dmz_host', '$my_ip', 'internal ip of api server') DEFINE_integer('ec2_port', 8773, 'cloud controller port') DEFINE_string('ec2_scheme', 'http', 'prefix for ec2') DEFINE_string('ec2_path', '/services/Cloud', 'suffix for ec2') +DEFINE_string('osapi_extensions_path', '/var/lib/nova/extensions', + 'default directory for nova extensions') DEFINE_string('osapi_host', '$my_ip', 'ip of api server') DEFINE_string('osapi_scheme', 'http', 'prefix for openstack') DEFINE_integer('osapi_port', 8774, 'OpenStack API port') diff --git a/nova/tests/api/openstack/extensions/widgets.py b/nova/tests/api/openstack/extensions/widgets.py new file mode 100644 index 000000000..bc0947223 --- /dev/null +++ b/nova/tests/api/openstack/extensions/widgets.py @@ -0,0 +1,27 @@ +from nova import wsgi + +class WidgetController(wsgi.Controller): + + def index(self, req): + return "Buy more widgets!" + +class WidgetExtensionResource(object): + + def __init__(self): + pass + + def add_routes(self, mapper): + mapper.resource('widget', 'widgets', controller=WidgetController()) + + +class WidgetExtension(object): + + def __init__(self): + pass + + def get_resources(self): + return WidgetExtensionResource() + + +def get_extension(): + return WidgetExtension() diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index f5332c84a..ff41d6d99 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -16,12 +16,16 @@ # under the License. import unittest +import os.path import webob +from nova import flags from nova.api import openstack import nova.wsgi +FLAGS = flags.FLAGS + class StubController(nova.wsgi.Controller): def __init__(self, body): @@ -81,3 +85,18 @@ class ExtensionTest(unittest.TestCase): self.assertEqual(response_body, response.body) +class ExtensionManagerTest(unittest.TestCase): + + def setUp(self): + FLAGS.osapi_extensions_path = os.path.join(os.path.dirname(__file__), + "extensions") + + def test_get_resources(self): + router = openstack.APIRouter() + request = webob.Request.blank("/widgets") + response = request.get_response(router) + self.assertEqual(200, response.status_int) + self.assertEqual("Buy more widgets!", response.body) + + + -- cgit From 2bfa7b29c7882da559041cea771b9243555828fa Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Sun, 13 Mar 2011 13:51:42 -0400 Subject: The extension name is constructed from the camel cased module_name + 'Extension'. --- nova/api/openstack/extensions.py | 9 ++++++++- nova/tests/api/openstack/extensions/widgets.py | 14 +++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 24846d9cd..13789863b 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -38,6 +38,12 @@ class ExtensionManager(object): return resources def _load_extensions(self): + """ + Load extensions from the configured path. The extension name is + constructed from the camel cased module_name + 'Extension'. If your + extension module was named widgets.py the extension class within that + module should be 'WidgetsExtension'. + """ if not os.path.exists(self.path): return @@ -46,7 +52,8 @@ class ExtensionManager(object): ext_path = os.path.join(self.path, f) if file_ext.lower() == '.py': mod = imp.load_source(mod_name, ext_path) - self.extensions.append(getattr(mod, 'get_extension')()) + ext_name = mod_name[0].upper() + mod_name[1:] + 'Extension' + self.extensions.append(getattr(mod, ext_name)()) class ExtensionResource(object): diff --git a/nova/tests/api/openstack/extensions/widgets.py b/nova/tests/api/openstack/extensions/widgets.py index bc0947223..e03fc7776 100644 --- a/nova/tests/api/openstack/extensions/widgets.py +++ b/nova/tests/api/openstack/extensions/widgets.py @@ -1,27 +1,23 @@ from nova import wsgi -class WidgetController(wsgi.Controller): +class WidgetsController(wsgi.Controller): def index(self, req): return "Buy more widgets!" -class WidgetExtensionResource(object): +class WidgetsExtensionResource(object): def __init__(self): pass def add_routes(self, mapper): - mapper.resource('widget', 'widgets', controller=WidgetController()) + mapper.resource('widget', 'widgets', controller=WidgetsController()) -class WidgetExtension(object): +class WidgetsExtension(object): def __init__(self): pass def get_resources(self): - return WidgetExtensionResource() - - -def get_extension(): - return WidgetExtension() + return WidgetsExtensionResource() -- cgit From 229c5bc3324d5df39ca959d71a540a806bc5ad3e Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 14 Mar 2011 16:58:03 -0400 Subject: Implement action extensions. --- nova/api/openstack/__init__.py | 1 - nova/api/openstack/extensions.py | 106 +++++++++++++++++++++++++ nova/tests/api/openstack/extensions/widgets.py | 22 +++++ nova/tests/api/openstack/test_extensions.py | 44 +++++++++- 4 files changed, 170 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 9b7b76a91..8a458eea1 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -124,7 +124,6 @@ class APIRouter(wsgi.Router): if ext_mgr is None: ext_mgr = extensions.ExtensionManager(FLAGS.osapi_extensions_path) for resource in ext_mgr.get_resources(): - print resource resource.add_routes(mapper) super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 13789863b..e41de3120 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -18,11 +18,101 @@ import imp import os import sys +import routes +import webob.dec +import webob.exc + +from nova import flags +from nova import log as logging +from nova import wsgi + + +LOG = logging.getLogger('extensions') + + +FLAGS = flags.FLAGS + + +class ExtensionActionController(wsgi.Controller): + + def __init__(self, application, action_name, handler): + + self.application = application + self.action_name = action_name + self.handler = handler + + def action(self, req, id): + + input_dict = self._deserialize(req.body, req.get_content_type()) + if self.action_name in input_dict: + return self.handler(input_dict, req, id) + # no action handler found (bump to downstream application) + res = self.application + return res + + +class ExtensionMiddleware(wsgi.Middleware): + """ + Extensions middleware that intercepts configured routes for extensions. + """ + @classmethod + def factory(cls, global_config, **local_config): + """ paste factory """ + def _factory(app): + return cls(app, **local_config) + return _factory + + def __init__(self, application, ext_mgr=None): + mapper = routes.Mapper() + + if ext_mgr is None: + ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path) + + # create custom mapper connections for extended actions + for action in ext_mgr.get_actions(): + controller = ExtensionActionController(application, action.name, + action.handler) + 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'])) + + self._router = routes.middleware.RoutesMiddleware(self._dispatch, + mapper) + + super(ExtensionMiddleware, self).__init__(application) + + @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): + """ + Called by self._router after matching the incoming request to a route + and putting the information into req.environ. Either 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 ExtensionManager(object): def __init__(self, path): + LOG.audit(_('Initializing extension manager.')) self.path = path self.extensions = [] @@ -37,6 +127,12 @@ class ExtensionManager(object): resources.append(ext.get_resources()) return resources + def get_actions(self): + actions = [] + for ext in self.extensions: + actions.extend(ext.get_actions()) + return actions + def _load_extensions(self): """ Load extensions from the configured path. The extension name is @@ -48,6 +144,7 @@ class ExtensionManager(object): return for f in os.listdir(self.path): + LOG.audit(_('Loading extension file: %s'), f) mod_name, file_ext = os.path.splitext(os.path.split(f)[-1]) ext_path = os.path.join(self.path, f) if file_ext.lower() == '.py': @@ -56,6 +153,15 @@ class ExtensionManager(object): self.extensions.append(getattr(mod, ext_name)()) +class ExtensionAction(object): + + def __init__(self, member, collection, name, handler): + self.member = member + self.collection = collection + self.name = name + self.handler = handler + + class ExtensionResource(object): """ Example ExtensionResource object. All ExtensionResource objects should diff --git a/nova/tests/api/openstack/extensions/widgets.py b/nova/tests/api/openstack/extensions/widgets.py index e03fc7776..6839d2bb2 100644 --- a/nova/tests/api/openstack/extensions/widgets.py +++ b/nova/tests/api/openstack/extensions/widgets.py @@ -1,10 +1,14 @@ from nova import wsgi +from nova.api.openstack import extensions + + class WidgetsController(wsgi.Controller): def index(self, req): return "Buy more widgets!" + class WidgetsExtensionResource(object): def __init__(self): @@ -21,3 +25,21 @@ class WidgetsExtension(object): def get_resources(self): return WidgetsExtensionResource() + + def get_actions(self): + actions = [] + actions.append(extensions.ExtensionAction('server', 'servers', + 'add_widget', + self._add_widget)) + actions.append(extensions.ExtensionAction('server', 'servers', + 'delete_widget', + self._delete_widget)) + return actions + + def _add_widget(self, input_dict, req, id): + + return "Widget Added." + + def _delete_widget(self, input_dict, req, id): + + return "Widget Deleted." diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index ff41d6d99..f8d217e9c 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -15,10 +15,10 @@ # License for the specific language governing permissions and limitations # under the License. +import json import unittest -import os.path - import webob +import os.path from nova import flags from nova.api import openstack @@ -26,6 +26,7 @@ import nova.wsgi FLAGS = flags.FLAGS + class StubController(nova.wsgi.Controller): def __init__(self, body): @@ -34,6 +35,7 @@ class StubController(nova.wsgi.Controller): def index(self, req): return self.body + class StubExtensionManager(object): def __init__(self, resources): @@ -42,6 +44,7 @@ class StubExtensionManager(object): def get_resources(self): return self.resources + class WidgetExtensionResource(object): def __init__(self, name, collection, wsgi_app): @@ -52,6 +55,7 @@ class WidgetExtensionResource(object): def add_routes(self, mapper): mapper.resource(self.name, self.collection, controller=self.wsgi_app) + class ExtensionTest(unittest.TestCase): def test_no_extension_present(self): @@ -99,4 +103,40 @@ class ExtensionManagerTest(unittest.TestCase): self.assertEqual("Buy more widgets!", response.body) +class ExtendedActionTest(unittest.TestCase): + def setUp(self): + FLAGS.osapi_extensions_path = os.path.join(os.path.dirname(__file__), + "extensions") + + def test_extended_action(self): + app = openstack.APIRouter() + ext_midware = openstack.extensions.ExtensionMiddleware(app) + body = dict(add_widget=dict(name="test")) + request = webob.Request.blank("/servers/1/action") + request.method = 'POST' + request.content_type = 'application/json' + request.body = json.dumps(body) + response = request.get_response(ext_midware) + self.assertEqual(200, response.status_int) + self.assertEqual("Widget Added.", response.body) + + def test_invalid_action_body(self): + app = openstack.APIRouter() + ext_midware = openstack.extensions.ExtensionMiddleware(app) + body = dict(blah=dict(name="test")) # Doesn't exist + request = webob.Request.blank("/servers/1/action") + request.method = 'POST' + request.content_type = 'application/json' + request.body = json.dumps(body) + response = request.get_response(ext_midware) + self.assertEqual(501, response.status_int) + + def test_invalid_action(self): + app = openstack.APIRouter() + ext_midware = openstack.extensions.ExtensionMiddleware(app) + request = webob.Request.blank("/asdf/1/action") + request.method = 'POST' + request.content_type = 'application/json' + response = request.get_response(ext_midware) + self.assertEqual(404, response.status_int) -- cgit From f0141b1616e1b1fc9e52e33b37cc3a1091c57587 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 14 Mar 2011 22:24:34 -0400 Subject: Moved extended resource code into the extensions.py module. --- nova/api/openstack/__init__.py | 6 --- nova/api/openstack/extensions.py | 29 +++++++---- nova/tests/api/openstack/extensions/widgets.py | 15 ++---- nova/tests/api/openstack/test_extensions.py | 66 +++++++++++++------------- 4 files changed, 59 insertions(+), 57 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 8a458eea1..ff91c77cf 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -31,7 +31,6 @@ from nova.api.openstack import accounts from nova.api.openstack import faults from nova.api.openstack import backup_schedules from nova.api.openstack import consoles -from nova.api.openstack import extensions from nova.api.openstack import flavors from nova.api.openstack import images from nova.api.openstack import servers @@ -121,11 +120,6 @@ class APIRouter(wsgi.Router): collection={'detail': 'GET'}, controller=shared_ip_groups.Controller()) - if ext_mgr is None: - ext_mgr = extensions.ExtensionManager(FLAGS.osapi_extensions_path) - for resource in ext_mgr.get_resources(): - resource.add_routes(mapper) - super(APIRouter, self).__init__(mapper) diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index e41de3120..f32471051 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -68,7 +68,15 @@ class ExtensionMiddleware(wsgi.Middleware): if ext_mgr is None: ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path) - # create custom mapper connections for extended actions + # extended resources + for resource in ext_mgr.get_resources(): + mapper.resource(resource.member, resource.collection, + controller=resource.controller, + collection=resource.collection_actions, + member=resource.member_actions, + parent_resource=resource.parent) + + # extended actions for action in ext_mgr.get_actions(): controller = ExtensionActionController(application, action.name, action.handler) @@ -124,10 +132,13 @@ class ExtensionManager(object): """ resources = [] for ext in self.extensions: - resources.append(ext.get_resources()) + resources.extend(ext.get_resources()) return resources def get_actions(self): + """ + returns a list of ExtensionAction objects + """ actions = [] for ext in self.extensions: actions.extend(ext.get_actions()) @@ -163,10 +174,12 @@ class ExtensionAction(object): class ExtensionResource(object): - """ - Example ExtensionResource object. All ExtensionResource objects should - adhere to this interface. - """ - def add_routes(self, mapper): - pass + def __init__(self, member, collection, controller, + parent=None, collection_actions={}, member_actions={}): + self.member = member + self.collection = collection + self.controller = controller + self.parent = parent + self.collection_actions = collection_actions + self.member_actions = member_actions diff --git a/nova/tests/api/openstack/extensions/widgets.py b/nova/tests/api/openstack/extensions/widgets.py index 6839d2bb2..d5a2d95d9 100644 --- a/nova/tests/api/openstack/extensions/widgets.py +++ b/nova/tests/api/openstack/extensions/widgets.py @@ -9,22 +9,17 @@ class WidgetsController(wsgi.Controller): return "Buy more widgets!" -class WidgetsExtensionResource(object): - - def __init__(self): - pass - - def add_routes(self, mapper): - mapper.resource('widget', 'widgets', controller=WidgetsController()) - - class WidgetsExtension(object): def __init__(self): pass def get_resources(self): - return WidgetsExtensionResource() + resources = [] + widgets = extensions.ExtensionResource('widget', 'widgets', + WidgetsController()) + resources.append(widgets) + return resources def get_actions(self): actions = [] diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index f8d217e9c..080760c14 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -22,6 +22,7 @@ import os.path from nova import flags from nova.api import openstack +from nova.api.openstack import extensions import nova.wsgi FLAGS = flags.FLAGS @@ -38,53 +39,51 @@ class StubController(nova.wsgi.Controller): class StubExtensionManager(object): - def __init__(self, resources): - self.resources = resources + def __init__(self, resource): + self.resource = resource def get_resources(self): - return self.resources + resources = [] + if self.resource: + resources.append(self.resource) + return resources + def get_actions(self): + actions = [] + return actions -class WidgetExtensionResource(object): - def __init__(self, name, collection, wsgi_app): - self.name = name - self.collection = collection - self.wsgi_app = wsgi_app - - def add_routes(self, mapper): - mapper.resource(self.name, self.collection, controller=self.wsgi_app) - - -class ExtensionTest(unittest.TestCase): +class ExtensionResourceTest(unittest.TestCase): def test_no_extension_present(self): - manager = StubExtensionManager([]) - router = openstack.APIRouter(manager) + manager = StubExtensionManager(None) + app = openstack.APIRouter() + ext_midware = extensions.ExtensionMiddleware(app, manager) request = webob.Request.blank("/widgets") - response = request.get_response(router) + response = request.get_response(ext_midware) self.assertEqual(404, response.status_int) def test_get_resources(self): response_body = "Buy more widgets!" - response = webob.Response() - response.body = response_body - resource1 = WidgetExtensionResource("widget", "widgets", response) - manager = StubExtensionManager([resource1]) - router = openstack.APIRouter(manager) + widgets = extensions.ExtensionResource('widget', 'widgets', + StubController(response_body)) + manager = StubExtensionManager(widgets) + app = openstack.APIRouter() + ext_midware = extensions.ExtensionMiddleware(app, manager) request = webob.Request.blank("/widgets") - response = request.get_response(router) + response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) def test_get_resources_with_controller(self): response_body = "Buy more widgets!" - controller = StubController(response_body) - resource1 = WidgetExtensionResource("widget", "widgets", controller) - manager = StubExtensionManager([resource1]) - router = openstack.APIRouter(manager) + widgets = extensions.ExtensionResource('widget', 'widgets', + StubController(response_body)) + manager = StubExtensionManager(widgets) + app = openstack.APIRouter() + ext_midware = extensions.ExtensionMiddleware(app, manager) request = webob.Request.blank("/widgets") - response = request.get_response(router) + response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) @@ -96,9 +95,10 @@ class ExtensionManagerTest(unittest.TestCase): "extensions") def test_get_resources(self): - router = openstack.APIRouter() + app = openstack.APIRouter() + ext_midware = extensions.ExtensionMiddleware(app) request = webob.Request.blank("/widgets") - response = request.get_response(router) + response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual("Buy more widgets!", response.body) @@ -111,7 +111,7 @@ class ExtendedActionTest(unittest.TestCase): def test_extended_action(self): app = openstack.APIRouter() - ext_midware = openstack.extensions.ExtensionMiddleware(app) + ext_midware = extensions.ExtensionMiddleware(app) body = dict(add_widget=dict(name="test")) request = webob.Request.blank("/servers/1/action") request.method = 'POST' @@ -123,7 +123,7 @@ class ExtendedActionTest(unittest.TestCase): def test_invalid_action_body(self): app = openstack.APIRouter() - ext_midware = openstack.extensions.ExtensionMiddleware(app) + ext_midware = extensions.ExtensionMiddleware(app) body = dict(blah=dict(name="test")) # Doesn't exist request = webob.Request.blank("/servers/1/action") request.method = 'POST' @@ -134,7 +134,7 @@ class ExtendedActionTest(unittest.TestCase): def test_invalid_action(self): app = openstack.APIRouter() - ext_midware = openstack.extensions.ExtensionMiddleware(app) + ext_midware = extensions.ExtensionMiddleware(app) request = webob.Request.blank("/asdf/1/action") request.method = 'POST' request.content_type = 'application/json' -- cgit From 0053b776276e9cac617c812931c248be7e49fea2 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Tue, 15 Mar 2011 23:00:09 -0400 Subject: Add ResponseExtensions. --- nova/api/openstack/extensions.py | 144 ++++++++++++++++++++----- nova/tests/api/openstack/extensions/widgets.py | 26 +++-- nova/tests/api/openstack/test_extensions.py | 103 +++++++++++++----- 3 files changed, 213 insertions(+), 60 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index f32471051..8b8806c8a 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -33,24 +33,43 @@ LOG = logging.getLogger('extensions') FLAGS = flags.FLAGS -class ExtensionActionController(wsgi.Controller): +class ActionExtensionController(wsgi.Controller): - def __init__(self, application, action_name, handler): + def __init__(self, application): self.application = application - self.action_name = action_name - self.handler = handler + self.action_handlers = {} + + def add_action(self, action_name, handler): + self.action_handlers[action_name] = handler def action(self, req, id): input_dict = self._deserialize(req.body, req.get_content_type()) - if self.action_name in input_dict: - return self.handler(input_dict, req, id) + for action_name, handler in self.action_handlers.iteritems(): + if action_name in input_dict: + return handler(input_dict, req, id) # no action handler found (bump to downstream application) res = self.application return res +class ResponseExtensionController(wsgi.Controller): + + def __init__(self, application): + self.application = application + self.handlers = [] + + def add_handler(self, handler): + self.handlers.append(handler) + + def process(self, req, *args, **kwargs): + res = req.get_response(self.application) + # currently response handlers are un-ordered + for handler in self.handlers: + return handler(res) + + class ExtensionMiddleware(wsgi.Middleware): """ Extensions middleware that intercepts configured routes for extensions. @@ -62,33 +81,80 @@ class ExtensionMiddleware(wsgi.Middleware): return cls(app, **local_config) return _factory + def _actions_by_collection(self, application, ext_mgr): + """ + Return a dict of ActionExtensionController objects by collection + """ + action_controllers = {} + for action in ext_mgr.get_actions(): + if not action.collection in action_controllers.keys(): + controller = ActionExtensionController(application) + action_controllers[action.collection] = controller + return action_controllers + + def _responses_by_collection(self, application, ext_mgr): + """ + Return a dict of ResponseExtensionController objects by collection + """ + response_ext_controllers = {} + for resp_ext in ext_mgr.get_response_extensions(): + if not resp_ext.url_route in response_ext_controllers.keys(): + controller = ResponseExtensionController(application) + response_ext_controllers[resp_ext.url_route] = controller + return response_ext_controllers + def __init__(self, application, ext_mgr=None): - mapper = routes.Mapper() if ext_mgr is None: ext_mgr = ExtensionManager(FLAGS.osapi_extensions_path) + self.ext_mgr = ext_mgr + + mapper = routes.Mapper() # extended resources for resource in ext_mgr.get_resources(): - mapper.resource(resource.member, resource.collection, + 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._actions_by_collection(application, ext_mgr) for action in ext_mgr.get_actions(): - controller = ExtensionActionController(application, action.name, - action.handler) - mapper.connect("/%s/{id}/action.:(format)" % action.collection, + LOG.debug(_('Extended collection/action: %s/%s'), + action.collection, + action.action_name) + controller = action_controllers[action.collection] + controller.add_action(action.action_name, action.handler) + + mapper.connect("/%s/:(id)/action.:(format)" % action.collection, action='action', controller=controller, conditions=dict(method=['POST'])) - mapper.connect("/%s/{id}/action" % action.collection, + mapper.connect("/%s/:(id)/action" % action.collection, action='action', controller=controller, conditions=dict(method=['POST'])) + # extended responses + resp_controllers = self._responses_by_collection(application, ext_mgr) + for response_ext in ext_mgr.get_response_extensions(): + LOG.debug(_('Extended response: %s'), response_ext.url_route) + controller = resp_controllers[response_ext.url_route] + controller.add_handler(response_ext.handler) + mapper.connect(response_ext.url_route + '.:(format)', + action='process', + controller=controller, + conditions=response_ext.conditions) + + mapper.connect(response_ext.url_route, + action='process', + controller=controller, + conditions=response_ext.conditions) + self._router = routes.middleware.RoutesMiddleware(self._dispatch, mapper) @@ -106,9 +172,8 @@ class ExtensionMiddleware(wsgi.Middleware): @webob.dec.wsgify(RequestClass=wsgi.Request) def _dispatch(req): """ - Called by self._router after matching the incoming request to a route - and putting the information into req.environ. Either returns the - routed WSGI app's response or defers to the extended application. + Returns the routed WSGI app's response or defers to the extended + application. """ match = req.environ['wsgiorg.routing_args'][1] if not match: @@ -128,7 +193,7 @@ class ExtensionManager(object): def get_resources(self): """ - returns a list of ExtensionResource objects + returns a list of ResourceExtension objects """ resources = [] for ext in self.extensions: @@ -137,13 +202,22 @@ class ExtensionManager(object): def get_actions(self): """ - returns a list of ExtensionAction objects + returns a list of ActionExtension objects """ actions = [] for ext in self.extensions: actions.extend(ext.get_actions()) return actions + def get_response_extensions(self): + """ + returns a list of ResponseExtension objects + """ + response_exts = [] + for ext in self.extensions: + response_exts.extend(ext.get_response_extensions()) + return response_exts + def _load_extensions(self): """ Load extensions from the configured path. The extension name is @@ -160,24 +234,42 @@ class ExtensionManager(object): ext_path = os.path.join(self.path, f) if file_ext.lower() == '.py': mod = imp.load_source(mod_name, ext_path) - ext_name = mod_name[0].upper() + mod_name[1:] + 'Extension' + ext_name = mod_name[0].upper() + mod_name[1:] self.extensions.append(getattr(mod, ext_name)()) -class ExtensionAction(object): +class ResponseExtension(object): + """ + ResponseExtension objects can be used to add data to responses from + core nova OpenStack API controllers. + """ - def __init__(self, member, collection, name, handler): - self.member = member + def __init__(self, url_route, method, handler): + self.url_route = url_route + self.conditions = dict(method=[method]) + self.handler = handler + + +class ActionExtension(object): + """ + ActionExtension objects can be used to add custom actions to core nova + nova OpenStack API controllers. + """ + + def __init__(self, collection, action_name, handler): self.collection = collection - self.name = name + self.action_name = action_name self.handler = handler -class ExtensionResource(object): +class ResourceExtension(object): + """ + ResourceExtension objects can be used to add add top level resources + to the OpenStack API in nova. + """ - def __init__(self, member, collection, controller, - parent=None, collection_actions={}, member_actions={}): - self.member = member + def __init__(self, collection, controller, parent=None, + collection_actions={}, member_actions={}): self.collection = collection self.controller = controller self.parent = parent diff --git a/nova/tests/api/openstack/extensions/widgets.py b/nova/tests/api/openstack/extensions/widgets.py index d5a2d95d9..f463721f1 100644 --- a/nova/tests/api/openstack/extensions/widgets.py +++ b/nova/tests/api/openstack/extensions/widgets.py @@ -1,3 +1,5 @@ +import json + from nova import wsgi from nova.api.openstack import extensions @@ -9,28 +11,40 @@ class WidgetsController(wsgi.Controller): return "Buy more widgets!" -class WidgetsExtension(object): +class Widgets(object): def __init__(self): pass def get_resources(self): resources = [] - widgets = extensions.ExtensionResource('widget', 'widgets', + widgets = extensions.ResourceExtension('widgets', WidgetsController()) resources.append(widgets) return resources def get_actions(self): actions = [] - actions.append(extensions.ExtensionAction('server', 'servers', - 'add_widget', + actions.append(extensions.ActionExtension('servers', 'add_widget', self._add_widget)) - actions.append(extensions.ExtensionAction('server', 'servers', - 'delete_widget', + actions.append(extensions.ActionExtension('servers', 'delete_widget', self._delete_widget)) return actions + def get_response_extensions(self): + response_exts = [] + + def _resp_handler(res): + # only handle JSON responses + data = json.loads(res.body) + data['flavor']['widgets'] = "Buy more widgets!" + return data + + widgets = extensions.ResponseExtension('/v1.0/flavors/:(id)', 'GET', + _resp_handler) + response_exts.append(widgets) + return response_exts + def _add_widget(self, input_dict, req, id): return "Widget Added." diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 080760c14..8725c8f0e 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -16,13 +16,17 @@ # under the License. import json +import stubout import unittest import webob import os.path +from nova import context from nova import flags from nova.api import openstack from nova.api.openstack import extensions +from nova.api.openstack import flavors +from nova.tests.api.openstack import fakes import nova.wsgi FLAGS = flags.FLAGS @@ -39,21 +43,31 @@ class StubController(nova.wsgi.Controller): class StubExtensionManager(object): - def __init__(self, resource): - self.resource = resource + def __init__(self, resource_ext=None, action_ext=None, response_ext=None): + self.resource_ext = resource_ext + self.action_ext = action_ext + self.response_ext = response_ext def get_resources(self): - resources = [] - if self.resource: - resources.append(self.resource) - return resources + resource_exts = [] + if self.resource_ext: + resource_exts.append(self.resource_ext) + return resource_exts def get_actions(self): - actions = [] - return actions + action_exts = [] + if self.action_ext: + action_exts.append(self.action_ext) + return action_exts + def get_response_extensions(self): + response_exts = [] + if self.response_ext: + response_exts.append(self.response_ext) + return response_exts -class ExtensionResourceTest(unittest.TestCase): + +class ResourceExtensionTest(unittest.TestCase): def test_no_extension_present(self): manager = StubExtensionManager(None) @@ -65,7 +79,7 @@ class ExtensionResourceTest(unittest.TestCase): def test_get_resources(self): response_body = "Buy more widgets!" - widgets = extensions.ExtensionResource('widget', 'widgets', + widgets = extensions.ResourceExtension('widgets', StubController(response_body)) manager = StubExtensionManager(widgets) app = openstack.APIRouter() @@ -77,7 +91,7 @@ class ExtensionResourceTest(unittest.TestCase): def test_get_resources_with_controller(self): response_body = "Buy more widgets!" - widgets = extensions.ExtensionResource('widget', 'widgets', + widgets = extensions.ResourceExtension('widgets', StubController(response_body)) manager = StubExtensionManager(widgets) app = openstack.APIRouter() @@ -103,40 +117,73 @@ class ExtensionManagerTest(unittest.TestCase): self.assertEqual("Buy more widgets!", response.body) -class ExtendedActionTest(unittest.TestCase): +class ActionExtensionTest(unittest.TestCase): def setUp(self): FLAGS.osapi_extensions_path = os.path.join(os.path.dirname(__file__), "extensions") - def test_extended_action(self): + def _send_server_action_request(self, url, body): app = openstack.APIRouter() ext_midware = extensions.ExtensionMiddleware(app) - body = dict(add_widget=dict(name="test")) - request = webob.Request.blank("/servers/1/action") + request = webob.Request.blank(url) request.method = 'POST' request.content_type = 'application/json' request.body = json.dumps(body) response = request.get_response(ext_midware) + return response + + def test_extended_action(self): + body = dict(add_widget=dict(name="test")) + response = self._send_server_action_request("/servers/1/action", body) self.assertEqual(200, response.status_int) self.assertEqual("Widget Added.", response.body) + body = dict(delete_widget=dict(name="test")) + response = self._send_server_action_request("/servers/1/action", body) + self.assertEqual(200, response.status_int) + self.assertEqual("Widget Deleted.", response.body) + def test_invalid_action_body(self): - app = openstack.APIRouter() - ext_midware = extensions.ExtensionMiddleware(app) body = dict(blah=dict(name="test")) # Doesn't exist - request = webob.Request.blank("/servers/1/action") - request.method = 'POST' - request.content_type = 'application/json' - request.body = json.dumps(body) - response = request.get_response(ext_midware) + response = self._send_server_action_request("/servers/1/action", body) self.assertEqual(501, response.status_int) def test_invalid_action(self): - app = openstack.APIRouter() - ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/asdf/1/action") - request.method = 'POST' - request.content_type = 'application/json' - response = request.get_response(ext_midware) + body = dict(blah=dict(name="test")) + response = self._send_server_action_request("/asdf/1/action", body) self.assertEqual(404, response.status_int) + + +class ResponseExtensionTest(unittest.TestCase): + + def setUp(self): + super(ResponseExtensionTest, self).setUp() + self.stubs = stubout.StubOutForTesting() + fakes.FakeAuthManager.reset_fake_data() + fakes.FakeAuthDatabase.data = {} + fakes.stub_out_networking(self.stubs) + fakes.stub_out_rate_limiting(self.stubs) + fakes.stub_out_auth(self.stubs) + self.context = context.get_admin_context() + + def test_get_resources(self): + + test_resp = "Buy more widgets!" + + def _resp_handler(res): + # only handle JSON responses + data = json.loads(res.body) + data['flavor']['widgets'] = test_resp + return data + + widgets = extensions.ResponseExtension('/v1.0/flavors/:(id)', 'GET', + _resp_handler) + manager = StubExtensionManager(None, None, widgets) + app = fakes.wsgi_app() + ext_midware = extensions.ExtensionMiddleware(app, manager) + request = webob.Request.blank("/v1.0/flavors/1") + response = request.get_response(ext_midware) + self.assertEqual(200, response.status_int) + response_data = json.loads(response.body) + self.assertEqual(test_resp, response_data['flavor']['widgets']) -- cgit From d714df5ed4a6a1d4f1c0f7680c2fbb6a6abb81a5 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 16 Mar 2011 11:02:22 -0400 Subject: Implement top level extensions. --- nova/api/openstack/extensions.py | 94 +++++++++++++++++++++-- nova/tests/api/openstack/extensions/foxinsocks.py | 70 +++++++++++++++++ nova/tests/api/openstack/extensions/widgets.py | 54 ------------- nova/tests/api/openstack/test_extensions.py | 68 +++++++++++----- 4 files changed, 204 insertions(+), 82 deletions(-) create mode 100644 nova/tests/api/openstack/extensions/foxinsocks.py delete mode 100644 nova/tests/api/openstack/extensions/widgets.py (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 8b8806c8a..66ddd8078 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -25,6 +25,7 @@ import webob.exc from nova import flags from nova import log as logging from nova import wsgi +from nova.api.openstack import faults LOG = logging.getLogger('extensions') @@ -70,6 +71,42 @@ class ResponseExtensionController(wsgi.Controller): return handler(res) +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: implement extension links + return ext_data + + def index(self, req): + extensions = [] + for alias, ext in self.extension_manager.extensions.iteritems(): + extensions.append(self._translate(ext)) + return dict(extensions=extensions) + + def show(self, req, id): + # NOTE: the extensions alias is used as the 'id' for show + ext = self.extension_manager.extensions[id] + return self._translate(ext) + + def delete(self, req, id): + raise faults.Fault(exc.HTTPNotFound()) + + def create(self, req): + raise faults.Fault(exc.HTTPNotFound()) + + def delete(self, req, id): + raise faults.Fault(exc.HTTPNotFound()) + + class ExtensionMiddleware(wsgi.Middleware): """ Extensions middleware that intercepts configured routes for extensions. @@ -183,12 +220,17 @@ class ExtensionMiddleware(wsgi.Middleware): class ExtensionManager(object): + """ + Load extensions from the configured extension path. + See nova/tests/api/openstack/extensions/foxinsocks.py for an example + extension implementation. + """ def __init__(self, path): LOG.audit(_('Initializing extension manager.')) self.path = path - self.extensions = [] + self.extensions = {} self._load_extensions() def get_resources(self): @@ -196,8 +238,14 @@ class ExtensionManager(object): returns a list of ResourceExtension objects """ resources = [] - for ext in self.extensions: - resources.extend(ext.get_resources()) + resources.append(ResourceExtension('extensions', + ExtensionController(self))) + for alias, ext in self.extensions.iteritems(): + try: + resources.extend(ext.get_resources()) + except AttributeError: + # NOTE: Extension aren't required to have resource extensions + pass return resources def get_actions(self): @@ -205,8 +253,12 @@ class ExtensionManager(object): returns a list of ActionExtension objects """ actions = [] - for ext in self.extensions: - actions.extend(ext.get_actions()) + for alias, ext in self.extensions.iteritems(): + try: + actions.extend(ext.get_actions()) + except AttributeError: + # NOTE: Extension aren't required to have action extensions + pass return actions def get_response_extensions(self): @@ -214,16 +266,36 @@ class ExtensionManager(object): returns a list of ResponseExtension objects """ response_exts = [] - for ext in self.extensions: - response_exts.extend(ext.get_response_extensions()) + for alias, ext in self.extensions.iteritems(): + try: + response_exts.extend(ext.get_response_extensions()) + except AttributeError: + # NOTE: Extension aren't required to have response extensions + pass return response_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)) + def _load_extensions(self): """ Load extensions from the configured path. The extension name is constructed from the camel cased module_name + 'Extension'. If your extension module was named widgets.py the extension class within that module should be 'WidgetsExtension'. + + See nova/tests/api/openstack/extensions/foxinsocks.py for an example + extension implementation. """ if not os.path.exists(self.path): return @@ -235,7 +307,13 @@ class ExtensionManager(object): if file_ext.lower() == '.py': mod = imp.load_source(mod_name, ext_path) ext_name = mod_name[0].upper() + mod_name[1:] - self.extensions.append(getattr(mod, ext_name)()) + try: + new_ext = getattr(mod, ext_name)() + self._check_extension(new_ext) + self.extensions[new_ext.get_alias()] = new_ext + except AttributeError as ex: + LOG.exception(_("Exception loading extension: %s"), + unicode(ex)) class ResponseExtension(object): diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py new file mode 100644 index 000000000..09a328273 --- /dev/null +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -0,0 +1,70 @@ +import json + +from nova import wsgi + +from nova.api.openstack import extensions + + +class FoxInSocksController(wsgi.Controller): + + def index(self, req): + return "Try to say this Mr. Knox, sir..." + + +class Foxinsocks(object): + + def __init__(self): + pass + + def get_name(self): + return "Fox In Socks" + + def get_alias(self): + return "FOXNSOX" + + def get_description(self): + return "The Fox In Socks Extension" + + def get_namespace(self): + return "http://www.fox.in.socks/api/ext/pie/v1.0" + + def get_updated(self): + return "2011-01-22T13:25:27-06:00" + + def get_resources(self): + resources = [] + resource = extensions.ResourceExtension('foxnsocks', + FoxInSocksController()) + resources.append(resource) + return resources + + def get_actions(self): + actions = [] + actions.append(extensions.ActionExtension('servers', 'add_tweedle', + self._add_tweedle)) + actions.append(extensions.ActionExtension('servers', 'delete_tweedle', + self._delete_tweedle)) + return actions + + def get_response_extensions(self): + response_exts = [] + + def _resp_handler(res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['flavor']['googoose'] = "Gooey goo for chewy chewing!" + return data + + resp_ext = extensions.ResponseExtension('/v1.0/flavors/:(id)', 'GET', + _resp_handler) + response_exts.append(resp_ext) + return response_exts + + def _add_tweedle(self, input_dict, req, id): + + return "Tweedle Beetle Added." + + def _delete_tweedle(self, input_dict, req, id): + + return "Tweedle Beetle Deleted." diff --git a/nova/tests/api/openstack/extensions/widgets.py b/nova/tests/api/openstack/extensions/widgets.py deleted file mode 100644 index f463721f1..000000000 --- a/nova/tests/api/openstack/extensions/widgets.py +++ /dev/null @@ -1,54 +0,0 @@ -import json - -from nova import wsgi - -from nova.api.openstack import extensions - - -class WidgetsController(wsgi.Controller): - - def index(self, req): - return "Buy more widgets!" - - -class Widgets(object): - - def __init__(self): - pass - - def get_resources(self): - resources = [] - widgets = extensions.ResourceExtension('widgets', - WidgetsController()) - resources.append(widgets) - return resources - - def get_actions(self): - actions = [] - actions.append(extensions.ActionExtension('servers', 'add_widget', - self._add_widget)) - actions.append(extensions.ActionExtension('servers', 'delete_widget', - self._delete_widget)) - return actions - - def get_response_extensions(self): - response_exts = [] - - def _resp_handler(res): - # only handle JSON responses - data = json.loads(res.body) - data['flavor']['widgets'] = "Buy more widgets!" - return data - - widgets = extensions.ResponseExtension('/v1.0/flavors/:(id)', 'GET', - _resp_handler) - response_exts.append(widgets) - return response_exts - - def _add_widget(self, input_dict, req, id): - - return "Widget Added." - - def _delete_widget(self, input_dict, req, id): - - return "Widget Deleted." diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 8725c8f0e..0f99dec55 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -48,6 +48,15 @@ class StubExtensionManager(object): self.action_ext = action_ext self.response_ext = response_ext + def get_name(self): + return "Tweedle Beetle Extension" + + def get_alias(self): + return "TWDLBETL" + + def get_description(self): + return "Provides access to Tweedle Beetles" + def get_resources(self): resource_exts = [] if self.resource_ext: @@ -67,36 +76,53 @@ class StubExtensionManager(object): return response_exts +class ExtensionControllerTest(unittest.TestCase): + + def test_index(self): + app = openstack.APIRouter() + ext_midware = extensions.ExtensionMiddleware(app) + request = webob.Request.blank("/extensions") + response = request.get_response(ext_midware) + self.assertEqual(200, response.status_int) + + def test_get_by_alias(self): + app = openstack.APIRouter() + ext_midware = extensions.ExtensionMiddleware(app) + request = webob.Request.blank("/extensions/FOXNSOX") + response = request.get_response(ext_midware) + self.assertEqual(200, response.status_int) + +response_body = "Try to say this Mr. Knox, sir..." + class ResourceExtensionTest(unittest.TestCase): + def test_no_extension_present(self): manager = StubExtensionManager(None) app = openstack.APIRouter() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/widgets") + request = webob.Request.blank("/blah") response = request.get_response(ext_midware) self.assertEqual(404, response.status_int) def test_get_resources(self): - response_body = "Buy more widgets!" - widgets = extensions.ResourceExtension('widgets', + res_ext = extensions.ResourceExtension('tweedles', StubController(response_body)) - manager = StubExtensionManager(widgets) + manager = StubExtensionManager(res_ext) app = openstack.APIRouter() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/widgets") + request = webob.Request.blank("/tweedles") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) def test_get_resources_with_controller(self): - response_body = "Buy more widgets!" - widgets = extensions.ResourceExtension('widgets', + res_ext = extensions.ResourceExtension('tweedles', StubController(response_body)) - manager = StubExtensionManager(widgets) + manager = StubExtensionManager(res_ext) app = openstack.APIRouter() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/widgets") + request = webob.Request.blank("/tweedles") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) self.assertEqual(response_body, response.body) @@ -104,6 +130,8 @@ class ResourceExtensionTest(unittest.TestCase): class ExtensionManagerTest(unittest.TestCase): + response_body = "Try to say this Mr. Knox, sir..." + def setUp(self): FLAGS.osapi_extensions_path = os.path.join(os.path.dirname(__file__), "extensions") @@ -111,10 +139,10 @@ class ExtensionManagerTest(unittest.TestCase): def test_get_resources(self): app = openstack.APIRouter() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/widgets") + request = webob.Request.blank("/foxnsocks") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) - self.assertEqual("Buy more widgets!", response.body) + self.assertEqual(response_body, response.body) class ActionExtensionTest(unittest.TestCase): @@ -134,15 +162,15 @@ class ActionExtensionTest(unittest.TestCase): return response def test_extended_action(self): - body = dict(add_widget=dict(name="test")) + body = dict(add_tweedle=dict(name="test")) response = self._send_server_action_request("/servers/1/action", body) self.assertEqual(200, response.status_int) - self.assertEqual("Widget Added.", response.body) + self.assertEqual("Tweedle Beetle Added.", response.body) - body = dict(delete_widget=dict(name="test")) + body = dict(delete_tweedle=dict(name="test")) response = self._send_server_action_request("/servers/1/action", body) self.assertEqual(200, response.status_int) - self.assertEqual("Widget Deleted.", response.body) + self.assertEqual("Tweedle Beetle Deleted.", response.body) def test_invalid_action_body(self): body = dict(blah=dict(name="test")) # Doesn't exist @@ -169,21 +197,21 @@ class ResponseExtensionTest(unittest.TestCase): def test_get_resources(self): - test_resp = "Buy more widgets!" + test_resp = "Gooey goo for chewy chewing!" def _resp_handler(res): # only handle JSON responses data = json.loads(res.body) - data['flavor']['widgets'] = test_resp + data['flavor']['googoose'] = test_resp return data - widgets = extensions.ResponseExtension('/v1.0/flavors/:(id)', 'GET', + resp_ext = extensions.ResponseExtension('/v1.0/flavors/:(id)', 'GET', _resp_handler) - manager = StubExtensionManager(None, None, widgets) + manager = StubExtensionManager(None, None, resp_ext) app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app, manager) request = webob.Request.blank("/v1.0/flavors/1") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) response_data = json.loads(response.body) - self.assertEqual(test_resp, response_data['flavor']['widgets']) + self.assertEqual(test_resp, response_data['flavor']['googoose']) -- cgit From b2456e983178b97ad94f48c77ef210000d6d6ca4 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 16 Mar 2011 14:03:38 -0400 Subject: Move mapper code into the _action_ext_controllers and _response_ext_controllers methods. --- nova/api/openstack/extensions.py | 65 ++++++++++++----------- nova/tests/api/openstack/extensions/foxinsocks.py | 2 +- nova/tests/api/openstack/test_extensions.py | 7 +-- 3 files changed, 39 insertions(+), 35 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 66ddd8078..4adfcfc5b 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -118,7 +118,7 @@ class ExtensionMiddleware(wsgi.Middleware): return cls(app, **local_config) return _factory - def _actions_by_collection(self, application, ext_mgr): + def _action_ext_controllers(self, application, ext_mgr, mapper): """ Return a dict of ActionExtensionController objects by collection """ @@ -126,18 +126,38 @@ class ExtensionMiddleware(wsgi.Middleware): 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 _responses_by_collection(self, application, ext_mgr): + def _response_ext_controllers(self, application, ext_mgr, mapper): """ Return a dict of ResponseExtensionController objects by collection """ response_ext_controllers = {} for resp_ext in ext_mgr.get_response_extensions(): - if not resp_ext.url_route in response_ext_controllers.keys(): + if not resp_ext.key in response_ext_controllers.keys(): controller = ResponseExtensionController(application) - response_ext_controllers[resp_ext.url_route] = controller + mapper.connect(resp_ext.url_route + '.:(format)', + action='process', + controller=controller, + conditions=resp_ext.conditions) + + mapper.connect(resp_ext.url_route, + action='process', + controller=controller, + conditions=resp_ext.conditions) + response_ext_controllers[resp_ext.key] = controller + return response_ext_controllers def __init__(self, application, ext_mgr=None): @@ -159,38 +179,20 @@ class ExtensionMiddleware(wsgi.Middleware): parent_resource=resource.parent) # extended actions - action_controllers = self._actions_by_collection(application, ext_mgr) + action_controllers = self._action_ext_controllers(application, ext_mgr, + mapper) for action in ext_mgr.get_actions(): - LOG.debug(_('Extended collection/action: %s/%s'), - action.collection, - action.action_name) + LOG.debug(_('Extended action: %s'), action.action_name) controller = action_controllers[action.collection] controller.add_action(action.action_name, action.handler) - 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'])) - # extended responses - resp_controllers = self._responses_by_collection(application, ext_mgr) + resp_controllers = self._response_ext_controllers(application, ext_mgr, + mapper) for response_ext in ext_mgr.get_response_extensions(): - LOG.debug(_('Extended response: %s'), response_ext.url_route) - controller = resp_controllers[response_ext.url_route] + LOG.debug(_('Extended response: %s'), response_ext.key) + controller = resp_controllers[response_ext.key] controller.add_handler(response_ext.handler) - mapper.connect(response_ext.url_route + '.:(format)', - action='process', - controller=controller, - conditions=response_ext.conditions) - - mapper.connect(response_ext.url_route, - action='process', - controller=controller, - conditions=response_ext.conditions) self._router = routes.middleware.RoutesMiddleware(self._dispatch, mapper) @@ -322,10 +324,11 @@ class ResponseExtension(object): core nova OpenStack API controllers. """ - def __init__(self, url_route, method, handler): + def __init__(self, method, url_route, handler): self.url_route = url_route - self.conditions = dict(method=[method]) self.handler = handler + self.conditions = dict(method=[method]) + self.key = "%s-%s" % (method, url_route) class ActionExtension(object): diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 09a328273..2e93d8a55 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -56,7 +56,7 @@ class Foxinsocks(object): data['flavor']['googoose'] = "Gooey goo for chewy chewing!" return data - resp_ext = extensions.ResponseExtension('/v1.0/flavors/:(id)', 'GET', + resp_ext = extensions.ResponseExtension('GET', '/flavors/:(id)', _resp_handler) response_exts.append(resp_ext) return response_exts diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 0f99dec55..149f1973e 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -31,6 +31,8 @@ import nova.wsgi FLAGS = flags.FLAGS +response_body = "Try to say this Mr. Knox, sir..." + class StubController(nova.wsgi.Controller): @@ -92,11 +94,9 @@ class ExtensionControllerTest(unittest.TestCase): response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) -response_body = "Try to say this Mr. Knox, sir..." class ResourceExtensionTest(unittest.TestCase): - def test_no_extension_present(self): manager = StubExtensionManager(None) app = openstack.APIRouter() @@ -205,7 +205,8 @@ class ResponseExtensionTest(unittest.TestCase): data['flavor']['googoose'] = test_resp return data - resp_ext = extensions.ResponseExtension('/v1.0/flavors/:(id)', 'GET', + resp_ext = extensions.ResponseExtension('GET', + '/v1.0/flavors/:(id)', _resp_handler) manager = StubExtensionManager(None, None, resp_ext) app = fakes.wsgi_app() -- cgit From f91cd67c403d7de54600eea0d91c223af0493788 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Wed, 16 Mar 2011 14:09:29 -0400 Subject: Comment update. --- nova/api/openstack/extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 4adfcfc5b..557b12fd9 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -345,7 +345,7 @@ class ActionExtension(object): class ResourceExtension(object): """ - ResourceExtension objects can be used to add add top level resources + ResourceExtension objects can be used to add top level resources to the OpenStack API in nova. """ -- cgit From 41c097000c1eeb4f1532b22f136c383b8174e6cc Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 17 Mar 2011 11:35:32 -0400 Subject: Add tests and code to handle multiple ResponseExtension objects. --- nova/api/openstack/extensions.py | 13 ++++++++++++- nova/tests/api/openstack/extensions/foxinsocks.py | 17 ++++++++++++++--- nova/tests/api/openstack/test_extensions.py | 17 ++++++++++++++++- 3 files changed, 42 insertions(+), 5 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 557b12fd9..23181d2a6 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -66,9 +66,20 @@ class ResponseExtensionController(wsgi.Controller): def process(self, req, *args, **kwargs): res = req.get_response(self.application) + content_type = req.best_match_content_type() # currently response handlers are un-ordered for handler in self.handlers: - return handler(res) + res=handler(res) + try: + body = res.body + headers = res.headers + except AttributeError: + body = self._serialize(res, content_type) + headers={"Content-Type": content_type} + res = webob.Response() + res.body = body + res.headers = headers + return res class ExtensionController(wsgi.Controller): diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 2e93d8a55..fa979c7b5 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -49,16 +49,27 @@ class Foxinsocks(object): def get_response_extensions(self): response_exts = [] - def _resp_handler(res): + def _goose_handler(res): #NOTE: This only handles JSON responses. # You can use content type header to test for XML. data = json.loads(res.body) data['flavor']['googoose'] = "Gooey goo for chewy chewing!" return data - resp_ext = extensions.ResponseExtension('GET', '/flavors/:(id)', - _resp_handler) + resp_ext = extensions.ResponseExtension('GET', '/v1.0/flavors/:(id)', + _goose_handler) response_exts.append(resp_ext) + + def _bands_handler(res): + #NOTE: This only handles JSON responses. + # You can use content type header to test for XML. + data = json.loads(res.body) + data['big_bands'] = 'Pig Bands!' + return data + + resp_ext2 = extensions.ResponseExtension('GET', '/v1.0/flavors/:(id)', + _bands_handler) + response_exts.append(resp_ext2) return response_exts def _add_tweedle(self, input_dict, req, id): diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 149f1973e..11ed61e0d 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -195,7 +195,7 @@ class ResponseExtensionTest(unittest.TestCase): fakes.stub_out_auth(self.stubs) self.context = context.get_admin_context() - def test_get_resources(self): + def test_get_resources_with_stub_mgr(self): test_resp = "Gooey goo for chewy chewing!" @@ -208,6 +208,7 @@ class ResponseExtensionTest(unittest.TestCase): resp_ext = extensions.ResponseExtension('GET', '/v1.0/flavors/:(id)', _resp_handler) + manager = StubExtensionManager(None, None, resp_ext) app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app, manager) @@ -216,3 +217,17 @@ class ResponseExtensionTest(unittest.TestCase): self.assertEqual(200, response.status_int) response_data = json.loads(response.body) self.assertEqual(test_resp, response_data['flavor']['googoose']) + + def test_get_resources_with_mgr(self): + + test_resp = "Gooey goo for chewy chewing!" + + app = fakes.wsgi_app() + ext_midware = extensions.ExtensionMiddleware(app) + request = webob.Request.blank("/v1.0/flavors/1") + response = request.get_response(ext_midware) + self.assertEqual(200, response.status_int) + response_data = json.loads(response.body) + print response_data + self.assertEqual(test_resp, response_data['flavor']['googoose']) + self.assertEqual("Pig Bands!", response_data['big_bands']) -- cgit From 012c94e5304b9e00477409b92bf73f4316b19260 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 21 Mar 2011 13:56:55 -0400 Subject: Fix limit unit tests (reconciles w/ trunk changes). --- nova/tests/api/openstack/test_extensions.py | 2 -- 1 file changed, 2 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 11ed61e0d..d1f8c659e 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -190,8 +190,6 @@ class ResponseExtensionTest(unittest.TestCase): self.stubs = stubout.StubOutForTesting() fakes.FakeAuthManager.reset_fake_data() fakes.FakeAuthDatabase.data = {} - fakes.stub_out_networking(self.stubs) - fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_auth(self.stubs) self.context = context.get_admin_context() -- cgit From f988df6c6f29d6c885d44c6768297aaf489faf34 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 21 Mar 2011 13:58:39 -0400 Subject: Fix pep8 issues in nova/api/openstack/extensions.py. --- nova/api/openstack/extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index 23181d2a6..f881dbde7 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -69,13 +69,13 @@ class ResponseExtensionController(wsgi.Controller): content_type = req.best_match_content_type() # currently response handlers are un-ordered for handler in self.handlers: - res=handler(res) + res = handler(res) try: body = res.body headers = res.headers except AttributeError: body = self._serialize(res, content_type) - headers={"Content-Type": content_type} + headers = {"Content-Type": content_type} res = webob.Response() res.body = body res.headers = headers -- cgit From fbd94f236adaa906fcc9c90de94e491e3d75653b Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 21 Mar 2011 13:59:26 -0400 Subject: Added copyright header. --- nova/tests/api/openstack/extensions/foxinsocks.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'nova') diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index fa979c7b5..249dd81bf 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -1,3 +1,20 @@ +# 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 from nova import wsgi -- cgit From ff2d6dc656c03b8aeab5e50c5d39ca9dcde9b9b1 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Mon, 21 Mar 2011 14:00:39 -0400 Subject: Updated comment per the extension naming convention we actually use. --- nova/api/openstack/extensions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'nova') diff --git a/nova/api/openstack/extensions.py b/nova/api/openstack/extensions.py index f881dbde7..9d98d849a 100644 --- a/nova/api/openstack/extensions.py +++ b/nova/api/openstack/extensions.py @@ -303,9 +303,9 @@ class ExtensionManager(object): def _load_extensions(self): """ Load extensions from the configured path. The extension name is - constructed from the camel cased module_name + 'Extension'. If your - extension module was named widgets.py the extension class within that - module should be 'WidgetsExtension'. + constructed from the module_name. If your extension module was named + widgets.py the extension class within that module should be + 'Widgets'. See nova/tests/api/openstack/extensions/foxinsocks.py for an example extension implementation. -- cgit From 12184874da4369891b2eae49982623fc6c9315e3 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 24 Mar 2011 13:26:57 -0400 Subject: Updated to use new APIRouterV11 class in tests. --- nova/tests/api/openstack/test_extensions.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index d1f8c659e..4bc823d15 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -81,14 +81,14 @@ class StubExtensionManager(object): class ExtensionControllerTest(unittest.TestCase): def test_index(self): - app = openstack.APIRouter() + app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) request = webob.Request.blank("/extensions") response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) def test_get_by_alias(self): - app = openstack.APIRouter() + app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) request = webob.Request.blank("/extensions/FOXNSOX") response = request.get_response(ext_midware) @@ -99,7 +99,7 @@ class ResourceExtensionTest(unittest.TestCase): def test_no_extension_present(self): manager = StubExtensionManager(None) - app = openstack.APIRouter() + app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app, manager) request = webob.Request.blank("/blah") response = request.get_response(ext_midware) @@ -109,7 +109,7 @@ class ResourceExtensionTest(unittest.TestCase): res_ext = extensions.ResourceExtension('tweedles', StubController(response_body)) manager = StubExtensionManager(res_ext) - app = openstack.APIRouter() + app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app, manager) request = webob.Request.blank("/tweedles") response = request.get_response(ext_midware) @@ -120,7 +120,7 @@ class ResourceExtensionTest(unittest.TestCase): res_ext = extensions.ResourceExtension('tweedles', StubController(response_body)) manager = StubExtensionManager(res_ext) - app = openstack.APIRouter() + app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app, manager) request = webob.Request.blank("/tweedles") response = request.get_response(ext_midware) @@ -137,7 +137,7 @@ class ExtensionManagerTest(unittest.TestCase): "extensions") def test_get_resources(self): - app = openstack.APIRouter() + app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) request = webob.Request.blank("/foxnsocks") response = request.get_response(ext_midware) @@ -152,7 +152,7 @@ class ActionExtensionTest(unittest.TestCase): "extensions") def _send_server_action_request(self, url, body): - app = openstack.APIRouter() + app = openstack.APIRouterV11() ext_midware = extensions.ExtensionMiddleware(app) request = webob.Request.blank(url) request.method = 'POST' -- cgit From e5069f27cd9e6551a6b035d6fff1b02a6bf0b492 Mon Sep 17 00:00:00 2001 From: Dan Prince Date: Thu, 24 Mar 2011 13:56:25 -0400 Subject: Reconcile tests with latest trunk merges. --- nova/tests/api/openstack/extensions/foxinsocks.py | 4 ++-- nova/tests/api/openstack/test_extensions.py | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'nova') diff --git a/nova/tests/api/openstack/extensions/foxinsocks.py b/nova/tests/api/openstack/extensions/foxinsocks.py index 249dd81bf..0860b51ac 100644 --- a/nova/tests/api/openstack/extensions/foxinsocks.py +++ b/nova/tests/api/openstack/extensions/foxinsocks.py @@ -73,7 +73,7 @@ class Foxinsocks(object): data['flavor']['googoose'] = "Gooey goo for chewy chewing!" return data - resp_ext = extensions.ResponseExtension('GET', '/v1.0/flavors/:(id)', + resp_ext = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', _goose_handler) response_exts.append(resp_ext) @@ -84,7 +84,7 @@ class Foxinsocks(object): data['big_bands'] = 'Pig Bands!' return data - resp_ext2 = extensions.ResponseExtension('GET', '/v1.0/flavors/:(id)', + resp_ext2 = extensions.ResponseExtension('GET', '/v1.1/flavors/:(id)', _bands_handler) response_exts.append(resp_ext2) return response_exts diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py index 4bc823d15..481d34ed1 100644 --- a/nova/tests/api/openstack/test_extensions.py +++ b/nova/tests/api/openstack/test_extensions.py @@ -193,6 +193,10 @@ class ResponseExtensionTest(unittest.TestCase): fakes.stub_out_auth(self.stubs) self.context = context.get_admin_context() + def tearDown(self): + self.stubs.UnsetAll() + super(ResponseExtensionTest, self).tearDown() + def test_get_resources_with_stub_mgr(self): test_resp = "Gooey goo for chewy chewing!" @@ -204,13 +208,14 @@ class ResponseExtensionTest(unittest.TestCase): return data resp_ext = extensions.ResponseExtension('GET', - '/v1.0/flavors/:(id)', + '/v1.1/flavors/:(id)', _resp_handler) manager = StubExtensionManager(None, None, resp_ext) app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app, manager) - request = webob.Request.blank("/v1.0/flavors/1") + request = webob.Request.blank("/v1.1/flavors/1") + request.environ['api.version'] = '1.1' response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) response_data = json.loads(response.body) @@ -222,10 +227,10 @@ class ResponseExtensionTest(unittest.TestCase): app = fakes.wsgi_app() ext_midware = extensions.ExtensionMiddleware(app) - request = webob.Request.blank("/v1.0/flavors/1") + request = webob.Request.blank("/v1.1/flavors/1") + request.environ['api.version'] = '1.1' response = request.get_response(ext_midware) self.assertEqual(200, response.status_int) response_data = json.loads(response.body) - print response_data self.assertEqual(test_resp, response_data['flavor']['googoose']) self.assertEqual("Pig Bands!", response_data['big_bands']) -- cgit