summaryrefslogtreecommitdiffstats
path: root/build/interface.py
diff options
context:
space:
mode:
Diffstat (limited to 'build/interface.py')
-rw-r--r--build/interface.py552
1 files changed, 552 insertions, 0 deletions
diff --git a/build/interface.py b/build/interface.py
new file mode 100644
index 00000000..709fd677
--- /dev/null
+++ b/build/interface.py
@@ -0,0 +1,552 @@
+# Copyright 2012 OpenStack Foundation
+# 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 abc
+import eventlet
+import netaddr
+from oslo_config import cfg
+from oslo_log import log as logging
+import six
+
+from neutron._i18n import _, _LE, _LI, _LW
+from neutron.agent.common import ovs_lib
+from neutron.agent.linux import ip_lib
+from neutron.agent.linux import utils
+from neutron.common import constants as n_const
+from neutron.common import exceptions
+from neutron.common import ipv6_utils
+
+
+LOG = logging.getLogger(__name__)
+
+OPTS = [
+ cfg.StrOpt('ovs_integration_bridge',
+ default='br-int',
+ help=_('Name of Open vSwitch bridge to use')),
+ cfg.BoolOpt('ovs_use_veth',
+ default=False,
+ help=_('Uses veth for an OVS interface or not. '
+ 'Support kernels with limited namespace support '
+ '(e.g. RHEL 6.5) so long as ovs_use_veth is set to '
+ 'True.')),
+ cfg.IntOpt('network_device_mtu',
+ deprecated_for_removal=True,
+ help=_('MTU setting for device. This option will be removed in '
+ 'Newton. Please use the system-wide segment_mtu setting '
+ 'which the agents will take into account when wiring '
+ 'VIFs.')),
+]
+
+
+@six.add_metaclass(abc.ABCMeta)
+class LinuxInterfaceDriver(object):
+
+ # from linux IF_NAMESIZE
+ DEV_NAME_LEN = 14
+ DEV_NAME_PREFIX = n_const.TAP_DEVICE_PREFIX
+
+ def __init__(self, conf):
+ self.conf = conf
+ if self.conf.network_device_mtu:
+ self._validate_network_device_mtu()
+
+ def _validate_network_device_mtu(self):
+ if (ipv6_utils.is_enabled() and
+ self.conf.network_device_mtu < n_const.IPV6_MIN_MTU):
+ LOG.error(_LE("IPv6 protocol requires a minimum MTU of "
+ "%(min_mtu)s, while the configured value is "
+ "%(current_mtu)s"), {'min_mtu': n_const.IPV6_MIN_MTU,
+ 'current_mtu': self.conf.network_device_mtu})
+ raise SystemExit(1)
+
+ @property
+ def use_gateway_ips(self):
+ """Whether to use gateway IPs instead of unique IP allocations.
+
+ In each place where the DHCP agent runs, and for each subnet for
+ which DHCP is handling out IP addresses, the DHCP port needs -
+ at the Linux level - to have an IP address within that subnet.
+ Generally this needs to be a unique Neutron-allocated IP
+ address, because the subnet's underlying L2 domain is bridged
+ across multiple compute hosts and network nodes, and for HA
+ there may be multiple DHCP agents running on that same bridged
+ L2 domain.
+
+ However, if the DHCP ports - on multiple compute/network nodes
+ but for the same network - are _not_ bridged to each other,
+ they do not need each to have a unique IP address. Instead
+ they can all share the same address from the relevant subnet.
+ This works, without creating any ambiguity, because those
+ ports are not all present on the same L2 domain, and because
+ no data within the network is ever sent to that address.
+ (DHCP requests are broadcast, and it is the network's job to
+ ensure that such a broadcast will reach at least one of the
+ available DHCP servers. DHCP responses will be sent _from_
+ the DHCP port address.)
+
+ Specifically, for networking backends where it makes sense,
+ the DHCP agent allows all DHCP ports to use the subnet's
+ gateway IP address, and thereby to completely avoid any unique
+ IP address allocation. This behaviour is selected by running
+ the DHCP agent with a configured interface driver whose
+ 'use_gateway_ips' property is True.
+
+ When an operator deploys Neutron with an interface driver that
+ makes use_gateway_ips True, they should also ensure that a
+ gateway IP address is defined for each DHCP-enabled subnet,
+ and that the gateway IP address doesn't change during the
+ subnet's lifetime.
+ """
+ return False
+
+ def init_l3(self, device_name, ip_cidrs, namespace=None,
+ preserve_ips=None, clean_connections=False):
+ """Set the L3 settings for the interface using data from the port.
+
+ ip_cidrs: list of 'X.X.X.X/YY' strings
+ preserve_ips: list of ip cidrs that should not be removed from device
+ clean_connections: Boolean to indicate if we should cleanup connections
+ associated to removed ips
+ """
+ preserve_ips = preserve_ips or []
+ device = ip_lib.IPDevice(device_name, namespace=namespace)
+
+ # The LLA generated by the operating system is not known to
+ # Neutron, so it would be deleted if we added it to the 'previous'
+ # list here
+ default_ipv6_lla = ip_lib.get_ipv6_lladdr(device.link.address)
+ previous = {addr['cidr'] for addr in device.addr.list(
+ filters=['permanent'])} - {default_ipv6_lla}
+
+ # add new addresses
+ for ip_cidr in ip_cidrs:
+
+ net = netaddr.IPNetwork(ip_cidr)
+ # Convert to compact IPv6 address because the return values of
+ # "ip addr list" are compact.
+ if net.version == 6:
+ ip_cidr = str(net)
+ if ip_cidr in previous:
+ previous.remove(ip_cidr)
+ continue
+
+ device.addr.add(ip_cidr)
+
+ # clean up any old addresses
+ for ip_cidr in previous:
+ if ip_cidr not in preserve_ips:
+ if clean_connections:
+ device.delete_addr_and_conntrack_state(ip_cidr)
+ else:
+ device.addr.delete(ip_cidr)
+
+ def init_router_port(self,
+ device_name,
+ ip_cidrs,
+ namespace,
+ preserve_ips=None,
+ extra_subnets=None,
+ clean_connections=False):
+ """Set the L3 settings for a router interface using data from the port.
+
+ ip_cidrs: list of 'X.X.X.X/YY' strings
+ preserve_ips: list of ip cidrs that should not be removed from device
+ clean_connections: Boolean to indicate if we should cleanup connections
+ associated to removed ips
+ extra_subnets: An iterable of cidrs to add as routes without address
+ """
+ LOG.debug("init_router_port: device_name(%s), namespace(%s)",
+ device_name, namespace)
+ self.init_l3(device_name=device_name,
+ ip_cidrs=ip_cidrs,
+ namespace=namespace,
+ preserve_ips=preserve_ips or [],
+ clean_connections=clean_connections)
+
+ device = ip_lib.IPDevice(device_name, namespace=namespace)
+
+ # Manage on-link routes (routes without an associated address)
+ new_onlink_cidrs = set(s['cidr'] for s in extra_subnets or [])
+
+ v4_onlink = device.route.list_onlink_routes(n_const.IP_VERSION_4)
+ v6_onlink = device.route.list_onlink_routes(n_const.IP_VERSION_6)
+ existing_onlink_cidrs = set(r['cidr'] for r in v4_onlink + v6_onlink)
+
+ for route in new_onlink_cidrs - existing_onlink_cidrs:
+ LOG.debug("adding onlink route(%s)", route)
+ device.route.add_onlink_route(route)
+ for route in (existing_onlink_cidrs - new_onlink_cidrs -
+ set(preserve_ips or [])):
+ LOG.debug("deleting onlink route(%s)", route)
+ device.route.delete_onlink_route(route)
+
+ def add_ipv6_addr(self, device_name, v6addr, namespace, scope='global'):
+ device = ip_lib.IPDevice(device_name,
+ namespace=namespace)
+ net = netaddr.IPNetwork(v6addr)
+ device.addr.add(str(net), scope)
+
+ def delete_ipv6_addr(self, device_name, v6addr, namespace):
+ device = ip_lib.IPDevice(device_name,
+ namespace=namespace)
+ device.delete_addr_and_conntrack_state(v6addr)
+
+ def delete_ipv6_addr_with_prefix(self, device_name, prefix, namespace):
+ """Delete the first listed IPv6 address that falls within a given
+ prefix.
+ """
+ device = ip_lib.IPDevice(device_name, namespace=namespace)
+ net = netaddr.IPNetwork(prefix)
+ for address in device.addr.list(scope='global', filters=['permanent']):
+ ip_address = netaddr.IPNetwork(address['cidr'])
+ if ip_address in net:
+ device.delete_addr_and_conntrack_state(address['cidr'])
+ break
+
+ def get_ipv6_llas(self, device_name, namespace):
+ device = ip_lib.IPDevice(device_name,
+ namespace=namespace)
+
+ return device.addr.list(scope='link', ip_version=6)
+
+ def check_bridge_exists(self, bridge):
+ if not ip_lib.device_exists(bridge):
+ raise exceptions.BridgeDoesNotExist(bridge=bridge)
+
+ def get_device_name(self, port):
+ return (self.DEV_NAME_PREFIX + port.id)[:self.DEV_NAME_LEN]
+
+ @staticmethod
+ def configure_ipv6_ra(namespace, dev_name):
+ """Configure acceptance of IPv6 route advertisements on an intf."""
+ # Learn the default router's IP address via RAs
+ ip_lib.IPWrapper(namespace=namespace).netns.execute(
+ ['sysctl', '-w', 'net.ipv6.conf.%s.accept_ra=2' % dev_name])
+
+ @abc.abstractmethod
+ def plug_new(self, network_id, port_id, device_name, mac_address,
+ bridge=None, namespace=None, prefix=None, mtu=None):
+ """Plug in the interface only for new devices that don't exist yet."""
+
+ def plug(self, network_id, port_id, device_name, mac_address,
+ bridge=None, namespace=None, prefix=None, mtu=None):
+ if not ip_lib.device_exists(device_name,
+ namespace=namespace):
+ try:
+ self.plug_new(network_id, port_id, device_name, mac_address,
+ bridge, namespace, prefix, mtu)
+ except TypeError:
+ self.plug_new(network_id, port_id, device_name, mac_address,
+ bridge, namespace, prefix)
+ else:
+ LOG.info(_LI("Device %s already exists"), device_name)
+
+ @abc.abstractmethod
+ def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+ """Unplug the interface."""
+
+ @property
+ def bridged(self):
+ """Whether the DHCP port is bridged to the VM TAP interfaces.
+
+ When the DHCP port is bridged to the TAP interfaces for the
+ VMs for which it is providing DHCP service - as is the case
+ for most Neutron network implementations - the DHCP server
+ only needs to listen on the DHCP port, and will still receive
+ DHCP requests from all the relevant VMs.
+
+ If the DHCP port is not bridged to the relevant VM TAP
+ interfaces, the DHCP server needs to listen explicitly on
+ those TAP interfaces, and to treat those as aliases of the
+ DHCP port where the IP subnet is defined.
+ """
+ return True
+
+
+class NullDriver(LinuxInterfaceDriver):
+ def plug_new(self, network_id, port_id, device_name, mac_address,
+ bridge=None, namespace=None, prefix=None, mtu=None):
+ pass
+
+ def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+ pass
+
+class NSDriver(LinuxInterfaceDriver):
+ """Device independent driver enabling creation of a non device specific
+ interface in network spaces. Attachment to the device is not performed.
+ """
+ MAX_TIME_FOR_DEVICE_EXISTENCE = 30
+
+ @classmethod
+ def _device_is_created_in_time(cls, device_name):
+ """See if device is created, within time limit."""
+ attempt = 0
+ while attempt < NSDriver.MAX_TIME_FOR_DEVICE_EXISTENCE:
+ if ip_lib.device_exists(device_name):
+ return True
+ attempt += 1
+ eventlet.sleep(1)
+ LOG.error(_LE("Device %(dev)s was not created in %(time)d seconds"),
+ {'dev': device_name,
+ 'time': NSDriver.MAX_TIME_FOR_DEVICE_EXISTENCE})
+ return False
+
+ def _configure_mtu(self, ns_dev, mtu=None):
+ # Need to set MTU, after added to namespace. See review
+ # https://review.openstack.org/327651
+ try:
+ # Note: network_device_mtu will be deprecated in future
+ mtu_override = self.conf.network_device_mtu
+ except cfg.NoSuchOptError:
+ LOG.warning(_LW("Config setting for MTU deprecated - any "
+ "override will be ignored."))
+ mtu_override = None
+ if mtu_override:
+ mtu = mtu_override
+ LOG.debug("Overriding MTU to %d", mtu)
+ if mtu:
+ ns_dev.link.set_mtu(mtu)
+ else:
+ LOG.debug("No MTU provided - skipping setting value")
+
+ def plug(self, network_id, port_id, device_name, mac_address,
+ bridge=None, namespace=None, prefix=None, mtu=None):
+
+ # Overriding this, we still want to add an existing device into the
+ # namespace.
+ self.plug_new(network_id, port_id, device_name, mac_address,
+ bridge, namespace, prefix, mtu)
+
+ def plug_new(self, network_id, port_id, device_name, mac_address,
+ bridge=None, namespace=None, prefix=None, mtu=None):
+
+ ip = ip_lib.IPWrapper()
+ ns_dev = ip.device(device_name)
+
+ LOG.debug("Plugging dev: '%s' into namespace: '%s' ",
+ device_name, namespace)
+
+ # Wait for device creation
+ if not self._device_is_created_in_time(device_name):
+ return
+
+ ns_dev.link.set_address(mac_address)
+
+ if namespace:
+ namespace_obj = ip.ensure_namespace(namespace)
+ namespace_obj.add_device_to_namespace(ns_dev)
+
+ self._configure_mtu(ns_dev, mtu)
+
+ ns_dev.link.set_up()
+
+ def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+ # Device removal is done externally. Just remove the namespace
+ LOG.debug("Removing namespace: '%s'", namespace)
+ ip_lib.IPWrapper(namespace).garbage_collect_namespace()
+
+
+class OVSInterfaceDriver(LinuxInterfaceDriver):
+ """Driver for creating an internal interface on an OVS bridge."""
+
+ DEV_NAME_PREFIX = n_const.TAP_DEVICE_PREFIX
+
+ def __init__(self, conf):
+ super(OVSInterfaceDriver, self).__init__(conf)
+ if self.conf.ovs_use_veth:
+ self.DEV_NAME_PREFIX = 'ns-'
+
+ def _get_tap_name(self, dev_name, prefix=None):
+ if self.conf.ovs_use_veth:
+ dev_name = dev_name.replace(prefix or self.DEV_NAME_PREFIX,
+ n_const.TAP_DEVICE_PREFIX)
+ return dev_name
+
+ def _ovs_add_port(self, bridge, device_name, port_id, mac_address,
+ internal=True):
+ attrs = [('external_ids', {'iface-id': port_id,
+ 'iface-status': 'active',
+ 'attached-mac': mac_address})]
+ if internal:
+ attrs.insert(0, ('type', 'internal'))
+
+ ovs = ovs_lib.OVSBridge(bridge)
+ ovs.replace_port(device_name, *attrs)
+
+ def plug_new(self, network_id, port_id, device_name, mac_address,
+ bridge=None, namespace=None, prefix=None, mtu=None):
+ """Plug in the interface."""
+ if not bridge:
+ bridge = self.conf.ovs_integration_bridge
+
+ self.check_bridge_exists(bridge)
+
+ ip = ip_lib.IPWrapper()
+ tap_name = self._get_tap_name(device_name, prefix)
+
+ if self.conf.ovs_use_veth:
+ # Create ns_dev in a namespace if one is configured.
+ root_dev, ns_dev = ip.add_veth(tap_name,
+ device_name,
+ namespace2=namespace)
+ root_dev.disable_ipv6()
+ else:
+ ns_dev = ip.device(device_name)
+
+ internal = not self.conf.ovs_use_veth
+ self._ovs_add_port(bridge, tap_name, port_id, mac_address,
+ internal=internal)
+
+ ns_dev.link.set_address(mac_address)
+
+ # Add an interface created by ovs to the namespace.
+ if not self.conf.ovs_use_veth and namespace:
+ namespace_obj = ip.ensure_namespace(namespace)
+ namespace_obj.add_device_to_namespace(ns_dev)
+
+ # NOTE(ihrachys): the order here is significant: we must set MTU after
+ # the device is moved into a namespace, otherwise OVS bridge does not
+ # allow to set MTU that is higher than the least of all device MTUs on
+ # the bridge
+ mtu = self.conf.network_device_mtu or mtu
+ if mtu:
+ ns_dev.link.set_mtu(mtu)
+ if self.conf.ovs_use_veth:
+ root_dev.link.set_mtu(mtu)
+ else:
+ LOG.warning(_LW("No MTU configured for port %s"), port_id)
+
+ ns_dev.link.set_up()
+ if self.conf.ovs_use_veth:
+ root_dev.link.set_up()
+
+ def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+ """Unplug the interface."""
+ if not bridge:
+ bridge = self.conf.ovs_integration_bridge
+
+ tap_name = self._get_tap_name(device_name, prefix)
+ self.check_bridge_exists(bridge)
+ ovs = ovs_lib.OVSBridge(bridge)
+
+ try:
+ ovs.delete_port(tap_name)
+ if self.conf.ovs_use_veth:
+ device = ip_lib.IPDevice(device_name, namespace=namespace)
+ device.link.delete()
+ LOG.debug("Unplugged interface '%s'", device_name)
+ except RuntimeError:
+ LOG.error(_LE("Failed unplugging interface '%s'"),
+ device_name)
+
+
+class IVSInterfaceDriver(LinuxInterfaceDriver):
+ """Driver for creating an internal interface on an IVS bridge."""
+
+ DEV_NAME_PREFIX = n_const.TAP_DEVICE_PREFIX
+
+ def __init__(self, conf):
+ super(IVSInterfaceDriver, self).__init__(conf)
+ self.DEV_NAME_PREFIX = 'ns-'
+
+ def _get_tap_name(self, dev_name, prefix=None):
+ dev_name = dev_name.replace(prefix or self.DEV_NAME_PREFIX,
+ n_const.TAP_DEVICE_PREFIX)
+ return dev_name
+
+ def _ivs_add_port(self, device_name, port_id, mac_address):
+ cmd = ['ivs-ctl', 'add-port', device_name]
+ utils.execute(cmd, run_as_root=True)
+
+ def plug_new(self, network_id, port_id, device_name, mac_address,
+ bridge=None, namespace=None, prefix=None, mtu=None):
+ """Plug in the interface."""
+ ip = ip_lib.IPWrapper()
+ tap_name = self._get_tap_name(device_name, prefix)
+
+ root_dev, ns_dev = ip.add_veth(tap_name, device_name)
+ root_dev.disable_ipv6()
+
+ self._ivs_add_port(tap_name, port_id, mac_address)
+
+ ns_dev = ip.device(device_name)
+ ns_dev.link.set_address(mac_address)
+
+ mtu = self.conf.network_device_mtu or mtu
+ if mtu:
+ ns_dev.link.set_mtu(mtu)
+ root_dev.link.set_mtu(mtu)
+ else:
+ LOG.warning(_LW("No MTU configured for port %s"), port_id)
+
+ if namespace:
+ namespace_obj = ip.ensure_namespace(namespace)
+ namespace_obj.add_device_to_namespace(ns_dev)
+
+ ns_dev.link.set_up()
+ root_dev.link.set_up()
+
+ def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+ """Unplug the interface."""
+ tap_name = self._get_tap_name(device_name, prefix)
+ try:
+ cmd = ['ivs-ctl', 'del-port', tap_name]
+ utils.execute(cmd, run_as_root=True)
+ device = ip_lib.IPDevice(device_name, namespace=namespace)
+ device.link.delete()
+ LOG.debug("Unplugged interface '%s'", device_name)
+ except RuntimeError:
+ LOG.error(_LE("Failed unplugging interface '%s'"),
+ device_name)
+
+
+class BridgeInterfaceDriver(LinuxInterfaceDriver):
+ """Driver for creating bridge interfaces."""
+
+ DEV_NAME_PREFIX = 'ns-'
+
+ def plug_new(self, network_id, port_id, device_name, mac_address,
+ bridge=None, namespace=None, prefix=None, mtu=None):
+ """Plugin the interface."""
+ ip = ip_lib.IPWrapper()
+
+ # Enable agent to define the prefix
+ tap_name = device_name.replace(prefix or self.DEV_NAME_PREFIX,
+ n_const.TAP_DEVICE_PREFIX)
+ # Create ns_veth in a namespace if one is configured.
+ root_veth, ns_veth = ip.add_veth(tap_name, device_name,
+ namespace2=namespace)
+ root_veth.disable_ipv6()
+ ns_veth.link.set_address(mac_address)
+
+ mtu = self.conf.network_device_mtu or mtu
+ if mtu:
+ root_veth.link.set_mtu(mtu)
+ ns_veth.link.set_mtu(mtu)
+ else:
+ LOG.warning(_LW("No MTU configured for port %s"), port_id)
+
+ root_veth.link.set_up()
+ ns_veth.link.set_up()
+
+ def unplug(self, device_name, bridge=None, namespace=None, prefix=None):
+ """Unplug the interface."""
+ device = ip_lib.IPDevice(device_name, namespace=namespace)
+ try:
+ device.link.delete()
+ LOG.debug("Unplugged interface '%s'", device_name)
+ except RuntimeError:
+ LOG.error(_LE("Failed unplugging interface '%s'"),
+ device_name)