OLD | NEW |
---|---|
1 # coding: utf-8 | 1 # coding: utf-8 |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
5 """Defines class Rietveld to easily access a rietveld instance. | 5 """Defines class Rietveld to easily access a rietveld instance. |
6 | 6 |
7 Security implications: | 7 Security implications: |
8 | 8 |
9 The following hypothesis are made: | 9 The following hypothesis are made: |
10 - Rietveld enforces: | 10 - Rietveld enforces: |
(...skipping 19 matching lines...) Expand all Loading... | |
30 import third_party.oauth2client.client as oa2client | 30 import third_party.oauth2client.client as oa2client |
31 from third_party import httplib2 | 31 from third_party import httplib2 |
32 | 32 |
33 # Appengine replies with 302 when authentication fails (sigh.) | 33 # Appengine replies with 302 when authentication fails (sigh.) |
34 oa2client.REFRESH_STATUS_CODES.append(302) | 34 oa2client.REFRESH_STATUS_CODES.append(302) |
35 upload.LOGGER.setLevel(logging.WARNING) # pylint: disable=E1103 | 35 upload.LOGGER.setLevel(logging.WARNING) # pylint: disable=E1103 |
36 | 36 |
37 | 37 |
38 class Rietveld(object): | 38 class Rietveld(object): |
39 """Accesses rietveld.""" | 39 """Accesses rietveld.""" |
40 def __init__(self, url, email, password, extra_headers=None): | 40 def __init__(self, url, email, password, extra_headers=None, maxtries=None): |
41 self.url = url.rstrip('/') | 41 self.url = url.rstrip('/') |
42 # Email and password are accessed by commit queue, keep them. | 42 # Email and password are accessed by commit queue, keep them. |
43 self.email = email | 43 self.email = email |
44 self.password = password | 44 self.password = password |
45 # TODO(maruel): It's not awesome but maybe necessary to retrieve the value. | 45 # TODO(maruel): It's not awesome but maybe necessary to retrieve the value. |
46 # It happens when the presubmit check is ran out of process, the cookie | 46 # It happens when the presubmit check is ran out of process, the cookie |
47 # needed to be recreated from the credentials. Instead, it should pass the | 47 # needed to be recreated from the credentials. Instead, it should pass the |
48 # email and the cookie. | 48 # email and the cookie. |
49 if email and password: | 49 if email and password: |
50 get_creds = lambda: (email, password) | 50 get_creds = lambda: (email, password) |
51 self.rpc_server = upload.HttpRpcServer( | 51 self.rpc_server = upload.HttpRpcServer( |
52 self.url, | 52 self.url, |
53 get_creds, | 53 get_creds, |
54 extra_headers=extra_headers or {}) | 54 extra_headers=extra_headers or {}) |
55 else: | 55 else: |
56 if email == '': | 56 if email == '': |
57 # If email is given as an empty string, then assume we want to make | 57 # If email is given as an empty string, then assume we want to make |
58 # requests that do not need authentication. Bypass authentication by | 58 # requests that do not need authentication. Bypass authentication by |
59 # setting the auth_function to None. | 59 # setting the auth_function to None. |
60 self.rpc_server = upload.HttpRpcServer(url, None) | 60 self.rpc_server = upload.HttpRpcServer(url, None) |
61 else: | 61 else: |
62 self.rpc_server = upload.GetRpcServer(url, email) | 62 self.rpc_server = upload.GetRpcServer(url, email) |
63 | 63 |
64 self._xsrf_token = None | 64 self._xsrf_token = None |
65 self._xsrf_token_time = None | 65 self._xsrf_token_time = None |
66 | 66 |
67 self._maxtries = maxtries or 40 | |
68 | |
67 def xsrf_token(self): | 69 def xsrf_token(self): |
68 if (not self._xsrf_token_time or | 70 if (not self._xsrf_token_time or |
69 (time.time() - self._xsrf_token_time) > 30*60): | 71 (time.time() - self._xsrf_token_time) > 30*60): |
70 self._xsrf_token_time = time.time() | 72 self._xsrf_token_time = time.time() |
71 self._xsrf_token = self.get( | 73 self._xsrf_token = self.get( |
72 '/xsrf_token', | 74 '/xsrf_token', |
73 extra_headers={'X-Requesting-XSRF-Token': '1'}) | 75 extra_headers={'X-Requesting-XSRF-Token': '1'}) |
74 return self._xsrf_token | 76 return self._xsrf_token |
75 | 77 |
76 def get_pending_issues(self): | 78 def get_pending_issues(self): |
(...skipping 329 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
406 def trap_http_500(msg): | 408 def trap_http_500(msg): |
407 """Converts an incorrect ErrorExit() call into a HTTPError exception.""" | 409 """Converts an incorrect ErrorExit() call into a HTTPError exception.""" |
408 m = re.search(r'(50\d) Server Error', msg) | 410 m = re.search(r'(50\d) Server Error', msg) |
409 if m: | 411 if m: |
410 # Fake an HTTPError exception. Cheezy. :( | 412 # Fake an HTTPError exception. Cheezy. :( |
411 raise urllib2.HTTPError( | 413 raise urllib2.HTTPError( |
412 request_path, int(m.group(1)), msg, None, None) | 414 request_path, int(m.group(1)), msg, None, None) |
413 old_error_exit(msg) | 415 old_error_exit(msg) |
414 upload.ErrorExit = trap_http_500 | 416 upload.ErrorExit = trap_http_500 |
415 | 417 |
416 maxtries = 40 | 418 for retry in xrange(self._maxtries): |
417 for retry in xrange(maxtries): | |
418 try: | 419 try: |
419 logging.debug('%s' % request_path) | 420 logging.debug('%s' % request_path) |
420 result = self.rpc_server.Send(request_path, **kwargs) | 421 result = self.rpc_server.Send(request_path, **kwargs) |
421 # Sometimes GAE returns a HTTP 200 but with HTTP 500 as the content. | 422 # Sometimes GAE returns a HTTP 200 but with HTTP 500 as the content. |
422 # How nice. | 423 # How nice. |
423 return result | 424 return result |
424 except urllib2.HTTPError, e: | 425 except urllib2.HTTPError, e: |
425 if retry >= (maxtries - 1): | 426 if retry >= (self._maxtries - 1): |
426 raise | 427 raise |
427 flake_codes = [500, 502, 503] | 428 flake_codes = [500, 502, 503] |
428 if retry_on_404: | 429 if retry_on_404: |
429 flake_codes.append(404) | 430 flake_codes.append(404) |
430 if e.code not in flake_codes: | 431 if e.code not in flake_codes: |
431 raise | 432 raise |
432 except urllib2.URLError, e: | 433 except urllib2.URLError, e: |
433 if retry >= (maxtries - 1): | 434 if retry >= (self._maxtries - 1): |
434 raise | 435 raise |
435 if (not 'Name or service not known' in e.reason and | 436 if (not 'Name or service not known' in e.reason and |
436 not 'EOF occurred in violation of protocol' in e.reason): | 437 not 'EOF occurred in violation of protocol' in e.reason): |
437 # Usually internal GAE flakiness. | 438 # Usually internal GAE flakiness. |
438 raise | 439 raise |
439 except ssl.SSLError, e: | 440 except ssl.SSLError, e: |
440 if retry >= (maxtries - 1): | 441 if retry >= (self._maxtries - 1): |
441 raise | 442 raise |
442 if not 'timed out' in str(e): | 443 if not 'timed out' in str(e): |
443 raise | 444 raise |
444 # If reaching this line, loop again. Uses a small backoff. | 445 # If reaching this line, loop again. Uses a small backoff. |
445 time.sleep(min(10, 1+retry*2)) | 446 time.sleep(min(10, 1+retry*2)) |
446 finally: | 447 finally: |
447 upload.ErrorExit = old_error_exit | 448 upload.ErrorExit = old_error_exit |
448 | 449 |
449 # DEPRECATED. | 450 # DEPRECATED. |
450 Send = get | 451 Send = get |
(...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
568 This class is supposed to be used only by bots, since this kind of | 569 This class is supposed to be used only by bots, since this kind of |
569 access is restricted to service accounts. | 570 access is restricted to service accounts. |
570 """ | 571 """ |
571 # The parent__init__ is not called on purpose. | 572 # The parent__init__ is not called on purpose. |
572 # pylint: disable=W0231 | 573 # pylint: disable=W0231 |
573 def __init__(self, | 574 def __init__(self, |
574 url, | 575 url, |
575 client_email, | 576 client_email, |
576 client_private_key_file, | 577 client_private_key_file, |
577 private_key_password=None, | 578 private_key_password=None, |
578 extra_headers=None): | 579 extra_headers=None, |
580 maxtries=None): | |
579 | 581 |
580 # These attributes are accessed by commit queue. Keep them. | 582 # These attributes are accessed by commit queue. Keep them. |
581 self.email = client_email | 583 self.email = client_email |
582 self.private_key_file = client_private_key_file | 584 self.private_key_file = client_private_key_file |
583 | 585 |
584 if private_key_password is None: # '' means 'empty password' | 586 if private_key_password is None: # '' means 'empty password' |
585 private_key_password = 'notasecret' | 587 private_key_password = 'notasecret' |
586 | 588 |
587 self.url = url.rstrip('/') | 589 self.url = url.rstrip('/') |
588 bot_url = self.url + '/bots' | 590 bot_url = self.url + '/bots' |
589 | 591 |
590 with open(client_private_key_file, 'rb') as f: | 592 with open(client_private_key_file, 'rb') as f: |
591 client_private_key = f.read() | 593 client_private_key = f.read() |
592 logging.info('Using OAuth login: %s' % client_email) | 594 logging.info('Using OAuth login: %s' % client_email) |
593 self.rpc_server = OAuthRpcServer(bot_url, | 595 self.rpc_server = OAuthRpcServer(bot_url, |
594 client_email, | 596 client_email, |
595 client_private_key, | 597 client_private_key, |
596 private_key_password=private_key_password, | 598 private_key_password=private_key_password, |
597 extra_headers=extra_headers or {}) | 599 extra_headers=extra_headers or {}) |
598 self._xsrf_token = None | 600 self._xsrf_token = None |
599 self._xsrf_token_time = None | 601 self._xsrf_token_time = None |
600 | 602 |
603 self._maxtries = 40 or maxtries | |
Michael Achenbach
2015/02/19 11:20:33
Hmm, sad that there's no super call.
Jens Widell
2015/02/19 11:32:39
This is the same as "self._maxtries = 40". Did you
Michael Achenbach
2015/02/19 11:33:56
Ups, totally missed that. Good catch!
Paweł Hajdan Jr.
2015/02/19 11:49:04
Good catch, fixed in https://codereview.chromium.o
| |
604 | |
601 | 605 |
602 class CachingRietveld(Rietveld): | 606 class CachingRietveld(Rietveld): |
603 """Caches the common queries. | 607 """Caches the common queries. |
604 | 608 |
605 Not to be used in long-standing processes, like the commit queue. | 609 Not to be used in long-standing processes, like the commit queue. |
606 """ | 610 """ |
607 def __init__(self, *args, **kwargs): | 611 def __init__(self, *args, **kwargs): |
608 super(CachingRietveld, self).__init__(*args, **kwargs) | 612 super(CachingRietveld, self).__init__(*args, **kwargs) |
609 self._cache = {} | 613 self._cache = {} |
610 | 614 |
(...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
725 self, issue, patchset, reason, clobber, revision, builders_and_tests, | 729 self, issue, patchset, reason, clobber, revision, builders_and_tests, |
726 master=None, category='cq'): | 730 master=None, category='cq'): |
727 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % | 731 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % |
728 (builders_and_tests, issue)) | 732 (builders_and_tests, issue)) |
729 | 733 |
730 def trigger_distributed_try_jobs( # pylint:disable=R0201 | 734 def trigger_distributed_try_jobs( # pylint:disable=R0201 |
731 self, issue, patchset, reason, clobber, revision, masters, | 735 self, issue, patchset, reason, clobber, revision, masters, |
732 category='cq'): | 736 category='cq'): |
733 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % | 737 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % |
734 (masters, issue)) | 738 (masters, issue)) |
OLD | NEW |