Vxlan / L2population support to Linuxbridge Agent

This patch introduces VXLAN support for Linuxbridge agent alongside
with ml2 plugin support in linubridge mechnism driver.

A new vxlan configuration section is added for vxlan related parameters.

The agent also implements l2population RPC callbacks which allows ml2
plugin using l2population mechnism driver to populate vxlan forwarding
and neighbor tables following portbinding events. It allows agent to
respond locally to ARP requests for remote VMs and avoid dataplane based
learning. This should help limiting the use of multicast or flooding
for broadcast emulation in vxlan networks.

These changes should anyway have a limited risk, as agent behaviour
shouldn't be affected, except when vxlan is enabled alongside ml2 plugin.

Implements: blueprint l2-population

Change-Id: I99a961c53f9e451409f1affb079042936d8ae5c6
This commit is contained in:
Francois Eleouet 2013-09-11 14:19:39 +02:00
parent b6133c35dd
commit 7e79e6973e
13 changed files with 652 additions and 29 deletions

View File

@ -31,6 +31,33 @@
# physical_interface_mappings =
# Example: physical_interface_mappings = physnet1:eth1
[vxlan]
# (BoolOpt) enable VXLAN on the agent
# VXLAN support can be enabled when agent is managed by ml2 plugin using
# linuxbridge mechanism driver. Useless if set while using linuxbridge plugin.
# enable_vxlan = False
#
# (IntOpt) use specific TTL for vxlan interface protocol packets
# ttl =
#
# (IntOpt) use specific TOS for vxlan interface protocol packets
# tos =
#
# (StrOpt) multicast group to use for broadcast emulation.
# This group must be the same on all the agents.
# vxlan_group = 224.0.0.1
#
# (StrOpt) Local IP address to use for VXLAN endpoints (required)
# local_ip =
#
# (BoolOpt) Flag to enable l2population extension. This option should be used
# in conjunction with ml2 plugin l2population mechanism driver (in that case,
# both linuxbridge and l2population mechanism drivers should be loaded).
# It enables plugin to populate VXLAN forwarding table, in order to limit
# the use of broadcast emulation (multicast will be turned off if kernel and
# iproute2 supports unicast flooding - requires 3.11 kernel and iproute2 3.10)
# l2_population = False
[agent]
# Agent's polling interval in seconds
# polling_interval = 2

View File

@ -12,6 +12,7 @@
# unclear whether both variants are necessary, but I'm transliterating
# from the old mechanism
brctl: CommandFilter, brctl, root
bridge: CommandFilter, bridge, root
# ip_lib
ip: IpFilter, ip, root

View File

@ -146,6 +146,27 @@ class IPWrapper(SubProcessBase):
if self.namespace:
device.link.set_netns(self.namespace)
def add_vxlan(self, name, vni, group=None, dev=None, ttl=None, tos=None,
local=None, port=None):
cmd = ['add', name, 'type', 'vxlan', 'id', vni, 'proxy']
if group:
cmd.extend(['group', group])
if dev:
cmd.extend(['dev', dev])
if ttl:
cmd.extend(['ttl', ttl])
if tos:
cmd.extend(['tos', tos])
if local:
cmd.extend(['local', local])
# tuple: min,max
if port and len(port) == 2:
cmd.extend(['port', port[0], port[1]])
elif port:
raise exceptions.NetworkVxlanPortRangeError(vxlan_range=port)
self._as_root('', 'link', cmd)
return (IPDevice(name, self.root_helper, self.namespace))
@classmethod
def get_namespaces(cls, root_helper):
output = cls._execute('', 'netns', ('list',), root_helper=root_helper)
@ -449,3 +470,10 @@ def device_exists(device_name, root_helper=None, namespace=None):
except RuntimeError:
return False
return bool(address)
def iproute_arg_supported(command, arg, root_helper=None):
command += ['help']
stdout, stderr = utils.execute(command, root_helper=root_helper,
check_exit_code=False, return_stderr=True)
return any(arg in line for line in stderr.split('\n'))

View File

@ -44,6 +44,7 @@ DHCP_RESPONSE_PORT = 68
MIN_VLAN_TAG = 1
MAX_VLAN_TAG = 4094
MAX_VXLAN_VNI = 16777215
FLOODING_ENTRY = ['00:00:00:00:00:00', '0.0.0.0']
EXT_NS_COMP = '_backward_comp_e_ns'

View File

@ -293,3 +293,7 @@ class NetworkVlanRangeError(NeutronException):
if isinstance(kwargs['vlan_range'], tuple):
kwargs['vlan_range'] = "%d:%d" % kwargs['vlan_range']
super(NetworkVlanRangeError, self).__init__(**kwargs)
class NetworkVxlanPortRangeError(object):
message = _("Invalid network VXLAN port range: '%(vxlan_range)s'")

View File

