Merge lp:~rackspace-titan/nova/openstack-api-version-split into lp:~hudson-openstack/nova/trunk

Proposed by Brian Waldon
Status: Merged
Approved by: Paul Voccio
Approved revision: 805
Merged at revision: 821
Proposed branch: lp:~rackspace-titan/nova/openstack-api-version-split
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 632 lines (+371/-77)
12 files modified
etc/api-paste.ini (+1/-0)
nova/api/openstack/__init__.py (+5/-2)
nova/api/openstack/auth.py (+2/-0)
nova/api/openstack/common.py (+4/-0)
nova/api/openstack/servers.py (+25/-63)
nova/api/openstack/views/addresses.py (+54/-0)
nova/api/openstack/views/flavors.py (+51/-0)
nova/api/openstack/views/images.py (+51/-0)
nova/api/openstack/views/servers.py (+132/-0)
nova/tests/api/openstack/fakes.py (+1/-0)
nova/tests/api/openstack/test_servers.py (+44/-11)
plugins/xenserver/xenapi/etc/xapi.d/plugins/glance (+1/-1)
To merge this branch: bzr merge lp:~rackspace-titan/nova/openstack-api-version-split
Reviewer Review Type Date Requested Status
Paul Voccio (community) Approve
Brian Lamar (community) Approve
Eric Day (community) Approve
Review via email: mp+53510@code.launchpad.net

Commit message

- general approach for openstack api versioning
- openstack api version now preserved in request context
- added view builder classes to handle os api responses
- added imageRef and flavorRef to os api v1.1 servers
- modified addresses container structure in os api v1.1 servers

Description of the change

- general approach for openstack api versioning
- openstack api version now preserved in request context
- added view builder classes to handle os api responses
- added imageRef and flavorRef to os api v1.1 servers
- modified addresses container structure in os api v1.1 servers

To post a comment you must log in.
Revision history for this message
Naveed Massjouni (ironcamel) wrote :

I really like the view builders concept. Good job!

Revision history for this message
Eric Day (eday) wrote :

40: nova.context should not hold anything openstack API specific. You probably want to store version in the req.environ dict if you need it somewhere.

592: how did that get in there? :)

Overall, I'm not sure I agree with the approach. It versions the serialization, but not the controllers. I'm wondering if we should instead use the old serialization conventions and just make versioned controllers that would call the correct one. You would then specify versioned controllers in the paste config which seems a bit more straightforward.

review: Needs Fixing
798. By Naveed Massjouni

Per Eric Day's suggest, the verson is not store in the request environ instead
of the nova.context.

799. By Naveed Massjouni

Updating paste config.

Revision history for this message
Naveed Massjouni (ironcamel) wrote :

> 40: nova.context should not hold anything openstack API specific. You probably
> want to store version in the req.environ dict if you need it somewhere.

Done. Thanks.

>
> 592: how did that get in there? :)

Modified that line to make pep8 happy, after merge from trunk.

>
> Overall, I'm not sure I agree with the approach. It versions the
> serialization, but not the controllers. I'm wondering if we should instead use
> the old serialization conventions and just make versioned controllers that
> would call the correct one. You would then specify versioned controllers in
> the paste config which seems a bit more straightforward.

I agree that controllers should also be versioned. The good thing about this merge prop as it stands is that it separates out the view logic from the controller logic. We considered versioning controllers as well. We decided to hold off on that until it became necessary for a feature we were working on. We will be working in that direction, as we are implementing rest of 1.1 features for the openstack api in the next couple of days. We wanted to make our approach public as soon as possible to get feedback.

Revision history for this message
Eric Day (eday) wrote :

39: might want something more specific than 'version' for the var, as I could see this conflicting with other things. Something like nova.api.openstack.version would have less chance of conflicting.

256,296: Shouldn't use python3 formatting, we're still supporting at least 2.5 afaik and this isn't consistent with the rest of the code base

Need license headers on all the new files.

As I mentioned on IRC, I think the get_view_builder functions should go away once the controllers are versioned since you can call the version directly. This won't block once these other issues are fixed though.

review: Needs Fixing
Revision history for this message
Naveed Massjouni (ironcamel) wrote :

Thanks Eric. I just incorporated your suggestions. I changed 'version' key of the request environ to the more descriptive 'api.version', as we discussed on irc.

