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