Support IPv6

This commit is contained in:
NTT PF Lab. 2010-12-24 20:38:49 +09:00
parent 0a93a9298d
commit c5c58cb20d
24 changed files with 702 additions and 32 deletions

View File

@ -87,7 +87,7 @@ flags.DECLARE('num_networks', 'nova.network.manager')
flags.DECLARE('network_size', 'nova.network.manager')
flags.DECLARE('vlan_start', 'nova.network.manager')
flags.DECLARE('vpn_start', 'nova.network.manager')
flags.DECLARE('fixed_range_v6', 'nova.network.manager')
class VpnCommands(object):
"""Class for managing VPNs."""
@ -411,11 +411,11 @@ class NetworkCommands(object):
"""Class for managing networks."""
def create(self, fixed_range=None, num_networks=None,
network_size=None, vlan_start=None, vpn_start=None):
network_size=None, vlan_start=None, vpn_start=None,fixed_range_v6=None):
"""Creates fixed ips for host by range
arguments: [fixed_range=FLAG], [num_networks=FLAG],
[network_size=FLAG], [vlan_start=FLAG],
[vpn_start=FLAG]"""
[vpn_start=FLAG],[fixed_range_v6=FLAG]"""
if not fixed_range:
fixed_range = FLAGS.fixed_range
if not num_networks:
@ -426,11 +426,15 @@ class NetworkCommands(object):
vlan_start = FLAGS.vlan_start
if not vpn_start:
vpn_start = FLAGS.vpn_start
if not fixed_range_v6:
fixed_range_v6 = FLAGS.fixed_range_v6
net_manager = utils.import_object(FLAGS.network_manager)
net_manager.create_networks(context.get_admin_context(),
fixed_range, int(num_networks),
int(network_size), int(vlan_start),
int(vpn_start))
int(vpn_start),fixed_range_v6)
CATEGORIES = [
('user', UserCommands),

View File

@ -0,0 +1,37 @@
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
# Copyright (c) 2010, Eucalyptus Systems, Inc.
# All rights reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish, dis-
# tribute, sublicense, and/or sell copies of the Software, and to permit
# persons to whom the Software is furnished to do so, subject to the fol-
# lowing conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
def connect_ec2(aws_access_key_id=None, aws_secret_access_key=None, **kwargs):
"""
:type aws_access_key_id: string
:param aws_access_key_id: Your AWS Access Key ID
:type aws_secret_access_key: string
:param aws_secret_access_key: Your AWS Secret Access Key
:rtype: :class:`boto.ec2.connection.EC2Connection`
:return: A connection to Amazon's EC2
"""
from boto_v6.ec2.connection import EC2ConnectionV6
return EC2ConnectionV6(aws_access_key_id, aws_secret_access_key, **kwargs)

View File

View File

@ -0,0 +1,41 @@
'''
Created on 2010/12/20
@author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
'''
import boto
import boto.ec2
from boto_v6.ec2.instance import ReservationV6
class EC2ConnectionV6(boto.ec2.EC2Connection):
'''
EC2Connection for OpenStack IPV6 mode
'''
def get_all_instances(self, instance_ids=None, filters=None):
"""
Retrieve all the instances associated with your account.
:type instance_ids: list
:param instance_ids: A list of strings of instance IDs
:type filters: dict
:param filters: Optional filters that can be used to limit
the results returned. Filters are provided
in the form of a dictionary consisting of
filter names as the key and filter values
as the value. The set of allowable filter
names/values is dependent on the request
being performed. Check the EC2 API guide
for details.
:rtype: list
:return: A list of :class:`boto.ec2.instance.Reservation`
"""
params = {}
if instance_ids:
self.build_list_params(params, instance_ids, 'InstanceId')
if filters:
self.build_filter_params(params, filters)
return self.get_list('DescribeInstances', params,
[('item', ReservationV6)])

View File

@ -0,0 +1,33 @@
'''
Created on 2010/12/20
@author: Nachi Ueno <ueno.nachi@lab.ntt.co.jp>
'''
import boto
from boto.resultset import ResultSet
from boto.ec2.instance import Reservation
from boto.ec2.instance import Group
from boto.ec2.instance import Instance
class ReservationV6(Reservation):
def startElement(self, name, attrs, connection):
if name == 'instancesSet':
self.instances = ResultSet([('item', InstanceV6)])
return self.instances
elif name == 'groupSet':
self.groups = ResultSet([('item', Group)])
return self.groups
else:
return None
class InstanceV6(Instance):
def __init__(self, connection=None):
Instance.__init__(self, connection)
self.public_dns_name_v6 = None
def endElement(self, name, value, connection):
Instance.endElement(self, name, value, connection)
if name == 'dnsNameV6':
self.dns_name_v6 = value

View File

@ -85,6 +85,10 @@ if [ "$CMD" == "install" ]; then
sudo apt-get install -y python-twisted python-sqlalchemy python-mox python-greenlet python-carrot
sudo apt-get install -y python-daemon python-eventlet python-gflags python-tornado python-ipy
sudo apt-get install -y python-libvirt python-libxml2 python-routes
#For IPV6
sudo apt-get install -y python-netaddr
sudo apt-get install -y radvd
if [ "$USE_MYSQL" == 1 ]; then
cat <<MYSQL_PRESEED | debconf-set-selections
mysql-server-5.1 mysql-server/root_password password $MYSQL_PASS
@ -106,6 +110,8 @@ function screen_it {
if [ "$CMD" == "run" ]; then
killall dnsmasq
#For IPv6
killall radvd
screen -d -m -S nova -t nova
sleep 1
if [ "$USE_MYSQL" == 1 ]; then

View File

@ -31,7 +31,7 @@ import time
from nova import context
import IPy
import urllib
from nova import crypto
from nova import db
from nova import exception
@ -307,6 +307,7 @@ class CloudController(object):
values['group_id'] = source_security_group['id']
elif cidr_ip:
# If this fails, it throws an exception. This is what we want.
cidr_ip = urllib.unquote(cidr_ip).decode()
IPy.IP(cidr_ip)
values['cidr'] = cidr_ip
else:
@ -635,10 +636,16 @@ class CloudController(object):
if instance['fixed_ip']['floating_ips']:
fixed = instance['fixed_ip']
floating_addr = fixed['floating_ips'][0]['address']
if instance['fixed_ip']['network'] and FLAGS.use_ipv6:
i['dnsNameV6'] = utils.to_global_ipv6(
instance['fixed_ip']['network']['cidr_v6'],
instance['mac_address'])
i['privateDnsName'] = fixed_addr
i['publicDnsName'] = floating_addr
i['dnsName'] = i['publicDnsName'] or i['privateDnsName']
i['keyName'] = instance['key_name']
if context.user.is_admin():
i['keyName'] = '%s (%s, %s)' % (i['keyName'],
instance['project_id'],

View File

@ -240,6 +240,9 @@ def fixed_ip_get_instance(context, address):
"""Get an instance for a fixed ip by address."""
return IMPL.fixed_ip_get_instance(context, address)
def fixed_ip_get_instance_v6(context, address):
return IMPL.fixed_ip_get_instance_v6(context, address)
def fixed_ip_get_network(context, address):
"""Get a network for a fixed ip by address."""
@ -298,6 +301,9 @@ def instance_get_fixed_address(context, instance_id):
"""Get the fixed ip address of an instance."""
return IMPL.instance_get_fixed_address(context, instance_id)
def instance_get_fixed_address_v6(context, instance_id):
return IMPL.instance_get_fixed_address_v6(context, instance_id)
def instance_get_floating_address(context, instance_id):
"""Get the first floating ip address of an instance."""
@ -476,6 +482,9 @@ def project_get_network(context, project_id):
"""
return IMPL.project_get_network(context, project_id)
def project_get_network_v6(context, project_id):
return IMPL.project_get_network_v6(context, project_id)
###################

