]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
OVS agent implementation of l2-population
authorFrancois Eleouet <f.eleouet@gmail.com>
Thu, 22 Aug 2013 14:51:01 +0000 (16:51 +0200)
committerFrancois Eleouet <f.eleouet@gmail.com>
Wed, 11 Sep 2013 23:13:32 +0000 (01:13 +0200)
This patchset implements l2-population RPC callbacks in OVS agents,
it enables plugin to populate forwarding table following portbindings
events.

For now, it doesn't include ARP responder implementation which is
deferred to a future patchset (As this feature isn't yet supported by
OVS, it will require the use of an external responder such as ebtables)

It anyway brings some improvements in tunnelling management, as agent
will tear-down unecessary tunnels, and flood packets on a per-network
basis rather than to all other agents.

These changes should anyway have a limited risk, as tunnel management
won't be affected as long as l2_population option is not set. This
option must be used in conjonction with ml2 plugin using l2population
mechanism driver.

Implements: blueprint l2-population

Change-Id: I5185eefedb0ff392bc8b99d16f810813e26ff58d

etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini
neutron/agent/linux/ovs_lib.py
neutron/plugins/ml2/drivers/l2pop/constants.py
neutron/plugins/openvswitch/agent/ovs_neutron_agent.py
neutron/plugins/openvswitch/common/config.py
neutron/tests/unit/openvswitch/test_ovs_lib.py
neutron/tests/unit/openvswitch/test_ovs_neutron_agent.py
neutron/tests/unit/openvswitch/test_ovs_tunnel.py

index 48442db5213576cd3bec3b646cc640676a37a2df..9a1b948d2d61a22cc568b98a1d44230751144c54 100644 (file)
 # veth_mtu =
 # Example: veth_mtu = 1504
 
+# (BoolOpt) Flag to enable l2-population extension. This option should only be
+# used in conjunction with ml2 plugin and l2population mechanism driver. It'll
+# enable plugin to populate remote ports macs and IPs (using fdb_add/remove
+# RPC calbbacks instead of tunnel_sync/update) on OVS agents in order to
+# optimize tunnel management.
+#
+# l2_population = False
+
 [securitygroup]
 # Firewall driver for realizing neutron security group function.
 # firewall_driver = neutron.agent.firewall.NoopFirewallDriver
index 2c2bd9d8b507687c89fa5f8dce47e0ce03585b1f..faa335ea6a62f8f687dfff6f042be798117bb579 100644 (file)
@@ -49,6 +49,8 @@ class OVSBridge:
         self.br_name = br_name
         self.root_helper = root_helper
         self.re_id = self.re_compile_id()
+        self.defer_apply_flows = False
+        self.deferred_flows = {'add': '', 'mod': '', 'del': ''}
 
     def re_compile_id(self):
         external = 'external_ids\s*'
@@ -92,10 +94,11 @@ class OVSBridge:
         args = ["clear", table_name, record, column]
         self.run_vsctl(args)
 
-    def run_ofctl(self, cmd, args):
+    def run_ofctl(self, cmd, args, process_input=None):
         full_args = ["ovs-ofctl", cmd, self.br_name] + args
         try:
-            return utils.execute(full_args, root_helper=self.root_helper)
+            return utils.execute(full_args, root_helper=self.root_helper,
+                                 process_input=process_input)
         except Exception as e:
             LOG.error(_("Unable to execute %(cmd)s. Exception: %(exception)s"),
                       {'cmd': full_args, 'exception': e})
@@ -161,11 +164,17 @@ class OVSBridge:
 
     def add_flow(self, **kwargs):
         flow_str = self.add_or_mod_flow_str(**kwargs)
-        self.run_ofctl("add-flow", [flow_str])
+        if self.defer_apply_flows:
+            self.deferred_flows['add'] += flow_str + '\n'
+        else:
+            self.run_ofctl("add-flow", [flow_str])
 
     def mod_flow(self, **kwargs):
         flow_str = self.add_or_mod_flow_str(**kwargs)
-        self.run_ofctl("mod-flows", [flow_str])
+        if self.defer_apply_flows:
+            self.deferred_flows['mod'] += flow_str + '\n'
+        else:
+            self.run_ofctl("mod-flows", [flow_str])
 
     def delete_flows(self, **kwargs):
         kwargs['delete'] = True
@@ -173,12 +182,32 @@ class OVSBridge:
         if "actions" in kwargs:
             flow_expr_arr.append("actions=%s" % (kwargs["actions"]))
         flow_str = ",".join(flow_expr_arr)
