diff --git a/bin/nova-manage b/bin/nova-manage index 0c1b621ed7..e0f6f9323c 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -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), diff --git a/contrib/boto_v6/__init__.py b/contrib/boto_v6/__init__.py new file mode 100644 index 0000000000..9fec157f17 --- /dev/null +++ b/contrib/boto_v6/__init__.py @@ -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) diff --git a/contrib/boto_v6/ec2/__init__.py b/contrib/boto_v6/ec2/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contrib/boto_v6/ec2/connection.py b/contrib/boto_v6/ec2/connection.py new file mode 100644 index 0000000000..151b76a556 --- /dev/null +++ b/contrib/boto_v6/ec2/connection.py @@ -0,0 +1,41 @@ +''' +Created on 2010/12/20 + +@author: Nachi Ueno +''' +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)]) diff --git a/contrib/boto_v6/ec2/instance.py b/contrib/boto_v6/ec2/instance.py new file mode 100644 index 0000000000..255114935e --- /dev/null +++ b/contrib/boto_v6/ec2/instance.py @@ -0,0 +1,33 @@ +''' +Created on 2010/12/20 + +@author: Nachi Ueno +''' +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 diff --git a/contrib/nova.sh b/contrib/nova.sh index 30df4edb65..bc46740ada 100755 --- a/contrib/nova.sh +++ b/contrib/nova.sh @@ -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 < /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()) diff --git a/nova/network/manager.py b/nova/network/manager.py index a7298b47f0..ceea6966fe 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -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): diff --git a/nova/test.py b/nova/test.py index 5c2a728196..ee2fc2720a 100644 --- a/nova/test.py +++ b/nova/test.py @@ -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 diff --git a/nova/tests/api_unittest.py b/nova/tests/api_unittest.py index 33d4cb294e..a508235c43 100644 --- a/nova/tests/api_unittest.py +++ b/nova/tests/api_unittest.py @@ -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 diff --git a/nova/tests/network_unittest.py b/nova/tests/network_unittest.py index 6f4705719c..0a4b50e96e 100644 --- a/nova/tests/network_unittest.py +++ b/nova/tests/network_unittest.py @@ -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 diff --git a/nova/utils.py b/nova/utils.py index 142584df8b..211a2cb75f 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -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() diff --git a/nova/virt/libvirt.qemu.xml.template b/nova/virt/libvirt.qemu.xml.template index 2538b1adef..0ffe17e8dd 100644 --- a/nova/virt/libvirt.qemu.xml.template +++ b/nova/virt/libvirt.qemu.xml.template @@ -23,6 +23,7 @@ + diff --git a/nova/virt/libvirt.uml.xml.template b/nova/virt/libvirt.uml.xml.template index bb8b479113..0d355b81cf 100644 --- a/nova/virt/libvirt.uml.xml.template +++ b/nova/virt/libvirt.uml.xml.template @@ -17,6 +17,7 @@ + diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 18085089f3..71d9f781dd 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -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): + ''' @@ -722,6 +727,14 @@ class NWFilterFirewall(object): ''' + nova_ra_filter = ''' + d707fa71-4fb5-4b27-9ab7-ba5ca19c8804 + + + + ''' + def nova_base_ipv4_filter(self): retval = "" for protocol in ['tcp', 'udp', 'icmp']: @@ -736,13 +749,13 @@ class NWFilterFirewall(object): def nova_base_ipv6_filter(self): retval = "" - 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 += """ - <%s-ipv6 /> + <%s /> """ % (action, direction, - priority, protocol) + priority, protocol) retval += '' return retval @@ -755,6 +768,15 @@ class NWFilterFirewall(object): retval += '' return retval + def nova_project_filter_v6(self, project, net, mask): + retval = "" % project + for protocol in ['tcp-ipv6', 'udp-ipv6', 'icmpv6']: + retval += """ + <%s srcipaddr='%s' srcipmask='%s' /> + """ % (protocol, net, mask) + retval += '' + 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 = "\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 += " \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 += \ + " \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 += "" 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 += "\n" - xml = "%s" % \ - (security_group_id, rule_xml,) + xml = " \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) - diff --git a/smoketests/flags.py b/smoketests/flags.py index ae4d095085..35f432a778 100644 --- a/smoketests/flags.py +++ b/smoketests/flags.py @@ -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') diff --git a/smoketests/public_network_smoketests.py b/smoketests/public_network_smoketests.py new file mode 100644 index 0000000000..bfc2b20ba1 --- /dev/null +++ b/smoketests/public_network_smoketests.py @@ -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)) diff --git a/smoketests/user_smoketests.py b/smoketests/user_smoketests.py index d29e3aea32..8bb2ddebb2 100644 --- a/smoketests/user_smoketests.py +++ b/smoketests/user_smoketests.py @@ -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):