# 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
# 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
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)
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'))
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'
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'")
# Neutron OpenVSwitch Plugin.
# @author: Sumit Naiksatam, Cisco Systems, Inc.
+import distutils.version as dist_version
import os
+import platform
import sys
import time
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
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:
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)
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)
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:
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')
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:
# 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:
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"),
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,
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'],
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):
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:
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)
+
+ 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):
+class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
+ l2pop_rpc.L2populationRpcCallBackMixin):
# Set RPC API version to 1.0 by default.
# history
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:
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.
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}
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)
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'])
-
- # update plugin about port status
- self.plugin_rpc.update_device_up(self.context,
- device,
- self.agent_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)
+ else:
+ self.plugin_rpc.update_device_down(self.context,
+ device,
+ self.agent_id)
else:
self.remove_port_binding(details['network_id'],
details['port_id'])
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):
DEFAULT_VLAN_RANGES = []
DEFAULT_INTERFACE_MAPPINGS = []
+DEFAULT_VXLAN_GROUP = '224.0.0.1'
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,
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)
# 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
from neutron.common import constants
-SUPPORTED_AGENT_TYPES = [constants.AGENT_TYPE_OVS]
+SUPPORTED_AGENT_TYPES = [constants.AGENT_TYPE_OVS,
+ constants.AGENT_TYPE_LINUXBRIDGE]
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:
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)
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):
'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):
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:
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"),
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)
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")
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,
)
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
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"),
"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
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"),
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)
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}]
class LinuxbridgeMechanismVlanTestCase(LinuxbridgeMechanismBaseTestCase,
base.AgentMechanismVlanTestCase):
pass
+
+
+class LinuxbridgeMechanismGreTestCase(LinuxbridgeMechanismBaseTestCase,
+ base.AgentMechanismGreTestCase):
+ pass