-        self.run_ofctl("del-flows", [flow_str])
+        if self.defer_apply_flows:
+            self.deferred_flows['del'] += flow_str + '\n'
+        else:
+            self.run_ofctl("del-flows", [flow_str])
+
+    def defer_apply_on(self):
+        LOG.debug(_('defer_apply_on'))
+        self.defer_apply_flows = True
+
+    def defer_apply_off(self):
+        LOG.debug(_('defer_apply_off'))
+        for action, flows in self.deferred_flows.items():
+            if flows:
+                LOG.debug(_('Applying following deferred flows '
+                            'to bridge %s'), self.br_name)
+                for line in flows.splitlines():
+                    LOG.debug(_('%(action)s: %(flow)s'),
+                              {'action': action, 'flow': line})
+                self.run_ofctl('%s-flows' % action, ['-'], flows)
+        self.defer_apply_flows = False
+        self.deferred_flows = {'add': '', 'mod': '', 'del': ''}
 
     def add_tunnel_port(self, port_name, remote_ip, local_ip,
                         tunnel_type=constants.TYPE_GRE,
                         vxlan_udp_port=constants.VXLAN_UDP_PORT):
-        self.run_vsctl(["add-port", self.br_name, port_name])
+        self.run_vsctl(["--may-exist", "add-port", self.br_name, port_name])
         self.set_db_attribute("Interface", port_name, "type", tunnel_type)
         if tunnel_type == constants.TYPE_VXLAN:
             # Only set the VXLAN UDP port if it's not the default
index 74ca3a1ab9dc705d3e4940e73209df0c377dcb56..85ed7a5e4ecf005abfc898f9c9bba7edd250e770 100644 (file)
@@ -17,4 +17,6 @@
 # @author: Francois Eleouet, Orange
 # @author: Mathieu Rohon, Orange
 
-SUPPORTED_AGENT_TYPES = []
+from neutron.common import constants
+
+SUPPORTED_AGENT_TYPES = [constants.AGENT_TYPE_OVS]
index 5a5182dc7f70d20ff55c6d6af0a63ea533a29cbe..eefe384367fda0000ddc115b872292b30b452a64 100644 (file)
@@ -29,6 +29,7 @@ import time
 import eventlet
 from oslo.config import cfg
 
+from neutron.agent import l2population_rpc
 from neutron.agent.linux import ip_lib
 from neutron.agent.linux import ovs_lib
 from neutron.agent import rpc as agent_rpc
@@ -66,6 +67,8 @@ class LocalVLANMapping:
         self.physical_network = physical_network
         self.segmentation_id = segmentation_id
         self.vif_ports = vif_ports
+        # set of tunnel ports on which packets should be flooded
+        self.tun_ofports = set()
 
     def __str__(self):
         return ("lv-id = %s type = %s phys-net = %s phys-id = %s" %
@@ -116,7 +119,8 @@ class OVSSecurityGroupAgent(sg_rpc.SecurityGroupAgentRpcMixin):
         self.init_firewall()
 
 
-class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
+class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
+                      l2population_rpc.L2populationRpcCallBackMixin):
     '''Implements OVS-based tunneling, VLANs and flat networks.
 
     Two local bridges are created: an integration bridge (defaults to
@@ -151,7 +155,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
     def __init__(self, integ_br, tun_br, local_ip,
                  bridge_mappings, root_helper,
                  polling_interval, tunnel_types=None,
-                 veth_mtu=None):
+                 veth_mtu=None, l2_population=False):
         '''Constructor.
 
         :param integ_br: name of the integration bridge.
@@ -170,12 +174,15 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
         self.available_local_vlans = set(xrange(q_const.MIN_VLAN_TAG,
                                                 q_const.MAX_VLAN_TAG))
         self.tunnel_types = tunnel_types or []
+        self.l2_pop = l2_population
         self.agent_state = {
             'binary': 'neutron-openvswitch-agent',
             'host': cfg.CONF.host,
             'topic': q_const.L2_AGENT_TOPIC,
             'configurations': {'bridge_mappings': bridge_mappings,
-                               'tunnel_types': self.tunnel_types},
+                               'tunnel_types': self.tunnel_types,
+                               'tunneling_ip': local_ip,
+                               'l2_population': self.l2_pop},
             'agent_type': q_const.AGENT_TYPE_OVS,
             'start_flag': True}
 
@@ -187,8 +194,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
         self.setup_integration_br()
         self.setup_physical_bridges(bridge_mappings)
         self.local_vlan_map = {}
-        self.tun_br_ofports = {constants.TYPE_GRE: set(),
-                               constants.TYPE_VXLAN: set()}
+        self.tun_br_ofports = {constants.TYPE_GRE: {},
+                               constants.TYPE_VXLAN: {}}
 
         self.polling_interval = polling_interval
 
