diff options
-rw-r--r-- | nova/network/model.py | 12 | ||||
-rw-r--r-- | nova/tests/network/test_network_info.py | 76 | ||||
-rw-r--r-- | nova/virt/netutils.py | 114 |
3 files changed, 187 insertions, 15 deletions
diff --git a/nova/network/model.py b/nova/network/model.py index 28651737b..cf01d98cf 100644 --- a/nova/network/model.py +++ b/nova/network/model.py @@ -293,6 +293,13 @@ class VIF(Model): return vif +def get_netmask(ip, subnet): + """Returns the netmask appropriate for injection into a guest.""" + if ip['version'] == 4: + return str(subnet.as_netaddr().netmask) + return subnet.as_netaddr()._prefixlen + + class NetworkInfo(list): """Stores and manipulates network information for a Nova instance.""" @@ -325,10 +332,7 @@ class NetworkInfo(list): return ip['address'] def fixed_ip_dict(ip, subnet): - if ip['version'] == 4: - netmask = str(subnet.as_netaddr().netmask) - else: - netmask = subnet.as_netaddr()._prefixlen + netmask = get_netmask(ip, subnet) return {'ip': ip['address'], 'enabled': '1', diff --git a/nova/tests/network/test_network_info.py b/nova/tests/network/test_network_info.py index 960f3583a..56522e6a5 100644 --- a/nova/tests/network/test_network_info.py +++ b/nova/tests/network/test_network_info.py @@ -2,6 +2,7 @@ # Copyright 2011 OpenStack Foundation # All Rights Reserved. +# Copyright 2013 IBM Corp. # # 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 @@ -19,6 +20,7 @@ from nova import exception from nova.network import model from nova import test from nova.tests import fake_network_cache_model +from nova.virt import netutils class RouteTests(test.TestCase): @@ -347,3 +349,77 @@ class NetworkInfoTests(test.TestCase): [fake_network_cache_model.new_ip({'address': '10.10.0.2'}), fake_network_cache_model.new_ip( {'address': '10.10.0.3'})] * 4) + + def _test_injected_network_template(self, should_inject, use_ipv6=False, + legacy=False): + """Check that netutils properly decides whether to inject based on + whether the supplied subnet is static or dynamic. + """ + network = fake_network_cache_model.new_network({'subnets': []}) + if should_inject: + network.add_subnet(fake_network_cache_model.new_subnet()) + if use_ipv6: + gateway_ip = fake_network_cache_model.new_ip(dict( + address='1234:567::1')) + ip = fake_network_cache_model.new_ip(dict( + address='1234:567::2')) + subnet_dict = dict( + cidr='1234:567::/48', + gateway=gateway_ip, + ips=[ip]) + network.add_subnet(fake_network_cache_model.new_subnet( + subnet_dict)) + else: + subnet_dict = dict(dhcp_server='10.10.0.1') + network.add_subnet(fake_network_cache_model.new_subnet( + subnet_dict)) + # Behave as though CONF.flat_injected is True + network['meta']['injected'] = True + vif = fake_network_cache_model.new_vif({'network': network}) + ninfo = model.NetworkInfo([vif]) + if legacy: + ninfo = ninfo.legacy() + + template = netutils.get_injected_network_template(ninfo, + use_ipv6=use_ipv6) + + # NOTE(bnemec): There is a bug with legacy network info that causes + # it to inject regardless of whether the network is static or dynamic. + # This can't be fixed without changes that would potentially break + # existing code, so until legacy network info goes away this test + # will just ignore the improper behavior. + if not should_inject and not legacy: + self.assertTrue(template is None) + else: + self.assertTrue('auto eth0' in template) + self.assertTrue('iface eth0 inet static' in template) + self.assertTrue('address 10.10.0.2' in template) + self.assertTrue('netmask 255.255.255.0' in template) + self.assertTrue('broadcast 10.10.0.255' in template) + self.assertTrue('gateway 10.10.0.1' in template) + self.assertTrue('dns-nameservers 1.2.3.4 2.3.4.5' in template) + if use_ipv6: + self.assertTrue('iface eth0 inet6 static' in template) + self.assertTrue('address 1234:567::2' in template) + self.assertTrue('netmask 48' in template) + self.assertTrue('gateway 1234:567::1' in template) + + def test_injection_static(self): + self._test_injected_network_template(should_inject=True) + + def test_injection_static_ipv6(self): + self._test_injected_network_template(should_inject=True, use_ipv6=True) + + def test_injection_dynamic(self): + self._test_injected_network_template(should_inject=False) + + def test_injection_static_legacy(self): + self._test_injected_network_template(should_inject=True, legacy=True) + + def test_injection_static_ipv6_legacy(self): + self._test_injected_network_template(should_inject=True, + use_ipv6=True, + legacy=True) + + def test_injection_dynamic_legacy(self): + self._test_injected_network_template(should_inject=False, legacy=True) diff --git a/nova/virt/netutils.py b/nova/virt/netutils.py index d38197948..301fa3b09 100644 --- a/nova/virt/netutils.py +++ b/nova/virt/netutils.py @@ -4,6 +4,7 @@ # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # Copyright (c) 2010 Citrix Systems, Inc. +# Copyright 2013 IBM Corp. # # 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 @@ -25,6 +26,8 @@ import netaddr from oslo.config import cfg +from nova.network import model + CONF = cfg.CONF CONF.import_opt('use_ipv6', 'nova.netconf') CONF.import_opt('injected_network_template', 'nova.virt.disk.api') @@ -55,6 +58,91 @@ def get_ip_version(cidr): return int(net.version) +def get_non_legacy_network_template(network_info, use_ipv6=CONF.use_ipv6, + template=CONF.injected_network_template): + """A new version of get_injected_network_template that does not rely on + legacy network info. + + Returns a rendered network template for the given network_info. When + libvirt's dependency on using legacy network info for network config + injection goes away, this function can replace + get_injected_network_template entirely. + + :param network_info: + :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` + :param use_ipv6: If False, do not return IPv6 template information + even if an IPv6 subnet is present in network_info. + :param template: Path to the interfaces template file. + """ + if not (network_info and template): + return + + nets = [] + ifc_num = -1 + ipv6_is_available = False + + for vif in network_info: + if not vif['network'] or not vif['network']['subnets']: + continue + + network = vif['network'] + # NOTE(bnemec): The template only supports a single subnet per + # interface and I'm not sure how/if that can be fixed, so this + # code only takes the first subnet of the appropriate type. + subnet_v4 = [i for i in network['subnets'] if i['version'] == 4][0] + subnet_v6 = [i for i in network['subnets'] if i['version'] == 6] + if subnet_v6: + subnet_v6 = subnet_v6[0] + + ifc_num += 1 + + if (not network.get_meta('injected') or not subnet_v4['ips'] or + subnet_v4.get_meta('dhcp_server') is not None): + continue + + ip = subnet_v4['ips'][0] + address = ip['address'] + netmask = model.get_netmask(ip, subnet_v4) + gateway = '' + if subnet_v4['gateway']: + gateway = subnet_v4['gateway']['address'] + broadcast = str(subnet_v4.as_netaddr().broadcast) + dns = ' '.join([i['address'] for i in subnet_v4['dns']]) + # NOTE(bnemec): I don't think this code would handle a pure IPv6 + # environment properly, but I don't have such an environment in + # which to test/fix that. + address_v6 = None + gateway_v6 = None + netmask_v6 = None + have_ipv6 = (use_ipv6 and subnet_v6) + if have_ipv6: + if subnet_v6['ips']: + ipv6_is_available = True + ip_v6 = subnet_v6['ips'][0] + address_v6 = ip_v6['address'] + netmask_v6 = model.get_netmask(ip_v6, subnet_v6) + gateway_v6 = '' + if subnet_v6['gateway']: + gateway_v6 = subnet_v6['gateway']['address'] + + net_info = {'name': 'eth%d' % ifc_num, + 'address': address, + 'netmask': netmask, + 'gateway': gateway, + 'broadcast': broadcast, + 'dns': dns, + 'address_v6': address_v6, + 'gateway_v6': gateway_v6, + 'netmask_v6': netmask_v6, + } + nets.append(net_info) + + if not nets: + return + + return build_template(template, nets, ipv6_is_available) + + def get_injected_network_template(network_info, use_ipv6=CONF.use_ipv6, template=CONF.injected_network_template): """ @@ -62,17 +150,20 @@ def get_injected_network_template(network_info, use_ipv6=CONF.use_ipv6, :param network_info: :py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info` - - Note: this code actually depends on the legacy network_info, but will - convert the type itself if necessary. + :param use_ipv6: If False, do not return IPv6 template information + even if an IPv6 subnet is present in network_info. + :param template: Path to the interfaces template file. """ - if network_info is None: - return None + if not (network_info and template): + return - # the code below depends on the legacy 'network_info' - if hasattr(network_info, 'legacy'): - network_info = network_info.legacy() + # If we're passed new network_info, make use of it instead of forcing + # it to the legacy format required below. + if isinstance(network_info, model.NetworkInfo): + return get_non_legacy_network_template(network_info, + use_ipv6, + template) nets = [] ifc_num = -1 @@ -107,11 +198,12 @@ def get_injected_network_template(network_info, use_ipv6=CONF.use_ipv6, nets.append(net_info) if have_injected_networks is False: - return None + return + + return build_template(template, nets, ipv6_is_available) - if not template: - return None +def build_template(template, nets, ipv6_is_available): _late_load_cheetah() ifc_template = open(template).read() |