View File

@ -504,6 +504,16 @@ def fixed_ip_get_instance(context, address):
fixed_ip_ref = fixed_ip_get_by_address(context, address)
return fixed_ip_ref.instance
@require_context
def fixed_ip_get_instance_v6(context, address):
session = get_session()
mac = utils.to_mac(address)
result = session.query(models.Instance
).filter_by(mac_address=mac
).first()
return result
@require_admin_context
def fixed_ip_get_network(context, address):
@ -692,6 +702,15 @@ def instance_get_fixed_address(context, instance_id):
return None
return instance_ref.fixed_ip['address']
@require_context
def instance_get_fixed_address_v6(context, instance_id):
session = get_session()
with session.begin():
instance_ref = instance_get(context, instance_id, session=session)
network_ref = project_get_network(context, context.project_id)
prefix = network_ref.cidr_v6
mac = instance_ref.mac_address
return utils.to_global_ipv6(prefix, mac)
@require_context
def instance_get_floating_address(context, instance_id):
@ -1004,6 +1023,10 @@ def project_get_network(context, project_id):
first()
return rv
@require_context
def project_get_network_v6(context, project_id):
return project_get_network(context, project_id)
###################

View File

@ -389,6 +389,10 @@ class Network(BASE, NovaBase):
injected = Column(Boolean, default=False)
cidr = Column(String(255), unique=True)
cidr_v6 = Column(String(255), unique=True)
ra_server = Column(String(255))
netmask = Column(String(255))
bridge = Column(String(255))
gateway = Column(String(255))

View File