@@ -242,6 +249,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
                      [topics.NETWORK, topics.DELETE],
                      [constants.TUNNEL, topics.UPDATE],
                      [topics.SECURITY_GROUP, topics.UPDATE]]
+        if self.l2_pop:
+            consumers.append([topics.L2POPULATION,
+                              topics.UPDATE, cfg.CONF.host])
         self.connection = agent_rpc.create_consumers(self.dispatcher,
                                                      self.topic,
                                                      consumers)
@@ -263,7 +273,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
         # The network may not be defined on this agent
         lvm = self.local_vlan_map.get(network_id)
         if lvm:
-            self.reclaim_local_vlan(network_id, lvm)
+            self.reclaim_local_vlan(network_id)
         else:
             LOG.debug(_("Network %s not used on agent."), network_id)
 
@@ -313,7 +323,94 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
         if tunnel_ip == self.local_ip:
             return
         tun_name = '%s-%s' % (tunnel_type, tunnel_id)
-        self.setup_tunnel_port(tun_name, tunnel_ip, tunnel_type)
+        if not self.l2_pop:
+            self.setup_tunnel_port(tun_name, tunnel_ip, tunnel_type)
+
+    def fdb_add(self, context, fdb_entries):
+        LOG.debug(_("fdb_add received"))
+        for network_id, values in fdb_entries.items():
+            lvm = self.local_vlan_map.get(network_id)
+            if not lvm:
+                # Agent doesn't manage any port in this network
+                continue
+            agent_ports = values.get('ports')
+            agent_ports.pop(self.local_ip, None)
+            if len(agent_ports):
+                self.tun_br.defer_apply_on()
+                for agent_ip, ports in agent_ports.items():
+                    # Ensure we have a tunnel port with this remote agent
+                    ofport = self.tun_br_ofports[
+                        lvm.network_type].get(agent_ip)
+                    if not ofport:
+                        port_name = '%s-%s' % (lvm.network_type, agent_ip)
+                        ofport = self.setup_tunnel_port(port_name, agent_ip,
+                                                        lvm.network_type)
+                        if ofport == 0:
+                            continue
+                    for port in ports:
+                        self._add_fdb_flow(port, agent_ip, lvm, ofport)
+                self.tun_br.defer_apply_off()
+
+    def fdb_remove(self, context, fdb_entries):
+        LOG.debug(_("fdb_remove received"))
+        for network_id, values in fdb_entries.items():
+            lvm = self.local_vlan_map.get(network_id)
+            if not lvm:
+                # Agent doesn't manage any more ports in this network
+                continue
+            agent_ports = values.get('ports')
+            agent_ports.pop(self.local_ip, None)
+            if len(agent_ports):
+                self.tun_br.defer_apply_on()
+                for agent_ip, ports in agent_ports.items():
+                    ofport = self.tun_br_ofports[
+                        lvm.network_type].get(agent_ip)
+                    if not ofport:
+                        continue
+                    for port in ports:
+                        self._del_fdb_flow(port, agent_ip, lvm, ofport)
+                self.tun_br.defer_apply_off()
+
+    def _add_fdb_flow(self, port_info, agent_ip, lvm, ofport):
+        if port_info == q_const.FLOODING_ENTRY:
+            lvm.tun_ofports.add(ofport)
+            ofports = ','.join(lvm.tun_ofports)
+            self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
+                                 priority=1,
+                                 dl_vlan=lvm.vlan,
+                                 actions="strip_vlan,set_tunnel:%s,"
+                                 "output:%s" % (lvm.segmentation_id, ofports))
+        else:
+            # TODO(feleouet): add ARP responder entry
+            self.tun_br.add_flow(table=constants.UCAST_TO_TUN,
+                                 priority=2,
+                                 dl_vlan=lvm.vlan,
+                                 dl_dst=port_info[0],
+                                 actions="strip_vlan,set_tunnel:%s,output:%s" %
+                                 (lvm.segmentation_id, ofport))
+
+    def _del_fdb_flow(self, port_info, agent_ip, lvm, ofport):
+        if port_info == q_const.FLOODING_ENTRY:
+            lvm.tun_ofports.remove(ofport)
+            if len(lvm.tun_ofports) > 0:
+                ofports = ','.join(lvm.tun_ofports)
+                self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
+                                     priority=1,
+                                     dl_vlan=lvm.vlan,
+                                     actions="strip_vlan,"
+                                     "set_tunnel:%s,output:%s" %
+                                     (lvm.segmentation_id, ofports))
+            else:
+                # This local vlan doesn't require any more tunelling
+                self.tun_br.delete_flows(table=constants.FLOOD_TO_TUN,
+                                         dl_vlan=lvm.vlan)
+            # Check if this tunnel port is still used
+            self.cleanup_tunnel_port(ofport, lvm.network_type)
+        else:
+            #TODO(feleouet): remove ARP responder entry
+            self.tun_br.delete_flows(table=constants.UCAST_TO_TUN,
+                                     dl_vlan=lvm.vlan,
+                                     dl_dst=port_info[0])
 
     def create_rpc_dispatcher(self):
         '''Get the rpc dispatcher for this manager.
@@ -348,12 +445,14 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
         if network_type in constants.TUNNEL_NETWORK_TYPES:
             if self.enable_tunneling:
                 # outbound broadcast/multicast
-                ofports = ','.join(self.tun_br_ofports[network_type])
-                self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
-                                     priority=1,
-                                     dl_vlan=lvid,
-                                     actions="strip_vlan,set_tunnel:%s,"
-                                     "output:%s" % (segmentation_id, ofports))
+                ofports = ','.join(self.tun_br_ofports[network_type].values())
+                if ofports:
+                    self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
+                                         priority=1,
+                                         dl_vlan=lvid,
+                                         actions="strip_vlan,"
+                                         "set_tunnel:%s,output:%s" %
+                                         (segmentation_id, ofports))
                 # inbound from tunnels: set lvid in the right table
                 # and resubmit to Table LEARN_FROM_TUN for mac learning
                 self.tun_br.add_flow(table=constants.TUN_TABLE[network_type],
@@ -415,13 +514,18 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
                       {'network_type': network_type,
                        'net_uuid': net_uuid})
 
-    def reclaim_local_vlan(self, net_uuid, lvm):
+    def reclaim_local_vlan(self, net_uuid):
         '''Reclaim a local VLAN.
 
         :param net_uuid: the network uuid associated with this vlan.
         :param lvm: a LocalVLANMapping object that tracks (vlan, lsw_id,
             vif_ids) mapping.
         '''
+        lvm = self.local_vlan_map.pop(net_uuid, None)
+        if lvm is None:
+            LOG.debug(_("Network %s not used on agent."), net_uuid)
+            return
+
         LOG.info(_("Reclaiming vlan = %(vlan_id)s from net-id = %(net_uuid)s"),
                  {'vlan_id': lvm.vlan,
                   'net_uuid': net_uuid})
@@ -432,6 +536,10 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
                     table=constants.TUN_TABLE[lvm.network_type],
                     tun_id=lvm.segmentation_id)
                 self.tun_br.delete_flows(dl_vlan=lvm.vlan)
+                if self.l2_pop:
+                    # Try to remove tunnel ports if not used by other networks
+                    for ofport in lvm.tun_ofports:
+                        self.cleanup_tunnel_port(ofport, lvm.network_type)
         elif lvm.network_type == constants.TYPE_FLAT:
             if lvm.physical_network in self.phys_brs:
                 # outbound
@@ -463,7 +571,6 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
                       {'network_type': lvm.network_type,
                        'net_uuid': net_uuid})
 
-        del self.local_vlan_map[net_uuid]
         self.available_local_vlans.add(lvm.vlan)
 
     def port_bound(self, port, net_uuid,
@@ -509,7 +616,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
         lvm.vif_ports.pop(vif_id, None)
 
         if not lvm.vif_ports:
-            self.reclaim_local_vlan(net_uuid, lvm)
+            self.reclaim_local_vlan(net_uuid)
 
     def port_dead(self, port):
         '''Once a port has no binding, put it on the "dead vlan".
