Merge lp:~tpatil/nova/add-options-network-create-os-apis into lp:~hudson-openstack/nova/trunk

Proposed by Tushar Patil
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
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

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://docs.nttpflab.com/servers/api/v1.0"
        name="new-server-test" imageId="1" flavorId="1">
  <metadata>
    <meta key="My Server Name">Apache1</meta>
  </metadata>
  <personality>
    <file path="/etc/banner.txt">
        ICAgICAgDQoiQSBjbG91ZCBkb2VzIG5vdCBrbm93IHdoeSBp
    </file>
  </personality>
  <networks>
      <network uuid="6622436e-5289-460f-8479-e4dcc63f16c5" fixed_ip="10.0.0.3">
      <network uuid="d97efefc-e071-4174-b6dd-b33af0a37706" fixed_ip="10.0.1.3">
  </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.

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

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 :)

review: Needs Fixing
Revision history for this message
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/contrib directory of nova. Please confirm.

Revision history for this message
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/contrib
> directory of nova. Please confirm.

You should add a module in api.openstack.contrib, but the only code you need in it is an implementation of nova.api.openstack.extensions.ExtensionDescriptor. See api.openstack.contrib.volumes for an example of the descriptor. Basically, you need to describe the difference in our (Nova's) implementation of the v1.1 spec.

Revision history for this message
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.

Revision history for this message
Tushar Patil (tpatil) wrote :

> You should add a module in api.openstack.contrib, but the only code you need
> in it is an implementation of
> nova.api.openstack.extensions.ExtensionDescriptor. See
> api.openstack.contrib.volumes for an example of the descriptor. Basically, you
> 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.

Revision history for this message
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.

Revision history for this message
Tushar Patil (tpatil) wrote :

Fixed all broken unit test cases after merging trunk changes. Please review.

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

I think it makes more sense for you to put whatever changes you need to make in the main create_instance_helper.py module. The duplication in nova/api/openstack/contrib/createserverext.py is going to be difficult to 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).

Revision history for this message
Tushar Patil (tpatil) wrote :

> I think it makes more sense for you to put whatever changes you need to make
> in the main create_instance_helper.py module. The duplication in
> nova/api/openstack/contrib/createserverext.py is going to be difficult to 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/openstack/contrip/test_createserverext.py.

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_ref_from_req_data to both ControllerV11 and ControllerV10. ControllerV10 will simply return None whereas ControllerV11 will return the actual network reference data.

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

> > I think it makes more sense for you to put whatever changes you need to make
> > in the main create_instance_helper.py module. The duplication in
> > nova/api/openstack/contrib/createserverext.py is going to be difficult to
> 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/openstack/contrip/test_createserverext.py.
>
> 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_ref_from_req_data to both ControllerV11 and
> 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.

Revision history for this message
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_instance_helper.py, my changes will be accessible by both OS V1.0 and V1.1 since CreateInstanceHelper class is used by both of the controllers. 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?
http://10.2.3.150:8774/v1.1/os-create-server-ext
http://10.2.3.150:8774/v1.0/servers
http://10.2.3.150:8774/v1.1/servers

XML request
-------------
<?xml version="1.0" encoding="UTF-8"?>
<server name="new-server-test" flavorRef="1"
        imageRef="46">
  <metadata>
    <meta key="My Server Name">Apache1</meta>
  </metadata>
<personality/>
<networks>
    <network id="1" fixed_ip="10.0.1.15" />
</networks>
</server>

Revision history for this message
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_instance_helper.py, my changes will be accessible by both OS V1.0 and
> V1.1 since CreateInstanceHelper class is used by both of the controllers.
> 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.

Revision history for this message
Tushar Patil (tpatil) wrote :

Couple of new extensions are added into trunk because of which the test_extensions unit testcases are broken.

review: Needs Fixing
Revision history for this message
Tushar Patil (tpatil) wrote :

Fixed all broken unit testcases after merging my branch with the trunk changes. Please review.

Revision history for this message
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_instance_helper.py, where the code calls:

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_api.validate_networks(context, requested_networks)

This way, a NetworkManager way the typing could be specific to the network manager.

Thoughts?

Revision history for this message
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_instance_helper.py, where the code calls:
>
> 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_api.validate_networks(context, requested_networks)
>
> 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.

Revision history for this message
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://code.launchpad.net/~tpatil/nova/add-options-network-create-os-apis/+merge/68292
> 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Revision history for this message
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.

Revision history for this message
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://code.launchpad.net/~tpatil/nova/add-options-network-create-os-apis/+merge/68292
> 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
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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:

https://bugs.launchpad.net/nova/+bug/828267

Revision history for this message
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_ip_associate'? I think the 'by_address' part is implied. Please correct me if I'm wrong here.

477: This name could be made a bit clearer by avoiding repetition of 'network'. 'network_get_all_by_uuids' might be a better option

492: I think this function can be accomplished with a keyword argument (maybe 'project'?) to the network_get_networks_by_uuids function.

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_associate_pool from any client code I wrote.

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 :)

Revision history for this message
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_ip_associate'? I think the
> '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_associate_by_address into one in the fixed_ip_associate method.

> 477: This name could be made a bit clearer by avoiding repetition of
> 'network'. 'network_get_all_by_uuids' might be a better option

Fixed

> 492: I think this function can be accomplished with a keyword argument (maybe
> 'project'?) to the network_get_networks_by_uuids function.

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_associate_pool from any client
> 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

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

Looks great, Tushar!

review: Approve
Revision history for this message
Tushar Patil (tpatil) :
review: Approve
Revision history for this message
Vish Ishaya (vishvananda) wrote :

looks good.

review: Approve
Revision history for this message
OpenStack Infra (hudson-openstack) wrote :

Attempt to merge into lp:nova failed due to conflicts:

text conflict in nova/api/openstack/create_instance_helper.py
text conflict in nova/compute/api.py
text conflict in nova/tests/api/openstack/test_servers.py

Revision history for this message
Tushar Patil (tpatil) wrote :

Resolved all conflicts and fixed broken unit tests in the test_createservertext.py due to the changes in the extension which now includes ProjectMapper.

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

