Merge lp:~soren/nova/iptables-concurrency into lp:~hudson-openstack/nova/trunk
- iptables-concurrency
- Merge into trunk
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~soren/nova/iptables-concurrency | ||||
Merge into: | lp:~hudson-openstack/nova/trunk | ||||
Diff against target: |
1296 lines (+720/-285) 7 files modified
nova/flags.py (+2/-0) nova/network/linux_net.py (+350/-111) nova/tests/test_misc.py (+36/-1) nova/tests/test_network.py (+142/-0) nova/tests/test_virt.py (+36/-19) nova/utils.py (+49/-26) nova/virt/libvirt_conn.py (+105/-128) |
||||
To merge this branch: | bzr merge lp:~soren/nova/iptables-concurrency | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Christian Berendt (community) | Approve | ||
Vish Ishaya (community) | Approve | ||
Rick Harris (community) | Needs Fixing | ||
Review via email: mp+50597@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-02-28.
Commit message
Description of the change
Add a new IptablesManager that takes care of all uses of iptables.
Port all uses of iptables (in linux_net and libvirt_conn) over to use this new manager.
It wraps all uses of iptables so that each component can maintain its own set of rules without interfering with other components and/or existing system rules.
iptables-restore is an atomic interface to netfilter in the kernel. This means we can make a bunch of changes at a time, minimising the number of calls to iptables.
Jay Pipes (jaypipes) wrote : | # |
Rick Harris (rconradharris) wrote : | # |
lgtm, just a few minor-nits:
> 626 + print rule
Might be better to log here (or remove if now unnecessary)
> 227 + new_filter = self._modify_
> 228 + tables[table])
228 needs to be indented 1 space to the right.
> 238 + new_filter = filter(lambda l: binary_name not in l, current_lines)
Super-minor, but I'd lean against using a single letter variable name here. Aside from the fact that 'l' (ell) and '1' (one) look similar, I think it reads better if we use var name like 'line'. (Though, in most cases, I think 'i' and 'j' are fine for counting variables).
Of course this is personal preference, so, if you disagree here, feel free to disregard :)
> 245 + elif seen_chains == 1:
`seen_chains` is a boolean, so unless I'm misunderstanding, this should just be `elif seen_chains`. Related, perhaps that block should be rewritten as
if seen_chains:
<blah>
else:
<blah>
> 241 + for rules_index in range(len(
This might be clearer if rewritten as
for rule_index, rule in enumerate(
> + new_filter[
`rules_index` may end up uninitialized if the len(new_filter) == 0. We should probably handle that case even if it's unlikely (or even impossible) in practice.
Todd Willey (xtoddx) wrote : | # |
So nova-local is now used for FORWARD and OUTPUT, whereas before it was only used for FORWARD, correct? Why the change?
Is use_nova_chains used anymore? It seems to have been replaced with the 'wrap' parameter. It may be prettier to have a helper function wrapped_
I'm not quite done with this review, but I wanted to give you my early comments.
Vish Ishaya (vishvananda) wrote : | # |
This is awesome stuff soren. I'm testing and there are a few issues:
1. The purpose of SNATTING is so that all the snatting rules can run AFTER all of the postrouting rules. It looks like you've done it the other way around. Also this seems to break with multiple workers on the same host.
1 60 nova-network-
1 60 nova-network-
1 60 nova-compute-
1 60 nova-compute-
2. The snatting rule for public ips is broken:
404 + ("SNATTING", "-d %s -j SNAT --to %s" % (fixed_ip, floating_ip))]
should be: "-s %s..."
3. I think you added a rule to both SNATTING and POSTROUTING by accident:
333 + iptables_
334 + "-s %s -j SNAT --to-source %s" % \
335 + (FLAGS.fixed_range,
336 + FLAGS.routing_
337 +
338 + iptables_
339 + "-s %s -j SNAT --to-source %s" % \
340 + (FLAGS.fixed_range,
341 + FLAGS.routing_
This rule should only be in SNATTING
4. The old code added the above rule to the SNATTING chain, but added all other floating ips to the beginning of the chain. The purpose of this is so that it is a fallback. If an instance has a public ip it snats to its public ip, otherwise it uses the routing source ip. The current ordering means that all instances will always use the routing_source_ip. Consider the following:
Chain nova-network-
pkts bytes target prot opt in out source destination
0 0 SNAT all -- * * 10.0.0.0/8 0.0.0.0/0 to:10.0.0.1
0 0 SNAT all -- * * 10.0.0.2 0.0.0.0/0 to:10.6.6.0
(the 10.0.0.2 was in destination, i moved it manually, because it is addressed by item 2 above)
10.0.0.2 is the fixed ip of the instance
10.6.6.0 is the public_ip of the instance
10.0.0.1 is the routing_source_ip
note that the snat rule at the bottom will never get hit because the first rule will catch all of them. In order to make this work properly you either need to add another chain that always runs after SNATTING, or you need a way to ensure that the floating rules get added to the beginning of the SNATTING chain.
Vish
Soren Hansen (soren) wrote : | # |
2011/2/22 Rick Harris <email address hidden>:
>> 245 + elif seen_chains == 1:
> `seen_chains` is a boolean, so unless I'm misunderstanding, this should just be `elif seen_chains`.
Actually, it could just be rewritten as "else:" :) It's a leftover
from a point where seen_chains had 4 states (and was named
differently). Good catch, thanks.
Related, perhaps that block should be rewritten as
>
> if seen_chains:
> <blah>
> else:
> <blah>
I prefer it the way it is. It matches the order of the output from
iptables-save (which is first
a comment, then a specification of the table to which the following
output pertains, then the chains,
then the rules).
>> 241 + for rules_index in range(len(
>
> This might be clearer if rewritten as
>
> for rule_index, rule in enumerate(
Done.
>> + new_filter[
> `rules_index` may end up uninitialized if the len(new_filter) == 0. We should probably handle that case even if it's unlikely (or even impossible) in practice.
I now initialise rules_index to 0.
--
Soren Hansen | http://
Ubuntu Developer | http://
OpenStack Developer | http://
Soren Hansen (soren) wrote : | # |
2011/2/22 Todd Willey <email address hidden>:
> So nova-local is now used for FORWARD and OUTPUT, whereas before it was only used for FORWARD, correct? Why the change?
This is the original code that is being replaced:
858 - our_chains += [':nova-local - [0:0]']
859 - our_rules += ['-A FORWARD -j nova-local']
860 - our_rules += ['-A OUTPUT -j nova-local']
So no change.
> Is use_nova_chains used anymore? It seems to have been replaced with the 'wrap' parameter.
True. I've removed the flag now.
> It may be prettier to have a helper function wrapped_
Why/when do you have to look in two places to work this out?
I really like that consumers of this IptablesManager need not worry
about the the naming at all, but can just define whatever name they
want, and IptablesManager ensures that it doesn't collide with
anything else.
> That also might let us get rid of the IptablesRule class and just store rules as tuples of (chain, rule).
I started out that way, actually, but ended up rewriting it like this.
I found it more readable this way.
> I'm not quite done with this review, but I wanted to give you my early comments.
Np. Thanks!
--
Soren Hansen | http://
Ubuntu Developer | http://
OpenStack Developer | http://
Soren Hansen (soren) wrote : | # |
2011/2/22 Vish Ishaya <email address hidden>:
> This is awesome stuff soren. I'm testing and there are a few issues:
>
> 1. The purpose of SNATTING is so that all the snatting rules can run AFTER all of the postrouting rules. It looks like you've done it the other way around.
Ah, I see. Yeah, that intent wasn't entirely clear to me. Fixed, thanks!
> Also this seems to break with multiple workers on the same host.
Yes, the "local" chain has the same problem. I realised this as I was
falling asleep last night. I'll try to come up with a way to address
this.
> 2. The snatting rule for public ips is broken:
> 404 + ("SNATTING", "-d %s -j SNAT --to %s" % (fixed_ip, floating_ip))]
> should be: "-s %s..."
Wow. Good catch!
> 3. I think you added a rule to both SNATTING and POSTROUTING by accident:
> 333 + iptables_
> 334 + "-s %s -j SNAT --to-source %s" % \
> 335 + (FLAGS.fixed_range,
> 336 + FLAGS.routing_
> 337 +
> 338 + iptables_
> 339 + "-s %s -j SNAT --to-source %s" % \
> 340 + (FLAGS.fixed_range,
> 341 + FLAGS.routing_
>
> This rule should only be in SNATTING
Fixed, thanks!
> 4. The old code added the above rule to the SNATTING chain, but added all other floating ips to the beginning of the chain. The purpose of this is so that it is a fallback. If an instance has a public ip it snats to its public ip, otherwise it uses the routing source ip. The current ordering means that all instances will always use the routing_source_ip. Consider the following:
That makes perfect sense. Thanks! I didn't realise this was how it
worked before. I'll fix this.
--
Soren Hansen | http://
Ubuntu Developer | http://
OpenStack Developer | http://
Vish Ishaya (vishvananda) wrote : | # |
The rules seem to be working correctly now. One final suggestion:
The SNATTING chain is user created. I capitalized it originally because all of the default chains are capitalized. Now that we are using it along with a bunch of other custom chains (like floating-snat), i'm thinking for consistency it should be "snatting" or even "snat".
I'll go ahead and approve and leave it up to you if you want to change it.
Soren Hansen (soren) wrote : | # |
I did consider that at one point, but I think I decided not to because I was
tired of adjusting the unit tests. ;) It's a good idea, though. I'll do
that once I'm back near my laptop.
- 721. By Soren Hansen
-
Rename "SNATTING" chain to "snat".
Christian Berendt (berendt) wrote : | # |
I tried spawning > 20 instances (hypervisor KVM) at the same time on one nova-compute node and I got 2 failed instances. I think the problem is, that there are only 15 (or 16?) available NBD devices at the same time.
(nova.compute.
So there has to be a hard limit somewhere while using KVM as hypervisor. Is there a flag to limit the number of spawning instances on one node at the same time? If not we should introduce one. Or is it possible to use more than 15 NBD devices at the same time?
I tested the code against rev 713 and I don't get the error described in #722477. So it seems working for me :) (but i didn't read the code)
Christian Berendt (berendt) wrote : | # |
It's possible to load the nbd module using more devices
example: modprobe nbd nbds_max=32
Soren Hansen (soren) wrote : | # |
Iirc, you can actually just mknod more devices, and the driver creates the
kernel structs on the fly.
Den 23/02/2011 13.29 skrev "Christian Berendt" <email address hidden>:
> It's possible to load the nbd module using more devices
>
> example: modprobe nbd nbds_max=32
> --
> https:/
> You are the owner of lp:~soren/nova/iptables-concurrency.
Christian Berendt (berendt) wrote : | # |
> Iirc, you can actually just mknod more devices, and the driver creates the
> kernel structs on the fly.
Problem is, that the number of the devices is hardcoded to 16 in nova/virt/disk.py. But this is offtopic, I'll create a bug for this issue later...
Soren Hansen (soren) wrote : | # |
I've spotted another concurrency problem that I might as well fix now..
- 722. By Soren Hansen
-
Merge sync branch.
- 723. By Soren Hansen
-
Wrap IptablesManager
.apply( ) calls in utils.synchronized to avoid having different workers step on each other's toes. - 724. By Soren Hansen
-
Merge lock_path change.
- 725. By Soren Hansen
-
Merge sync branch.
- 726. By Soren Hansen
-
Merge sync branch and trunk
- 727. By Soren Hansen
-
Merge trunk
- 728. By Soren Hansen
-
Make iptables rules class __ne__ just be inverted __eq__.
- 729. By Soren Hansen
-
Log failed command execution if there are more retry attempts left.
- 730. By Soren Hansen
-
Use IptablesManager
.semapahore from securitygroups driver to ensure we don't apply half a rule set. - 731. By Soren Hansen
-
Merge trunk
- 732. By Soren Hansen
-
Merge trunk
- 733. By Soren Hansen
-
PEP8
Unmerged revisions
Preview Diff
1 | === modified file 'nova/flags.py' |
2 | --- nova/flags.py 2011-02-25 01:04:25 +0000 |
3 | +++ nova/flags.py 2011-02-28 22:36:03 +0000 |
4 | @@ -321,6 +321,8 @@ |
5 | |
6 | DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'), |
7 | "Top-level directory for maintaining nova's state") |
8 | +DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'), |
9 | + "Directory for lock files") |
10 | DEFINE_string('logdir', None, 'output to a per-service log file in named ' |
11 | 'directory') |
12 | |
13 | |
14 | === modified file 'nova/network/linux_net.py' |
15 | --- nova/network/linux_net.py 2011-02-17 12:46:24 +0000 |
16 | +++ nova/network/linux_net.py 2011-02-28 22:36:03 +0000 |
17 | @@ -17,15 +17,17 @@ |
18 | Implements vlans, bridges, and iptables rules using linux utilities. |
19 | """ |
20 | |
21 | +import inspect |
22 | import os |
23 | |
24 | +from eventlet import semaphore |
25 | + |
26 | from nova import db |
27 | from nova import exception |
28 | from nova import flags |
29 | from nova import log as logging |
30 | from nova import utils |
31 | |
32 | - |
33 | LOG = logging.getLogger("nova.linux_net") |
34 | |
35 | |
36 | @@ -52,8 +54,6 @@ |
37 | 'location of nova-dhcpbridge') |
38 | flags.DEFINE_string('routing_source_ip', '$my_ip', |
39 | 'Public IP of network host') |
40 | -flags.DEFINE_bool('use_nova_chains', False, |
41 | - 'use the nova_ routing chains instead of default') |
42 | flags.DEFINE_string('input_chain', 'INPUT', |
43 | 'chain to add nova_input to') |
44 | |
45 | @@ -63,73 +63,334 @@ |
46 | 'dmz range that should be accepted') |
47 | |
48 | |
49 | +binary_name = os.path.basename(inspect.stack()[-1][1]) |
50 | + |
51 | + |
52 | +class IptablesRule(object): |
53 | + """An iptables rule |
54 | + |
55 | + You shouldn't need to use this class directly, it's only used by |
56 | + IptablesManager |
57 | + """ |
58 | + def __init__(self, chain, rule, wrap=True, top=False): |
59 | + self.chain = chain |
60 | + self.rule = rule |
61 | + self.wrap = wrap |
62 | + self.top = top |
63 | + |
64 | + def __eq__(self, other): |
65 | + return ((self.chain == other.chain) and |
66 | + (self.rule == other.rule) and |
67 | + (self.top == other.top) and |
68 | + (self.wrap == other.wrap)) |
69 | + |
70 | + def __ne__(self, other): |
71 | + return ((self.chain != other.chain) or |
72 | + (self.rule != other.rule) or |
73 | + (self.top != other.top) or |
74 | + (self.wrap != other.wrap)) |
75 | + |
76 | + def __str__(self): |
77 | + if self.wrap: |
78 | + chain = '%s-%s' % (binary_name, self.chain) |
79 | + else: |
80 | + chain = self.chain |
81 | + return '-A %s %s' % (chain, self.rule) |
82 | + |
83 | + |
84 | +class IptablesTable(object): |
85 | + """An iptables table""" |
86 | + |
87 | + def __init__(self): |
88 | + self.rules = [] |
89 | + self.chains = set() |
90 | + self.unwrapped_chains = set() |
91 | + |
92 | + def add_chain(self, name, wrap=True): |
93 | + """Adds a named chain to the table |
94 | + |
95 | + The chain name is wrapped to be unique for the component creating |
96 | + it, so different components of Nova can safely create identically |
97 | + named chains without interfering with one another. |
98 | + |
99 | + At the moment, its wrapped name is <binary name>-<chain name>, |
100 | + so if nova-compute creates a chain named "OUTPUT", it'll actually |
101 | + end up named "nova-compute-OUTPUT". |
102 | + """ |
103 | + if wrap: |
104 | + self.chains.add(name) |
105 | + else: |
106 | + self.unwrapped_chains.add(name) |
107 | + |
108 | + def remove_chain(self, name, wrap=True): |
109 | + """Remove named chain |
110 | + |
111 | + This removal "cascades". All rule in the chain are removed, as are |
112 | + all rules in other chains that jump to it. |
113 | + |
114 | + If the chain is not found, this is merely logged. |
115 | + """ |
116 | + if wrap: |
117 | + chain_set = self.chains |
118 | + else: |
119 | + chain_set = self.unwrapped_chains |
120 | + |
121 | + if name not in chain_set: |
122 | + LOG.debug(_("Attempted to remove chain %s which doesn't exist"), |
123 | + name) |
124 | + return |
125 | + |
126 | + chain_set.remove(name) |
127 | + self.rules = filter(lambda r: r.chain != name, self.rules) |
128 | + |
129 | + if wrap: |
130 | + jump_snippet = '-j %s-%s' % (binary_name, name) |
131 | + else: |
132 | + jump_snippet = '-j %s' % (name,) |
133 | + |
134 | + self.rules = filter(lambda r: jump_snippet not in r.rule, self.rules) |
135 | + |
136 | + def add_rule(self, chain, rule, wrap=True, top=False): |
137 | + """Add a rule to the table |
138 | + |
139 | + This is just like what you'd feed to iptables, just without |
140 | + the "-A <chain name>" bit at the start. |
141 | + |
142 | + However, if you need to jump to one of your wrapped chains, |
143 | + prepend its name with a '$' which will ensure the wrapping |
144 | + is applied correctly. |
145 | + """ |
146 | + if wrap and chain not in self.chains: |
147 | + raise ValueError(_("Unknown chain: %r") % chain) |
148 | + |
149 | + if '$' in rule: |
150 | + rule = ' '.join(map(self._wrap_target_chain, rule.split(' '))) |
151 | + |
152 | + self.rules.append(IptablesRule(chain, rule, wrap, top)) |
153 | + |
154 | + def _wrap_target_chain(self, s): |
155 | + if s.startswith('$'): |
156 | + return '%s-%s' % (binary_name, s[1:]) |
157 | + return s |
158 | + |
159 | + def remove_rule(self, chain, rule, wrap=True, top=False): |
160 | + """Remove a rule from a chain |
161 | + |
162 | + Note: The rule must be exactly identical to the one that was added. |
163 | + You cannot switch arguments around like you can with the iptables |
164 | + CLI tool. |
165 | + """ |
166 | + try: |
167 | + self.rules.remove(IptablesRule(chain, rule, wrap, top)) |
168 | + except ValueError: |
169 | + LOG.debug(_("Tried to remove rule that wasn't there:" |
170 | + " %(chain)r %(rule)r %(wrap)r %(top)r"), |
171 | + {'chain': chain, 'rule': rule, |
172 | + 'top': top, 'wrap': wrap}) |
173 | + |
174 | + |
175 | +class IptablesManager(object): |
176 | + """Wrapper for iptables |
177 | + |
178 | + See IptablesTable for some usage docs |
179 | + |
180 | + A number of chains are set up to begin with. |
181 | + |
182 | + First, nova-filter-top. It's added at the top of FORWARD and OUTPUT. Its |
183 | + name is not wrapped, so it's shared between the various nova workers. It's |
184 | + intended for rules that need to live at the top of the FORWARD and OUTPUT |
185 | + chains. It's in both the ipv4 and ipv6 set of tables. |
186 | + |
187 | + For ipv4 and ipv6, the builtin INPUT, OUTPUT, and FORWARD filter chains are |
188 | + wrapped, meaning that the "real" INPUT chain has a rule that jumps to the |
189 | + wrapped INPUT chain, etc. Additionally, there's a wrapped chain named |
190 | + "local" which is jumped to from nova-filter-top. |
191 | + |
192 | + For ipv4, the builtin PREROUTING, OUTPUT, and POSTROUTING nat chains are |
193 | + wrapped in the same was as the builtin filter chains. Additionally, there's |
194 | + a snat chain that is applied after the POSTROUTING chain. |
195 | + """ |
196 | + def __init__(self, execute=None): |
197 | + if not execute: |
198 | + if FLAGS.fake_network: |
199 | + self.execute = lambda *args, **kwargs: ('', '') |
200 | + else: |
201 | + self.execute = utils.execute |
202 | + else: |
203 | + self.execute = execute |
204 | + |
205 | + self.ipv4 = {'filter': IptablesTable(), |
206 | + 'nat': IptablesTable()} |
207 | + self.ipv6 = {'filter': IptablesTable()} |
208 | + |
209 | + # Add a nova-filter-top chain. It's intended to be shared |
210 | + # among the various nova components. It sits at the very top |
211 | + # of FORWARD and OUTPUT. |
212 | + for tables in [self.ipv4, self.ipv6]: |
213 | + tables['filter'].add_chain('nova-filter-top', wrap=False) |
214 | + tables['filter'].add_rule('FORWARD', '-j nova-filter-top', |
215 | + wrap=False, top=True) |
216 | + tables['filter'].add_rule('OUTPUT', '-j nova-filter-top', |
217 | + wrap=False, top=True) |
218 | + |
219 | + tables['filter'].add_chain('local') |
220 | + tables['filter'].add_rule('nova-filter-top', '-j $local', |
221 | + wrap=False) |
222 | + |
223 | + # Wrap the builtin chains |
224 | + builtin_chains = {4: {'filter': ['INPUT', 'OUTPUT', 'FORWARD'], |
225 | + 'nat': ['PREROUTING', 'OUTPUT', 'POSTROUTING']}, |
226 | + 6: {'filter': ['INPUT', 'OUTPUT', 'FORWARD']}} |
227 | + |
228 | + for ip_version in builtin_chains: |
229 | + if ip_version == 4: |
230 | + tables = self.ipv4 |
231 | + elif ip_version == 6: |
232 | + tables = self.ipv6 |
233 | + |
234 | + for table, chains in builtin_chains[ip_version].iteritems(): |
235 | + for chain in chains: |
236 | + tables[table].add_chain(chain) |
237 | + tables[table].add_rule(chain, '-j $%s' % (chain,), |
238 | + wrap=False) |
239 | + |
240 | + # Add a nova-postrouting-bottom chain. It's intended to be shared |
241 | + # among the various nova components. We set it as the last chain |
242 | + # of POSTROUTING chain. |
243 | + self.ipv4['nat'].add_chain('nova-postrouting-bottom', wrap=False) |
244 | + self.ipv4['nat'].add_rule('POSTROUTING', '-j nova-postrouting-bottom', |
245 | + wrap=False) |
246 | + |
247 | + # We add a snat chain to the shared nova-postrouting-bottom chain |
248 | + # so that it's applied last. |
249 | + self.ipv4['nat'].add_chain('snat') |
250 | + self.ipv4['nat'].add_rule('nova-postrouting-bottom', '-j $snat', |
251 | + wrap=False) |
252 | + |
253 | + # And then we add a floating-snat chain and jump to first thing in |
254 | + # the snat chain. |
255 | + self.ipv4['nat'].add_chain('floating-snat') |
256 | + self.ipv4['nat'].add_rule('snat', '-j $floating-snat') |
257 | + |
258 | + self.semaphore = semaphore.Semaphore() |
259 | + |
260 | + @utils.synchronized('iptables') |
261 | + def apply(self): |
262 | + """Apply the current in-memory set of iptables rules |
263 | + |
264 | + This will blow away any rules left over from previous runs of the |
265 | + same component of Nova, and replace them with our current set of |
266 | + rules. This happens atomically, thanks to iptables-restore. |
267 | + |
268 | + We wrap the call in a semaphore lock, so that we don't race with |
269 | + ourselves. In the event of a race with another component running |
270 | + an iptables-* command at the same time, we retry up to 5 times. |
271 | + """ |
272 | + with self.semaphore: |
273 | + s = [('iptables', self.ipv4)] |
274 | + if FLAGS.use_ipv6: |
275 | + s += [('ip6tables', self.ipv6)] |
276 | + |
277 | + for cmd, tables in s: |
278 | + for table in tables: |
279 | + current_table, _ = self.execute('sudo %s-save -t %s' % |
280 | + (cmd, table), attempts=5) |
281 | + current_lines = current_table.split('\n') |
282 | + new_filter = self._modify_rules(current_lines, |
283 | + tables[table]) |
284 | + self.execute('sudo %s-restore' % (cmd,), |
285 | + process_input='\n'.join(new_filter), |
286 | + attempts=5) |
287 | + |
288 | + def _modify_rules(self, current_lines, table, binary=None): |
289 | + unwrapped_chains = table.unwrapped_chains |
290 | + chains = table.chains |
291 | + rules = table.rules |
292 | + |
293 | + # Remove any trace of our rules |
294 | + new_filter = filter(lambda line: binary_name not in line, |
295 | + current_lines) |
296 | + |
297 | + seen_chains = False |
298 | + rules_index = 0 |
299 | + for rules_index, rule in enumerate(new_filter): |
300 | + if not seen_chains: |
301 | + if rule.startswith(':'): |
302 | + seen_chains = True |
303 | + else: |
304 | + if not rule.startswith(':'): |
305 | + break |
306 | + |
307 | + our_rules = [] |
308 | + for rule in rules: |
309 | + rule_str = str(rule) |
310 | + if rule.top: |
311 | + # rule.top == True means we want this rule to be at the top. |
312 | + # Further down, we weed out duplicates from the bottom of the |
313 | + # list, so here we remove the dupes ahead of time. |
314 | + new_filter = filter(lambda s: s.strip() != rule_str.strip(), |
315 | + new_filter) |
316 | + our_rules += [rule_str] |
317 | + |
318 | + new_filter[rules_index:rules_index] = our_rules |
319 | + |
320 | + new_filter[rules_index:rules_index] = [':%s - [0:0]' % \ |
321 | + (name,) \ |
322 | + for name in unwrapped_chains] |
323 | + new_filter[rules_index:rules_index] = [':%s-%s - [0:0]' % \ |
324 | + (binary_name, name,) \ |
325 | + for name in chains] |
326 | + |
327 | + seen_lines = set() |
328 | + |
329 | + def _weed_out_duplicates(line): |
330 | + line = line.strip() |
331 | + if line in seen_lines: |
332 | + return False |
333 | + else: |
334 | + seen_lines.add(line) |
335 | + return True |
336 | + |
337 | + # We filter duplicates, letting the *last* occurrence take |
338 | + # precendence. |
339 | + new_filter.reverse() |
340 | + new_filter = filter(_weed_out_duplicates, new_filter) |
341 | + new_filter.reverse() |
342 | + return new_filter |
343 | + |
344 | + |
345 | +iptables_manager = IptablesManager() |
346 | + |
347 | + |
348 | def metadata_forward(): |
349 | """Create forwarding rule for metadata""" |
350 | - _confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 " |
351 | - "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT " |
352 | - "--to-destination %s:%s" % (FLAGS.ec2_dmz_host, FLAGS.ec2_port)) |
353 | + iptables_manager.ipv4['nat'].add_rule("PREROUTING", |
354 | + "-s 0.0.0.0/0 -d 169.254.169.254/32 " |
355 | + "-p tcp -m tcp --dport 80 -j DNAT " |
356 | + "--to-destination %s:%s" % \ |
357 | + (FLAGS.ec2_dmz_host, FLAGS.ec2_port)) |
358 | + iptables_manager.apply() |
359 | |
360 | |
361 | def init_host(): |
362 | """Basic networking setup goes here""" |
363 | |
364 | - if FLAGS.use_nova_chains: |
365 | - _execute("sudo iptables -N nova_input", check_exit_code=False) |
366 | - _execute("sudo iptables -D %s -j nova_input" % FLAGS.input_chain, |
367 | - check_exit_code=False) |
368 | - _execute("sudo iptables -A %s -j nova_input" % FLAGS.input_chain) |
369 | - |
370 | - _execute("sudo iptables -N nova_forward", check_exit_code=False) |
371 | - _execute("sudo iptables -D FORWARD -j nova_forward", |
372 | - check_exit_code=False) |
373 | - _execute("sudo iptables -A FORWARD -j nova_forward") |
374 | - |
375 | - _execute("sudo iptables -N nova_output", check_exit_code=False) |
376 | - _execute("sudo iptables -D OUTPUT -j nova_output", |
377 | - check_exit_code=False) |
378 | - _execute("sudo iptables -A OUTPUT -j nova_output") |
379 | - |
380 | - _execute("sudo iptables -t nat -N nova_prerouting", |
381 | - check_exit_code=False) |
382 | - _execute("sudo iptables -t nat -D PREROUTING -j nova_prerouting", |
383 | - check_exit_code=False) |
384 | - _execute("sudo iptables -t nat -A PREROUTING -j nova_prerouting") |
385 | - |
386 | - _execute("sudo iptables -t nat -N nova_postrouting", |
387 | - check_exit_code=False) |
388 | - _execute("sudo iptables -t nat -D POSTROUTING -j nova_postrouting", |
389 | - check_exit_code=False) |
390 | - _execute("sudo iptables -t nat -A POSTROUTING -j nova_postrouting") |
391 | - |
392 | - _execute("sudo iptables -t nat -N nova_snatting", |
393 | - check_exit_code=False) |
394 | - _execute("sudo iptables -t nat -D POSTROUTING -j nova_snatting", |
395 | - check_exit_code=False) |
396 | - _execute("sudo iptables -t nat -A POSTROUTING -j nova_snatting") |
397 | - |
398 | - _execute("sudo iptables -t nat -N nova_output", check_exit_code=False) |
399 | - _execute("sudo iptables -t nat -D OUTPUT -j nova_output", |
400 | - check_exit_code=False) |
401 | - _execute("sudo iptables -t nat -A OUTPUT -j nova_output") |
402 | - else: |
403 | - # NOTE(vish): This makes it easy to ensure snatting rules always |
404 | - # come after the accept rules in the postrouting chain |
405 | - _execute("sudo iptables -t nat -N SNATTING", |
406 | - check_exit_code=False) |
407 | - _execute("sudo iptables -t nat -D POSTROUTING -j SNATTING", |
408 | - check_exit_code=False) |
409 | - _execute("sudo iptables -t nat -A POSTROUTING -j SNATTING") |
410 | - |
411 | # NOTE(devcamcar): Cloud public SNAT entries and the default |
412 | # SNAT rule for outbound traffic. |
413 | - _confirm_rule("SNATTING", "-t nat -s %s " |
414 | - "-j SNAT --to-source %s" |
415 | - % (FLAGS.fixed_range, FLAGS.routing_source_ip), append=True) |
416 | - |
417 | - _confirm_rule("POSTROUTING", "-t nat -s %s -d %s -j ACCEPT" % |
418 | - (FLAGS.fixed_range, FLAGS.dmz_cidr)) |
419 | - _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" % |
420 | - {'range': FLAGS.fixed_range}) |
421 | + iptables_manager.ipv4['nat'].add_rule("snat", |
422 | + "-s %s -j SNAT --to-source %s" % \ |
423 | + (FLAGS.fixed_range, |
424 | + FLAGS.routing_source_ip)) |
425 | + |
426 | + iptables_manager.ipv4['nat'].add_rule("POSTROUTING", |
427 | + "-s %s -d %s -j ACCEPT" % \ |
428 | + (FLAGS.fixed_range, FLAGS.dmz_cidr)) |
429 | + |
430 | + iptables_manager.ipv4['nat'].add_rule("POSTROUTING", |
431 | + "-s %(range)s -d %(range)s " |
432 | + "-j ACCEPT" % \ |
433 | + {'range': FLAGS.fixed_range}) |
434 | + iptables_manager.apply() |
435 | |
436 | |
437 | def bind_floating_ip(floating_ip, check_exit_code=True): |
438 | @@ -147,31 +408,36 @@ |
439 | |
440 | def ensure_vlan_forward(public_ip, port, private_ip): |
441 | """Sets up forwarding rules for vlan""" |
442 | - _confirm_rule("FORWARD", "-d %s -p udp --dport 1194 -j ACCEPT" % |
443 | - private_ip) |
444 | - _confirm_rule("PREROUTING", |
445 | - "-t nat -d %s -p udp --dport %s -j DNAT --to %s:1194" |
446 | - % (public_ip, port, private_ip)) |
447 | + iptables_manager.ipv4['filter'].add_rule("FORWARD", |
448 | + "-d %s -p udp " |
449 | + "--dport 1194 " |
450 | + "-j ACCEPT" % private_ip) |
451 | + iptables_manager.ipv4['nat'].add_rule("PREROUTING", |
452 | + "-d %s -p udp " |
453 | + "--dport %s -j DNAT --to %s:1194" % |
454 | + (public_ip, port, private_ip)) |
455 | + iptables_manager.apply() |
456 | |
457 | |
458 | def ensure_floating_forward(floating_ip, fixed_ip): |
459 | """Ensure floating ip forwarding rule""" |
460 | - _confirm_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s" |
461 | - % (floating_ip, fixed_ip)) |
462 | - _confirm_rule("OUTPUT", "-t nat -d %s -j DNAT --to %s" |
463 | - % (floating_ip, fixed_ip)) |
464 | - _confirm_rule("SNATTING", "-t nat -s %s -j SNAT --to %s" |
465 | - % (fixed_ip, floating_ip)) |
466 | + for chain, rule in floating_forward_rules(floating_ip, fixed_ip): |
467 | + iptables_manager.ipv4['nat'].add_rule(chain, rule) |
468 | + iptables_manager.apply() |
469 | |
470 | |
471 | def remove_floating_forward(floating_ip, fixed_ip): |
472 | """Remove forwarding for floating ip""" |
473 | - _remove_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s" |
474 | - % (floating_ip, fixed_ip)) |
475 | - _remove_rule("OUTPUT", "-t nat -d %s -j DNAT --to %s" |
476 | - % (floating_ip, fixed_ip)) |
477 | - _remove_rule("SNATTING", "-t nat -s %s -j SNAT --to %s" |
478 | - % (fixed_ip, floating_ip)) |
479 | + for chain, rule in floating_forward_rules(floating_ip, fixed_ip): |
480 | + iptables_manager.ipv4['nat'].remove_rule(chain, rule) |
481 | + iptables_manager.apply() |
482 | + |
483 | + |
484 | +def floating_forward_rules(floating_ip, fixed_ip): |
485 | + return [("PREROUTING", "-d %s -j DNAT --to %s" % (floating_ip, fixed_ip)), |
486 | + ("OUTPUT", "-d %s -j DNAT --to %s" % (floating_ip, fixed_ip)), |
487 | + ("floating-snat", |
488 | + "-s %s -j SNAT --to %s" % (fixed_ip, floating_ip))] |
489 | |
490 | |
491 | def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None): |
492 | @@ -258,19 +524,12 @@ |
493 | "enslave it to bridge %s.\n" % (interface, bridge)): |
494 | raise exception.Error("Failed to add interface: %s" % err) |
495 | |
496 | - if FLAGS.use_nova_chains: |
497 | - (out, err) = _execute("sudo iptables -N nova_forward", |
498 | - check_exit_code=False) |
499 | - if err != 'iptables: Chain already exists.\n': |
500 | - # NOTE(vish): chain didn't exist link chain |
501 | - _execute("sudo iptables -D FORWARD -j nova_forward", |
502 | - check_exit_code=False) |
503 | - _execute("sudo iptables -A FORWARD -j nova_forward") |
504 | - |
505 | - _confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge) |
506 | - _confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge) |
507 | - _execute("sudo iptables -N nova-local", check_exit_code=False) |
508 | - _confirm_rule("FORWARD", "-j nova-local") |
509 | + iptables_manager.ipv4['filter'].add_rule("FORWARD", |
510 | + "--in-interface %s -j ACCEPT" % \ |
511 | + bridge) |
512 | + iptables_manager.ipv4['filter'].add_rule("FORWARD", |
513 | + "--out-interface %s -j ACCEPT" % \ |
514 | + bridge) |
515 | |
516 | |
517 | def get_dhcp_hosts(context, network_id): |
518 | @@ -390,26 +649,6 @@ |
519 | return not err |
520 | |
521 | |
522 | -def _confirm_rule(chain, cmd, append=False): |
523 | - """Delete and re-add iptables rule""" |
524 | - if FLAGS.use_nova_chains: |
525 | - chain = "nova_%s" % chain.lower() |
526 | - if append: |
527 | - loc = "-A" |
528 | - else: |
529 | - loc = "-I" |
530 | - _execute("sudo iptables --delete %s %s" % (chain, cmd), |
531 | - check_exit_code=False) |
532 | - _execute("sudo iptables %s %s %s" % (loc, chain, cmd)) |
533 | - |
534 | - |
535 | -def _remove_rule(chain, cmd): |
536 | - """Remove iptables rule""" |
537 | - if FLAGS.use_nova_chains: |
538 | - chain = "%s" % chain.lower() |
539 | - _execute("sudo iptables --delete %s %s" % (chain, cmd)) |
540 | - |
541 | - |
542 | def _dnsmasq_cmd(net): |
543 | """Builds dnsmasq command""" |
544 | cmd = ['sudo -E dnsmasq', |
545 | |
546 | === modified file 'nova/tests/test_misc.py' |
547 | --- nova/tests/test_misc.py 2011-02-21 18:55:25 +0000 |
548 | +++ nova/tests/test_misc.py 2011-02-28 22:36:03 +0000 |
549 | @@ -14,10 +14,14 @@ |
550 | # License for the specific language governing permissions and limitations |
551 | # under the License. |
552 | |
553 | +from datetime import datetime |
554 | +import errno |
555 | import os |
556 | +import select |
557 | +import time |
558 | |
559 | from nova import test |
560 | -from nova.utils import parse_mailmap, str_dict_replace |
561 | +from nova.utils import parse_mailmap, str_dict_replace, synchronized |
562 | |
563 | |
564 | class ProjectTestCase(test.TestCase): |
565 | @@ -55,3 +59,34 @@ |
566 | '%r not listed in Authors' % missing) |
567 | finally: |
568 | tree.unlock() |
569 | + |
570 | + |
571 | +class LockTestCase(test.TestCase): |
572 | + def test_synchronized(self): |
573 | + rpipe, wpipe = os.pipe() |
574 | + pid = os.fork() |
575 | + if pid > 0: |
576 | + os.close(wpipe) |
577 | + |
578 | + @synchronized('testlock') |
579 | + def f(): |
580 | + rfds, _, __ = select.select([rpipe], [], [], 1) |
581 | + self.assertEquals(len(rfds), 0, "The other process, which was" |
582 | + " supposed to be locked, " |
583 | + "wrote on its end of the " |
584 | + "pipe") |
585 | + os.close(rpipe) |
586 | + |
587 | + f() |
588 | + else: |
589 | + os.close(rpipe) |
590 | + |
591 | + @synchronized('testlock') |
592 | + def g(): |
593 | + try: |
594 | + os.write(wpipe, "foo") |
595 | + except OSError, e: |
596 | + self.assertEquals(e.errno, errno.EPIPE) |
597 | + return |
598 | + g() |
599 | + os._exit(0) |
600 | |
601 | === modified file 'nova/tests/test_network.py' |
602 | --- nova/tests/test_network.py 2011-02-23 19:20:07 +0000 |
603 | +++ nova/tests/test_network.py 2011-02-28 22:36:03 +0000 |
604 | @@ -29,11 +29,153 @@ |
605 | from nova import test |
606 | from nova import utils |
607 | from nova.auth import manager |
608 | +from nova.network import linux_net |
609 | |
610 | FLAGS = flags.FLAGS |
611 | LOG = logging.getLogger('nova.tests.network') |
612 | |
613 | |
614 | +class IptablesManagerTestCase(test.TestCase): |
615 | + sample_filter = ['#Generated by iptables-save on Fri Feb 18 15:17:05 2011', |
616 | + '*filter', |
617 | + ':INPUT ACCEPT [2223527:305688874]', |
618 | + ':FORWARD ACCEPT [0:0]', |
619 | + ':OUTPUT ACCEPT [2172501:140856656]', |
620 | + ':nova-compute-FORWARD - [0:0]', |
621 | + ':nova-compute-INPUT - [0:0]', |
622 | + ':nova-compute-local - [0:0]', |
623 | + ':nova-compute-OUTPUT - [0:0]', |
624 | + ':nova-filter-top - [0:0]', |
625 | + '-A FORWARD -j nova-filter-top ', |
626 | + '-A OUTPUT -j nova-filter-top ', |
627 | + '-A nova-filter-top -j nova-compute-local ', |
628 | + '-A INPUT -j nova-compute-INPUT ', |
629 | + '-A OUTPUT -j nova-compute-OUTPUT ', |
630 | + '-A FORWARD -j nova-compute-FORWARD ', |
631 | + '-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT ', |
632 | + '-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT ', |
633 | + '-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT ', |
634 | + '-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ', |
635 | + '-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT ', |
636 | + '-A FORWARD -i virbr0 -o virbr0 -j ACCEPT ', |
637 | + '-A FORWARD -o virbr0 -j REJECT --reject-with ' |
638 | + 'icmp-port-unreachable ', |
639 | + '-A FORWARD -i virbr0 -j REJECT --reject-with ' |
640 | + 'icmp-port-unreachable ', |
641 | + 'COMMIT', |
642 | + '# Completed on Fri Feb 18 15:17:05 2011'] |
643 | + |
644 | + sample_nat = ['# Generated by iptables-save on Fri Feb 18 15:17:05 2011', |
645 | + '*nat', |
646 | + ':PREROUTING ACCEPT [3936:762355]', |
647 | + ':INPUT ACCEPT [2447:225266]', |
648 | + ':OUTPUT ACCEPT [63491:4191863]', |
649 | + ':POSTROUTING ACCEPT [63112:4108641]', |
650 | + ':nova-compute-OUTPUT - [0:0]', |
651 | + ':nova-compute-floating-ip-snat - [0:0]', |
652 | + ':nova-compute-SNATTING - [0:0]', |
653 | + ':nova-compute-PREROUTING - [0:0]', |
654 | + ':nova-compute-POSTROUTING - [0:0]', |
655 | + ':nova-postrouting-bottom - [0:0]', |
656 | + '-A PREROUTING -j nova-compute-PREROUTING ', |
657 | + '-A OUTPUT -j nova-compute-OUTPUT ', |
658 | + '-A POSTROUTING -j nova-compute-POSTROUTING ', |
659 | + '-A POSTROUTING -j nova-postrouting-bottom ', |
660 | + '-A nova-postrouting-bottom -j nova-compute-SNATTING ', |
661 | + '-A nova-compute-SNATTING -j nova-compute-floating-ip-snat ', |
662 | + 'COMMIT', |
663 | + '# Completed on Fri Feb 18 15:17:05 2011'] |
664 | + |
665 | + def setUp(self): |
666 | + super(IptablesManagerTestCase, self).setUp() |
667 | + self.manager = linux_net.IptablesManager() |
668 | + |
669 | + def test_filter_rules_are_wrapped(self): |
670 | + current_lines = self.sample_filter |
671 | + |
672 | + table = self.manager.ipv4['filter'] |
673 | + table.add_rule('FORWARD', '-s 1.2.3.4/5 -j DROP') |
674 | + new_lines = self.manager._modify_rules(current_lines, table) |
675 | + self.assertTrue('-A run_tests.py-FORWARD ' |
676 | + '-s 1.2.3.4/5 -j DROP' in new_lines) |
677 | + |
678 | + table.remove_rule('FORWARD', '-s 1.2.3.4/5 -j DROP') |
679 | + new_lines = self.manager._modify_rules(current_lines, table) |
680 | + self.assertTrue('-A run_tests.py-FORWARD ' |
681 | + '-s 1.2.3.4/5 -j DROP' not in new_lines) |
682 | + |
683 | + def test_nat_rules(self): |
684 | + current_lines = self.sample_nat |
685 | + new_lines = self.manager._modify_rules(current_lines, |
686 | + self.manager.ipv4['nat']) |
687 | + |
688 | + for line in [':nova-compute-OUTPUT - [0:0]', |
689 | + ':nova-compute-floating-ip-snat - [0:0]', |
690 | + ':nova-compute-SNATTING - [0:0]', |
691 | + ':nova-compute-PREROUTING - [0:0]', |
692 | + ':nova-compute-POSTROUTING - [0:0]']: |
693 | + self.assertTrue(line in new_lines, "One of nova-compute's chains " |
694 | + "went missing.") |
695 | + |
696 | + seen_lines = set() |
697 | + for line in new_lines: |
698 | + line = line.strip() |
699 | + self.assertTrue(line not in seen_lines, |
700 | + "Duplicate line: %s" % line) |
701 | + seen_lines.add(line) |
702 | + |
703 | + last_postrouting_line = '' |
704 | + |
705 | + for line in new_lines: |
706 | + if line.startswith('-A POSTROUTING'): |
707 | + last_postrouting_line = line |
708 | + |
709 | + self.assertTrue('-j nova-postrouting-bottom' in last_postrouting_line, |
710 | + "Last POSTROUTING rule does not jump to " |
711 | + "nova-postouting-bottom: %s" % last_postrouting_line) |
712 | + |
713 | + for chain in ['POSTROUTING', 'PREROUTING', 'OUTPUT']: |
714 | + self.assertTrue('-A %s -j run_tests.py-%s' \ |
715 | + % (chain, chain) in new_lines, |
716 | + "Built-in chain %s not wrapped" % (chain,)) |
717 | + |
718 | + def test_filter_rules(self): |
719 | + current_lines = self.sample_filter |
720 | + new_lines = self.manager._modify_rules(current_lines, |
721 | + self.manager.ipv4['filter']) |
722 | + |
723 | + for line in [':nova-compute-FORWARD - [0:0]', |
724 | + ':nova-compute-INPUT - [0:0]', |
725 | + ':nova-compute-local - [0:0]', |
726 | + ':nova-compute-OUTPUT - [0:0]']: |
727 | + self.assertTrue(line in new_lines, "One of nova-compute's chains" |
728 | + " went missing.") |
729 | + |
730 | + seen_lines = set() |
731 | + for line in new_lines: |
732 | + line = line.strip() |
733 | + self.assertTrue(line not in seen_lines, |
734 | + "Duplicate line: %s" % line) |
735 | + seen_lines.add(line) |
736 | + |
737 | + for chain in ['FORWARD', 'OUTPUT']: |
738 | + for line in new_lines: |
739 | + if line.startswith('-A %s' % chain): |
740 | + self.assertTrue('-j nova-filter-top' in line, |
741 | + "First %s rule does not " |
742 | + "jump to nova-filter-top" % chain) |
743 | + break |
744 | + |
745 | + self.assertTrue('-A nova-filter-top ' |
746 | + '-j run_tests.py-local' in new_lines, |
747 | + "nova-filter-top does not jump to wrapped local chain") |
748 | + |
749 | + for chain in ['INPUT', 'OUTPUT', 'FORWARD']: |
750 | + self.assertTrue('-A %s -j run_tests.py-%s' \ |
751 | + % (chain, chain) in new_lines, |
752 | + "Built-in chain %s not wrapped" % (chain,)) |
753 | + |
754 | + |
755 | class NetworkTestCase(test.TestCase): |
756 | """Test cases for network code""" |
757 | def setUp(self): |
758 | |
759 | === modified file 'nova/tests/test_virt.py' |
760 | --- nova/tests/test_virt.py 2011-02-23 19:56:37 +0000 |
761 | +++ nova/tests/test_virt.py 2011-02-28 22:36:03 +0000 |
762 | @@ -14,6 +14,7 @@ |
763 | # License for the specific language governing permissions and limitations |
764 | # under the License. |
765 | |
766 | +import re |
767 | from xml.etree.ElementTree import fromstring as xml_to_tree |
768 | from xml.dom.minidom import parseString as xml_to_dom |
769 | |
770 | @@ -234,16 +235,22 @@ |
771 | self.manager.delete_user(self.user) |
772 | super(IptablesFirewallTestCase, self).tearDown() |
773 | |
774 | - in_rules = [ |
775 | + in_nat_rules = [ |
776 | + '# Generated by iptables-save v1.4.10 on Sat Feb 19 00:03:19 2011', |
777 | + '*nat', |
778 | + ':PREROUTING ACCEPT [1170:189210]', |
779 | + ':INPUT ACCEPT [844:71028]', |
780 | + ':OUTPUT ACCEPT [5149:405186]', |
781 | + ':POSTROUTING ACCEPT [5063:386098]' |
782 | + ] |
783 | + |
784 | + in_filter_rules = [ |
785 | '# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010', |
786 | '*filter', |
787 | ':INPUT ACCEPT [969615:281627771]', |
788 | ':FORWARD ACCEPT [0:0]', |
789 | ':OUTPUT ACCEPT [915599:63811649]', |
790 | ':nova-block-ipv4 - [0:0]', |
791 | - '-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT ', |
792 | - '-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT ', |
793 | - '-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT ', |
794 | '-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ', |
795 | '-A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED' |
796 | ',ESTABLISHED -j ACCEPT ', |
797 | @@ -255,7 +262,7 @@ |
798 | '# Completed on Mon Dec 6 11:54:13 2010', |
799 | ] |
800 | |
801 | - in6_rules = [ |
802 | + in6_filter_rules = [ |
803 | '# Generated by ip6tables-save v1.4.4 on Tue Jan 18 23:47:56 2011', |
804 | '*filter', |
805 | ':INPUT ACCEPT [349155:75810423]', |
806 | @@ -315,23 +322,32 @@ |
807 | instance_ref = db.instance_get(admin_ctxt, instance_ref['id']) |
808 | |
809 | # self.fw.add_instance(instance_ref) |
810 | - def fake_iptables_execute(cmd, process_input=None): |
811 | + def fake_iptables_execute(cmd, process_input=None, attempts=5): |
812 | if cmd == 'sudo ip6tables-save -t filter': |
813 | - return '\n'.join(self.in6_rules), None |
814 | + return '\n'.join(self.in6_filter_rules), None |
815 | if cmd == 'sudo iptables-save -t filter': |
816 | - return '\n'.join(self.in_rules), None |
817 | + return '\n'.join(self.in_filter_rules), None |
818 | + if cmd == 'sudo iptables-save -t nat': |
819 | + return '\n'.join(self.in_nat_rules), None |
820 | if cmd == 'sudo iptables-restore': |
821 | - self.out_rules = process_input.split('\n') |
822 | + lines = process_input.split('\n') |
823 | + if '*filter' in lines: |
824 | + self.out_rules = lines |
825 | return '', '' |
826 | if cmd == 'sudo ip6tables-restore': |
827 | - self.out6_rules = process_input.split('\n') |
828 | + lines = process_input.split('\n') |
829 | + if '*filter' in lines: |
830 | + self.out6_rules = lines |
831 | return '', '' |
832 | - self.fw.execute = fake_iptables_execute |
833 | + |
834 | + from nova.network import linux_net |
835 | + linux_net.iptables_manager.execute = fake_iptables_execute |
836 | |
837 | self.fw.prepare_instance_filter(instance_ref) |
838 | self.fw.apply_instance_filter(instance_ref) |
839 | |
840 | - in_rules = filter(lambda l: not l.startswith('#'), self.in_rules) |
841 | + in_rules = filter(lambda l: not l.startswith('#'), |
842 | + self.in_filter_rules) |
843 | for rule in in_rules: |
844 | if not 'nova' in rule: |
845 | self.assertTrue(rule in self.out_rules, |
846 | @@ -354,17 +370,18 @@ |
847 | self.assertTrue(security_group_chain, |
848 | "The security group chain wasn't added") |
849 | |
850 | - self.assertTrue('-A %s -p icmp -s 192.168.11.0/24 -j ACCEPT' % \ |
851 | - security_group_chain in self.out_rules, |
852 | + regex = re.compile('-A .* -p icmp -s 192.168.11.0/24 -j ACCEPT') |
853 | + self.assertTrue(len(filter(regex.match, self.out_rules)) > 0, |
854 | "ICMP acceptance rule wasn't added") |
855 | |
856 | - self.assertTrue('-A %s -p icmp -s 192.168.11.0/24 -m icmp --icmp-type ' |
857 | - '8 -j ACCEPT' % security_group_chain in self.out_rules, |
858 | + regex = re.compile('-A .* -p icmp -s 192.168.11.0/24 -m icmp ' |
859 | + '--icmp-type 8 -j ACCEPT') |
860 | + self.assertTrue(len(filter(regex.match, self.out_rules)) > 0, |
861 | "ICMP Echo Request acceptance rule wasn't added") |
862 | |
863 | - self.assertTrue('-A %s -p tcp -s 192.168.10.0/24 -m multiport ' |
864 | - '--dports 80:81 -j ACCEPT' % security_group_chain \ |
865 | - in self.out_rules, |
866 | + regex = re.compile('-A .* -p tcp -s 192.168.10.0/24 -m multiport ' |
867 | + '--dports 80:81 -j ACCEPT') |
868 | + self.assertTrue(len(filter(regex.match, self.out_rules)) > 0, |
869 | "TCP port 80/81 acceptance rule wasn't added") |
870 | db.instance_destroy(admin_ctxt, instance_ref['id']) |
871 | |
872 | |
873 | === modified file 'nova/utils.py' |
874 | --- nova/utils.py 2011-02-23 22:50:33 +0000 |
875 | +++ nova/utils.py 2011-02-28 22:36:03 +0000 |
876 | @@ -25,6 +25,7 @@ |
877 | import datetime |
878 | import inspect |
879 | import json |
880 | +import lockfile |
881 | import os |
882 | import random |
883 | import socket |
884 | @@ -43,11 +44,13 @@ |
885 | |
886 | from nova import exception |
887 | from nova.exception import ProcessExecutionError |
888 | +from nova import flags |
889 | from nova import log as logging |
890 | |
891 | |
892 | LOG = logging.getLogger("nova.utils") |
893 | TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" |
894 | +FLAGS = flags.FLAGS |
895 | |
896 | |
897 | def import_class(import_str): |
898 | @@ -128,32 +131,41 @@ |
899 | execute("curl --fail %s -o %s" % (url, target)) |
900 | |
901 | |
902 | -def execute(cmd, process_input=None, addl_env=None, check_exit_code=True): |
903 | - LOG.debug(_("Running cmd (subprocess): %s"), cmd) |
904 | - env = os.environ.copy() |
905 | - if addl_env: |
906 | - env.update(addl_env) |
907 | - obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, |
908 | - stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) |
909 | - result = None |
910 | - if process_input != None: |
911 | - result = obj.communicate(process_input) |
912 | - else: |
913 | - result = obj.communicate() |
914 | - obj.stdin.close() |
915 | - if obj.returncode: |
916 | - LOG.debug(_("Result was %s") % obj.returncode) |
917 | - if check_exit_code and obj.returncode != 0: |
918 | - (stdout, stderr) = result |
919 | - raise ProcessExecutionError(exit_code=obj.returncode, |
920 | - stdout=stdout, |
921 | - stderr=stderr, |
922 | - cmd=cmd) |
923 | - # NOTE(termie): this appears to be necessary to let the subprocess call |
924 | - # clean something up in between calls, without it two |
925 | - # execute calls in a row hangs the second one |
926 | - greenthread.sleep(0) |
927 | - return result |
928 | +def execute(cmd, process_input=None, addl_env=None, check_exit_code=True, |
929 | + attempts=1): |
930 | + while attempts > 0: |
931 | + attempts -= 1 |
932 | + try: |
933 | + LOG.debug(_("Running cmd (subprocess): %s"), cmd) |
934 | + env = os.environ.copy() |
935 | + if addl_env: |
936 | + env.update(addl_env) |
937 | + obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE, |
938 | + stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env) |
939 | + result = None |
940 | + if process_input != None: |
941 | + result = obj.communicate(process_input) |
942 | + else: |
943 | + result = obj.communicate() |
944 | + obj.stdin.close() |
945 | + if obj.returncode: |
946 | + LOG.debug(_("Result was %s") % obj.returncode) |
947 | + if check_exit_code and obj.returncode != 0: |
948 | + (stdout, stderr) = result |
949 | + raise ProcessExecutionError(exit_code=obj.returncode, |
950 | + stdout=stdout, |
951 | + stderr=stderr, |
952 | + cmd=cmd) |
953 | + # NOTE(termie): this appears to be necessary to let the subprocess |
954 | + # call clean something up in between calls, without |
955 | + # it two execute calls in a row hangs the second one |
956 | + greenthread.sleep(0) |
957 | + return result |
958 | + except ProcessExecutionError: |
959 | + if not attempts: |
960 | + raise |
961 | + else: |
962 | + greenthread.sleep(random.randint(20, 200) / 100.0) |
963 | |
964 | |
965 | def ssh_execute(ssh, cmd, process_input=None, |
966 | @@ -491,6 +503,17 @@ |
967 | return json.loads(s) |
968 | |
969 | |
970 | +def synchronized(name): |
971 | + def wrap(f): |
972 | + def inner(*args, **kwargs): |
973 | + lock = lockfile.FileLock(os.path.join(FLAGS.lock_path, |
974 | + 'nova-%s.lock' % name)) |
975 | + with lock: |
976 | + return f(*args, **kwargs) |
977 | + return inner |
978 | + return wrap |
979 | + |
980 | + |
981 | def ensure_b64_encoding(val): |
982 | """Safety method to ensure that values expected to be base64-encoded |
983 | actually are. If they are, the value is returned unchanged. Otherwise, |
984 | |
985 | === modified file 'nova/virt/libvirt_conn.py' |
986 | --- nova/virt/libvirt_conn.py 2011-01-27 23:58:22 +0000 |
987 | +++ nova/virt/libvirt_conn.py 2011-02-28 22:36:03 +0000 |
988 | @@ -44,8 +44,6 @@ |
989 | from xml.dom import minidom |
990 | |
991 | |
992 | -from eventlet import greenthread |
993 | -from eventlet import event |
994 | from eventlet import tpool |
995 | |
996 | import IPy |
997 | @@ -56,7 +54,6 @@ |
998 | from nova import flags |
999 | from nova import log as logging |
1000 | from nova import utils |
1001 | -#from nova.api import context |
1002 | from nova.auth import manager |
1003 | from nova.compute import instance_types |
1004 | from nova.compute import power_state |
1005 | @@ -1206,10 +1203,14 @@ |
1006 | |
1007 | class IptablesFirewallDriver(FirewallDriver): |
1008 | def __init__(self, execute=None, **kwargs): |
1009 | - self.execute = execute or utils.execute |
1010 | + from nova.network import linux_net |
1011 | + self.iptables = linux_net.iptables_manager |
1012 | self.instances = {} |
1013 | self.nwfilter = NWFilterFirewall(kwargs['get_connection']) |
1014 | |
1015 | + self.iptables.ipv4['filter'].add_chain('sg-fallback') |
1016 | + self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP') |
1017 | + |
1018 | def setup_basic_filtering(self, instance): |
1019 | """Use NWFilter from libvirt for this.""" |
1020 | return self.nwfilter.setup_basic_filtering(instance) |
1021 | @@ -1218,127 +1219,98 @@ |
1022 | """No-op. Everything is done in prepare_instance_filter""" |
1023 | pass |
1024 | |
1025 | - def remove_instance(self, instance): |
1026 | + def unfilter_instance(self, instance): |
1027 | if instance['id'] in self.instances: |
1028 | del self.instances[instance['id']] |
1029 | + self.remove_filters_for_instance(instance) |
1030 | + self.iptables.apply() |
1031 | else: |
1032 | LOG.info(_('Attempted to unfilter instance %s which is not ' |
1033 | 'filtered'), instance['id']) |
1034 | |
1035 | - def add_instance(self, instance): |
1036 | + def prepare_instance_filter(self, instance): |
1037 | self.instances[instance['id']] = instance |
1038 | - |
1039 | - def unfilter_instance(self, instance): |
1040 | - self.remove_instance(instance) |
1041 | - self.apply_ruleset() |
1042 | - |
1043 | - def prepare_instance_filter(self, instance): |
1044 | - self.add_instance(instance) |
1045 | - self.apply_ruleset() |
1046 | - |
1047 | - def apply_ruleset(self): |
1048 | - current_filter, _ = self.execute('sudo iptables-save -t filter') |
1049 | - current_lines = current_filter.split('\n') |
1050 | - new_filter = self.modify_rules(current_lines, 4) |
1051 | - self.execute('sudo iptables-restore', |
1052 | - process_input='\n'.join(new_filter)) |
1053 | - if(FLAGS.use_ipv6): |
1054 | - current_filter, _ = self.execute('sudo ip6tables-save -t filter') |
1055 | - current_lines = current_filter.split('\n') |
1056 | - new_filter = self.modify_rules(current_lines, 6) |
1057 | - self.execute('sudo ip6tables-restore', |
1058 | - process_input='\n'.join(new_filter)) |
1059 | - |
1060 | - def modify_rules(self, current_lines, ip_version=4): |
1061 | + self.add_filters_for_instance(instance) |
1062 | + self.iptables.apply() |
1063 | + |
1064 | + def add_filters_for_instance(self, instance): |
1065 | + chain_name = self._instance_chain_name(instance) |
1066 | + |
1067 | + self.iptables.ipv4['filter'].add_chain(chain_name) |
1068 | + ipv4_address = self._ip_for_instance(instance) |
1069 | + self.iptables.ipv4['filter'].add_rule('local', |
1070 | + '-d %s -j $%s' % |
1071 | + (ipv4_address, chain_name)) |
1072 | + |
1073 | + if FLAGS.use_ipv6: |
1074 | + self.iptables.ipv6['filter'].add_chain(chain_name) |
1075 | + ipv6_address = self._ip_for_instance_v6(instance) |
1076 | + self.iptables.ipv6['filter'].add_rule('local', |
1077 | + '-d %s -j $%s' % |
1078 | + (ipv6_address, |
1079 | + chain_name)) |
1080 | + |
1081 | + ipv4_rules, ipv6_rules = self.instance_rules(instance) |
1082 | + |
1083 | + for rule in ipv4_rules: |
1084 | + self.iptables.ipv4['filter'].add_rule(chain_name, rule) |
1085 | + |
1086 | + if FLAGS.use_ipv6: |
1087 | + for rule in ipv6_rules: |
1088 | + self.iptables.ipv6['filter'].add_rule(chain_name, rule) |
1089 | + |
1090 | + def remove_filters_for_instance(self, instance): |
1091 | + chain_name = self._instance_chain_name(instance) |
1092 | + |
1093 | + self.iptables.ipv4['filter'].remove_chain(chain_name) |
1094 | + if FLAGS.use_ipv6: |
1095 | + self.iptables.ipv6['filter'].remove_chain(chain_name) |
1096 | + |
1097 | + def instance_rules(self, instance): |
1098 | ctxt = context.get_admin_context() |
1099 | - # Remove any trace of nova rules. |
1100 | - new_filter = filter(lambda l: 'nova-' not in l, current_lines) |
1101 | - |
1102 | - seen_chains = False |
1103 | - for rules_index in range(len(new_filter)): |
1104 | - if not seen_chains: |
1105 | - if new_filter[rules_index].startswith(':'): |
1106 | - seen_chains = True |
1107 | - elif seen_chains == 1: |
1108 | - if not new_filter[rules_index].startswith(':'): |
1109 | - break |
1110 | - |
1111 | - our_chains = [':nova-fallback - [0:0]'] |
1112 | - our_rules = ['-A nova-fallback -j DROP'] |
1113 | - |
1114 | - our_chains += [':nova-local - [0:0]'] |
1115 | - our_rules += ['-A FORWARD -j nova-local'] |
1116 | - our_rules += ['-A OUTPUT -j nova-local'] |
1117 | - |
1118 | - security_groups = {} |
1119 | - # Add our chains |
1120 | - # First, we add instance chains and rules |
1121 | - for instance_id in self.instances: |
1122 | - instance = self.instances[instance_id] |
1123 | - chain_name = self._instance_chain_name(instance) |
1124 | - if(ip_version == 4): |
1125 | - ip_address = self._ip_for_instance(instance) |
1126 | - elif(ip_version == 6): |
1127 | - ip_address = self._ip_for_instance_v6(instance) |
1128 | - |
1129 | - our_chains += [':%s - [0:0]' % chain_name] |
1130 | - |
1131 | - # Jump to the per-instance chain |
1132 | - our_rules += ['-A nova-local -d %s -j %s' % (ip_address, |
1133 | - chain_name)] |
1134 | - |
1135 | - # Always drop invalid packets |
1136 | - our_rules += ['-A %s -m state --state ' |
1137 | - 'INVALID -j DROP' % (chain_name,)] |
1138 | - |
1139 | - # Allow established connections |
1140 | - our_rules += ['-A %s -m state --state ' |
1141 | - 'ESTABLISHED,RELATED -j ACCEPT' % (chain_name,)] |
1142 | - |
1143 | - # Jump to each security group chain in turn |
1144 | - for security_group in \ |
1145 | - db.security_group_get_by_instance(ctxt, |
1146 | - instance['id']): |
1147 | - security_groups[security_group['id']] = security_group |
1148 | - |
1149 | - sg_chain_name = self._security_group_chain_name( |
1150 | + |
1151 | + ipv4_rules = [] |
1152 | + ipv6_rules = [] |
1153 | + |
1154 | + # Always drop invalid packets |
1155 | + ipv4_rules += ['-m state --state ' 'INVALID -j DROP'] |
1156 | + ipv6_rules += ['-m state --state ' 'INVALID -j DROP'] |
1157 | + |
1158 | + # Allow established connections |
1159 | + ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT'] |
1160 | + ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT'] |
1161 | + |
1162 | + dhcp_server = self._dhcp_server_for_instance(instance) |
1163 | + ipv4_rules += ['-s %s -p udp --sport 67 --dport 68 ' |
1164 | + '-j ACCEPT' % (dhcp_server,)] |
1165 | + |
1166 | + #Allow project network traffic |
1167 | + if FLAGS.allow_project_net_traffic: |
1168 | + cidr = self._project_cidr_for_instance(instance) |
1169 | + ipv4_rules += ['-s %s -j ACCEPT' % (cidr,)] |
1170 | + |
1171 | + # We wrap these in FLAGS.use_ipv6 because they might cause |
1172 | + # a DB lookup. The other ones are just list operations, so |
1173 | + # they're not worth the clutter. |
1174 | + if FLAGS.use_ipv6: |
1175 | + # Allow RA responses |
1176 | + ra_server = self._ra_server_for_instance(instance) |
1177 | + if ra_server: |
1178 | + ipv6_rules += ['-s %s/128 -p icmpv6 -j ACCEPT' % (ra_server,)] |
1179 | + |
1180 | + #Allow project network traffic |
1181 | + if FLAGS.allow_project_net_traffic: |
1182 | + cidrv6 = self._project_cidrv6_for_instance(instance) |
1183 | + ipv6_rules += ['-s %s -j ACCEPT' % (cidrv6,)] |
1184 | + |
1185 | + security_groups = db.security_group_get_by_instance(ctxt, |
1186 | + instance['id']) |
1187 | + |
1188 | + # then, security group chains and rules |
1189 | + for security_group in security_groups: |
1190 | + rules = db.security_group_rule_get_by_security_group(ctxt, |
1191 | security_group['id']) |
1192 | |
1193 | - our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)] |
1194 | - |
1195 | - if(ip_version == 4): |
1196 | - # Allow DHCP responses |
1197 | - dhcp_server = self._dhcp_server_for_instance(instance) |
1198 | - our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68 ' |
1199 | - '-j ACCEPT ' % (chain_name, dhcp_server)] |
1200 | - #Allow project network traffic |
1201 | - if (FLAGS.allow_project_net_traffic): |
1202 | - cidr = self._project_cidr_for_instance(instance) |
1203 | - our_rules += ['-A %s -s %s -j ACCEPT' % (chain_name, cidr)] |
1204 | - elif(ip_version == 6): |
1205 | - # Allow RA responses |
1206 | - ra_server = self._ra_server_for_instance(instance) |
1207 | - if ra_server: |
1208 | - our_rules += ['-A %s -s %s -p icmpv6 -j ACCEPT' % |
1209 | - (chain_name, ra_server + "/128")] |
1210 | - #Allow project network traffic |
1211 | - if (FLAGS.allow_project_net_traffic): |
1212 | - cidrv6 = self._project_cidrv6_for_instance(instance) |
1213 | - our_rules += ['-A %s -s %s -j ACCEPT' % |
1214 | - (chain_name, cidrv6)] |
1215 | - |
1216 | - # If nothing matches, jump to the fallback chain |
1217 | - our_rules += ['-A %s -j nova-fallback' % (chain_name,)] |
1218 | - |
1219 | - # then, security group chains and rules |
1220 | - for security_group_id in security_groups: |
1221 | - chain_name = self._security_group_chain_name(security_group_id) |
1222 | - our_chains += [':%s - [0:0]' % chain_name] |
1223 | - |
1224 | - rules = \ |
1225 | - db.security_group_rule_get_by_security_group(ctxt, |
1226 | - security_group_id) |
1227 | - |
1228 | for rule in rules: |
1229 | logging.info('%r', rule) |
1230 | |
1231 | @@ -1348,14 +1320,16 @@ |
1232 | continue |
1233 | |
1234 | version = _get_ip_version(rule.cidr) |
1235 | - if version != ip_version: |
1236 | - continue |
1237 | + if version == 4: |
1238 | + rules = ipv4_rules |
1239 | + else: |
1240 | + rules = ipv6_rules |
1241 | |
1242 | protocol = rule.protocol |
1243 | if version == 6 and rule.protocol == 'icmp': |
1244 | protocol = 'icmpv6' |
1245 | |
1246 | - args = ['-A', chain_name, '-p', protocol, '-s', rule.cidr] |
1247 | + args = ['-p', protocol, '-s', rule.cidr] |
1248 | |
1249 | if rule.protocol in ['udp', 'tcp']: |
1250 | if rule.from_port == rule.to_port: |
1251 | @@ -1376,32 +1350,35 @@ |
1252 | icmp_type_arg += '/%s' % icmp_code |
1253 | |
1254 | if icmp_type_arg: |
1255 | - if(ip_version == 4): |
1256 | + if version == 4: |
1257 | args += ['-m', 'icmp', '--icmp-type', |
1258 | icmp_type_arg] |
1259 | - elif(ip_version == 6): |
1260 | + elif version == 6: |
1261 | args += ['-m', 'icmp6', '--icmpv6-type', |
1262 | icmp_type_arg] |
1263 | |
1264 | args += ['-j ACCEPT'] |
1265 | - our_rules += [' '.join(args)] |
1266 | - |
1267 | - new_filter[rules_index:rules_index] = our_rules |
1268 | - new_filter[rules_index:rules_index] = our_chains |
1269 | - logging.info('new_filter: %s', '\n'.join(new_filter)) |
1270 | - return new_filter |
1271 | + rules += [' '.join(args)] |
1272 | + |
1273 | + ipv4_rules += ['-j $sg-fallback'] |
1274 | + ipv6_rules += ['-j $sg-fallback'] |
1275 | + |
1276 | + return ipv4_rules, ipv6_rules |
1277 | |
1278 | def refresh_security_group_members(self, security_group): |
1279 | pass |
1280 | |
1281 | def refresh_security_group_rules(self, security_group): |
1282 | - self.apply_ruleset() |
1283 | + for instance in self.instances.values(): |
1284 | + self.remove_filters_for_instance(instance) |
1285 | + self.add_filters_for_instance(instance) |
1286 | + self.iptables.apply() |
1287 | |
1288 | def _security_group_chain_name(self, security_group_id): |
1289 | return 'nova-sg-%s' % (security_group_id,) |
1290 | |
1291 | def _instance_chain_name(self, instance): |
1292 | - return 'nova-inst-%s' % (instance['id'],) |
1293 | + return 'inst-%s' % (instance['id'],) |
1294 | |
1295 | def _ip_for_instance(self, instance): |
1296 | return db.instance_get_fixed_address(context.get_admin_context(), |
Seems like a lot of voodoo to me. Without any docstrings or documentation in these modules, it's all just Greek to me, not knowing the ins and outs of iptables...