@@ -737,16 +844,19 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
         if ofport < 0:
             LOG.error(_("Failed to set-up %(type)s tunnel port to %(ip)s"),
                       {'type': tunnel_type, 'ip': remote_ip})
-        else:
-            self.tun_br_ofports[tunnel_type].add(ofport)
-            # Add flow in default table to resubmit to the right
-            # tunelling table (lvid will be set in the latter)
-            self.tun_br.add_flow(priority=1,
-                                 in_port=ofport,
-                                 actions="resubmit(,%s)" %
-                                 constants.TUN_TABLE[tunnel_type])
+            return 0
+
+        self.tun_br_ofports[tunnel_type][remote_ip] = ofport
+        # Add flow in default table to resubmit to the right
+        # tunelling table (lvid will be set in the latter)
+        self.tun_br.add_flow(priority=1,
+                             in_port=ofport,
+                             actions="resubmit(,%s)" %
+                             constants.TUN_TABLE[tunnel_type])
+
+        ofports = ','.join(self.tun_br_ofports[tunnel_type].values())
+        if ofports and not self.l2_pop:
             # Update flooding flows to include the new tunnel
-            ofports = ','.join(self.tun_br_ofports[tunnel_type])
             for network_id, vlan_mapping in self.local_vlan_map.iteritems():
                 if vlan_mapping.network_type == tunnel_type:
                     self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
