Merge lp:~ntt-pf-lab/nova/ipv6-support into lp:~hudson-openstack/nova/trunk

Proposed by Nachi Ueno
Status: Superseded
Proposed branch: lp:~ntt-pf-lab/nova/ipv6-support
Merge into: lp:~hudson-openstack/nova/trunk
Diff against target: 1536 lines (+808/-53)
22 files modified
bin/nova-manage (+7/-3)
contrib/boto_v6/__init__.py (+37/-0)
contrib/boto_v6/ec2/connection.py (+41/-0)
contrib/boto_v6/ec2/instance.py (+37/-0)
contrib/nova.sh (+6/-0)
nova/api/ec2/cloud.py (+13/-0)
nova/db/api.py (+12/-0)
nova/db/sqlalchemy/api.py (+27/-0)
nova/db/sqlalchemy/models.py (+4/-0)
nova/network/linux_net.py (+88/-0)
nova/network/manager.py (+29/-3)
nova/test.py (+2/-1)
nova/tests/test_api.py (+67/-0)
nova/tests/test_network.py (+21/-0)
nova/utils.py (+49/-0)
nova/virt/libvirt.xml.template (+1/-0)
nova/virt/libvirt_conn.py (+137/-34)
smoketests/admin_smoketests.py (+1/-2)
smoketests/base.py (+15/-2)
smoketests/flags.py (+2/-1)
smoketests/public_network_smoketests.py (+180/-0)
smoketests/user_smoketests.py (+32/-7)
To merge this branch: bzr merge lp:~ntt-pf-lab/nova/ipv6-support
Reviewer Review Type Date Requested Status
Devin Carlen (community) Needs Information
Soren Hansen (community) Needs Fixing
Review via email: mp+45228@code.launchpad.net

This proposal has been superseded by a proposal from 2011-01-12.

Description of the change

OpenStack Compute (Nova) IPv4/IPv6 dual stack support
http://wiki.openstack.org/BexarIpv6supportReadme

Tested with
 unit test
 smoke test

No conflict with current branch r 515.

To post a comment you must log in.
lp:~ntt-pf-lab/nova/ipv6-support updated
474. By Nachi Ueno

Fixed:Create instance fails when use_ipv6=False

475. By Nachi Ueno

Fixed for pep8

476. By Nachi Ueno

missing _()

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

Please find my comments inline.

Please note that ipv6 is still a bit of a mystery to me, so I'll review
structure and style moreso than technical correctness.

2011/1/5 Nachi Ueno <email address hidden>:
> Nachi Ueno has proposed merging lp:~ntt-pf-lab/nova/ipv6-support into lp:nova.
>
> Requested reviews:
>  Nova Core (nova-core)
>
> For more details, see:
> https://code.launchpad.net/~ntt-pf-lab/nova/ipv6-support/+merge/45228
>
> OpenStack Compute (Nova) IPv4/IPv6 dual stack support
> http://wiki.openstack.org/BexarIpv6supportReadme
>
> Tested with
>  unit test
>  smoke test
>
> No conflict with current branch r 515.
> --
> https://code.launchpad.net/~ntt-pf-lab/nova/ipv6-support/+merge/45228
> You are subscribed to branch lp:nova.
>
> === modified file 'bin/nova-manage'
> --- bin/nova-manage     2010-12-28 20:11:41 +0000
> +++ bin/nova-manage     2011-01-05 11:56:16 +0000
> @@ -89,7 +89,7 @@
>  flags.DECLARE('network_size', 'nova.network.manager')
>  flags.DECLARE('vlan_start', 'nova.network.manager')
>  flags.DECLARE('vpn_start', 'nova.network.manager')
> -
> +flags.DECLARE('fixed_range_v6', 'nova.network.manager')
>
>  class VpnCommands(object):
>     """Class for managing VPNs."""
> @@ -432,11 +432,11 @@
>     """Class for managing networks."""
>
>     def create(self, fixed_range=None, num_networks=None,
> -               network_size=None, vlan_start=None, vpn_start=None):
> +               network_size=None, vlan_start=None, vpn_start=None,fixed_range_v6=None):

This line is too long. Nova is pep8 clean. You can install the pep8
utility and check for yourself.

