Merge lp:~justin-fathomdb/nova/bug740576 into lp:~hudson-openstack/nova/trunk
- bug740576
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Vish Ishaya | ||||
Approved revision: | 856 | ||||
Merged at revision: | 949 | ||||
Proposed branch: | lp:~justin-fathomdb/nova/bug740576 | ||||
Merge into: | lp:~hudson-openstack/nova/trunk | ||||
Diff against target: |
702 lines (+165/-53) 21 files modified
nova/api/direct.py (+5/-1) nova/api/openstack/accounts.py (+2/-3) nova/api/openstack/backup_schedules.py (+2/-2) nova/api/openstack/common.py (+11/-0) nova/api/openstack/consoles.py (+2/-2) nova/api/openstack/extensions.py (+6/-4) nova/api/openstack/faults.py (+3/-2) nova/api/openstack/flavors.py (+5/-2) nova/api/openstack/image_metadata.py (+2/-1) nova/api/openstack/images.py (+4/-4) nova/api/openstack/limits.py (+2/-2) nova/api/openstack/server_metadata.py (+2/-1) nova/api/openstack/servers.py (+4/-1) nova/api/openstack/shared_ip_groups.py (+2/-2) nova/api/openstack/users.py (+1/-2) nova/api/openstack/zones.py (+2/-4) nova/tests/api/openstack/test_faults.py (+5/-4) nova/tests/api/openstack/test_images.py (+13/-6) nova/tests/api/openstack/test_limits.py (+13/-5) nova/tests/integrated/test_xml.py (+56/-0) nova/wsgi.py (+23/-5) |
||||
To merge this branch: | bzr merge lp:~justin-fathomdb/nova/bug740576 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Brian Lamar (community) | Approve | ||
Vish Ishaya (community) | Approve | ||
Brian Waldon (community) | Approve | ||
Soren Hansen (community) | Approve | ||
termie (community) | Needs Fixing | ||
Review via email: mp+54447@code.launchpad.net |
Commit message
Description of the change
Support providing an XML namespace on the XML output from the OpenStack API
justinsb (justin-fathomdb) wrote : | # |
Thanks Brian. Yes - get_default_xmlns is the hook point at the moment. But
it's actually complicated by the fact that we don't have a 1.1 XSD yet (or
even an XML namespace?) It may be that the right place is actually in the
serialization_
particular namespace.
I'm about to open a bug on the fact that we don't have an XML NS or XSD for
v1.1. Once we have that, the best way to handle this should be a lot
clearer, but I wanted to put in a suggested placeholder hook.
I'd like to see this land first though, because without it I can't do any
validation against the XSD schemas we do have.
Brian Lamar (blamar) wrote : | # |
Should we put OpenStack API specific information in nova/wsgi.py? I think that should go in the nova/api/
Unit tests for verification?
justinsb (justin-fathomdb) wrote : | # |
Thanks Brian (L). I agree that the OpenStack namespace shouldn't be in wsgi.py long-term. Until we see what's going to happen with v1.1 I don't think we really know where it should go. I'm guessing we'll need a new class OpenStackController from which all Openstack controllers will derive, but it's too early to tell. I added a big note, and hopefully we can get the v1.1 schema soon and get this all fixed 'for real'.
Thankfully there are some unit tests that were broken, but I had forgotten to push them. The limits tests check the XML explicitly, so I added the namespace check there.
Brian Lamar (blamar) wrote : | # |
Hey Justin, I really don't think a note should be the answer. Having implementation-
Based on the current code I would:
1) Add support for an XML namespace at the Serializer level
2) Break out a custom OpenStack Controller and Serializer and re-point all current Controllers to these new ones which have the XML namespace.
Seeing how we have a good deal of versioning changes coming down the line, this is going to change significantly and I'd like to propose that I can handle this bug after working with the versioning merge props, or that you wait on this until the versioning changes go in.
justinsb (justin-fathomdb) wrote : | # |
Brian: Which merge proposal are you talking about here? Is it going to make the cutoff?
Brian Lamar (blamar) wrote : | # |
https:/
It was merged 12 hours ago, sorry for the lag in discussion here, but I've been working on some other branches. I'm going to look at this now to see what this change might entail re: the current trunk.
justinsb (justin-fathomdb) wrote : | # |
No worries. This is a bugfix, so isn't subject to FF, so doesn't need to
get attention today.
termie (termie) wrote : | # |
I think I agree with Brian Lamar as to where this code should live, a subclass of our controller/
justinsb (justin-fathomdb) wrote : | # |
Refactored per suggestions; fixed up failing tests and added an explicit one.
Brian Lamar (blamar) wrote : | # |
Justin, quick question: nova/tests/
Are those integration tests testing v1.0 or v1.1?
Thanks!
justinsb (justin-fathomdb) wrote : | # |
The integrated tests currently only test one endpoint, and they're bound to
v1.1 so that I can test extensions (v1.1 only) The v1.1 bit of the URL is
supplied by the TestOpenStackClient in
nova/tests/
Limits hasn't changed between v1.0 and v1.1 (that I know of), so presumably
we're not going to change the XML namespace for the fun of it.
Servers has changed between v1.0 and v1.1, so that element must come from
the new XML namespace.
That's what the test is checking. Fun, huh?
Brian Lamar (blamar) wrote : | # |
v1.1 Limits: http://
v1.0 Limits: http://
I think the v1.1 limits XMLNS is a typo, but they are absolutely different.
Brian Lamar (blamar) wrote : | # |
justinsb (justin-fathomdb) wrote : | # |
Thanks Brian. And bugger.
I was going by the fact that the limits.py implementation doesn't
differentiate.
This is sort of my worst fears realized... even once we get the namespace
declared, the XML output is likely to not be 100% correct. We need the
namespace and the schema to even start verifying this.
Do you know of any off the top of your head that legitimately didn't change?
If not, I suggest we go with the namespace declaration as-is if we're
otherwise happy, and then fix the other problems. Otherwise this patch is
going to get huge, as my patches have a tendency to do.
justinsb (justin-fathomdb) wrote : | # |
I filed bug #746016 to track the limits problem. I think it's a much bigger
problem (and applies to JSON and XML), so I'd suggest that we shouldn't
block this branch from merging because of it.
Brian Waldon (bcwaldon) wrote : | # |
Looking better. The following came to mind while reading over your diff. They aren't anything major:
1) Would it make more sense to put the OpenstackController and xmlns strings in (a newly created) api/openstack/
2) Does the word "default" belong in "get_default_
justinsb (justin-fathomdb) wrote : | # |
Thanks Brian. I feel 'common' is already there, so I'm leaning towards
sticking with what we've got. Don't know how others feel?
The default in get_default_xmlns is because it won't be applied if a
namespace is applied in some other way. For example, I suspect we'll come
across a need to explicitly specify namespaces for some responses, for
example if we need to mix namespaces in the same response.
Brian Waldon (bcwaldon) wrote : | # |
> Thanks Brian. I feel 'common' is already there, so I'm leaning towards
> sticking with what we've got. Don't know how others feel?
The other issue is that there is already 'wsgi' related code in __init__.py, so if anything, OpenstackController should live there.
> The default in get_default_xmlns is because it won't be applied if a
> namespace is applied in some other way. For example, I suspect we'll come
> across a need to explicitly specify namespaces for some responses, for
> example if we need to mix namespaces in the same response.
Okay. I missed that in the code.
justinsb (justin-fathomdb) wrote : | # |
I initially put it into __init__.py, but - to be honest - I'm still a python
newbie, and couldn't figure out how to import it. Any pointers?
Brian Waldon (bcwaldon) wrote : | # |
> I initially put it into __init__.py, but - to be honest - I'm still a python
> newbie, and couldn't figure out how to import it. Any pointers?
I wouldn't say I'm a pro either, but you should be able to import the parent module (nova.api.
Vish Ishaya (vishvananda) wrote : | # |
In general, we should be keeping code out of init.
Class definitions can go there, but in general I prefer keeping them out as well.
On Mar 31, 2011, at 1:56 PM, Brian Waldon wrote:
>> I initially put it into __init__.py, but - to be honest - I'm still a python
>> newbie, and couldn't figure out how to import it. Any pointers?
>
> I wouldn't say I'm a pro either, but you should be able to import the parent module (nova.api.
> --
> https:/
> You are subscribed to branch lp:nova.
Brian Waldon (bcwaldon) wrote : | # |
> In general, we should be keeping code out of init.
>
> Class definitions can go there, but in general I prefer keeping them out as
> well.
>
I definitely agree. But in this case, most wsgi-related code already lives in __init__.py and it would be a pain to change it across the project.
justinsb (justin-fathomdb) wrote : | # |
Sorry Brian - missed your comment.
Let's get this merged... I don't mind where it lives. Brian - are you OK with Vish making the call as PTL? Or could you explain a bit more why this needs to live in __init__ - in the patch below it's in common and it doesn't look too bad...
Brian Lamar (blamar) wrote : | # |
Should XML_NS_V10 and XML_NS_V11 be flags? I could see them changing and they might be needed other places. Otherwise looks ok to me!
justinsb (justin-fathomdb) wrote : | # |
I don't think we should let end-users change the schema namespace, so I
don't think it should be a flag. I think if anyone other code needs them
they can just import the 'common' module (or wherever they end up)
Brian Waldon (bcwaldon) wrote : | # |
> Let's get this merged... I don't mind where it lives. Brian - are you OK with
> Vish making the call as PTL? Or could you explain a bit more why this needs
> to live in __init__ - in the patch below it's in common and it doesn't look
> too bad...
I do agree with Vish that we should keep as much code out of __init__.py as possible. We have already disregarded that guideline and put the OS-specific wsgi code directly in __init__.py (such as APIRouter and FaultWrapper). My end goal here is to make a wsgi-specific module (perhaps api/openstack/
Vish Ishaya (vishvananda) wrote : | # |
> I do agree with Vish that we should keep as much code out of __init__.py as
> possible. We have already disregarded that guideline and put the OS-specific
> wsgi code directly in __init__.py (such as APIRouter and FaultWrapper). My end
> goal here is to make a wsgi-specific module (perhaps api/openstack/
> and for this branch I wanted to group what will go there into __init__.py for
> now. I would be fine leaving it in common.py with the assumption that it will
> be moving for Diablo.
This is fine with me. Please make a bug for this so we don't lose track of it.
Brian Lamar (blamar) : | # |
Vish Ishaya (vishvananda) wrote : | # |
Termie is away today. I also think his concerns have been addressed, so I'm going to go ahead and approve this.
OpenStack Infra (hudson-openstack) wrote : | # |
The attempt to merge lp:~justin-fathomdb/nova/bug740576 into lp:nova failed. Below is the output from the failed tests.
AccountsTest
test_
test_
test_
test_
AdminAPITest
test_
test_
APITest
test_
Test
test_
test_
test_bad_token OK
test_
test_
test_no_user OK
test_
TestFunctional
test_
test_
TestLimiter
test_
LimiterTest
test_
test_
test_
test_
test_
test_
test_
test_
test_
test_
test_
test_
test_
ActionExtensionTest
test_
test_
test_
ExtensionContro
test_
test_index OK
ExtensionManage
test_
ResourceExtensi
test_
test_
test_
ResponseExtensi
test_
test_
TestFaults
test_
test_
...
justinsb (justin-fathomdb) wrote : | # |
Another test was added that didn't check the namespace during the approval process. Remerged with trunk & fixed
Preview Diff
1 | === modified file 'nova/api/direct.py' |
2 | --- nova/api/direct.py 2011-03-24 20:20:15 +0000 |
3 | +++ nova/api/direct.py 2011-04-06 20:56:32 +0000 |
4 | @@ -206,10 +206,14 @@ |
5 | # NOTE(vish): make sure we have no unicode keys for py2.6. |
6 | params = dict([(str(k), v) for (k, v) in params.iteritems()]) |
7 | result = method(context, **params) |
8 | + |
9 | if result is None or type(result) is str or type(result) is unicode: |
10 | return result |
11 | + |
12 | try: |
13 | - return self._serialize(result, req.best_match_content_type()) |
14 | + content_type = req.best_match_content_type() |
15 | + default_xmlns = self.get_default_xmlns(req) |
16 | + return self._serialize(result, content_type, default_xmlns) |
17 | except: |
18 | raise exception.Error("returned non-serializable type: %s" |
19 | % result) |
20 | |
21 | === modified file 'nova/api/openstack/accounts.py' |
22 | --- nova/api/openstack/accounts.py 2011-03-18 14:34:08 +0000 |
23 | +++ nova/api/openstack/accounts.py 2011-04-06 20:56:32 +0000 |
24 | @@ -13,15 +13,14 @@ |
25 | # License for the specific language governing permissions and limitations |
26 | # under the License. |
27 | |
28 | -import common |
29 | import webob.exc |
30 | |
31 | from nova import exception |
32 | from nova import flags |
33 | from nova import log as logging |
34 | -from nova import wsgi |
35 | |
36 | from nova.auth import manager |
37 | +from nova.api.openstack import common |
38 | from nova.api.openstack import faults |
39 | |
40 | FLAGS = flags.FLAGS |
41 | @@ -35,7 +34,7 @@ |
42 | manager=account.project_manager_id) |
43 | |
44 | |
45 | -class Controller(wsgi.Controller): |
46 | +class Controller(common.OpenstackController): |
47 | |
48 | _serialization_metadata = { |
49 | 'application/xml': { |
50 | |
51 | === modified file 'nova/api/openstack/backup_schedules.py' |
52 | --- nova/api/openstack/backup_schedules.py 2011-03-28 18:31:12 +0000 |
53 | +++ nova/api/openstack/backup_schedules.py 2011-04-06 20:56:32 +0000 |
54 | @@ -19,7 +19,7 @@ |
55 | |
56 | from webob import exc |
57 | |
58 | -from nova import wsgi |
59 | +from nova.api.openstack import common |
60 | from nova.api.openstack import faults |
61 | import nova.image.service |
62 | |
63 | @@ -29,7 +29,7 @@ |
64 | return dict(backupSchedule=inst) |
65 | |
66 | |
67 | -class Controller(wsgi.Controller): |
68 | +class Controller(common.OpenstackController): |
69 | """ The backup schedule API controller for the Openstack API """ |
70 | |
71 | _serialization_metadata = { |
72 | |
73 | === modified file 'nova/api/openstack/common.py' |
74 | --- nova/api/openstack/common.py 2011-03-28 01:48:32 +0000 |
75 | +++ nova/api/openstack/common.py 2011-04-06 20:56:32 +0000 |
76 | @@ -22,6 +22,7 @@ |
77 | from nova import exception |
78 | from nova import flags |
79 | from nova import log as logging |
80 | +from nova import wsgi |
81 | |
82 | |
83 | LOG = logging.getLogger('common') |
84 | @@ -30,6 +31,10 @@ |
85 | FLAGS = flags.FLAGS |
86 | |
87 | |
88 | +XML_NS_V10 = 'http://docs.rackspacecloud.com/servers/api/v1.0' |
89 | +XML_NS_V11 = 'http://docs.openstack.org/compute/api/v1.1' |
90 | + |
91 | + |
92 | def limited(items, request, max_limit=FLAGS.osapi_max_limit): |
93 | """ |
94 | Return a slice of items according to requested offset and limit. |
95 | @@ -128,3 +133,9 @@ |
96 | except: |
97 | LOG.debug(_("Error extracting id from href: %s") % href) |
98 | raise webob.exc.HTTPBadRequest(_('could not parse id from href')) |
99 | + |
100 | + |
101 | +class OpenstackController(wsgi.Controller): |
102 | + def get_default_xmlns(self, req): |
103 | + # Use V10 by default |
104 | + return XML_NS_V10 |
105 | |
106 | === modified file 'nova/api/openstack/consoles.py' |
107 | --- nova/api/openstack/consoles.py 2011-03-11 19:49:32 +0000 |
108 | +++ nova/api/openstack/consoles.py 2011-04-06 20:56:32 +0000 |
109 | @@ -19,7 +19,7 @@ |
110 | |
111 | from nova import console |
112 | from nova import exception |
113 | -from nova import wsgi |
114 | +from nova.api.openstack import common |
115 | from nova.api.openstack import faults |
116 | |
117 | |
118 | @@ -43,7 +43,7 @@ |
119 | return dict(console=info) |
120 | |
121 | |
122 | -class Controller(wsgi.Controller): |
123 | +class Controller(common.OpenstackController): |
124 | """The Consoles Controller for the Openstack API""" |
125 | |
126 | _serialization_metadata = { |
127 | |
128 | === modified file 'nova/api/openstack/extensions.py' |
129 | --- nova/api/openstack/extensions.py 2011-03-30 01:16:09 +0000 |
130 | +++ nova/api/openstack/extensions.py 2011-04-06 20:56:32 +0000 |
131 | @@ -28,6 +28,7 @@ |
132 | from nova import flags |
133 | from nova import log as logging |
134 | from nova import wsgi |
135 | +from nova.api.openstack import common |
136 | from nova.api.openstack import faults |
137 | |
138 | |
139 | @@ -115,7 +116,7 @@ |
140 | return response_exts |
141 | |
142 | |
143 | -class ActionExtensionController(wsgi.Controller): |
144 | +class ActionExtensionController(common.OpenstackController): |
145 | |
146 | def __init__(self, application): |
147 | |
148 | @@ -136,7 +137,7 @@ |
149 | return res |
150 | |
151 | |
152 | -class ResponseExtensionController(wsgi.Controller): |
153 | +class ResponseExtensionController(common.OpenstackController): |
154 | |
155 | def __init__(self, application): |
156 | self.application = application |
157 | @@ -155,7 +156,8 @@ |
158 | body = res.body |
159 | headers = res.headers |
160 | except AttributeError: |
161 | - body = self._serialize(res, content_type) |
162 | + default_xmlns = None |
163 | + body = self._serialize(res, content_type, default_xmlns) |
164 | headers = {"Content-Type": content_type} |
165 | res = webob.Response() |
166 | res.body = body |
167 | @@ -163,7 +165,7 @@ |
168 | return res |
169 | |
170 | |
171 | -class ExtensionController(wsgi.Controller): |
172 | +class ExtensionController(common.OpenstackController): |
173 | |
174 | def __init__(self, extension_manager): |
175 | self.extension_manager = extension_manager |
176 | |
177 | === modified file 'nova/api/openstack/faults.py' |
178 | --- nova/api/openstack/faults.py 2011-03-29 18:56:18 +0000 |
179 | +++ nova/api/openstack/faults.py 2011-04-06 20:56:32 +0000 |
180 | @@ -20,10 +20,10 @@ |
181 | import webob.exc |
182 | |
183 | from nova import wsgi |
184 | +from nova.api.openstack import common |
185 | |
186 | |
187 | class Fault(webob.exc.HTTPException): |
188 | - |
189 | """An RS API fault response.""" |
190 | |
191 | _fault_names = { |
192 | @@ -57,7 +57,8 @@ |
193 | fault_data[fault_name]['retryAfter'] = retry |
194 | # 'code' is an attribute on the fault tag itself |
195 | metadata = {'application/xml': {'attributes': {fault_name: 'code'}}} |
196 | - serializer = wsgi.Serializer(metadata) |
197 | + default_xmlns = common.XML_NS_V10 |
198 | + serializer = wsgi.Serializer(metadata, default_xmlns) |
199 | content_type = req.best_match_content_type() |
200 | self.wrapped_exc.body = serializer.serialize(fault_data, content_type) |
201 | self.wrapped_exc.content_type = content_type |
202 | |
203 | === modified file 'nova/api/openstack/flavors.py' |
204 | --- nova/api/openstack/flavors.py 2011-03-24 16:46:39 +0000 |
205 | +++ nova/api/openstack/flavors.py 2011-04-06 20:56:32 +0000 |
206 | @@ -19,11 +19,11 @@ |
207 | |
208 | from nova import db |
209 | from nova import exception |
210 | -from nova import wsgi |
211 | +from nova.api.openstack import common |
212 | from nova.api.openstack import views |
213 | |
214 | |
215 | -class Controller(wsgi.Controller): |
216 | +class Controller(common.OpenstackController): |
217 | """Flavor controller for the OpenStack API.""" |
218 | |
219 | _serialization_metadata = { |
220 | @@ -76,3 +76,6 @@ |
221 | def _get_view_builder(self, req): |
222 | base_url = req.application_url |
223 | return views.flavors.ViewBuilderV11(base_url) |
224 | + |
225 | + def get_default_xmlns(self, req): |
226 | + return common.XML_NS_V11 |
227 | |
228 | === modified file 'nova/api/openstack/image_metadata.py' |
229 | --- nova/api/openstack/image_metadata.py 2011-03-25 14:07:42 +0000 |
230 | +++ nova/api/openstack/image_metadata.py 2011-04-06 20:56:32 +0000 |
231 | @@ -20,13 +20,14 @@ |
232 | from nova import flags |
233 | from nova import utils |
234 | from nova import wsgi |
235 | +from nova.api.openstack import common |
236 | from nova.api.openstack import faults |
237 | |
238 | |
239 | FLAGS = flags.FLAGS |
240 | |
241 | |
242 | -class Controller(wsgi.Controller): |
243 | +class Controller(common.OpenstackController): |
244 | """The image metadata API controller for the Openstack API""" |
245 | |
246 | def __init__(self): |
247 | |
248 | === modified file 'nova/api/openstack/images.py' |
249 | --- nova/api/openstack/images.py 2011-03-29 16:12:26 +0000 |
250 | +++ nova/api/openstack/images.py 2011-04-06 20:56:32 +0000 |
251 | @@ -13,8 +13,6 @@ |
252 | # License for the specific language governing permissions and limitations |
253 | # under the License. |
254 | |
255 | -import datetime |
256 | - |
257 | import webob.exc |
258 | |
259 | from nova import compute |
260 | @@ -22,7 +20,6 @@ |
261 | from nova import flags |
262 | from nova import log |
263 | from nova import utils |
264 | -from nova import wsgi |
265 | from nova.api.openstack import common |
266 | from nova.api.openstack import faults |
267 | from nova.api.openstack.views import images as images_view |
268 | @@ -32,7 +29,7 @@ |
269 | FLAGS = flags.FLAGS |
270 | |
271 | |
272 | -class Controller(wsgi.Controller): |
273 | +class Controller(common.OpenstackController): |
274 | """Base `wsgi.Controller` for retrieving/displaying images.""" |
275 | |
276 | _serialization_metadata = { |
277 | @@ -153,3 +150,6 @@ |
278 | """Property to get the ViewBuilder class we need to use.""" |
279 | base_url = request.application_url |
280 | return images_view.ViewBuilderV11(base_url) |
281 | + |
282 | + def get_default_xmlns(self, req): |
283 | + return common.XML_NS_V11 |
284 | |
285 | === modified file 'nova/api/openstack/limits.py' |
286 | --- nova/api/openstack/limits.py 2011-03-17 20:26:52 +0000 |
287 | +++ nova/api/openstack/limits.py 2011-04-06 20:56:32 +0000 |
288 | @@ -31,8 +31,8 @@ |
289 | from webob.dec import wsgify |
290 | |
291 | from nova import wsgi |
292 | +from nova.api.openstack import common |
293 | from nova.api.openstack import faults |
294 | -from nova.wsgi import Controller |
295 | from nova.wsgi import Middleware |
296 | |
297 | |
298 | @@ -43,7 +43,7 @@ |
299 | PER_DAY = 60 * 60 * 24 |
300 | |
301 | |
302 | -class LimitsController(Controller): |
303 | +class LimitsController(common.OpenstackController): |
304 | """ |
305 | Controller for accessing limits in the OpenStack API. |
306 | """ |
307 | |
308 | === modified file 'nova/api/openstack/server_metadata.py' |
309 | --- nova/api/openstack/server_metadata.py 2011-03-22 14:01:18 +0000 |
310 | +++ nova/api/openstack/server_metadata.py 2011-04-06 20:56:32 +0000 |
311 | @@ -19,10 +19,11 @@ |
312 | |
313 | from nova import compute |
314 | from nova import wsgi |
315 | +from nova.api.openstack import common |
316 | from nova.api.openstack import faults |
317 | |
318 | |
319 | -class Controller(wsgi.Controller): |
320 | +class Controller(common.OpenstackController): |
321 | """ The server metadata API controller for the Openstack API """ |
322 | |
323 | def __init__(self): |
324 | |
325 | === modified file 'nova/api/openstack/servers.py' |
326 | --- nova/api/openstack/servers.py 2011-04-04 17:56:19 +0000 |
327 | +++ nova/api/openstack/servers.py 2011-04-06 20:56:32 +0000 |
328 | @@ -44,7 +44,7 @@ |
329 | FLAGS = flags.FLAGS |
330 | |
331 | |
332 | -class Controller(wsgi.Controller): |
333 | +class Controller(common.OpenstackController): |
334 | """ The Server API controller for the OpenStack API """ |
335 | |
336 | _serialization_metadata = { |
337 | @@ -648,6 +648,9 @@ |
338 | def _limit_items(self, items, req): |
339 | return common.limited_by_marker(items, req) |
340 | |
341 | + def get_default_xmlns(self, req): |
342 | + return common.XML_NS_V11 |
343 | + |
344 | |
345 | class ServerCreateRequestXMLDeserializer(object): |
346 | """ |
347 | |
348 | === modified file 'nova/api/openstack/shared_ip_groups.py' |
349 | --- nova/api/openstack/shared_ip_groups.py 2011-03-28 18:02:53 +0000 |
350 | +++ nova/api/openstack/shared_ip_groups.py 2011-04-06 20:56:32 +0000 |
351 | @@ -17,7 +17,7 @@ |
352 | |
353 | from webob import exc |
354 | |
355 | -from nova import wsgi |
356 | +from nova.api.openstack import common |
357 | from nova.api.openstack import faults |
358 | |
359 | |
360 | @@ -32,7 +32,7 @@ |
361 | return dict(sharedIpGroups=inst) |
362 | |
363 | |
364 | -class Controller(wsgi.Controller): |
365 | +class Controller(common.OpenstackController): |
366 | """ The Shared IP Groups Controller for the Openstack API """ |
367 | |
368 | _serialization_metadata = { |
369 | |
370 | === modified file 'nova/api/openstack/users.py' |
371 | --- nova/api/openstack/users.py 2011-03-16 19:15:57 +0000 |
372 | +++ nova/api/openstack/users.py 2011-04-06 20:56:32 +0000 |
373 | @@ -18,7 +18,6 @@ |
374 | from nova import exception |
375 | from nova import flags |
376 | from nova import log as logging |
377 | -from nova import wsgi |
378 | from nova.api.openstack import common |
379 | from nova.api.openstack import faults |
380 | from nova.auth import manager |
381 | @@ -35,7 +34,7 @@ |
382 | admin=user.admin) |
383 | |
384 | |
385 | -class Controller(wsgi.Controller): |
386 | +class Controller(common.OpenstackController): |
387 | |
388 | _serialization_metadata = { |
389 | 'application/xml': { |
390 | |
391 | === modified file 'nova/api/openstack/zones.py' |
392 | --- nova/api/openstack/zones.py 2011-03-24 19:04:24 +0000 |
393 | +++ nova/api/openstack/zones.py 2011-04-06 20:56:32 +0000 |
394 | @@ -13,12 +13,10 @@ |
395 | # License for the specific language governing permissions and limitations |
396 | # under the License. |
397 | |
398 | -import common |
399 | - |
400 | from nova import db |
401 | from nova import flags |
402 | from nova import log as logging |
403 | -from nova import wsgi |
404 | +from nova.api.openstack import common |
405 | from nova.scheduler import api |
406 | |
407 | |
408 | @@ -43,7 +41,7 @@ |
409 | 'deleted', 'deleted_at', 'updated_at')) |
410 | |
411 | |
412 | -class Controller(wsgi.Controller): |
413 | +class Controller(common.OpenstackController): |
414 | |
415 | _serialization_metadata = { |
416 | 'application/xml': { |
417 | |
418 | === modified file 'nova/tests/api/openstack/test_faults.py' |
419 | --- nova/tests/api/openstack/test_faults.py 2011-03-30 13:39:35 +0000 |
420 | +++ nova/tests/api/openstack/test_faults.py 2011-04-06 20:56:32 +0000 |
421 | @@ -22,6 +22,7 @@ |
422 | import webob.exc |
423 | |
424 | from nova import test |
425 | +from nova.api.openstack import common |
426 | from nova.api.openstack import faults |
427 | |
428 | |
429 | @@ -47,10 +48,10 @@ |
430 | response = request.get_response(fault) |
431 | |
432 | expected = self._prepare_xml(""" |
433 | - <badRequest code="400"> |
434 | + <badRequest code="400" xmlns="%s"> |
435 | <message>scram</message> |
436 | </badRequest> |
437 | - """) |
438 | + """ % common.XML_NS_V10) |
439 | actual = self._prepare_xml(response.body) |
440 | |
441 | self.assertEqual(response.content_type, "application/xml") |
442 | @@ -91,11 +92,11 @@ |
443 | response = request.get_response(fault) |
444 | |
445 | expected = self._prepare_xml(""" |
446 | - <overLimit code="413"> |
447 | + <overLimit code="413" xmlns="%s"> |
448 | <message>sorry</message> |
449 | <retryAfter>4</retryAfter> |
450 | </overLimit> |
451 | - """) |
452 | + """ % common.XML_NS_V10) |
453 | actual = self._prepare_xml(response.body) |
454 | |
455 | self.assertEqual(expected, actual) |
456 | |
457 | === modified file 'nova/tests/api/openstack/test_images.py' |
458 | --- nova/tests/api/openstack/test_images.py 2011-04-04 14:59:44 +0000 |
459 | +++ nova/tests/api/openstack/test_images.py 2011-04-06 20:56:32 +0000 |
460 | @@ -146,7 +146,7 @@ |
461 | for x in [1, 2, 3]: |
462 | tempfile.mkstemp(prefix='ami-', dir=self.tempdir) |
463 | # create some valid image directories names |
464 | - for x in ["1485baed", "1a60f0ee", "3123a73d"]: |
465 | + for x in ["1485baed", "1a60f0ee", "3123a73d"]: |
466 | os.makedirs(os.path.join(self.tempdir, x)) |
467 | found_image_ids = self.service._ids() |
468 | self.assertEqual(True, isinstance(found_image_ids, list)) |
469 | @@ -335,7 +335,8 @@ |
470 | name="public image" |
471 | updated="%(expected_now)s" |
472 | created="%(expected_now)s" |
473 | - status="ACTIVE" /> |
474 | + status="ACTIVE" |
475 | + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> |
476 | """ % (locals())) |
477 | |
478 | self.assertEqual(expected_image.toxml(), actual_image.toxml()) |
479 | @@ -353,7 +354,8 @@ |
480 | name="None" |
481 | updated="%(expected_now)s" |
482 | created="%(expected_now)s" |
483 | - status="ACTIVE" /> |
484 | + status="ACTIVE" |
485 | + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0" /> |
486 | """ % (locals())) |
487 | |
488 | self.assertEqual(expected_image.toxml(), actual_image.toxml()) |
489 | @@ -372,7 +374,8 @@ |
490 | name="public image" |
491 | updated="%(expected_now)s" |
492 | created="%(expected_now)s" |
493 | - status="ACTIVE"> |
494 | + status="ACTIVE" |
495 | + xmlns="http://docs.openstack.org/compute/api/v1.1"> |
496 | <links> |
497 | <link href="%(expected_href)s" rel="self"/> |
498 | <link href="%(expected_href)s" rel="bookmark" |
499 | @@ -408,7 +411,8 @@ |
500 | self.assertEqual(404, response.status_int) |
501 | |
502 | expected = minidom.parseString(""" |
503 | - <itemNotFound code="404"> |
504 | + <itemNotFound code="404" |
505 | + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> |
506 | <message> |
507 | Image not found. |
508 | </message> |
509 | @@ -441,8 +445,11 @@ |
510 | response = request.get_response(fakes.wsgi_app()) |
511 | self.assertEqual(404, response.status_int) |
512 | |
513 | + # NOTE(justinsb): I believe this should still use the v1.0 XSD, |
514 | + # because the element hasn't changed definition |
515 | expected = minidom.parseString(""" |
516 | - <itemNotFound code="404"> |
517 | + <itemNotFound code="404" |
518 | + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> |
519 | <message> |
520 | Image not found. |
521 | </message> |
522 | |
523 | === modified file 'nova/tests/api/openstack/test_limits.py' |
524 | --- nova/tests/api/openstack/test_limits.py 2011-03-17 20:26:52 +0000 |
525 | +++ nova/tests/api/openstack/test_limits.py 2011-04-06 20:56:32 +0000 |
526 | @@ -136,10 +136,17 @@ |
527 | request = self._get_index_request("application/xml") |
528 | response = request.get_response(self.controller) |
529 | |
530 | - expected = "<limits><rate/><absolute/></limits>" |
531 | - body = response.body.replace("\n", "").replace(" ", "") |
532 | - |
533 | - self.assertEqual(expected, body) |
534 | + expected = parseString(""" |
535 | + <limits |
536 | + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> |
537 | + <rate/> |
538 | + <absolute/> |
539 | + </limits> |
540 | + """.replace(" ", "")) |
541 | + |
542 | + body = parseString(response.body.replace(" ", "")) |
543 | + |
544 | + self.assertEqual(expected.toxml(), body.toxml()) |
545 | |
546 | def test_index_xml(self): |
547 | """Test getting limit details in XML.""" |
548 | @@ -148,7 +155,8 @@ |
549 | response = request.get_response(self.controller) |
550 | |
551 | expected = parseString(""" |
552 | - <limits> |
553 | + <limits |
554 | + xmlns="http://docs.rackspacecloud.com/servers/api/v1.0"> |
555 | <rate> |
556 | <limit URI="*" regex=".*" remaining="10" resetTime="0" |
557 | unit="MINUTE" value="10" verb="GET"/> |
558 | |
559 | === added file 'nova/tests/integrated/test_xml.py' |
560 | --- nova/tests/integrated/test_xml.py 1970-01-01 00:00:00 +0000 |
561 | +++ nova/tests/integrated/test_xml.py 2011-04-06 20:56:32 +0000 |
562 | @@ -0,0 +1,56 @@ |
563 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
564 | + |
565 | +# Copyright 2011 Justin Santa Barbara |
566 | +# All Rights Reserved. |
567 | +# |
568 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
569 | +# not use this file except in compliance with the License. You may obtain |
570 | +# a copy of the License at |
571 | +# |
572 | +# http://www.apache.org/licenses/LICENSE-2.0 |
573 | +# |
574 | +# Unless required by applicable law or agreed to in writing, software |
575 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
576 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
577 | +# License for the specific language governing permissions and limitations |
578 | +# under the License. |
579 | + |
580 | +from nova import flags |
581 | +from nova.log import logging |
582 | +from nova.tests.integrated import integrated_helpers |
583 | +from nova.api.openstack import common |
584 | + |
585 | + |
586 | +LOG = logging.getLogger('nova.tests.integrated') |
587 | + |
588 | + |
589 | +FLAGS = flags.FLAGS |
590 | +FLAGS.verbose = True |
591 | + |
592 | + |
593 | +class XmlTests(integrated_helpers._IntegratedTestBase): |
594 | + """"Some basic XML sanity checks.""" |
595 | + |
596 | + def test_namespace_limits(self): |
597 | + """/limits should have v1.0 namespace (hasn't changed in 1.1).""" |
598 | + headers = {} |
599 | + headers['Accept'] = 'application/xml' |
600 | + |
601 | + response = self.api.api_request('/limits', headers=headers) |
602 | + data = response.read() |
603 | + LOG.debug("data: %s" % data) |
604 | + |
605 | + prefix = '<limits xmlns="%s"' % common.XML_NS_V10 |
606 | + self.assertTrue(data.startswith(prefix)) |
607 | + |
608 | + def test_namespace_servers(self): |
609 | + """/servers should have v1.1 namespace (has changed in 1.1).""" |
610 | + headers = {} |
611 | + headers['Accept'] = 'application/xml' |
612 | + |
613 | + response = self.api.api_request('/servers', headers=headers) |
614 | + data = response.read() |
615 | + LOG.debug("data: %s" % data) |
616 | + |
617 | + prefix = '<servers xmlns="%s"' % common.XML_NS_V11 |
618 | + self.assertTrue(data.startswith(prefix)) |
619 | |
620 | === modified file 'nova/wsgi.py' |
621 | --- nova/wsgi.py 2011-04-05 12:55:19 +0000 |
622 | +++ nova/wsgi.py 2011-04-06 20:56:32 +0000 |
623 | @@ -355,24 +355,25 @@ |
624 | |
625 | if type(result) is dict: |
626 | content_type = req.best_match_content_type() |
627 | - body = self._serialize(result, content_type) |
628 | + default_xmlns = self.get_default_xmlns(req) |
629 | + body = self._serialize(result, content_type, default_xmlns) |
630 | |
631 | response = webob.Response() |
632 | response.headers["Content-Type"] = content_type |
633 | response.body = body |
634 | return response |
635 | - |
636 | else: |
637 | return result |
638 | |
639 | - def _serialize(self, data, content_type): |
640 | + def _serialize(self, data, content_type, default_xmlns): |
641 | """ |
642 | Serialize the given dict to the provided content_type. |
643 | Uses self._serialization_metadata if it exists, which is a dict mapping |
644 | MIME types to information needed to serialize to that type. |
645 | """ |
646 | _metadata = getattr(type(self), "_serialization_metadata", {}) |
647 | - serializer = Serializer(_metadata) |
648 | + |
649 | + serializer = Serializer(_metadata, default_xmlns) |
650 | try: |
651 | return serializer.serialize(data, content_type) |
652 | except exception.InvalidContentType: |
653 | @@ -388,19 +389,24 @@ |
654 | serializer = Serializer(_metadata) |
655 | return serializer.deserialize(data, content_type) |
656 | |
657 | + def get_default_xmlns(self, req): |
658 | + """Provide the XML namespace to use if none is otherwise specified.""" |
659 | + return None |
660 | + |
661 | |
662 | class Serializer(object): |
663 | """ |
664 | Serializes and deserializes dictionaries to certain MIME types. |
665 | """ |
666 | |
667 | - def __init__(self, metadata=None): |
668 | + def __init__(self, metadata=None, default_xmlns=None): |
669 | """ |
670 | Create a serializer based on the given WSGI environment. |
671 | 'metadata' is an optional dict mapping MIME types to information |
672 | needed to serialize a dictionary to that type. |
673 | """ |
674 | self.metadata = metadata or {} |
675 | + self.default_xmlns = default_xmlns |
676 | |
677 | def _get_serialize_handler(self, content_type): |
678 | handlers = { |
679 | @@ -478,11 +484,23 @@ |
680 | root_key = data.keys()[0] |
681 | doc = minidom.Document() |
682 | node = self._to_xml_node(doc, metadata, root_key, data[root_key]) |
683 | + |
684 | + xmlns = node.getAttribute('xmlns') |
685 | + if not xmlns and self.default_xmlns: |
686 | + node.setAttribute('xmlns', self.default_xmlns) |
687 | + |
688 | return node.toprettyxml(indent=' ') |
689 | |
690 | def _to_xml_node(self, doc, metadata, nodename, data): |
691 | """Recursive method to convert data members to XML nodes.""" |
692 | result = doc.createElement(nodename) |
693 | + |
694 | + # Set the xml namespace if one is specified |
695 | + # TODO(justinsb): We could also use prefixes on the keys |
696 | + xmlns = metadata.get('xmlns', None) |
697 | + if xmlns: |
698 | + result.setAttribute('xmlns', xmlns) |
699 | + |
700 | if type(data) is list: |
701 | singular = metadata.get('plurals', {}).get(nodename, None) |
702 | if singular is None: |
How do you plan to handle versioning? Right now if I do a request on /v1.1/servers/ 1.xml, I get the v1.0 url.
I think it would be much more elegant to solve this problem once we move away from centralized serialization and delegate that responsibility to some sort of resource- or controller-specific response object.
If you want to stick with this approach, I guess you could have get_default_xmlns inspect the request object and build the url.