Merge "Add retry support to pecan"
This commit is contained in:
commit
f3eb620ebf
@ -13,27 +13,76 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
import functools
|
||||
|
||||
from neutron_lib import constants
|
||||
import pecan
|
||||
from pecan import request
|
||||
|
||||
from neutron.api.v2 import attributes as api_attributes
|
||||
from neutron.db import api as db_api
|
||||
from neutron import manager
|
||||
|
||||
# Utility functions for Pecan controllers.
|
||||
|
||||
|
||||
def expose(*args, **kwargs):
|
||||
class Fakecode(object):
|
||||
co_varnames = ()
|
||||
|
||||
|
||||
def _composed(*decorators):
|
||||
"""Takes a list of decorators and returns a single decorator."""
|
||||
|
||||
def final_decorator(f):
|
||||
for d in decorators:
|
||||
# workaround for pecan bug that always assumes decorators
|
||||
# have a __code__ attr
|
||||
if not hasattr(d, '__code__'):
|
||||
setattr(d, '__code__', Fakecode())
|
||||
f = d(f)
|
||||
return f
|
||||
return final_decorator
|
||||
|
||||
|
||||
def _protect_original_resources(f):
|
||||
"""Wrapper to ensure that mutated resources are discarded on retries."""
|
||||
|
||||
@functools.wraps(f)
|
||||
def wrapped(*args, **kwargs):
|
||||
ctx = request.context
|
||||
if 'resources' in ctx:
|
||||
orig = ctx.get('protected_resources')
|
||||
if not orig:
|
||||
# this is the first call so we just take the whole reference
|
||||
ctx['protected_resources'] = ctx['resources']
|
||||
# TODO(blogan): Once bug 157751 is fixed and released in
|
||||
# neutron-lib this memo will no longer be needed. This is just
|
||||
# quick way to not depend on a release of neutron-lib.
|
||||
# The version that has that bug fix will need to be updated in
|
||||
# neutron-lib.
|
||||
memo = {id(constants.ATTR_NOT_SPECIFIED):
|
||||
constants.ATTR_NOT_SPECIFIED}
|
||||
ctx['resources'] = copy.deepcopy(ctx['protected_resources'],
|
||||
memo=memo)
|
||||
return f(*args, **kwargs)
|
||||
return wrapped
|
||||
|
||||
|
||||
def _pecan_generator_wrapper(func, *args, **kwargs):
|
||||
"""Helper function so we don't have to specify json for everything."""
|
||||
kwargs.setdefault('content_type', 'application/json')
|
||||
kwargs.setdefault('template', 'json')
|
||||
return pecan.expose(*args, **kwargs)
|
||||
return _composed(_protect_original_resources, db_api.retry_db_errors,
|
||||
func(*args, **kwargs))
|
||||
|
||||
|
||||
def expose(*args, **kwargs):
|
||||
return _pecan_generator_wrapper(pecan.expose, *args, **kwargs)
|
||||
|
||||
|
||||
def when(index, *args, **kwargs):
|
||||
"""Helper function so we don't have to specify json for everything."""
|
||||
kwargs.setdefault('content_type', 'application/json')
|
||||
kwargs.setdefault('template', 'json')
|
||||
return index.when(*args, **kwargs)
|
||||
return _pecan_generator_wrapper(index.when, *args, **kwargs)
|
||||
|
||||
|
||||
class NeutronPecanController(object):
|
||||
|
@ -15,6 +15,7 @@ from collections import namedtuple
|
||||
import mock
|
||||
from neutron_lib import constants as n_const
|
||||
from oslo_config import cfg
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_policy import policy as oslo_policy
|
||||
from oslo_serialization import jsonutils
|
||||
import pecan
|
||||
@ -268,6 +269,26 @@ class TestResourceController(TestRootController):
|
||||
headers={'X-Project-Id': 'tenid'})
|
||||
self.assertEqual(response.status_int, 201)
|
||||
|
||||
def test_post_with_retry(self):
|
||||
self._create_failed = False
|
||||
orig = self.plugin.create_port
|
||||
|
||||
def new_create(*args, **kwargs):
|
||||
if not self._create_failed:
|
||||
self._create_failed = True
|
||||
raise db_exc.RetryRequest(ValueError())
|
||||
return orig(*args, **kwargs)
|
||||
|
||||
with mock.patch.object(self.plugin, 'create_port',
|
||||
new=new_create):
|
||||
response = self.app.post_json(
|
||||
'/v2.0/ports.json',
|
||||
params={'port': {'network_id': self.port['network_id'],
|
||||
'admin_state_up': True,
|
||||
'tenant_id': 'tenid'}},
|
||||
headers={'X-Project-Id': 'tenid'})
|
||||
self.assertEqual(201, response.status_int)
|
||||
|
||||
def test_put(self):
|
||||
response = self.app.put_json('/v2.0/ports/%s.json' % self.port['id'],
|
||||
params={'port': {'name': 'test'}},
|
||||
|
Loading…
x
Reference in New Issue
Block a user