Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(157)

Side by Side Diff: rietveld.py

Issue 1681333005: Raise exceptions properly on HTTP errors from OAuthRpcServer (which is only used on bots) (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: All 200s are successes Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | tests/rietveld_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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:
11 - Nobody else than issue owner can upload a patch set 11 - Nobody else than issue owner can upload a patch set
12 - Verifies the issue owner credentials when creating new issues 12 - Verifies the issue owner credentials when creating new issues
13 - A issue owner can't change once the issue is created 13 - A issue owner can't change once the issue is created
14 - A patch set cannot be modified 14 - A patch set cannot be modified
15 """ 15 """
16 16
17 import copy 17 import copy
18 import errno 18 import errno
19 import json 19 import json
20 import logging 20 import logging
21 import re 21 import re
22 import socket 22 import socket
23 import ssl 23 import ssl
24 import StringIO
24 import sys 25 import sys
25 import time 26 import time
26 import urllib 27 import urllib
27 import urllib2 28 import urllib2
28 import urlparse 29 import urlparse
29 30
30 import patch 31 import patch
31 32
32 from third_party import upload 33 from third_party import upload
33 import third_party.oauth2client.client as oa2client 34 import third_party.oauth2client.client as oa2client
(...skipping 368 matching lines...) Expand 10 before | Expand all | Expand 10 after
402 try: 403 try:
403 # Sadly, upload.py calls ErrorExit() which does a sys.exit(1) on HTTP 404 # Sadly, upload.py calls ErrorExit() which does a sys.exit(1) on HTTP
404 # 500 in AbstractRpcServer.Send(). 405 # 500 in AbstractRpcServer.Send().
405 old_error_exit = upload.ErrorExit 406 old_error_exit = upload.ErrorExit
406 def trap_http_500(msg): 407 def trap_http_500(msg):
407 """Converts an incorrect ErrorExit() call into a HTTPError exception.""" 408 """Converts an incorrect ErrorExit() call into a HTTPError exception."""
408 m = re.search(r'(50\d) Server Error', msg) 409 m = re.search(r'(50\d) Server Error', msg)
409 if m: 410 if m:
410 # Fake an HTTPError exception. Cheezy. :( 411 # Fake an HTTPError exception. Cheezy. :(
411 raise urllib2.HTTPError( 412 raise urllib2.HTTPError(
412 request_path, int(m.group(1)), msg, None, None) 413 request_path, int(m.group(1)), msg, None, StringIO.StringIO())
413 old_error_exit(msg) 414 old_error_exit(msg)
414 upload.ErrorExit = trap_http_500 415 upload.ErrorExit = trap_http_500
415 416
416 for retry in xrange(self._maxtries): 417 for retry in xrange(self._maxtries):
417 try: 418 try:
418 logging.debug('%s' % request_path) 419 logging.debug('%s' % request_path)
419 result = self.rpc_server.Send(request_path, **kwargs) 420 return self.rpc_server.Send(request_path, **kwargs)
420 # Sometimes GAE returns a HTTP 200 but with HTTP 500 as the content.
421 # How nice.
422 return result
423 except urllib2.HTTPError, e: 421 except urllib2.HTTPError, e:
424 if retry >= (self._maxtries - 1): 422 if retry >= (self._maxtries - 1):
425 raise 423 raise
426 flake_codes = [500, 502, 503] 424 flake_codes = {500, 502, 503}
427 if retry_on_404: 425 if retry_on_404:
428 flake_codes.append(404) 426 flake_codes.add(404)
429 if e.code not in flake_codes: 427 if e.code not in flake_codes:
430 raise 428 raise
431 except urllib2.URLError, e: 429 except urllib2.URLError, e:
432 if retry >= (self._maxtries - 1): 430 if retry >= (self._maxtries - 1):
433 raise 431 raise
434 432
435 def is_transient(): 433 def is_transient():
436 # The idea here is to retry if the error isn't permanent. 434 # The idea here is to retry if the error isn't permanent.
437 # Unfortunately, there are so many different possible errors, 435 # Unfortunately, there are so many different possible errors,
438 # that we end up enumerating those that are known to us to be 436 # that we end up enumerating those that are known to us to be
439 # transient. 437 # transient.
440 # The reason can be a string or another exception, e.g., 438 # The reason can be a string or another exception, e.g.,
441 # socket.error or whatever else. 439 # socket.error or whatever else.
442 reason_as_str = str(e.reason) 440 reason_as_str = str(e.reason)
443 for retry_anyway in [ 441 for retry_anyway in (
444 'Name or service not known', 442 'Name or service not known',
445 'EOF occurred in violation of protocol', 443 'EOF occurred in violation of protocol',
446 'timed out']: 444 'timed out'):
447 if retry_anyway in reason_as_str: 445 if retry_anyway in reason_as_str:
448 return True 446 return True
449 return False # Assume permanent otherwise. 447 return False # Assume permanent otherwise.
450 if not is_transient(): 448 if not is_transient():
451 raise 449 raise
452 except socket.error, e: 450 except socket.error, e:
453 if retry >= (self._maxtries - 1): 451 if retry >= (self._maxtries - 1):
454 raise 452 raise
455 if not 'timed out' in str(e): 453 if not 'timed out' in str(e):
456 raise 454 raise
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
521 extra_headers=None, 519 extra_headers=None,
522 **kwargs): 520 **kwargs):
523 """Send a POST or GET request to the server. 521 """Send a POST or GET request to the server.
524 522
525 Args: 523 Args:
526 request_path: path on the server to hit. This is concatenated with the 524 request_path: path on the server to hit. This is concatenated with the
527 value of 'host' provided to the constructor. 525 value of 'host' provided to the constructor.
528 payload: request is a POST if not None, GET otherwise 526 payload: request is a POST if not None, GET otherwise
529 timeout: in seconds 527 timeout: in seconds
530 extra_headers: (dict) 528 extra_headers: (dict)
529
530 Returns: the HTTP response body as a string
531
532 Raises:
533 urllib2.HTTPError
531 """ 534 """
532 # This method signature should match upload.py:AbstractRpcServer.Send() 535 # This method signature should match upload.py:AbstractRpcServer.Send()
533 method = 'GET' 536 method = 'GET'
534 537
535 headers = self.extra_headers.copy() 538 headers = self.extra_headers.copy()
536 headers.update(extra_headers or {}) 539 headers.update(extra_headers or {})
537 540
538 if payload is not None: 541 if payload is not None:
539 method = 'POST' 542 method = 'POST'
540 headers['Content-Type'] = content_type 543 headers['Content-Type'] = content_type
541 544
542 prev_timeout = self._http.timeout 545 prev_timeout = self._http.timeout
543 try: 546 try:
544 if timeout: 547 if timeout:
545 self._http.timeout = timeout 548 self._http.timeout = timeout
546 # TODO(pgervais) implement some kind of retry mechanism (see upload.py).
547 url = self.host + request_path 549 url = self.host + request_path
548 if kwargs: 550 if kwargs:
549 url += "?" + urllib.urlencode(kwargs) 551 url += "?" + urllib.urlencode(kwargs)
550 552
551 # This weird loop is there to detect when the OAuth2 token has expired. 553 # This weird loop is there to detect when the OAuth2 token has expired.
552 # This is specific to appengine *and* rietveld. It relies on the 554 # This is specific to appengine *and* rietveld. It relies on the
553 # assumption that a 302 is triggered only by an expired OAuth2 token. This 555 # assumption that a 302 is triggered only by an expired OAuth2 token. This
554 # prevents any usage of redirections in pages accessed this way. 556 # prevents any usage of redirections in pages accessed this way.
555 557
556 # This variable is used to make sure the following loop runs only twice. 558 # This variable is used to make sure the following loop runs only twice.
557 redirect_caught = False 559 redirect_caught = False
558 while True: 560 while True:
559 try: 561 try:
560 ret = self._http.request(url, 562 ret = self._http.request(url,
561 method=method, 563 method=method,
562 body=payload, 564 body=payload,
563 headers=headers, 565 headers=headers,
564 redirections=0) 566 redirections=0)
565 except httplib2.RedirectLimit: 567 except httplib2.RedirectLimit:
566 if redirect_caught or method != 'GET': 568 if redirect_caught or method != 'GET':
567 logging.error('Redirection detected after logging in. Giving up.') 569 logging.error('Redirection detected after logging in. Giving up.')
568 raise 570 raise
569 redirect_caught = True 571 redirect_caught = True
570 logging.debug('Redirection detected. Trying to log in again...') 572 logging.debug('Redirection detected. Trying to log in again...')
571 self.creds.access_token = None 573 self.creds.access_token = None
572 continue 574 continue
573 break 575 break
574 576
577 if ret[0].status >= 300:
578 raise urllib2.HTTPError(
579 request_path, int(ret[0]['status']), ret[1], None,
580 StringIO.StringIO())
581
575 return ret[1] 582 return ret[1]
576 583
577 finally: 584 finally:
578 self._http.timeout = prev_timeout 585 self._http.timeout = prev_timeout
579 586
580 587
581 class JwtOAuth2Rietveld(Rietveld): 588 class JwtOAuth2Rietveld(Rietveld):
582 """Access to Rietveld using OAuth authentication. 589 """Access to Rietveld using OAuth authentication.
583 590
584 This class is supposed to be used only by bots, since this kind of 591 This class is supposed to be used only by bots, since this kind of
(...skipping 152 matching lines...) Expand 10 before | Expand all | Expand 10 after
737 self, issue, patchset, reason, clobber, revision, builders_and_tests, 744 self, issue, patchset, reason, clobber, revision, builders_and_tests,
738 master=None, category='cq'): 745 master=None, category='cq'):
739 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % 746 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' %
740 (builders_and_tests, issue)) 747 (builders_and_tests, issue))
741 748
742 def trigger_distributed_try_jobs( # pylint:disable=R0201 749 def trigger_distributed_try_jobs( # pylint:disable=R0201
743 self, issue, patchset, reason, clobber, revision, masters, 750 self, issue, patchset, reason, clobber, revision, masters,
744 category='cq'): 751 category='cq'):
745 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' % 752 logging.info('ReadOnlyRietveld: triggering try jobs %r for issue %d' %
746 (masters, issue)) 753 (masters, issue))
OLDNEW
« no previous file with comments | « no previous file | tests/rietveld_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698