Merge lp:~rackspace-titan/nova/wsgi-refactor into lp:~hudson-openstack/nova/trunk
- wsgi-refactor
- Merge into trunk
Proposed by
Brian Waldon
Status: | Merged |
---|---|
Approved by: | Brian Lamar |
Approved revision: | 1252 |
Merged at revision: | 1261 |
Proposed branch: | lp:~rackspace-titan/nova/wsgi-refactor |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
1184 lines (+318/-209) 21 files modified
nova/api/openstack/accounts.py (+3/-3) nova/api/openstack/backup_schedules.py (+7/-6) nova/api/openstack/consoles.py (+1/-11) nova/api/openstack/contrib/floating_ips.py (+2/-2) nova/api/openstack/create_instance_helper.py (+1/-1) nova/api/openstack/flavors.py (+4/-2) nova/api/openstack/image_metadata.py (+3/-2) nova/api/openstack/images.py (+4/-2) nova/api/openstack/ips.py (+3/-2) nova/api/openstack/limits.py (+4/-2) nova/api/openstack/server_metadata.py (+4/-2) nova/api/openstack/servers.py (+6/-4) nova/api/openstack/shared_ip_groups.py (+6/-6) nova/api/openstack/users.py (+4/-2) nova/api/openstack/versions.py (+3/-2) nova/api/openstack/wsgi.py (+132/-73) nova/api/openstack/zones.py (+5/-4) nova/tests/api/openstack/contrib/test_floating_ips.py (+3/-0) nova/tests/api/openstack/test_servers.py (+23/-23) nova/tests/api/openstack/test_wsgi.py (+97/-56) nova/tests/integrated/api/client.py (+3/-4) |
To merge this branch: | bzr merge lp:~rackspace-titan/nova/wsgi-refactor |
Related bugs: | |
Related blueprints: |
Openstack API 1.1 Finalization
(Essential)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brian Lamar (community) | Approve | ||
Josh Kearney (community) | Approve | ||
Review via email: mp+67084@code.launchpad.net |
Commit message
Expanding OSAPI wsgi module to allow handling of headers and status codes
Description of the change
- Expanding OSAPI wsgi module to allow handling of headers and status codes
- Allows us to fix many of our OSAPI-related bugs efficiently:
https:/
https:/
https:/
To post a comment you must log in.
Revision history for this message
Brian Lamar (blamar) wrote : | # |
After talking it through with Waldon I like the separation of serialization between body and headers. Serializing bodies shouldn't include HTTP codes/headers and vice versa. Passes all my tests and I'm looking forward to the other bug fixes using this branch. Thanks!
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'nova/api/openstack/accounts.py' |
2 | --- nova/api/openstack/accounts.py 2011-05-20 18:42:19 +0000 |
3 | +++ nova/api/openstack/accounts.py 2011-07-11 17:17:32 +0000 |
4 | @@ -87,8 +87,8 @@ |
5 | }, |
6 | } |
7 | |
8 | - serializers = { |
9 | + body_serializers = { |
10 | 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), |
11 | } |
12 | - |
13 | - return wsgi.Resource(Controller(), serializers=serializers) |
14 | + serializer = wsgi.ResponseSerializer(body_serializers) |
15 | + return wsgi.Resource(Controller(), serializer=serializer) |
16 | |
17 | === modified file 'nova/api/openstack/backup_schedules.py' |
18 | --- nova/api/openstack/backup_schedules.py 2011-05-20 18:42:19 +0000 |
19 | +++ nova/api/openstack/backup_schedules.py 2011-07-11 17:17:32 +0000 |
20 | @@ -34,20 +34,20 @@ |
21 | def __init__(self): |
22 | pass |
23 | |
24 | - def index(self, req, server_id): |
25 | + def index(self, req, server_id, **kwargs): |
26 | """ Returns the list of backup schedules for a given instance """ |
27 | return faults.Fault(exc.HTTPNotImplemented()) |
28 | |
29 | - def show(self, req, server_id, id): |
30 | + def show(self, req, server_id, id, **kwargs): |
31 | """ Returns a single backup schedule for a given instance """ |
32 | return faults.Fault(exc.HTTPNotImplemented()) |
33 | |
34 | - def create(self, req, server_id, body): |
35 | + def create(self, req, server_id, **kwargs): |
36 | """ No actual update method required, since the existing API allows |
37 | both create and update through a POST """ |
38 | return faults.Fault(exc.HTTPNotImplemented()) |
39 | |
40 | - def delete(self, req, server_id, id): |
41 | + def delete(self, req, server_id, id, **kwargs): |
42 | """ Deletes an existing backup schedule """ |
43 | return faults.Fault(exc.HTTPNotImplemented()) |
44 | |
45 | @@ -59,9 +59,10 @@ |
46 | }, |
47 | } |
48 | |
49 | - serializers = { |
50 | + body_serializers = { |
51 | 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, |
52 | metadata=metadata), |
53 | } |
54 | |
55 | - return wsgi.Resource(Controller(), serializers=serializers) |
56 | + serializer = wsgi.ResponseSerializer(body_serializers) |
57 | + return wsgi.Resource(Controller(), serializer=serializer) |
58 | |
59 | === modified file 'nova/api/openstack/consoles.py' |
60 | --- nova/api/openstack/consoles.py 2011-05-20 18:42:19 +0000 |
61 | +++ nova/api/openstack/consoles.py 2011-07-11 17:17:32 +0000 |
62 | @@ -90,14 +90,4 @@ |
63 | |
64 | |
65 | def create_resource(): |
66 | - metadata = { |
67 | - 'attributes': { |
68 | - 'console': [], |
69 | - }, |
70 | - } |
71 | - |
72 | - serializers = { |
73 | - 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), |
74 | - } |
75 | - |
76 | - return wsgi.Resource(Controller(), serializers=serializers) |
77 | + return wsgi.Resource(Controller()) |
78 | |
79 | === modified file 'nova/api/openstack/contrib/floating_ips.py' |
80 | --- nova/api/openstack/contrib/floating_ips.py 2011-06-29 17:58:10 +0000 |
81 | +++ nova/api/openstack/contrib/floating_ips.py 2011-07-11 17:17:32 +0000 |
82 | @@ -78,7 +78,7 @@ |
83 | |
84 | return _translate_floating_ips_view(floating_ips) |
85 | |
86 | - def create(self, req, body): |
87 | + def create(self, req): |
88 | context = req.environ['nova.context'] |
89 | |
90 | try: |
91 | @@ -124,7 +124,7 @@ |
92 | "floating_ip": floating_ip, |
93 | "fixed_ip": fixed_ip}} |
94 | |
95 | - def disassociate(self, req, id, body): |
96 | + def disassociate(self, req, id): |
97 | """ POST /floating_ips/{id}/disassociate """ |
98 | context = req.environ['nova.context'] |
99 | floating_ip = self.network_api.get_floating_ip(context, id) |
100 | |
101 | === modified file 'nova/api/openstack/create_instance_helper.py' |
102 | --- nova/api/openstack/create_instance_helper.py 2011-06-29 22:22:56 +0000 |
103 | +++ nova/api/openstack/create_instance_helper.py 2011-07-11 17:17:32 +0000 |
104 | @@ -289,7 +289,7 @@ |
105 | """Deserialize an xml-formatted server create request""" |
106 | dom = minidom.parseString(string) |
107 | server = self._extract_server(dom) |
108 | - return {'server': server} |
109 | + return {'body': {'server': server}} |
110 | |
111 | def _extract_server(self, node): |
112 | """Marshal the server attribute of a parsed request""" |
113 | |
114 | === modified file 'nova/api/openstack/flavors.py' |
115 | --- nova/api/openstack/flavors.py 2011-06-30 13:28:21 +0000 |
116 | +++ nova/api/openstack/flavors.py 2011-07-11 17:17:32 +0000 |
117 | @@ -85,8 +85,10 @@ |
118 | '1.1': wsgi.XMLNS_V11, |
119 | }[version] |
120 | |
121 | - serializers = { |
122 | + body_serializers = { |
123 | 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns), |
124 | } |
125 | |
126 | - return wsgi.Resource(controller, serializers=serializers) |
127 | + serializer = wsgi.ResponseSerializer(body_serializers) |
128 | + |
129 | + return wsgi.Resource(controller, serializer=serializer) |
130 | |
131 | === modified file 'nova/api/openstack/image_metadata.py' |
132 | --- nova/api/openstack/image_metadata.py 2011-06-28 15:59:46 +0000 |
133 | +++ nova/api/openstack/image_metadata.py 2011-07-11 17:17:32 +0000 |
134 | @@ -160,8 +160,9 @@ |
135 | |
136 | |
137 | def create_resource(): |
138 | - serializers = { |
139 | + body_serializers = { |
140 | 'application/xml': ImageMetadataXMLSerializer(), |
141 | } |
142 | + serializer = wsgi.ResponseSerializer(body_serializers) |
143 | |
144 | - return wsgi.Resource(Controller(), serializers=serializers) |
145 | + return wsgi.Resource(Controller(), serializer=serializer) |
146 | |
147 | === modified file 'nova/api/openstack/images.py' |
148 | --- nova/api/openstack/images.py 2011-07-11 13:13:22 +0000 |
149 | +++ nova/api/openstack/images.py 2011-07-11 17:17:32 +0000 |
150 | @@ -348,8 +348,10 @@ |
151 | '1.1': ImageXMLSerializer(), |
152 | }[version] |
153 | |
154 | - serializers = { |
155 | + body_serializers = { |
156 | 'application/xml': xml_serializer, |
157 | } |
158 | |
159 | - return wsgi.Resource(controller, serializers=serializers) |
160 | + serializer = wsgi.ResponseSerializer(body_serializers) |
161 | + |
162 | + return wsgi.Resource(controller, serializer=serializer) |
163 | |
164 | === modified file 'nova/api/openstack/ips.py' |
165 | --- nova/api/openstack/ips.py 2011-06-24 12:01:51 +0000 |
166 | +++ nova/api/openstack/ips.py 2011-07-11 17:17:32 +0000 |
167 | @@ -70,9 +70,10 @@ |
168 | }, |
169 | } |
170 | |
171 | - serializers = { |
172 | + body_serializers = { |
173 | 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, |
174 | xmlns=wsgi.XMLNS_V10), |
175 | } |
176 | + serializer = wsgi.ResponseSerializer(body_serializers) |
177 | |
178 | - return wsgi.Resource(Controller(), serializers=serializers) |
179 | + return wsgi.Resource(Controller(), serializer=serializer) |
180 | |
181 | === modified file 'nova/api/openstack/limits.py' |
182 | --- nova/api/openstack/limits.py 2011-06-14 01:14:26 +0000 |
183 | +++ nova/api/openstack/limits.py 2011-07-11 17:17:32 +0000 |
184 | @@ -97,12 +97,14 @@ |
185 | }, |
186 | } |
187 | |
188 | - serializers = { |
189 | + body_serializers = { |
190 | 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, |
191 | metadata=metadata), |
192 | } |
193 | |
194 | - return wsgi.Resource(controller, serializers=serializers) |
195 | + serializer = wsgi.ResponseSerializer(body_serializers) |
196 | + |
197 | + return wsgi.Resource(controller, serializer=serializer) |
198 | |
199 | |
200 | class Limit(object): |
201 | |
202 | === modified file 'nova/api/openstack/server_metadata.py' |
203 | --- nova/api/openstack/server_metadata.py 2011-06-24 12:01:51 +0000 |
204 | +++ nova/api/openstack/server_metadata.py 2011-07-11 17:17:32 +0000 |
205 | @@ -123,8 +123,10 @@ |
206 | |
207 | |
208 | def create_resource(): |
209 | - serializers = { |
210 | + body_serializers = { |
211 | 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V11), |
212 | } |
213 | |
214 | - return wsgi.Resource(Controller(), serializers=serializers) |
215 | + serializer = wsgi.ResponseSerializer(body_serializers) |
216 | + |
217 | + return wsgi.Resource(Controller(), serializer=serializer) |
218 | |
219 | === modified file 'nova/api/openstack/servers.py' |
220 | --- nova/api/openstack/servers.py 2011-07-08 15:59:17 +0000 |
221 | +++ nova/api/openstack/servers.py 2011-07-11 17:17:32 +0000 |
222 | @@ -624,14 +624,16 @@ |
223 | '1.1': wsgi.XMLNS_V11, |
224 | }[version] |
225 | |
226 | - serializers = { |
227 | + body_serializers = { |
228 | 'application/xml': wsgi.XMLDictSerializer(metadata=metadata, |
229 | xmlns=xmlns), |
230 | } |
231 | |
232 | - deserializers = { |
233 | + body_deserializers = { |
234 | 'application/xml': helper.ServerXMLDeserializer(), |
235 | } |
236 | |
237 | - return wsgi.Resource(controller, serializers=serializers, |
238 | - deserializers=deserializers) |
239 | + serializer = wsgi.ResponseSerializer(body_serializers) |
240 | + deserializer = wsgi.RequestDeserializer(body_deserializers) |
241 | + |
242 | + return wsgi.Resource(controller, deserializer, serializer) |
243 | |
244 | === modified file 'nova/api/openstack/shared_ip_groups.py' |
245 | --- nova/api/openstack/shared_ip_groups.py 2011-05-20 18:42:19 +0000 |
246 | +++ nova/api/openstack/shared_ip_groups.py 2011-07-11 17:17:32 +0000 |
247 | @@ -24,27 +24,27 @@ |
248 | class Controller(object): |
249 | """ The Shared IP Groups Controller for the Openstack API """ |
250 | |
251 | - def index(self, req): |
252 | + def index(self, req, **kwargs): |
253 | """ Returns a list of Shared IP Groups for the user """ |
254 | raise faults.Fault(exc.HTTPNotImplemented()) |
255 | |
256 | - def show(self, req, id): |
257 | + def show(self, req, id, **kwargs): |
258 | """ Shows in-depth information on a specific Shared IP Group """ |
259 | raise faults.Fault(exc.HTTPNotImplemented()) |
260 | |
261 | - def update(self, req, id, body): |
262 | + def update(self, req, id, **kwargs): |
263 | """ You can't update a Shared IP Group """ |
264 | raise faults.Fault(exc.HTTPNotImplemented()) |
265 | |
266 | - def delete(self, req, id): |
267 | + def delete(self, req, id, **kwargs): |
268 | """ Deletes a Shared IP Group """ |
269 | raise faults.Fault(exc.HTTPNotImplemented()) |
270 | |
271 | - def detail(self, req): |
272 | + def detail(self, req, **kwargs): |
273 | """ Returns a complete list of Shared IP Groups """ |
274 | raise faults.Fault(exc.HTTPNotImplemented()) |
275 | |
276 | - def create(self, req, body): |
277 | + def create(self, req, **kwargs): |
278 | """ Creates a new Shared IP group """ |
279 | raise faults.Fault(exc.HTTPNotImplemented()) |
280 | |
281 | |
282 | === modified file 'nova/api/openstack/users.py' |
283 | --- nova/api/openstack/users.py 2011-05-20 18:42:19 +0000 |
284 | +++ nova/api/openstack/users.py 2011-07-11 17:17:32 +0000 |
285 | @@ -105,8 +105,10 @@ |
286 | }, |
287 | } |
288 | |
289 | - serializers = { |
290 | + body_serializers = { |
291 | 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), |
292 | } |
293 | |
294 | - return wsgi.Resource(Controller(), serializers=serializers) |
295 | + serializer = wsgi.ResponseSerializer(body_serializers) |
296 | + |
297 | + return wsgi.Resource(Controller(), serializer=serializer) |
298 | |
299 | === modified file 'nova/api/openstack/versions.py' |
300 | --- nova/api/openstack/versions.py 2011-06-07 18:48:13 +0000 |
301 | +++ nova/api/openstack/versions.py 2011-07-11 17:17:32 +0000 |
302 | @@ -31,11 +31,12 @@ |
303 | } |
304 | } |
305 | |
306 | - serializers = { |
307 | + body_serializers = { |
308 | 'application/xml': wsgi.XMLDictSerializer(metadata=metadata), |
309 | } |
310 | + serializer = wsgi.ResponseSerializer(body_serializers) |
311 | |
312 | - wsgi.Resource.__init__(self, None, serializers=serializers) |
313 | + wsgi.Resource.__init__(self, None, serializer=serializer) |
314 | |
315 | def dispatch(self, request, *args): |
316 | """Respond to a request for all OpenStack API versions.""" |
317 | |
318 | === modified file 'nova/api/openstack/wsgi.py' |
319 | --- nova/api/openstack/wsgi.py 2011-07-01 19:53:06 +0000 |
320 | +++ nova/api/openstack/wsgi.py 2011-07-11 17:17:32 +0000 |
321 | @@ -46,38 +46,51 @@ |
322 | |
323 | """ |
324 | if not "Content-Type" in self.headers: |
325 | - raise exception.InvalidContentType(content_type=None) |
326 | + return None |
327 | |
328 | allowed_types = ("application/xml", "application/json") |
329 | content_type = self.content_type |
330 | |
331 | if content_type not in allowed_types: |
332 | raise exception.InvalidContentType(content_type=content_type) |
333 | - else: |
334 | - return content_type |
335 | - |
336 | - |
337 | -class TextDeserializer(object): |
338 | - """Custom request body deserialization based on controller action name.""" |
339 | + |
340 | + return content_type |
341 | + |
342 | + |
343 | +class ActionDispatcher(object): |
344 | + """Maps method name to local methods through action name.""" |
345 | + |
346 | + def dispatch(self, *args, **kwargs): |
347 | + """Find and call local method.""" |
348 | + action = kwargs.pop('action', 'default') |
349 | + action_method = getattr(self, str(action), self.default) |
350 | + return action_method(*args, **kwargs) |
351 | + |
352 | + def default(self, data): |
353 | + raise NotImplementedError() |
354 | + |
355 | + |
356 | +class TextDeserializer(ActionDispatcher): |
357 | + """Default request body deserialization""" |
358 | |
359 | def deserialize(self, datastring, action='default'): |
360 | - """Find local deserialization method and parse request body.""" |
361 | - action_method = getattr(self, str(action), self.default) |
362 | - return action_method(datastring) |
363 | + return self.dispatch(datastring, action=action) |
364 | |
365 | def default(self, datastring): |
366 | - """Default deserialization code should live here""" |
367 | - raise NotImplementedError() |
368 | + return {} |
369 | |
370 | |
371 | class JSONDeserializer(TextDeserializer): |
372 | |
373 | - def default(self, datastring): |
374 | + def _from_json(self, datastring): |
375 | try: |
376 | return utils.loads(datastring) |
377 | except ValueError: |
378 | - raise exception.MalformedRequestBody( |
379 | - reason=_("malformed JSON in request body")) |
380 | + msg = _("cannot understand JSON") |
381 | + raise exception.MalformedRequestBody(reason=msg) |
382 | + |
383 | + def default(self, datastring): |
384 | + return {'body': self._from_json(datastring)} |
385 | |
386 | |
387 | class XMLDeserializer(TextDeserializer): |
388 | @@ -90,15 +103,15 @@ |
389 | super(XMLDeserializer, self).__init__() |
390 | self.metadata = metadata or {} |
391 | |
392 | - def default(self, datastring): |
393 | + def _from_xml(self, datastring): |
394 | plurals = set(self.metadata.get('plurals', {})) |
395 | |
396 | try: |
397 | node = minidom.parseString(datastring).childNodes[0] |
398 | return {node.nodeName: self._from_xml_node(node, plurals)} |
399 | except expat.ExpatError: |
400 | - raise exception.MalformedRequestBody( |
401 | - reason=_("malformed XML in request body")) |
402 | + msg = _("cannot understand XML") |
403 | + raise exception.MalformedRequestBody(reason=msg) |
404 | |
405 | def _from_xml_node(self, node, listnames): |
406 | """Convert a minidom node to a simple Python type. |
407 | @@ -121,21 +134,32 @@ |
408 | listnames) |
409 | return result |
410 | |
411 | + def default(self, datastring): |
412 | + return {'body': self._from_xml(datastring)} |
413 | + |
414 | + |
415 | +class RequestHeadersDeserializer(ActionDispatcher): |
416 | + """Default request headers deserializer""" |
417 | + |
418 | + def deserialize(self, request, action): |
419 | + return self.dispatch(request, action=action) |
420 | + |
421 | + def default(self, request): |
422 | + return {} |
423 | + |
424 | |
425 | class RequestDeserializer(object): |
426 | """Break up a Request object into more useful pieces.""" |
427 | |
428 | - def __init__(self, deserializers=None): |
429 | - """ |
430 | - :param deserializers: dictionary of content-type-specific deserializers |
431 | - |
432 | - """ |
433 | - self.deserializers = { |
434 | + def __init__(self, body_deserializers=None, headers_deserializer=None): |
435 | + self.body_deserializers = { |
436 | 'application/xml': XMLDeserializer(), |
437 | 'application/json': JSONDeserializer(), |
438 | } |
439 | + self.body_deserializers.update(body_deserializers or {}) |
440 | |
441 | - self.deserializers.update(deserializers or {}) |
442 | + self.headers_deserializer = headers_deserializer or \ |
443 | + RequestHeadersDeserializer() |
444 | |
445 | def deserialize(self, request): |
446 | """Extract necessary pieces of the request. |
447 | @@ -149,26 +173,42 @@ |
448 | action_args = self.get_action_args(request.environ) |
449 | action = action_args.pop('action', None) |
450 | |
451 | - if request.method.lower() in ('post', 'put'): |
452 | - if len(request.body) == 0: |
453 | - action_args['body'] = None |
454 | - else: |
455 | - content_type = request.get_content_type() |
456 | - deserializer = self.get_deserializer(content_type) |
457 | - |
458 | - try: |
459 | - body = deserializer.deserialize(request.body, action) |
460 | - action_args['body'] = body |
461 | - except exception.InvalidContentType: |
462 | - action_args['body'] = None |
463 | + action_args.update(self.deserialize_headers(request, action)) |
464 | + action_args.update(self.deserialize_body(request, action)) |
465 | |
466 | accept = self.get_expected_content_type(request) |
467 | |
468 | return (action, action_args, accept) |
469 | |
470 | - def get_deserializer(self, content_type): |
471 | - try: |
472 | - return self.deserializers[content_type] |
473 | + def deserialize_headers(self, request, action): |
474 | + return self.headers_deserializer.deserialize(request, action) |
475 | + |
476 | + def deserialize_body(self, request, action): |
477 | + try: |
478 | + content_type = request.get_content_type() |
479 | + except exception.InvalidContentType: |
480 | + LOG.debug(_("Unrecognized Content-Type provided in request")) |
481 | + return {} |
482 | + |
483 | + if content_type is None: |
484 | + LOG.debug(_("No Content-Type provided in request")) |
485 | + return {} |
486 | + |
487 | + if not len(request.body) > 0: |
488 | + LOG.debug(_("Empty body provided in request")) |
489 | + return {} |
490 | + |
491 | + try: |
492 | + deserializer = self.get_body_deserializer(content_type) |
493 | + except exception.InvalidContentType: |
494 | + LOG.debug(_("Unable to deserialize body as provided Content-Type")) |
495 | + raise |
496 | + |
497 | + return deserializer.deserialize(request.body, action) |
498 | + |
499 | + def get_body_deserializer(self, content_type): |
500 | + try: |
501 | + return self.body_deserializers[content_type] |
502 | except (KeyError, TypeError): |
503 | raise exception.InvalidContentType(content_type=content_type) |
504 | |
505 | @@ -195,20 +235,18 @@ |
506 | return args |
507 | |
508 | |
509 | -class DictSerializer(object): |
510 | - """Custom response body serialization based on controller action name.""" |
511 | +class DictSerializer(ActionDispatcher): |
512 | + """Default request body serialization""" |
513 | |
514 | def serialize(self, data, action='default'): |
515 | - """Find local serialization method and encode response body.""" |
516 | - action_method = getattr(self, str(action), self.default) |
517 | - return action_method(data) |
518 | + return self.dispatch(data, action=action) |
519 | |
520 | def default(self, data): |
521 | - """Default serialization code should live here""" |
522 | - raise NotImplementedError() |
523 | + return "" |
524 | |
525 | |
526 | class JSONDictSerializer(DictSerializer): |
527 | + """Default JSON request body serialization""" |
528 | |
529 | def default(self, data): |
530 | return utils.dumps(data) |
531 | @@ -295,19 +333,28 @@ |
532 | return result |
533 | |
534 | |
535 | +class ResponseHeadersSerializer(ActionDispatcher): |
536 | + """Default response headers serialization""" |
537 | + |
538 | + def serialize(self, response, data, action): |
539 | + self.dispatch(response, data, action=action) |
540 | + |
541 | + def default(self, response, data): |
542 | + response.status_int = 200 |
543 | + |
544 | + |
545 | class ResponseSerializer(object): |
546 | """Encode the necessary pieces into a response object""" |
547 | |
548 | - def __init__(self, serializers=None): |
549 | - """ |
550 | - :param serializers: dictionary of content-type-specific serializers |
551 | - |
552 | - """ |
553 | - self.serializers = { |
554 | + def __init__(self, body_serializers=None, headers_serializer=None): |
555 | + self.body_serializers = { |
556 | 'application/xml': XMLDictSerializer(), |
557 | 'application/json': JSONDictSerializer(), |
558 | } |
559 | - self.serializers.update(serializers or {}) |
560 | + self.body_serializers.update(body_serializers or {}) |
561 | + |
562 | + self.headers_serializer = headers_serializer or \ |
563 | + ResponseHeadersSerializer() |
564 | |
565 | def serialize(self, response_data, content_type, action='default'): |
566 | """Serialize a dict into a string and wrap in a wsgi.Request object. |
567 | @@ -317,16 +364,21 @@ |
568 | |
569 | """ |
570 | response = webob.Response() |
571 | + self.serialize_headers(response, response_data, action) |
572 | + self.serialize_body(response, response_data, content_type, action) |
573 | + return response |
574 | + |
575 | + def serialize_headers(self, response, data, action): |
576 | + self.headers_serializer.serialize(response, data, action) |
577 | + |
578 | + def serialize_body(self, response, data, content_type, action): |
579 | response.headers['Content-Type'] = content_type |
580 | - |
581 | - serializer = self.get_serializer(content_type) |
582 | - response.body = serializer.serialize(response_data, action) |
583 | - |
584 | - return response |
585 | - |
586 | - def get_serializer(self, content_type): |
587 | + serializer = self.get_body_serializer(content_type) |
588 | + response.body = serializer.serialize(data, action) |
589 | + |
590 | + def get_body_serializer(self, content_type): |
591 | try: |
592 | - return self.serializers[content_type] |
593 | + return self.body_serializers[content_type] |
594 | except (KeyError, TypeError): |
595 | raise exception.InvalidContentType(content_type=content_type) |
596 | |
597 | @@ -343,16 +395,18 @@ |
598 | serialized by requested content type. |
599 | |
600 | """ |
601 | - def __init__(self, controller, serializers=None, deserializers=None): |
602 | + def __init__(self, controller, deserializer=None, serializer=None): |
603 | """ |
604 | :param controller: object that implement methods created by routes lib |
605 | - :param serializers: dict of content-type specific text serializers |
606 | - :param deserializers: dict of content-type specific text deserializers |
607 | + :param deserializer: object that can serialize the output of a |
608 | + controller into a webob response |
609 | + :param serializer: object that can deserialize a webob request |
610 | + into necessary pieces |
611 | |
612 | """ |
613 | self.controller = controller |
614 | - self.serializer = ResponseSerializer(serializers) |
615 | - self.deserializer = RequestDeserializer(deserializers) |
616 | + self.deserializer = deserializer or RequestDeserializer() |
617 | + self.serializer = serializer or ResponseSerializer() |
618 | |
619 | @webob.dec.wsgify(RequestClass=Request) |
620 | def __call__(self, request): |
621 | @@ -362,8 +416,7 @@ |
622 | "url": request.url}) |
623 | |
624 | try: |
625 | - action, action_args, accept = self.deserializer.deserialize( |
626 | - request) |
627 | + action, args, accept = self.deserializer.deserialize(request) |
628 | except exception.InvalidContentType: |
629 | msg = _("Unsupported Content-Type") |
630 | return webob.exc.HTTPBadRequest(explanation=msg) |
631 | @@ -371,11 +424,13 @@ |
632 | msg = _("Malformed request body") |
633 | return faults.Fault(webob.exc.HTTPBadRequest(explanation=msg)) |
634 | |
635 | - action_result = self.dispatch(request, action, action_args) |
636 | + action_result = self.dispatch(request, action, args) |
637 | |
638 | #TODO(bcwaldon): find a more elegant way to pass through non-dict types |
639 | - if type(action_result) is dict: |
640 | - response = self.serializer.serialize(action_result, accept, action) |
641 | + if type(action_result) is dict or action_result is None: |
642 | + response = self.serializer.serialize(action_result, |
643 | + accept, |
644 | + action=action) |
645 | else: |
646 | response = action_result |
647 | |
648 | @@ -394,4 +449,8 @@ |
649 | """Find action-spefic method on controller and call it.""" |
650 | |
651 | controller_method = getattr(self.controller, action) |
652 | - return controller_method(req=request, **action_args) |
653 | + try: |
654 | + return controller_method(req=request, **action_args) |
655 | + except TypeError, exc: |
656 | + LOG.debug(str(exc)) |
657 | + return webob.exc.HTTPBadRequest() |
658 | |
659 | === modified file 'nova/api/openstack/zones.py' |
660 | --- nova/api/openstack/zones.py 2011-06-14 19:59:16 +0000 |
661 | +++ nova/api/openstack/zones.py 2011-07-11 17:17:32 +0000 |
662 | @@ -196,14 +196,15 @@ |
663 | }, |
664 | } |
665 | |
666 | - serializers = { |
667 | + body_serializers = { |
668 | 'application/xml': wsgi.XMLDictSerializer(xmlns=wsgi.XMLNS_V10, |
669 | metadata=metadata), |
670 | } |
671 | + serializer = wsgi.ResponseSerializer(body_serializers) |
672 | |
673 | - deserializers = { |
674 | + body_deserializers = { |
675 | 'application/xml': helper.ServerXMLDeserializer(), |
676 | } |
677 | + deserializer = wsgi.RequestDeserializer(body_deserializers) |
678 | |
679 | - return wsgi.Resource(controller, serializers=serializers, |
680 | - deserializers=deserializers) |
681 | + return wsgi.Resource(controller, deserializer, serializer) |
682 | |
683 | === modified file 'nova/tests/api/openstack/contrib/test_floating_ips.py' |
684 | --- nova/tests/api/openstack/contrib/test_floating_ips.py 2011-06-27 16:36:53 +0000 |
685 | +++ nova/tests/api/openstack/contrib/test_floating_ips.py 2011-07-11 17:17:32 +0000 |
686 | @@ -139,7 +139,9 @@ |
687 | def test_floating_ip_allocate(self): |
688 | req = webob.Request.blank('/v1.1/os-floating-ips') |
689 | req.method = 'POST' |
690 | + req.headers['Content-Type'] = 'application/json' |
691 | res = req.get_response(fakes.wsgi_app()) |
692 | + print res |
693 | self.assertEqual(res.status_int, 200) |
694 | ip = json.loads(res.body)['allocated'] |
695 | expected = { |
696 | @@ -177,6 +179,7 @@ |
697 | def test_floating_ip_disassociate(self): |
698 | req = webob.Request.blank('/v1.1/os-floating-ips/1/disassociate') |
699 | req.method = 'POST' |
700 | + req.headers['Content-Type'] = 'application/json' |
701 | res = req.get_response(fakes.wsgi_app()) |
702 | self.assertEqual(res.status_int, 200) |
703 | ip = json.loads(res.body)['disassociated'] |
704 | |
705 | === modified file 'nova/tests/api/openstack/test_servers.py' |
706 | --- nova/tests/api/openstack/test_servers.py 2011-07-08 16:47:34 +0000 |
707 | +++ nova/tests/api/openstack/test_servers.py 2011-07-11 17:17:32 +0000 |
708 | @@ -905,7 +905,7 @@ |
709 | req = webob.Request.blank('/v1.0/servers/1') |
710 | req.method = 'PUT' |
711 | res = req.get_response(fakes.wsgi_app()) |
712 | - self.assertEqual(res.status_int, 422) |
713 | + self.assertEqual(res.status_int, 400) |
714 | |
715 | def test_update_nonstring_name(self): |
716 | """ Confirm that update is filtering params """ |
717 | @@ -1608,7 +1608,7 @@ |
718 | "imageId": "1", |
719 | "flavorId": "1", |
720 | }} |
721 | - self.assertEquals(request, expected) |
722 | + self.assertEquals(request['body'], expected) |
723 | |
724 | def test_request_with_empty_metadata(self): |
725 | serial_request = """ |
726 | @@ -1623,7 +1623,7 @@ |
727 | "flavorId": "1", |
728 | "metadata": {}, |
729 | }} |
730 | - self.assertEquals(request, expected) |
731 | + self.assertEquals(request['body'], expected) |
732 | |
733 | def test_request_with_empty_personality(self): |
734 | serial_request = """ |
735 | @@ -1638,7 +1638,7 @@ |
736 | "flavorId": "1", |
737 | "personality": [], |
738 | }} |
739 | - self.assertEquals(request, expected) |
740 | + self.assertEquals(request['body'], expected) |
741 | |
742 | def test_request_with_empty_metadata_and_personality(self): |
743 | serial_request = """ |
744 | @@ -1655,7 +1655,7 @@ |
745 | "metadata": {}, |
746 | "personality": [], |
747 | }} |
748 | - self.assertEquals(request, expected) |
749 | + self.assertEquals(request['body'], expected) |
750 | |
751 | def test_request_with_empty_metadata_and_personality_reversed(self): |
752 | serial_request = """ |
753 | @@ -1672,7 +1672,7 @@ |
754 | "metadata": {}, |
755 | "personality": [], |
756 | }} |
757 | - self.assertEquals(request, expected) |
758 | + self.assertEquals(request['body'], expected) |
759 | |
760 | def test_request_with_one_personality(self): |
761 | serial_request = """ |
762 | @@ -1684,7 +1684,7 @@ |
763 | </server>""" |
764 | request = self.deserializer.deserialize(serial_request, 'create') |
765 | expected = [{"path": "/etc/conf", "contents": "aabbccdd"}] |
766 | - self.assertEquals(request["server"]["personality"], expected) |
767 | + self.assertEquals(request['body']["server"]["personality"], expected) |
768 | |
769 | def test_request_with_two_personalities(self): |
770 | serial_request = """ |
771 | @@ -1695,7 +1695,7 @@ |
772 | request = self.deserializer.deserialize(serial_request, 'create') |
773 | expected = [{"path": "/etc/conf", "contents": "aabbccdd"}, |
774 | {"path": "/etc/sudoers", "contents": "abcd"}] |
775 | - self.assertEquals(request["server"]["personality"], expected) |
776 | + self.assertEquals(request['body']["server"]["personality"], expected) |
777 | |
778 | def test_request_second_personality_node_ignored(self): |
779 | serial_request = """ |
780 | @@ -1710,7 +1710,7 @@ |
781 | </server>""" |
782 | request = self.deserializer.deserialize(serial_request, 'create') |
783 | expected = [{"path": "/etc/conf", "contents": "aabbccdd"}] |
784 | - self.assertEquals(request["server"]["personality"], expected) |
785 | + self.assertEquals(request['body']["server"]["personality"], expected) |
786 | |
787 | def test_request_with_one_personality_missing_path(self): |
788 | serial_request = """ |
789 | @@ -1719,7 +1719,7 @@ |
790 | <personality><file>aabbccdd</file></personality></server>""" |
791 | request = self.deserializer.deserialize(serial_request, 'create') |
792 | expected = [{"contents": "aabbccdd"}] |
793 | - self.assertEquals(request["server"]["personality"], expected) |
794 | + self.assertEquals(request['body']["server"]["personality"], expected) |
795 | |
796 | def test_request_with_one_personality_empty_contents(self): |
797 | serial_request = """ |
798 | @@ -1728,7 +1728,7 @@ |
799 | <personality><file path="/etc/conf"></file></personality></server>""" |
800 | request = self.deserializer.deserialize(serial_request, 'create') |
801 | expected = [{"path": "/etc/conf", "contents": ""}] |
802 | - self.assertEquals(request["server"]["personality"], expected) |
803 | + self.assertEquals(request['body']["server"]["personality"], expected) |
804 | |
805 | def test_request_with_one_personality_empty_contents_variation(self): |
806 | serial_request = """ |
807 | @@ -1737,7 +1737,7 @@ |
808 | <personality><file path="/etc/conf"/></personality></server>""" |
809 | request = self.deserializer.deserialize(serial_request, 'create') |
810 | expected = [{"path": "/etc/conf", "contents": ""}] |
811 | - self.assertEquals(request["server"]["personality"], expected) |
812 | + self.assertEquals(request['body']["server"]["personality"], expected) |
813 | |
814 | def test_request_with_one_metadata(self): |
815 | serial_request = """ |
816 | @@ -1749,7 +1749,7 @@ |
817 | </server>""" |
818 | request = self.deserializer.deserialize(serial_request, 'create') |
819 | expected = {"alpha": "beta"} |
820 | - self.assertEquals(request["server"]["metadata"], expected) |
821 | + self.assertEquals(request['body']["server"]["metadata"], expected) |
822 | |
823 | def test_request_with_two_metadata(self): |
824 | serial_request = """ |
825 | @@ -1762,7 +1762,7 @@ |
826 | </server>""" |
827 | request = self.deserializer.deserialize(serial_request, 'create') |
828 | expected = {"alpha": "beta", "foo": "bar"} |
829 | - self.assertEquals(request["server"]["metadata"], expected) |
830 | + self.assertEquals(request['body']["server"]["metadata"], expected) |
831 | |
832 | def test_request_with_metadata_missing_value(self): |
833 | serial_request = """ |
834 | @@ -1774,7 +1774,7 @@ |
835 | </server>""" |
836 | request = self.deserializer.deserialize(serial_request, 'create') |
837 | expected = {"alpha": ""} |
838 | - self.assertEquals(request["server"]["metadata"], expected) |
839 | + self.assertEquals(request['body']["server"]["metadata"], expected) |
840 | |
841 | def test_request_with_two_metadata_missing_value(self): |
842 | serial_request = """ |
843 | @@ -1787,7 +1787,7 @@ |
844 | </server>""" |
845 | request = self.deserializer.deserialize(serial_request, 'create') |
846 | expected = {"alpha": "", "delta": ""} |
847 | - self.assertEquals(request["server"]["metadata"], expected) |
848 | + self.assertEquals(request['body']["server"]["metadata"], expected) |
849 | |
850 | def test_request_with_metadata_missing_key(self): |
851 | serial_request = """ |
852 | @@ -1799,7 +1799,7 @@ |
853 | </server>""" |
854 | request = self.deserializer.deserialize(serial_request, 'create') |
855 | expected = {"": "beta"} |
856 | - self.assertEquals(request["server"]["metadata"], expected) |
857 | + self.assertEquals(request['body']["server"]["metadata"], expected) |
858 | |
859 | def test_request_with_two_metadata_missing_key(self): |
860 | serial_request = """ |
861 | @@ -1812,7 +1812,7 @@ |
862 | </server>""" |
863 | request = self.deserializer.deserialize(serial_request, 'create') |
864 | expected = {"": "gamma"} |
865 | - self.assertEquals(request["server"]["metadata"], expected) |
866 | + self.assertEquals(request['body']["server"]["metadata"], expected) |
867 | |
868 | def test_request_with_metadata_duplicate_key(self): |
869 | serial_request = """ |
870 | @@ -1825,7 +1825,7 @@ |
871 | </server>""" |
872 | request = self.deserializer.deserialize(serial_request, 'create') |
873 | expected = {"foo": "baz"} |
874 | - self.assertEquals(request["server"]["metadata"], expected) |
875 | + self.assertEquals(request['body']["server"]["metadata"], expected) |
876 | |
877 | def test_canonical_request_from_docs(self): |
878 | serial_request = """ |
879 | @@ -1871,7 +1871,7 @@ |
880 | ], |
881 | }} |
882 | request = self.deserializer.deserialize(serial_request, 'create') |
883 | - self.assertEqual(request, expected) |
884 | + self.assertEqual(request['body'], expected) |
885 | |
886 | def test_request_xmlser_with_flavor_image_href(self): |
887 | serial_request = """ |
888 | @@ -1881,9 +1881,9 @@ |
889 | flavorRef="http://localhost:8774/v1.1/flavors/1"> |
890 | </server>""" |
891 | request = self.deserializer.deserialize(serial_request, 'create') |
892 | - self.assertEquals(request["server"]["flavorRef"], |
893 | + self.assertEquals(request['body']["server"]["flavorRef"], |
894 | "http://localhost:8774/v1.1/flavors/1") |
895 | - self.assertEquals(request["server"]["imageRef"], |
896 | + self.assertEquals(request['body']["server"]["imageRef"], |
897 | "http://localhost:8774/v1.1/images/1") |
898 | |
899 | |
900 | @@ -1948,7 +1948,7 @@ |
901 | |
902 | def _get_create_request_json(self, body_dict): |
903 | req = webob.Request.blank('/v1.0/servers') |
904 | - req.content_type = 'application/json' |
905 | + req.headers['Content-Type'] = 'application/json' |
906 | req.method = 'POST' |
907 | req.body = json.dumps(body_dict) |
908 | return req |
909 | |
910 | === modified file 'nova/tests/api/openstack/test_wsgi.py' |
911 | --- nova/tests/api/openstack/test_wsgi.py 2011-06-24 12:01:51 +0000 |
912 | +++ nova/tests/api/openstack/test_wsgi.py 2011-07-11 17:17:32 +0000 |
913 | @@ -12,8 +12,7 @@ |
914 | def test_content_type_missing(self): |
915 | request = wsgi.Request.blank('/tests/123', method='POST') |
916 | request.body = "<body />" |
917 | - self.assertRaises(exception.InvalidContentType, |
918 | - request.get_content_type) |
919 | + self.assertEqual(None, request.get_content_type()) |
920 | |
921 | def test_content_type_unsupported(self): |
922 | request = wsgi.Request.blank('/tests/123', method='POST') |
923 | @@ -76,24 +75,48 @@ |
924 | self.assertEqual(result, "application/json") |
925 | |
926 | |
927 | +class ActionDispatcherTest(test.TestCase): |
928 | + def test_dispatch(self): |
929 | + serializer = wsgi.ActionDispatcher() |
930 | + serializer.create = lambda x: 'pants' |
931 | + self.assertEqual(serializer.dispatch({}, action='create'), 'pants') |
932 | + |
933 | + def test_dispatch_action_None(self): |
934 | + serializer = wsgi.ActionDispatcher() |
935 | + serializer.create = lambda x: 'pants' |
936 | + serializer.default = lambda x: 'trousers' |
937 | + self.assertEqual(serializer.dispatch({}, action=None), 'trousers') |
938 | + |
939 | + def test_dispatch_default(self): |
940 | + serializer = wsgi.ActionDispatcher() |
941 | + serializer.create = lambda x: 'pants' |
942 | + serializer.default = lambda x: 'trousers' |
943 | + self.assertEqual(serializer.dispatch({}, action='update'), 'trousers') |
944 | + |
945 | + |
946 | +class ResponseHeadersSerializerTest(test.TestCase): |
947 | + def test_default(self): |
948 | + serializer = wsgi.ResponseHeadersSerializer() |
949 | + response = webob.Response() |
950 | + serializer.serialize(response, {'v': '123'}, 'asdf') |
951 | + self.assertEqual(response.status_int, 200) |
952 | + |
953 | + def test_custom(self): |
954 | + class Serializer(wsgi.ResponseHeadersSerializer): |
955 | + def update(self, response, data): |
956 | + response.status_int = 404 |
957 | + response.headers['X-Custom-Header'] = data['v'] |
958 | + serializer = Serializer() |
959 | + response = webob.Response() |
960 | + serializer.serialize(response, {'v': '123'}, 'update') |
961 | + self.assertEqual(response.status_int, 404) |
962 | + self.assertEqual(response.headers['X-Custom-Header'], '123') |
963 | + |
964 | + |
965 | class DictSerializerTest(test.TestCase): |
966 | - def test_dispatch(self): |
967 | - serializer = wsgi.DictSerializer() |
968 | - serializer.create = lambda x: 'pants' |
969 | - serializer.default = lambda x: 'trousers' |
970 | - self.assertEqual(serializer.serialize({}, 'create'), 'pants') |
971 | - |
972 | def test_dispatch_default(self): |
973 | serializer = wsgi.DictSerializer() |
974 | - serializer.create = lambda x: 'pants' |
975 | - serializer.default = lambda x: 'trousers' |
976 | - self.assertEqual(serializer.serialize({}, 'update'), 'trousers') |
977 | - |
978 | - def test_dispatch_action_None(self): |
979 | - serializer = wsgi.DictSerializer() |
980 | - serializer.create = lambda x: 'pants' |
981 | - serializer.default = lambda x: 'trousers' |
982 | - self.assertEqual(serializer.serialize({}, None), 'trousers') |
983 | + self.assertEqual(serializer.serialize({}, 'update'), '') |
984 | |
985 | |
986 | class XMLDictSerializerTest(test.TestCase): |
987 | @@ -117,23 +140,9 @@ |
988 | |
989 | |
990 | class TextDeserializerTest(test.TestCase): |
991 | - def test_dispatch(self): |
992 | - deserializer = wsgi.TextDeserializer() |
993 | - deserializer.create = lambda x: 'pants' |
994 | - deserializer.default = lambda x: 'trousers' |
995 | - self.assertEqual(deserializer.deserialize({}, 'create'), 'pants') |
996 | - |
997 | def test_dispatch_default(self): |
998 | deserializer = wsgi.TextDeserializer() |
999 | - deserializer.create = lambda x: 'pants' |
1000 | - deserializer.default = lambda x: 'trousers' |
1001 | - self.assertEqual(deserializer.deserialize({}, 'update'), 'trousers') |
1002 | - |
1003 | - def test_dispatch_action_None(self): |
1004 | - deserializer = wsgi.TextDeserializer() |
1005 | - deserializer.create = lambda x: 'pants' |
1006 | - deserializer.default = lambda x: 'trousers' |
1007 | - self.assertEqual(deserializer.deserialize({}, None), 'trousers') |
1008 | + self.assertEqual(deserializer.deserialize({}, 'update'), {}) |
1009 | |
1010 | |
1011 | class JSONDeserializerTest(test.TestCase): |
1012 | @@ -144,12 +153,17 @@ |
1013 | "bs": ["1", "2", "3", {"c": {"c1": "1"}}], |
1014 | "d": {"e": "1"}, |
1015 | "f": "1"}}""" |
1016 | - as_dict = dict(a={ |
1017 | - 'a1': '1', |
1018 | - 'a2': '2', |
1019 | - 'bs': ['1', '2', '3', {'c': dict(c1='1')}], |
1020 | - 'd': {'e': '1'}, |
1021 | - 'f': '1'}) |
1022 | + as_dict = { |
1023 | + 'body': { |
1024 | + 'a': { |
1025 | + 'a1': '1', |
1026 | + 'a2': '2', |
1027 | + 'bs': ['1', '2', '3', {'c': {'c1': '1'}}], |
1028 | + 'd': {'e': '1'}, |
1029 | + 'f': '1', |
1030 | + }, |
1031 | + }, |
1032 | + } |
1033 | deserializer = wsgi.JSONDeserializer() |
1034 | self.assertEqual(deserializer.deserialize(data), as_dict) |
1035 | |
1036 | @@ -163,23 +177,44 @@ |
1037 | <f>1</f> |
1038 | </a> |
1039 | """.strip() |
1040 | - as_dict = dict(a={ |
1041 | - 'a1': '1', |
1042 | - 'a2': '2', |
1043 | - 'bs': ['1', '2', '3', {'c': dict(c1='1')}], |
1044 | - 'd': {'e': '1'}, |
1045 | - 'f': '1'}) |
1046 | + as_dict = { |
1047 | + 'body': { |
1048 | + 'a': { |
1049 | + 'a1': '1', |
1050 | + 'a2': '2', |
1051 | + 'bs': ['1', '2', '3', {'c': {'c1': '1'}}], |
1052 | + 'd': {'e': '1'}, |
1053 | + 'f': '1', |
1054 | + }, |
1055 | + }, |
1056 | + } |
1057 | metadata = {'plurals': {'bs': 'b', 'ts': 't'}} |
1058 | deserializer = wsgi.XMLDeserializer(metadata=metadata) |
1059 | self.assertEqual(deserializer.deserialize(xml), as_dict) |
1060 | |
1061 | def test_xml_empty(self): |
1062 | xml = """<a></a>""" |
1063 | - as_dict = {"a": {}} |
1064 | + as_dict = {"body": {"a": {}}} |
1065 | deserializer = wsgi.XMLDeserializer() |
1066 | self.assertEqual(deserializer.deserialize(xml), as_dict) |
1067 | |
1068 | |
1069 | +class RequestHeadersDeserializerTest(test.TestCase): |
1070 | + def test_default(self): |
1071 | + deserializer = wsgi.RequestHeadersDeserializer() |
1072 | + req = wsgi.Request.blank('/') |
1073 | + self.assertEqual(deserializer.deserialize(req, 'asdf'), {}) |
1074 | + |
1075 | + def test_custom(self): |
1076 | + class Deserializer(wsgi.RequestHeadersDeserializer): |
1077 | + def update(self, request): |
1078 | + return {'a': request.headers['X-Custom-Header']} |
1079 | + deserializer = Deserializer() |
1080 | + req = wsgi.Request.blank('/') |
1081 | + req.headers['X-Custom-Header'] = 'b' |
1082 | + self.assertEqual(deserializer.deserialize(req, 'update'), {'a': 'b'}) |
1083 | + |
1084 | + |
1085 | class ResponseSerializerTest(test.TestCase): |
1086 | def setUp(self): |
1087 | class JSONSerializer(object): |
1088 | @@ -190,29 +225,36 @@ |
1089 | def serialize(self, data, action='default'): |
1090 | return 'pew_xml' |
1091 | |
1092 | - self.serializers = { |
1093 | + class HeadersSerializer(object): |
1094 | + def serialize(self, response, data, action): |
1095 | + response.status_int = 404 |
1096 | + |
1097 | + self.body_serializers = { |
1098 | 'application/json': JSONSerializer(), |
1099 | 'application/XML': XMLSerializer(), |
1100 | } |
1101 | |
1102 | - self.serializer = wsgi.ResponseSerializer(serializers=self.serializers) |
1103 | + self.serializer = wsgi.ResponseSerializer(self.body_serializers, |
1104 | + HeadersSerializer()) |
1105 | |
1106 | def tearDown(self): |
1107 | pass |
1108 | |
1109 | def test_get_serializer(self): |
1110 | - self.assertEqual(self.serializer.get_serializer('application/json'), |
1111 | - self.serializers['application/json']) |
1112 | + ctype = 'application/json' |
1113 | + self.assertEqual(self.serializer.get_body_serializer(ctype), |
1114 | + self.body_serializers[ctype]) |
1115 | |
1116 | def test_get_serializer_unknown_content_type(self): |
1117 | self.assertRaises(exception.InvalidContentType, |
1118 | - self.serializer.get_serializer, |
1119 | + self.serializer.get_body_serializer, |
1120 | 'application/unknown') |
1121 | |
1122 | def test_serialize_response(self): |
1123 | response = self.serializer.serialize({}, 'application/json') |
1124 | self.assertEqual(response.headers['Content-Type'], 'application/json') |
1125 | self.assertEqual(response.body, 'pew_json') |
1126 | + self.assertEqual(response.status_int, 404) |
1127 | |
1128 | def test_serialize_response_dict_to_unknown_content_type(self): |
1129 | self.assertRaises(exception.InvalidContentType, |
1130 | @@ -230,24 +272,23 @@ |
1131 | def deserialize(self, data, action='default'): |
1132 | return 'pew_xml' |
1133 | |
1134 | - self.deserializers = { |
1135 | + self.body_deserializers = { |
1136 | 'application/json': JSONDeserializer(), |
1137 | 'application/XML': XMLDeserializer(), |
1138 | } |
1139 | |
1140 | - self.deserializer = wsgi.RequestDeserializer( |
1141 | - deserializers=self.deserializers) |
1142 | + self.deserializer = wsgi.RequestDeserializer(self.body_deserializers) |
1143 | |
1144 | def tearDown(self): |
1145 | pass |
1146 | |
1147 | def test_get_deserializer(self): |
1148 | - expected = self.deserializer.get_deserializer('application/json') |
1149 | - self.assertEqual(expected, self.deserializers['application/json']) |
1150 | + expected = self.deserializer.get_body_deserializer('application/json') |
1151 | + self.assertEqual(expected, self.body_deserializers['application/json']) |
1152 | |
1153 | def test_get_deserializer_unknown_content_type(self): |
1154 | self.assertRaises(exception.InvalidContentType, |
1155 | - self.deserializer.get_deserializer, |
1156 | + self.deserializer.get_body_deserializer, |
1157 | 'application/unknown') |
1158 | |
1159 | def test_get_expected_content_type(self): |
1160 | |
1161 | === modified file 'nova/tests/integrated/api/client.py' |
1162 | --- nova/tests/integrated/api/client.py 2011-06-24 12:01:51 +0000 |
1163 | +++ nova/tests/integrated/api/client.py 2011-07-11 17:17:32 +0000 |
1164 | @@ -71,8 +71,8 @@ |
1165 | self.auth_uri = auth_uri |
1166 | |
1167 | def request(self, url, method='GET', body=None, headers=None): |
1168 | - if headers is None: |
1169 | - headers = {} |
1170 | + _headers = {'Content-Type': 'application/json'} |
1171 | + _headers.update(headers or {}) |
1172 | |
1173 | parsed_url = urlparse.urlparse(url) |
1174 | port = parsed_url.port |
1175 | @@ -94,9 +94,8 @@ |
1176 | LOG.info(_("Doing %(method)s on %(relative_url)s") % locals()) |
1177 | if body: |
1178 | LOG.info(_("Body: %s") % body) |
1179 | - headers.setdefault('Content-Type', 'application/json') |
1180 | |
1181 | - conn.request(method, relative_url, body, headers) |
1182 | + conn.request(method, relative_url, body, _headers) |
1183 | response = conn.getresponse() |
1184 | return response |
1185 |
Changes look good and tests pass.