Merge lp:~soren/nova/ec2-security-groups into lp:~hudson-openstack/nova/trunk
- ec2-security-groups
- Merge into trunk
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 |
Related bugs: | |
Related blueprints: |
Support EC2 Security Groups
(Medium)
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Gundlach (community) | Approve | ||
Jay Pipes (community) | Approve | ||
Review via email: mp+36119@code.launchpad.net |
Commit message
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)
Vish Ishaya (vishvananda) wrote : | # |
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:/
> 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/
> --- nova/auth/
> +++ nova/auth/
> @@ -490,6 +490,12 @@
> except:
> drv.delete_
> raise
> +
> + values = { 'name' : 'default',
> + 'description' : 'default',
> + 'user_id' : User.safe_
> + 'project_id' : project.id }
> + db.security_
> return project
>
> def modify_
> @@ -565,6 +571,16 @@
> except:
> logging.
> project)
> + try:
> + project_id = Project.
> + groups = db.security_
> + project_
> + for group in groups:
> + db.security_
> + except:
> + logging.
> + project)
> +
> with self.driver() as drv:
> drv.delete_
>
>
> === modified file 'nova/compute/
> --- nova/compute/
> +++ nova/compute/
> @@ -64,6 +64,11 @@
>
> @defer.
> @exception.
> + def refresh_
> + yield self.driver.
> +
> + @defer.
> + @exception.wrap...
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. :(
Soren Hansen (soren) wrote : | # |
Phew. There we go. All cleaned up, and happy test cases again :)
Vish Ishaya (vishvananda) wrote : | # |
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/
--- nova/api/
+++ nova/api/
@@ -725,9 +725,9 @@
for security_group_name in security_group_arg:
- group = db.security_
- context.project.id,
- security_
+ group = db.security_
+ context.project.id,
+ security_
@@ -744,6 +744,7 @@
type_data = INSTANCE_
+ base_options[
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:/
> 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/
> --- nova/auth/
> +++ nova/auth/
> @@ -490,6 +490,12 @@
> except:
> drv.delete_
> raise
> +
> + values = { 'name' : 'default',
> + 'description' : 'default',
> + 'user_id' : User.safe_
> + 'project_id' : project.id }
> + db.security_
> return project
>
> def modify_
> @@ -565,6 +571,16 @@
> except:
> logging.exce...
Vish Ishaya (vishvananda) wrote : | # |
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:/
> 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/
> --- nova/auth/
> +++ nova/auth/
> @@ -490,6 +490,12 @@
> except:
> drv.delete_
> raise
> +
> + values = { 'name' : 'default',
> + 'description' : 'default',
> + 'user_id' : User.safe_
> + 'project_id' : project.id }
> + db.security_
> return project
>
> def modify_
> @@ -565,6 +571,16 @@
> except:
> logging.
> project)
> + try:
> + project_id = Project.
> + groups = db.security_
> + project_
> + for group in groups:
> + db.security_
> + except:
> + logging.
> + project)
> +
> with self.driver() as drv:
> drv.delete_
>
>
> === modified file 'nova/compute/
> --- nova/compute/
> +++ nova/compute/
> @@ -64,6 +64,11 @@
>
> @defer.
> @exception.
> + def refresh_
> + yield self.driver.
> +
> + @defer.
> + @exception.
> def run_instance(self, context, instance_id, **_kwargs):
> """Launch a new instance with specified options."""
> instance_ref = self.db.
>
> === modified file 'nova/db/api.py'
> --- nova/db/api.py 2010-09-21 02:17:36 +0000
> +++ nova/db/api....
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.
Vish Ishaya (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/
yield self.driver.
File "/usr/local/
result = g.send(result)
File "/srv/cloud/
yield self._conn.
File "/usr/lib/
if ret is None:raise libvirtError(
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/
File "/usr/lib/
File "/usr/lib/
--- <exception caught here> ---
File "/usr/local/
result = context.call(ctx, function, *args, **kwargs)
File "/usr/local/
return self.currentCon
File "/usr/local/
return func(*args,**kw)
File "/usr/lib/
if ret is None:raise libvirtError(
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...
Soren Hansen (soren) wrote : | # |
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/
> yield self.driver.
> File "/usr/local/
> result = g.send(result)
> File "/srv/cloud/
> yield self._conn.
> File "/usr/lib/
> if ret is None:raise libvirtError(
> 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=
<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 ...
Vish Ishaya (vishvananda) wrote : | # |
Test comment to see if it will subscribe me
Vish Ishaya (vishvananda) wrote : | # |
problem running multiple tests, it may not be instance i-1. This patch fixes.
=== modified file 'nova/tests/
--- nova/tests/
+++ nova/tests/
@@ -178,8 +178,14 @@
return True
+ self.fake_
+
+ instance_ref = db.instance_
+ 'project_id': 'fake'})
+ inst_id = instance_ref['id']
+
def _ensure_
- instance_filter = 'nova-instance-i-1'
+ instance_filter = 'nova-instance-%s' % instance_
for required in [secgroup_filter, 'allow-
@@ -187,11 +193,6 @@
- self.fake_
-
- inst_id = db.instance_
- 'project_id': 'fake'})['id']
-
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.
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.
Jay Pipes (jaypipes) wrote : | # |
Approved. This is excellent work. Especially on the test cases.
Michael Gundlach (gundlach) wrote : | # |
Approve.
FYI db/sqlalchemy/
OpenStack Infra (hudson-openstack) wrote : | # |
Attempt to merge into lp:nova failed due to conflicts:
text conflict in nova/db/
OpenStack Infra (hudson-openstack) wrote : | # |
Attempt to merge into lp:nova failed due to conflicts:
text conflict in nova/tests/
OpenStack Infra (hudson-openstack) wrote : | # |
Attempt to merge into lp:nova failed due to conflicts:
text conflict in nova/tests/
Soren Hansen (soren) wrote : | # |
Oh, come on!
- 320. By Soren Hansen
-
Merge trunk.
- 321. By Soren Hansen
-
Merge trunk (that's 10 times now, count 'em\!)
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:/
> You are subscribed to branch lp:~soren/nova/ec2-security-groups.
Preview Diff
1 | === modified file 'nova/api/ec2/__init__.py' |
2 | --- nova/api/ec2/__init__.py 2010-09-30 09:47:05 +0000 |
3 | +++ nova/api/ec2/__init__.py 2010-10-12 20:19:40 +0000 |
4 | @@ -142,6 +142,8 @@ |
5 | 'CreateKeyPair': ['all'], |
6 | 'DeleteKeyPair': ['all'], |
7 | 'DescribeSecurityGroups': ['all'], |
8 | + 'AuthorizeSecurityGroupIngress': ['netadmin'], |
9 | + 'RevokeSecurityGroupIngress': ['netadmin'], |
10 | 'CreateSecurityGroup': ['netadmin'], |
11 | 'DeleteSecurityGroup': ['netadmin'], |
12 | 'GetConsoleOutput': ['projectmanager', 'sysadmin'], |
13 | |
14 | === modified file 'nova/api/ec2/cloud.py' |
15 | --- nova/api/ec2/cloud.py 2010-10-11 16:01:15 +0000 |
16 | +++ nova/api/ec2/cloud.py 2010-10-12 20:19:40 +0000 |
17 | @@ -28,6 +28,8 @@ |
18 | import os |
19 | import time |
20 | |
21 | +import IPy |
22 | + |
23 | from nova import crypto |
24 | from nova import db |
25 | from nova import exception |
26 | @@ -43,6 +45,7 @@ |
27 | FLAGS = flags.FLAGS |
28 | flags.DECLARE('storage_availability_zone', 'nova.volume.manager') |
29 | |
30 | +InvalidInputException = exception.InvalidInputException |
31 | |
32 | class QuotaError(exception.ApiError): |
33 | """Quota Exceeeded""" |
34 | @@ -127,6 +130,15 @@ |
35 | result[key] = [line] |
36 | return result |
37 | |
38 | + def _trigger_refresh_security_group(self, security_group): |
39 | + nodes = set([instance['host'] for instance in security_group.instances |
40 | + if instance['host'] is not None]) |
41 | + for node in nodes: |
42 | + rpc.call('%s.%s' % (FLAGS.compute_topic, node), |
43 | + { "method": "refresh_security_group", |
44 | + "args": { "context": None, |
45 | + "security_group_id": security_group.id}}) |
46 | + |
47 | def get_metadata(self, address): |
48 | instance_ref = db.fixed_ip_get_instance(None, address) |
49 | if instance_ref is None: |
50 | @@ -246,18 +258,195 @@ |
51 | pass |
52 | return True |
53 | |
54 | - def describe_security_groups(self, context, group_names, **kwargs): |
55 | - groups = {'securityGroupSet': []} |
56 | - |
57 | - # Stubbed for now to unblock other things. |
58 | - return groups |
59 | - |
60 | - def create_security_group(self, context, group_name, **kwargs): |
61 | + def describe_security_groups(self, context, group_name=None, **kwargs): |
62 | + self._ensure_default_security_group(context) |
63 | + if context.user.is_admin(): |
64 | + groups = db.security_group_get_all(context) |
65 | + else: |
66 | + groups = db.security_group_get_by_project(context, |
67 | + context.project.id) |
68 | + groups = [self._format_security_group(context, g) for g in groups] |
69 | + if not group_name is None: |
70 | + groups = [g for g in groups if g.name in group_name] |
71 | + |
72 | + return {'securityGroupInfo': groups } |
73 | + |
74 | + def _format_security_group(self, context, group): |
75 | + g = {} |
76 | + g['groupDescription'] = group.description |
77 | + g['groupName'] = group.name |
78 | + g['ownerId'] = group.project_id |
79 | + g['ipPermissions'] = [] |
80 | + for rule in group.rules: |
81 | + r = {} |
82 | + r['ipProtocol'] = rule.protocol |
83 | + r['fromPort'] = rule.from_port |
84 | + r['toPort'] = rule.to_port |
85 | + r['groups'] = [] |
86 | + r['ipRanges'] = [] |
87 | + if rule.group_id: |
88 | + source_group = db.security_group_get(context, rule.group_id) |
89 | + r['groups'] += [{'groupName': source_group.name, |
90 | + 'userId': source_group.project_id}] |
91 | + else: |
92 | + r['ipRanges'] += [{'cidrIp': rule.cidr}] |
93 | + g['ipPermissions'] += [r] |
94 | + return g |
95 | + |
96 | + |
97 | + def _authorize_revoke_rule_args_to_dict(self, context, |
98 | + to_port=None, from_port=None, |
99 | + ip_protocol=None, cidr_ip=None, |
100 | + user_id=None, |
101 | + source_security_group_name=None, |
102 | + source_security_group_owner_id=None): |
103 | + |
104 | + values = {} |
105 | + |
106 | + if source_security_group_name: |
107 | + source_project_id = self._get_source_project_id(context, |
108 | + source_security_group_owner_id) |
109 | + |
110 | + source_security_group = \ |
111 | + db.security_group_get_by_name(context, |
112 | + source_project_id, |
113 | + source_security_group_name) |
114 | + values['group_id'] = source_security_group['id'] |
115 | + elif cidr_ip: |
116 | + # If this fails, it throws an exception. This is what we want. |
117 | + IPy.IP(cidr_ip) |
118 | + values['cidr'] = cidr_ip |
119 | + else: |
120 | + values['cidr'] = '0.0.0.0/0' |
121 | + |
122 | + if ip_protocol and from_port and to_port: |
123 | + from_port = int(from_port) |
124 | + to_port = int(to_port) |
125 | + ip_protocol = str(ip_protocol) |
126 | + |
127 | + if ip_protocol.upper() not in ['TCP','UDP','ICMP']: |
128 | + raise InvalidInputException('%s is not a valid ipProtocol' % |
129 | + (ip_protocol,)) |
130 | + if ((min(from_port, to_port) < -1) or |
131 | + (max(from_port, to_port) > 65535)): |
132 | + raise InvalidInputException('Invalid port range') |
133 | + |
134 | + values['protocol'] = ip_protocol |
135 | + values['from_port'] = from_port |
136 | + values['to_port'] = to_port |
137 | + else: |
138 | + # If cidr based filtering, protocol and ports are mandatory |
139 | + if 'cidr' in values: |
140 | + return None |
141 | + |
142 | + return values |
143 | + |
144 | + |
145 | + def _security_group_rule_exists(self, security_group, values): |
146 | + """Indicates whether the specified rule values are already |
147 | + defined in the given security group. |
148 | + """ |
149 | + for rule in security_group.rules: |
150 | + if 'group_id' in values: |
151 | + if rule['group_id'] == values['group_id']: |
152 | + return True |
153 | + else: |
154 | + is_duplicate = True |
155 | + for key in ('cidr', 'from_port', 'to_port', 'protocol'): |
156 | + if rule[key] != values[key]: |
157 | + is_duplicate = False |
158 | + break |
159 | + if is_duplicate: |
160 | + return True |
161 | + return False |
162 | + |
163 | + |
164 | + def revoke_security_group_ingress(self, context, group_name, **kwargs): |
165 | + self._ensure_default_security_group(context) |
166 | + security_group = db.security_group_get_by_name(context, |
167 | + context.project.id, |
168 | + group_name) |
169 | + |
170 | + criteria = self._authorize_revoke_rule_args_to_dict(context, **kwargs) |
171 | + if criteria == None: |
172 | + raise exception.ApiError("No rule for the specified parameters.") |
173 | + |
174 | + for rule in security_group.rules: |
175 | + match = True |
176 | + for (k,v) in criteria.iteritems(): |
177 | + if getattr(rule, k, False) != v: |
178 | + match = False |
179 | + if match: |
180 | + db.security_group_rule_destroy(context, rule['id']) |
181 | + self._trigger_refresh_security_group(security_group) |
182 | + return True |
183 | + raise exception.ApiError("No rule for the specified parameters.") |
184 | + |
185 | + # TODO(soren): This has only been tested with Boto as the client. |
186 | + # Unfortunately, it seems Boto is using an old API |
187 | + # for these operations, so support for newer API versions |
188 | + # is sketchy. |
189 | + def authorize_security_group_ingress(self, context, group_name, **kwargs): |
190 | + self._ensure_default_security_group(context) |
191 | + security_group = db.security_group_get_by_name(context, |
192 | + context.project.id, |
193 | + group_name) |
194 | + |
195 | + values = self._authorize_revoke_rule_args_to_dict(context, **kwargs) |
196 | + values['parent_group_id'] = security_group.id |
197 | + |
198 | + if self._security_group_rule_exists(security_group, values): |
199 | + raise exception.ApiError('This rule already exists in group %s' % |
200 | + group_name) |
201 | + |
202 | + security_group_rule = db.security_group_rule_create(context, values) |
203 | + |
204 | + self._trigger_refresh_security_group(security_group) |
205 | + |
206 | return True |
207 | |
208 | + |
209 | + def _get_source_project_id(self, context, source_security_group_owner_id): |
210 | + if source_security_group_owner_id: |
211 | + # Parse user:project for source group. |
212 | + source_parts = source_security_group_owner_id.split(':') |
213 | + |
214 | + # If no project name specified, assume it's same as user name. |
215 | + # Since we're looking up by project name, the user name is not |
216 | + # used here. It's only read for EC2 API compatibility. |
217 | + if len(source_parts) == 2: |
218 | + source_project_id = source_parts[1] |
219 | + else: |
220 | + source_project_id = source_parts[0] |
221 | + else: |
222 | + source_project_id = context.project.id |
223 | + |
224 | + return source_project_id |
225 | + |
226 | + |
227 | + def create_security_group(self, context, group_name, group_description): |
228 | + self._ensure_default_security_group(context) |
229 | + if db.security_group_exists(context, context.project.id, group_name): |
230 | + raise exception.ApiError('group %s already exists' % group_name) |
231 | + |
232 | + group = {'user_id' : context.user.id, |
233 | + 'project_id': context.project.id, |
234 | + 'name': group_name, |
235 | + 'description': group_description} |
236 | + group_ref = db.security_group_create(context, group) |
237 | + |
238 | + return {'securityGroupSet': [self._format_security_group(context, |
239 | + group_ref)]} |
240 | + |
241 | + |
242 | def delete_security_group(self, context, group_name, **kwargs): |
243 | + security_group = db.security_group_get_by_name(context, |
244 | + context.project.id, |
245 | + group_name) |
246 | + db.security_group_destroy(context, security_group.id) |
247 | return True |
248 | |
249 | + |
250 | def get_console_output(self, context, instance_id, **kwargs): |
251 | # instance_id is passed in as a list of instances |
252 | ec2_id = instance_id[0] |
253 | @@ -554,6 +743,18 @@ |
254 | "project_id": context.project.id}}) |
255 | return db.queue_get_for(context, FLAGS.network_topic, host) |
256 | |
257 | + def _ensure_default_security_group(self, context): |
258 | + try: |
259 | + db.security_group_get_by_name(context, |
260 | + context.project.id, |
261 | + 'default') |
262 | + except exception.NotFound: |
263 | + values = { 'name' : 'default', |
264 | + 'description' : 'default', |
265 | + 'user_id' : context.user.id, |
266 | + 'project_id' : context.project.id } |
267 | + group = db.security_group_create(context, values) |
268 | + |
269 | def run_instances(self, context, **kwargs): |
270 | instance_type = kwargs.get('instance_type', 'm1.small') |
271 | if instance_type not in INSTANCE_TYPES: |
272 | @@ -601,8 +802,17 @@ |
273 | kwargs['key_name']) |
274 | key_data = key_pair_ref['public_key'] |
275 | |
276 | - # TODO: Get the real security group of launch in here |
277 | - security_group = "default" |
278 | + security_group_arg = kwargs.get('security_group', ["default"]) |
279 | + if not type(security_group_arg) is list: |
280 | + security_group_arg = [security_group_arg] |
281 | + |
282 | + security_groups = [] |
283 | + self._ensure_default_security_group(context) |
284 | + for security_group_name in security_group_arg: |
285 | + group = db.security_group_get_by_name(context, |
286 | + context.project.id, |
287 | + security_group_name) |
288 | + security_groups.append(group['id']) |
289 | |
290 | reservation_id = utils.generate_uid('r') |
291 | base_options = {} |
292 | @@ -616,12 +826,12 @@ |
293 | base_options['user_id'] = context.user.id |
294 | base_options['project_id'] = context.project.id |
295 | base_options['user_data'] = kwargs.get('user_data', '') |
296 | - base_options['security_group'] = security_group |
297 | - base_options['instance_type'] = instance_type |
298 | + |
299 | base_options['display_name'] = kwargs.get('display_name') |
300 | base_options['display_description'] = kwargs.get('display_description') |
301 | |
302 | type_data = INSTANCE_TYPES[instance_type] |
303 | + base_options['instance_type'] = instance_type |
304 | base_options['memory_mb'] = type_data['memory_mb'] |
305 | base_options['vcpus'] = type_data['vcpus'] |
306 | base_options['local_gb'] = type_data['local_gb'] |
307 | @@ -630,6 +840,10 @@ |
308 | instance_ref = db.instance_create(context, base_options) |
309 | inst_id = instance_ref['id'] |
310 | |
311 | + for security_group_id in security_groups: |
312 | + db.instance_add_security_group(context, inst_id, |
313 | + security_group_id) |
314 | + |
315 | inst = {} |
316 | inst['mac_address'] = utils.generate_mac() |
317 | inst['launch_index'] = num |
318 | |
319 | === modified file 'nova/auth/manager.py' |
320 | --- nova/auth/manager.py 2010-10-12 14:23:24 +0000 |
321 | +++ nova/auth/manager.py 2010-10-12 20:19:40 +0000 |
322 | @@ -490,6 +490,7 @@ |
323 | except: |
324 | drv.delete_project(project.id) |
325 | raise |
326 | + |
327 | return project |
328 | |
329 | def modify_project(self, project, manager_user=None, description=None): |
330 | @@ -565,6 +566,7 @@ |
331 | except: |
332 | logging.exception('Could not destroy network for %s', |
333 | project) |
334 | + |
335 | with self.driver() as drv: |
336 | drv.delete_project(Project.safe_id(project)) |
337 | |
338 | |
339 | === modified file 'nova/compute/manager.py' |
340 | --- nova/compute/manager.py 2010-10-12 17:42:43 +0000 |
341 | +++ nova/compute/manager.py 2010-10-12 20:19:40 +0000 |
342 | @@ -64,6 +64,11 @@ |
343 | |
344 | @defer.inlineCallbacks |
345 | @exception.wrap_exception |
346 | + def refresh_security_group(self, context, security_group_id, **_kwargs): |
347 | + yield self.driver.refresh_security_group(security_group_id) |
348 | + |
349 | + @defer.inlineCallbacks |
350 | + @exception.wrap_exception |
351 | def run_instance(self, context, instance_id, **_kwargs): |
352 | """Launch a new instance with specified options.""" |
353 | instance_ref = self.db.instance_get(context, instance_id) |
354 | |
355 | === modified file 'nova/db/api.py' |
356 | --- nova/db/api.py 2010-10-12 14:23:24 +0000 |
357 | +++ nova/db/api.py 2010-10-12 20:19:40 +0000 |
358 | @@ -304,6 +304,11 @@ |
359 | return IMPL.instance_update(context, instance_id, values) |
360 | |
361 | |
362 | +def instance_add_security_group(context, instance_id, security_group_id): |
363 | + """Associate the given security group with the given instance""" |
364 | + return IMPL.instance_add_security_group(context, instance_id, security_group_id) |
365 | + |
366 | + |
367 | ################### |
368 | |
369 | |
370 | @@ -571,6 +576,71 @@ |
371 | return IMPL.volume_update(context, volume_id, values) |
372 | |
373 | |
374 | +#################### |
375 | + |
376 | + |
377 | +def security_group_get_all(context): |
378 | + """Get all security groups""" |
379 | + return IMPL.security_group_get_all(context) |
380 | + |
381 | + |
382 | +def security_group_get(context, security_group_id): |
383 | + """Get security group by its internal id""" |
384 | + return IMPL.security_group_get(context, security_group_id) |
385 | + |
386 | + |
387 | +def security_group_get_by_name(context, project_id, group_name): |
388 | + """Returns a security group with the specified name from a project""" |
389 | + return IMPL.security_group_get_by_name(context, project_id, group_name) |
390 | + |
391 | + |
392 | +def security_group_get_by_project(context, project_id): |
393 | + """Get all security groups belonging to a project""" |
394 | + return IMPL.security_group_get_by_project(context, project_id) |
395 | + |
396 | + |
397 | +def security_group_get_by_instance(context, instance_id): |
398 | + """Get security groups to which the instance is assigned""" |
399 | + return IMPL.security_group_get_by_instance(context, instance_id) |
400 | + |
401 | + |
402 | +def security_group_exists(context, project_id, group_name): |
403 | + """Indicates if a group name exists in a project""" |
404 | + return IMPL.security_group_exists(context, project_id, group_name) |
405 | + |
406 | + |
407 | +def security_group_create(context, values): |
408 | + """Create a new security group""" |
409 | + return IMPL.security_group_create(context, values) |
410 | + |
411 | + |
412 | +def security_group_destroy(context, security_group_id): |
413 | + """Deletes a security group""" |
414 | + return IMPL.security_group_destroy(context, security_group_id) |
415 | + |
416 | + |
417 | +def security_group_destroy_all(context): |
418 | + """Deletes a security group""" |
419 | + return IMPL.security_group_destroy_all(context) |
420 | + |
421 | + |
422 | +#################### |
423 | + |
424 | + |
425 | +def security_group_rule_create(context, values): |
426 | + """Create a new security group""" |
427 | + return IMPL.security_group_rule_create(context, values) |
428 | + |
429 | + |
430 | +def security_group_rule_get_by_security_group(context, security_group_id): |
431 | + """Get all rules for a a given security group""" |
432 | + return IMPL.security_group_rule_get_by_security_group(context, security_group_id) |
433 | + |
434 | +def security_group_rule_destroy(context, security_group_rule_id): |
435 | + """Deletes a security group rule""" |
436 | + return IMPL.security_group_rule_destroy(context, security_group_rule_id) |
437 | + |
438 | + |
439 | ################### |
440 | |
441 | |
442 | |
443 | === modified file 'nova/db/sqlalchemy/api.py' |
444 | --- nova/db/sqlalchemy/api.py 2010-10-05 19:52:12 +0000 |
445 | +++ nova/db/sqlalchemy/api.py 2010-10-12 20:19:40 +0000 |
446 | @@ -29,8 +29,11 @@ |
447 | from nova.db.sqlalchemy.session import get_session |
448 | from sqlalchemy import or_ |
449 | from sqlalchemy.exc import IntegrityError |
450 | -from sqlalchemy.orm import joinedload, joinedload_all |
451 | -from sqlalchemy.sql import exists, func |
452 | +from sqlalchemy.orm import joinedload |
453 | +from sqlalchemy.orm import joinedload_all |
454 | +from sqlalchemy.sql import exists |
455 | +from sqlalchemy.sql import func |
456 | +from sqlalchemy.orm.exc import NoResultFound |
457 | |
458 | FLAGS = flags.FLAGS |
459 | |
460 | @@ -571,11 +574,13 @@ |
461 | |
462 | if is_admin_context(context): |
463 | result = session.query(models.Instance |
464 | + ).options(joinedload('security_groups') |
465 | ).filter_by(id=instance_id |
466 | ).filter_by(deleted=can_read_deleted(context) |
467 | ).first() |
468 | elif is_user_context(context): |
469 | result = session.query(models.Instance |
470 | + ).options(joinedload('security_groups') |
471 | ).filter_by(project_id=context.project.id |
472 | ).filter_by(id=instance_id |
473 | ).filter_by(deleted=False |
474 | @@ -591,6 +596,7 @@ |
475 | session = get_session() |
476 | return session.query(models.Instance |
477 | ).options(joinedload_all('fixed_ip.floating_ips') |
478 | + ).options(joinedload('security_groups') |
479 | ).filter_by(deleted=can_read_deleted(context) |
480 | ).all() |
481 | |
482 | @@ -600,6 +606,7 @@ |
483 | session = get_session() |
484 | return session.query(models.Instance |
485 | ).options(joinedload_all('fixed_ip.floating_ips') |
486 | + ).options(joinedload('security_groups') |
487 | ).filter_by(deleted=can_read_deleted(context) |
488 | ).filter_by(user_id=user_id |
489 | ).all() |
490 | @@ -612,6 +619,7 @@ |
491 | session = get_session() |
492 | return session.query(models.Instance |
493 | ).options(joinedload_all('fixed_ip.floating_ips') |
494 | + ).options(joinedload('security_groups') |
495 | ).filter_by(project_id=project_id |
496 | ).filter_by(deleted=can_read_deleted(context) |
497 | ).all() |
498 | @@ -624,12 +632,14 @@ |
499 | if is_admin_context(context): |
500 | return session.query(models.Instance |
501 | ).options(joinedload_all('fixed_ip.floating_ips') |
502 | + ).options(joinedload('security_groups') |
503 | ).filter_by(reservation_id=reservation_id |
504 | ).filter_by(deleted=can_read_deleted(context) |
505 | ).all() |
506 | elif is_user_context(context): |
507 | return session.query(models.Instance |
508 | ).options(joinedload_all('fixed_ip.floating_ips') |
509 | + ).options(joinedload('security_groups') |
510 | ).filter_by(project_id=context.project.id |
511 | ).filter_by(reservation_id=reservation_id |
512 | ).filter_by(deleted=False |
513 | @@ -642,11 +652,13 @@ |
514 | |
515 | if is_admin_context(context): |
516 | result = session.query(models.Instance |
517 | + ).options(joinedload('security_groups') |
518 | ).filter_by(internal_id=internal_id |
519 | ).filter_by(deleted=can_read_deleted(context) |
520 | ).first() |
521 | elif is_user_context(context): |
522 | result = session.query(models.Instance |
523 | + ).options(joinedload('security_groups') |
524 | ).filter_by(project_id=context.project.id |
525 | ).filter_by(internal_id=internal_id |
526 | ).filter_by(deleted=False |
527 | @@ -718,6 +730,18 @@ |
528 | instance_ref.save(session=session) |
529 | |
530 | |
531 | +def instance_add_security_group(context, instance_id, security_group_id): |
532 | + """Associate the given security group with the given instance""" |
533 | + session = get_session() |
534 | + with session.begin(): |
535 | + instance_ref = instance_get(context, instance_id, session=session) |
536 | + security_group_ref = security_group_get(context, |
537 | + security_group_id, |
538 | + session=session) |
539 | + instance_ref.security_groups += [security_group_ref] |
540 | + instance_ref.save(session=session) |
541 | + |
542 | + |
543 | ################### |
544 | |
545 | |
546 | @@ -1192,6 +1216,7 @@ |
547 | |
548 | @require_admin_context |
549 | def volume_get_all(context): |
550 | + session = get_session() |
551 | return session.query(models.Volume |
552 | ).filter_by(deleted=can_read_deleted(context) |
553 | ).all() |
554 | @@ -1282,6 +1307,163 @@ |
555 | ################### |
556 | |
557 | |
558 | +@require_context |
559 | +def security_group_get_all(context): |
560 | + session = get_session() |
561 | + return session.query(models.SecurityGroup |
562 | + ).filter_by(deleted=can_read_deleted(context) |
563 | + ).options(joinedload_all('rules') |
564 | + ).all() |
565 | + |
566 | + |
567 | +@require_context |
568 | +def security_group_get(context, security_group_id, session=None): |
569 | + if not session: |
570 | + session = get_session() |
571 | + if is_admin_context(context): |
572 | + result = session.query(models.SecurityGroup |
573 | + ).filter_by(deleted=can_read_deleted(context), |
574 | + ).filter_by(id=security_group_id |
575 | + ).options(joinedload_all('rules') |
576 | + ).first() |
577 | + else: |
578 | + result = session.query(models.SecurityGroup |
579 | + ).filter_by(deleted=False |
580 | + ).filter_by(id=security_group_id |
581 | + ).filter_by(project_id=context.project_id |
582 | + ).options(joinedload_all('rules') |
583 | + ).first() |
584 | + if not result: |
585 | + raise exception.NotFound("No secuity group with id %s" % |
586 | + security_group_id) |
587 | + return result |
588 | + |
589 | + |
590 | +@require_context |
591 | +def security_group_get_by_name(context, project_id, group_name): |
592 | + session = get_session() |
593 | + result = session.query(models.SecurityGroup |
594 | + ).filter_by(project_id=project_id |
595 | + ).filter_by(name=group_name |
596 | + ).filter_by(deleted=False |
597 | + ).options(joinedload_all('rules') |
598 | + ).options(joinedload_all('instances') |
599 | + ).first() |
600 | + if not result: |
601 | + raise exception.NotFound( |
602 | + 'No security group named %s for project: %s' \ |
603 | + % (group_name, project_id)) |
604 | + return result |
605 | + |
606 | + |
607 | +@require_context |
608 | +def security_group_get_by_project(context, project_id): |
609 | + session = get_session() |
610 | + return session.query(models.SecurityGroup |
611 | + ).filter_by(project_id=project_id |
612 | + ).filter_by(deleted=False |
613 | + ).options(joinedload_all('rules') |
614 | + ).all() |
615 | + |
616 | + |
617 | +@require_context |
618 | +def security_group_get_by_instance(context, instance_id): |
619 | + session = get_session() |
620 | + return session.query(models.SecurityGroup |
621 | + ).filter_by(deleted=False |
622 | + ).options(joinedload_all('rules') |
623 | + ).join(models.SecurityGroup.instances |
624 | + ).filter_by(id=instance_id |
625 | + ).filter_by(deleted=False |
626 | + ).all() |
627 | + |
628 | + |
629 | +@require_context |
630 | +def security_group_exists(context, project_id, group_name): |
631 | + try: |
632 | + group = security_group_get_by_name(context, project_id, group_name) |
633 | + return group != None |
634 | + except exception.NotFound: |
635 | + return False |
636 | + |
637 | + |
638 | +@require_context |
639 | +def security_group_create(context, values): |
640 | + security_group_ref = models.SecurityGroup() |
641 | + # FIXME(devcamcar): Unless I do this, rules fails with lazy load exception |
642 | + # once save() is called. This will get cleaned up in next orm pass. |
643 | + security_group_ref.rules |
644 | + for (key, value) in values.iteritems(): |
645 | + security_group_ref[key] = value |
646 | + security_group_ref.save() |
647 | + return security_group_ref |
648 | + |
649 | + |
650 | +@require_context |
651 | +def security_group_destroy(context, security_group_id): |
652 | + session = get_session() |
653 | + with session.begin(): |
654 | + # TODO(vish): do we have to use sql here? |
655 | + session.execute('update security_groups set deleted=1 where id=:id', |
656 | + {'id': security_group_id}) |
657 | + session.execute('update security_group_rules set deleted=1 ' |
658 | + 'where group_id=:id', |
659 | + {'id': security_group_id}) |
660 | + |
661 | +@require_context |
662 | +def security_group_destroy_all(context, session=None): |
663 | + if not session: |
664 | + session = get_session() |
665 | + with session.begin(): |
666 | + # TODO(vish): do we have to use sql here? |
667 | + session.execute('update security_groups set deleted=1') |
668 | + session.execute('update security_group_rules set deleted=1') |
669 | + |
670 | + |
671 | +################### |
672 | + |
673 | + |
674 | +@require_context |
675 | +def security_group_rule_get(context, security_group_rule_id, session=None): |
676 | + if not session: |
677 | + session = get_session() |
678 | + if is_admin_context(context): |
679 | + result = session.query(models.SecurityGroupIngressRule |
680 | + ).filter_by(deleted=can_read_deleted(context) |
681 | + ).filter_by(id=security_group_rule_id |
682 | + ).first() |
683 | + else: |
684 | + # TODO(vish): Join to group and check for project_id |
685 | + result = session.query(models.SecurityGroupIngressRule |
686 | + ).filter_by(deleted=False |
687 | + ).filter_by(id=security_group_rule_id |
688 | + ).first() |
689 | + if not result: |
690 | + raise exception.NotFound("No secuity group rule with id %s" % |
691 | + security_group_rule_id) |
692 | + return result |
693 | + |
694 | + |
695 | +@require_context |
696 | +def security_group_rule_create(context, values): |
697 | + security_group_rule_ref = models.SecurityGroupIngressRule() |
698 | + for (key, value) in values.iteritems(): |
699 | + security_group_rule_ref[key] = value |
700 | + security_group_rule_ref.save() |
701 | + return security_group_rule_ref |
702 | + |
703 | +@require_context |
704 | +def security_group_rule_destroy(context, security_group_rule_id): |
705 | + session = get_session() |
706 | + with session.begin(): |
707 | + security_group_rule = security_group_rule_get(context, |
708 | + security_group_rule_id, |
709 | + session=session) |
710 | + security_group_rule.delete(session=session) |
711 | + |
712 | + |
713 | +################### |
714 | + |
715 | @require_admin_context |
716 | def user_get(context, id, session=None): |
717 | if not session: |
718 | @@ -1491,6 +1673,8 @@ |
719 | ################### |
720 | |
721 | |
722 | + |
723 | +@require_admin_context |
724 | def host_get_networks(context, host): |
725 | session = get_session() |
726 | with session.begin(): |
727 | |
728 | === modified file 'nova/db/sqlalchemy/models.py' |
729 | --- nova/db/sqlalchemy/models.py 2010-10-12 19:02:24 +0000 |
730 | +++ nova/db/sqlalchemy/models.py 2010-10-12 20:19:40 +0000 |
731 | @@ -187,7 +187,6 @@ |
732 | launch_index = Column(Integer) |
733 | key_name = Column(String(255)) |
734 | key_data = Column(Text) |
735 | - security_group = Column(String(255)) |
736 | |
737 | state = Column(Integer) |
738 | state_description = Column(String(255)) |
739 | @@ -289,10 +288,66 @@ |
740 | 'ExportDevice.deleted==False)') |
741 | |
742 | |
743 | +class SecurityGroupInstanceAssociation(BASE, NovaBase): |
744 | + __tablename__ = 'security_group_instance_association' |
745 | + id = Column(Integer, primary_key=True) |
746 | + security_group_id = Column(Integer, ForeignKey('security_groups.id')) |
747 | + instance_id = Column(Integer, ForeignKey('instances.id')) |
748 | + |
749 | + |
750 | +class SecurityGroup(BASE, NovaBase): |
751 | + """Represents a security group""" |
752 | + __tablename__ = 'security_groups' |
753 | + id = Column(Integer, primary_key=True) |
754 | + |
755 | + name = Column(String(255)) |
756 | + description = Column(String(255)) |
757 | + user_id = Column(String(255)) |
758 | + project_id = Column(String(255)) |
759 | + |
760 | + instances = relationship(Instance, |
761 | + secondary="security_group_instance_association", |
762 | + primaryjoin="and_(SecurityGroup.id == SecurityGroupInstanceAssociation.security_group_id," |
763 | + "SecurityGroup.deleted == False)", |
764 | + secondaryjoin="and_(SecurityGroupInstanceAssociation.instance_id == Instance.id," |
765 | + "Instance.deleted == False)", |
766 | + backref='security_groups') |
767 | + |
768 | + @property |
769 | + def user(self): |
770 | + return auth.manager.AuthManager().get_user(self.user_id) |
771 | + |
772 | + @property |
773 | + def project(self): |
774 | + return auth.manager.AuthManager().get_project(self.project_id) |
775 | + |
776 | + |
777 | +class SecurityGroupIngressRule(BASE, NovaBase): |
778 | + """Represents a rule in a security group""" |
779 | + __tablename__ = 'security_group_rules' |
780 | + id = Column(Integer, primary_key=True) |
781 | + |
782 | + parent_group_id = Column(Integer, ForeignKey('security_groups.id')) |
783 | + parent_group = relationship("SecurityGroup", backref="rules", |
784 | + foreign_keys=parent_group_id, |
785 | + primaryjoin="and_(SecurityGroupIngressRule.parent_group_id == SecurityGroup.id," |
786 | + "SecurityGroupIngressRule.deleted == False)") |
787 | + |
788 | + protocol = Column(String(5)) # "tcp", "udp", or "icmp" |
789 | + from_port = Column(Integer) |
790 | + to_port = Column(Integer) |
791 | + cidr = Column(String(255)) |
792 | + |
793 | + # Note: This is not the parent SecurityGroup. It's SecurityGroup we're |
794 | + # granting access for. |
795 | + group_id = Column(Integer, ForeignKey('security_groups.id')) |
796 | + |
797 | + |
798 | class KeyPair(BASE, NovaBase): |
799 | """Represents a public key pair for ssh""" |
800 | __tablename__ = 'key_pairs' |
801 | id = Column(Integer, primary_key=True) |
802 | + |
803 | name = Column(String(255)) |
804 | |
805 | user_id = Column(String(255)) |
806 | @@ -461,9 +516,10 @@ |
807 | def register_models(): |
808 | """Register Models and create metadata""" |
809 | from sqlalchemy import create_engine |
810 | - models = (Service, Instance, Volume, ExportDevice, |
811 | - FixedIp, FloatingIp, Network, NetworkIndex, |
812 | - AuthToken, UserProjectAssociation, User, Project) # , Image, Host) |
813 | + models = (Service, Instance, Volume, ExportDevice, FixedIp, |
814 | + FloatingIp, Network, NetworkIndex, SecurityGroup, |
815 | + SecurityGroupIngressRule, SecurityGroupInstanceAssociation, |
816 | + AuthToken, User, Project) # , Image, Host |
817 | engine = create_engine(FLAGS.sql_connection, echo=False) |
818 | for model in models: |
819 | model.metadata.create_all(engine) |
820 | |
821 | === modified file 'nova/db/sqlalchemy/session.py' |
822 | --- nova/db/sqlalchemy/session.py 2010-09-08 02:48:12 +0000 |
823 | +++ nova/db/sqlalchemy/session.py 2010-10-12 20:19:40 +0000 |
824 | @@ -36,7 +36,8 @@ |
825 | if not _MAKER: |
826 | if not _ENGINE: |
827 | _ENGINE = create_engine(FLAGS.sql_connection, echo=False) |
828 | - _MAKER = sessionmaker(bind=_ENGINE, |
829 | - autocommit=autocommit, |
830 | - expire_on_commit=expire_on_commit) |
831 | - return _MAKER() |
832 | + _MAKER = (sessionmaker(bind=_ENGINE, |
833 | + autocommit=autocommit, |
834 | + expire_on_commit=expire_on_commit)) |
835 | + session = _MAKER() |
836 | + return session |
837 | |
838 | === modified file 'nova/exception.py' |
839 | --- nova/exception.py 2010-09-08 04:53:40 +0000 |
840 | +++ nova/exception.py 2010-10-12 20:19:40 +0000 |
841 | @@ -69,6 +69,9 @@ |
842 | class Invalid(Error): |
843 | pass |
844 | |
845 | +class InvalidInputException(Error): |
846 | + pass |
847 | + |
848 | |
849 | def wrap_exception(f): |
850 | def _wrap(*args, **kw): |
851 | |
852 | === modified file 'nova/network/manager.py' |
853 | --- nova/network/manager.py 2010-10-04 07:38:16 +0000 |
854 | +++ nova/network/manager.py 2010-10-12 20:19:40 +0000 |
855 | @@ -211,7 +211,6 @@ |
856 | # in the datastore? |
857 | net = {} |
858 | net['injected'] = True |
859 | - net['network_str'] = FLAGS.flat_network_network |
860 | net['netmask'] = FLAGS.flat_network_netmask |
861 | net['bridge'] = FLAGS.flat_network_bridge |
862 | net['gateway'] = FLAGS.flat_network_gateway |
863 | |
864 | === modified file 'nova/process.py' |
865 | --- nova/process.py 2010-09-11 15:16:16 +0000 |
866 | +++ nova/process.py 2010-10-12 20:19:40 +0000 |
867 | @@ -113,7 +113,7 @@ |
868 | if self.started_deferred: |
869 | self.started_deferred.callback(self) |
870 | if self.process_input: |
871 | - self.transport.write(self.process_input) |
872 | + self.transport.write(str(self.process_input)) |
873 | self.transport.closeStdin() |
874 | |
875 | def get_process_output(executable, args=None, env=None, path=None, |
876 | |
877 | === modified file 'nova/test.py' |
878 | --- nova/test.py 2010-09-25 02:25:12 +0000 |
879 | +++ nova/test.py 2010-10-12 20:19:40 +0000 |
880 | @@ -31,6 +31,7 @@ |
881 | from twisted.internet import defer |
882 | from twisted.trial import unittest |
883 | |
884 | +from nova import db |
885 | from nova import fakerabbit |
886 | from nova import flags |
887 | from nova import rpc |
888 | @@ -83,6 +84,7 @@ |
889 | |
890 | if FLAGS.fake_rabbit: |
891 | fakerabbit.reset_all() |
892 | + db.security_group_destroy_all(None) |
893 | |
894 | super(TrialTestCase, self).tearDown() |
895 | |
896 | |
897 | === modified file 'nova/tests/api_unittest.py' |
898 | --- nova/tests/api_unittest.py 2010-09-23 14:45:21 +0000 |
899 | +++ nova/tests/api_unittest.py 2010-10-12 20:19:40 +0000 |
900 | @@ -91,6 +91,9 @@ |
901 | self.host = '127.0.0.1' |
902 | |
903 | self.app = api.API() |
904 | + |
905 | + def expect_http(self, host=None, is_secure=False): |
906 | + """Returns a new EC2 connection""" |
907 | self.ec2 = boto.connect_ec2( |
908 | aws_access_key_id='fake', |
909 | aws_secret_access_key='fake', |
910 | @@ -100,9 +103,6 @@ |
911 | path='/services/Cloud') |
912 | |
913 | self.mox.StubOutWithMock(self.ec2, 'new_http_connection') |
914 | - |
915 | - def expect_http(self, host=None, is_secure=False): |
916 | - """Returns a new EC2 connection""" |
917 | http = FakeHttplibConnection( |
918 | self.app, '%s:8773' % (self.host), False) |
919 | # pylint: disable-msg=E1103 |
920 | @@ -138,3 +138,185 @@ |
921 | self.assertEquals(len(results), 1) |
922 | self.manager.delete_project(project) |
923 | self.manager.delete_user(user) |
924 | + |
925 | + def test_get_all_security_groups(self): |
926 | + """Test that we can retrieve security groups""" |
927 | + self.expect_http() |
928 | + self.mox.ReplayAll() |
929 | + user = self.manager.create_user('fake', 'fake', 'fake', admin=True) |
930 | + project = self.manager.create_project('fake', 'fake', 'fake') |
931 | + |
932 | + rv = self.ec2.get_all_security_groups() |
933 | + |
934 | + self.assertEquals(len(rv), 1) |
935 | + self.assertEquals(rv[0].name, 'default') |
936 | + |
937 | + self.manager.delete_project(project) |
938 | + self.manager.delete_user(user) |
939 | + |
940 | + def test_create_delete_security_group(self): |
941 | + """Test that we can create a security group""" |
942 | + self.expect_http() |
943 | + self.mox.ReplayAll() |
944 | + user = self.manager.create_user('fake', 'fake', 'fake', admin=True) |
945 | + project = self.manager.create_project('fake', 'fake', 'fake') |
946 | + |
947 | + # At the moment, you need both of these to actually be netadmin |
948 | + self.manager.add_role('fake', 'netadmin') |
949 | + project.add_role('fake', 'netadmin') |
950 | + |
951 | + security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ |
952 | + for x in range(random.randint(4, 8))) |
953 | + |
954 | + self.ec2.create_security_group(security_group_name, 'test group') |
955 | + |
956 | + self.expect_http() |
957 | + self.mox.ReplayAll() |
958 | + |
959 | + rv = self.ec2.get_all_security_groups() |
960 | + self.assertEquals(len(rv), 2) |
961 | + self.assertTrue(security_group_name in [group.name for group in rv]) |
962 | + |
963 | + self.expect_http() |
964 | + self.mox.ReplayAll() |
965 | + |
966 | + self.ec2.delete_security_group(security_group_name) |
967 | + |
968 | + self.manager.delete_project(project) |
969 | + self.manager.delete_user(user) |
970 | + |
971 | + def test_authorize_revoke_security_group_cidr(self): |
972 | + """ |
973 | + Test that we can add and remove CIDR based rules |
974 | + to a security group |
975 | + """ |
976 | + self.expect_http() |
977 | + self.mox.ReplayAll() |
978 | + user = self.manager.create_user('fake', 'fake', 'fake') |
979 | + project = self.manager.create_project('fake', 'fake', 'fake') |
980 | + |
981 | + # At the moment, you need both of these to actually be netadmin |
982 | + self.manager.add_role('fake', 'netadmin') |
983 | + project.add_role('fake', 'netadmin') |
984 | + |
985 | + security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ |
986 | + for x in range(random.randint(4, 8))) |
987 | + |
988 | + group = self.ec2.create_security_group(security_group_name, 'test group') |
989 | + |
990 | + self.expect_http() |
991 | + self.mox.ReplayAll() |
992 | + group.connection = self.ec2 |
993 | + |
994 | + group.authorize('tcp', 80, 81, '0.0.0.0/0') |
995 | + |
996 | + self.expect_http() |
997 | + self.mox.ReplayAll() |
998 | + |
999 | + rv = self.ec2.get_all_security_groups() |
1000 | + # I don't bother checkng that we actually find it here, |
1001 | + # because the create/delete unit test further up should |
1002 | + # be good enough for that. |
1003 | + for group in rv: |
1004 | + if group.name == security_group_name: |
1005 | + self.assertEquals(len(group.rules), 1) |
1006 | + self.assertEquals(int(group.rules[0].from_port), 80) |
1007 | + self.assertEquals(int(group.rules[0].to_port), 81) |
1008 | + self.assertEquals(len(group.rules[0].grants), 1) |
1009 | + self.assertEquals(str(group.rules[0].grants[0]), '0.0.0.0/0') |
1010 | + |
1011 | + self.expect_http() |
1012 | + self.mox.ReplayAll() |
1013 | + group.connection = self.ec2 |
1014 | + |
1015 | + group.revoke('tcp', 80, 81, '0.0.0.0/0') |
1016 | + |
1017 | + self.expect_http() |
1018 | + self.mox.ReplayAll() |
1019 | + |
1020 | + self.ec2.delete_security_group(security_group_name) |
1021 | + |
1022 | + self.expect_http() |
1023 | + self.mox.ReplayAll() |
1024 | + group.connection = self.ec2 |
1025 | + |
1026 | + rv = self.ec2.get_all_security_groups() |
1027 | + |
1028 | + self.assertEqual(len(rv), 1) |
1029 | + self.assertEqual(rv[0].name, 'default') |
1030 | + |
1031 | + self.manager.delete_project(project) |
1032 | + self.manager.delete_user(user) |
1033 | + |
1034 | + return |
1035 | + |
1036 | + def test_authorize_revoke_security_group_foreign_group(self): |
1037 | + """ |
1038 | + Test that we can grant and revoke another security group access |
1039 | + to a security group |
1040 | + """ |
1041 | + self.expect_http() |
1042 | + self.mox.ReplayAll() |
1043 | + user = self.manager.create_user('fake', 'fake', 'fake', admin=True) |
1044 | + project = self.manager.create_project('fake', 'fake', 'fake') |
1045 | + |
1046 | + # At the moment, you need both of these to actually be netadmin |
1047 | + self.manager.add_role('fake', 'netadmin') |
1048 | + project.add_role('fake', 'netadmin') |
1049 | + |
1050 | + security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ |
1051 | + for x in range(random.randint(4, 8))) |
1052 | + other_security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd") \ |
1053 | + for x in range(random.randint(4, 8))) |
1054 | + |
1055 | + group = self.ec2.create_security_group(security_group_name, 'test group') |
1056 | + |
1057 | + self.expect_http() |
1058 | + self.mox.ReplayAll() |
1059 | + |
1060 | + other_group = self.ec2.create_security_group(other_security_group_name, |
1061 | + 'some other group') |
1062 | + |
1063 | + self.expect_http() |
1064 | + self.mox.ReplayAll() |
1065 | + group.connection = self.ec2 |
1066 | + |
1067 | + group.authorize(src_group=other_group) |
1068 | + |
1069 | + self.expect_http() |
1070 | + self.mox.ReplayAll() |
1071 | + |
1072 | + rv = self.ec2.get_all_security_groups() |
1073 | + |
1074 | + # I don't bother checkng that we actually find it here, |
1075 | + # because the create/delete unit test further up should |
1076 | + # be good enough for that. |
1077 | + for group in rv: |
1078 | + if group.name == security_group_name: |
1079 | + self.assertEquals(len(group.rules), 1) |
1080 | + self.assertEquals(len(group.rules[0].grants), 1) |
1081 | + self.assertEquals(str(group.rules[0].grants[0]), |
1082 | + '%s-%s' % (other_security_group_name, 'fake')) |
1083 | + |
1084 | + |
1085 | + self.expect_http() |
1086 | + self.mox.ReplayAll() |
1087 | + |
1088 | + rv = self.ec2.get_all_security_groups() |
1089 | + |
1090 | + for group in rv: |
1091 | + if group.name == security_group_name: |
1092 | + self.expect_http() |
1093 | + self.mox.ReplayAll() |
1094 | + group.connection = self.ec2 |
1095 | + group.revoke(src_group=other_group) |
1096 | + |
1097 | + self.expect_http() |
1098 | + self.mox.ReplayAll() |
1099 | + |
1100 | + self.ec2.delete_security_group(security_group_name) |
1101 | + |
1102 | + self.manager.delete_project(project) |
1103 | + self.manager.delete_user(user) |
1104 | + |
1105 | + return |
1106 | |
1107 | === modified file 'nova/tests/objectstore_unittest.py' |
1108 | --- nova/tests/objectstore_unittest.py 2010-10-11 12:09:24 +0000 |
1109 | +++ nova/tests/objectstore_unittest.py 2010-10-12 20:19:40 +0000 |
1110 | @@ -210,7 +210,7 @@ |
1111 | """Setup users, projects, and start a test server.""" |
1112 | super(S3APITestCase, self).setUp() |
1113 | |
1114 | - FLAGS.auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver', |
1115 | + FLAGS.auth_driver = 'nova.auth.ldapdriver.FakeLdapDriver' |
1116 | FLAGS.buckets_path = os.path.join(OSS_TEMPDIR, 'buckets') |
1117 | |
1118 | self.auth_manager = manager.AuthManager() |
1119 | |
1120 | === modified file 'nova/tests/virt_unittest.py' |
1121 | --- nova/tests/virt_unittest.py 2010-10-05 08:07:37 +0000 |
1122 | +++ nova/tests/virt_unittest.py 2010-10-12 20:19:40 +0000 |
1123 | @@ -14,11 +14,16 @@ |
1124 | # License for the specific language governing permissions and limitations |
1125 | # under the License. |
1126 | |
1127 | -from xml.etree.ElementTree import fromstring as parseXml |
1128 | +from xml.etree.ElementTree import fromstring as xml_to_tree |
1129 | +from xml.dom.minidom import parseString as xml_to_dom |
1130 | |
1131 | +from nova import db |
1132 | from nova import flags |
1133 | from nova import test |
1134 | +from nova.api import context |
1135 | +from nova.api.ec2 import cloud |
1136 | from nova.auth import manager |
1137 | + |
1138 | # Needed to get FLAGS.instances_path defined: |
1139 | from nova.compute import manager as compute_manager |
1140 | from nova.virt import libvirt_conn |
1141 | @@ -33,34 +38,49 @@ |
1142 | FLAGS.instances_path = '' |
1143 | |
1144 | def test_get_uri_and_template(self): |
1145 | - instance = { 'name' : 'i-cafebabe', |
1146 | - 'id' : 'i-cafebabe', |
1147 | + ip = '10.11.12.13' |
1148 | + |
1149 | + instance = { 'internal_id' : 1, |
1150 | 'memory_kb' : '1024000', |
1151 | 'basepath' : '/some/path', |
1152 | 'bridge_name' : 'br100', |
1153 | 'mac_address' : '02:12:34:46:56:67', |
1154 | 'vcpus' : 2, |
1155 | 'project_id' : 'fake', |
1156 | - 'ip_address' : '10.11.12.13', |
1157 | 'bridge' : 'br101', |
1158 | 'instance_type' : 'm1.small'} |
1159 | |
1160 | + instance_ref = db.instance_create(None, instance) |
1161 | + network_ref = db.project_get_network(None, self.project.id) |
1162 | + |
1163 | + fixed_ip = { 'address' : ip, |
1164 | + 'network_id' : network_ref['id'] } |
1165 | + |
1166 | + fixed_ip_ref = db.fixed_ip_create(None, fixed_ip) |
1167 | + db.fixed_ip_update(None, ip, { 'allocated' : True, |
1168 | + 'instance_id' : instance_ref['id'] }) |
1169 | + |
1170 | type_uri_map = { 'qemu' : ('qemu:///system', |
1171 | - [(lambda t: t.find('.').tag, 'domain'), |
1172 | - (lambda t: t.find('.').get('type'), 'qemu'), |
1173 | + [(lambda t: t.find('.').get('type'), 'qemu'), |
1174 | (lambda t: t.find('./os/type').text, 'hvm'), |
1175 | (lambda t: t.find('./devices/emulator'), None)]), |
1176 | 'kvm' : ('qemu:///system', |
1177 | - [(lambda t: t.find('.').tag, 'domain'), |
1178 | - (lambda t: t.find('.').get('type'), 'kvm'), |
1179 | + [(lambda t: t.find('.').get('type'), 'kvm'), |
1180 | (lambda t: t.find('./os/type').text, 'hvm'), |
1181 | (lambda t: t.find('./devices/emulator'), None)]), |
1182 | 'uml' : ('uml:///system', |
1183 | - [(lambda t: t.find('.').tag, 'domain'), |
1184 | - (lambda t: t.find('.').get('type'), 'uml'), |
1185 | + [(lambda t: t.find('.').get('type'), 'uml'), |
1186 | (lambda t: t.find('./os/type').text, 'uml')]), |
1187 | } |
1188 | |
1189 | + common_checks = [(lambda t: t.find('.').tag, 'domain'), |
1190 | + (lambda t: \ |
1191 | + t.find('./devices/interface/filterref/parameter') \ |
1192 | + .get('name'), 'IP'), |
1193 | + (lambda t: \ |
1194 | + t.find('./devices/interface/filterref/parameter') \ |
1195 | + .get('value'), '10.11.12.13')] |
1196 | + |
1197 | for (libvirt_type,(expected_uri, checks)) in type_uri_map.iteritems(): |
1198 | FLAGS.libvirt_type = libvirt_type |
1199 | conn = libvirt_conn.LibvirtConnection(True) |
1200 | @@ -68,13 +88,18 @@ |
1201 | uri, template = conn.get_uri_and_template() |
1202 | self.assertEquals(uri, expected_uri) |
1203 | |
1204 | - xml = conn.to_xml(instance) |
1205 | - tree = parseXml(xml) |
1206 | + xml = conn.to_xml(instance_ref) |
1207 | + tree = xml_to_tree(xml) |
1208 | for i, (check, expected_result) in enumerate(checks): |
1209 | self.assertEqual(check(tree), |
1210 | expected_result, |
1211 | '%s failed check %d' % (xml, i)) |
1212 | |
1213 | + for i, (check, expected_result) in enumerate(common_checks): |
1214 | + self.assertEqual(check(tree), |
1215 | + expected_result, |
1216 | + '%s failed common check %d' % (xml, i)) |
1217 | + |
1218 | # Deliberately not just assigning this string to FLAGS.libvirt_uri and |
1219 | # checking against that later on. This way we make sure the |
1220 | # implementation doesn't fiddle around with the FLAGS. |
1221 | @@ -90,3 +115,138 @@ |
1222 | def tearDown(self): |
1223 | self.manager.delete_project(self.project) |
1224 | self.manager.delete_user(self.user) |
1225 | + |
1226 | +class NWFilterTestCase(test.TrialTestCase): |
1227 | + def setUp(self): |
1228 | + super(NWFilterTestCase, self).setUp() |
1229 | + |
1230 | + class Mock(object): |
1231 | + pass |
1232 | + |
1233 | + self.manager = manager.AuthManager() |
1234 | + self.user = self.manager.create_user('fake', 'fake', 'fake', admin=True) |
1235 | + self.project = self.manager.create_project('fake', 'fake', 'fake') |
1236 | + self.context = context.APIRequestContext(self.user, self.project) |
1237 | + |
1238 | + self.fake_libvirt_connection = Mock() |
1239 | + |
1240 | + self.fw = libvirt_conn.NWFilterFirewall(self.fake_libvirt_connection) |
1241 | + |
1242 | + def tearDown(self): |
1243 | + self.manager.delete_project(self.project) |
1244 | + self.manager.delete_user(self.user) |
1245 | + |
1246 | + |
1247 | + def test_cidr_rule_nwfilter_xml(self): |
1248 | + cloud_controller = cloud.CloudController() |
1249 | + cloud_controller.create_security_group(self.context, |
1250 | + 'testgroup', |
1251 | + 'test group description') |
1252 | + cloud_controller.authorize_security_group_ingress(self.context, |
1253 | + 'testgroup', |
1254 | + from_port='80', |
1255 | + to_port='81', |
1256 | + ip_protocol='tcp', |
1257 | + cidr_ip='0.0.0.0/0') |
1258 | + |
1259 | + |
1260 | + security_group = db.security_group_get_by_name(self.context, |
1261 | + 'fake', |
1262 | + 'testgroup') |
1263 | + |
1264 | + xml = self.fw.security_group_to_nwfilter_xml(security_group.id) |
1265 | + |
1266 | + dom = xml_to_dom(xml) |
1267 | + self.assertEqual(dom.firstChild.tagName, 'filter') |
1268 | + |
1269 | + rules = dom.getElementsByTagName('rule') |
1270 | + self.assertEqual(len(rules), 1) |
1271 | + |
1272 | + # It's supposed to allow inbound traffic. |
1273 | + self.assertEqual(rules[0].getAttribute('action'), 'accept') |
1274 | + self.assertEqual(rules[0].getAttribute('direction'), 'in') |
1275 | + |
1276 | + # Must be lower priority than the base filter (which blocks everything) |
1277 | + self.assertTrue(int(rules[0].getAttribute('priority')) < 1000) |
1278 | + |
1279 | + ip_conditions = rules[0].getElementsByTagName('tcp') |
1280 | + self.assertEqual(len(ip_conditions), 1) |
1281 | + self.assertEqual(ip_conditions[0].getAttribute('srcipaddr'), '0.0.0.0') |
1282 | + self.assertEqual(ip_conditions[0].getAttribute('srcipmask'), '0.0.0.0') |
1283 | + self.assertEqual(ip_conditions[0].getAttribute('dstportstart'), '80') |
1284 | + self.assertEqual(ip_conditions[0].getAttribute('dstportend'), '81') |
1285 | + |
1286 | + |
1287 | + self.teardown_security_group() |
1288 | + |
1289 | + def teardown_security_group(self): |
1290 | + cloud_controller = cloud.CloudController() |
1291 | + cloud_controller.delete_security_group(self.context, 'testgroup') |
1292 | + |
1293 | + |
1294 | + def setup_and_return_security_group(self): |
1295 | + cloud_controller = cloud.CloudController() |
1296 | + cloud_controller.create_security_group(self.context, |
1297 | + 'testgroup', |
1298 | + 'test group description') |
1299 | + cloud_controller.authorize_security_group_ingress(self.context, |
1300 | + 'testgroup', |
1301 | + from_port='80', |
1302 | + to_port='81', |
1303 | + ip_protocol='tcp', |
1304 | + cidr_ip='0.0.0.0/0') |
1305 | + |
1306 | + return db.security_group_get_by_name(self.context, 'fake', 'testgroup') |
1307 | + |
1308 | + def test_creates_base_rule_first(self): |
1309 | + # These come pre-defined by libvirt |
1310 | + self.defined_filters = ['no-mac-spoofing', |
1311 | + 'no-ip-spoofing', |
1312 | + 'no-arp-spoofing', |
1313 | + 'allow-dhcp-server'] |
1314 | + |
1315 | + self.recursive_depends = {} |
1316 | + for f in self.defined_filters: |
1317 | + self.recursive_depends[f] = [] |
1318 | + |
1319 | + def _filterDefineXMLMock(xml): |
1320 | + dom = xml_to_dom(xml) |
1321 | + name = dom.firstChild.getAttribute('name') |
1322 | + self.recursive_depends[name] = [] |
1323 | + for f in dom.getElementsByTagName('filterref'): |
1324 | + ref = f.getAttribute('filter') |
1325 | + self.assertTrue(ref in self.defined_filters, |
1326 | + ('%s referenced filter that does ' + |
1327 | + 'not yet exist: %s') % (name, ref)) |
1328 | + dependencies = [ref] + self.recursive_depends[ref] |
1329 | + self.recursive_depends[name] += dependencies |
1330 | + |
1331 | + self.defined_filters.append(name) |
1332 | + return True |
1333 | + |
1334 | + self.fake_libvirt_connection.nwfilterDefineXML = _filterDefineXMLMock |
1335 | + |
1336 | + instance_ref = db.instance_create(self.context, |
1337 | + {'user_id': 'fake', |
1338 | + 'project_id': 'fake'}) |
1339 | + inst_id = instance_ref['id'] |
1340 | + |
1341 | + def _ensure_all_called(_): |
1342 | + instance_filter = 'nova-instance-%s' % instance_ref['name'] |
1343 | + secgroup_filter = 'nova-secgroup-%s' % self.security_group['id'] |
1344 | + for required in [secgroup_filter, 'allow-dhcp-server', |
1345 | + 'no-arp-spoofing', 'no-ip-spoofing', |
1346 | + 'no-mac-spoofing']: |
1347 | + self.assertTrue(required in self.recursive_depends[instance_filter], |
1348 | + "Instance's filter does not include %s" % required) |
1349 | + |
1350 | + self.security_group = self.setup_and_return_security_group() |
1351 | + |
1352 | + db.instance_add_security_group(self.context, inst_id, self.security_group.id) |
1353 | + instance = db.instance_get(self.context, inst_id) |
1354 | + |
1355 | + d = self.fw.setup_nwfilters_for_instance(instance) |
1356 | + d.addCallback(_ensure_all_called) |
1357 | + d.addCallback(lambda _:self.teardown_security_group()) |
1358 | + |
1359 | + return d |
1360 | |
1361 | === modified file 'nova/virt/interfaces.template' |
1362 | --- nova/virt/interfaces.template 2010-08-13 21:45:26 +0000 |
1363 | +++ nova/virt/interfaces.template 2010-10-12 20:19:40 +0000 |
1364 | @@ -10,7 +10,6 @@ |
1365 | iface eth0 inet static |
1366 | address %(address)s |
1367 | netmask %(netmask)s |
1368 | - network %(network)s |
1369 | broadcast %(broadcast)s |
1370 | gateway %(gateway)s |
1371 | dns-nameservers %(dns)s |
1372 | |
1373 | === modified file 'nova/virt/libvirt.qemu.xml.template' |
1374 | --- nova/virt/libvirt.qemu.xml.template 2010-09-07 12:34:37 +0000 |
1375 | +++ nova/virt/libvirt.qemu.xml.template 2010-10-12 20:19:40 +0000 |
1376 | @@ -20,6 +20,10 @@ |
1377 | <source bridge='%(bridge_name)s'/> |
1378 | <mac address='%(mac_address)s'/> |
1379 | <!-- <model type='virtio'/> CANT RUN virtio network right now --> |
1380 | + <filterref filter="nova-instance-%(name)s"> |
1381 | + <parameter name="IP" value="%(ip_address)s" /> |
1382 | + <parameter name="DHCPSERVER" value="%(dhcp_server)s" /> |
1383 | + </filterref> |
1384 | </interface> |
1385 | <serial type="file"> |
1386 | <source path='%(basepath)s/console.log'/> |
1387 | |
1388 | === modified file 'nova/virt/libvirt.uml.xml.template' |
1389 | --- nova/virt/libvirt.uml.xml.template 2010-09-07 12:34:37 +0000 |
1390 | +++ nova/virt/libvirt.uml.xml.template 2010-10-12 20:19:40 +0000 |
1391 | @@ -14,6 +14,10 @@ |
1392 | <interface type='bridge'> |
1393 | <source bridge='%(bridge_name)s'/> |
1394 | <mac address='%(mac_address)s'/> |
1395 | + <filterref filter="nova-instance-%(name)s"> |
1396 | + <parameter name="IP" value="%(ip_address)s" /> |
1397 | + <parameter name="DHCPSERVER" value="%(dhcp_server)s" /> |
1398 | + </filterref> |
1399 | </interface> |
1400 | <console type="file"> |
1401 | <source path='%(basepath)s/console.log'/> |
1402 | |
1403 | === modified file 'nova/virt/libvirt_conn.py' |
1404 | --- nova/virt/libvirt_conn.py 2010-09-09 15:55:09 +0000 |
1405 | +++ nova/virt/libvirt_conn.py 2010-10-12 20:19:40 +0000 |
1406 | @@ -25,14 +25,17 @@ |
1407 | import os |
1408 | import shutil |
1409 | |
1410 | +import IPy |
1411 | from twisted.internet import defer |
1412 | from twisted.internet import task |
1413 | +from twisted.internet import threads |
1414 | |
1415 | from nova import db |
1416 | from nova import exception |
1417 | from nova import flags |
1418 | from nova import process |
1419 | from nova import utils |
1420 | +#from nova.api import context |
1421 | from nova.auth import manager |
1422 | from nova.compute import disk |
1423 | from nova.compute import instance_types |
1424 | @@ -60,6 +63,9 @@ |
1425 | '', |
1426 | 'Override the default libvirt URI (which is dependent' |
1427 | ' on libvirt_type)') |
1428 | +flags.DEFINE_bool('allow_project_net_traffic', |
1429 | + True, |
1430 | + 'Whether to allow in project network traffic') |
1431 | |
1432 | |
1433 | def get_connection(read_only): |
1434 | @@ -134,7 +140,7 @@ |
1435 | d.addCallback(lambda _: self._cleanup(instance)) |
1436 | # FIXME: What does this comment mean? |
1437 | # TODO(termie): short-circuit me for tests |
1438 | - # WE'LL save this for when we do shutdown, |
1439 | + # WE'LL save this for when we do shutdown, |
1440 | # instead of destroy - but destroy returns immediately |
1441 | timer = task.LoopingCall(f=None) |
1442 | def _wait_for_shutdown(): |
1443 | @@ -214,6 +220,7 @@ |
1444 | instance['id'], |
1445 | power_state.NOSTATE, |
1446 | 'launching') |
1447 | + yield NWFilterFirewall(self._conn).setup_nwfilters_for_instance(instance) |
1448 | yield self._create_image(instance, xml) |
1449 | yield self._conn.createXML(xml, 0) |
1450 | # TODO(termie): this should actually register |
1451 | @@ -285,7 +292,6 @@ |
1452 | address = db.instance_get_fixed_address(None, inst['id']) |
1453 | with open(FLAGS.injected_network_template) as f: |
1454 | net = f.read() % {'address': address, |
1455 | - 'network': network_ref['network'], |
1456 | 'netmask': network_ref['netmask'], |
1457 | 'gateway': network_ref['gateway'], |
1458 | 'broadcast': network_ref['broadcast'], |
1459 | @@ -317,6 +323,9 @@ |
1460 | network = db.project_get_network(None, instance['project_id']) |
1461 | # FIXME(vish): stick this in db |
1462 | instance_type = instance_types.INSTANCE_TYPES[instance['instance_type']] |
1463 | + ip_address = db.instance_get_fixed_address({}, instance['id']) |
1464 | + # Assume that the gateway also acts as the dhcp server. |
1465 | + dhcp_server = network['gateway'] |
1466 | xml_info = {'type': FLAGS.libvirt_type, |
1467 | 'name': instance['name'], |
1468 | 'basepath': os.path.join(FLAGS.instances_path, |
1469 | @@ -324,7 +333,9 @@ |
1470 | 'memory_kb': instance_type['memory_mb'] * 1024, |
1471 | 'vcpus': instance_type['vcpus'], |
1472 | 'bridge_name': network['bridge'], |
1473 | - 'mac_address': instance['mac_address']} |
1474 | + 'mac_address': instance['mac_address'], |
1475 | + 'ip_address': ip_address, |
1476 | + 'dhcp_server': dhcp_server } |
1477 | libvirt_xml = self.libvirt_xml % xml_info |
1478 | logging.debug('instance %s: finished toXML method', instance['name']) |
1479 | |
1480 | @@ -438,3 +449,195 @@ |
1481 | """ |
1482 | domain = self._conn.lookupByName(instance_name) |
1483 | return domain.interfaceStats(interface) |
1484 | + |
1485 | + |
1486 | + def refresh_security_group(self, security_group_id): |
1487 | + fw = NWFilterFirewall(self._conn) |
1488 | + fw.ensure_security_group_filter(security_group_id) |
1489 | + |
1490 | + |
1491 | +class NWFilterFirewall(object): |
1492 | + """ |
1493 | + This class implements a network filtering mechanism versatile |
1494 | + enough for EC2 style Security Group filtering by leveraging |
1495 | + libvirt's nwfilter. |
1496 | + |
1497 | + First, all instances get a filter ("nova-base-filter") applied. |
1498 | + This filter drops all incoming ipv4 and ipv6 connections. |
1499 | + Outgoing connections are never blocked. |
1500 | + |
1501 | + Second, every security group maps to a nwfilter filter(*). |
1502 | + NWFilters can be updated at runtime and changes are applied |
1503 | + immediately, so changes to security groups can be applied at |
1504 | + runtime (as mandated by the spec). |
1505 | + |
1506 | + Security group rules are named "nova-secgroup-<id>" where <id> |
1507 | + is the internal id of the security group. They're applied only on |
1508 | + hosts that have instances in the security group in question. |
1509 | + |
1510 | + Updates to security groups are done by updating the data model |
1511 | + (in response to API calls) followed by a request sent to all |
1512 | + the nodes with instances in the security group to refresh the |
1513 | + security group. |
1514 | + |
1515 | + Each instance has its own NWFilter, which references the above |
1516 | + mentioned security group NWFilters. This was done because |
1517 | + interfaces can only reference one filter while filters can |
1518 | + reference multiple other filters. This has the added benefit of |
1519 | + actually being able to add and remove security groups from an |
1520 | + instance at run time. This functionality is not exposed anywhere, |
1521 | + though. |
1522 | + |
1523 | + Outstanding questions: |
1524 | + |
1525 | + The name is unique, so would there be any good reason to sync |
1526 | + the uuid across the nodes (by assigning it from the datamodel)? |
1527 | + |
1528 | + |
1529 | + (*) This sentence brought to you by the redundancy department of |
1530 | + redundancy. |
1531 | + """ |
1532 | + |
1533 | + def __init__(self, get_connection): |
1534 | + self._conn = get_connection |
1535 | + |
1536 | + |
1537 | + nova_base_filter = '''<filter name='nova-base' chain='root'> |
1538 | + <uuid>26717364-50cf-42d1-8185-29bf893ab110</uuid> |
1539 | + <filterref filter='no-mac-spoofing'/> |
1540 | + <filterref filter='no-ip-spoofing'/> |
1541 | + <filterref filter='no-arp-spoofing'/> |
1542 | + <filterref filter='allow-dhcp-server'/> |
1543 | + <filterref filter='nova-allow-dhcp-server'/> |
1544 | + <filterref filter='nova-base-ipv4'/> |
1545 | + <filterref filter='nova-base-ipv6'/> |
1546 | + </filter>''' |
1547 | + |
1548 | + nova_dhcp_filter = '''<filter name='nova-allow-dhcp-server' chain='ipv4'> |
1549 | + <uuid>891e4787-e5c0-d59b-cbd6-41bc3c6b36fc</uuid> |
1550 | + <rule action='accept' direction='out' |
1551 | + priority='100'> |
1552 | + <udp srcipaddr='0.0.0.0' |
1553 | + dstipaddr='255.255.255.255' |
1554 | + srcportstart='68' |
1555 | + dstportstart='67'/> |
1556 | + </rule> |
1557 | + <rule action='accept' direction='in' priority='100'> |
1558 | + <udp srcipaddr='$DHCPSERVER' |
1559 | + srcportstart='67' |
1560 | + dstportstart='68'/> |
1561 | + </rule> |
1562 | + </filter>''' |
1563 | + |
1564 | + def nova_base_ipv4_filter(self): |
1565 | + retval = "<filter name='nova-base-ipv4' chain='ipv4'>" |
1566 | + for protocol in ['tcp', 'udp', 'icmp']: |
1567 | + for direction,action,priority in [('out','accept', 399), |
1568 | + ('inout','drop', 400)]: |
1569 | + retval += """<rule action='%s' direction='%s' priority='%d'> |
1570 | + <%s /> |
1571 | + </rule>""" % (action, direction, |
1572 | + priority, protocol) |
1573 | + retval += '</filter>' |
1574 | + return retval |
1575 | + |
1576 | + |
1577 | + def nova_base_ipv6_filter(self): |
1578 | + retval = "<filter name='nova-base-ipv6' chain='ipv6'>" |
1579 | + for protocol in ['tcp', 'udp', 'icmp']: |
1580 | + for direction,action,priority in [('out','accept',399), |
1581 | + ('inout','drop',400)]: |
1582 | + retval += """<rule action='%s' direction='%s' priority='%d'> |
1583 | + <%s-ipv6 /> |
1584 | + </rule>""" % (action, direction, |
1585 | + priority, protocol) |
1586 | + retval += '</filter>' |
1587 | + return retval |
1588 | + |
1589 | + |
1590 | + def nova_project_filter(self, project, net, mask): |
1591 | + retval = "<filter name='nova-project-%s' chain='ipv4'>" % project |
1592 | + for protocol in ['tcp', 'udp', 'icmp']: |
1593 | + retval += """<rule action='accept' direction='in' priority='200'> |
1594 | + <%s srcipaddr='%s' srcipmask='%s' /> |
1595 | + </rule>""" % (protocol, net, mask) |
1596 | + retval += '</filter>' |
1597 | + return retval |
1598 | + |
1599 | + |
1600 | + def _define_filter(self, xml): |
1601 | + if callable(xml): |
1602 | + xml = xml() |
1603 | + d = threads.deferToThread(self._conn.nwfilterDefineXML, xml) |
1604 | + return d |
1605 | + |
1606 | + |
1607 | + @staticmethod |
1608 | + def _get_net_and_mask(cidr): |
1609 | + net = IPy.IP(cidr) |
1610 | + return str(net.net()), str(net.netmask()) |
1611 | + |
1612 | + @defer.inlineCallbacks |
1613 | + def setup_nwfilters_for_instance(self, instance): |
1614 | + """ |
1615 | + Creates an NWFilter for the given instance. In the process, |
1616 | + it makes sure the filters for the security groups as well as |
1617 | + the base filter are all in place. |
1618 | + """ |
1619 | + |
1620 | + yield self._define_filter(self.nova_base_ipv4_filter) |
1621 | + yield self._define_filter(self.nova_base_ipv6_filter) |
1622 | + yield self._define_filter(self.nova_dhcp_filter) |
1623 | + yield self._define_filter(self.nova_base_filter) |
1624 | + |
1625 | + nwfilter_xml = ("<filter name='nova-instance-%s' chain='root'>\n" + |
1626 | + " <filterref filter='nova-base' />\n" |
1627 | + ) % instance['name'] |
1628 | + |
1629 | + if FLAGS.allow_project_net_traffic: |
1630 | + network_ref = db.project_get_network({}, instance['project_id']) |
1631 | + net, mask = self._get_net_and_mask(network_ref['cidr']) |
1632 | + project_filter = self.nova_project_filter(instance['project_id'], |
1633 | + net, mask) |
1634 | + yield self._define_filter(project_filter) |
1635 | + |
1636 | + nwfilter_xml += (" <filterref filter='nova-project-%s' />\n" |
1637 | + ) % instance['project_id'] |
1638 | + |
1639 | + for security_group in instance.security_groups: |
1640 | + yield self.ensure_security_group_filter(security_group['id']) |
1641 | + |
1642 | + nwfilter_xml += (" <filterref filter='nova-secgroup-%d' />\n" |
1643 | + ) % security_group['id'] |
1644 | + nwfilter_xml += "</filter>" |
1645 | + |
1646 | + yield self._define_filter(nwfilter_xml) |
1647 | + return |
1648 | + |
1649 | + def ensure_security_group_filter(self, security_group_id): |
1650 | + return self._define_filter( |
1651 | + self.security_group_to_nwfilter_xml(security_group_id)) |
1652 | + |
1653 | + |
1654 | + def security_group_to_nwfilter_xml(self, security_group_id): |
1655 | + security_group = db.security_group_get({}, security_group_id) |
1656 | + rule_xml = "" |
1657 | + for rule in security_group.rules: |
1658 | + rule_xml += "<rule action='accept' direction='in' priority='300'>" |
1659 | + if rule.cidr: |
1660 | + net, mask = self._get_net_and_mask(rule.cidr) |
1661 | + rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % (rule.protocol, net, mask) |
1662 | + if rule.protocol in ['tcp', 'udp']: |
1663 | + rule_xml += "dstportstart='%s' dstportend='%s' " % \ |
1664 | + (rule.from_port, rule.to_port) |
1665 | + elif rule.protocol == 'icmp': |
1666 | + logging.info('rule.protocol: %r, rule.from_port: %r, rule.to_port: %r' % (rule.protocol, rule.from_port, rule.to_port)) |
1667 | + if rule.from_port != -1: |
1668 | + rule_xml += "type='%s' " % rule.from_port |
1669 | + if rule.to_port != -1: |
1670 | + rule_xml += "code='%s' " % rule.to_port |
1671 | + |
1672 | + rule_xml += '/>\n' |
1673 | + rule_xml += "</rule>\n" |
1674 | + xml = '''<filter name='nova-secgroup-%s' chain='ipv4'>%s</filter>''' % (security_group_id, rule_xml,) |
1675 | + return xml |
1676 | |
1677 | === modified file 'run_tests.py' |
1678 | --- run_tests.py 2010-10-05 08:06:54 +0000 |
1679 | +++ run_tests.py 2010-10-12 20:19:40 +0000 |
1680 | @@ -65,6 +65,7 @@ |
1681 | from nova.tests.validator_unittest import * |
1682 | from nova.tests.virt_unittest import * |
1683 | from nova.tests.volume_unittest import * |
1684 | +from nova.tests.virt_unittest import * |
1685 | |
1686 | |
1687 | FLAGS = flags.FLAGS |
Heya, needs a quick merge with trunk...there's conflicts.