Merge lp:~soren/nova/ec2-security-groups into lp:~hudson-openstack/nova/trunk

Proposed by Soren Hansen
Status: Merged
Approved by: Soren Hansen
Approved revision: 321
Merged at revision: 344
Proposed branch: lp:~soren/nova/ec2-security-groups
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 1687 lines (+1134/-43)
20 files modified
nova/api/ec2/__init__.py (+2/-0)
nova/api/ec2/cloud.py (+225/-11)
nova/auth/manager.py (+2/-0)
nova/compute/manager.py (+5/-0)
nova/db/api.py (+70/-0)
nova/db/sqlalchemy/api.py (+186/-2)
nova/db/sqlalchemy/models.py (+60/-4)
nova/db/sqlalchemy/session.py (+5/-4)
nova/exception.py (+3/-0)
nova/network/manager.py (+0/-1)
nova/process.py (+1/-1)
nova/test.py (+2/-0)
nova/tests/api_unittest.py (+185/-3)
nova/tests/objectstore_unittest.py (+1/-1)
nova/tests/virt_unittest.py (+172/-12)
nova/virt/interfaces.template (+0/-1)
nova/virt/libvirt.qemu.xml.template (+4/-0)
nova/virt/libvirt.uml.xml.template (+4/-0)
nova/virt/libvirt_conn.py (+206/-3)
run_tests.py (+1/-0)
To merge this branch: bzr merge lp:~soren/nova/ec2-security-groups
Reviewer Review Type Date Requested Status
Michael Gundlach (community) Approve
Jay Pipes (community) Approve
Review via email: mp+36119@code.launchpad.net

Description of the change

This patch adds support for EC2 security groups using libvirt's nwfilter mechanism, which in turn uses iptables and ebtables on the individual compute nodes.
This has a number of benefits:
 * Inter-VM network traffic can take the fastest route through the network without our having to worry about getting it through a central firewall.
 * Not relying on a central firewall also removes a potential SPOF.
 * The filtering load is distributed, offering great scalability.

Caveats:
 * It only works with libvirt and only with libvirt drivers that support nwfilter (qemu (and thus kvm) and uml, at the moment)

To post a comment you must log in.
Revision history for this message
Jay Pipes (jaypipes) wrote :

Heya, needs a quick merge with trunk...there's conflicts.

review: Needs Fixing
Revision history for this message
Vish Ishaya (vishvananda) wrote :
Download full text (53.8 KiB)

Soren,

I merged your branch with trunk and fixed a few errors that popped up. Unfortunately libvirt is complaining heartily when i try and launch an instance and not giving very useful error messages. I'm using libvirt from the nova-core ppa
virsh fails too
error: Failed to create domain from libvirt.xml
error: Unknown failure

On Sep 21, 2010, at 3:09 AM, Soren Hansen wrote:

> Soren Hansen has proposed merging lp:~soren/nova/ec2-security-groups into lp:nova.
>
> Requested reviews:
> Nova Core (nova-core)
>
>
> This patch adds support for EC2 security groups using libvirt's nwfilter mechanism, which in turn uses iptables and ebtables on the individual compute nodes.
> This has a number of benefits:
> * Inter-VM network traffic can take the fastest route through the network without our having to worry about getting it through a central firewall.
> * Not relying on a central firewall also removes a potential SPOF.
> * The filtering load is distributed, offering great scalability.
>
> Caveats:
> * It only works with libvirt and only with libvirt drivers that support nwfilter (qemu (and thus kvm) and uml, at the moment)
> --
> https://code.launchpad.net/~soren/nova/ec2-security-groups/+merge/36119
> Your team Nova Core is requested to review the proposed merge of lp:~soren/nova/ec2-security-groups into lp:nova.
> === modified file 'nova/auth/manager.py'
> --- nova/auth/manager.py 2010-09-21 05:08:31 +0000
> +++ nova/auth/manager.py 2010-09-21 10:09:06 +0000
> @@ -490,6 +490,12 @@
> except:
> drv.delete_project(project.id)
> raise
> +
> + values = { 'name' : 'default',
> + 'description' : 'default',
> + 'user_id' : User.safe_id(manager_user),
> + 'project_id' : project.id }
> + db.security_group_create({}, values)
> return project
>
> def modify_project(self, project, manager_user=None, description=None):
> @@ -565,6 +571,16 @@
> except:
> logging.exception('Could not destroy network for %s',
> project)
> + try:
> + project_id = Project.safe_id(project)
> + groups = db.security_group_get_by_project(context={},
> + project_id=project_id)
> + for group in groups:
> + db.security_group_destroy({}, group['id'])
> + except:
> + logging.exception('Could not destroy security groups for %s',
> + project)
> +
> with self.driver() as drv:
> drv.delete_project(Project.safe_id(project))
>
>
> === modified file 'nova/compute/manager.py'
> --- nova/compute/manager.py 2010-09-13 09:15:02 +0000
> +++ nova/compute/manager.py 2010-09-21 10:09:06 +0000
> @@ -64,6 +64,11 @@
>
> @defer.inlineCallbacks
> @exception.wrap_exception
> + def refresh_security_group(self, context, security_group_id, **_kwargs):
> + yield self.driver.refresh_security_group(security_group_id)
> +
> + @defer.inlineCallbacks
> + @exception.wrap...

Revision history for this message
Soren Hansen (soren) wrote :

Erk... that's a lot of fallout. I've got it mostly done, but I have a few unit tests that are acting up. I'm also not feeling very well today, so I may not get it done today after all. :(

Revision history for this message
Soren Hansen (soren) wrote :

Phew. There we go. All cleaned up, and happy test cases again :)

Revision history for this message
Vish Ishaya (vishvananda) wrote :
Download full text (54.9 KiB)

I still can't get libvirt to actually run the instance with this patch in. Is there some configuration i need to do that is left out? I'm using libvirt from nova-core and it chokes badly trying to launch with the filter in the url.

Also, a couple typos in your latest version:

=== modified file 'nova/api/ec2/cloud.py'
--- nova/api/ec2/cloud.py 2010-09-22 20:16:12 +0000
+++ nova/api/ec2/cloud.py 2010-09-22 23:40:30 +0000
@@ -725,9 +725,9 @@

         security_groups = []
         for security_group_name in security_group_arg:
- group = db.security_group_get_by_project(context,
- context.project.id,
- security_group_name)
+ group = db.security_group_get_by_name(context,
+ context.project.id,
+ security_group_name)
             security_groups.append(group['id'])

         reservation_id = utils.generate_uid('r')
@@ -744,6 +744,7 @@
         base_options['user_data'] = kwargs.get('user_data', '')

         type_data = INSTANCE_TYPES[instance_type]
+ base_options['instance_type'] = instance_type
         base_options['memory_mb'] = type_data['memory_mb']
         base_options['vcpus'] = type_data['vcpus']
         base_options['local_gb'] = type_data['local_gb']

On Sep 21, 2010, at 3:09 AM, Soren Hansen wrote:

> Soren Hansen has proposed merging lp:~soren/nova/ec2-security-groups into lp:nova.
>
> Requested reviews:
> Nova Core (nova-core)
>
>
> This patch adds support for EC2 security groups using libvirt's nwfilter mechanism, which in turn uses iptables and ebtables on the individual compute nodes.
> This has a number of benefits:
> * Inter-VM network traffic can take the fastest route through the network without our having to worry about getting it through a central firewall.
> * Not relying on a central firewall also removes a potential SPOF.
> * The filtering load is distributed, offering great scalability.
>
> Caveats:
> * It only works with libvirt and only with libvirt drivers that support nwfilter (qemu (and thus kvm) and uml, at the moment)
> --
> https://code.launchpad.net/~soren/nova/ec2-security-groups/+merge/36119
> Your team Nova Core is requested to review the proposed merge of lp:~soren/nova/ec2-security-groups into lp:nova.
> === modified file 'nova/auth/manager.py'
> --- nova/auth/manager.py 2010-09-21 05:08:31 +0000
> +++ nova/auth/manager.py 2010-09-21 10:09:06 +0000
> @@ -490,6 +490,12 @@
> except:
> drv.delete_project(project.id)
> raise
> +
> + values = { 'name' : 'default',
> + 'description' : 'default',
> + 'user_id' : User.safe_id(manager_user),
> + 'project_id' : project.id }
> + db.security_group_create({}, values)
> return project
>
> def modify_project(self, project, manager_user=None, description=None):
> @@ -565,6 +571,16 @@
> except:
> logging.exce...

Revision history for this message
Vish Ishaya (vishvananda) wrote :
Download full text (53.5 KiB)

s/in the url/in the xml

On Sep 21, 2010, at 3:09 AM, Soren Hansen wrote:

> Soren Hansen has proposed merging lp:~soren/nova/ec2-security-groups into lp:nova.
>
> Requested reviews:
> Nova Core (nova-core)
>
>
> This patch adds support for EC2 security groups using libvirt's nwfilter mechanism, which in turn uses iptables and ebtables on the individual compute nodes.
> This has a number of benefits:
> * Inter-VM network traffic can take the fastest route through the network without our having to worry about getting it through a central firewall.
> * Not relying on a central firewall also removes a potential SPOF.
> * The filtering load is distributed, offering great scalability.
>
> Caveats:
> * It only works with libvirt and only with libvirt drivers that support nwfilter (qemu (and thus kvm) and uml, at the moment)
> --
> https://code.launchpad.net/~soren/nova/ec2-security-groups/+merge/36119
> Your team Nova Core is requested to review the proposed merge of lp:~soren/nova/ec2-security-groups into lp:nova.
> === modified file 'nova/auth/manager.py'
> --- nova/auth/manager.py 2010-09-21 05:08:31 +0000
> +++ nova/auth/manager.py 2010-09-21 10:09:06 +0000
> @@ -490,6 +490,12 @@
> except:
> drv.delete_project(project.id)
> raise
> +
> + values = { 'name' : 'default',
> + 'description' : 'default',
> + 'user_id' : User.safe_id(manager_user),
> + 'project_id' : project.id }
> + db.security_group_create({}, values)
> return project
>
> def modify_project(self, project, manager_user=None, description=None):
> @@ -565,6 +571,16 @@
> except:
> logging.exception('Could not destroy network for %s',
> project)
> + try:
> + project_id = Project.safe_id(project)
> + groups = db.security_group_get_by_project(context={},
> + project_id=project_id)
> + for group in groups:
> + db.security_group_destroy({}, group['id'])
> + except:
> + logging.exception('Could not destroy security groups for %s',
> + project)
> +
> with self.driver() as drv:
> drv.delete_project(Project.safe_id(project))
>
>
> === modified file 'nova/compute/manager.py'
> --- nova/compute/manager.py 2010-09-13 09:15:02 +0000
> +++ nova/compute/manager.py 2010-09-21 10:09:06 +0000
> @@ -64,6 +64,11 @@
>
> @defer.inlineCallbacks
> @exception.wrap_exception
> + def refresh_security_group(self, context, security_group_id, **_kwargs):
> + yield self.driver.refresh_security_group(security_group_id)
> +
> + @defer.inlineCallbacks
> + @exception.wrap_exception
> def run_instance(self, context, instance_id, **_kwargs):
> """Launch a new instance with specified options."""
> instance_ref = self.db.instance_get(context, instance_id)
>
> === modified file 'nova/db/api.py'
> --- nova/db/api.py 2010-09-21 02:17:36 +0000
> +++ nova/db/api....

Revision history for this message
Soren Hansen (soren) wrote :

As for the libvirt problem, it turns out to be apparmour that gets in the way.

Run this on the box, and you should be fine:

sudo aa-complain /usr/sbin/libvirtd

Have fun.

Revision history for this message
Vish Ishaya (vishvananda) wrote :
Download full text (56.6 KiB)

