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:
parent
98a9308728
commit
81e793d9f1
@ -31,6 +31,33 @@
|
|||||||
# physical_interface_mappings =
|
# physical_interface_mappings =
|
||||||
# Example: physical_interface_mappings = physnet1:eth1
|
# 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]
|
||||||
# Agent's polling interval in seconds
|
# Agent's polling interval in seconds
|
||||||
# polling_interval = 2
|
# polling_interval = 2
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
# unclear whether both variants are necessary, but I'm transliterating
|
# unclear whether both variants are necessary, but I'm transliterating
|
||||||
# from the old mechanism
|
# from the old mechanism
|
||||||
brctl: CommandFilter, brctl, root
|
brctl: CommandFilter, brctl, root
|
||||||
|
bridge: CommandFilter, bridge, root
|
||||||
|
|
||||||
# ip_lib
|
# ip_lib
|
||||||
ip: IpFilter, ip, root
|
ip: IpFilter, ip, root
|
||||||
|
@ -146,6 +146,27 @@ class IPWrapper(SubProcessBase):
|
|||||||
if self.namespace:
|
if self.namespace:
|
||||||
device.link.set_netns(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
|
@classmethod
|
||||||
def get_namespaces(cls, root_helper):
|
def get_namespaces(cls, root_helper):
|
||||||
output = cls._execute('', 'netns', ('list',), root_helper=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:
|
except RuntimeError:
|
||||||
return False
|
return False
|
||||||
return bool(address)
|
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'))
|
||||||
|
@ -44,6 +44,7 @@ DHCP_RESPONSE_PORT = 68
|
|||||||
|
|
||||||
MIN_VLAN_TAG = 1
|
MIN_VLAN_TAG = 1
|
||||||
MAX_VLAN_TAG = 4094
|
MAX_VLAN_TAG = 4094
|
||||||
|
MAX_VXLAN_VNI = 16777215
|
||||||
FLOODING_ENTRY = ['00:00:00:00:00:00', '0.0.0.0']
|
FLOODING_ENTRY = ['00:00:00:00:00:00', '0.0.0.0']
|
||||||
|
|
||||||
EXT_NS_COMP = '_backward_comp_e_ns'
|
EXT_NS_COMP = '_backward_comp_e_ns'
|
||||||
|
@ -293,3 +293,7 @@ class NetworkVlanRangeError(NeutronException):
|
|||||||
if isinstance(kwargs['vlan_range'], tuple):
|
if isinstance(kwargs['vlan_range'], tuple):
|
||||||
kwargs['vlan_range'] = "%d:%d" % kwargs['vlan_range']
|
kwargs['vlan_range'] = "%d:%d" % kwargs['vlan_range']
|
||||||
super(NetworkVlanRangeError, self).__init__(**kwargs)
|
super(NetworkVlanRangeError, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkVxlanPortRangeError(object):
|
||||||
|
message = _("Invalid network VXLAN port range: '%(vxlan_range)s'")
|
||||||
|
@ -22,7 +22,9 @@
|
|||||||
# Neutron OpenVSwitch Plugin.
|
# Neutron OpenVSwitch Plugin.
|
||||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||||
|
|
||||||
|
import distutils.version as dist_version
|
||||||
import os
|
import os
|
||||||
|
import platform
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -30,6 +32,7 @@ import eventlet
|
|||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
import pyudev
|
import pyudev
|
||||||
|
|
||||||
|
from neutron.agent import l2population_rpc as l2pop_rpc
|
||||||
from neutron.agent.linux import ip_lib
|
from neutron.agent.linux import ip_lib
|
||||||
from neutron.agent.linux import utils
|
from neutron.agent.linux import utils
|
||||||
from neutron.agent import rpc as agent_rpc
|
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/"
|
BRIDGE_INTERFACES_FS = BRIDGE_FS + BRIDGE_NAME_PLACEHOLDER + "/brif/"
|
||||||
DEVICE_NAME_PLACEHOLDER = "device_name"
|
DEVICE_NAME_PLACEHOLDER = "device_name"
|
||||||
BRIDGE_PORT_FS_FOR_DEVICE = BRIDGE_FS + DEVICE_NAME_PLACEHOLDER + "/brport"
|
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:
|
class LinuxBridgeManager:
|
||||||
@ -63,6 +74,18 @@ class LinuxBridgeManager:
|
|||||||
self.interface_mappings = interface_mappings
|
self.interface_mappings = interface_mappings
|
||||||
self.root_helper = root_helper
|
self.root_helper = root_helper
|
||||||
self.ip = ip_lib.IPWrapper(self.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()
|
self.udev = pyudev.Context()
|
||||||
monitor = pyudev.Monitor.from_netlink(self.udev)
|
monitor = pyudev.Monitor.from_netlink(self.udev)
|
||||||
@ -105,6 +128,13 @@ class LinuxBridgeManager:
|
|||||||
tap_device_name = TAP_INTERFACE_PREFIX + interface_id[0:11]
|
tap_device_name = TAP_INTERFACE_PREFIX + interface_id[0:11]
|
||||||
return tap_device_name
|
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):
|
def get_all_neutron_bridges(self):
|
||||||
neutron_bridge_list = []
|
neutron_bridge_list = []
|
||||||
bridge_list = os.listdir(BRIDGE_FS)
|
bridge_list = os.listdir(BRIDGE_FS)
|
||||||
@ -119,6 +149,21 @@ class LinuxBridgeManager:
|
|||||||
BRIDGE_NAME_PLACEHOLDER, bridge_name)
|
BRIDGE_NAME_PLACEHOLDER, bridge_name)
|
||||||
return os.listdir(bridge_interface_path)
|
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):
|
def get_bridge_for_tap_device(self, tap_device_name):
|
||||||
bridges = self.get_all_neutron_bridges()
|
bridges = self.get_all_neutron_bridges()
|
||||||
for bridge in bridges:
|
for bridge in bridges:
|
||||||
@ -144,6 +189,18 @@ class LinuxBridgeManager:
|
|||||||
if self.ensure_bridge(bridge_name, interface, ips, gateway):
|
if self.ensure_bridge(bridge_name, interface, ips, gateway):
|
||||||
return interface
|
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):
|
def get_interface_details(self, interface):
|
||||||
device = self.ip.device(interface)
|
device = self.ip.device(interface)
|
||||||
ips = device.addr.list(scope='global')
|
ips = device.addr.list(scope='global')
|
||||||
@ -184,6 +241,26 @@ class LinuxBridgeManager:
|
|||||||
LOG.debug(_("Done creating subinterface %s"), interface)
|
LOG.debug(_("Done creating subinterface %s"), interface)
|
||||||
return 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,
|
def update_interface_ip_details(self, destination, source, ips,
|
||||||
gateway):
|
gateway):
|
||||||
if ips or gateway:
|
if ips or gateway:
|
||||||
@ -244,6 +321,12 @@ class LinuxBridgeManager:
|
|||||||
# Check if the interface is part of the bridge
|
# Check if the interface is part of the bridge
|
||||||
if not self.interface_exists_on_bridge(bridge_name, interface):
|
if not self.interface_exists_on_bridge(bridge_name, interface):
|
||||||
try:
|
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],
|
utils.execute(['brctl', 'addif', bridge_name, interface],
|
||||||
root_helper=self.root_helper)
|
root_helper=self.root_helper)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -258,6 +341,13 @@ class LinuxBridgeManager:
|
|||||||
network_type,
|
network_type,
|
||||||
physical_network,
|
physical_network,
|
||||||
segmentation_id):
|
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)
|
physical_interface = self.interface_mappings.get(physical_network)
|
||||||
if not physical_interface:
|
if not physical_interface:
|
||||||
LOG.error(_("No mapping for physical network %s"),
|
LOG.error(_("No mapping for physical network %s"),
|
||||||
@ -315,6 +405,9 @@ class LinuxBridgeManager:
|
|||||||
|
|
||||||
def add_interface(self, network_id, network_type, physical_network,
|
def add_interface(self, network_id, network_type, physical_network,
|
||||||
segmentation_id, port_id):
|
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)
|
tap_device_name = self.get_tap_device_name(port_id)
|
||||||
return self.add_tap_interface(network_id, network_type,
|
return self.add_tap_interface(network_id, network_type,
|
||||||
physical_network, segmentation_id,
|
physical_network, segmentation_id,
|
||||||
@ -333,9 +426,10 @@ class LinuxBridgeManager:
|
|||||||
self.update_interface_ip_details(interface,
|
self.update_interface_ip_details(interface,
|
||||||
bridge_name,
|
bridge_name,
|
||||||
ips, gateway)
|
ips, gateway)
|
||||||
else:
|
elif interface.startswith(physical_interface):
|
||||||
if interface.startswith(physical_interface):
|
|
||||||
self.delete_vlan(interface)
|
self.delete_vlan(interface)
|
||||||
|
elif interface.startswith(VXLAN_INTERFACE_PREFIX):
|
||||||
|
self.delete_vxlan(interface)
|
||||||
|
|
||||||
LOG.debug(_("Deleting bridge %s"), bridge_name)
|
LOG.debug(_("Deleting bridge %s"), bridge_name)
|
||||||
if utils.execute(['ip', 'link', 'set', bridge_name, 'down'],
|
if utils.execute(['ip', 'link', 'set', bridge_name, 'down'],
|
||||||
@ -350,6 +444,13 @@ class LinuxBridgeManager:
|
|||||||
LOG.error(_("Cannot delete bridge %s, does not exist"),
|
LOG.error(_("Cannot delete bridge %s, does not exist"),
|
||||||
bridge_name)
|
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):
|
def remove_interface(self, bridge_name, interface_name):
|
||||||
if self.device_exists(bridge_name):
|
if self.device_exists(bridge_name):
|
||||||
if not self.is_device_on_bridge(interface_name):
|
if not self.is_device_on_bridge(interface_name):
|
||||||
@ -384,6 +485,15 @@ class LinuxBridgeManager:
|
|||||||
return
|
return
|
||||||
LOG.debug(_("Done deleting subinterface %s"), interface)
|
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):
|
def update_devices(self, registered_devices):
|
||||||
devices = self.udev_get_tap_devices()
|
devices = self.udev_get_tap_devices()
|
||||||
if devices == registered_devices:
|
if devices == registered_devices:
|
||||||
@ -408,8 +518,92 @@ class LinuxBridgeManager:
|
|||||||
def udev_get_name(self, device):
|
def udev_get_name(self, device):
|
||||||
return device.sys_name
|
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.
|
# Set RPC API version to 1.0 by default.
|
||||||
# history
|
# history
|
||||||
@ -451,15 +645,19 @@ class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
|
|||||||
segmentation_id) = lconst.interpret_vlan_id(vlan_id)
|
segmentation_id) = lconst.interpret_vlan_id(vlan_id)
|
||||||
physical_network = kwargs.get('physical_network')
|
physical_network = kwargs.get('physical_network')
|
||||||
# create the networking for the port
|
# create the networking for the port
|
||||||
self.agent.br_mgr.add_interface(port['network_id'],
|
if self.agent.br_mgr.add_interface(port['network_id'],
|
||||||
network_type,
|
network_type,
|
||||||
physical_network,
|
physical_network,
|
||||||
segmentation_id,
|
segmentation_id,
|
||||||
port['id'])
|
port['id']):
|
||||||
# update plugin about port status
|
# update plugin about port status
|
||||||
self.agent.plugin_rpc.update_device_up(self.context,
|
self.agent.plugin_rpc.update_device_up(self.context,
|
||||||
tap_device_name,
|
tap_device_name,
|
||||||
self.agent.agent_id)
|
self.agent.agent_id)
|
||||||
|
else:
|
||||||
|
self.plugin_rpc.update_device_down(self.context,
|
||||||
|
tap_device_name,
|
||||||
|
self.agent.agent_id)
|
||||||
else:
|
else:
|
||||||
bridge_name = self.agent.br_mgr.get_bridge_name(
|
bridge_name = self.agent.br_mgr.get_bridge_name(
|
||||||
port['network_id'])
|
port['network_id'])
|
||||||
@ -472,6 +670,50 @@ class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
|
|||||||
except rpc_common.Timeout:
|
except rpc_common.Timeout:
|
||||||
LOG.error(_("RPC timeout while updating port %s"), port['id'])
|
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):
|
def create_rpc_dispatcher(self):
|
||||||
'''Get the rpc dispatcher for this manager.
|
'''Get the rpc dispatcher for this manager.
|
||||||
|
|
||||||
@ -493,11 +735,16 @@ class LinuxBridgeNeutronAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
|
|||||||
self.polling_interval = polling_interval
|
self.polling_interval = polling_interval
|
||||||
self.root_helper = root_helper
|
self.root_helper = root_helper
|
||||||
self.setup_linux_bridge(interface_mappings)
|
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 = {
|
self.agent_state = {
|
||||||
'binary': 'neutron-linuxbridge-agent',
|
'binary': 'neutron-linuxbridge-agent',
|
||||||
'host': cfg.CONF.host,
|
'host': cfg.CONF.host,
|
||||||
'topic': constants.L2_AGENT_TOPIC,
|
'topic': constants.L2_AGENT_TOPIC,
|
||||||
'configurations': {'interface_mappings': interface_mappings},
|
'configurations': configurations,
|
||||||
'agent_type': constants.AGENT_TYPE_LINUXBRIDGE,
|
'agent_type': constants.AGENT_TYPE_LINUXBRIDGE,
|
||||||
'start_flag': True}
|
'start_flag': True}
|
||||||
|
|
||||||
@ -541,6 +788,9 @@ class LinuxBridgeNeutronAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
|
|||||||
consumers = [[topics.PORT, topics.UPDATE],
|
consumers = [[topics.PORT, topics.UPDATE],
|
||||||
[topics.NETWORK, topics.DELETE],
|
[topics.NETWORK, topics.DELETE],
|
||||||
[topics.SECURITY_GROUP, topics.UPDATE]]
|
[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.connection = agent_rpc.create_consumers(self.dispatcher,
|
||||||
self.topic,
|
self.topic,
|
||||||
consumers)
|
consumers)
|
||||||
@ -596,16 +846,20 @@ class LinuxBridgeNeutronAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
|
|||||||
vlan_id = details.get('vlan_id')
|
vlan_id = details.get('vlan_id')
|
||||||
(network_type,
|
(network_type,
|
||||||
segmentation_id) = lconst.interpret_vlan_id(vlan_id)
|
segmentation_id) = lconst.interpret_vlan_id(vlan_id)
|
||||||
self.br_mgr.add_interface(details['network_id'],
|
if self.br_mgr.add_interface(details['network_id'],
|
||||||
network_type,
|
network_type,
|
||||||
details['physical_network'],
|
details['physical_network'],
|
||||||
segmentation_id,
|
segmentation_id,
|
||||||
details['port_id'])
|
details['port_id']):
|
||||||
|
|
||||||
# update plugin about port status
|
# update plugin about port status
|
||||||
self.plugin_rpc.update_device_up(self.context,
|
self.plugin_rpc.update_device_up(self.context,
|
||||||
device,
|
device,
|
||||||
self.agent_id)
|
self.agent_id)
|
||||||
|
else:
|
||||||
|
self.plugin_rpc.update_device_down(self.context,
|
||||||
|
device,
|
||||||
|
self.agent_id)
|
||||||
else:
|
else:
|
||||||
self.remove_port_binding(details['network_id'],
|
self.remove_port_binding(details['network_id'],
|
||||||
details['port_id'])
|
details['port_id'])
|
||||||
@ -628,9 +882,9 @@ class LinuxBridgeNeutronAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin):
|
|||||||
resync = True
|
resync = True
|
||||||
if details['exists']:
|
if details['exists']:
|
||||||
LOG.info(_("Port %s updated."), device)
|
LOG.info(_("Port %s updated."), device)
|
||||||
# Nothing to do regarding local networking
|
|
||||||
else:
|
else:
|
||||||
LOG.debug(_("Device %s not defined on plugin"), device)
|
LOG.debug(_("Device %s not defined on plugin"), device)
|
||||||
|
self.br_mgr.remove_empty_bridges()
|
||||||
return resync
|
return resync
|
||||||
|
|
||||||
def daemon_loop(self):
|
def daemon_loop(self):
|
||||||
|
@ -23,6 +23,7 @@ from neutron.agent.common import config
|
|||||||
|
|
||||||
DEFAULT_VLAN_RANGES = []
|
DEFAULT_VLAN_RANGES = []
|
||||||
DEFAULT_INTERFACE_MAPPINGS = []
|
DEFAULT_INTERFACE_MAPPINGS = []
|
||||||
|
DEFAULT_VXLAN_GROUP = '224.0.0.1'
|
||||||
|
|
||||||
|
|
||||||
vlan_opts = [
|
vlan_opts = [
|
||||||
@ -35,6 +36,25 @@ vlan_opts = [
|
|||||||
"or <physical_network>")),
|
"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 = [
|
bridge_opts = [
|
||||||
cfg.ListOpt('physical_interface_mappings',
|
cfg.ListOpt('physical_interface_mappings',
|
||||||
default=DEFAULT_INTERFACE_MAPPINGS,
|
default=DEFAULT_INTERFACE_MAPPINGS,
|
||||||
@ -52,6 +72,7 @@ agent_opts = [
|
|||||||
|
|
||||||
|
|
||||||
cfg.CONF.register_opts(vlan_opts, "VLANS")
|
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(bridge_opts, "LINUX_BRIDGE")
|
||||||
cfg.CONF.register_opts(agent_opts, "AGENT")
|
cfg.CONF.register_opts(agent_opts, "AGENT")
|
||||||
config.register_agent_state_opts_helper(cfg.CONF)
|
config.register_agent_state_opts_helper(cfg.CONF)
|
||||||
|
@ -23,9 +23,18 @@ LOCAL_VLAN_ID = -2
|
|||||||
# Values for network_type
|
# Values for network_type
|
||||||
TYPE_FLAT = 'flat'
|
TYPE_FLAT = 'flat'
|
||||||
TYPE_VLAN = 'vlan'
|
TYPE_VLAN = 'vlan'
|
||||||
|
TYPE_VXLAN = 'vxlan'
|
||||||
TYPE_LOCAL = 'local'
|
TYPE_LOCAL = 'local'
|
||||||
TYPE_NONE = 'none'
|
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
|
# TODO(rkukura): Eventually remove this function, which provides
|
||||||
# temporary backward compatibility with pre-Havana RPC and DB vlan_id
|
# temporary backward compatibility with pre-Havana RPC and DB vlan_id
|
||||||
|
@ -19,4 +19,5 @@
|
|||||||
|
|
||||||
from neutron.common import constants
|
from neutron.common import constants
|
||||||
|
|
||||||
SUPPORTED_AGENT_TYPES = [constants.AGENT_TYPE_OVS]
|
SUPPORTED_AGENT_TYPES = [constants.AGENT_TYPE_OVS,
|
||||||
|
constants.AGENT_TYPE_LINUXBRIDGE]
|
||||||
|
@ -40,12 +40,17 @@ class LinuxbridgeMechanismDriver(mech_agent.AgentMechanismDriverBase):
|
|||||||
|
|
||||||
def check_segment_for_agent(self, segment, agent):
|
def check_segment_for_agent(self, segment, agent):
|
||||||
mappings = agent['configurations'].get('interface_mappings', {})
|
mappings = agent['configurations'].get('interface_mappings', {})
|
||||||
|
tunnel_types = agent['configurations'].get('tunnel_types', [])
|
||||||
LOG.debug(_("Checking segment: %(segment)s "
|
LOG.debug(_("Checking segment: %(segment)s "
|
||||||
"for mappings: %(mappings)s"),
|
"for mappings: %(mappings)s "
|
||||||
{'segment': segment, 'mappings': mappings})
|
"with tunnel_types: %(tunnel_types)s"),
|
||||||
|
{'segment': segment, 'mappings': mappings,
|
||||||
|
'tunnel_types': tunnel_types})
|
||||||
network_type = segment[api.NETWORK_TYPE]
|
network_type = segment[api.NETWORK_TYPE]
|
||||||
if network_type == 'local':
|
if network_type == 'local':
|
||||||
return True
|
return True
|
||||||
|
elif network_type in tunnel_types:
|
||||||
|
return True
|
||||||
elif network_type in ['flat', 'vlan']:
|
elif network_type in ['flat', 'vlan']:
|
||||||
return segment[api.PHYSICAL_NETWORK] in mappings
|
return segment[api.PHYSICAL_NETWORK] in mappings
|
||||||
else:
|
else:
|
||||||
|
@ -35,3 +35,8 @@ class ConfigurationTest(base.BaseTestCase):
|
|||||||
self.assertEqual(0,
|
self.assertEqual(0,
|
||||||
len(cfg.CONF.LINUX_BRIDGE.
|
len(cfg.CONF.LINUX_BRIDGE.
|
||||||
physical_interface_mappings))
|
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)
|
||||||
|
@ -23,11 +23,24 @@ import testtools
|
|||||||
|
|
||||||
from neutron.agent.linux import ip_lib
|
from neutron.agent.linux import ip_lib
|
||||||
from neutron.agent.linux import utils
|
from neutron.agent.linux import utils
|
||||||
|
from neutron.common import constants
|
||||||
from neutron.openstack.common.rpc import common as rpc_common
|
from neutron.openstack.common.rpc import common as rpc_common
|
||||||
from neutron.plugins.linuxbridge.agent import linuxbridge_neutron_agent
|
from neutron.plugins.linuxbridge.agent import linuxbridge_neutron_agent
|
||||||
from neutron.plugins.linuxbridge.common import constants as lconst
|
from neutron.plugins.linuxbridge.common import constants as lconst
|
||||||
from neutron.tests import base
|
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):
|
class TestLinuxBridge(base.BaseTestCase):
|
||||||
|
|
||||||
@ -61,6 +74,14 @@ class TestLinuxBridge(base.BaseTestCase):
|
|||||||
'network_id', lconst.TYPE_VLAN, 'physnet1', 7)
|
'network_id', lconst.TYPE_VLAN, 'physnet1', 7)
|
||||||
self.assertTrue(vlan_bridge_func.called)
|
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):
|
class TestLinuxBridgeAgent(base.BaseTestCase):
|
||||||
|
|
||||||
@ -184,6 +205,12 @@ class TestLinuxBridgeManager(base.BaseTestCase):
|
|||||||
self.assertEqual(self.lbm.get_tap_device_name(if_id),
|
self.assertEqual(self.lbm.get_tap_device_name(if_id),
|
||||||
"tap")
|
"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):
|
def test_get_all_neutron_bridges(self):
|
||||||
br_list = ["br-int", "brq1", "brq2", "br-ex"]
|
br_list = ["br-int", "brq1", "brq2", "br-ex"]
|
||||||
with mock.patch.object(os, 'listdir') as listdir_fn:
|
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"),
|
self.assertEqual(self.lbm.get_interfaces_on_bridge("br0"),
|
||||||
["qbr1"])
|
["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):
|
def test_get_bridge_for_tap_device(self):
|
||||||
with contextlib.nested(
|
with contextlib.nested(
|
||||||
mock.patch.object(self.lbm, "get_all_neutron_bridges"),
|
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"))
|
self.assertIsNone(self.lbm.ensure_vlan("eth0", "1"))
|
||||||
exec_fn.assert_called_once()
|
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):
|
def test_update_interface_ip_details(self):
|
||||||
gwdict = dict(gateway='1.1.1.1',
|
gwdict = dict(gateway='1.1.1.1',
|
||||||
metric=50)
|
metric=50)
|
||||||
@ -330,8 +393,10 @@ class TestLinuxBridgeManager(base.BaseTestCase):
|
|||||||
mock.patch.object(self.lbm, 'device_exists'),
|
mock.patch.object(self.lbm, 'device_exists'),
|
||||||
mock.patch.object(utils, 'execute'),
|
mock.patch.object(utils, 'execute'),
|
||||||
mock.patch.object(self.lbm, 'update_interface_ip_details'),
|
mock.patch.object(self.lbm, 'update_interface_ip_details'),
|
||||||
mock.patch.object(self.lbm, 'interface_exists_on_bridge')
|
mock.patch.object(self.lbm, 'interface_exists_on_bridge'),
|
||||||
) as (de_fn, exec_fn, upd_fn, ie_fn):
|
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
|
de_fn.return_value = False
|
||||||
exec_fn.return_value = False
|
exec_fn.return_value = False
|
||||||
self.assertEqual(self.lbm.ensure_bridge("br0", None), "br0")
|
self.assertEqual(self.lbm.ensure_bridge("br0", None), "br0")
|
||||||
@ -349,6 +414,20 @@ class TestLinuxBridgeManager(base.BaseTestCase):
|
|||||||
self.lbm.ensure_bridge("br0", "eth0")
|
self.lbm.ensure_bridge("br0", "eth0")
|
||||||
ie_fn.assert_called_with("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):
|
def test_ensure_physical_in_bridge(self):
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
self.lbm.ensure_physical_in_bridge("123", lconst.TYPE_VLAN,
|
self.lbm.ensure_physical_in_bridge("123", lconst.TYPE_VLAN,
|
||||||
@ -367,6 +446,14 @@ class TestLinuxBridgeManager(base.BaseTestCase):
|
|||||||
)
|
)
|
||||||
self.assertTrue(vlbr_fn.called)
|
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):
|
def test_add_tap_interface(self):
|
||||||
with mock.patch.object(self.lbm, "device_exists") as de_fn:
|
with mock.patch.object(self.lbm, "device_exists") as de_fn:
|
||||||
de_fn.return_value = False
|
de_fn.return_value = False
|
||||||
@ -434,6 +521,20 @@ class TestLinuxBridgeManager(base.BaseTestCase):
|
|||||||
updif_fn.assert_called_with("eth1", "br0", "ips", "gateway")
|
updif_fn.assert_called_with("eth1", "br0", "ips", "gateway")
|
||||||
del_vlan.assert_called_with("eth1.1")
|
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):
|
def test_remove_interface(self):
|
||||||
with contextlib.nested(
|
with contextlib.nested(
|
||||||
mock.patch.object(self.lbm, "device_exists"),
|
mock.patch.object(self.lbm, "device_exists"),
|
||||||
@ -481,11 +582,71 @@ class TestLinuxBridgeManager(base.BaseTestCase):
|
|||||||
"removed": set(["dev3"])
|
"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):
|
class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
cfg.CONF.set_override('local_ip', LOCAL_IP, 'VXLAN')
|
||||||
|
self.addCleanup(cfg.CONF.reset)
|
||||||
super(TestLinuxBridgeRpcCallbacks, self).setUp()
|
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):
|
class FakeLBAgent(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.agent_id = 1
|
self.agent_id = 1
|
||||||
@ -493,11 +654,19 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase):
|
|||||||
LinuxBridgeManager({'physnet1': 'eth1'},
|
LinuxBridgeManager({'physnet1': 'eth1'},
|
||||||
cfg.CONF.AGENT.root_helper))
|
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(
|
self.lb_rpc = linuxbridge_neutron_agent.LinuxBridgeRpcCallbacks(
|
||||||
object(),
|
object(),
|
||||||
FakeLBAgent()
|
FakeLBAgent()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.root_helper = cfg.CONF.AGENT.root_helper
|
||||||
|
|
||||||
def test_network_delete(self):
|
def test_network_delete(self):
|
||||||
with contextlib.nested(
|
with contextlib.nested(
|
||||||
mock.patch.object(self.lb_rpc.agent.br_mgr, "get_bridge_name"),
|
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.lb_rpc.port_update(mock.Mock(), port=port)
|
||||||
self.assertTrue(plugin_rpc.update_device_down.called)
|
self.assertTrue(plugin_rpc.update_device_down.called)
|
||||||
self.assertEqual(log.call_count, 1)
|
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)
|
||||||
|
@ -25,10 +25,14 @@ class LinuxbridgeMechanismBaseTestCase(base.AgentMechanismBaseTestCase):
|
|||||||
AGENT_TYPE = constants.AGENT_TYPE_LINUXBRIDGE
|
AGENT_TYPE = constants.AGENT_TYPE_LINUXBRIDGE
|
||||||
|
|
||||||
GOOD_MAPPINGS = {'fake_physical_network': 'fake_interface'}
|
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_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,
|
AGENTS = [{'alive': True,
|
||||||
'configurations': GOOD_CONFIGS}]
|
'configurations': GOOD_CONFIGS}]
|
||||||
@ -63,3 +67,8 @@ class LinuxbridgeMechanismFlatTestCase(LinuxbridgeMechanismBaseTestCase,
|
|||||||
class LinuxbridgeMechanismVlanTestCase(LinuxbridgeMechanismBaseTestCase,
|
class LinuxbridgeMechanismVlanTestCase(LinuxbridgeMechanismBaseTestCase,
|
||||||
base.AgentMechanismVlanTestCase):
|
base.AgentMechanismVlanTestCase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LinuxbridgeMechanismGreTestCase(LinuxbridgeMechanismBaseTestCase,
|
||||||
|
base.AgentMechanismGreTestCase):
|
||||||
|
pass
|
||||||
|
Loading…
Reference in New Issue
Block a user