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 |