@@ -756,6 +866,20 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
                                          "set_tunnel:%s,output:%s" %
                                          (vlan_mapping.segmentation_id,
                                           ofports))
+        return ofport
+
+    def cleanup_tunnel_port(self, tun_ofport, tunnel_type):
+        # Check if this tunnel port is still used
+        for lvm in self.local_vlan_map.values():
+            if tun_ofport in lvm.tun_ofports:
+                break
+        # If not, remove it
+        else:
+            for remote_ip, ofport in self.tun_br_ofports[tunnel_type].items():
+                if ofport == tun_ofport:
+                    port_name = '%s-%s' % (tunnel_type, remote_ip)
+                    self.tun_br.delete_port(port_name)
+                    self.tun_br_ofports[tunnel_type].pop(remote_ip, None)
 
     def treat_devices_added(self, devices):
         resync = False
@@ -883,14 +1007,15 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin):
                 details = self.plugin_rpc.tunnel_sync(self.context,
                                                       self.local_ip,
                                                       tunnel_type)
-                tunnels = details['tunnels']
-                for tunnel in tunnels:
-                    if self.local_ip != tunnel['ip_address']:
-                        tunnel_id = tunnel.get('id', tunnel['ip_address'])
-                        tun_name = '%s-%s' % (tunnel_type, tunnel_id)
-                        self.setup_tunnel_port(tun_name,
-                                               tunnel['ip_address'],
-                                               tunnel_type)
+                if not self.l2_pop:
+                    tunnels = details['tunnels']
+                    for tunnel in tunnels:
+                        if self.local_ip != tunnel['ip_address']:
+                            tunnel_id = tunnel.get('id', tunnel['ip_address'])
+                            tun_name = '%s-%s' % (tunnel_type, tunnel_id)
+                            self.setup_tunnel_port(tun_name,
+                                                   tunnel['ip_address'],
+                                                   tunnel_type)
         except Exception as e:
             LOG.debug(_("Unable to sync tunnel IP %(local_ip)s: %(e)s"),
                       {'local_ip': self.local_ip, 'e': e})
@@ -1011,6 +1136,7 @@ def create_agent_config_map(config):
         polling_interval=config.AGENT.polling_interval,
         tunnel_types=config.AGENT.tunnel_types,
         veth_mtu=config.AGENT.veth_mtu,
+        l2_population=config.AGENT.l2_population,
     )
 
     # If enable_tunneling is TRUE, set tunnel_type to default to GRE
index 76e522f4fba5b1ed7baf2d9e613765706ec32bba..1aab164d8e7ffa0e5b5ff9fa811e62d048ca4b70 100644 (file)
@@ -69,6 +69,9 @@ agent_opts = [
                help=_("The UDP port to use for VXLAN tunnels.")),
     cfg.IntOpt('veth_mtu', default=None,
                help=_("MTU size of veth interfaces")),
+    cfg.BoolOpt('l2_population', default=False,
+                help=_("Use ml2 l2population mechanism driver to learn "
+                       "remote mac and IPs and improve tunnel scalability")),
 ]
 
 
index 1b9486affe4a5a5bfdc279b3aa4d91d59dae4675..ab9ff8fe4f83af7e9912b442bb029f23210e60ab 100644 (file)
@@ -158,8 +158,8 @@ class OVS_Lib_Test(base.BaseTestCase):
 
     def test_count_flows(self):
         utils.execute(["ovs-ofctl", "dump-flows", self.BR_NAME],
-                      root_helper=self.root_helper).AndReturn('ignore'
-                                                              '\nflow-1\n')
+                      root_helper=self.root_helper,
+                      process_input=None).AndReturn('ignore\nflow-1\n')
         self.mox.ReplayAll()
 
         # counts the number of flows as total lines of output - 2
@@ -183,13 +183,37 @@ class OVS_Lib_Test(base.BaseTestCase):
         self.br.delete_flows(dl_vlan=vid)
         self.mox.VerifyAll()
 