@ -53,6 +53,7 @@ flags.DEFINE_string('routing_source_ip', '127.0.0.1',
flags.DEFINE_bool('use_nova_chains', False,
'use the nova_ routing chains instead of default')
DEFAULT_PORTS = [("tcp", 80), ("tcp", 22), ("udp", 1194), ("tcp", 443)]
@ -75,6 +76,11 @@ def init_host():
FLAGS.fixed_range)
_confirm_rule("POSTROUTING", "-t nat -s %(range)s -d %(range)s -j ACCEPT" %
{'range': FLAGS.fixed_range})
if(FLAGS.use_ipv6):
_execute('sudo bash -c ' +
'"echo 1 > /proc/sys/net/ipv6/conf/all/forwarding"')
_execute('sudo bash -c ' +
'"echo 0 > /proc/sys/net/ipv6/conf/all/accept_ra"')
def bind_floating_ip(floating_ip):
@ -158,6 +164,10 @@ def ensure_bridge(bridge, interface, net_attrs=None):
net_attrs['gateway'],
net_attrs['broadcast'],
net_attrs['netmask']))
if(FLAGS.use_ipv6):
_execute("sudo ifconfig %s add %s up" % \
(bridge,
net_attrs['cidr_v6']))
else:
_execute("sudo ifconfig %s up" % bridge)
_confirm_rule("FORWARD", "--in-interface %s -j ACCEPT" % bridge)
@ -213,6 +223,50 @@ def update_dhcp(context, network_id):
_execute(command, addl_env=env)
def update_ra(context, network_id):
network_ref = db.network_get(context, network_id)
conffile = _ra_file(network_ref['bridge'], 'conf')
with open(conffile, 'w') as f:
conf_str = """
interface %s
{
AdvSendAdvert on;
MinRtrAdvInterval 3;
MaxRtrAdvInterval 10;
prefix %s
{
AdvOnLink on;
AdvAutonomous on;
};
};
""" % (network_ref['bridge'], network_ref['cidr_v6'])
f.write(conf_str)
# Make sure dnsmasq can actually read it (it setuid()s to "nobody")
os.chmod(conffile, 0644)
pid = _ra_pid_for(network_ref['bridge'])
# if dnsmasq is already running, then tell it to reload
if pid:
out, _err = _execute('cat /proc/%d/cmdline'
% pid, check_exit_code=False)
if conffile in out:
try:
_execute('sudo kill -HUP %d' % pid)
return
except Exception as exc: # pylint: disable-msg=W0703
logging.debug("Hupping radvd threw %s", exc)
else:
logging.debug("Pid %d is stale, relaunching radvd", pid)
command = _ra_cmd(network_ref)
_execute(command)
db.network_update(context, network_id,
{"ra_server":
utils.get_my_linklocal(network_ref['bridge'])})
def _host_dhcp(fixed_ip_ref):
"""Return a host string for an address"""
instance_ref = fixed_ip_ref['instance']
@ -268,6 +322,15 @@ def _dnsmasq_cmd(net):
return ''.join(cmd)
def _ra_cmd(net):
"""Builds dnsmasq command"""
cmd = ['sudo -E radvd',
# ' -u nobody',
' -C %s' % _ra_file(net['bridge'], 'conf'),
' -p %s' % _ra_file(net['bridge'], 'pid')]
return ''.join(cmd)
def _stop_dnsmasq(network):
"""Stops the dnsmasq instance for a given network"""
pid = _dnsmasq_pid_for(network)
@ -289,6 +352,16 @@ def _dhcp_file(bridge, kind):
kind))
def _ra_file(bridge, kind):
"""Return path to a pid, leases or conf file for a bridge"""
if not os.path.exists(FLAGS.networks_path):
os.makedirs(FLAGS.networks_path)
return os.path.abspath("%s/nova-ra-%s.%s" % (FLAGS.networks_path,
bridge,
kind))
def _dnsmasq_pid_for(bridge):
"""Returns the pid for prior dnsmasq instance for a bridge
@ -302,3 +375,18 @@ def _dnsmasq_pid_for(bridge):
if os.path.exists(pid_file):
with open(pid_file, 'r') as f:
return int(f.read())
def _ra_pid_for(bridge):
"""Returns the pid for prior dnsmasq instance for a bridge
Returns None if no pid file exists
If machine has rebooted pid might be incorrect (caller should check)
"""
pid_file = _ra_file(bridge, 'pid')
if os.path.exists(pid_file):
with open(pid_file, 'r') as f:
return int(f.read())

View File

@ -80,6 +80,7 @@ flags.DEFINE_integer('network_size', 256,
flags.DEFINE_string('floating_range', '4.4.4.0/24',
'Floating IP address block')
flags.DEFINE_string('fixed_range', '10.0.0.0/8', 'Fixed IP address block')
flags.DEFINE_string('fixed_range_v6', 'fd00::/48', 'Fixed IPv6 address block')
flags.DEFINE_integer('cnt_vpn_clients', 5,
'Number of addresses reserved for vpn clients')
flags.DEFINE_string('network_driver', 'nova.network.linux_net',
@ -88,6 +89,8 @@ flags.DEFINE_bool('update_dhcp_on_disassociate', False,
'Whether to update dhcp when fixed_ip is disassociated')
flags.DEFINE_integer('fixed_ip_disassociate_timeout', 600,
'Seconds after which a deallocated ip is disassociated')
flags.DEFINE_bool('use_ipv6', True,
'use the ipv6')
class AddressAlreadyAllocated(exception.Error):
@ -217,7 +220,7 @@ class NetworkManager(manager.Manager):
"""Get the network for the current context."""
raise NotImplementedError()
def create_networks(self, context, num_networks, network_size,
def create_networks(self, context, num_networks, network_size, cidr_v6,
*args, **kwargs):
"""Create networks based on parameters."""
raise NotImplementedError()
@ -307,9 +310,11 @@ class FlatManager(NetworkManager):
pass
def create_networks(self, context, cidr, num_networks, network_size,
*args, **kwargs):
cidr_v6, *args, **kwargs):
"""Create networks based on parameters."""
fixed_net = IPy.IP(cidr)
fixed_net_v6 = IPy.IP(cidr_v6)
significant_bits_v6 = 64
for index in range(num_networks):
start = index * network_size
significant_bits = 32 - int(math.log(network_size, 2))
@ -322,7 +327,13 @@ class FlatManager(NetworkManager):
net['gateway'] = str(project_net[1])
net['broadcast'] = str(project_net.broadcast())
net['dhcp_start'] = str(project_net[2])
if(FLAGS.use_ipv6):
cidr_v6 = "%s/%s" % (fixed_net_v6[0], significant_bits_v6)
net['cidr_v6'] = cidr_v6
network_ref = self.db.network_create_safe(context, net)
if network_ref:
self._create_fixed_ips(context, network_ref['id'])
@ -466,12 +477,16 @@ class VlanManager(NetworkManager):
pass
def create_networks(self, context, cidr, num_networks, network_size,
vlan_start, vpn_start):
vlan_start, vpn_start, cidr_v6):
"""Create networks based on parameters."""
fixed_net = IPy.IP(cidr)
fixed_net_v6 = IPy.IP(cidr_v6)
network_size_v6 = 1 << 64
significant_bits_v6 = 64
for index in range(num_networks):
vlan = vlan_start + index
start = index * network_size
start_v6 = index * network_size_v6
significant_bits = 32 - int(math.log(network_size, 2))
cidr = "%s/%s" % (fixed_net[start], significant_bits)
project_net = IPy.IP(cidr)
@ -484,6 +499,13 @@ class VlanManager(NetworkManager):
net['dhcp_start'] = str(project_net[3])
net['vlan'] = vlan
net['bridge'] = 'br%s' % vlan
if(FLAGS.use_ipv6):
cidr_v6 = "%s/%s" % (
fixed_net_v6[start_v6],
significant_bits_v6
)
net['cidr_v6'] = cidr_v6
# NOTE(vish): This makes ports unique accross the cloud, a more
# robust solution would be to make them unique per ip
net['vpn_public_port'] = vpn_start + index
@ -506,6 +528,8 @@ class VlanManager(NetworkManager):
network_ref['bridge'],
network_ref)
self.driver.update_dhcp(context, network_id)
if(FLAGS.use_ipv6):
self.driver.update_ra(context, network_id)
@property
def _bottom_reserved_ips(self):

View File

@ -70,7 +70,8 @@ class TrialTestCase(unittest.TestCase):
FLAGS.fixed_range,
5, 16,
FLAGS.vlan_start,
FLAGS.vpn_start)
FLAGS.vpn_start,
FLAGS.fixed_range_v6)
# emulate some of the mox stuff, we can't use the metaclass
# because it screws with our generators

View File

@ -24,6 +24,7 @@ import httplib
import random
import StringIO
import webob
import logging
from nova import context
from nova import flags
@ -265,6 +266,72 @@ class ApiEc2TestCase(test.TrialTestCase):
return
def test_authorize_revoke_security_group_cidr_v6(self):
"""
Test that we can add and remove CIDR based rules
to a security group for IPv6
"""
self.expect_http()
self.mox.ReplayAll()
user = self.manager.create_user('fake', 'fake', 'fake')
project = self.manager.create_project('fake', 'fake', 'fake')
# At the moment, you need both of these to actually be netadmin
self.manager.add_role('fake', 'netadmin')
project.add_role('fake', 'netadmin')
security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd")
for x in range(random.randint(4, 8)))
group = self.ec2.create_security_group(security_group_name,
'test group')
self.expect_http()
self.mox.ReplayAll()
group.connection = self.ec2
group.authorize('tcp', 80, 81, '::/0')
self.expect_http()
self.mox.ReplayAll()
rv = self.ec2.get_all_security_groups()
# I don't bother checkng that we actually find it here,
# because the create/delete unit test further up should
# be good enough for that.
for group in rv:
if group.name == security_group_name:
self.assertEquals(len(group.rules), 1)
self.assertEquals(int(group.rules[0].from_port), 80)
self.assertEquals(int(group.rules[0].to_port), 81)
self.assertEquals(len(group.rules[0].grants), 1)
self.assertEquals(str(group.rules[0].grants[0]), '::/0')
self.expect_http()
self.mox.ReplayAll()
group.connection = self.ec2
group.revoke('tcp', 80, 81, '::/0')
self.expect_http()
self.mox.ReplayAll()
self.ec2.delete_security_group(security_group_name)
self.expect_http()
self.mox.ReplayAll()
group.connection = self.ec2
rv = self.ec2.get_all_security_groups()
self.assertEqual(len(rv), 1)
self.assertEqual(rv[0].name, 'default')
self.manager.delete_project(project)
self.manager.delete_user(user)
return
def test_authorize_revoke_security_group_foreign_group(self):
"""
Test that we can grant and revoke another security group access

