Implement API extensions for the Openstack API. Based on the Openstack 1.1 API the following types of extensions are supported:
-Top level resources (extension) -Action extensions (add an extra action to a core nova controller) -Response extensions (inject data into response from core nova controllers) To add an extension simply drop an extension file into the configured osapi_extensions_path (which defaults to /var/lib/nova/extensions). See nova/tests/api/openstack/extensions/foxinsocks.py for an example Extension.
This commit is contained in:
commit
f186c8ecc2
@ -74,7 +74,7 @@ use = egg:Paste#urlmap
|
||||
pipeline = faultwrap auth ratelimit osapiapp10
|
||||
|
||||
[pipeline:openstackapi11]
|
||||
pipeline = faultwrap auth ratelimit osapiapp11
|
||||
pipeline = faultwrap auth ratelimit extensions osapiapp11
|
||||
|
||||
[filter:faultwrap]
|
||||
paste.filter_factory = nova.api.openstack:FaultWrapper.factory
|
||||
@ -85,6 +85,9 @@ paste.filter_factory = nova.api.openstack.auth:AuthMiddleware.factory
|
||||
[filter:ratelimit]
|
||||
paste.filter_factory = nova.api.openstack.limits:RateLimitingMiddleware.factory
|
||||
|
||||
[filter:extensions]
|
||||
paste.filter_factory = nova.api.openstack.extensions:ExtensionMiddleware.factory
|
||||
|
||||
[app:osapiapp10]
|
||||
paste.app_factory = nova.api.openstack:APIRouterV10.factory
|
||||
|
||||
|
@ -71,7 +71,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_mgr=None):
|
||||
self.server_members = {}
|
||||
mapper = routes.Mapper()
|
||||
self._setup_routes(mapper)
|
||||
|
369
nova/api/openstack/extensions.py
Normal file
369
nova/api/openstack/extensions.py
Normal file
@ -0,0 +1,369 @@
|
||||
# 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 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
|
||||
from nova.api.openstack import faults
|
||||
|
||||
|
||||
LOG = logging.getLogger('extensions')
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
|
||||
|
||||
class ActionExtensionController(wsgi.Controller):
|
||||
|
||||
def __init__(self, application):
|
||||
|
||||
self.application = application
|
||||
self.action_handlers = {}
|
||||
|
||||
def add_action(self, action_name, handler):
|
||||
self.action_handlers[action_name] = handler
|
||||
|
||||
def action(self, req, id):
|
||||
|
||||
input_dict = self._deserialize(req.body, req.get_content_type())
|
||||
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)
|
||||
content_type = req.best_match_content_type()
|
||||
# currently response handlers are un-ordered
|
||||
for handler in self.handlers:
|
||||
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):
|
||||
|
||||
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.
|
||||
"""
|
||||
@classmethod
|
||||
def factory(cls, global_config, **local_config):
|
||||
""" paste factory """
|
||||
def _factory(app):
|
||||
return cls(app, **local_config)
|
||||
return _factory
|
||||
|
||||
def _action_ext_controllers(self, application, ext_mgr, mapper):
|
||||
"""
|
||||
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)
|
||||
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 _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.key in response_ext_controllers.keys():
|
||||
controller = ResponseExtensionController(application)
|
||||
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):
|
||||
|
||||
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():
|
||||
LOG.debug(_('Extended resource: %s'),
|
||||
resource.collection)
|
||||
mapper.resource(resource.collection, resource.collection,
|
||||
controller=resource.controller,
|
||||
collection=resource.collection_actions,
|
||||
member=resource.member_actions,
|
||||
parent_resource=resource.parent)
|
||||
|
||||
# extended actions
|
||||
action_controllers = self._action_ext_controllers(application, ext_mgr,
|
||||
mapper)
|
||||
for action in ext_mgr.get_actions():
|
||||
LOG.debug(_('Extended action: %s'), action.action_name)
|
||||
controller = action_controllers[action.collection]
|
||||
controller.add_action(action.action_name, action.handler)
|
||||
|
||||
# extended responses
|
||||
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.key)
|
||||
controller = resp_controllers[response_ext.key]
|
||||
controller.add_handler(response_ext.handler)
|
||||
|
||||
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):
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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._load_extensions()
|
||||
|
||||
def get_resources(self):
|
||||
"""
|
||||
returns a list of ResourceExtension objects
|
||||
"""
|
||||
resources = []
|
||||
resources.append(ResourceExtension('extensions',
|
||||
ExtensionController(self)))
|
||||
for alias, ext in self.extensions.iteritems():
|
||||
try:
|
||||
resources.extend(ext.get_resources())
|
||||
except AttributeError:
|
||||
# NOTE: Extension aren't required to have resource extensions
|
||||
pass
|
||||
return resources
|
||||
|
||||
def get_actions(self):
|
||||
"""
|
||||
returns a list of ActionExtension objects
|
||||
"""
|
||||
actions = []
|
||||
for alias, ext in self.extensions.iteritems():
|
||||
try:
|
||||
actions.extend(ext.get_actions())
|
||||
except AttributeError:
|
||||
# NOTE: Extension aren't required to have action extensions
|
||||
pass
|
||||
return actions
|
||||
|
||||
def get_response_extensions(self):
|
||||
"""
|
||||
returns a list of ResponseExtension objects
|
||||
"""
|
||||
response_exts = []
|
||||
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 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.
|
||||
"""
|
||||
if not os.path.exists(self.path):
|
||||
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':
|
||||
mod = imp.load_source(mod_name, ext_path)
|
||||
ext_name = mod_name[0].upper() + mod_name[1:]
|
||||
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):
|
||||
"""
|
||||
ResponseExtension objects can be used to add data to responses from
|
||||
core nova OpenStack API controllers.
|
||||
"""
|
||||
|
||||
def __init__(self, method, url_route, handler):
|
||||
self.url_route = url_route
|
||||
self.handler = handler
|
||||
self.conditions = dict(method=[method])
|
||||
self.key = "%s-%s" % (method, url_route)
|
||||
|
||||
|
||||
class ActionExtension(object):
|
||||
"""
|
||||
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.action_name = action_name
|
||||
self.handler = handler
|
||||
|
||||
|
||||
class ResourceExtension(object):
|
||||
"""
|
||||
ResourceExtension objects can be used to add top level resources
|
||||
to the OpenStack API in nova.
|
||||
"""
|
||||
|
||||
def __init__(self, collection, controller, parent=None,
|
||||
collection_actions={}, member_actions={}):
|
||||
self.collection = collection
|
||||
self.controller = controller
|
||||
self.parent = parent
|
||||
self.collection_actions = collection_actions
|
||||
self.member_actions = member_actions
|
@ -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')
|
||||
|
98
nova/tests/api/openstack/extensions/foxinsocks.py
Normal file
98
nova/tests/api/openstack/extensions/foxinsocks.py
Normal file
@ -0,0 +1,98 @@
|
||||
# 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
|
||||
|
||||
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 _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', '/v1.1/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.1/flavors/:(id)',
|
||||
_bands_handler)
|
||||
response_exts.append(resp_ext2)
|
||||
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."
|
236
nova/tests/api/openstack/test_extensions.py
Normal file
236
nova/tests/api/openstack/test_extensions.py
Normal file
@ -0,0 +1,236 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import json
|
||||
import 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
|
||||
|
||||
response_body = "Try to say this Mr. Knox, sir..."
|
||||
|
||||
|
||||
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, 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_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:
|
||||
resource_exts.append(self.resource_ext)
|
||||
return resource_exts
|
||||
|
||||
def get_actions(self):
|
||||
action_exts = []
|
||||
if self.action_ext:
|
||||
action_exts.append(self.action_ext)
|
||||
return action_exts
|
||||
|
||||
def get_response_extensions(self):
|
||||
response_exts = []
|
||||
if self.response_ext:
|
||||
response_exts.append(self.response_ext)
|
||||
return response_exts
|
||||
|
||||
|
||||
class ExtensionControllerTest(unittest.TestCase):
|
||||
|
||||
def test_index(self):
|
||||
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.APIRouterV11()
|
||||
ext_midware = extensions.ExtensionMiddleware(app)
|
||||
request = webob.Request.blank("/extensions/FOXNSOX")
|
||||
response = request.get_response(ext_midware)
|
||||
self.assertEqual(200, response.status_int)
|
||||
|
||||
|
||||
class ResourceExtensionTest(unittest.TestCase):
|
||||
|
||||
def test_no_extension_present(self):
|
||||
manager = StubExtensionManager(None)
|
||||
app = openstack.APIRouterV11()
|
||||
ext_midware = extensions.ExtensionMiddleware(app, manager)
|
||||
request = webob.Request.blank("/blah")
|
||||
response = request.get_response(ext_midware)
|
||||
self.assertEqual(404, response.status_int)
|
||||
|
||||
def test_get_resources(self):
|
||||
res_ext = extensions.ResourceExtension('tweedles',
|
||||
StubController(response_body))
|
||||
manager = StubExtensionManager(res_ext)
|
||||
app = openstack.APIRouterV11()
|
||||
ext_midware = extensions.ExtensionMiddleware(app, manager)
|
||||
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):
|
||||
res_ext = extensions.ResourceExtension('tweedles',
|
||||
StubController(response_body))
|
||||
manager = StubExtensionManager(res_ext)
|
||||
app = openstack.APIRouterV11()
|
||||
ext_midware = extensions.ExtensionMiddleware(app, manager)
|
||||
request = webob.Request.blank("/tweedles")
|
||||
response = request.get_response(ext_midware)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(response_body, response.body)
|
||||
|
||||
|
||||
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")
|
||||
|
||||
def test_get_resources(self):
|
||||
app = openstack.APIRouterV11()
|
||||
ext_midware = extensions.ExtensionMiddleware(app)
|
||||
request = webob.Request.blank("/foxnsocks")
|
||||
response = request.get_response(ext_midware)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(response_body, response.body)
|
||||
|
||||
|
||||
class ActionExtensionTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
FLAGS.osapi_extensions_path = os.path.join(os.path.dirname(__file__),
|
||||
"extensions")
|
||||
|
||||
def _send_server_action_request(self, url, body):
|
||||
app = openstack.APIRouterV11()
|
||||
ext_midware = extensions.ExtensionMiddleware(app)
|
||||
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_tweedle=dict(name="test"))
|
||||
response = self._send_server_action_request("/servers/1/action", body)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual("Tweedle Beetle Added.", response.body)
|
||||
|
||||
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("Tweedle Beetle Deleted.", response.body)
|
||||
|
||||
def test_invalid_action_body(self):
|
||||
body = dict(blah=dict(name="test")) # Doesn't exist
|
||||
response = self._send_server_action_request("/servers/1/action", body)
|
||||
self.assertEqual(501, response.status_int)
|
||||
|
||||
def test_invalid_action(self):
|
||||
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_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!"
|
||||
|
||||
def _resp_handler(res):
|
||||
# only handle JSON responses
|
||||
data = json.loads(res.body)
|
||||
data['flavor']['googoose'] = test_resp
|
||||
return data
|
||||
|
||||
resp_ext = extensions.ResponseExtension('GET',
|
||||
'/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.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)
|
||||
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.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)
|
||||
self.assertEqual(test_resp, response_data['flavor']['googoose'])
|
||||
self.assertEqual("Pig Bands!", response_data['big_bands'])
|
Loading…
Reference in New Issue
Block a user