Merge lp:~elkirya/nova/floating-os-api into lp:~hudson-openstack/nova/trunk

Proposed by Eldar Nugaev
Status: Merged
Approved by: Devin Carlen
Approved revision: 1205
Merged at revision: 1218
Proposed branch: lp:~elkirya/nova/floating-os-api
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 598 lines (+449/-7)
10 files modified
.mailmap (+4/-0)
Authors (+3/-2)
nova/api/openstack/contrib/floating_ips.py (+172/-0)
nova/db/api.py (+8/-0)
nova/db/sqlalchemy/api.py (+41/-2)
nova/exception.py (+4/-0)
nova/network/api.py (+13/-0)
nova/tests/api/openstack/contrib/__init__.py (+15/-0)
nova/tests/api/openstack/contrib/test_floating_ips.py (+186/-0)
nova/tests/api/openstack/fakes.py (+3/-3)
To merge this branch: bzr merge lp:~elkirya/nova/floating-os-api
Reviewer Review Type Date Requested Status
Devin Carlen (community) Approve
Brian Waldon (community) Approve
Vish Ishaya (community) Approve
Review via email: mp+65845@code.launchpad.net

Commit message

Added floating IP support in OS API

To post a comment you must log in.
lp:~elkirya/nova/floating-os-api updated
1199. By Ilya Alekseyev

trunk merged. conflicts resolved

1200. By Ilya Alekseyev

merged

Revision history for this message
Vish Ishaya (vishvananda) wrote :

Can you edit .Mailmap for secondary addresses instead of adding multiple emails in authors?

lp:~elkirya/nova/floating-os-api updated
1201. By Ilya Alekseyev

mailmap

1202. By Ilya Alekseyev

mailmap

Revision history for this message
Ilya Alekseyev (ilyaalekseyev) wrote :

> Can you edit .Mailmap for secondary addresses instead of adding multiple
> emails in authors?

Yep, sorry. Fixed.

Revision history for this message
Vish Ishaya (vishvananda) wrote :

This looks good. Passing it off to Brian for review on os api implementation.

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

This is really cool stuff, guys. I did find a few things:

Can you please add the "os-" prefix to your extension alias? Refer to the Volumes extension as an example.

192: Can we call this class FloatingIPs, or FloatingIPsExtension?

212: the spacing looks a bit odd here. Can you line up the arguments to the ResourceExtension call?

311: This exception message seems a bit counter-intuitive. Would you prefer to actually use the FloatingIpNotFound exception that already exists? If the "fixed_ip" is the problem, you can move the existing FloatingIpNotFound exception to FloatinIpNotFoundForFixedAddress, and rename FloatingIpNotDefined.

325, 333: Can you make these method names more specific? get_floating_ip and list_floating_ips sound better

367,368: I'm not sure about the non-OpenStack copyright. Vish, can you comment on this?

review: Needs Fixing
Revision history for this message
Thierry Carrez (ttx) wrote :

Non-OpenStack copyrights are OK (since we do not require copyright assignments). However the mention "All rights reserved" is probably not OK and in contradiction with the terms of the Apache license mentioned below it (disclaimer: IANAL)

Revision history for this message
Thierry Carrez (ttx) wrote :

Apparently the mention "All rights reserved" is OK since it only applies to copyright and does not interfere with license, so ignore me.

lp:~elkirya/nova/floating-os-api updated
1203. By Ilya Alekseyev

review issues fixed

1204. By Eldar Nugaev

fixed pep style

Revision history for this message
Eldar Nugaev (reldan) wrote :

Hi Brian
Thank you for very cool review.
Fixed.
But we cannot rename then class, because we get WARNING extensions [-] Did not find expected name "Floating_ips" in /home/ilya/work/floating-os-api/nova/api/openstack/contrib/floating_ips.py

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

That's fine, Eldar. One last thing:

211: Can you make this "os-floating-ips"? I would appreciate the consistency.

lp:~elkirya/nova/floating-os-api updated
1205. By Eldar Nugaev

changed extension alias to os-floating-ips