View File

@ -97,6 +97,27 @@ class NetworkTestCase(test.TrialTestCase):
self.context.project_id = self.projects[project_num].id
self.network.deallocate_fixed_ip(self.context, address)
def test_private_ipv6(self):
"""Make sure ipv6 is OK"""
if FLAGS.use_ipv6:
instance_ref = self._create_instance(1)
network_ref = db.project_get_network(
self.context,
self.context.project_id)
address_v6 = db.instance_get_fixed_address_v6(
self.context,
instance_ref['id'])
self.assertEqual(instance_ref['mac_address'],
utils.to_mac(address_v6))
instance_ref2 = db.fixed_ip_get_instance_v6(
self.context,
address_v6)
self.assertEqual(instance_ref['id'], instance_ref2['id'])
self.assertEqual(address_v6,
utils.to_global_ipv6(
network_ref['cidr_v6'],
instance_ref['mac_address']))
def test_public_network_association(self):
"""Makes sure that we can allocaate a public ip"""
# TODO(vish): better way of adding floating ips

View File

@ -30,6 +30,8 @@ import subprocess
import socket
import sys
from xml.sax import saxutils
import re
import netaddr
from twisted.internet.threads import deferToThread
@ -45,10 +47,14 @@ TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
def import_class(import_str):
"""Returns a class from a string including module and class"""
mod_str, _sep, class_str = import_str.rpartition('.')
logging.debug(import_str)
try:
__import__(mod_str)
return getattr(sys.modules[mod_str], class_str)
except (ImportError, ValueError, AttributeError):
logging.debug(ImportError)
logging.debug(ValueError)
logging.debug(AttributeError)
raise exception.NotFound('Class %s cannot be found' % class_str)
@ -165,6 +171,39 @@ def get_my_ip():
return "127.0.0.1"
def get_my_linklocal(interface):
if getattr(FLAGS, 'fake_tests', None):
return 'fe00::'
try:
if_str = execute("ifconfig %s" % interface)
condition = "\s+inet6\s+addr:\s+([0-9a-f:]+/\d+)\s+Scope:Link"
links = [re.search(condition, x) for x in if_str[0].split('\n')]
address = [w.group(1) for w in links if w is not None]
if address[0] is not None:
return address[0]
else:
return None
except RuntimeError as ex:
logging.warn("Couldn't get Link Local IP of %s :%s", interface, ex)
return None
def to_global_ipv6(prefix, mac):
mac64 = netaddr.EUI(mac).eui64().words
int_addr = int(''.join(['%02x' % i for i in mac64]), 16)
mac64_addr = netaddr.IPAddress(int_addr)
maskIP = netaddr.IPNetwork(prefix).ip
return (mac64_addr ^ netaddr.IPAddress('::0200:0:0:0') | maskIP).format()
def to_mac(ipv6_address):
address = netaddr.IPAddress(ipv6_address)
mask1 = netaddr.IPAddress("::ffff:ffff:ffff:ffff")
mask2 = netaddr.IPAddress("::0200:0:0:0")
mac64 = netaddr.EUI(int(address & mask1 ^ mask2)).words
return ":".join(["%02x" % i for i in mac64[0:3] + mac64[5:8]])
def isotime(at=None):
if not at:
at = datetime.datetime.utcnow()

