Merge lp:~rackspace-titan/nova/limits-v1-1-response-formatting into lp:~hudson-openstack/nova/trunk
- limits-v1-1-response-formatting
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Devin Carlen |
Approved revision: | 1300 |
Merged at revision: | 1304 |
Proposed branch: | lp:~rackspace-titan/nova/limits-v1-1-response-formatting |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
428 lines (+270/-15) 3 files modified
nova/api/openstack/limits.py (+59/-2) nova/api/openstack/views/limits.py (+5/-1) nova/tests/api/openstack/test_limits.py (+206/-12) |
To merge this branch: | bzr merge lp:~rackspace-titan/nova/limits-v1-1-response-formatting |
Related bugs: | |
Related blueprints: |
Openstack API 1.1 Finalization
(Essential)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brian Waldon (community) | Approve | ||
Devin Carlen (community) | Approve | ||
Review via email: mp+68695@code.launchpad.net |
Commit message
Description of the change
Adds an XML serializer for limits and adds tests for the Limits view builder.
Brian Waldon (bcwaldon) wrote : | # |
Looks good. One thing that seems a bit off is the format of next-available. The code is returning an integer (1311272226) while the spec expects a string (2011-12-
OpenStack Infra (hudson-openstack) wrote : | # |
The attempt to merge lp:~rackspace-titan/nova/limits-v1-1-response-formatting into lp:nova failed. Below is the output from the failed tests.
FloatingIpTest
test_
test_
test_
test_
test_
test_
test_
FixedIpTest
test_
test_
test_
test_
FlavorsExtraSpe
test_create OK 0.05
test_
test_delete OK 0.05
test_index OK 0.05
test_
test_show OK 0.05
test_
test_
test_
test_
test_
AccountsTest
test_
test_
test_
test_
AdminAPITest
test_
test_
APITest
test_
test_
test_
Test
test_
test_
test_
test_
test_bad_token OK 0.06
test_
test_
test_no_user OK 0.05
test_
test_
Alex Meade (alex-meade) wrote : | # |
The merge failed because hudson is in a different timezone. I'm updating to use utc time, assuming this is the way to get since utils.py has a Z at the end of TIME_FORMAT.
Devin Carlen (devcamcar) wrote : | # |
Trying again.
Brian Waldon (bcwaldon) : | # |
Preview Diff
1 | === modified file 'nova/api/openstack/limits.py' |
2 | --- nova/api/openstack/limits.py 2011-07-14 16:37:32 +0000 |
3 | +++ nova/api/openstack/limits.py 2011-07-21 21:14:31 +0000 |
4 | @@ -25,6 +25,7 @@ |
5 | import time |
6 | import urllib |
7 | import webob.exc |
8 | +from xml.dom import minidom |
9 | |
10 | from collections import defaultdict |
11 | |
12 | @@ -76,6 +77,58 @@ |
13 | return limits_views.ViewBuilderV11() |
14 | |
15 | |
16 | +class LimitsXMLSerializer(wsgi.XMLDictSerializer): |
17 | + |
18 | + xmlns = wsgi.XMLNS_V11 |
19 | + |
20 | + def __init__(self): |
21 | + pass |
22 | + |
23 | + def _create_rates_node(self, xml_doc, rates): |
24 | + rates_node = xml_doc.createElement('rates') |
25 | + for rate in rates: |
26 | + rate_node = xml_doc.createElement('rate') |
27 | + rate_node.setAttribute('uri', rate['uri']) |
28 | + rate_node.setAttribute('regex', rate['regex']) |
29 | + |
30 | + for limit in rate['limit']: |
31 | + limit_node = xml_doc.createElement('limit') |
32 | + limit_node.setAttribute('value', str(limit['value'])) |
33 | + limit_node.setAttribute('verb', limit['verb']) |
34 | + limit_node.setAttribute('remaining', str(limit['remaining'])) |
35 | + limit_node.setAttribute('unit', limit['unit']) |
36 | + limit_node.setAttribute('next-available', |
37 | + str(limit['next-available'])) |
38 | + rate_node.appendChild(limit_node) |
39 | + |
40 | + rates_node.appendChild(rate_node) |
41 | + return rates_node |
42 | + |
43 | + def _create_absolute_node(self, xml_doc, absolutes): |
44 | + absolute_node = xml_doc.createElement('absolute') |
45 | + for key, value in absolutes.iteritems(): |
46 | + limit_node = xml_doc.createElement('limit') |
47 | + limit_node.setAttribute('name', key) |
48 | + limit_node.setAttribute('value', str(value)) |
49 | + absolute_node.appendChild(limit_node) |
50 | + return absolute_node |
51 | + |
52 | + def _limits_to_xml(self, xml_doc, limits): |
53 | + limits_node = xml_doc.createElement('limits') |
54 | + rates_node = self._create_rates_node(xml_doc, limits['rate']) |
55 | + limits_node.appendChild(rates_node) |
56 | + |
57 | + absolute_node = self._create_absolute_node(xml_doc, limits['absolute']) |
58 | + limits_node.appendChild(absolute_node) |
59 | + |
60 | + return limits_node |
61 | + |
62 | + def index(self, limits_dict): |
63 | + xml_doc = minidom.Document() |
64 | + node = self._limits_to_xml(xml_doc, limits_dict['limits']) |
65 | + return self.to_xml_string(node, False) |
66 | + |
67 | + |
68 | def create_resource(version='1.0'): |
69 | controller = { |
70 | '1.0': LimitsControllerV10, |
71 | @@ -97,9 +150,13 @@ |
72 | }, |
73 | } |
74 | |
75 | + xml_serializer = { |
76 | + '1.0': wsgi.XMLDictSerializer(xmlns=xmlns, metadata=metadata), |
77 | + '1.1': LimitsXMLSerializer(), |
78 | + }[version] |
79 | + |
80 | body_serializers = { |
81 | - 'application/xml': wsgi.XMLDictSerializer(xmlns=xmlns, |
82 | - metadata=metadata), |
83 | + 'application/xml': xml_serializer, |
84 | } |
85 | |
86 | serializer = wsgi.ResponseSerializer(body_serializers) |
87 | |
88 | === modified file 'nova/api/openstack/views/limits.py' |
89 | --- nova/api/openstack/views/limits.py 2011-06-06 14:49:29 +0000 |
90 | +++ nova/api/openstack/views/limits.py 2011-07-21 21:14:31 +0000 |
91 | @@ -15,9 +15,11 @@ |
92 | # License for the specific language governing permissions and limitations |
93 | # under the License. |
94 | |
95 | +import datetime |
96 | import time |
97 | |
98 | from nova.api.openstack import common |
99 | +from nova import utils |
100 | |
101 | |
102 | class ViewBuilder(object): |
103 | @@ -113,10 +115,12 @@ |
104 | return limits |
105 | |
106 | def _build_rate_limit(self, rate_limit): |
107 | + next_avail = \ |
108 | + datetime.datetime.utcfromtimestamp(rate_limit["resetTime"]) |
109 | return { |
110 | "verb": rate_limit["verb"], |
111 | "value": rate_limit["value"], |
112 | "remaining": int(rate_limit["remaining"]), |
113 | "unit": rate_limit["unit"], |
114 | - "next-available": rate_limit["resetTime"], |
115 | + "next-available": utils.isotime(at=next_avail), |
116 | } |
117 | |
118 | === modified file 'nova/tests/api/openstack/test_limits.py' |
119 | --- nova/tests/api/openstack/test_limits.py 2011-07-14 17:40:38 +0000 |
120 | +++ nova/tests/api/openstack/test_limits.py 2011-07-21 21:14:31 +0000 |
121 | @@ -24,11 +24,12 @@ |
122 | import time |
123 | import unittest |
124 | import webob |
125 | - |
126 | -from xml.dom.minidom import parseString |
127 | +from xml.dom import minidom |
128 | |
129 | import nova.context |
130 | from nova.api.openstack import limits |
131 | +from nova.api.openstack import views |
132 | +from nova import test |
133 | |
134 | |
135 | TEST_LIMITS = [ |
136 | @@ -166,7 +167,7 @@ |
137 | request = self._get_index_request("application/xml") |
138 | response = request.get_response(self.controller) |
139 | |
140 | - expected = parseString(""" |
141 | + expected = minidom.parseString(""" |
142 | <limits |
143 | xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> |
144 | <rate/> |
145 | @@ -174,7 +175,7 @@ |
146 | </limits> |
147 | """.replace(" ", "")) |
148 | |
149 | - body = parseString(response.body.replace(" ", "")) |
150 | + body = minidom.parseString(response.body.replace(" ", "")) |
151 | |
152 | self.assertEqual(expected.toxml(), body.toxml()) |
153 | |
154 | @@ -184,7 +185,7 @@ |
155 | request = self._populate_limits(request) |
156 | response = request.get_response(self.controller) |
157 | |
158 | - expected = parseString(""" |
159 | + expected = minidom.parseString(""" |
160 | <limits |
161 | xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> |
162 | <rate> |
163 | @@ -196,7 +197,7 @@ |
164 | <absolute/> |
165 | </limits> |
166 | """.replace(" ", "")) |
167 | - body = parseString(response.body.replace(" ", "")) |
168 | + body = minidom.parseString(response.body.replace(" ", "")) |
169 | |
170 | self.assertEqual(expected.toxml(), body.toxml()) |
171 | |
172 | @@ -210,6 +211,7 @@ |
173 | """Run before each test.""" |
174 | BaseLimitTestSuite.setUp(self) |
175 | self.controller = limits.create_resource('1.1') |
176 | + self.maxDiff = None |
177 | |
178 | def _get_index_request(self, accept_header="application/json"): |
179 | """Helper to set routing arguments.""" |
180 | @@ -266,14 +268,14 @@ |
181 | "limit": [ |
182 | { |
183 | "verb": "GET", |
184 | - "next-available": 0, |
185 | + "next-available": "1970-01-01T00:00:00Z", |
186 | "unit": "MINUTE", |
187 | "value": 10, |
188 | "remaining": 10, |
189 | }, |
190 | { |
191 | "verb": "POST", |
192 | - "next-available": 0, |
193 | + "next-available": "1970-01-01T00:00:00Z", |
194 | "unit": "HOUR", |
195 | "value": 5, |
196 | "remaining": 5, |
197 | @@ -286,7 +288,7 @@ |
198 | "limit": [ |
199 | { |
200 | "verb": "GET", |
201 | - "next-available": 0, |
202 | + "next-available": "1970-01-01T00:00:00Z", |
203 | "unit": "MINUTE", |
204 | "value": 5, |
205 | "remaining": 5, |
206 | @@ -328,7 +330,7 @@ |
207 | "limit": [ |
208 | { |
209 | "verb": "GET", |
210 | - "next-available": 0, |
211 | + "next-available": "1970-01-01T00:00:00Z", |
212 | "unit": "MINUTE", |
213 | "value": 10, |
214 | "remaining": 10, |
215 | @@ -341,7 +343,7 @@ |
216 | "limit": [ |
217 | { |
218 | "verb": "GET", |
219 | - "next-available": 0, |
220 | + "next-available": "1970-01-01T00:00:00Z", |
221 | "unit": "MINUTE", |
222 | "value": 10, |
223 | "remaining": 10, |
224 | @@ -458,7 +460,7 @@ |
225 | response = request.get_response(self.app) |
226 | self.assertEqual(response.status_int, 403) |
227 | |
228 | - root = parseString(response.body).childNodes[0] |
229 | + root = minidom.parseString(response.body).childNodes[0] |
230 | expected = "Only 1 GET request(s) can be made to * every minute." |
231 | |
232 | details = root.getElementsByTagName("details") |
233 | @@ -904,3 +906,195 @@ |
234 | "made to /delayed every minute.") |
235 | |
236 | self.assertEqual((delay, error), expected) |
237 | + |
238 | + |
239 | +class LimitsViewBuilderV11Test(test.TestCase): |
240 | + |
241 | + def setUp(self): |
242 | + self.view_builder = views.limits.ViewBuilderV11() |
243 | + self.rate_limits = [ |
244 | + { |
245 | + "URI": "*", |
246 | + "regex": ".*", |
247 | + "value": 10, |
248 | + "verb": "POST", |
249 | + "remaining": 2, |
250 | + "unit": "MINUTE", |
251 | + "resetTime": 1311272226 |
252 | + }, |
253 | + { |
254 | + "URI": "*/servers", |
255 | + "regex": "^/servers", |
256 | + "value": 50, |
257 | + "verb": "POST", |
258 | + "remaining": 10, |
259 | + "unit": "DAY", |
260 | + "resetTime": 1311272226 |
261 | + }, |
262 | + ] |
263 | + self.absolute_limits = { |
264 | + "metadata_items": 1, |
265 | + "injected_files": 5, |
266 | + "injected_file_content_bytes": 5, |
267 | + } |
268 | + |
269 | + def tearDown(self): |
270 | + pass |
271 | + |
272 | + def test_build_limits(self): |
273 | + expected_limits = { |
274 | + "limits": { |
275 | + "rate": [ |
276 | + { |
277 | + "uri": "*", |
278 | + "regex": ".*", |
279 | + "limit": [ |
280 | + { |
281 | + "value": 10, |
282 | + "verb": "POST", |
283 | + "remaining": 2, |
284 | + "unit": "MINUTE", |
285 | + "next-available": "2011-07-21T18:17:06Z" |
286 | + }, |
287 | + ] |
288 | + }, |
289 | + { |
290 | + "uri": "*/servers", |
291 | + "regex": "^/servers", |
292 | + "limit": [ |
293 | + { |
294 | + "value": 50, |
295 | + "verb": "POST", |
296 | + "remaining": 10, |
297 | + "unit": "DAY", |
298 | + "next-available": "2011-07-21T18:17:06Z" |
299 | + }, |
300 | + ] |
301 | + }, |
302 | + ], |
303 | + "absolute": { |
304 | + "maxServerMeta": 1, |
305 | + "maxImageMeta": 1, |
306 | + "maxPersonality": 5, |
307 | + "maxPersonalitySize": 5 |
308 | + } |
309 | + } |
310 | + } |
311 | + |
312 | + output = self.view_builder.build(self.rate_limits, |
313 | + self.absolute_limits) |
314 | + self.assertDictMatch(output, expected_limits) |
315 | + |
316 | + def test_build_limits_empty_limits(self): |
317 | + expected_limits = { |
318 | + "limits": { |
319 | + "rate": [], |
320 | + "absolute": {} |
321 | + } |
322 | + } |
323 | + |
324 | + abs_limits = {} |
325 | + rate_limits = [] |
326 | + output = self.view_builder.build(rate_limits, abs_limits) |
327 | + self.assertDictMatch(output, expected_limits) |
328 | + |
329 | + |
330 | +class LimitsXMLSerializationTest(test.TestCase): |
331 | + |
332 | + def setUp(self): |
333 | + self.maxDiff = None |
334 | + |
335 | + def tearDown(self): |
336 | + pass |
337 | + |
338 | + def test_index(self): |
339 | + serializer = limits.LimitsXMLSerializer() |
340 | + |
341 | + fixture = { |
342 | + "limits": { |
343 | + "rate": [ |
344 | + { |
345 | + "uri": "*", |
346 | + "regex": ".*", |
347 | + "limit": [ |
348 | + { |
349 | + "value": 10, |
350 | + "verb": "POST", |
351 | + "remaining": 2, |
352 | + "unit": "MINUTE", |
353 | + "next-available": "2011-12-15T22:42:45Z" |
354 | + }, |
355 | + ] |
356 | + }, |
357 | + { |
358 | + "uri": "*/servers", |
359 | + "regex": "^/servers", |
360 | + "limit": [ |
361 | + { |
362 | + "value": 50, |
363 | + "verb": "POST", |
364 | + "remaining": 10, |
365 | + "unit": "DAY", |
366 | + "next-available": "2011-12-15T22:42:45Z" |
367 | + }, |
368 | + ] |
369 | + }, |
370 | + ], |
371 | + "absolute": { |
372 | + "maxServerMeta": 1, |
373 | + "maxImageMeta": 1, |
374 | + "maxPersonality": 5, |
375 | + "maxPersonalitySize": 10240 |
376 | + } |
377 | + } |
378 | + } |
379 | + |
380 | + output = serializer.serialize(fixture, 'index') |
381 | + actual = minidom.parseString(output.replace(" ", "")) |
382 | + |
383 | + expected = minidom.parseString(""" |
384 | + <limits xmlns="http://docs.openstack.org/compute/api/v1.1"> |
385 | + <rates> |
386 | + <rate uri="*" regex=".*"> |
387 | + <limit value="10" verb="POST" remaining="2" |
388 | + unit="MINUTE" |
389 | + next-available="2011-12-15T22:42:45Z"/> |
390 | + </rate> |
391 | + <rate uri="*/servers" regex="^/servers"> |
392 | + <limit value="50" verb="POST" remaining="10" |
393 | + unit="DAY" |
394 | + next-available="2011-12-15T22:42:45Z"/> |
395 | + </rate> |
396 | + </rates> |
397 | + <absolute> |
398 | + <limit name="maxServerMeta" value="1"/> |
399 | + <limit name="maxPersonality" value="5"/> |
400 | + <limit name="maxImageMeta" value="1"/> |
401 | + <limit name="maxPersonalitySize" value="10240"/> |
402 | + </absolute> |
403 | + </limits> |
404 | + """.replace(" ", "")) |
405 | + |
406 | + self.assertEqual(expected.toxml(), actual.toxml()) |
407 | + |
408 | + def test_index_no_limits(self): |
409 | + serializer = limits.LimitsXMLSerializer() |
410 | + |
411 | + fixture = { |
412 | + "limits": { |
413 | + "rate": [], |
414 | + "absolute": {} |
415 | + } |
416 | + } |
417 | + |
418 | + output = serializer.serialize(fixture, 'index') |
419 | + actual = minidom.parseString(output.replace(" ", "")) |
420 | + |
421 | + expected = minidom.parseString(""" |
422 | + <limits xmlns="http://docs.openstack.org/compute/api/v1.1"> |
423 | + <rates /> |
424 | + <absolute /> |
425 | + </limits> |
426 | + """.replace(" ", "")) |
427 | + |
428 | + self.assertEqual(expected.toxml(), actual.toxml()) |
lgtm