Still not quite there on the security groups. Using your aa-complain fix i still get the following error in lucid:
libvir: Network Filtererror : internal error Could not get access to ACL tech driver 'ebiptables'
ERROR:root:instance i-1: Failed to spawn
Traceback (most recent call last):
  File "/srv/cloud/nova/nova/compute/manager.py", line 91, in run_instance
    yield self.driver.spawn(instance_ref)
  File "/usr/local/lib/python2.6/dist-packages/twisted/internet/defer.py", line 823, in _inlineCallbacks
    result = g.send(result)
  File "/srv/cloud/nova/nova/virt/libvirt_conn.py", line 220, in spawn
    yield self._conn.createXML(xml, 0)
  File "/usr/lib/python2.6/dist-packages/libvirt.py", line 1289, in createXML
    if ret is None:raise libvirtError('virDomainCreateXML() failed', conn=self)
libvirtError: internal error Could not get access to ACL tech driver 'ebiptables'

I tried downloading and installing the maverick dpkg from the nova-repo
I grabbed libnl, libvirt0 and libvirt-bin and they install and run fine. Now I can actually run machines again, but every time it tries to update the rules on the compute host, I get the following:
libvir: Network Filtererror : internal error cannot create rule since iptables tool is missing.
2010-09-25 09:11:49+0000 [-] Unhandled error in Deferred:
2010-09-25 09:11:49+0000 [-] Unhandled Error
        Traceback (most recent call last):
          File "/usr/lib/python2.6/threading.py", line 504, in __bootstrap
            self.__bootstrap_inner()
          File "/usr/lib/python2.6/threading.py", line 532, in __bootstrap_inner
            self.run()
          File "/usr/lib/python2.6/threading.py", line 484, in run
            self.__target(*self.__args, **self.__kwargs)
        --- <exception caught here> ---
          File "/usr/local/lib/python2.6/dist-packages/twisted/python/threadpool.py", line 210, in _worker
            result = context.call(ctx, function, *args, **kwargs)
          File "/usr/local/lib/python2.6/dist-packages/twisted/python/context.py", line 59, in callWithContext
            return self.currentContext().callWithContext(ctx, func, *args, **kw)
          File "/usr/local/lib/python2.6/dist-packages/twisted/python/context.py", line 37, in callWithContext
            return func(*args,**kw)
          File "/usr/lib/python2.6/dist-packages/libvirt.py", line 1646, in nwfilterDefineXML
            if ret is None:raise libvirtError('virNWFilterDefineXML() failed', conn=self)
        libvirt.libvirtError: internal error cannot create rule since iptables tool is missing.

So apparently, I'm missing the 'iptables tool'. Do i need to upgrade another package to a maverick version that wasn't listed as a depend properly in the dpkg?

Or is there some way that I can get a working lucid version?

Also DHCPackets are not coming through at all. I'm thinking that it is due to not being able to set up the rules properly, but I'm a bit surprised the dhcpdiscover isn't getting out, since it is udp.

Any suggestions are most welcome, since we really need to get the security groups tested ASAP.

Thanks,
Vish

On Sep 21, 2010, at 3:09 AM, Soren Hansen wrote:

> Soren Hansen has proposed merging lp...

Revision history for this message
Soren Hansen (soren) wrote :
Download full text (3.6 KiB)

On 25-09-2010 11:34, vishvananda wrote:
> Still not quite there on the security groups. Using your aa-complain fix i still get the following error in lucid:
> libvir: Network Filtererror : internal error Could not get access to ACL tech driver 'ebiptables'
> ERROR:root:instance i-1: Failed to spawn
> Traceback (most recent call last):
> File "/srv/cloud/nova/nova/compute/manager.py", line 91, in run_instance
> yield self.driver.spawn(instance_ref)
> File "/usr/local/lib/python2.6/dist-packages/twisted/internet/defer.py", line 823, in _inlineCallbacks
> result = g.send(result)
> File "/srv/cloud/nova/nova/virt/libvirt_conn.py", line 220, in spawn
> yield self._conn.createXML(xml, 0)
> File "/usr/lib/python2.6/dist-packages/libvirt.py", line 1289, in createXML
> if ret is None:raise libvirtError('virDomainCreateXML() failed', conn=self)
> libvirtError: internal error Could not get access to ACL tech driver 'ebiptables'

Do you have ebtables installed? This corner of libvirt has horrendous
error reporting, unfortunately. You need gawk, ebtables, and iptables
installed. Well, and bash and grep, but I can't imagine you'd be missing
those.

> I tried downloading and installing the maverick dpkg from the nova-repo
> I grabbed libnl, libvirt0 and libvirt-bin and they install and run fine. Now I can actually run machines again, but every time it tries to update the rules on the compute host, I get the following:
> libvir: Network Filtererror : internal error cannot create rule since iptables tool is missing.

Glancing at libvirt's code, that error code means it either failed to
locate iptables in your $PATH, or running "iptables -L FORWARD" failed.
Do you have iptables installed and in the $PATH of the libvirtd process?
(if you're using debian or ubuntu packages, you should be fine). Same

> So apparently, I'm missing the 'iptables tool'. Do i need to upgrade another package to a maverick version that wasn't listed as a depend properly in the dpkg?

Any reasonably recent version of iptables should be fine. Lucid is
definitely reasonably recent.

> Also DHCPackets are not coming through at all.

Yeah. That was the embarassing thing I was talking about on IRC
yesterday :) When I was attempting to get the networking stuff working
and couldn't get DHCP to work I was actually using this branch, and it
turns out it filters dhcp by default. To get DHCP to work, we can either
add a reference to the allow-dhcp or (preferably) allow-dhcp-server
filter for the instance. If the latter, we need to know the IP of the
DHCP server. If we have that, we can just add
<filterref filter="allow-dhcp-server">
  <parameter name="DHCPSERVER" value="<ip of DHCP server>" />
</filterref>

> I'm thinking that it is due to not being able to set up the rules properly, but I'm a bit surprised the dhcpdiscover isn't getting out, since it is udp.

That's an artefact of the no-ip-spoofing filter (part of the
clean-traffic which is included by nova-base-filter), I think. It
refuses traffic from an IP other than the one designated in the libvirt
XML (including DHCP requests that have a NULL IP, IIRC). Adding the
above mentioned allow-dhcp(-server) filter should ...

Read more...

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

Test comment to see if it will subscribe me

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

problem running multiple tests, it may not be instance i-1. This patch fixes.

=== modified file 'nova/tests/virt_unittest.py'
--- nova/tests/virt_unittest.py 2010-09-28 08:26:29 +0000
+++ nova/tests/virt_unittest.py 2010-09-28 17:47:54 +0000
@@ -178,8 +178,14 @@
             self.defined_filters.append(name)
             return True

+ self.fake_libvirt_connection.nwfilterDefineXML = _filterDefineXMLMock
+
+ instance_ref = db.instance_create({}, {'user_id': 'fake',
+ 'project_id': 'fake'})
+ inst_id = instance_ref['id']
+
         def _ensure_all_called(_):
