blob: b5b83afbe4b9189a22364c0d2830e194546de5d0 [file] [log] [blame]
jamesren5a13d8d2010-02-12 00:46:09 +00001import logging, pprint, re, urllib, getpass, urlparse
showardf828c772010-01-25 21:49:42 +00002import httplib2
3from django.utils import 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
showardf828c772010-01-25 21:49:42 +00008_http = httplib2.Http()
jamesren5a13d8d2010-02-12 00:46:09 +00009_request_headers = {}
10
11
12def _get_request_headers(uri):
13 server = urlparse.urlparse(uri)[0:2]
14 if server in _request_headers:
15 return _request_headers[server]
16
17 headers = rpc_client_lib.authorization_headers(getpass.getuser(), uri)
18 headers['Content-Type'] = 'application/json'
19
20 _request_headers[server] = headers
21 return headers
showardf828c772010-01-25 21:49:42 +000022
23
jamesren93270d12010-04-09 20:44:33 +000024def _clear_request_headers(uri):
25 server = urlparse.urlparse(uri)[0:2]
26 if server in _request_headers:
27 del _request_headers[server]
28
29
30def _site_verify_response_default(headers, response_body):
31 return headers['status'] != '401'
32
33
showardf828c772010-01-25 21:49:42 +000034class RestClientError(Exception):
35 pass
36
37
38class ClientError(Exception):
39 pass
40
41
42class ServerError(Exception):
43 pass
44
45
46class Response(object):
47 def __init__(self, httplib_response, httplib_content):
48 self.status = int(httplib_response['status'])
49 self.headers = httplib_response
50 self.entity_body = httplib_content
51
52
53 def decoded_body(self):
54 return simplejson.loads(self.entity_body)
55
56
57 def __str__(self):
58 return '\n'.join([str(self.status), self.entity_body])
59
60
61class Resource(object):
showardf46ad4c2010-02-03 20:28:59 +000062 def __init__(self, representation_dict):
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
showardf46ad4c2010-02-03 20:28:59 +000078 def load(cls, uri):
79 directory = cls({'href': uri})
showardf828c772010-01-25 21:49:42 +000080 return directory.get()
81
82
83 def _read_representation(self, value):
84 # recursively convert representation dicts to Resource objects
85 if isinstance(value, list):
86 return [self._read_representation(element) for element in value]
87 if isinstance(value, dict):
88 converted_dict = dict((key, self._read_representation(sub_value))
89 for key, sub_value in value.iteritems())
90 if 'href' in converted_dict:
showardf46ad4c2010-02-03 20:28:59 +000091 return type(self)(converted_dict)
showardf828c772010-01-25 21:49:42 +000092 return converted_dict
93 return value
94
95
96 def _write_representation(self, value):
97 # recursively convert Resource objects to representation dicts
98 if isinstance(value, list):
99 return [self._write_representation(element) for element in value]
100 if isinstance(value, dict):
101 return dict((key, self._write_representation(sub_value))
102 for key, sub_value in value.iteritems())
103 if isinstance(value, Resource):
104 return value._representation()
105 return value
106
107
108 def _representation(self):
109 return dict((key, self._write_representation(value))
110 for key, value in self.__dict__.iteritems()
111 if not key.startswith('_')
112 and not callable(value))
113
114
showardf46ad4c2010-02-03 20:28:59 +0000115 def _do_request(self, method, uri, query_parameters, encoded_body):
showardf828c772010-01-25 21:49:42 +0000116 if query_parameters:
showardf46ad4c2010-02-03 20:28:59 +0000117 query_string = '?' + urllib.urlencode(query_parameters)
118 else:
119 query_string = ''
120 full_uri = uri + query_string
showardf828c772010-01-25 21:49:42 +0000121
122 if encoded_body:
123 entity_body = simplejson.dumps(encoded_body)
124 else:
125 entity_body = None
126
showardf828c772010-01-25 21:49:42 +0000127 logging.debug('%s %s', method, full_uri)
128 if entity_body:
129 logging.debug(entity_body)
jamesren93270d12010-04-09 20:44:33 +0000130
131 site_verify = utils.import_site_function(
132 __file__, 'autotest_lib.frontend.shared.site_rest_client',
133 'site_verify_response', _site_verify_response_default)
showardf828c772010-01-25 21:49:42 +0000134 headers, response_body = _http.request(
showardf46ad4c2010-02-03 20:28:59 +0000135 full_uri, method, body=entity_body,
jamesren5a13d8d2010-02-12 00:46:09 +0000136 headers=_get_request_headers(uri))
jamesren93270d12010-04-09 20:44:33 +0000137 if not site_verify(headers, response_body):
138 logging.debug('Response verification failed, clearing headers and '
139 'trying again:\n%s', response_body)
140 _clear_request_headers(uri)
141 headers, response_body = _http.request(
142 full_uri, method, body=entity_body,
143 headers=_get_request_headers(uri))
144
showardf828c772010-01-25 21:49:42 +0000145 logging.debug('Response: %s', headers['status'])
146
showardf46ad4c2010-02-03 20:28:59 +0000147 return Response(headers, response_body)
148
149
150 def _request(self, method, query_parameters=None, encoded_body=None):
151 if query_parameters is None:
152 query_parameters = {}
153
154 response = self._do_request(method, self.href, query_parameters,
155 encoded_body)
156
showardf828c772010-01-25 21:49:42 +0000157 if 300 <= response.status < 400: # redirection
158 raise NotImplementedError(str(response)) # TODO
159 if 400 <= response.status < 500:
160 raise ClientError(str(response))
161 if 500 <= response.status < 600:
162 raise ServerError(str(response))
163 return response
164
165
166 def _stringify_query_parameter(self, value):
167 if isinstance(value, (list, tuple)):
168 return ','.join(value)
169 return str(value)
170
171
172 def get(self, **query_parameters):
173 string_parameters = dict((key, self._stringify_query_parameter(value))
174 for key, value in query_parameters.iteritems()
175 if value is not None)
176 response = self._request('GET', query_parameters=string_parameters)
177 assert response.status == 200
178 return self._read_representation(response.decoded_body())
179
180
181 def put(self):
182 response = self._request('PUT', encoded_body=self._representation())
183 assert response.status == 200
184 return self._read_representation(response.decoded_body())
185
186
187 def delete(self):
188 response = self._request('DELETE')
189 assert response.status == 204 # no content
190
191
192 def post(self, request_dict):
193 # request_dict may still have resources in it
194 request_dict = self._write_representation(request_dict)
195 response = self._request('POST', encoded_body=request_dict)
196 assert response.status == 201 # created
197 return self._read_representation({'href': response.headers['location']})