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(path)
|
||||||
submap.connect("%s.:(format)" % 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,
|
mapper.resource(resource.collection, resource.collection,
|
||||||
controller=resource.controller,
|
controller=resource.controller,
|
||||||
member=resource.member_actions,
|
member=resource.member_actions,
|
||||||
|
@ -632,14 +642,17 @@ class ResourceExtension(object):
|
||||||
"""Add top level resources to the OpenStack API in Neutron."""
|
"""Add top level resources to the OpenStack API in Neutron."""
|
||||||
|
|
||||||
def __init__(self, collection, controller, parent=None, path_prefix="",
|
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_actions = collection_actions or {}
|
||||||
|
collection_methods = collection_methods or {}
|
||||||
member_actions = member_actions or {}
|
member_actions = member_actions or {}
|
||||||
attr_map = attr_map or {}
|
attr_map = attr_map or {}
|
||||||
self.collection = collection
|
self.collection = collection
|
||||||
self.controller = controller
|
self.controller = controller
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.collection_actions = collection_actions
|
self.collection_actions = collection_actions
|
||||||
|
self.collection_methods = collection_methods
|
||||||
self.member_actions = member_actions
|
self.member_actions = member_actions
|
||||||
self.path_prefix = path_prefix
|
self.path_prefix = path_prefix
|
||||||
self.attr_map = attr_map
|
self.attr_map = attr_map
|
||||||
|
|
|
@ -39,14 +39,15 @@ class Request(wsgi.Request):
|
||||||
pass
|
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
|
"""Represents an API entity resource and the associated serialization and
|
||||||
deserialization logic
|
deserialization logic
|
||||||
"""
|
"""
|
||||||
default_deserializers = {'application/json': wsgi.JSONDeserializer()}
|
default_deserializers = {'application/json': wsgi.JSONDeserializer()}
|
||||||
default_serializers = {'application/json': wsgi.JSONDictSerializer()}
|
default_serializers = {'application/json': wsgi.JSONDictSerializer()}
|
||||||
format_types = {'json': 'application/json'}
|
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_deserializers.update(deserializers or {})
|
||||||
default_serializers.update(serializers or {})
|
default_serializers.update(serializers or {})
|
||||||
|
|
|
@ -160,6 +160,9 @@ class ResourceExtensionTest(base.BaseTestCase):
|
||||||
def custom_member_action(self, request, id):
|
def custom_member_action(self, request, id):
|
||||||
return {'member_action': 'value'}
|
return {'member_action': 'value'}
|
||||||
|
|
||||||
|
def custom_collection_method(self, request, **kwargs):
|
||||||
|
return {'collection': 'value'}
|
||||||
|
|
||||||
def custom_collection_action(self, request, **kwargs):
|
def custom_collection_action(self, request, **kwargs):
|
||||||
return {'collection': 'value'}
|
return {'collection': 'value'}
|
||||||
|
|
||||||
|
@ -355,6 +358,80 @@ class ResourceExtensionTest(base.BaseTestCase):
|
||||||
self.assertEqual(200, response.status_int)
|
self.assertEqual(200, response.status_int)
|
||||||
self.assertEqual(jsonutils.loads(response.body)['collection'], "value")
|
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):
|
def test_resource_extension_with_custom_member_action_and_attr_map(self):
|
||||||
controller = self.ResourceExtensionController()
|
controller = self.ResourceExtensionController()
|
||||||
member = {'custom_member_action': "GET"}
|
member = {'custom_member_action': "GET"}
|
||||||
|
|
|
@ -290,6 +290,18 @@ class ResourceTestCase(base.BaseTestCase):
|
||||||
res = resource.delete('', extra_environ=environ)
|
res = resource.delete('', extra_environ=environ)
|
||||||
self.assertEqual(204, res.status_int)
|
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,
|
def _test_error_log_level(self, expected_webob_exc, expect_log_info=False,
|
||||||
use_fault_map=True, exc_raised=None):
|
use_fault_map=True, exc_raised=None):
|
||||||
if not exc_raised:
|
if not exc_raised:
|
||||||
|
|
Loading…
Reference in New Issue