]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Vxlan / L2population support to Linuxbridge Agent
authorFrancois Eleouet <f.eleouet@gmail.com>
Wed, 11 Sep 2013 12:19:39 +0000 (14:19 +0200)
committerFrancois Eleouet <f.eleouet@gmail.com>
Thu, 12 Sep 2013 00:51:37 +0000 (02:51 +0200)
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

13 files changed:
etc/neutron/plugins/linuxbridge/linuxbridge_conf.ini
etc/neutron/rootwrap.d/linuxbridge-plugin.filters
neutron/agent/linux/ip_lib.py
neutron/common/constants.py
neutron/common/exceptions.py
neutron/plugins/linuxbridge/agent/linuxbridge_neutron_agent.py
neutron/plugins/linuxbridge/common/config.py
neutron/plugins/linuxbridge/common/constants.py
neutron/plugins/ml2/drivers/l2pop/constants.py
neutron/plugins/ml2/drivers/mech_linuxbridge.py
neutron/tests/unit/linuxbridge/test_defaults.py
neutron/tests/unit/linuxbridge/test_lb_neutron_agent.py
neutron/tests/unit/ml2/test_mech_linuxbridge.py

index 8b459fcc3edddd8e7bcc447d77587e83fa785a35..b8cbd3c0c12ccdca2b9535a926b0deb47d16adc6 100644 (file)
 # 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
index 4f98ce94e66f75b63e54b03ee3c73652113d23f6..46224fff61b30d19a58ff455d55230b145fdf755 100644 (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
index 3eadbfbb9a78e564f5e7dd3a204f361b4497fe66..c8a0e672a436eb7a82394e41adda08380235c058 100644 (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'))
index 03af7373cf225c580d5bf4a5693532945ac1446e..016fa94c6f3ee3f5a7c2a60daae0af77d4094f01 100644 (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'
index cc64089be6a90f99a41fccbb71c1c0900748fad7..b51f91b77df99253273e4ac4f900f09c36d5331d 100644 (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'")
index f045f757dd859e80e67bb636b74e7a30087c81ab..549a08c9e64f2b427ea4511015030a3ff2c0f72d 100755 (executable)
@@ -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)
+
+    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
@@ -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'])
-
-                    # 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'])
@@ -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):
index 6a12f037e6cf15320236b118821de0edc0c3a7bd..5b90021098e5f6f910a18965aa428813fbf53674 100644 (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)
index 24614af8ccc7449f9895ded51fab323dd093f2b7..2b258f5906fff734ec1923b8b51042404a1f7afb 100644 (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
index 85ed7a5e4ecf005abfc898f9c9bba7edd250e770..2c9b7f96fe9468d847f33df2073eb68b48e72a2b 100644 (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]
index 15ca7d66c2edbbb3572253ea9a537ce537498aa0..233737f46a42c179d7d68199f1201a53ea245b94 100644 (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:
index ae41630845b8756a30520079167f8738c84d8fc9..b9ff6e0d9f2e050d1c6606ac5911f9d8980b8b7a 100644 (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)
index 9b60a9b9e60ebfa03ec4d5ae43d34fa18042ec93..fd40f74d2112e180be11d1d4f7e517704df43d93 100644 (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)
index 6ccc5b0b685315fbd56fd12f41522c33e7a52413..66903c02bf6421ce2ad7ddba2d5c86a267a945ed 100644 (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