Merge lp:~allenap/maas/retry-context-not-active into lp:~maas-committers/maas/trunk

Proposed by Gavin Panella
Status: Merged
Approved by: Gavin Panella
Approved revision: no longer in the source branch.
Merged at revision: 5823
Proposed branch: lp:~allenap/maas/retry-context-not-active
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 173 lines (+90/-19)
3 files modified
src/maasserver/utils/orm.py (+1/-0)
src/maasserver/utils/tests/test_views.py (+63/-0)
src/maasserver/utils/views.py (+26/-19)
To merge this branch: bzr merge lp:~allenap/maas/retry-context-not-active
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+320083@code.launchpad.net

Commit message

Use the retry context in web requests too.

Previously the retry context was only activated when using the @transactional decorator.

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good.

I am guessing this issue only popped up because of the changes you made to the static IP address allocation at the beginning of the cycle?

review: Approve
Revision history for this message
Gavin Panella (allenap) wrote :

> I am guessing this issue only popped up because of the changes you
> made to the static IP address allocation at the beginning of the
> cycle?

Yeah :-/

Thanks for the review.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/utils/orm.py'
--- src/maasserver/utils/orm.py 2016-12-07 12:46:14 +0000
+++ src/maasserver/utils/orm.py 2017-03-16 16:43:12 +0000
@@ -24,6 +24,7 @@
24 'post_commit_do',24 'post_commit_do',
25 'psql_array',25 'psql_array',
26 'request_transaction_retry',26 'request_transaction_retry',
27 'retry_context',
27 'retry_on_retryable_failure',28 'retry_on_retryable_failure',
28 'savepoint',29 'savepoint',
29 'TotallyDisconnected',30 'TotallyDisconnected',
3031
=== modified file 'src/maasserver/utils/tests/test_views.py'
--- src/maasserver/utils/tests/test_views.py 2017-01-28 00:51:47 +0000
+++ src/maasserver/utils/tests/test_views.py 2017-03-16 16:43:12 +0000
@@ -7,6 +7,7 @@
77
8import http.client8import http.client
9import io9import io
10from itertools import count
10import logging11import logging
11from random import (12from random import (
12 randint,13 randint,
@@ -40,6 +41,8 @@
40from maasserver.utils.orm import (41from maasserver.utils.orm import (
41 make_deadlock_failure,42 make_deadlock_failure,
42 post_commit_hooks,43 post_commit_hooks,
44 request_transaction_retry,
45 retry_context,
43 validate_in_transaction,46 validate_in_transaction,
44)47)
45from maasserver.utils.views import HttpResponseConflict48from maasserver.utils.views import HttpResponseConflict
@@ -386,6 +389,49 @@
386 response.reason_phrase,389 response.reason_phrase,
387 Equals(REASON_PHRASES[http.client.CONFLICT]))390 Equals(REASON_PHRASES[http.client.CONFLICT]))
388391
392 def test__get_response_prepare_retry_context_before_each_try(self):
393
394 class ObserveContext:
395
396 def __init__(self, callback, name):
397 self.callback = callback
398 self.name = name
399
400 def __enter__(self):
401 self.callback("%s:enter" % self.name)
402
403 def __exit__(self, *exc_info):
404 self.callback("%s:exit" % self.name)
405
406 counter = count(1)
407 calls = []
408
409 def get_response_and_retry(request):
410 calls.append("get_response")
411 request_transaction_retry(ObserveContext(
412 calls.append, "context#%d" % next(counter)))
413
414 get_response = self.patch(WSGIHandler, "get_response")
415 get_response.side_effect = get_response_and_retry
416
417 reset_request = self.patch_autospec(views, "reset_request")
418 reset_request.side_effect = lambda request: request
419
420 request = make_request()
421 request.path = factory.make_name("path")
422 handler = views.WebApplicationHandler(3)
423 handler.get_response(request)
424
425 self.assertThat(calls, Equals([
426 "get_response", # 1st attempt.
427 "context#1:enter", # Retry requested, enter 1st new context.
428 "get_response", # 2nd attempt.
429 "context#2:enter", # Retry requested, enter 2nd new context.
430 "get_response", # 3rd attempt, and last.
431 "context#2:exit", # Exit 2nd context.
432 "context#1:exit", # Exit 1st context.
433 ]))
434
389 def test__get_response_logs_retry_and_resets_request(self):435 def test__get_response_logs_retry_and_resets_request(self):
390 timeout = 1.0 + (random() * 99)436 timeout = 1.0 + (random() * 99)
391 handler = views.WebApplicationHandler(2, timeout)437 handler = views.WebApplicationHandler(2, timeout)
@@ -430,6 +476,23 @@
430476
431 self.assertThat(get_response, MockCalledOnceWith(request))477 self.assertThat(get_response, MockCalledOnceWith(request))
432478
479 def test__get_response_is_in_retry_context_in_transaction(self):
480 handler = views.WebApplicationHandler(2)
481
482 def check_retry_context_active(request):
483 self.assertThat(retry_context.active, Is(True))
484
485 get_response = self.patch(WSGIHandler, "get_response")
486 get_response.side_effect = check_retry_context_active
487
488 request = make_request()
489 request.path = factory.make_name("path")
490
491 self.assertThat(retry_context.active, Is(False))
492 handler.get_response(request)
493 self.assertThat(retry_context.active, Is(False))
494 self.assertThat(get_response, MockCalledOnceWith(request))
495
433 def test__get_response_restores_files_across_requests(self):496 def test__get_response_restores_files_across_requests(self):
434 handler = views.WebApplicationHandler(3)497 handler = views.WebApplicationHandler(3)
435 file_content = sample_binary_data498 file_content = sample_binary_data
436499
=== modified file 'src/maasserver/utils/views.py'
--- src/maasserver/utils/views.py 2016-10-21 06:19:34 +0000
+++ src/maasserver/utils/views.py 2017-03-16 16:43:12 +0000
@@ -23,6 +23,8 @@
23 gen_retry_intervals,23 gen_retry_intervals,
24 is_retryable_failure,24 is_retryable_failure,
25 post_commit_hooks,25 post_commit_hooks,
26 retry_context,
27 RetryTransaction,
26)28)
27from piston3.authentication import initialize_server_request29from piston3.authentication import initialize_server_request
28from piston3.models import Nonce30from piston3.models import Nonce
@@ -145,7 +147,9 @@
145 # Add it to the retry set if this response was caused by a147 # Add it to the retry set if this response was caused by a
146 # retryable failure.148 # retryable failure.
147 exc_type, exc_value, exc_traceback = exc_info149 exc_type, exc_value, exc_traceback = exc_info
148 if is_retryable_failure(exc_value):150 if isinstance(exc_value, RetryTransaction):
151 self.__retry.add(response)
152 elif is_retryable_failure(exc_value):
149 self.__retry.add(response)153 self.__retry.add(response)
150 else:154 else:
151 logger.error(155 logger.error(
@@ -220,21 +224,24 @@
220 retry_attempts = self.__retry_attempts224 retry_attempts = self.__retry_attempts
221 retry_set = self.__retry225 retry_set = self.__retry
222226
223 for attempt in count(1):227 with retry_context:
224 response = get_response(request)228 for attempt in count(1):
225 if response in retry_set:229 retry_context.prepare()
226 elapsed, remaining, wait = next(retry_details)230 response = get_response(request)
227 if attempt == retry_attempts or wait == 0:231 if response in retry_set:
228 # Time's up: this was the final attempt.232 elapsed, remaining, wait = next(retry_details)
229 log_final_failed_attempt(request, attempt, elapsed)233 if attempt == retry_attempts or wait == 0:
230 conflict_response = HttpResponseConflict(response)234 # Time's up: this was the final attempt.
231 conflict_response.render()235 log_final_failed_attempt(request, attempt, elapsed)
232 return conflict_response236 conflict_response = HttpResponseConflict(response)
233237 conflict_response.render()
234 # We'll retry after a brief interlude.238 return conflict_response
235 log_failed_attempt(request, attempt, elapsed, remaining, wait)239 else:
236 delete_oauth_nonce(request)240 # We'll retry after a brief interlude.
237 request = reset_request(request)241 log_failed_attempt(
238 sleep(wait)242 request, attempt, elapsed, remaining, wait)
239 else:243 delete_oauth_nonce(request)
240 return response244 request = reset_request(request)
245 sleep(wait)
246 else:
247 return response