Merge lp:~soren/nova/iptables-concurrency into lp:~hudson-openstack/nova/trunk

Proposed by Soren Hansen
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
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.

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.

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

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...

Revision history for this message
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_rules(current_lines,
> 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(new_filter)):

This might be clearer if rewritten as

  for rule_index, rule in enumerate(new_filter):

> + new_filter[rules_index:rules_index] = [str(rule) for rule in rules]

`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.

review: Needs Fixing
Revision history for this message
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_chain('INPUT') or nova_chain('FORWARD') that we could use as the first parameter to add_rule, since we'd have the name in one spot, instead of having to look at two different parameters to determine what the actual chain name is. That also might let us get rid of the IptablesRule class and just store rules as tuples of (chain, rule).

I'm not quite done with this review, but I wanted to give you my early comments.

Revision history for this message
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-SNATTING all -- * * 0.0.0.0/0 0.0.0.0/0
    1 60 nova-network-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0
    1 60 nova-compute-SNATTING all -- * * 0.0.0.0/0 0.0.0.0/0
    1 60 nova-compute-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0

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_manager.ipv4['nat'].add_rule("SNATTING",
334 + "-s %s -j SNAT --to-source %s" % \
335 + (FLAGS.fixed_range,
336 + FLAGS.routing_source_ip))
337 +
338 + iptables_manager.ipv4['nat'].add_rule("POSTROUTING",
339 + "-s %s -j SNAT --to-source %s" % \
340 + (FLAGS.fixed_range,
341 + FLAGS.routing_source_ip))

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-SNATTING (1 references)
 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

Revision history for this message
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(new_filter)):
>
> This might be clearer if rewritten as
>
>  for rule_index, rule in enumerate(new_filter):

Done.

>> +        new_filter[rules_index:rules_index] = [str(rule) for rule in rules]
> `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://linux2go.dk/
Ubuntu Developer    | http://www.ubuntu.com/
OpenStack Developer | http://www.openstack.org/

Revision history for this message
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_chain('INPUT') or nova_chain('FORWARD') that we could use as the first parameter to add_rule, since we'd have the name in one spot, instead of having to look at two different parameters to determine what the actual chain name is.

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://linux2go.dk/
Ubuntu Developer    | http://www.ubuntu.com/
OpenStack Developer | http://www.openstack.org/

Revision history for this message
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_manager.ipv4['nat'].add_rule("SNATTING",
> 334     +                                          "-s %s -j SNAT --to-source %s" % \
> 335     +                                           (FLAGS.fixed_range,
> 336     +                                            FLAGS.routing_source_ip))
> 337     +
> 338     +    iptables_manager.ipv4['nat'].add_rule("POSTROUTING",
> 339     +                                          "-s %s -j SNAT --to-source %s" % \
> 340     +                                           (FLAGS.fixed_range,
> 341     +                                            FLAGS.routing_source_ip))
>
> 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://linux2go.dk/
Ubuntu Developer    | http://www.ubuntu.com/
OpenStack Developer | http://www.openstack.org/

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

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

lp:~soren/nova/iptables-concurrency updated
721. By Soren Hansen

Rename "SNATTING" chain to "snat".

Revision history for this message
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.manager): TRACE: Error: No free nbd devices

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)

Revision history for this message
Christian Berendt (berendt) wrote :

It's possible to load the nbd module using more devices

example: modprobe nbd nbds_max=32

Revision history for this message
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://code.launchpad.net/~soren/nova/iptables-concurrency/+merge/50597
> You are the owner of lp:~soren/nova/iptables-concurrency.

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

Revision history for this message
Christian Berendt (berendt) wrote :

lgtm

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

I've spotted another concurrency problem that I might as well fix now..

