summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorDan Prince <dan.prince@rackspace.com>2011-03-16 11:02:22 -0400
committerDan Prince <dan.prince@rackspace.com>2011-03-16 11:02:22 -0400
commitd714df5ed4a6a1d4f1c0f7680c2fbb6a6abb81a5 (patch)
tree35878d1353608f9f371600ae46917619b73e210d /nova
parent0053b776276e9cac617c812931c248be7e49fea2 (diff)
Implement top level extensions.
Diffstat (limited to 'nova')
-rw-r--r--nova/api/openstack/extensions.py94
-rw-r--r--nova/tests/api/openstack/extensions/foxinsocks.py70
-rw-r--r--nova/tests/api/openstack/extensions/widgets.py54
-rw-r--r--nova/tests/api/openstack/test_extensions.py68
4 files changed, 204 insertions, 82 deletions
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'])