Make API framework more flexible for various extensions
This patch adds a function to Neutron extension mechanism so that we can add API methods to "/resources" path rather than "/resources/action" path. This patch also allow extentions to control correspondences between action and status in API request. Change-Id: I862086f7528583e65d7bee794f011ddff6ae8901 Partial-Implements: blueprint add-tags-to-core-resources Related-Bug: #1489291
This commit is contained in:
parent
b380b15d4c
commit
0ae3c172ae
|
@ -275,6 +275,16 @@ class ExtensionMiddleware(base.ConfigurableMiddleware):
|
|||
submap.connect(path)
|
||||
submap.connect("%s.:(format)" % path)
|
||||
|
||||
for action, method in resource.collection_methods.items():
|
||||
conditions = dict(method=[method])
|
||||
path = "/%s" % resource.collection
|
||||
with mapper.submapper(controller=resource.controller,
|
||||
action=action,
|
||||
path_prefix=path_prefix,
|
||||
conditions=conditions) as submap:
|
||||
submap.connect(path)
|
||||
submap.connect("%s.:(format)" % path)
|
||||
|
||||
mapper.resource(resource.collection, resource.collection,
|
||||
controller=resource.controller,
|
||||
member=resource.member_actions,
|
||||
|
@ -632,14 +642,17 @@ class ResourceExtension(object):
|
|||
"""Add top level resources to the OpenStack API in Neutron."""
|
||||
|
||||
def __init__(self, collection, controller, parent=None, path_prefix="",
|
||||
collection_actions=None, member_actions=None, attr_map=None):
|
||||
collection_actions=None, member_actions=None, attr_map=None,
|
||||
collection_methods=None):
|
||||
collection_actions = collection_actions or {}
|
||||
collection_methods = collection_methods or {}
|
||||
member_actions = member_actions or {}
|
||||
attr_map = attr_map or {}
|
||||
self.collection = collection
|
||||
self.controller = controller
|
||||
self.parent = parent
|
||||
self.collection_actions = collection_actions
|
||||
self.collection_methods = collection_methods
|
||||
self.member_actions = member_actions
|
||||
self.path_prefix = path_prefix
|
||||
self.attr_map = attr_map
|
||||
|
|
|
@ -39,14 +39,15 @@ class Request(wsgi.Request):
|
|||
pass
|
||||
|
||||
|
||||
def Resource(controller, faults=None, deserializers=None, serializers=None):
|
||||
def Resource(controller, faults=None, deserializers=None, serializers=None,
|
||||
action_status=None):
|
||||
"""Represents an API entity resource and the associated serialization and
|
||||
deserialization logic
|
||||
"""
|
||||
default_deserializers = {'application/json': wsgi.JSONDeserializer()}
|
||||
default_serializers = {'application/json': wsgi.JSONDictSerializer()}
|
||||
format_types = {'json': 'application/json'}
|
||||
action_status = dict(create=201, delete=204)
|
||||
action_status = action_status or dict(create=201, delete=204)
|
||||
|
||||
default_deserializers.update(deserializers or {})
|
||||
default_serializers.update(serializers or {})
|
||||
|
|
|
@ -160,6 +160,9 @@ class ResourceExtensionTest(base.BaseTestCase):
|
|||
def custom_member_action(self, request, id):
|
||||
return {'member_action': 'value'}
|
||||
|
||||
def custom_collection_method(self, request, **kwargs):
|
||||
return {'collection': 'value'}
|
||||
|
||||
def custom_collection_action(self, request, **kwargs):
|
||||
return {'collection': 'value'}
|
||||
|
||||
|
@ -355,6 +358,80 @@ class ResourceExtensionTest(base.BaseTestCase):
|
|||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual(jsonutils.loads(response.body)['collection'], "value")
|
||||
|
||||
def test_resource_extension_for_get_custom_collection_method(self):
|
||||
controller = self.ResourceExtensionController()
|
||||
collections = {'custom_collection_method': "GET"}
|
||||
res_ext = extensions.ResourceExtension('tweedles', controller,
|
||||
collection_methods=collections)
|
||||
test_app = _setup_extensions_test_app(SimpleExtensionManager(res_ext))
|
||||
|
||||
response = test_app.get("/tweedles")
|
||||
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual("value", jsonutils.loads(response.body)['collection'])
|
||||
|
||||
def test_resource_extension_for_put_custom_collection_method(self):
|
||||
controller = self.ResourceExtensionController()
|
||||
collections = {'custom_collection_method': "PUT"}
|
||||
res_ext = extensions.ResourceExtension('tweedles', controller,
|
||||
collection_methods=collections)
|
||||
test_app = _setup_extensions_test_app(SimpleExtensionManager(res_ext))
|
||||
|
||||
response = test_app.put("/tweedles")
|
||||
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual('value', jsonutils.loads(response.body)['collection'])
|
||||
|
||||
def test_resource_extension_for_post_custom_collection_method(self):
|
||||
controller = self.ResourceExtensionController()
|
||||
collections = {'custom_collection_method': "POST"}
|
||||
res_ext = extensions.ResourceExtension('tweedles', controller,
|
||||
collection_methods=collections)
|
||||
test_app = _setup_extensions_test_app(SimpleExtensionManager(res_ext))
|
||||
|
||||
response = test_app.post("/tweedles")
|
||||
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual('value', jsonutils.loads(response.body)['collection'])
|
||||
|
||||
def test_resource_extension_for_delete_custom_collection_method(self):
|
||||
controller = self.ResourceExtensionController()
|
||||
collections = {'custom_collection_method': "DELETE"}
|
||||
res_ext = extensions.ResourceExtension('tweedles', controller,
|
||||
collection_methods=collections)
|
||||
test_app = _setup_extensions_test_app(SimpleExtensionManager(res_ext))
|
||||
|
||||
response = test_app.delete("/tweedles")
|
||||
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual('value', jsonutils.loads(response.body)['collection'])
|
||||
|
||||
def test_resource_ext_for_formatted_req_on_custom_collection_method(self):
|
||||
controller = self.ResourceExtensionController()
|
||||
collections = {'custom_collection_method': "GET"}
|
||||
res_ext = extensions.ResourceExtension('tweedles', controller,
|
||||
collection_methods=collections)
|
||||
test_app = _setup_extensions_test_app(SimpleExtensionManager(res_ext))
|
||||
|
||||
response = test_app.get("/tweedles.json")
|
||||
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual("value", jsonutils.loads(response.body)['collection'])
|
||||
|
||||
def test_resource_ext_for_nested_resource_custom_collection_method(self):
|
||||
controller = self.ResourceExtensionController()
|
||||
collections = {'custom_collection_method': "GET"}
|
||||
parent = {'collection_name': 'beetles', 'member_name': 'beetle'}
|
||||
res_ext = extensions.ResourceExtension('tweedles', controller,
|
||||
collection_methods=collections,
|
||||
parent=parent)
|
||||
test_app = _setup_extensions_test_app(SimpleExtensionManager(res_ext))
|
||||
|
||||
response = test_app.get("/beetles/beetle_id/tweedles")
|
||||
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual("value", jsonutils.loads(response.body)['collection'])
|
||||
|
||||
def test_resource_extension_with_custom_member_action_and_attr_map(self):
|
||||
controller = self.ResourceExtensionController()
|
||||
member = {'custom_member_action': "GET"}
|
||||
|
|
|
@ -290,6 +290,18 @@ class ResourceTestCase(base.BaseTestCase):
|
|||
res = resource.delete('', extra_environ=environ)
|
||||
self.assertEqual(204, res.status_int)
|
||||
|
||||
def test_action_status(self):
|
||||
controller = mock.MagicMock()
|
||||
controller.test = lambda request: {'foo': 'bar'}
|
||||
action_status = {'test_200': 200, 'test_201': 201, 'test_204': 204}
|
||||
resource = webtest.TestApp(
|
||||
wsgi_resource.Resource(controller,
|
||||
action_status=action_status))
|
||||
for action in action_status:
|
||||
environ = {'wsgiorg.routing_args': (None, {'action': action})}
|
||||
res = resource.get('', extra_environ=environ)
|
||||
self.assertEqual(action_status[action], res.status_int)
|
||||
|
||||
def _test_error_log_level(self, expected_webob_exc, expect_log_info=False,
|
||||
use_fault_map=True, exc_raised=None):
|
||||
if not exc_raised:
|
||||
|
|
Loading…
Reference in New Issue