Merge lp:~tpatil/nova/add-options-network-create-os-apis into lp:~hudson-openstack/nova/trunk
- add-options-network-create-os-apis
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Vish Ishaya |
Approved revision: | 1282 |
Merged at revision: | 1476 |
Proposed branch: | lp:~tpatil/nova/add-options-network-create-os-apis |
Merge into: | lp:~hudson-openstack/nova/trunk |
Diff against target: |
1617 lines (+1069/-68) 17 files modified
bin/nova-manage (+19/-16) nova/api/openstack/contrib/createserverext.py (+66/-0) nova/api/openstack/create_instance_helper.py (+72/-1) nova/compute/api.py (+34/-17) nova/compute/manager.py (+4/-1) nova/db/api.py (+9/-3) nova/db/sqlalchemy/api.py (+55/-8) nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py (+43/-0) nova/db/sqlalchemy/models.py (+1/-0) nova/exception.py (+22/-0) nova/network/api.py (+9/-0) nova/network/manager.py (+100/-21) nova/tests/api/openstack/contrib/test_createserverext.py (+306/-0) nova/tests/api/openstack/test_extensions.py (+1/-0) nova/tests/api/openstack/test_servers.py (+183/-0) nova/tests/test_network.py (+129/-1) nova/utils.py (+16/-0) |
To merge this branch: | bzr merge lp:~tpatil/nova/add-options-network-create-os-apis |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Vish Ishaya (community) | Approve | ||
Tushar Patil (community) | Approve | ||
Brian Waldon (community) | Approve | ||
Review via email: mp+68292@code.launchpad.net |
Commit message
Description of the change
Our goal is to add optional parameter to the Create server OS 1.0 and 1.1 API to achieve following objectives:-
1) Specify number and order of networks to the create server API.
In the current implementation every instance you launch for a project having 3 networks assigned to it will always have 3 vnics. In this case it is not possible to have one instance with 2 vnics ,another with 1 vnic and so on. This is not flexible enough and the network resources are not used effectively.
2) Specify fixed IP address to the vnic at the boot time. When you launch a server, you can specify the fixed IP address you want to be assigned to the vnic from a particular network. If this fixed IP address is already in use, it will give exception.
Example of Server Create API request XML:
<?xml version="1.0" encoding="UTF-8"?>
<server xmlns="http://
<metadata>
<meta key="My Server Name">Apache1<
</metadata>
<personality>
<file path="/
</file>
</personality>
<networks>
<network uuid="6622436e-
<network uuid="d97efefc-
</networks>
</server>
3) Networks is an optional parameter, so if you don't provide any networks to the server Create API, it will behave exactly the same as of today.
This feature is supported to all of the network models.
Tushar Patil (tpatil) wrote : | # |
Thank you for your time to review my code.
> 1) The v1.0 api cannot be changed. You will need to limit your changes to the
> v1.1 api.
Ok, understood.
> 2) This is not in the v1.1 spec, so it will have to be documented as an
> extension. I think it is okay for the v1.1 code to remain unchanged, but you
> will need to add an extension descriptor to document the change.
You mean I should add this as an extension under api/openstack/
Brian Waldon (bcwaldon) wrote : | # |
> Thank you for your time to review my code.
>
> > 1) The v1.0 api cannot be changed. You will need to limit your changes to
> the
> > v1.1 api.
> Ok, understood.
> > 2) This is not in the v1.1 spec, so it will have to be documented as an
> > extension. I think it is okay for the v1.1 code to remain unchanged, but you
> > will need to add an extension descriptor to document the change.
> You mean I should add this as an extension under api/openstack/
> directory of nova. Please confirm.
You should add a module in api.openstack.
Tushar Patil (tpatil) wrote : | # |
I have merged trunk changes to my code which includes ha-net changes. Request you all to please review my code.
Tushar Patil (tpatil) wrote : | # |
> You should add a module in api.openstack.
> in it is an implementation of
> nova.api.
> api.openstack.
> need to describe the difference in our (Nova's) implementation of the v1.1
> spec.
Fixed. I have implemented a new extension for the Create server API with an additional network information parameter.
Tushar Patil (tpatil) wrote : | # |
Set the status to Work in progress as I am merging the latest changes in the trunk after network refactoring code is in. Some of the my unit test cases are failing so fixing them right now.
Tushar Patil (tpatil) wrote : | # |
Fixed all broken unit test cases after merging trunk changes. Please review.
Brian Waldon (bcwaldon) wrote : | # |
I think it makes more sense for you to put whatever changes you need to make in the main create_
Tushar Patil (tpatil) wrote : | # |
> I think it makes more sense for you to put whatever changes you need to make
> in the main create_
> nova/api/
> up with. Like I said earlier, your code can live in the main codebase, you
> just need to have an ExtensionDescriptor in contrib/ (which you do).
Now, I have understood your comments correctly. I will revert my changes accordingly. I will also have to write my unit test cases in the test_server.py instead of test/api/
In one of your previous comments, you mention I have to limit these new changes only in the V1.1 without disturbing V1.0. To do this, I will need to add a new method _network_
Brian Waldon (bcwaldon) wrote : | # |
> > I think it makes more sense for you to put whatever changes you need to make
> > in the main create_
> > nova/api/
> keep
> > up with. Like I said earlier, your code can live in the main codebase, you
> > just need to have an ExtensionDescriptor in contrib/ (which you do).
> Now, I have understood your comments correctly. I will revert my changes
> accordingly. I will also have to write my unit test cases in the
> test_server.py instead of test/api/
>
> In one of your previous comments, you mention I have to limit these new
> changes only in the V1.1 without disturbing V1.0. To do this, I will need to
> add a new method _network_
> ControllerV10. ControllerV10 will simply return None whereas ControllerV11
> will return the actual network reference data.
Great. Don't worry about only exposing this in V1.1. It isn't changing the interface, it is just adding to it. We will cover this in the future once we have schema validation.
Tushar Patil (tpatil) wrote : | # |
> Great. Don't worry about only exposing this in V1.1. It isn't changing the
> interface, it is just adding to it. We will cover this in the future once we
> have schema validation.
I need one more clarification to avoid rework. If I add everything in the create_
But till the time schema validation is not implemented, it is possible to create server with the required network information using below URIs. Is this acceptable?
http://
http://
http://
XML request
-------------
<?xml version="1.0" encoding="UTF-8"?>
<server name="new-
<metadata>
<meta key="My Server Name">Apache1<
</metadata>
<personality/>
<networks>
<network id="1" fixed_ip=
</networks>
</server>
Brian Waldon (bcwaldon) wrote : | # |
> > Great. Don't worry about only exposing this in V1.1. It isn't changing the
> > interface, it is just adding to it. We will cover this in the future once we
> > have schema validation.
>
> I need one more clarification to avoid rework. If I add everything in the
> create_
> V1.1 since CreateInstanceH
> After schema validation will be implemented each controller will do the schema
> validation based on its own schema and take an action.
>
> But till the time schema validation is not implemented, it is possible to
> create server with the required network information using below URIs. Is this
> acceptable?
Yep, this is fine.
Tushar Patil (tpatil) wrote : | # |
Couple of new extensions are added into trunk because of which the test_extensions unit testcases are broken.
Tushar Patil (tpatil) wrote : | # |
Fixed all broken unit testcases after merging my branch with the trunk changes. Please review.
dan wendlandt (danwent) wrote : | # |
Hi Tushar,
Thanks for this work, it will indeed be very helpful. In fact, we need something quite similar for our QuantumManager work in nova. I've implemented this in another branch, but I don't see a point in trying to merge two sets of code that do basically the same thing. I think with one small tweak this code could work for us as well.
The change would be to no longer assume that the network-id must be an integer, but instead allow it to be a UUID (which is what quantum uses to identify networks).
This would apply in create_
network_id = int(network_id)
and then catches any resulting exceptions. It seems like that validation could actually be performed as part of the call to validate networks:
self.network_
This way, a NetworkManager way the typing could be specific to the network manager.
Thoughts?
Tushar Patil (tpatil) wrote : | # |
> Hi Tushar,
>
> Thanks for this work, it will indeed be very helpful. In fact, we need
> something quite similar for our QuantumManager work in nova. I've implemented
> this in another branch, but I don't see a point in trying to merge two sets of
> code that do basically the same thing. I think with one small tweak this code
> could work for us as well.
>
> The change would be to no longer assume that the network-id must be an
> integer, but instead allow it to be a UUID (which is what quantum uses to
> identify networks).
>
> This would apply in create_
>
> network_id = int(network_id)
>
> and then catches any resulting exceptions. It seems like that validation
> could actually be performed as part of the call to validate networks:
>
> self.network_
>
> This way, a NetworkManager way the typing could be specific to the network
> manager.
>
> Thoughts?
It make sense to use uuid in order to comply with quantum.
I am thinking of generating this uuid for network in the db/api.py network_create_safe in the same manner as the uuid is generated for instances in the instance_create method. Next expose this uuid in the nova-manage network list command.
dan wendlandt (danwent) wrote : | # |
On Mon, Aug 15, 2011 at 3:11 PM, Tushar Patil <email address hidden> wrote:
>
> >
> > This way, a NetworkManager way the typing could be specific to the
> network
> > manager.
> >
> > Thoughts?
> It make sense to use uuid in order to comply with quantum.
>
Great. The QuantumManager doesn't actually require Nova to store a UUID in
the networks table, as the QuantumManager simply uses the UUID in a call to
Quantum. So all I really need (I believe) is to remove the check that
forces the value to be an integer.
>
> I am thinking of generating this uuid for network in the db/api.py
> network_create_safe in the same manner as the uuid is generated for
> instances in the instance_create method. Next expose this uuid in the
> nova-manage network list command.
> --
>
> https:/
> You are subscribed to branch
> lp:~tpatil/nova/add-options-network-create-os-apis.
>
--
~~~~~~~
Dan Wendlandt
Nicira Networks, Inc.
www.nicira.com | www.openvswitch.org
Sr. Product Manager
cell: 650-906-2650
~~~~~~~
Tushar Patil (tpatil) wrote : | # |
> Great. The QuantumManager doesn't actually require Nova to store a UUID in
> the networks table, as the QuantumManager simply uses the UUID in a call to
> Quantum. So all I really need (I believe) is to remove the check that
> forces the value to be an integer.
>
I really don't want to change the API format later if I decide to use quantum as network service.
Hence I wanted to make these changes in the diablo timeframe.
dan wendlandt (danwent) wrote : | # |
On Tue, Aug 16, 2011 at 10:56 AM, Tushar Patil <email address hidden> wrote:
> > Great. The QuantumManager doesn't actually require Nova to store a UUID
> in
> > the networks table, as the QuantumManager simply uses the UUID in a call
> to
> > Quantum. So all I really need (I believe) is to remove the check that
> > forces the value to be an integer.
> >
> I really don't want to change the API format later if I decide to use
> quantum as network service.
> Hence I wanted to make these changes in the diablo timeframe.
>
great!
> --
>
> https:/
> You are subscribed to branch
> lp:~tpatil/nova/add-options-network-create-os-apis.
>
--
~~~~~~~
Dan Wendlandt
Nicira Networks, Inc.
www.nicira.com | www.openvswitch.org
Sr. Product Manager
cell: 650-906-2650
~~~~~~~
Tushar Patil (tpatil) wrote : | # |
Quantum uses uuid for referencing networks instead of integer id. I have modified API format (networks section) to accept network as uuid instead of id so that this extension could be used with both new quantum and the existing network service.
Vish Ishaya (vishvananda) wrote : | # |
> 2) Specify fixed IP address to the vnic at the boot time. When you launch a server, you can specify the fixed IP address you want to be assigned to the vnic from a particular network. If this fixed IP address is already in use, it will give exception.
For this to be useful, it seems like we need to recycle ip addresses more quickly. I'm wondering about switching to expiring dnsmasq whenever an ip is released. This requires building the dhcp_release script that is in dnsmasq contrib and using it to manually release ips when they are disassociated.
Tushar Patil (tpatil) wrote : | # |
>
> > 2) Specify fixed IP address to the vnic at the boot time. When you launch a
> server, you can specify the fixed IP address you want to be assigned to the
> vnic from a particular network. If this fixed IP address is already in use, it
> will give exception.
>
> For this to be useful, it seems like we need to recycle ip addresses more
> quickly. I'm wondering about switching to expiring dnsmasq whenever an ip is
> released. This requires building the dhcp_release script that is in dnsmasq
> contrib and using it to manually release ips when they are disassociated.
It's a good idea. I think this can be worked upon later after this code is merged into trunk.
Vish Ishaya (vishvananda) wrote : | # |
On Aug 17, 2011, at 11:31 AM, Tushar Patil wrote:
> It's a good idea. I think this can be worked upon later after this code is merged into trunk.
Agreed. I have monty putting the needed scripts for dnsmasq in our ppa. I made a bug to track the issue here:
Brian Waldon (bcwaldon) wrote : | # |
Sorry this has sat so long, Tushar! I like the code quite a bit, bug being such a big merge prop, I did find a handful of things:
267: I'm curious why you chose to break this out into a separate function. Can you please explain why?
454: Can this function simply be 'fixed_
477: This name could be made a bit clearer by avoiding repetition of 'network'. 'network_
492: I think this function can be accomplished with a keyword argument (maybe 'project'?) to the network_
509: I'm a bit confused by this change. I could use some help understanding what is going on.
520: I think this behavior could lead to some unexpected consequences. What is the motive behind supporting this? If I understand what is happening correctly, I would explicitly call fixed_ip_
628: This migration number needs to increase.
695: I don't see this exception class as adding much value at this point. How would you feel about just removing it?
747: This could slow down the code quite a bit. Is it necessary to do this over rpc, or can the network api just take care of the validation?
871/956: These functions are extremely similar. I think you could write it in such a way that you didn't need to copy it into the VlanManager.
Feel free to contact me in IRC to discuss any of these changes :)
Tushar Patil (tpatil) wrote : | # |
> Sorry this has sat so long, Tushar! I like the code quite a bit, bug being
> such a big merge prop, I did find a handful of things:
>
> 267: I'm curious why you chose to break this out into a separate function. Can
> you please explain why?
Reverted back to its original state. Fixed
> 454: Can this function simply be 'fixed_
> 'by_address' part is implied. Please correct me if I'm wrong here.
Fixed as per our discussion IRC, I have combined the functionality of existing fixed_ip_associate and newly added method fixed_ip_
> 477: This name could be made a bit clearer by avoiding repetition of
> 'network'. 'network_
Fixed
> 492: I think this function can be accomplished with a keyword argument (maybe
> 'project'?) to the network_
Fixed
> 509: I'm a bit confused by this change. I could use some help understanding
> what is going on.
I really don't know how this change happen from my side. I have reverted it back to its original state.Fixed
> 520: I think this behavior could lead to some unexpected consequences. What is
> the motive behind supporting this? If I understand what is happening
> correctly, I would explicitly call fixed_ip_
> code I wrote.
Fixed
> 628: This migration number needs to increase.
Fixed
> 695: I don't see this exception class as adding much value at this point. How
> would you feel about just removing it?
Removed this exception class.Fixed
> 747: This could slow down the code quite a bit. Is it necessary to do this
> over rpc, or can the network api just take care of the validation?
Validation is different for different network models and I don't think it make sense to do this validation based on the network_manager flag in the network/api.py, so rpc call is necessary.
Do you have any suggestion here?
> 871/956: These functions are extremely similar. I think you could write it in
> such a way that you didn't need to copy it into the VlanManager.
Fixed
Please review
Brian Waldon (bcwaldon) wrote : | # |
Looks great, Tushar!
Tushar Patil (tpatil) : | # |
OpenStack Infra (hudson-openstack) wrote : | # |
Attempt to merge into lp:nova failed due to conflicts:
text conflict in nova/api/
text conflict in nova/compute/api.py
text conflict in nova/tests/
Tushar Patil (tpatil) wrote : | # |
Resolved all conflicts and fixed broken unit tests in the test_createserv
Vish Ishaya (vishvananda) wrote : | # |
thx, trying again.
Preview Diff
1 | === modified file 'bin/nova-manage' |
2 | --- bin/nova-manage 2011-08-15 20:33:37 +0000 |
3 | +++ bin/nova-manage 2011-08-22 23:37:26 +0000 |
4 | @@ -763,23 +763,26 @@ |
5 | |
6 | def list(self): |
7 | """List all created networks""" |
8 | - print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % ( |
9 | - _('IPv4'), |
10 | - _('IPv6'), |
11 | - _('start address'), |
12 | - _('DNS1'), |
13 | - _('DNS2'), |
14 | - _('VlanID'), |
15 | - 'project') |
16 | + _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" |
17 | + print _fmt % (_('id'), |
18 | + _('IPv4'), |
19 | + _('IPv6'), |
20 | + _('start address'), |
21 | + _('DNS1'), |
22 | + _('DNS2'), |
23 | + _('VlanID'), |
24 | + _('project'), |
25 | + _("uuid")) |
26 | for network in db.network_get_all(context.get_admin_context()): |
27 | - print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % ( |
28 | - network.cidr, |
29 | - network.cidr_v6, |
30 | - network.dhcp_start, |
31 | - network.dns1, |
32 | - network.dns2, |
33 | - network.vlan, |
34 | - network.project_id) |
35 | + print _fmt % (network.id, |
36 | + network.cidr, |
37 | + network.cidr_v6, |
38 | + network.dhcp_start, |
39 | + network.dns1, |
40 | + network.dns2, |
41 | + network.vlan, |
42 | + network.project_id, |
43 | + network.uuid) |
44 | |
45 | @args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>', |
46 | help='Network to delete') |
47 | |
48 | === added file 'nova/api/openstack/contrib/createserverext.py' |
49 | --- nova/api/openstack/contrib/createserverext.py 1970-01-01 00:00:00 +0000 |
50 | +++ nova/api/openstack/contrib/createserverext.py 2011-08-22 23:37:26 +0000 |
51 | @@ -0,0 +1,66 @@ |
52 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
53 | + |
54 | +# Copyright 2011 OpenStack LLC. |
55 | +# |
56 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
57 | +# not use this file except in compliance with the License. You may obtain |
58 | +# a copy of the License at |
59 | +# |
60 | +# http://www.apache.org/licenses/LICENSE-2.0 |
61 | +# |
62 | +# Unless required by applicable law or agreed to in writing, software |
63 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
64 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
65 | +# License for the specific language governing permissions and limitations |
66 | +# under the License |
67 | + |
68 | +from nova.api.openstack import create_instance_helper as helper |
69 | +from nova.api.openstack import extensions |
70 | +from nova.api.openstack import servers |
71 | +from nova.api.openstack import wsgi |
72 | + |
73 | + |
74 | +class Createserverext(extensions.ExtensionDescriptor): |
75 | + """The servers create ext |
76 | + |
77 | + Exposes addFixedIp and removeFixedIp actions on servers. |
78 | + |
79 | + """ |
80 | + def get_name(self): |
81 | + return "Createserverext" |
82 | + |
83 | + def get_alias(self): |
84 | + return "os-create-server-ext" |
85 | + |
86 | + def get_description(self): |
87 | + return "Extended support to the Create Server v1.1 API" |
88 | + |
89 | + def get_namespace(self): |
90 | + return "http://docs.openstack.org/ext/createserverext/api/v1.1" |
91 | + |
92 | + def get_updated(self): |
93 | + return "2011-07-19T00:00:00+00:00" |
94 | + |
95 | + def get_resources(self): |
96 | + resources = [] |
97 | + |
98 | + headers_serializer = servers.HeadersSerializer() |
99 | + body_serializers = { |
100 | + 'application/xml': servers.ServerXMLSerializer(), |
101 | + } |
102 | + |
103 | + body_deserializers = { |
104 | + 'application/xml': helper.ServerXMLDeserializerV11(), |
105 | + } |
106 | + |
107 | + serializer = wsgi.ResponseSerializer(body_serializers, |
108 | + headers_serializer) |
109 | + deserializer = wsgi.RequestDeserializer(body_deserializers) |
110 | + |
111 | + res = extensions.ResourceExtension('os-create-server-ext', |
112 | + controller=servers.ControllerV11(), |
113 | + deserializer=deserializer, |
114 | + serializer=serializer) |
115 | + resources.append(res) |
116 | + |
117 | + return resources |
118 | |
119 | === modified file 'nova/api/openstack/create_instance_helper.py' |
120 | --- nova/api/openstack/create_instance_helper.py 2011-08-22 03:17:04 +0000 |
121 | +++ nova/api/openstack/create_instance_helper.py 2011-08-22 23:37:26 +0000 |
122 | @@ -29,7 +29,7 @@ |
123 | from nova.compute import instance_types |
124 | from nova.api.openstack import common |
125 | from nova.api.openstack import wsgi |
126 | - |
127 | +from nova.rpc.common import RemoteError |
128 | |
129 | LOG = logging.getLogger('nova.api.openstack.create_instance_helper') |
130 | FLAGS = flags.FLAGS |
131 | @@ -120,6 +120,11 @@ |
132 | |
133 | sg_names = list(set(sg_names)) |
134 | |
135 | + requested_networks = server_dict.get('networks') |
136 | + if requested_networks is not None: |
137 | + requested_networks = self._get_requested_networks( |
138 | + requested_networks) |
139 | + |
140 | try: |
141 | flavor_id = self.controller._flavor_id_from_req_data(body) |
142 | except ValueError as error: |
143 | @@ -175,6 +180,7 @@ |
144 | reservation_id=reservation_id, |
145 | min_count=min_count, |
146 | max_count=max_count, |
147 | + requested_networks=requested_networks, |
148 | security_group=sg_names, |
149 | user_data=user_data, |
150 | availability_zone=availability_zone)) |
151 | @@ -188,6 +194,10 @@ |
152 | raise exc.HTTPBadRequest(explanation=msg) |
153 | except exception.SecurityGroupNotFound as error: |
154 | raise exc.HTTPBadRequest(explanation=unicode(error)) |
155 | + except RemoteError as err: |
156 | + msg = "%(err_type)s: %(err_msg)s" % \ |
157 | + {'err_type': err.exc_type, 'err_msg': err.value} |
158 | + raise exc.HTTPBadRequest(explanation=msg) |
159 | # Let the caller deal with unhandled exceptions. |
160 | |
161 | def _handle_quota_error(self, error): |
162 | @@ -316,6 +326,46 @@ |
163 | raise exc.HTTPBadRequest(explanation=msg) |
164 | return password |
165 | |
166 | + def _get_requested_networks(self, requested_networks): |
167 | + """ |
168 | + Create a list of requested networks from the networks attribute |
169 | + """ |
170 | + networks = [] |
171 | + for network in requested_networks: |
172 | + try: |
173 | + network_uuid = network['uuid'] |
174 | + |
175 | + if not utils.is_uuid_like(network_uuid): |
176 | + msg = _("Bad networks format: network uuid is not in" |
177 | + " proper format (%s)") % network_uuid |
178 | + raise exc.HTTPBadRequest(explanation=msg) |
179 | + |
180 | + #fixed IP address is optional |
181 | + #if the fixed IP address is not provided then |
182 | + #it will use one of the available IP address from the network |
183 | + address = network.get('fixed_ip', None) |
184 | + if address is not None and not utils.is_valid_ipv4(address): |
185 | + msg = _("Invalid fixed IP address (%s)") % address |
186 | + raise exc.HTTPBadRequest(explanation=msg) |
187 | + # check if the network id is already present in the list, |
188 | + # we don't want duplicate networks to be passed |
189 | + # at the boot time |
190 | + for id, ip in networks: |
191 | + if id == network_uuid: |
192 | + expl = _("Duplicate networks (%s) are not allowed")\ |
193 | + % network_uuid |
194 | + raise exc.HTTPBadRequest(explanation=expl) |
195 | + |
196 | + networks.append((network_uuid, address)) |
197 | + except KeyError as key: |
198 | + expl = _('Bad network format: missing %s') % key |
199 | + raise exc.HTTPBadRequest(explanation=expl) |
200 | + except TypeError: |
201 | + expl = _('Bad networks format') |
202 | + raise exc.HTTPBadRequest(explanation=expl) |
203 | + |
204 | + return networks |
205 | + |
206 | |
207 | class ServerXMLDeserializer(wsgi.XMLDeserializer): |
208 | """ |
209 | @@ -480,6 +530,10 @@ |
210 | if personality is not None: |
211 | server["personality"] = personality |
212 | |
213 | + networks = self._extract_networks(server_node) |
214 | + if networks is not None: |
215 | + server["networks"] = networks |
216 | + |
217 | security_groups = self._extract_security_groups(server_node) |
218 | if security_groups is not None: |
219 | server["security_groups"] = security_groups |
220 | @@ -501,6 +555,23 @@ |
221 | else: |
222 | return None |
223 | |
224 | + def _extract_networks(self, server_node): |
225 | + """Marshal the networks attribute of a parsed request""" |
226 | + node = self.find_first_child_named(server_node, "networks") |
227 | + if node is not None: |
228 | + networks = [] |
229 | + for network_node in self.find_children_named(node, |
230 | + "network"): |
231 | + item = {} |
232 | + if network_node.hasAttribute("uuid"): |
233 | + item["uuid"] = network_node.getAttribute("uuid") |
234 | + if network_node.hasAttribute("fixed_ip"): |
235 | + item["fixed_ip"] = network_node.getAttribute("fixed_ip") |
236 | + networks.append(item) |
237 | + return networks |
238 | + else: |
239 | + return None |
240 | + |
241 | def _extract_security_groups(self, server_node): |
242 | """Marshal the security_groups attribute of a parsed request""" |
243 | node = self.find_first_child_named(server_node, "security_groups") |
244 | |
245 | === modified file 'nova/compute/api.py' |
246 | --- nova/compute/api.py 2011-08-22 03:17:04 +0000 |
247 | +++ nova/compute/api.py 2011-08-22 23:37:26 +0000 |
248 | @@ -146,6 +146,16 @@ |
249 | LOG.warn(msg) |
250 | raise quota.QuotaError(msg, "MetadataLimitExceeded") |
251 | |
252 | + def _check_requested_networks(self, context, requested_networks): |
253 | + """ Check if the networks requested belongs to the project |
254 | + and the fixed IP address for each network provided is within |
255 | + same the network block |
256 | + """ |
257 | + if requested_networks is None: |
258 | + return |
259 | + |
260 | + self.network_api.validate_networks(context, requested_networks) |
261 | + |
262 | def _check_create_parameters(self, context, instance_type, |
263 | image_href, kernel_id=None, ramdisk_id=None, |
264 | min_count=None, max_count=None, |
265 | @@ -153,7 +163,8 @@ |
266 | key_name=None, key_data=None, security_group='default', |
267 | availability_zone=None, user_data=None, metadata=None, |
268 | injected_files=None, admin_password=None, zone_blob=None, |
269 | - reservation_id=None, access_ip_v4=None, access_ip_v6=None): |
270 | + reservation_id=None, access_ip_v4=None, access_ip_v6=None, |
271 | + requested_networks=None): |
272 | """Verify all the input parameters regardless of the provisioning |
273 | strategy being performed.""" |
274 | |
275 | @@ -182,6 +193,7 @@ |
276 | |
277 | self._check_metadata_properties_quota(context, metadata) |
278 | self._check_injected_file_quota(context, injected_files) |
279 | + self._check_requested_networks(context, requested_networks) |
280 | |
281 | (image_service, image_id) = nova.image.get_image_service(image_href) |
282 | image = image_service.show(context, image_id) |
283 | @@ -400,9 +412,9 @@ |
284 | def _ask_scheduler_to_create_instance(self, context, base_options, |
285 | instance_type, zone_blob, |
286 | availability_zone, injected_files, |
287 | - admin_password, |
288 | - image, |
289 | - instance_id=None, num_instances=1): |
290 | + admin_password, image, |
291 | + instance_id=None, num_instances=1, |
292 | + requested_networks=None): |
293 | """Send the run_instance request to the schedulers for processing.""" |
294 | pid = context.project_id |
295 | uid = context.user_id |
296 | @@ -430,7 +442,8 @@ |
297 | "request_spec": request_spec, |
298 | "availability_zone": availability_zone, |
299 | "admin_password": admin_password, |
300 | - "injected_files": injected_files}}) |
301 | + "injected_files": injected_files, |
302 | + "requested_networks": requested_networks}}) |
303 | |
304 | def create_all_at_once(self, context, instance_type, |
305 | image_href, kernel_id=None, ramdisk_id=None, |
306 | @@ -440,7 +453,8 @@ |
307 | availability_zone=None, user_data=None, metadata=None, |
308 | injected_files=None, admin_password=None, zone_blob=None, |
309 | reservation_id=None, block_device_mapping=None, |
310 | - access_ip_v4=None, access_ip_v6=None): |
311 | + access_ip_v4=None, access_ip_v6=None, |
312 | + requested_networks=None): |
313 | """Provision the instances by passing the whole request to |
314 | the Scheduler for execution. Returns a Reservation ID |
315 | related to the creation of all of these instances.""" |
316 | @@ -456,14 +470,15 @@ |
317 | key_name, key_data, security_group, |
318 | availability_zone, user_data, metadata, |
319 | injected_files, admin_password, zone_blob, |
320 | - reservation_id, access_ip_v4, access_ip_v6) |
321 | + reservation_id, access_ip_v4, access_ip_v6, |
322 | + requested_networks) |
323 | |
324 | self._ask_scheduler_to_create_instance(context, base_options, |
325 | instance_type, zone_blob, |
326 | availability_zone, injected_files, |
327 | - admin_password, |
328 | - image, |
329 | - num_instances=num_instances) |
330 | + admin_password, image, |
331 | + num_instances=num_instances, |
332 | + requested_networks=requested_networks) |
333 | |
334 | return base_options['reservation_id'] |
335 | |
336 | @@ -475,7 +490,8 @@ |
337 | availability_zone=None, user_data=None, metadata=None, |
338 | injected_files=None, admin_password=None, zone_blob=None, |
339 | reservation_id=None, block_device_mapping=None, |
340 | - access_ip_v4=None, access_ip_v6=None): |
341 | + access_ip_v4=None, access_ip_v6=None, |
342 | + requested_networks=None): |
343 | """ |
344 | Provision the instances by sending off a series of single |
345 | instance requests to the Schedulers. This is fine for trival |
346 | @@ -499,7 +515,8 @@ |
347 | key_name, key_data, security_group, |
348 | availability_zone, user_data, metadata, |
349 | injected_files, admin_password, zone_blob, |
350 | - reservation_id, access_ip_v4, access_ip_v6) |
351 | + reservation_id, access_ip_v4, access_ip_v6, |
352 | + requested_networks) |
353 | |
354 | block_device_mapping = block_device_mapping or [] |
355 | instances = [] |
356 | @@ -513,11 +530,11 @@ |
357 | instance_id = instance['id'] |
358 | |
359 | self._ask_scheduler_to_create_instance(context, base_options, |
360 | - instance_type, zone_blob, |
361 | - availability_zone, injected_files, |
362 | - admin_password, |
363 | - image, |
364 | - instance_id=instance_id) |
365 | + instance_type, zone_blob, |
366 | + availability_zone, injected_files, |
367 | + admin_password, image, |
368 | + instance_id=instance_id, |
369 | + requested_networks=requested_networks) |
370 | |
371 | return [dict(x.iteritems()) for x in instances] |
372 | |
373 | |
374 | === modified file 'nova/compute/manager.py' |
375 | --- nova/compute/manager.py 2011-08-18 20:44:30 +0000 |
376 | +++ nova/compute/manager.py 2011-08-22 23:37:26 +0000 |
377 | @@ -382,6 +382,8 @@ |
378 | context = context.elevated() |
379 | instance = self.db.instance_get(context, instance_id) |
380 | |
381 | + requested_networks = kwargs.get('requested_networks', None) |
382 | + |
383 | if instance['name'] in self.driver.list_instances(): |
384 | raise exception.Error(_("Instance has already been created")) |
385 | |
386 | @@ -411,7 +413,8 @@ |
387 | # will eventually also need to save the address here. |
388 | if not FLAGS.stub_network: |
389 | network_info = self.network_api.allocate_for_instance(context, |
390 | - instance, vpn=is_vpn) |
391 | + instance, vpn=is_vpn, |
392 | + requested_networks=requested_networks) |
393 | LOG.debug(_("instance network_info: |%s|"), network_info) |
394 | else: |
395 | # TODO(tr3buchet) not really sure how this should be handled. |
396 | |
397 | === modified file 'nova/db/api.py' |
398 | --- nova/db/api.py 2011-08-18 00:17:47 +0000 |
399 | +++ nova/db/api.py 2011-08-22 23:37:26 +0000 |
400 | @@ -323,13 +323,13 @@ |
401 | #################### |
402 | |
403 | |
404 | -def fixed_ip_associate(context, address, instance_id): |
405 | +def fixed_ip_associate(context, address, instance_id, network_id=None): |
406 | """Associate fixed ip to instance. |
407 | |
408 | Raises if fixed ip is not available. |
409 | |
410 | """ |
411 | - return IMPL.fixed_ip_associate(context, address, instance_id) |
412 | + return IMPL.fixed_ip_associate(context, address, instance_id, network_id) |
413 | |
414 | |
415 | def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None): |
416 | @@ -396,7 +396,6 @@ |
417 | """Create a fixed ip from the values dictionary.""" |
418 | return IMPL.fixed_ip_update(context, address, values) |
419 | |
420 | - |
421 | #################### |
422 | |
423 | |
424 | @@ -686,7 +685,14 @@ |
425 | return IMPL.network_get_all(context) |
426 | |
427 | |
428 | +def network_get_all_by_uuids(context, network_uuids, project_id=None): |
429 | + """Return networks by ids.""" |
430 | + return IMPL.network_get_all_by_uuids(context, network_uuids, project_id) |
431 | + |
432 | + |
433 | # pylint: disable=C0103 |
434 | + |
435 | + |
436 | def network_get_associated_fixed_ips(context, network_id): |
437 | """Get all network's ips that have been associated.""" |
438 | return IMPL.network_get_associated_fixed_ips(context, network_id) |
439 | |
440 | === modified file 'nova/db/sqlalchemy/api.py' |
441 | --- nova/db/sqlalchemy/api.py 2011-08-20 20:15:05 +0000 |
442 | +++ nova/db/sqlalchemy/api.py 2011-08-22 23:37:26 +0000 |
443 | @@ -652,23 +652,36 @@ |
444 | ################### |
445 | |
446 | |
447 | -@require_context |
448 | -def fixed_ip_associate(context, address, instance_id): |
449 | +@require_admin_context |
450 | +def fixed_ip_associate(context, address, instance_id, network_id=None): |
451 | session = get_session() |
452 | with session.begin(): |
453 | - instance = instance_get(context, instance_id, session=session) |
454 | + network_or_none = or_(models.FixedIp.network_id == network_id, |
455 | + models.FixedIp.network_id == None) |
456 | fixed_ip_ref = session.query(models.FixedIp).\ |
457 | + filter(network_or_none).\ |
458 | + filter_by(reserved=False).\ |
459 | + filter_by(deleted=False).\ |
460 | filter_by(address=address).\ |
461 | - filter_by(deleted=False).\ |
462 | - filter_by(instance=None).\ |
463 | with_lockmode('update').\ |
464 | first() |
465 | # NOTE(vish): if with_lockmode isn't supported, as in sqlite, |
466 | # then this has concurrency issues |
467 | - if not fixed_ip_ref: |
468 | - raise exception.NoMoreFixedIps() |
469 | - fixed_ip_ref.instance = instance |
470 | + if fixed_ip_ref is None: |
471 | + raise exception.FixedIpNotFoundForNetwork(address=address, |
472 | + network_id=network_id) |
473 | + if fixed_ip_ref.instance is not None: |
474 | + raise exception.FixedIpAlreadyInUse(address=address) |
475 | + |
476 | + if not fixed_ip_ref.network: |
477 | + fixed_ip_ref.network = network_get(context, |
478 | + network_id, |
479 | + session=session) |
480 | + fixed_ip_ref.instance = instance_get(context, |
481 | + instance_id, |
482 | + session=session) |
483 | session.add(fixed_ip_ref) |
484 | + return fixed_ip_ref['address'] |
485 | |
486 | |
487 | @require_admin_context |
488 | @@ -1755,6 +1768,40 @@ |
489 | return result |
490 | |
491 | |
492 | +@require_admin_context |
493 | +def network_get_all_by_uuids(context, network_uuids, project_id=None): |
494 | + session = get_session() |
495 | + project_or_none = or_(models.Network.project_id == project_id, |
496 | + models.Network.project_id == None) |
497 | + result = session.query(models.Network).\ |
498 | + filter(models.Network.uuid.in_(network_uuids)).\ |
499 | + filter(project_or_none).\ |
500 | + filter_by(deleted=False).all() |
501 | + if not result: |
502 | + raise exception.NoNetworksFound() |
503 | + |
504 | + #check if host is set to all of the networks |
505 | + # returned in the result |
506 | + for network in result: |
507 | + if network['host'] is None: |
508 | + raise exception.NetworkHostNotSet(network_id=network['id']) |
509 | + |
510 | + #check if the result contains all the networks |
511 | + #we are looking for |
512 | + for network_uuid in network_uuids: |
513 | + found = False |
514 | + for network in result: |
515 | + if network['uuid'] == network_uuid: |
516 | + found = True |
517 | + break |
518 | + if not found: |
519 | + if project_id: |
520 | + raise exception.NetworkNotFoundForProject(network_uuid=uuid, |
521 | + project_id=context.project_id) |
522 | + raise exception.NetworkNotFound(network_id=network_uuid) |
523 | + |
524 | + return result |
525 | + |
526 | # NOTE(vish): pylint complains because of the long method name, but |
527 | # it fits with the names of the rest of the methods |
528 | # pylint: disable=C0103 |
529 | |
530 | === added file 'nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py' |
531 | --- nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py 1970-01-01 00:00:00 +0000 |
532 | +++ nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py 2011-08-22 23:37:26 +0000 |
533 | @@ -0,0 +1,43 @@ |
534 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
535 | + |
536 | +# Copyright 2011 OpenStack LLC. |
537 | +# |
538 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
539 | +# not use this file except in compliance with the License. You may obtain |
540 | +# a copy of the License at |
541 | +# |
542 | +# http://www.apache.org/licenses/LICENSE-2.0 |
543 | +# |
544 | +# Unless required by applicable law or agreed to in writing, software |
545 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
546 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
547 | +# License for the specific language governing permissions and limitations |
548 | +# under the License. |
549 | + |
550 | +from sqlalchemy import Column, Integer, MetaData, String, Table |
551 | + |
552 | +from nova import utils |
553 | + |
554 | + |
555 | +meta = MetaData() |
556 | + |
557 | +networks = Table("networks", meta, |
558 | + Column("id", Integer(), primary_key=True, nullable=False)) |
559 | +uuid_column = Column("uuid", String(36)) |
560 | + |
561 | + |
562 | +def upgrade(migrate_engine): |
563 | + meta.bind = migrate_engine |
564 | + networks.create_column(uuid_column) |
565 | + |
566 | + rows = migrate_engine.execute(networks.select()) |
567 | + for row in rows: |
568 | + networks_uuid = str(utils.gen_uuid()) |
569 | + migrate_engine.execute(networks.update()\ |
570 | + .where(networks.c.id == row[0])\ |
571 | + .values(uuid=networks_uuid)) |
572 | + |
573 | + |
574 | +def downgrade(migrate_engine): |
575 | + meta.bind = migrate_engine |
576 | + networks.drop_column(uuid_column) |
577 | |
578 | === modified file 'nova/db/sqlalchemy/models.py' |
579 | --- nova/db/sqlalchemy/models.py 2011-08-19 15:26:32 +0000 |
580 | +++ nova/db/sqlalchemy/models.py 2011-08-22 23:37:26 +0000 |
581 | @@ -561,6 +561,7 @@ |
582 | |
583 | project_id = Column(String(255)) |
584 | host = Column(String(255)) # , ForeignKey('hosts.id')) |
585 | + uuid = Column(String(36)) |
586 | |
587 | |
588 | class VirtualInterface(BASE, NovaBase): |
589 | |
590 | === modified file 'nova/exception.py' |
591 | --- nova/exception.py 2011-08-20 22:38:13 +0000 |
592 | +++ nova/exception.py 2011-08-22 23:37:26 +0000 |
593 | @@ -423,6 +423,15 @@ |
594 | message = _("No networks defined.") |
595 | |
596 | |
597 | +class NetworkNotFoundForProject(NotFound): |
598 | + message = _("Either Network uuid %(network_uuid)s is not present or " |
599 | + "is not assigned to the project %(project_id)s.") |
600 | + |
601 | + |
602 | +class NetworkHostNotSet(NovaException): |
603 | + message = _("Host is not set to the network (%(network_id)s).") |
604 | + |
605 | + |
606 | class DatastoreNotFound(NotFound): |
607 | message = _("Could not find the datastore reference(s) which the VM uses.") |
608 | |
609 | @@ -456,6 +465,19 @@ |
610 | message = _("Host %(host)s has zero fixed ips.") |
611 | |
612 | |
613 | +class FixedIpNotFoundForNetwork(FixedIpNotFound): |
614 | + message = _("Fixed IP address (%(address)s) does not exist in " |
615 | + "network (%(network_uuid)s).") |
616 | + |
617 | + |
618 | +class FixedIpAlreadyInUse(NovaException): |
619 | + message = _("Fixed IP address %(address)s is already in use.") |
620 | + |
621 | + |
622 | +class FixedIpInvalid(Invalid): |
623 | + message = _("Fixed IP address %(address)s is invalid.") |
624 | + |
625 | + |
626 | class NoMoreFixedIps(Error): |
627 | message = _("Zero fixed ips available.") |
628 | |
629 | |
630 | === modified file 'nova/network/api.py' |
631 | --- nova/network/api.py 2011-07-27 21:39:27 +0000 |
632 | +++ nova/network/api.py 2011-08-22 23:37:26 +0000 |
633 | @@ -195,3 +195,12 @@ |
634 | return rpc.call(context, FLAGS.network_topic, |
635 | {'method': 'get_instance_nw_info', |
636 | 'args': args}) |
637 | + |
638 | + def validate_networks(self, context, requested_networks): |
639 | + """validate the networks passed at the time of creating |
640 | + the server |
641 | + """ |
642 | + args = {'networks': requested_networks} |
643 | + return rpc.call(context, FLAGS.network_topic, |
644 | + {'method': 'validate_networks', |
645 | + 'args': args}) |
646 | |
647 | === modified file 'nova/network/manager.py' |
648 | --- nova/network/manager.py 2011-08-18 06:50:50 +0000 |
649 | +++ nova/network/manager.py 2011-08-22 23:37:26 +0000 |
650 | @@ -131,7 +131,15 @@ |
651 | green_pool = greenpool.GreenPool() |
652 | |
653 | vpn = kwargs.pop('vpn') |
654 | + requested_networks = kwargs.pop('requested_networks') |
655 | + |
656 | for network in networks: |
657 | + address = None |
658 | + if requested_networks is not None: |
659 | + for address in (fixed_ip for (uuid, fixed_ip) in \ |
660 | + requested_networks if network['uuid'] == uuid): |
661 | + break |
662 | + |
663 | # NOTE(vish): if we are not multi_host pass to the network host |
664 | if not network['multi_host']: |
665 | host = network['host'] |
666 | @@ -148,6 +156,7 @@ |
667 | args = {} |
668 | args['instance_id'] = instance_id |
669 | args['network_id'] = network['id'] |
670 | + args['address'] = address |
671 | args['vpn'] = vpn |
672 | |
673 | green_pool.spawn_n(rpc.call, context, topic, |
674 | @@ -155,7 +164,8 @@ |
675 | 'args': args}) |
676 | else: |
677 | # i am the correct host, run here |
678 | - self.allocate_fixed_ip(context, instance_id, network, vpn=vpn) |
679 | + self.allocate_fixed_ip(context, instance_id, network, |
680 | + vpn=vpn, address=address) |
681 | |
682 | # wait for all of the allocates (if any) to finish |
683 | green_pool.waitall() |
684 | @@ -199,6 +209,7 @@ |
685 | """ |
686 | instance_id = kwargs.get('instance_id') |
687 | project_id = kwargs.get('project_id') |
688 | + requested_networks = kwargs.get('requested_networks') |
689 | LOG.debug(_("floating IP allocation for instance |%s|"), instance_id, |
690 | context=context) |
691 | # call the next inherited class's allocate_for_instance() |
692 | @@ -380,16 +391,21 @@ |
693 | self.compute_api.trigger_security_group_members_refresh(admin_context, |
694 | group_ids) |
695 | |
696 | - def _get_networks_for_instance(self, context, instance_id, project_id): |
697 | + def _get_networks_for_instance(self, context, instance_id, project_id, |
698 | + requested_networks=None): |
699 | """Determine & return which networks an instance should connect to.""" |
700 | # TODO(tr3buchet) maybe this needs to be updated in the future if |
701 | # there is a better way to determine which networks |
702 | # a non-vlan instance should connect to |
703 | - try: |
704 | - networks = self.db.network_get_all(context) |
705 | - except exception.NoNetworksFound: |
706 | - return [] |
707 | - |
708 | + if requested_networks is not None and len(requested_networks) != 0: |
709 | + network_uuids = [uuid for (uuid, fixed_ip) in requested_networks] |
710 | + networks = self.db.network_get_all_by_uuids(context, |
711 | + network_uuids) |
712 | + else: |
713 | + try: |
714 | + networks = self.db.network_get_all(context) |
715 | + except exception.NoNetworksFound: |
716 | + return [] |
717 | # return only networks which are not vlan networks |
718 | return [network for network in networks if |
719 | not network['vlan']] |
720 | @@ -403,16 +419,18 @@ |
721 | host = kwargs.pop('host') |
722 | project_id = kwargs.pop('project_id') |
723 | type_id = kwargs.pop('instance_type_id') |
724 | + requested_networks = kwargs.get('requested_networks') |
725 | vpn = kwargs.pop('vpn') |
726 | admin_context = context.elevated() |
727 | LOG.debug(_("network allocations for instance %s"), instance_id, |
728 | context=context) |
729 | - networks = self._get_networks_for_instance(admin_context, instance_id, |
730 | - project_id) |
731 | - LOG.warn(networks) |
732 | + networks = self._get_networks_for_instance(admin_context, |
733 | + instance_id, project_id, |
734 | + requested_networks=requested_networks) |
735 | self._allocate_mac_addresses(context, instance_id, networks) |
736 | - self._allocate_fixed_ips(admin_context, instance_id, host, networks, |
737 | - vpn=vpn) |
738 | + self._allocate_fixed_ips(admin_context, instance_id, |
739 | + host, networks, vpn=vpn, |
740 | + requested_networks=requested_networks) |
741 | return self.get_instance_nw_info(context, instance_id, type_id, host) |
742 | |
743 | def deallocate_for_instance(self, context, **kwargs): |
744 | @@ -570,9 +588,15 @@ |
745 | # network_get_by_compute_host |
746 | address = None |
747 | if network['cidr']: |
748 | - address = self.db.fixed_ip_associate_pool(context.elevated(), |
749 | - network['id'], |
750 | - instance_id) |
751 | + address = kwargs.get('address', None) |
752 | + if address: |
753 | + address = self.db.fixed_ip_associate(context, |
754 | + address, instance_id, |
755 | + network['id']) |
756 | + else: |
757 | + address = self.db.fixed_ip_associate_pool(context.elevated(), |
758 | + network['id'], |
759 | + instance_id) |
760 | self._do_trigger_security_group_members_refresh_for_instance( |
761 | instance_id) |
762 | get_vif = self.db.virtual_interface_get_by_instance_and_network |
763 | @@ -798,6 +822,35 @@ |
764 | """Sets up network on this host.""" |
765 | raise NotImplementedError() |
766 | |
767 | + def validate_networks(self, context, networks): |
768 | + """check if the networks exists and host |
769 | + is set to each network. |
770 | + """ |
771 | + if networks is None or len(networks) == 0: |
772 | + return |
773 | + |
774 | + network_uuids = [uuid for (uuid, fixed_ip) in networks] |
775 | + |
776 | + self._get_networks_by_uuids(context, network_uuids) |
777 | + |
778 | + for network_uuid, address in networks: |
779 | + # check if the fixed IP address is valid and |
780 | + # it actually belongs to the network |
781 | + if address is not None: |
782 | + if not utils.is_valid_ipv4(address): |
783 | + raise exception.FixedIpInvalid(address=address) |
784 | + |
785 | + fixed_ip_ref = self.db.fixed_ip_get_by_address(context, |
786 | + address) |
787 | + if fixed_ip_ref['network']['uuid'] != network_uuid: |
788 | + raise exception.FixedIpNotFoundForNetwork(address=address, |
789 | + network_uuid=network_uuid) |
790 | + if fixed_ip_ref['instance'] is not None: |
791 | + raise exception.FixedIpAlreadyInUse(address=address) |
792 | + |
793 | + def _get_networks_by_uuids(self, context, network_uuids): |
794 | + return self.db.network_get_all_by_uuids(context, network_uuids) |
795 | + |
796 | |
797 | class FlatManager(NetworkManager): |
798 | """Basic network where no vlans are used. |
799 | @@ -832,8 +885,16 @@ |
800 | def _allocate_fixed_ips(self, context, instance_id, host, networks, |
801 | **kwargs): |
802 | """Calls allocate_fixed_ip once for each network.""" |
803 | + requested_networks = kwargs.pop('requested_networks') |
804 | for network in networks: |
805 | - self.allocate_fixed_ip(context, instance_id, network) |
806 | + address = None |
807 | + if requested_networks is not None: |
808 | + for address in (fixed_ip for (uuid, fixed_ip) in \ |
809 | + requested_networks if network['uuid'] == uuid): |
810 | + break |
811 | + |
812 | + self.allocate_fixed_ip(context, instance_id, |
813 | + network, address=address) |
814 | |
815 | def deallocate_fixed_ip(self, context, address, **kwargs): |
816 | """Returns a fixed ip to the pool.""" |
817 | @@ -927,9 +988,15 @@ |
818 | address, |
819 | instance_id) |
820 | else: |
821 | - address = self.db.fixed_ip_associate_pool(context, |
822 | - network['id'], |
823 | - instance_id) |
824 | + address = kwargs.get('address', None) |
825 | + if address: |
826 | + address = self.db.fixed_ip_associate(context, address, |
827 | + instance_id, |
828 | + network['id']) |
829 | + else: |
830 | + address = self.db.fixed_ip_associate_pool(context, |
831 | + network['id'], |
832 | + instance_id) |
833 | self._do_trigger_security_group_members_refresh_for_instance( |
834 | instance_id) |
835 | vif = self.db.virtual_interface_get_by_instance_and_network(context, |
836 | @@ -945,10 +1012,18 @@ |
837 | """Force adds another network to a project.""" |
838 | self.db.network_associate(context, project_id, force=True) |
839 | |
840 | - def _get_networks_for_instance(self, context, instance_id, project_id): |
841 | + def _get_networks_for_instance(self, context, instance_id, project_id, |
842 | + requested_networks=None): |
843 | """Determine which networks an instance should connect to.""" |
844 | # get networks associated with project |
845 | - return self.db.project_get_networks(context, project_id) |
846 | + if requested_networks is not None and len(requested_networks) != 0: |
847 | + network_uuids = [uuid for (uuid, fixed_ip) in requested_networks] |
848 | + networks = self.db.network_get_all_by_uuids(context, |
849 | + network_uuids, |
850 | + project_id) |
851 | + else: |
852 | + networks = self.db.project_get_networks(context, project_id) |
853 | + return networks |
854 | |
855 | def create_networks(self, context, **kwargs): |
856 | """Create networks based on parameters.""" |
857 | @@ -997,6 +1072,10 @@ |
858 | self.db.network_update(context, network_ref['id'], |
859 | {'gateway_v6': gateway}) |
860 | |
861 | + def _get_networks_by_uuids(self, context, network_uuids): |
862 | + return self.db.network_get_all_by_uuids(context, network_uuids, |
863 | + context.project_id) |
864 | + |
865 | @property |
866 | def _bottom_reserved_ips(self): |
867 | """Number of reserved ips at the bottom of the range.""" |
868 | |
869 | === added file 'nova/tests/api/openstack/contrib/test_createserverext.py' |
870 | --- nova/tests/api/openstack/contrib/test_createserverext.py 1970-01-01 00:00:00 +0000 |
871 | +++ nova/tests/api/openstack/contrib/test_createserverext.py 2011-08-22 23:37:26 +0000 |
872 | @@ -0,0 +1,306 @@ |
873 | +# vim: tabstop=4 shiftwidth=4 softtabstop=4 |
874 | + |
875 | +# Copyright 2010-2011 OpenStack LLC. |
876 | +# All Rights Reserved. |
877 | +# |
878 | +# Licensed under the Apache License, Version 2.0 (the "License"); you may |
879 | +# not use this file except in compliance with the License. You may obtain |
880 | +# a copy of the License at |
881 | +# |
882 | +# http://www.apache.org/licenses/LICENSE-2.0 |
883 | +# |
884 | +# Unless required by applicable law or agreed to in writing, software |
885 | +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
886 | +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
887 | +# License for the specific language governing permissions and limitations |
888 | +# under the License. |
889 | + |
890 | +import base64 |
891 | +import json |
892 | +import unittest |
893 | +from xml.dom import minidom |
894 | + |
895 | +import stubout |
896 | +import webob |
897 | + |
898 | +from nova import exception |
899 | +from nova import flags |
900 | +from nova import test |
901 | +from nova import utils |
902 | +import nova.api.openstack |
903 | +from nova.api.openstack import servers |
904 | +from nova.api.openstack.contrib import createserverext |
905 | +import nova.compute.api |
906 | + |
907 | +import nova.scheduler.api |
908 | +import nova.image.fake |
909 | +import nova.rpc |
910 | +from nova.tests.api.openstack import fakes |
911 | + |
912 | + |
913 | +FLAGS = flags.FLAGS |
914 | +FLAGS.verbose = True |
915 | + |
916 | +FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' |
917 | + |
918 | +FAKE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), |
919 | + ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '10.0.2.12')] |
920 | + |
921 | +DUPLICATE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'), |
922 | + ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12')] |
923 | + |
924 | +INVALID_NETWORKS = [('invalid', 'invalid-ip-address')] |
925 | + |
926 | + |
927 | +class CreateserverextTest(test.TestCase): |
928 | + |
929 | + def setUp(self): |
930 | + super(CreateserverextTest, self).setUp() |
931 | + self.stubs = stubout.StubOutForTesting() |
932 | + fakes.FakeAuthManager.auth_data = {} |
933 | + fakes.FakeAuthDatabase.data = {} |
934 | + fakes.stub_out_auth(self.stubs) |
935 | + fakes.stub_out_image_service(self.stubs) |
936 | + fakes.stub_out_key_pair_funcs(self.stubs) |
937 | + self.allow_admin = FLAGS.allow_admin_api |
938 | + |
939 | + def tearDown(self): |
940 | + self.stubs.UnsetAll() |
941 | + FLAGS.allow_admin_api = self.allow_admin |
942 | + super(CreateserverextTest, self).tearDown() |
943 | + |
944 | + def _setup_mock_compute_api(self): |
945 | + |
946 | + class MockComputeAPI(nova.compute.API): |
947 | + |
948 | + def __init__(self): |
949 | + self.injected_files = None |
950 | + self.networks = None |
951 | + |
952 | + def create(self, *args, **kwargs): |
953 | + if 'injected_files' in kwargs: |
954 | + self.injected_files = kwargs['injected_files'] |
955 | + else: |
956 | + self.injected_files = None |
957 | + |
958 | + if 'requested_networks' in kwargs: |
959 | + self.networks = kwargs['requested_networks'] |
960 | + else: |
961 | + self.networks = None |
962 | + return [{'id': '1234', 'display_name': 'fakeinstance', |
963 | + 'uuid': FAKE_UUID, |
964 | + 'created_at': "", |
965 | + 'updated_at': ""}] |
966 | + |
967 | + def set_admin_password(self, *args, **kwargs): |
968 | + pass |
969 | + |
970 | + def make_stub_method(canned_return): |
971 | + def stub_method(*args, **kwargs): |
972 | + return canned_return |
973 | + return stub_method |
974 | + |
975 | + compute_api = MockComputeAPI() |
976 | + self.stubs.Set(nova.compute, 'API', make_stub_method(compute_api)) |
977 | + self.stubs.Set( |
978 | + nova.api.openstack.create_instance_helper.CreateInstanceHelper, |
979 | + '_get_kernel_ramdisk_from_image', make_stub_method((1, 1))) |
980 | + return compute_api |
981 | + |
982 | + def _create_networks_request_dict(self, networks): |
983 | + server = {} |
984 | + server['name'] = 'new-server-test' |
985 | + server['imageRef'] = 1 |
986 | + server['flavorRef'] = 1 |
987 | + if networks is not None: |
988 | + network_list = [] |
989 | + for uuid, fixed_ip in networks: |
990 | + network_list.append({'uuid': uuid, 'fixed_ip': fixed_ip}) |
991 | + server['networks'] = network_list |
992 | + return {'server': server} |
993 | + |
994 | + def _get_create_request_json(self, body_dict): |
995 | + req = webob.Request.blank('/v1.1/123/os-create-server-ext') |
996 | + req.headers['Content-Type'] = 'application/json' |
997 | + req.method = 'POST' |
998 | + req.body = json.dumps(body_dict) |
999 | + return req |
1000 | + |
1001 | + def _run_create_instance_with_mock_compute_api(self, request): |
1002 | + compute_api = self._setup_mock_compute_api() |
1003 | + response = request.get_response(fakes.wsgi_app()) |
1004 | + return compute_api, response |
1005 | + |
1006 | + def _format_xml_request_body(self, body_dict): |
1007 | + server = body_dict['server'] |
1008 | + body_parts = [] |
1009 | + body_parts.extend([ |
1010 | + '<?xml version="1.0" encoding="UTF-8"?>', |
1011 | + '<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.1"', |
1012 | + ' name="%s" imageRef="%s" flavorRef="%s">' % ( |
1013 | + server['name'], server['imageRef'], server['flavorRef'])]) |
1014 | + if 'metadata' in server: |
1015 | + metadata = server['metadata'] |
1016 | + body_parts.append('<metadata>') |
1017 | + for item in metadata.iteritems(): |
1018 | + body_parts.append('<meta key="%s">%s</meta>' % item) |
1019 | + body_parts.append('</metadata>') |
1020 | + if 'personality' in server: |
1021 | + personalities = server['personality'] |
1022 | + body_parts.append('<personality>') |
1023 | + for file in personalities: |
1024 | + item = (file['path'], file['contents']) |
1025 | + body_parts.append('<file path="%s">%s</file>' % item) |
1026 | + body_parts.append('</personality>') |
1027 | + if 'networks' in server: |
1028 | + networks = server['networks'] |
1029 | + body_parts.append('<networks>') |
1030 | + for network in networks: |
1031 | + item = (network['uuid'], network['fixed_ip']) |
1032 | + body_parts.append('<network uuid="%s" fixed_ip="%s"></network>' |
1033 | + % item) |
1034 | + body_parts.append('</networks>') |
1035 | + body_parts.append('</server>') |
1036 | + return ''.join(body_parts) |
1037 | + |
1038 | + def _get_create_request_xml(self, body_dict): |
1039 | + req = webob.Request.blank('/v1.1/123/os-create-server-ext') |
1040 | + req.content_type = 'application/xml' |
1041 | + req.accept = 'application/xml' |
1042 | + req.method = 'POST' |
1043 | + req.body = self._format_xml_request_body(body_dict) |
1044 | + return req |
1045 | + |
1046 | + def _create_instance_with_networks_json(self, networks): |
1047 | + body_dict = self._create_networks_request_dict(networks) |
1048 | + request = self._get_create_request_json(body_dict) |
1049 | + compute_api, response = \ |
1050 | + self._run_create_instance_with_mock_compute_api(request) |
1051 | + return request, response, compute_api.networks |
1052 | + |
1053 | + def _create_instance_with_networks_xml(self, networks): |
1054 | + body_dict = self._create_networks_request_dict(networks) |
1055 | + request = self._get_create_request_xml(body_dict) |
1056 | + compute_api, response = \ |
1057 | + self._run_create_instance_with_mock_compute_api(request) |
1058 | + return request, response, compute_api.networks |
1059 | + |
1060 | + def test_create_instance_with_no_networks(self): |
1061 | + request, response, networks = \ |
1062 | + self._create_instance_with_networks_json(networks=None) |
1063 | + self.assertEquals(response.status_int, 202) |
1064 | + self.assertEquals(networks, None) |
1065 | + |
1066 | + def test_create_instance_with_no_networks_xml(self): |
1067 | + request, response, networks = \ |
1068 | + self._create_instance_with_networks_xml(networks=None) |
1069 | + self.assertEquals(response.status_int, 202) |
1070 | + self.assertEquals(networks, None) |
1071 | + |
1072 | + def test_create_instance_with_one_network(self): |
1073 | + request, response, networks = \ |
1074 | + self._create_instance_with_networks_json([FAKE_NETWORKS[0]]) |
1075 | + self.assertEquals(response.status_int, 202) |
1076 | + self.assertEquals(networks, [FAKE_NETWORKS[0]]) |
1077 | + |
1078 | + def test_create_instance_with_one_network_xml(self): |
1079 | + request, response, networks = \ |
1080 | + self._create_instance_with_networks_xml([FAKE_NETWORKS[0]]) |
1081 | + self.assertEquals(response.status_int, 202) |
1082 | + self.assertEquals(networks, [FAKE_NETWORKS[0]]) |
1083 | + |
1084 | + def test_create_instance_with_two_networks(self): |
1085 | + request, response, networks = \ |
1086 | + self._create_instance_with_networks_json(FAKE_NETWORKS) |
1087 | + self.assertEquals(response.status_int, 202) |
1088 | + self.assertEquals(networks, FAKE_NETWORKS) |
1089 | + |
1090 | + def test_create_instance_with_two_networks_xml(self): |
1091 | + request, response, networks = \ |
1092 | + self._create_instance_with_networks_xml(FAKE_NETWORKS) |
1093 | + self.assertEquals(response.status_int, 202) |
1094 | + self.assertEquals(networks, FAKE_NETWORKS) |
1095 | + |
1096 | + def test_create_instance_with_duplicate_networks(self): |
1097 | + request, response, networks = \ |
1098 | + self._create_instance_with_networks_json(DUPLICATE_NETWORKS) |
1099 | + self.assertEquals(response.status_int, 400) |
1100 | + self.assertEquals(networks, None) |
1101 | + |
1102 | + def test_create_instance_with_duplicate_networks_xml(self): |
1103 | + request, response, networks = \ |
1104 | + self._create_instance_with_networks_xml(DUPLICATE_NETWORKS) |
1105 | + self.assertEquals(response.status_int, 400) |
1106 | + self.assertEquals(networks, None) |
1107 | + |
1108 | + def test_create_instance_with_network_no_id(self): |
1109 | + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) |
1110 | + del body_dict['server']['networks'][0]['uuid'] |
1111 | + request = self._get_create_request_json(body_dict) |
1112 | + compute_api, response = \ |
1113 | + self._run_create_instance_with_mock_compute_api(request) |
1114 | + self.assertEquals(response.status_int, 400) |
1115 | + self.assertEquals(compute_api.networks, None) |
1116 | + |
1117 | + def test_create_instance_with_network_no_id_xml(self): |
1118 | + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) |
1119 | + request = self._get_create_request_xml(body_dict) |
1120 | + uuid = ' uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"' |
1121 | + request.body = request.body.replace(uuid, '') |
1122 | + compute_api, response = \ |
1123 | + self._run_create_instance_with_mock_compute_api(request) |
1124 | + self.assertEquals(response.status_int, 400) |
1125 | + self.assertEquals(compute_api.networks, None) |
1126 | + |
1127 | + def test_create_instance_with_network_invalid_id(self): |
1128 | + request, response, networks = \ |
1129 | + self._create_instance_with_networks_json(INVALID_NETWORKS) |
1130 | + self.assertEquals(response.status_int, 400) |
1131 | + self.assertEquals(networks, None) |
1132 | + |
1133 | + def test_create_instance_with_network_invalid_id_xml(self): |
1134 | + request, response, networks = \ |
1135 | + self._create_instance_with_networks_xml(INVALID_NETWORKS) |
1136 | + self.assertEquals(response.status_int, 400) |
1137 | + self.assertEquals(networks, None) |
1138 | + |
1139 | + def test_create_instance_with_network_empty_fixed_ip(self): |
1140 | + networks = [('1', '')] |
1141 | + request, response, networks = \ |
1142 | + self._create_instance_with_networks_json(networks) |
1143 | + self.assertEquals(response.status_int, 400) |
1144 | + self.assertEquals(networks, None) |
1145 | + |
1146 | + def test_create_instance_with_network_non_string_fixed_ip(self): |
1147 | + networks = [('1', 12345)] |
1148 | + request, response, networks = \ |
1149 | + self._create_instance_with_networks_json(networks) |
1150 | + self.assertEquals(response.status_int, 400) |
1151 | + self.assertEquals(networks, None) |
1152 | + |
1153 | + def test_create_instance_with_network_empty_fixed_ip_xml(self): |
1154 | + networks = [('1', '')] |
1155 | + request, response, networks = \ |
1156 | + self._create_instance_with_networks_xml(networks) |
1157 | + self.assertEquals(response.status_int, 400) |
1158 | + self.assertEquals(networks, None) |
1159 | + |
1160 | + def test_create_instance_with_network_no_fixed_ip(self): |
1161 | + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) |
1162 | + del body_dict['server']['networks'][0]['fixed_ip'] |
1163 | + request = self._get_create_request_json(body_dict) |
1164 | + compute_api, response = \ |
1165 | + self._run_create_instance_with_mock_compute_api(request) |
1166 | + self.assertEquals(response.status_int, 202) |
1167 | + self.assertEquals(compute_api.networks, |
1168 | + [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)]) |
1169 | + |
1170 | + def test_create_instance_with_network_no_fixed_ip_xml(self): |
1171 | + body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]]) |
1172 | + request = self._get_create_request_xml(body_dict) |
1173 | + request.body = request.body.replace(' fixed_ip="10.0.1.12"', '') |
1174 | + compute_api, response = \ |
1175 | + self._run_create_instance_with_mock_compute_api(request) |
1176 | + self.assertEquals(response.status_int, 202) |
1177 | + self.assertEquals(compute_api.networks, |
1178 | + [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)]) |
1179 | |
1180 | === modified file 'nova/tests/api/openstack/test_extensions.py' |
1181 | --- nova/tests/api/openstack/test_extensions.py 2011-08-19 13:54:05 +0000 |
1182 | +++ nova/tests/api/openstack/test_extensions.py 2011-08-22 23:37:26 +0000 |
1183 | @@ -85,6 +85,7 @@ |
1184 | ext_path = os.path.join(os.path.dirname(__file__), "extensions") |
1185 | self.flags(osapi_extensions_path=ext_path) |
1186 | self.ext_list = [ |
1187 | + "Createserverext", |
1188 | "FlavorExtraSpecs", |
1189 | "Floating_ips", |
1190 | "Fox In Socks", |
1191 | |
1192 | === modified file 'nova/tests/api/openstack/test_servers.py' |
1193 | --- nova/tests/api/openstack/test_servers.py 2011-08-22 14:19:01 +0000 |
1194 | +++ nova/tests/api/openstack/test_servers.py 2011-08-22 23:37:26 +0000 |
1195 | @@ -1890,6 +1890,29 @@ |
1196 | res = req.get_response(fakes.wsgi_app()) |
1197 | self.assertEqual(res.status_int, 400) |
1198 | |
1199 | + def test_create_instance_whitespace_name(self): |
1200 | + self._setup_for_create_instance() |
1201 | + |
1202 | + body = { |
1203 | + 'server': { |
1204 | + 'name': ' ', |
1205 | + 'imageId': 3, |
1206 | + 'flavorId': 1, |
1207 | + 'metadata': { |
1208 | + 'hello': 'world', |
1209 | + 'open': 'stack', |
1210 | + }, |
1211 | + 'personality': {}, |
1212 | + }, |
1213 | + } |
1214 | + |
1215 | + req = webob.Request.blank('/v1.0/servers') |
1216 | + req.method = 'POST' |
1217 | + req.body = json.dumps(body) |
1218 | + req.headers["content-type"] = "application/json" |
1219 | + res = req.get_response(fakes.wsgi_app()) |
1220 | + self.assertEqual(res.status_int, 400) |
1221 | + |
1222 | def test_update_server_no_body(self): |
1223 | req = webob.Request.blank('/v1.0/servers/1') |
1224 | req.method = 'PUT' |
1225 | @@ -2829,6 +2852,164 @@ |
1226 | } |
1227 | self.assertEquals(request['body'], expected) |
1228 | |
1229 | + def test_request_with_empty_networks(self): |
1230 | + serial_request = """ |
1231 | +<server xmlns="http://docs.openstack.org/compute/api/v1.1" |
1232 | + name="new-server-test" imageRef="1" flavorRef="1"> |
1233 | + <networks/> |
1234 | +</server>""" |
1235 | + request = self.deserializer.deserialize(serial_request, 'create') |
1236 | + expected = {"server": { |
1237 | + "name": "new-server-test", |
1238 | + "imageRef": "1", |
1239 | + "flavorRef": "1", |
1240 | + "networks": [] |
1241 | + }} |
1242 | + self.assertEquals(request['body'], expected) |
1243 | + |
1244 | + def test_request_with_one_network(self): |
1245 | + serial_request = """ |
1246 | +<server xmlns="http://docs.openstack.org/compute/api/v1.1" |
1247 | + name="new-server-test" imageRef="1" flavorRef="1"> |
1248 | + <networks> |
1249 | + <network uuid="1" fixed_ip="10.0.1.12"/> |
1250 | + </networks> |
1251 | +</server>""" |
1252 | + request = self.deserializer.deserialize(serial_request, 'create') |
1253 | + expected = {"server": { |
1254 | + "name": "new-server-test", |
1255 | + "imageRef": "1", |
1256 | + "flavorRef": "1", |
1257 | + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}], |
1258 | + }} |
1259 | + self.assertEquals(request['body'], expected) |
1260 | + |
1261 | + def test_request_with_two_networks(self): |
1262 | + serial_request = """ |
1263 | +<server xmlns="http://docs.openstack.org/compute/api/v1.1" |
1264 | + name="new-server-test" imageRef="1" flavorRef="1"> |
1265 | + <networks> |
1266 | + <network uuid="1" fixed_ip="10.0.1.12"/> |
1267 | + <network uuid="2" fixed_ip="10.0.2.12"/> |
1268 | + </networks> |
1269 | +</server>""" |
1270 | + request = self.deserializer.deserialize(serial_request, 'create') |
1271 | + expected = {"server": { |
1272 | + "name": "new-server-test", |
1273 | + "imageRef": "1", |
1274 | + "flavorRef": "1", |
1275 | + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}, |
1276 | + {"uuid": "2", "fixed_ip": "10.0.2.12"}], |
1277 | + }} |
1278 | + self.assertEquals(request['body'], expected) |
1279 | + |
1280 | + def test_request_with_second_network_node_ignored(self): |
1281 | + serial_request = """ |
1282 | +<server xmlns="http://docs.openstack.org/compute/api/v1.1" |
1283 | + name="new-server-test" imageRef="1" flavorRef="1"> |
1284 | + <networks> |
1285 | + <network uuid="1" fixed_ip="10.0.1.12"/> |
1286 | + </networks> |
1287 | + <networks> |
1288 | + <network uuid="2" fixed_ip="10.0.2.12"/> |
1289 | + </networks> |
1290 | +</server>""" |
1291 | + request = self.deserializer.deserialize(serial_request, 'create') |
1292 | + expected = {"server": { |
1293 | + "name": "new-server-test", |
1294 | + "imageRef": "1", |
1295 | + "flavorRef": "1", |
1296 | + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}], |
1297 | + }} |
1298 | + self.assertEquals(request['body'], expected) |
1299 | + |
1300 | + def test_request_with_one_network_missing_id(self): |
1301 | + serial_request = """ |
1302 | +<server xmlns="http://docs.openstack.org/compute/api/v1.1" |
1303 | + name="new-server-test" imageRef="1" flavorRef="1"> |
1304 | + <networks> |
1305 | + <network fixed_ip="10.0.1.12"/> |
1306 | + </networks> |
1307 | +</server>""" |
1308 | + request = self.deserializer.deserialize(serial_request, 'create') |
1309 | + expected = {"server": { |
1310 | + "name": "new-server-test", |
1311 | + "imageRef": "1", |
1312 | + "flavorRef": "1", |
1313 | + "networks": [{"fixed_ip": "10.0.1.12"}], |
1314 | + }} |
1315 | + self.assertEquals(request['body'], expected) |
1316 | + |
1317 | + def test_request_with_one_network_missing_fixed_ip(self): |
1318 | + serial_request = """ |
1319 | +<server xmlns="http://docs.openstack.org/compute/api/v1.1" |
1320 | + name="new-server-test" imageRef="1" flavorRef="1"> |
1321 | + <networks> |
1322 | + <network uuid="1"/> |
1323 | + </networks> |
1324 | +</server>""" |
1325 | + request = self.deserializer.deserialize(serial_request, 'create') |
1326 | + expected = {"server": { |
1327 | + "name": "new-server-test", |
1328 | + "imageRef": "1", |
1329 | + "flavorRef": "1", |
1330 | + "networks": [{"uuid": "1"}], |
1331 | + }} |
1332 | + self.assertEquals(request['body'], expected) |
1333 | + |
1334 | + def test_request_with_one_network_empty_id(self): |
1335 | + serial_request = """ |
1336 | + <server xmlns="http://docs.openstack.org/compute/api/v1.1" |
1337 | + name="new-server-test" imageRef="1" flavorRef="1"> |
1338 | + <networks> |
1339 | + <network uuid="" fixed_ip="10.0.1.12"/> |
1340 | + </networks> |
1341 | + </server>""" |
1342 | + request = self.deserializer.deserialize(serial_request, 'create') |
1343 | + expected = {"server": { |
1344 | + "name": "new-server-test", |
1345 | + "imageRef": "1", |
1346 | + "flavorRef": "1", |
1347 | + "networks": [{"uuid": "", "fixed_ip": "10.0.1.12"}], |
1348 | + }} |
1349 | + self.assertEquals(request['body'], expected) |
1350 | + |
1351 | + def test_request_with_one_network_empty_fixed_ip(self): |
1352 | + serial_request = """ |
1353 | + <server xmlns="http://docs.openstack.org/compute/api/v1.1" |
1354 | + name="new-server-test" imageRef="1" flavorRef="1"> |
1355 | + <networks> |
1356 | + <network uuid="1" fixed_ip=""/> |
1357 | + </networks> |
1358 | + </server>""" |
1359 | + request = self.deserializer.deserialize(serial_request, 'create') |
1360 | + expected = {"server": { |
1361 | + "name": "new-server-test", |
1362 | + "imageRef": "1", |
1363 | + "flavorRef": "1", |
1364 | + "networks": [{"uuid": "1", "fixed_ip": ""}], |
1365 | + }} |
1366 | + self.assertEquals(request['body'], expected) |
1367 | + |
1368 | + def test_request_with_networks_duplicate_ids(self): |
1369 | + serial_request = """ |
1370 | + <server xmlns="http://docs.openstack.org/compute/api/v1.1" |
1371 | + name="new-server-test" imageRef="1" flavorRef="1"> |
1372 | + <networks> |
1373 | + <network uuid="1" fixed_ip="10.0.1.12"/> |
1374 | + <network uuid="1" fixed_ip="10.0.2.12"/> |
1375 | + </networks> |
1376 | + </server>""" |
1377 | + request = self.deserializer.deserialize(serial_request, 'create') |
1378 | + expected = {"server": { |
1379 | + "name": "new-server-test", |
1380 | + "imageRef": "1", |
1381 | + "flavorRef": "1", |
1382 | + "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}, |
1383 | + {"uuid": "1", "fixed_ip": "10.0.2.12"}], |
1384 | + }} |
1385 | + self.assertEquals(request['body'], expected) |
1386 | + |
1387 | |
1388 | class TestAddressesXMLSerialization(test.TestCase): |
1389 | |
1390 | @@ -2899,12 +3080,14 @@ |
1391 | |
1392 | def __init__(self): |
1393 | self.injected_files = None |
1394 | + self.networks = None |
1395 | |
1396 | def create(self, *args, **kwargs): |
1397 | if 'injected_files' in kwargs: |
1398 | self.injected_files = kwargs['injected_files'] |
1399 | else: |
1400 | self.injected_files = None |
1401 | + |
1402 | return [{'id': '1234', 'display_name': 'fakeinstance', |
1403 | 'uuid': FAKE_UUID}] |
1404 | |
1405 | |
1406 | === modified file 'nova/tests/test_network.py' |
1407 | --- nova/tests/test_network.py 2011-08-18 06:50:50 +0000 |
1408 | +++ nova/tests/test_network.py 2011-08-22 23:37:26 +0000 |
1409 | @@ -15,6 +15,7 @@ |
1410 | # License for the specific language governing permissions and limitations |
1411 | # under the License. |
1412 | |
1413 | +from nova import context |
1414 | from nova import db |
1415 | from nova import exception |
1416 | from nova import log as logging |
1417 | @@ -41,6 +42,7 @@ |
1418 | |
1419 | |
1420 | networks = [{'id': 0, |
1421 | + 'uuid': "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", |
1422 | 'label': 'test0', |
1423 | 'injected': False, |
1424 | 'multi_host': False, |
1425 | @@ -60,6 +62,7 @@ |
1426 | 'project_id': 'fake_project', |
1427 | 'vpn_public_address': '192.168.0.2'}, |
1428 | {'id': 1, |
1429 | + 'uuid': "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", |
1430 | 'label': 'test1', |
1431 | 'injected': False, |
1432 | 'multi_host': False, |
1433 | @@ -126,6 +129,8 @@ |
1434 | super(FlatNetworkTestCase, self).setUp() |
1435 | self.network = network_manager.FlatManager(host=HOST) |
1436 | self.network.db = db |
1437 | + self.context = context.RequestContext('testuser', 'testproject', |
1438 | + is_admin=False) |
1439 | |
1440 | def test_get_instance_nw_info(self): |
1441 | self.mox.StubOutWithMock(db, 'fixed_ip_get_by_instance') |
1442 | @@ -183,12 +188,73 @@ |
1443 | 'netmask': '255.255.255.0'}] |
1444 | self.assertDictListMatch(nw[1]['ips'], check) |
1445 | |
1446 | + def test_validate_networks(self): |
1447 | + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') |
1448 | + self.mox.StubOutWithMock(db, "fixed_ip_get_by_address") |
1449 | + |
1450 | + requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", |
1451 | + "192.168.1.100")] |
1452 | + db.network_get_all_by_uuids(mox.IgnoreArg(), |
1453 | + mox.IgnoreArg()).AndReturn(networks) |
1454 | + |
1455 | + fixed_ips[1]['network'] = FakeModel(**networks[1]) |
1456 | + fixed_ips[1]['instance'] = None |
1457 | + db.fixed_ip_get_by_address(mox.IgnoreArg(), |
1458 | + mox.IgnoreArg()).AndReturn(fixed_ips[1]) |
1459 | + |
1460 | + self.mox.ReplayAll() |
1461 | + self.network.validate_networks(self.context, requested_networks) |
1462 | + |
1463 | + def test_validate_networks_none_requested_networks(self): |
1464 | + self.network.validate_networks(self.context, None) |
1465 | + |
1466 | + def test_validate_networks_empty_requested_networks(self): |
1467 | + requested_networks = [] |
1468 | + self.mox.ReplayAll() |
1469 | + |
1470 | + self.network.validate_networks(self.context, requested_networks) |
1471 | + |
1472 | + def test_validate_networks_invalid_fixed_ip(self): |
1473 | + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') |
1474 | + requested_networks = [(1, "192.168.0.100.1")] |
1475 | + db.network_get_all_by_uuids(mox.IgnoreArg(), |
1476 | + mox.IgnoreArg()).AndReturn(networks) |
1477 | + self.mox.ReplayAll() |
1478 | + |
1479 | + self.assertRaises(exception.FixedIpInvalid, |
1480 | + self.network.validate_networks, None, |
1481 | + requested_networks) |
1482 | + |
1483 | + def test_validate_networks_empty_fixed_ip(self): |
1484 | + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') |
1485 | + |
1486 | + requested_networks = [(1, "")] |
1487 | + db.network_get_all_by_uuids(mox.IgnoreArg(), |
1488 | + mox.IgnoreArg()).AndReturn(networks) |
1489 | + self.mox.ReplayAll() |
1490 | + |
1491 | + self.assertRaises(exception.FixedIpInvalid, |
1492 | + self.network.validate_networks, |
1493 | + None, requested_networks) |
1494 | + |
1495 | + def test_validate_networks_none_fixed_ip(self): |
1496 | + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') |
1497 | + |
1498 | + requested_networks = [(1, None)] |
1499 | + db.network_get_all_by_uuids(mox.IgnoreArg(), |
1500 | + mox.IgnoreArg()).AndReturn(networks) |
1501 | + self.mox.ReplayAll() |
1502 | + |
1503 | + self.network.validate_networks(None, requested_networks) |
1504 | + |
1505 | |
1506 | class VlanNetworkTestCase(test.TestCase): |
1507 | def setUp(self): |
1508 | super(VlanNetworkTestCase, self).setUp() |
1509 | self.network = network_manager.VlanManager(host=HOST) |
1510 | self.network.db = db |
1511 | + self.context = context.RequestContext('testuser', 'testproject', |
1512 | + is_admin=False) |
1513 | |
1514 | def test_vpn_allocate_fixed_ip(self): |
1515 | self.mox.StubOutWithMock(db, 'fixed_ip_associate') |
1516 | @@ -232,7 +298,7 @@ |
1517 | |
1518 | network = dict(networks[0]) |
1519 | network['vpn_private_address'] = '192.168.0.2' |
1520 | - self.network.allocate_fixed_ip(None, 0, network) |
1521 | + self.network.allocate_fixed_ip(self.context, 0, network) |
1522 | |
1523 | def test_create_networks_too_big(self): |
1524 | self.assertRaises(ValueError, self.network.create_networks, None, |
1525 | @@ -243,6 +309,68 @@ |
1526 | num_networks=100, vlan_start=1, |
1527 | cidr='192.168.0.1/24', network_size=100) |
1528 | |
1529 | + def test_validate_networks(self): |
1530 | + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') |
1531 | + self.mox.StubOutWithMock(db, "fixed_ip_get_by_address") |
1532 | + |
1533 | + requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb", |
1534 | + "192.168.1.100")] |
1535 | + db.network_get_all_by_uuids(mox.IgnoreArg(), |
1536 | + mox.IgnoreArg(), |
1537 | + mox.IgnoreArg()).AndReturn(networks) |
1538 | + |
1539 | + fixed_ips[1]['network'] = FakeModel(**networks[1]) |
1540 | + fixed_ips[1]['instance'] = None |
1541 | + db.fixed_ip_get_by_address(mox.IgnoreArg(), |
1542 | + mox.IgnoreArg()).AndReturn(fixed_ips[1]) |
1543 | + |
1544 | + self.mox.ReplayAll() |
1545 | + self.network.validate_networks(self.context, requested_networks) |
1546 | + |
1547 | + def test_validate_networks_none_requested_networks(self): |
1548 | + self.network.validate_networks(self.context, None) |
1549 | + |
1550 | + def test_validate_networks_empty_requested_networks(self): |
1551 | + requested_networks = [] |
1552 | + self.mox.ReplayAll() |
1553 | + |
1554 | + self.network.validate_networks(self.context, requested_networks) |
1555 | + |
1556 | + def test_validate_networks_invalid_fixed_ip(self): |
1557 | + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') |
1558 | + requested_networks = [(1, "192.168.0.100.1")] |
1559 | + db.network_get_all_by_uuids(mox.IgnoreArg(), |
1560 | + mox.IgnoreArg(), |
1561 | + mox.IgnoreArg()).AndReturn(networks) |
1562 | + self.mox.ReplayAll() |
1563 | + |
1564 | + self.assertRaises(exception.FixedIpInvalid, |
1565 | + self.network.validate_networks, self.context, |
1566 | + requested_networks) |
1567 | + |
1568 | + def test_validate_networks_empty_fixed_ip(self): |
1569 | + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') |
1570 | + |
1571 | + requested_networks = [(1, "")] |
1572 | + db.network_get_all_by_uuids(mox.IgnoreArg(), |
1573 | + mox.IgnoreArg(), |
1574 | + mox.IgnoreArg()).AndReturn(networks) |
1575 | + self.mox.ReplayAll() |
1576 | + |
1577 | + self.assertRaises(exception.FixedIpInvalid, |
1578 | + self.network.validate_networks, |
1579 | + self.context, requested_networks) |
1580 | + |
1581 | + def test_validate_networks_none_fixed_ip(self): |
1582 | + self.mox.StubOutWithMock(db, 'network_get_all_by_uuids') |
1583 | + |
1584 | + requested_networks = [(1, None)] |
1585 | + db.network_get_all_by_uuids(mox.IgnoreArg(), |
1586 | + mox.IgnoreArg(), |
1587 | + mox.IgnoreArg()).AndReturn(networks) |
1588 | + self.mox.ReplayAll() |
1589 | + self.network.validate_networks(self.context, requested_networks) |
1590 | + |
1591 | |
1592 | class CommonNetworkTestCase(test.TestCase): |
1593 | |
1594 | |
1595 | === modified file 'nova/utils.py' |
1596 | --- nova/utils.py 2011-08-22 12:28:12 +0000 |
1597 | +++ nova/utils.py 2011-08-22 23:37:26 +0000 |
1598 | @@ -844,3 +844,19 @@ |
1599 | return True if int(val) else False |
1600 | except ValueError: |
1601 | return val.lower() == 'true' |
1602 | + |
1603 | + |
1604 | +def is_valid_ipv4(address): |
1605 | + """valid the address strictly as per format xxx.xxx.xxx.xxx. |
1606 | + where xxx is a value between 0 and 255. |
1607 | + """ |
1608 | + parts = address.split(".") |
1609 | + if len(parts) != 4: |
1610 | + return False |
1611 | + for item in parts: |
1612 | + try: |
1613 | + if not 0 <= int(item) <= 255: |
1614 | + return False |
1615 | + except ValueError: |
1616 | + return False |
1617 | + return True |
Hey Tushar,
This seems like a very useful feature, but the implementation in the apis will have to change a little:
1) The v1.0 api cannot be changed. You will need to limit your changes to the v1.1 api.
2) This is not in the v1.1 spec, so it will have to be documented as an extension. I think it is okay for the v1.1 code to remain unchanged, but you will need to add an extension descriptor to document the change.
I will gladly take a closer look at this once those things are addressed :)