Revision history for this message
Eldar Nugaev (reldan) wrote :

Thank you!
Fixed

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

Good work, Eldar.

review: Approve
Revision history for this message
Eldar Nugaev (reldan) wrote :

Thank you for approve.
Could you please change the proposal status to approve?

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

I would very much like to hear from another reviewer. I requested a review from nova-core.

Revision history for this message
Devin Carlen (devcamcar) wrote :

Brian, this is great!

review: Approve
Revision history for this message
Devin Carlen (devcamcar) wrote :

Oh, I misread. I mean, Edgar, this is great! :)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.mailmap'
2--- .mailmap 2011-05-11 19:16:37 +0000
3+++ .mailmap 2011-06-27 16:39:52 +0000
4@@ -47,3 +47,7 @@
5 <vishvananda@gmail.com> <root@mirror.nasanebula.net>
6 <vishvananda@gmail.com> <root@ubuntu>
7 <vishvananda@gmail.com> <vishvananda@yahoo.com>
8+<ilyaalekseyev@acm.org> <ialekseev@griddynamics.com>
9+<ilyaalekseyev@acm.org> <ilya@oscloud.ru>
10+<reldan@oscloud.ru> <enugaev@griddynamics.com>
11+<kshileev@gmail.com> <kshileev@griddynamics.com>
12\ No newline at end of file
13
14=== modified file 'Authors'
15--- Authors 2011-06-24 12:01:51 +0000
16+++ Authors 2011-06-27 16:39:52 +0000
17@@ -22,14 +22,14 @@
18 Dean Troyer <dtroyer@gmail.com>
19 Devin Carlen <devin.carlen@gmail.com>
20 Ed Leafe <ed@leafe.com>
21-Eldar Nugaev <enugaev@griddynamics.com>
22+Eldar Nugaev <reldan@oscloud.ru>
23 Eric Day <eday@oddments.org>
24 Eric Windisch <eric@cloudscaling.com>
25 Ewan Mellor <ewan.mellor@citrix.com>
26 Gabe Westmaas <gabe.westmaas@rackspace.com>
27 Hisaharu Ishii <ishii.hisaharu@lab.ntt.co.jp>
28 Hisaki Ohara <hisaki.ohara@intel.com>
29-Ilya Alekseyev <ialekseev@griddynamics.com>
30+Ilya Alekseyev <ilyaalekseyev@acm.org>
31 Isaku Yamahata <yamahata@valinux.co.jp>
32 Jason Cannavale <jason.cannavale@rackspace.com>
33 Jason Koelker <jason@koelker.net>
34@@ -53,6 +53,7 @@
35 Ken Pepple <ken.pepple@gmail.com>
36 Kevin Bringard <kbringard@attinteractive.com>
37 Kevin L. Mitchell <kevin.mitchell@rackspace.com>
38+Kirill Shileev <kshileev@gmail.com>
39 Koji Iida <iida.koji@lab.ntt.co.jp>
40 Lorin Hochstein <lorin@isi.edu>
41 Lvov Maxim <usrleon@gmail.com>
42
43=== added file 'nova/api/openstack/contrib/floating_ips.py'
44--- nova/api/openstack/contrib/floating_ips.py 1970-01-01 00:00:00 +0000
45+++ nova/api/openstack/contrib/floating_ips.py 2011-06-27 16:39:52 +0000
46@@ -0,0 +1,172 @@
47+# vim: tabstop=4 shiftwidth=4 softtabstop=4
48+
49+# Copyright 2011 OpenStack LLC.
50+# Copyright 2011 Grid Dynamics
51+# Copyright 2011 Eldar Nugaev, Kirill Shileev, Ilya Alekseyev
52+#
53+# Licensed under the Apache License, Version 2.0 (the "License"); you may
54+# not use this file except in compliance with the License. You may obtain
55+# a copy of the License at
56+#
57+# http://www.apache.org/licenses/LICENSE-2.0
58+#
59+# Unless required by applicable law or agreed to in writing, software
60+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
61+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
62+# License for the specific language governing permissions and limitations
63+# under the License
64+from webob import exc
65+
66+from nova import exception
67+from nova import network
68+from nova import rpc
69+from nova.api.openstack import faults
70+from nova.api.openstack import extensions
71+
72+
73+def _translate_floating_ip_view(floating_ip):
74+ result = {'id': floating_ip['id'],
75+ 'ip': floating_ip['address']}
76+ if 'fixed_ip' in floating_ip:
77+ result['fixed_ip'] = floating_ip['fixed_ip']['address']
78+ else:
79+ result['fixed_ip'] = None
80+ if 'instance' in floating_ip:
81+ result['instance_id'] = floating_ip['instance']['id']
82+ else:
83+ result['instance_id'] = None
84+ return {'floating_ip': result}
85+
86+
87+def _translate_floating_ips_view(floating_ips):
88+ return {'floating_ips': [_translate_floating_ip_view(floating_ip)
89+ for floating_ip in floating_ips]}
90+
91+
92+class FloatingIPController(object):
93+ """The Floating IPs API controller for the OpenStack API."""
94+
95+ _serialization_metadata = {
96+ 'application/xml': {
97+ "attributes": {
98+ "floating_ip": [
99+ "id",
100+ "ip",
101+ "instance_id",
102+ "fixed_ip",
103+ ]}}}
104+
105+ def __init__(self):
106+ self.network_api = network.API()
107+ super(FloatingIPController, self).__init__()
108+
109+ def show(self, req, id):
110+ """Return data about the given floating ip."""
111+ context = req.environ['nova.context']
112+
113+ try:
114+ floating_ip = self.network_api.get_floating_ip(context, id)
115+ except exception.NotFound:
116+ return faults.Fault(exc.HTTPNotFound())
117+
118+ return _translate_floating_ip_view(floating_ip)
119+
120+ def index(self, req):
121+ context = req.environ['nova.context']
122+
123+ floating_ips = self.network_api.list_floating_ips(context)
124+
125+ return _translate_floating_ips_view(floating_ips)
126+
127+ def create(self, req, body):
128+ context = req.environ['nova.context']
129+
130+ try:
131+ address = self.network_api.allocate_floating_ip(context)
132+ ip = self.network_api.get_floating_ip_by_ip(context, address)
133+ except rpc.RemoteError as ex:
134+ if ex.exc_type == 'NoMoreAddresses':
135+ raise exception.NoMoreFloatingIps()
136+ else:
137+ raise
138+
139+ return {'allocated': {
140+ "id": ip['id'],
141+ "floating_ip": ip['address']}}
142+
143+ def delete(self, req, id):
144+ context = req.environ['nova.context']
145+
146+ ip = self.network_api.get_floating_ip(context, id)
147+ self.network_api.release_floating_ip(context, address=ip)
148+
149+ return {'released': {
150+ "id": ip['id'],
151+ "floating_ip": ip['address']}}
152+
153+ def associate(self, req, id, body):
154+ """ /floating_ips/{id}/associate fixed ip in body """
155+ context = req.environ['nova.context']
156+ floating_ip = self._get_ip_by_id(context, id)
157+
158+ fixed_ip = body['associate_address']['fixed_ip']
159+
160+ try:
161+ self.network_api.associate_floating_ip(context,
162+ floating_ip, fixed_ip)
163+ except rpc.RemoteError:
164+ raise
165+
166+ return {'associated':
167+ {
168+ "floating_ip_id": id,
169+ "floating_ip": floating_ip,
170+ "fixed_ip": fixed_ip}}
171+
172+ def disassociate(self, req, id, body):
173+ """ POST /floating_ips/{id}/disassociate """
174+ context = req.environ['nova.context']
175+ floating_ip = self.network_api.get_floating_ip(context, id)
176+ address = floating_ip['address']
177+ fixed_ip = floating_ip['fixed_ip']['address']
178+
179+ try:
180+ self.network_api.disassociate_floating_ip(context, address)
181+ except rpc.RemoteError:
182+ raise
183+
184+ return {'disassociated': {'floating_ip': address,
185+ 'fixed_ip': fixed_ip}}
186+
187+ def _get_ip_by_id(self, context, value):
188+ """Checks that value is id and then returns its address."""
189+ return self.network_api.get_floating_ip(context, value)['address']
190+
191+
192+class Floating_ips(extensions.ExtensionDescriptor):
193+ def get_name(self):
194+ return "Floating_ips"
195+
196+ def get_alias(self):
197+ return "os-floating-ips"
198+
199+ def get_description(self):
200+ return "Floating IPs support"
201+
202+ def get_namespace(self):
203+ return "http://docs.openstack.org/ext/floating_ips/api/v1.1"
204+
205+ def get_updated(self):
206+ return "2011-06-16T00:00:00+00:00"
207+
208+ def get_resources(self):
209+ resources = []
210+
211+ res = extensions.ResourceExtension('os-floating-ips',
212+ FloatingIPController(),
213+ member_actions={
214+ 'associate': 'POST',
215+ 'disassociate': 'POST'})
216+ resources.append(res)
217+
218+ return resources
219
220=== modified file 'nova/db/api.py'
221--- nova/db/api.py 2011-06-25 19:38:07 +0000
222+++ nova/db/api.py 2011-06-27 16:39:52 +0000
223@@ -223,6 +223,9 @@
224
225 ###################
226
227+def floating_ip_get(context, floating_ip_id):
228+ return IMPL.floating_ip_get(context, floating_ip_id)
229+
230
231 def floating_ip_allocate_address(context, host, project_id):
232 """Allocate free floating ip and return the address.
233@@ -289,6 +292,11 @@
234 return IMPL.floating_ip_get_by_address(context, address)
235
236
237+def floating_ip_get_by_ip(context, ip):
238+ """Get a floating ip by floating address."""
239+ return IMPL.floating_ip_get_by_ip(context, ip)
240+
241+
242 def floating_ip_update(context, address, values):
243 """Update a floating ip by address or raise if it doesn't exist."""
244 return IMPL.floating_ip_update(context, address, values)
245
246=== modified file 'nova/db/sqlalchemy/api.py'
247--- nova/db/sqlalchemy/api.py 2011-06-25 19:38:07 +0000
248+++ nova/db/sqlalchemy/api.py 2011-06-27 16:39:52 +0000
249@@ -428,6 +428,29 @@
250
251
252 ###################
253+@require_context
254+def floating_ip_get(context, id):
255+ session = get_session()
256+ result = None
257+ if is_admin_context(context):
258+ result = session.query(models.FloatingIp).\
259+ options(joinedload('fixed_ip')).\
260+ options(joinedload_all('fixed_ip.instance')).\
261+ filter_by(id=id).\
262+ filter_by(deleted=can_read_deleted(context)).\
263+ first()
264+ elif is_user_context(context):
265+ result = session.query(models.FloatingIp).\
266+ options(joinedload('fixed_ip')).\
267+ options(joinedload_all('fixed_ip.instance')).\
268+ filter_by(project_id=context.project_id).\
269+ filter_by(id=id).\
270+ filter_by(deleted=False).\
271+ first()
272+ if not result:
273+ raise exception.FloatingIpNotFoundForFixedAddress()
274+
275+ return result
276
277
278 @require_context
279@@ -582,7 +605,23 @@
280 filter_by(deleted=can_read_deleted(context)).\
281 first()
282 if not result:
283- raise exception.FloatingIpNotFound(fixed_ip=address)
284+ raise exception.FloatingIpNotFoundForFixedAddress(fixed_ip=address)
285+
286+ return result
287+
288+
289+@require_context
290+def floating_ip_get_by_ip(context, ip, session=None):
291+ if not session:
292+ session = get_session()
293+
294+ result = session.query(models.FloatingIp).\
295+ filter_by(address=ip).\
296+ filter_by(deleted=can_read_deleted(context)).\
297+ first()
298+
299+ if not result:
300+ raise exception.FloatingIpNotFound(floating_ip=ip)
301
302 return result
303
304@@ -722,7 +761,7 @@
305 options(joinedload('instance')).\
306 first()
307 if not result:
308- raise exception.FloatingIpNotFound(fixed_ip=address)
309+ raise exception.FloatingIpNotFoundForFixedAddress(fixed_ip=address)
310
311 if is_user_context(context):
312 authorize_project_context(context, result.instance.project_id)
313
314=== modified file 'nova/exception.py'
315--- nova/exception.py 2011-06-24 12:01:51 +0000
316+++ nova/exception.py 2011-06-27 16:39:52 +0000
317@@ -361,6 +361,10 @@
318
319
320 class FloatingIpNotFound(NotFound):
321+ message = _("Floating ip %(floating_ip)s not found")
322+
323+
324+class FloatingIpNotFoundForFixedAddress(NotFound):
325 message = _("Floating ip not found for fixed address %(fixed_ip)s.")
326
327
328
329=== modified file 'nova/network/api.py'
330--- nova/network/api.py 2011-05-11 19:24:01 +0000
331+++ nova/network/api.py 2011-06-27 16:39:52 +0000
332@@ -34,6 +34,19 @@
333 class API(base.Base):
334 """API for interacting with the network manager."""
335
336+ def get_floating_ip(self, context, id):
337+ rv = self.db.floating_ip_get(context, id)
338+ return dict(rv.iteritems())
339+
340+ def get_floating_ip_by_ip(self, context, address):
341+ res = self.db.floating_ip_get_by_ip(context, address)
342+ return dict(res.iteritems())
343+
344+ def list_floating_ips(self, context):
345+ ips = self.db.floating_ip_get_all_by_project(context,
346+ context.project_id)
347+ return ips
348+
349 def allocate_floating_ip(self, context):
350 if quota.allowed_floating_ips(context, 1) < 1:
351 LOG.warn(_('Quota exceeeded for %s, tried to allocate '
352
353=== added directory 'nova/tests/api/openstack/contrib'
354=== added file 'nova/tests/api/openstack/contrib/__init__.py'
355--- nova/tests/api/openstack/contrib/__init__.py 1970-01-01 00:00:00 +0000
356+++ nova/tests/api/openstack/contrib/__init__.py 2011-06-27 16:39:52 +0000
357@@ -0,0 +1,15 @@
358+# vim: tabstop=4 shiftwidth=4 softtabstop=4
359+
360+# Copyright 2011 OpenStack LLC
361+#
362+# Licensed under the Apache License, Version 2.0 (the "License"); you may
363+# not use this file except in compliance with the License. You may obtain
364+# a copy of the License at
365+#
366+# http://www.apache.org/licenses/LICENSE-2.0
367+#
368+# Unless required by applicable law or agreed to in writing, software
369+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
370+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
371+# License for the specific language governing permissions and limitations
372+# under the License.
373
374=== added file 'nova/tests/api/openstack/contrib/test_floating_ips.py'
375--- nova/tests/api/openstack/contrib/test_floating_ips.py 1970-01-01 00:00:00 +0000
376+++ nova/tests/api/openstack/contrib/test_floating_ips.py 2011-06-27 16:39:52 +0000
377@@ -0,0 +1,186 @@
378+# Copyright 2011 Eldar Nugaev
379+# All Rights Reserved.
380+#
381+# Licensed under the Apache License, Version 2.0 (the "License"); you may
382+# not use this file except in compliance with the License. You may obtain
383+# a copy of the License at
384+#
385+# http://www.apache.org/licenses/LICENSE-2.0
386+#
387+# Unless required by applicable law or agreed to in writing, software
388+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
389+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
390+# License for the specific language governing permissions and limitations
391+# under the License.
392+
393+import json
394+import stubout
395+import webob
396+
397+from nova import context
398+from nova import db
399+from nova import test
400+from nova import network
401+from nova.tests.api.openstack import fakes
402+
403+
404+from nova.api.openstack.contrib.floating_ips import FloatingIPController
405+from nova.api.openstack.contrib.floating_ips import _translate_floating_ip_view
406+
407+
408+def network_api_get_floating_ip(self, context, id):
409+ return {'id': 1, 'address': '10.10.10.10',
410+ 'fixed_ip': {'address': '11.0.0.1'}}
411+
412+
413+def network_api_list_floating_ips(self, context):
414+ return [{'id': 1,
415+ 'address': '10.10.10.10',
416+ 'instance': {'id': 11},
417+ 'fixed_ip': {'address': '10.0.0.1'}},
418+ {'id': 2,
419+ 'address': '10.10.10.11'}]
420+
421+
422+def network_api_allocate(self, context):
423+ return '10.10.10.10'
424+
425+
426+def network_api_release(self, context, address):
427+ pass
428+
429+
430+def network_api_associate(self, context, floating_ip, fixed_ip):
431+ pass
432+
433+
434+def network_api_disassociate(self, context, floating_address):
435+ pass
436+
437+
438+class FloatingIpTest(test.TestCase):
439+ address = "10.10.10.10"
440+
441+ def _create_floating_ip(self):
442+ """Create a floating ip object."""
443+ host = "fake_host"
444+ return db.floating_ip_create(self.context,
445+ {'address': self.address,
446+ 'host': host})
447+
448+ def _delete_floating_ip(self):
449+ db.floating_ip_destroy(self.context, self.address)
450+
451+ def setUp(self):
452+ super(FloatingIpTest, self).setUp()
453+ self.controller = FloatingIPController()
454+ self.stubs = stubout.StubOutForTesting()
455+ fakes.FakeAuthManager.reset_fake_data()
456+ fakes.FakeAuthDatabase.data = {}
457+ fakes.stub_out_networking(self.stubs)
458+ fakes.stub_out_rate_limiting(self.stubs)
459+ fakes.stub_out_auth(self.stubs)
460+ self.stubs.Set(network.api.API, "get_floating_ip",
461+ network_api_get_floating_ip)
462+ self.stubs.Set(network.api.API, "list_floating_ips",
463+ network_api_list_floating_ips)
464+ self.stubs.Set(network.api.API, "allocate_floating_ip",
465+ network_api_allocate)
466+ self.stubs.Set(network.api.API, "release_floating_ip",
467+ network_api_release)
468+ self.stubs.Set(network.api.API, "associate_floating_ip",
469+ network_api_associate)
470+ self.stubs.Set(network.api.API, "disassociate_floating_ip",
471+ network_api_disassociate)
472+ self.context = context.get_admin_context()
473+ self._create_floating_ip()
474+
475+ def tearDown(self):
476+ self.stubs.UnsetAll()
477+ self._delete_floating_ip()
478+ super(FloatingIpTest, self).tearDown()
479+
480+ def test_translate_floating_ip_view(self):
481+ floating_ip_address = self._create_floating_ip()
482+ floating_ip = db.floating_ip_get_by_address(self.context,
483+ floating_ip_address)
484+ view = _translate_floating_ip_view(floating_ip)
485+ self.assertTrue('floating_ip' in view)
486+ self.assertTrue(view['floating_ip']['id'])
487+ self.assertEqual(view['floating_ip']['ip'], self.address)
488+ self.assertEqual(view['floating_ip']['fixed_ip'], None)
489+ self.assertEqual(view['floating_ip']['instance_id'], None)
490+
491+ def test_floating_ips_list(self):
492+ req = webob.Request.blank('/v1.1/os-floating-ips')
493+ res = req.get_response(fakes.wsgi_app())
494+ self.assertEqual(res.status_int, 200)
495+ res_dict = json.loads(res.body)
496+ response = {'floating_ips': [{'floating_ip': {'instance_id': 11,
497+ 'ip': '10.10.10.10',
498+ 'fixed_ip': '10.0.0.1',
499+ 'id': 1}},
500+ {'floating_ip': {'instance_id': None,
501+ 'ip': '10.10.10.11',
502+ 'fixed_ip': None,
503+ 'id': 2}}]}
504+ self.assertEqual(res_dict, response)
505+
506+ def test_floating_ip_show(self):
507+ req = webob.Request.blank('/v1.1/os-floating-ips/1')
508+ res = req.get_response(fakes.wsgi_app())
509+ self.assertEqual(res.status_int, 200)
510+ res_dict = json.loads(res.body)
511+ self.assertEqual(res_dict['floating_ip']['id'], 1)
512+ self.assertEqual(res_dict['floating_ip']['ip'], '10.10.10.10')
513+ self.assertEqual(res_dict['floating_ip']['fixed_ip'], '11.0.0.1')
514+ self.assertEqual(res_dict['floating_ip']['instance_id'], None)
515+
516+ def test_floating_ip_allocate(self):
517+ req = webob.Request.blank('/v1.1/os-floating-ips')
518+ req.method = 'POST'
519+ res = req.get_response(fakes.wsgi_app())
520+ self.assertEqual(res.status_int, 200)
521+ ip = json.loads(res.body)['allocated']
522+ expected = {
523+ "id": 1,
524+ "floating_ip": '10.10.10.10'}
525+ self.assertEqual(ip, expected)
526+
527+ def test_floating_ip_release(self):
528+ req = webob.Request.blank('/v1.1/os-floating-ips/1')
529+ req.method = 'DELETE'
530+ res = req.get_response(fakes.wsgi_app())
531+ self.assertEqual(res.status_int, 200)
532+ actual = json.loads(res.body)['released']
533+ expected = {
534+ "id": 1,
535+ "floating_ip": '10.10.10.10'}
536+ self.assertEqual(actual, expected)
537+
538+ def test_floating_ip_associate(self):
539+ body = dict(associate_address=dict(fixed_ip='1.2.3.4'))
540+ req = webob.Request.blank('/v1.1/os-floating-ips/1/associate')
541+ req.method = 'POST'
542+ req.body = json.dumps(body)
543+ req.headers["content-type"] = "application/json"
544+
545+ res = req.get_response(fakes.wsgi_app())
546+ self.assertEqual(res.status_int, 200)
547+ actual = json.loads(res.body)['associated']
548+ expected = {
549+ "floating_ip_id": '1',
550+ "floating_ip": "10.10.10.10",
551+ "fixed_ip": "1.2.3.4"}
552+ self.assertEqual(actual, expected)
553+
554+ def test_floating_ip_disassociate(self):
555+ req = webob.Request.blank('/v1.1/os-floating-ips/1/disassociate')
556+ req.method = 'POST'
557+ res = req.get_response(fakes.wsgi_app())
558+ self.assertEqual(res.status_int, 200)
559+ ip = json.loads(res.body)['disassociated']
560+ expected = {
561+ "floating_ip": '10.10.10.10',
562+ "fixed_ip": '11.0.0.1'}
563+ self.assertEqual(ip, expected)
564
565=== modified file 'nova/tests/api/openstack/fakes.py'
566--- nova/tests/api/openstack/fakes.py 2011-06-24 12:01:51 +0000
567+++ nova/tests/api/openstack/fakes.py 2011-06-27 16:39:52 +0000
568@@ -16,7 +16,6 @@
569 # under the License.
570
571 import copy
572-import json
573 import random
574 import string
575
576@@ -29,11 +28,11 @@
577
578 from nova import context
579 from nova import exception as exc
580-from nova import flags
581 from nova import utils
582 import nova.api.openstack.auth
583 from nova.api import openstack
584 from nova.api.openstack import auth
585+from nova.api.openstack import extensions
586 from nova.api.openstack import versions
587 from nova.api.openstack import limits
588 from nova.auth.manager import User, Project
589@@ -82,7 +81,8 @@
590 api10 = openstack.FaultWrapper(auth.AuthMiddleware(
591 limits.RateLimitingMiddleware(inner_app10)))
592 api11 = openstack.FaultWrapper(auth.AuthMiddleware(
593- limits.RateLimitingMiddleware(inner_app11)))
594+ limits.RateLimitingMiddleware(
595+ extensions.ExtensionMiddleware(inner_app11))))
596 mapper['/v1.0'] = api10
597 mapper['/v1.1'] = api11
598 mapper['/'] = openstack.FaultWrapper(versions.Versions())