View File

@ -23,6 +23,7 @@
<filterref filter="nova-instance-%(name)s">
<parameter name="IP" value="%(ip_address)s" />
<parameter name="DHCPSERVER" value="%(dhcp_server)s" />
<parameter name="RASERVER" value="%(ra_server)s" />
</filterref>
</interface>
<serial type="file">

View File

@ -17,6 +17,7 @@
<filterref filter="nova-instance-%(name)s">
<parameter name="IP" value="%(ip_address)s" />
<parameter name="DHCPSERVER" value="%(dhcp_server)s" />
<parameter name="RASERVER" value="%(ra_server)s" />
</filterref>
</interface>
<console type="file">

View File

@ -514,6 +514,8 @@ class LibvirtConnection(object):
instance['id'])
# Assume that the gateway also acts as the dhcp server.
dhcp_server = network['gateway']
#TODO ipv6
ra_server = network['ra_server']
xml_info = {'type': FLAGS.libvirt_type,
'name': instance['name'],
'basepath': os.path.join(FLAGS.instances_path,
@ -523,11 +525,13 @@ class LibvirtConnection(object):
'bridge_name': network['bridge'],
'mac_address': instance['mac_address'],
'ip_address': ip_address,
'dhcp_server': dhcp_server}
'dhcp_server': dhcp_server,
'ra_server': ra_server}
if rescue:
libvirt_xml = self.rescue_xml % xml_info
else:
libvirt_xml = self.libvirt_xml % xml_info
logging.debug('instance %s: finished toXML method', instance['name'])
return libvirt_xml
@ -701,6 +705,7 @@ class NWFilterFirewall(object):
<filterref filter='no-arp-spoofing'/>
<filterref filter='allow-dhcp-server'/>
<filterref filter='nova-allow-dhcp-server'/>
<filterref filter='nova-allow-ra-server'/>
<filterref filter='nova-base-ipv4'/>
<filterref filter='nova-base-ipv6'/>
</filter>'''
@ -722,6 +727,14 @@ class NWFilterFirewall(object):
</rule>
</filter>'''
nova_ra_filter = '''<filter name='nova-allow-ra-server' chain='root'>
<uuid>d707fa71-4fb5-4b27-9ab7-ba5ca19c8804</uuid>
<rule action='accept' direction='inout'
priority='100'>
<icmpv6 srcipaddr='$RASERVER'/>
</rule>
</filter>'''
def nova_base_ipv4_filter(self):
retval = "<filter name='nova-base-ipv4' chain='ipv4'>"
for protocol in ['tcp', 'udp', 'icmp']:
@ -736,13 +749,13 @@ class NWFilterFirewall(object):
def nova_base_ipv6_filter(self):
retval = "<filter name='nova-base-ipv6' chain='ipv6'>"
for protocol in ['tcp', 'udp', 'icmp']:
for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
for direction, action, priority in [('out', 'accept', 399),
('inout', 'drop', 400)]:
retval += """<rule action='%s' direction='%s' priority='%d'>
<%s-ipv6 />
<%s />
</rule>""" % (action, direction,
priority, protocol)
priority, protocol)
retval += '</filter>'
return retval
@ -755,6 +768,15 @@ class NWFilterFirewall(object):
retval += '</filter>'
return retval
def nova_project_filter_v6(self, project, net, mask):
retval = "<filter name='nova-project-%s-v6' chain='ipv6'>" % project
for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']:
retval += """<rule action='accept' direction='inout' priority='200'>
<%s srcipaddr='%s' srcipmask='%s' />
</rule>""" % (protocol, net, mask)
retval += '</filter>'
return retval
def _define_filter(self, xml):
if callable(xml):
xml = xml()
@ -766,6 +788,11 @@ class NWFilterFirewall(object):
net = IPy.IP(cidr)
return str(net.net()), str(net.netmask())
@staticmethod
def _get_ip_version(cidr):
net = IPy.IP(cidr)
return int(net.version())
@defer.inlineCallbacks
def setup_nwfilters_for_instance(self, instance):
"""
@ -777,6 +804,7 @@ class NWFilterFirewall(object):
yield self._define_filter(self.nova_base_ipv4_filter)
yield self._define_filter(self.nova_base_ipv6_filter)
yield self._define_filter(self.nova_dhcp_filter)
yield self._define_filter(self.nova_ra_filter)
yield self._define_filter(self.nova_base_filter)
nwfilter_xml = "<filter name='nova-instance-%s' chain='root'>\n" \
@ -787,12 +815,22 @@ class NWFilterFirewall(object):
network_ref = db.project_get_network(context.get_admin_context(),
instance['project_id'])
net, mask = self._get_net_and_mask(network_ref['cidr'])
if(FLAGS.use_ipv6):
net_v6, mask_v6 = self._get_net_and_mask(
network_ref['cidr_v6'])
project_filter = self.nova_project_filter(instance['project_id'],
net, mask)
yield self._define_filter(project_filter)
nwfilter_xml += " <filterref filter='nova-project-%s' />\n" % \
instance['project_id']
if(FLAGS.use_ipv6):
project_filter_v6 = self.nova_project_filter_v6(
instance['project_id'],
net_v6, mask_v6)
yield self._define_filter(project_filter_v6)
nwfilter_xml += \
" <filterref filter='nova-project-%s-v6' />\n" % \
instance['project_id']
for security_group in instance.security_groups:
yield self.ensure_security_group_filter(security_group['id'])
@ -812,12 +850,21 @@ class NWFilterFirewall(object):
security_group = db.security_group_get(context.get_admin_context(),
security_group_id)
rule_xml = ""
version = 4
v6protocol = {'tcp':'tcp-ipv6', 'udp':'udp-ipv6', 'icmp':'icmpv6'}
for rule in security_group.rules:
rule_xml += "<rule action='accept' direction='in' priority='300'>"
if rule.cidr:
version = self._get_ip_version(rule.cidr)
net, mask = self._get_net_and_mask(rule.cidr)
rule_xml += "<%s srcipaddr='%s' srcipmask='%s' " % \
(rule.protocol, net, mask)
if(FLAGS.use_ipv6 and version == 6):
rule_xml += "<%s " % v6protocol[rule.protocol]
rule_xml += "srcipaddr='%s' " % net
rule_xml += "srcipmask='%s' " % mask
else:
rule_xml += "<%s " % rule.protocol
rule_xml += "srcipaddr='%s' " % net
rule_xml += "srcipmask='%s' " % mask
if rule.protocol in ['tcp', 'udp']:
rule_xml += "dstportstart='%s' dstportend='%s' " % \
(rule.from_port, rule.to_port)
@ -832,6 +879,9 @@ class NWFilterFirewall(object):
rule_xml += '/>\n'
rule_xml += "</rule>\n"
xml = "<filter name='nova-secgroup-%s' chain='ipv4'>%s</filter>" % \
(security_group_id, rule_xml,)
xml = "<filter name='nova-secgroup-%s' " % security_group_id
if(FLAGS.use_ipv6):
xml += "chain='root'>%s</filter>" % rule_xml
else:
xml += "chain='ipv4'>%s</filter>" % rule_xml
return xml

View File

@ -36,7 +36,7 @@ flags.DEFINE_string('suite', None, 'Specific test suite to run ' + SUITE_NAMES)
# TODO(devamcar): Use random tempfile
ZIP_FILENAME = '/tmp/nova-me-x509.zip'
TEST_PREFIX = 'test%s' % int(random.random()*1000000)
TEST_PREFIX = 'test%s' % int(random.random() * 1000000)
TEST_USERNAME = '%suser' % TEST_PREFIX
TEST_PROJECTNAME = '%sproject' % TEST_PREFIX
@ -89,4 +89,3 @@ class UserTests(AdminSmokeTestCase):
if __name__ == "__main__":
suites = {'user': unittest.makeSuite(UserTests)}
sys.exit(base.run_tests(suites))

View File

@ -17,6 +17,7 @@
# under the License.
import boto
import boto_v6
import commands
import httplib
import os
@ -69,6 +70,17 @@ class SmokeTestCase(unittest.TestCase):
'test.')
parts = self.split_clc_url(clc_url)
if FLAGS.use_ipv6:
return boto_v6.connect_ec2(aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
is_secure=parts['is_secure'],
region=RegionInfo(None,
'nova',
parts['ip']),
port=parts['port'],
path='/services/Cloud',
**kwargs)
return boto.connect_ec2(aws_access_key_id=access_key,
aws_secret_access_key=secret_key,
is_secure=parts['is_secure'],
@ -115,7 +127,8 @@ class SmokeTestCase(unittest.TestCase):
return True
def upload_image(self, bucket_name, image):
cmd = 'euca-upload-bundle -b %s -m /tmp/%s.manifest.xml' % (bucket_name, image)
cmd = 'euca-upload-bundle -b '
cmd += '%s -m /tmp/%s.manifest.xml' % (bucket_name, image)
status, output = commands.getstatusoutput(cmd)
if status != 0:
print '%s -> \n %s' % (cmd, output)
@ -130,6 +143,7 @@ class SmokeTestCase(unittest.TestCase):
raise Exception(output)
return True
def run_tests(suites):
argv = FLAGS(sys.argv)
@ -151,4 +165,3 @@ def run_tests(suites):
else:
for suite in suites.itervalues():
unittest.TextTestRunner(verbosity=2).run(suite)

View File

@ -33,6 +33,7 @@ DEFINE_bool = DEFINE_bool
# __GLOBAL FLAGS ONLY__
# Define any app-specific flags in their own files, docs at:
# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#39
DEFINE_string('region', 'nova', 'Region to use')
DEFINE_string('test_image', 'ami-tiny', 'Image to use for launch tests')
DEFINE_string('use_ipv6', True, 'use the ipv6 or not')

View File

@ -0,0 +1,180 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import commands
import os
import random
import socket
import sys
import time
import unittest
from smoketests import flags
from smoketests import base
from smoketests import user_smoketests
#Note that this test should run from
#public network (outside of private network segments)
#Please set EC2_URL correctly
#You should use admin account in this test
FLAGS = flags.FLAGS
TEST_PREFIX = 'test%s' % int(random.random() * 1000000)
TEST_BUCKET = '%s_bucket' % TEST_PREFIX
TEST_KEY = '%s_key' % TEST_PREFIX
TEST_KEY2 = '%s_key2' % TEST_PREFIX
TEST_DATA = {}
class InstanceTestsFromPublic(user_smoketests.UserSmokeTestCase):
def test_001_can_create_keypair(self):
key = self.create_key_pair(self.conn, TEST_KEY)
self.assertEqual(key.name, TEST_KEY)
def test_002_security_group(self):
security_group_name = "".join(random.choice("sdiuisudfsdcnpaqwertasd")
for x in range(random.randint(4, 8)))
group = self.conn.create_security_group(security_group_name,
'test group')
group.connection = self.conn
group.authorize('tcp', 22, 22, '0.0.0.0/0')
if FLAGS.use_ipv6:
group.authorize('tcp', 22, 22, '::/0')
reservation = self.conn.run_instances(FLAGS.test_image,
key_name=TEST_KEY,
security_groups=[security_group_name],
instance_type='m1.tiny')
self.data['security_group_name'] = security_group_name
self.data['group'] = group
self.data['instance_id'] = reservation.instances[0].id
def test_003_instance_with_group_runs_within_60_seconds(self):
reservations = self.conn.get_all_instances([self.data['instance_id']])
instance = reservations[0].instances[0]
# allow 60 seconds to exit pending with IP
for x in xrange(60):
instance.update()
if instance.state == u'running':
break
time.sleep(1)
else:
self.fail('instance failed to start')
ip = reservations[0].instances[0].private_dns_name
self.failIf(ip == '0.0.0.0')
self.data['private_ip'] = ip
if FLAGS.use_ipv6:
ipv6 = reservations[0].instances[0].dns_name_v6
self.failIf(ipv6 is None)
self.data['ip_v6'] = ipv6
def test_004_can_ssh_to_ipv6(self):
if FLAGS.use_ipv6:
for x in xrange(20):
try:
conn = self.connect_ssh(
self.data['ip_v6'], TEST_KEY)
conn.close()
except Exception as ex:
print ex
time.sleep(1)
else:
break
else:
self.fail('could not ssh to instance')
def test_012_can_create_instance_with_keypair(self):
if 'instance_id' in self.data:
self.conn.terminate_instances([self.data['instance_id']])
reservation = self.conn.run_instances(FLAGS.test_image,
key_name=TEST_KEY,
instance_type='m1.tiny')
self.assertEqual(len(reservation.instances), 1)
self.data['instance_id'] = reservation.instances[0].id
def test_013_instance_runs_within_60_seconds(self):
reservations = self.conn.get_all_instances([self.data['instance_id']])
instance = reservations[0].instances[0]
# allow 60 seconds to exit pending with IP
for x in xrange(60):
instance.update()
if instance.state == u'running':
break
time.sleep(1)
else:
self.fail('instance failed to start')
ip = reservations[0].instances[0].private_dns_name
self.failIf(ip == '0.0.0.0')
self.data['private_ip'] = ip
if FLAGS.use_ipv6:
ipv6 = reservations[0].instances[0].dns_name_v6
self.failIf(ipv6 is None)
self.data['ip_v6'] = ipv6
def test_014_can_not_ping_private_ip(self):
for x in xrange(4):
# ping waits for 1 second
status, output = commands.getstatusoutput(
'ping -c1 %s' % self.data['private_ip'])
if status == 0:
self.fail('can ping private ip from public network')
if FLAGS.use_ipv6:
status, output = commands.getstatusoutput(
'ping6 -c1 %s' % self.data['ip_v6'])
if status == 0:
self.fail('can ping ipv6 from public network')
else:
pass
def test_015_can_not_ssh_to_private_ip(self):
for x in xrange(1):
try:
conn = self.connect_ssh(self.data['private_ip'], TEST_KEY)
conn.close()
except Exception:
time.sleep(1)
else:
self.fail('can ssh for ipv4 address from public network')
if FLAGS.use_ipv6:
for x in xrange(1):
try:
conn = self.connect_ssh(
self.data['ip_v6'], TEST_KEY)
conn.close()
except Exception:
time.sleep(1)
else:
self.fail('can ssh for ipv6 address from public network')
def test_999_tearDown(self):
self.delete_key_pair(self.conn, TEST_KEY)
security_group_name = self.data['security_group_name']
group = self.data['group']
if group:
group.revoke('tcp', 22, 22, '0.0.0.0/0')
if FLAGS.use_ipv6:
group.revoke('tcp', 22, 22, '::/0')
self.conn.delete_security_group(security_group_name)
if 'instance_id' in self.data:
self.conn.terminate_instances([self.data['instance_id']])
if __name__ == "__main__":
suites = {'instance': unittest.makeSuite(InstanceTestsFromPublic)}
sys.exit(base.run_tests(suites))

View File

@ -37,7 +37,7 @@ flags.DEFINE_string('bundle_kernel', 'openwrt-x86-vmlinuz',
flags.DEFINE_string('bundle_image', 'openwrt-x86-ext2.image',
'Local image file to use for bundling tests')
TEST_PREFIX = 'test%s' % int (random.random()*1000000)
TEST_PREFIX = 'test%s' % int(random.random() * 1000000)
TEST_BUCKET = '%s_bucket' % TEST_PREFIX
TEST_KEY = '%s_key' % TEST_PREFIX
TEST_DATA = {}
@ -71,7 +71,7 @@ class ImageTests(UserSmokeTestCase):
def test_006_can_register_kernel(self):
kernel_id = self.conn.register_image('%s/%s.manifest.xml' %
(TEST_BUCKET, FLAGS.bundle_kernel))
(TEST_BUCKET, FLAGS.bundle_kernel))
self.assert_(kernel_id is not None)
self.data['kernel_id'] = kernel_id
@ -83,7 +83,7 @@ class ImageTests(UserSmokeTestCase):
time.sleep(1)
else:
print image.state
self.assert_(False) # wasn't available within 10 seconds
self.assert_(False) # wasn't available within 10 seconds
self.assert_(image.type == 'machine')
for i in xrange(10):
@ -92,7 +92,7 @@ class ImageTests(UserSmokeTestCase):
break
time.sleep(1)
else:
self.assert_(False) # wasn't available within 10 seconds
self.assert_(False) # wasn't available within 10 seconds
self.assert_(kernel.type == 'kernel')
def test_008_can_describe_image_attribute(self):
@ -137,20 +137,23 @@ class InstanceTests(UserSmokeTestCase):
self.data['instance_id'] = reservation.instances[0].id
def test_003_instance_runs_within_60_seconds(self):
reservations = self.conn.get_all_instances([data['instance_id']])
reservations = self.conn.get_all_instances([self.data['instance_id']])
instance = reservations[0].instances[0]
# allow 60 seconds to exit pending with IP
for x in xrange(60):
instance.update()
if instance.state == u'running':
break
break
time.sleep(1)
else:
self.fail('instance failed to start')
ip = reservations[0].instances[0].private_dns_name
self.failIf(ip == '0.0.0.0')
self.data['private_ip'] = ip
print self.data['private_ip']
if FLAGS.use_ipv6:
ipv6 = reservations[0].instances[0].dns_name_v6
self.failIf(ipv6 is None)
self.data['ip_v6'] = ipv6
def test_004_can_ping_private_ip(self):
for x in xrange(120):
@ -159,6 +162,11 @@ class InstanceTests(UserSmokeTestCase):
'ping -c1 %s' % self.data['private_ip'])
if status == 0:
break
if FLAGS.use_ipv6:
status, output = commands.getstatusoutput(
'ping6 -c1 %s' % self.data['ip_v6'])
if status == 0:
break
else:
self.fail('could not ping instance')
@ -174,6 +182,19 @@ class InstanceTests(UserSmokeTestCase):
else:
self.fail('could not ssh to instance')
if FLAGS.use_ipv6:
for x in xrange(30):
try:
conn = self.connect_ssh(
self.data['ip_v6'], TEST_KEY)
conn.close()
except Exception:
time.sleep(1)
else:
break
else:
self.fail('could not ssh to instance')
def test_006_can_allocate_elastic_ip(self):
result = self.conn.allocate_address()
self.assertTrue(hasattr(result, 'public_ip'))
@ -206,8 +227,8 @@ class InstanceTests(UserSmokeTestCase):
def test_999_tearDown(self):
self.delete_key_pair(self.conn, TEST_KEY)
if self.data.has_key('instance_id'):
self.conn.terminate_instances([data['instance_id']])
if 'instance_id' in self.data:
self.conn.terminate_instances([self.data['instance_id']])
class VolumeTests(UserSmokeTestCase):