Merge lp:~bcwaldon/nova/osapi-flavors-links into lp:~hudson-openstack/nova/trunk

Proposed by Brian Waldon
Status: Superseded
Proposed branch: lp:~bcwaldon/nova/osapi-flavors-links
Merge into: lp:~hudson-openstack/nova/trunk
Prerequisite: lp:~bcwaldon/nova/osapi-flavors-1_1
Diff against target: 392 lines (+250/-38)
4 files modified
nova/api/openstack/flavors.py (+28/-26)
nova/api/openstack/views/flavors.py (+70/-4)
nova/db/sqlalchemy/api.py (+1/-1)
nova/tests/api/openstack/test_flavors.py (+151/-7)
To merge this branch: bzr merge lp:~bcwaldon/nova/osapi-flavors-links
Reviewer Review Type Date Requested Status
Titan Pending
Nova Core security contacts Pending
Review via email: mp+53875@code.launchpad.net

This proposal has been superseded by a proposal from 2011-03-21.

Commit message

Add a "links" container to flavors entities for Openstack API v1.1

Description of the change

Add a "links" container to flavors entities for Openstack API v1.1

To post a comment you must log in.
813. By Brian Waldon

merging trunk r843

814. By Brian Waldon

making Controller._get_flavors is_detail a keyword argument

815. By Brian Waldon

merged trunk r864

816. By Brian Waldon

