Merge lp:~rackspace-titan/nova/limits-v1-1-response-formatting into lp:~hudson-openstack/nova/trunk

Proposed by Alex Meade
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
Reviewer Review Type Date Requested Status
Brian Waldon (community) Approve
Devin Carlen (community) Approve
Review via email: mp+68695@code.launchpad.net

Description of the change

Adds an XML serializer for limits and adds tests for the Limits view builder.

To post a comment you must log in.
Revision history for this message
Devin Carlen (devcamcar) wrote :

lgtm

review: Approve
Revision history for this message
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-15T22:42:45Z)

review: Needs Fixing
Revision history for this message
Brian Waldon (bcwaldon) wrote :

Looks good.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :
Download full text (189.5 KiB)

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_floating_ip_allocate OK 0.30
    test_floating_ip_associate OK 0.11
    test_floating_ip_disassociate OK 0.10
    test_floating_ip_release OK 0.10
    test_floating_ip_show OK 0.10
    test_floating_ips_list OK 0.09
    test_translate_floating_ip_view OK 0.04
FixedIpTest
    test_add_fixed_ip OK 0.08
    test_add_fixed_ip_no_network OK 0.08
    test_remove_fixed_ip OK 0.25
    test_remove_fixed_ip_no_address OK 0.07
FlavorsExtraSpecsTest
    test_create OK 0.05
    test_create_empty_body OK 0.05
    test_delete OK 0.05
    test_index OK 0.05
    test_index_no_data OK 0.05
    test_show OK 0.05
    test_show_spec_not_found OK 0.05
    test_update_item OK 0.05
    test_update_item_body_uri_mismatch OK 0.05
    test_update_item_empty_body OK 0.05
    test_update_item_too_many_keys OK 0.05
AccountsTest
    test_account_create OK 0.36
    test_account_delete OK 0.17
    test_account_update OK 0.18
    test_get_account OK 0.17
AdminAPITest
    test_admin_disabled OK 0.14
    test_admin_enabled OK 0.42
APITest
    test_exceptions_are_converted_to_faults OK 0.01
    test_malformed_json OK 0.06
    test_malformed_xml OK 0.06
Test
    test_authorize_project OK 0.11
    test_authorize_token OK 0.11
    test_authorize_user OK 0.06
    test_bad_project OK 0.31
    test_bad_token OK 0.06
    test_bad_user_bad_key OK 0.06
    test_bad_user_good_key OK 0.05
    test_no_user OK 0.05
    test_not_existing_project OK 0.10
    test_token_expiry ...

Revision history for this message
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.

Revision history for this message
Devin Carlen (devcamcar) wrote :

Trying again.

Revision history for this message
Brian Waldon (bcwaldon) :
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/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())