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