thx, trying again.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/nova-manage'
--- bin/nova-manage 2011-08-15 20:33:37 +0000
+++ bin/nova-manage 2011-08-22 23:37:26 +0000
@@ -763,23 +763,26 @@
763763
764 def list(self):764 def list(self):
765 """List all created networks"""765 """List all created networks"""
766 print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (766 _fmt = "%-5s\t%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s"
767 _('IPv4'),767 print _fmt % (_('id'),
768 _('IPv6'),768 _('IPv4'),
769 _('start address'),769 _('IPv6'),
770 _('DNS1'),770 _('start address'),
771 _('DNS2'),771 _('DNS1'),
772 _('VlanID'),772 _('DNS2'),
773 'project')773 _('VlanID'),
774 _('project'),
775 _("uuid"))
774 for network in db.network_get_all(context.get_admin_context()):776 for network in db.network_get_all(context.get_admin_context()):
775 print "%-18s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s\t%-15s" % (777 print _fmt % (network.id,
776 network.cidr,778 network.cidr,
777 network.cidr_v6,779 network.cidr_v6,
778 network.dhcp_start,780 network.dhcp_start,
779 network.dns1,781 network.dns1,
780 network.dns2,782 network.dns2,
781 network.vlan,783 network.vlan,
782 network.project_id)784 network.project_id,
785 network.uuid)
783786
784 @args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>',787 @args('--network', dest="fixed_range", metavar='<x.x.x.x/yy>',
785 help='Network to delete')788 help='Network to delete')
786789
=== added file 'nova/api/openstack/contrib/createserverext.py'
--- nova/api/openstack/contrib/createserverext.py 1970-01-01 00:00:00 +0000
+++ nova/api/openstack/contrib/createserverext.py 2011-08-22 23:37:26 +0000
@@ -0,0 +1,66 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 OpenStack LLC.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License
16
17from nova.api.openstack import create_instance_helper as helper
18from nova.api.openstack import extensions
19from nova.api.openstack import servers
20from nova.api.openstack import wsgi
21
22
23class Createserverext(extensions.ExtensionDescriptor):
24 """The servers create ext
25
26 Exposes addFixedIp and removeFixedIp actions on servers.
27
28 """
29 def get_name(self):
30 return "Createserverext"
31
32 def get_alias(self):
33 return "os-create-server-ext"
34
35 def get_description(self):
36 return "Extended support to the Create Server v1.1 API"
37
38 def get_namespace(self):
39 return "http://docs.openstack.org/ext/createserverext/api/v1.1"
40
41 def get_updated(self):
42 return "2011-07-19T00:00:00+00:00"
43
44 def get_resources(self):
45 resources = []
46
47 headers_serializer = servers.HeadersSerializer()
48 body_serializers = {
49 'application/xml': servers.ServerXMLSerializer(),
50 }
51
52 body_deserializers = {
53 'application/xml': helper.ServerXMLDeserializerV11(),
54 }
55
56 serializer = wsgi.ResponseSerializer(body_serializers,
57 headers_serializer)
58 deserializer = wsgi.RequestDeserializer(body_deserializers)
59
60 res = extensions.ResourceExtension('os-create-server-ext',
61 controller=servers.ControllerV11(),
62 deserializer=deserializer,
63 serializer=serializer)
64 resources.append(res)
65
66 return resources
067
=== modified file 'nova/api/openstack/create_instance_helper.py'
--- nova/api/openstack/create_instance_helper.py 2011-08-22 03:17:04 +0000
+++ nova/api/openstack/create_instance_helper.py 2011-08-22 23:37:26 +0000
@@ -29,7 +29,7 @@
29from nova.compute import instance_types29from nova.compute import instance_types
30from nova.api.openstack import common30from nova.api.openstack import common
31from nova.api.openstack import wsgi31from nova.api.openstack import wsgi
3232from nova.rpc.common import RemoteError
3333
34LOG = logging.getLogger('nova.api.openstack.create_instance_helper')34LOG = logging.getLogger('nova.api.openstack.create_instance_helper')
35FLAGS = flags.FLAGS35FLAGS = flags.FLAGS
@@ -120,6 +120,11 @@
120120
121 sg_names = list(set(sg_names))121 sg_names = list(set(sg_names))
122122
123 requested_networks = server_dict.get('networks')
124 if requested_networks is not None:
125 requested_networks = self._get_requested_networks(
126 requested_networks)
127
123 try:128 try:
124 flavor_id = self.controller._flavor_id_from_req_data(body)129 flavor_id = self.controller._flavor_id_from_req_data(body)
125 except ValueError as error:130 except ValueError as error:
@@ -175,6 +180,7 @@
175 reservation_id=reservation_id,180 reservation_id=reservation_id,
176 min_count=min_count,181 min_count=min_count,
177 max_count=max_count,182 max_count=max_count,
183 requested_networks=requested_networks,
178 security_group=sg_names,184 security_group=sg_names,
179 user_data=user_data,185 user_data=user_data,
180 availability_zone=availability_zone))186 availability_zone=availability_zone))
@@ -188,6 +194,10 @@
188 raise exc.HTTPBadRequest(explanation=msg)194 raise exc.HTTPBadRequest(explanation=msg)
189 except exception.SecurityGroupNotFound as error:195 except exception.SecurityGroupNotFound as error:
190 raise exc.HTTPBadRequest(explanation=unicode(error))196 raise exc.HTTPBadRequest(explanation=unicode(error))
197 except RemoteError as err:
198 msg = "%(err_type)s: %(err_msg)s" % \
199 {'err_type': err.exc_type, 'err_msg': err.value}
200 raise exc.HTTPBadRequest(explanation=msg)
191 # Let the caller deal with unhandled exceptions.201 # Let the caller deal with unhandled exceptions.
192202
193 def _handle_quota_error(self, error):203 def _handle_quota_error(self, error):
@@ -316,6 +326,46 @@
316 raise exc.HTTPBadRequest(explanation=msg)326 raise exc.HTTPBadRequest(explanation=msg)
317 return password327 return password
318328
329 def _get_requested_networks(self, requested_networks):
330 """
331 Create a list of requested networks from the networks attribute
332 """
333 networks = []
334 for network in requested_networks:
335 try:
336 network_uuid = network['uuid']
337
338 if not utils.is_uuid_like(network_uuid):
339 msg = _("Bad networks format: network uuid is not in"
340 " proper format (%s)") % network_uuid
341 raise exc.HTTPBadRequest(explanation=msg)
342
343 #fixed IP address is optional
344 #if the fixed IP address is not provided then
345 #it will use one of the available IP address from the network
346 address = network.get('fixed_ip', None)
347 if address is not None and not utils.is_valid_ipv4(address):
348 msg = _("Invalid fixed IP address (%s)") % address
349 raise exc.HTTPBadRequest(explanation=msg)
350 # check if the network id is already present in the list,
351 # we don't want duplicate networks to be passed
352 # at the boot time
353 for id, ip in networks:
354 if id == network_uuid:
355 expl = _("Duplicate networks (%s) are not allowed")\
356 % network_uuid
357 raise exc.HTTPBadRequest(explanation=expl)
358
359 networks.append((network_uuid, address))
360 except KeyError as key:
361 expl = _('Bad network format: missing %s') % key
362 raise exc.HTTPBadRequest(explanation=expl)
363 except TypeError:
364 expl = _('Bad networks format')
365 raise exc.HTTPBadRequest(explanation=expl)
366
367 return networks
368
319369
320class ServerXMLDeserializer(wsgi.XMLDeserializer):370class ServerXMLDeserializer(wsgi.XMLDeserializer):
321 """371 """
@@ -480,6 +530,10 @@
480 if personality is not None:530 if personality is not None:
481 server["personality"] = personality531 server["personality"] = personality
482532
533 networks = self._extract_networks(server_node)
534 if networks is not None:
535 server["networks"] = networks
536
483 security_groups = self._extract_security_groups(server_node)537 security_groups = self._extract_security_groups(server_node)
484 if security_groups is not None:538 if security_groups is not None:
485 server["security_groups"] = security_groups539 server["security_groups"] = security_groups
@@ -501,6 +555,23 @@
501 else:555 else:
502 return None556 return None
503557
558 def _extract_networks(self, server_node):
559 """Marshal the networks attribute of a parsed request"""
560 node = self.find_first_child_named(server_node, "networks")
561 if node is not None:
562 networks = []
563 for network_node in self.find_children_named(node,
564 "network"):
565 item = {}
566 if network_node.hasAttribute("uuid"):
567 item["uuid"] = network_node.getAttribute("uuid")
568 if network_node.hasAttribute("fixed_ip"):
569 item["fixed_ip"] = network_node.getAttribute("fixed_ip")
570 networks.append(item)
571 return networks
572 else:
573 return None
574
504 def _extract_security_groups(self, server_node):575 def _extract_security_groups(self, server_node):
505 """Marshal the security_groups attribute of a parsed request"""576 """Marshal the security_groups attribute of a parsed request"""
506 node = self.find_first_child_named(server_node, "security_groups")577 node = self.find_first_child_named(server_node, "security_groups")
507578
=== modified file 'nova/compute/api.py'
--- nova/compute/api.py 2011-08-22 03:17:04 +0000
+++ nova/compute/api.py 2011-08-22 23:37:26 +0000
@@ -146,6 +146,16 @@
146 LOG.warn(msg)146 LOG.warn(msg)
147 raise quota.QuotaError(msg, "MetadataLimitExceeded")147 raise quota.QuotaError(msg, "MetadataLimitExceeded")
148148
149 def _check_requested_networks(self, context, requested_networks):
150 """ Check if the networks requested belongs to the project
151 and the fixed IP address for each network provided is within
152 same the network block
153 """
154 if requested_networks is None:
155 return
156
157 self.network_api.validate_networks(context, requested_networks)
158
149 def _check_create_parameters(self, context, instance_type,159 def _check_create_parameters(self, context, instance_type,
150 image_href, kernel_id=None, ramdisk_id=None,160 image_href, kernel_id=None, ramdisk_id=None,
151 min_count=None, max_count=None,161 min_count=None, max_count=None,
@@ -153,7 +163,8 @@
153 key_name=None, key_data=None, security_group='default',163 key_name=None, key_data=None, security_group='default',
154 availability_zone=None, user_data=None, metadata=None,164 availability_zone=None, user_data=None, metadata=None,
155 injected_files=None, admin_password=None, zone_blob=None,165 injected_files=None, admin_password=None, zone_blob=None,
156 reservation_id=None, access_ip_v4=None, access_ip_v6=None):166 reservation_id=None, access_ip_v4=None, access_ip_v6=None,
167 requested_networks=None):
157 """Verify all the input parameters regardless of the provisioning168 """Verify all the input parameters regardless of the provisioning
158 strategy being performed."""169 strategy being performed."""
159170
@@ -182,6 +193,7 @@
182193
183 self._check_metadata_properties_quota(context, metadata)194 self._check_metadata_properties_quota(context, metadata)
184 self._check_injected_file_quota(context, injected_files)195 self._check_injected_file_quota(context, injected_files)
196 self._check_requested_networks(context, requested_networks)
185197
186 (image_service, image_id) = nova.image.get_image_service(image_href)198 (image_service, image_id) = nova.image.get_image_service(image_href)
187 image = image_service.show(context, image_id)199 image = image_service.show(context, image_id)
@@ -400,9 +412,9 @@
400 def _ask_scheduler_to_create_instance(self, context, base_options,412 def _ask_scheduler_to_create_instance(self, context, base_options,
401 instance_type, zone_blob,413 instance_type, zone_blob,
402 availability_zone, injected_files,414 availability_zone, injected_files,
403 admin_password,415 admin_password, image,
404 image,416 instance_id=None, num_instances=1,
405 instance_id=None, num_instances=1):417 requested_networks=None):
406 """Send the run_instance request to the schedulers for processing."""418 """Send the run_instance request to the schedulers for processing."""
407 pid = context.project_id419 pid = context.project_id
408 uid = context.user_id420 uid = context.user_id
@@ -430,7 +442,8 @@
430 "request_spec": request_spec,442 "request_spec": request_spec,
431 "availability_zone": availability_zone,443 "availability_zone": availability_zone,
432 "admin_password": admin_password,444 "admin_password": admin_password,
433 "injected_files": injected_files}})445 "injected_files": injected_files,
446 "requested_networks": requested_networks}})
434447
435 def create_all_at_once(self, context, instance_type,448 def create_all_at_once(self, context, instance_type,
436 image_href, kernel_id=None, ramdisk_id=None,449 image_href, kernel_id=None, ramdisk_id=None,
@@ -440,7 +453,8 @@
440 availability_zone=None, user_data=None, metadata=None,453 availability_zone=None, user_data=None, metadata=None,
441 injected_files=None, admin_password=None, zone_blob=None,454 injected_files=None, admin_password=None, zone_blob=None,
442 reservation_id=None, block_device_mapping=None,455 reservation_id=None, block_device_mapping=None,
443 access_ip_v4=None, access_ip_v6=None):456 access_ip_v4=None, access_ip_v6=None,
457 requested_networks=None):
444 """Provision the instances by passing the whole request to458 """Provision the instances by passing the whole request to
445 the Scheduler for execution. Returns a Reservation ID459 the Scheduler for execution. Returns a Reservation ID
446 related to the creation of all of these instances."""460 related to the creation of all of these instances."""
@@ -456,14 +470,15 @@
456 key_name, key_data, security_group,470 key_name, key_data, security_group,
457 availability_zone, user_data, metadata,471 availability_zone, user_data, metadata,
458 injected_files, admin_password, zone_blob,472 injected_files, admin_password, zone_blob,
459 reservation_id, access_ip_v4, access_ip_v6)473 reservation_id, access_ip_v4, access_ip_v6,
474 requested_networks)
460475
461 self._ask_scheduler_to_create_instance(context, base_options,476 self._ask_scheduler_to_create_instance(context, base_options,
462 instance_type, zone_blob,477 instance_type, zone_blob,
463 availability_zone, injected_files,478 availability_zone, injected_files,
464 admin_password,479 admin_password, image,
465 image,480 num_instances=num_instances,
466 num_instances=num_instances)481 requested_networks=requested_networks)
467482
468 return base_options['reservation_id']483 return base_options['reservation_id']
469484
@@ -475,7 +490,8 @@
475 availability_zone=None, user_data=None, metadata=None,490 availability_zone=None, user_data=None, metadata=None,
476 injected_files=None, admin_password=None, zone_blob=None,491 injected_files=None, admin_password=None, zone_blob=None,
477 reservation_id=None, block_device_mapping=None,492 reservation_id=None, block_device_mapping=None,
478 access_ip_v4=None, access_ip_v6=None):493 access_ip_v4=None, access_ip_v6=None,
494 requested_networks=None):
479 """495 """
480 Provision the instances by sending off a series of single496 Provision the instances by sending off a series of single
481 instance requests to the Schedulers. This is fine for trival497 instance requests to the Schedulers. This is fine for trival
@@ -499,7 +515,8 @@
499 key_name, key_data, security_group,515 key_name, key_data, security_group,
500 availability_zone, user_data, metadata,516 availability_zone, user_data, metadata,
501 injected_files, admin_password, zone_blob,517 injected_files, admin_password, zone_blob,
502 reservation_id, access_ip_v4, access_ip_v6)518 reservation_id, access_ip_v4, access_ip_v6,
519 requested_networks)
503520
504 block_device_mapping = block_device_mapping or []521 block_device_mapping = block_device_mapping or []
505 instances = []522 instances = []
@@ -513,11 +530,11 @@
513 instance_id = instance['id']530 instance_id = instance['id']
514531
515 self._ask_scheduler_to_create_instance(context, base_options,532 self._ask_scheduler_to_create_instance(context, base_options,
516 instance_type, zone_blob,533 instance_type, zone_blob,
517 availability_zone, injected_files,534 availability_zone, injected_files,
518 admin_password,535 admin_password, image,
519 image,536 instance_id=instance_id,
520 instance_id=instance_id)537 requested_networks=requested_networks)
521538
522 return [dict(x.iteritems()) for x in instances]539 return [dict(x.iteritems()) for x in instances]
523540
524541
=== modified file 'nova/compute/manager.py'
--- nova/compute/manager.py 2011-08-18 20:44:30 +0000
+++ nova/compute/manager.py 2011-08-22 23:37:26 +0000
@@ -382,6 +382,8 @@
382 context = context.elevated()382 context = context.elevated()
383 instance = self.db.instance_get(context, instance_id)383 instance = self.db.instance_get(context, instance_id)
384384
385 requested_networks = kwargs.get('requested_networks', None)
386
385 if instance['name'] in self.driver.list_instances():387 if instance['name'] in self.driver.list_instances():
386 raise exception.Error(_("Instance has already been created"))388 raise exception.Error(_("Instance has already been created"))
387389
@@ -411,7 +413,8 @@
411 # will eventually also need to save the address here.413 # will eventually also need to save the address here.
412 if not FLAGS.stub_network:414 if not FLAGS.stub_network:
413 network_info = self.network_api.allocate_for_instance(context,415 network_info = self.network_api.allocate_for_instance(context,
414 instance, vpn=is_vpn)416 instance, vpn=is_vpn,
417 requested_networks=requested_networks)
415 LOG.debug(_("instance network_info: |%s|"), network_info)418 LOG.debug(_("instance network_info: |%s|"), network_info)
416 else:419 else:
417 # TODO(tr3buchet) not really sure how this should be handled.420 # TODO(tr3buchet) not really sure how this should be handled.
418421
=== modified file 'nova/db/api.py'
--- nova/db/api.py 2011-08-18 00:17:47 +0000
+++ nova/db/api.py 2011-08-22 23:37:26 +0000
@@ -323,13 +323,13 @@
323####################323####################
324324
325325
326def fixed_ip_associate(context, address, instance_id):326def fixed_ip_associate(context, address, instance_id, network_id=None):
327 """Associate fixed ip to instance.327 """Associate fixed ip to instance.
328328
329 Raises if fixed ip is not available.329 Raises if fixed ip is not available.
330330
331 """331 """
332 return IMPL.fixed_ip_associate(context, address, instance_id)332 return IMPL.fixed_ip_associate(context, address, instance_id, network_id)
333333
334334
335def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None):335def fixed_ip_associate_pool(context, network_id, instance_id=None, host=None):
@@ -396,7 +396,6 @@
396 """Create a fixed ip from the values dictionary."""396 """Create a fixed ip from the values dictionary."""
397 return IMPL.fixed_ip_update(context, address, values)397 return IMPL.fixed_ip_update(context, address, values)
398398
399
400####################399####################
401400
402401
@@ -686,7 +685,14 @@
686 return IMPL.network_get_all(context)685 return IMPL.network_get_all(context)
687686
688687
688def network_get_all_by_uuids(context, network_uuids, project_id=None):
689 """Return networks by ids."""
690 return IMPL.network_get_all_by_uuids(context, network_uuids, project_id)
691
692
689# pylint: disable=C0103693# pylint: disable=C0103
694
695
690def network_get_associated_fixed_ips(context, network_id):696def network_get_associated_fixed_ips(context, network_id):
691 """Get all network's ips that have been associated."""697 """Get all network's ips that have been associated."""
692 return IMPL.network_get_associated_fixed_ips(context, network_id)698 return IMPL.network_get_associated_fixed_ips(context, network_id)
693699
=== modified file 'nova/db/sqlalchemy/api.py'
--- nova/db/sqlalchemy/api.py 2011-08-20 20:15:05 +0000
+++ nova/db/sqlalchemy/api.py 2011-08-22 23:37:26 +0000
@@ -652,23 +652,36 @@
652###################652###################
653653
654654
655@require_context655@require_admin_context
656def fixed_ip_associate(context, address, instance_id):656def fixed_ip_associate(context, address, instance_id, network_id=None):
657 session = get_session()657 session = get_session()
658 with session.begin():658 with session.begin():
659 instance = instance_get(context, instance_id, session=session)659 network_or_none = or_(models.FixedIp.network_id == network_id,
660 models.FixedIp.network_id == None)
660 fixed_ip_ref = session.query(models.FixedIp).\661 fixed_ip_ref = session.query(models.FixedIp).\
662 filter(network_or_none).\
663 filter_by(reserved=False).\
664 filter_by(deleted=False).\
661 filter_by(address=address).\665 filter_by(address=address).\
662 filter_by(deleted=False).\
663 filter_by(instance=None).\
664 with_lockmode('update').\666 with_lockmode('update').\
665 first()667 first()
666 # NOTE(vish): if with_lockmode isn't supported, as in sqlite,668 # NOTE(vish): if with_lockmode isn't supported, as in sqlite,
667 # then this has concurrency issues669 # then this has concurrency issues
668 if not fixed_ip_ref:670 if fixed_ip_ref is None:
669 raise exception.NoMoreFixedIps()671 raise exception.FixedIpNotFoundForNetwork(address=address,
670 fixed_ip_ref.instance = instance672 network_id=network_id)
673 if fixed_ip_ref.instance is not None:
674 raise exception.FixedIpAlreadyInUse(address=address)
675
676 if not fixed_ip_ref.network:
677 fixed_ip_ref.network = network_get(context,
678 network_id,
679 session=session)
680 fixed_ip_ref.instance = instance_get(context,
681 instance_id,
682 session=session)
671 session.add(fixed_ip_ref)683 session.add(fixed_ip_ref)
684 return fixed_ip_ref['address']
672685
673686
674@require_admin_context687@require_admin_context
@@ -1755,6 +1768,40 @@
1755 return result1768 return result
17561769
17571770
1771@require_admin_context
1772def network_get_all_by_uuids(context, network_uuids, project_id=None):
1773 session = get_session()
1774 project_or_none = or_(models.Network.project_id == project_id,
1775 models.Network.project_id == None)
1776 result = session.query(models.Network).\
1777 filter(models.Network.uuid.in_(network_uuids)).\
1778 filter(project_or_none).\
1779 filter_by(deleted=False).all()
1780 if not result:
1781 raise exception.NoNetworksFound()
1782
1783 #check if host is set to all of the networks
1784 # returned in the result
1785 for network in result:
1786 if network['host'] is None:
1787 raise exception.NetworkHostNotSet(network_id=network['id'])
1788
1789 #check if the result contains all the networks
1790 #we are looking for
1791 for network_uuid in network_uuids:
1792 found = False
1793 for network in result:
1794 if network['uuid'] == network_uuid:
1795 found = True
1796 break
1797 if not found:
1798 if project_id:
1799 raise exception.NetworkNotFoundForProject(network_uuid=uuid,
1800 project_id=context.project_id)
1801 raise exception.NetworkNotFound(network_id=network_uuid)
1802
1803 return result
1804
1758# NOTE(vish): pylint complains because of the long method name, but1805# NOTE(vish): pylint complains because of the long method name, but
1759# it fits with the names of the rest of the methods1806# it fits with the names of the rest of the methods
1760# pylint: disable=C01031807# pylint: disable=C0103
17611808
=== added file 'nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py'
--- nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py 1970-01-01 00:00:00 +0000
+++ nova/db/sqlalchemy/migrate_repo/versions/040_add_uuid_to_networks.py 2011-08-22 23:37:26 +0000
@@ -0,0 +1,43 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2011 OpenStack LLC.
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16
17from sqlalchemy import Column, Integer, MetaData, String, Table
18
19from nova import utils
20
21
22meta = MetaData()
23
24networks = Table("networks", meta,
25 Column("id", Integer(), primary_key=True, nullable=False))
26uuid_column = Column("uuid", String(36))
27
28
29def upgrade(migrate_engine):
30 meta.bind = migrate_engine
31 networks.create_column(uuid_column)
32
33 rows = migrate_engine.execute(networks.select())
34 for row in rows:
35 networks_uuid = str(utils.gen_uuid())
36 migrate_engine.execute(networks.update()\
37 .where(networks.c.id == row[0])\
38 .values(uuid=networks_uuid))
39
40
41def downgrade(migrate_engine):
42 meta.bind = migrate_engine
43 networks.drop_column(uuid_column)
044
=== modified file 'nova/db/sqlalchemy/models.py'
--- nova/db/sqlalchemy/models.py 2011-08-19 15:26:32 +0000
+++ nova/db/sqlalchemy/models.py 2011-08-22 23:37:26 +0000
@@ -561,6 +561,7 @@
561561
562 project_id = Column(String(255))562 project_id = Column(String(255))
563 host = Column(String(255)) # , ForeignKey('hosts.id'))563 host = Column(String(255)) # , ForeignKey('hosts.id'))
564 uuid = Column(String(36))
564565
565566
566class VirtualInterface(BASE, NovaBase):567class VirtualInterface(BASE, NovaBase):
567568
=== modified file 'nova/exception.py'
--- nova/exception.py 2011-08-20 22:38:13 +0000
+++ nova/exception.py 2011-08-22 23:37:26 +0000
@@ -423,6 +423,15 @@
423 message = _("No networks defined.")423 message = _("No networks defined.")
424424
425425
426class NetworkNotFoundForProject(NotFound):
427 message = _("Either Network uuid %(network_uuid)s is not present or "
428 "is not assigned to the project %(project_id)s.")
429
430
431class NetworkHostNotSet(NovaException):
432 message = _("Host is not set to the network (%(network_id)s).")
433
434
426class DatastoreNotFound(NotFound):435class DatastoreNotFound(NotFound):
427 message = _("Could not find the datastore reference(s) which the VM uses.")436 message = _("Could not find the datastore reference(s) which the VM uses.")
428437
@@ -456,6 +465,19 @@
456 message = _("Host %(host)s has zero fixed ips.")465 message = _("Host %(host)s has zero fixed ips.")
457466
458467
468class FixedIpNotFoundForNetwork(FixedIpNotFound):
469 message = _("Fixed IP address (%(address)s) does not exist in "
470 "network (%(network_uuid)s).")
471
472
473class FixedIpAlreadyInUse(NovaException):
474 message = _("Fixed IP address %(address)s is already in use.")
475
476
477class FixedIpInvalid(Invalid):
478 message = _("Fixed IP address %(address)s is invalid.")
479
480
459class NoMoreFixedIps(Error):481class NoMoreFixedIps(Error):
460 message = _("Zero fixed ips available.")482 message = _("Zero fixed ips available.")
461483
462484
=== modified file 'nova/network/api.py'
--- nova/network/api.py 2011-07-27 21:39:27 +0000
+++ nova/network/api.py 2011-08-22 23:37:26 +0000
@@ -195,3 +195,12 @@
195 return rpc.call(context, FLAGS.network_topic,195 return rpc.call(context, FLAGS.network_topic,
196 {'method': 'get_instance_nw_info',196 {'method': 'get_instance_nw_info',
197 'args': args})197 'args': args})
198
199 def validate_networks(self, context, requested_networks):
200 """validate the networks passed at the time of creating
201 the server
202 """
203 args = {'networks': requested_networks}
204 return rpc.call(context, FLAGS.network_topic,
205 {'method': 'validate_networks',
206 'args': args})
198207
=== modified file 'nova/network/manager.py'
--- nova/network/manager.py 2011-08-18 06:50:50 +0000
+++ nova/network/manager.py 2011-08-22 23:37:26 +0000
@@ -131,7 +131,15 @@
131 green_pool = greenpool.GreenPool()131 green_pool = greenpool.GreenPool()
132132
133 vpn = kwargs.pop('vpn')133 vpn = kwargs.pop('vpn')
134 requested_networks = kwargs.pop('requested_networks')
135
134 for network in networks:136 for network in networks:
137 address = None
138 if requested_networks is not None:
139 for address in (fixed_ip for (uuid, fixed_ip) in \
140 requested_networks if network['uuid'] == uuid):
141 break
142
135 # NOTE(vish): if we are not multi_host pass to the network host143 # NOTE(vish): if we are not multi_host pass to the network host
136 if not network['multi_host']:144 if not network['multi_host']:
137 host = network['host']145 host = network['host']
@@ -148,6 +156,7 @@
148 args = {}156 args = {}
149 args['instance_id'] = instance_id157 args['instance_id'] = instance_id
150 args['network_id'] = network['id']158 args['network_id'] = network['id']
159 args['address'] = address
151 args['vpn'] = vpn160 args['vpn'] = vpn
152161
153 green_pool.spawn_n(rpc.call, context, topic,162 green_pool.spawn_n(rpc.call, context, topic,
@@ -155,7 +164,8 @@
155 'args': args})164 'args': args})
156 else:165 else:
157 # i am the correct host, run here166 # i am the correct host, run here
158 self.allocate_fixed_ip(context, instance_id, network, vpn=vpn)167 self.allocate_fixed_ip(context, instance_id, network,
168 vpn=vpn, address=address)
159169
160 # wait for all of the allocates (if any) to finish170 # wait for all of the allocates (if any) to finish
161 green_pool.waitall()171 green_pool.waitall()
@@ -199,6 +209,7 @@
199 """209 """
200 instance_id = kwargs.get('instance_id')210 instance_id = kwargs.get('instance_id')
201 project_id = kwargs.get('project_id')211 project_id = kwargs.get('project_id')
212 requested_networks = kwargs.get('requested_networks')
202 LOG.debug(_("floating IP allocation for instance |%s|"), instance_id,213 LOG.debug(_("floating IP allocation for instance |%s|"), instance_id,
203 context=context)214 context=context)
204 # call the next inherited class's allocate_for_instance()215 # call the next inherited class's allocate_for_instance()
@@ -380,16 +391,21 @@
380 self.compute_api.trigger_security_group_members_refresh(admin_context,391 self.compute_api.trigger_security_group_members_refresh(admin_context,
381 group_ids)392 group_ids)
382393
383 def _get_networks_for_instance(self, context, instance_id, project_id):394 def _get_networks_for_instance(self, context, instance_id, project_id,
395 requested_networks=None):
384 """Determine & return which networks an instance should connect to."""396 """Determine & return which networks an instance should connect to."""
385 # TODO(tr3buchet) maybe this needs to be updated in the future if397 # TODO(tr3buchet) maybe this needs to be updated in the future if
386 # there is a better way to determine which networks398 # there is a better way to determine which networks
387 # a non-vlan instance should connect to399 # a non-vlan instance should connect to
388 try:400 if requested_networks is not None and len(requested_networks) != 0:
389 networks = self.db.network_get_all(context)401 network_uuids = [uuid for (uuid, fixed_ip) in requested_networks]
390 except exception.NoNetworksFound:402 networks = self.db.network_get_all_by_uuids(context,
391 return []403 network_uuids)
392404 else:
405 try:
406 networks = self.db.network_get_all(context)
407 except exception.NoNetworksFound:
408 return []
393 # return only networks which are not vlan networks409 # return only networks which are not vlan networks
394 return [network for network in networks if410 return [network for network in networks if
395 not network['vlan']]411 not network['vlan']]
@@ -403,16 +419,18 @@
403 host = kwargs.pop('host')419 host = kwargs.pop('host')
404 project_id = kwargs.pop('project_id')420 project_id = kwargs.pop('project_id')
405 type_id = kwargs.pop('instance_type_id')421 type_id = kwargs.pop('instance_type_id')
422 requested_networks = kwargs.get('requested_networks')
406 vpn = kwargs.pop('vpn')423 vpn = kwargs.pop('vpn')
407 admin_context = context.elevated()424 admin_context = context.elevated()
408 LOG.debug(_("network allocations for instance %s"), instance_id,425 LOG.debug(_("network allocations for instance %s"), instance_id,
409 context=context)426 context=context)
410 networks = self._get_networks_for_instance(admin_context, instance_id,427 networks = self._get_networks_for_instance(admin_context,
411 project_id)428 instance_id, project_id,
412 LOG.warn(networks)429 requested_networks=requested_networks)
413 self._allocate_mac_addresses(context, instance_id, networks)430 self._allocate_mac_addresses(context, instance_id, networks)
414 self._allocate_fixed_ips(admin_context, instance_id, host, networks,431 self._allocate_fixed_ips(admin_context, instance_id,
415 vpn=vpn)432 host, networks, vpn=vpn,
433 requested_networks=requested_networks)
416 return self.get_instance_nw_info(context, instance_id, type_id, host)434 return self.get_instance_nw_info(context, instance_id, type_id, host)
417435
418 def deallocate_for_instance(self, context, **kwargs):436 def deallocate_for_instance(self, context, **kwargs):
@@ -570,9 +588,15 @@
570 # network_get_by_compute_host588 # network_get_by_compute_host
571 address = None589 address = None
572 if network['cidr']:590 if network['cidr']:
573 address = self.db.fixed_ip_associate_pool(context.elevated(),591 address = kwargs.get('address', None)
574 network['id'],592 if address:
575 instance_id)593 address = self.db.fixed_ip_associate(context,
594 address, instance_id,
595 network['id'])
596 else:
597 address = self.db.fixed_ip_associate_pool(context.elevated(),
598 network['id'],
599 instance_id)
576 self._do_trigger_security_group_members_refresh_for_instance(600 self._do_trigger_security_group_members_refresh_for_instance(
577 instance_id)601 instance_id)
578 get_vif = self.db.virtual_interface_get_by_instance_and_network602 get_vif = self.db.virtual_interface_get_by_instance_and_network
@@ -798,6 +822,35 @@
798 """Sets up network on this host."""822 """Sets up network on this host."""
799 raise NotImplementedError()823 raise NotImplementedError()
800824
825 def validate_networks(self, context, networks):
826 """check if the networks exists and host
827 is set to each network.
828 """
829 if networks is None or len(networks) == 0:
830 return
831
832 network_uuids = [uuid for (uuid, fixed_ip) in networks]
833
834 self._get_networks_by_uuids(context, network_uuids)
835
836 for network_uuid, address in networks:
837 # check if the fixed IP address is valid and
838 # it actually belongs to the network
839 if address is not None:
840 if not utils.is_valid_ipv4(address):
841 raise exception.FixedIpInvalid(address=address)
842
843 fixed_ip_ref = self.db.fixed_ip_get_by_address(context,
844 address)
845 if fixed_ip_ref['network']['uuid'] != network_uuid:
846 raise exception.FixedIpNotFoundForNetwork(address=address,
847 network_uuid=network_uuid)
848 if fixed_ip_ref['instance'] is not None:
849 raise exception.FixedIpAlreadyInUse(address=address)
850
851 def _get_networks_by_uuids(self, context, network_uuids):
852 return self.db.network_get_all_by_uuids(context, network_uuids)
853
801854
802class FlatManager(NetworkManager):855class FlatManager(NetworkManager):
803 """Basic network where no vlans are used.856 """Basic network where no vlans are used.
@@ -832,8 +885,16 @@
832 def _allocate_fixed_ips(self, context, instance_id, host, networks,885 def _allocate_fixed_ips(self, context, instance_id, host, networks,
833 **kwargs):886 **kwargs):
834 """Calls allocate_fixed_ip once for each network."""887 """Calls allocate_fixed_ip once for each network."""
888 requested_networks = kwargs.pop('requested_networks')
835 for network in networks:889 for network in networks:
836 self.allocate_fixed_ip(context, instance_id, network)890 address = None
891 if requested_networks is not None:
892 for address in (fixed_ip for (uuid, fixed_ip) in \
893 requested_networks if network['uuid'] == uuid):
894 break
895
896 self.allocate_fixed_ip(context, instance_id,
897 network, address=address)
837898
838 def deallocate_fixed_ip(self, context, address, **kwargs):899 def deallocate_fixed_ip(self, context, address, **kwargs):
839 """Returns a fixed ip to the pool."""900 """Returns a fixed ip to the pool."""
@@ -927,9 +988,15 @@
927 address,988 address,
928 instance_id)989 instance_id)
929 else:990 else:
930 address = self.db.fixed_ip_associate_pool(context,991 address = kwargs.get('address', None)
931 network['id'],992 if address:
932 instance_id)993 address = self.db.fixed_ip_associate(context, address,
994 instance_id,
995 network['id'])
996 else:
997 address = self.db.fixed_ip_associate_pool(context,
998 network['id'],
999 instance_id)
933 self._do_trigger_security_group_members_refresh_for_instance(1000 self._do_trigger_security_group_members_refresh_for_instance(
934 instance_id)1001 instance_id)
935 vif = self.db.virtual_interface_get_by_instance_and_network(context,1002 vif = self.db.virtual_interface_get_by_instance_and_network(context,
@@ -945,10 +1012,18 @@
945 """Force adds another network to a project."""1012 """Force adds another network to a project."""
946 self.db.network_associate(context, project_id, force=True)1013 self.db.network_associate(context, project_id, force=True)
9471014
948 def _get_networks_for_instance(self, context, instance_id, project_id):1015 def _get_networks_for_instance(self, context, instance_id, project_id,
1016 requested_networks=None):
949 """Determine which networks an instance should connect to."""1017 """Determine which networks an instance should connect to."""
950 # get networks associated with project1018 # get networks associated with project
951 return self.db.project_get_networks(context, project_id)1019 if requested_networks is not None and len(requested_networks) != 0:
1020 network_uuids = [uuid for (uuid, fixed_ip) in requested_networks]
1021 networks = self.db.network_get_all_by_uuids(context,
1022 network_uuids,
1023 project_id)
1024 else:
1025 networks = self.db.project_get_networks(context, project_id)
1026 return networks
9521027
953 def create_networks(self, context, **kwargs):1028 def create_networks(self, context, **kwargs):
954 """Create networks based on parameters."""1029 """Create networks based on parameters."""
@@ -997,6 +1072,10 @@
997 self.db.network_update(context, network_ref['id'],1072 self.db.network_update(context, network_ref['id'],
998 {'gateway_v6': gateway})1073 {'gateway_v6': gateway})
9991074
1075 def _get_networks_by_uuids(self, context, network_uuids):
1076 return self.db.network_get_all_by_uuids(context, network_uuids,
1077 context.project_id)
1078
1000 @property1079 @property
1001 def _bottom_reserved_ips(self):1080 def _bottom_reserved_ips(self):
1002 """Number of reserved ips at the bottom of the range."""1081 """Number of reserved ips at the bottom of the range."""
10031082
=== added file 'nova/tests/api/openstack/contrib/test_createserverext.py'
--- nova/tests/api/openstack/contrib/test_createserverext.py 1970-01-01 00:00:00 +0000
+++ nova/tests/api/openstack/contrib/test_createserverext.py 2011-08-22 23:37:26 +0000
@@ -0,0 +1,306 @@
1# vim: tabstop=4 shiftwidth=4 softtabstop=4
2
3# Copyright 2010-2011 OpenStack LLC.
4# All Rights Reserved.
5#
6# Licensed under the Apache License, Version 2.0 (the "License"); you may
7# not use this file except in compliance with the License. You may obtain
8# a copy of the License at
9#
10# http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15# License for the specific language governing permissions and limitations
16# under the License.
17
18import base64
19import json
20import unittest
21from xml.dom import minidom
22
23import stubout
24import webob
25
26from nova import exception
27from nova import flags
28from nova import test
29from nova import utils
30import nova.api.openstack
31from nova.api.openstack import servers
32from nova.api.openstack.contrib import createserverext
33import nova.compute.api
34
35import nova.scheduler.api
36import nova.image.fake
37import nova.rpc
38from nova.tests.api.openstack import fakes
39
40
41FLAGS = flags.FLAGS
42FLAGS.verbose = True
43
44FAKE_UUID = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
45
46FAKE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'),
47 ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', '10.0.2.12')]
48
49DUPLICATE_NETWORKS = [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12'),
50 ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', '10.0.1.12')]
51
52INVALID_NETWORKS = [('invalid', 'invalid-ip-address')]
53
54
55class CreateserverextTest(test.TestCase):
56
57 def setUp(self):
58 super(CreateserverextTest, self).setUp()
59 self.stubs = stubout.StubOutForTesting()
60 fakes.FakeAuthManager.auth_data = {}
61 fakes.FakeAuthDatabase.data = {}
62 fakes.stub_out_auth(self.stubs)
63 fakes.stub_out_image_service(self.stubs)
64 fakes.stub_out_key_pair_funcs(self.stubs)
65 self.allow_admin = FLAGS.allow_admin_api
66
67 def tearDown(self):
68 self.stubs.UnsetAll()
69 FLAGS.allow_admin_api = self.allow_admin
70 super(CreateserverextTest, self).tearDown()
71
72 def _setup_mock_compute_api(self):
73
74 class MockComputeAPI(nova.compute.API):
75
76 def __init__(self):
77 self.injected_files = None
78 self.networks = None
79
80 def create(self, *args, **kwargs):
81 if 'injected_files' in kwargs:
82 self.injected_files = kwargs['injected_files']
83 else:
84 self.injected_files = None
85
86 if 'requested_networks' in kwargs:
87 self.networks = kwargs['requested_networks']
88 else:
89 self.networks = None
90 return [{'id': '1234', 'display_name': 'fakeinstance',
91 'uuid': FAKE_UUID,
92 'created_at': "",
93 'updated_at': ""}]
94
95 def set_admin_password(self, *args, **kwargs):
96 pass
97
98 def make_stub_method(canned_return):
99 def stub_method(*args, **kwargs):
100 return canned_return
101 return stub_method
102
103 compute_api = MockComputeAPI()
104 self.stubs.Set(nova.compute, 'API', make_stub_method(compute_api))
105 self.stubs.Set(
106 nova.api.openstack.create_instance_helper.CreateInstanceHelper,
107 '_get_kernel_ramdisk_from_image', make_stub_method((1, 1)))
108 return compute_api
109
110 def _create_networks_request_dict(self, networks):
111 server = {}
112 server['name'] = 'new-server-test'
113 server['imageRef'] = 1
114 server['flavorRef'] = 1
115 if networks is not None:
116 network_list = []
117 for uuid, fixed_ip in networks:
118 network_list.append({'uuid': uuid, 'fixed_ip': fixed_ip})
119 server['networks'] = network_list
120 return {'server': server}
121
122 def _get_create_request_json(self, body_dict):
123 req = webob.Request.blank('/v1.1/123/os-create-server-ext')
124 req.headers['Content-Type'] = 'application/json'
125 req.method = 'POST'
126 req.body = json.dumps(body_dict)
127 return req
128
129 def _run_create_instance_with_mock_compute_api(self, request):
130 compute_api = self._setup_mock_compute_api()
131 response = request.get_response(fakes.wsgi_app())
132 return compute_api, response
133
134 def _format_xml_request_body(self, body_dict):
135 server = body_dict['server']
136 body_parts = []
137 body_parts.extend([
138 '<?xml version="1.0" encoding="UTF-8"?>',
139 '<server xmlns="http://docs.rackspacecloud.com/servers/api/v1.1"',
140 ' name="%s" imageRef="%s" flavorRef="%s">' % (
141 server['name'], server['imageRef'], server['flavorRef'])])
142 if 'metadata' in server:
143 metadata = server['metadata']
144 body_parts.append('<metadata>')
145 for item in metadata.iteritems():
146 body_parts.append('<meta key="%s">%s</meta>' % item)
147 body_parts.append('</metadata>')
148 if 'personality' in server:
149 personalities = server['personality']
150 body_parts.append('<personality>')
151 for file in personalities:
152 item = (file['path'], file['contents'])
153 body_parts.append('<file path="%s">%s</file>' % item)
154 body_parts.append('</personality>')
155 if 'networks' in server:
156 networks = server['networks']
157 body_parts.append('<networks>')
158 for network in networks:
159 item = (network['uuid'], network['fixed_ip'])
160 body_parts.append('<network uuid="%s" fixed_ip="%s"></network>'
161 % item)
162 body_parts.append('</networks>')
163 body_parts.append('</server>')
164 return ''.join(body_parts)
165
166 def _get_create_request_xml(self, body_dict):
167 req = webob.Request.blank('/v1.1/123/os-create-server-ext')
168 req.content_type = 'application/xml'
169 req.accept = 'application/xml'
170 req.method = 'POST'
171 req.body = self._format_xml_request_body(body_dict)
172 return req
173
174 def _create_instance_with_networks_json(self, networks):
175 body_dict = self._create_networks_request_dict(networks)
176 request = self._get_create_request_json(body_dict)
177 compute_api, response = \
178 self._run_create_instance_with_mock_compute_api(request)
179 return request, response, compute_api.networks
180
181 def _create_instance_with_networks_xml(self, networks):
182 body_dict = self._create_networks_request_dict(networks)
183 request = self._get_create_request_xml(body_dict)
184 compute_api, response = \
185 self._run_create_instance_with_mock_compute_api(request)
186 return request, response, compute_api.networks
187
188 def test_create_instance_with_no_networks(self):
189 request, response, networks = \
190 self._create_instance_with_networks_json(networks=None)
191 self.assertEquals(response.status_int, 202)
192 self.assertEquals(networks, None)
193
194 def test_create_instance_with_no_networks_xml(self):
195 request, response, networks = \
196 self._create_instance_with_networks_xml(networks=None)
197 self.assertEquals(response.status_int, 202)
198 self.assertEquals(networks, None)
199
200 def test_create_instance_with_one_network(self):
201 request, response, networks = \
202 self._create_instance_with_networks_json([FAKE_NETWORKS[0]])
203 self.assertEquals(response.status_int, 202)
204 self.assertEquals(networks, [FAKE_NETWORKS[0]])
205
206 def test_create_instance_with_one_network_xml(self):
207 request, response, networks = \
208 self._create_instance_with_networks_xml([FAKE_NETWORKS[0]])
209 self.assertEquals(response.status_int, 202)
210 self.assertEquals(networks, [FAKE_NETWORKS[0]])
211
212 def test_create_instance_with_two_networks(self):
213 request, response, networks = \
214 self._create_instance_with_networks_json(FAKE_NETWORKS)
215 self.assertEquals(response.status_int, 202)
216 self.assertEquals(networks, FAKE_NETWORKS)
217
218 def test_create_instance_with_two_networks_xml(self):
219 request, response, networks = \
220 self._create_instance_with_networks_xml(FAKE_NETWORKS)
221 self.assertEquals(response.status_int, 202)
222 self.assertEquals(networks, FAKE_NETWORKS)
223
224 def test_create_instance_with_duplicate_networks(self):
225 request, response, networks = \
226 self._create_instance_with_networks_json(DUPLICATE_NETWORKS)
227 self.assertEquals(response.status_int, 400)
228 self.assertEquals(networks, None)
229
230 def test_create_instance_with_duplicate_networks_xml(self):
231 request, response, networks = \
232 self._create_instance_with_networks_xml(DUPLICATE_NETWORKS)
233 self.assertEquals(response.status_int, 400)
234 self.assertEquals(networks, None)
235
236 def test_create_instance_with_network_no_id(self):
237 body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]])
238 del body_dict['server']['networks'][0]['uuid']
239 request = self._get_create_request_json(body_dict)
240 compute_api, response = \
241 self._run_create_instance_with_mock_compute_api(request)
242 self.assertEquals(response.status_int, 400)
243 self.assertEquals(compute_api.networks, None)
244
245 def test_create_instance_with_network_no_id_xml(self):
246 body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]])
247 request = self._get_create_request_xml(body_dict)
248 uuid = ' uuid="aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"'
249 request.body = request.body.replace(uuid, '')
250 compute_api, response = \
251 self._run_create_instance_with_mock_compute_api(request)
252 self.assertEquals(response.status_int, 400)
253 self.assertEquals(compute_api.networks, None)
254
255 def test_create_instance_with_network_invalid_id(self):
256 request, response, networks = \
257 self._create_instance_with_networks_json(INVALID_NETWORKS)
258 self.assertEquals(response.status_int, 400)
259 self.assertEquals(networks, None)
260
261 def test_create_instance_with_network_invalid_id_xml(self):
262 request, response, networks = \
263 self._create_instance_with_networks_xml(INVALID_NETWORKS)
264 self.assertEquals(response.status_int, 400)
265 self.assertEquals(networks, None)
266
267 def test_create_instance_with_network_empty_fixed_ip(self):
268 networks = [('1', '')]
269 request, response, networks = \
270 self._create_instance_with_networks_json(networks)
271 self.assertEquals(response.status_int, 400)
272 self.assertEquals(networks, None)
273
274 def test_create_instance_with_network_non_string_fixed_ip(self):
275 networks = [('1', 12345)]
276 request, response, networks = \
277 self._create_instance_with_networks_json(networks)
278 self.assertEquals(response.status_int, 400)
279 self.assertEquals(networks, None)
280
281 def test_create_instance_with_network_empty_fixed_ip_xml(self):
282 networks = [('1', '')]
283 request, response, networks = \
284 self._create_instance_with_networks_xml(networks)
285 self.assertEquals(response.status_int, 400)
286 self.assertEquals(networks, None)
287
288 def test_create_instance_with_network_no_fixed_ip(self):
289 body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]])
290 del body_dict['server']['networks'][0]['fixed_ip']
291 request = self._get_create_request_json(body_dict)
292 compute_api, response = \
293 self._run_create_instance_with_mock_compute_api(request)
294 self.assertEquals(response.status_int, 202)
295 self.assertEquals(compute_api.networks,
296 [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)])
297
298 def test_create_instance_with_network_no_fixed_ip_xml(self):
299 body_dict = self._create_networks_request_dict([FAKE_NETWORKS[0]])
300 request = self._get_create_request_xml(body_dict)
301 request.body = request.body.replace(' fixed_ip="10.0.1.12"', '')
302 compute_api, response = \
303 self._run_create_instance_with_mock_compute_api(request)
304 self.assertEquals(response.status_int, 202)
305 self.assertEquals(compute_api.networks,
306 [('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', None)])
0307
=== modified file 'nova/tests/api/openstack/test_extensions.py'
--- nova/tests/api/openstack/test_extensions.py 2011-08-19 13:54:05 +0000
+++ nova/tests/api/openstack/test_extensions.py 2011-08-22 23:37:26 +0000
@@ -85,6 +85,7 @@
85 ext_path = os.path.join(os.path.dirname(__file__), "extensions")85 ext_path = os.path.join(os.path.dirname(__file__), "extensions")
86 self.flags(osapi_extensions_path=ext_path)86 self.flags(osapi_extensions_path=ext_path)
87 self.ext_list = [87 self.ext_list = [
88 "Createserverext",
88 "FlavorExtraSpecs",89 "FlavorExtraSpecs",
89 "Floating_ips",90 "Floating_ips",
90 "Fox In Socks",91 "Fox In Socks",
9192
=== modified file 'nova/tests/api/openstack/test_servers.py'
--- nova/tests/api/openstack/test_servers.py 2011-08-22 14:19:01 +0000
+++ nova/tests/api/openstack/test_servers.py 2011-08-22 23:37:26 +0000
@@ -1890,6 +1890,29 @@
1890 res = req.get_response(fakes.wsgi_app())1890 res = req.get_response(fakes.wsgi_app())
1891 self.assertEqual(res.status_int, 400)1891 self.assertEqual(res.status_int, 400)
18921892
1893 def test_create_instance_whitespace_name(self):
1894 self._setup_for_create_instance()
1895
1896 body = {
1897 'server': {
1898 'name': ' ',
1899 'imageId': 3,
1900 'flavorId': 1,
1901 'metadata': {
1902 'hello': 'world',
1903 'open': 'stack',
1904 },
1905 'personality': {},
1906 },
1907 }
1908
1909 req = webob.Request.blank('/v1.0/servers')
1910 req.method = 'POST'
1911 req.body = json.dumps(body)
1912 req.headers["content-type"] = "application/json"
1913 res = req.get_response(fakes.wsgi_app())
1914 self.assertEqual(res.status_int, 400)
1915
1893 def test_update_server_no_body(self):1916 def test_update_server_no_body(self):
1894 req = webob.Request.blank('/v1.0/servers/1')1917 req = webob.Request.blank('/v1.0/servers/1')
1895 req.method = 'PUT'1918 req.method = 'PUT'
@@ -2829,6 +2852,164 @@
2829 }2852 }
2830 self.assertEquals(request['body'], expected)2853 self.assertEquals(request['body'], expected)
28312854
2855 def test_request_with_empty_networks(self):
2856 serial_request = """
2857<server xmlns="http://docs.openstack.org/compute/api/v1.1"
2858 name="new-server-test" imageRef="1" flavorRef="1">
2859 <networks/>
2860</server>"""
2861 request = self.deserializer.deserialize(serial_request, 'create')
2862 expected = {"server": {
2863 "name": "new-server-test",
2864 "imageRef": "1",
2865 "flavorRef": "1",
2866 "networks": []
2867 }}
2868 self.assertEquals(request['body'], expected)
2869
2870 def test_request_with_one_network(self):
2871 serial_request = """
2872<server xmlns="http://docs.openstack.org/compute/api/v1.1"
2873 name="new-server-test" imageRef="1" flavorRef="1">
2874 <networks>
2875 <network uuid="1" fixed_ip="10.0.1.12"/>
2876 </networks>
2877</server>"""
2878 request = self.deserializer.deserialize(serial_request, 'create')
2879 expected = {"server": {
2880 "name": "new-server-test",
2881 "imageRef": "1",
2882 "flavorRef": "1",
2883 "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}],
2884 }}
2885 self.assertEquals(request['body'], expected)
2886
2887 def test_request_with_two_networks(self):
2888 serial_request = """
2889<server xmlns="http://docs.openstack.org/compute/api/v1.1"
2890 name="new-server-test" imageRef="1" flavorRef="1">
2891 <networks>
2892 <network uuid="1" fixed_ip="10.0.1.12"/>
2893 <network uuid="2" fixed_ip="10.0.2.12"/>
2894 </networks>
2895</server>"""
2896 request = self.deserializer.deserialize(serial_request, 'create')
2897 expected = {"server": {
2898 "name": "new-server-test",
2899 "imageRef": "1",
2900 "flavorRef": "1",
2901 "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"},
2902 {"uuid": "2", "fixed_ip": "10.0.2.12"}],
2903 }}
2904 self.assertEquals(request['body'], expected)
2905
2906 def test_request_with_second_network_node_ignored(self):
2907 serial_request = """
2908<server xmlns="http://docs.openstack.org/compute/api/v1.1"
2909 name="new-server-test" imageRef="1" flavorRef="1">
2910 <networks>
2911 <network uuid="1" fixed_ip="10.0.1.12"/>
2912 </networks>
2913 <networks>
2914 <network uuid="2" fixed_ip="10.0.2.12"/>
2915 </networks>
2916</server>"""
2917 request = self.deserializer.deserialize(serial_request, 'create')
2918 expected = {"server": {
2919 "name": "new-server-test",
2920 "imageRef": "1",
2921 "flavorRef": "1",
2922 "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"}],
2923 }}
2924 self.assertEquals(request['body'], expected)
2925
2926 def test_request_with_one_network_missing_id(self):
2927 serial_request = """
2928<server xmlns="http://docs.openstack.org/compute/api/v1.1"
2929 name="new-server-test" imageRef="1" flavorRef="1">
2930 <networks>
2931 <network fixed_ip="10.0.1.12"/>
2932 </networks>
2933</server>"""
2934 request = self.deserializer.deserialize(serial_request, 'create')
2935 expected = {"server": {
2936 "name": "new-server-test",
2937 "imageRef": "1",
2938 "flavorRef": "1",
2939 "networks": [{"fixed_ip": "10.0.1.12"}],
2940 }}
2941 self.assertEquals(request['body'], expected)
2942
2943 def test_request_with_one_network_missing_fixed_ip(self):
2944 serial_request = """
2945<server xmlns="http://docs.openstack.org/compute/api/v1.1"
2946 name="new-server-test" imageRef="1" flavorRef="1">
2947 <networks>
2948 <network uuid="1"/>
2949 </networks>
2950</server>"""
2951 request = self.deserializer.deserialize(serial_request, 'create')
2952 expected = {"server": {
2953 "name": "new-server-test",
2954 "imageRef": "1",
2955 "flavorRef": "1",
2956 "networks": [{"uuid": "1"}],
2957 }}
2958 self.assertEquals(request['body'], expected)
2959
2960 def test_request_with_one_network_empty_id(self):
2961 serial_request = """
2962 <server xmlns="http://docs.openstack.org/compute/api/v1.1"
2963 name="new-server-test" imageRef="1" flavorRef="1">
2964 <networks>
2965 <network uuid="" fixed_ip="10.0.1.12"/>
2966 </networks>
2967 </server>"""
2968 request = self.deserializer.deserialize(serial_request, 'create')
2969 expected = {"server": {
2970 "name": "new-server-test",
2971 "imageRef": "1",
2972 "flavorRef": "1",
2973 "networks": [{"uuid": "", "fixed_ip": "10.0.1.12"}],
2974 }}
2975 self.assertEquals(request['body'], expected)
2976
2977 def test_request_with_one_network_empty_fixed_ip(self):
2978 serial_request = """
2979 <server xmlns="http://docs.openstack.org/compute/api/v1.1"
2980 name="new-server-test" imageRef="1" flavorRef="1">
2981 <networks>
2982 <network uuid="1" fixed_ip=""/>
2983 </networks>
2984 </server>"""
2985 request = self.deserializer.deserialize(serial_request, 'create')
2986 expected = {"server": {
2987 "name": "new-server-test",
2988 "imageRef": "1",
2989 "flavorRef": "1",
2990 "networks": [{"uuid": "1", "fixed_ip": ""}],
2991 }}
2992 self.assertEquals(request['body'], expected)
2993
2994 def test_request_with_networks_duplicate_ids(self):
2995 serial_request = """
2996 <server xmlns="http://docs.openstack.org/compute/api/v1.1"
2997 name="new-server-test" imageRef="1" flavorRef="1">
2998 <networks>
2999 <network uuid="1" fixed_ip="10.0.1.12"/>
3000 <network uuid="1" fixed_ip="10.0.2.12"/>
3001 </networks>
3002 </server>"""
3003 request = self.deserializer.deserialize(serial_request, 'create')
3004 expected = {"server": {
3005 "name": "new-server-test",
3006 "imageRef": "1",
3007 "flavorRef": "1",
3008 "networks": [{"uuid": "1", "fixed_ip": "10.0.1.12"},
3009 {"uuid": "1", "fixed_ip": "10.0.2.12"}],
3010 }}
3011 self.assertEquals(request['body'], expected)
3012
28323013
2833class TestAddressesXMLSerialization(test.TestCase):3014class TestAddressesXMLSerialization(test.TestCase):
28343015
@@ -2899,12 +3080,14 @@
28993080
2900 def __init__(self):3081 def __init__(self):
2901 self.injected_files = None3082 self.injected_files = None
3083 self.networks = None
29023084
2903 def create(self, *args, **kwargs):3085 def create(self, *args, **kwargs):
2904 if 'injected_files' in kwargs:3086 if 'injected_files' in kwargs:
2905 self.injected_files = kwargs['injected_files']3087 self.injected_files = kwargs['injected_files']
2906 else:3088 else:
2907 self.injected_files = None3089 self.injected_files = None
3090
2908 return [{'id': '1234', 'display_name': 'fakeinstance',3091 return [{'id': '1234', 'display_name': 'fakeinstance',
2909 'uuid': FAKE_UUID}]3092 'uuid': FAKE_UUID}]
29103093
29113094
=== modified file 'nova/tests/test_network.py'
--- nova/tests/test_network.py 2011-08-18 06:50:50 +0000
+++ nova/tests/test_network.py 2011-08-22 23:37:26 +0000
@@ -15,6 +15,7 @@
15# License for the specific language governing permissions and limitations15# License for the specific language governing permissions and limitations
16# under the License.16# under the License.
1717
18from nova import context
18from nova import db19from nova import db
19from nova import exception20from nova import exception
20from nova import log as logging21from nova import log as logging
@@ -41,6 +42,7 @@
4142
4243
43networks = [{'id': 0,44networks = [{'id': 0,
45 'uuid': "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
44 'label': 'test0',46 'label': 'test0',
45 'injected': False,47 'injected': False,
46 'multi_host': False,48 'multi_host': False,
@@ -60,6 +62,7 @@
60 'project_id': 'fake_project',62 'project_id': 'fake_project',
61 'vpn_public_address': '192.168.0.2'},63 'vpn_public_address': '192.168.0.2'},
62 {'id': 1,64 {'id': 1,
65 'uuid': "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
63 'label': 'test1',66 'label': 'test1',
64 'injected': False,67 'injected': False,
65 'multi_host': False,68 'multi_host': False,
@@ -126,6 +129,8 @@
126 super(FlatNetworkTestCase, self).setUp()129 super(FlatNetworkTestCase, self).setUp()
127 self.network = network_manager.FlatManager(host=HOST)130 self.network = network_manager.FlatManager(host=HOST)
128 self.network.db = db131 self.network.db = db
132 self.context = context.RequestContext('testuser', 'testproject',
133 is_admin=False)
129134
130 def test_get_instance_nw_info(self):135 def test_get_instance_nw_info(self):
131 self.mox.StubOutWithMock(db, 'fixed_ip_get_by_instance')136 self.mox.StubOutWithMock(db, 'fixed_ip_get_by_instance')
@@ -183,12 +188,73 @@
183 'netmask': '255.255.255.0'}]188 'netmask': '255.255.255.0'}]
184 self.assertDictListMatch(nw[1]['ips'], check)189 self.assertDictListMatch(nw[1]['ips'], check)
185190
191 def test_validate_networks(self):
192 self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
193 self.mox.StubOutWithMock(db, "fixed_ip_get_by_address")
194
195 requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
196 "192.168.1.100")]
197 db.network_get_all_by_uuids(mox.IgnoreArg(),
198 mox.IgnoreArg()).AndReturn(networks)
199
200 fixed_ips[1]['network'] = FakeModel(**networks[1])
201 fixed_ips[1]['instance'] = None
202 db.fixed_ip_get_by_address(mox.IgnoreArg(),
203 mox.IgnoreArg()).AndReturn(fixed_ips[1])
204
205 self.mox.ReplayAll()
206 self.network.validate_networks(self.context, requested_networks)
207
208 def test_validate_networks_none_requested_networks(self):
209 self.network.validate_networks(self.context, None)
210
211 def test_validate_networks_empty_requested_networks(self):
212 requested_networks = []
213 self.mox.ReplayAll()
214
215 self.network.validate_networks(self.context, requested_networks)
216
217 def test_validate_networks_invalid_fixed_ip(self):
218 self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
219 requested_networks = [(1, "192.168.0.100.1")]
220 db.network_get_all_by_uuids(mox.IgnoreArg(),
221 mox.IgnoreArg()).AndReturn(networks)
222 self.mox.ReplayAll()
223
224 self.assertRaises(exception.FixedIpInvalid,
225 self.network.validate_networks, None,
226 requested_networks)
227
228 def test_validate_networks_empty_fixed_ip(self):
229 self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
230
231 requested_networks = [(1, "")]
232 db.network_get_all_by_uuids(mox.IgnoreArg(),
233 mox.IgnoreArg()).AndReturn(networks)
234 self.mox.ReplayAll()
235
236 self.assertRaises(exception.FixedIpInvalid,
237 self.network.validate_networks,
238 None, requested_networks)
239
240 def test_validate_networks_none_fixed_ip(self):
241 self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
242
243 requested_networks = [(1, None)]
244 db.network_get_all_by_uuids(mox.IgnoreArg(),
245 mox.IgnoreArg()).AndReturn(networks)
246 self.mox.ReplayAll()
247
248 self.network.validate_networks(None, requested_networks)
249
186250
187class VlanNetworkTestCase(test.TestCase):251class VlanNetworkTestCase(test.TestCase):
188 def setUp(self):252 def setUp(self):
189 super(VlanNetworkTestCase, self).setUp()253 super(VlanNetworkTestCase, self).setUp()
190 self.network = network_manager.VlanManager(host=HOST)254 self.network = network_manager.VlanManager(host=HOST)
191 self.network.db = db255 self.network.db = db
256 self.context = context.RequestContext('testuser', 'testproject',
257 is_admin=False)
192258
193 def test_vpn_allocate_fixed_ip(self):259 def test_vpn_allocate_fixed_ip(self):
194 self.mox.StubOutWithMock(db, 'fixed_ip_associate')260 self.mox.StubOutWithMock(db, 'fixed_ip_associate')
@@ -232,7 +298,7 @@
232298
233 network = dict(networks[0])299 network = dict(networks[0])
234 network['vpn_private_address'] = '192.168.0.2'300 network['vpn_private_address'] = '192.168.0.2'
235 self.network.allocate_fixed_ip(None, 0, network)301 self.network.allocate_fixed_ip(self.context, 0, network)
236302
237 def test_create_networks_too_big(self):303 def test_create_networks_too_big(self):
238 self.assertRaises(ValueError, self.network.create_networks, None,304 self.assertRaises(ValueError, self.network.create_networks, None,
@@ -243,6 +309,68 @@
243 num_networks=100, vlan_start=1,309 num_networks=100, vlan_start=1,
244 cidr='192.168.0.1/24', network_size=100)310 cidr='192.168.0.1/24', network_size=100)
245311
312 def test_validate_networks(self):
313 self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
314 self.mox.StubOutWithMock(db, "fixed_ip_get_by_address")
315
316 requested_networks = [("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
317 "192.168.1.100")]
318 db.network_get_all_by_uuids(mox.IgnoreArg(),
319 mox.IgnoreArg(),
320 mox.IgnoreArg()).AndReturn(networks)
321
322 fixed_ips[1]['network'] = FakeModel(**networks[1])
323 fixed_ips[1]['instance'] = None
324 db.fixed_ip_get_by_address(mox.IgnoreArg(),
325 mox.IgnoreArg()).AndReturn(fixed_ips[1])
326
327 self.mox.ReplayAll()
328 self.network.validate_networks(self.context, requested_networks)
329
330 def test_validate_networks_none_requested_networks(self):
331 self.network.validate_networks(self.context, None)
332
333 def test_validate_networks_empty_requested_networks(self):
334 requested_networks = []
335 self.mox.ReplayAll()
336
337 self.network.validate_networks(self.context, requested_networks)
338
339 def test_validate_networks_invalid_fixed_ip(self):
340 self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
341 requested_networks = [(1, "192.168.0.100.1")]
342 db.network_get_all_by_uuids(mox.IgnoreArg(),
343 mox.IgnoreArg(),
344 mox.IgnoreArg()).AndReturn(networks)
345 self.mox.ReplayAll()
346
347 self.assertRaises(exception.FixedIpInvalid,
348 self.network.validate_networks, self.context,
349 requested_networks)
350
351 def test_validate_networks_empty_fixed_ip(self):
352 self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
353
354 requested_networks = [(1, "")]
355 db.network_get_all_by_uuids(mox.IgnoreArg(),
356 mox.IgnoreArg(),
357 mox.IgnoreArg()).AndReturn(networks)
358 self.mox.ReplayAll()
359
360 self.assertRaises(exception.FixedIpInvalid,
361 self.network.validate_networks,
362 self.context, requested_networks)
363
364 def test_validate_networks_none_fixed_ip(self):
365 self.mox.StubOutWithMock(db, 'network_get_all_by_uuids')
366
367 requested_networks = [(1, None)]
368 db.network_get_all_by_uuids(mox.IgnoreArg(),
369 mox.IgnoreArg(),
370 mox.IgnoreArg()).AndReturn(networks)
371 self.mox.ReplayAll()
372 self.network.validate_networks(self.context, requested_networks)
373
246374
247class CommonNetworkTestCase(test.TestCase):375class CommonNetworkTestCase(test.TestCase):
248376
249377
=== modified file 'nova/utils.py'
--- nova/utils.py 2011-08-22 12:28:12 +0000
+++ nova/utils.py 2011-08-22 23:37:26 +0000
@@ -844,3 +844,19 @@
844 return True if int(val) else False844 return True if int(val) else False
845 except ValueError:845 except ValueError:
846 return val.lower() == 'true'846 return val.lower() == 'true'
847
848
849def is_valid_ipv4(address):
850 """valid the address strictly as per format xxx.xxx.xxx.xxx.
851 where xxx is a value between 0 and 255.
852 """
853 parts = address.split(".")
854 if len(parts) != 4:
855 return False
856 for item in parts:
857 try:
858 if not 0 <= int(item) <= 255:
859 return False
860 except ValueError:
861 return False
862 return True