+    def test_defer_apply_flows(self):
+        self.mox.StubOutWithMock(self.br, 'add_or_mod_flow_str')
+        self.br.add_or_mod_flow_str(
+            flow='added_flow_1').AndReturn('added_flow_1')
+        self.br.add_or_mod_flow_str(
+            flow='added_flow_2').AndReturn('added_flow_2')
+
+        self.mox.StubOutWithMock(self.br, '_build_flow_expr_arr')
+        self.br._build_flow_expr_arr(delete=True,
+                                     flow='deleted_flow_1'
+                                     ).AndReturn(['deleted_flow_1'])
+        self.mox.StubOutWithMock(self.br, 'run_ofctl')
+        self.br.run_ofctl('add-flows', ['-'], 'added_flow_1\nadded_flow_2\n')
+        self.br.run_ofctl('del-flows', ['-'], 'deleted_flow_1\n')
+        self.mox.ReplayAll()
+
+        self.br.defer_apply_on()
+        self.br.add_flow(flow='added_flow_1')
+        self.br.defer_apply_on()
+        self.br.add_flow(flow='added_flow_2')
+        self.br.delete_flows(flow='deleted_flow_1')
+        self.br.defer_apply_off()
+        self.mox.VerifyAll()
+
     def test_add_tunnel_port(self):
         pname = "tap99"
         local_ip = "1.1.1.1"
         remote_ip = "9.9.9.9"
         ofport = "6"
 