>         """Creates fixed ips for host by range
>         arguments: [fixed_range=FLAG], [num_networks=FLAG],
>                    [network_size=FLAG], [vlan_start=FLAG],
> -                   [vpn_start=FLAG]"""
> +                   [vpn_start=FLAG],[fixed_range_v6=FLAG]"""

Missing a space (for consistency).

>         if not fixed_range:
>             fixed_range = FLAGS.fixed_range
>         if not num_networks:
> @@ -447,11 +447,15 @@
>             vlan_start = FLAGS.vlan_start
>         if not vpn_start:
>             vpn_start = FLAGS.vpn_start
> +        if not fixed_range_v6:
> +            fixed_range_v6 = FLAGS.fixed_range_v6
>         net_manager = utils.import_object(FLAGS.network_manager)
>         net_manager.create_networks(context.get_admin_context(),
>                                     fixed_range, int(num_networks),
>                                     int(network_size), int(vlan_start),
> -                                    int(vpn_start))
> +                                    int(vpn_start),fixed_range_v6)
> +
> +

Same here. Please add a space (pep8 will tell you this, too).

> === modified file 'nova/api/ec2/cloud.py'
> --- nova/api/ec2/cloud.py       2011-01-03 19:29:39 +0000
> +++ nova/api/ec2/cloud.py       2011-01-05 11:56:16 +0000
> @@ -30,7 +30,7 @@
>
>  from nova import context
>  import IPy
> -
> +import urllib
>  from nova import crypto
>  from nova import db
>  from nova import exception
> @@ -347,6 +347,7 @@
>             values['group_id'] = source_security_group['id']
>         elif cidr_ip:
>             # If this fails, it throws an...

Read more...

Revision history for this message
Soren Hansen (soren) :
review: Needs Fixing
Revision history for this message
Devin Carlen (devcamcar) wrote :

It looks like we are essentially providing our own IPV6 version of boto with this patch, which is a bit scary. If we aren't able to use the official libraries (boto, etc.) for IPV6, then maybe we shouldn't be trying to do this with the EC2 API? Forgive me if this has already been discussed by the community, but I think it's a fair question.

review: Needs Information
Revision history for this message
Nachi Ueno (nati-ueno) wrote :

>Devin Carlen
Thank you for your comment.
We extended DescribeInstance. However we will revert DescribeInstance,and We will add DescribeInstanceV6 today.
Users,who don't need to see v6 address in a DescribeInstance response,can use standard boto libraries with no side effect in current version.
(Users can use EC2 API and DescribeInstanceV6 API)

We continue to support IPv6 version of boto.
As you can see, we created boto_v6 by extending boto classes.
So the code is really simple :).

lp:~ntt-pf-lab/nova/ipv6-support updated
477. By Nachi Ueno

changed exception class

478. By Hisaharu Ishii

Fixed for pep8
Remove temporary debugging

479. By Nachi Ueno

Add DescribeInstanceV6 for backward compatibility

480. By Nachi Ueno

Fixed bug

481. By Nachi Ueno

Merged with r548

482. By Hisaharu Ishii

Change command to get link local address
Remove superfluous code

483. By Nachi Ueno

Merged with 549

484. By Nachi Ueno

Fixed syntax errors

485. By Hisaharu Ishii

Support IPv6 firewall with IptablesFirewallDriver

486. By Hisaharu Ishii

Merged with r551

487. By Nachi Ueno

fixed method signature of modify_rules
fixed unit_test for ipv6

488. By Nachi Ueno

merged with r555

489. By Koji Iida

Fixed missing _().
Fixed to follow logging to LOG changes.
Fixed merge miss (get_fixed_ip was moved away).
Update some missing comments.

490. By Nachi Ueno

Merged with 557

491. By Hisaharu Ishii

Fixed Authors

492. By Nachi Ueno

Added netaddr for pip-requires

493. By Nachi Ueno

Moved commands which needs sudo to nova.sh

494. By Nachi Ueno

Merged with r561

495. By Hisaharu Ishii

Merged with r562

496. By Hisaharu Ishii

sort Authors

497. By Koji Iida

Merged to rev.563

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/nova-manage'
2--- bin/nova-manage 2011-01-10 15:13:30 +0000
3+++ bin/nova-manage 2011-01-12 13:09:31 +0000
4@@ -89,6 +89,7 @@
5 flags.DECLARE('network_size', 'nova.network.manager')
6 flags.DECLARE('vlan_start', 'nova.network.manager')
7 flags.DECLARE('vpn_start', 'nova.network.manager')
8+flags.DECLARE('fixed_range_v6', 'nova.network.manager')
9
10
11 class VpnCommands(object):
12@@ -437,11 +438,12 @@
13 """Class for managing networks."""
14
15 def create(self, fixed_range=None, num_networks=None,
16- network_size=None, vlan_start=None, vpn_start=None):
17+ network_size=None, vlan_start=None, vpn_start=None,
18+ fixed_range_v6=None):
19 """Creates fixed ips for host by range
20 arguments: [fixed_range=FLAG], [num_networks=FLAG],
21 [network_size=FLAG], [vlan_start=FLAG],
22- [vpn_start=FLAG]"""
23+ [vpn_start=FLAG], [fixed_range_v6=FLAG]"""
24 if not fixed_range:
25 fixed_range = FLAGS.fixed_range
26 if not num_networks:
27@@ -452,11 +454,13 @@
28 vlan_start = FLAGS.vlan_start
29 if not vpn_start:
30 vpn_start = FLAGS.vpn_start
31+ if not fixed_range_v6:
32+ fixed_range_v6 = FLAGS.fixed_range_v6
33 net_manager = utils.import_object(FLAGS.network_manager)
34 net_manager.create_networks(context.get_admin_context(),
35 fixed_range, int(num_networks),
36 int(network_size), int(vlan_start),
37- int(vpn_start))
38+ int(vpn_start), fixed_range_v6)
39
40
41 class ServiceCommands(object):
42
43=== added directory 'contrib/boto_v6'
44=== added file 'contrib/boto_v6/__init__.py'
45--- contrib/boto_v6/__init__.py 1970-01-01 00:00:00 +0000
46+++ contrib/boto_v6/__init__.py 2011-01-12 13:09:31 +0000
47@@ -0,0 +1,37 @@
48+# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
49+# Copyright (c) 2010, Eucalyptus Systems, Inc.
50+# All rights reserved.
51+#
52+# Permission is hereby granted, free of charge, to any person obtaining a
53+# copy of this software and associated documentation files (the
54+# "Software"), to deal in the Software without restriction, including
55+# without limitation the rights to use, copy, modify, merge, publish, dis-
56+# tribute, sublicense, and/or sell copies of the Software, and to permit
57+# persons to whom the Software is furnished to do so, subject to the fol-
58+# lowing conditions:
59+#
60+# The above copyright notice and this permission notice shall be included
61+# in all copies or substantial portions of the Software.
62+#
63+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
64+# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
65+# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
66+# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
67+# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
68+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
69+# IN THE SOFTWARE.
70+
71+
72+def connect_ec2(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
73+ """
74+ :type aws_access_key_id: string
75+ :param aws_access_key_id: Your AWS Access Key ID
76+
77+ :type aws_secret_access_key: string
78+ :param aws_secret_access_key: Your AWS Secret Access Key
79+
80+ :rtype: :class:`boto.ec2.connection.EC2Connection`
81+ :return: A connection to Amazon's EC2
82+ """
83+ from boto_v6.ec2.connection import EC2ConnectionV6
84+ return EC2ConnectionV6(aws_access_key_id, aws_secret_access_key, **kwargs)
85
86=== added directory 'contrib/boto_v6/ec2'
87=== added file 'contrib/boto_v6/ec2/__init__.py'
88=== added file 'contrib/boto_v6/ec2/connection.py'
89--- contrib/boto_v6/ec2/connection.py 1970-01-01 00:00:00 +0000
90+++ contrib/boto_v6/ec2/connection.py 2011-01-12 13:09:31 +0000
91@@ -0,0 +1,41 @@
92+'''
93+Created on 2010/12/20
94+
95+@author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
96+'''
97+import boto
98+import boto.ec2
99+from boto_v6.ec2.instance import ReservationV6
100+
101+
102+class EC2ConnectionV6(boto.ec2.EC2Connection):
103+ '''
104+ EC2Connection for OpenStack IPV6 mode
105+ '''
106+ def get_all_instances(self, instance_ids=None, filters=None):
107+ """
108+ Retrieve all the instances associated with your account.
109+
110+ :type instance_ids: list
111+ :param instance_ids: A list of strings of instance IDs
112+
113+ :type filters: dict
114+ :param filters: Optional filters that can be used to limit
115+ the results returned. Filters are provided
116+ in the form of a dictionary consisting of
117+ filter names as the key and filter values
118+ as the value. The set of allowable filter
119+ names/values is dependent on the request
120+ being performed. Check the EC2 API guide
121+ for details.
122+
123+ :rtype: list
124+ :return: A list of :class:`boto.ec2.instance.Reservation`
125+ """
126+ params = {}
127+ if instance_ids:
128+ self.build_list_params(params, instance_ids, 'InstanceId')
129+ if filters:
130+ self.build_filter_params(params, filters)
131+ return self.get_list('DescribeInstancesV6', params,
132+ [('item', ReservationV6)])
133
134=== added file 'contrib/boto_v6/ec2/instance.py'
135--- contrib/boto_v6/ec2/instance.py 1970-01-01 00:00:00 +0000
136+++ contrib/boto_v6/ec2/instance.py 2011-01-12 13:09:31 +0000
137@@ -0,0 +1,37 @@
138+'''
139+Created on 2010/12/20
140+
141+@author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
142+'''
143+import boto
144+from boto.resultset import ResultSet
145+from boto.ec2.instance import Reservation
146+from boto.ec2.instance import Group
147+from boto.ec2.instance import Instance
148+
149+
150+class ReservationV6(Reservation):
151+ def startElement(self, name, attrs, connection):
152+ if name == 'instancesSet':
153+ self.instances = ResultSet([('item', InstanceV6)])
154+ return self.instances
155+ elif name == 'groupSet':
156+ self.groups = ResultSet([('item', Group)])
157+ return self.groups
158+ else:
159+ return None
160+
161+
162+class InstanceV6(Instance):
163+ def __init__(self, connection=None):
164+ Instance.__init__(self, connection)
165+ self.dns_name_v6 = None
166+
167+ def endElement(self, name, value, connection):
168+ Instance.endElement(self, name, value, connection)
169+ if name == 'dnsNameV6':
170+ self.dns_name_v6 = value
171+
172+ def _update(self, updated):
173+ self.__dict__.update(updated.__dict__)
174+ self.dns_name_v6 = updated.dns_name_v6
175
176=== modified file 'contrib/nova.sh'
177--- contrib/nova.sh 2010-12-30 00:07:41 +0000
178+++ contrib/nova.sh 2011-01-12 13:09:31 +0000
179@@ -86,6 +86,10 @@
180 sudo apt-get install -y python-twisted python-sqlalchemy python-mox python-greenlet python-carrot
181 sudo apt-get install -y python-daemon python-eventlet python-gflags python-tornado python-ipy
182 sudo apt-get install -y python-libvirt python-libxml2 python-routes
183+#For IPV6
184+ sudo apt-get install -y python-netaddr
185+ sudo apt-get install -y radvd
186+
187 if [ "$USE_MYSQL" == 1 ]; then
188 cat <<MYSQL_PRESEED | debconf-set-selections
189 mysql-server-5.1 mysql-server/root_password password $MYSQL_PASS
190@@ -107,6 +111,8 @@
191
192 if [ "$CMD" == "run" ]; then
193 killall dnsmasq
194+ #For IPv6
195+ killall radvd
196 screen -d -m -S nova -t nova
197 sleep 1
198 if [ "$USE_MYSQL" == 1 ]; then
199
200=== modified file 'nova/api/ec2/cloud.py'
201--- nova/api/ec2/cloud.py 2011-01-12 11:34:16 +0000
202+++ nova/api/ec2/cloud.py 2011-01-12 13:09:31 +0000
203@@ -26,9 +26,11 @@
204 import datetime
205 import IPy
206 import os
207+import urllib
208
209 from nova import compute
210 from nova import context
211+
212 from nova import crypto
213 from nova import db
214 from nova import exception
215@@ -378,6 +380,7 @@
216 values['group_id'] = source_security_group['id']
217 elif cidr_ip:
218 # If this fails, it throws an exception. This is what we want.
219+ cidr_ip = urllib.unquote(cidr_ip).decode()
220 IPy.IP(cidr_ip)
221 values['cidr'] = cidr_ip
222 else:
223@@ -639,6 +642,10 @@
224 def describe_instances(self, context, **kwargs):
225 return self._format_describe_instances(context, **kwargs)
226
227+ def describe_instances_v6(self, context, **kwargs):
228+ kwargs['use_v6'] = True
229+ return self._format_describe_instances(context, **kwargs)
230+
231 def _format_describe_instances(self, context, **kwargs):
232 return {'reservationSet': self._format_instances(context, **kwargs)}
233
234@@ -674,10 +681,16 @@
235 if instance['fixed_ip']['floating_ips']:
236 fixed = instance['fixed_ip']
237 floating_addr = fixed['floating_ips'][0]['address']
238+ if instance['fixed_ip']['network'] and 'use_v6' in kwargs:
239+ i['dnsNameV6'] = utils.to_global_ipv6(
240+ instance['fixed_ip']['network']['cidr_v6'],
241+ instance['mac_address'])
242+
243 i['privateDnsName'] = fixed_addr
244 i['publicDnsName'] = floating_addr
245 i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
246 i['keyName'] = instance['key_name']
247+
248 if context.user.is_admin():
249 i['keyName'] = '%s (%s, %s)' % (i['keyName'],
250 instance['project_id'],
251
252=== modified file 'nova/db/api.py'
253--- nova/db/api.py 2011-01-12 11:34:16 +0000
254+++ nova/db/api.py 2011-01-12 13:09:31 +0000
255@@ -295,6 +295,10 @@
256 return IMPL.fixed_ip_get_instance(context, address)
257
258
259+def fixed_ip_get_instance_v6(context, address):
260+ return IMPL.fixed_ip_get_instance_v6(context, address)
261+
262+
263 def fixed_ip_get_network(context, address):
264 """Get a network for a fixed ip by address."""
265 return IMPL.fixed_ip_get_network(context, address)
266@@ -353,6 +357,10 @@
267 return IMPL.instance_get_fixed_address(context, instance_id)
268
269
270+def instance_get_fixed_address_v6(context, instance_id):
271+ return IMPL.instance_get_fixed_address_v6(context, instance_id)
272+
273+
274 def instance_get_floating_address(context, instance_id):
275 """Get the first floating ip address of an instance."""
276 return IMPL.instance_get_floating_address(context, instance_id)
277@@ -548,6 +556,10 @@
278 return IMPL.project_get_network(context, project_id)
279
280
281+def project_get_network_v6(context, project_id):
282+ return IMPL.project_get_network_v6(context, project_id)
283+
284+
285 ###################
286
287
288
289=== modified file 'nova/db/sqlalchemy/api.py'
290--- nova/db/sqlalchemy/api.py 2011-01-12 11:34:16 +0000
291+++ nova/db/sqlalchemy/api.py 2011-01-12 13:09:31 +0000
292@@ -606,6 +606,17 @@
293 return fixed_ip_ref.instance
294
295
296+@require_context
297+def fixed_ip_get_instance_v6(context, address):
298+ session = get_session()
299+ mac = utils.to_mac(address)
300+
301+ result = session.query(models.Instance
302+ ).filter_by(mac_address=mac
303+ ).first()
304+ return result
305+
306+
307 @require_admin_context
308 def fixed_ip_get_network(context, address):
309 fixed_ip_ref = fixed_ip_get_by_address(context, address)
310@@ -792,6 +803,17 @@
311
312
313 @require_context
314+def instance_get_fixed_address_v6(context, instance_id):
315+ session = get_session()
316+ with session.begin():
317+ instance_ref = instance_get(context, instance_id, session=session)
318+ network_ref = network_get_by_instance(context, instance_id)
319+ prefix = network_ref.cidr_v6
320+ mac = instance_ref.mac_address
321+ return utils.to_global_ipv6(prefix, mac)
322+
323+
324+@require_context
325 def instance_get_floating_address(context, instance_id):
326 session = get_session()
327 with session.begin():
328@@ -1128,6 +1150,11 @@
329 return result
330
331
332+@require_context
333+def project_get_network_v6(context, project_id):
334+ return project_get_network(context, project_id)
335+
336+
337 ###################
338
339
340
341=== modified file 'nova/db/sqlalchemy/models.py'
342--- nova/db/sqlalchemy/models.py 2011-01-12 11:34:16 +0000
343+++ nova/db/sqlalchemy/models.py 2011-01-12 13:09:31 +0000
344@@ -411,6 +411,10 @@
345
346 injected = Column(Boolean, default=False)
347 cidr = Column(String(255), unique=True)
348+ cidr_v6 = Column(String(255), unique=True)
349+
350+ ra_server = Column(String(255))
351+
352 netmask = Column(String(255))
353 bridge = Column(String(255))
354 gateway = Column(String(255))
355
356=== modified file 'nova/network/linux_net.py'
357--- nova/network/linux_net.py 2011-01-11 05:49:46 +0000
358+++ nova/network/linux_net.py 2011-01-12 13:09:31 +0000
359@@ -50,6 +50,7 @@
360 'Public IP of network host')
361 flags.DEFINE_bool('use_nova_chains', False,
362 'use the nova_ routing chains instead of default')
363+
364 flags.DEFINE_string('dns_server', None,
365 'if set, uses specific dns server for dnsmasq')
366 flags.DEFINE_string('dmz_cidr', '10.128.0.0/24',
367@@ -123,6 +124,11 @@
368 (FLAGS.fixed_range, FLAGS.dmz_cidr))
369 _confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" %
370 {'range': FLAGS.fixed_range})
371+ if(FLAGS.use_ipv6):
372+ _execute('sudo bash -c ' +
373+ '"echo 1 > /proc/sys/net/ipv6/conf/all/forwarding"')
374+ _execute('sudo bash -c ' +
375+ '"echo 0 > /proc/sys/net/ipv6/conf/all/accept_ra"')
376
377
378 def bind_floating_ip(floating_ip, check_exit_code=True):
379@@ -196,6 +202,10 @@
380 net_attrs['gateway'],
381 net_attrs['broadcast'],
382 net_attrs['netmask']))
383+ if(FLAGS.use_ipv6):
384+ _execute("sudo ifconfig %s add %s up" % \
385+ (bridge,
386+ net_attrs['cidr_v6']))
387 else:
388 _execute("sudo ifconfig %s up" % bridge)
389 if FLAGS.use_nova_chains:
390@@ -262,6 +272,50 @@
391 _execute(command, addl_env=env)
392
393
394+def update_ra(context, network_id):
395+ network_ref = db.network_get(context, network_id)
396+
397+ conffile = _ra_file(network_ref['bridge'], 'conf')
398+ with open(conffile, 'w') as f:
399+ conf_str = """
400+interface %s
401+{
402+ AdvSendAdvert on;
403+ MinRtrAdvInterval 3;
404+ MaxRtrAdvInterval 10;
405+ prefix %s
406+ {
407+ AdvOnLink on;
408+ AdvAutonomous on;
409+ };
410+};
411+""" % (network_ref['bridge'], network_ref['cidr_v6'])
412+ f.write(conf_str)
413+
414+ # Make sure dnsmasq can actually read it (it setuid()s to "nobody")
415+ os.chmod(conffile, 0644)
416+
417+ pid = _ra_pid_for(network_ref['bridge'])
418+
419+ # if dnsmasq is already running, then tell it to reload
420+ if pid:
421+ out, _err = _execute('cat /proc/%d/cmdline'
422+ % pid, check_exit_code=False)
423+ if conffile in out:
424+ try:
425+ _execute('sudo kill -HUP %d' % pid)
426+ return
427+ except Exception as exc: # pylint: disable-msg=W0703
428+ logging.debug("Hupping radvd threw %s", exc)
429+ else:
430+ logging.debug("Pid %d is stale, relaunching radvd", pid)
431+ command = _ra_cmd(network_ref)
432+ _execute(command)
433+ db.network_update(context, network_id,
434+ {"ra_server":
435+ utils.get_my_linklocal(network_ref['bridge'])})
436+
437+
438 def _host_dhcp(fixed_ip_ref):
439 """Return a host string for an address"""
440 instance_ref = fixed_ip_ref['instance']
441@@ -323,6 +377,15 @@
442 return ''.join(cmd)
443
444
445+def _ra_cmd(net):
446+ """Builds dnsmasq command"""
447+ cmd = ['sudo -E radvd',
448+# ' -u nobody',
449+ ' -C %s' % _ra_file(net['bridge'], 'conf'),
450+ ' -p %s' % _ra_file(net['bridge'], 'pid')]
451+ return ''.join(cmd)
452+
453+
454 def _stop_dnsmasq(network):
455 """Stops the dnsmasq instance for a given network"""
456 pid = _dnsmasq_pid_for(network)
457@@ -344,6 +407,16 @@
458 kind))
459
460
461+def _ra_file(bridge, kind):
462+ """Return path to a pid, leases or conf file for a bridge"""
463+
464+ if not os.path.exists(FLAGS.networks_path):
465+ os.makedirs(FLAGS.networks_path)
466+ return os.path.abspath("%s/nova-ra-%s.%s" % (FLAGS.networks_path,
467+ bridge,
468+ kind))
469+
470+
471 def _dnsmasq_pid_for(bridge):
472 """Returns the pid for prior dnsmasq instance for a bridge
473
474@@ -357,3 +430,18 @@
475 if os.path.exists(pid_file):
476 with open(pid_file, 'r') as f:
477 return int(f.read())
478+
479+
480+def _ra_pid_for(bridge):
481+ """Returns the pid for prior dnsmasq instance for a bridge
482+
483+ Returns None if no pid file exists
484+
485+ If machine has rebooted pid might be incorrect (caller should check)
486+ """
487+
488+ pid_file = _ra_file(bridge, 'pid')
489+
490+ if os.path.exists(pid_file):
491+ with open(pid_file, 'r') as f:
492+ return int(f.read())
493
494=== modified file 'nova/network/manager.py'
495--- nova/network/manager.py 2011-01-10 02:08:54 +0000
496+++ nova/network/manager.py 2011-01-12 13:09:31 +0000
497@@ -82,6 +82,7 @@
498 flags.DEFINE_string('floating_range', '4.4.4.0/24',
499 'Floating IP address block')
500 flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block')
501+flags.DEFINE_string('fixed_range_v6', 'fd00::/48', 'Fixed IPv6 address block')
502 flags.DEFINE_integer('cnt_vpn_clients', 5,
503 'Number of addresses reserved for vpn clients')
504 flags.DEFINE_string('network_driver', 'nova.network.linux_net',
505@@ -90,6 +91,9 @@
506 'Whether to update dhcp when fixed_ip is disassociated')
507 flags.DEFINE_integer('fixed_ip_disassociate_timeout', 600,
508 'Seconds after which a deallocated ip is disassociated')
509+
510+flags.DEFINE_bool('use_ipv6', True,
511+ 'use the ipv6')
512 flags.DEFINE_string('network_host', socket.gethostname(),
513 'Network host to use for ip allocation in flat modes')
514 flags.DEFINE_bool('fake_call', False,
515@@ -235,7 +239,7 @@
516 """Get the network host for the current context."""
517 raise NotImplementedError()
518
519- def create_networks(self, context, num_networks, network_size,
520+ def create_networks(self, context, num_networks, network_size, cidr_v6,
521 *args, **kwargs):
522 """Create networks based on parameters."""
523 raise NotImplementedError()
524@@ -321,9 +325,11 @@
525 pass
526
527 def create_networks(self, context, cidr, num_networks, network_size,
528- *args, **kwargs):
529+ cidr_v6, *args, **kwargs):
530 """Create networks based on parameters."""
531 fixed_net = IPy.IP(cidr)
532+ fixed_net_v6 = IPy.IP(cidr_v6)
533+ significant_bits_v6 = 64
534 for index in range(num_networks):
535 start = index * network_size
536 significant_bits = 32 - int(math.log(network_size, 2))
537@@ -336,7 +342,13 @@
538 net['gateway'] = str(project_net[1])
539 net['broadcast'] = str(project_net.broadcast())
540 net['dhcp_start'] = str(project_net[2])
541+
542+ if(FLAGS.use_ipv6):
543+ cidr_v6 = "%s/%s" % (fixed_net_v6[0], significant_bits_v6)
544+ net['cidr_v6'] = cidr_v6
545+
546 network_ref = self.db.network_create_safe(context, net)
547+
548 if network_ref:
549 self._create_fixed_ips(context, network_ref['id'])
550
551@@ -482,12 +494,16 @@
552 network_ref['bridge'])
553
554 def create_networks(self, context, cidr, num_networks, network_size,
555- vlan_start, vpn_start):
556+ vlan_start, vpn_start, cidr_v6):
557 """Create networks based on parameters."""
558 fixed_net = IPy.IP(cidr)
559+ fixed_net_v6 = IPy.IP(cidr_v6)
560+ network_size_v6 = 1 << 64
561+ significant_bits_v6 = 64
562 for index in range(num_networks):
563 vlan = vlan_start + index
564 start = index * network_size
565+ start_v6 = index * network_size_v6
566 significant_bits = 32 - int(math.log(network_size, 2))
567 cidr = "%s/%s" % (fixed_net[start], significant_bits)
568 project_net = IPy.IP(cidr)
569@@ -500,6 +516,13 @@
570 net['dhcp_start'] = str(project_net[3])
571 net['vlan'] = vlan
572 net['bridge'] = 'br%s' % vlan
573+ if(FLAGS.use_ipv6):
574+ cidr_v6 = "%s/%s" % (
575+ fixed_net_v6[start_v6],
576+ significant_bits_v6
577+ )
578+ net['cidr_v6'] = cidr_v6
579+
580 # NOTE(vish): This makes ports unique accross the cloud, a more
581 # robust solution would be to make them unique per ip
582 net['vpn_public_port'] = vpn_start + index
583@@ -538,6 +561,7 @@
584 self.driver.ensure_vlan_bridge(network_ref['vlan'],
585 network_ref['bridge'],
586 network_ref)
587+
588 # NOTE(vish): only ensure this forward if the address hasn't been set
589 # manually.
590 if address == FLAGS.vpn_ip:
591@@ -546,6 +570,8 @@
592 network_ref['vpn_private_address'])
593 if not FLAGS.fake_network:
594 self.driver.update_dhcp(context, network_id)
595+ if(FLAGS.use_ipv6):
596+ self.driver.update_ra(context, network_id)
597
598 @property
599 def _bottom_reserved_ips(self):
600
601=== modified file 'nova/test.py'
602--- nova/test.py 2010-12-17 01:05:54 +0000
603+++ nova/test.py 2011-01-12 13:09:31 +0000
604@@ -156,7 +156,8 @@
605 FLAGS.fixed_range,
606 5, 16,
607 FLAGS.vlan_start,
608- FLAGS.vpn_start)
609+ FLAGS.vpn_start,
610+ FLAGS.fixed_range_v6)
611
612 # emulate some of the mox stuff, we can't use the metaclass
613 # because it screws with our generators
614
615=== modified file 'nova/tests/test_api.py'
616--- nova/tests/test_api.py 2010-12-17 01:05:54 +0000
617+++ nova/tests/test_api.py 2011-01-12 13:09:31 +0000
618@@ -24,6 +24,7 @@
619 import random
620 import StringIO
621 import webob
622+import logging
623
624 from nova import context
625 from nova import flags
626@@ -265,6 +266,72 @@
627
628 return
629
630+ def test_authorize_revoke_security_group_cidr_v6(self):
631+ """
632+ Test that we can add and remove CIDR based rules
633+ to a security group for IPv6
634+ """
635+ self.expect_http()
636+ self.mox.ReplayAll()
637+ user = self.manager.create_user('fake', 'fake', 'fake')
638+ project = self.manager.create_project('fake', 'fake', 'fake')
639+
640+ # At the moment, you need both of these to actually be netadmin
641+ self.manager.add_role('fake', 'netadmin')
642+ project.add_role('fake', 'netadmin')
643+
644+ security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd")
645+ for x in range(random.randint(4, 8)))
646+
647+ group = self.ec2.create_security_group(security_group_name,
648+ 'test group')
649+
650+ self.expect_http()
651+ self.mox.ReplayAll()
652+ group.connection = self.ec2
653+
654+ group.authorize('tcp', 80, 81, '::/0')
655+
656+ self.expect_http()
657+ self.mox.ReplayAll()
658+
659+ rv = self.ec2.get_all_security_groups()
660+ # I don't bother checkng that we actually find it here,
661+ # because the create/delete unit test further up should
662+ # be good enough for that.
663+ for group in rv:
664+ if group.name == security_group_name:
665+ self.assertEquals(len(group.rules), 1)
666+ self.assertEquals(int(group.rules[0].from_port), 80)
667+ self.assertEquals(int(group.rules[0].to_port), 81)
668+ self.assertEquals(len(group.rules[0].grants), 1)
669+ self.assertEquals(str(group.rules[0].grants[0]), '::/0')
670+
671+ self.expect_http()
672+ self.mox.ReplayAll()
673+ group.connection = self.ec2
674+
675+ group.revoke('tcp', 80, 81, '::/0')
676+
677+ self.expect_http()
678+ self.mox.ReplayAll()
679+
680+ self.ec2.delete_security_group(security_group_name)
681+
682+ self.expect_http()
683+ self.mox.ReplayAll()
684+ group.connection = self.ec2
685+
686+ rv = self.ec2.get_all_security_groups()
687+
688+ self.assertEqual(len(rv), 1)
689+ self.assertEqual(rv[0].name, 'default')
690+
691+ self.manager.delete_project(project)
692+ self.manager.delete_user(user)
693+
694+ return
695+
696 def test_authorize_revoke_security_group_foreign_group(self):
697 """
698 Test that we can grant and revoke another security group access
699
700=== modified file 'nova/tests/test_network.py'
701--- nova/tests/test_network.py 2011-01-04 05:23:35 +0000
702+++ nova/tests/test_network.py 2011-01-12 13:09:31 +0000
703@@ -96,6 +96,27 @@
704 self.context.project_id = self.projects[project_num].id
705 self.network.deallocate_fixed_ip(self.context, address)
706
707+ def test_private_ipv6(self):
708+ """Make sure ipv6 is OK"""
709+ if FLAGS.use_ipv6:
710+ instance_ref = self._create_instance(1)
711+ network_ref = db.project_get_network(
712+ self.context,
713+ self.context.project_id)
714+ address_v6 = db.instance_get_fixed_address_v6(
715+ self.context,
716+ instance_ref['id'])
717+ self.assertEqual(instance_ref['mac_address'],
718+ utils.to_mac(address_v6))
719+ instance_ref2 = db.fixed_ip_get_instance_v6(
720+ self.context,
721+ address_v6)
722+ self.assertEqual(instance_ref['id'], instance_ref2['id'])
723+ self.assertEqual(address_v6,
724+ utils.to_global_ipv6(
725+ network_ref['cidr_v6'],
726+ instance_ref['mac_address']))
727+
728 def test_public_network_association(self):
729 """Makes sure that we can allocaate a public ip"""
730 # TODO(vish): better way of adding floating ips
731
732=== modified file 'nova/utils.py'
733--- nova/utils.py 2011-01-12 01:32:12 +0000
734+++ nova/utils.py 2011-01-12 13:09:31 +0000
735@@ -30,6 +30,8 @@
736 import sys
737 import time
738 from xml.sax import saxutils
739+import re
740+import netaddr
741
742 from eventlet import event
743 from eventlet import greenthread
744@@ -200,6 +202,53 @@
745 return int(address.split(".")[-1])
746
747
748+def get_my_ip():
749+ """Returns the actual ip of the local machine."""
750+ try:
751+ csock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
752+ csock.connect(('8.8.8.8', 80))
753+ (addr, port) = csock.getsockname()
754+ csock.close()
755+ return addr
756+ except socket.gaierror as ex:
757+ logging.warn(_("Couldn't get IP, using 127.0.0.1 %s"), ex)
758+ return "127.0.0.1"
759+
760+
761+def get_my_linklocal(interface):
762+ try:
763+ if_str = execute("ip -f inet6 -o addr show %s" % interface)
764+ condition = "\s+inet6\s+([0-9a-f:]+/\d+)\s+scope\s+link"
765+ links = [re.search(condition, x) for x in if_str[0].split('\n')]
766+ address = [w.group(1) for w in links if w is not None]
767+ if address[0] is not None:
768+ return address[0]
769+ else:
770+ return 'fe00::'
771+ except IndexError as ex:
772+ logging.warn(_("Couldn't get Link Local IP of %s :%s"), interface, ex)
773+ except ProcessExecutionError as ex:
774+ logging.warn(_("Couldn't get Link Local IP of %s :%s"), interface, ex)
775+ except:
776+ return 'fe00::'
777+
778+
779+def to_global_ipv6(prefix, mac):
780+ mac64 = netaddr.EUI(mac).eui64().words
781+ int_addr = int(''.join(['%02x' % i for i in mac64]), 16)
782+ mac64_addr = netaddr.IPAddress(int_addr)
783+ maskIP = netaddr.IPNetwork(prefix).ip
784+ return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).format()
785+
786+
787+def to_mac(ipv6_address):
788+ address = netaddr.IPAddress(ipv6_address)
789+ mask1 = netaddr.IPAddress("::ffff:ffff:ffff:ffff")
790+ mask2 = netaddr.IPAddress("::0200:0:0:0")
791+ mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words
792+ return ":".join(["%02x" % i for i in mac64[0:3] + mac64[5:8]])
793+
794+
795 def utcnow():
796 """Overridable version of datetime.datetime.utcnow."""
797 if utcnow.override_time:
798
799=== modified file 'nova/virt/libvirt.xml.template'
800--- nova/virt/libvirt.xml.template 2011-01-05 00:22:47 +0000
801+++ nova/virt/libvirt.xml.template 2011-01-12 13:09:31 +0000
802@@ -66,6 +66,7 @@
803 <filterref filter="nova-instance-${name}">
804 <parameter name="IP" value="${ip_address}" />
805 <parameter name="DHCPSERVER" value="${dhcp_server}" />
806+ <parameter name="RASERVER" value="${ra_server}" />
807 #if $getVar('extra_params', False)
808 ${extra_params}
809 #end if
810
811=== modified file 'nova/virt/libvirt_conn.py'
812--- nova/virt/libvirt_conn.py 2011-01-12 01:32:12 +0000
813+++ nova/virt/libvirt_conn.py 2011-01-12 13:09:31 +0000
814@@ -127,6 +127,16 @@
815 return str(net.net()), str(net.netmask())
816
817
818+def _get_net_and_prefixlen(cidr):
819+ net = IPy.IP(cidr)
820+ return str(net.net()), str(net.prefixlen())
821+
822+
823+def _get_ip_version(cidr):
824+ net = IPy.IP(cidr)
825+ return int(net.version())
826+
827+
828 class LibvirtConnection(object):
829
830 def __init__(self, read_only):
831@@ -370,7 +380,6 @@
832 instance['id'],
833 power_state.NOSTATE,
834 'launching')
835-
836 self.nwfilter.setup_basic_filtering(instance)
837 self.firewall_driver.prepare_instance_filter(instance)
838 self._create_image(instance, xml)
839@@ -539,12 +548,16 @@
840 if network_ref['injected']:
841 admin_context = context.get_admin_context()
842 address = db.instance_get_fixed_address(admin_context, inst['id'])
843+ ra_server = network_ref['ra_server']
844+ if not ra_server:
845+ ra_server = "fd00::"
846 with open(FLAGS.injected_network_template) as f:
847 net = f.read() % {'address': address,
848 'netmask': network_ref['netmask'],
849 'gateway': network_ref['gateway'],
850 'broadcast': network_ref['broadcast'],
851- 'dns': network_ref['dns']}
852+ 'dns': network_ref['dns'],
853+ 'ra_server': ra_server}
854 if key or net:
855 if key:
856 LOG.info(_('instance %s: injecting key into image %s'),
857@@ -599,13 +612,30 @@
858 instance['id'])
859 # Assume that the gateway also acts as the dhcp server.
860 dhcp_server = network['gateway']
861-
862+ ra_server = network['ra_server']
863+ if not ra_server:
864+ ra_server = 'fd00::'
865 if FLAGS.allow_project_net_traffic:
866- net, mask = _get_net_and_mask(network['cidr'])
867- extra_params = ("<parameter name=\"PROJNET\" "
868- "value=\"%s\" />\n"
869- "<parameter name=\"PROJMASK\" "
870- "value=\"%s\" />\n") % (net, mask)
871+ if FLAGS.use_ipv6:
872+ net, mask = _get_net_and_mask(network['cidr'])
873+ net_v6, prefixlen_v6 = _get_net_and_prefixlen(
874+ network['cidr_v6'])
875+ extra_params = ("<parameter name=\"PROJNET\" "
876+ "value=\"%s\" />\n"
877+ "<parameter name=\"PROJMASK\" "
878+ "value=\"%s\" />\n"
879+ "<parameter name=\"PROJNETV6\" "
880+ "value=\"%s\" />\n"
881+ "<parameter name=\"PROJMASKV6\" "
882+ "value=\"%s\" />\n") % \
883+ (net, mask, net_v6, prefixlen_v6)
884+ else:
885+ net, mask = _get_net_and_mask(network['cidr'])
886+ extra_params = ("<parameter name=\"PROJNET\" "
887+ "value=\"%s\" />\n"
888+ "<parameter name=\"PROJMASK\" "
889+ "value=\"%s\" />\n") % \
890+ (net, mask)
891 else:
892 extra_params = "\n"
893
894@@ -619,6 +649,7 @@
895 'mac_address': instance['mac_address'],
896 'ip_address': ip_address,
897 'dhcp_server': dhcp_server,
898+ 'ra_server': ra_server,
899 'extra_params': extra_params,
900 'rescue': rescue}
901 if not rescue:
902@@ -876,6 +907,15 @@
903 </rule>
904 </filter>'''
905
906+ def nova_ra_filter(self):
907+ return '''<filter name='nova-allow-ra-server' chain='root'>
908+ <uuid>d707fa71-4fb5-4b27-9ab7-ba5ca19c8804</uuid>
909+ <rule action='accept' direction='inout'
910+ priority='100'>
911+ <icmpv6 srcipaddr='$RASERVER'/>
912+ </rule>
913+ </filter>'''
914+
915 def setup_basic_filtering(self, instance):
916 """Set up basic filtering (MAC, IP, and ARP spoofing protection)"""
917 logging.info('called setup_basic_filtering in nwfilter')
918@@ -900,13 +940,17 @@
919 ['no-mac-spoofing',
920 'no-ip-spoofing',
921 'no-arp-spoofing',
922- 'allow-dhcp-server']))
923+ 'allow-dhcp-server'
924+ ]))
925 self._define_filter(self.nova_base_ipv4_filter)
926 self._define_filter(self.nova_base_ipv6_filter)
927 self._define_filter(self.nova_dhcp_filter)
928+ self._define_filter(self.nova_ra_filter)
929 self._define_filter(self.nova_vpn_filter)
930 if FLAGS.allow_project_net_traffic:
931 self._define_filter(self.nova_project_filter)
932+ if FLAGS.use_ipv6:
933+ self._define_filter(self.nova_project_filter_v6)
934
935 self.static_filters_configured = True
936
937@@ -938,13 +982,13 @@
938
939 def nova_base_ipv6_filter(self):
940 retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
941- for protocol in ['tcp', 'udp', 'icmp']:
942+ for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
943 for direction, action, priority in [('out', 'accept', 399),
944 ('in', 'drop', 400)]:
945 retval += """<rule action='%s' direction='%s' priority='%d'>
946- <%s-ipv6 />
947+ <%s />
948 </rule>""" % (action, direction,
949- priority, protocol)
950+ priority, protocol)
951 retval += '</filter>'
952 return retval
953
954@@ -957,10 +1001,20 @@
955 retval += '</filter>'
956 return retval
957
958+ def nova_project_filter_v6(self):
959+ retval = "<filter name='nova-project-v6' chain='ipv6'>"
960+ for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
961+ retval += """<rule action='accept' direction='inout'
962+ priority='200'>
963+ <%s srcipaddr='$PROJNETV6'
964+ srcipmask='$PROJMASKV6' />
965+ </rule>""" % (protocol)
966+ retval += '</filter>'
967+ return retval
968+
969 def _define_filter(self, xml):
970 if callable(xml):
971 xml = xml()
972-
973 # execute in a native thread and block current greenthread until done
974 tpool.execute(self._conn.nwfilterDefineXML, xml)
975
976@@ -970,7 +1024,6 @@
977 it makes sure the filters for the security groups as well as
978 the base filter are all in place.
979 """
980-
981 if instance['image_id'] == FLAGS.vpn_image_id:
982 base_filter = 'nova-vpn'
983 else:
984@@ -982,11 +1035,15 @@
985 instance_secgroup_filter_children = ['nova-base-ipv4',
986 'nova-base-ipv6',
987 'nova-allow-dhcp-server']
988+ if FLAGS.use_ipv6:
989+ instance_secgroup_filter_children += ['nova-allow-ra-server']
990
991 ctxt = context.get_admin_context()
992
993 if FLAGS.allow_project_net_traffic:
994 instance_filter_children += ['nova-project']
995+ if FLAGS.use_ipv6:
996+ instance_filter_children += ['nova-project-v6']
997
998 for security_group in db.security_group_get_by_instance(ctxt,
999 instance['id']):
1000@@ -1014,12 +1071,19 @@
1001 security_group = db.security_group_get(context.get_admin_context(),
1002 security_group_id)
1003 rule_xml = ""
1004+ v6protocol = {'tcp': 'tcp-ipv6', 'udp': 'udp-ipv6', 'icmp': 'icmpv6'}
1005 for rule in security_group.rules:
1006 rule_xml += "<rule action='accept' direction='in' priority='300'>"
1007 if rule.cidr:
1008- net, mask = _get_net_and_mask(rule.cidr)
1009- rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
1010- (rule.protocol, net, mask)
1011+ version = _get_ip_version(rule.cidr)
1012+ if(FLAGS.use_ipv6 and version == 6):
1013+ net, prefixlen = _get_net_and_prefixlen(rule.cidr)
1014+ rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
1015+ (v6protocol[rule.protocol], net, prefixlen)
1016+ else:
1017+ net, mask = _get_net_and_mask(rule.cidr)
1018+ rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
1019+ (rule.protocol, net, mask)
1020 if rule.protocol in ['tcp', 'udp']:
1021 rule_xml += "dstportstart='%s' dstportend='%s' " % \
1022 (rule.from_port, rule.to_port)
1023@@ -1034,8 +1098,11 @@
1024
1025 rule_xml += '/>\n'
1026 rule_xml += "</rule>\n"
1027- xml = "<filter name='nova-secgroup-%s' chain='ipv4'>%s</filter>" % \
1028- (security_group_id, rule_xml,)
1029+ xml = "<filter name='nova-secgroup-%s' " % security_group_id
1030+ if(FLAGS.use_ipv6):
1031+ xml += "chain='root'>%s</filter>" % rule_xml
1032+ else:
1033+ xml += "chain='ipv4'>%s</filter>" % rule_xml
1034 return xml
1035
1036 def _instance_filter_name(self, instance):
1037@@ -1064,11 +1131,17 @@
1038 def apply_ruleset(self):
1039 current_filter, _ = self.execute('sudo iptables-save -t filter')
1040 current_lines = current_filter.split('\n')
1041- new_filter = self.modify_rules(current_lines)
1042+ new_filter = self.modify_rules(current_lines, 4)
1043 self.execute('sudo iptables-restore',
1044 process_input='\n'.join(new_filter))
1045+ if(FLAGS.use_ipv6):
1046+ current_filter, _ = self.execute('sudo ip6tables-save -t filter')
1047+ current_lines = current_filter.split('\n')
1048+ new_filter = self.modify_rules(current_lines, 6)
1049+ self.execute('sudo ip6tables-restore',
1050+ process_input='\n'.join(new_filter))
1051
1052- def modify_rules(self, current_lines):
1053+ def modify_rules(self, current_lines, ip_version):
1054 ctxt = context.get_admin_context()
1055 # Remove any trace of nova rules.
1056 new_filter = filter(lambda l: 'nova-' not in l, current_lines)
1057@@ -1082,8 +1155,8 @@
1058 if not new_filter[rules_index].startswith(':'):
1059 break
1060
1061- our_chains = [':nova-ipv4-fallback - [0:0]']
1062- our_rules = ['-A nova-ipv4-fallback -j DROP']
1063+ our_chains = [':nova-fallback - [0:0]']
1064+ our_rules = ['-A nova-fallback -j DROP']
1065
1066 our_chains += [':nova-local - [0:0]']
1067 our_rules += ['-A FORWARD -j nova-local']
1068@@ -1093,7 +1166,10 @@
1069 # First, we add instance chains and rules
1070 for instance in self.instances:
1071 chain_name = self._instance_chain_name(instance)
1072- ip_address = self._ip_for_instance(instance)
1073+ if(ip_version == 4):
1074+ ip_address = self._ip_for_instance(instance)
1075+ elif(ip_version == 6):
1076+ ip_address = self._ip_for_instance_v6(instance)
1077
1078 our_chains += [':%s - [0:0]' % chain_name]
1079
1080@@ -1119,13 +1195,19 @@
1081
1082 our_rules += ['-A %s -j %s' % (chain_name, sg_chain_name)]
1083
1084- # Allow DHCP responses
1085- dhcp_server = self._dhcp_server_for_instance(instance)
1086- our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' %
1087- (chain_name, dhcp_server)]
1088+ if(ip_version == 4):
1089+ # Allow DHCP responses
1090+ dhcp_server = self._dhcp_server_for_instance(instance)
1091+ our_rules += ['-A %s -s %s -p udp --sport 67 --dport 68' %
1092+ (chain_name, dhcp_server)]
1093+ elif(ip_version == 6):
1094+ # Allow RA responses
1095+ ra_server = self._ra_server_for_instance(instance)
1096+ our_rules += ['-A %s -s %s -p icmpv6' %
1097+ (chain_name, ra_server)]
1098
1099 # If nothing matches, jump to the fallback chain
1100- our_rules += ['-A %s -j nova-ipv4-fallback' % (chain_name,)]
1101+ our_rules += ['-A %s -j nova-fallback' % (chain_name,)]
1102
1103 # then, security group chains and rules
1104 for security_group in security_groups:
1105@@ -1138,15 +1220,22 @@
1106
1107 for rule in rules:
1108 logging.info('%r', rule)
1109- args = ['-A', chain_name, '-p', rule.protocol]
1110
1111- if rule.cidr:
1112- args += ['-s', rule.cidr]
1113- else:
1114+ if not rule.cidr:
1115 # Eventually, a mechanism to grant access for security
1116 # groups will turn up here. It'll use ipsets.
1117 continue
1118
1119+ version = _get_ip_version(rule.cidr)
1120+ if version != ip_version:
1121+ continue
1122+
1123+ protocol = rule.protocol
1124+ if version == 6 and rule.protocol == 'icmp':
1125+ protocol = 'icmpv6'
1126+
1127+ args = ['-A', chain_name, '-p', protocol, '-s', rule.cidr]
1128+
1129 if rule.protocol in ['udp', 'tcp']:
1130 if rule.from_port == rule.to_port:
1131 args += ['--dport', '%s' % (rule.from_port,)]
1132@@ -1166,7 +1255,12 @@
1133 icmp_type_arg += '/%s' % icmp_code
1134
1135 if icmp_type_arg:
1136- args += ['-m', 'icmp', '--icmp-type', icmp_type_arg]
1137+ if(ip_version == 4):
1138+ args += ['-m', 'icmp', '--icmp-type',
1139+ icmp_type_arg]
1140+ elif(ip_version == 6):
1141+ args += ['-m', 'icmp6', '--icmpv6-type',
1142+ icmp_type_arg]
1143
1144 args += ['-j ACCEPT']
1145 our_rules += [' '.join(args)]
1146@@ -1192,7 +1286,16 @@
1147 return db.instance_get_fixed_address(context.get_admin_context(),
1148 instance['id'])
1149
1150+ def _ip_for_instance_v6(self, instance):
1151+ return db.instance_get_fixed_address_v6(context.get_admin_context(),
1152+ instance['id'])
1153+
1154 def _dhcp_server_for_instance(self, instance):
1155 network = db.project_get_network(context.get_admin_context(),
1156 instance['project_id'])
1157 return network['gateway']
1158+
1159+ def _ra_server_for_instance(self, instance):
1160+ network = db.project_get_network(context.get_admin_context(),
1161+ instance['project_id'])
1162+ return network['ra_server']
1163
1164=== modified file 'smoketests/admin_smoketests.py'
1165--- smoketests/admin_smoketests.py 2011-01-06 01:51:05 +0000
1166+++ smoketests/admin_smoketests.py 2011-01-12 13:09:31 +0000
1167@@ -43,7 +43,7 @@
1168 # TODO(devamcar): Use random tempfile
1169 ZIP_FILENAME = '/tmp/nova-me-x509.zip'
1170
1171-TEST_PREFIX = 'test%s' % int(random.random()*1000000)
1172+TEST_PREFIX = 'test%s' % int(random.random() * 1000000)
1173 TEST_USERNAME = '%suser' % TEST_PREFIX
1174 TEST_PROJECTNAME = '%sproject' % TEST_PREFIX
1175
1176@@ -96,4 +96,3 @@
1177 if __name__ == "__main__":
1178 suites = {'user': unittest.makeSuite(UserTests)}
1179 sys.exit(base.run_tests(suites))
1180-
1181
1182=== modified file 'smoketests/base.py'
1183--- smoketests/base.py 2010-11-04 22:50:23 +0000
1184+++ smoketests/base.py 2011-01-12 13:09:31 +0000
1185@@ -17,6 +17,7 @@
1186 # under the License.
1187
1188 import boto
1189+import boto_v6
1190 import commands
1191 import httplib
1192 import os
1193@@ -69,6 +70,17 @@
1194 'test.')
1195
1196 parts = self.split_clc_url(clc_url)
1197+ if FLAGS.use_ipv6:
1198+ return boto_v6.connect_ec2(aws_access_key_id=access_key,
1199+ aws_secret_access_key=secret_key,
1200+ is_secure=parts['is_secure'],
1201+ region=RegionInfo(None,
1202+ 'nova',
1203+ parts['ip']),
1204+ port=parts['port'],
1205+ path='/services/Cloud',
1206+ **kwargs)
1207+
1208 return boto.connect_ec2(aws_access_key_id=access_key,
1209 aws_secret_access_key=secret_key,
1210 is_secure=parts['is_secure'],
1211@@ -115,7 +127,8 @@
1212 return True
1213
1214 def upload_image(self, bucket_name, image):
1215- cmd = 'euca-upload-bundle -b %s -m /tmp/%s.manifest.xml' % (bucket_name, image)
1216+ cmd = 'euca-upload-bundle -b '
1217+ cmd += '%s -m /tmp/%s.manifest.xml' % (bucket_name, image)
1218 status, output = commands.getstatusoutput(cmd)
1219 if status != 0:
1220 print '%s -> \n %s' % (cmd, output)
1221@@ -130,6 +143,7 @@
1222 raise Exception(output)
1223 return True
1224
1225+
1226 def run_tests(suites):
1227 argv = FLAGS(sys.argv)
1228
1229@@ -151,4 +165,3 @@
1230 else:
1231 for suite in suites.itervalues():
1232 unittest.TextTestRunner(verbosity=2).run(suite)
1233-
1234
1235=== modified file 'smoketests/flags.py'
1236--- smoketests/flags.py 2010-11-04 22:50:23 +0000
1237+++ smoketests/flags.py 2011-01-12 13:09:31 +0000
1238@@ -33,6 +33,7 @@
1239 # __GLOBAL FLAGS ONLY__
1240 # Define any app-specific flags in their own files, docs at:
1241 # http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
1242+
1243 DEFINE_string('region', 'nova', 'Region to use')
1244 DEFINE_string('test_image', 'ami-tiny', 'Image to use for launch tests')
1245-
1246+DEFINE_string('use_ipv6', True, 'use the ipv6 or not')
1247
1248=== added file 'smoketests/public_network_smoketests.py'
1249--- smoketests/public_network_smoketests.py 1970-01-01 00:00:00 +0000
1250+++ smoketests/public_network_smoketests.py 2011-01-12 13:09:31 +0000
1251@@ -0,0 +1,180 @@
1252+# vim: tabstop=4 shiftwidth=4 softtabstop=4
1253+
1254+# Copyright 2010 United States Government as represented by the
1255+# Administrator of the National Aeronautics and Space Administration.
1256+# All Rights Reserved.
1257+#
1258+# Licensed under the Apache License, Version 2.0 (the "License"); you may
1259+# not use this file except in compliance with the License. You may obtain
1260+# a copy of the License at
1261+#
1262+# http://www.apache.org/licenses/LICENSE-2.0
1263+#
1264+# Unless required by applicable law or agreed to in writing, software
1265+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
1266+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
1267+# License for the specific language governing permissions and limitations
1268+# under the License.
1269+
1270+import commands
1271+import os
1272+import random
1273+import socket
1274+import sys
1275+import time
1276+import unittest
1277+
1278+from smoketests import flags
1279+from smoketests import base
1280+from smoketests import user_smoketests
1281+
1282+#Note that this test should run from
1283+#public network (outside of private network segments)
1284+#Please set EC2_URL correctly
1285+#You should use admin account in this test
1286+
1287+FLAGS = flags.FLAGS
1288+
1289+TEST_PREFIX = 'test%s' % int(random.random() * 1000000)
1290+TEST_BUCKET = '%s_bucket' % TEST_PREFIX
1291+TEST_KEY = '%s_key' % TEST_PREFIX
1292+TEST_KEY2 = '%s_key2' % TEST_PREFIX
1293+TEST_DATA = {}
1294+
1295+
1296+class InstanceTestsFromPublic(user_smoketests.UserSmokeTestCase):
1297+ def test_001_can_create_keypair(self):
1298+ key = self.create_key_pair(self.conn, TEST_KEY)
1299+ self.assertEqual(key.name, TEST_KEY)
1300+
1301+ def test_002_security_group(self):
1302+ security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd")
1303+ for x in range(random.randint(4, 8)))
1304+ group = self.conn.create_security_group(security_group_name,
1305+ 'test group')
1306+ group.connection = self.conn
1307+ group.authorize('tcp', 22, 22, '0.0.0.0/0')
1308+ if FLAGS.use_ipv6:
1309+ group.authorize('tcp', 22, 22, '::/0')
1310+
1311+ reservation = self.conn.run_instances(FLAGS.test_image,
1312+ key_name=TEST_KEY,
1313+ security_groups=[security_group_name],
1314+ instance_type='m1.tiny')
1315+ self.data['security_group_name'] = security_group_name
1316+ self.data['group'] = group
1317+ self.data['instance_id'] = reservation.instances[0].id
1318+
1319+ def test_003_instance_with_group_runs_within_60_seconds(self):
1320+ reservations = self.conn.get_all_instances([self.data['instance_id']])
1321+ instance = reservations[0].instances[0]
1322+ # allow 60 seconds to exit pending with IP
1323+ for x in xrange(60):
1324+ instance.update()
1325+ if instance.state == u'running':
1326+ break
1327+ time.sleep(1)
1328+ else:
1329+ self.fail('instance failed to start')
1330+ ip = reservations[0].instances[0].private_dns_name
1331+ self.failIf(ip == '0.0.0.0')
1332+ self.data['private_ip'] = ip
1333+ if FLAGS.use_ipv6:
1334+ ipv6 = reservations[0].instances[0].dns_name_v6
1335+ self.failIf(ipv6 is None)
1336+ self.data['ip_v6'] = ipv6
1337+
1338+ def test_004_can_ssh_to_ipv6(self):
1339+ if FLAGS.use_ipv6:
1340+ for x in xrange(20):
1341+ try:
1342+ conn = self.connect_ssh(
1343+ self.data['ip_v6'], TEST_KEY)
1344+ conn.close()
1345+ except Exception as ex:
1346+ print ex
1347+ time.sleep(1)
1348+ else:
1349+ break
1350+ else:
1351+ self.fail('could not ssh to instance')
1352+
1353+ def test_012_can_create_instance_with_keypair(self):
1354+ if 'instance_id' in self.data:
1355+ self.conn.terminate_instances([self.data['instance_id']])
1356+ reservation = self.conn.run_instances(FLAGS.test_image,
1357+ key_name=TEST_KEY,
1358+ instance_type='m1.tiny')
1359+ self.assertEqual(len(reservation.instances), 1)
1360+ self.data['instance_id'] = reservation.instances[0].id
1361+
1362+ def test_013_instance_runs_within_60_seconds(self):
1363+ reservations = self.conn.get_all_instances([self.data['instance_id']])
1364+ instance = reservations[0].instances[0]
1365+ # allow 60 seconds to exit pending with IP
1366+ for x in xrange(60):
1367+ instance.update()
1368+ if instance.state == u'running':
1369+ break
1370+ time.sleep(1)
1371+ else:
1372+ self.fail('instance failed to start')
1373+ ip = reservations[0].instances[0].private_dns_name
1374+ self.failIf(ip == '0.0.0.0')
1375+ self.data['private_ip'] = ip
1376+ if FLAGS.use_ipv6:
1377+ ipv6 = reservations[0].instances[0].dns_name_v6
1378+ self.failIf(ipv6 is None)
1379+ self.data['ip_v6'] = ipv6
1380+
1381+ def test_014_can_not_ping_private_ip(self):
1382+ for x in xrange(4):
1383+ # ping waits for 1 second
1384+ status, output = commands.getstatusoutput(
1385+ 'ping -c1 %s' % self.data['private_ip'])
1386+ if status == 0:
1387+ self.fail('can ping private ip from public network')
1388+ if FLAGS.use_ipv6:
1389+ status, output = commands.getstatusoutput(
1390+ 'ping6 -c1 %s' % self.data['ip_v6'])
1391+ if status == 0:
1392+ self.fail('can ping ipv6 from public network')
1393+ else:
1394+ pass
1395+
1396+ def test_015_can_not_ssh_to_private_ip(self):
1397+ for x in xrange(1):
1398+ try:
1399+ conn = self.connect_ssh(self.data['private_ip'], TEST_KEY)
1400+ conn.close()
1401+ except Exception:
1402+ time.sleep(1)
1403+ else:
1404+ self.fail('can ssh for ipv4 address from public network')
1405+
1406+ if FLAGS.use_ipv6:
1407+ for x in xrange(1):
1408+ try:
1409+ conn = self.connect_ssh(
1410+ self.data['ip_v6'], TEST_KEY)
1411+ conn.close()
1412+ except Exception:
1413+ time.sleep(1)
1414+ else:
1415+ self.fail('can ssh for ipv6 address from public network')
1416+
1417+ def test_999_tearDown(self):
1418+ self.delete_key_pair(self.conn, TEST_KEY)
1419+ security_group_name = self.data['security_group_name']
1420+ group = self.data['group']
1421+ if group:
1422+ group.revoke('tcp', 22, 22, '0.0.0.0/0')
1423+ if FLAGS.use_ipv6:
1424+ group.revoke('tcp', 22, 22, '::/0')
1425+ self.conn.delete_security_group(security_group_name)
1426+ if 'instance_id' in self.data:
1427+ self.conn.terminate_instances([self.data['instance_id']])
1428+
1429+if __name__ == "__main__":
1430+ suites = {'instance': unittest.makeSuite(InstanceTestsFromPublic)}
1431+ sys.exit(base.run_tests(suites))
1432
1433=== modified file 'smoketests/user_smoketests.py'
1434--- smoketests/user_smoketests.py 2011-01-06 01:51:05 +0000
1435+++ smoketests/user_smoketests.py 2011-01-12 13:09:31 +0000
1436@@ -45,7 +45,7 @@
1437 flags.DEFINE_string('bundle_image', 'openwrt-x86-ext2.image',
1438 'Local image file to use for bundling tests')
1439
1440-TEST_PREFIX = 'test%s' % int (random.random()*1000000)
1441+TEST_PREFIX = 'test%s' % int(random.random() * 1000000)
1442 TEST_BUCKET = '%s_bucket' % TEST_PREFIX
1443 TEST_KEY = '%s_key' % TEST_PREFIX
1444 TEST_GROUP = '%s_group' % TEST_PREFIX
1445@@ -80,7 +80,7 @@
1446
1447 def test_006_can_register_kernel(self):
1448 kernel_id = self.conn.register_image('%s/%s.manifest.xml' %
1449- (TEST_BUCKET, FLAGS.bundle_kernel))
1450+ (TEST_BUCKET, FLAGS.bundle_kernel))
1451 self.assert_(kernel_id is not None)
1452 self.data['kernel_id'] = kernel_id
1453
1454@@ -92,7 +92,7 @@
1455 time.sleep(1)
1456 else:
1457 print image.state
1458- self.assert_(False) # wasn't available within 10 seconds
1459+ self.assert_(False) # wasn't available within 10 seconds
1460 self.assert_(image.type == 'machine')
1461
1462 for i in xrange(10):
1463@@ -101,7 +101,7 @@
1464 break
1465 time.sleep(1)
1466 else:
1467- self.assert_(False) # wasn't available within 10 seconds
1468+ self.assert_(False) # wasn't available within 10 seconds
1469 self.assert_(kernel.type == 'kernel')
1470
1471 def test_008_can_describe_image_attribute(self):
1472@@ -152,14 +152,17 @@
1473 for x in xrange(60):
1474 instance.update()
1475 if instance.state == u'running':
1476- break
1477+ break
1478 time.sleep(1)
1479 else:
1480 self.fail('instance failed to start')
1481 ip = reservations[0].instances[0].private_dns_name
1482 self.failIf(ip == '0.0.0.0')
1483 self.data['private_ip'] = ip
1484- print self.data['private_ip']
1485+ if FLAGS.use_ipv6:
1486+ ipv6 = reservations[0].instances[0].dns_name_v6
1487+ self.failIf(ipv6 is None)
1488+ self.data['ip_v6'] = ipv6
1489
1490 def test_004_can_ping_private_ip(self):
1491 for x in xrange(120):
1492@@ -171,6 +174,16 @@
1493 else:
1494 self.fail('could not ping instance')
1495
1496+ if FLAGS.use_ipv6:
1497+ for x in xrange(120):
1498+ # ping waits for 1 second
1499+ status, output = commands.getstatusoutput(
1500+ 'ping6 -c1 %s' % self.data['ip_v6'])
1501+ if status == 0:
1502+ break
1503+ else:
1504+ self.fail('could not ping instance')
1505+
1506 def test_005_can_ssh_to_private_ip(self):
1507 for x in xrange(30):
1508 try:
1509@@ -183,6 +196,19 @@
1510 else:
1511 self.fail('could not ssh to instance')
1512
1513+ if FLAGS.use_ipv6:
1514+ for x in xrange(30):
1515+ try:
1516+ conn = self.connect_ssh(
1517+ self.data['ip_v6'], TEST_KEY)
1518+ conn.close()
1519+ except Exception:
1520+ time.sleep(1)
1521+ else:
1522+ break
1523+ else:
1524+ self.fail('could not ssh to instance v6')
1525+
1526 def test_006_can_allocate_elastic_ip(self):
1527 result = self.conn.allocate_address()
1528 self.assertTrue(hasattr(result, 'public_ip'))
1529@@ -388,7 +414,6 @@
1530 raise Exception("Timeout")
1531 time.sleep(1)
1532
1533-
1534 def test_999_tearDown(self):
1535 self.conn.delete_key_pair(TEST_KEY)
1536 self.conn.delete_security_group(TEST_GROUP)