Chromium Code Reviews| 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 |