-        utils.execute(["ovs-vsctl", self.TO, "add-port",
+        utils.execute(["ovs-vsctl", self.TO, "--may-exist", "add-port",
                        self.BR_NAME, pname], root_helper=self.root_helper)
         utils.execute(["ovs-vsctl", self.TO, "set", "Interface",
                        pname, "type=gre"], root_helper=self.root_helper)
index 3cdd9d9eefdb0bae748fe05a941b5288c1b53867..e74fc3149589ee86f34d3ed4d865640aa7e448e3 100644 (file)
@@ -23,6 +23,7 @@ import testtools
 
 from neutron.agent.linux import ip_lib
 from neutron.agent.linux import ovs_lib
+from neutron.common import constants as n_const
 from neutron.openstack.common.rpc import common as rpc_common
 from neutron.plugins.openvswitch.agent import ovs_neutron_agent
 from neutron.plugins.openvswitch.common import constants
@@ -254,15 +255,18 @@ class TestOvsNeutronAgent(base.BaseTestCase):
             )
 
     def test_network_delete(self):
-        with mock.patch.object(self.agent, "reclaim_local_vlan") as recl_fn:
+        with contextlib.nested(
+            mock.patch.object(self.agent, "reclaim_local_vlan"),
+            mock.patch.object(self.agent.tun_br, "cleanup_tunnel_port")
+        ) as (recl_fn, clean_tun_fn):
             self.agent.network_delete("unused_context",
                                       network_id="123")
             self.assertFalse(recl_fn.called)
-
             self.agent.local_vlan_map["123"] = "LVM object"
             self.agent.network_delete("unused_context",
                                       network_id="123")
-            recl_fn.assert_called_with("123", self.agent.local_vlan_map["123"])
+            self.assertFalse(clean_tun_fn.called)
+            recl_fn.assert_called_with("123")
 
     def test_port_update(self):
         with contextlib.nested(
@@ -412,6 +416,158 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                                       constants.MINIMUM_OVS_VXLAN_VERSION,
                                       expecting_ok=False)
 
+    def _prepare_l2_pop_ofports(self):
+        lvm1 = mock.Mock()
+        lvm1.network_type = 'gre'
+        lvm1.vlan = 'vlan1'
+        lvm1.segmentation_id = 'seg1'
+        lvm1.tun_ofports = set(['1'])
+        lvm2 = mock.Mock()
+        lvm2.network_type = 'gre'
+        lvm2.vlan = 'vlan2'
+        lvm2.segmentation_id = 'seg2'
+        lvm2.tun_ofports = set(['1', '2'])
+        self.agent.local_vlan_map = {'net1': lvm1, 'net2': lvm2}
+        self.agent.tun_br_ofports = {'gre':
+                                     {'ip_agent_1': '1', 'ip_agent_2': '2'}}
+
+    def test_fdb_ignore_network(self):
+        self._prepare_l2_pop_ofports()
+        fdb_entry = {'net3': {}}
+        with contextlib.nested(
+            mock.patch.object(self.agent.tun_br, 'add_flow'),
+            mock.patch.object(self.agent.tun_br, 'delete_flows'),
+            mock.patch.object(self.agent, 'setup_tunnel_port'),
+            mock.patch.object(self.agent, 'cleanup_tunnel_port')
+        ) as (add_flow_fn, del_flow_fn, add_tun_fn, clean_tun_fn):
+            self.agent.fdb_add(None, fdb_entry)
+            self.assertFalse(add_flow_fn.called)
+            self.assertFalse(add_tun_fn.called)
+            self.agent.fdb_remove(None, fdb_entry)
+            self.assertFalse(del_flow_fn.called)
+            self.assertFalse(clean_tun_fn.called)
+
+    def test_fdb_ignore_self(self):
+        self._prepare_l2_pop_ofports()
+        self.agent.local_ip = 'agent_ip'
+        fdb_entry = {'net2':
+                     {'network_type': 'gre',
+                      'segment_id': 'tun2',
+                      'ports':
+                      {'agent_ip':
+                       [['mac', 'ip'],
+                        n_const.FLOODING_ENTRY]}}}
+        with mock.patch.object(self.agent.tun_br,
+                               "defer_apply_on") as defer_fn:
+            self.agent.fdb_add(None, fdb_entry)
+            self.assertFalse(defer_fn.called)
+
+            self.agent.fdb_remove(None, fdb_entry)
+            self.assertFalse(defer_fn.called)
+
+    def test_fdb_add_flows(self):
+        self._prepare_l2_pop_ofports()
+        fdb_entry = {'net1':
+                     {'network_type': 'gre',
+                      'segment_id': 'tun1',
+                      'ports':
+                      {'ip_agent_2':
+                       [['mac', 'ip'],
+                        n_const.FLOODING_ENTRY]}}}
+        with contextlib.nested(
+            mock.patch.object(self.agent.tun_br, 'add_flow'),
+            mock.patch.object(self.agent.tun_br, 'mod_flow'),
+            mock.patch.object(self.agent.tun_br, 'setup_tunnel_port'),
+        ) as (add_flow_fn, mod_flow_fn, add_tun_fn):
+            add_tun_fn.return_value = '2'
+            self.agent.fdb_add(None, fdb_entry)
+            add_flow_fn.assert_called_with(table=constants.UCAST_TO_TUN,
+                                           priority=2,
+                                           dl_vlan='vlan1',
+                                           dl_dst='mac',
+                                           actions='strip_vlan,'
+                                           'set_tunnel:seg1,output:2')
+            mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN,
+                                           priority=1,
+                                           dl_vlan='vlan1',
+                                           actions='strip_vlan,'
+                                           'set_tunnel:seg1,output:1,2')
+
+    def test_fdb_del_flows(self):
+        self._prepare_l2_pop_ofports()
+        fdb_entry = {'net2':
+                     {'network_type': 'gre',
+                      'segment_id': 'tun2',
+                      'ports':
+                      {'ip_agent_2':
+                       [['mac', 'ip'],
+                        n_const.FLOODING_ENTRY]}}}
+        with contextlib.nested(
+            mock.patch.object(self.agent.tun_br, 'mod_flow'),
+            mock.patch.object(self.agent.tun_br, 'delete_flows'),
+        ) as (mod_flow_fn, del_flow_fn):
+            self.agent.fdb_remove(None, fdb_entry)
+            del_flow_fn.assert_called_with(table=constants.UCAST_TO_TUN,
+                                           dl_vlan='vlan2',
+                                           dl_dst='mac')
+            mod_flow_fn.assert_called_with(table=constants.FLOOD_TO_TUN,
+                                           priority=1,
+                                           dl_vlan='vlan2',
+                                           actions='strip_vlan,'
+                                           'set_tunnel:seg2,output:1')
+
+    def test_fdb_add_port(self):
+        self._prepare_l2_pop_ofports()
+        fdb_entry = {'net1':
+                     {'network_type': 'gre',
+                      'segment_id': 'tun1',
+                      'ports': {'ip_agent_1': [['mac', 'ip']]}}}
+        with contextlib.nested(
+            mock.patch.object(self.agent.tun_br, 'add_flow'),
+            mock.patch.object(self.agent.tun_br, 'mod_flow'),
+            mock.patch.object(self.agent, 'setup_tunnel_port')
+        ) as (add_flow_fn, mod_flow_fn, add_tun_fn):
+            self.agent.fdb_add(None, fdb_entry)
+            self.assertFalse(add_tun_fn.called)
+            fdb_entry['net1']['ports']['ip_agent_3'] = [['mac', 'ip']]
+            self.agent.fdb_add(None, fdb_entry)
+            add_tun_fn.assert_called_with('gre-ip_agent_3', 'ip_agent_3',
+                                          'gre')
+
+    def test_fdb_del_port(self):
+        self._prepare_l2_pop_ofports()
+        fdb_entry = {'net2':
+                     {'network_type': 'gre',
+                      'segment_id': 'tun2',
+                      'ports': {'ip_agent_2': [n_const.FLOODING_ENTRY]}}}
+        with contextlib.nested(
+            mock.patch.object(self.agent.tun_br, 'delete_flows'),
+            mock.patch.object(self.agent.tun_br, 'delete_port')
+        ) as (del_flow_fn, del_port_fn):
+            self.agent.fdb_remove(None, fdb_entry)
+            del_port_fn.assert_called_once_with('gre-ip_agent_2')
+
+    def test_recl_lv_port_to_preserve(self):
+        self._prepare_l2_pop_ofports()
+        self.agent.l2_pop = True
+        self.agent.enable_tunneling = True
+        with mock.patch.object(
+            self.agent.tun_br, 'cleanup_tunnel_port'
+        ) as clean_tun_fn:
+            self.agent.reclaim_local_vlan('net1')
+            self.assertFalse(clean_tun_fn.called)
+
+    def test_recl_lv_port_to_remove(self):
+        self._prepare_l2_pop_ofports()
+        self.agent.l2_pop = True
+        self.agent.enable_tunneling = True
+        with contextlib.nested(
+            mock.patch.object(self.agent.tun_br, 'delete_port'),
+            mock.patch.object(self.agent.tun_br, 'delete_flows')
+        ) as (del_port_fn, del_flow_fn):
+            self.agent.reclaim_local_vlan('net2')
+            del_port_fn.assert_called_once_with('gre-ip_agent_2')
+
 
 class AncillaryBridgesTest(base.BaseTestCase):
 
