| OLD | NEW |
| 1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. | 1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """ | 5 """ |
| 6 Utilities for requesting information for a gerrit server via https. | 6 Utilities for requesting information for a gerrit server via https. |
| 7 | 7 |
| 8 https://gerrit-review.googlesource.com/Documentation/rest-api.html | 8 https://gerrit-review.googlesource.com/Documentation/rest-api.html |
| 9 """ | 9 """ |
| 10 | 10 |
| 11 import base64 | 11 import base64 |
| 12 import httplib | 12 import httplib |
| 13 import json | 13 import json |
| 14 import logging | 14 import logging |
| 15 import netrc | 15 import netrc |
| 16 import os | 16 import os |
| 17 import re |
| 17 import time | 18 import time |
| 18 import urllib | 19 import urllib |
| 19 from cStringIO import StringIO | 20 from cStringIO import StringIO |
| 20 | 21 |
| 21 try: | 22 try: |
| 22 NETRC = netrc.netrc() | 23 NETRC = netrc.netrc() |
| 23 except (IOError, netrc.NetrcParseError): | 24 except (IOError, netrc.NetrcParseError): |
| 24 NETRC = netrc.netrc(os.devnull) | 25 NETRC = netrc.netrc(os.devnull) |
| 25 LOGGER = logging.getLogger() | 26 LOGGER = logging.getLogger() |
| 26 TRY_LIMIT = 5 | 27 TRY_LIMIT = 5 |
| 27 | 28 |
| 28 # Controls the transport protocol used to communicate with gerrit. | 29 # Controls the transport protocol used to communicate with gerrit. |
| 29 # This is parameterized primarily to enable GerritTestCase. | 30 # This is parameterized primarily to enable GerritTestCase. |
| 30 GERRIT_PROTOCOL = 'https' | 31 GERRIT_PROTOCOL = 'https' |
| 31 | 32 |
| 32 | 33 |
| 33 class GerritError(Exception): | 34 class GerritError(Exception): |
| 34 """Exception class for errors commuicating with the gerrit-on-borg service.""" | 35 """Exception class for errors commuicating with the gerrit-on-borg service.""" |
| 35 def __init__(self, http_status, *args, **kwargs): | 36 def __init__(self, http_status, *args, **kwargs): |
| 36 super(GerritError, self).__init__(*args, **kwargs) | 37 super(GerritError, self).__init__(*args, **kwargs) |
| 37 self.http_status = http_status | 38 self.http_status = http_status |
| 38 self.message = '(%d) %s' % (self.http_status, self.message) | 39 self.message = '(%d) %s' % (self.http_status, self.message) |
| 39 | 40 |
| 40 | 41 |
| 42 class GerritAuthenticationError(GerritError): |
| 43 """Exception class for authentication errors during Gerrit communication.""" |
| 44 |
| 45 |
| 41 def _QueryString(param_dict, first_param=None): | 46 def _QueryString(param_dict, first_param=None): |
| 42 """Encodes query parameters in the key:val[+key:val...] format specified here: | 47 """Encodes query parameters in the key:val[+key:val...] format specified here: |
| 43 | 48 |
| 44 https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#lis
t-changes | 49 https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#lis
t-changes |
| 45 """ | 50 """ |
| 46 q = [urllib.quote(first_param)] if first_param else [] | 51 q = [urllib.quote(first_param)] if first_param else [] |
| 47 q.extend(['%s:%s' % (key, val) for key, val in param_dict.iteritems()]) | 52 q.extend(['%s:%s' % (key, val) for key, val in param_dict.iteritems()]) |
| 48 return '+'.join(q) | 53 return '+'.join(q) |
| 49 | 54 |
| 50 | 55 |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 108 expect_status: Success is indicated by this status in the response. | 113 expect_status: Success is indicated by this status in the response. |
| 109 ignore_404: For many requests, gerrit-on-borg will return 404 if the request | 114 ignore_404: For many requests, gerrit-on-borg will return 404 if the request |
| 110 doesn't match the database contents. In most such cases, we | 115 doesn't match the database contents. In most such cases, we |
| 111 want the API to return None rather than raise an Exception. | 116 want the API to return None rather than raise an Exception. |
| 112 Returns: A string buffer containing the connection's reply. | 117 Returns: A string buffer containing the connection's reply. |
| 113 """ | 118 """ |
| 114 | 119 |
| 115 sleep_time = 0.5 | 120 sleep_time = 0.5 |
| 116 for idx in range(TRY_LIMIT): | 121 for idx in range(TRY_LIMIT): |
| 117 response = conn.getresponse() | 122 response = conn.getresponse() |
| 123 |
| 124 # Check if this is an authentication issue. |
| 125 www_authenticate = response.getheader('www-authenticate') |
| 126 if (response.status in (httplib.UNAUTHORIZED, httplib.FOUND) and |
| 127 www_authenticate): |
| 128 auth_match = re.search('realm="([^"]+)"', www_authenticate, re.I) |
| 129 host = auth_match.group(1) if auth_match else conn.req_host |
| 130 reason = ('Authentication failed. Please make sure your .netrc file ' |
| 131 'has credentials for %s' % host) |
| 132 raise GerritAuthenticationError(response.status, reason) |
| 133 |
| 118 # If response.status < 500 then the result is final; break retry loop. | 134 # If response.status < 500 then the result is final; break retry loop. |
| 119 if response.status < 500: | 135 if response.status < 500: |
| 120 break | 136 break |
| 121 # A status >=500 is assumed to be a possible transient error; retry. | 137 # A status >=500 is assumed to be a possible transient error; retry. |
| 122 http_version = 'HTTP/%s' % ('1.1' if response.version == 11 else '1.0') | 138 http_version = 'HTTP/%s' % ('1.1' if response.version == 11 else '1.0') |
| 123 msg = ( | 139 msg = ( |
| 124 'A transient error occured while querying %s:\n' | 140 'A transient error occured while querying %s:\n' |
| 125 '%s %s %s\n' | 141 '%s %s %s\n' |
| 126 '%s %d %s' % ( | 142 '%s %d %s' % ( |
| 127 conn.host, conn.req_params['method'], conn.req_params['url'], | 143 conn.host, conn.req_params['method'], conn.req_params['url'], |
| (...skipping 302 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 430 username = review.get('email', jmsg.get('name', '')) | 446 username = review.get('email', jmsg.get('name', '')) |
| 431 raise GerritError(200, 'Unable to set %s label for user "%s"' | 447 raise GerritError(200, 'Unable to set %s label for user "%s"' |
| 432 ' on change %s.' % (label, username, change)) | 448 ' on change %s.' % (label, username, change)) |
| 433 jmsg = GetChangeCurrentRevision(host, change) | 449 jmsg = GetChangeCurrentRevision(host, change) |
| 434 if not jmsg: | 450 if not jmsg: |
| 435 raise GerritError( | 451 raise GerritError( |
| 436 200, 'Could not get review information for change "%s"' % change) | 452 200, 'Could not get review information for change "%s"' % change) |
| 437 elif jmsg[0]['current_revision'] != revision: | 453 elif jmsg[0]['current_revision'] != revision: |
| 438 raise GerritError(200, 'While resetting labels on change "%s", ' | 454 raise GerritError(200, 'While resetting labels on change "%s", ' |
| 439 'a new patchset was uploaded.' % change) | 455 'a new patchset was uploaded.' % change) |
| OLD | NEW |