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:
Kevin Benton 2016-12-19 14:12:18 -08:00
parent c130caec75
commit d67a96664d
4 changed files with 35 additions and 8 deletions

View File

@ -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):

View File

@ -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:

View File

@ -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,

View File

@ -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)