index e7196b358cf7c7744e932a4d669b74c145bbcf26..d5bd080929e900c5054166d4d681f91e89cfa557 100644 (file)
@@ -44,10 +44,7 @@ LVM_FLAT = ovs_neutron_agent.LocalVLANMapping(
 LVM_VLAN = ovs_neutron_agent.LocalVLANMapping(
     LV_ID, 'vlan', 'net1', LS_ID, VIF_PORTS)
 
-GRE_OFPORTS = set(['11', '12'])
-VXLAN_OFPORTS = set(['13', '14'])
-TUN_OFPORTS = {constants.TYPE_GRE: GRE_OFPORTS,
-               constants.TYPE_VXLAN: VXLAN_OFPORTS}
+TUN_OFPORTS = {constants.TYPE_GRE: {'ip1': '11', 'ip2': '12'}}
 
 BCAST_MAC = "01:00:00:00:00:00/01:00:00:00:00:00"
 UCAST_MAC = "00:00:00:00:00:00/01:00:00:00:00:00"
@@ -198,12 +195,13 @@ class TunnelTest(base.BaseTestCase):
         self.mox.VerifyAll()
 
     def testProvisionLocalVlan(self):
+        ofports = ','.join(TUN_OFPORTS[constants.TYPE_GRE].values())
         self.mock_tun_bridge.mod_flow(table=constants.FLOOD_TO_TUN,
                                       priority=1,
                                       dl_vlan=LV_ID,
                                       actions="strip_vlan,"
                                       "set_tunnel:%s,output:%s" %
-                                      (LS_ID, ','.join(GRE_OFPORTS)))
+                                      (LS_ID, ofports))
 
         self.mock_tun_bridge.add_flow(table=constants.TUN_TABLE['gre'],
                                       priority=1,
@@ -302,7 +300,7 @@ class TunnelTest(base.BaseTestCase):
                                               self.VETH_MTU)
         a.available_local_vlans = set()
         a.local_vlan_map[NET_UUID] = LVM
-        a.reclaim_local_vlan(NET_UUID, LVM)
+        a.reclaim_local_vlan(NET_UUID)
         self.assertTrue(LVM.vlan in a.available_local_vlans)
         self.mox.VerifyAll()
 
@@ -325,7 +323,7 @@ class TunnelTest(base.BaseTestCase):
 
         a.available_local_vlans = set()
         a.local_vlan_map[NET_UUID] = LVM_FLAT
-        a.reclaim_local_vlan(NET_UUID, LVM_FLAT)
+        a.reclaim_local_vlan(NET_UUID)
         self.assertTrue(LVM_FLAT.vlan in a.available_local_vlans)
         self.mox.VerifyAll()
 
@@ -348,7 +346,7 @@ class TunnelTest(base.BaseTestCase):
 
         a.available_local_vlans = set()
         a.local_vlan_map[NET_UUID] = LVM_VLAN
-        a.reclaim_local_vlan(NET_UUID, LVM_VLAN)
+        a.reclaim_local_vlan(NET_UUID)
         self.assertTrue(LVM_VLAN.vlan in a.available_local_vlans)
         self.mox.VerifyAll()
 
@@ -370,7 +368,7 @@ class TunnelTest(base.BaseTestCase):
     def testPortUnbound(self):
         self.mox.StubOutWithMock(
             ovs_neutron_agent.OVSNeutronAgent, 'reclaim_local_vlan')
-        ovs_neutron_agent.OVSNeutronAgent.reclaim_local_vlan(NET_UUID, LVM)
+        ovs_neutron_agent.OVSNeutronAgent.reclaim_local_vlan(NET_UUID)
 
         self.mox.ReplayAll()