adding versioned controllers

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/openstack/flavors.py'
2--- nova/api/openstack/flavors.py 2011-03-17 18:35:46 +0000
3+++ nova/api/openstack/flavors.py 2011-03-17 18:35:46 +0000
4@@ -15,16 +15,12 @@
5 # License for the specific language governing permissions and limitations
6 # under the License.
7
8-from webob import exc
9+import webob
10
11 from nova import db
12-from nova import context
13-from nova.api.openstack import faults
14-from nova.api.openstack import common
15-from nova.compute import instance_types
16+from nova import exception
17+from nova import wsgi
18 from nova.api.openstack.views import flavors as flavors_views
19-from nova import wsgi
20-import nova.api.openstack
21
22
23 class Controller(wsgi.Controller):
24@@ -33,33 +29,39 @@
25 _serialization_metadata = {
26 'application/xml': {
27 "attributes": {
28- "flavor": ["id", "name", "ram", "disk"]}}}
29+ "flavor": ["id", "name", "ram", "disk"],
30+ "link": ["rel", "type", "href"],
31+ }
32+ }
33+ }
34
35 def index(self, req):
36 """Return all flavors in brief."""
37- return dict(flavors=[dict(id=flavor['id'], name=flavor['name'])
38- for flavor in self.detail(req)['flavors']])
39+ items = self._get_flavors(req, False)
40+ return dict(flavors=items)
41
42 def detail(self, req):
43 """Return all flavors in detail."""
44- items = [self.show(req, id)['flavor'] for id in self._all_ids(req)]
45+ items = self._get_flavors(req, True)
46 return dict(flavors=items)
47
48+ def _get_flavors(self, req, is_detail):
49+ """Helper function that returns a list of flavor dicts."""
50+ ctxt = req.environ['nova.context']
51+ flavors = db.api.instance_type_get_all(ctxt)
52+ builder = flavors_views.get_view_builder(req)
53+ items = [builder.build(flavor, is_detail=is_detail)
54+ for flavor in flavors.values()]
55+ return items
56+
57 def show(self, req, id):
58 """Return data about the given flavor id."""
59- ctxt = req.environ['nova.context']
60- flavor = db.api.instance_type_get_by_flavor_id(ctxt, id)
61- values = {
62- "id": flavor["flavorid"],
63- "name": flavor["name"],
64- "ram": flavor["memory_mb"],
65- "disk": flavor["local_gb"],
66- }
67+ try:
68+ ctxt = req.environ['nova.context']
69+ flavor = db.api.instance_type_get_by_flavor_id(ctxt, id)
70+ except exception.NotFound:
71+ return webob.exc.HTTPNotFound()
72+
73+ builder = flavors_views.get_view_builder(req)
74+ values = builder.build(flavor, is_detail=True)
75 return dict(flavor=values)
76-
77- def _all_ids(self, req):
78- """Return the list of all flavorids."""
79- ctxt = req.environ['nova.context']
80- inst_types = db.api.instance_type_get_all(ctxt)
81- flavor_ids = [inst_types[i]['flavorid'] for i in inst_types.keys()]
82- return sorted(flavor_ids)
83
84=== modified file 'nova/api/openstack/views/flavors.py'
85--- nova/api/openstack/views/flavors.py 2011-03-17 18:35:46 +0000
86+++ nova/api/openstack/views/flavors.py 2011-03-17 18:35:46 +0000
87@@ -32,20 +32,86 @@
88
89
90 class ViewBuilder(object):
91- def __init__(self):
92+
93+ def build(self, flavor_obj, is_detail=False):
94+ """Generic method used to generate a flavor entity."""
95+ if is_detail:
96+ flavor = self._build_detail(flavor_obj)
97+ else:
98+ flavor = self._build_simple(flavor_obj)
99+
100+ self._build_extra(flavor)
101+
102+ return flavor
103+
104+ def _build_simple(self, flavor_obj):
105+ """Build a minimal representation of a flavor."""
106+ return {
107+ "id": flavor_obj["flavorid"],
108+ "name": flavor_obj["name"],
109+ }
110+
111+ def _build_detail(self, flavor_obj):
112+ """Build a more complete representation of a flavor."""
113+ simple = self._build_simple(flavor_obj)
114+
115+ detail = {
116+ "ram": flavor_obj["memory_mb"],
117+ "disk": flavor_obj["local_gb"],
118+ }
119+
120+ detail.update(simple)
121+
122+ return detail
123+
124+ def _build_extra(self, flavor_obj):
125+ """Hook for version-specific changes to newly created flavor object."""
126 pass
127
128- def build(self, flavor_obj):
129- raise NotImplementedError()
130-
131
132 class ViewBuilder_1_1(ViewBuilder):
133+ """Openstack API v1.1 flavors view builder."""
134+
135 def __init__(self, base_url):
136+ """
137+ :param base_url: url of the root wsgi application
138+ """
139 self.base_url = base_url
140
141+ def _build_extra(self, flavor_obj):
142+ flavor_obj["links"] = self._build_links(flavor_obj)
143+
144+ def _build_links(self, flavor_obj):
145+ """Generate a container of links that refer to the provided flavor."""
146+ href = self.generate_href(flavor_obj["id"])
147+
148+ links = [
149+ {
150+ "rel": "self",
151+ "href": href,
152+ },
153+ {
154+ "rel": "bookmark",
155+ "type": "application/json",
156+ "href": href,
157+ },
158+ {
159+ "rel": "bookmark",
160+ "type": "application/xml",
161+ "href": href,
162+ },
163+ ]
164+
165+ return links
166+
167 def generate_href(self, flavor_id):
168+ """Create an url that refers to a specific flavor id."""
169 return "%s/flavors/%s" % (self.base_url, flavor_id)
170
171
172 class ViewBuilder_1_0(ViewBuilder):
173+ """
174+ Openstack API v1.0 flavors view builder. Currently, there
175+ are no 1.0-specific attributes of a flavor.
176+ """
177 pass
178
179=== modified file 'nova/db/sqlalchemy/api.py'
180--- nova/db/sqlalchemy/api.py 2011-03-15 07:45:35 +0000
181+++ nova/db/sqlalchemy/api.py 2011-03-17 18:35:46 +0000
182@@ -2372,7 +2372,7 @@
183 filter_by(flavorid=int(id)).\
184 first()
185 if not inst_type:
186- raise exception.NotFound(_("No flavor with name %s") % id)
187+ raise exception.NotFound(_("No flavor with flavorid %s") % id)
188 else:
189 return dict(inst_type)
190
191
192=== modified file 'nova/tests/api/openstack/test_flavors.py'
193--- nova/tests/api/openstack/test_flavors.py 2011-03-17 18:35:46 +0000
194+++ nova/tests/api/openstack/test_flavors.py 2011-03-17 18:35:46 +0000
195@@ -19,11 +19,10 @@
196 import stubout
197 import webob
198
199+import nova.db.api
200+from nova import context
201+from nova import exception
202 from nova import test
203-import nova.api
204-from nova import context
205-from nova.api.openstack import flavors
206-from nova import db
207 from nova.tests.api.openstack import fakes
208
209
210@@ -48,6 +47,10 @@
211 return instance_types
212
213
214+def return_instance_type_not_found(context, flavorid):
215+ raise exception.NotFound()
216+
217+
218 class FlavorsTest(test.TestCase):
219 def setUp(self):
220 super(FlavorsTest, self).setUp()
221@@ -67,7 +70,7 @@
222 self.stubs.UnsetAll()
223 super(FlavorsTest, self).tearDown()
224
225- def test_get_flavor_list(self):
226+ def test_get_flavor_list_v1_0(self):
227 req = webob.Request.blank('/v1.0/flavors')
228 res = req.get_response(fakes.wsgi_app())
229 self.assertEqual(res.status_int, 200)
230@@ -84,7 +87,7 @@
231 ]
232 self.assertEqual(flavors, expected)
233
234- def test_get_flavor_list_detail(self):
235+ def test_get_flavor_list_detail_v1_0(self):
236 req = webob.Request.blank('/v1.0/flavors/detail')
237 res = req.get_response(fakes.wsgi_app())
238 self.assertEqual(res.status_int, 200)
239@@ -105,7 +108,7 @@
240 ]
241 self.assertEqual(flavors, expected)
242
243- def test_get_flavor_by_id(self):
244+ def test_get_flavor_by_id_v1_0(self):
245 req = webob.Request.blank('/v1.0/flavors/12')
246 res = req.get_response(fakes.wsgi_app())
247 self.assertEqual(res.status_int, 200)
248@@ -117,3 +120,144 @@
249 "disk": "10",
250 }
251 self.assertEqual(flavor, expected)
252+
253+ def test_get_flavor_by_invalid_id(self):
254+ self.stubs.Set(nova.db.api, "instance_type_get_by_flavor_id",
255+ return_instance_type_not_found)
256+ req = webob.Request.blank('/v1.0/flavors/asdf')
257+ res = req.get_response(fakes.wsgi_app())
258+ self.assertEqual(res.status_int, 404)
259+
260+ def test_get_flavor_by_id_v1_1(self):
261+ req = webob.Request.blank('/v1.1/flavors/12')
262+ req.environ['api.version'] = '1.1'
263+ res = req.get_response(fakes.wsgi_app())
264+ self.assertEqual(res.status_int, 200)
265+ flavor = json.loads(res.body)["flavor"]
266+ expected = {
267+ "id": "12",
268+ "name": "flavor 12",
269+ "ram": "256",
270+ "disk": "10",
271+ "links": [
272+ {
273+ "rel": "self",
274+ "href": "http://localhost/v1.1/flavors/12",
275+ },
276+ {
277+ "rel": "bookmark",
278+ "type": "application/json",
279+ "href": "http://localhost/v1.1/flavors/12",
280+ },
281+ {
282+ "rel": "bookmark",
283+ "type": "application/xml",
284+ "href": "http://localhost/v1.1/flavors/12",
285+ },
286+ ],
287+ }
288+ self.assertEqual(flavor, expected)
289+
290+ def test_get_flavor_list_v1_1(self):
291+ req = webob.Request.blank('/v1.1/flavors')
292+ req.environ['api.version'] = '1.1'
293+ res = req.get_response(fakes.wsgi_app())
294+ self.assertEqual(res.status_int, 200)
295+ flavor = json.loads(res.body)["flavors"]
296+ expected = [
297+ {
298+ "id": "1",
299+ "name": "flavor 1",
300+ "links": [
301+ {
302+ "rel": "self",
303+ "href": "http://localhost/v1.1/flavors/1",
304+ },
305+ {
306+ "rel": "bookmark",
307+ "type": "application/json",
308+ "href": "http://localhost/v1.1/flavors/1",
309+ },
310+ {
311+ "rel": "bookmark",
312+ "type": "application/xml",
313+ "href": "http://localhost/v1.1/flavors/1",
314+ },
315+ ],
316+ },
317+ {
318+ "id": "2",
319+ "name": "flavor 2",
320+ "links": [
321+ {
322+ "rel": "self",
323+ "href": "http://localhost/v1.1/flavors/2",
324+ },
325+ {
326+ "rel": "bookmark",
327+ "type": "application/json",
328+ "href": "http://localhost/v1.1/flavors/2",
329+ },
330+ {
331+ "rel": "bookmark",
332+ "type": "application/xml",
333+ "href": "http://localhost/v1.1/flavors/2",
334+ },
335+ ],
336+ },
337+ ]
338+ self.assertEqual(flavor, expected)
339+
340+ def test_get_flavor_list_detail_v1_1(self):
341+ req = webob.Request.blank('/v1.1/flavors/detail')
342+ req.environ['api.version'] = '1.1'
343+ res = req.get_response(fakes.wsgi_app())
344+ self.assertEqual(res.status_int, 200)
345+ flavor = json.loads(res.body)["flavors"]
346+ expected = [
347+ {
348+ "id": "1",
349+ "name": "flavor 1",
350+ "ram": "256",
351+ "disk": "10",
352+ "links": [
353+ {
354+ "rel": "self",
355+ "href": "http://localhost/v1.1/flavors/1",
356+ },
357+ {
358+ "rel": "bookmark",
359+ "type": "application/json",
360+ "href": "http://localhost/v1.1/flavors/1",
361+ },
362+ {
363+ "rel": "bookmark",
364+ "type": "application/xml",
365+ "href": "http://localhost/v1.1/flavors/1",
366+ },
367+ ],
368+ },
369+ {
370+ "id": "2",
371+ "name": "flavor 2",
372+ "ram": "256",
373+ "disk": "10",
374+ "links": [
375+ {
376+ "rel": "self",
377+ "href": "http://localhost/v1.1/flavors/2",
378+ },
379+ {
380+ "rel": "bookmark",
381+ "type": "application/json",
382+ "href": "http://localhost/v1.1/flavors/2",
383+ },
384+ {
385+ "rel": "bookmark",
386+ "type": "application/xml",
387+ "href": "http://localhost/v1.1/flavors/2",
388+ },
389+ ],
390+ },
391+ ]
392+ self.assertEqual(flavor, expected)