Merge lp:~rackspace-titan/nova/server-addresses-lp761652 into lp:~hudson-openstack/nova/trunk

Proposed by Brian Waldon
Status: Merged
Approved by: Dan Prince
Approved revision: 1251
Merged at revision: 1277
Proposed branch: lp:~rackspace-titan/nova/server-addresses-lp761652
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 648 lines (+307/-71)
7 files modified
nova/api/openstack/__init__.py (+4/-8)
nova/api/openstack/ips.py (+68/-19)
nova/api/openstack/servers.py (+13/-13)
nova/api/openstack/views/addresses.py (+30/-9)
nova/api/openstack/views/servers.py (+12/-1)
nova/db/sqlalchemy/api.py (+16/-8)
nova/tests/api/openstack/test_servers.py (+164/-13)
To merge this branch: bzr merge lp:~rackspace-titan/nova/server-addresses-lp761652
Reviewer Review Type Date Requested Status
Dan Prince (community) Approve
Matt Dietz (community) Approve
Review via email: mp+66830@code.launchpad.net

Description of the change

- Present ip addresses in their actual networks, not just a static public/private
- Floating ip addresses are grouped into the networks with their associated fixed ips
- Add addresses attribute to server entities

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

Just a heads up, the images mapper was duplicated in the v1.0 api router. This branch removes the extra one.

Revision history for this message
Matt Dietz (cerberus) wrote :

366 +def require_instance_exists(f):

I'm a little bit concerned about this decorator, not because it isn'ta good idea (it is) but because I worry that people will get lazy with
enforcing an instance actually exists. I realize that it merged in a previous patch,
but regardless, I personally think the responsibility
of it should fall to the code interested in performing something with an instance,
not to the database to enforce.

Beyond that, it seems like it would introduce a bit of unnecessary overhead when someone
is being a good Nova citizen?

I realize you're only using it in a few places currently, but it would only be a matter of time
before it was used everywhere.

What do you think?

At the very least, wouldn't you want to set the name of the wrapper method ala:

399 - new_func.__name__ = func.__name__

With all that said, I won't hold up the review. I'm just genuinely curious

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

> 366 +def require_instance_exists(f):
>
> I'm a little bit concerned about this decorator, not because it isn'ta good
> idea (it is) but because I worry that people will get lazy with
> enforcing an instance actually exists. I realize that it merged in a previous
> patch,
> but regardless, I personally think the responsibility
> of it should fall to the code interested in performing something with an
> instance,
> not to the database to enforce.
>
> Beyond that, it seems like it would introduce a bit of unnecessary overhead
> when someone
> is being a good Nova citizen?

I can definitely agree that it could duplicate the existence check in some cases. However, I feel whomever is adding code to Nova should first see what is available to them. I originally wrote this decorator because I was duplicating code all over the place in the OSAPI. I felt this was a natural place to do this check, but if you think there is a better solution, I would love to hear your thoughts.

> At the very least, wouldn't you want to set the name of the wrapper method
> ala:
>
> 399 - new_func.__name__ = func.__name__

I removed that because none of the other decorators had it, but I think you're right, it should be added back.

Revision history for this message
Dan Prince (dan-prince) wrote :

I would side with Matt on this in that the require_instance_exists decorator might be a bit to convenient and risky in that it does the instance_get (which might be more expensive than someone expects because joins are involved, etc.)

The require_instance_exists is only used by the metadata methods in the DB API at the moment. Perhaps instead of using the decorator those functions could be updated to do their updates/deletes/or creates and then check to ensure a record was in fact updated. See the 'instance_type_destroy' as an example. If we did this we can probably avoid the extra SQL hit and get the check for free (after the requested update/create/or delete operation). In cases where this doesn't work maybe just inline the instance_get which is simple enough.

In any case I'm not sure nits on this issue belong in this merge prop. Just wanted to chime in on the conversation since we are having it.

Revision history for this message
Matt Dietz (cerberus) wrote :

"In any case I'm not sure nits on this issue belong in this merge prop. Just wanted to chime in on the conversation since we are having it."

