Add functionality to define requests without body

This allows functions that do not accept bodies to define this in the
router file. As currently many requests will cause a 500 if a body is
supplied when the API request does not expect it.

This currently only affects the core parts of the v2 api, that is,
calls to v2/images and v2/schemas. It does not cover the "tasks" API
or the metadefs api as I was keeping this patch concise. As this
does not affect the behaviour if not included this makes no change to
the metadefs api behaviour.

DocImpact

Partial-Bug: 1475647

Change-Id: Ieb510e5516128078d40d39fd9b4f339ce64e10e7
This commit is contained in:
Niall Bunting 2015-07-28 15:05:10 +00:00 committed by Brian Rosmaita
parent 32404fad45
commit 9b430f9951
3 changed files with 107 additions and 14 deletions

View File

@ -40,7 +40,8 @@ class API(wsgi.Router):
mapper.connect('/schemas/image',
controller=schemas_resource,
action='image',
conditions={'method': ['GET']})
conditions={'method': ['GET']},
body_reject=True)
mapper.connect('/schemas/image',
controller=reject_method_resource,
action='reject',
@ -48,7 +49,8 @@ class API(wsgi.Router):
mapper.connect('/schemas/images',
controller=schemas_resource,
action='images',
conditions={'method': ['GET']})
conditions={'method': ['GET']},
body_reject=True)
mapper.connect('/schemas/images',
controller=reject_method_resource,
action='reject',
@ -56,7 +58,8 @@ class API(wsgi.Router):
mapper.connect('/schemas/member',
controller=schemas_resource,
action='member',
conditions={'method': ['GET']})
conditions={'method': ['GET']},
body_reject=True)
mapper.connect('/schemas/member',
controller=reject_method_resource,
action='reject',
@ -65,7 +68,8 @@ class API(wsgi.Router):
mapper.connect('/schemas/members',
controller=schemas_resource,
action='members',
conditions={'method': ['GET']})
conditions={'method': ['GET']},
body_reject=True)
mapper.connect('/schemas/members',
controller=reject_method_resource,
action='reject',
@ -388,11 +392,13 @@ class API(wsgi.Router):
mapper.connect('/images/{image_id}',
controller=images_resource,
action='show',
conditions={'method': ['GET']})
conditions={'method': ['GET']},
body_reject=True)
mapper.connect('/images/{image_id}',
controller=images_resource,
action='delete',
conditions={'method': ['DELETE']})
conditions={'method': ['DELETE']},
body_reject=True)
mapper.connect('/images/{image_id}',
controller=reject_method_resource,
action='reject',
@ -402,11 +408,13 @@ class API(wsgi.Router):
mapper.connect('/images/{image_id}/actions/deactivate',
controller=image_actions_resource,
action='deactivate',
conditions={'method': ['POST']})
conditions={'method': ['POST']},
body_reject=True)
mapper.connect('/images/{image_id}/actions/reactivate',
controller=image_actions_resource,
action='reactivate',
conditions={'method': ['POST']})
conditions={'method': ['POST']},
body_reject=True)
mapper.connect('/images/{image_id}/actions/deactivate',
controller=reject_method_resource,
action='reject',
@ -420,7 +428,8 @@ class API(wsgi.Router):
mapper.connect('/images/{image_id}/file',
controller=image_data_resource,
action='download',
conditions={'method': ['GET']})
conditions={'method': ['GET']},
body_reject=True)
mapper.connect('/images/{image_id}/file',
controller=image_data_resource,
action='upload',
@ -434,11 +443,13 @@ class API(wsgi.Router):
mapper.connect('/images/{image_id}/tags/{tag_value}',
controller=image_tags_resource,
action='update',
conditions={'method': ['PUT']})
conditions={'method': ['PUT']},
body_reject=True)
mapper.connect('/images/{image_id}/tags/{tag_value}',
controller=image_tags_resource,
action='delete',
conditions={'method': ['DELETE']})
conditions={'method': ['DELETE']},
body_reject=True)
mapper.connect('/images/{image_id}/tags/{tag_value}',
controller=reject_method_resource,
action='reject',
@ -448,7 +459,8 @@ class API(wsgi.Router):
mapper.connect('/images/{image_id}/members',
controller=image_members_resource,
action='index',
conditions={'method': ['GET']})
conditions={'method': ['GET']},
body_reject=True)
mapper.connect('/images/{image_id}/members',
controller=image_members_resource,
action='create',
@ -461,7 +473,8 @@ class API(wsgi.Router):
mapper.connect('/images/{image_id}/members/{member_id}',
controller=image_members_resource,
action='show',
conditions={'method': ['GET']})
conditions={'method': ['GET']},
body_reject=True)
mapper.connect('/images/{image_id}/members/{member_id}',
controller=image_members_resource,
action='update',
@ -469,7 +482,8 @@ class API(wsgi.Router):
mapper.connect('/images/{image_id}/members/{member_id}',
controller=image_members_resource,
action='delete',
conditions={'method': ['DELETE']})
conditions={'method': ['DELETE']},
body_reject=True)
mapper.connect('/images/{image_id}/members/{member_id}',
controller=reject_method_resource,
action='reject',

View File

@ -38,6 +38,7 @@ from oslo_concurrency import processutils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import strutils
import routes
import routes.middleware
import six
@ -877,8 +878,13 @@ class Resource(object):
"""WSGI method that controls (de)serialization and method dispatch."""
action_args = self.get_action_args(request.environ)
action = action_args.pop('action', None)
body_reject = strutils.bool_from_string(
action_args.pop('body_reject', None))
try:
if body_reject and self.deserializer.has_body(request):
msg = _('A body is not expected with this request.')
raise webob.exc.HTTPBadRequest(explanation=msg)
deserialized_request = self.dispatch(self.deserializer,
action, request)
action_args.update(deserialized_request)

View File

@ -638,6 +638,79 @@ class TestImages(functional.FunctionalTest):
self.stop_servers()
def test_methods_that_dont_accept_illegal_bodies(self):
# Check images can be reached
self.start_servers(**self.__dict__.copy())
path = self._url('/v2/images')
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
# Test all the schemas
schema_urls = [
'/v2/schemas/images',
'/v2/schemas/image',
'/v2/schemas/members',
'/v2/schemas/member',
]
for value in schema_urls:
path = self._url(value)
data = jsonutils.dumps(["body"])
response = requests.get(path, headers=self._headers(), data=data)
self.assertEqual(400, response.status_code)
# Create image for use with tests
path = self._url('/v2/images')
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps({'name': 'image'})
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
image = jsonutils.loads(response.text)
image_id = image['id']
test_urls = [
('/v2/images/%s', 'get'),
('/v2/images/%s/actions/deactivate', 'post'),
('/v2/images/%s/actions/reactivate', 'post'),
('/v2/images/%s/tags/mytag', 'put'),
('/v2/images/%s/tags/mytag', 'delete'),
('/v2/images/%s/members', 'get'),
('/v2/images/%s/file', 'get'),
('/v2/images/%s', 'delete'),
]
for link, method in test_urls:
path = self._url(link % image_id)
data = jsonutils.dumps(["body"])
response = getattr(requests, method)(
path, headers=self._headers(), data=data)
self.assertEqual(400, response.status_code)
# DELETE /images/imgid without legal json
path = self._url('/v2/images/%s' % image_id)
data = '{"hello"]'
response = requests.delete(path, headers=self._headers(), data=data)
self.assertEqual(400, response.status_code)
# POST /images/imgid/members
path = self._url('/v2/images/%s/members' % image_id)
data = jsonutils.dumps({'member': TENANT3})
response = requests.post(path, headers=self._headers(), data=data)
self.assertEqual(200, response.status_code)
# GET /images/imgid/members/memid
path = self._url('/v2/images/%s/members/%s' % (image_id, TENANT3))
data = jsonutils.dumps(["body"])
response = requests.get(path, headers=self._headers(), data=data)
self.assertEqual(400, response.status_code)
# DELETE /images/imgid/members/memid
path = self._url('/v2/images/%s/members/%s' % (image_id, TENANT3))
data = jsonutils.dumps(["body"])
response = requests.delete(path, headers=self._headers(), data=data)
self.assertEqual(400, response.status_code)
self.stop_servers()
def test_download_random_access(self):
self.start_servers(**self.__dict__.copy())
# Create another image (with two deployer-defined properties)