blob: 9c4f5d5f007fe71aeb39f306b939ed5184696e5f [file] [log] [blame]
jamesrencd7a81a2010-04-21 20:39:08 +00001import copy, getpass, logging, pprint, re, urllib, urlparse
showardf828c772010-01-25 21:49:42 +00002import httplib2
jamesrencd7a81a2010-04-21 20:39:08 +00003from django.utils import datastructures, simplejson
jamesren5a13d8d2010-02-12 00:46:09 +00004from autotest_lib.frontend.afe import rpc_client_lib
jamesren93270d12010-04-09 20:44:33 +00005from autotest_lib.client.common_lib import utils
showardf828c772010-01-25 21:49:42 +00006
7
jamesren5a13d8d2010-02-12 00:46:09 +00008_request_headers = {}
9
10
11def _get_request_headers(uri):
12 server = urlparse.urlparse(uri)[0:2]
13 if server in _request_headers:
14 return _request_headers[server]
15
16 headers = rpc_client_lib.authorization_headers(getpass.getuser(), uri)
17 headers['Content-Type'] = 'application/json'
18
19 _request_headers[server] = headers
20 return headers
showardf828c772010-01-25 21:49:42 +000021
22
jamesren93270d12010-04-09 20:44:33 +000023def _clear_request_headers(uri):
24 server = urlparse.urlparse(uri)[0:2]
25 if server in _request_headers:
26 del _request_headers[server]
27
28
29def _site_verify_response_default(headers, response_body):
30 return headers['status'] != '401'
31
32
showardf828c772010-01-25 21:49:42 +000033class RestClientError(Exception):
34 pass
35
36
37class ClientError(Exception):
38 pass
39
40
41class ServerError(Exception):
42 pass
43
44
45class Response(object):
46 def __init__(self, httplib_response, httplib_content):
47 self.status = int(httplib_response['status'])
48 self.headers = httplib_response
49 self.entity_body = httplib_content
50
51
52 def decoded_body(self):
53 return simplejson.loads(self.entity_body)
54
55
56 def __str__(self):
57 return '\n'.join([str(self.status), self.entity_body])
58
59
60class Resource(object):
jamesrencd7a81a2010-04-21 20:39:08 +000061 def __init__(self, representation_dict, http):
62 self._http = http
showardf828c772010-01-25 21:49:42 +000063 assert 'href' in representation_dict
64 for key, value in representation_dict.iteritems():
65 setattr(self, str(key), value)
66
67
68 def __repr__(self):
69 return 'Resource(%r)' % self._representation()
70
71
72 def pprint(self):
73 # pretty-print support for debugging/interactive use
74 pprint.pprint(self._representation())
75
76
77 @classmethod
jamesrencd7a81a2010-04-21 20:39:08 +000078 def load(cls, uri, http=None):
79 if not http:
80 http = httplib2.Http()
81 directory = cls({'href': uri}, http)
showardf828c772010-01-25 21:49:42 +000082 return directory.get()
83
84
85 def _read_representation(self, value):
86 # recursively convert representation dicts to Resource objects
87 if isinstance(value, list):
88 return [self._read_representation(element) for element in value]
89 if isinstance(value, dict):
90 converted_dict = dict((key, self._read_representation(sub_value))
91 for key, sub_value in value.iteritems())
92 if 'href' in converted_dict:
jamesrencd7a81a2010-04-21 20:39:08 +000093 return type(self)(converted_dict, http=self._http)
showardf828c772010-01-25 21:49:42 +000094 return converted_dict
95 return value
96
97
98 def _write_representation(self, value):
99 # recursively convert Resource objects to representation dicts
100 if isinstance(value, list):
101 return [self._write_representation(element) for element in value]
102 if isinstance(value, dict):
103 return dict((key, self._write_representation(sub_value))
104 for key, sub_value in value.iteritems())
105 if isinstance(value, Resource):
106 return value._representation()
107 return value
108
109
110 def _representation(self):
111 return dict((key, self._write_representation(value))
112 for key, value in self.__dict__.iteritems()
113 if not key.startswith('_')
114 and not callable(value))
115
116
showardf46ad4c2010-02-03 20:28:59 +0000117 def _do_request(self, method, uri, query_parameters, encoded_body):
jamesrencd7a81a2010-04-21 20:39:08 +0000118 uri_parts = [uri]
showardf828c772010-01-25 21:49:42 +0000119 if query_parameters:
jamesrencd7a81a2010-04-21 20:39:08 +0000120 if '?' in uri:
121 uri_parts += '&'
122 else:
123 uri_parts += '?'
124 uri_parts += urllib.urlencode(query_parameters, doseq=True)
125 full_uri = ''.join(uri_parts)
showardf828c772010-01-25 21:49:42 +0000126
127 if encoded_body:
128 entity_body = simplejson.dumps(encoded_body)
129 else:
130 entity_body = None
131
showardf828c772010-01-25 21:49:42 +0000132 logging.debug('%s %s', method, full_uri)
133 if entity_body:
134 logging.debug(entity_body)
jamesren93270d12010-04-09 20:44:33 +0000135
136 site_verify = utils.import_site_function(
137 __file__, 'autotest_lib.frontend.shared.site_rest_client',
138 'site_verify_response', _site_verify_response_default)
jamesrencd7a81a2010-04-21 20:39:08 +0000139 headers, response_body = self._http.request(
showardf46ad4c2010-02-03 20:28:59 +0000140 full_uri, method, body=entity_body,
jamesren5a13d8d2010-02-12 00:46:09 +0000141 headers=_get_request_headers(uri))
jamesren93270d12010-04-09 20:44:33 +0000142 if not site_verify(headers, response_body):
143 logging.debug('Response verification failed, clearing headers and '
144 'trying again:\n%s', response_body)
145 _clear_request_headers(uri)
146 headers, response_body = _http.request(
147 full_uri, method, body=entity_body,
148 headers=_get_request_headers(uri))
149
showardf828c772010-01-25 21:49:42 +0000150 logging.debug('Response: %s', headers['status'])
151
showardf46ad4c2010-02-03 20:28:59 +0000152 return Response(headers, response_body)
153
154
155 def _request(self, method, query_parameters=None, encoded_body=None):
156 if query_parameters is None:
157 query_parameters = {}
158
159 response = self._do_request(method, self.href, query_parameters,
160 encoded_body)
161
showardf828c772010-01-25 21:49:42 +0000162 if 300 <= response.status < 400: # redirection
jamesrencd7a81a2010-04-21 20:39:08 +0000163 return self._do_request(method, response.headers['location'],
164 query_parameters, encoded_body)
showardf828c772010-01-25 21:49:42 +0000165 if 400 <= response.status < 500:
166 raise ClientError(str(response))
167 if 500 <= response.status < 600:
168 raise ServerError(str(response))
169 return response
170
171
172 def _stringify_query_parameter(self, value):
173 if isinstance(value, (list, tuple)):
jamesrencd7a81a2010-04-21 20:39:08 +0000174 return ','.join(self._stringify_query_parameter(item)
175 for item in value)
showardf828c772010-01-25 21:49:42 +0000176 return str(value)
177
178
jamesrencd7a81a2010-04-21 20:39:08 +0000179 def _iterlists(self, mapping):
180 """This effectively lets us treat dicts as MultiValueDicts."""
181 if hasattr(mapping, 'iterlists'): # mapping is already a MultiValueDict
182 return mapping.iterlists()
183 return ((key, (value,)) for key, value in mapping.iteritems())
184
185
186 def get(self, query_parameters=None, **kwarg_query_parameters):
187 """
188 @param query_parameters: a dict or MultiValueDict
189 """
190 query_parameters = copy.copy(query_parameters) # avoid mutating original
191 if query_parameters is None:
192 query_parameters = {}
193 query_parameters.update(kwarg_query_parameters)
194
195 string_parameters = datastructures.MultiValueDict()
196 for key, values in self._iterlists(query_parameters):
197 string_parameters.setlist(
198 key, [self._stringify_query_parameter(value)
199 for value in values])
200
201 response = self._request('GET',
202 query_parameters=string_parameters.lists())
showardf828c772010-01-25 21:49:42 +0000203 assert response.status == 200
204 return self._read_representation(response.decoded_body())
205
206
jamesrencd7a81a2010-04-21 20:39:08 +0000207 def get_full(self, results_limit, query_parameters=None,
208 **kwarg_query_parameters):
209 """
210 Like get() for collections, when the full collection is expected.
211
212 @param results_limit: maxmimum number of results to allow
213 @raises ClientError if there are more than results_limit results.
214 """
215 result = self.get(query_parameters=query_parameters,
216 items_per_page=results_limit,
217 **kwarg_query_parameters)
218 if result.total_results > results_limit:
219 raise ClientError(
220 'Too many results (%s > %s) for request %s (%s %s)'
221 % (result.total_results, results_limit, self.href,
222 query_parameters, kwarg_query_parameters))
223 return result
224
225
226
showardf828c772010-01-25 21:49:42 +0000227 def put(self):
228 response = self._request('PUT', encoded_body=self._representation())
229 assert response.status == 200
230 return self._read_representation(response.decoded_body())
231
232
233 def delete(self):
234 response = self._request('DELETE')
235 assert response.status == 204 # no content
236
237
238 def post(self, request_dict):
239 # request_dict may still have resources in it
240 request_dict = self._write_representation(request_dict)
241 response = self._request('POST', encoded_body=request_dict)
242 assert response.status == 201 # created
243 return self._read_representation({'href': response.headers['location']})