@ -22,7 +22,9 @@
# Neutron OpenVSwitch Plugin.
# @author: Sumit Naiksatam, Cisco Systems, Inc.
import distutils.version as dist_version
import os
import platform
import sys
import time
@ -30,6 +32,7 @@ import eventlet
from oslo.config import cfg
import pyudev
from neutron.agent import l2population_rpc as l2pop_rpc
from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils
from neutron.agent import rpc as agent_rpc
@ -56,6 +59,14 @@ BRIDGE_NAME_PLACEHOLDER = "bridge_name"
BRIDGE_INTERFACES_FS = BRIDGE_FS + BRIDGE_NAME_PLACEHOLDER + "/brif/"
DEVICE_NAME_PLACEHOLDER = "device_name"
BRIDGE_PORT_FS_FOR_DEVICE = BRIDGE_FS + DEVICE_NAME_PLACEHOLDER + "/brport"
VXLAN_INTERFACE_PREFIX = "vxlan-"
class NetworkSegment:
def __init__(self, network_type, physical_network, segmentation_id):
self.network_type = network_type
self.physical_network = physical_network
self.segmentation_id = segmentation_id
class LinuxBridgeManager:
@ -63,6 +74,18 @@ class LinuxBridgeManager:
self.interface_mappings = interface_mappings
self.root_helper = root_helper
self.ip = ip_lib.IPWrapper(self.root_helper)
# VXLAN related parameters:
self.local_ip = cfg.CONF.VXLAN.local_ip
self.vxlan_mode = lconst.VXLAN_NONE
if cfg.CONF.VXLAN.enable_vxlan:
self.local_int = self.get_interface_by_ip(self.local_ip)
if self.local_int:
self.check_vxlan_support()
else:
LOG.warning(_('VXLAN is enabled, a valid local_ip '
'must be provided'))
# Store network mapping to segments
self.network_map = {}
self.udev = pyudev.Context()
monitor = pyudev.Monitor.from_netlink(self.udev)
@ -105,6 +128,13 @@ class LinuxBridgeManager:
tap_device_name = TAP_INTERFACE_PREFIX + interface_id[0:11]
return tap_device_name
def get_vxlan_device_name(self, segmentation_id):
if 0 <= int(segmentation_id) <= constants.MAX_VXLAN_VNI:
return VXLAN_INTERFACE_PREFIX + str(segmentation_id)
else:
LOG.warning(_("Invalid Segementation ID: %s, will lead to "
"incorrect vxlan device name"), segmentation_id)
def get_all_neutron_bridges(self):
neutron_bridge_list = []
bridge_list = os.listdir(BRIDGE_FS)
@ -119,6 +149,21 @@ class LinuxBridgeManager:
BRIDGE_NAME_PLACEHOLDER, bridge_name)
return os.listdir(bridge_interface_path)
def get_tap_devices_count(self, bridge_name):
bridge_interface_path = BRIDGE_INTERFACES_FS.replace(
BRIDGE_NAME_PLACEHOLDER, bridge_name)
try:
if_list = os.listdir(bridge_interface_path)
return len([interface for interface in if_list if
interface.startswith(TAP_INTERFACE_PREFIX)])
except OSError:
return 0
def get_interface_by_ip(self, ip):
for device in self.ip.get_devices():
if device.addr.list(to=ip):
return device.name
def get_bridge_for_tap_device(self, tap_device_name):
bridges = self.get_all_neutron_bridges()
for bridge in bridges:
@ -144,6 +189,18 @@ class LinuxBridgeManager:
if self.ensure_bridge(bridge_name, interface, ips, gateway):
return interface
def ensure_vxlan_bridge(self, network_id, segmentation_id):
"""Create a vxlan and bridge unless they already exist."""
interface = self.ensure_vxlan(segmentation_id)
if not interface:
LOG.error(_("Failed creating vxlan interface for "
"%(segmentation_id)s"),
{segmentation_id: segmentation_id})
return
bridge_name = self.get_bridge_name(network_id)
self.ensure_bridge(bridge_name, interface)
return interface
def get_interface_details(self, interface):
device = self.ip.device(interface)
ips = device.addr.list(scope='global')
@ -184,6 +241,26 @@ class LinuxBridgeManager:
LOG.debug(_("Done creating subinterface %s"), interface)
return interface
def ensure_vxlan(self, segmentation_id):
"""Create a vxlan unless it already exists."""
interface = self.get_vxlan_device_name(segmentation_id)
if not self.device_exists(interface):
LOG.debug(_("Creating vxlan interface %(interface)s for "
"VNI %(segmentation_id)s"),
{'interface': interface,
'segmentation_id': segmentation_id})
args = {'dev': self.local_int}
if self.vxlan_mode == lconst.VXLAN_MCAST:
args['group'] = cfg.CONF.VXLAN.vxlan_group
if cfg.CONF.VXLAN.ttl:
args['ttl'] = cfg.CONF.VXLAN.ttl
if cfg.CONF.VXLAN.tos:
args['tos'] = cfg.CONF.VXLAN.tos
int_vxlan = self.ip.add_vxlan(interface, segmentation_id, **args)
int_vxlan.link.set_up()
LOG.debug(_("Done creating vxlan interface %s"), interface)
return interface
def update_interface_ip_details(self, destination, source, ips,
gateway):
if ips or gateway:
@ -244,6 +321,12 @@ class LinuxBridgeManager:
# Check if the interface is part of the bridge
if not self.interface_exists_on_bridge(bridge_name, interface):
try:
# Check if the interface is not enslaved in another bridge
if self.is_device_on_bridge(interface):
bridge = self.get_bridge_for_tap_device(interface)
utils.execute(['brctl', 'delif', bridge, interface],
root_helper=self.root_helper)
utils.execute(['brctl', 'addif', bridge_name, interface],
root_helper=self.root_helper)
except Exception as e:
@ -258,6 +341,13 @@ class LinuxBridgeManager:
network_type,
physical_network,
segmentation_id):
if network_type == lconst.TYPE_VXLAN:
if self.vxlan_mode == lconst.VXLAN_NONE:
LOG.error(_("Unable to add vxlan interface for network %s"),
network_id)
return
return self.ensure_vxlan_bridge(network_id, segmentation_id)
physical_interface = self.interface_mappings.get(physical_network)
if not physical_interface:
LOG.error(_("No mapping for physical network %s"),
@ -315,6 +405,9 @@ class LinuxBridgeManager:
def add_interface(self, network_id, network_type, physical_network,
segmentation_id, port_id):
self.network_map[network_id] = NetworkSegment(network_type,
physical_network,
segmentation_id)
tap_device_name = self.get_tap_device_name(port_id)
return self.add_tap_interface(network_id, network_type,
physical_network, segmentation_id,
@ -333,9 +426,10 @@ class LinuxBridgeManager:
self.update_interface_ip_details(interface,
bridge_name,
ips, gateway)
else:
if interface.startswith(physical_interface):
self.delete_vlan(interface)
elif interface.startswith(physical_interface):
self.delete_vlan(interface)
elif interface.startswith(VXLAN_INTERFACE_PREFIX):
self.delete_vxlan(interface)
LOG.debug(_("Deleting bridge %s"), bridge_name)
if utils.execute(['ip', 'link', 'set', bridge_name, 'down'],
@ -350,6 +444,13 @@ class LinuxBridgeManager:
LOG.error(_("Cannot delete bridge %s, does not exist"),
bridge_name)
def remove_empty_bridges(self):
for network_id in self.network_map.keys():
bridge_name = self.get_bridge_name(network_id)
if not self.get_tap_devices_count(bridge_name):
self.delete_vlan_bridge(bridge_name)
del self.network_map[network_id]
def remove_interface(self, bridge_name, interface_name):
if self.device_exists(bridge_name):
if not self.is_device_on_bridge(interface_name):
@ -384,6 +485,15 @@ class LinuxBridgeManager:
return
LOG.debug(_("Done deleting subinterface %s"), interface)
def delete_vxlan(self, interface):
if self.device_exists(interface):
LOG.debug(_("Deleting vxlan interface %s for vlan"),
interface)
int_vxlan = self.ip.device(interface)
int_vxlan.link.set_down()
int_vxlan.link.delete()
LOG.debug(_("Done deleting vxlan interface %s"), interface)
def update_devices(self, registered_devices):
devices = self.udev_get_tap_devices()
if devices == registered_devices:
@ -408,8 +518,92 @@ class LinuxBridgeManager:
def udev_get_name(self, device):
return device.sys_name
def check_vxlan_support(self):
kernel_version = dist_version.LooseVersion(platform.release())
if cfg.CONF.VXLAN.l2_population and (
kernel_version > dist_version.LooseVersion(
lconst.MIN_VXLAN_KVER[lconst.VXLAN_UCAST])) and (
ip_lib.iproute_arg_supported(['bridge', 'fdb'],
'append', self.root_helper)):
self.vxlan_mode = lconst.VXLAN_UCAST
elif (kernel_version > dist_version.LooseVersion(
lconst.MIN_VXLAN_KVER[lconst.VXLAN_MCAST])) and (
ip_lib.iproute_arg_supported(['ip', 'link', 'add',
'type', 'vxlan'], 'proxy',
self.root_helper)):
if cfg.CONF.VXLAN.vxlan_group:
self.vxlan_mode = lconst.VXLAN_MCAST
else:
self.vxlan_mode = lconst.VXLAN_NONE
LOG.warning(_('VXLAN muticast group must be provided in '
'vxlan_group option to enable VXLAN'))
else:
self.vxlan_mode = lconst.VXLAN_NONE
LOG.warning(_('Unable to use VXLAN, it requires at least 3.8 '
'linux kernel and iproute2 3.8'))
LOG.debug(_('Using %s VXLAN mode'), self.vxlan_mode)
class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
def fdb_ip_entry_exists(self, mac, ip, interface):
entries = utils.execute(['ip', 'neigh', 'show', 'to', ip,
'dev', interface],
root_helper=self.root_helper)
return mac in entries
def fdb_bridge_entry_exists(self, mac, interface, agent_ip=None):
entries = utils.execute(['bridge', 'fdb', 'show', 'dev', interface],
root_helper=self.root_helper)
if not agent_ip:
return mac in entries
return (agent_ip in entries and mac in entries)
def add_fdb_ip_entry(self, mac, ip, interface):
utils.execute(['ip', 'neigh', 'add', ip, 'lladdr', mac,
'dev', interface, 'nud', 'permanent'],
root_helper=self.root_helper,
check_exit_code=False)
def remove_fdb_ip_entry(self, mac, ip, interface):
utils.execute(['ip', 'neigh', 'del', ip, 'lladdr', mac,
'dev', interface],
root_helper=self.root_helper,
check_exit_code=False)
def add_fdb_bridge_entry(self, mac, agent_ip, interface, operation="add"):
utils.execute(['bridge', 'fdb', operation, mac, 'dev', interface,
'dst', agent_ip],
root_helper=self.root_helper,
check_exit_code=False)
def remove_fdb_bridge_entry(self, mac, agent_ip, interface):
utils.execute(['bridge', 'fdb', 'del', mac, 'dev', interface,
'dst', agent_ip],
root_helper=self.root_helper,
check_exit_code=False)
def add_fdb_entries(self, agent_ip, ports, interface):
for mac, ip in ports:
if mac != constants.FLOODING_ENTRY[0]:
self.add_fdb_ip_entry(mac, ip, interface)
self.add_fdb_bridge_entry(mac, agent_ip, interface)
elif self.vxlan_mode == lconst.VXLAN_UCAST:
if self.fdb_bridge_entry_exists(mac, interface):
self.add_fdb_bridge_entry(mac, agent_ip, interface,
"append")
else:
self.add_fdb_bridge_entry(mac, agent_ip, interface)
def remove_fdb_entries(self, agent_ip, ports, interface):
for mac, ip in ports:
if mac != constants.FLOODING_ENTRY[0]:
self.remove_fdb_ip_entry(mac, ip, interface)
self.remove_fdb_bridge_entry(mac, agent_ip, interface)
elif self.vxlan_mode == lconst.VXLAN_UCAST:
self.remove_fdb_bridge_entry(mac, agent_ip, interface)
class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
l2pop_rpc.L2populationRpcCallBackMixin):
# Set RPC API version to 1.0 by default.
# history
@ -451,13 +645,17 @@ class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
segmentation_id) = lconst.interpret_vlan_id(vlan_id)
physical_network = kwargs.get('physical_network')
# create the networking for the port
self.agent.br_mgr.add_interface(port['network_id'],
network_type,
physical_network,
segmentation_id,
port['id'])
# update plugin about port status
self.agent.plugin_rpc.update_device_up(self.context,
if self.agent.br_mgr.add_interface(port['network_id'],
network_type,
physical_network,
segmentation_id,
port['id']):
# update plugin about port status
self.agent.plugin_rpc.update_device_up(self.context,
tap_device_name,
self.agent.agent_id)
else:
self.plugin_rpc.update_device_down(self.context,
tap_device_name,
self.agent.agent_id)
else:
@ -472,6 +670,50 @@ class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
except rpc_common.Timeout:
LOG.error(_("RPC timeout while updating port %s"), port['id'])
def fdb_add(self, context, fdb_entries):
LOG.debug(_("fdb_add received"))
for network_id, values in fdb_entries.items():
segment = self.agent.br_mgr.network_map.get(network_id)
if not segment:
return
if segment.network_type != lconst.TYPE_VXLAN:
return
interface = self.agent.br_mgr.get_vxlan_device_name(
segment.segmentation_id)
agent_ports = values.get('ports')
for agent_ip, ports in agent_ports.items():
if agent_ip == self.agent.br_mgr.local_ip:
continue
self.agent.br_mgr.add_fdb_entries(agent_ip,
ports,
interface)
def fdb_remove(self, context, fdb_entries):
LOG.debug(_("fdb_remove received"))
for network_id, values in fdb_entries.items():
segment = self.agent.br_mgr.network_map.get(network_id)
if not segment:
return
if segment.network_type != lconst.TYPE_VXLAN:
return
interface = self.agent.br_mgr.get_vxlan_device_name(
segment.segmentation_id)
agent_ports = values.get('ports')
for agent_ip, ports in agent_ports.items():
if agent_ip == self.agent.br_mgr.local_ip:
continue
self.agent.br_mgr.remove_fdb_entries(agent_ip,
ports,
interface)
def create_rpc_dispatcher(self):
'''Get the rpc dispatcher for this manager.
@ -493,11 +735,16 @@ class LinuxBridgeNeutronAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
self.polling_interval = polling_interval
self.root_helper = root_helper
self.setup_linux_bridge(interface_mappings)
configurations = {'interface_mappings': interface_mappings}
if self.br_mgr.vxlan_mode is not lconst.VXLAN_NONE:
configurations['tunneling_ip'] = self.br_mgr.local_ip
configurations['tunnel_types'] = [lconst.TYPE_VXLAN]
configurations['l2_population'] = cfg.CONF.VXLAN.l2_population
self.agent_state = {
'binary': 'neutron-linuxbridge-agent',
'host': cfg.CONF.host,
'topic': constants.L2_AGENT_TOPIC,
'configurations': {'interface_mappings': interface_mappings},
'configurations': configurations,
'agent_type': constants.AGENT_TYPE_LINUXBRIDGE,
'start_flag': True}
@ -541,6 +788,9 @@ class LinuxBridgeNeutronAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
consumers = [[topics.PORT, topics.UPDATE],
[topics.NETWORK, topics.DELETE],
[topics.SECURITY_GROUP, topics.UPDATE]]
if cfg.CONF.VXLAN.l2_population:
consumers.append([topics.L2POPULATION,
topics.UPDATE, cfg.CONF.host])
self.connection = agent_rpc.create_consumers(self.dispatcher,
self.topic,
consumers)
@ -596,16 +846,20 @@ class LinuxBridgeNeutronAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
vlan_id = details.get('vlan_id')
(network_type,
segmentation_id) = lconst.interpret_vlan_id(vlan_id)
self.br_mgr.add_interface(details['network_id'],
network_type,
details['physical_network'],
segmentation_id,
details['port_id'])
if self.br_mgr.add_interface(details['network_id'],
network_type,
details['physical_network'],
segmentation_id,
details['port_id']):
# update plugin about port status
self.plugin_rpc.update_device_up(self.context,
device,
self.agent_id)
# update plugin about port status
self.plugin_rpc.update_device_up(self.context,
device,
self.agent_id)
else:
self.plugin_rpc.update_device_down(self.context,
device,
self.agent_id)
else:
self.remove_port_binding(details['network_id'],
details['port_id'])
@ -628,9 +882,9 @@ class LinuxBridgeNeutronAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
resync = True
if details['exists']:
LOG.info(_("Port %s updated."), device)
# Nothing to do regarding local networking
else:
LOG.debug(_("Device %s not defined on plugin"), device)
self.br_mgr.remove_empty_bridges()
return resync
def daemon_loop(self):

View File

@ -23,6 +23,7 @@ from neutron.agent.common import config
DEFAULT_VLAN_RANGES = []
DEFAULT_INTERFACE_MAPPINGS = []
DEFAULT_VXLAN_GROUP = '224.0.0.1'
vlan_opts = [
@ -35,6 +36,25 @@ vlan_opts = [
"or <physical_network>")),
]
vxlan_opts = [
cfg.BoolOpt('enable_vxlan', default=False,
help=_("Enable VXLAN on the agent. Can be enabled when "
"agent is managed by ml2 plugin using linuxbridge "
"mechanism driver")),
cfg.IntOpt('ttl',
help=_("TTL for vxlan interface protocol packets.")),
cfg.IntOpt('tos',
help=_("TOS for vxlan interface protocol packets.")),
cfg.StrOpt('vxlan_group', default=DEFAULT_VXLAN_GROUP,
help=_("Multicast group for vxlan interface.")),
cfg.StrOpt('local_ip', default='',
help=_("Local IP address of the VXLAN endpoints.")),
cfg.BoolOpt('l2_population', default=False,
help=_("Extension to use alongside ml2 plugin's l2population "
"mechanism driver. It enables the plugin to populate "
"VXLAN forwarding table.")),
]
bridge_opts = [
cfg.ListOpt('physical_interface_mappings',
default=DEFAULT_INTERFACE_MAPPINGS,
@ -52,6 +72,7 @@ agent_opts = [
cfg.CONF.register_opts(vlan_opts, "VLANS")
cfg.CONF.register_opts(vxlan_opts, "VXLAN")
cfg.CONF.register_opts(bridge_opts, "LINUX_BRIDGE")
cfg.CONF.register_opts(agent_opts, "AGENT")
config.register_agent_state_opts_helper(cfg.CONF)

View File

@ -23,9 +23,18 @@ LOCAL_VLAN_ID = -2
# Values for network_type
TYPE_FLAT = 'flat'
TYPE_VLAN = 'vlan'
TYPE_VXLAN = 'vxlan'
TYPE_LOCAL = 'local'
TYPE_NONE = 'none'
# Supported VXLAN features
VXLAN_NONE = 'not_supported'
VXLAN_MCAST = 'multicast_flooding'
VXLAN_UCAST = 'unicast_flooding'
# Corresponding minimal kernel versions requirements
MIN_VXLAN_KVER = {VXLAN_MCAST: '3.8', VXLAN_UCAST: '3.11'}
# TODO(rkukura): Eventually remove this function, which provides
# temporary backward compatibility with pre-Havana RPC and DB vlan_id

View File

@ -19,4 +19,5 @@
from neutron.common import constants
SUPPORTED_AGENT_TYPES = [constants.AGENT_TYPE_OVS]
SUPPORTED_AGENT_TYPES = [constants.AGENT_TYPE_OVS,
constants.AGENT_TYPE_LINUXBRIDGE]

View File

@ -40,12 +40,17 @@ class LinuxbridgeMechanismDriver(mech_agent.AgentMechanismDriverBase):
def check_segment_for_agent(self, segment, agent):
mappings = agent['configurations'].get('interface_mappings', {})
tunnel_types = agent['configurations'].get('tunnel_types', [])
LOG.debug(_("Checking segment: %(segment)s "
"for mappings: %(mappings)s"),
{'segment': segment, 'mappings': mappings})
"for mappings: %(mappings)s "
"with tunnel_types: %(tunnel_types)s"),
{'segment': segment, 'mappings': mappings,
'tunnel_types': tunnel_types})
network_type = segment[api.NETWORK_TYPE]
if network_type == 'local':
return True
elif network_type in tunnel_types:
return True
elif network_type in ['flat', 'vlan']:
return segment[api.PHYSICAL_NETWORK] in mappings
else:

View File

@ -35,3 +35,8 @@ class ConfigurationTest(base.BaseTestCase):
self.assertEqual(0,
len(cfg.CONF.LINUX_BRIDGE.
physical_interface_mappings))
self.assertEqual(False, cfg.CONF.VXLAN.enable_vxlan)
self.assertEqual(config.DEFAULT_VXLAN_GROUP,
cfg.CONF.VXLAN.vxlan_group)
self.assertEqual(0, len(cfg.CONF.VXLAN.local_ip))
self.assertEqual(False, cfg.CONF.VXLAN.l2_population)

View File

@ -23,11 +23,24 @@ import testtools
from neutron.agent.linux import ip_lib
from neutron.agent.linux import utils
from neutron.common import constants
from neutron.openstack.common.rpc import common as rpc_common
from neutron.plugins.linuxbridge.agent import linuxbridge_neutron_agent
from neutron.plugins.linuxbridge.common import constants as lconst
from neutron.tests import base
LOCAL_IP = '192.168.0.33'
class FakeIpLinkCommand(object):
def set_up(self):
pass
class FakeIpDevice(object):
def __init__(self):
self.link = FakeIpLinkCommand()
class TestLinuxBridge(base.BaseTestCase):
@ -61,6 +74,14 @@ class TestLinuxBridge(base.BaseTestCase):
'network_id', lconst.TYPE_VLAN, 'physnet1', 7)
self.assertTrue(vlan_bridge_func.called)
def test_ensure_physical_in_bridge_vxlan(self):
self.linux_bridge.vxlan_mode = lconst.VXLAN_UCAST
with mock.patch.object(self.linux_bridge,
'ensure_vxlan_bridge') as vxlan_bridge_func:
self.linux_bridge.ensure_physical_in_bridge(
'network_id', 'vxlan', 'physnet1', 7)
self.assertTrue(vxlan_bridge_func.called)
class TestLinuxBridgeAgent(base.BaseTestCase):
@ -184,6 +205,12 @@ class TestLinuxBridgeManager(base.BaseTestCase):
self.assertEqual(self.lbm.get_tap_device_name(if_id),
"tap")
def test_get_vxlan_device_name(self):
vn_id = constants.MAX_VXLAN_VNI
self.assertEqual(self.lbm.get_vxlan_device_name(vn_id),
"vxlan-" + str(vn_id))
self.assertEqual(self.lbm.get_vxlan_device_name(vn_id + 1), None)
def test_get_all_neutron_bridges(self):
br_list = ["br-int", "brq1", "brq2", "br-ex"]
with mock.patch.object(os, 'listdir') as listdir_fn:
@ -201,6 +228,25 @@ class TestLinuxBridgeManager(base.BaseTestCase):
self.assertEqual(self.lbm.get_interfaces_on_bridge("br0"),
["qbr1"])
def test_get_tap_devices_count(self):
with mock.patch.object(os, 'listdir') as listdir_fn:
listdir_fn.return_value = ['tap2101', 'eth0.100', 'vxlan-1000']
self.assertEqual(self.lbm.get_tap_devices_count('br0'), 1)
listdir_fn.side_effect = OSError()
self.assertEqual(self.lbm.get_tap_devices_count('br0'), 0)
def test_get_interface_by_ip(self):
with contextlib.nested(
mock.patch.object(ip_lib.IPWrapper, 'get_devices'),
mock.patch.object(ip_lib.IpAddrCommand, 'list')
) as (get_dev_fn, ip_list_fn):
device = mock.Mock()
device.name = 'dev_name'
get_dev_fn.return_value = [device]
ip_list_fn.returnvalue = mock.Mock()
self.assertEqual(self.lbm.get_interface_by_ip(LOCAL_IP),
'dev_name')
def test_get_bridge_for_tap_device(self):
with contextlib.nested(
mock.patch.object(self.lbm, "get_all_neutron_bridges"),
@ -299,6 +345,23 @@ class TestLinuxBridgeManager(base.BaseTestCase):
self.assertIsNone(self.lbm.ensure_vlan("eth0", "1"))
exec_fn.assert_called_once()
def test_ensure_vxlan(self):
seg_id = "12345678"
self.lbm.local_int = 'eth0'
self.lbm.vxlan_mode = lconst.VXLAN_MCAST
with mock.patch.object(self.lbm, 'device_exists') as de_fn:
de_fn.return_value = True
self.assertEqual(self.lbm.ensure_vxlan(seg_id), "vxlan-" + seg_id)
de_fn.return_value = False
with mock.patch.object(self.lbm.ip,
'add_vxlan') as add_vxlan_fn:
add_vxlan_fn.return_value = FakeIpDevice()
self.assertEqual(self.lbm.ensure_vxlan(seg_id),
"vxlan-" + seg_id)
add_vxlan_fn.assert_called_with("vxlan-" + seg_id, seg_id,
group="224.0.0.1",
dev=self.lbm.local_int)
def test_update_interface_ip_details(self):
gwdict = dict(gateway='1.1.1.1',
metric=50)
@ -330,8 +393,10 @@ class TestLinuxBridgeManager(base.BaseTestCase):
mock.patch.object(self.lbm, 'device_exists'),
mock.patch.object(utils, 'execute'),
mock.patch.object(self.lbm, 'update_interface_ip_details'),
mock.patch.object(self.lbm, 'interface_exists_on_bridge')
) as (de_fn, exec_fn, upd_fn, ie_fn):
mock.patch.object(self.lbm, 'interface_exists_on_bridge'),
mock.patch.object(self.lbm, 'is_device_on_bridge'),
mock.patch.object(self.lbm, 'get_bridge_for_tap_device'),
) as (de_fn, exec_fn, upd_fn, ie_fn, if_br_fn, get_if_br_fn):
de_fn.return_value = False
exec_fn.return_value = False
self.assertEqual(self.lbm.ensure_bridge("br0", None), "br0")
@ -349,6 +414,20 @@ class TestLinuxBridgeManager(base.BaseTestCase):
self.lbm.ensure_bridge("br0", "eth0")
ie_fn.assert_called_with("br0", "eth0")
exec_fn.reset_mock()
exec_fn.side_effect = None
de_fn.return_value = True
ie_fn.return_value = False
get_if_br_fn.return_value = "br1"
self.lbm.ensure_bridge("br0", "eth0")
expected = [
mock.call(['brctl', 'delif', 'br1', 'eth0'],
root_helper=self.root_helper),
mock.call(['brctl', 'addif', 'br0', 'eth0'],
root_helper=self.root_helper),
]
exec_fn.assert_has_calls(expected)
def test_ensure_physical_in_bridge(self):
self.assertFalse(
self.lbm.ensure_physical_in_bridge("123", lconst.TYPE_VLAN,
@ -367,6 +446,14 @@ class TestLinuxBridgeManager(base.BaseTestCase):
)
self.assertTrue(vlbr_fn.called)
with mock.patch.object(self.lbm, "ensure_vxlan_bridge") as vlbr_fn:
self.lbm.vxlan_mode = lconst.VXLAN_MCAST
self.assertTrue(
self.lbm.ensure_physical_in_bridge("123", lconst.TYPE_VXLAN,
"physnet1", "1")
)
self.assertTrue(vlbr_fn.called)
def test_add_tap_interface(self):
with mock.patch.object(self.lbm, "device_exists") as de_fn:
de_fn.return_value = False
@ -434,6 +521,20 @@ class TestLinuxBridgeManager(base.BaseTestCase):
updif_fn.assert_called_with("eth1", "br0", "ips", "gateway")
del_vlan.assert_called_with("eth1.1")
def test_remove_empty_bridges(self):
self.lbm.network_map = {'net1': mock.Mock(), 'net2': mock.Mock()}
def tap_count_side_effect(*args):
return 0 if args[0] == 'brqnet1' else 1
with contextlib.nested(
mock.patch.object(self.lbm, "delete_vlan_bridge"),
mock.patch.object(self.lbm, "get_tap_devices_count",
side_effect=tap_count_side_effect),
) as (del_br_fn, count_tap_fn):
self.lbm.remove_empty_bridges()
del_br_fn.assert_called_once_with('brqnet1')
def test_remove_interface(self):
with contextlib.nested(
mock.patch.object(self.lbm, "device_exists"),
@ -481,11 +582,71 @@ class TestLinuxBridgeManager(base.BaseTestCase):
"removed": set(["dev3"])
})
def _check_vxlan_support(self, kernel_version, vxlan_proxy_supported,
fdb_append_supported, l2_population,
expected_mode):
def iproute_supported_side_effect(*args):
if args[1] == 'proxy':
return vxlan_proxy_supported
elif args[1] == 'append':
return fdb_append_supported
with contextlib.nested(
mock.patch("platform.release", return_value=kernel_version),
mock.patch.object(ip_lib, 'iproute_arg_supported',
side_effect=iproute_supported_side_effect),
) as (kver_fn, ip_arg_fn):
self.lbm.check_vxlan_support()
self.assertEqual(self.lbm.vxlan_mode, expected_mode)
def test_vxlan_mode_ucast(self):
self._check_vxlan_support(kernel_version='3.12',
vxlan_proxy_supported=True,
fdb_append_supported=True,
l2_population=True,
expected_mode=lconst.VXLAN_MCAST)
def test_vxlan_mode_mcast(self):
self._check_vxlan_support(kernel_version='3.12',
vxlan_proxy_supported=True,
fdb_append_supported=False,
l2_population=True,
expected_mode=lconst.VXLAN_MCAST)
self._check_vxlan_support(kernel_version='3.10',
vxlan_proxy_supported=True,
fdb_append_supported=True,
l2_population=True,
expected_mode=lconst.VXLAN_MCAST)
def test_vxlan_mode_unsupported(self):
self._check_vxlan_support(kernel_version='3.7',
vxlan_proxy_supported=True,
fdb_append_supported=True,
l2_population=False,
expected_mode=lconst.VXLAN_NONE)
self._check_vxlan_support(kernel_version='3.10',
vxlan_proxy_supported=False,
fdb_append_supported=False,
l2_population=False,
expected_mode=lconst.VXLAN_NONE)
cfg.CONF.set_override('vxlan_group', '', 'VXLAN')
self._check_vxlan_support(kernel_version='3.12',
vxlan_proxy_supported=True,
fdb_append_supported=True,
l2_population=True,
expected_mode=lconst.VXLAN_NONE)
class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
def setUp(self):
cfg.CONF.set_override('local_ip', LOCAL_IP, 'VXLAN')
self.addCleanup(cfg.CONF.reset)
super(TestLinuxBridgeRpcCallbacks, self).setUp()
self.u_execute_p = mock.patch('neutron.agent.linux.utils.execute')
self.u_execute = self.u_execute_p.start()
self.addCleanup(self.u_execute_p.stop)
class FakeLBAgent(object):
def __init__(self):
self.agent_id = 1
@ -493,11 +654,19 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
LinuxBridgeManager({'physnet1': 'eth1'},
cfg.CONF.AGENT.root_helper))
self.br_mgr.vxlan_mode = lconst.VXLAN_UCAST
segment = mock.Mock()
segment.network_type = 'vxlan'
segment.segmentation_id = 1
self.br_mgr.network_map['net_id'] = segment
self.lb_rpc = linuxbridge_neutron_agent.LinuxBridgeRpcCallbacks(
object(),
FakeLBAgent()
)
self.root_helper = cfg.CONF.AGENT.root_helper
def test_network_delete(self):
with contextlib.nested(
mock.patch.object(self.lb_rpc.agent.br_mgr, "get_bridge_name"),
@ -620,3 +789,92 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
self.lb_rpc.port_update(mock.Mock(), port=port)
self.assertTrue(plugin_rpc.update_device_down.called)
self.assertEqual(log.call_count, 1)
def test_fdb_add(self):
fdb_entries = {'net_id':
{'ports':
{'agent_ip': [constants.FLOODING_ENTRY,
['port_mac', 'port_ip']]},
'network_type': 'vxlan',
'segment_id': 1}}
with mock.patch.object(utils, 'execute',
return_value='') as execute_fn:
self.lb_rpc.fdb_add(None, fdb_entries)
expected = [
mock.call(['bridge', 'fdb', 'show', 'dev', 'vxlan-1'],
root_helper=self.root_helper),
mock.call(['bridge', 'fdb', 'add',
constants.FLOODING_ENTRY[0],
'dev', 'vxlan-1', 'dst', 'agent_ip'],
root_helper=self.root_helper,
check_exit_code=False),
mock.call(['ip', 'neigh', 'add', 'port_ip', 'lladdr',
'port_mac', 'dev', 'vxlan-1', 'nud', 'permanent'],
root_helper=self.root_helper,
check_exit_code=False),
mock.call(['bridge', 'fdb', 'add', 'port_mac', 'dev',
'vxlan-1', 'dst', 'agent_ip'],
root_helper=self.root_helper,
check_exit_code=False),
]
execute_fn.assert_has_calls(expected)
def test_fdb_ignore(self):
fdb_entries = {'net_id':
{'ports':
{LOCAL_IP: [constants.FLOODING_ENTRY,
['port_mac', 'port_ip']]},
'network_type': 'vxlan',
'segment_id': 1}}
with mock.patch.object(utils, 'execute',
return_value='') as execute_fn:
self.lb_rpc.fdb_add(None, fdb_entries)
self.lb_rpc.fdb_remove(None, fdb_entries)
self.assertFalse(execute_fn.called)
fdb_entries = {'other_net_id':
{'ports':
{'192.168.0.67': [constants.FLOODING_ENTRY,
['port_mac', 'port_ip']]},
'network_type': 'vxlan',
'segment_id': 1}}
with mock.patch.object(utils, 'execute',
return_value='') as execute_fn:
self.lb_rpc.fdb_add(None, fdb_entries)
self.lb_rpc.fdb_remove(None, fdb_entries)
self.assertFalse(execute_fn.called)
def test_fdb_remove(self):
fdb_entries = {'net_id':
{'ports':
{'agent_ip': [constants.FLOODING_ENTRY,
['port_mac', 'port_ip']]},
'network_type': 'vxlan',
'segment_id': 1}}
with mock.patch.object(utils, 'execute',
return_value='') as execute_fn:
self.lb_rpc.fdb_remove(None, fdb_entries)
expected = [
mock.call(['bridge', 'fdb', 'del',
constants.FLOODING_ENTRY[0],
'dev', 'vxlan-1', 'dst', 'agent_ip'],
root_helper=self.root_helper,
check_exit_code=False),
mock.call(['ip', 'neigh', 'del', 'port_ip', 'lladdr',
'port_mac', 'dev', 'vxlan-1'],
root_helper=self.root_helper,
check_exit_code=False),
mock.call(['bridge', 'fdb', 'del', 'port_mac',
'dev', 'vxlan-1', 'dst', 'agent_ip'],
root_helper=self.root_helper,
check_exit_code=False),
]
execute_fn.assert_has_calls(expected)

View File

@ -25,10 +25,14 @@ class LinuxbridgeMechanismBaseTestCase(base.AgentMechanismBaseTestCase):
AGENT_TYPE = constants.AGENT_TYPE_LINUXBRIDGE
GOOD_MAPPINGS = {'fake_physical_network': 'fake_interface'}
GOOD_CONFIGS = {'interface_mappings': GOOD_MAPPINGS}
GOOD_TUNNEL_TYPES = ['gre', 'vxlan']
GOOD_CONFIGS = {'interface_mappings': GOOD_MAPPINGS,
'tunnel_types': GOOD_TUNNEL_TYPES}
BAD_MAPPINGS = {'wrong_physical_network': 'wrong_interface'}
BAD_CONFIGS = {'interface_mappings': BAD_MAPPINGS}
BAD_TUNNEL_TYPES = ['bad_tunnel_type']
BAD_CONFIGS = {'interface_mappings': BAD_MAPPINGS,
'tunnel_types': BAD_TUNNEL_TYPES}
AGENTS = [{'alive': True,
'configurations': GOOD_CONFIGS}]
@ -63,3 +67,8 @@ class LinuxbridgeMechanismFlatTestCase(LinuxbridgeMechanismBaseTestCase,
class LinuxbridgeMechanismVlanTestCase(LinuxbridgeMechanismBaseTestCase,
base.AgentMechanismVlanTestCase):
pass
class LinuxbridgeMechanismGreTestCase(LinuxbridgeMechanismBaseTestCase,
base.AgentMechanismGreTestCase):
pass