Merge lp:~rvb/maas/retry-bug-1398082 into lp:~maas-committers/maas/trunk

Proposed by Raphaël Badin
Status: Merged
Approved by: Raphaël Badin
Approved revision: no longer in the source branch.
Merged at revision: 3397
Proposed branch: lp:~rvb/maas/retry-bug-1398082
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 89 lines (+42/-3)
2 files modified
src/maasserver/utils/tests/test_views.py (+14/-0)
src/maasserver/utils/views.py (+28/-3)
To merge this branch: bzr merge lp:~rvb/maas/retry-bug-1398082
Reviewer Review Type Date Requested Status
Graham Binns (community) Approve
Review via email: mp+243372@code.launchpad.net

Commit message

Fix the retry code so that it copes with API views: for these views the request is the second argument passed to the view and not the first as with UI views.

Description of the change

extract_request() is very conservative (i.e. it doesn't return the first http.HttpRequest object it finds in the arguments) because the nature and the ordering of the arguments passed to views is very well defined so I figured it would be much better to be on the safe side and raise assertion errors if we ever encounter a case we didn't plan for.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

LGTM. Nice to have your Django-wisdom back :)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/utils/tests/test_views.py'
2--- src/maasserver/utils/tests/test_views.py 2014-11-12 23:32:25 +0000
3+++ src/maasserver/utils/tests/test_views.py 2014-12-02 10:25:00 +0000
4@@ -205,6 +205,20 @@
5
6 self.assertThat(response, Is(sentinel.response))
7
8+ def test__retries_API_calls(self):
9+
10+ def api_view(handler, request, attempt=count(1)):
11+ if next(attempt) == 3:
12+ return sentinel.response
13+ else:
14+ request_transaction_retry()
15+
16+ retry_view = views.RetryView.make(api_view, 4)
17+
18+ response = retry_view(factory.make_name(), HttpRequest())
19+
20+ self.assertThat(response, Is(sentinel.response))
21+
22 def test__make_does_not_double_wrap(self):
23 view = lambda: None
24 retry_view = views.RetryView.make(view)
25
26=== modified file 'src/maasserver/utils/views.py'
27--- src/maasserver/utils/views.py 2014-11-12 23:32:03 +0000
28+++ src/maasserver/utils/views.py 2014-12-02 10:25:00 +0000
29@@ -20,6 +20,7 @@
30 from inspect import getmodule
31 from textwrap import dedent
32
33+from django import http
34 from django.conf.urls import url
35 from django.db import transaction
36 from maasserver.utils.orm import is_serialization_failure
37@@ -89,7 +90,30 @@
38 }
39 )
40
41- def __call__(self, request, *args, **kwargs):
42+ def extract_request(self, *args):
43+ """Extract the request object from the arguments passed to a view.
44+
45+ The position of the request in the list of arguments is different
46+ depending on the nature of the view: if the view is a UI view it's
47+ the first argument and if the view is a piston-based API view, it's
48+ the second argument.
49+ """
50+ if len(args) < 1:
51+ assert False, (
52+ "Couldn't find request in arguments (no argument).")
53+ elif isinstance(args[0], http.HttpRequest):
54+ # This is a UI view, the request is the first argument.
55+ return args[0]
56+ if len(args) < 2:
57+ assert False, (
58+ "Couldn't find request in arguments (no second argument).")
59+ elif isinstance(args[1], http.HttpRequest):
60+ # This is an API view, the request is the second argument (the
61+ # handler is the first argument).
62+ return args[1]
63+ assert False, "Couldn't find request in arguments."
64+
65+ def __call__(self, *args, **kwargs):
66 """Invoke the wrapped view with retry logic.
67
68 :returns: The response from `self.atomic_view`, or, if the number of
69@@ -97,9 +121,10 @@
70 """
71 for attempt in xrange(1, self.retries + 1):
72 try:
73- return self.atomic_view(request, *args, **kwargs)
74+ return self.atomic_view(*args, **kwargs)
75 except Exception as e:
76 if is_serialization_failure(e):
77+ request = self.extract_request(*args)
78 log_retry(request, attempt)
79 reset_request(request)
80 continue
81@@ -107,7 +132,7 @@
82 raise
83 else:
84 # Last chance, unadorned by retry logic.
85- return self.atomic_view(request, *args, **kwargs)
86+ return self.atomic_view(*args, **kwargs)
87
88
89 def retry_url(regex, view, kwargs=None, name=None, prefix=''):