- instance_filter = 'nova-instance-i-1'
+ instance_filter = 'nova-instance-%s' % instance_ref['str_id']
             secgroup_filter = 'nova-secgroup-%s' % self.security_group['id']
             for required in [secgroup_filter, 'allow-dhcp-server',
                              'no-arp-spoofing', 'no-ip-spoofing',
@@ -187,11 +193,6 @@
                 self.assertTrue(required in self.recursive_depends[instance_filter],
                             "Instance's filter does not include %s" % required)

- self.fake_libvirt_connection.nwfilterDefineXML = _filterDefineXMLMock
-
- inst_id = db.instance_create({}, {'user_id': 'fake',
- 'project_id': 'fake'})['id']
-
         self.security_group = self.setup_and_return_security_group()

         db.instance_add_security_group({}, inst_id, self.security_group.id)

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

After that fix, it is an approve from me. But we really need to make sure the lucid package is easily accessible from the ppa and works out of the box. I suggest we make sure that the needed app-armor rules are in, it is the most current libvirt, and that we add dependencies on gawk, iptables, and ebtables.

Revision history for this message
Soren Hansen (soren) wrote :

I've pushed a new libvirt to the nova-core Lucid ppa. It's identical to the one in Ubuntu Maverick, except macvtap has been disabled (Lucid's kernel does not support it). This fixes the AppArmour problems.

I've added iptables, ebtables, and gawk as dependencies of nova-compute.

Revision history for this message
Jay Pipes (jaypipes) wrote :

Approved. This is excellent work. Especially on the test cases.

review: Approve
Revision history for this message
Michael Gundlach (gundlach) wrote :

Approve.

FYI db/sqlalchemy/api.py looks like it could be using dict.update instead of a tight for loop in a few places, in case you missed that.

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/db/sqlalchemy/api.py

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

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

text conflict in nova/tests/virt_unittest.py

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

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

text conflict in nova/tests/virt_unittest.py

Revision history for this message
Soren Hansen (soren) wrote :

Oh, come on!

lp:~soren/nova/ec2-security-groups updated
320. By Soren Hansen

Merge trunk.

321. By Soren Hansen

Merge trunk (that's 10 times now, count 'em\!)

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

Woo hoo!
On Oct 12, 2010 1:28 PM, <email address hidden> wrote:
> The proposal to merge lp:~soren/nova/ec2-security-groups into lp:nova has
been updated.
>
> Status: Approved => Merged
> --
> https://code.launchpad.net/~soren/nova/ec2-security-groups/+merge/36119
> You are subscribed to branch lp:~soren/nova/ec2-security-groups.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'nova/api/ec2/__init__.py'
--- nova/api/ec2/__init__.py 2010-09-30 09:47:05 +0000
+++ nova/api/ec2/__init__.py 2010-10-12 20:19:40 +0000
@@ -142,6 +142,8 @@
142 'CreateKeyPair': ['all'],142 'CreateKeyPair': ['all'],
143 'DeleteKeyPair': ['all'],143 'DeleteKeyPair': ['all'],
144 'DescribeSecurityGroups': ['all'],144 'DescribeSecurityGroups': ['all'],
145 'AuthorizeSecurityGroupIngress': ['netadmin'],
146 'RevokeSecurityGroupIngress': ['netadmin'],
145 'CreateSecurityGroup': ['netadmin'],147 'CreateSecurityGroup': ['netadmin'],
146 'DeleteSecurityGroup': ['netadmin'],148 'DeleteSecurityGroup': ['netadmin'],
147 'GetConsoleOutput': ['projectmanager', 'sysadmin'],149 'GetConsoleOutput': ['projectmanager', 'sysadmin'],
148150
=== modified file 'nova/api/ec2/cloud.py'
--- nova/api/ec2/cloud.py 2010-10-11 16:01:15 +0000
+++ nova/api/ec2/cloud.py 2010-10-12 20:19:40 +0000
@@ -28,6 +28,8 @@
28import os28import os
29import time29import time
3030
31import IPy
32
31from nova import crypto33from nova import crypto
32from nova import db34from nova import db
33from nova import exception35from nova import exception
@@ -43,6 +45,7 @@
43FLAGS = flags.FLAGS45FLAGS = flags.FLAGS
44flags.DECLARE('storage_availability_zone', 'nova.volume.manager')46flags.DECLARE('storage_availability_zone', 'nova.volume.manager')
4547
48InvalidInputException = exception.InvalidInputException
4649
47class QuotaError(exception.ApiError):50class QuotaError(exception.ApiError):
48 """Quota Exceeeded"""51 """Quota Exceeeded"""
@@ -127,6 +130,15 @@
127 result[key] = [line]130 result[key] = [line]
128 return result131 return result
129132
133 def _trigger_refresh_security_group(self, security_group):
134 nodes = set([instance['host'] for instance in security_group.instances
135 if instance['host'] is not None])
136 for node in nodes:
137 rpc.call('%s.%s' % (FLAGS.compute_topic, node),
138 { "method": "refresh_security_group",
139 "args": { "context": None,
140 "security_group_id": security_group.id}})
141
130 def get_metadata(self, address):142 def get_metadata(self, address):
131 instance_ref = db.fixed_ip_get_instance(None, address)143 instance_ref = db.fixed_ip_get_instance(None, address)
132 if instance_ref is None:144 if instance_ref is None:
@@ -246,18 +258,195 @@
246 pass258 pass
247 return True259 return True
248260
249 def describe_security_groups(self, context, group_names, **kwargs):261 def describe_security_groups(self, context, group_name=None, **kwargs):
250 groups = {'securityGroupSet': []}262 self._ensure_default_security_group(context)
251263 if context.user.is_admin():
252 # Stubbed for now to unblock other things.264 groups = db.security_group_get_all(context)
253 return groups265 else:
254266 groups = db.security_group_get_by_project(context,
255 def create_security_group(self, context, group_name, **kwargs):267 context.project.id)
268 groups = [self._format_security_group(context, g) for g in groups]
269 if not group_name is None:
270 groups = [g for g in groups if g.name in group_name]
271
272 return {'securityGroupInfo': groups }
273
274 def _format_security_group(self, context, group):
275 g = {}
276 g['groupDescription'] = group.description
277 g['groupName'] = group.name
278 g['ownerId'] = group.project_id
279 g['ipPermissions'] = []
280 for rule in group.rules:
281 r = {}
282 r['ipProtocol'] = rule.protocol
283 r['fromPort'] = rule.from_port
284 r['toPort'] = rule.to_port
285 r['groups'] = []
286 r['ipRanges'] = []
287 if rule.group_id:
288 source_group = db.security_group_get(context, rule.group_id)
289 r['groups'] += [{'groupName': source_group.name,
290 'userId': source_group.project_id}]
291 else:
292 r['ipRanges'] += [{'cidrIp': rule.cidr}]
293 g['ipPermissions'] += [r]
294 return g
295
296
297 def _authorize_revoke_rule_args_to_dict(self, context,
298 to_port=None, from_port=None,
299 ip_protocol=None, cidr_ip=None,
300 user_id=None,
301 source_security_group_name=None,
302 source_security_group_owner_id=None):
303
304 values = {}
305
306 if source_security_group_name:
307 source_project_id = self._get_source_project_id(context,
308 source_security_group_owner_id)
309
310 source_security_group = \
311 db.security_group_get_by_name(context,
312 source_project_id,
313 source_security_group_name)
314 values['group_id'] = source_security_group['id']
315 elif cidr_ip:
316 # If this fails, it throws an exception. This is what we want.
317 IPy.IP(cidr_ip)
318 values['cidr'] = cidr_ip
319 else:
320 values['cidr'] = '0.0.0.0/0'
321
322 if ip_protocol and from_port and to_port:
323 from_port = int(from_port)
324 to_port = int(to_port)
325 ip_protocol = str(ip_protocol)
326
327 if ip_protocol.upper() not in ['TCP','UDP','ICMP']:
328 raise InvalidInputException('%s is not a valid ipProtocol' %
329 (ip_protocol,))
330 if ((min(from_port, to_port) < -1) or
331 (max(from_port, to_port) > 65535)):
332 raise InvalidInputException('Invalid port range')
333
334 values['protocol'] = ip_protocol
335 values['from_port'] = from_port
336 values['to_port'] = to_port
337 else:
338 # If cidr based filtering, protocol and ports are mandatory
339 if 'cidr' in values:
340 return None
341
342 return values
343
344
345 def _security_group_rule_exists(self, security_group, values):
346 """Indicates whether the specified rule values are already
347 defined in the given security group.
348 """
349 for rule in security_group.rules:
350 if 'group_id' in values:
351 if rule['group_id'] == values['group_id']:
352 return True
353 else:
354 is_duplicate = True
355 for key in ('cidr', 'from_port', 'to_port', 'protocol'):
356 if rule[key] != values[key]:
357 is_duplicate = False
358 break
359 if is_duplicate:
360 return True
361 return False
362
363
364 def revoke_security_group_ingress(self, context, group_name, **kwargs):
365 self._ensure_default_security_group(context)
366 security_group = db.security_group_get_by_name(context,
367 context.project.id,
368 group_name)
369
370 criteria = self._authorize_revoke_rule_args_to_dict(context, **kwargs)
371 if criteria == None:
372 raise exception.ApiError("No rule for the specified parameters.")
373
374 for rule in security_group.rules:
375 match = True
376 for (k,v) in criteria.iteritems():
377 if getattr(rule, k, False) != v:
378 match = False
379 if match:
380 db.security_group_rule_destroy(context, rule['id'])
381 self._trigger_refresh_security_group(security_group)
382 return True
383 raise exception.ApiError("No rule for the specified parameters.")
384
385 # TODO(soren): This has only been tested with Boto as the client.
386 # Unfortunately, it seems Boto is using an old API
387 # for these operations, so support for newer API versions
388 # is sketchy.
389 def authorize_security_group_ingress(self, context, group_name, **kwargs):
390 self._ensure_default_security_group(context)
391 security_group = db.security_group_get_by_name(context,
392 context.project.id,
393 group_name)
394
395 values = self._authorize_revoke_rule_args_to_dict(context, **kwargs)
396 values['parent_group_id'] = security_group.id
397
398 if self._security_group_rule_exists(security_group, values):
399 raise exception.ApiError('This rule already exists in group %s' %
400 group_name)
401
402 security_group_rule = db.security_group_rule_create(context, values)
403
404 self._trigger_refresh_security_group(security_group)
405
256 return True406 return True
257407
408
409 def _get_source_project_id(self, context, source_security_group_owner_id):
410 if source_security_group_owner_id:
411 # Parse user:project for source group.
412 source_parts = source_security_group_owner_id.split(':')
413
414 # If no project name specified, assume it's same as user name.
415 # Since we're looking up by project name, the user name is not
416 # used here. It's only read for EC2 API compatibility.
417 if len(source_parts) == 2:
418 source_project_id = source_parts[1]
419 else:
420 source_project_id = source_parts[0]
421 else:
422 source_project_id = context.project.id
423
424 return source_project_id
425
426
427 def create_security_group(self, context, group_name, group_description):
428 self._ensure_default_security_group(context)
429 if db.security_group_exists(context, context.project.id, group_name):
430 raise exception.ApiError('group %s already exists' % group_name)
431
432 group = {'user_id' : context.user.id,
433 'project_id': context.project.id,
434 'name': group_name,
435 'description': group_description}
436 group_ref = db.security_group_create(context, group)
437
438 return {'securityGroupSet': [self._format_security_group(context,
439 group_ref)]}
440
441
258 def delete_security_group(self, context, group_name, **kwargs):442 def delete_security_group(self, context, group_name, **kwargs):
443 security_group = db.security_group_get_by_name(context,
444 context.project.id,
445 group_name)
446 db.security_group_destroy(context, security_group.id)
259 return True447 return True
260448
449
261 def get_console_output(self, context, instance_id, **kwargs):450 def get_console_output(self, context, instance_id, **kwargs):
262 # instance_id is passed in as a list of instances451 # instance_id is passed in as a list of instances
263 ec2_id = instance_id[0]452 ec2_id = instance_id[0]
@@ -554,6 +743,18 @@
554 "project_id": context.project.id}})743 "project_id": context.project.id}})
555 return db.queue_get_for(context, FLAGS.network_topic, host)744 return db.queue_get_for(context, FLAGS.network_topic, host)
556745
746 def _ensure_default_security_group(self, context):
747 try:
748 db.security_group_get_by_name(context,
749 context.project.id,
750 'default')
751 except exception.NotFound:
752 values = { 'name' : 'default',
753 'description' : 'default',
754 'user_id' : context.user.id,
755 'project_id' : context.project.id }
756 group = db.security_group_create(context, values)
757
557 def run_instances(self, context, **kwargs):758 def run_instances(self, context, **kwargs):
558 instance_type = kwargs.get('instance_type', 'm1.small')759 instance_type = kwargs.get('instance_type', 'm1.small')
559 if instance_type not in INSTANCE_TYPES:760 if instance_type not in INSTANCE_TYPES:
@@ -601,8 +802,17 @@
601 kwargs['key_name'])802 kwargs['key_name'])
602 key_data = key_pair_ref['public_key']803 key_data = key_pair_ref['public_key']
603804
604 # TODO: Get the real security group of launch in here805 security_group_arg = kwargs.get('security_group', ["default"])
605 security_group = "default"806 if not type(security_group_arg) is list:
807 security_group_arg = [security_group_arg]
808
809 security_groups = []
810 self._ensure_default_security_group(context)
811 for security_group_name in security_group_arg:
812 group = db.security_group_get_by_name(context,
813 context.project.id,
814 security_group_name)
815 security_groups.append(group['id'])
606816
607 reservation_id = utils.generate_uid('r')817 reservation_id = utils.generate_uid('r')
608 base_options = {}818 base_options = {}
@@ -616,12 +826,12 @@
616 base_options['user_id'] = context.user.id826 base_options['user_id'] = context.user.id
617 base_options['project_id'] = context.project.id827 base_options['project_id'] = context.project.id
618 base_options['user_data'] = kwargs.get('user_data', '')828 base_options['user_data'] = kwargs.get('user_data', '')
619 base_options['security_group'] = security_group829
620 base_options['instance_type'] = instance_type
621 base_options['display_name'] = kwargs.get('display_name')830 base_options['display_name'] = kwargs.get('display_name')
622 base_options['display_description'] = kwargs.get('display_description')831 base_options['display_description'] = kwargs.get('display_description')
623832
624 type_data = INSTANCE_TYPES[instance_type]833 type_data = INSTANCE_TYPES[instance_type]
834 base_options['instance_type'] = instance_type
625 base_options['memory_mb'] = type_data['memory_mb']835 base_options['memory_mb'] = type_data['memory_mb']
626 base_options['vcpus'] = type_data['vcpus']836 base_options['vcpus'] = type_data['vcpus']
627 base_options['local_gb'] = type_data['local_gb']837 base_options['local_gb'] = type_data['local_gb']
@@ -630,6 +840,10 @@
630 instance_ref = db.instance_create(context, base_options)840 instance_ref = db.instance_create(context, base_options)
631 inst_id = instance_ref['id']841 inst_id = instance_ref['id']
632842
843 for security_group_id in security_groups:
844 db.instance_add_security_group(context, inst_id,
845 security_group_id)
846
633 inst = {}847 inst = {}
634 inst['mac_address'] = utils.generate_mac()848 inst['mac_address'] = utils.generate_mac()
635 inst['launch_index'] = num849 inst['launch_index'] = num
636850
=== modified file 'nova/auth/manager.py'
--- nova/auth/manager.py 2010-10-12 14:23:24 +0000
+++ nova/auth/manager.py 2010-10-12 20:19:40 +0000
@@ -490,6 +490,7 @@
490 except:490 except:
491 drv.delete_project(project.id)491 drv.delete_project(project.id)
492 raise492 raise
493
493 return project494 return project
494495
495 def modify_project(self, project, manager_user=None, description=None):496 def modify_project(self, project, manager_user=None, description=None):
@@ -565,6 +566,7 @@
565 except:566 except:
566 logging.exception('Could not destroy network for %s',567 logging.exception('Could not destroy network for %s',
567 project)568 project)
569
568 with self.driver() as drv:570 with self.driver() as drv:
569 drv.delete_project(Project.safe_id(project))571 drv.delete_project(Project.safe_id(project))
570572
571573
=== modified file 'nova/compute/manager.py'
--- nova/compute/manager.py 2010-10-12 17:42:43 +0000
+++ nova/compute/manager.py 2010-10-12 20:19:40 +0000
@@ -64,6 +64,11 @@
6464
65 @defer.inlineCallbacks65 @defer.inlineCallbacks
66 @exception.wrap_exception66 @exception.wrap_exception
67 def refresh_security_group(self, context, security_group_id, **_kwargs):
68 yield self.driver.refresh_security_group(security_group_id)
69
70 @defer.inlineCallbacks
71 @exception.wrap_exception
67 def run_instance(self, context, instance_id, **_kwargs):72 def run_instance(self, context, instance_id, **_kwargs):
68 """Launch a new instance with specified options."""73 """Launch a new instance with specified options."""
69 instance_ref = self.db.instance_get(context, instance_id)74 instance_ref = self.db.instance_get(context, instance_id)
7075
=== modified file 'nova/db/api.py'
--- nova/db/api.py 2010-10-12 14:23:24 +0000
+++ nova/db/api.py 2010-10-12 20:19:40 +0000
@@ -304,6 +304,11 @@
304 return IMPL.instance_update(context, instance_id, values)304 return IMPL.instance_update(context, instance_id, values)
305305
306306
307def instance_add_security_group(context, instance_id, security_group_id):
308 """Associate the given security group with the given instance"""
309 return IMPL.instance_add_security_group(context, instance_id, security_group_id)
310
311
307###################312###################
308313
309314
@@ -571,6 +576,71 @@
571 return IMPL.volume_update(context, volume_id, values)576 return IMPL.volume_update(context, volume_id, values)
572577
573578
579####################
580
581
582def security_group_get_all(context):
583 """Get all security groups"""
584 return IMPL.security_group_get_all(context)
585
586
587def security_group_get(context, security_group_id):
588 """Get security group by its internal id"""
589 return IMPL.security_group_get(context, security_group_id)
590
591
592def security_group_get_by_name(context, project_id, group_name):
593 """Returns a security group with the specified name from a project"""
594 return IMPL.security_group_get_by_name(context, project_id, group_name)
595
596
597def security_group_get_by_project(context, project_id):
598 """Get all security groups belonging to a project"""
599 return IMPL.security_group_get_by_project(context, project_id)
600
601
602def security_group_get_by_instance(context, instance_id):
603 """Get security groups to which the instance is assigned"""
604 return IMPL.security_group_get_by_instance(context, instance_id)
605
606
607def security_group_exists(context, project_id, group_name):
608 """Indicates if a group name exists in a project"""
609 return IMPL.security_group_exists(context, project_id, group_name)
610
611
612def security_group_create(context, values):
613 """Create a new security group"""
614 return IMPL.security_group_create(context, values)
615
616
617def security_group_destroy(context, security_group_id):
618 """Deletes a security group"""
619 return IMPL.security_group_destroy(context, security_group_id)
620
621
622def security_group_destroy_all(context):
623 """Deletes a security group"""
624 return IMPL.security_group_destroy_all(context)
625
626
627####################
628
629
630def security_group_rule_create(context, values):
631 """Create a new security group"""
632 return IMPL.security_group_rule_create(context, values)
633
634
635def security_group_rule_get_by_security_group(context, security_group_id):
636 """Get all rules for a a given security group"""
637 return IMPL.security_group_rule_get_by_security_group(context, security_group_id)
638
639def security_group_rule_destroy(context, security_group_rule_id):
640 """Deletes a security group rule"""
641 return IMPL.security_group_rule_destroy(context, security_group_rule_id)
642
643
574###################644###################
575645
576646
577647
=== modified file 'nova/db/sqlalchemy/api.py'
--- nova/db/sqlalchemy/api.py 2010-10-05 19:52:12 +0000
+++ nova/db/sqlalchemy/api.py 2010-10-12 20:19:40 +0000
@@ -29,8 +29,11 @@
29from nova.db.sqlalchemy.session import get_session29from nova.db.sqlalchemy.session import get_session
30from sqlalchemy import or_30from sqlalchemy import or_
31from sqlalchemy.exc import IntegrityError31from sqlalchemy.exc import IntegrityError
32from sqlalchemy.orm import joinedload, joinedload_all32from sqlalchemy.orm import joinedload
33from sqlalchemy.sql import exists, func33from sqlalchemy.orm import joinedload_all
34from sqlalchemy.sql import exists
35from sqlalchemy.sql import func
36from sqlalchemy.orm.exc import NoResultFound
3437
35FLAGS = flags.FLAGS38FLAGS = flags.FLAGS
3639
@@ -571,11 +574,13 @@
571574
572 if is_admin_context(context):575 if is_admin_context(context):
573 result = session.query(models.Instance576 result = session.query(models.Instance
577 ).options(joinedload('security_groups')
574 ).filter_by(id=instance_id578 ).filter_by(id=instance_id
575 ).filter_by(deleted=can_read_deleted(context)579 ).filter_by(deleted=can_read_deleted(context)
576 ).first()580 ).first()
577 elif is_user_context(context):581 elif is_user_context(context):
578 result = session.query(models.Instance582 result = session.query(models.Instance
583 ).options(joinedload('security_groups')
579 ).filter_by(project_id=context.project.id584 ).filter_by(project_id=context.project.id
580 ).filter_by(id=instance_id585 ).filter_by(id=instance_id
581 ).filter_by(deleted=False586 ).filter_by(deleted=False
@@ -591,6 +596,7 @@
591 session = get_session()596 session = get_session()
592 return session.query(models.Instance597 return session.query(models.Instance
593 ).options(joinedload_all('fixed_ip.floating_ips')598 ).options(joinedload_all('fixed_ip.floating_ips')
599 ).options(joinedload('security_groups')
594 ).filter_by(deleted=can_read_deleted(context)600 ).filter_by(deleted=can_read_deleted(context)
595 ).all()601 ).all()
596602
@@ -600,6 +606,7 @@
600 session = get_session()606 session = get_session()
601 return session.query(models.Instance607 return session.query(models.Instance
602 ).options(joinedload_all('fixed_ip.floating_ips')608 ).options(joinedload_all('fixed_ip.floating_ips')
609 ).options(joinedload('security_groups')
603 ).filter_by(deleted=can_read_deleted(context)610 ).filter_by(deleted=can_read_deleted(context)
604 ).filter_by(user_id=user_id611 ).filter_by(user_id=user_id
605 ).all()612 ).all()
@@ -612,6 +619,7 @@
612 session = get_session()619 session = get_session()
613 return session.query(models.Instance620 return session.query(models.Instance
614 ).options(joinedload_all('fixed_ip.floating_ips')621 ).options(joinedload_all('fixed_ip.floating_ips')
622 ).options(joinedload('security_groups')
615 ).filter_by(project_id=project_id623 ).filter_by(project_id=project_id
616 ).filter_by(deleted=can_read_deleted(context)624 ).filter_by(deleted=can_read_deleted(context)
617 ).all()625 ).all()
@@ -624,12 +632,14 @@
624 if is_admin_context(context):632 if is_admin_context(context):
625 return session.query(models.Instance633 return session.query(models.Instance
626 ).options(joinedload_all('fixed_ip.floating_ips')634 ).options(joinedload_all('fixed_ip.floating_ips')
635 ).options(joinedload('security_groups')
627 ).filter_by(reservation_id=reservation_id636 ).filter_by(reservation_id=reservation_id
628 ).filter_by(deleted=can_read_deleted(context)637 ).filter_by(deleted=can_read_deleted(context)
629 ).all()638 ).all()
630 elif is_user_context(context):639 elif is_user_context(context):
631 return session.query(models.Instance640 return session.query(models.Instance
632 ).options(joinedload_all('fixed_ip.floating_ips')641 ).options(joinedload_all('fixed_ip.floating_ips')
642 ).options(joinedload('security_groups')
633 ).filter_by(project_id=context.project.id643 ).filter_by(project_id=context.project.id
634 ).filter_by(reservation_id=reservation_id644 ).filter_by(reservation_id=reservation_id
635 ).filter_by(deleted=False645 ).filter_by(deleted=False
@@ -642,11 +652,13 @@
642652
643 if is_admin_context(context):653 if is_admin_context(context):
644 result = session.query(models.Instance654 result = session.query(models.Instance
655 ).options(joinedload('security_groups')
645 ).filter_by(internal_id=internal_id656 ).filter_by(internal_id=internal_id
646 ).filter_by(deleted=can_read_deleted(context)657 ).filter_by(deleted=can_read_deleted(context)
647 ).first()658 ).first()
648 elif is_user_context(context):659 elif is_user_context(context):
649 result = session.query(models.Instance660 result = session.query(models.Instance
661 ).options(joinedload('security_groups')
650 ).filter_by(project_id=context.project.id662 ).filter_by(project_id=context.project.id
651 ).filter_by(internal_id=internal_id663 ).filter_by(internal_id=internal_id
652 ).filter_by(deleted=False664 ).filter_by(deleted=False
@@ -718,6 +730,18 @@
718 instance_ref.save(session=session)730 instance_ref.save(session=session)
719731
720732
733def instance_add_security_group(context, instance_id, security_group_id):
734 """Associate the given security group with the given instance"""
735 session = get_session()
736 with session.begin():
737 instance_ref = instance_get(context, instance_id, session=session)
738 security_group_ref = security_group_get(context,
739 security_group_id,
740 session=session)
741 instance_ref.security_groups += [security_group_ref]
742 instance_ref.save(session=session)
743
744
721###################745###################
722746
723747
@@ -1192,6 +1216,7 @@
11921216
1193@require_admin_context1217@require_admin_context
1194def volume_get_all(context):1218def volume_get_all(context):
1219 session = get_session()
1195 return session.query(models.Volume1220 return session.query(models.Volume
1196 ).filter_by(deleted=can_read_deleted(context)1221 ).filter_by(deleted=can_read_deleted(context)
1197 ).all()1222 ).all()
@@ -1282,6 +1307,163 @@
1282###################1307###################
12831308
12841309
1310@require_context
1311def security_group_get_all(context):
1312 session = get_session()
1313 return session.query(models.SecurityGroup
1314 ).filter_by(deleted=can_read_deleted(context)
1315 ).options(joinedload_all('rules')
1316 ).all()
1317
1318
1319@require_context
1320def security_group_get(context, security_group_id, session=None):
1321 if not session:
1322 session = get_session()
1323 if is_admin_context(context):
1324 result = session.query(models.SecurityGroup
1325 ).filter_by(deleted=can_read_deleted(context),
1326 ).filter_by(id=security_group_id
1327 ).options(joinedload_all('rules')
1328 ).first()
1329 else:
1330 result = session.query(models.SecurityGroup
1331 ).filter_by(deleted=False
1332 ).filter_by(id=security_group_id
1333 ).filter_by(project_id=context.project_id
1334 ).options(joinedload_all('rules')
1335 ).first()
1336 if not result:
1337 raise exception.NotFound("No secuity group with id %s" %
1338 security_group_id)
1339 return result
1340
1341
1342@require_context
1343def security_group_get_by_name(context, project_id, group_name):
1344 session = get_session()
1345 result = session.query(models.SecurityGroup
1346 ).filter_by(project_id=project_id
1347 ).filter_by(name=group_name
1348 ).filter_by(deleted=False
1349 ).options(joinedload_all('rules')
1350 ).options(joinedload_all('instances')
1351 ).first()
1352 if not result:
1353 raise exception.NotFound(
1354 'No security group named %s for project: %s' \
1355 % (group_name, project_id))
1356 return result
1357
1358
1359@require_context
1360def security_group_get_by_project(context, project_id):
1361 session = get_session()
1362 return session.query(models.SecurityGroup
1363 ).filter_by(project_id=project_id
1364 ).filter_by(deleted=False
1365 ).options(joinedload_all('rules')
1366 ).all()
1367
1368
1369@require_context
1370def security_group_get_by_instance(context, instance_id):
1371 session = get_session()
1372 return session.query(models.SecurityGroup
1373 ).filter_by(deleted=False
1374 ).options(joinedload_all('rules')
1375 ).join(models.SecurityGroup.instances
1376 ).filter_by(id=instance_id
1377 ).filter_by(deleted=False
1378 ).all()
1379
1380
1381@require_context
1382def security_group_exists(context, project_id, group_name):
1383 try:
1384 group = security_group_get_by_name(context, project_id, group_name)
1385 return group != None
1386 except exception.NotFound:
1387 return False
1388
1389
1390@require_context
1391def security_group_create(context, values):
1392 security_group_ref = models.SecurityGroup()
1393 # FIXME(devcamcar): Unless I do this, rules fails with lazy load exception
1394 # once save() is called. This will get cleaned up in next orm pass.
1395 security_group_ref.rules
1396 for (key, value) in values.iteritems():
1397 security_group_ref[key] = value
1398 security_group_ref.save()
1399 return security_group_ref
1400
1401
1402@require_context
1403def security_group_destroy(context, security_group_id):
1404 session = get_session()
1405 with session.begin():
1406 # TODO(vish): do we have to use sql here?
1407 session.execute('update security_groups set deleted=1 where id=:id',
1408 {'id': security_group_id})
1409 session.execute('update security_group_rules set deleted=1 '
1410 'where group_id=:id',
1411 {'id': security_group_id})
1412
1413@require_context
1414def security_group_destroy_all(context, session=None):
1415 if not session:
1416 session = get_session()
1417 with session.begin():
1418 # TODO(vish): do we have to use sql here?
1419 session.execute('update security_groups set deleted=1')
1420 session.execute('update security_group_rules set deleted=1')
1421
1422
1423###################
1424
1425
1426@require_context
1427def security_group_rule_get(context, security_group_rule_id, session=None):
1428 if not session:
1429 session = get_session()
1430 if is_admin_context(context):
1431 result = session.query(models.SecurityGroupIngressRule
1432 ).filter_by(deleted=can_read_deleted(context)
1433 ).filter_by(id=security_group_rule_id
1434 ).first()
1435 else:
1436 # TODO(vish): Join to group and check for project_id
1437 result = session.query(models.SecurityGroupIngressRule
1438 ).filter_by(deleted=False
1439 ).filter_by(id=security_group_rule_id
1440 ).first()
1441 if not result:
1442 raise exception.NotFound("No secuity group rule with id %s" %
1443 security_group_rule_id)
1444 return result
1445
1446
1447@require_context
1448def security_group_rule_create(context, values):
1449 security_group_rule_ref = models.SecurityGroupIngressRule()
1450 for (key, value) in values.iteritems():
1451 security_group_rule_ref[key] = value
1452 security_group_rule_ref.save()
1453 return security_group_rule_ref
1454
1455@require_context
1456def security_group_rule_destroy(context, security_group_rule_id):
1457 session = get_session()
1458 with session.begin():
1459 security_group_rule = security_group_rule_get(context,
1460 security_group_rule_id,
1461 session=session)
1462 security_group_rule.delete(session=session)
1463
1464
1465###################
1466
1285@require_admin_context1467@require_admin_context
1286def user_get(context, id, session=None):1468def user_get(context, id, session=None):
1287 if not session:1469 if not session:
@@ -1491,6 +1673,8 @@
1491###################1673###################
14921674
14931675
1676
1677@require_admin_context
1494def host_get_networks(context, host):1678def host_get_networks(context, host):
1495 session = get_session()1679 session = get_session()
1496 with session.begin():1680 with session.begin():
14971681
=== modified file 'nova/db/sqlalchemy/models.py'
--- nova/db/sqlalchemy/models.py 2010-10-12 19:02:24 +0000
+++ nova/db/sqlalchemy/models.py 2010-10-12 20:19:40 +0000
@@ -187,7 +187,6 @@
187 launch_index = Column(Integer)187 launch_index = Column(Integer)
188 key_name = Column(String(255))188 key_name = Column(String(255))
189 key_data = Column(Text)189 key_data = Column(Text)
190 security_group = Column(String(255))
191190
192 state = Column(Integer)191 state = Column(Integer)
193 state_description = Column(String(255))192 state_description = Column(String(255))
@@ -289,10 +288,66 @@
289 'ExportDevice.deleted==False)')288 'ExportDevice.deleted==False)')
290289
291290
291class SecurityGroupInstanceAssociation(BASE, NovaBase):
292 __tablename__ = 'security_group_instance_association'
293 id = Column(Integer, primary_key=True)
294 security_group_id = Column(Integer, ForeignKey('security_groups.id'))
295 instance_id = Column(Integer, ForeignKey('instances.id'))
296
297
298class SecurityGroup(BASE, NovaBase):
299 """Represents a security group"""
300 __tablename__ = 'security_groups'
301 id = Column(Integer, primary_key=True)
302
303 name = Column(String(255))
304 description = Column(String(255))
305 user_id = Column(String(255))
306 project_id = Column(String(255))
307
308 instances = relationship(Instance,
309 secondary="security_group_instance_association",
310 primaryjoin="and_(SecurityGroup.id == SecurityGroupInstanceAssociation.security_group_id,"
311 "SecurityGroup.deleted == False)",
312 secondaryjoin="and_(SecurityGroupInstanceAssociation.instance_id == Instance.id,"
313 "Instance.deleted == False)",
314 backref='security_groups')
315
316 @property
317 def user(self):
318 return auth.manager.AuthManager().get_user(self.user_id)
319
320 @property
321 def project(self):
322 return auth.manager.AuthManager().get_project(self.project_id)
323
324
325class SecurityGroupIngressRule(BASE, NovaBase):
326 """Represents a rule in a security group"""
327 __tablename__ = 'security_group_rules'
328 id = Column(Integer, primary_key=True)
329
330 parent_group_id = Column(Integer, ForeignKey('security_groups.id'))
331 parent_group = relationship("SecurityGroup", backref="rules",
332 foreign_keys=parent_group_id,
333 primaryjoin="and_(SecurityGroupIngressRule.parent_group_id == SecurityGroup.id,"
334 "SecurityGroupIngressRule.deleted == False)")
335
336 protocol = Column(String(5)) # "tcp", "udp", or "icmp"
337 from_port = Column(Integer)
338 to_port = Column(Integer)
339 cidr = Column(String(255))
340
341 # Note: This is not the parent SecurityGroup. It's SecurityGroup we're
342 # granting access for.
343 group_id = Column(Integer, ForeignKey('security_groups.id'))
344
345
292class KeyPair(BASE, NovaBase):346class KeyPair(BASE, NovaBase):
293 """Represents a public key pair for ssh"""347 """Represents a public key pair for ssh"""
294 __tablename__ = 'key_pairs'348 __tablename__ = 'key_pairs'
295 id = Column(Integer, primary_key=True)349 id = Column(Integer, primary_key=True)
350
296 name = Column(String(255))351 name = Column(String(255))
297352
298 user_id = Column(String(255))353 user_id = Column(String(255))
@@ -461,9 +516,10 @@
461def register_models():516def register_models():
462 """Register Models and create metadata"""517 """Register Models and create metadata"""
463 from sqlalchemy import create_engine518 from sqlalchemy import create_engine
464 models = (Service, Instance, Volume, ExportDevice,519 models = (Service, Instance, Volume, ExportDevice, FixedIp,
465 FixedIp, FloatingIp, Network, NetworkIndex,520 FloatingIp, Network, NetworkIndex, SecurityGroup,
466 AuthToken, UserProjectAssociation, User, Project) # , Image, Host)521 SecurityGroupIngressRule, SecurityGroupInstanceAssociation,
522 AuthToken, User, Project) # , Image, Host
467 engine = create_engine(FLAGS.sql_connection, echo=False)523 engine = create_engine(FLAGS.sql_connection, echo=False)
468 for model in models:524 for model in models:
469 model.metadata.create_all(engine)525 model.metadata.create_all(engine)
470526
=== modified file 'nova/db/sqlalchemy/session.py'
--- nova/db/sqlalchemy/session.py 2010-09-08 02:48:12 +0000
+++ nova/db/sqlalchemy/session.py 2010-10-12 20:19:40 +0000
@@ -36,7 +36,8 @@
36 if not _MAKER:36 if not _MAKER:
37 if not _ENGINE:37 if not _ENGINE:
38 _ENGINE = create_engine(FLAGS.sql_connection, echo=False)38 _ENGINE = create_engine(FLAGS.sql_connection, echo=False)
39 _MAKER = sessionmaker(bind=_ENGINE,39 _MAKER = (sessionmaker(bind=_ENGINE,
40 autocommit=autocommit,40 autocommit=autocommit,
41 expire_on_commit=expire_on_commit)41 expire_on_commit=expire_on_commit))
42 return _MAKER()42 session = _MAKER()
43 return session
4344
=== modified file 'nova/exception.py'
--- nova/exception.py 2010-09-08 04:53:40 +0000
+++ nova/exception.py 2010-10-12 20:19:40 +0000
@@ -69,6 +69,9 @@
69class Invalid(Error):69class Invalid(Error):
70 pass70 pass
7171
72class InvalidInputException(Error):
73 pass
74
7275
73def wrap_exception(f):76def wrap_exception(f):
74 def _wrap(*args, **kw):77 def _wrap(*args, **kw):
7578
=== modified file 'nova/network/manager.py'
--- nova/network/manager.py 2010-10-04 07:38:16 +0000
+++ nova/network/manager.py 2010-10-12 20:19:40 +0000
@@ -211,7 +211,6 @@
211 # in the datastore?211 # in the datastore?
212 net = {}212 net = {}
213 net['injected'] = True213 net['injected'] = True
214 net['network_str'] = FLAGS.flat_network_network
215 net['netmask'] = FLAGS.flat_network_netmask214 net['netmask'] = FLAGS.flat_network_netmask
216 net['bridge'] = FLAGS.flat_network_bridge215 net['bridge'] = FLAGS.flat_network_bridge
217 net['gateway'] = FLAGS.flat_network_gateway216 net['gateway'] = FLAGS.flat_network_gateway
218217
=== modified file 'nova/process.py'
--- nova/process.py 2010-09-11 15:16:16 +0000
+++ nova/process.py 2010-10-12 20:19:40 +0000
@@ -113,7 +113,7 @@
113 if self.started_deferred:113 if self.started_deferred:
114 self.started_deferred.callback(self)114 self.started_deferred.callback(self)
115 if self.process_input:115 if self.process_input:
116 self.transport.write(self.process_input)116 self.transport.write(str(self.process_input))
117 self.transport.closeStdin()117 self.transport.closeStdin()
118118
119def get_process_output(executable, args=None, env=None, path=None,119def get_process_output(executable, args=None, env=None, path=None,
120120
=== modified file 'nova/test.py'
--- nova/test.py 2010-09-25 02:25:12 +0000
+++ nova/test.py 2010-10-12 20:19:40 +0000
@@ -31,6 +31,7 @@
31from twisted.internet import defer31from twisted.internet import defer
32from twisted.trial import unittest32from twisted.trial import unittest
3333
34from nova import db
34from nova import fakerabbit35from nova import fakerabbit
35from nova import flags36from nova import flags
36from nova import rpc37from nova import rpc
@@ -83,6 +84,7 @@
8384
84 if FLAGS.fake_rabbit:85 if FLAGS.fake_rabbit:
85 fakerabbit.reset_all()86 fakerabbit.reset_all()
87 db.security_group_destroy_all(None)
8688
87 super(TrialTestCase, self).tearDown()89 super(TrialTestCase, self).tearDown()
8890
8991
=== modified file 'nova/tests/api_unittest.py'
--- nova/tests/api_unittest.py 2010-09-23 14:45:21 +0000
+++ nova/tests/api_unittest.py 2010-10-12 20:19:40 +0000
@@ -91,6 +91,9 @@
91 self.host = '127.0.0.1'91 self.host = '127.0.0.1'
9292
93 self.app = api.API()93 self.app = api.API()
94
95 def expect_http(self, host=None, is_secure=False):
96 """Returns a new EC2 connection"""
94 self.ec2 = boto.connect_ec2(97 self.ec2 = boto.connect_ec2(
95 aws_access_key_id='fake',98 aws_access_key_id='fake',
96 aws_secret_access_key='fake',99 aws_secret_access_key='fake',
@@ -100,9 +103,6 @@
100 path='/services/Cloud')103 path='/services/Cloud')
101104
102 self.mox.StubOutWithMock(self.ec2, 'new_http_connection')105 self.mox.StubOutWithMock(self.ec2, 'new_http_connection')
103
104 def expect_http(self, host=None, is_secure=False):
105 """Returns a new EC2 connection"""
106 http = FakeHttplibConnection(106 http = FakeHttplibConnection(
107 self.app, '%s:8773' % (self.host), False)107 self.app, '%s:8773' % (self.host), False)
108 # pylint: disable-msg=E1103108 # pylint: disable-msg=E1103
@@ -138,3 +138,185 @@
138 self.assertEquals(len(results), 1)138 self.assertEquals(len(results), 1)
139 self.manager.delete_project(project)139 self.manager.delete_project(project)
140 self.manager.delete_user(user)140 self.manager.delete_user(user)
141
142 def test_get_all_security_groups(self):
143 """Test that we can retrieve security groups"""
144 self.expect_http()
145 self.mox.ReplayAll()
146 user = self.manager.create_user('fake', 'fake', 'fake', admin=True)
147 project = self.manager.create_project('fake', 'fake', 'fake')
148
149 rv = self.ec2.get_all_security_groups()
150
151 self.assertEquals(len(rv), 1)
152 self.assertEquals(rv[0].name, 'default')
153
154 self.manager.delete_project(project)
155 self.manager.delete_user(user)
156
157 def test_create_delete_security_group(self):
158 """Test that we can create a security group"""
159 self.expect_http()
160 self.mox.ReplayAll()
161 user = self.manager.create_user('fake', 'fake', 'fake', admin=True)
162 project = self.manager.create_project('fake', 'fake', 'fake')
163
164 # At the moment, you need both of these to actually be netadmin
165 self.manager.add_role('fake', 'netadmin')
166 project.add_role('fake', 'netadmin')
167
168 security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \
169 for x in range(random.randint(4, 8)))
170
171 self.ec2.create_security_group(security_group_name, 'test group')
172
173 self.expect_http()
174 self.mox.ReplayAll()
175
176 rv = self.ec2.get_all_security_groups()
177 self.assertEquals(len(rv), 2)
178 self.assertTrue(security_group_name in [group.name for group in rv])
179
180 self.expect_http()
181 self.mox.ReplayAll()
182
183 self.ec2.delete_security_group(security_group_name)
184
185 self.manager.delete_project(project)
186 self.manager.delete_user(user)
187
188 def test_authorize_revoke_security_group_cidr(self):
189 """
190 Test that we can add and remove CIDR based rules
191 to a security group
192 """
193 self.expect_http()
194 self.mox.ReplayAll()
195 user = self.manager.create_user('fake', 'fake', 'fake')
196 project = self.manager.create_project('fake', 'fake', 'fake')
197
198 # At the moment, you need both of these to actually be netadmin
199 self.manager.add_role('fake', 'netadmin')
200 project.add_role('fake', 'netadmin')
201
202 security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \
203 for x in range(random.randint(4, 8)))
204
205 group = self.ec2.create_security_group(security_group_name, 'test group')
206
207 self.expect_http()
208 self.mox.ReplayAll()
209 group.connection = self.ec2
210
211 group.authorize('tcp', 80, 81, '0.0.0.0/0')
212
213 self.expect_http()
214 self.mox.ReplayAll()
215
216 rv = self.ec2.get_all_security_groups()
217 # I don't bother checkng that we actually find it here,
218 # because the create/delete unit test further up should
219 # be good enough for that.
220 for group in rv:
221 if group.name == security_group_name:
222 self.assertEquals(len(group.rules), 1)
223 self.assertEquals(int(group.rules[0].from_port), 80)
224 self.assertEquals(int(group.rules[0].to_port), 81)
225 self.assertEquals(len(group.rules[0].grants), 1)
226 self.assertEquals(str(group.rules[0].grants[0]), '0.0.0.0/0')
227
228 self.expect_http()
229 self.mox.ReplayAll()
230 group.connection = self.ec2
231
232 group.revoke('tcp', 80, 81, '0.0.0.0/0')
233
234 self.expect_http()
235 self.mox.ReplayAll()
236
237 self.ec2.delete_security_group(security_group_name)
238
239 self.expect_http()
240 self.mox.ReplayAll()
241 group.connection = self.ec2
242
243 rv = self.ec2.get_all_security_groups()
244
245 self.assertEqual(len(rv), 1)
246 self.assertEqual(rv[0].name, 'default')
247
248 self.manager.delete_project(project)
249 self.manager.delete_user(user)
250
251 return
252
253 def test_authorize_revoke_security_group_foreign_group(self):
254 """
255 Test that we can grant and revoke another security group access
256 to a security group
257 """
258 self.expect_http()
259 self.mox.ReplayAll()
260 user = self.manager.create_user('fake', 'fake', 'fake', admin=True)
261 project = self.manager.create_project('fake', 'fake', 'fake')
262
263 # At the moment, you need both of these to actually be netadmin
264 self.manager.add_role('fake', 'netadmin')
265 project.add_role('fake', 'netadmin')
266
267 security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \
268 for x in range(random.randint(4, 8)))
269 other_security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \
270 for x in range(random.randint(4, 8)))
271
272 group = self.ec2.create_security_group(security_group_name, 'test group')
273
274 self.expect_http()
275 self.mox.ReplayAll()
276
277 other_group = self.ec2.create_security_group(other_security_group_name,
278 'some other group')
279
280 self.expect_http()
281 self.mox.ReplayAll()
282 group.connection = self.ec2
283
284 group.authorize(src_group=other_group)
285
286 self.expect_http()
287 self.mox.ReplayAll()
288
289 rv = self.ec2.get_all_security_groups()
290
291 # I don't bother checkng that we actually find it here,
292 # because the create/delete unit test further up should
293 # be good enough for that.
294 for group in rv:
295 if group.name == security_group_name:
296 self.assertEquals(len(group.rules), 1)
297 self.assertEquals(len(group.rules[0].grants), 1)
298 self.assertEquals(str(group.rules[0].grants[0]),
299 '%s-%s' % (other_security_group_name, 'fake'))
300
301
302 self.expect_http()
303 self.mox.ReplayAll()
304
305 rv = self.ec2.get_all_security_groups()
306
307 for group in rv:
308 if group.name == security_group_name:
309 self.expect_http()
310 self.mox.ReplayAll()
311 group.connection = self.ec2
312 group.revoke(src_group=other_group)
313
314 self.expect_http()
315 self.mox.ReplayAll()
316
317 self.ec2.delete_security_group(security_group_name)
318
319 self.manager.delete_project(project)
320 self.manager.delete_user(user)
321
322 return
141323
=== modified file 'nova/tests/objectstore_unittest.py'
--- nova/tests/objectstore_unittest.py 2010-10-11 12:09:24 +0000
+++ nova/tests/objectstore_unittest.py 2010-10-12 20:19:40 +0000
@@ -210,7 +210,7 @@
210 """Setup users, projects, and start a test server."""210 """Setup users, projects, and start a test server."""
211 super(S3APITestCase, self).setUp()211 super(S3APITestCase, self).setUp()
212212
213 FLAGS.auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver',213 FLAGS.auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver'
214 FLAGS.buckets_path = os.path.join(OSS_TEMPDIR, 'buckets')214 FLAGS.buckets_path = os.path.join(OSS_TEMPDIR, 'buckets')
215215
216 self.auth_manager = manager.AuthManager()216 self.auth_manager = manager.AuthManager()
217217
=== modified file 'nova/tests/virt_unittest.py'
--- nova/tests/virt_unittest.py 2010-10-05 08:07:37 +0000
+++ nova/tests/virt_unittest.py 2010-10-12 20:19:40 +0000
@@ -14,11 +14,16 @@
14# License for the specific language governing permissions and limitations14# License for the specific language governing permissions and limitations
15# under the License.15# under the License.
1616
17from xml.etree.ElementTree import fromstring as parseXml17from xml.etree.ElementTree import fromstring as xml_to_tree
18from xml.dom.minidom import parseString as xml_to_dom
1819
20from nova import db
19from nova import flags21from nova import flags
20from nova import test22from nova import test
23from nova.api import context
24from nova.api.ec2 import cloud
21from nova.auth import manager25from nova.auth import manager
26
22# Needed to get FLAGS.instances_path defined:27# Needed to get FLAGS.instances_path defined:
23from nova.compute import manager as compute_manager28from nova.compute import manager as compute_manager
24from nova.virt import libvirt_conn29from nova.virt import libvirt_conn
@@ -33,34 +38,49 @@
33 FLAGS.instances_path = ''38 FLAGS.instances_path = ''
3439
35 def test_get_uri_and_template(self):40 def test_get_uri_and_template(self):
36 instance = { 'name' : 'i-cafebabe',41 ip = '10.11.12.13'
37 'id' : 'i-cafebabe',42
43 instance = { 'internal_id' : 1,
38 'memory_kb' : '1024000',44 'memory_kb' : '1024000',
39 'basepath' : '/some/path',45 'basepath' : '/some/path',
40 'bridge_name' : 'br100',46 'bridge_name' : 'br100',
41 'mac_address' : '02:12:34:46:56:67',47 'mac_address' : '02:12:34:46:56:67',
42 'vcpus' : 2,48 'vcpus' : 2,
43 'project_id' : 'fake',49 'project_id' : 'fake',
44 'ip_address' : '10.11.12.13',
45 'bridge' : 'br101',50 'bridge' : 'br101',
46 'instance_type' : 'm1.small'}51 'instance_type' : 'm1.small'}
4752
53 instance_ref = db.instance_create(None, instance)
54 network_ref = db.project_get_network(None, self.project.id)
55
56 fixed_ip = { 'address' : ip,
57 'network_id' : network_ref['id'] }
58
59 fixed_ip_ref = db.fixed_ip_create(None, fixed_ip)
60 db.fixed_ip_update(None, ip, { 'allocated' : True,
61 'instance_id' : instance_ref['id'] })
62
48 type_uri_map = { 'qemu' : ('qemu:///system',63 type_uri_map = { 'qemu' : ('qemu:///system',
49 [(lambda t: t.find('.').tag, 'domain'),64 [(lambda t: t.find('.').get('type'), 'qemu'),
50 (lambda t: t.find('.').get('type'), 'qemu'),
51 (lambda t: t.find('./os/type').text, 'hvm'),65 (lambda t: t.find('./os/type').text, 'hvm'),
52 (lambda t: t.find('./devices/emulator'), None)]),66 (lambda t: t.find('./devices/emulator'), None)]),
53 'kvm' : ('qemu:///system',67 'kvm' : ('qemu:///system',
54 [(lambda t: t.find('.').tag, 'domain'),68 [(lambda t: t.find('.').get('type'), 'kvm'),
55 (lambda t: t.find('.').get('type'), 'kvm'),
56 (lambda t: t.find('./os/type').text, 'hvm'),69 (lambda t: t.find('./os/type').text, 'hvm'),
57 (lambda t: t.find('./devices/emulator'), None)]),70 (lambda t: t.find('./devices/emulator'), None)]),
58 'uml' : ('uml:///system',71 'uml' : ('uml:///system',
59 [(lambda t: t.find('.').tag, 'domain'),72 [(lambda t: t.find('.').get('type'), 'uml'),
60 (lambda t: t.find('.').get('type'), 'uml'),
61 (lambda t: t.find('./os/type').text, 'uml')]),73 (lambda t: t.find('./os/type').text, 'uml')]),
62 }74 }
6375
76 common_checks = [(lambda t: t.find('.').tag, 'domain'),
77 (lambda t: \
78 t.find('./devices/interface/filterref/parameter') \
79 .get('name'), 'IP'),
80 (lambda t: \
81 t.find('./devices/interface/filterref/parameter') \
82 .get('value'), '10.11.12.13')]
83
64 for (libvirt_type,(expected_uri, checks)) in type_uri_map.iteritems():84 for (libvirt_type,(expected_uri, checks)) in type_uri_map.iteritems():
65 FLAGS.libvirt_type = libvirt_type85 FLAGS.libvirt_type = libvirt_type
66 conn = libvirt_conn.LibvirtConnection(True)86 conn = libvirt_conn.LibvirtConnection(True)
@@ -68,13 +88,18 @@
68 uri, template = conn.get_uri_and_template()88 uri, template = conn.get_uri_and_template()
69 self.assertEquals(uri, expected_uri)89 self.assertEquals(uri, expected_uri)
7090
71 xml = conn.to_xml(instance)91 xml = conn.to_xml(instance_ref)
72 tree = parseXml(xml)92 tree = xml_to_tree(xml)
73 for i, (check, expected_result) in enumerate(checks):93 for i, (check, expected_result) in enumerate(checks):
74 self.assertEqual(check(tree),94 self.assertEqual(check(tree),
75 expected_result,95 expected_result,
76 '%s failed check %d' % (xml, i))96 '%s failed check %d' % (xml, i))
7797
98 for i, (check, expected_result) in enumerate(common_checks):
99 self.assertEqual(check(tree),
100 expected_result,
101 '%s failed common check %d' % (xml, i))
102
78 # Deliberately not just assigning this string to FLAGS.libvirt_uri and103 # Deliberately not just assigning this string to FLAGS.libvirt_uri and
79 # checking against that later on. This way we make sure the104 # checking against that later on. This way we make sure the
80 # implementation doesn't fiddle around with the FLAGS.105 # implementation doesn't fiddle around with the FLAGS.
@@ -90,3 +115,138 @@
90 def tearDown(self):115 def tearDown(self):
91 self.manager.delete_project(self.project)116 self.manager.delete_project(self.project)
92 self.manager.delete_user(self.user)117 self.manager.delete_user(self.user)
118
119class NWFilterTestCase(test.TrialTestCase):
120 def setUp(self):
121 super(NWFilterTestCase, self).setUp()
122
123 class Mock(object):
124 pass
125
126 self.manager = manager.AuthManager()
127 self.user = self.manager.create_user('fake', 'fake', 'fake', admin=True)
128 self.project = self.manager.create_project('fake', 'fake', 'fake')
129 self.context = context.APIRequestContext(self.user, self.project)
130
131 self.fake_libvirt_connection = Mock()
132
133 self.fw = libvirt_conn.NWFilterFirewall(self.fake_libvirt_connection)
134
135 def tearDown(self):
136 self.manager.delete_project(self.project)
137 self.manager.delete_user(self.user)
138
139
140 def test_cidr_rule_nwfilter_xml(self):
141 cloud_controller = cloud.CloudController()
142 cloud_controller.create_security_group(self.context,
143 'testgroup',
144 'test group description')
145 cloud_controller.authorize_security_group_ingress(self.context,
146 'testgroup',
147 from_port='80',
148 to_port='81',
149 ip_protocol='tcp',
150 cidr_ip='0.0.0.0/0')
151
152
153 security_group = db.security_group_get_by_name(self.context,
154 'fake',
155 'testgroup')
156
157 xml = self.fw.security_group_to_nwfilter_xml(security_group.id)
158
159 dom = xml_to_dom(xml)
160 self.assertEqual(dom.firstChild.tagName, 'filter')
161
162 rules = dom.getElementsByTagName('rule')
163 self.assertEqual(len(rules), 1)
164
165 # It's supposed to allow inbound traffic.
166 self.assertEqual(rules[0].getAttribute('action'), 'accept')
167 self.assertEqual(rules[0].getAttribute('direction'), 'in')
168
169 # Must be lower priority than the base filter (which blocks everything)
170 self.assertTrue(int(rules[0].getAttribute('priority')) < 1000)
171
172 ip_conditions = rules[0].getElementsByTagName('tcp')
173 self.assertEqual(len(ip_conditions), 1)
174 self.assertEqual(ip_conditions[0].getAttribute('srcipaddr'), '0.0.0.0')
175 self.assertEqual(ip_conditions[0].getAttribute('srcipmask'), '0.0.0.0')
176 self.assertEqual(ip_conditions[0].getAttribute('dstportstart'), '80')
177 self.assertEqual(ip_conditions[0].getAttribute('dstportend'), '81')
178
179
180 self.teardown_security_group()
181
182 def teardown_security_group(self):
183 cloud_controller = cloud.CloudController()
184 cloud_controller.delete_security_group(self.context, 'testgroup')
185
186
187 def setup_and_return_security_group(self):
188 cloud_controller = cloud.CloudController()
189 cloud_controller.create_security_group(self.context,
190 'testgroup',
191 'test group description')
192 cloud_controller.authorize_security_group_ingress(self.context,
193 'testgroup',
194 from_port='80',
195 to_port='81',
196 ip_protocol='tcp',
197 cidr_ip='0.0.0.0/0')
198
199 return db.security_group_get_by_name(self.context, 'fake', 'testgroup')
200
201 def test_creates_base_rule_first(self):
202 # These come pre-defined by libvirt
203 self.defined_filters = ['no-mac-spoofing',
204 'no-ip-spoofing',
205 'no-arp-spoofing',
206 'allow-dhcp-server']
207
208 self.recursive_depends = {}
209 for f in self.defined_filters:
210 self.recursive_depends[f] = []
211
212 def _filterDefineXMLMock(xml):
213 dom = xml_to_dom(xml)
214 name = dom.firstChild.getAttribute('name')
215 self.recursive_depends[name] = []
216 for f in dom.getElementsByTagName('filterref'):
217 ref = f.getAttribute('filter')
218 self.assertTrue(ref in self.defined_filters,
219 ('%s referenced filter that does ' +
220 'not yet exist: %s') % (name, ref))
221 dependencies = [ref] + self.recursive_depends[ref]
222 self.recursive_depends[name] += dependencies
223
224 self.defined_filters.append(name)
225 return True
226
227 self.fake_libvirt_connection.nwfilterDefineXML = _filterDefineXMLMock
228
229 instance_ref = db.instance_create(self.context,
230 {'user_id': 'fake',
231 'project_id': 'fake'})
232 inst_id = instance_ref['id']
233
234 def _ensure_all_called(_):
235 instance_filter = 'nova-instance-%s' % instance_ref['name']
236 secgroup_filter = 'nova-secgroup-%s' % self.security_group['id']
237 for required in [secgroup_filter, 'allow-dhcp-server',
238 'no-arp-spoofing', 'no-ip-spoofing',
239 'no-mac-spoofing']:
240 self.assertTrue(required in self.recursive_depends[instance_filter],
241 "Instance's filter does not include %s" % required)
242
243 self.security_group = self.setup_and_return_security_group()
244
245 db.instance_add_security_group(self.context, inst_id, self.security_group.id)
246 instance = db.instance_get(self.context, inst_id)
247
248 d = self.fw.setup_nwfilters_for_instance(instance)
249 d.addCallback(_ensure_all_called)
250 d.addCallback(lambda _:self.teardown_security_group())
251
252 return d
93253
=== modified file 'nova/virt/interfaces.template'
--- nova/virt/interfaces.template 2010-08-13 21:45:26 +0000
+++ nova/virt/interfaces.template 2010-10-12 20:19:40 +0000
@@ -10,7 +10,6 @@
10iface eth0 inet static10iface eth0 inet static
11 address %(address)s11 address %(address)s
12 netmask %(netmask)s12 netmask %(netmask)s
13 network %(network)s
14 broadcast %(broadcast)s13 broadcast %(broadcast)s
15 gateway %(gateway)s14 gateway %(gateway)s
16 dns-nameservers %(dns)s15 dns-nameservers %(dns)s
1716
=== modified file 'nova/virt/libvirt.qemu.xml.template'
--- nova/virt/libvirt.qemu.xml.template 2010-09-07 12:34:37 +0000
+++ nova/virt/libvirt.qemu.xml.template 2010-10-12 20:19:40 +0000
@@ -20,6 +20,10 @@
20 <source bridge='%(bridge_name)s'/>20 <source bridge='%(bridge_name)s'/>
21 <mac address='%(mac_address)s'/>21 <mac address='%(mac_address)s'/>
22 <!-- <model type='virtio'/> CANT RUN virtio network right now -->22 <!-- <model type='virtio'/> CANT RUN virtio network right now -->
23 <filterref filter="nova-instance-%(name)s">
24 <parameter name="IP" value="%(ip_address)s" />
25 <parameter name="DHCPSERVER" value="%(dhcp_server)s" />
26 </filterref>
23 </interface>27 </interface>
24 <serial type="file">28 <serial type="file">
25 <source path='%(basepath)s/console.log'/>29 <source path='%(basepath)s/console.log'/>
2630
=== modified file 'nova/virt/libvirt.uml.xml.template'
--- nova/virt/libvirt.uml.xml.template 2010-09-07 12:34:37 +0000
+++ nova/virt/libvirt.uml.xml.template 2010-10-12 20:19:40 +0000
@@ -14,6 +14,10 @@
14 <interface type='bridge'>14 <interface type='bridge'>
15 <source bridge='%(bridge_name)s'/>15 <source bridge='%(bridge_name)s'/>
16 <mac address='%(mac_address)s'/>16 <mac address='%(mac_address)s'/>
17 <filterref filter="nova-instance-%(name)s">
18 <parameter name="IP" value="%(ip_address)s" />
19 <parameter name="DHCPSERVER" value="%(dhcp_server)s" />
20 </filterref>
17 </interface>21 </interface>
18 <console type="file">22 <console type="file">
19 <source path='%(basepath)s/console.log'/>23 <source path='%(basepath)s/console.log'/>
2024
=== modified file 'nova/virt/libvirt_conn.py'
--- nova/virt/libvirt_conn.py 2010-09-09 15:55:09 +0000
+++ nova/virt/libvirt_conn.py 2010-10-12 20:19:40 +0000
@@ -25,14 +25,17 @@
25import os25import os
26import shutil26import shutil
2727
28import IPy
28from twisted.internet import defer29from twisted.internet import defer
29from twisted.internet import task30from twisted.internet import task
31from twisted.internet import threads
3032
31from nova import db33from nova import db
32from nova import exception34from nova import exception
33from nova import flags35from nova import flags
34from nova import process36from nova import process
35from nova import utils37from nova import utils
38#from nova.api import context
36from nova.auth import manager39from nova.auth import manager
37from nova.compute import disk40from nova.compute import disk
38from nova.compute import instance_types41from nova.compute import instance_types
@@ -60,6 +63,9 @@
60 '',63 '',
61 'Override the default libvirt URI (which is dependent'64 'Override the default libvirt URI (which is dependent'
62 ' on libvirt_type)')65 ' on libvirt_type)')
66flags.DEFINE_bool('allow_project_net_traffic',
67 True,
68 'Whether to allow in project network traffic')
6369
6470
65def get_connection(read_only):71def get_connection(read_only):
@@ -134,7 +140,7 @@
134 d.addCallback(lambda _: self._cleanup(instance))140 d.addCallback(lambda _: self._cleanup(instance))
135 # FIXME: What does this comment mean?141 # FIXME: What does this comment mean?
136 # TODO(termie): short-circuit me for tests142 # TODO(termie): short-circuit me for tests
137 # WE'LL save this for when we do shutdown,143 # WE'LL save this for when we do shutdown,
138 # instead of destroy - but destroy returns immediately144 # instead of destroy - but destroy returns immediately
139 timer = task.LoopingCall(f=None)145 timer = task.LoopingCall(f=None)
140 def _wait_for_shutdown():146 def _wait_for_shutdown():
@@ -214,6 +220,7 @@
214 instance['id'],220 instance['id'],
215 power_state.NOSTATE,221 power_state.NOSTATE,
216 'launching')222 'launching')
223 yield NWFilterFirewall(self._conn).setup_nwfilters_for_instance(instance)
217 yield self._create_image(instance, xml)224 yield self._create_image(instance, xml)
218 yield self._conn.createXML(xml, 0)225 yield self._conn.createXML(xml, 0)
219 # TODO(termie): this should actually register226 # TODO(termie): this should actually register
@@ -285,7 +292,6 @@
285 address = db.instance_get_fixed_address(None, inst['id'])292 address = db.instance_get_fixed_address(None, inst['id'])
286 with open(FLAGS.injected_network_template) as f:293 with open(FLAGS.injected_network_template) as f:
287 net = f.read() % {'address': address,294 net = f.read() % {'address': address,
288 'network': network_ref['network'],
289 'netmask': network_ref['netmask'],295 'netmask': network_ref['netmask'],
290 'gateway': network_ref['gateway'],296 'gateway': network_ref['gateway'],
291 'broadcast': network_ref['broadcast'],297 'broadcast': network_ref['broadcast'],
@@ -317,6 +323,9 @@
317 network = db.project_get_network(None, instance['project_id'])323 network = db.project_get_network(None, instance['project_id'])
318 # FIXME(vish): stick this in db324 # FIXME(vish): stick this in db
319 instance_type = instance_types.INSTANCE_TYPES[instance['instance_type']]325 instance_type = instance_types.INSTANCE_TYPES[instance['instance_type']]
326 ip_address = db.instance_get_fixed_address({}, instance['id'])
327 # Assume that the gateway also acts as the dhcp server.
328 dhcp_server = network['gateway']
320 xml_info = {'type': FLAGS.libvirt_type,329 xml_info = {'type': FLAGS.libvirt_type,
321 'name': instance['name'],330 'name': instance['name'],
322 'basepath': os.path.join(FLAGS.instances_path,331 'basepath': os.path.join(FLAGS.instances_path,
@@ -324,7 +333,9 @@
324 'memory_kb': instance_type['memory_mb'] * 1024,333 'memory_kb': instance_type['memory_mb'] * 1024,
325 'vcpus': instance_type['vcpus'],334 'vcpus': instance_type['vcpus'],
326 'bridge_name': network['bridge'],335 'bridge_name': network['bridge'],
327 'mac_address': instance['mac_address']}336 'mac_address': instance['mac_address'],
337 'ip_address': ip_address,
338 'dhcp_server': dhcp_server }
328 libvirt_xml = self.libvirt_xml % xml_info339 libvirt_xml = self.libvirt_xml % xml_info
329 logging.debug('instance %s: finished toXML method', instance['name'])340 logging.debug('instance %s: finished toXML method', instance['name'])
330341
@@ -438,3 +449,195 @@
438 """449 """
439 domain = self._conn.lookupByName(instance_name)450 domain = self._conn.lookupByName(instance_name)
440 return domain.interfaceStats(interface)451 return domain.interfaceStats(interface)
452
453
454 def refresh_security_group(self, security_group_id):
455 fw = NWFilterFirewall(self._conn)
456 fw.ensure_security_group_filter(security_group_id)
457
458
459class NWFilterFirewall(object):
460 """
461 This class implements a network filtering mechanism versatile
462 enough for EC2 style Security Group filtering by leveraging
463 libvirt's nwfilter.
464
465 First, all instances get a filter ("nova-base-filter") applied.
466 This filter drops all incoming ipv4 and ipv6 connections.
467 Outgoing connections are never blocked.
468
469 Second, every security group maps to a nwfilter filter(*).
470 NWFilters can be updated at runtime and changes are applied
471 immediately, so changes to security groups can be applied at
472 runtime (as mandated by the spec).
473
474 Security group rules are named "nova-secgroup-<id>" where <id>
475 is the internal id of the security group. They're applied only on
476 hosts that have instances in the security group in question.
477
478 Updates to security groups are done by updating the data model
479 (in response to API calls) followed by a request sent to all
480 the nodes with instances in the security group to refresh the
481 security group.
482
483 Each instance has its own NWFilter, which references the above
484 mentioned security group NWFilters. This was done because
485 interfaces can only reference one filter while filters can
486 reference multiple other filters. This has the added benefit of
487 actually being able to add and remove security groups from an
488 instance at run time. This functionality is not exposed anywhere,
489 though.
490
491 Outstanding questions:
492
493 The name is unique, so would there be any good reason to sync
494 the uuid across the nodes (by assigning it from the datamodel)?
495
496
497 (*) This sentence brought to you by the redundancy department of
498 redundancy.
499 """
500
501 def __init__(self, get_connection):
502 self._conn = get_connection
503
504
505 nova_base_filter = '''<filter name='nova-base' chain='root'>
506 <uuid>26717364-50cf-42d1-8185-29bf893ab110</uuid>
507 <filterref filter='no-mac-spoofing'/>
508 <filterref filter='no-ip-spoofing'/>
509 <filterref filter='no-arp-spoofing'/>
510 <filterref filter='allow-dhcp-server'/>
511 <filterref filter='nova-allow-dhcp-server'/>
512 <filterref filter='nova-base-ipv4'/>
513 <filterref filter='nova-base-ipv6'/>
514 </filter>'''
515
516 nova_dhcp_filter = '''<filter name='nova-allow-dhcp-server' chain='ipv4'>
517 <uuid>891e4787-e5c0-d59b-cbd6-41bc3c6b36fc</uuid>
518 <rule action='accept' direction='out'
519 priority='100'>
520 <udp srcipaddr='0.0.0.0'
521 dstipaddr='255.255.255.255'
522 srcportstart='68'
523 dstportstart='67'/>
524 </rule>
525 <rule action='accept' direction='in' priority='100'>
526 <udp srcipaddr='$DHCPSERVER'
527 srcportstart='67'
528 dstportstart='68'/>
529 </rule>
530 </filter>'''
531
532 def nova_base_ipv4_filter(self):
533 retval = "<filter name='nova-base-ipv4' chain='ipv4'>"
534 for protocol in ['tcp', 'udp', 'icmp']:
535 for direction,action,priority in [('out','accept', 399),
536 ('inout','drop', 400)]:
537 retval += """<rule action='%s' direction='%s' priority='%d'>
538 <%s />
539 </rule>""" % (action, direction,
540 priority, protocol)
541 retval += '</filter>'
542 return retval
543
544
545 def nova_base_ipv6_filter(self):
546 retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
547 for protocol in ['tcp', 'udp', 'icmp']:
548 for direction,action,priority in [('out','accept',399),
549 ('inout','drop',400)]:
550 retval += """<rule action='%s' direction='%s' priority='%d'>
551 <%s-ipv6 />
552 </rule>""" % (action, direction,
553 priority, protocol)
554 retval += '</filter>'
555 return retval
556
557
558 def nova_project_filter(self, project, net, mask):
559 retval = "<filter name='nova-project-%s' chain='ipv4'>" % project
560 for protocol in ['tcp', 'udp', 'icmp']:
561 retval += """<rule action='accept' direction='in' priority='200'>
562 <%s srcipaddr='%s' srcipmask='%s' />
563 </rule>""" % (protocol, net, mask)
564 retval += '</filter>'
565 return retval
566
567
568 def _define_filter(self, xml):
569 if callable(xml):
570 xml = xml()
571 d = threads.deferToThread(self._conn.nwfilterDefineXML, xml)
572 return d
573
574
575 @staticmethod
576 def _get_net_and_mask(cidr):
577 net = IPy.IP(cidr)
578 return str(net.net()), str(net.netmask())
579
580 @defer.inlineCallbacks
581 def setup_nwfilters_for_instance(self, instance):
582 """
583 Creates an NWFilter for the given instance. In the process,
584 it makes sure the filters for the security groups as well as
585 the base filter are all in place.
586 """
587
588 yield self._define_filter(self.nova_base_ipv4_filter)
589 yield self._define_filter(self.nova_base_ipv6_filter)
590 yield self._define_filter(self.nova_dhcp_filter)
591 yield self._define_filter(self.nova_base_filter)
592
593 nwfilter_xml = ("<filter name='nova-instance-%s' chain='root'>\n" +
594 " <filterref filter='nova-base' />\n"
595 ) % instance['name']
596
597 if FLAGS.allow_project_net_traffic:
598 network_ref = db.project_get_network({}, instance['project_id'])
599 net, mask = self._get_net_and_mask(network_ref['cidr'])
600 project_filter = self.nova_project_filter(instance['project_id'],
601 net, mask)
602 yield self._define_filter(project_filter)
603
604 nwfilter_xml += (" <filterref filter='nova-project-%s' />\n"
605 ) % instance['project_id']
606
607 for security_group in instance.security_groups:
608 yield self.ensure_security_group_filter(security_group['id'])
609
610 nwfilter_xml += (" <filterref filter='nova-secgroup-%d' />\n"
611 ) % security_group['id']
612 nwfilter_xml += "</filter>"
613
614 yield self._define_filter(nwfilter_xml)
615 return
616
617 def ensure_security_group_filter(self, security_group_id):
618 return self._define_filter(
619 self.security_group_to_nwfilter_xml(security_group_id))
620
621
622 def security_group_to_nwfilter_xml(self, security_group_id):
623 security_group = db.security_group_get({}, security_group_id)
624 rule_xml = ""
625 for rule in security_group.rules:
626 rule_xml += "<rule action='accept' direction='in' priority='300'>"
627 if rule.cidr:
628 net, mask = self._get_net_and_mask(rule.cidr)
629 rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % (rule.protocol, net, mask)
630 if rule.protocol in ['tcp', 'udp']:
631 rule_xml += "dstportstart='%s' dstportend='%s' " % \
632 (rule.from_port, rule.to_port)
633 elif rule.protocol == 'icmp':
634 logging.info('rule.protocol: %r, rule.from_port: %r, rule.to_port: %r' % (rule.protocol, rule.from_port, rule.to_port))
635 if rule.from_port != -1:
636 rule_xml += "type='%s' " % rule.from_port
637 if rule.to_port != -1:
638 rule_xml += "code='%s' " % rule.to_port
639
640 rule_xml += '/>\n'
641 rule_xml += "</rule>\n"
642 xml = '''<filter name='nova-secgroup-%s' chain='ipv4'>%s</filter>''' % (security_group_id, rule_xml,)
643 return xml
441644
=== modified file 'run_tests.py'
--- run_tests.py 2010-10-05 08:06:54 +0000
+++ run_tests.py 2010-10-12 20:19:40 +0000
@@ -65,6 +65,7 @@
65from nova.tests.validator_unittest import *65from nova.tests.validator_unittest import *
66from nova.tests.virt_unittest import *66from nova.tests.virt_unittest import *
67from nova.tests.volume_unittest import *67from nova.tests.volume_unittest import *
68from nova.tests.virt_unittest import *
6869
6970
70FLAGS = flags.FLAGS71FLAGS = flags.FLAGS