800. By Naveed Massjouni

As suggested by Eric Day:
 * changed request.environ version key to more descriptive 'api.version'
 * removed python3 string formatting
 * added licenses to headers on new files

801. By Brian Waldon

adding imageRef and flavorRef attributes to servers serialization metadata

802. By Naveed Massjouni

Adding newlines for pep8.

803. By Naveed Massjouni

req envirom param 'nova.api.openstack.version' should be 'api.version'

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

Line 150: except exception.NotFound:

Potentially nitpicky, which one of the three statements in the try/except can raise the NotFound exception? Can we just put the one line which can raise the exception in there?

Line 518: req.environ['api.version'] = '1.0'

Should this be 'openstack.api.version' or something more specific to OpenStack?

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

> Line 150: except exception.NotFound:
>
> Potentially nitpicky, which one of the three statements in the try/except can
> raise the NotFound exception? Can we just put the one line which can raise the
> exception in there?
>

Fixed.

>
> Line 518: req.environ['api.version'] = '1.0'
>
> Should this be 'openstack.api.version' or something more specific to
> OpenStack?

In our chat, we didn't have a good enough reason to make these versions api-specific. Leaving it alone for now.

Revision history for this message
Eric Day (eday) wrote :

Looks good! I'll be looking forward to see how view classes change with versioned controllers. :)

review: Approve
Revision history for this message
Naveed Massjouni (ironcamel) wrote :

Thanks Eric! And as you requested, here is the merge prop for versioned controllers :)
https://code.launchpad.net/~rackspace-titan/nova/openstack-api-versioned-controllers/+merge/53748

I branched that off of this branch, so this branch needs to go in first.

Revision history for this message
Brian Lamar (blamar) :
review: Approve
804. By Naveed Massjouni

Merge from trunk.

805. By Naveed Massjouni

moving code out of try/except that would never trigger NotFound

Revision history for this message
Paul Voccio (pvo) wrote :

