Merge lp:~justin-fathomdb/nova/bug740576 into lp:~hudson-openstack/nova/trunk

Proposed by justinsb
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
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

Description of the change

Support providing an XML namespace on the XML output from the OpenStack API

To post a comment you must log in.
Revision history for this message
Brian Waldon (bcwaldon) wrote :

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.

review: Needs Information
Revision history for this message
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_metadata, where you can also put an xmlns to override any
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.

Revision history for this message
Brian Lamar (blamar) wrote :

Should we put OpenStack API specific information in nova/wsgi.py? I think that should go in the nova/api/openstack/* namespace.

Unit tests for verification?

review: Needs Fixing
Revision history for this message
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.

Revision history for this message
Brian Lamar (blamar) wrote :

Hey Justin, I really don't think a note should be the answer. Having implementation-specific information in library level shared code shouldn't happen and breaking out an OpenStack API specific Controller or Serializer is the way to go IMO.

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.

Revision history for this message
justinsb (justin-fathomdb) wrote :

Brian: Which merge proposal are you talking about here? Is it going to make the cutoff?

Revision history for this message
Brian Lamar (blamar) wrote :

https://code.launchpad.net/~rackspace-titan/nova/openstack-api-versioned-controllers

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.

Revision history for this message
justinsb (justin-fathomdb) wrote :

No worries. This is a bugfix, so isn't subject to FF, so doesn't need to
get attention today.

Revision history for this message
termie (termie) wrote :

I think I agree with Brian Lamar as to where this code should live, a subclass of our controller/serializer that the nova.api.openstack code uses

review: Needs Fixing
Revision history for this message
justinsb (justin-fathomdb) wrote :

Refactored per suggestions; fixed up failing tests and added an explicit one.

Revision history for this message
Brian Lamar (blamar) wrote :

Justin, quick question: nova/tests/integrated/test_xml.py seems to make some assumptions which I don't understand. /limits should be available via /v1.0/limits and /v1.1/limits, and /servers should be available via /v1.0/servers and /v1.1/servers which return their corresponding XML namespace strings.

Are those integration tests testing v1.0 or v1.1?

Thanks!

review: Needs Information
Revision history for this message
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/api/integrated/api/client.py

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?

Revision history for this message
Brian Lamar (blamar) wrote :

v1.1 Limits: http://paste.openstack.org/show/1037/

v1.0 Limits: http://paste.openstack.org/show/1038/

I think the v1.1 limits XMLNS is a typo, but they are absolutely different.

Revision history for this message
Brian Lamar (blamar) wrote :
Revision history for this message
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.

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

Revision history for this message
Soren Hansen (soren) wrote :

lgtm

review: Approve
Revision history for this message
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/wsgi.py module?
2) Does the word "default" belong in "get_default_xmlns"? This isn't really a default, it is explicitly set.

review: Needs Information
Revision history for this message
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.

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

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

Revision history for this message
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.openstack) and access it like so: nova.api.openstack.OpenstackController

Revision history for this message
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.openstack) and access it like so: nova.api.openstack.OpenstackController
> --
> https://code.launchpad.net/~justin-fathomdb/nova/bug740576/+merge/54447
> You are subscribed to branch lp:nova.

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

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

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

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

Revision history for this message
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/wsgi.py), 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.

review: Approve
Revision history for this message
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/wsgi.py),
> 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.

review: Approve
Revision history for this message
Brian Lamar (blamar) :
review: Approve
Revision history for this message
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.

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

The attempt to merge lp:~justin-fathomdb/nova/bug740576 into lp:nova failed. Below is the output from the failed tests.

AccountsTest
    test_account_create OK
    test_account_delete OK
    test_account_update OK
    test_get_account OK
AdminAPITest
    test_admin_disabled OK
    test_admin_enabled OK
APITest
    test_exceptions_are_converted_to_faults OK
Test
    test_authorize_token OK
    test_authorize_user OK
    test_bad_token OK
    test_bad_user_bad_key OK
    test_bad_user_good_key OK
    test_no_user OK
    test_token_expiry OK
TestFunctional
    test_token_doesnotexist OK
    test_token_expiry OK
TestLimiter
    test_authorize_token OK
LimiterTest
    test_limiter_custom_max_limit OK
    test_limiter_limit_and_offset OK
    test_limiter_limit_medium OK
    test_limiter_limit_over_max OK
    test_limiter_limit_zero OK
    test_limiter_negative_limit OK
    test_limiter_negative_offset OK
    test_limiter_nothing OK
    test_limiter_offset_bad OK
    test_limiter_offset_blank OK
    test_limiter_offset_medium OK
    test_limiter_offset_over_max OK
    test_limiter_offset_zero OK
ActionExtensionTest
    test_extended_action OK
    test_invalid_action OK
    test_invalid_action_body OK
ExtensionControllerTest
    test_get_by_alias OK
    test_index OK
ExtensionManagerTest
    test_get_resources OK
ResourceExtensionTest
    test_get_resources OK
    test_get_resources_with_controller OK
    test_no_extension_present OK
ResponseExtensionTest
    test_get_resources_with_mgr OK
    test_get_resources_with_stub_mgr OK
TestFaults
    test_400_fault_json OK
    test_400_fault_xml OK
...

Revision history for this message
justinsb (justin-fathomdb) wrote :

Another test was added that didn't check the namespace during the approval process. Remerged with trunk & fixed

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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: