Don't return content when we set HTTP 204
Pecan json renderer converts a None response from a controller into a string 'null' in the HTTP response. This directly conflicts with us setting an HTTP 204 in the response to HTTP DELETE calls. The legacy API did not return data on HTTP DELETE calls. The result of saying we were providing no content and then providing content was strange behavior with Apache2 mod_proxy (the way devstack deploys Neutron) where connections would be unusable after a DELETE call. Since mod_proxy re-uses connections, this would cause long pauses until connection timeouts occurred. Change-Id: Ie2fd9108f7b8a60c2574dab31b586ee43fd2a789
This commit is contained in:
parent
c130caec75
commit
d67a96664d
|
@ -19,7 +19,6 @@ from oslo_config import cfg
|
|||
from oslo_utils import importutils
|
||||
import pecan
|
||||
from pecan import request
|
||||
from pecan import response
|
||||
|
||||
from neutron._i18n import _
|
||||
from neutron.api.v2 import attributes
|
||||
|
@ -112,12 +111,11 @@ class QuotaController(utils.NeutronPecanController):
|
|||
neutron_context, self._tenant_id, key, value)
|
||||
return get_tenant_quotas(self._tenant_id, self._driver)
|
||||
|
||||
@utils.when(index, method='DELETE')
|
||||
@utils.when_delete(index)
|
||||
def delete(self):
|
||||
neutron_context = request.context.get('neutron_context')
|
||||
self._driver.delete_tenant_quota(neutron_context,
|
||||
self._tenant_id)
|
||||
response.status = 204
|
||||
|
||||
@utils.when(index, method='POST')
|
||||
def not_supported(self):
|
||||
|
|
|
@ -69,10 +69,8 @@ class ItemController(utils.NeutronPecanController):
|
|||
updater_args.append(data)
|
||||
return {self.resource: self.plugin_updater(*updater_args)}
|
||||
|
||||
@utils.when(index, method='DELETE')
|
||||
@utils.when_delete(index)
|
||||
def delete(self):
|
||||
# TODO(kevinbenton): setting code could be in a decorator
|
||||
pecan.response.status = 204
|
||||
neutron_context = request.context['neutron_context']
|
||||
deleter_args = [neutron_context, self.item]
|
||||
if 'parent_id' in request.context:
|
||||
|
|
|
@ -87,6 +87,34 @@ def when(index, *args, **kwargs):
|
|||
return _pecan_generator_wrapper(index.when, *args, **kwargs)
|
||||
|
||||
|
||||
def when_delete(index, *args, **kwargs):
|
||||
kwargs['method'] = 'DELETE'
|
||||
deco = _pecan_generator_wrapper(index.when, *args, **kwargs)
|
||||
return _composed(_set_del_code, deco)
|
||||
|
||||
|
||||
def _set_del_code(f):
|
||||
"""Handle logic of disabling json templating engine and setting HTTP code.
|
||||
|
||||
We return 204 on delete without content. However, pecan defaults empty
|
||||
responses with the json template engine to 'null', which is not empty
|
||||
content. This breaks connection re-use for some clients due to the
|
||||
inconsistency. So we need to detect when there is no response and
|
||||
disable the json templating engine.
|
||||
See https://github.com/pecan/pecan/issues/72
|
||||
"""
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapped(*args, **kwargs):
|
||||
f(*args, **kwargs)
|
||||
pecan.response.status = 204
|
||||
pecan.override_template(None)
|
||||
# NOTE(kevinbenton): we are explicitly not returning the DELETE
|
||||
# response from the controller because that is the legacy Neutron
|
||||
# API behavior.
|
||||
return wrapped
|
||||
|
||||
|
||||
class NeutronPecanController(object):
|
||||
|
||||
LIST = 'list'
|
||||
|
@ -214,11 +242,10 @@ class ShimItemController(NeutronPecanController):
|
|||
def index(self):
|
||||
pecan.abort(405)
|
||||
|
||||
@when(index, method='DELETE')
|
||||
@when_delete(index)
|
||||
def delete(self):
|
||||
if not self.controller_delete:
|
||||
pecan.abort(405)
|
||||
pecan.response.status = 204
|
||||
shim_request = ShimRequest(request.context['neutron_context'])
|
||||
uri_identifiers = request.context['uri_identifiers']
|
||||
return self.controller_delete(shim_request, self.item,
|
||||
|
|
|
@ -229,6 +229,7 @@ class TestQuotasController(test_functional.PecanFunctionalTest):
|
|||
response = self.app.delete(url, headers={'X-Project-Id': 'admin',
|
||||
'X-Roles': 'admin'})
|
||||
self.assertEqual(204, response.status_int)
|
||||
self.assertFalse(response.body)
|
||||
# As DELETE does not return a body we need another GET
|
||||
response = self.app.get(url, headers={'X-Project-Id': 'foo'})
|
||||
self.assertEqual(200, response.status_int)
|
||||
|
@ -261,6 +262,7 @@ class TestQuotasController(test_functional.PecanFunctionalTest):
|
|||
response = self.app.delete(url, headers={'X-Project-Id': 'admin',
|
||||
'X-Roles': 'admin'})
|
||||
self.assertEqual(204, response.status_int)
|
||||
self.assertFalse(response.body)
|
||||
response = self.app.get(self.base_url,
|
||||
headers={'X-Project-Id': 'admin',
|
||||
'X-Roles': 'admin'})
|
||||
|
@ -388,6 +390,7 @@ class TestResourceController(TestRootController):
|
|||
response = self.app.delete('/v2.0/ports/%s.json' % self.port['id'],
|
||||
headers={'X-Project-Id': 'tenid'})
|
||||
self.assertEqual(response.status_int, 204)
|
||||
self.assertFalse(response.body)
|
||||
|
||||
def test_plugin_initialized(self):
|
||||
self.assertIsNotNone(manager.NeutronManager._instance)
|
||||
|
@ -819,6 +822,7 @@ class TestL3AgentShimControllers(test_functional.PecanFunctionalTest):
|
|||
'/v2.0/agents/%(a)s/l3-routers/%(n)s.json' % {
|
||||
'a': self.agent.id, 'n': self.router['id']}, headers=headers)
|
||||
self.assertEqual(204, response.status_int)
|
||||
self.assertFalse(response.body)
|
||||
response = self.app.get(
|
||||
'/v2.0/routers/%s/l3-agents.json' % self.router['id'],
|
||||
headers=headers)
|
||||
|
|
Loading…
Reference in New Issue