lgtm

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'etc/api-paste.ini'
2--- etc/api-paste.ini 2011-03-07 19:33:24 +0000
3+++ etc/api-paste.ini 2011-03-17 17:53:41 +0000
4@@ -68,6 +68,7 @@
5 use = egg:Paste#urlmap
6 /: osversions
7 /v1.0: openstackapi
8+/v1.1: openstackapi
9
10 [pipeline:openstackapi]
11 pipeline = faultwrap auth ratelimit osapiapp
12
13=== modified file 'nova/api/openstack/__init__.py'
14--- nova/api/openstack/__init__.py 2011-03-11 19:49:32 +0000
15+++ nova/api/openstack/__init__.py 2011-03-17 17:53:41 +0000
16@@ -128,8 +128,11 @@
17 def __call__(self, req):
18 """Respond to a request for all OpenStack API versions."""
19 response = {
20- "versions": [
21- dict(status="CURRENT", id="v1.0")]}
22+ "versions": [
23+ dict(status="DEPRECATED", id="v1.0"),
24+ dict(status="CURRENT", id="v1.1"),
25+ ],
26+ }
27 metadata = {
28 "application/xml": {
29 "attributes": dict(version=["status", "id"])}}
30
31=== modified file 'nova/api/openstack/auth.py'
32--- nova/api/openstack/auth.py 2011-03-11 22:19:14 +0000
33+++ nova/api/openstack/auth.py 2011-03-17 17:53:41 +0000
34@@ -69,6 +69,8 @@
35 return faults.Fault(webob.exc.HTTPUnauthorized())
36
37 req.environ['nova.context'] = context.RequestContext(user, account)
38+ version = req.path.split('/')[1].replace('v', '')
39+ req.environ['api.version'] = version
40 return self.application
41
42 def has_authentication(self, req):
43
44=== modified file 'nova/api/openstack/common.py'
45--- nova/api/openstack/common.py 2011-03-09 22:10:24 +0000
46+++ nova/api/openstack/common.py 2011-03-17 17:53:41 +0000
47@@ -74,3 +74,7 @@
48 if abs(hash(image_id)) == int(image_hash):
49 return image_id
50 raise exception.NotFound(image_hash)
51+
52+
53+def get_api_version(req):
54+ return req.environ.get('api.version')
55
56=== modified file 'nova/api/openstack/servers.py'
57--- nova/api/openstack/servers.py 2011-03-16 19:16:16 +0000
58+++ nova/api/openstack/servers.py 2011-03-17 17:53:41 +0000
59@@ -29,6 +29,8 @@
60 from nova import utils
61 from nova.api.openstack import common
62 from nova.api.openstack import faults
63+from nova.api.openstack.views import servers as servers_views
64+from nova.api.openstack.views import addresses as addresses_views
65 from nova.auth import manager as auth_manager
66 from nova.compute import instance_types
67 from nova.compute import power_state
68@@ -36,63 +38,9 @@
69
70
71 LOG = logging.getLogger('server')
72-
73-
74 FLAGS = flags.FLAGS
75
76
77-def _translate_detail_keys(inst):
78- """ Coerces into dictionary format, mapping everything to Rackspace-like
79- attributes for return"""
80- power_mapping = {
81- None: 'build',
82- power_state.NOSTATE: 'build',
83- power_state.RUNNING: 'active',
84- power_state.BLOCKED: 'active',
85- power_state.SUSPENDED: 'suspended',
86- power_state.PAUSED: 'paused',
87- power_state.SHUTDOWN: 'active',
88- power_state.SHUTOFF: 'active',
89- power_state.CRASHED: 'error',
90- power_state.FAILED: 'error'}
91- inst_dict = {}
92-
93- mapped_keys = dict(status='state', imageId='image_id',
94- flavorId='instance_type', name='display_name', id='id')
95-
96- for k, v in mapped_keys.iteritems():
97- inst_dict[k] = inst[v]
98-
99- inst_dict['status'] = power_mapping[inst_dict['status']]
100- inst_dict['addresses'] = dict(public=[], private=[])
101-
102- # grab single private fixed ip
103- private_ips = utils.get_from_path(inst, 'fixed_ip/address')
104- inst_dict['addresses']['private'] = private_ips
105-
106- # grab all public floating ips
107- public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
108- inst_dict['addresses']['public'] = public_ips
109-
110- # Return the metadata as a dictionary
111- metadata = {}
112- for item in inst['metadata']:
113- metadata[item['key']] = item['value']
114- inst_dict['metadata'] = metadata
115-
116- inst_dict['hostId'] = ''
117- if inst['host']:
118- inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest()
119-
120- return dict(server=inst_dict)
121-
122-
123-def _translate_keys(inst):
124- """ Coerces into dictionary format, excluding all model attributes
125- save for id and name """
126- return dict(server=dict(id=inst['id'], name=inst['display_name']))
127-
128-
129 class Controller(wsgi.Controller):
130 """ The Server API controller for the OpenStack API """
131
132@@ -100,36 +48,49 @@
133 'application/xml': {
134 "attributes": {
135 "server": ["id", "imageId", "name", "flavorId", "hostId",
136- "status", "progress", "adminPass"]}}}
137+ "status", "progress", "adminPass", "flavorRef",
138+ "imageRef"]}}}
139
140 def __init__(self):
141 self.compute_api = compute.API()
142 self._image_service = utils.import_object(FLAGS.image_service)
143 super(Controller, self).__init__()
144
145+ def ips(self, req, id):
146+ try:
147+ instance = self.compute_api.get(req.environ['nova.context'], id)
148+ except exception.NotFound:
149+ return faults.Fault(exc.HTTPNotFound())
150+
151+ builder = addresses_views.get_view_builder(req)
152+ return builder.build(instance)
153+
154 def index(self, req):
155 """ Returns a list of server names and ids for a given user """
156- return self._items(req, entity_maker=_translate_keys)
157+ return self._items(req, is_detail=False)
158
159 def detail(self, req):
160 """ Returns a list of server details for a given user """
161- return self._items(req, entity_maker=_translate_detail_keys)
162+ return self._items(req, is_detail=True)
163
164- def _items(self, req, entity_maker):
165+ def _items(self, req, is_detail):
166 """Returns a list of servers for a given user.
167
168- entity_maker - either _translate_detail_keys or _translate_keys
169+ builder - the response model builder
170 """
171 instance_list = self.compute_api.get_all(req.environ['nova.context'])
172 limited_list = common.limited(instance_list, req)
173- res = [entity_maker(inst)['server'] for inst in limited_list]
174- return dict(servers=res)
175+ builder = servers_views.get_view_builder(req)
176+ servers = [builder.build(inst, is_detail)['server']
177+ for inst in limited_list]
178+ return dict(servers=servers)
179
180 def show(self, req, id):
181 """ Returns server details by server id """
182 try:
183 instance = self.compute_api.get(req.environ['nova.context'], id)
184- return _translate_detail_keys(instance)
185+ builder = servers_views.get_view_builder(req)
186+ return builder.build(instance, is_detail=True)
187 except exception.NotFound:
188 return faults.Fault(exc.HTTPNotFound())
189
190@@ -191,7 +152,8 @@
191 except QuotaError as error:
192 self._handle_quota_error(error)
193
194- server = _translate_keys(instances[0])
195+ builder = servers_views.get_view_builder(req)
196+ server = builder.build(instances[0], is_detail=False)
197 password = "%s%s" % (server['server']['name'][:4],
198 utils.generate_password(12))
199 server['server']['adminPass'] = password
200
201=== added directory 'nova/api/openstack/views'
202=== added file 'nova/api/openstack/views/__init__.py'
203=== added file 'nova/api/openstack/views/addresses.py'
204--- nova/api/openstack/views/addresses.py 1970-01-01 00:00:00 +0000
205+++ nova/api/openstack/views/addresses.py 2011-03-17 17:53:41 +0000
206@@ -0,0 +1,54 @@
207+# vim: tabstop=4 shiftwidth=4 softtabstop=4
208+
209+# Copyright 2010-2011 OpenStack LLC.
210+# All Rights Reserved.
211+#
212+# Licensed under the Apache License, Version 2.0 (the "License"); you may
213+# not use this file except in compliance with the License. You may obtain
214+# a copy of the License at
215+#
216+# http://www.apache.org/licenses/LICENSE-2.0
217+#
218+# Unless required by applicable law or agreed to in writing, software
219+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
220+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
221+# License for the specific language governing permissions and limitations
222+# under the License.
223+
224+from nova import utils
225+from nova.api.openstack import common
226+
227+
228+def get_view_builder(req):
229+ '''
230+ A factory method that returns the correct builder based on the version of
231+ the api requested.
232+ '''
233+ version = common.get_api_version(req)
234+ if version == '1.1':
235+ return ViewBuilder_1_1()
236+ else:
237+ return ViewBuilder_1_0()
238+
239+
240+class ViewBuilder(object):
241+ ''' Models a server addresses response as a python dictionary.'''
242+
243+ def build(self, inst):
244+ raise NotImplementedError()
245+
246+
247+class ViewBuilder_1_0(ViewBuilder):
248+ def build(self, inst):
249+ private_ips = utils.get_from_path(inst, 'fixed_ip/address')
250+ public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
251+ return dict(public=public_ips, private=private_ips)
252+
253+
254+class ViewBuilder_1_1(ViewBuilder):
255+ def build(self, inst):
256+ private_ips = utils.get_from_path(inst, 'fixed_ip/address')
257+ private_ips = [dict(version=4, addr=a) for a in private_ips]
258+ public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
259+ public_ips = [dict(version=4, addr=a) for a in public_ips]
260+ return dict(public=public_ips, private=private_ips)
261
262=== added file 'nova/api/openstack/views/flavors.py'
263--- nova/api/openstack/views/flavors.py 1970-01-01 00:00:00 +0000
264+++ nova/api/openstack/views/flavors.py 2011-03-17 17:53:41 +0000
265@@ -0,0 +1,51 @@
266+# vim: tabstop=4 shiftwidth=4 softtabstop=4
267+
268+# Copyright 2010-2011 OpenStack LLC.
269+# All Rights Reserved.
270+#
271+# Licensed under the Apache License, Version 2.0 (the "License"); you may
272+# not use this file except in compliance with the License. You may obtain
273+# a copy of the License at
274+#
275+# http://www.apache.org/licenses/LICENSE-2.0
276+#
277+# Unless required by applicable law or agreed to in writing, software
278+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
279+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
280+# License for the specific language governing permissions and limitations
281+# under the License.
282+
283+from nova.api.openstack import common
284+
285+
286+def get_view_builder(req):
287+ '''
288+ A factory method that returns the correct builder based on the version of
289+ the api requested.
290+ '''
291+ version = common.get_api_version(req)
292+ base_url = req.application_url
293+ if version == '1.1':
294+ return ViewBuilder_1_1(base_url)
295+ else:
296+ return ViewBuilder_1_0()
297+
298+
299+class ViewBuilder(object):
300+ def __init__(self):
301+ pass
302+
303+ def build(self, flavor_obj):
304+ raise NotImplementedError()
305+
306+
307+class ViewBuilder_1_1(ViewBuilder):
308+ def __init__(self, base_url):
309+ self.base_url = base_url
310+
311+ def generate_href(self, flavor_id):
312+ return "%s/flavors/%s" % (self.base_url, flavor_id)
313+
314+
315+class ViewBuilder_1_0(ViewBuilder):
316+ pass
317
318=== added file 'nova/api/openstack/views/images.py'
319--- nova/api/openstack/views/images.py 1970-01-01 00:00:00 +0000
320+++ nova/api/openstack/views/images.py 2011-03-17 17:53:41 +0000
321@@ -0,0 +1,51 @@
322+# vim: tabstop=4 shiftwidth=4 softtabstop=4
323+
324+# Copyright 2010-2011 OpenStack LLC.
325+# All Rights Reserved.
326+#
327+# Licensed under the Apache License, Version 2.0 (the "License"); you may
328+# not use this file except in compliance with the License. You may obtain
329+# a copy of the License at
330+#
331+# http://www.apache.org/licenses/LICENSE-2.0
332+#
333+# Unless required by applicable law or agreed to in writing, software
334+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
335+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
336+# License for the specific language governing permissions and limitations
337+# under the License.
338+
339+from nova.api.openstack import common
340+
341+
342+def get_view_builder(req):
343+ '''
344+ A factory method that returns the correct builder based on the version of
345+ the api requested.
346+ '''
347+ version = common.get_api_version(req)
348+ base_url = req.application_url
349+ if version == '1.1':
350+ return ViewBuilder_1_1(base_url)
351+ else:
352+ return ViewBuilder_1_0()
353+
354+
355+class ViewBuilder(object):
356+ def __init__(self):
357+ pass
358+
359+ def build(self, image_obj):
360+ raise NotImplementedError()
361+
362+
363+class ViewBuilder_1_1(ViewBuilder):
364+ def __init__(self, base_url):
365+ self.base_url = base_url
366+
367+ def generate_href(self, image_id):
368+ return "%s/images/%s" % (self.base_url, image_id)
369+
370+
371+class ViewBuilder_1_0(ViewBuilder):
372+ pass
373
374=== added file 'nova/api/openstack/views/servers.py'
375--- nova/api/openstack/views/servers.py 1970-01-01 00:00:00 +0000
376+++ nova/api/openstack/views/servers.py 2011-03-17 17:53:41 +0000
377@@ -0,0 +1,132 @@
378+# vim: tabstop=4 shiftwidth=4 softtabstop=4
379+
380+# Copyright 2010-2011 OpenStack LLC.
381+# All Rights Reserved.
382+#
383+# Licensed under the Apache License, Version 2.0 (the "License"); you may
384+# not use this file except in compliance with the License. You may obtain
385+# a copy of the License at
386+#
387+# http://www.apache.org/licenses/LICENSE-2.0
388+#
389+# Unless required by applicable law or agreed to in writing, software
390+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
391+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
392+# License for the specific language governing permissions and limitations
393+# under the License.
394+
395+import hashlib
396+from nova.compute import power_state
397+from nova.api.openstack import common
398+from nova.api.openstack.views import addresses as addresses_view
399+from nova.api.openstack.views import flavors as flavors_view
400+from nova.api.openstack.views import images as images_view
401+from nova import utils
402+
403+
404+def get_view_builder(req):
405+ '''
406+ A factory method that returns the correct builder based on the version of
407+ the api requested.
408+ '''
409+ version = common.get_api_version(req)
410+ addresses_builder = addresses_view.get_view_builder(req)
411+ if version == '1.1':
412+ flavor_builder = flavors_view.get_view_builder(req)
413+ image_builder = images_view.get_view_builder(req)
414+ return ViewBuilder_1_1(addresses_builder, flavor_builder,
415+ image_builder)
416+ else:
417+ return ViewBuilder_1_0(addresses_builder)
418+
419+
420+class ViewBuilder(object):
421+ '''
422+ Models a server response as a python dictionary.
423+ Abstract methods: _build_image, _build_flavor
424+ '''
425+
426+ def __init__(self, addresses_builder):
427+ self.addresses_builder = addresses_builder
428+
429+ def build(self, inst, is_detail):
430+ """
431+ Coerces into dictionary format, mapping everything to
432+ Rackspace-like attributes for return
433+ """
434+ if is_detail:
435+ return self._build_detail(inst)
436+ else:
437+ return self._build_simple(inst)
438+
439+ def _build_simple(self, inst):
440+ return dict(server=dict(id=inst['id'], name=inst['display_name']))
441+
442+ def _build_detail(self, inst):
443+ power_mapping = {
444+ None: 'build',
445+ power_state.NOSTATE: 'build',
446+ power_state.RUNNING: 'active',
447+ power_state.BLOCKED: 'active',
448+ power_state.SUSPENDED: 'suspended',
449+ power_state.PAUSED: 'paused',
450+ power_state.SHUTDOWN: 'active',
451+ power_state.SHUTOFF: 'active',
452+ power_state.CRASHED: 'error',
453+ power_state.FAILED: 'error'}
454+ inst_dict = {}
455+
456+ #mapped_keys = dict(status='state', imageId='image_id',
457+ # flavorId='instance_type', name='display_name', id='id')
458+
459+ mapped_keys = dict(status='state', name='display_name', id='id')
460+
461+ for k, v in mapped_keys.iteritems():
462+ inst_dict[k] = inst[v]
463+
464+ inst_dict['status'] = power_mapping[inst_dict['status']]
465+ inst_dict['addresses'] = self.addresses_builder.build(inst)
466+
467+ # Return the metadata as a dictionary
468+ metadata = {}
469+ for item in inst['metadata']:
470+ metadata[item['key']] = item['value']
471+ inst_dict['metadata'] = metadata
472+
473+ inst_dict['hostId'] = ''
474+ if inst['host']:
475+ inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest()
476+
477+ self._build_image(inst_dict, inst)
478+ self._build_flavor(inst_dict, inst)
479+
480+ return dict(server=inst_dict)
481+
482+ def _build_image(self, response, inst):
483+ raise NotImplementedError()
484+
485+ def _build_flavor(self, response, inst):
486+ raise NotImplementedError()
487+
488+
489+class ViewBuilder_1_0(ViewBuilder):
490+ def _build_image(self, response, inst):
491+ response["imageId"] = inst["image_id"]
492+
493+ def _build_flavor(self, response, inst):
494+ response["flavorId"] = inst["instance_type"]
495+
496+
497+class ViewBuilder_1_1(ViewBuilder):
498+ def __init__(self, addresses_builder, flavor_builder, image_builder):
499+ ViewBuilder.__init__(self, addresses_builder)
500+ self.flavor_builder = flavor_builder
501+ self.image_builder = image_builder
502+
503+ def _build_image(self, response, inst):
504+ image_id = inst["image_id"]
505+ response["imageRef"] = self.image_builder.generate_href(image_id)
506+
507+ def _build_flavor(self, response, inst):
508+ flavor_id = inst["instance_type"]
509+ response["flavorRef"] = self.flavor_builder.generate_href(flavor_id)
510
511=== modified file 'nova/tests/api/openstack/fakes.py'
512--- nova/tests/api/openstack/fakes.py 2011-03-16 19:16:16 +0000
513+++ nova/tests/api/openstack/fakes.py 2011-03-17 17:53:41 +0000
514@@ -79,6 +79,7 @@
515 api = openstack.FaultWrapper(auth.AuthMiddleware(
516 ratelimiting.RateLimitingMiddleware(inner_application)))
517 mapper['/v1.0'] = api
518+ mapper['/v1.1'] = api
519 mapper['/'] = openstack.FaultWrapper(openstack.Versions())
520 return mapper
521
522
523=== modified file 'nova/tests/api/openstack/test_servers.py'
524--- nova/tests/api/openstack/test_servers.py 2011-03-16 19:16:16 +0000
525+++ nova/tests/api/openstack/test_servers.py 2011-03-17 17:53:41 +0000
526@@ -24,6 +24,7 @@
527 import stubout
528 import webob
529
530+from nova import context
531 from nova import db
532 from nova import flags
533 from nova import test
534@@ -81,7 +82,7 @@
535 "admin_pass": "",
536 "user_id": user_id,
537 "project_id": "",
538- "image_id": 10,
539+ "image_id": "10",
540 "kernel_id": "",
541 "ramdisk_id": "",
542 "launch_index": 0,
543@@ -94,7 +95,7 @@
544 "local_gb": 0,
545 "hostname": "",
546 "host": None,
547- "instance_type": "",
548+ "instance_type": "1",
549 "user_data": "",
550 "reservation_id": "",
551 "mac_address": "",
552@@ -179,6 +180,25 @@
553 self.assertEqual(len(addresses["private"]), 1)
554 self.assertEqual(addresses["private"][0], private)
555
556+ def test_get_server_by_id_with_addresses_v1_1(self):
557+ private = "192.168.0.3"
558+ public = ["1.2.3.4"]
559+ new_return_server = return_server_with_addresses(private, public)
560+ self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
561+ req = webob.Request.blank('/v1.1/servers/1')
562+ req.environ['api.version'] = '1.1'
563+ res = req.get_response(fakes.wsgi_app())
564+ res_dict = json.loads(res.body)
565+ self.assertEqual(res_dict['server']['id'], '1')
566+ self.assertEqual(res_dict['server']['name'], 'server1')
567+ addresses = res_dict['server']['addresses']
568+ self.assertEqual(len(addresses["public"]), len(public))
569+ self.assertEqual(addresses["public"][0],
570+ {"version": 4, "addr": public[0]})
571+ self.assertEqual(len(addresses["private"]), 1)
572+ self.assertEqual(addresses["private"][0],
573+ {"version": 4, "addr": private})
574+
575 def test_get_server_list(self):
576 req = webob.Request.blank('/v1.0/servers')
577 res = req.get_response(fakes.wsgi_app())
578@@ -339,19 +359,32 @@
579 res = req.get_response(fakes.wsgi_app())
580 self.assertEqual(res.status, '404 Not Found')
581
582- def test_get_all_server_details(self):
583+ def test_get_all_server_details_v1_0(self):
584 req = webob.Request.blank('/v1.0/servers/detail')
585 res = req.get_response(fakes.wsgi_app())
586 res_dict = json.loads(res.body)
587
588- i = 0
589- for s in res_dict['servers']:
590- self.assertEqual(s['id'], i)
591- self.assertEqual(s['hostId'], '')
592- self.assertEqual(s['name'], 'server%d' % i)
593- self.assertEqual(s['imageId'], 10)
594- self.assertEqual(s['metadata']['seq'], i)
595- i += 1
596+ for i, s in enumerate(res_dict['servers']):
597+ self.assertEqual(s['id'], i)
598+ self.assertEqual(s['hostId'], '')
599+ self.assertEqual(s['name'], 'server%d' % i)
600+ self.assertEqual(s['imageId'], '10')
601+ self.assertEqual(s['flavorId'], '1')
602+ self.assertEqual(s['metadata']['seq'], i)
603+
604+ def test_get_all_server_details_v1_1(self):
605+ req = webob.Request.blank('/v1.1/servers/detail')
606+ req.environ['api.version'] = '1.1'
607+ res = req.get_response(fakes.wsgi_app())
608+ res_dict = json.loads(res.body)
609+
610+ for i, s in enumerate(res_dict['servers']):
611+ self.assertEqual(s['id'], i)
612+ self.assertEqual(s['hostId'], '')
613+ self.assertEqual(s['name'], 'server%d' % i)
614+ self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10')
615+ self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1')
616+ self.assertEqual(s['metadata']['seq'], i)
617
618 def test_get_all_server_details_with_host(self):
619 '''
620
621=== modified file 'plugins/xenserver/xenapi/etc/xapi.d/plugins/glance'
622--- plugins/xenserver/xenapi/etc/xapi.d/plugins/glance 2011-03-11 20:14:48 +0000
623+++ plugins/xenserver/xenapi/etc/xapi.d/plugins/glance 2011-03-17 17:53:41 +0000
624@@ -216,7 +216,7 @@
625 'x-image-meta-status': 'queued',
626 'x-image-meta-disk-format': 'vhd',
627 'x-image-meta-container-format': 'ovf',
628- 'x-image-meta-property-os-type': os_type
629+ 'x-image-meta-property-os-type': os_type,
630 }
631
632 for header, value in headers.iteritems():