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: 1528 lines (+799/-53)
23 files modified
Authors (+4/-0)
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 (+66/-0)
nova/tests/test_network.py (+22/-0)
nova/utils.py (+36/-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
Soren Hansen Pending
Devin Carlen Pending
Review via email: mp+46067@code.launchpad.net

This proposal supersedes a proposal from 2011-01-12.

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

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 555.
Fixed comment by Soren

To post a comment you must log in.
Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal
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) : Posted in a previous version of this proposal
review: Needs Fixing
Revision history for this message
Devin Carlen (devcamcar) wrote : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

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

Revision history for this message
Soren Hansen (soren) wrote : Posted in a previous version of this proposal
Download full text (5.8 KiB)

I'm getting a number of failures when I run the test suite.

======================================================================
ERROR: test_associate_disassociate_address (nova.tests.test_cloud.CloudTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/soren/src/openstack/nova/nova/nova/tests/test_cloud.py", line 112, in test_associate_disassociate_address
    public_ip=address)
  File "/home/soren/src/openstack/nova/nova/nova/api/ec2/cloud.py", line 758, in associate_address
    self.compute_api.associate_floating_ip(context, instance_id, public_ip)
  File "/home/soren/src/openstack/nova/nova/nova/compute/api.py", line 501, in associate_floating_ip
    instance['fixed_ip'])
  File "/home/soren/src/openstack/nova/nova/nova/network/api.py", line 70, in associate_floating_ip
    host = fixed_ip['network']['host']
  File "/home/soren/src/openstack/nova/nova/nova/db/sqlalchemy/models.py", line 74, in __getitem__
    return getattr(self, key)
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/attributes.py", line 163, in __get__
    instance_dict(instance))
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/attributes.py", line 382, in get
    value = callable_(passive=passive)
  File "/usr/lib/python2.6/dist-packages/sqlalchemy/orm/strategies.py", line 578, in __call__
    (mapperutil.state_str(state), self.key)
DetachedInstanceError: Parent instance <FixedIp at 0x5a13e90> is not bound to a Session; lazy load operation of attribute 'network' cannot proceed

======================================================================
ERROR: test_private_ipv6 (nova.tests.test_network.NetworkTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/soren/src/openstack/nova/nova/nova/tests/test_network.py", line 108, in test_private_ipv6
    instance_ref['id'])
  File "/home/soren/src/openstack/nova/nova/nova/db/api.py", line 361, in instance_get_fixed_address_v6
    return IMPL.instance_get_fixed_address_v6(context, instance_id)
  File "/home/soren/src/openstack/nova/nova/nova/db/sqlalchemy/api.py", line 107, in wrapper
    return f(*args, **kwargs)
  File "/home/soren/src/openstack/nova/nova/nova/db/sqlalchemy/api.py", line 810, in instance_get_fixed_address_v6
    network_ref = network_get_by_instance(context, instance_id)
  File "/home/soren/src/openstack/nova/nova/nova/db/sqlalchemy/api.py", line 95, in wrapper
    raise exception.NotAuthorized()
NotAuthorized: None

======================================================================
ERROR: test_static_filters (nova.tests.test_virt.IptablesFirewallTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/soren/src/openstack/nova/nova/nova/tests/test_virt.py", line 306, in test_static_filters
    out_rules = self.fw.modify_rules(self.in_rules)
TypeError: modify_rules() takes exactly 3 arguments (2 given)

======================================================================
FAIL: test_describe_instances (nova.tests.test_cloud.CloudTestCase)
---------------------------...

Read more...

review: Needs Fixing
Revision history for this message
Nachi Ueno (nati-ueno) wrote : Posted in a previous version of this proposal

Thank you.
We fixed some bug.

export TEST=1
nova/contrib/nova.sh run

~~~~
----------------------------------------------------------------------
Ran 277 tests in 501.898s

OK

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