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
=== modified file 'nova/api/openstack/__init__.py'
--- nova/api/openstack/__init__.py 2011-06-24 14:37:43 +0000
+++ nova/api/openstack/__init__.py 2011-07-14 18:50:41 +0000
@@ -125,6 +125,10 @@
125 collection={'detail': 'GET'},125 collection={'detail': 'GET'},
126 member=self.server_members)126 member=self.server_members)
127127
128 mapper.resource("ip", "ips", controller=ips.create_resource(version),
129 parent_resource=dict(member_name='server',
130 collection_name='servers'))
131
128 mapper.resource("image", "images",132 mapper.resource("image", "images",
129 controller=images.create_resource(version),133 controller=images.create_resource(version),
130 collection={'detail': 'GET'})134 collection={'detail': 'GET'})
@@ -144,9 +148,6 @@
144148
145 def _setup_routes(self, mapper):149 def _setup_routes(self, mapper):
146 super(APIRouterV10, self)._setup_routes(mapper, '1.0')150 super(APIRouterV10, self)._setup_routes(mapper, '1.0')
147 mapper.resource("image", "images",
148 controller=images.create_resource('1.0'),
149 collection={'detail': 'GET'})
150151
151 mapper.resource("shared_ip_group", "shared_ip_groups",152 mapper.resource("shared_ip_group", "shared_ip_groups",
152 collection={'detail': 'GET'},153 collection={'detail': 'GET'},
@@ -157,11 +158,6 @@
157 parent_resource=dict(member_name='server',158 parent_resource=dict(member_name='server',
158 collection_name='servers'))159 collection_name='servers'))
159160
160 mapper.resource("ip", "ips", controller=ips.create_resource(),
161 collection=dict(public='GET', private='GET'),
162 parent_resource=dict(member_name='server',
163 collection_name='servers'))
164
165161
166class APIRouterV11(APIRouter):162class APIRouterV11(APIRouter):
167 """Define routes specific to OpenStack API V1.1."""163 """Define routes specific to OpenStack API V1.1."""
168164
=== modified file 'nova/api/openstack/ips.py'
--- nova/api/openstack/ips.py 2011-07-06 20:28:10 +0000
+++ nova/api/openstack/ips.py 2011-07-14 18:50:41 +0000
@@ -23,6 +23,7 @@
23from nova.api.openstack import faults23from nova.api.openstack import faults
24import nova.api.openstack.views.addresses24import nova.api.openstack.views.addresses
25from nova.api.openstack import wsgi25from nova.api.openstack import wsgi
26from nova import db
2627
2728
28class Controller(object):29class Controller(object):
@@ -30,7 +31,6 @@
3031
31 def __init__(self):32 def __init__(self):
32 self.compute_api = nova.compute.API()33 self.compute_api = nova.compute.API()
33 self.builder = nova.api.openstack.views.addresses.ViewBuilderV10()
3434
35 def _get_instance(self, req, server_id):35 def _get_instance(self, req, server_id):
36 try:36 try:
@@ -40,21 +40,6 @@
40 return faults.Fault(exc.HTTPNotFound())40 return faults.Fault(exc.HTTPNotFound())
41 return instance41 return instance
4242
43 def index(self, req, server_id):
44 instance = self._get_instance(req, server_id)
45 return {'addresses': self.builder.build(instance)}
46
47 def public(self, req, server_id):
48 instance = self._get_instance(req, server_id)
49 return {'public': self.builder.build_public_parts(instance)}
50
51 def private(self, req, server_id):
52 instance = self._get_instance(req, server_id)
53 return {'private': self.builder.build_private_parts(instance)}
54
55 def show(self, req, server_id, id):
56 return faults.Fault(exc.HTTPNotImplemented())
57
58 def create(self, req, server_id, body):43 def create(self, req, server_id, body):
59 return faults.Fault(exc.HTTPNotImplemented())44 return faults.Fault(exc.HTTPNotImplemented())
6045
@@ -62,7 +47,71 @@
62 return faults.Fault(exc.HTTPNotImplemented())47 return faults.Fault(exc.HTTPNotImplemented())
6348
6449
65def create_resource():50class ControllerV10(Controller):
51
52 def index(self, req, server_id):
53 instance = self._get_instance(req, server_id)
54 builder = nova.api.openstack.views.addresses.ViewBuilderV10()
55 return {'addresses': builder.build(instance)}
56
57 def show(self, req, server_id, id):
58 instance = self._get_instance(req, server_id)
59 builder = self._get_view_builder(req)
60 if id == 'private':
61 view = builder.build_private_parts(instance)
62 elif id == 'public':
63 view = builder.build_public_parts(instance)
64 else:
65 msg = _("Only private and public networks available")
66 return faults.Fault(exc.HTTPNotFound(explanation=msg))
67
68 return {id: view}
69
70 def _get_view_builder(self, req):
71 return nova.api.openstack.views.addresses.ViewBuilderV10()
72
73
74class ControllerV11(Controller):
75
76 def index(self, req, server_id):
77 context = req.environ['nova.context']
78 interfaces = self._get_virtual_interfaces(context, server_id)
79 networks = self._get_view_builder(req).build(interfaces)
80 return {'addresses': networks}
81
82 def show(self, req, server_id, id):
83 context = req.environ['nova.context']
84 interfaces = self._get_virtual_interfaces(context, server_id)
85 network = self._get_view_builder(req).build_network(interfaces, id)
86
87 if network is None:
88 msg = _("Instance is not a member of specified network")
89 return faults.Fault(exc.HTTPNotFound(explanation=msg))
90
91 return network
92
93 def _get_virtual_interfaces(self, context, server_id):
94 try:
95 return db.api.virtual_interface_get_by_instance(context, server_id)
96 except nova.exception.InstanceNotFound:
97 msg = _("Instance does not exist")
98 raise exc.HTTPNotFound(explanation=msg)
99
100 def _get_view_builder(self, req):
101 return nova.api.openstack.views.addresses.ViewBuilderV11()
102
103
104def create_resource(version):
105 controller = {
106 '1.0': ControllerV10,
107 '1.1': ControllerV11,
108 }[version]()
109
110 xmlns = {
111 '1.0': wsgi.XMLNS_V10,
112 '1.1': wsgi.XMLNS_V11,
113 }[version]
114
66 metadata = {115 metadata = {
67 'list_collections': {116 'list_collections': {
68 'public': {'item_name': 'ip', 'item_key': 'addr'},117 'public': {'item_name': 'ip', 'item_key': 'addr'},
@@ -72,8 +121,8 @@
72121
73 body_serializers = {122 body_serializers = {
74 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,123 'application/xml': wsgi.XMLDictSerializer(metadata=metadata,
75 xmlns=wsgi.XMLNS_V10),124 xmlns=xmlns),
76 }125 }
77 serializer = wsgi.ResponseSerializer(body_serializers)126 serializer = wsgi.ResponseSerializer(body_serializers)
78127
79 return wsgi.Resource(Controller(), serializer=serializer)128 return wsgi.Resource(controller, serializer=serializer)
80129
=== modified file 'nova/api/openstack/servers.py'
--- nova/api/openstack/servers.py 2011-07-12 16:13:02 +0000
+++ nova/api/openstack/servers.py 2011-07-14 18:50:41 +0000
@@ -19,6 +19,7 @@
19from webob import exc19from webob import exc
2020
21from nova import compute21from nova import compute
22from nova import db
22from nova import exception23from nova import exception
23from nova import flags24from nova import flags
24from nova import log as logging25from nova import log as logging
@@ -62,7 +63,7 @@
62 return exc.HTTPBadRequest(explanation=str(err))63 return exc.HTTPBadRequest(explanation=str(err))
63 return servers64 return servers
6465
65 def _get_view_builder(self, req):66 def _build_view(self, req, instance, is_detail=False):
66 raise NotImplementedError()67 raise NotImplementedError()
6768
68 def _limit_items(self, items, req):69 def _limit_items(self, items, req):
@@ -88,8 +89,7 @@
88 fixed_ip=fixed_ip,89 fixed_ip=fixed_ip,
89 recurse_zones=recurse_zones)90 recurse_zones=recurse_zones)
90 limited_list = self._limit_items(instance_list, req)91 limited_list = self._limit_items(instance_list, req)
91 builder = self._get_view_builder(req)92 servers = [self._build_view(req, inst, is_detail)['server']
92 servers = [builder.build(inst, is_detail)['server']
93 for inst in limited_list]93 for inst in limited_list]
94 return dict(servers=servers)94 return dict(servers=servers)
9595
@@ -99,8 +99,7 @@
99 try:99 try:
100 instance = self.compute_api.routing_get(100 instance = self.compute_api.routing_get(
101 req.environ['nova.context'], id)101 req.environ['nova.context'], id)
102 builder = self._get_view_builder(req)102 return self._build_view(req, instance, is_detail=True)
103 return builder.build(instance, is_detail=True)
104 except exception.NotFound:103 except exception.NotFound:
105 return faults.Fault(exc.HTTPNotFound())104 return faults.Fault(exc.HTTPNotFound())
106105
@@ -121,8 +120,7 @@
121 for key in ['instance_type', 'image_ref']:120 for key in ['instance_type', 'image_ref']:
122 inst[key] = extra_values[key]121 inst[key] = extra_values[key]
123122
124 builder = self._get_view_builder(req)123 server = self._build_view(req, inst, is_detail=True)
125 server = builder.build(inst, is_detail=True)
126 server['server']['adminPass'] = extra_values['password']124 server['server']['adminPass'] = extra_values['password']
127 return server125 return server
128126
@@ -426,10 +424,10 @@
426 def _flavor_id_from_req_data(self, data):424 def _flavor_id_from_req_data(self, data):
427 return data['server']['flavorId']425 return data['server']['flavorId']
428426
429 def _get_view_builder(self, req):427 def _build_view(self, req, instance, is_detail=False):
430 addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV10()428 addresses = nova.api.openstack.views.addresses.ViewBuilderV10()
431 return nova.api.openstack.views.servers.ViewBuilderV10(429 builder = nova.api.openstack.views.servers.ViewBuilderV10(addresses)
432 addresses_builder)430 return builder.build(instance, is_detail=is_detail)
433431
434 def _limit_items(self, items, req):432 def _limit_items(self, items, req):
435 return common.limited(items, req)433 return common.limited(items, req)
@@ -498,16 +496,18 @@
498 href = data['server']['flavorRef']496 href = data['server']['flavorRef']
499 return common.get_id_from_href(href)497 return common.get_id_from_href(href)
500498
501 def _get_view_builder(self, req):499 def _build_view(self, req, instance, is_detail=False):
502 base_url = req.application_url500 base_url = req.application_url
503 flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11(501 flavor_builder = nova.api.openstack.views.flavors.ViewBuilderV11(
504 base_url)502 base_url)
505 image_builder = nova.api.openstack.views.images.ViewBuilderV11(503 image_builder = nova.api.openstack.views.images.ViewBuilderV11(
506 base_url)504 base_url)
507 addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11()505 addresses_builder = nova.api.openstack.views.addresses.ViewBuilderV11()
508 return nova.api.openstack.views.servers.ViewBuilderV11(506 builder = nova.api.openstack.views.servers.ViewBuilderV11(
509 addresses_builder, flavor_builder, image_builder, base_url)507 addresses_builder, flavor_builder, image_builder, base_url)
510508
509 return builder.build(instance, is_detail=is_detail)
510
511 def _action_change_password(self, input_dict, req, id):511 def _action_change_password(self, input_dict, req, id):
512 context = req.environ['nova.context']512 context = req.environ['nova.context']
513 if (not 'changePassword' in input_dict513 if (not 'changePassword' in input_dict
514514
=== modified file 'nova/api/openstack/views/addresses.py'
--- nova/api/openstack/views/addresses.py 2011-06-30 19:42:51 +0000
+++ nova/api/openstack/views/addresses.py 2011-07-14 18:50:41 +0000
@@ -20,13 +20,14 @@
2020
2121
22class ViewBuilder(object):22class ViewBuilder(object):
23 ''' Models a server addresses response as a python dictionary.'''23 """Models a server addresses response as a python dictionary."""
2424
25 def build(self, inst):25 def build(self, inst):
26 raise NotImplementedError()26 raise NotImplementedError()
2727
2828
29class ViewBuilderV10(ViewBuilder):29class ViewBuilderV10(ViewBuilder):
30
30 def build(self, inst):31 def build(self, inst):
31 private_ips = self.build_private_parts(inst)32 private_ips = self.build_private_parts(inst)
32 public_ips = self.build_public_parts(inst)33 public_ips = self.build_public_parts(inst)
@@ -40,11 +41,31 @@
4041
4142
42class ViewBuilderV11(ViewBuilder):43class ViewBuilderV11(ViewBuilder):
43 def build(self, inst):44
44 # TODO(tr3buchet) - this shouldn't be hard coded to 4...45 def build(self, interfaces):
45 private_ips = utils.get_from_path(inst, 'fixed_ips/address')46 networks = {}
46 private_ips = [dict(version=4, addr=a) for a in private_ips]47 for interface in interfaces:
47 public_ips = utils.get_from_path(inst,48 network_label = interface['network']['label']
48 'fixed_ips/floating_ips/address')49
49 public_ips = [dict(version=4, addr=a) for a in public_ips]50 if network_label not in networks:
50 return dict(public=public_ips, private=private_ips)51 networks[network_label] = []
52
53 networks[network_label].extend(self._extract_ipv4(interface))
54
55 return networks
56
57 def build_network(self, interfaces, network_label):
58 for interface in interfaces:
59 if interface['network']['label'] == network_label:
60 ips = self._extract_ipv4(interface)
61 return {network_label: list(ips)}
62 return None
63
64 def _extract_ipv4(self, interface):
65 for fixed_ip in interface['fixed_ips']:
66 yield self._build_ip_entity(fixed_ip['address'], 4)
67 for floating_ip in fixed_ip.get('floating_ips', []):
68 yield self._build_ip_entity(floating_ip['address'], 4)
69
70 def _build_ip_entity(self, address, version):
71 return {'addr': address, 'version': version}
5172
=== modified file 'nova/api/openstack/views/servers.py'
--- nova/api/openstack/views/servers.py 2011-07-07 13:42:13 +0000
+++ nova/api/openstack/views/servers.py 2011-07-14 18:50:41 +0000
@@ -77,7 +77,6 @@
77 inst_dict = {77 inst_dict = {
78 'id': inst['id'],78 'id': inst['id'],
79 'name': inst['display_name'],79 'name': inst['display_name'],
80 'addresses': self.addresses_builder.build(inst),
81 'status': power_mapping[inst.get('state')]}80 'status': power_mapping[inst.get('state')]}
8281
83 ctxt = nova.context.get_admin_context()82 ctxt = nova.context.get_admin_context()
@@ -98,10 +97,15 @@
9897
99 self._build_image(inst_dict, inst)98 self._build_image(inst_dict, inst)
100 self._build_flavor(inst_dict, inst)99 self._build_flavor(inst_dict, inst)
100 self._build_addresses(inst_dict, inst)
101101
102 inst_dict['uuid'] = inst['uuid']102 inst_dict['uuid'] = inst['uuid']
103 return dict(server=inst_dict)103 return dict(server=inst_dict)
104104
105 def _build_addresses(self, response, inst):
106 """Return the addresses sub-resource of a server."""
107 raise NotImplementedError()
108
105 def _build_image(self, response, inst):109 def _build_image(self, response, inst):
106 """Return the image sub-resource of a server."""110 """Return the image sub-resource of a server."""
107 raise NotImplementedError()111 raise NotImplementedError()
@@ -128,6 +132,9 @@
128 if 'instance_type' in dict(inst):132 if 'instance_type' in dict(inst):
129 response['flavorId'] = inst['instance_type']['flavorid']133 response['flavorId'] = inst['instance_type']['flavorid']
130134
135 def _build_addresses(self, response, inst):
136 response['addresses'] = self.addresses_builder.build(inst)
137
131138
132class ViewBuilderV11(ViewBuilder):139class ViewBuilderV11(ViewBuilder):
133 """Model an Openstack API V1.0 server response."""140 """Model an Openstack API V1.0 server response."""
@@ -151,6 +158,10 @@
151 flavor_ref = self.flavor_builder.generate_href(flavor_id)158 flavor_ref = self.flavor_builder.generate_href(flavor_id)
152 response["flavorRef"] = flavor_ref159 response["flavorRef"] = flavor_ref
153160
161 def _build_addresses(self, response, inst):
162 interfaces = inst.get('virtual_interfaces', [])
163 response['addresses'] = self.addresses_builder.build(interfaces)
164
154 def _build_extra(self, response, inst):165 def _build_extra(self, response, inst):
155 self._build_links(response, inst)166 self._build_links(response, inst)
156167
157168
=== modified file 'nova/db/sqlalchemy/api.py'
--- nova/db/sqlalchemy/api.py 2011-07-01 15:07:08 +0000
+++ nova/db/sqlalchemy/api.py 2011-07-14 18:50:41 +0000
@@ -118,8 +118,23 @@
118 return wrapper118 return wrapper
119119
120120
121def require_instance_exists(f):
122 """Decorator to require the specified instance to exist.
123
124 Requres the wrapped function to use context and instance_id as
125 their first two arguments.
126 """
127
128 def wrapper(context, instance_id, *args, **kwargs):
129 db.api.instance_get(context, instance_id)
130 return f(context, instance_id, *args, **kwargs)
131 wrapper.__name__ = f.__name__
132 return wrapper
133
134
121###################135###################
122136
137
123@require_admin_context138@require_admin_context
124def service_destroy(context, service_id):139def service_destroy(context, service_id):
125 session = get_session()140 session = get_session()
@@ -921,6 +936,7 @@
921936
922937
923@require_context938@require_context
939@require_instance_exists
924def virtual_interface_get_by_instance(context, instance_id):940def virtual_interface_get_by_instance(context, instance_id):
925 """Gets all virtual interfaces for instance.941 """Gets all virtual interfaces for instance.
926942
@@ -3071,14 +3087,6 @@
3071####################3087####################
30723088
30733089
3074def require_instance_exists(func):
3075 def new_func(context, instance_id, *args, **kwargs):
3076 db.api.instance_get(context, instance_id)
3077 return func(context, instance_id, *args, **kwargs)
3078 new_func.__name__ = func.__name__
3079 return new_func
3080
3081
3082@require_context3090@require_context
3083@require_instance_exists3091@require_instance_exists
3084def instance_metadata_get(context, instance_id):3092def instance_metadata_get(context, instance_id):
30853093
=== modified file 'nova/tests/api/openstack/test_servers.py'
--- nova/tests/api/openstack/test_servers.py 2011-07-11 21:26:46 +0000
+++ nova/tests/api/openstack/test_servers.py 2011-07-14 18:50:41 +0000
@@ -65,6 +65,18 @@
65 return stub_instance(id, uuid=uuid)65 return stub_instance(id, uuid=uuid)
6666
6767
68def return_virtual_interface_by_instance(interfaces):
69 def _return_virtual_interface_by_instance(context, instance_id):
70 return interfaces
71 return _return_virtual_interface_by_instance
72
73
74def return_virtual_interface_instance_nonexistant(interfaces):
75 def _return_virtual_interface_by_instance(context, instance_id):
76 raise exception.InstanceNotFound(instance_id=instance_id)
77 return _return_virtual_interface_by_instance
78
79
68def return_server_with_addresses(private, public):80def return_server_with_addresses(private, public):
69 def _return_server(context, id):81 def _return_server(context, id):
70 return stub_instance(id, private_address=private,82 return stub_instance(id, private_address=private,
@@ -72,6 +84,12 @@
72 return _return_server84 return _return_server
7385
7486
87def return_server_with_interfaces(interfaces):
88 def _return_server(context, id):
89 return stub_instance(id, interfaces=interfaces)
90 return _return_server
91
92
75def return_server_with_power_state(power_state):93def return_server_with_power_state(power_state):
76 def _return_server(context, id):94 def _return_server(context, id):
77 return stub_instance(id, power_state=power_state)95 return stub_instance(id, power_state=power_state)
@@ -124,10 +142,13 @@
124142
125def stub_instance(id, user_id=1, private_address=None, public_addresses=None,143def stub_instance(id, user_id=1, private_address=None, public_addresses=None,
126 host=None, power_state=0, reservation_id="",144 host=None, power_state=0, reservation_id="",
127 uuid=FAKE_UUID):145 uuid=FAKE_UUID, interfaces=None):
128 metadata = []146 metadata = []
129 metadata.append(InstanceMetadata(key='seq', value=id))147 metadata.append(InstanceMetadata(key='seq', value=id))
130148
149 if interfaces is None:
150 interfaces = []
151
131 inst_type = instance_types.get_instance_type_by_flavor_id(1)152 inst_type = instance_types.get_instance_type_by_flavor_id(1)
132153
133 if public_addresses is None:154 if public_addresses is None:
@@ -171,7 +192,8 @@
171 "display_description": "",192 "display_description": "",
172 "locked": False,193 "locked": False,
173 "metadata": metadata,194 "metadata": metadata,
174 "uuid": uuid}195 "uuid": uuid,
196 "virtual_interfaces": interfaces}
175197
176 instance["fixed_ips"] = {198 instance["fixed_ips"] = {
177 "address": private_address,199 "address": private_address,
@@ -411,23 +433,152 @@
411 self.assertEquals(ip.getAttribute('addr'), private)433 self.assertEquals(ip.getAttribute('addr'), private)
412434
413 def test_get_server_by_id_with_addresses_v1_1(self):435 def test_get_server_by_id_with_addresses_v1_1(self):
414 private = "192.168.0.3"436 interfaces = [
415 public = ["1.2.3.4"]437 {
416 new_return_server = return_server_with_addresses(private, public)438 'network': {'label': 'network_1'},
439 'fixed_ips': [
440 {'address': '192.168.0.3'},
441 {'address': '192.168.0.4'},
442 ],
443 },
444 {
445 'network': {'label': 'network_2'},
446 'fixed_ips': [
447 {'address': '172.19.0.1'},
448 {'address': '172.19.0.2'},
449 ],
450 },
451 ]
452 new_return_server = return_server_with_interfaces(interfaces)
417 self.stubs.Set(nova.db.api, 'instance_get', new_return_server)453 self.stubs.Set(nova.db.api, 'instance_get', new_return_server)
454
418 req = webob.Request.blank('/v1.1/servers/1')455 req = webob.Request.blank('/v1.1/servers/1')
419 res = req.get_response(fakes.wsgi_app())456 res = req.get_response(fakes.wsgi_app())
457
420 res_dict = json.loads(res.body)458 res_dict = json.loads(res.body)
421 self.assertEqual(res_dict['server']['id'], 1)459 self.assertEqual(res_dict['server']['id'], 1)
422 self.assertEqual(res_dict['server']['name'], 'server1')460 self.assertEqual(res_dict['server']['name'], 'server1')
423 addresses = res_dict['server']['addresses']461 addresses = res_dict['server']['addresses']
424 # RM(4047): Figure otu what is up with the 1.1 api and multi-nic462 expected = {
425 #self.assertEqual(len(addresses["public"]), len(public))463 'network_1': [
426 #self.assertEqual(addresses["public"][0],464 {'addr': '192.168.0.3', 'version': 4},
427 # {"version": 4, "addr": public[0]})465 {'addr': '192.168.0.4', 'version': 4},
428 #self.assertEqual(len(addresses["private"]), 1)466 ],
429 #self.assertEqual(addresses["private"][0],467 'network_2': [
430 # {"version": 4, "addr": private})468 {'addr': '172.19.0.1', 'version': 4},
469 {'addr': '172.19.0.2', 'version': 4},
470 ],
471 }
472
473 self.assertEqual(addresses, expected)
474
475 def test_get_server_addresses_v1_1(self):
476 interfaces = [
477 {
478 'network': {'label': 'network_1'},
479 'fixed_ips': [
480 {'address': '192.168.0.3'},
481 {'address': '192.168.0.4'},
482 ],
483 },
484 {
485 'network': {'label': 'network_2'},
486 'fixed_ips': [
487 {
488 'address': '172.19.0.1',
489 'floating_ips': [
490 {'address': '1.2.3.4'},
491 ],
492 },
493 {'address': '172.19.0.2'},
494 ],
495 },
496 ]
497
498 _return_vifs = return_virtual_interface_by_instance(interfaces)
499 self.stubs.Set(nova.db.api,
500 'virtual_interface_get_by_instance',
501 _return_vifs)
502
503 req = webob.Request.blank('/v1.1/servers/1/ips')
504 res = req.get_response(fakes.wsgi_app())
505 res_dict = json.loads(res.body)
506
507 expected = {
508 'addresses': {
509 'network_1': [
510 {'version': 4, 'addr': '192.168.0.3'},
511 {'version': 4, 'addr': '192.168.0.4'},
512 ],
513 'network_2': [
514 {'version': 4, 'addr': '172.19.0.1'},
515 {'version': 4, 'addr': '1.2.3.4'},
516 {'version': 4, 'addr': '172.19.0.2'},
517 ],
518 },
519 }
520
521 self.assertEqual(res_dict, expected)
522
523 def test_get_server_addresses_single_network_v1_1(self):
524 interfaces = [
525 {
526 'network': {'label': 'network_1'},
527 'fixed_ips': [
528 {'address': '192.168.0.3'},
529 {'address': '192.168.0.4'},
530 ],
531 },
532 {
533 'network': {'label': 'network_2'},
534 'fixed_ips': [
535 {
536 'address': '172.19.0.1',
537 'floating_ips': [
538 {'address': '1.2.3.4'},
539 ],
540 },
541 {'address': '172.19.0.2'},
542 ],
543 },
544 ]
545 _return_vifs = return_virtual_interface_by_instance(interfaces)
546 self.stubs.Set(nova.db.api,
547 'virtual_interface_get_by_instance',
548 _return_vifs)
549
550 req = webob.Request.blank('/v1.1/servers/1/ips/network_2')
551 res = req.get_response(fakes.wsgi_app())
552 self.assertEqual(res.status_int, 200)
553 res_dict = json.loads(res.body)
554 expected = {
555 'network_2': [
556 {'version': 4, 'addr': '172.19.0.1'},
557 {'version': 4, 'addr': '1.2.3.4'},
558 {'version': 4, 'addr': '172.19.0.2'},
559 ],
560 }
561 self.assertEqual(res_dict, expected)
562
563 def test_get_server_addresses_nonexistant_network_v1_1(self):
564 _return_vifs = return_virtual_interface_by_instance([])
565 self.stubs.Set(nova.db.api,
566 'virtual_interface_get_by_instance',
567 _return_vifs)
568
569 req = webob.Request.blank('/v1.1/servers/1/ips/network_0')
570 res = req.get_response(fakes.wsgi_app())
571 self.assertEqual(res.status_int, 404)
572
573 def test_get_server_addresses_nonexistant_server_v1_1(self):
574 _return_vifs = return_virtual_interface_instance_nonexistant([])
575 self.stubs.Set(nova.db.api,
576 'virtual_interface_get_by_instance',
577 _return_vifs)
578
579 req = webob.Request.blank('/v1.1/servers/600/ips')
580 res = req.get_response(fakes.wsgi_app())
581 self.assertEqual(res.status_int, 404)
431582
432 def test_get_server_list(self):583 def test_get_server_list(self):
433 req = webob.Request.blank('/v1.0/servers')584 req = webob.Request.blank('/v1.0/servers')
@@ -787,13 +938,13 @@
787938
788 res = req.get_response(fakes.wsgi_app())939 res = req.get_response(fakes.wsgi_app())
789940
941 self.assertEqual(res.status_int, 200)
790 server = json.loads(res.body)['server']942 server = json.loads(res.body)['server']
791 self.assertEqual(16, len(server['adminPass']))943 self.assertEqual(16, len(server['adminPass']))
792 self.assertEqual('server_test', server['name'])944 self.assertEqual('server_test', server['name'])
793 self.assertEqual(1, server['id'])945 self.assertEqual(1, server['id'])
794 self.assertEqual(flavor_ref, server['flavorRef'])946 self.assertEqual(flavor_ref, server['flavorRef'])
795 self.assertEqual(image_href, server['imageRef'])947 self.assertEqual(image_href, server['imageRef'])
796 self.assertEqual(res.status_int, 200)
797948
798 def test_create_instance_v1_1_bad_href(self):949 def test_create_instance_v1_1_bad_href(self):
799 self._setup_for_create_instance()950 self._setup_for_create_instance()