From 9728ae541fc211e66260410b5dcb3bb3a92361ec Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Fri, 20 Jan 2012 17:25:08 -0800 Subject: Add support for pluggable l3 backends This will allow us to support backends other than linux_net (i.e. quantum L3 when it is available) for defining L3 connectivity. Change-Id: I0b2ece2278bd68166741107a88cedd106d1ab651 --- nova/network/l3.py | 149 ++++++++++++++++++++++++++++++++++++++++ nova/network/manager.py | 61 ++++++---------- nova/network/quantum/manager.py | 28 ++++---- nova/tests/test_quantum.py | 4 +- nova/utils.py | 9 +++ 5 files changed, 193 insertions(+), 58 deletions(-) create mode 100644 nova/network/l3.py (limited to 'nova') diff --git a/nova/network/l3.py b/nova/network/l3.py new file mode 100644 index 000000000..034790f35 --- /dev/null +++ b/nova/network/l3.py @@ -0,0 +1,149 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 Nicira Networks, Inc +# 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 random + +from nova import flags +from nova import log as logging +from nova import utils +from nova.network import linux_net + +LOG = logging.getLogger("nova.network.l3") + +FLAGS = flags.FLAGS + + +class L3Driver(object): + """Abstract class that defines a generic L3 API""" + + def __init__(self, l3_lib=None): + raise NotImplementedError() + + def initialize(self, **kwargs): + """Set up basic L3 networking functionality""" + raise NotImplementedError() + + def initialize_network(self, network): + """Enable rules for a specific network""" + raise NotImplementedError() + + def initialize_gateway(self, network): + """Set up a gateway on this network""" + raise NotImplementedError() + + def is_initialized(self): + """:returns: True/False (whether the driver is initialized)""" + raise NotImplementedError() + + def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + """Add a floating IP bound to the fixed IP with an optional + l3_interface_id. Some drivers won't care about the + l3_interface_id so just pass None in that case""" + raise NotImplementedError() + + def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + raise NotImplementedError() + + def add_vpn(self, public_ip, port, private_ip): + raise NotImplementedError() + + def remove_vpn(self, public_ip, port, private_ip): + raise NotImplementedError() + + def teardown(self): + raise NotImplementedError() + + +class LinuxNetL3(L3Driver): + """L3 driver that uses linux_net as the backend""" + def __init__(self): + self.initialized = False + + def initialize(self, **kwargs): + if self.initialized: + return + LOG.debug("Initializing linux_net L3 driver") + linux_net.init_host() + linux_net.ensure_metadata_ip() + linux_net.metadata_forward() + self.initialized = True + + def is_initialized(self): + return self.initialized == True + + def initialize_network(self, cidr): + linux_net.add_snat_rule(cidr) + + def initialize_gateway(self, network_ref): + mac_address = utils.generate_mac_address() + dev = linux_net.plug(network_ref, mac_address, + gateway=(network_ref['gateway'] is not None)) + linux_net.initialize_gateway_device(dev, network_ref) + + def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + linux_net.bind_floating_ip(floating_ip, l3_interface_id) + linux_net.ensure_floating_forward(floating_ip, fixed_ip) + + def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + linux_net.unbind_floating_ip(floating_ip, l3_interface_id) + linux_net.remove_floating_forward(floating_ip, fixed_ip) + + def add_vpn(self, public_ip, port, private_ip): + linux_net.ensure_vpn_forward(public_ip, port, private_ip) + + def remove_vpn(self, public_ip, port, private_ip): + # Linux net currently doesn't implement any way of removing + # the VPN forwarding rules + pass + + def teardown(self): + pass + + +class NullL3(L3Driver): + """The L3 driver that doesn't do anything. This class can be used when + nova-network shuld not manipulate L3 forwarding at all (e.g., in a Flat + or FlatDHCP scenario""" + def __init__(self): + pass + + def initialize(self, **kwargs): + pass + + def is_initialized(self): + return True + + def initialize_network(self, cidr): + pass + + def initialize_gateway(self, network_ref): + pass + + def add_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + pass + + def remove_floating_ip(self, floating_ip, fixed_ip, l3_interface_id): + pass + + def add_vpn(self, public_ip, port, private_ip): + pass + + def remove_vpn(self, public_ip, port, private_ip): + pass + + def teardown(self): + pass diff --git a/nova/network/manager.py b/nova/network/manager.py index 3a0350bc2..350c01a69 100644 --- a/nova/network/manager.py +++ b/nova/network/manager.py @@ -155,8 +155,12 @@ network_opts = [ cfg.StrOpt('dhcp_domain', default='novalocal', help='domain to use for building the hostnames'), + cfg.StrOpt('l3_lib', + default='nova.network.l3.LinuxNetL3', + help="Indicates underlying L3 management library") ] + FLAGS = flags.FLAGS FLAGS.add_options(network_opts) @@ -264,16 +268,13 @@ class FloatingIP(object): fixed_address = floating_ip['fixed_ip']['address'] interface = floating_ip['interface'] try: - self.driver.bind_floating_ip(floating_ip['address'], - interface) + self.l3driver.add_floating_ip(floating_ip['address'], + fixed_address, floating_ip['interface']) except exception.ProcessExecutionError: msg = _('Interface %(interface)s not found' % locals()) LOG.debug(msg) raise exception.NoFloatingIpInterface(interface=interface) - self.driver.ensure_floating_forward(floating_ip['address'], - fixed_address) - @wrap_check_policy def allocate_for_instance(self, context, **kwargs): """Handles allocating the floating IP resources for an instance. @@ -458,7 +459,8 @@ class FloatingIP(object): self.host) try: # gogo driver time - self.driver.bind_floating_ip(floating_address, interface) + self.l3driver.add_floating_ip(floating_address, fixed_address, + interface) except exception.ProcessExecutionError as e: fixed_address = self.db.floating_ip_disassociate(context, floating_address) @@ -466,9 +468,6 @@ class FloatingIP(object): msg = _('Interface %(interface)s not found' % locals()) LOG.error(msg) raise exception.NoFloatingIpInterface(interface=interface) - raise - - self.driver.ensure_floating_forward(floating_address, fixed_address) @wrap_check_policy def disassociate_floating_ip(self, context, address, @@ -520,8 +519,7 @@ class FloatingIP(object): fixed_address = self.db.floating_ip_disassociate(context, address) # go go driver time - self.driver.unbind_floating_ip(address, interface) - self.driver.remove_floating_forward(address, fixed_address) + self.l3driver.remove_floating_ip(address, fixed_address, interface) @wrap_check_policy def get_floating_ip(self, context, id): @@ -694,6 +692,8 @@ class NetworkManager(manager.SchedulerDependentManager): # already imported ipam, import nova ipam here if not hasattr(self, 'ipam'): self._import_ipam_lib('nova.network.quantum.nova_ipam_lib') + l3_lib = kwargs.get("l3_lib", FLAGS.l3_lib) + self.l3driver = utils.import_object(l3_lib) super(NetworkManager, self).__init__(service_name='network', *args, **kwargs) @@ -1064,7 +1064,7 @@ class NetworkManager(manager.SchedulerDependentManager): self.add_virtual_interface(context, instance_id, network['id']) def add_virtual_interface(self, context, instance_id, network_id): - vif = {'address': self.generate_mac_address(), + vif = {'address': utils.generate_mac_address(), 'instance_id': instance_id, 'network_id': network_id, 'uuid': str(utils.gen_uuid())} @@ -1073,20 +1073,12 @@ class NetworkManager(manager.SchedulerDependentManager): try: return self.db.virtual_interface_create(context, vif) except exception.VirtualInterfaceCreateException: - vif['address'] = self.generate_mac_address() + vif['address'] = utils.generate_mac_address() else: self.db.virtual_interface_delete_by_instance(context, instance_id) raise exception.VirtualInterfaceMacAddressException() - def generate_mac_address(self): - """Generate an Ethernet MAC address.""" - mac = [0x02, 0x16, 0x3e, - random.randint(0x00, 0x7f), - random.randint(0x00, 0xff), - random.randint(0x00, 0xff)] - return ':'.join(map(lambda x: "%02x" % x, mac)) - @wrap_check_policy def add_fixed_ip_to_instance(self, context, instance_id, host, network_id): """Adds a fixed ip to an instance from specified network.""" @@ -1558,23 +1550,18 @@ class FlatDHCPManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): """Do any initialization that needs to be run if this is a standalone service. """ - self.driver.init_host() - self.driver.ensure_metadata_ip() - + self.l3driver.initialize() super(FlatDHCPManager, self).init_host() self.init_host_floating_ips() - self.driver.metadata_forward() - def _setup_network(self, context, network_ref): """Sets up network on this host.""" network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) - mac_address = self.generate_mac_address() - dev = self.driver.plug(network_ref, mac_address) - self.driver.initialize_gateway_device(dev, network_ref) + self.l3driver.initialize_gateway(network_ref) if not FLAGS.fake_network: + dev = self.driver.get_dev(network_ref) self.driver.update_dhcp(context, dev, network_ref) if(FLAGS.use_ipv6): self.driver.update_ra(context, dev, network_ref) @@ -1627,14 +1614,10 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): standalone service. """ - self.driver.init_host() - self.driver.ensure_metadata_ip() - + self.l3driver.initialize() NetworkManager.init_host(self) self.init_host_floating_ips() - self.driver.metadata_forward() - def allocate_fixed_ip(self, context, instance_id, network, **kwargs): """Gets a fixed ip from the pool.""" if kwargs.get('vpn', None): @@ -1711,17 +1694,15 @@ class VlanManager(RPCAllocateFixedIP, FloatingIP, NetworkManager): address = network_ref['vpn_public_address'] network_ref['dhcp_server'] = self._get_dhcp_ip(context, network_ref) - mac_address = self.generate_mac_address() - dev = self.driver.plug(network_ref, mac_address) - self.driver.initialize_gateway_device(dev, network_ref) + self.l3driver.initialize_gateway(network_ref) # NOTE(vish): only ensure this forward if the address hasn't been set # manually. if address == FLAGS.vpn_ip and hasattr(self.driver, "ensure_vpn_forward"): - self.driver.ensure_vpn_forward(FLAGS.vpn_ip, - network_ref['vpn_public_port'], - network_ref['vpn_private_address']) + self.l3driver.add_vpn(FLAGS.vpn_ip, + network_ref['vpn_public_port'], + network_ref['vpn_private_address']) if not FLAGS.fake_network: self.driver.update_dhcp(context, dev, network_ref) if(FLAGS.use_ipv6): diff --git a/nova/network/quantum/manager.py b/nova/network/quantum/manager.py index f209e7abc..518676be0 100644 --- a/nova/network/quantum/manager.py +++ b/nova/network/quantum/manager.py @@ -27,11 +27,10 @@ from nova import exception from nova import flags from nova import log as logging from nova.network import manager -from nova.network.quantum import quantum_connection from nova.network.quantum import melange_ipam_lib +from nova.network.quantum import quantum_connection from nova import utils - LOG = logging.getLogger("nova.network.quantum.manager") quantum_opts = [ @@ -93,10 +92,8 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): # Don't call into self.driver (linux_net) unless dhcp is enabled if not FLAGS.quantum_use_dhcp: return - - # Initialize forwarding rules for anything specified in - # FLAGS.fixed_range() - self.driver.init_host() + # Initialize general L3 networking + self.l3driver.initialize() # Initialize floating ip support (only works for nova ipam currently) if FLAGS.quantum_ipam_lib == 'nova.network.quantum.nova_ipam_lib': LOG.debug("Initializing FloatingIP support") @@ -104,13 +101,15 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): # Set up all the forwarding rules for any network that has a # gateway set. networks = self.get_all_networks() + cidrs = [] for net in networks: if net['gateway']: LOG.debug("Initializing NAT: %s (cidr: %s, gw: %s)" % ( net['label'], net['cidr'], net['gateway'])) - self.driver.add_snat_rule(net['cidr']) - self.driver.ensure_metadata_ip() - self.driver.metadata_forward() + cidrs.append(net['cidr']) + # .. and for each network + for c in cidrs: + self.l3driver.initialize_network(c) def _get_nova_id(self, instance=None): # When creating the network we need to pass in an identifier for @@ -213,9 +212,8 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): priority, cidr, gateway, gateway_v6, cidr_v6, dns1, dns2) - # Initialize forwarding if gateway is set - if gateway and FLAGS.quantum_use_dhcp: - self.driver.add_snat_rule(cidr) + # Initialize forwarding + self.l3driver.initialize_network(cidr) return [{'uuid': quantum_net_id}] @@ -408,10 +406,8 @@ class QuantumManager(manager.FloatingIP, manager.FlatManager): port = self.q_conn.get_port_by_attachment(q_tenant_id, quantum_net_id, interface_id) if not port: # No dhcp server has been started - mac_address = self.generate_mac_address() - dev = self.driver.plug(network_ref, mac_address, - gateway=(network_ref['gateway'] is not None)) - self.driver.initialize_gateway_device(dev, network_ref) + self.l3driver.initialize_gateway(network_ref) + dev = self.driver.get_dev(network_ref) LOG.debug("Intializing DHCP for network: %s" % network_ref) self.q_conn.create_and_attach_port(q_tenant_id, diff --git a/nova/tests/test_quantum.py b/nova/tests/test_quantum.py index 32f30fbf6..2e60bbc45 100644 --- a/nova/tests/test_quantum.py +++ b/nova/tests/test_quantum.py @@ -397,8 +397,8 @@ class QuantumNovaMACGenerationTestCase(QuantumNovaTestCase): def test_local_mac_address_creation(self): self.flags(use_melange_mac_generation=False) fake_mac = "ab:cd:ef:ab:cd:ef" - self.stubs.Set(manager.FlatManager, "generate_mac_address", - lambda x: fake_mac) + self.stubs.Set(utils, "generate_mac_address", + lambda: fake_mac) project_id = "fake_project1" ctx = context.RequestContext('user1', project_id) self._create_network(networks[0]) diff --git a/nova/utils.py b/nova/utils.py index 675ec8623..0d3067fd3 100644 --- a/nova/utils.py +++ b/nova/utils.py @@ -1399,3 +1399,12 @@ def service_is_up(service): # Timestamps in DB are UTC. elapsed = total_seconds(utcnow() - last_heartbeat) return abs(elapsed) <= FLAGS.service_down_time + + +def generate_mac_address(): + """Generate an Ethernet MAC address.""" + mac = [0x02, 0x16, 0x3e, + random.randint(0x00, 0x7f), + random.randint(0x00, 0xff), + random.randint(0x00, 0xff)] + return ':'.join(map(lambda x: "%02x" % x, mac)) -- cgit