You're right. It's not a good enough reason to hold this patch up, but I would like to consider alternatives in the future.

review: Approve
Revision history for this message
Dan Prince (dan-prince) wrote :

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'nova/api/openstack/__init__.py'
2--- nova/api/openstack/__init__.py 2011-06-24 14:37:43 +0000
3+++ nova/api/openstack/__init__.py 2011-07-14 18:50:41 +0000
4@@ -125,6 +125,10 @@
5 collection={'detail': 'GET'},
6 member=self.server_members)
7
8+ mapper.resource("ip", "ips", controller=ips.create_resource(version),
9+ parent_resource=dict(member_name='server',
10+ collection_name='servers'))
11+
12 mapper.resource("image", "images",
13 controller=images.create_resource(version),
14 collection={'detail': 'GET'})
15@@ -144,9 +148,6 @@
16
17 def _setup_routes(self, mapper):
18 super(APIRouterV10, self)._setup_routes(mapper, '1.0')
19- mapper.resource("image", "images",
20- controller=images.create_resource('1.0'),
21- collection={'detail': 'GET'})
22
23 mapper.resource("shared_ip_group", "shared_ip_groups",
24 collection={'detail': 'GET'},
25@@ -157,11 +158,6 @@
26 parent_resource=dict(member_name='server',
27 collection_name='servers'))
28
29- mapper.resource("ip", "ips", controller=ips.create_resource(),
30- collection=dict(public='GET', private='GET'),
31- parent_resource=dict(member_name='server',
32- collection_name='servers'))
33-
34
35 class APIRouterV11(APIRouter):
36 """Define routes specific to OpenStack API V1.1."""
37
38=== modified file 'nova/api/openstack/ips.py'
39--- nova/api/openstack/ips.py 2011-07-06 20:28:10 +0000
40+++ nova/api/openstack/ips.py 2011-07-14 18:50:41 +0000
41@@ -23,6 +23,7 @@
42 from nova.api.openstack import faults
43 import nova.api.openstack.views.addresses
44 from nova.api.openstack import wsgi
45+from nova import db
46
47
48 class Controller(object):
49@@ -30,7 +31,6 @@
50
51 def __init__(self):
52 self.compute_api = nova.compute.API()
53- self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
54
55 def _get_instance(self, req, server_id):
56 try:
57@@ -40,21 +40,6 @@
58 return faults.Fault(exc.HTTPNotFound())
59 return instance
60
61- def index(self, req, server_id):
62- instance = self._get_instance(req, server_id)
63- return {'addresses': self.builder.build(instance)}
64-
65- def public(self, req, server_id):
66- instance = self._get_instance(req, server_id)
67- return {'public': self.builder.build_public_parts(instance)}
68-
69- def private(self, req, server_id):
70- instance = self._get_instance(req, server_id)
71- return {'private': self.builder.build_private_parts(instance)}
72-
73- def show(self, req, server_id, id):
74- return faults.Fault(exc.HTTPNotImplemented())
75-
76 def create(self, req, server_id, body):
77 return faults.Fault(exc.HTTPNotImplemented())
78
79@@ -62,7 +47,71 @@
80 return faults.Fault(exc.HTTPNotImplemented())
81
82
83-def create_resource():
84+class ControllerV10(Controller):
85+
86+ def index(self, req, server_id):
87+ instance = self._get_instance(req, server_id)
88+ builder = nova.api.openstack.views.addresses.ViewBuilderV10()
89+ return {'addresses': builder.build(instance)}
90+
91+ def show(self, req, server_id, id):
92+ instance = self._get_instance(req, server_id)
93+ builder = self._get_view_builder(req)
94+ if id == 'private':
95+ view = builder.build_private_parts(instance)
96+ elif id == 'public':
97+ view = builder.build_public_parts(instance)
98+ else:
99+ msg = _("Only private and public networks available")
100+ return faults.Fault(exc.HTTPNotFound(explanation=msg))
101+
102+ return {id: view}
103+
104+ def _get_view_builder(self, req):
105+ return nova.api.openstack.views.addresses.ViewBuilderV10()
106+
107+
108+class ControllerV11(Controller):
109+
110+ def index(self, req, server_id):
111+ context = req.environ['nova.context']
112+ interfaces = self._get_virtual_interfaces(context, server_id)
113+ networks = self._get_view_builder(req).build(interfaces)
114+ return {'addresses': networks}
115+
116+ def show(self, req, server_id, id):
117+ context = req.environ['nova.context']
118+ interfaces = self._get_virtual_interfaces(context, server_id)
119+ network = self._get_view_builder(req).build_network(interfaces, id)
120+
121+ if network is None:
122+ msg = _("Instance is not a member of specified network")
123+ return faults.Fault(exc.HTTPNotFound(explanation=msg))
124+
125+ return network
126+
127+ def _get_virtual_interfaces(self, context, server_id):
128+ try:
129+ return db.api.virtual_interface_get_by_instance(context, server_id)
130+ except nova.exception.InstanceNotFound:
131+ msg = _("Instance does not exist")
132+ raise exc.HTTPNotFound(explanation=msg)
133+
134+ def _get_view_builder(self, req):
135+ return nova.api.openstack.views.addresses.ViewBuilderV11()
136+
137+
138+def create_resource(version):
139+ controller = {
140+ '1.0': ControllerV10,
141+ '1.1': ControllerV11,
142+ }[version]()
143+
144+ xmlns = {
145+ '1.0': wsgi.XMLNS_V10,
146+ '1.1': wsgi.XMLNS_V11,
147+ }[version]
148+
149 metadata = {
150 'list_collections': {
151 'public': {'item_name': 'ip', 'item_key': 'addr'},
152@@ -72,8 +121,8 @@
153
154 body_serializers = {
155 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
156- xmlns=wsgi.XMLNS_V10),
157+ xmlns=xmlns),
158 }
159 serializer = wsgi.ResponseSerializer(body_serializers)
160
161- return wsgi.Resource(Controller(), serializer=serializer)
162+ return wsgi.Resource(controller, serializer=serializer)
163
164=== modified file 'nova/api/openstack/servers.py'
165--- nova/api/openstack/servers.py 2011-07-12 16:13:02 +0000
166+++ nova/api/openstack/servers.py 2011-07-14 18:50:41 +0000
167@@ -19,6 +19,7 @@
168 from webob import exc
169
170 from nova import compute
171+from nova import db
172 from nova import exception
173 from nova import flags
174 from nova import log as logging
175@@ -62,7 +63,7 @@
176 return exc.HTTPBadRequest(explanation=str(err))
177 return servers
178
179- def _get_view_builder(self, req):
180+ def _build_view(self, req, instance, is_detail=False):
181 raise NotImplementedError()
182
183 def _limit_items(self, items, req):
184@@ -88,8 +89,7 @@
185 fixed_ip=fixed_ip,
186 recurse_zones=recurse_zones)
187 limited_list = self._limit_items(instance_list, req)
188- builder = self._get_view_builder(req)
189- servers = [builder.build(inst, is_detail)['server']
190+ servers = [self._build_view(req, inst, is_detail)['server']
191 for inst in limited_list]
192 return dict(servers=servers)
193
194@@ -99,8 +99,7 @@
195 try:
196 instance = self.compute_api.routing_get(
197 req.environ['nova.context'], id)
198- builder = self._get_view_builder(req)
199- return builder.build(instance, is_detail=True)
200+ return self._build_view(req, instance, is_detail=True)
201 except exception.NotFound:
202 return faults.Fault(exc.HTTPNotFound())
203
204@@ -121,8 +120,7 @@
205 for key in ['instance_type', 'image_ref']:
206 inst[key] = extra_values[key]
207
208- builder = self._get_view_builder(req)
209- server = builder.build(inst, is_detail=True)
210+ server = self._build_view(req, inst, is_detail=True)
211 server['server']['adminPass'] = extra_values['password']
212 return server
213
214@@ -426,10 +424,10 @@
215 def _flavor_id_from_req_data(self, data):
216 return data['server']['flavorId']
217
218- def _get_view_builder(self, req):
219- addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV10()
220- return nova.api.openstack.views.servers.ViewBuilderV10(
221- addresses_builder)
222+ def _build_view(self, req, instance, is_detail=False):
223+ addresses = nova.api.openstack.views.addresses.ViewBuilderV10()
224+ builder = nova.api.openstack.views.servers.ViewBuilderV10(addresses)
225+ return builder.build(instance, is_detail=is_detail)
226
227 def _limit_items(self, items, req):
228 return common.limited(items, req)
229@@ -498,16 +496,18 @@
230 href = data['server']['flavorRef']
231 return common.get_id_from_href(href)
232
233- def _get_view_builder(self, req):
234+ def _build_view(self, req, instance, is_detail=False):
235 base_url = req.application_url
236 flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11(
237 base_url)
238 image_builder = nova.api.openstack.views.images.ViewBuilderV11(
239 base_url)
240 addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11()
241- return nova.api.openstack.views.servers.ViewBuilderV11(
242+ builder = nova.api.openstack.views.servers.ViewBuilderV11(
243 addresses_builder, flavor_builder, image_builder, base_url)
244
245+ return builder.build(instance, is_detail=is_detail)
246+
247 def _action_change_password(self, input_dict, req, id):
248 context = req.environ['nova.context']
249 if (not 'changePassword' in input_dict
250
251=== modified file 'nova/api/openstack/views/addresses.py'
252--- nova/api/openstack/views/addresses.py 2011-06-30 19:42:51 +0000
253+++ nova/api/openstack/views/addresses.py 2011-07-14 18:50:41 +0000
254@@ -20,13 +20,14 @@
255
256
257 class ViewBuilder(object):
258- ''' Models a server addresses response as a python dictionary.'''
259+ """Models a server addresses response as a python dictionary."""
260
261 def build(self, inst):
262 raise NotImplementedError()
263
264
265 class ViewBuilderV10(ViewBuilder):
266+
267 def build(self, inst):
268 private_ips = self.build_private_parts(inst)
269 public_ips = self.build_public_parts(inst)
270@@ -40,11 +41,31 @@
271
272
273 class ViewBuilderV11(ViewBuilder):
274- def build(self, inst):
275- # TODO(tr3buchet) - this shouldn't be hard coded to 4...
276- private_ips = utils.get_from_path(inst, 'fixed_ips/address')
277- private_ips = [dict(version=4, addr=a) for a in private_ips]
278- public_ips = utils.get_from_path(inst,
279- 'fixed_ips/floating_ips/address')
280- public_ips = [dict(version=4, addr=a) for a in public_ips]
281- return dict(public=public_ips, private=private_ips)
282+
283+ def build(self, interfaces):
284+ networks = {}
285+ for interface in interfaces:
286+ network_label = interface['network']['label']
287+
288+ if network_label not in networks:
289+ networks[network_label] = []
290+
291+ networks[network_label].extend(self._extract_ipv4(interface))
292+
293+ return networks
294+
295+ def build_network(self, interfaces, network_label):
296+ for interface in interfaces:
297+ if interface['network']['label'] == network_label:
298+ ips = self._extract_ipv4(interface)
299+ return {network_label: list(ips)}
300+ return None
301+
302+ def _extract_ipv4(self, interface):
303+ for fixed_ip in interface['fixed_ips']:
304+ yield self._build_ip_entity(fixed_ip['address'], 4)
305+ for floating_ip in fixed_ip.get('floating_ips', []):
306+ yield self._build_ip_entity(floating_ip['address'], 4)
307+
308+ def _build_ip_entity(self, address, version):
309+ return {'addr': address, 'version': version}
310
311=== modified file 'nova/api/openstack/views/servers.py'
312--- nova/api/openstack/views/servers.py 2011-07-07 13:42:13 +0000
313+++ nova/api/openstack/views/servers.py 2011-07-14 18:50:41 +0000
314@@ -77,7 +77,6 @@
315 inst_dict = {
316 'id': inst['id'],
317 'name': inst['display_name'],
318- 'addresses': self.addresses_builder.build(inst),
319 'status': power_mapping[inst.get('state')]}
320
321 ctxt = nova.context.get_admin_context()
322@@ -98,10 +97,15 @@
323
324 self._build_image(inst_dict, inst)
325 self._build_flavor(inst_dict, inst)
326+ self._build_addresses(inst_dict, inst)
327
328 inst_dict['uuid'] = inst['uuid']
329 return dict(server=inst_dict)
330
331+ def _build_addresses(self, response, inst):
332+ """Return the addresses sub-resource of a server."""
333+ raise NotImplementedError()
334+
335 def _build_image(self, response, inst):
336 """Return the image sub-resource of a server."""
337 raise NotImplementedError()
338@@ -128,6 +132,9 @@
339 if 'instance_type' in dict(inst):
340 response['flavorId'] = inst['instance_type']['flavorid']
341
342+ def _build_addresses(self, response, inst):
343+ response['addresses'] = self.addresses_builder.build(inst)
344+
345
346 class ViewBuilderV11(ViewBuilder):
347 """Model an Openstack API V1.0 server response."""
348@@ -151,6 +158,10 @@
349 flavor_ref = self.flavor_builder.generate_href(flavor_id)
350 response["flavorRef"] = flavor_ref
351
352+ def _build_addresses(self, response, inst):
353+ interfaces = inst.get('virtual_interfaces', [])
354+ response['addresses'] = self.addresses_builder.build(interfaces)
355+
356 def _build_extra(self, response, inst):
357 self._build_links(response, inst)
358
359
360=== modified file 'nova/db/sqlalchemy/api.py'
361--- nova/db/sqlalchemy/api.py 2011-07-01 15:07:08 +0000
362+++ nova/db/sqlalchemy/api.py 2011-07-14 18:50:41 +0000
363@@ -118,8 +118,23 @@
364 return wrapper
365
366
367+def require_instance_exists(f):
368+ """Decorator to require the specified instance to exist.
369+
370+ Requres the wrapped function to use context and instance_id as
371+ their first two arguments.
372+ """
373+
374+ def wrapper(context, instance_id, *args, **kwargs):
375+ db.api.instance_get(context, instance_id)
376+ return f(context, instance_id, *args, **kwargs)
377+ wrapper.__name__ = f.__name__
378+ return wrapper
379+
380+
381 ###################
382
383+
384 @require_admin_context
385 def service_destroy(context, service_id):
386 session = get_session()
387@@ -921,6 +936,7 @@
388
389
390 @require_context
391+@require_instance_exists
392 def virtual_interface_get_by_instance(context, instance_id):
393 """Gets all virtual interfaces for instance.
394
395@@ -3071,14 +3087,6 @@
396 ####################
397
398
399-def require_instance_exists(func):
400- def new_func(context, instance_id, *args, **kwargs):
401- db.api.instance_get(context, instance_id)
402- return func(context, instance_id, *args, **kwargs)
403- new_func.__name__ = func.__name__
404- return new_func
405-
406-
407 @require_context
408 @require_instance_exists
409 def instance_metadata_get(context, instance_id):
410
411=== modified file 'nova/tests/api/openstack/test_servers.py'
412--- nova/tests/api/openstack/test_servers.py 2011-07-11 21:26:46 +0000
413+++ nova/tests/api/openstack/test_servers.py 2011-07-14 18:50:41 +0000
414@@ -65,6 +65,18 @@
415 return stub_instance(id, uuid=uuid)
416
417
418+def return_virtual_interface_by_instance(interfaces):
419+ def _return_virtual_interface_by_instance(context, instance_id):
420+ return interfaces
421+ return _return_virtual_interface_by_instance
422+
423+
424+def return_virtual_interface_instance_nonexistant(interfaces):
425+ def _return_virtual_interface_by_instance(context, instance_id):
426+ raise exception.InstanceNotFound(instance_id=instance_id)
427+ return _return_virtual_interface_by_instance
428+
429+
430 def return_server_with_addresses(private, public):
431 def _return_server(context, id):
432 return stub_instance(id, private_address=private,
433@@ -72,6 +84,12 @@
434 return _return_server
435
436
437+def return_server_with_interfaces(interfaces):
438+ def _return_server(context, id):
439+ return stub_instance(id, interfaces=interfaces)
440+ return _return_server
441+
442+
443 def return_server_with_power_state(power_state):
444 def _return_server(context, id):
445 return stub_instance(id, power_state=power_state)
446@@ -124,10 +142,13 @@
447
448 def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
449 host=None, power_state=0, reservation_id="",
450- uuid=FAKE_UUID):
451+ uuid=FAKE_UUID, interfaces=None):
452 metadata = []
453 metadata.append(InstanceMetadata(key='seq', value=id))
454
455+ if interfaces is None:
456+ interfaces = []
457+
458 inst_type = instance_types.get_instance_type_by_flavor_id(1)
459
460 if public_addresses is None:
461@@ -171,7 +192,8 @@
462 "display_description": "",
463 "locked": False,
464 "metadata": metadata,
465- "uuid": uuid}
466+ "uuid": uuid,
467+ "virtual_interfaces": interfaces}
468
469 instance["fixed_ips"] = {
470 "address": private_address,
471@@ -411,23 +433,152 @@
472 self.assertEquals(ip.getAttribute('addr'), private)
473
474 def test_get_server_by_id_with_addresses_v1_1(self):
475- private = "192.168.0.3"
476- public = ["1.2.3.4"]
477- new_return_server = return_server_with_addresses(private, public)
478+ interfaces = [
479+ {
480+ 'network': {'label': 'network_1'},
481+ 'fixed_ips': [
482+ {'address': '192.168.0.3'},
483+ {'address': '192.168.0.4'},
484+ ],
485+ },
486+ {
487+ 'network': {'label': 'network_2'},
488+ 'fixed_ips': [
489+ {'address': '172.19.0.1'},
490+ {'address': '172.19.0.2'},
491+ ],
492+ },
493+ ]
494+ new_return_server = return_server_with_interfaces(interfaces)
495 self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
496+
497 req = webob.Request.blank('/v1.1/servers/1')
498 res = req.get_response(fakes.wsgi_app())
499+
500 res_dict = json.loads(res.body)
501 self.assertEqual(res_dict['server']['id'], 1)
502 self.assertEqual(res_dict['server']['name'], 'server1')
503 addresses = res_dict['server']['addresses']
504- # RM(4047): Figure otu what is up with the 1.1 api and multi-nic
505- #self.assertEqual(len(addresses["public"]), len(public))
506- #self.assertEqual(addresses["public"][0],
507- # {"version": 4, "addr": public[0]})
508- #self.assertEqual(len(addresses["private"]), 1)
509- #self.assertEqual(addresses["private"][0],
510- # {"version": 4, "addr": private})
511+ expected = {
512+ 'network_1': [
513+ {'addr': '192.168.0.3', 'version': 4},
514+ {'addr': '192.168.0.4', 'version': 4},
515+ ],
516+ 'network_2': [
517+ {'addr': '172.19.0.1', 'version': 4},
518+ {'addr': '172.19.0.2', 'version': 4},
519+ ],
520+ }
521+
522+ self.assertEqual(addresses, expected)
523+
524+ def test_get_server_addresses_v1_1(self):
525+ interfaces = [
526+ {
527+ 'network': {'label': 'network_1'},
528+ 'fixed_ips': [
529+ {'address': '192.168.0.3'},
530+ {'address': '192.168.0.4'},
531+ ],
532+ },
533+ {
534+ 'network': {'label': 'network_2'},
535+ 'fixed_ips': [
536+ {
537+ 'address': '172.19.0.1',
538+ 'floating_ips': [
539+ {'address': '1.2.3.4'},
540+ ],
541+ },
542+ {'address': '172.19.0.2'},
543+ ],
544+ },
545+ ]
546+
547+ _return_vifs = return_virtual_interface_by_instance(interfaces)
548+ self.stubs.Set(nova.db.api,
549+ 'virtual_interface_get_by_instance',
550+ _return_vifs)
551+
552+ req = webob.Request.blank('/v1.1/servers/1/ips')
553+ res = req.get_response(fakes.wsgi_app())
554+ res_dict = json.loads(res.body)
555+
556+ expected = {
557+ 'addresses': {
558+ 'network_1': [
559+ {'version': 4, 'addr': '192.168.0.3'},
560+ {'version': 4, 'addr': '192.168.0.4'},
561+ ],
562+ 'network_2': [
563+ {'version': 4, 'addr': '172.19.0.1'},
564+ {'version': 4, 'addr': '1.2.3.4'},
565+ {'version': 4, 'addr': '172.19.0.2'},
566+ ],
567+ },
568+ }
569+
570+ self.assertEqual(res_dict, expected)
571+
572+ def test_get_server_addresses_single_network_v1_1(self):
573+ interfaces = [
574+ {
575+ 'network': {'label': 'network_1'},
576+ 'fixed_ips': [
577+ {'address': '192.168.0.3'},
578+ {'address': '192.168.0.4'},
579+ ],
580+ },
581+ {
582+ 'network': {'label': 'network_2'},
583+ 'fixed_ips': [
584+ {
585+ 'address': '172.19.0.1',
586+ 'floating_ips': [
587+ {'address': '1.2.3.4'},
588+ ],
589+ },
590+ {'address': '172.19.0.2'},
591+ ],
592+ },
593+ ]
594+ _return_vifs = return_virtual_interface_by_instance(interfaces)
595+ self.stubs.Set(nova.db.api,
596+ 'virtual_interface_get_by_instance',
597+ _return_vifs)
598+
599+ req = webob.Request.blank('/v1.1/servers/1/ips/network_2')
600+ res = req.get_response(fakes.wsgi_app())
601+ self.assertEqual(res.status_int, 200)
602+ res_dict = json.loads(res.body)
603+ expected = {
604+ 'network_2': [
605+ {'version': 4, 'addr': '172.19.0.1'},
606+ {'version': 4, 'addr': '1.2.3.4'},
607+ {'version': 4, 'addr': '172.19.0.2'},
608+ ],
609+ }
610+ self.assertEqual(res_dict, expected)
611+
612+ def test_get_server_addresses_nonexistant_network_v1_1(self):
613+ _return_vifs = return_virtual_interface_by_instance([])
614+ self.stubs.Set(nova.db.api,
615+ 'virtual_interface_get_by_instance',
616+ _return_vifs)
617+
618+ req = webob.Request.blank('/v1.1/servers/1/ips/network_0')
619+ res = req.get_response(fakes.wsgi_app())
620+ self.assertEqual(res.status_int, 404)
621+
622+ def test_get_server_addresses_nonexistant_server_v1_1(self):
623+ _return_vifs = return_virtual_interface_instance_nonexistant([])
624+ self.stubs.Set(nova.db.api,
625+ 'virtual_interface_get_by_instance',
626+ _return_vifs)
627+
628+ req = webob.Request.blank('/v1.1/servers/600/ips')
629+ res = req.get_response(fakes.wsgi_app())
630+ self.assertEqual(res.status_int, 404)
631
632 def test_get_server_list(self):
633 req = webob.Request.blank('/v1.0/servers')
634@@ -787,13 +938,13 @@
635
636 res = req.get_response(fakes.wsgi_app())
637
638+ self.assertEqual(res.status_int, 200)
639 server = json.loads(res.body)['server']
640 self.assertEqual(16, len(server['adminPass']))
641 self.assertEqual('server_test', server['name'])
642 self.assertEqual(1, server['id'])
643 self.assertEqual(flavor_ref, server['flavorRef'])
644 self.assertEqual(image_href, server['imageRef'])
645- self.assertEqual(res.status_int, 200)
646
647 def test_create_instance_v1_1_bad_href(self):
648 self._setup_for_create_instance()