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
=== modified file 'etc/api-paste.ini'
--- etc/api-paste.ini 2011-03-07 19:33:24 +0000
+++ etc/api-paste.ini 2011-03-17 17:53:41 +0000
@@ -68,6 +68,7 @@
68use = egg:Paste#urlmap68use = egg:Paste#urlmap
69/: osversions69/: osversions
70/v1.0: openstackapi70/v1.0: openstackapi
71/v1.1: openstackapi
7172
72[pipeline:openstackapi]73[pipeline:openstackapi]
73pipeline = faultwrap auth ratelimit osapiapp74pipeline = faultwrap auth ratelimit osapiapp
7475
=== modified file 'nova/api/openstack/__init__.py'
--- nova/api/openstack/__init__.py 2011-03-11 19:49:32 +0000
+++ nova/api/openstack/__init__.py 2011-03-17 17:53:41 +0000
@@ -128,8 +128,11 @@
128 def __call__(self, req):128 def __call__(self, req):
129 """Respond to a request for all OpenStack API versions."""129 """Respond to a request for all OpenStack API versions."""
130 response = {130 response = {
131 "versions": [131 "versions": [
132 dict(status="CURRENT", id="v1.0")]}132 dict(status="DEPRECATED", id="v1.0"),
133 dict(status="CURRENT", id="v1.1"),
134 ],
135 }
133 metadata = {136 metadata = {
134 "application/xml": {137 "application/xml": {
135 "attributes": dict(version=["status", "id"])}}138 "attributes": dict(version=["status", "id"])}}
136139
=== modified file 'nova/api/openstack/auth.py'
--- nova/api/openstack/auth.py 2011-03-11 22:19:14 +0000
+++ nova/api/openstack/auth.py 2011-03-17 17:53:41 +0000
@@ -69,6 +69,8 @@
69 return faults.Fault(webob.exc.HTTPUnauthorized())69 return faults.Fault(webob.exc.HTTPUnauthorized())
7070
71 req.environ['nova.context'] = context.RequestContext(user, account)71 req.environ['nova.context'] = context.RequestContext(user, account)
72 version = req.path.split('/')[1].replace('v', '')
73 req.environ['api.version'] = version
72 return self.application74 return self.application
7375
74 def has_authentication(self, req):76 def has_authentication(self, req):
7577
=== modified file 'nova/api/openstack/common.py'
--- nova/api/openstack/common.py 2011-03-09 22:10:24 +0000
+++ nova/api/openstack/common.py 2011-03-17 17:53:41 +0000
@@ -74,3 +74,7 @@
74 if abs(hash(image_id)) == int(image_hash):74 if abs(hash(image_id)) == int(image_hash):
75 return image_id75 return image_id
76 raise exception.NotFound(image_hash)76 raise exception.NotFound(image_hash)
77
78
79def get_api_version(req):
80 return req.environ.get('api.version')
7781
=== modified file 'nova/api/openstack/servers.py'
--- nova/api/openstack/servers.py 2011-03-16 19:16:16 +0000
+++ nova/api/openstack/servers.py 2011-03-17 17:53:41 +0000
@@ -29,6 +29,8 @@
29from nova import utils29from nova import utils
30from nova.api.openstack import common30from nova.api.openstack import common
31from nova.api.openstack import faults31from nova.api.openstack import faults
32from nova.api.openstack.views import servers as servers_views
33from nova.api.openstack.views import addresses as addresses_views
32from nova.auth import manager as auth_manager34from nova.auth import manager as auth_manager
33from nova.compute import instance_types35from nova.compute import instance_types
34from nova.compute import power_state36from nova.compute import power_state
@@ -36,63 +38,9 @@
3638
3739
38LOG = logging.getLogger('server')40LOG = logging.getLogger('server')
39
40
41FLAGS = flags.FLAGS41FLAGS = flags.FLAGS
4242
4343
44def _translate_detail_keys(inst):
45 """ Coerces into dictionary format, mapping everything to Rackspace-like
46 attributes for return"""
47 power_mapping = {
48 None: 'build',
49 power_state.NOSTATE: 'build',
50 power_state.RUNNING: 'active',
51 power_state.BLOCKED: 'active',
52 power_state.SUSPENDED: 'suspended',
53 power_state.PAUSED: 'paused',
54 power_state.SHUTDOWN: 'active',
55 power_state.SHUTOFF: 'active',
56 power_state.CRASHED: 'error',
57 power_state.FAILED: 'error'}
58 inst_dict = {}
59
60 mapped_keys = dict(status='state', imageId='image_id',
61 flavorId='instance_type', name='display_name', id='id')
62
63 for k, v in mapped_keys.iteritems():
64 inst_dict[k] = inst[v]
65
66 inst_dict['status'] = power_mapping[inst_dict['status']]
67 inst_dict['addresses'] = dict(public=[], private=[])
68
69 # grab single private fixed ip
70 private_ips = utils.get_from_path(inst, 'fixed_ip/address')
71 inst_dict['addresses']['private'] = private_ips
72
73 # grab all public floating ips
74 public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
75 inst_dict['addresses']['public'] = public_ips
76
77 # Return the metadata as a dictionary
78 metadata = {}
79 for item in inst['metadata']:
80 metadata[item['key']] = item['value']
81 inst_dict['metadata'] = metadata
82
83 inst_dict['hostId'] = ''
84 if inst['host']:
85 inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest()
86
87 return dict(server=inst_dict)
88
89
90def _translate_keys(inst):
91 """ Coerces into dictionary format, excluding all model attributes
92 save for id and name """
93 return dict(server=dict(id=inst['id'], name=inst['display_name']))
94
95
96class Controller(wsgi.Controller):44class Controller(wsgi.Controller):
97 """ The Server API controller for the OpenStack API """45 """ The Server API controller for the OpenStack API """
9846
@@ -100,36 +48,49 @@
100 'application/xml': {48 'application/xml': {
101 "attributes": {49 "attributes": {
102 "server": ["id", "imageId", "name", "flavorId", "hostId",50 "server": ["id", "imageId", "name", "flavorId", "hostId",
103 "status", "progress", "adminPass"]}}}51 "status", "progress", "adminPass", "flavorRef",
52 "imageRef"]}}}
10453
105 def __init__(self):54 def __init__(self):
106 self.compute_api = compute.API()55 self.compute_api = compute.API()
107 self._image_service = utils.import_object(FLAGS.image_service)56 self._image_service = utils.import_object(FLAGS.image_service)
108 super(Controller, self).__init__()57 super(Controller, self).__init__()
10958
59 def ips(self, req, id):
60 try:
61 instance = self.compute_api.get(req.environ['nova.context'], id)
62 except exception.NotFound:
63 return faults.Fault(exc.HTTPNotFound())
64
65 builder = addresses_views.get_view_builder(req)
66 return builder.build(instance)
67
110 def index(self, req):68 def index(self, req):
111 """ Returns a list of server names and ids for a given user """69 """ Returns a list of server names and ids for a given user """
112 return self._items(req, entity_maker=_translate_keys)70 return self._items(req, is_detail=False)
11371
114 def detail(self, req):72 def detail(self, req):
115 """ Returns a list of server details for a given user """73 """ Returns a list of server details for a given user """
116 return self._items(req, entity_maker=_translate_detail_keys)74 return self._items(req, is_detail=True)
11775
118 def _items(self, req, entity_maker):76 def _items(self, req, is_detail):
119 """Returns a list of servers for a given user.77 """Returns a list of servers for a given user.
12078
121 entity_maker - either _translate_detail_keys or _translate_keys79 builder - the response model builder
122 """80 """
123 instance_list = self.compute_api.get_all(req.environ['nova.context'])81 instance_list = self.compute_api.get_all(req.environ['nova.context'])
124 limited_list = common.limited(instance_list, req)82 limited_list = common.limited(instance_list, req)
125 res = [entity_maker(inst)['server'] for inst in limited_list]83 builder = servers_views.get_view_builder(req)
126 return dict(servers=res)84 servers = [builder.build(inst, is_detail)['server']
85 for inst in limited_list]
86 return dict(servers=servers)
12787
128 def show(self, req, id):88 def show(self, req, id):
129 """ Returns server details by server id """89 """ Returns server details by server id """
130 try:90 try:
131 instance = self.compute_api.get(req.environ['nova.context'], id)91 instance = self.compute_api.get(req.environ['nova.context'], id)
132 return _translate_detail_keys(instance)92 builder = servers_views.get_view_builder(req)
93 return builder.build(instance, is_detail=True)
133 except exception.NotFound:94 except exception.NotFound:
134 return faults.Fault(exc.HTTPNotFound())95 return faults.Fault(exc.HTTPNotFound())
13596
@@ -191,7 +152,8 @@
191 except QuotaError as error:152 except QuotaError as error:
192 self._handle_quota_error(error)153 self._handle_quota_error(error)
193154
194 server = _translate_keys(instances[0])155 builder = servers_views.get_view_builder(req)
156 server = builder.build(instances[0], is_detail=False)
195 password = "%s%s" % (server['server']['name'][:4],157 password = "%s%s" % (server['server']['name'][:4],
196 utils.generate_password(12))158 utils.generate_password(12))
197 server['server']['adminPass'] = password159 server['server']['adminPass'] = password
198160
=== added directory 'nova/api/openstack/views'
=== added file 'nova/api/openstack/views/__init__.py'
=== added file 'nova/api/openstack/views/addresses.py'
--- nova/api/openstack/views/addresses.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/views/addresses.py 2011-03-17 17:53:41 +0000
@@ -0,0 +1,54 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18from nova import utils
19from nova.api.openstack import common
20
21
22def get_view_builder(req):
23 '''
24 A factory method that returns the correct builder based on the version of
25 the api requested.
26 '''
27 version = common.get_api_version(req)
28 if version == '1.1':
29 return ViewBuilder_1_1()
30 else:
31 return ViewBuilder_1_0()
32
33
34class ViewBuilder(object):
35 ''' Models a server addresses response as a python dictionary.'''
36
37 def build(self, inst):
38 raise NotImplementedError()
39
40
41class ViewBuilder_1_0(ViewBuilder):
42 def build(self, inst):
43 private_ips = utils.get_from_path(inst, 'fixed_ip/address')
44 public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
45 return dict(public=public_ips, private=private_ips)
46
47
48class ViewBuilder_1_1(ViewBuilder):
49 def build(self, inst):
50 private_ips = utils.get_from_path(inst, 'fixed_ip/address')
51 private_ips = [dict(version=4, addr=a) for a in private_ips]
52 public_ips = utils.get_from_path(inst, 'fixed_ip/floating_ips/address')
53 public_ips = [dict(version=4, addr=a) for a in public_ips]
54 return dict(public=public_ips, private=private_ips)
055
=== added file 'nova/api/openstack/views/flavors.py'
--- nova/api/openstack/views/flavors.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/views/flavors.py 2011-03-17 17:53:41 +0000
@@ -0,0 +1,51 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18from nova.api.openstack import common
19
20
21def get_view_builder(req):
22 '''
23 A factory method that returns the correct builder based on the version of
24 the api requested.
25 '''
26 version = common.get_api_version(req)
27 base_url = req.application_url
28 if version == '1.1':
29 return ViewBuilder_1_1(base_url)
30 else:
31 return ViewBuilder_1_0()
32
33
34class ViewBuilder(object):
35 def __init__(self):
36 pass
37
38 def build(self, flavor_obj):
39 raise NotImplementedError()
40
41
42class ViewBuilder_1_1(ViewBuilder):
43 def __init__(self, base_url):
44 self.base_url = base_url
45
46 def generate_href(self, flavor_id):
47 return "%s/flavors/%s" % (self.base_url, flavor_id)
48
49
50class ViewBuilder_1_0(ViewBuilder):
51 pass
052
=== added file 'nova/api/openstack/views/images.py'
--- nova/api/openstack/views/images.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/views/images.py 2011-03-17 17:53:41 +0000
@@ -0,0 +1,51 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18from nova.api.openstack import common
19
20
21def get_view_builder(req):
22 '''
23 A factory method that returns the correct builder based on the version of
24 the api requested.
25 '''
26 version = common.get_api_version(req)
27 base_url = req.application_url
28 if version == '1.1':
29 return ViewBuilder_1_1(base_url)
30 else:
31 return ViewBuilder_1_0()
32
33
34class ViewBuilder(object):
35 def __init__(self):
36 pass
37
38 def build(self, image_obj):
39 raise NotImplementedError()
40
41
42class ViewBuilder_1_1(ViewBuilder):
43 def __init__(self, base_url):
44 self.base_url = base_url
45
46 def generate_href(self, image_id):
47 return "%s/images/%s" % (self.base_url, image_id)
48
49
50class ViewBuilder_1_0(ViewBuilder):
51 pass
052
=== added file 'nova/api/openstack/views/servers.py'
--- nova/api/openstack/views/servers.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/views/servers.py 2011-03-17 17:53:41 +0000
@@ -0,0 +1,132 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18import hashlib
19from nova.compute import power_state
20from nova.api.openstack import common
21from nova.api.openstack.views import addresses as addresses_view
22from nova.api.openstack.views import flavors as flavors_view
23from nova.api.openstack.views import images as images_view
24from nova import utils
25
26
27def get_view_builder(req):
28 '''
29 A factory method that returns the correct builder based on the version of
30 the api requested.
31 '''
32 version = common.get_api_version(req)
33 addresses_builder = addresses_view.get_view_builder(req)
34 if version == '1.1':
35 flavor_builder = flavors_view.get_view_builder(req)
36 image_builder = images_view.get_view_builder(req)
37 return ViewBuilder_1_1(addresses_builder, flavor_builder,
38 image_builder)
39 else:
40 return ViewBuilder_1_0(addresses_builder)
41
42
43class ViewBuilder(object):
44 '''
45 Models a server response as a python dictionary.
46 Abstract methods: _build_image, _build_flavor
47 '''
48
49 def __init__(self, addresses_builder):
50 self.addresses_builder = addresses_builder
51
52 def build(self, inst, is_detail):
53 """
54 Coerces into dictionary format, mapping everything to
55 Rackspace-like attributes for return
56 """
57 if is_detail:
58 return self._build_detail(inst)
59 else:
60 return self._build_simple(inst)
61
62 def _build_simple(self, inst):
63 return dict(server=dict(id=inst['id'], name=inst['display_name']))
64
65 def _build_detail(self, inst):
66 power_mapping = {
67 None: 'build',
68 power_state.NOSTATE: 'build',
69 power_state.RUNNING: 'active',
70 power_state.BLOCKED: 'active',
71 power_state.SUSPENDED: 'suspended',
72 power_state.PAUSED: 'paused',
73 power_state.SHUTDOWN: 'active',
74 power_state.SHUTOFF: 'active',
75 power_state.CRASHED: 'error',
76 power_state.FAILED: 'error'}
77 inst_dict = {}
78
79 #mapped_keys = dict(status='state', imageId='image_id',
80 # flavorId='instance_type', name='display_name', id='id')
81
82 mapped_keys = dict(status='state', name='display_name', id='id')
83
84 for k, v in mapped_keys.iteritems():
85 inst_dict[k] = inst[v]
86
87 inst_dict['status'] = power_mapping[inst_dict['status']]
88 inst_dict['addresses'] = self.addresses_builder.build(inst)
89
90 # Return the metadata as a dictionary
91 metadata = {}
92 for item in inst['metadata']:
93 metadata[item['key']] = item['value']
94 inst_dict['metadata'] = metadata
95
96 inst_dict['hostId'] = ''
97 if inst['host']:
98 inst_dict['hostId'] = hashlib.sha224(inst['host']).hexdigest()
99
100 self._build_image(inst_dict, inst)
101 self._build_flavor(inst_dict, inst)
102
103 return dict(server=inst_dict)
104
105 def _build_image(self, response, inst):
106 raise NotImplementedError()
107
108 def _build_flavor(self, response, inst):
109 raise NotImplementedError()
110
111
112class ViewBuilder_1_0(ViewBuilder):
113 def _build_image(self, response, inst):
114 response["imageId"] = inst["image_id"]
115
116 def _build_flavor(self, response, inst):
117 response["flavorId"] = inst["instance_type"]
118
119
120class ViewBuilder_1_1(ViewBuilder):
121 def __init__(self, addresses_builder, flavor_builder, image_builder):
122 ViewBuilder.__init__(self, addresses_builder)
123 self.flavor_builder = flavor_builder
124 self.image_builder = image_builder
125
126 def _build_image(self, response, inst):
127 image_id = inst["image_id"]
128 response["imageRef"] = self.image_builder.generate_href(image_id)
129
130 def _build_flavor(self, response, inst):
131 flavor_id = inst["instance_type"]
132 response["flavorRef"] = self.flavor_builder.generate_href(flavor_id)
0133
=== modified file 'nova/tests/api/openstack/fakes.py'
--- nova/tests/api/openstack/fakes.py 2011-03-16 19:16:16 +0000
+++ nova/tests/api/openstack/fakes.py 2011-03-17 17:53:41 +0000
@@ -79,6 +79,7 @@
79 api = openstack.FaultWrapper(auth.AuthMiddleware(79 api = openstack.FaultWrapper(auth.AuthMiddleware(
80 ratelimiting.RateLimitingMiddleware(inner_application)))80 ratelimiting.RateLimitingMiddleware(inner_application)))
81 mapper['/v1.0'] = api81 mapper['/v1.0'] = api
82 mapper['/v1.1'] = api
82 mapper['/'] = openstack.FaultWrapper(openstack.Versions())83 mapper['/'] = openstack.FaultWrapper(openstack.Versions())
83 return mapper84 return mapper
8485
8586
=== modified file 'nova/tests/api/openstack/test_servers.py'
--- nova/tests/api/openstack/test_servers.py 2011-03-16 19:16:16 +0000
+++ nova/tests/api/openstack/test_servers.py 2011-03-17 17:53:41 +0000
@@ -24,6 +24,7 @@
24import stubout24import stubout
25import webob25import webob
2626
27from nova import context
27from nova import db28from nova import db
28from nova import flags29from nova import flags
29from nova import test30from nova import test
@@ -81,7 +82,7 @@
81 "admin_pass": "",82 "admin_pass": "",
82 "user_id": user_id,83 "user_id": user_id,
83 "project_id": "",84 "project_id": "",
84 "image_id": 10,85 "image_id": "10",
85 "kernel_id": "",86 "kernel_id": "",
86 "ramdisk_id": "",87 "ramdisk_id": "",
87 "launch_index": 0,88 "launch_index": 0,
@@ -94,7 +95,7 @@
94 "local_gb": 0,95 "local_gb": 0,
95 "hostname": "",96 "hostname": "",
96 "host": None,97 "host": None,
97 "instance_type": "",98 "instance_type": "1",
98 "user_data": "",99 "user_data": "",
99 "reservation_id": "",100 "reservation_id": "",
100 "mac_address": "",101 "mac_address": "",
@@ -179,6 +180,25 @@
179 self.assertEqual(len(addresses["private"]), 1)180 self.assertEqual(len(addresses["private"]), 1)
180 self.assertEqual(addresses["private"][0], private)181 self.assertEqual(addresses["private"][0], private)
181182
183 def test_get_server_by_id_with_addresses_v1_1(self):
184 private = "192.168.0.3"
185 public = ["1.2.3.4"]
186 new_return_server = return_server_with_addresses(private, public)
187 self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
188 req = webob.Request.blank('/v1.1/servers/1')
189 req.environ['api.version'] = '1.1'
190 res = req.get_response(fakes.wsgi_app())
191 res_dict = json.loads(res.body)
192 self.assertEqual(res_dict['server']['id'], '1')
193 self.assertEqual(res_dict['server']['name'], 'server1')
194 addresses = res_dict['server']['addresses']
195 self.assertEqual(len(addresses["public"]), len(public))
196 self.assertEqual(addresses["public"][0],
197 {"version": 4, "addr": public[0]})
198 self.assertEqual(len(addresses["private"]), 1)
199 self.assertEqual(addresses["private"][0],
200 {"version": 4, "addr": private})
201
182 def test_get_server_list(self):202 def test_get_server_list(self):
183 req = webob.Request.blank('/v1.0/servers')203 req = webob.Request.blank('/v1.0/servers')
184 res = req.get_response(fakes.wsgi_app())204 res = req.get_response(fakes.wsgi_app())
@@ -339,19 +359,32 @@
339 res = req.get_response(fakes.wsgi_app())359 res = req.get_response(fakes.wsgi_app())
340 self.assertEqual(res.status, '404 Not Found')360 self.assertEqual(res.status, '404 Not Found')
341361
342 def test_get_all_server_details(self):362 def test_get_all_server_details_v1_0(self):
343 req = webob.Request.blank('/v1.0/servers/detail')363 req = webob.Request.blank('/v1.0/servers/detail')
344 res = req.get_response(fakes.wsgi_app())364 res = req.get_response(fakes.wsgi_app())
345 res_dict = json.loads(res.body)365 res_dict = json.loads(res.body)
346366
347 i = 0367 for i, s in enumerate(res_dict['servers']):
348 for s in res_dict['servers']:368 self.assertEqual(s['id'], i)
349 self.assertEqual(s['id'], i)369 self.assertEqual(s['hostId'], '')
350 self.assertEqual(s['hostId'], '')370 self.assertEqual(s['name'], 'server%d' % i)
351 self.assertEqual(s['name'], 'server%d' % i)371 self.assertEqual(s['imageId'], '10')
352 self.assertEqual(s['imageId'], 10)372 self.assertEqual(s['flavorId'], '1')
353 self.assertEqual(s['metadata']['seq'], i)373 self.assertEqual(s['metadata']['seq'], i)
354 i += 1374
375 def test_get_all_server_details_v1_1(self):
376 req = webob.Request.blank('/v1.1/servers/detail')
377 req.environ['api.version'] = '1.1'
378 res = req.get_response(fakes.wsgi_app())
379 res_dict = json.loads(res.body)
380
381 for i, s in enumerate(res_dict['servers']):
382 self.assertEqual(s['id'], i)
383 self.assertEqual(s['hostId'], '')
384 self.assertEqual(s['name'], 'server%d' % i)
385 self.assertEqual(s['imageRef'], 'http://localhost/v1.1/images/10')
386 self.assertEqual(s['flavorRef'], 'http://localhost/v1.1/flavors/1')
387 self.assertEqual(s['metadata']['seq'], i)
355388
356 def test_get_all_server_details_with_host(self):389 def test_get_all_server_details_with_host(self):
357 '''390 '''
358391
=== modified file 'plugins/xenserver/xenapi/etc/xapi.d/plugins/glance'
--- plugins/xenserver/xenapi/etc/xapi.d/plugins/glance 2011-03-11 20:14:48 +0000
+++ plugins/xenserver/xenapi/etc/xapi.d/plugins/glance 2011-03-17 17:53:41 +0000
@@ -216,7 +216,7 @@
216 'x-image-meta-status': 'queued',216 'x-image-meta-status': 'queued',
217 'x-image-meta-disk-format': 'vhd',217 'x-image-meta-disk-format': 'vhd',
218 'x-image-meta-container-format': 'ovf',218 'x-image-meta-container-format': 'ovf',
219 'x-image-meta-property-os-type': os_type219 'x-image-meta-property-os-type': os_type,
220 }220 }
221221
222 for header, value in headers.iteritems():222 for header, value in headers.iteritems():