lp:~soren/nova/iptables-concurrency updated
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'nova/flags.py'
--- nova/flags.py 2011-02-25 01:04:25 +0000
+++ nova/flags.py 2011-02-28 22:36:03 +0000
@@ -321,6 +321,8 @@
321321
322DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),322DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../'),
323 "Top-level directory for maintaining nova's state")323 "Top-level directory for maintaining nova's state")
324DEFINE_string('lock_path', os.path.join(os.path.dirname(__file__), '../'),
325 "Directory for lock files")
324DEFINE_string('logdir', None, 'output to a per-service log file in named '326DEFINE_string('logdir', None, 'output to a per-service log file in named '
325 'directory')327 'directory')
326328
327329
=== modified file 'nova/network/linux_net.py'
--- nova/network/linux_net.py 2011-02-17 12:46:24 +0000
+++ nova/network/linux_net.py 2011-02-28 22:36:03 +0000
@@ -17,15 +17,17 @@
17Implements vlans, bridges, and iptables rules using linux utilities.17Implements vlans, bridges, and iptables rules using linux utilities.
18"""18"""
1919
20import inspect
20import os21import os
2122
23from eventlet import semaphore
24
22from nova import db25from nova import db
23from nova import exception26from nova import exception
24from nova import flags27from nova import flags
25from nova import log as logging28from nova import log as logging
26from nova import utils29from nova import utils
2730
28
29LOG = logging.getLogger("nova.linux_net")31LOG = logging.getLogger("nova.linux_net")
3032
3133
@@ -52,8 +54,6 @@
52 'location of nova-dhcpbridge')54 'location of nova-dhcpbridge')
53flags.DEFINE_string('routing_source_ip', '$my_ip',55flags.DEFINE_string('routing_source_ip', '$my_ip',
54 'Public IP of network host')56 'Public IP of network host')
55flags.DEFINE_bool('use_nova_chains', False,
56 'use the nova_ routing chains instead of default')
57flags.DEFINE_string('input_chain', 'INPUT',57flags.DEFINE_string('input_chain', 'INPUT',
58 'chain to add nova_input to')58 'chain to add nova_input to')
5959
@@ -63,73 +63,334 @@
63 'dmz range that should be accepted')63 'dmz range that should be accepted')
6464
6565
66binary_name = os.path.basename(inspect.stack()[-1][1])
67
68
69class IptablesRule(object):
70 """An iptables rule
71
72 You shouldn't need to use this class directly, it's only used by
73 IptablesManager
74 """
75 def __init__(self, chain, rule, wrap=True, top=False):
76 self.chain = chain
77 self.rule = rule
78 self.wrap = wrap
79 self.top = top
80
81 def __eq__(self, other):
82 return ((self.chain == other.chain) and
83 (self.rule == other.rule) and
84 (self.top == other.top) and
85 (self.wrap == other.wrap))
86
87 def __ne__(self, other):
88 return ((self.chain != other.chain) or
89 (self.rule != other.rule) or
90 (self.top != other.top) or
91 (self.wrap != other.wrap))
92
93 def __str__(self):
94 if self.wrap:
95 chain = '%s-%s' % (binary_name, self.chain)
96 else:
97 chain = self.chain
98 return '-A %s %s' % (chain, self.rule)
99
100
101class IptablesTable(object):
102 """An iptables table"""
103
104 def __init__(self):
105 self.rules = []
106 self.chains = set()
107 self.unwrapped_chains = set()
108
109 def add_chain(self, name, wrap=True):
110 """Adds a named chain to the table
111
112 The chain name is wrapped to be unique for the component creating
113 it, so different components of Nova can safely create identically
114 named chains without interfering with one another.
115
116 At the moment, its wrapped name is <binary name>-<chain name>,
117 so if nova-compute creates a chain named "OUTPUT", it'll actually
118 end up named "nova-compute-OUTPUT".
119 """
120 if wrap:
121 self.chains.add(name)
122 else:
123 self.unwrapped_chains.add(name)
124
125 def remove_chain(self, name, wrap=True):
126 """Remove named chain
127
128 This removal "cascades". All rule in the chain are removed, as are
129 all rules in other chains that jump to it.
130
131 If the chain is not found, this is merely logged.
132 """
133 if wrap:
134 chain_set = self.chains
135 else:
136 chain_set = self.unwrapped_chains
137
138 if name not in chain_set:
139 LOG.debug(_("Attempted to remove chain %s which doesn't exist"),
140 name)
141 return
142
143 chain_set.remove(name)
144 self.rules = filter(lambda r: r.chain != name, self.rules)
145
146 if wrap:
147 jump_snippet = '-j %s-%s' % (binary_name, name)
148 else:
149 jump_snippet = '-j %s' % (name,)
150
151 self.rules = filter(lambda r: jump_snippet not in r.rule, self.rules)
152
153 def add_rule(self, chain, rule, wrap=True, top=False):
154 """Add a rule to the table
155
156 This is just like what you'd feed to iptables, just without
157 the "-A <chain name>" bit at the start.
158
159 However, if you need to jump to one of your wrapped chains,
160 prepend its name with a '$' which will ensure the wrapping
161 is applied correctly.
162 """
163 if wrap and chain not in self.chains:
164 raise ValueError(_("Unknown chain: %r") % chain)
165
166 if '$' in rule:
167 rule = ' '.join(map(self._wrap_target_chain, rule.split(' ')))
168
169 self.rules.append(IptablesRule(chain, rule, wrap, top))
170
171 def _wrap_target_chain(self, s):
172 if s.startswith('$'):
173 return '%s-%s' % (binary_name, s[1:])
174 return s
175
176 def remove_rule(self, chain, rule, wrap=True, top=False):
177 """Remove a rule from a chain
178
179 Note: The rule must be exactly identical to the one that was added.
180 You cannot switch arguments around like you can with the iptables
181 CLI tool.
182 """
183 try:
184 self.rules.remove(IptablesRule(chain, rule, wrap, top))
185 except ValueError:
186 LOG.debug(_("Tried to remove rule that wasn't there:"
187 " %(chain)r %(rule)r %(wrap)r %(top)r"),
188 {'chain': chain, 'rule': rule,
189 'top': top, 'wrap': wrap})
190
191
192class IptablesManager(object):
193 """Wrapper for iptables
194
195 See IptablesTable for some usage docs
196
197 A number of chains are set up to begin with.
198
199 First, nova-filter-top. It's added at the top of FORWARD and OUTPUT. Its
200 name is not wrapped, so it's shared between the various nova workers. It's
201 intended for rules that need to live at the top of the FORWARD and OUTPUT
202 chains. It's in both the ipv4 and ipv6 set of tables.
203
204 For ipv4 and ipv6, the builtin INPUT, OUTPUT, and FORWARD filter chains are
205 wrapped, meaning that the "real" INPUT chain has a rule that jumps to the
206 wrapped INPUT chain, etc. Additionally, there's a wrapped chain named
207 "local" which is jumped to from nova-filter-top.
208
209 For ipv4, the builtin PREROUTING, OUTPUT, and POSTROUTING nat chains are
210 wrapped in the same was as the builtin filter chains. Additionally, there's
211 a snat chain that is applied after the POSTROUTING chain.
212 """
213 def __init__(self, execute=None):
214 if not execute:
215 if FLAGS.fake_network:
216 self.execute = lambda *args, **kwargs: ('', '')
217 else:
218 self.execute = utils.execute
219 else:
220 self.execute = execute
221
222 self.ipv4 = {'filter': IptablesTable(),
223 'nat': IptablesTable()}
224 self.ipv6 = {'filter': IptablesTable()}
225
226 # Add a nova-filter-top chain. It's intended to be shared
227 # among the various nova components. It sits at the very top
228 # of FORWARD and OUTPUT.
229 for tables in [self.ipv4, self.ipv6]:
230 tables['filter'].add_chain('nova-filter-top', wrap=False)
231 tables['filter'].add_rule('FORWARD', '-j nova-filter-top',
232 wrap=False, top=True)
233 tables['filter'].add_rule('OUTPUT', '-j nova-filter-top',
234 wrap=False, top=True)
235
236 tables['filter'].add_chain('local')
237 tables['filter'].add_rule('nova-filter-top', '-j $local',
238 wrap=False)
239
240 # Wrap the builtin chains
241 builtin_chains = {4: {'filter': ['INPUT', 'OUTPUT', 'FORWARD'],
242 'nat': ['PREROUTING', 'OUTPUT', 'POSTROUTING']},
243 6: {'filter': ['INPUT', 'OUTPUT', 'FORWARD']}}
244
245 for ip_version in builtin_chains:
246 if ip_version == 4:
247 tables = self.ipv4
248 elif ip_version == 6:
249 tables = self.ipv6
250
251 for table, chains in builtin_chains[ip_version].iteritems():
252 for chain in chains:
253 tables[table].add_chain(chain)
254 tables[table].add_rule(chain, '-j $%s' % (chain,),
255 wrap=False)
256
257 # Add a nova-postrouting-bottom chain. It's intended to be shared
258 # among the various nova components. We set it as the last chain
259 # of POSTROUTING chain.
260 self.ipv4['nat'].add_chain('nova-postrouting-bottom', wrap=False)
261 self.ipv4['nat'].add_rule('POSTROUTING', '-j nova-postrouting-bottom',
262 wrap=False)
263
264 # We add a snat chain to the shared nova-postrouting-bottom chain
265 # so that it's applied last.
266 self.ipv4['nat'].add_chain('snat')
267 self.ipv4['nat'].add_rule('nova-postrouting-bottom', '-j $snat',
268 wrap=False)
269
270 # And then we add a floating-snat chain and jump to first thing in
271 # the snat chain.
272 self.ipv4['nat'].add_chain('floating-snat')
273 self.ipv4['nat'].add_rule('snat', '-j $floating-snat')
274
275 self.semaphore = semaphore.Semaphore()
276
277 @utils.synchronized('iptables')
278 def apply(self):
279 """Apply the current in-memory set of iptables rules
280
281 This will blow away any rules left over from previous runs of the
282 same component of Nova, and replace them with our current set of
283 rules. This happens atomically, thanks to iptables-restore.
284
285 We wrap the call in a semaphore lock, so that we don't race with
286 ourselves. In the event of a race with another component running
287 an iptables-* command at the same time, we retry up to 5 times.
288 """
289 with self.semaphore:
290 s = [('iptables', self.ipv4)]
291 if FLAGS.use_ipv6:
292 s += [('ip6tables', self.ipv6)]
293
294 for cmd, tables in s:
295 for table in tables:
296 current_table, _ = self.execute('sudo %s-save -t %s' %
297 (cmd, table), attempts=5)
298 current_lines = current_table.split('\n')
299 new_filter = self._modify_rules(current_lines,
300 tables[table])
301 self.execute('sudo %s-restore' % (cmd,),
302 process_input='\n'.join(new_filter),
303 attempts=5)
304
305 def _modify_rules(self, current_lines, table, binary=None):
306 unwrapped_chains = table.unwrapped_chains
307 chains = table.chains
308 rules = table.rules
309
310 # Remove any trace of our rules
311 new_filter = filter(lambda line: binary_name not in line,
312 current_lines)
313
314 seen_chains = False
315 rules_index = 0
316 for rules_index, rule in enumerate(new_filter):
317 if not seen_chains:
318 if rule.startswith(':'):
319 seen_chains = True
320 else:
321 if not rule.startswith(':'):
322 break
323
324 our_rules = []
325 for rule in rules:
326 rule_str = str(rule)
327 if rule.top:
328 # rule.top == True means we want this rule to be at the top.
329 # Further down, we weed out duplicates from the bottom of the
330 # list, so here we remove the dupes ahead of time.
331 new_filter = filter(lambda s: s.strip() != rule_str.strip(),
332 new_filter)
333 our_rules += [rule_str]
334
335 new_filter[rules_index:rules_index] = our_rules
336
337 new_filter[rules_index:rules_index] = [':%s - [0:0]' % \
338 (name,) \
339 for name in unwrapped_chains]
340 new_filter[rules_index:rules_index] = [':%s-%s - [0:0]' % \
341 (binary_name, name,) \
342 for name in chains]
343
344 seen_lines = set()
345
346 def _weed_out_duplicates(line):
347 line = line.strip()
348 if line in seen_lines:
349 return False
350 else:
351 seen_lines.add(line)
352 return True
353
354 # We filter duplicates, letting the *last* occurrence take
355 # precendence.
356 new_filter.reverse()
357 new_filter = filter(_weed_out_duplicates, new_filter)
358 new_filter.reverse()
359 return new_filter
360
361
362iptables_manager = IptablesManager()
363
364
66def metadata_forward():365def metadata_forward():
67 """Create forwarding rule for metadata"""366 """Create forwarding rule for metadata"""
68 _confirm_rule("PREROUTING", "-t nat -s 0.0.0.0/0 "367 iptables_manager.ipv4['nat'].add_rule("PREROUTING",
69 "-d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT "368 "-s 0.0.0.0/0 -d 169.254.169.254/32 "
70 "--to-destination %s:%s" % (FLAGS.ec2_dmz_host, FLAGS.ec2_port))369 "-p tcp -m tcp --dport 80 -j DNAT "
370 "--to-destination %s:%s" % \
371 (FLAGS.ec2_dmz_host, FLAGS.ec2_port))
372 iptables_manager.apply()
71373
72374
73def init_host():375def init_host():
74 """Basic networking setup goes here"""376 """Basic networking setup goes here"""
75377
76 if FLAGS.use_nova_chains:
77 _execute("sudo iptables -N nova_input", check_exit_code=False)
78 _execute("sudo iptables -D %s -j nova_input" % FLAGS.input_chain,
79 check_exit_code=False)
80 _execute("sudo iptables -A %s -j nova_input" % FLAGS.input_chain)
81
82 _execute("sudo iptables -N nova_forward", check_exit_code=False)
83 _execute("sudo iptables -D FORWARD -j nova_forward",
84 check_exit_code=False)
85 _execute("sudo iptables -A FORWARD -j nova_forward")
86
87 _execute("sudo iptables -N nova_output", check_exit_code=False)
88 _execute("sudo iptables -D OUTPUT -j nova_output",
89 check_exit_code=False)
90 _execute("sudo iptables -A OUTPUT -j nova_output")
91
92 _execute("sudo iptables -t nat -N nova_prerouting",
93 check_exit_code=False)
94 _execute("sudo iptables -t nat -D PREROUTING -j nova_prerouting",
95 check_exit_code=False)
96 _execute("sudo iptables -t nat -A PREROUTING -j nova_prerouting")
97
98 _execute("sudo iptables -t nat -N nova_postrouting",
99 check_exit_code=False)
100 _execute("sudo iptables -t nat -D POSTROUTING -j nova_postrouting",
101 check_exit_code=False)
102 _execute("sudo iptables -t nat -A POSTROUTING -j nova_postrouting")
103
104 _execute("sudo iptables -t nat -N nova_snatting",
105 check_exit_code=False)
106 _execute("sudo iptables -t nat -D POSTROUTING -j nova_snatting",
107 check_exit_code=False)
108 _execute("sudo iptables -t nat -A POSTROUTING -j nova_snatting")
109
110 _execute("sudo iptables -t nat -N nova_output", check_exit_code=False)
111 _execute("sudo iptables -t nat -D OUTPUT -j nova_output",
112 check_exit_code=False)
113 _execute("sudo iptables -t nat -A OUTPUT -j nova_output")
114 else:
115 # NOTE(vish): This makes it easy to ensure snatting rules always
116 # come after the accept rules in the postrouting chain
117 _execute("sudo iptables -t nat -N SNATTING",
118 check_exit_code=False)
119 _execute("sudo iptables -t nat -D POSTROUTING -j SNATTING",
120 check_exit_code=False)
121 _execute("sudo iptables -t nat -A POSTROUTING -j SNATTING")
122
123 # NOTE(devcamcar): Cloud public SNAT entries and the default378 # NOTE(devcamcar): Cloud public SNAT entries and the default
124 # SNAT rule for outbound traffic.379 # SNAT rule for outbound traffic.
125 _confirm_rule("SNATTING", "-t nat -s %s "380 iptables_manager.ipv4['nat'].add_rule("snat",
126 "-j SNAT --to-source %s"381 "-s %s -j SNAT --to-source %s" % \
127 % (FLAGS.fixed_range, FLAGS.routing_source_ip), append=True)382 (FLAGS.fixed_range,
128383 FLAGS.routing_source_ip))
129 _confirm_rule("POSTROUTING", "-t nat -s %s -d %s -j ACCEPT" %384
130 (FLAGS.fixed_range, FLAGS.dmz_cidr))385 iptables_manager.ipv4['nat'].add_rule("POSTROUTING",
131 _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" %386 "-s %s -d %s -j ACCEPT" % \
132 {'range': FLAGS.fixed_range})387 (FLAGS.fixed_range, FLAGS.dmz_cidr))
388
389 iptables_manager.ipv4['nat'].add_rule("POSTROUTING",
390 "-s %(range)s -d %(range)s "
391 "-j ACCEPT" % \
392 {'range': FLAGS.fixed_range})
393 iptables_manager.apply()
133394
134395
135def bind_floating_ip(floating_ip, check_exit_code=True):396def bind_floating_ip(floating_ip, check_exit_code=True):
@@ -147,31 +408,36 @@
147408
148def ensure_vlan_forward(public_ip, port, private_ip):409def ensure_vlan_forward(public_ip, port, private_ip):
149 """Sets up forwarding rules for vlan"""410 """Sets up forwarding rules for vlan"""
150 _confirm_rule("FORWARD", "-d %s -p udp --dport 1194 -j ACCEPT" %411 iptables_manager.ipv4['filter'].add_rule("FORWARD",
151 private_ip)412 "-d %s -p udp "
152 _confirm_rule("PREROUTING",413 "--dport 1194 "
153 "-t nat -d %s -p udp --dport %s -j DNAT --to %s:1194"414 "-j ACCEPT" % private_ip)
154 % (public_ip, port, private_ip))415 iptables_manager.ipv4['nat'].add_rule("PREROUTING",
416 "-d %s -p udp "
417 "--dport %s -j DNAT --to %s:1194" %
418 (public_ip, port, private_ip))
419 iptables_manager.apply()
155420
156421
157def ensure_floating_forward(floating_ip, fixed_ip):422def ensure_floating_forward(floating_ip, fixed_ip):
158 """Ensure floating ip forwarding rule"""423 """Ensure floating ip forwarding rule"""
159 _confirm_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s"424 for chain, rule in floating_forward_rules(floating_ip, fixed_ip):
160 % (floating_ip, fixed_ip))425 iptables_manager.ipv4['nat'].add_rule(chain, rule)
161 _confirm_rule("OUTPUT", "-t nat -d %s -j DNAT --to %s"426 iptables_manager.apply()
162 % (floating_ip, fixed_ip))
163 _confirm_rule("SNATTING", "-t nat -s %s -j SNAT --to %s"
164 % (fixed_ip, floating_ip))
165427
166428
167def remove_floating_forward(floating_ip, fixed_ip):429def remove_floating_forward(floating_ip, fixed_ip):
168 """Remove forwarding for floating ip"""430 """Remove forwarding for floating ip"""
169 _remove_rule("PREROUTING", "-t nat -d %s -j DNAT --to %s"431 for chain, rule in floating_forward_rules(floating_ip, fixed_ip):
170 % (floating_ip, fixed_ip))432 iptables_manager.ipv4['nat'].remove_rule(chain, rule)
171 _remove_rule("OUTPUT", "-t nat -d %s -j DNAT --to %s"433 iptables_manager.apply()
172 % (floating_ip, fixed_ip))434
173 _remove_rule("SNATTING", "-t nat -s %s -j SNAT --to %s"435
174 % (fixed_ip, floating_ip))436def floating_forward_rules(floating_ip, fixed_ip):
437 return [("PREROUTING", "-d %s -j DNAT --to %s" % (floating_ip, fixed_ip)),
438 ("OUTPUT", "-d %s -j DNAT --to %s" % (floating_ip, fixed_ip)),
439 ("floating-snat",
440 "-s %s -j SNAT --to %s" % (fixed_ip, floating_ip))]
175441
176442
177def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):443def ensure_vlan_bridge(vlan_num, bridge, net_attrs=None):
@@ -258,19 +524,12 @@
258 "enslave it to bridge %s.\n" % (interface, bridge)):524 "enslave it to bridge %s.\n" % (interface, bridge)):
259 raise exception.Error("Failed to add interface: %s" % err)525 raise exception.Error("Failed to add interface: %s" % err)
260526
261 if FLAGS.use_nova_chains:527 iptables_manager.ipv4['filter'].add_rule("FORWARD",
262 (out, err) = _execute("sudo iptables -N nova_forward",528 "--in-interface %s -j ACCEPT" % \
263 check_exit_code=False)529 bridge)
264 if err != 'iptables: Chain already exists.\n':530 iptables_manager.ipv4['filter'].add_rule("FORWARD",
265 # NOTE(vish): chain didn't exist link chain531 "--out-interface %s -j ACCEPT" % \
266 _execute("sudo iptables -D FORWARD -j nova_forward",532 bridge)
267 check_exit_code=False)
268 _execute("sudo iptables -A FORWARD -j nova_forward")
269
270 _confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge)
271 _confirm_rule("FORWARD", "--out-interface %s -j ACCEPT" % bridge)
272 _execute("sudo iptables -N nova-local", check_exit_code=False)
273 _confirm_rule("FORWARD", "-j nova-local")
274533
275534
276def get_dhcp_hosts(context, network_id):535def get_dhcp_hosts(context, network_id):
@@ -390,26 +649,6 @@
390 return not err649 return not err
391650
392651
393def _confirm_rule(chain, cmd, append=False):
394 """Delete and re-add iptables rule"""
395 if FLAGS.use_nova_chains:
396 chain = "nova_%s" % chain.lower()
397 if append:
398 loc = "-A"
399 else:
400 loc = "-I"
401 _execute("sudo iptables --delete %s %s" % (chain, cmd),
402 check_exit_code=False)
403 _execute("sudo iptables %s %s %s" % (loc, chain, cmd))
404
405
406def _remove_rule(chain, cmd):
407 """Remove iptables rule"""
408 if FLAGS.use_nova_chains:
409 chain = "%s" % chain.lower()
410 _execute("sudo iptables --delete %s %s" % (chain, cmd))
411
412
413def _dnsmasq_cmd(net):652def _dnsmasq_cmd(net):
414 """Builds dnsmasq command"""653 """Builds dnsmasq command"""
415 cmd = ['sudo -E dnsmasq',654 cmd = ['sudo -E dnsmasq',
416655
=== modified file 'nova/tests/test_misc.py'
--- nova/tests/test_misc.py 2011-02-21 18:55:25 +0000
+++ nova/tests/test_misc.py 2011-02-28 22:36:03 +0000
@@ -14,10 +14,14 @@
14# License for the specific language governing permissions and limitations14# License for the specific language governing permissions and limitations
15# under the License.15# under the License.
1616
17from datetime import datetime
18import errno
17import os19import os
20import select
21import time
1822
19from nova import test23from nova import test
20from nova.utils import parse_mailmap, str_dict_replace24from nova.utils import parse_mailmap, str_dict_replace, synchronized
2125
2226
23class ProjectTestCase(test.TestCase):27class ProjectTestCase(test.TestCase):
@@ -55,3 +59,34 @@
55 '%r not listed in Authors' % missing)59 '%r not listed in Authors' % missing)
56 finally:60 finally:
57 tree.unlock()61 tree.unlock()
62
63
64class LockTestCase(test.TestCase):
65 def test_synchronized(self):
66 rpipe, wpipe = os.pipe()
67 pid = os.fork()
68 if pid > 0:
69 os.close(wpipe)
70
71 @synchronized('testlock')
72 def f():
73 rfds, _, __ = select.select([rpipe], [], [], 1)
74 self.assertEquals(len(rfds), 0, "The other process, which was"
75 " supposed to be locked, "
76 "wrote on its end of the "
77 "pipe")
78 os.close(rpipe)
79
80 f()
81 else:
82 os.close(rpipe)
83
84 @synchronized('testlock')
85 def g():
86 try:
87 os.write(wpipe, "foo")
88 except OSError, e:
89 self.assertEquals(e.errno, errno.EPIPE)
90 return
91 g()
92 os._exit(0)
5893
=== modified file 'nova/tests/test_network.py'
--- nova/tests/test_network.py 2011-02-23 19:20:07 +0000
+++ nova/tests/test_network.py 2011-02-28 22:36:03 +0000
@@ -29,11 +29,153 @@
29from nova import test29from nova import test
30from nova import utils30from nova import utils
31from nova.auth import manager31from nova.auth import manager
32from nova.network import linux_net
3233
33FLAGS = flags.FLAGS34FLAGS = flags.FLAGS
34LOG = logging.getLogger('nova.tests.network')35LOG = logging.getLogger('nova.tests.network')
3536
3637
38class IptablesManagerTestCase(test.TestCase):
39 sample_filter = ['#Generated by iptables-save on Fri Feb 18 15:17:05 2011',
40 '*filter',
41 ':INPUT ACCEPT [2223527:305688874]',
42 ':FORWARD ACCEPT [0:0]',
43 ':OUTPUT ACCEPT [2172501:140856656]',
44 ':nova-compute-FORWARD - [0:0]',
45 ':nova-compute-INPUT - [0:0]',
46 ':nova-compute-local - [0:0]',
47 ':nova-compute-OUTPUT - [0:0]',
48 ':nova-filter-top - [0:0]',
49 '-A FORWARD -j nova-filter-top ',
50 '-A OUTPUT -j nova-filter-top ',
51 '-A nova-filter-top -j nova-compute-local ',
52 '-A INPUT -j nova-compute-INPUT ',
53 '-A OUTPUT -j nova-compute-OUTPUT ',
54 '-A FORWARD -j nova-compute-FORWARD ',
55 '-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT ',
56 '-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT ',
57 '-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT ',
58 '-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ',
59 '-A FORWARD -s 192.168.122.0/24 -i virbr0 -j ACCEPT ',
60 '-A FORWARD -i virbr0 -o virbr0 -j ACCEPT ',
61 '-A FORWARD -o virbr0 -j REJECT --reject-with '
62 'icmp-port-unreachable ',
63 '-A FORWARD -i virbr0 -j REJECT --reject-with '
64 'icmp-port-unreachable ',
65 'COMMIT',
66 '# Completed on Fri Feb 18 15:17:05 2011']
67
68 sample_nat = ['# Generated by iptables-save on Fri Feb 18 15:17:05 2011',
69 '*nat',
70 ':PREROUTING ACCEPT [3936:762355]',
71 ':INPUT ACCEPT [2447:225266]',
72 ':OUTPUT ACCEPT [63491:4191863]',
73 ':POSTROUTING ACCEPT [63112:4108641]',
74 ':nova-compute-OUTPUT - [0:0]',
75 ':nova-compute-floating-ip-snat - [0:0]',
76 ':nova-compute-SNATTING - [0:0]',
77 ':nova-compute-PREROUTING - [0:0]',
78 ':nova-compute-POSTROUTING - [0:0]',
79 ':nova-postrouting-bottom - [0:0]',
80 '-A PREROUTING -j nova-compute-PREROUTING ',
81 '-A OUTPUT -j nova-compute-OUTPUT ',
82 '-A POSTROUTING -j nova-compute-POSTROUTING ',
83 '-A POSTROUTING -j nova-postrouting-bottom ',
84 '-A nova-postrouting-bottom -j nova-compute-SNATTING ',
85 '-A nova-compute-SNATTING -j nova-compute-floating-ip-snat ',
86 'COMMIT',
87 '# Completed on Fri Feb 18 15:17:05 2011']
88
89 def setUp(self):
90 super(IptablesManagerTestCase, self).setUp()
91 self.manager = linux_net.IptablesManager()
92
93 def test_filter_rules_are_wrapped(self):
94 current_lines = self.sample_filter
95
96 table = self.manager.ipv4['filter']
97 table.add_rule('FORWARD', '-s 1.2.3.4/5 -j DROP')
98 new_lines = self.manager._modify_rules(current_lines, table)
99 self.assertTrue('-A run_tests.py-FORWARD '
100 '-s 1.2.3.4/5 -j DROP' in new_lines)
101
102 table.remove_rule('FORWARD', '-s 1.2.3.4/5 -j DROP')
103 new_lines = self.manager._modify_rules(current_lines, table)
104 self.assertTrue('-A run_tests.py-FORWARD '
105 '-s 1.2.3.4/5 -j DROP' not in new_lines)
106
107 def test_nat_rules(self):
108 current_lines = self.sample_nat
109 new_lines = self.manager._modify_rules(current_lines,
110 self.manager.ipv4['nat'])
111
112 for line in [':nova-compute-OUTPUT - [0:0]',
113 ':nova-compute-floating-ip-snat - [0:0]',
114 ':nova-compute-SNATTING - [0:0]',
115 ':nova-compute-PREROUTING - [0:0]',
116 ':nova-compute-POSTROUTING - [0:0]']:
117 self.assertTrue(line in new_lines, "One of nova-compute's chains "
118 "went missing.")
119
120 seen_lines = set()
121 for line in new_lines:
122 line = line.strip()
123 self.assertTrue(line not in seen_lines,
124 "Duplicate line: %s" % line)
125 seen_lines.add(line)
126
127 last_postrouting_line = ''
128
129 for line in new_lines:
130 if line.startswith('-A POSTROUTING'):
131 last_postrouting_line = line
132
133 self.assertTrue('-j nova-postrouting-bottom' in last_postrouting_line,
134 "Last POSTROUTING rule does not jump to "
135 "nova-postouting-bottom: %s" % last_postrouting_line)
136
137 for chain in ['POSTROUTING', 'PREROUTING', 'OUTPUT']:
138 self.assertTrue('-A %s -j run_tests.py-%s' \
139 % (chain, chain) in new_lines,
140 "Built-in chain %s not wrapped" % (chain,))
141
142 def test_filter_rules(self):
143 current_lines = self.sample_filter
144 new_lines = self.manager._modify_rules(current_lines,
145 self.manager.ipv4['filter'])
146
147 for line in [':nova-compute-FORWARD - [0:0]',
148 ':nova-compute-INPUT - [0:0]',
149 ':nova-compute-local - [0:0]',
150 ':nova-compute-OUTPUT - [0:0]']:
151 self.assertTrue(line in new_lines, "One of nova-compute's chains"
152 " went missing.")
153
154 seen_lines = set()
155 for line in new_lines:
156 line = line.strip()
157 self.assertTrue(line not in seen_lines,
158 "Duplicate line: %s" % line)
159 seen_lines.add(line)
160
161 for chain in ['FORWARD', 'OUTPUT']:
162 for line in new_lines:
163 if line.startswith('-A %s' % chain):
164 self.assertTrue('-j nova-filter-top' in line,
165 "First %s rule does not "
166 "jump to nova-filter-top" % chain)
167 break
168
169 self.assertTrue('-A nova-filter-top '
170 '-j run_tests.py-local' in new_lines,
171 "nova-filter-top does not jump to wrapped local chain")
172
173 for chain in ['INPUT', 'OUTPUT', 'FORWARD']:
174 self.assertTrue('-A %s -j run_tests.py-%s' \
175 % (chain, chain) in new_lines,
176 "Built-in chain %s not wrapped" % (chain,))
177
178
37class NetworkTestCase(test.TestCase):179class NetworkTestCase(test.TestCase):
38 """Test cases for network code"""180 """Test cases for network code"""
39 def setUp(self):181 def setUp(self):
40182
=== modified file 'nova/tests/test_virt.py'
--- nova/tests/test_virt.py 2011-02-23 19:56:37 +0000
+++ nova/tests/test_virt.py 2011-02-28 22:36:03 +0000
@@ -14,6 +14,7 @@
14# License for the specific language governing permissions and limitations14# License for the specific language governing permissions and limitations
15# under the License.15# under the License.
1616
17import re
17from xml.etree.ElementTree import fromstring as xml_to_tree18from xml.etree.ElementTree import fromstring as xml_to_tree
18from xml.dom.minidom import parseString as xml_to_dom19from xml.dom.minidom import parseString as xml_to_dom
1920
@@ -234,16 +235,22 @@
234 self.manager.delete_user(self.user)235 self.manager.delete_user(self.user)
235 super(IptablesFirewallTestCase, self).tearDown()236 super(IptablesFirewallTestCase, self).tearDown()
236237
237 in_rules = [238 in_nat_rules = [
239 '# Generated by iptables-save v1.4.10 on Sat Feb 19 00:03:19 2011',
240 '*nat',
241 ':PREROUTING ACCEPT [1170:189210]',
242 ':INPUT ACCEPT [844:71028]',
243 ':OUTPUT ACCEPT [5149:405186]',
244 ':POSTROUTING ACCEPT [5063:386098]'
245 ]
246
247 in_filter_rules = [
238 '# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010',248 '# Generated by iptables-save v1.4.4 on Mon Dec 6 11:54:13 2010',
239 '*filter',249 '*filter',
240 ':INPUT ACCEPT [969615:281627771]',250 ':INPUT ACCEPT [969615:281627771]',
241 ':FORWARD ACCEPT [0:0]',251 ':FORWARD ACCEPT [0:0]',
242 ':OUTPUT ACCEPT [915599:63811649]',252 ':OUTPUT ACCEPT [915599:63811649]',
243 ':nova-block-ipv4 - [0:0]',253 ':nova-block-ipv4 - [0:0]',
244 '-A INPUT -i virbr0 -p udp -m udp --dport 53 -j ACCEPT ',
245 '-A INPUT -i virbr0 -p tcp -m tcp --dport 53 -j ACCEPT ',
246 '-A INPUT -i virbr0 -p udp -m udp --dport 67 -j ACCEPT ',
247 '-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ',254 '-A INPUT -i virbr0 -p tcp -m tcp --dport 67 -j ACCEPT ',
248 '-A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED'255 '-A FORWARD -d 192.168.122.0/24 -o virbr0 -m state --state RELATED'
249 ',ESTABLISHED -j ACCEPT ',256 ',ESTABLISHED -j ACCEPT ',
@@ -255,7 +262,7 @@
255 '# Completed on Mon Dec 6 11:54:13 2010',262 '# Completed on Mon Dec 6 11:54:13 2010',
256 ]263 ]
257264
258 in6_rules = [265 in6_filter_rules = [
259 '# Generated by ip6tables-save v1.4.4 on Tue Jan 18 23:47:56 2011',266 '# Generated by ip6tables-save v1.4.4 on Tue Jan 18 23:47:56 2011',
260 '*filter',267 '*filter',
261 ':INPUT ACCEPT [349155:75810423]',268 ':INPUT ACCEPT [349155:75810423]',
@@ -315,23 +322,32 @@
315 instance_ref = db.instance_get(admin_ctxt, instance_ref['id'])322 instance_ref = db.instance_get(admin_ctxt, instance_ref['id'])
316323
317# self.fw.add_instance(instance_ref)324# self.fw.add_instance(instance_ref)
318 def fake_iptables_execute(cmd, process_input=None):325 def fake_iptables_execute(cmd, process_input=None, attempts=5):
319 if cmd == 'sudo ip6tables-save -t filter':326 if cmd == 'sudo ip6tables-save -t filter':
320 return '\n'.join(self.in6_rules), None327 return '\n'.join(self.in6_filter_rules), None
321 if cmd == 'sudo iptables-save -t filter':328 if cmd == 'sudo iptables-save -t filter':
322 return '\n'.join(self.in_rules), None329 return '\n'.join(self.in_filter_rules), None
330 if cmd == 'sudo iptables-save -t nat':
331 return '\n'.join(self.in_nat_rules), None
323 if cmd == 'sudo iptables-restore':332 if cmd == 'sudo iptables-restore':
324 self.out_rules = process_input.split('\n')333 lines = process_input.split('\n')
334 if '*filter' in lines:
335 self.out_rules = lines
325 return '', ''336 return '', ''
326 if cmd == 'sudo ip6tables-restore':337 if cmd == 'sudo ip6tables-restore':
327 self.out6_rules = process_input.split('\n')338 lines = process_input.split('\n')
339 if '*filter' in lines:
340 self.out6_rules = lines
328 return '', ''341 return '', ''
329 self.fw.execute = fake_iptables_execute342
343 from nova.network import linux_net
344 linux_net.iptables_manager.execute = fake_iptables_execute
330345
331 self.fw.prepare_instance_filter(instance_ref)346 self.fw.prepare_instance_filter(instance_ref)
332 self.fw.apply_instance_filter(instance_ref)347 self.fw.apply_instance_filter(instance_ref)
333348
334 in_rules = filter(lambda l: not l.startswith('#'), self.in_rules)349 in_rules = filter(lambda l: not l.startswith('#'),
350 self.in_filter_rules)
335 for rule in in_rules:351 for rule in in_rules:
336 if not 'nova' in rule:352 if not 'nova' in rule:
337 self.assertTrue(rule in self.out_rules,353 self.assertTrue(rule in self.out_rules,
@@ -354,17 +370,18 @@
354 self.assertTrue(security_group_chain,370 self.assertTrue(security_group_chain,
355 "The security group chain wasn't added")371 "The security group chain wasn't added")
356372
357 self.assertTrue('-A %s -p icmp -s 192.168.11.0/24 -j ACCEPT' % \373 regex = re.compile('-A .* -p icmp -s 192.168.11.0/24 -j ACCEPT')
358 security_group_chain in self.out_rules,374 self.assertTrue(len(filter(regex.match, self.out_rules)) > 0,
359 "ICMP acceptance rule wasn't added")375 "ICMP acceptance rule wasn't added")
360376
361 self.assertTrue('-A %s -p icmp -s 192.168.11.0/24 -m icmp --icmp-type '377 regex = re.compile('-A .* -p icmp -s 192.168.11.0/24 -m icmp '
362 '8 -j ACCEPT' % security_group_chain in self.out_rules,378 '--icmp-type 8 -j ACCEPT')
379 self.assertTrue(len(filter(regex.match, self.out_rules)) > 0,
363 "ICMP Echo Request acceptance rule wasn't added")380 "ICMP Echo Request acceptance rule wasn't added")
364381
365 self.assertTrue('-A %s -p tcp -s 192.168.10.0/24 -m multiport '382 regex = re.compile('-A .* -p tcp -s 192.168.10.0/24 -m multiport '
366 '--dports 80:81 -j ACCEPT' % security_group_chain \383 '--dports 80:81 -j ACCEPT')
367 in self.out_rules,384 self.assertTrue(len(filter(regex.match, self.out_rules)) > 0,
368 "TCP port 80/81 acceptance rule wasn't added")385 "TCP port 80/81 acceptance rule wasn't added")
369 db.instance_destroy(admin_ctxt, instance_ref['id'])386 db.instance_destroy(admin_ctxt, instance_ref['id'])
370387
371388
=== modified file 'nova/utils.py'
--- nova/utils.py 2011-02-23 22:50:33 +0000
+++ nova/utils.py 2011-02-28 22:36:03 +0000
@@ -25,6 +25,7 @@
25import datetime25import datetime
26import inspect26import inspect
27import json27import json
28import lockfile
28import os29import os
29import random30import random
30import socket31import socket
@@ -43,11 +44,13 @@
4344
44from nova import exception45from nova import exception
45from nova.exception import ProcessExecutionError46from nova.exception import ProcessExecutionError
47from nova import flags
46from nova import log as logging48from nova import log as logging
4749
4850
49LOG = logging.getLogger("nova.utils")51LOG = logging.getLogger("nova.utils")
50TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"52TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
53FLAGS = flags.FLAGS
5154
5255
53def import_class(import_str):56def import_class(import_str):
@@ -128,32 +131,41 @@
128 execute("curl --fail %s -o %s" % (url, target))131 execute("curl --fail %s -o %s" % (url, target))
129132
130133
131def execute(cmd, process_input=None, addl_env=None, check_exit_code=True):134def execute(cmd, process_input=None, addl_env=None, check_exit_code=True,
132 LOG.debug(_("Running cmd (subprocess): %s"), cmd)135 attempts=1):
133 env = os.environ.copy()136 while attempts > 0:
134 if addl_env:137 attempts -= 1
135 env.update(addl_env)138 try:
136 obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,139 LOG.debug(_("Running cmd (subprocess): %s"), cmd)
137 stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)140 env = os.environ.copy()
138 result = None141 if addl_env:
139 if process_input != None:142 env.update(addl_env)
140 result = obj.communicate(process_input)143 obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
141 else:144 stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
142 result = obj.communicate()145 result = None
143 obj.stdin.close()146 if process_input != None:
144 if obj.returncode:147 result = obj.communicate(process_input)
145 LOG.debug(_("Result was %s") % obj.returncode)148 else:
146 if check_exit_code and obj.returncode != 0:149 result = obj.communicate()
147 (stdout, stderr) = result150 obj.stdin.close()
148 raise ProcessExecutionError(exit_code=obj.returncode,151 if obj.returncode:
149 stdout=stdout,152 LOG.debug(_("Result was %s") % obj.returncode)
150 stderr=stderr,153 if check_exit_code and obj.returncode != 0:
151 cmd=cmd)154 (stdout, stderr) = result
152 # NOTE(termie): this appears to be necessary to let the subprocess call155 raise ProcessExecutionError(exit_code=obj.returncode,
153 # clean something up in between calls, without it two156 stdout=stdout,
154 # execute calls in a row hangs the second one157 stderr=stderr,
155 greenthread.sleep(0)158 cmd=cmd)
156 return result159 # NOTE(termie): this appears to be necessary to let the subprocess
160 # call clean something up in between calls, without
161 # it two execute calls in a row hangs the second one
162 greenthread.sleep(0)
163 return result
164 except ProcessExecutionError:
165 if not attempts:
166 raise
167 else:
168 greenthread.sleep(random.randint(20, 200) / 100.0)
157169
158170
159def ssh_execute(ssh, cmd, process_input=None,171def ssh_execute(ssh, cmd, process_input=None,
@@ -491,6 +503,17 @@
491 return json.loads(s)503 return json.loads(s)
492504
493505
506def synchronized(name):
507 def wrap(f):
508 def inner(*args, **kwargs):
509 lock = lockfile.FileLock(os.path.join(FLAGS.lock_path,
510 'nova-%s.lock' % name))
511 with lock:
512 return f(*args, **kwargs)
513 return inner
514 return wrap
515
516
494def ensure_b64_encoding(val):517def ensure_b64_encoding(val):
495 """Safety method to ensure that values expected to be base64-encoded518 """Safety method to ensure that values expected to be base64-encoded
496 actually are. If they are, the value is returned unchanged. Otherwise,519 actually are. If they are, the value is returned unchanged. Otherwise,
497520
=== modified file 'nova/virt/libvirt_conn.py'
--- nova/virt/libvirt_conn.py 2011-01-27 23:58:22 +0000
+++ nova/virt/libvirt_conn.py 2011-02-28 22:36:03 +0000
@@ -44,8 +44,6 @@
44from xml.dom import minidom44from xml.dom import minidom
4545
4646
47from eventlet import greenthread
48from eventlet import event
49from eventlet import tpool47from eventlet import tpool
5048
51import IPy49import IPy
@@ -56,7 +54,6 @@
56from nova import flags54from nova import flags
57from nova import log as logging55from nova import log as logging
58from nova import utils56from nova import utils
59#from nova.api import context
60from nova.auth import manager57from nova.auth import manager
61from nova.compute import instance_types58from nova.compute import instance_types
62from nova.compute import power_state59from nova.compute import power_state
@@ -1206,10 +1203,14 @@
12061203
1207class IptablesFirewallDriver(FirewallDriver):1204class IptablesFirewallDriver(FirewallDriver):
1208 def __init__(self, execute=None, **kwargs):1205 def __init__(self, execute=None, **kwargs):
1209 self.execute = execute or utils.execute1206 from nova.network import linux_net
1207 self.iptables = linux_net.iptables_manager
1210 self.instances = {}1208 self.instances = {}
1211 self.nwfilter = NWFilterFirewall(kwargs['get_connection'])1209 self.nwfilter = NWFilterFirewall(kwargs['get_connection'])
12121210
1211 self.iptables.ipv4['filter'].add_chain('sg-fallback')
1212 self.iptables.ipv4['filter'].add_rule('sg-fallback', '-j DROP')
1213
1213 def setup_basic_filtering(self, instance):1214 def setup_basic_filtering(self, instance):
1214 """Use NWFilter from libvirt for this."""1215 """Use NWFilter from libvirt for this."""
1215 return self.nwfilter.setup_basic_filtering(instance)1216 return self.nwfilter.setup_basic_filtering(instance)
@@ -1218,127 +1219,98 @@
1218 """No-op. Everything is done in prepare_instance_filter"""1219 """No-op. Everything is done in prepare_instance_filter"""
1219 pass1220 pass
12201221
1221 def remove_instance(self, instance):1222 def unfilter_instance(self, instance):
1222 if instance['id'] in self.instances:1223 if instance['id'] in self.instances:
1223 del self.instances[instance['id']]1224 del self.instances[instance['id']]
1225 self.remove_filters_for_instance(instance)
1226 self.iptables.apply()
1224 else:1227 else:
1225 LOG.info(_('Attempted to unfilter instance %s which is not '1228 LOG.info(_('Attempted to unfilter instance %s which is not '
1226 'filtered'), instance['id'])1229 'filtered'), instance['id'])
12271230
1228 def add_instance(self, instance):1231 def prepare_instance_filter(self, instance):
1229 self.instances[instance['id']] = instance1232 self.instances[instance['id']] = instance
12301233 self.add_filters_for_instance(instance)
1231 def unfilter_instance(self, instance):1234 self.iptables.apply()
1232 self.remove_instance(instance)1235
1233 self.apply_ruleset()1236 def add_filters_for_instance(self, instance):
12341237 chain_name = self._instance_chain_name(instance)
1235 def prepare_instance_filter(self, instance):1238
1236 self.add_instance(instance)1239 self.iptables.ipv4['filter'].add_chain(chain_name)
1237 self.apply_ruleset()1240 ipv4_address = self._ip_for_instance(instance)
12381241 self.iptables.ipv4['filter'].add_rule('local',
1239 def apply_ruleset(self):1242 '-d %s -j $%s' %
1240 current_filter, _ = self.execute('sudo iptables-save -t filter')1243 (ipv4_address, chain_name))
1241 current_lines = current_filter.split('\n')1244
1242 new_filter = self.modify_rules(current_lines, 4)1245 if FLAGS.use_ipv6:
1243 self.execute('sudo iptables-restore',1246 self.iptables.ipv6['filter'].add_chain(chain_name)
1244 process_input='\n'.join(new_filter))1247 ipv6_address = self._ip_for_instance_v6(instance)
1245 if(FLAGS.use_ipv6):1248 self.iptables.ipv6['filter'].add_rule('local',
1246 current_filter, _ = self.execute('sudo ip6tables-save -t filter')1249 '-d %s -j $%s' %
1247 current_lines = current_filter.split('\n')1250 (ipv6_address,
1248 new_filter = self.modify_rules(current_lines, 6)1251 chain_name))
1249 self.execute('sudo ip6tables-restore',1252
1250 process_input='\n'.join(new_filter))1253 ipv4_rules, ipv6_rules = self.instance_rules(instance)
12511254
1252 def modify_rules(self, current_lines, ip_version=4):1255 for rule in ipv4_rules:
1256 self.iptables.ipv4['filter'].add_rule(chain_name, rule)
1257
1258 if FLAGS.use_ipv6:
1259 for rule in ipv6_rules:
1260 self.iptables.ipv6['filter'].add_rule(chain_name, rule)
1261
1262 def remove_filters_for_instance(self, instance):
1263 chain_name = self._instance_chain_name(instance)
1264
1265 self.iptables.ipv4['filter'].remove_chain(chain_name)
1266 if FLAGS.use_ipv6:
1267 self.iptables.ipv6['filter'].remove_chain(chain_name)
1268
1269 def instance_rules(self, instance):
1253 ctxt = context.get_admin_context()1270 ctxt = context.get_admin_context()
1254 # Remove any trace of nova rules.1271
1255 new_filter = filter(lambda l: 'nova-' not in l, current_lines)1272 ipv4_rules = []
12561273 ipv6_rules = []
1257 seen_chains = False1274
1258 for rules_index in range(len(new_filter)):1275 # Always drop invalid packets
1259 if not seen_chains:1276 ipv4_rules += ['-m state --state ' 'INVALID -j DROP']
1260 if new_filter[rules_index].startswith(':'):1277 ipv6_rules += ['-m state --state ' 'INVALID -j DROP']
1261 seen_chains = True1278
1262 elif seen_chains == 1:1279 # Allow established connections
1263 if not new_filter[rules_index].startswith(':'):1280 ipv4_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
1264 break1281 ipv6_rules += ['-m state --state ESTABLISHED,RELATED -j ACCEPT']
12651282
1266 our_chains = [':nova-fallback - [0:0]']1283 dhcp_server = self._dhcp_server_for_instance(instance)
1267 our_rules = ['-A nova-fallback -j DROP']1284 ipv4_rules += ['-s %s -p udp --sport 67 --dport 68 '
12681285 '-j ACCEPT' % (dhcp_server,)]
1269 our_chains += [':nova-local - [0:0]']1286
1270 our_rules += ['-A FORWARD -j nova-local']1287 #Allow project network traffic
1271 our_rules += ['-A OUTPUT -j nova-local']1288 if FLAGS.allow_project_net_traffic:
12721289 cidr = self._project_cidr_for_instance(instance)
1273 security_groups = {}1290 ipv4_rules += ['-s %s -j ACCEPT' % (cidr,)]
1274 # Add our chains1291
1275 # First, we add instance chains and rules1292 # We wrap these in FLAGS.use_ipv6 because they might cause
1276 for instance_id in self.instances:1293 # a DB lookup. The other ones are just list operations, so
1277 instance = self.instances[instance_id]1294 # they're not worth the clutter.
1278 chain_name = self._instance_chain_name(instance)1295 if FLAGS.use_ipv6:
1279 if(ip_version == 4):1296 # Allow RA responses
1280 ip_address = self._ip_for_instance(instance)1297 ra_server = self._ra_server_for_instance(instance)
1281 elif(ip_version == 6):1298 if ra_server:
1282 ip_address = self._ip_for_instance_v6(instance)1299 ipv6_rules += ['-s %s/128 -p icmpv6 -j ACCEPT' % (ra_server,)]
12831300
1284 our_chains += [':%s - [0:0]' % chain_name]1301 #Allow project network traffic
12851302 if FLAGS.allow_project_net_traffic:
1286 # Jump to the per-instance chain1303 cidrv6 = self._project_cidrv6_for_instance(instance)
1287 our_rules += ['-A nova-local -d %s -j %s' % (ip_address,1304 ipv6_rules += ['-s %s -j ACCEPT' % (cidrv6,)]
1288 chain_name)]1305
12891306 security_groups = db.security_group_get_by_instance(ctxt,
1290 # Always drop invalid packets1307 instance['id'])
1291 our_rules += ['-A %s -m state --state '1308
1292 'INVALID -j DROP' % (chain_name,)]1309 # then, security group chains and rules
12931310 for security_group in security_groups:
1294 # Allow established connections1311 rules = db.security_group_rule_get_by_security_group(ctxt,
1295 our_rules += ['-A %s -m state --state '
1296 'ESTABLISHED,RELATED -j ACCEPT' % (chain_name,)]
1297
1298 # Jump to each security group chain in turn
1299 for security_group in \
1300 db.security_group_get_by_instance(ctxt,
1301 instance['id']):
1302 security_groups[security_group['id']] = security_group
1303
1304 sg_chain_name = self._security_group_chain_name(
1305 security_group['id'])1312 security_group['id'])
13061313
1307 our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)]
1308
1309 if(ip_version == 4):
1310 # Allow DHCP responses
1311 dhcp_server = self._dhcp_server_for_instance(instance)
1312 our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68 '
1313 '-j ACCEPT ' % (chain_name, dhcp_server)]
1314 #Allow project network traffic
1315 if (FLAGS.allow_project_net_traffic):
1316 cidr = self._project_cidr_for_instance(instance)
1317 our_rules += ['-A %s -s %s -j ACCEPT' % (chain_name, cidr)]
1318 elif(ip_version == 6):
1319 # Allow RA responses
1320 ra_server = self._ra_server_for_instance(instance)
1321 if ra_server:
1322 our_rules += ['-A %s -s %s -p icmpv6 -j ACCEPT' %
1323 (chain_name, ra_server + "/128")]
1324 #Allow project network traffic
1325 if (FLAGS.allow_project_net_traffic):
1326 cidrv6 = self._project_cidrv6_for_instance(instance)
1327 our_rules += ['-A %s -s %s -j ACCEPT' %
1328 (chain_name, cidrv6)]
1329
1330 # If nothing matches, jump to the fallback chain
1331 our_rules += ['-A %s -j nova-fallback' % (chain_name,)]
1332
1333 # then, security group chains and rules
1334 for security_group_id in security_groups:
1335 chain_name = self._security_group_chain_name(security_group_id)
1336 our_chains += [':%s - [0:0]' % chain_name]
1337
1338 rules = \
1339 db.security_group_rule_get_by_security_group(ctxt,
1340 security_group_id)
1341
1342 for rule in rules:1314 for rule in rules:
1343 logging.info('%r', rule)1315 logging.info('%r', rule)
13441316
@@ -1348,14 +1320,16 @@
1348 continue1320 continue
13491321
1350 version = _get_ip_version(rule.cidr)1322 version = _get_ip_version(rule.cidr)
1351 if version != ip_version:1323 if version == 4:
1352 continue1324 rules = ipv4_rules
1325 else:
1326 rules = ipv6_rules
13531327
1354 protocol = rule.protocol1328 protocol = rule.protocol
1355 if version == 6 and rule.protocol == 'icmp':1329 if version == 6 and rule.protocol == 'icmp':
1356 protocol = 'icmpv6'1330 protocol = 'icmpv6'
13571331
1358 args = ['-A', chain_name, '-p', protocol, '-s', rule.cidr]1332 args = ['-p', protocol, '-s', rule.cidr]
13591333
1360 if rule.protocol in ['udp', 'tcp']:1334 if rule.protocol in ['udp', 'tcp']:
1361 if rule.from_port == rule.to_port:1335 if rule.from_port == rule.to_port:
@@ -1376,32 +1350,35 @@
1376 icmp_type_arg += '/%s' % icmp_code1350 icmp_type_arg += '/%s' % icmp_code
13771351
1378 if icmp_type_arg:1352 if icmp_type_arg:
1379 if(ip_version == 4):1353 if version == 4:
1380 args += ['-m', 'icmp', '--icmp-type',1354 args += ['-m', 'icmp', '--icmp-type',
1381 icmp_type_arg]1355 icmp_type_arg]
1382 elif(ip_version == 6):1356 elif version == 6:
1383 args += ['-m', 'icmp6', '--icmpv6-type',1357 args += ['-m', 'icmp6', '--icmpv6-type',
1384 icmp_type_arg]1358 icmp_type_arg]
13851359
1386 args += ['-j ACCEPT']1360 args += ['-j ACCEPT']
1387 our_rules += [' '.join(args)]1361 rules += [' '.join(args)]
13881362
1389 new_filter[rules_index:rules_index] = our_rules1363 ipv4_rules += ['-j $sg-fallback']
1390 new_filter[rules_index:rules_index] = our_chains1364 ipv6_rules += ['-j $sg-fallback']
1391 logging.info('new_filter: %s', '\n'.join(new_filter))1365
1392 return new_filter1366 return ipv4_rules, ipv6_rules
13931367
1394 def refresh_security_group_members(self, security_group):1368 def refresh_security_group_members(self, security_group):
1395 pass1369 pass
13961370
1397 def refresh_security_group_rules(self, security_group):1371 def refresh_security_group_rules(self, security_group):
1398 self.apply_ruleset()1372 for instance in self.instances.values():
1373 self.remove_filters_for_instance(instance)
1374 self.add_filters_for_instance(instance)
1375 self.iptables.apply()
13991376
1400 def _security_group_chain_name(self, security_group_id):1377 def _security_group_chain_name(self, security_group_id):
1401 return 'nova-sg-%s' % (security_group_id,)1378 return 'nova-sg-%s' % (security_group_id,)
14021379
1403 def _instance_chain_name(self, instance):1380 def _instance_chain_name(self, instance):
1404 return 'nova-inst-%s' % (instance['id'],)1381 return 'inst-%s' % (instance['id'],)
14051382
1406 def _ip_for_instance(self, instance):1383 def _ip_for_instance(self, instance):
1407 return db.instance_get_fixed_address(context.get_admin_context(),1384 return db.instance_get_fixed_address(context.get_admin_context(),