]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
OVS-agent: Separate ovs-ofctl using code as a driver
authorYAMAMOTO Takashi <yamamoto@valinux.co.jp>
Mon, 2 Mar 2015 04:14:48 +0000 (13:14 +0900)
committerYAMAMOTO Takashi <yamamoto@valinux.co.jp>
Mon, 25 May 2015 09:48:02 +0000 (18:48 +0900)
This is a preparation to introduce another Ryu-based implementation.
The aim is to replace this with the new Ryu-based implementation
eventually.

Add a config option for OVS-agent which selects the implementation.
Currently, the only available choice is 'ovs-ofctl'.

Also, this commit simplifies DVR logics by reducing duplications
and makes some of DVR UTs actually check the flows rather than just
"add_flow is called".

Partially-Implements: blueprint ovs-ofctl-to-python
Change-Id: Ie1224f8a1c17268cd7d1c474ed82fdfb8852eaa8

25 files changed:
etc/neutron/plugins/openvswitch/ovs_neutron_plugin.ini
neutron/cmd/eventlet/plugins/ovs_neutron_agent.py
neutron/plugins/openvswitch/agent/main.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/openflow/__init__.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_dvr_process.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_int.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_phys.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_tun.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/main.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ofswitch.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge.py [new file with mode: 0644]
neutron/plugins/openvswitch/agent/ovs_dvr_neutron_agent.py
neutron/plugins/openvswitch/agent/ovs_neutron_agent.py
neutron/plugins/openvswitch/common/config.py
neutron/tests/functional/agent/test_ovs_flows.py
neutron/tests/unit/plugins/openvswitch/agent/openflow/__init__.py [new file with mode: 0644]
neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py [new file with mode: 0644]
neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge_test_base.py [new file with mode: 0644]
neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_int.py [new file with mode: 0644]
neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_phys.py [new file with mode: 0644]
neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py [new file with mode: 0644]
neutron/tests/unit/plugins/openvswitch/agent/ovs_test_base.py [new file with mode: 0644]
neutron/tests/unit/plugins/openvswitch/agent/test_ovs_neutron_agent.py
neutron/tests/unit/plugins/openvswitch/test_ovs_tunnel.py

index 42673cf1b8bf0f929c5512e8f224f528c85b5407..85586c5969b7c7794a739eaebd35eb2801311b3d 100644 (file)
 #   ovs-vsctl set-manager ptcp:6640:127.0.0.1
 # ovsdb_connection = tcp:127.0.0.1:6640
 
+# (StrOpt) OpenFlow interface to use.
+# 'ovs-ofctl' is currently the only available choice.
+# of_interface = ovs-ofctl
+
 [agent]
 # Agent's polling interval in seconds
 # polling_interval = 2
index ef686f86c6c02a14fbabf8119157f492c906d32a..2d545bc5316d22fa35bb3d296e301eddb2645d33 100644 (file)
@@ -13,8 +13,8 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-from neutron.plugins.openvswitch.agent import ovs_neutron_agent
+import neutron.plugins.openvswitch.agent.main as agent_main
 
 
 def main():
-    ovs_neutron_agent.main()
+    agent_main.main()
diff --git a/neutron/plugins/openvswitch/agent/main.py b/neutron/plugins/openvswitch/agent/main.py
new file mode 100644 (file)
index 0000000..7dbac9f
--- /dev/null
@@ -0,0 +1,45 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014 Fumihiko Kakuma <kakuma at valinux co jp>
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import sys
+
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_utils import importutils
+
+from neutron.common import config as common_config
+from neutron.common import utils as n_utils
+
+
+LOG = logging.getLogger(__name__)
+cfg.CONF.import_group('OVS', 'neutron.plugins.openvswitch.common.config')
+
+
+_main_modules = {
+    'ovs-ofctl': 'neutron.plugins.openvswitch.agent.openflow.ovs_ofctl.main',
+}
+
+
+def main():
+    common_config.init(sys.argv[1:])
+    driver_name = cfg.CONF.OVS.of_interface
+    mod_name = _main_modules[driver_name]
+    mod = importutils.import_module(mod_name)
+    mod.init_config()
+    common_config.setup_logging()
+    n_utils.log_opt_values(LOG)
+    mod.main()
diff --git a/neutron/plugins/openvswitch/agent/openflow/__init__.py b/neutron/plugins/openvswitch/agent/openflow/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_dvr_process.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_dvr_process.py
new file mode 100644 (file)
index 0000000..46db4ec
--- /dev/null
@@ -0,0 +1,89 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+# Copyright 2011 VMware, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+
+class OVSDVRProcessMixin(object):
+    """Common logic for br-tun and br-phys' DVR_PROCESS tables.
+
+    Inheriters should provide self.dvr_process_table_id and
+    self.dvr_process_next_table_id.
+    """
+
+    def install_dvr_process_ipv4(self, vlan_tag, gateway_ip):
+        # block ARP
+        self.add_flow(table=self.dvr_process_table_id,
+                      priority=3,
+                      dl_vlan=vlan_tag,
+                      proto='arp',
+                      nw_dst=gateway_ip,
+                      actions='drop')
+
+    def delete_dvr_process_ipv4(self, vlan_tag, gateway_ip):
+        self.delete_flows(table=self.dvr_process_table_id,
+                          dl_vlan=vlan_tag,
+                          proto='arp',
+                          nw_dst=gateway_ip)
+
+    def install_dvr_process_ipv6(self, vlan_tag, gateway_mac):
+        # block RA
+        self.add_flow(table=self.dvr_process_table_id,
+                      priority=3,
+                      dl_vlan=vlan_tag,
+                      proto='icmp6',
+                      dl_src=gateway_mac,
+                      actions='drop')
+
+    def delete_dvr_process_ipv6(self, vlan_tag, gateway_mac):
+        self.delete_flows(table=self.dvr_process_table_id,
+                          dl_vlan=vlan_tag,
+                          proto='icmp6',
+                          dl_src=gateway_mac)
+
+    def install_dvr_process(self, vlan_tag, vif_mac, dvr_mac_address):
+        self.add_flow(table=self.dvr_process_table_id,
+                      priority=2,
+                      dl_vlan=vlan_tag,
+                      dl_dst=vif_mac,
+                      actions="drop")
+        self.add_flow(table=self.dvr_process_table_id,
+                      priority=1,
+                      dl_vlan=vlan_tag,
+                      dl_src=vif_mac,
+                      actions="mod_dl_src:%s,resubmit(,%s)" %
+                      (dvr_mac_address, self.dvr_process_next_table_id))
+
+    def delete_dvr_process(self, vlan_tag, vif_mac):
+        self.delete_flows(table=self.dvr_process_table_id,
+                          dl_vlan=vlan_tag,
+                          dl_dst=vif_mac)
+        self.delete_flows(table=self.dvr_process_table_id,
+                          dl_vlan=vlan_tag,
+                          dl_src=vif_mac)
diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_int.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_int.py
new file mode 100644 (file)
index 0000000..34e0c2f
--- /dev/null
@@ -0,0 +1,133 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+"""
+* references
+** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic
+"""
+
+from neutron.plugins.common import constants as p_const
+from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import ovs_bridge
+from neutron.plugins.openvswitch.common import constants
+
+
+class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge):
+    """openvswitch agent br-int specific logic."""
+
+    def setup_default_table(self):
+        self.delete_flows()
+        self.install_normal()
+        self.setup_canary_table()
+        self.install_drop(table_id=constants.ARP_SPOOF_TABLE)
+
+    def setup_canary_table(self):
+        self.install_drop(constants.CANARY_TABLE)
+
+    def check_canary_table(self):
+        canary_flows = self.dump_flows(constants.CANARY_TABLE)
+        if canary_flows == '':
+            return constants.OVS_RESTARTED
+        elif canary_flows is None:
+            return constants.OVS_DEAD
+        else:
+            return constants.OVS_NORMAL
+
+    def provision_local_vlan(self, port, lvid, segmentation_id):
+        if segmentation_id is None:
+            dl_vlan = 0xffff
+        else:
+            dl_vlan = segmentation_id
+        self.add_flow(priority=3,
+                      in_port=port,
+                      dl_vlan=dl_vlan,
+                      actions="mod_vlan_vid:%s,normal" % lvid)
+
+    def reclaim_local_vlan(self, port, segmentation_id):
+        if segmentation_id is None:
+            dl_vlan = 0xffff
+        else:
+            dl_vlan = segmentation_id
+        self.delete_flows(in_port=port, dl_vlan=dl_vlan)
+
+    @staticmethod
+    def _dvr_to_src_mac_table_id(network_type):
+        if network_type == p_const.TYPE_VLAN:
+            return constants.DVR_TO_SRC_MAC_VLAN
+        else:
+            return constants.DVR_TO_SRC_MAC
+
+    def install_dvr_to_src_mac(self, network_type,
+                               vlan_tag, gateway_mac, dst_mac, dst_port):
+        table_id = self._dvr_to_src_mac_table_id(network_type)
+        self.add_flow(table=table_id,
+                      priority=4,
+                      dl_vlan=vlan_tag,
+                      dl_dst=dst_mac,
+                      actions="strip_vlan,mod_dl_src:%s,"
+                      "output:%s" % (gateway_mac, dst_port))
+
+    def delete_dvr_to_src_mac(self, network_type, vlan_tag, dst_mac):
+        table_id = self._dvr_to_src_mac_table_id(network_type)
+        self.delete_flows(table=table_id,
+                          dl_vlan=vlan_tag,
+                          dl_dst=dst_mac)
+
+    def add_dvr_mac_vlan(self, mac, port):
+        self.install_goto(table_id=constants.LOCAL_SWITCHING,
+                          priority=4,
+                          in_port=port,
+                          eth_src=mac,
+                          dest_table_id=constants.DVR_TO_SRC_MAC_VLAN)
+
+    def remove_dvr_mac_vlan(self, mac):
+        # REVISIT(yamamoto): match in_port as well?
+        self.delete_flows(table_id=constants.LOCAL_SWITCHING,
+                          eth_src=mac)
+
+    def add_dvr_mac_tun(self, mac, port):
+        # Table LOCAL_SWITCHING will now sort DVR traffic from other
+        # traffic depending on in_port
+        self.install_goto(table_id=constants.LOCAL_SWITCHING,
+                          priority=2,
+                          in_port=port,
+                          eth_src=mac,
+                          dest_table_id=constants.DVR_TO_SRC_MAC)
+
+    def remove_dvr_mac_tun(self, mac, port):
+        self.delete_flows(table_id=constants.LOCAL_SWITCHING,
+                          in_port=port, eth_src=mac)
+
+    def install_arp_spoofing_protection(self, port, ip_addresses):
+        # allow ARPs as long as they match addresses that actually
+        # belong to the port.
+        for ip in ip_addresses:
+            self.install_normal(
+                table_id=constants.ARP_SPOOF_TABLE, priority=2,
+                proto='arp', arp_spa=ip, in_port=port)
+
+        # Now that the rules are ready, direct ARP traffic from the port into
+        # the anti-spoof table.
+        # This strategy fails gracefully because OVS versions that can't match
+        # on ARP headers will just process traffic normally.
+        self.add_flow(table=constants.LOCAL_SWITCHING,
+                      priority=10, proto='arp', in_port=port,
+                      actions=("resubmit(,%s)" % constants.ARP_SPOOF_TABLE))
+
+    def delete_arp_spoofing_protection(self, port):
+        self.delete_flows(table_id=constants.LOCAL_SWITCHING,
+                          in_port=port, proto='arp')
+        self.delete_flows(table_id=constants.ARP_SPOOF_TABLE,
+                          in_port=port)
diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_phys.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_phys.py
new file mode 100644 (file)
index 0000000..3498bb8
--- /dev/null
@@ -0,0 +1,59 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import br_dvr_process
+from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import ovs_bridge
+from neutron.plugins.openvswitch.common import constants
+
+
+class OVSPhysicalBridge(ovs_bridge.OVSAgentBridge,
+                        br_dvr_process.OVSDVRProcessMixin):
+    """openvswitch agent physical bridge specific logic."""
+
+    # Used by OVSDVRProcessMixin
+    dvr_process_table_id = constants.DVR_PROCESS_VLAN
+    dvr_process_next_table_id = constants.LOCAL_VLAN_TRANSLATION
+
+    def setup_default_table(self):
+        self.delete_flows()
+        self.install_normal()
+
+    def provision_local_vlan(self, port, lvid, segmentation_id, distributed):
+        table_id = constants.LOCAL_VLAN_TRANSLATION if distributed else 0
+        if segmentation_id is None:
+            self.add_flow(table=table_id,
+                          priority=4,
+                          in_port=port,
+                          dl_vlan=lvid,
+                          actions="strip_vlan,normal")
+        else:
+            self.add_flow(table=table_id,
+                          priority=4,
+                          in_port=port,
+                          dl_vlan=lvid,
+                          actions="mod_vlan_vid:%s,normal" % segmentation_id)
+
+    def reclaim_local_vlan(self, port, lvid):
+        self.delete_flows(in_port=port, dl_vlan=lvid)
+
+    def add_dvr_mac_vlan(self, mac, port):
+        self.install_output(table_id=constants.DVR_NOT_LEARN_VLAN,
+            priority=2, eth_src=mac, port=port)
+
+    def remove_dvr_mac_vlan(self, mac):
+        # REVISIT(yamamoto): match in_port as well?
+        self.delete_flows(table_id=constants.DVR_NOT_LEARN_VLAN,
+            eth_src=mac)
diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_tun.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/br_tun.py
new file mode 100644 (file)
index 0000000..4407e2f
--- /dev/null
@@ -0,0 +1,246 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+# Copyright 2011 VMware, Inc.
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import functools
+
+import netaddr
+
+from neutron.agent.common import ovs_lib
+from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import br_dvr_process
+from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import ovs_bridge
+from neutron.plugins.openvswitch.common import constants
+
+
+class OVSTunnelBridge(ovs_bridge.OVSAgentBridge,
+                      br_dvr_process.OVSDVRProcessMixin):
+    """openvswitch agent tunnel bridge specific logic."""
+
+    # Used by OVSDVRProcessMixin
+    dvr_process_table_id = constants.DVR_PROCESS
+    dvr_process_next_table_id = constants.PATCH_LV_TO_TUN
+
+    def setup_default_table(self, patch_int_ofport, arp_responder_enabled):
+        # Table 0 (default) will sort incoming traffic depending on in_port
+        self.add_flow(priority=1,
+                      in_port=patch_int_ofport,
+                      actions="resubmit(,%s)" %
+                      constants.PATCH_LV_TO_TUN)
+        self.add_flow(priority=0, actions="drop")
+
+        if arp_responder_enabled:
+            # ARP broadcast-ed request go to the local ARP_RESPONDER table to
+            # be locally resolved
+            # REVISIT(yamamoto): arp_op=arp.ARP_REQUEST
+            self.add_flow(table=constants.PATCH_LV_TO_TUN,
+                          priority=1,
+                          proto='arp',
+                          dl_dst="ff:ff:ff:ff:ff:ff",
+                          actions=("resubmit(,%s)" %
+                                   constants.ARP_RESPONDER))
+
+        # PATCH_LV_TO_TUN table will handle packets coming from patch_int
+        # unicasts go to table UCAST_TO_TUN where remote addresses are learnt
+        self.add_flow(table=constants.PATCH_LV_TO_TUN,
+                      priority=0,
+                      dl_dst="00:00:00:00:00:00/01:00:00:00:00:00",
+                      actions="resubmit(,%s)" % constants.UCAST_TO_TUN)
+
+        # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding
+        self.add_flow(table=constants.PATCH_LV_TO_TUN,
+                      priority=0,
+                      dl_dst="01:00:00:00:00:00/01:00:00:00:00:00",
+                      actions="resubmit(,%s)" % constants.FLOOD_TO_TUN)
+
+        # Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id
+        # for each tunnel type, and resubmit to table LEARN_FROM_TUN where
+        # remote mac addresses will be learnt
+        for tunnel_type in constants.TUNNEL_NETWORK_TYPES:
+            self.add_flow(table=constants.TUN_TABLE[tunnel_type],
+                          priority=0,
+                          actions="drop")
+
+        # LEARN_FROM_TUN table will have a single flow using a learn action to
+        # dynamically set-up flows in UCAST_TO_TUN corresponding to remote mac
+        # addresses (assumes that lvid has already been set by a previous flow)
+        learned_flow = ("table=%s,"
+                        "priority=1,"
+                        "hard_timeout=300,"
+                        "NXM_OF_VLAN_TCI[0..11],"
+                        "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],"
+                        "load:0->NXM_OF_VLAN_TCI[],"
+                        "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],"
+                        "output:NXM_OF_IN_PORT[]" %
+                        constants.UCAST_TO_TUN)
+        # Once remote mac addresses are learnt, output packet to patch_int
+        self.add_flow(table=constants.LEARN_FROM_TUN,
+                      priority=1,
+                      actions="learn(%s),output:%s" %
+                      (learned_flow, patch_int_ofport))
+
+        # Egress unicast will be handled in table UCAST_TO_TUN, where remote
+        # mac addresses will be learned. For now, just add a default flow that
+        # will resubmit unknown unicasts to table FLOOD_TO_TUN to treat them
+        # as broadcasts/multicasts
+        self.add_flow(table=constants.UCAST_TO_TUN,
+                      priority=0,
+                      actions="resubmit(,%s)" %
+                      constants.FLOOD_TO_TUN)
+
+        if arp_responder_enabled:
+            # If none of the ARP entries correspond to the requested IP, the
+            # broadcast-ed packet is resubmitted to the flooding table
+            self.add_flow(table=constants.ARP_RESPONDER,
+                          priority=0,
+                          actions="resubmit(,%s)" %
+                          constants.FLOOD_TO_TUN)
+
+        # FLOOD_TO_TUN will handle flooding in tunnels based on lvid,
+        # for now, add a default drop action
+        self.install_drop(table_id=constants.FLOOD_TO_TUN)
+
+    def provision_local_vlan(self, network_type, lvid, segmentation_id,
+                             distributed=False):
+        if distributed:
+            table_id = constants.DVR_NOT_LEARN
+        else:
+            table_id = constants.LEARN_FROM_TUN
+        self.add_flow(table=constants.TUN_TABLE[network_type],
+                      priority=1,
+                      tun_id=segmentation_id,
+                      actions="mod_vlan_vid:%s,"
+                      "resubmit(,%s)" %
+                      (lvid, table_id))
+
+    def reclaim_local_vlan(self, network_type, segmentation_id):
+        self.delete_flows(table=constants.TUN_TABLE[network_type],
+                          tun_id=segmentation_id)
+
+    @staticmethod
+    def _ofport_set_to_str(ports_set):
+        return ",".join(map(str, ports_set))
+
+    def install_flood_to_tun(self, vlan, tun_id, ports, deferred_br=None):
+        br = deferred_br if deferred_br else self
+        br.mod_flow(table=constants.FLOOD_TO_TUN,
+                    dl_vlan=vlan,
+                    actions="strip_vlan,set_tunnel:%s,output:%s" %
+                    (tun_id, self._ofport_set_to_str(ports)))
+
+    def delete_flood_to_tun(self, vlan, deferred_br=None):
+        br = deferred_br if deferred_br else self
+        br.delete_flows(table=constants.FLOOD_TO_TUN, dl_vlan=vlan)
+
+    def install_unicast_to_tun(self, vlan, tun_id, port, mac,
+                               deferred_br=None):
+        br = deferred_br if deferred_br else self
+        br.add_flow(table=constants.UCAST_TO_TUN,
+                    priority=2,
+                    dl_vlan=vlan,
+                    dl_dst=mac,
+                    actions="strip_vlan,set_tunnel:%s,output:%s" %
+                    (tun_id, port))
+
+    def delete_unicast_to_tun(self, vlan, mac, deferred_br=None):
+        br = deferred_br if deferred_br else self
+        if mac is None:
+            br.delete_flows(table=constants.UCAST_TO_TUN,
+                            dl_vlan=vlan)
+        else:
+            br.delete_flows(table=constants.UCAST_TO_TUN,
+                            dl_vlan=vlan,
+                            dl_dst=mac)
+
+    def install_arp_responder(self, vlan, ip, mac, deferred_br=None):
+        br = deferred_br if deferred_br else self
+        actions = constants.ARP_RESPONDER_ACTIONS % {
+            'mac': netaddr.EUI(mac, dialect=netaddr.mac_unix),
+            'ip': netaddr.IPAddress(ip),
+        }
+        br.add_flow(table=constants.ARP_RESPONDER,
+                    priority=1,
+                    proto='arp',
+                    dl_vlan=vlan,
+                    nw_dst='%s' % ip,
+                    actions=actions)
+
+    def delete_arp_responder(self, vlan, ip, deferred_br=None):
+        br = deferred_br if deferred_br else self
+        if ip is None:
+            br.delete_flows(table=constants.ARP_RESPONDER,
+                            proto='arp',
+                            dl_vlan=vlan)
+        else:
+            br.delete_flows(table=constants.ARP_RESPONDER,
+                            proto='arp',
+                            dl_vlan=vlan,
+                            nw_dst='%s' % ip)
+
+    def setup_tunnel_port(self, network_type, port):
+        self.add_flow(priority=1,
+                      in_port=port,
+                      actions="resubmit(,%s)" %
+                      constants.TUN_TABLE[network_type])
+
+    def cleanup_tunnel_port(self, port):
+        self.delete_flows(in_port=port)
+
+    def add_dvr_mac_tun(self, mac, port):
+        # Table DVR_NOT_LEARN ensures unique dvr macs in the cloud
+        # are not learnt, as they may result in flow explosions
+        self.install_output(table_id=constants.DVR_NOT_LEARN,
+                            priority=1,
+                            eth_src=mac,
+                            port=port)
+
+    def remove_dvr_mac_tun(self, mac):
+        # REVISIT(yamamoto): match in_port as well?
+        self.delete_flows(table_id=constants.DVR_NOT_LEARN,
+                          eth_src=mac)
+
+    def deferred(self, **kwargs):
+        return DeferredOVSTunnelBridge(self, **kwargs)
+
+
+class DeferredOVSTunnelBridge(ovs_lib.DeferredOVSBridge):
+    _METHODS = [
+        'install_unicast_to_tun',
+        'delete_unicast_to_tun',
+        'install_flood_to_tun',
+        'delete_flood_to_tun',
+        'install_arp_responder',
+        'delete_arp_responder',
+    ]
+
+    def __getattr__(self, name):
+        if name in self._METHODS:
+            m = getattr(self.br, name)
+            return functools.partial(m, deferred_br=self)
+        raise AttributeError(name)
diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/main.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/main.py
new file mode 100644 (file)
index 0000000..537c324
--- /dev/null
@@ -0,0 +1,33 @@
+# Copyright (C) 2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import br_int
+from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import br_phys
+from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import br_tun
+from neutron.plugins.openvswitch.agent import ovs_neutron_agent
+
+
+def init_config():
+    pass
+
+
+def main():
+    bridge_classes = {
+        'br_int': br_int.OVSIntegrationBridge,
+        'br_phys': br_phys.OVSPhysicalBridge,
+        'br_tun': br_tun.OVSTunnelBridge,
+    }
+    ovs_neutron_agent.main(bridge_classes)
diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ofswitch.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ofswitch.py
new file mode 100644 (file)
index 0000000..578e3e2
--- /dev/null
@@ -0,0 +1,74 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+# Field name mappings (from Ryu to ovs-ofctl)
+_keywords = {
+    'eth_src': 'dl_src',
+    'eth_dst': 'dl_dst',
+    'ipv4_src': 'nw_src',
+    'ipv4_dst': 'nw_dst',
+    'table_id': 'table',
+}
+
+
+class OpenFlowSwitchMixin(object):
+    """Mixin to provide common convenient routines for an openflow switch."""
+
+    @staticmethod
+    def _conv_args(kwargs):
+        for our_name, ovs_ofctl_name in _keywords.items():
+            if our_name in kwargs:
+                kwargs[ovs_ofctl_name] = kwargs.pop(our_name)
+        return kwargs
+
+    def dump_flows(self, table_id):
+        return self.dump_flows_for_table(table_id)
+
+    def install_goto_next(self, table_id):
+        self.install_goto(table_id=table_id, dest_table_id=table_id + 1)
+
+    def install_output(self, port, table_id=0, priority=0, **kwargs):
+        self.add_flow(table=table_id,
+                      priority=priority,
+                      actions="output:%s" % port,
+                      **self._conv_args(kwargs))
+
+    def install_normal(self, table_id=0, priority=0, **kwargs):
+        self.add_flow(table=table_id,
+                      priority=priority,
+                      actions="normal",
+                      **self._conv_args(kwargs))
+
+    def install_goto(self, dest_table_id, table_id=0, priority=0, **kwargs):
+        self.add_flow(table=table_id,
+                      priority=priority,
+                      actions="resubmit(,%s)" % dest_table_id,
+                      **self._conv_args(kwargs))
+
+    def install_drop(self, table_id=0, priority=0, **kwargs):
+        self.add_flow(table=table_id,
+                      priority=priority,
+                      actions="drop",
+                      **self._conv_args(kwargs))
+
+    def delete_flows(self, **kwargs):
+        # NOTE(yamamoto): super() points to ovs_lib.OVSBridge.
+        # See ovs_bridge.py how this class is actually used.
+        if kwargs:
+            super(OpenFlowSwitchMixin, self).delete_flows(
+                **self._conv_args(kwargs))
+        else:
+            super(OpenFlowSwitchMixin, self).remove_all_flows()
diff --git a/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge.py b/neutron/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge.py
new file mode 100644 (file)
index 0000000..179994d
--- /dev/null
@@ -0,0 +1,30 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+
+from neutron.agent.common import ovs_lib
+from neutron.plugins.openvswitch.agent.openflow.ovs_ofctl import ofswitch
+
+
+class OVSAgentBridge(ofswitch.OpenFlowSwitchMixin, ovs_lib.OVSBridge):
+    """Common code for bridges used by OVS agent"""
+
+    def setup_controllers(self, conf):
+        self.set_protocols("[OpenFlow10]")
+        self.del_controller()
+
+    def drop_port(self, in_port):
+        self.install_drop(priority=2, in_port=in_port)
index 43f4ac5e7e0a8f438c81ccb93fcdd6affa8d48ac..0243a555bdd40e8fa64bf31c2cffe56916da2a9b 100644 (file)
@@ -171,8 +171,9 @@ class OVSDVRNeutronAgent(object):
 
         if not self.in_distributed_mode():
             # switch all traffic using L2 learning
-            self.int_br.add_flow(table=constants.LOCAL_SWITCHING,
-                                 priority=1, actions="normal")
+            # REVISIT(yamamoto): why to install the same flow as
+            # setup_integration_br?
+            self.int_br.install_normal()
 
     def get_dvr_mac_address_with_retry(self):
         # Get the local DVR MAC Address from the Neutron Server.
@@ -204,52 +205,42 @@ class OVSDVRNeutronAgent(object):
         LOG.info(_LI("L2 Agent operating in DVR Mode with MAC %s"),
                  self.dvr_mac_address)
         # Remove existing flows in integration bridge
-        self.int_br.remove_all_flows()
+        self.int_br.delete_flows()
 
         # Add a canary flow to int_br to track OVS restarts
-        self.int_br.add_flow(table=constants.CANARY_TABLE, priority=0,
-                             actions="drop")
+        self.int_br.setup_canary_table()
 
         # Insert 'drop' action as the default for Table DVR_TO_SRC_MAC
-        self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC,
-                             priority=1,
-                             actions="drop")
+        self.int_br.install_drop(table_id=constants.DVR_TO_SRC_MAC, priority=1)
 
-        self.int_br.add_flow(table=constants.DVR_TO_SRC_MAC_VLAN,
-                             priority=1,
-                             actions="drop")
+        self.int_br.install_drop(table_id=constants.DVR_TO_SRC_MAC_VLAN,
+                                 priority=1)
 
         # Insert 'normal' action as the default for Table LOCAL_SWITCHING
-        self.int_br.add_flow(table=constants.LOCAL_SWITCHING,
-                             priority=1,
-                             actions="normal")
+        self.int_br.install_normal(table_id=constants.LOCAL_SWITCHING,
+                                   priority=1)
 
         for physical_network in self.bridge_mappings:
-            self.int_br.add_flow(table=constants.LOCAL_SWITCHING,
-                                 priority=2,
-                                 in_port=self.int_ofports[physical_network],
-                                 actions="drop")
+            self.int_br.install_drop(table_id=constants.LOCAL_SWITCHING,
+                                     priority=2,
+                                     in_port=self.int_ofports[
+                                         physical_network])
 
     def setup_dvr_flows_on_tun_br(self):
         '''Setup up initial dvr flows into br-tun'''
         if not self.enable_tunneling or not self.in_distributed_mode():
             return
 
-        self.tun_br.add_flow(priority=1,
-                             in_port=self.patch_int_ofport,
-                             actions="resubmit(,%s)" %
-                             constants.DVR_PROCESS)
+        self.tun_br.install_goto(dest_table_id=constants.DVR_PROCESS,
+                                 priority=1,
+                                 in_port=self.patch_int_ofport)
 
         # table-miss should be sent to learning table
-        self.tun_br.add_flow(table=constants.DVR_NOT_LEARN,
-                             priority=0,
-                             actions="resubmit(,%s)" %
-                             constants.LEARN_FROM_TUN)
+        self.tun_br.install_goto(table_id=constants.DVR_NOT_LEARN,
+                                 dest_table_id=constants.LEARN_FROM_TUN)
 
-        self.tun_br.add_flow(table=constants.DVR_PROCESS,
-                             priority=0,
-                             actions="resubmit(,%s)" %
-                             constants.PATCH_LV_TO_TUN)
+        self.tun_br.install_goto(table_id=constants.DVR_PROCESS,
+                                 dest_table_id=constants.PATCH_LV_TO_TUN)
 
     def setup_dvr_flows_on_phys_br(self):
         '''Setup up initial dvr flows into br-phys'''
@@ -257,27 +248,63 @@ class OVSDVRNeutronAgent(object):
             return
 
         for physical_network in self.bridge_mappings:
-            self.phys_brs[physical_network].add_flow(priority=2,
+            self.phys_brs[physical_network].install_goto(
                 in_port=self.phys_ofports[physical_network],
-                actions="resubmit(,%s)" %
-                constants.DVR_PROCESS_VLAN)
-            self.phys_brs[physical_network].add_flow(priority=1,
-                actions="resubmit(,%s)" %
-                constants.DVR_NOT_LEARN_VLAN)
-            self.phys_brs[physical_network].add_flow(
-                table=constants.DVR_PROCESS_VLAN,
-                priority=0,
-                actions="resubmit(,%s)" %
-                constants.LOCAL_VLAN_TRANSLATION)
-            self.phys_brs[physical_network].add_flow(
-                table=constants.LOCAL_VLAN_TRANSLATION,
                 priority=2,
-                in_port=self.phys_ofports[physical_network],
-                actions="drop")
-            self.phys_brs[physical_network].add_flow(
-                table=constants.DVR_NOT_LEARN_VLAN,
+                dest_table_id=constants.DVR_PROCESS_VLAN)
+            self.phys_brs[physical_network].install_goto(
                 priority=1,
-                actions="NORMAL")
+                dest_table_id=constants.DVR_NOT_LEARN_VLAN)
+            self.phys_brs[physical_network].install_goto(
+                table_id=constants.DVR_PROCESS_VLAN,
+                priority=0,
+                dest_table_id=constants.LOCAL_VLAN_TRANSLATION)
+            self.phys_brs[physical_network].install_drop(
+                table_id=constants.LOCAL_VLAN_TRANSLATION,
+                in_port=self.phys_ofports[physical_network],
+                priority=2)
+            self.phys_brs[physical_network].install_normal(
+                table_id=constants.DVR_NOT_LEARN_VLAN,
+                priority=1)
+
+    def _add_dvr_mac_for_phys_br(self, physical_network, mac):
+        self.int_br.add_dvr_mac_vlan(mac=mac,
+                                     port=self.int_ofports[physical_network])
+        phys_br = self.phys_brs[physical_network]
+        phys_br.add_dvr_mac_vlan(mac=mac,
+                                 port=self.phys_ofports[physical_network])
+
+    def _remove_dvr_mac_for_phys_br(self, physical_network, mac):
+        # REVISIT(yamamoto): match in_port as well?
+        self.int_br.remove_dvr_mac_vlan(mac=mac)
+        phys_br = self.phys_brs[physical_network]
+        # REVISIT(yamamoto): match in_port as well?
+        phys_br.remove_dvr_mac_vlan(mac=mac)
+
+    def _add_dvr_mac_for_tun_br(self, mac):
+        self.int_br.add_dvr_mac_tun(mac=mac, port=self.patch_tun_ofport)
+        self.tun_br.add_dvr_mac_tun(mac=mac, port=self.patch_int_ofport)
+
+    def _remove_dvr_mac_for_tun_br(self, mac):
+        self.int_br.remove_dvr_mac_tun(mac=mac, port=self.patch_tun_ofport)
+        # REVISIT(yamamoto): match in_port as well?
+        self.tun_br.remove_dvr_mac_tun(mac=mac)
+
+    def _add_dvr_mac(self, mac):
+        for physical_network in self.bridge_mappings:
+            self._add_dvr_mac_for_phys_br(physical_network, mac)
+        if self.enable_tunneling:
+            self._add_dvr_mac_for_tun_br(mac)
+        LOG.debug("Added DVR MAC flow for %s", mac)
+        self.registered_dvr_macs.add(mac)
+
+    def _remove_dvr_mac(self, mac):
+        for physical_network in self.bridge_mappings:
+            self._remove_dvr_mac_for_phys_br(physical_network, mac)
+        if self.enable_tunneling:
+            self._remove_dvr_mac_for_tun_br(mac)
+        LOG.debug("Removed DVR MAC flow for %s", mac)
+        self.registered_dvr_macs.remove(mac)
 
     def setup_dvr_mac_flows_on_all_brs(self):
         if not self.in_distributed_mode():
@@ -289,38 +316,7 @@ class OVSDVRNeutronAgent(object):
         for mac in dvr_macs:
             if mac['mac_address'] == self.dvr_mac_address:
                 continue
-            for physical_network in self.bridge_mappings:
-                self.int_br.add_flow(table=constants.LOCAL_SWITCHING,
-                    priority=4,
-                    in_port=self.int_ofports[physical_network],
-                    dl_src=mac['mac_address'],
-                    actions="resubmit(,%s)" %
-                    constants.DVR_TO_SRC_MAC_VLAN)
-                self.phys_brs[physical_network].add_flow(
-                    table=constants.DVR_NOT_LEARN_VLAN,
-                    priority=2,
-                    dl_src=mac['mac_address'],
-                    actions="output:%s" %
-                    self.phys_ofports[physical_network])
-
-            if self.enable_tunneling:
-                # Table 0 (default) will now sort DVR traffic from other
-                # traffic depending on in_port
-                self.int_br.add_flow(table=constants.LOCAL_SWITCHING,
-                                     priority=2,
-                                     in_port=self.patch_tun_ofport,
-                                     dl_src=mac['mac_address'],
-                                     actions="resubmit(,%s)" %
-                                     constants.DVR_TO_SRC_MAC)
-                # Table DVR_NOT_LEARN ensures unique dvr macs in the cloud
-                # are not learnt, as they may
-                # result in flow explosions
-                self.tun_br.add_flow(table=constants.DVR_NOT_LEARN,
-                                 priority=1,
-                                 dl_src=mac['mac_address'],
-                                 actions="output:%s" %
-                                 self.patch_int_ofport)
-            self.registered_dvr_macs.add(mac['mac_address'])
+            self._add_dvr_mac(mac['mac_address'])
 
     def dvr_mac_address_update(self, dvr_macs):
         if not self.dvr_mac_address:
@@ -342,50 +338,10 @@ class OVSDVRNeutronAgent(object):
         dvr_macs_removed = self.registered_dvr_macs - dvr_host_macs
 
         for oldmac in dvr_macs_removed:
-            for physical_network in self.bridge_mappings:
-                self.int_br.delete_flows(table=constants.LOCAL_SWITCHING,
-                    in_port=self.int_ofports[physical_network],
-                    dl_src=oldmac)
-                self.phys_brs[physical_network].delete_flows(
-                    table=constants.DVR_NOT_LEARN_VLAN,
-                    dl_src=oldmac)
-            if self.enable_tunneling:
-                self.int_br.delete_flows(table=constants.LOCAL_SWITCHING,
-                                         in_port=self.patch_tun_ofport,
-                                         dl_src=oldmac)
-                self.tun_br.delete_flows(table=constants.DVR_NOT_LEARN,
-                                         dl_src=oldmac)
-            LOG.debug("Removed DVR MAC flow for %s", oldmac)
-            self.registered_dvr_macs.remove(oldmac)
+            self._remove_dvr_mac(oldmac)
 
         for newmac in dvr_macs_added:
-            for physical_network in self.bridge_mappings:
-                self.int_br.add_flow(table=constants.LOCAL_SWITCHING,
-                    priority=4,
-                    in_port=self.int_ofports[physical_network],
-                    dl_src=newmac,
-                    actions="resubmit(,%s)" %
-                    constants.DVR_TO_SRC_MAC_VLAN)
-                self.phys_brs[physical_network].add_flow(
-                    table=constants.DVR_NOT_LEARN_VLAN,
-                    priority=2,
-                    dl_src=newmac,
-                    actions="output:%s" %
-                    self.phys_ofports[physical_network])
-            if self.enable_tunneling:
-                self.int_br.add_flow(table=constants.LOCAL_SWITCHING,
-                                     priority=2,
-                                     in_port=self.patch_tun_ofport,
-                                     dl_src=newmac,
-                                     actions="resubmit(,%s)" %
-                                     constants.DVR_TO_SRC_MAC)
-                self.tun_br.add_flow(table=constants.DVR_NOT_LEARN,
-                                     priority=1,
-                                     dl_src=newmac,
-                                     actions="output:%s" %
-                                     self.patch_int_ofport)
-            LOG.debug("Added DVR MAC flow for %s", newmac)
-            self.registered_dvr_macs.add(newmac)
+            self._add_dvr_mac(newmac)
 
     def in_distributed_mode(self):
         return self.dvr_mac_address is not None
@@ -394,16 +350,11 @@ class OVSDVRNeutronAgent(object):
         return device_owner == n_const.DEVICE_OWNER_DVR_INTERFACE
 
     def process_tunneled_network(self, network_type, lvid, segmentation_id):
-        if self.in_distributed_mode():
-            table_id = constants.DVR_NOT_LEARN
-        else:
-            table_id = constants.LEARN_FROM_TUN
-        self.tun_br.add_flow(table=constants.TUN_TABLE[network_type],
-                             priority=1,
-                             tun_id=segmentation_id,
-                             actions="mod_vlan_vid:%s,"
-                             "resubmit(,%s)" %
-                             (lvid, table_id))
+        self.tun_br.provision_local_vlan(
+            network_type=network_type,
+            lvid=lvid,
+            segmentation_id=segmentation_id,
+            distributed=self.in_distributed_mode())
 
     def _bind_distributed_router_interface_port(self, port, lvm,
                                                 fixed_ips, device_owner):
@@ -436,10 +387,8 @@ class OVSDVRNeutronAgent(object):
         # DVR takes over
         ldm.set_dvr_owned(True)
 
-        table_id = constants.DVR_TO_SRC_MAC
         vlan_to_use = lvm.vlan
         if lvm.network_type == p_const.TYPE_VLAN:
-            table_id = constants.DVR_TO_SRC_MAC_VLAN
             vlan_to_use = lvm.segmentation_id
 
         subnet_info = ldm.get_subnet_info()
@@ -469,73 +418,31 @@ class OVSDVRNeutronAgent(object):
                 comp_ovsport.add_subnet(subnet_uuid)
                 self.local_ports[vif.vif_id] = comp_ovsport
             # create rule for just this vm port
-            self.int_br.add_flow(table=table_id,
-                                 priority=4,
-                                 dl_vlan=vlan_to_use,
-                                 dl_dst=comp_ovsport.get_mac(),
-                                 actions="strip_vlan,mod_dl_src:%s,"
-                                 "output:%s" %
-                                 (subnet_info['gateway_mac'],
-                                  comp_ovsport.get_ofport()))
+            self.int_br.install_dvr_to_src_mac(
+                network_type=lvm.network_type,
+                vlan_tag=vlan_to_use,
+                gateway_mac=subnet_info['gateway_mac'],
+                dst_mac=comp_ovsport.get_mac(),
+                dst_port=comp_ovsport.get_ofport())
 
         if lvm.network_type == p_const.TYPE_VLAN:
-            args = {'table': constants.DVR_PROCESS_VLAN,
-                    'priority': 3,
-                    'dl_vlan': lvm.vlan,
-                    'actions': "drop"}
-            if ip_version == 4:
-                args['proto'] = 'arp'
-                args['nw_dst'] = subnet_info['gateway_ip']
-            else:
-                args['proto'] = 'icmp6'
-                args['icmp_type'] = n_const.ICMPV6_TYPE_RA
-                args['dl_src'] = subnet_info['gateway_mac']
-            # TODO(vivek) remove the IPv6 related add_flow once SNAT is not
+            # TODO(vivek) remove the IPv6 related flows once SNAT is not
             # used for IPv6 DVR.
-            self.phys_brs[lvm.physical_network].add_flow(**args)
-            self.phys_brs[lvm.physical_network].add_flow(
-                table=constants.DVR_PROCESS_VLAN,
-                priority=2,
-                dl_vlan=lvm.vlan,
-                dl_dst=port.vif_mac,
-                actions="drop")
-
-            self.phys_brs[lvm.physical_network].add_flow(
-                table=constants.DVR_PROCESS_VLAN,
-                priority=1,
-                dl_vlan=lvm.vlan,
-                dl_src=port.vif_mac,
-                actions="mod_dl_src:%s,resubmit(,%s)" %
-                (self.dvr_mac_address, constants.LOCAL_VLAN_TRANSLATION))
-
+            br = self.phys_brs[lvm.physical_network]
         if lvm.network_type in constants.TUNNEL_NETWORK_TYPES:
-            args = {'table': constants.DVR_PROCESS,
-                    'priority': 3,
-                    'dl_vlan': lvm.vlan,
-                    'actions': "drop"}
-            if ip_version == 4:
-                args['proto'] = 'arp'
-                args['nw_dst'] = subnet_info['gateway_ip']
-            else:
-                args['proto'] = 'icmp6'
-                args['icmp_type'] = n_const.ICMPV6_TYPE_RA
-                args['dl_src'] = subnet_info['gateway_mac']
-            # TODO(vivek) remove the IPv6 related add_flow once SNAT is not
-            # used for IPv6 DVR.
-            self.tun_br.add_flow(**args)
-            self.tun_br.add_flow(table=constants.DVR_PROCESS,
-                                 priority=2,
-                                 dl_vlan=lvm.vlan,
-                                 dl_dst=port.vif_mac,
-                                 actions="drop")
-
-            self.tun_br.add_flow(table=constants.DVR_PROCESS,
-                                 priority=1,
-                                 dl_vlan=lvm.vlan,
-                                 dl_src=port.vif_mac,
-                                 actions="mod_dl_src:%s,resubmit(,%s)" %
-                                 (self.dvr_mac_address,
-                                  constants.PATCH_LV_TO_TUN))
+            br = self.tun_br
+        # TODO(vivek) remove the IPv6 related flows once SNAT is not
+        # used for IPv6 DVR.
+        if ip_version == 4:
+            br.install_dvr_process_ipv4(
+                vlan_tag=lvm.vlan, gateway_ip=subnet_info['gateway_ip'])
+        else:
+            br.install_dvr_process_ipv6(
+                vlan_tag=lvm.vlan, gateway_mac=subnet_info['gateway_mac'])
+        br.install_dvr_process(
+            vlan_tag=lvm.vlan, vif_mac=port.vif_mac,
+            dvr_mac_address=self.dvr_mac_address)
+
         # the dvr router interface is itself a port, so capture it
         # queue this subnet to that port. A subnet appears only once as
         # a router interface on any given router
@@ -578,20 +485,16 @@ class OVSDVRNeutronAgent(object):
                                   port.vif_mac, device_owner)
                 ovsport.add_subnet(subnet_uuid)
                 self.local_ports[port.vif_id] = ovsport
-            table_id = constants.DVR_TO_SRC_MAC
             vlan_to_use = lvm.vlan
             if lvm.network_type == p_const.TYPE_VLAN:
-                table_id = constants.DVR_TO_SRC_MAC_VLAN
                 vlan_to_use = lvm.segmentation_id
             # create a rule for this vm port
-            self.int_br.add_flow(table=table_id,
-                                 priority=4,
-                                 dl_vlan=vlan_to_use,
-                                 dl_dst=ovsport.get_mac(),
-                                 actions="strip_vlan,mod_dl_src:%s,"
-                                 "output:%s" %
-                                 (subnet_info['gateway_mac'],
-                                  ovsport.get_ofport()))
+            self.int_br.install_dvr_to_src_mac(
+                network_type=lvm.network_type,
+                vlan_tag=vlan_to_use,
+                gateway_mac=subnet_info['gateway_mac'],
+                dst_mac=ovsport.get_mac(),
+                dst_port=ovsport.get_ofport())
 
     def _bind_centralized_snat_port_on_dvr_subnet(self, port, lvm,
                                                   fixed_ips, device_owner):
@@ -631,19 +534,15 @@ class OVSDVRNeutronAgent(object):
                           port.vif_mac, device_owner)
         ovsport.add_subnet(subnet_uuid)
         self.local_ports[port.vif_id] = ovsport
-        table_id = constants.DVR_TO_SRC_MAC
         vlan_to_use = lvm.vlan
         if lvm.network_type == p_const.TYPE_VLAN:
-            table_id = constants.DVR_TO_SRC_MAC_VLAN
             vlan_to_use = lvm.segmentation_id
-        self.int_br.add_flow(table=table_id,
-                             priority=4,
-                             dl_vlan=vlan_to_use,
-                             dl_dst=ovsport.get_mac(),
-                             actions="strip_vlan,mod_dl_src:%s,"
-                             " output:%s" %
-                             (subnet_info['gateway_mac'],
-                              ovsport.get_ofport()))
+        self.int_br.install_dvr_to_src_mac(
+            network_type=lvm.network_type,
+            vlan_tag=vlan_to_use,
+            gateway_mac=subnet_info['gateway_mac'],
+            dst_mac=ovsport.get_mac(),
+            dst_port=ovsport.get_ofport())
 
     def bind_port_to_dvr(self, port, local_vlan_map,
                          fixed_ips, device_owner):
@@ -681,10 +580,8 @@ class OVSDVRNeutronAgent(object):
         subnet_set = set(subnet_ids)
         network_type = lvm.network_type
         physical_network = lvm.physical_network
-        table_id = constants.DVR_TO_SRC_MAC
         vlan_to_use = lvm.vlan
         if network_type == p_const.TYPE_VLAN:
-            table_id = constants.DVR_TO_SRC_MAC_VLAN
             vlan_to_use = lvm.segmentation_id
         # ensure we process for all the subnets laid on this removed port
         for sub_uuid in subnet_set:
@@ -700,9 +597,9 @@ class OVSDVRNeutronAgent(object):
             compute_ports = ldm.get_compute_ofports()
             for vif_id in compute_ports:
                 comp_port = self.local_ports[vif_id]
-                self.int_br.delete_flows(table=table_id,
-                                         dl_vlan=vlan_to_use,
-                                         dl_dst=comp_port.get_mac())
+                self.int_br.delete_dvr_to_src_mac(
+                    network_type=network_type,
+                    vlan_tag=vlan_to_use, dst_mac=comp_port.get_mac())
             ldm.remove_all_compute_ofports()
 
             if ldm.get_csnat_ofport() == constants.OFPORT_INVALID:
@@ -711,47 +608,23 @@ class OVSDVRNeutronAgent(object):
                 # ports available on this agent anymore
                 self.local_dvr_map.pop(sub_uuid, None)
             if network_type == p_const.TYPE_VLAN:
-                args = {'table': constants.DVR_PROCESS_VLAN,
-                        'dl_vlan': lvm.vlan}
-                if ip_version == 4:
-                    args['proto'] = 'arp'
-                    args['nw_dst'] = subnet_info['gateway_ip']
-                else:
-                    args['proto'] = 'icmp6'
-                    args['icmp_type'] = n_const.ICMPV6_TYPE_RA
-                    args['dl_src'] = subnet_info['gateway_mac']
-                self.phys_br[physical_network].delete_flows(**args)
-
+                br = self.phys_br[physical_network]
             if network_type in constants.TUNNEL_NETWORK_TYPES:
-                args = {'table': constants.DVR_PROCESS,
-                        'dl_vlan': lvm.vlan}
-                if ip_version == 4:
-                    args['proto'] = 'arp'
-                    args['nw_dst'] = subnet_info['gateway_ip']
-                else:
-                    args['proto'] = 'icmp6'
-                    args['icmp_type'] = n_const.ICMPV6_TYPE_RA
-                    args['dl_src'] = subnet_info['gateway_mac']
-                self.tun_br.delete_flows(**args)
+                br = self.tun_br
+            if ip_version == 4:
+                br.delete_dvr_process_ipv4(
+                    vlan_tag=lvm.vlan, gateway_ip=subnet_info['gateway_ip'])
+            else:
+                br.delete_dvr_process_ipv6(
+                    vlan_tag=lvm.vlan, gateway_mac=subnet_info['gateway_mac'])
             ovsport.remove_subnet(sub_uuid)
 
         if lvm.network_type == p_const.TYPE_VLAN:
-            self.phys_br[physical_network].delete_flows(
-                table=constants.DVR_PROCESS_VLAN,
-                dl_vlan=lvm.vlan,
-                dl_dst=port.vif_mac)
-            self.phys_br[physical_network].delete_flows(
-                table=constants.DVR_PROCESS_VLAN,
-                dl_vlan=lvm.vlan,
-                dl_src=port.vif_mac)
-
+            br = self.phys_br[physical_network]
         if lvm.network_type in constants.TUNNEL_NETWORK_TYPES:
-            self.tun_br.delete_flows(table=constants.DVR_PROCESS,
-                                     dl_vlan=lvm.vlan,
-                                     dl_dst=port.vif_mac)
-            self.tun_br.delete_flows(table=constants.DVR_PROCESS,
-                                     dl_vlan=lvm.vlan,
-                                     dl_src=port.vif_mac)
+            br = self.tun_br
+        br.delete_dvr_process(vlan_tag=lvm.vlan, vif_mac=port.vif_mac)
+
         # release port state
         self.local_ports.pop(port.vif_id, None)
 
@@ -767,15 +640,13 @@ class OVSDVRNeutronAgent(object):
                 continue
             ldm = self.local_dvr_map[sub_uuid]
             ldm.remove_compute_ofport(port.vif_id)
-            table_id = constants.DVR_TO_SRC_MAC
             vlan_to_use = lvm.vlan
             if lvm.network_type == p_const.TYPE_VLAN:
-                table_id = constants.DVR_TO_SRC_MAC_VLAN
                 vlan_to_use = lvm.segmentation_id
             # first remove this vm port rule
-            self.int_br.delete_flows(table=table_id,
-                                     dl_vlan=vlan_to_use,
-                                     dl_dst=ovsport.get_mac())
+            self.int_br.delete_dvr_to_src_mac(
+                network_type=lvm.network_type,
+                vlan_tag=vlan_to_use, dst_mac=ovsport.get_mac())
         # release port state
         self.local_ports.pop(port.vif_id, None)
 
@@ -790,15 +661,13 @@ class OVSDVRNeutronAgent(object):
             return
         ldm = self.local_dvr_map[sub_uuid]
         ldm.set_csnat_ofport(constants.OFPORT_INVALID)
-        table_id = constants.DVR_TO_SRC_MAC
         vlan_to_use = lvm.vlan
         if lvm.network_type == p_const.TYPE_VLAN:
-            table_id = constants.DVR_TO_SRC_MAC_VLAN
             vlan_to_use = lvm.segmentation_id
         # then remove csnat port rule
-        self.int_br.delete_flows(table=table_id,
-                                 dl_vlan=vlan_to_use,
-                                 dl_dst=ovsport.get_mac())
+        self.int_br.delete_dvr_to_src_mac(
+            network_type=lvm.network_type,
+            vlan_tag=vlan_to_use, dst_mac=ovsport.get_mac())
         if not ldm.is_dvr_owned():
             # if not owned by DVR (only used for csnat), remove this
             # subnet state altogether
index 168e9093377ac4cbbd02db92ca11aea6cd666af3..cb3d44827c1c290350f5eb84a6362d31f50f30fc 100644 (file)
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # Copyright 2011 VMware, Inc.
 # All Rights Reserved.
 #
@@ -25,7 +24,6 @@ from oslo_log import log as logging
 import oslo_messaging
 from six import moves
 
-from neutron.agent.common import config
 from neutron.agent.common import ovs_lib
 from neutron.agent.common import polling
 from neutron.agent.common import utils
@@ -34,7 +32,6 @@ from neutron.agent.linux import ip_lib
 from neutron.agent import rpc as agent_rpc
 from neutron.agent import securitygroups_rpc as sg_rpc
 from neutron.api.rpc.handlers import dvr_rpc
-from neutron.common import config as common_config
 from neutron.common import constants as q_const
 from neutron.common import exceptions
 from neutron.common import topics
@@ -49,11 +46,16 @@ from neutron.plugins.openvswitch.common import constants
 
 LOG = logging.getLogger(__name__)
 cfg.CONF.import_group('AGENT', 'neutron.plugins.openvswitch.common.config')
+cfg.CONF.import_group('OVS', 'neutron.plugins.openvswitch.common.config')
 
 # A placeholder for dead vlans.
 DEAD_VLAN_TAG = p_const.MAX_VLAN_TAG + 1
 
 
+class _mac_mydialect(netaddr.mac_unix):
+    word_fmt = '%.2x'
+
+
 class DeviceListRetrievalError(exceptions.NeutronException):
     message = _("Unable to retrieve port details for devices: %(devices)s "
                 "because of error: %(error)s")
@@ -119,7 +121,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
     #   1.2 Support DVR (Distributed Virtual Router) RPC
     target = oslo_messaging.Target(version='1.2')
 
-    def __init__(self, integ_br, tun_br, local_ip,
+    def __init__(self, bridge_classes, integ_br, tun_br, local_ip,
                  bridge_mappings, polling_interval, tunnel_types=None,
                  veth_mtu=None, l2_population=False,
                  enable_distributed_routing=False,
@@ -132,6 +134,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                  quitting_rpc_timeout=None):
         '''Constructor.
 
+        :param bridge_classes: a dict for bridge classes.
         :param integ_br: name of the integration bridge.
         :param tun_br: name of the tunnel bridge.
         :param local_ip: local IP address of this hypervisor.
@@ -159,6 +162,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                SIGTERM is received
         '''
         super(OVSNeutronAgent, self).__init__()
+        self.br_int_cls = bridge_classes['br_int']
+        self.br_phys_cls = bridge_classes['br_phys']
+        self.br_tun_cls = bridge_classes['br_tun']
         self.use_veth_interconnection = use_veth_interconnection
         self.veth_mtu = veth_mtu
         self.available_local_vlans = set(moves.range(p_const.MIN_VLAN_TAG,
@@ -197,7 +203,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
         # Keep track of int_br's device count for use by _report_state()
         self.int_br_device_count = 0
 
-        self.int_br = ovs_lib.OVSBridge(integ_br)
+        self.int_br = self.br_int_cls(integ_br)
         self.setup_integration_br()
         # Stores port update notifications for processing in main rpc loop
         self.updated_ports = set()
@@ -440,21 +446,16 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
     def add_fdb_flow(self, br, port_info, remote_ip, lvm, ofport):
         if port_info == q_const.FLOODING_ENTRY:
             lvm.tun_ofports.add(ofport)
-            ofports = _ofport_set_to_str(lvm.tun_ofports)
-            br.mod_flow(table=constants.FLOOD_TO_TUN,
-                        dl_vlan=lvm.vlan,
-                        actions="strip_vlan,set_tunnel:%s,output:%s" %
-                        (lvm.segmentation_id, ofports))
+            br.install_flood_to_tun(lvm.vlan, lvm.segmentation_id,
+                                    lvm.tun_ofports)
         else:
             self.setup_entry_for_arp_reply(br, 'add', lvm.vlan,
                                            port_info.mac_address,
                                            port_info.ip_address)
-            br.add_flow(table=constants.UCAST_TO_TUN,
-                        priority=2,
-                        dl_vlan=lvm.vlan,
-                        dl_dst=port_info.mac_address,
-                        actions="strip_vlan,set_tunnel:%s,output:%s" %
-                        (lvm.segmentation_id, ofport))
+            br.install_unicast_to_tun(lvm.vlan,
+                                      lvm.segmentation_id,
+                                      ofport,
+                                      port_info.mac_address)
 
     def del_fdb_flow(self, br, port_info, remote_ip, lvm, ofport):
         if port_info == q_const.FLOODING_ENTRY:
@@ -463,21 +464,16 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                 return
             lvm.tun_ofports.remove(ofport)
             if len(lvm.tun_ofports) > 0:
-                ofports = _ofport_set_to_str(lvm.tun_ofports)
-                br.mod_flow(table=constants.FLOOD_TO_TUN,
-                            dl_vlan=lvm.vlan,
-                            actions="strip_vlan,set_tunnel:%s,output:%s" %
-                            (lvm.segmentation_id, ofports))
+                br.install_flood_to_tun(lvm.vlan, lvm.segmentation_id,
+                                        lvm.tun_ofports)
             else:
                 # This local vlan doesn't require any more tunnelling
-                br.delete_flows(table=constants.FLOOD_TO_TUN, dl_vlan=lvm.vlan)
+                br.delete_flood_to_tun(lvm.vlan)
         else:
             self.setup_entry_for_arp_reply(br, 'remove', lvm.vlan,
                                            port_info.mac_address,
                                            port_info.ip_address)
-            br.delete_flows(table=constants.UCAST_TO_TUN,
-                            dl_vlan=lvm.vlan,
-                            dl_dst=port_info.mac_address)
+            br.delete_unicast_to_tun(lvm.vlan, port_info.mac_address)
 
     def _fdb_chg_ip(self, context, fdb_entries):
         LOG.debug("update chg_ip received")
@@ -496,25 +492,39 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
         if not self.arp_responder_enabled:
             return
 
-        mac = netaddr.EUI(mac_address, dialect=netaddr.mac_unix)
-        ip = netaddr.IPAddress(ip_address)
+        mac = str(netaddr.EUI(mac_address, dialect=_mac_mydialect))
+        ip = str(netaddr.IPAddress(ip_address))
 
         if action == 'add':
-            actions = constants.ARP_RESPONDER_ACTIONS % {'mac': mac, 'ip': ip}
-            br.add_flow(table=constants.ARP_RESPONDER,
-                        priority=1,
-                        proto='arp',
-                        dl_vlan=local_vid,
-                        nw_dst='%s' % ip,
-                        actions=actions)
+            br.install_arp_responder(local_vid, ip, mac)
         elif action == 'remove':
-            br.delete_flows(table=constants.ARP_RESPONDER,
-                            proto='arp',
-                            dl_vlan=local_vid,
-                            nw_dst='%s' % ip)
+            br.delete_arp_responder(local_vid, ip)
         else:
             LOG.warning(_LW('Action %s not supported'), action)
 
+    def _local_vlan_for_flat(self, lvid, physical_network):
+        phys_br = self.phys_brs[physical_network]
+        phys_port = self.phys_ofports[physical_network]
+        int_br = self.int_br
+        int_port = self.int_ofports[physical_network]
+        phys_br.provision_local_vlan(port=phys_port, lvid=lvid,
+                                     segmentation_id=None,
+                                     distributed=False)
+        int_br.provision_local_vlan(port=int_port, lvid=lvid,
+                                    segmentation_id=None)
+
+    def _local_vlan_for_vlan(self, lvid, physical_network, segmentation_id):
+        distributed = self.enable_distributed_routing
+        phys_br = self.phys_brs[physical_network]
+        phys_port = self.phys_ofports[physical_network]
+        int_br = self.int_br
+        int_port = self.int_ofports[physical_network]
+        phys_br.provision_local_vlan(port=phys_port, lvid=lvid,
+                                     segmentation_id=segmentation_id,
+                                     distributed=distributed)
+        int_br.provision_local_vlan(port=int_port, lvid=lvid,
+                                    segmentation_id=segmentation_id)
+
     def provision_local_vlan(self, net_uuid, network_type, physical_network,
                              segmentation_id, local_vlan=None):
         '''Provisions a local VLAN.
@@ -554,28 +564,20 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
         if network_type in constants.TUNNEL_NETWORK_TYPES:
             if self.enable_tunneling:
                 # outbound broadcast/multicast
-                ofports = _ofport_set_to_str(
-                    self.tun_br_ofports[network_type].values())
+                ofports = self.tun_br_ofports[network_type].values()
                 if ofports:
-                    self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
-                                         dl_vlan=lvid,
-                                         actions="strip_vlan,"
-                                         "set_tunnel:%s,output:%s" %
-                                         (segmentation_id, ofports))
+                    self.tun_br.install_flood_to_tun(lvid,
+                                                     segmentation_id,
+                                                     ofports)
                 # inbound from tunnels: set lvid in the right table
                 # and resubmit to Table LEARN_FROM_TUN for mac learning
                 if self.enable_distributed_routing:
                     self.dvr_agent.process_tunneled_network(
                         network_type, lvid, segmentation_id)
                 else:
-                    self.tun_br.add_flow(
-                        table=constants.TUN_TABLE[network_type],
-                        priority=1,
-                        tun_id=segmentation_id,
-                        actions="mod_vlan_vid:%s,"
-                        "resubmit(,%s)" %
-                        (lvid, constants.LEARN_FROM_TUN))
-
+                    self.tun_br.provision_local_vlan(
+                        network_type=network_type, lvid=lvid,
+                        segmentation_id=segmentation_id)
             else:
                 LOG.error(_LE("Cannot provision %(network_type)s network for "
                               "net-id=%(net_uuid)s - tunneling disabled"),
@@ -583,18 +585,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                            'net_uuid': net_uuid})
         elif network_type == p_const.TYPE_FLAT:
             if physical_network in self.phys_brs:
-                # outbound
-                br = self.phys_brs[physical_network]
-                br.add_flow(priority=4,
-                            in_port=self.phys_ofports[physical_network],
-                            dl_vlan=lvid,
-                            actions="strip_vlan,normal")
-                # inbound
-                self.int_br.add_flow(
-                    priority=3,
-                    in_port=self.int_ofports[physical_network],
-                    dl_vlan=0xffff,
-                    actions="mod_vlan_vid:%s,normal" % lvid)
+                self._local_vlan_for_flat(lvid, physical_network)
             else:
                 LOG.error(_LE("Cannot provision flat network for "
                               "net-id=%(net_uuid)s - no bridge for "
@@ -603,26 +594,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                            'physical_network': physical_network})
         elif network_type == p_const.TYPE_VLAN:
             if physical_network in self.phys_brs:
-                # outbound
-                br = self.phys_brs[physical_network]
-                if self.enable_distributed_routing:
-                    br.add_flow(table=constants.LOCAL_VLAN_TRANSLATION,
-                            priority=4,
-                            in_port=self.phys_ofports[physical_network],
-                            dl_vlan=lvid,
-                            actions="mod_vlan_vid:%s,normal" % segmentation_id)
-                else:
-                    br.add_flow(priority=4,
-                            in_port=self.phys_ofports[physical_network],
-                            dl_vlan=lvid,
-                            actions="mod_vlan_vid:%s,normal" % segmentation_id)
-
-                # inbound
-                self.int_br.add_flow(priority=3,
-                                     in_port=self.
-                                     int_ofports[physical_network],
-                                     dl_vlan=segmentation_id,
-                                     actions="mod_vlan_vid:%s,normal" % lvid)
+                self._local_vlan_for_vlan(lvid, physical_network,
+                                          segmentation_id)
             else:
                 LOG.error(_LE("Cannot provision VLAN network for "
                               "net-id=%(net_uuid)s - no bridge for "
@@ -654,10 +627,12 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
 
         if lvm.network_type in constants.TUNNEL_NETWORK_TYPES:
             if self.enable_tunneling:
-                self.tun_br.delete_flows(
-                    table=constants.TUN_TABLE[lvm.network_type],
-                    tun_id=lvm.segmentation_id)
-                self.tun_br.delete_flows(dl_vlan=lvm.vlan)
+                self.tun_br.reclaim_local_vlan(
+                    network_type=lvm.network_type,
+                    segmentation_id=lvm.segmentation_id)
+                self.tun_br.delete_flood_to_tun(lvm.vlan)
+                self.tun_br.delete_unicast_to_tun(lvm.vlan, None)
+                self.tun_br.delete_arp_responder(lvm.vlan, None)
                 if self.l2_pop:
                     # Try to remove tunnel ports if not used by other networks
                     for ofport in lvm.tun_ofports:
@@ -667,24 +642,26 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
             if lvm.physical_network in self.phys_brs:
                 # outbound
                 br = self.phys_brs[lvm.physical_network]
-                br.delete_flows(in_port=self.phys_ofports[lvm.
-                                                          physical_network],
-                                dl_vlan=lvm.vlan)
+                br.reclaim_local_vlan(
+                    port=self.phys_ofports[lvm.physical_network],
+                    lvid=lvm.vlan)
                 # inbound
                 br = self.int_br
-                br.delete_flows(in_port=self.int_ofports[lvm.physical_network],
-                                dl_vlan=0xffff)
+                br.reclaim_local_vlan(
+                    port=self.int_ofports[lvm.physical_network],
+                    segmentation_id=None)
         elif lvm.network_type == p_const.TYPE_VLAN:
             if lvm.physical_network in self.phys_brs:
                 # outbound
                 br = self.phys_brs[lvm.physical_network]
-                br.delete_flows(in_port=self.phys_ofports[lvm.
-                                                          physical_network],
-                                dl_vlan=lvm.vlan)
+                br.reclaim_local_vlan(
+                    port=self.phys_ofports[lvm.physical_network],
+                    lvid=lvm.vlan)
                 # inbound
                 br = self.int_br
-                br.delete_flows(in_port=self.int_ofports[lvm.physical_network],
-                                dl_vlan=lvm.segmentation_id)
+                br.reclaim_local_vlan(
+                    port=self.int_ofports[lvm.physical_network],
+                    segmentation_id=lvm.segmentation_id)
         elif lvm.network_type == p_const.TYPE_LOCAL:
             # no flows needed for local networks
             pass
@@ -746,6 +723,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                 self.int_br.set_db_attribute(
                     "Port", port.port_name, "tag", lvm.vlan)
                 if port.ofport != -1:
+                    # NOTE(yamamoto): Remove possible drop_port flow
+                    # installed by port_dead.
                     self.int_br.delete_flows(in_port=port.ofport)
 
             # update plugin about port status
@@ -766,43 +745,22 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
     @staticmethod
     def setup_arp_spoofing_protection(bridge, vif, port_details):
         # clear any previous flows related to this port in our ARP table
-        bridge.delete_flows(table=constants.LOCAL_SWITCHING,
-                            in_port=vif.ofport, proto='arp')
-        bridge.delete_flows(table=constants.ARP_SPOOF_TABLE,
-                            in_port=vif.ofport)
+        bridge.delete_arp_spoofing_protection(port=vif.ofport)
         if not port_details.get('port_security_enabled', True):
             LOG.info(_LI("Skipping ARP spoofing rules for port '%s' because "
                          "it has port security disabled"), vif.port_name)
             return
-        # all of the rules here are based on 'in_port' match criteria
-        # so their cleanup will be handled by 'update_stale_ofport_rules'
-
         # collect all of the addresses and cidrs that belong to the port
-        addresses = [f['ip_address'] for f in port_details['fixed_ips']]
+        addresses = {f['ip_address'] for f in port_details['fixed_ips']}
         if port_details.get('allowed_address_pairs'):
-            addresses += [p['ip_address']
-                          for p in port_details['allowed_address_pairs']]
+            addresses |= {p['ip_address']
+                          for p in port_details['allowed_address_pairs']}
 
-        # allow ARPs as long as they match addresses that actually
-        # belong to the port.
-        for ip in addresses:
-            if netaddr.IPNetwork(ip).version != 4:
-                continue
-            bridge.add_flow(table=constants.ARP_SPOOF_TABLE, priority=2,
-                            proto='arp', arp_spa=ip, in_port=vif.ofport,
-                            actions="NORMAL")
-
-        # drop any ARPs in this table that aren't explicitly allowed
-        bridge.add_flow(table=constants.ARP_SPOOF_TABLE, priority=1,
-                        proto='arp', actions="DROP")
-
-        # Now that the rules are ready, direct ARP traffic from the port into
-        # the anti-spoof table.
-        # This strategy fails gracefully because OVS versions that can't match
-        # on ARP headers will just process traffic normally.
-        bridge.add_flow(table=constants.LOCAL_SWITCHING,
-                        priority=10, proto='arp', in_port=vif.ofport,
-                        actions=("resubmit(,%s)" % constants.ARP_SPOOF_TABLE))
+        addresses = {ip for ip in addresses
+                     if netaddr.IPNetwork(ip).version == 4}
+
+        bridge.install_arp_spoofing_protection(port=vif.ofport,
+                                               ip_addresses=addresses)
 
     def port_unbound(self, vif_id, net_uuid=None):
         '''Unbind port.
@@ -841,8 +799,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
         if cur_tag != DEAD_VLAN_TAG:
             self.int_br.set_db_attribute("Port", port.port_name, "tag",
                                          DEAD_VLAN_TAG)
-            self.int_br.add_flow(priority=2, in_port=port.ofport,
-                                 actions="drop")
+            self.int_br.drop_port(in_port=port.ofport)
 
     def setup_integration_br(self):
         '''Setup the integration bridge.
@@ -855,14 +812,11 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
         # which does nothing if bridge already exists.
         self.int_br.create()
         self.int_br.set_secure_mode()
+        self.int_br.setup_controllers(cfg.CONF)
 
         self.int_br.delete_port(cfg.CONF.OVS.int_peer_patch_port)
-        self.int_br.remove_all_flows()
-        # switch all traffic using L2 learning
-        self.int_br.add_flow(priority=1, actions="normal")
-        # Add a canary flow to int_br to track OVS restarts
-        self.int_br.add_flow(table=constants.CANARY_TABLE, priority=0,
-                             actions="drop")
+
+        self.int_br.setup_default_table()
 
     def setup_ancillary_bridges(self, integ_br, tun_br):
         '''Setup ancillary bridges - for example br-ex.'''
@@ -899,9 +853,10 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
         :param tun_br_name: the name of the tunnel bridge.
         '''
         if not self.tun_br:
-            self.tun_br = ovs_lib.OVSBridge(tun_br_name)
+            self.tun_br = self.br_tun_cls(tun_br_name)
 
         self.tun_br.reset_bridge(secure_mode=True)
+        self.tun_br.setup_controllers(cfg.CONF)
         self.patch_tun_ofport = self.int_br.add_patch_port(
             cfg.CONF.OVS.int_peer_patch_port, cfg.CONF.OVS.tun_peer_patch_port)
         self.patch_int_ofport = self.tun_br.add_patch_port(
@@ -913,83 +868,15 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                           "version of OVS does not support tunnels or patch "
                           "ports. Agent terminated!"))
             exit(1)
-        self.tun_br.remove_all_flows()
+        self.tun_br.delete_flows()
 
     def setup_tunnel_br(self):
         '''Setup the tunnel bridge.
 
         Add all flows to the tunnel bridge.
         '''
-        # Table 0 (default) will sort incoming traffic depending on in_port
-        self.tun_br.add_flow(priority=1,
-                             in_port=self.patch_int_ofport,
-                             actions="resubmit(,%s)" %
-                             constants.PATCH_LV_TO_TUN)
-        self.tun_br.add_flow(priority=0, actions="drop")
-        if self.arp_responder_enabled:
-            # ARP broadcast-ed request go to the local ARP_RESPONDER table to
-            # be locally resolved
-            self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN,
-                                 priority=1,
-                                 proto='arp',
-                                 dl_dst="ff:ff:ff:ff:ff:ff",
-                                 actions=("resubmit(,%s)" %
-                                          constants.ARP_RESPONDER))
-        # PATCH_LV_TO_TUN table will handle packets coming from patch_int
-        # unicasts go to table UCAST_TO_TUN where remote addresses are learnt
-        self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN,
-                             priority=0,
-                             dl_dst="00:00:00:00:00:00/01:00:00:00:00:00",
-                             actions="resubmit(,%s)" % constants.UCAST_TO_TUN)
-        # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding
-        self.tun_br.add_flow(table=constants.PATCH_LV_TO_TUN,
-                             priority=0,
-                             dl_dst="01:00:00:00:00:00/01:00:00:00:00:00",
-                             actions="resubmit(,%s)" % constants.FLOOD_TO_TUN)
-        # Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id
-        # for each tunnel type, and resubmit to table LEARN_FROM_TUN where
-        # remote mac addresses will be learnt
-        for tunnel_type in constants.TUNNEL_NETWORK_TYPES:
-            self.tun_br.add_flow(table=constants.TUN_TABLE[tunnel_type],
-                                 priority=0,
-                                 actions="drop")
-        # LEARN_FROM_TUN table will have a single flow using a learn action to
-        # dynamically set-up flows in UCAST_TO_TUN corresponding to remote mac
-        # addresses (assumes that lvid has already been set by a previous flow)
-        learned_flow = ("table=%s,"
-                        "priority=1,"
-                        "hard_timeout=300,"
-                        "NXM_OF_VLAN_TCI[0..11],"
-                        "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],"
-                        "load:0->NXM_OF_VLAN_TCI[],"
-                        "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],"
-                        "output:NXM_OF_IN_PORT[]" %
-                        constants.UCAST_TO_TUN)
-        # Once remote mac addresses are learnt, output packet to patch_int
-        self.tun_br.add_flow(table=constants.LEARN_FROM_TUN,
-                             priority=1,
-                             actions="learn(%s),output:%s" %
-                             (learned_flow, self.patch_int_ofport))
-        # Egress unicast will be handled in table UCAST_TO_TUN, where remote
-        # mac addresses will be learned. For now, just add a default flow that
-        # will resubmit unknown unicasts to table FLOOD_TO_TUN to treat them
-        # as broadcasts/multicasts
-        self.tun_br.add_flow(table=constants.UCAST_TO_TUN,
-                             priority=0,
-                             actions="resubmit(,%s)" %
-                             constants.FLOOD_TO_TUN)
-        if self.arp_responder_enabled:
-            # If none of the ARP entries correspond to the requested IP, the
-            # broadcast-ed packet is resubmitted to the flooding table
-            self.tun_br.add_flow(table=constants.ARP_RESPONDER,
-                                 priority=0,
-                                 actions="resubmit(,%s)" %
-                                 constants.FLOOD_TO_TUN)
-        # FLOOD_TO_TUN will handle flooding in tunnels based on lvid,
-        # for now, add a default drop action
-        self.tun_br.add_flow(table=constants.FLOOD_TO_TUN,
-                             priority=0,
-                             actions="drop")
+        self.tun_br.setup_default_table(self.patch_int_ofport,
+                                        self.arp_responder_enabled)
 
     def get_peer_name(self, prefix, name):
         """Construct a peer name based on the prefix and name.
@@ -1041,9 +928,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                           {'physical_network': physical_network,
                            'bridge': bridge})
                 sys.exit(1)
-            br = ovs_lib.OVSBridge(bridge)
-            br.remove_all_flows()
-            br.add_flow(priority=1, actions="normal")
+            br = self.br_phys_cls(bridge)
+            br.setup_controllers(cfg.CONF)
+            br.setup_default_table()
             self.phys_brs[physical_network] = br
 
             # interconnect physical and integration bridges using veth/patchs
@@ -1076,9 +963,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
             self.phys_ofports[physical_network] = phys_ofport
 
             # block all untranslated traffic between bridges
-            self.int_br.add_flow(priority=2, in_port=int_ofport,
-                                 actions="drop")
-            br.add_flow(priority=2, in_port=phys_ofport, actions="drop")
+            self.int_br.drop_port(in_port=int_ofport)
+            br.drop_port(in_port=phys_ofport)
 
             if self.use_veth_interconnection:
                 # enable veth to pass traffic
@@ -1114,7 +1000,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
         # delete any stale rules based on removed ofports
         ofports_deleted = set(previous.values()) - set(current.values())
         for ofport in ofports_deleted:
-            self.int_br.delete_flows(in_port=ofport)
+            self.int_br.delete_arp_spoofing_protection(port=ofport)
 
         # store map for next iteration
         self.vifname_to_ofport_map = current
@@ -1237,20 +1123,16 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
         self.tun_br_ofports[tunnel_type][remote_ip] = ofport
         # Add flow in default table to resubmit to the right
         # tunnelling table (lvid will be set in the latter)
-        br.add_flow(priority=1,
-                    in_port=ofport,
-                    actions="resubmit(,%s)" %
-                    constants.TUN_TABLE[tunnel_type])
+        br.setup_tunnel_port(tunnel_type, ofport)
 
-        ofports = _ofport_set_to_str(self.tun_br_ofports[tunnel_type].values())
+        ofports = self.tun_br_ofports[tunnel_type].values()
         if ofports and not self.l2_pop:
             # Update flooding flows to include the new tunnel
             for vlan_mapping in list(self.local_vlan_map.values()):
                 if vlan_mapping.network_type == tunnel_type:
-                    br.mod_flow(table=constants.FLOOD_TO_TUN,
-                                dl_vlan=vlan_mapping.vlan,
-                                actions="strip_vlan,set_tunnel:%s,output:%s" %
-                                (vlan_mapping.segmentation_id, ofports))
+                    br.install_flood_to_tun(vlan_mapping.vlan,
+                                            vlan_mapping.segmentation_id,
+                                            ofports)
         return ofport
 
     def setup_tunnel_port(self, br, remote_ip, network_type):
@@ -1276,7 +1158,7 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                     port_name = '%s-%s' % (tunnel_type,
                                            self.get_ip_in_hex(remote_ip))
                     br.delete_port(port_name)
-                    br.delete_flows(in_port=ofport)
+                    br.cleanup_tunnel_port(ofport)
                     self.tun_br_ofports[tunnel_type].pop(remote_ip, None)
 
     def treat_devices_added_or_updated(self, devices, ovs_restarted):
@@ -1521,18 +1403,14 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
 
     def check_ovs_status(self):
         # Check for the canary flow
-        canary_flow = self.int_br.dump_flows_for_table(constants.CANARY_TABLE)
-        if canary_flow == '':
+        status = self.int_br.check_canary_table()
+        if status == constants.OVS_RESTARTED:
             LOG.warn(_LW("OVS is restarted. OVSNeutronAgent will reset "
                          "bridges and recover ports."))
-            return constants.OVS_RESTARTED
-        elif canary_flow is None:
+        elif status == constants.OVS_DEAD:
             LOG.warn(_LW("OVS is dead. OVSNeutronAgent will keep running "
                          "and checking OVS status periodically."))
-            return constants.OVS_DEAD
-        else:
-            # OVS is in normal status
-            return constants.OVS_NORMAL
+        return status
 
     def loop_count_and_wait(self, start_time, port_stats):
         # sleep till end of polling interval
@@ -1688,6 +1566,9 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
             self.loop_count_and_wait(start, port_stats)
 
     def daemon_loop(self):
+        # Start everything.
+        LOG.info(_LI("Agent initialized successfully, now running... "))
+        signal.signal(signal.SIGTERM, self._handle_sigterm)
         with polling.get_polling_manager(
             self.minimize_polling,
             self.ovsdb_monitor_respawn_interval) as pm:
@@ -1713,10 +1594,6 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                                "Agent and Server side."))
 
 
-def _ofport_set_to_str(ofport_set):
-    return ",".join(map(str, ofport_set))
-
-
 def create_agent_config_map(config):
     """Create a map of agent config parameters.
 
@@ -1757,36 +1634,25 @@ def create_agent_config_map(config):
     return kwargs
 
 
-def main():
-    cfg.CONF.register_opts(ip_lib.OPTS)
-    config.register_root_helper(cfg.CONF)
-    common_config.init(sys.argv[1:])
-    common_config.setup_logging()
-    q_utils.log_opt_values(LOG)
-
-    try:
-        agent_config = create_agent_config_map(cfg.CONF)
-    except ValueError as e:
-        LOG.error(_LE('%s Agent terminated!'), e)
-        sys.exit(1)
-
+def prepare_xen_compute():
     is_xen_compute_host = 'rootwrap-xen-dom0' in cfg.CONF.AGENT.root_helper
     if is_xen_compute_host:
         # Force ip_lib to always use the root helper to ensure that ip
         # commands target xen dom0 rather than domU.
+        cfg.CONF.register_opts(ip_lib.OPTS)
         cfg.CONF.set_default('ip_lib_force_root', True)
 
+
+def main(bridge_classes):
+    try:
+        agent_config = create_agent_config_map(cfg.CONF)
+    except ValueError:
+        LOG.exception(_LE("Agent failed to create agent config map"))
+        raise SystemExit(1)
+    prepare_xen_compute()
     try:
-        agent = OVSNeutronAgent(**agent_config)
+        agent = OVSNeutronAgent(bridge_classes, **agent_config)
     except RuntimeError as e:
         LOG.error(_LE("%s Agent terminated!"), e)
         sys.exit(1)
-    signal.signal(signal.SIGTERM, agent._handle_sigterm)
-
-    # Start everything.
-    LOG.info(_LI("Agent initialized successfully, now running... "))
     agent.daemon_loop()
-
-
-if __name__ == "__main__":
-    main()
index 887a6c5cb861aa33c2072bf6eb8a7f7b07912ead..cbde4173338ad0dac91686096711e08637d34eff 100644 (file)
@@ -44,6 +44,8 @@ ovs_opts = [
     cfg.BoolOpt('use_veth_interconnection', default=False,
                 help=_("Use veths instead of patch ports to interconnect the "
                        "integration bridge to physical bridges.")),
+    cfg.StrOpt('of_interface', default='ovs-ofctl', choices=['ovs-ofctl'],
+               help=_("OpenFlow interface to use.")),
 ]
 
 agent_opts = [
index 73719d1c4486676bc438c161ecb152368f98f5c8..bf9936d633a5e0601fc2ad842e3cfd0f91f5f386 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import eventlet
+import mock
+
+from oslo_config import cfg
+from oslo_utils import importutils
+
 from neutron.agent.linux import ip_lib
 from neutron.cmd.sanity import checks
 from neutron.plugins.openvswitch.agent import ovs_neutron_agent as ovsagt
+from neutron.plugins.openvswitch.common import constants
 from neutron.tests.common import machine_fixtures
 from neutron.tests.common import net_helpers
 from neutron.tests.functional.agent import test_ovs_lib
@@ -23,16 +30,68 @@ from neutron.tests.functional import base
 from neutron.tests import tools
 
 
-class ARPSpoofTestCase(test_ovs_lib.OVSBridgeTestBase,
-                       base.BaseSudoTestCase):
+cfg.CONF.import_group('OVS', 'neutron.plugins.openvswitch.common.config')
+
 
+class _OVSAgentTestBase(test_ovs_lib.OVSBridgeTestBase,
+                        base.BaseSudoTestCase):
+    def setUp(self):
+        super(_OVSAgentTestBase, self).setUp()
+        self.br = self.useFixture(net_helpers.OVSBridgeFixture()).bridge
+        self.of_interface_mod = importutils.import_module(self._MAIN_MODULE)
+        self.br_int_cls = None
+        self.br_tun_cls = None
+        self.br_phys_cls = None
+        self.br_int = None
+        self.init_done = False
+        self.init_done_ev = eventlet.event.Event()
+        self._main_thread = eventlet.spawn(self._kick_main)
+        self.addCleanup(self._kill_main)
+
+        # Wait for _kick_main -> of_interface main -> _agent_main
+        # NOTE(yamamoto): This complexity came from how "native" of_interface
+        # runs its openflow controller.  "native" of_interface's main routine
+        # blocks while running the embedded openflow controller.  In that case,
+        # the agent rpc_loop runs in another thread.  However, for FT we need
+        # to run setUp() and test_xxx() in the same thread.  So I made this
+        # run of_interface's main in a separate thread instead.
+        while not self.init_done:
+            self.init_done_ev.wait()
+
+    def _kick_main(self):
+        with mock.patch.object(ovsagt, 'main', self._agent_main):
+            self.of_interface_mod.main()
+
+    def _kill_main(self):
+        self._main_thread.kill()
+        self._main_thread.wait()
+
+    def _agent_main(self, bridge_classes):
+        self.br_int_cls = bridge_classes['br_int']
+        self.br_phys_cls = bridge_classes['br_phys']
+        self.br_tun_cls = bridge_classes['br_tun']
+        self.br_int = self.br_int_cls(self.br.br_name)
+        self.br_int.set_secure_mode()
+        self.br_int.setup_controllers(cfg.CONF)
+        self.br_int.setup_default_table()
+
+        # signal to setUp()
+        self.init_done = True
+        self.init_done_ev.send()
+
+
+class _OVSAgentOFCtlTestBase(_OVSAgentTestBase):
+    _MAIN_MODULE = 'neutron.plugins.openvswitch.agent.openflow.ovs_ofctl.main'
+
+
+class _ARPSpoofTestCase(object):
     def setUp(self):
         if not checks.arp_header_match_supported():
             self.skipTest("ARP header matching not supported")
         # NOTE(kevinbenton): it would be way cooler to use scapy for
         # these but scapy requires the python process to be running as
         # root to bind to the ports.
-        super(ARPSpoofTestCase, self).setUp()
+        super(_ARPSpoofTestCase, self).setUp()
         self.src_addr = '192.168.0.1'
         self.dst_addr = '192.168.0.2'
         self.src_namespace = self.useFixture(
@@ -120,4 +179,22 @@ class ARPSpoofTestCase(test_ovs_lib.OVSBridgeTestBase,
                    'allowed_address_pairs': [
                         dict(ip_address=ip) for ip in addrs]}
         ovsagt.OVSNeutronAgent.setup_arp_spoofing_protection(
-            self.br, VifPort(), details)
+            self.br_int, VifPort(), details)
+
+
+class ARPSpoofOFCtlTestCase(_ARPSpoofTestCase, _OVSAgentOFCtlTestBase):
+    pass
+
+
+class _CanaryTableTestCase(object):
+    def test_canary_table(self):
+        self.br_int.delete_flows()
+        self.assertEqual(constants.OVS_RESTARTED,
+                         self.br_int.check_canary_table())
+        self.br_int.setup_canary_table()
+        self.assertEqual(constants.OVS_NORMAL,
+                         self.br_int.check_canary_table())
+
+
+class CanaryTableOFCtlTestCase(_CanaryTableTestCase, _OVSAgentOFCtlTestBase):
+    pass
diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/__init__.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge_test_base.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/ovs_bridge_test_base.py
new file mode 100644 (file)
index 0000000..5d071fe
--- /dev/null
@@ -0,0 +1,160 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import mock
+
+from neutron.tests.unit.plugins.openvswitch.agent import ovs_test_base
+
+
+call = mock.call  # short hand
+
+
+class OVSBridgeTestBase(ovs_test_base.OVSOFCtlTestBase):
+    def setup_bridge_mock(self, name, cls):
+        self.br = cls(name)
+        mock_add_flow = mock.patch.object(self.br, 'add_flow').start()
+        mock_mod_flow = mock.patch.object(self.br, 'mod_flow').start()
+        mock_delete_flows = mock.patch.object(self.br, 'delete_flows').start()
+        self.mock = mock.Mock()
+        self.mock.attach_mock(mock_add_flow, 'add_flow')
+        self.mock.attach_mock(mock_mod_flow, 'mod_flow')
+        self.mock.attach_mock(mock_delete_flows, 'delete_flows')
+
+    def test_drop_port(self):
+        in_port = 2345
+        self.br.drop_port(in_port=in_port)
+        expected = [
+            call.add_flow(priority=2, table=0, actions='drop',
+                          in_port=in_port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_goto(self):
+        dest_table_id = 123
+        priority = 99
+        in_port = 666
+        self.br.install_goto(dest_table_id=dest_table_id,
+                             priority=priority, in_port=in_port)
+        expected = [
+            call.add_flow(priority=priority, table=0,
+                          actions='resubmit(,%s)' % dest_table_id,
+                          in_port=in_port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_drop(self):
+        priority = 99
+        in_port = 666
+        self.br.install_drop(priority=priority, in_port=in_port)
+        expected = [
+            call.add_flow(priority=priority, table=0,
+                          actions='drop',
+                          in_port=in_port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_normal(self):
+        priority = 99
+        in_port = 666
+        self.br.install_normal(priority=priority, in_port=in_port)
+        expected = [
+            call.add_flow(priority=priority, table=0,
+                          actions='normal',
+                          in_port=in_port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+
+class OVSDVRProcessTestMixin(object):
+    def test_install_dvr_process_ipv4(self):
+        vlan_tag = 999
+        gateway_ip = '192.0.2.1'
+        self.br.install_dvr_process_ipv4(vlan_tag=vlan_tag,
+                                         gateway_ip=gateway_ip)
+        expected = [
+            call.add_flow(table=self.dvr_process_table_id,
+                          proto='arp', nw_dst=gateway_ip, actions='drop',
+                          priority=3, dl_vlan=vlan_tag),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_dvr_process_ipv4(self):
+        vlan_tag = 999
+        gateway_ip = '192.0.2.1'
+        self.br.delete_dvr_process_ipv4(vlan_tag=vlan_tag,
+                                        gateway_ip=gateway_ip)
+        expected = [
+            call.delete_flows(table=self.dvr_process_table_id,
+                              dl_vlan=vlan_tag, proto='arp',
+                              nw_dst=gateway_ip),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_dvr_process_ipv6(self):
+        vlan_tag = 999
+        gateway_mac = '08:60:6e:7f:74:e7'
+        self.br.install_dvr_process_ipv6(vlan_tag=vlan_tag,
+                                         gateway_mac=gateway_mac)
+        expected = [
+            call.add_flow(table=self.dvr_process_table_id,
+                          proto='icmp6', dl_src=gateway_mac, actions='drop',
+                          priority=3, dl_vlan=vlan_tag),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_dvr_process_ipv6(self):
+        vlan_tag = 999
+        gateway_mac = '08:60:6e:7f:74:e7'
+        self.br.delete_dvr_process_ipv6(vlan_tag=vlan_tag,
+                                        gateway_mac=gateway_mac)
+        expected = [
+            call.delete_flows(table=self.dvr_process_table_id,
+                              dl_vlan=vlan_tag, dl_src=gateway_mac,
+                              proto='icmp6'),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_dvr_process(self):
+        vlan_tag = 999
+        vif_mac = '00:0e:0c:5e:95:d0'
+        dvr_mac_address = 'f2:0b:a4:5b:b2:ab'
+        self.br.install_dvr_process(vlan_tag=vlan_tag,
+                                    vif_mac=vif_mac,
+                                    dvr_mac_address=dvr_mac_address)
+        expected = [
+            call.add_flow(priority=2, table=self.dvr_process_table_id,
+                          dl_dst=vif_mac, dl_vlan=vlan_tag, actions='drop'),
+            call.add_flow(priority=1, table=self.dvr_process_table_id,
+                          dl_vlan=vlan_tag, dl_src=vif_mac,
+                          actions='mod_dl_src:%(mac)s,resubmit(,%(next)s)' % {
+                              'mac': dvr_mac_address,
+                              'next': self.dvr_process_next_table_id,
+                          }),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_dvr_process(self):
+        vlan_tag = 999
+        vif_mac = '00:0e:0c:5e:95:d0'
+        self.br.delete_dvr_process(vlan_tag=vlan_tag,
+                                   vif_mac=vif_mac)
+        expected = [
+            call.delete_flows(table=self.dvr_process_table_id,
+                              dl_dst=vif_mac, dl_vlan=vlan_tag),
+            call.delete_flows(table=self.dvr_process_table_id,
+                              dl_vlan=vlan_tag, dl_src=vif_mac),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_int.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_int.py
new file mode 100644 (file)
index 0000000..27cf9de
--- /dev/null
@@ -0,0 +1,213 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import mock
+
+from neutron.tests.unit.plugins.openvswitch.agent.openflow.ovs_ofctl \
+    import ovs_bridge_test_base
+
+
+call = mock.call  # short hand
+
+
+class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
+    def setUp(self):
+        super(OVSIntegrationBridgeTest, self).setUp()
+        self.setup_bridge_mock('br-int', self.br_int_cls)
+
+    def test_setup_default_table(self):
+        self.br.setup_default_table()
+        expected = [
+            call.delete_flows(),
+            call.add_flow(priority=0, table=0, actions='normal'),
+            call.add_flow(priority=0, table=23, actions='drop'),
+            call.add_flow(priority=0, table=24, actions='drop'),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_provision_local_vlan(self):
+        port = 999
+        lvid = 888
+        segmentation_id = 777
+        self.br.provision_local_vlan(port=port, lvid=lvid,
+                                     segmentation_id=segmentation_id)
+        expected = [
+            call.add_flow(priority=3, dl_vlan=segmentation_id,
+                          in_port=port,
+                          actions='mod_vlan_vid:%s,normal' % lvid),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_provision_local_vlan_novlan(self):
+        port = 999
+        lvid = 888
+        segmentation_id = None
+        self.br.provision_local_vlan(port=port, lvid=lvid,
+                                     segmentation_id=segmentation_id)
+        expected = [
+            call.add_flow(priority=3, dl_vlan=0xffff,
+                          in_port=port,
+                          actions='mod_vlan_vid:%s,normal' % lvid),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_reclaim_local_vlan(self):
+        port = 999
+        segmentation_id = 777
+        self.br.reclaim_local_vlan(port=port, segmentation_id=segmentation_id)
+        expected = [
+            call.delete_flows(dl_vlan=segmentation_id, in_port=port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_reclaim_local_vlan_novlan(self):
+        port = 999
+        segmentation_id = None
+        self.br.reclaim_local_vlan(port=port, segmentation_id=segmentation_id)
+        expected = [
+            call.delete_flows(dl_vlan=0xffff, in_port=port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_dvr_to_src_mac(self):
+        network_type = 'vxlan'
+        vlan_tag = 1111
+        gateway_mac = '08:60:6e:7f:74:e7'
+        dst_mac = '00:02:b3:13:fe:3d'
+        dst_port = 6666
+        self.br.install_dvr_to_src_mac(network_type=network_type,
+                                       vlan_tag=vlan_tag,
+                                       gateway_mac=gateway_mac,
+                                       dst_mac=dst_mac,
+                                       dst_port=dst_port)
+        expected = [
+            call.add_flow(priority=4, table=1, dl_dst=dst_mac,
+                          dl_vlan=vlan_tag,
+                          actions='strip_vlan,mod_dl_src:%(mac)s,'
+                          'output:%(port)s' % {
+                              'mac': gateway_mac,
+                              'port': dst_port,
+                          }),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_dvr_to_src_mac(self):
+        network_type = 'vxlan'
+        vlan_tag = 1111
+        dst_mac = '00:02:b3:13:fe:3d'
+        self.br.delete_dvr_to_src_mac(network_type=network_type,
+                                      vlan_tag=vlan_tag,
+                                      dst_mac=dst_mac)
+        expected = [
+            call.delete_flows(table=1, dl_dst=dst_mac, dl_vlan=vlan_tag),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_dvr_to_src_mac_vlan(self):
+        network_type = 'vlan'
+        vlan_tag = 1111
+        gateway_mac = '08:60:6e:7f:74:e7'
+        dst_mac = '00:02:b3:13:fe:3d'
+        dst_port = 6666
+        self.br.install_dvr_to_src_mac(network_type=network_type,
+                                       vlan_tag=vlan_tag,
+                                       gateway_mac=gateway_mac,
+                                       dst_mac=dst_mac,
+                                       dst_port=dst_port)
+        expected = [
+            call.add_flow(priority=4, table=2, dl_dst=dst_mac,
+                          dl_vlan=vlan_tag,
+                          actions='strip_vlan,mod_dl_src:%(mac)s,'
+                          'output:%(port)s' % {
+                              'mac': gateway_mac,
+                              'port': dst_port,
+                          }),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_dvr_to_src_mac_vlan(self):
+        network_type = 'vlan'
+        vlan_tag = 1111
+        dst_mac = '00:02:b3:13:fe:3d'
+        self.br.delete_dvr_to_src_mac(network_type=network_type,
+                                      vlan_tag=vlan_tag,
+                                      dst_mac=dst_mac)
+        expected = [
+            call.delete_flows(table=2, dl_dst=dst_mac, dl_vlan=vlan_tag),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_add_dvr_mac_vlan(self):
+        mac = '00:02:b3:13:fe:3d'
+        port = 8888
+        self.br.add_dvr_mac_vlan(mac=mac, port=port)
+        expected = [
+            call.add_flow(priority=4, table=0, actions='resubmit(,2)',
+                          dl_src=mac, in_port=port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_remove_dvr_mac_vlan(self):
+        mac = '00:02:b3:13:fe:3d'
+        self.br.remove_dvr_mac_vlan(mac=mac)
+        expected = [
+            call.delete_flows(eth_src=mac, table_id=0),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_add_dvr_mac_tun(self):
+        mac = '00:02:b3:13:fe:3d'
+        port = 8888
+        self.br.add_dvr_mac_tun(mac=mac, port=port)
+        expected = [
+            call.add_flow(priority=2, table=0, actions='resubmit(,1)',
+                          dl_src=mac, in_port=port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_remove_dvr_mac_tun(self):
+        mac = '00:02:b3:13:fe:3d'
+        port = 8888
+        self.br.remove_dvr_mac_tun(mac=mac, port=port)
+        expected = [
+            call.delete_flows(eth_src=mac, table_id=0, in_port=port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_arp_spoofing_protection(self):
+        port = 8888
+        ip_addresses = ['192.0.2.1', '192.0.2.2/32']
+        self.br.install_arp_spoofing_protection(port, ip_addresses)
+        expected = [
+            call.add_flow(proto='arp', actions='normal',
+                          arp_spa='192.0.2.1',
+                          priority=2, table=24, in_port=8888),
+            call.add_flow(proto='arp', actions='normal',
+                          arp_spa='192.0.2.2/32',
+                          priority=2, table=24, in_port=8888),
+            call.add_flow(priority=10, table=0, in_port=8888,
+                          actions='resubmit(,24)', proto='arp')
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_arp_spoofing_protection(self):
+        port = 8888
+        self.br.delete_arp_spoofing_protection(port)
+        expected = [
+            call.delete_flows(table_id=0, in_port=8888, proto='arp'),
+            call.delete_flows(table_id=24, in_port=8888),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_phys.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_phys.py
new file mode 100644 (file)
index 0000000..ae8753a
--- /dev/null
@@ -0,0 +1,97 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import mock
+
+import neutron.plugins.openvswitch.common.constants as ovs_const
+from neutron.tests.unit.plugins.openvswitch.agent.openflow.ovs_ofctl \
+    import ovs_bridge_test_base
+
+
+call = mock.call  # short hand
+
+
+class OVSPhysicalBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
+                            ovs_bridge_test_base.OVSDVRProcessTestMixin):
+    dvr_process_table_id = ovs_const.DVR_PROCESS_VLAN
+    dvr_process_next_table_id = ovs_const.LOCAL_VLAN_TRANSLATION
+
+    def setUp(self):
+        super(OVSPhysicalBridgeTest, self).setUp()
+        self.setup_bridge_mock('br-phys', self.br_phys_cls)
+
+    def test_setup_default_table(self):
+        self.br.setup_default_table()
+        expected = [
+            call.delete_flows(),
+            call.add_flow(priority=0, table=0, actions='normal'),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_provision_local_vlan(self):
+        port = 999
+        lvid = 888
+        segmentation_id = 777
+        distributed = False
+        self.br.provision_local_vlan(port=port, lvid=lvid,
+                                     segmentation_id=segmentation_id,
+                                     distributed=distributed)
+        expected = [
+            call.add_flow(priority=4, table=0, dl_vlan=lvid, in_port=port,
+                          actions='mod_vlan_vid:%s,normal' % segmentation_id),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_provision_local_vlan_novlan(self):
+        port = 999
+        lvid = 888
+        segmentation_id = None
+        distributed = False
+        self.br.provision_local_vlan(port=port, lvid=lvid,
+                                     segmentation_id=segmentation_id,
+                                     distributed=distributed)
+        expected = [
+            call.add_flow(priority=4, table=0, dl_vlan=lvid, in_port=port,
+                          actions='strip_vlan,normal')
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_reclaim_local_vlan(self):
+        port = 999
+        lvid = 888
+        self.br.reclaim_local_vlan(port=port, lvid=lvid)
+        expected = [
+            call.delete_flows(dl_vlan=lvid, in_port=port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_add_dvr_mac_vlan(self):
+        mac = '00:02:b3:13:fe:3d'
+        port = 8888
+        self.br.add_dvr_mac_vlan(mac=mac, port=port)
+        expected = [
+            call.add_flow(priority=2, table=3, dl_src=mac,
+                          actions='output:%s' % port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_remove_dvr_mac_vlan(self):
+        mac = '00:02:b3:13:fe:3d'
+        self.br.remove_dvr_mac_vlan(mac=mac)
+        expected = [
+            call.delete_flows(eth_src=mac, table_id=3),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
diff --git a/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py b/neutron/tests/unit/plugins/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py
new file mode 100644 (file)
index 0000000..27a046d
--- /dev/null
@@ -0,0 +1,259 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import mock
+import netaddr
+
+import neutron.plugins.openvswitch.common.constants as ovs_const
+from neutron.tests.unit.plugins.openvswitch.agent.openflow.ovs_ofctl \
+    import ovs_bridge_test_base
+
+
+call = mock.call  # short hand
+
+
+class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
+                          ovs_bridge_test_base.OVSDVRProcessTestMixin):
+    dvr_process_table_id = ovs_const.DVR_PROCESS
+    dvr_process_next_table_id = ovs_const.PATCH_LV_TO_TUN
+
+    def setUp(self):
+        super(OVSTunnelBridgeTest, self).setUp()
+        self.setup_bridge_mock('br-tun', self.br_tun_cls)
+
+    def test_setup_default_table(self):
+        patch_int_ofport = 5555
+        arp_responder_enabled = False
+        self.br.setup_default_table(patch_int_ofport=patch_int_ofport,
+            arp_responder_enabled=arp_responder_enabled)
+        expected = [
+            call.add_flow(priority=1, in_port=patch_int_ofport,
+                          actions='resubmit(,2)'),
+            call.add_flow(priority=0, actions='drop'),
+            call.add_flow(priority=0, table=2,
+                          dl_dst='00:00:00:00:00:00/01:00:00:00:00:00',
+                          actions='resubmit(,20)'),
+            call.add_flow(priority=0, table=2,
+                          dl_dst='01:00:00:00:00:00/01:00:00:00:00:00',
+                          actions='resubmit(,22)'),
+            call.add_flow(priority=0, table=3, actions='drop'),
+            call.add_flow(priority=0, table=4, actions='drop'),
+            call.add_flow(priority=1, table=10,
+                          actions='learn(table=20,priority=1,'
+                          'hard_timeout=300,NXM_OF_VLAN_TCI[0..11],'
+                          'NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],'
+                          'load:0->NXM_OF_VLAN_TCI[],'
+                          'load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],'
+                          'output:NXM_OF_IN_PORT[]),'
+                          'output:%s' % patch_int_ofport),
+            call.add_flow(priority=0, table=20, actions='resubmit(,22)'),
+            call.add_flow(priority=0, table=22, actions='drop'),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_setup_default_table_arp_responder_enabled(self):
+        patch_int_ofport = 5555
+        arp_responder_enabled = True
+        self.br.setup_default_table(patch_int_ofport=patch_int_ofport,
+            arp_responder_enabled=arp_responder_enabled)
+        expected = [
+            call.add_flow(priority=1, in_port=patch_int_ofport,
+                          actions='resubmit(,2)'),
+            call.add_flow(priority=0, actions='drop'),
+            call.add_flow(priority=1, table=2, dl_dst='ff:ff:ff:ff:ff:ff',
+                          actions='resubmit(,21)', proto='arp'),
+            call.add_flow(priority=0, table=2,
+                          dl_dst='00:00:00:00:00:00/01:00:00:00:00:00',
+                          actions='resubmit(,20)'),
+            call.add_flow(priority=0, table=2,
+                          dl_dst='01:00:00:00:00:00/01:00:00:00:00:00',
+                          actions='resubmit(,22)'),
+            call.add_flow(priority=0, table=3, actions='drop'),
+            call.add_flow(priority=0, table=4, actions='drop'),
+            call.add_flow(priority=1, table=10,
+                          actions='learn(table=20,priority=1,'
+                          'hard_timeout=300,NXM_OF_VLAN_TCI[0..11],'
+                          'NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],'
+                          'load:0->NXM_OF_VLAN_TCI[],'
+                          'load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],'
+                          'output:NXM_OF_IN_PORT[]),'
+                          'output:%s' % patch_int_ofport),
+            call.add_flow(priority=0, table=20, actions='resubmit(,22)'),
+            call.add_flow(priority=0, table=21, actions='resubmit(,22)'),
+            call.add_flow(priority=0, table=22, actions='drop'),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_provision_local_vlan(self):
+        network_type = 'vxlan'
+        lvid = 888
+        segmentation_id = 777
+        distributed = False
+        self.br.provision_local_vlan(network_type=network_type, lvid=lvid,
+                                     segmentation_id=segmentation_id,
+                                     distributed=distributed)
+        expected = [
+            call.add_flow(priority=1, tun_id=segmentation_id,
+                          actions='mod_vlan_vid:%s,resubmit(,10)' % lvid,
+                          table=4),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_reclaim_local_vlan(self):
+        network_type = 'vxlan'
+        segmentation_id = 777
+        self.br.reclaim_local_vlan(network_type=network_type,
+                                   segmentation_id=segmentation_id)
+        expected = [
+            call.delete_flows(tun_id=segmentation_id, table=4),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_flood_to_tun(self):
+        vlan = 3333
+        tun_id = 2222
+        ports = [11, 44, 22, 33]
+        self.br.install_flood_to_tun(vlan=vlan,
+                                     tun_id=tun_id,
+                                     ports=ports)
+        expected = [
+            call.mod_flow(table=22, dl_vlan=vlan,
+                          actions='strip_vlan,set_tunnel:%(tun)s,'
+                          'output:%(ports)s' % {
+                              'tun': tun_id,
+                              'ports': ','.join(map(str, ports)),
+                          }),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_flood_to_tun(self):
+        vlan = 3333
+        self.br.delete_flood_to_tun(vlan=vlan)
+        expected = [
+            call.delete_flows(table=22, dl_vlan=vlan),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_unicast_to_tun(self):
+        vlan = 3333
+        port = 55
+        mac = '08:60:6e:7f:74:e7'
+        tun_id = 2222
+        self.br.install_unicast_to_tun(vlan=vlan,
+                                       tun_id=tun_id,
+                                       port=port,
+                                       mac=mac)
+        expected = [
+            call.add_flow(priority=2, table=20, dl_dst=mac, dl_vlan=vlan,
+                          actions='strip_vlan,set_tunnel:%(tun)s,'
+                          'output:%(port)s' % {
+                              'tun': tun_id,
+                              'port': port,
+                          }),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_unicast_to_tun(self):
+        vlan = 3333
+        mac = '08:60:6e:7f:74:e7'
+        self.br.delete_unicast_to_tun(vlan=vlan, mac=mac)
+        expected = [
+            call.delete_flows(table=20, dl_dst=mac, dl_vlan=vlan),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_unicast_to_tun_without_mac(self):
+        vlan = 3333
+        mac = None
+        self.br.delete_unicast_to_tun(vlan=vlan, mac=mac)
+        expected = [
+            call.delete_flows(table=20, dl_vlan=vlan),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_install_arp_responder(self):
+        vlan = 3333
+        ip = '192.0.2.1'
+        mac = '08:60:6e:7f:74:e7'
+        self.br.install_arp_responder(vlan=vlan, ip=ip, mac=mac)
+        expected = [
+            call.add_flow(proto='arp', nw_dst=ip,
+                          actions='move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[],'
+                          'mod_dl_src:%(mac)s,load:0x2->NXM_OF_ARP_OP[],'
+                          'move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[],'
+                          'move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[],'
+                          'load:%(mac)#x->NXM_NX_ARP_SHA[],'
+                          'load:%(ip)#x->NXM_OF_ARP_SPA[],in_port' % {
+                              'mac': netaddr.EUI(mac,
+                                                 dialect=netaddr.mac_unix),
+                              'ip': netaddr.IPAddress(ip),
+                          },
+                          priority=1, table=21, dl_vlan=vlan),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_arp_responder(self):
+        vlan = 3333
+        ip = '192.0.2.1'
+        self.br.delete_arp_responder(vlan=vlan, ip=ip)
+        expected = [
+            call.delete_flows(table=21, dl_vlan=vlan, proto='arp', nw_dst=ip),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_arp_responder_without_ip(self):
+        vlan = 3333
+        ip = None
+        self.br.delete_arp_responder(vlan=vlan, ip=ip)
+        expected = [
+            call.delete_flows(table=21, dl_vlan=vlan, proto='arp'),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_setup_tunnel_port(self):
+        network_type = 'vxlan'
+        port = 11111
+        self.br.setup_tunnel_port(network_type=network_type, port=port)
+        expected = [
+            call.add_flow(priority=1, in_port=port, actions='resubmit(,4)'),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_cleanup_tunnel_port(self):
+        port = 11111
+        self.br.cleanup_tunnel_port(port=port)
+        expected = [
+            call.delete_flows(in_port=port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_add_dvr_mac_tun(self):
+        mac = '00:02:b3:13:fe:3d'
+        port = 8888
+        self.br.add_dvr_mac_tun(mac=mac, port=port)
+        expected = [
+            call.add_flow(priority=1, table=9, dl_src=mac,
+                          actions='output:%s' % port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_remove_dvr_mac_tun(self):
+        mac = '00:02:b3:13:fe:3d'
+        self.br.remove_dvr_mac_tun(mac=mac)
+        expected = [
+            call.delete_flows(eth_src=mac, table_id=9),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
diff --git a/neutron/tests/unit/plugins/openvswitch/agent/ovs_test_base.py b/neutron/tests/unit/plugins/openvswitch/agent/ovs_test_base.py
new file mode 100644 (file)
index 0000000..0932a1e
--- /dev/null
@@ -0,0 +1,54 @@
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014 Fumihiko Kakuma <kakuma at valinux co jp>
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from oslo_utils import importutils
+
+from neutron.tests import base
+
+
+_AGENT_PACKAGE = 'neutron.plugins.openvswitch.agent'
+_AGENT_NAME = _AGENT_PACKAGE + '.ovs_neutron_agent'
+_DVR_AGENT_NAME = 'neutron.plugins.openvswitch.agent.ovs_dvr_neutron_agent'
+
+
+class OVSAgentConfigTestBase(base.BaseTestCase):
+    def setUp(self):
+        super(OVSAgentConfigTestBase, self).setUp()
+        self.mod_agent = importutils.import_module(_AGENT_NAME)
+        self.mod_dvr_agent = importutils.import_module(_DVR_AGENT_NAME)
+
+
+class OVSAgentTestBase(OVSAgentConfigTestBase):
+    def setUp(self):
+        super(OVSAgentTestBase, self).setUp()
+        self.br_int_cls = importutils.import_class(self._BR_INT_CLASS)
+        self.br_phys_cls = importutils.import_class(self._BR_PHYS_CLASS)
+        self.br_tun_cls = importutils.import_class(self._BR_TUN_CLASS)
+
+    def _bridge_classes(self):
+        return {
+            'br_int': self.br_int_cls,
+            'br_phys': self.br_phys_cls,
+            'br_tun': self.br_tun_cls,
+        }
+
+
+class OVSOFCtlTestBase(OVSAgentTestBase):
+    _DRIVER_PACKAGE = _AGENT_PACKAGE + '.openflow.ovs_ofctl'
+    _BR_INT_CLASS = _DRIVER_PACKAGE + '.br_int.OVSIntegrationBridge'
+    _BR_TUN_CLASS = _DRIVER_PACKAGE + '.br_tun.OVSTunnelBridge'
+    _BR_PHYS_CLASS = _DRIVER_PACKAGE + '.br_phys.OVSPhysicalBridge'
index c21d7da93d9c9e71d56dac9cb1d04b78acf8b076..fb068790c3578b614ea5ff037b8f1e2725031375 100644 (file)
@@ -17,7 +17,6 @@ import sys
 import time
 
 import mock
-import netaddr
 from oslo_config import cfg
 from oslo_log import log
 import oslo_messaging
@@ -30,9 +29,8 @@ from neutron.agent.linux import ip_lib
 from neutron.common import constants as n_const
 from neutron.plugins.common import constants as p_const
 from neutron.plugins.ml2.drivers.l2pop import rpc as l2pop_rpc
-from neutron.plugins.openvswitch.agent import ovs_neutron_agent
 from neutron.plugins.openvswitch.common import constants
-from neutron.tests import base
+from neutron.tests.unit.plugins.openvswitch.agent import ovs_test_base
 
 
 NOTIFIER = 'neutron.plugins.ml2.rpc.AgentNotifierApi'
@@ -48,10 +46,10 @@ class FakeVif(object):
     port_name = 'name'
 
 
-class CreateAgentConfigMap(base.BaseTestCase):
+class CreateAgentConfigMap(ovs_test_base.OVSAgentConfigTestBase):
 
     def test_create_agent_config_map_succeeds(self):
-        self.assertTrue(ovs_neutron_agent.create_agent_config_map(cfg.CONF))
+        self.assertTrue(self.mod_agent.create_agent_config_map(cfg.CONF))
 
     def test_create_agent_config_map_fails_for_invalid_tunnel_config(self):
         # An ip address is required for tunneling but there is no default,
@@ -59,29 +57,29 @@ class CreateAgentConfigMap(base.BaseTestCase):
         cfg.CONF.set_override('tunnel_types', [p_const.TYPE_GRE],
                               group='AGENT')
         with testtools.ExpectedException(ValueError):
-            ovs_neutron_agent.create_agent_config_map(cfg.CONF)
+            self.mod_agent.create_agent_config_map(cfg.CONF)
         cfg.CONF.set_override('tunnel_types', [p_const.TYPE_VXLAN],
                               group='AGENT')
         with testtools.ExpectedException(ValueError):
-            ovs_neutron_agent.create_agent_config_map(cfg.CONF)
+            self.mod_agent.create_agent_config_map(cfg.CONF)
 
     def test_create_agent_config_map_fails_no_local_ip(self):
         # An ip address is required for tunneling but there is no default
         cfg.CONF.set_override('tunnel_types', [p_const.TYPE_VXLAN],
                               group='AGENT')
         with testtools.ExpectedException(ValueError):
-            ovs_neutron_agent.create_agent_config_map(cfg.CONF)
+            self.mod_agent.create_agent_config_map(cfg.CONF)
 
     def test_create_agent_config_map_fails_for_invalid_tunnel_type(self):
         cfg.CONF.set_override('tunnel_types', ['foobar'], group='AGENT')
         with testtools.ExpectedException(ValueError):
-            ovs_neutron_agent.create_agent_config_map(cfg.CONF)
+            self.mod_agent.create_agent_config_map(cfg.CONF)
 
     def test_create_agent_config_map_multiple_tunnel_types(self):
         cfg.CONF.set_override('local_ip', '10.10.10.10', group='OVS')
         cfg.CONF.set_override('tunnel_types', [p_const.TYPE_GRE,
                               p_const.TYPE_VXLAN], group='AGENT')
-        cfgmap = ovs_neutron_agent.create_agent_config_map(cfg.CONF)
+        cfgmap = self.mod_agent.create_agent_config_map(cfg.CONF)
         self.assertEqual(cfgmap['tunnel_types'],
                          [p_const.TYPE_GRE, p_const.TYPE_VXLAN])
 
@@ -90,11 +88,11 @@ class CreateAgentConfigMap(base.BaseTestCase):
         # Verify setting only enable_tunneling will default tunnel_type to GRE
         cfg.CONF.set_override('enable_distributed_routing', True,
                               group='AGENT')
-        cfgmap = ovs_neutron_agent.create_agent_config_map(cfg.CONF)
+        cfgmap = self.mod_agent.create_agent_config_map(cfg.CONF)
         self.assertEqual(cfgmap['enable_distributed_routing'], True)
 
 
-class TestOvsNeutronAgent(base.BaseTestCase):
+class TestOvsNeutronAgent(object):
 
     def setUp(self):
         super(TestOvsNeutronAgent, self).setUp()
@@ -107,7 +105,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                              group='SECURITYGROUP')
         cfg.CONF.set_default('quitting_rpc_timeout', 10, 'AGENT')
         cfg.CONF.set_default('prevent_arp_spoofing', False, 'AGENT')
-        kwargs = ovs_neutron_agent.create_agent_config_map(cfg.CONF)
+        kwargs = self.mod_agent.create_agent_config_map(cfg.CONF)
 
         class MockFixedIntervalLoopingCall(object):
             def __init__(self, f):
@@ -117,18 +115,11 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                 self.f()
 
         with contextlib.nested(
-            mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.'
-                       'OVSNeutronAgent.setup_integration_br'),
-            mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.'
-                       'OVSNeutronAgent.setup_ancillary_bridges',
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
+                       'setup_integration_br'),
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
+                       'setup_ancillary_bridges',
                        return_value=[]),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'create'),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'set_secure_mode'),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'get_local_port_mac',
-                       return_value='00:00:00:00:00:01'),
             mock.patch('neutron.agent.linux.utils.get_interface_mac',
                        return_value='00:00:00:00:00:01'),
             mock.patch('neutron.agent.common.ovs_lib.BaseOVS.get_bridges'),
@@ -137,11 +128,12 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                        new=MockFixedIntervalLoopingCall),
             mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
                        'get_vif_ports', return_value=[])):
-            self.agent = ovs_neutron_agent.OVSNeutronAgent(**kwargs)
+            self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(),
+                                                        **kwargs)
             # set back to true because initial report state will succeed due
             # to mocked out RPC calls
             self.agent.use_call = True
-            self.agent.tun_br = mock.Mock()
+            self.agent.tun_br = self.br_tun_cls(br_name='br-tun')
         self.agent.sg_agent = mock.Mock()
 
     def _mock_port_bound(self, ofport=None, new_local_vlan=None,
@@ -153,22 +145,18 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                       'ip_address': '1.1.1.1'}]
         if old_local_vlan is not None:
             self.agent.local_vlan_map[net_uuid] = (
-                ovs_neutron_agent.LocalVLANMapping(
+                self.mod_agent.LocalVLANMapping(
                     old_local_vlan, None, None, None))
-        with contextlib.nested(
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'set_db_attribute', return_value=True),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'db_get_val', return_value={}),
-            mock.patch.object(self.agent.int_br, 'delete_flows')
-        ) as (set_ovs_db_func, get_ovs_db_func, delete_flows_func):
+        with mock.patch.object(self.agent, 'int_br', autospec=True) as int_br:
+            int_br.db_get_val.return_value = {}
+            int_br.set_db_attribute.return_value = True
             self.agent.port_bound(port, net_uuid, 'local', None, None,
                                   fixed_ips, "compute:None", False)
         vlan_mapping = {'net_uuid': net_uuid,
                         'network_type': 'local',
                         'physical_network': None,
                         'segmentation_id': None}
-        set_ovs_db_func.assert_called_once_with(
+        int_br.set_db_attribute.assert_called_once_with(
             "Port", mock.ANY, "other_config", vlan_mapping)
 
     def test_check_agent_configurations_for_dvr_raises(self):
@@ -202,29 +190,24 @@ class TestOvsNeutronAgent(base.BaseTestCase):
     def _test_port_dead(self, cur_tag=None):
         port = mock.Mock()
         port.ofport = 1
-        with contextlib.nested(
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'set_db_attribute', return_value=True),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'db_get_val', return_value=cur_tag),
-            mock.patch.object(self.agent.int_br, 'add_flow')
-        ) as (set_ovs_db_func, get_ovs_db_func, add_flow_func):
+        with mock.patch.object(self.agent, 'int_br') as int_br:
+            int_br.db_get_val.return_value = cur_tag
             self.agent.port_dead(port)
-        get_ovs_db_func.assert_called_once_with("Port", mock.ANY, "tag")
-        if cur_tag == ovs_neutron_agent.DEAD_VLAN_TAG:
-            self.assertFalse(set_ovs_db_func.called)
-            self.assertFalse(add_flow_func.called)
+        if cur_tag == self.mod_agent.DEAD_VLAN_TAG:
+            self.assertFalse(int_br.set_db_attribute.called)
+            self.assertFalse(int_br.drop_port.called)
         else:
-            set_ovs_db_func.assert_called_once_with(
-                "Port", mock.ANY, "tag", ovs_neutron_agent.DEAD_VLAN_TAG)
-            add_flow_func.assert_called_once_with(
-                priority=2, in_port=port.ofport, actions="drop")
+            int_br.assert_has_calls([
+                mock.call.set_db_attribute("Port", mock.ANY, "tag",
+                                           self.mod_agent.DEAD_VLAN_TAG),
+                mock.call.drop_port(in_port=port.ofport),
+            ])
 
     def test_port_dead(self):
         self._test_port_dead()
 
     def test_port_dead_with_port_already_dead(self):
-        self._test_port_dead(ovs_neutron_agent.DEAD_VLAN_TAG)
+        self._test_port_dead(self.mod_agent.DEAD_VLAN_TAG)
 
     def mock_scan_ports(self, vif_port_set=None, registered_ports=None,
                         updated_ports=None, port_tags_dict=None):
@@ -289,10 +272,10 @@ class TestOvsNeutronAgent(base.BaseTestCase):
         self.assertEqual(expected, actual)
 
     def test_update_ports_returns_changed_vlan(self):
-        br = ovs_lib.OVSBridge('br-int')
+        br = self.br_int_cls('br-int')
         mac = "ca:fe:de:ad:be:ef"
         port = ovs_lib.VifPort(1, 1, 1, mac, br)
-        lvm = ovs_neutron_agent.LocalVLANMapping(
+        lvm = self.mod_agent.LocalVLANMapping(
             1, '1', None, 1, {port.vif_id: port})
         local_vlan_map = {'1': lvm}
         vif_port_set = set([1, 3])
@@ -302,7 +285,10 @@ class TestOvsNeutronAgent(base.BaseTestCase):
             added=set([3]), current=vif_port_set,
             removed=set([2]), updated=set([1])
         )
-        with mock.patch.dict(self.agent.local_vlan_map, local_vlan_map):
+        with contextlib.nested(
+            mock.patch.dict(self.agent.local_vlan_map, local_vlan_map),
+            mock.patch.object(self.agent, 'tun_br', autospec=True),
+        ):
             actual = self.mock_scan_ports(
                 vif_port_set, registered_ports, port_tags_dict=port_tags_dict)
         self.assertEqual(expected, actual)
@@ -315,7 +301,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
             mock.patch.object(self.agent.int_br, 'get_vif_port_by_id',
                               return_value=mock.Mock())):
             self.assertRaises(
-                ovs_neutron_agent.DeviceListRetrievalError,
+                self.mod_agent.DeviceListRetrievalError,
                 self.agent.treat_devices_added_or_updated, [{}], False)
 
     def _mock_treat_devices_added_updated(self, details, port, func_name):
@@ -543,46 +529,37 @@ class TestOvsNeutronAgent(base.BaseTestCase):
             mock.patch.object(ip_lib, "device_exists"),
             mock.patch.object(sys, "exit"),
             mock.patch.object(utils, "execute"),
-            mock.patch.object(ovs_lib.OVSBridge, "remove_all_flows"),
-            mock.patch.object(ovs_lib.OVSBridge, "add_flow"),
-            mock.patch.object(ovs_lib.OVSBridge, "add_patch_port"),
-            mock.patch.object(ovs_lib.OVSBridge, "delete_port"),
-            mock.patch.object(ovs_lib.OVSBridge, "set_db_attribute"),
-            mock.patch.object(self.agent.int_br, "add_flow"),
-            mock.patch.object(self.agent.int_br, "add_patch_port"),
-            mock.patch.object(self.agent.int_br, "delete_port"),
-            mock.patch.object(self.agent.int_br, "set_db_attribute"),
-        ) as (devex_fn, sysexit_fn, utilsexec_fn, remflows_fn, ovs_add_flow_fn,
-              ovs_addpatch_port_fn, ovs_delport_fn, ovs_set_attr_fn,
-              br_add_flow_fn, br_addpatch_port_fn, br_delport_fn,
-              br_set_attr_fn):
+            mock.patch.object(self.agent, 'br_phys_cls'),
+            mock.patch.object(self.agent, 'int_br'),
+        ) as (devex_fn, sysexit_fn, utilsexec_fn,
+              phys_br_cls, int_br):
             devex_fn.return_value = True
             parent = mock.MagicMock()
-            parent.attach_mock(ovs_addpatch_port_fn, 'phy_add_patch_port')
-            parent.attach_mock(ovs_add_flow_fn, 'phy_add_flow')
-            parent.attach_mock(ovs_set_attr_fn, 'phy_set_attr')
-            parent.attach_mock(br_addpatch_port_fn, 'int_add_patch_port')
-            parent.attach_mock(br_add_flow_fn, 'int_add_flow')
-            parent.attach_mock(br_set_attr_fn, 'int_set_attr')
-
-            ovs_addpatch_port_fn.return_value = "phy_ofport"
-            br_addpatch_port_fn.return_value = "int_ofport"
+            phys_br = phys_br_cls()
+            parent.attach_mock(phys_br_cls, 'phys_br_cls')
+            parent.attach_mock(phys_br, 'phys_br')
+            parent.attach_mock(int_br, 'int_br')
+            phys_br.add_patch_port.return_value = "phy_ofport"
+            int_br.add_patch_port.return_value = "int_ofport"
             self.agent.setup_physical_bridges({"physnet1": "br-eth"})
             expected_calls = [
-                mock.call.phy_add_flow(priority=1, actions='normal'),
-                mock.call.int_add_patch_port('int-br-eth',
-                                             constants.NONEXISTENT_PEER),
-                mock.call.phy_add_patch_port('phy-br-eth',
-                                             constants.NONEXISTENT_PEER),
-                mock.call.int_add_flow(priority=2, in_port='int_ofport',
-                                       actions='drop'),
-                mock.call.phy_add_flow(priority=2, in_port='phy_ofport',
-                                       actions='drop'),
-                mock.call.int_set_attr('Interface', 'int-br-eth',
-                                       'options:peer', 'phy-br-eth'),
-                mock.call.phy_set_attr('Interface', 'phy-br-eth',
-                                       'options:peer', 'int-br-eth'),
-
+                mock.call.phys_br_cls('br-eth'),
+                mock.call.phys_br.setup_controllers(mock.ANY),
+                mock.call.phys_br.setup_default_table(),
+                mock.call.int_br.delete_port('int-br-eth'),
+                mock.call.phys_br.delete_port('phy-br-eth'),
+                mock.call.int_br.add_patch_port('int-br-eth',
+                                                constants.NONEXISTENT_PEER),
+                mock.call.phys_br.add_patch_port('phy-br-eth',
+                                                 constants.NONEXISTENT_PEER),
+                mock.call.int_br.drop_port(in_port='int_ofport'),
+                mock.call.phys_br.drop_port(in_port='phy_ofport'),
+                mock.call.int_br.set_db_attribute('Interface', 'int-br-eth',
+                                                  'options:peer',
+                                                  'phy-br-eth'),
+                mock.call.phys_br.set_db_attribute('Interface', 'phy-br-eth',
+                                                   'options:peer',
+                                                   'int-br-eth'),
             ]
             parent.assert_has_calls(expected_calls)
             self.assertEqual(self.agent.int_ofports["physnet1"],
@@ -596,19 +573,14 @@ class TestOvsNeutronAgent(base.BaseTestCase):
             mock.patch.object(ip_lib, "device_exists"),
             mock.patch.object(sys, "exit"),
             mock.patch.object(utils, "execute"),
-            mock.patch.object(ovs_lib.OVSBridge, "remove_all_flows"),
-            mock.patch.object(ovs_lib.OVSBridge, "add_flow"),
-            mock.patch.object(ovs_lib.OVSBridge, "add_port"),
-            mock.patch.object(ovs_lib.OVSBridge, "delete_port"),
-            mock.patch.object(self.agent.int_br, "add_port"),
-            mock.patch.object(self.agent.int_br, "delete_port"),
+            mock.patch.object(self.agent, 'br_phys_cls'),
+            mock.patch.object(self.agent, 'int_br'),
             mock.patch.object(ip_lib.IPWrapper, "add_veth"),
             mock.patch.object(ip_lib.IpLinkCommand, "delete"),
             mock.patch.object(ip_lib.IpLinkCommand, "set_up"),
             mock.patch.object(ip_lib.IpLinkCommand, "set_mtu"),
             mock.patch.object(ovs_lib.BaseOVS, "get_bridges")
-        ) as (devex_fn, sysexit_fn, utilsexec_fn, remflows_fn, ovs_addfl_fn,
-              ovs_addport_fn, ovs_delport_fn, br_addport_fn, br_delport_fn,
+        ) as (devex_fn, sysexit_fn, utilsexec_fn, phys_br_cls, int_br,
               addveth_fn, linkdel_fn, linkset_fn, linkmtu_fn, get_br_fn):
             devex_fn.return_value = True
             parent = mock.MagicMock()
@@ -617,8 +589,9 @@ class TestOvsNeutronAgent(base.BaseTestCase):
             parent.attach_mock(addveth_fn, 'add_veth')
             addveth_fn.return_value = (ip_lib.IPDevice("int-br-eth1"),
                                        ip_lib.IPDevice("phy-br-eth1"))
-            ovs_addport_fn.return_value = "phys_veth_ofport"
-            br_addport_fn.return_value = "int_veth_ofport"
+            phys_br = phys_br_cls()
+            phys_br.add_port.return_value = "phys_veth_ofport"
+            int_br.add_port.return_value = "int_veth_ofport"
             get_br_fn.return_value = ["br-eth"]
             self.agent.setup_physical_bridges({"physnet1": "br-eth"})
             expected_calls = [mock.call.link_delete(),
@@ -649,15 +622,10 @@ class TestOvsNeutronAgent(base.BaseTestCase):
         with contextlib.nested(
             mock.patch.object(self.agent.int_br, "add_patch_port",
                               return_value=1),
-            mock.patch.object(self.agent.tun_br, "add_patch_port",
-                              return_value=2),
-            mock.patch.object(self.agent.tun_br, "remove_all_flows"),
-            mock.patch.object(self.agent.tun_br, "add_flow"),
-            mock.patch.object(ovs_lib, "OVSBridge"),
-            mock.patch.object(self.agent.tun_br, "reset_bridge"),
+            mock.patch.object(self.agent, 'tun_br', autospec=True),
             mock.patch.object(sys, "exit")
-        ) as (intbr_patch_fn, tunbr_patch_fn, remove_all_fn,
-              add_flow_fn, ovs_br_fn, reset_br_fn, exit_fn):
+        ) as (intbr_patch_fn, tun_br, exit_fn):
+            tun_br.add_patch_port.return_value = 2
             self.agent.reset_tunnel_br(None)
             self.agent.setup_tunnel_br()
             self.assertTrue(intbr_patch_fn.called)
@@ -755,50 +723,22 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                        [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1),
                         n_const.FLOODING_ENTRY]}}}
 
-        class ActionMatcher(object):
-            def __init__(self, action_str):
-                self.ordered = self.order_ports(action_str)
-
-            def order_ports(self, action_str):
-                halves = action_str.split('output:')
-                ports = sorted(halves.pop().split(','))
-                halves.append(','.join(ports))
-                return 'output:'.join(halves)
-
-            def __eq__(self, other):
-                return self.ordered == self.order_ports(other)
-
         with contextlib.nested(
-            mock.patch.object(self.agent.tun_br, 'deferred'),
-            mock.patch.object(self.agent.tun_br, 'do_action_flows'),
-            mock.patch.object(self.agent, '_setup_tunnel_port'),
-        ) as (deferred_fn, do_action_flows_fn, add_tun_fn):
-            deferred_fn.return_value = ovs_lib.DeferredOVSBridge(
-                self.agent.tun_br)
+            mock.patch.object(self.agent, 'tun_br', autospec=True),
+            mock.patch.object(self.agent, '_setup_tunnel_port', autospec=True),
+        ) as (tun_br, add_tun_fn):
             self.agent.fdb_add(None, fdb_entry)
             self.assertFalse(add_tun_fn.called)
-            actions = (constants.ARP_RESPONDER_ACTIONS %
-                       {'mac': netaddr.EUI(FAKE_MAC, dialect=netaddr.mac_unix),
-                        'ip': netaddr.IPAddress(FAKE_IP1)})
+            deferred_br_call = mock.call.deferred().__enter__()
             expected_calls = [
-                mock.call('add', [dict(table=constants.ARP_RESPONDER,
-                                       priority=1,
-                                       proto='arp',
-                                       dl_vlan='vlan1',
-                                       nw_dst=FAKE_IP1,
-                                       actions=actions),
-                                  dict(table=constants.UCAST_TO_TUN,
-                                       priority=2,
-                                       dl_vlan='vlan1',
-                                       dl_dst=FAKE_MAC,
-                                       actions='strip_vlan,'
-                                       'set_tunnel:seg1,output:2')]),
-                mock.call('mod', [dict(table=constants.FLOOD_TO_TUN,
-                                       dl_vlan='vlan1',
-                                       actions=ActionMatcher('strip_vlan,'
-                                       'set_tunnel:seg1,output:1,2'))]),
+                deferred_br_call.install_arp_responder('vlan1', FAKE_IP1,
+                                                       FAKE_MAC),
+                deferred_br_call.install_unicast_to_tun('vlan1', 'seg1', '2',
+                                                        FAKE_MAC),
+                deferred_br_call.install_flood_to_tun('vlan1', 'seg1',
+                                                      set(['1', '2'])),
             ]
-            do_action_flows_fn.assert_has_calls(expected_calls)
+            tun_br.assert_has_calls(expected_calls)
 
     def test_fdb_del_flows(self):
         self._prepare_l2_pop_ofports()
@@ -809,28 +749,21 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                       {'2.2.2.2':
                        [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1),
                         n_const.FLOODING_ENTRY]}}}
-        with contextlib.nested(
-            mock.patch.object(self.agent.tun_br, 'deferred'),
-            mock.patch.object(self.agent.tun_br, 'do_action_flows'),
-        ) as (deferred_fn, do_action_flows_fn):
-            deferred_fn.return_value = ovs_lib.DeferredOVSBridge(
-                self.agent.tun_br)
+        with mock.patch.object(self.agent, 'tun_br', autospec=True) as br_tun:
             self.agent.fdb_remove(None, fdb_entry)
+            deferred_br_call = mock.call.deferred().__enter__()
             expected_calls = [
-                mock.call('mod', [dict(table=constants.FLOOD_TO_TUN,
-                                       dl_vlan='vlan2',
-                                       actions='strip_vlan,'
-                                       'set_tunnel:seg2,output:1')]),
-                mock.call('del', [dict(table=constants.ARP_RESPONDER,
-                                       proto='arp',
-                                       dl_vlan='vlan2',
-                                       nw_dst=FAKE_IP1),
-                                  dict(table=constants.UCAST_TO_TUN,
-                                       dl_vlan='vlan2',
-                                       dl_dst=FAKE_MAC),
-                                  dict(in_port='2')]),
+                mock.call.deferred(),
+                mock.call.deferred().__enter__(),
+                deferred_br_call.delete_arp_responder('vlan2', FAKE_IP1),
+                deferred_br_call.delete_unicast_to_tun('vlan2', FAKE_MAC),
+                deferred_br_call.install_flood_to_tun('vlan2', 'seg2',
+                                                      set(['1'])),
+                deferred_br_call.delete_port('gre-02020202'),
+                deferred_br_call.cleanup_tunnel_port('2'),
+                mock.call.deferred().__exit__(None, None, None),
             ]
-            do_action_flows_fn.assert_has_calls(expected_calls)
+            br_tun.assert_has_calls(expected_calls)
 
     def test_fdb_add_port(self):
         self._prepare_l2_pop_ofports()
@@ -840,17 +773,15 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                       'ports': {'1.1.1.1': [l2pop_rpc.PortInfo(FAKE_MAC,
                                                                FAKE_IP1)]}}}
         with contextlib.nested(
-            mock.patch.object(self.agent.tun_br, 'deferred'),
-            mock.patch.object(self.agent.tun_br, 'do_action_flows'),
+            mock.patch.object(self.agent, 'tun_br', autospec=True),
             mock.patch.object(self.agent, '_setup_tunnel_port')
-        ) as (deferred_fn, do_action_flows_fn, add_tun_fn):
-            deferred_br = ovs_lib.DeferredOVSBridge(self.agent.tun_br)
-            deferred_fn.return_value = deferred_br
+        ) as (tun_br, add_tun_fn):
             self.agent.fdb_add(None, fdb_entry)
             self.assertFalse(add_tun_fn.called)
             fdb_entry['net1']['ports']['10.10.10.10'] = [
                 l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1)]
             self.agent.fdb_add(None, fdb_entry)
+            deferred_br = tun_br.deferred().__enter__()
             add_tun_fn.assert_called_with(
                 deferred_br, 'gre-0a0a0a0a', '10.10.10.10', 'gre')
 
@@ -862,13 +793,12 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                       'ports': {'2.2.2.2': [n_const.FLOODING_ENTRY]}}}
         with contextlib.nested(
             mock.patch.object(self.agent.tun_br, 'deferred'),
-            mock.patch.object(self.agent.tun_br, 'do_action_flows'),
-            mock.patch.object(self.agent.tun_br, 'delete_port')
-        ) as (deferred_fn, do_action_flows_fn, delete_port_fn):
-            deferred_br = ovs_lib.DeferredOVSBridge(self.agent.tun_br)
-            deferred_fn.return_value = deferred_br
+            mock.patch.object(self.agent.tun_br, 'delete_port'),
+        ) as (defer_fn, delete_port_fn):
             self.agent.fdb_remove(None, fdb_entry)
-            delete_port_fn.assert_called_once_with('gre-02020202')
+            deferred_br = defer_fn().__enter__()
+            deferred_br.delete_port.assert_called_once_with('gre-02020202')
+            self.assertFalse(delete_port_fn.called)
 
     def test_fdb_update_chg_ip(self):
         self._prepare_l2_pop_ofports()
@@ -877,31 +807,13 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                         {'agent_ip':
                          {'before': [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP1)],
                           'after': [l2pop_rpc.PortInfo(FAKE_MAC, FAKE_IP2)]}}}}
-        with contextlib.nested(
-            mock.patch.object(self.agent.tun_br, 'deferred'),
-            mock.patch.object(self.agent.tun_br, 'do_action_flows'),
-        ) as (deferred_fn, do_action_flows_fn):
-            deferred_br = ovs_lib.DeferredOVSBridge(self.agent.tun_br)
-            deferred_fn.return_value = deferred_br
+        with mock.patch.object(self.agent.tun_br, 'deferred') as deferred_fn:
             self.agent.fdb_update(None, fdb_entries)
-            actions = (constants.ARP_RESPONDER_ACTIONS %
-                       {'mac': netaddr.EUI(FAKE_MAC, dialect=netaddr.mac_unix),
-                        'ip': netaddr.IPAddress(FAKE_IP2)})
-            expected_calls = [
-                mock.call('add', [dict(table=constants.ARP_RESPONDER,
-                                       priority=1,
-                                       proto='arp',
-                                       dl_vlan='vlan1',
-                                       nw_dst=FAKE_IP2,
-                                       actions=actions)]),
-                mock.call('del', [dict(table=constants.ARP_RESPONDER,
-                                       proto='arp',
-                                       dl_vlan='vlan1',
-                                       nw_dst=FAKE_IP1)])
-            ]
-            do_action_flows_fn.assert_has_calls(expected_calls)
-            self.assertEqual(len(expected_calls),
-                             len(do_action_flows_fn.mock_calls))
+            deferred_br = deferred_fn().__enter__()
+            deferred_br.assert_has_calls([
+                mock.call.install_arp_responder('vlan1', FAKE_IP2, FAKE_MAC),
+                mock.call.delete_arp_responder('vlan1', FAKE_IP1)
+            ])
 
     def test_del_fdb_flow_idempotency(self):
         lvm = mock.Mock()
@@ -922,22 +834,17 @@ class TestOvsNeutronAgent(base.BaseTestCase):
         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:
+        with mock.patch.object(self.agent, 'tun_br', autospec=True) as tun_br:
             self.agent.reclaim_local_vlan('net1')
-            self.assertFalse(clean_tun_fn.called)
+            self.assertFalse(tun_br.cleanup_tunnel_port.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):
+        with mock.patch.object(self.agent, 'tun_br', autospec=True) as tun_br:
             self.agent.reclaim_local_vlan('net2')
-            del_port_fn.assert_called_once_with('gre-02020202')
+            tun_br.delete_port.assert_called_once_with('gre-02020202')
 
     def test_daemon_loop_uses_polling_manager(self):
         with mock.patch(
@@ -952,7 +859,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
         with contextlib.nested(
             mock.patch.object(self.agent.tun_br, 'add_tunnel_port',
                               return_value=ovs_lib.INVALID_OFPORT),
-            mock.patch.object(ovs_neutron_agent.LOG, 'error')
+            mock.patch.object(self.mod_agent.LOG, 'error')
         ) as (add_tunnel_port_fn, log_error_fn):
             ofport = self.agent._setup_tunnel_port(
                 self.agent.tun_br, 'gre-1', 'remote_ip', p_const.TYPE_GRE)
@@ -968,7 +875,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
         with contextlib.nested(
             mock.patch.object(self.agent.tun_br, 'add_tunnel_port',
                               return_value=ovs_lib.INVALID_OFPORT),
-            mock.patch.object(ovs_neutron_agent.LOG, 'error')
+            mock.patch.object(self.mod_agent.LOG, 'error')
         ) as (add_tunnel_port_fn, log_error_fn):
             self.agent.dont_fragment = False
             ofport = self.agent._setup_tunnel_port(
@@ -1045,18 +952,18 @@ class TestOvsNeutronAgent(base.BaseTestCase):
         with contextlib.nested(
             mock.patch.object(async_process.AsyncProcess, "_spawn"),
             mock.patch.object(log.KeywordArgumentAdapter, 'exception'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'scan_ports'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'process_network_ports'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'check_ovs_status'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'setup_integration_br'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'setup_physical_bridges'),
             mock.patch.object(time, 'sleep'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'update_stale_ofport_rules')
         ) as (spawn_fn, log_exception, scan_ports, process_network_ports,
               check_ovs_status, setup_int_br, setup_phys_br, time_sleep,
@@ -1113,7 +1020,7 @@ class TestOvsNeutronAgent(base.BaseTestCase):
         # all of this is required just to get to the part of
         # treat_devices_added_or_updated that checks the prevent_arp_spoofing
         # flag
-        self.agent.int_br = mock.Mock()
+        self.agent.int_br = mock.create_autospec(self.agent.int_br)
         self.agent.treat_vif_port = mock.Mock()
         self.agent.get_vif_port_by_id = mock.Mock(return_value=FakeVif())
         self.agent.plugin_rpc = mock.Mock()
@@ -1127,27 +1034,24 @@ class TestOvsNeutronAgent(base.BaseTestCase):
         self.assertFalse(self.agent.setup_arp_spoofing_protection.called)
 
     def test_arp_spoofing_port_security_disabled(self):
-        int_br = mock.Mock()
+        int_br = mock.create_autospec(self.agent.int_br)
         self.agent.setup_arp_spoofing_protection(
             int_br, FakeVif(), {'port_security_enabled': False})
-        self.assertFalse(int_br.add_flows.called)
+        self.assertTrue(int_br.delete_arp_spoofing_protection.called)
+        self.assertFalse(int_br.install_arp_spoofing_protection.called)
 
     def test_arp_spoofing_basic_rule_setup(self):
         vif = FakeVif()
         fake_details = {'fixed_ips': []}
         self.agent.prevent_arp_spoofing = True
-        int_br = mock.Mock()
+        int_br = mock.create_autospec(self.agent.int_br)
         self.agent.setup_arp_spoofing_protection(int_br, vif, fake_details)
-        int_br.delete_flows.assert_has_calls(
-            [mock.call(table=mock.ANY, in_port=vif.ofport)])
-        # make sure redirect into spoof table is installed
-        int_br.add_flow.assert_any_call(
-            table=constants.LOCAL_SWITCHING, in_port=vif.ofport,
-            proto='arp', actions=mock.ANY, priority=10)
-        # make sure drop rule for replies is installed
-        int_br.add_flow.assert_any_call(
-            table=constants.ARP_SPOOF_TABLE,
-            proto='arp', actions='DROP', priority=mock.ANY)
+        self.assertEqual(
+            [mock.call(port=vif.ofport)],
+            int_br.delete_arp_spoofing_protection.mock_calls)
+        self.assertEqual(
+            [mock.call(ip_addresses=set(), port=vif.ofport)],
+            int_br.install_arp_spoofing_protection.mock_calls)
 
     def test_arp_spoofing_fixed_and_allowed_addresses(self):
         vif = FakeVif()
@@ -1158,14 +1062,14 @@ class TestOvsNeutronAgent(base.BaseTestCase):
                                       {'ip_address': '192.168.44.103/32'}]
         }
         self.agent.prevent_arp_spoofing = True
-        int_br = mock.Mock()
+        int_br = mock.create_autospec(self.agent.int_br)
         self.agent.setup_arp_spoofing_protection(int_br, vif, fake_details)
         # make sure all addresses are allowed
-        for addr in ('192.168.44.100', '192.168.44.101', '192.168.44.102/32',
-                     '192.168.44.103/32'):
-            int_br.add_flow.assert_any_call(
-                table=constants.ARP_SPOOF_TABLE, in_port=vif.ofport,
-                proto='arp', actions='NORMAL', arp_spa=addr, priority=mock.ANY)
+        addresses = {'192.168.44.100', '192.168.44.101', '192.168.44.102/32',
+                     '192.168.44.103/32'}
+        self.assertEqual(
+            [mock.call(port=vif.ofport, ip_addresses=addresses)],
+            int_br.install_arp_spoofing_protection.mock_calls)
 
     def test__get_ofport_moves(self):
         previous = {'port1': 1, 'port2': 2}
@@ -1184,8 +1088,9 @@ class TestOvsNeutronAgent(base.BaseTestCase):
         self.agent.int_br.get_vif_port_to_ofport_map.return_value = newmap
         self.agent.update_stale_ofport_rules()
         # rules matching port 1 should have been deleted
-        self.assertEqual(self.agent.int_br.delete_flows.mock_calls,
-                         [mock.call(in_port=1)])
+        self.assertEqual(
+            [mock.call(port=1)],
+            self.agent.int_br.delete_arp_spoofing_protection.mock_calls)
         # make sure the state was updated with the new map
         self.assertEqual(self.agent.vifname_to_ofport_map, newmap)
 
@@ -1210,22 +1115,24 @@ class TestOvsNeutronAgent(base.BaseTestCase):
 
         def add_new_vlan_mapping(*args, **kwargs):
             self.agent.local_vlan_map['bar'] = (
-                ovs_neutron_agent.LocalVLANMapping(1, 2, 3, 4))
+                self.mod_agent.LocalVLANMapping(1, 2, 3, 4))
         bridge = mock.Mock()
         tunnel_type = 'vxlan'
         self.agent.tun_br_ofports = {tunnel_type: dict()}
         self.agent.l2_pop = False
         self.agent.local_vlan_map = {
-            'foo': ovs_neutron_agent.LocalVLANMapping(4, tunnel_type, 2, 1)}
-        bridge.mod_flow.side_effect = add_new_vlan_mapping
-        with mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.'
-                        '_ofport_set_to_str', return_value=True):
-            self.agent._setup_tunnel_port(bridge, 1, 2,
-                                          tunnel_type=tunnel_type)
+            'foo': self.mod_agent.LocalVLANMapping(4, tunnel_type, 2, 1)}
+        bridge.install_flood_to_tun.side_effect = add_new_vlan_mapping
+        self.agent._setup_tunnel_port(bridge, 1, 2, tunnel_type=tunnel_type)
         self.assertIn('bar', self.agent.local_vlan_map)
 
 
-class AncillaryBridgesTest(base.BaseTestCase):
+class TestOvsNeutronAgentOFCtl(TestOvsNeutronAgent,
+                               ovs_test_base.OVSOFCtlTestBase):
+    pass
+
+
+class AncillaryBridgesTest(object):
 
     def setUp(self):
         super(AncillaryBridgesTest, self).setUp()
@@ -1237,7 +1144,7 @@ class AncillaryBridgesTest(base.BaseTestCase):
                              'neutron.agent.firewall.NoopFirewallDriver',
                              group='SECURITYGROUP')
         cfg.CONF.set_override('report_interval', 0, 'AGENT')
-        self.kwargs = ovs_neutron_agent.create_agent_config_map(cfg.CONF)
+        self.kwargs = self.mod_agent.create_agent_config_map(cfg.CONF)
 
     def _test_ancillary_bridges(self, bridges, ancillary):
         device_ids = ancillary[:]
@@ -1252,15 +1159,10 @@ class AncillaryBridgesTest(base.BaseTestCase):
                 return None
 
         with contextlib.nested(
-            mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.'
-                       'OVSNeutronAgent.setup_integration_br'),
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
+                       'setup_integration_br'),
             mock.patch('neutron.agent.linux.utils.get_interface_mac',
                        return_value='00:00:00:00:00:01'),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'get_local_port_mac',
-                       return_value='00:00:00:00:00:01'),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'set_secure_mode'),
             mock.patch('neutron.agent.common.ovs_lib.BaseOVS.get_bridges',
                        return_value=bridges),
             mock.patch('neutron.agent.common.ovs_lib.BaseOVS.'
@@ -1268,7 +1170,8 @@ class AncillaryBridgesTest(base.BaseTestCase):
                        side_effect=pullup_side_effect),
             mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
                        'get_vif_ports', return_value=[])):
-            self.agent = ovs_neutron_agent.OVSNeutronAgent(**self.kwargs)
+            self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(),
+                                                        **self.kwargs)
             self.assertEqual(len(ancillary), len(self.agent.ancillary_brs))
             if ancillary:
                 bridges = [br.br_name for br in self.agent.ancillary_brs]
@@ -1288,7 +1191,12 @@ class AncillaryBridgesTest(base.BaseTestCase):
         self._test_ancillary_bridges(bridges, ['br-ex1', 'br-ex2'])
 
 
-class TestOvsDvrNeutronAgent(base.BaseTestCase):
+class AncillaryBridgesTestOFCtl(AncillaryBridgesTest,
+                                ovs_test_base.OVSOFCtlTestBase):
+    pass
+
+
+class TestOvsDvrNeutronAgent(object):
 
     def setUp(self):
         super(TestOvsDvrNeutronAgent, self).setUp()
@@ -1299,7 +1207,7 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
         cfg.CONF.set_default('firewall_driver',
                              'neutron.agent.firewall.NoopFirewallDriver',
                              group='SECURITYGROUP')
-        kwargs = ovs_neutron_agent.create_agent_config_map(cfg.CONF)
+        kwargs = self.mod_agent.create_agent_config_map(cfg.CONF)
 
         class MockFixedIntervalLoopingCall(object):
             def __init__(self, f):
@@ -1309,18 +1217,11 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
                 self.f()
 
         with contextlib.nested(
-            mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.'
-                       'OVSNeutronAgent.setup_integration_br'),
-            mock.patch('neutron.plugins.openvswitch.agent.ovs_neutron_agent.'
-                       'OVSNeutronAgent.setup_ancillary_bridges',
-                       return_value=[]),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'create'),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'set_secure_mode'),
-            mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                       'get_local_port_mac',
-                       return_value='00:00:00:00:00:01'),
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
+                              'setup_integration_br'),
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
+                              'setup_ancillary_bridges',
+                              return_value=[]),
             mock.patch('neutron.agent.linux.utils.get_interface_mac',
                        return_value='00:00:00:00:00:01'),
             mock.patch('neutron.agent.common.ovs_lib.BaseOVS.get_bridges'),
@@ -1329,11 +1230,12 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
                        new=MockFixedIntervalLoopingCall),
             mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
                        'get_vif_ports', return_value=[])):
-            self.agent = ovs_neutron_agent.OVSNeutronAgent(**kwargs)
+            self.agent = self.mod_agent.OVSNeutronAgent(self._bridge_classes(),
+                                                        **kwargs)
             # set back to true because initial report state will succeed due
             # to mocked out RPC calls
             self.agent.use_call = True
-            self.agent.tun_br = mock.Mock()
+            self.agent.tun_br = self.br_tun_cls(br_name='br-tun')
         self.agent.sg_agent = mock.Mock()
 
     def _setup_for_dvr_test(self, ofport=10):
@@ -1371,6 +1273,36 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
         self._compute_fixed_ips = [{'subnet_id': 'my-subnet-uuid',
                                     'ip_address': '1.1.1.3'}]
 
+    @staticmethod
+    def _expected_port_bound(port, lvid):
+        return [
+            mock.call.db_get_val('Port', port.port_name, 'other_config'),
+            mock.call.set_db_attribute('Port', port.port_name, 'other_config',
+                                       mock.ANY),
+        ]
+
+    def _expected_install_dvr_process(self, lvid, port, ip_version,
+                                      gateway_ip, gateway_mac):
+        if ip_version == 4:
+            ipvx_calls = [
+                mock.call.install_dvr_process_ipv4(
+                    vlan_tag=lvid,
+                    gateway_ip=gateway_ip),
+            ]
+        else:
+            ipvx_calls = [
+                mock.call.install_dvr_process_ipv6(
+                    vlan_tag=lvid,
+                    gateway_mac=gateway_mac),
+            ]
+        return ipvx_calls + [
+            mock.call.install_dvr_process(
+                vlan_tag=lvid,
+                dvr_mac_address=self.agent.dvr_agent.dvr_mac_address,
+                vif_mac=port.vif_mac,
+            ),
+        ]
+
     def _test_port_bound_for_dvr_on_vlan_network(self, device_owner,
                                                  ip_version=4):
         self._setup_for_dvr_test()
@@ -1385,105 +1317,84 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
         physical_network = self._physical_network
         segmentation_id = self._segmentation_id
         network_type = p_const.TYPE_VLAN
+        int_br = mock.create_autospec(self.agent.int_br)
+        tun_br = mock.create_autospec(self.agent.tun_br)
+        phys_br = mock.create_autospec(self.br_phys_cls('br-phys'))
+        int_br.set_db_attribute.return_value = True
+        int_br.db_get_val.return_value = {}
         with contextlib.nested(
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                        'set_db_attribute',
-                        return_value=True),
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                           'db_get_val',
-                           return_value={})):
-            with contextlib.nested(
-                mock.patch.object(self.agent.dvr_agent.plugin_rpc,
-                                  'get_subnet_for_dvr',
-                                  return_value={
-                                      'gateway_ip': gateway_ip,
-                                      'cidr': cidr,
-                                      'ip_version': ip_version,
-                                      'gateway_mac': gateway_mac}),
-                mock.patch.object(self.agent.dvr_agent.plugin_rpc,
-                                  'get_ports_on_host_by_subnet',
-                                  return_value=[]),
-                mock.patch.object(self.agent.dvr_agent.int_br,
-                                  'get_vif_port_by_id',
-                                  return_value=self._port),
-                mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows'),
-                mock.patch.object(
-                    self.agent.dvr_agent.phys_brs[physical_network],
-                    'add_flow'),
-                mock.patch.object(
-                    self.agent.dvr_agent.phys_brs[physical_network],
-                    'delete_flows')
-            ) as (get_subnet_fn, get_cphost_fn,
-                  get_vif_fn, add_flow_int_fn,
-                  add_flow_tun_fn, delete_flows_tun_fn, add_flow_phys_fn,
-                  delete_flows_phys_fn):
-                self.agent.port_bound(
-                    self._port, self._net_uuid, network_type,
-                    physical_network, segmentation_id, self._fixed_ips,
-                    n_const.DEVICE_OWNER_DVR_INTERFACE, False)
-                lvm = self.agent.local_vlan_map[self._net_uuid]
-                phy_ofp = self.agent.dvr_agent.phys_ofports[physical_network]
-                int_ofp = self.agent.dvr_agent.int_ofports[physical_network]
-                expected_on_phys_br = [
-                    mock.call(table=constants.LOCAL_VLAN_TRANSLATION,
-                              priority=4,
-                              in_port=phy_ofp,
-                              dl_vlan=lvm.vlan,
-                              actions="mod_vlan_vid:%s,normal" %
-                              (lvm.segmentation_id)),
-                    mock.call(table=constants.DVR_PROCESS_VLAN,
-                              priority=2,
-                              dl_vlan=lvm.vlan,
-                              dl_dst=self._port.vif_mac,
-                              actions="drop"),
-                    mock.call(table=constants.DVR_PROCESS_VLAN,
-                              priority=1,
-                              dl_vlan=lvm.vlan,
-                              dl_src=self._port.vif_mac,
-                              actions="mod_dl_src:%s,resubmit(,%s)" %
-                              (self.agent.dvr_agent.dvr_mac_address,
-                               constants.LOCAL_VLAN_TRANSLATION))
-                            ]
-                if ip_version == 4:
-                    expected_on_phys_br.insert(1, mock.call(
-                        proto='arp',
-                        nw_dst=gateway_ip, actions='drop',
-                        priority=3, table=constants.DVR_PROCESS_VLAN,
-                        dl_vlan=lvm.vlan))
-                else:
-                    expected_on_phys_br.insert(1, mock.call(
-                        icmp_type=n_const.ICMPV6_TYPE_RA, proto='icmp6',
-                        dl_src=self._port.vif_mac, actions='drop',
-                        priority=3, table=constants.DVR_PROCESS_VLAN,
-                        dl_vlan=lvm.vlan))
-                self.assertEqual(expected_on_phys_br,
-                                 add_flow_phys_fn.call_args_list)
-                self.agent.port_bound(self._compute_port, self._net_uuid,
-                                      network_type, physical_network,
-                                      segmentation_id,
-                                      self._compute_fixed_ips,
-                                      device_owner, False)
-                expected_on_int_br = [
-                    mock.call(priority=3,
-                              in_port=int_ofp,
-                              dl_vlan=lvm.segmentation_id,
-                              actions="mod_vlan_vid:%s,normal" % lvm.vlan),
-                    mock.call(table=constants.DVR_TO_SRC_MAC_VLAN,
-                              priority=4,
-                              dl_dst=self._compute_port.vif_mac,
-                              dl_vlan=lvm.segmentation_id,
-                              actions="strip_vlan,mod_dl_src:%s,"
-                              "output:%s" %
-                              (gateway_mac,
-                               self._compute_port.ofport))
-                                      ]
-                self.assertEqual(expected_on_int_br,
-                                 add_flow_int_fn.call_args_list)
-                self.assertFalse(add_flow_tun_fn.called)
-                self.assertFalse(delete_flows_tun_fn.called)
-                self.assertFalse(delete_flows_phys_fn.called)
+            mock.patch.object(self.agent.dvr_agent.plugin_rpc,
+                              'get_subnet_for_dvr',
+                              return_value={
+                                  'gateway_ip': gateway_ip,
+                                  'cidr': cidr,
+                                  'ip_version': ip_version,
+                                  'gateway_mac': gateway_mac}),
+            mock.patch.object(self.agent.dvr_agent.plugin_rpc,
+                'get_ports_on_host_by_subnet',
+                return_value=[]),
+            mock.patch.object(self.agent.dvr_agent.int_br,
+                              'get_vif_port_by_id',
+                              return_value=self._port),
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.dict(self.agent.phys_brs,
+                            {physical_network: phys_br}),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+            mock.patch.dict(self.agent.dvr_agent.phys_brs,
+                            {physical_network: phys_br}),
+        ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _, _, _):
+            self.agent.port_bound(
+                self._port, self._net_uuid, network_type,
+                physical_network, segmentation_id, self._fixed_ips,
+                n_const.DEVICE_OWNER_DVR_INTERFACE, False)
+            phy_ofp = self.agent.dvr_agent.phys_ofports[physical_network]
+            int_ofp = self.agent.dvr_agent.int_ofports[physical_network]
+            lvid = self.agent.local_vlan_map[self._net_uuid].vlan
+            expected_on_phys_br = [
+                mock.call.provision_local_vlan(
+                    port=phy_ofp,
+                    lvid=lvid,
+                    segmentation_id=segmentation_id,
+                    distributed=True,
+                ),
+            ] + self._expected_install_dvr_process(
+                port=self._port,
+                lvid=lvid,
+                ip_version=ip_version,
+                gateway_ip=gateway_ip,
+                gateway_mac=gateway_mac)
+            expected_on_int_br = [
+                mock.call.provision_local_vlan(
+                    port=int_ofp,
+                    lvid=lvid,
+                    segmentation_id=segmentation_id,
+                ),
+            ] + self._expected_port_bound(self._port, lvid)
+            self.assertEqual(expected_on_int_br, int_br.mock_calls)
+            self.assertEqual([], tun_br.mock_calls)
+            self.assertEqual(expected_on_phys_br, phys_br.mock_calls)
+            int_br.reset_mock()
+            tun_br.reset_mock()
+            phys_br.reset_mock()
+            self.agent.port_bound(self._compute_port, self._net_uuid,
+                                  network_type, physical_network,
+                                  segmentation_id,
+                                  self._compute_fixed_ips,
+                                  device_owner, False)
+            expected_on_int_br = [
+                mock.call.install_dvr_to_src_mac(
+                    network_type=network_type,
+                    gateway_mac=gateway_mac,
+                    dst_mac=self._compute_port.vif_mac,
+                    dst_port=self._compute_port.ofport,
+                    vlan_tag=segmentation_id,
+                ),
+            ] + self._expected_port_bound(self._compute_port, lvid)
+            self.assertEqual(expected_on_int_br, int_br.mock_calls)
+            self.assertFalse([], tun_br.mock_calls)
+            self.assertFalse([], phys_br.mock_calls)
 
     def _test_port_bound_for_dvr_on_vxlan_network(self, device_owner,
                                                   ip_version=4):
@@ -1499,99 +1410,76 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
         self._compute_port.vif_mac = '77:88:99:00:11:22'
         physical_network = self._physical_network
         segmentation_id = self._segmentation_id
+        int_br = mock.create_autospec(self.agent.int_br)
+        tun_br = mock.create_autospec(self.agent.tun_br)
+        phys_br = mock.create_autospec(self.br_phys_cls('br-phys'))
+        int_br.set_db_attribute.return_value = True
+        int_br.db_get_val.return_value = {}
         with contextlib.nested(
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                        'set_db_attribute',
-                        return_value=True),
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                           'db_get_val',
-                           return_value={})):
-            with contextlib.nested(
-                mock.patch.object(self.agent.dvr_agent.plugin_rpc,
-                                  'get_subnet_for_dvr',
-                                  return_value={
-                                      'gateway_ip': gateway_ip,
-                                      'cidr': cidr,
-                                      'ip_version': ip_version,
-                                      'gateway_mac': gateway_mac}),
-                mock.patch.object(self.agent.dvr_agent.plugin_rpc,
-                    'get_ports_on_host_by_subnet',
-                    return_value=[]),
-                mock.patch.object(self.agent.dvr_agent.int_br,
-                                  'get_vif_port_by_id',
-                                  return_value=self._port),
-                mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows'),
-                mock.patch.object(
-                    self.agent.dvr_agent.phys_brs[physical_network],
-                    'add_flow'),
-                mock.patch.object(
-                    self.agent.dvr_agent.phys_brs[physical_network],
-                    'delete_flows')
-            ) as (get_subnet_fn, get_cphost_fn,
-                  get_vif_fn, add_flow_int_fn,
-                  add_flow_tun_fn, delete_flows_tun_fn,
-                  add_flow_phys_fn, delete_flows_phys_fn):
-                self.agent.port_bound(
-                    self._port, self._net_uuid, network_type,
-                    physical_network, segmentation_id, self._fixed_ips,
-                    n_const.DEVICE_OWNER_DVR_INTERFACE, False)
-                lvm = self.agent.local_vlan_map[self._net_uuid]
-                expected_on_tun_br = [
-                    mock.call(
-                        table=constants.TUN_TABLE['vxlan'],
-                        priority=1, tun_id=lvm.segmentation_id,
-                        actions="mod_vlan_vid:%s,"
-                        "resubmit(,%s)" %
-                        (lvm.vlan, constants.DVR_NOT_LEARN)),
-                    mock.call(
-                        table=constants.DVR_PROCESS, priority=2,
-                        dl_vlan=lvm.vlan,
-                        dl_dst=self._port.vif_mac,
-                        actions='drop'),
-                    mock.call(
-                        table=constants.DVR_PROCESS, priority=1,
-                        dl_vlan=lvm.vlan,
-                        dl_src=self._port.vif_mac,
-                        actions="mod_dl_src:%s,resubmit(,%s)" % (
-                            self.agent.dvr_agent.dvr_mac_address,
-                            constants.PATCH_LV_TO_TUN))]
-                if ip_version == 4:
-                    expected_on_tun_br.insert(1, mock.call(
-                        proto='arp',
-                        nw_dst=gateway_ip, actions='drop',
-                        priority=3, table=constants.DVR_PROCESS,
-                        dl_vlan=lvm.vlan))
-                else:
-                    expected_on_tun_br.insert(1, mock.call(
-                        icmp_type=n_const.ICMPV6_TYPE_RA,
-                        proto='icmp6',
-                        dl_src=self._port.vif_mac,
-                        actions='drop',
-                        priority=3, table=constants.DVR_PROCESS,
-                        dl_vlan=lvm.vlan))
-                self.assertEqual(expected_on_tun_br,
-                                 add_flow_tun_fn.call_args_list)
-                self.agent.port_bound(self._compute_port, self._net_uuid,
-                                      network_type, physical_network,
-                                      segmentation_id,
-                                      self._compute_fixed_ips,
-                                      device_owner, False)
-                expected_on_int_br = [
-                    mock.call(table=constants.DVR_TO_SRC_MAC, priority=4,
-                        dl_dst=self._compute_port.vif_mac,
-                        dl_vlan=lvm.vlan,
-                        actions="strip_vlan,mod_dl_src:%s,"
-                        "output:%s" %
-                        (gateway_mac, self._compute_port.ofport))
-                                ]
-                self.assertEqual(expected_on_int_br,
-                                 add_flow_int_fn.call_args_list)
-                self.assertFalse(add_flow_phys_fn.called)
-                self.assertFalse(add_flow_phys_fn.called)
-                self.assertFalse(delete_flows_tun_fn.called)
-                self.assertFalse(delete_flows_phys_fn.called)
+            mock.patch.object(self.agent.dvr_agent.plugin_rpc,
+                              'get_subnet_for_dvr',
+                              return_value={
+                                  'gateway_ip': gateway_ip,
+                                  'cidr': cidr,
+                                  'ip_version': ip_version,
+                                  'gateway_mac': gateway_mac}),
+            mock.patch.object(self.agent.dvr_agent.plugin_rpc,
+                'get_ports_on_host_by_subnet',
+                return_value=[]),
+            mock.patch.object(self.agent.dvr_agent.int_br,
+                              'get_vif_port_by_id',
+                              return_value=self._port),
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.dict(self.agent.phys_brs,
+                            {physical_network: phys_br}),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+            mock.patch.dict(self.agent.dvr_agent.phys_brs,
+                            {physical_network: phys_br}),
+        ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _, _, _):
+            self.agent.port_bound(
+                self._port, self._net_uuid, network_type,
+                physical_network, segmentation_id, self._fixed_ips,
+                n_const.DEVICE_OWNER_DVR_INTERFACE, False)
+            lvid = self.agent.local_vlan_map[self._net_uuid].vlan
+            expected_on_int_br = self._expected_port_bound(
+                self._port, lvid)
+            expected_on_tun_br = [
+                mock.call.provision_local_vlan(
+                    network_type=network_type,
+                    segmentation_id=segmentation_id,
+                    lvid=lvid,
+                    distributed=True),
+            ] + self._expected_install_dvr_process(
+                port=self._port,
+                lvid=lvid,
+                ip_version=ip_version,
+                gateway_ip=gateway_ip,
+                gateway_mac=gateway_mac)
+            self.assertEqual(expected_on_int_br, int_br.mock_calls)
+            self.assertEqual(expected_on_tun_br, tun_br.mock_calls)
+            self.assertEqual([], phys_br.mock_calls)
+            int_br.reset_mock()
+            tun_br.reset_mock()
+            phys_br.reset_mock()
+            self.agent.port_bound(self._compute_port, self._net_uuid,
+                                  network_type, physical_network,
+                                  segmentation_id,
+                                  self._compute_fixed_ips,
+                                  device_owner, False)
+            expected_on_int_br = [
+                mock.call.install_dvr_to_src_mac(
+                    network_type=network_type,
+                    gateway_mac=gateway_mac,
+                    dst_mac=self._compute_port.vif_mac,
+                    dst_port=self._compute_port.ofport,
+                    vlan_tag=lvid,
+                ),
+            ] + self._expected_port_bound(self._compute_port, lvid)
+            self.assertEqual(expected_on_int_br, int_br.mock_calls)
+            self.assertEqual([], tun_br.mock_calls)
+            self.assertEqual([], phys_br.mock_calls)
 
     def test_port_bound_for_dvr_with_compute_ports(self):
         self._test_port_bound_for_dvr_on_vlan_network(
@@ -1625,38 +1513,53 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
 
     def test_port_bound_for_dvr_with_csnat_ports(self, ofport=10):
         self._setup_for_dvr_test()
+        int_br = mock.create_autospec(self.agent.int_br)
+        tun_br = mock.create_autospec(self.agent.tun_br)
+        int_br.set_db_attribute.return_value = True
+        int_br.db_get_val.return_value = {}
         with contextlib.nested(
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                        'set_db_attribute',
-                        return_value=True),
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                           'db_get_val',
-                           return_value={})):
-            with contextlib.nested(
-                mock.patch.object(
-                    self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
-                    return_value={'gateway_ip': '1.1.1.1',
-                                  'cidr': '1.1.1.0/24',
-                                  'ip_version': 4,
-                                  'gateway_mac': 'aa:bb:cc:11:22:33'}),
-                mock.patch.object(self.agent.dvr_agent.plugin_rpc,
-                    'get_ports_on_host_by_subnet',
-                    return_value=[]),
-                mock.patch.object(self.agent.dvr_agent.int_br,
-                                  'get_vif_port_by_id',
-                                  return_value=self._port),
-                mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows')
-            ) as (get_subnet_fn, get_cphost_fn,
-                  get_vif_fn, add_flow_int_fn,
-                  add_flow_tun_fn, delete_flows_tun_fn):
-                self.agent.port_bound(
-                    self._port, self._net_uuid, 'vxlan',
-                    None, None, self._fixed_ips,
-                    n_const.DEVICE_OWNER_ROUTER_SNAT,
-                    False)
-                self.assertTrue(add_flow_int_fn.called)
+            mock.patch.object(
+                self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
+                return_value={'gateway_ip': '1.1.1.1',
+                              'cidr': '1.1.1.0/24',
+                              'ip_version': 4,
+                              'gateway_mac': 'aa:bb:cc:11:22:33'}),
+            mock.patch.object(self.agent.dvr_agent.plugin_rpc,
+                'get_ports_on_host_by_subnet',
+                return_value=[]),
+            mock.patch.object(self.agent.dvr_agent.int_br,
+                              'get_vif_port_by_id',
+                              return_value=self._port),
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+        ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _):
+            self.agent.port_bound(
+                self._port, self._net_uuid, 'vxlan',
+                None, None, self._fixed_ips,
+                n_const.DEVICE_OWNER_ROUTER_SNAT,
+                False)
+            lvid = self.agent.local_vlan_map[self._net_uuid].vlan
+            expected_on_int_br = [
+                mock.call.install_dvr_to_src_mac(
+                    network_type='vxlan',
+                    gateway_mac='aa:bb:cc:11:22:33',
+                    dst_mac=self._port.vif_mac,
+                    dst_port=self._port.ofport,
+                    vlan_tag=lvid,
+                ),
+            ] + self._expected_port_bound(self._port, lvid)
+            self.assertEqual(expected_on_int_br, int_br.mock_calls)
+            expected_on_tun_br = [
+                mock.call.provision_local_vlan(
+                    network_type='vxlan',
+                    lvid=lvid,
+                    segmentation_id=None,
+                    distributed=True,
+                ),
+            ]
+            self.assertEqual(expected_on_tun_br, tun_br.mock_calls)
 
     def test_treat_devices_removed_for_dvr_interface(self, ofport=10):
         self._test_treat_devices_removed_for_dvr_interface(ofport)
@@ -1672,77 +1575,79 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
         else:
             gateway_ip = '2001:100::1'
             cidr = '2001:100::0/64'
+        gateway_mac = 'aa:bb:cc:11:22:33'
+        int_br = mock.create_autospec(self.agent.int_br)
+        tun_br = mock.create_autospec(self.agent.tun_br)
+        int_br.set_db_attribute.return_value = True
+        int_br.db_get_val.return_value = {}
         with contextlib.nested(
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                        'set_db_attribute',
-                        return_value=True),
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                           'db_get_val',
-                           return_value={})):
-            with contextlib.nested(
-                mock.patch.object(
-                    self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
-                    return_value={'gateway_ip': gateway_ip,
-                                  'cidr': cidr,
-                                  'ip_version': ip_version,
-                                  'gateway_mac': 'aa:bb:cc:11:22:33'}),
-                mock.patch.object(self.agent.dvr_agent.plugin_rpc,
-                    'get_ports_on_host_by_subnet',
-                    return_value=[]),
-                mock.patch.object(self.agent.dvr_agent.int_br,
-                                  'get_vif_port_by_id',
-                                  return_value=self._port),
-                mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows')
-            ) as (get_subnet_fn, get_cphost_fn,
-                  get_vif_fn, add_flow_int_fn,
-                  add_flow_tun_fn, delete_flows_tun_fn):
-                self.agent.port_bound(
-                    self._port, self._net_uuid, 'vxlan',
-                    None, None, self._fixed_ips,
-                    n_const.DEVICE_OWNER_DVR_INTERFACE,
-                    False)
-                self.assertTrue(add_flow_tun_fn.called)
-
+            mock.patch.object(
+                self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
+                return_value={'gateway_ip': gateway_ip,
+                              'cidr': cidr,
+                              'ip_version': ip_version,
+                              'gateway_mac': gateway_mac}),
+            mock.patch.object(self.agent.dvr_agent.plugin_rpc,
+                'get_ports_on_host_by_subnet',
+                return_value=[]),
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+            mock.patch.object(self.agent.dvr_agent.int_br,
+                              'get_vif_port_by_id',
+                              return_value=self._port),
+        ) as (get_subnet_fn, get_cphost_fn, _, _, _, _, get_vif_fn):
+            self.agent.port_bound(
+                self._port, self._net_uuid, 'vxlan',
+                None, None, self._fixed_ips,
+                n_const.DEVICE_OWNER_DVR_INTERFACE,
+                False)
+            lvid = self.agent.local_vlan_map[self._net_uuid].vlan
+            self.assertEqual(self._expected_port_bound(self._port, lvid),
+                             int_br.mock_calls)
+            expected_on_tun_br = [
+                mock.call.provision_local_vlan(network_type='vxlan',
+                    lvid=lvid, segmentation_id=None, distributed=True),
+            ] + self._expected_install_dvr_process(
+                port=self._port,
+                lvid=lvid,
+                ip_version=ip_version,
+                gateway_ip=gateway_ip,
+                gateway_mac=gateway_mac)
+            self.assertEqual(expected_on_tun_br, tun_br.mock_calls)
+
+        int_br.reset_mock()
+        tun_br.reset_mock()
         with contextlib.nested(
             mock.patch.object(self.agent, 'reclaim_local_vlan'),
             mock.patch.object(self.agent.plugin_rpc, 'update_device_down',
                               return_value=None),
-            mock.patch.object(self.agent.dvr_agent.int_br, 'delete_flows'),
-            mock.patch.object(self.agent.dvr_agent.tun_br,
-                              'delete_flows')) as (reclaim_vlan_fn,
-                                                   update_dev_down_fn,
-                                                   delete_flows_int_fn,
-                                                   delete_flows_tun_fn):
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+        ) as (reclaim_vlan_fn, update_dev_down_fn, _, _, _, _):
                 self.agent.treat_devices_removed([self._port.vif_id])
                 if ip_version == 4:
-                    expected = [mock.call(
-                        proto='arp',
-                        nw_dst=gateway_ip,
-                        table=constants.DVR_PROCESS,
-                        dl_vlan=(
-                            self.agent.local_vlan_map[self._net_uuid].vlan))]
+                    expected = [
+                        mock.call.delete_dvr_process_ipv4(
+                            vlan_tag=lvid,
+                            gateway_ip=gateway_ip),
+                    ]
                 else:
-                    expected = [mock.call(
-                        icmp_type=n_const.ICMPV6_TYPE_RA, proto='icmp6',
-                        dl_src='aa:bb:cc:11:22:33',
-                        table=constants.DVR_PROCESS,
-                        dl_vlan=(
-                            self.agent.local_vlan_map[self._net_uuid].vlan))]
+                    expected = [
+                        mock.call.delete_dvr_process_ipv6(
+                            vlan_tag=lvid,
+                            gateway_mac=gateway_mac),
+                    ]
                 expected.extend([
-                    mock.call(
-                        table=constants.DVR_PROCESS,
-                        dl_dst=self._port.vif_mac,
-                        dl_vlan=(
-                            self.agent.local_vlan_map[self._net_uuid].vlan)),
-                    mock.call(
-                        table=constants.DVR_PROCESS,
-                        dl_vlan=(
-                            self.agent.local_vlan_map[self._net_uuid].vlan),
-                        dl_src=self._port.vif_mac)
+                    mock.call.delete_dvr_process(
+                        vlan_tag=lvid,
+                        vif_mac=self._port.vif_mac),
                 ])
-                self.assertEqual(expected, delete_flows_tun_fn.call_args_list)
+                self.assertEqual([], int_br.mock_calls)
+                self.assertEqual(expected, tun_br.mock_calls)
 
     def _test_treat_devices_removed_for_dvr(self, device_owner, ip_version=4):
         self._setup_for_dvr_test()
@@ -1752,61 +1657,91 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
         else:
             gateway_ip = '2001:100::1'
             cidr = '2001:100::0/64'
+        gateway_mac = 'aa:bb:cc:11:22:33'
+        int_br = mock.create_autospec(self.agent.int_br)
+        tun_br = mock.create_autospec(self.agent.tun_br)
+        int_br.set_db_attribute.return_value = True
+        int_br.db_get_val.return_value = {}
         with contextlib.nested(
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                        'set_db_attribute',
-                        return_value=True),
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                           'db_get_val',
-                           return_value={})):
-            with contextlib.nested(
-                mock.patch.object(
-                    self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
-                    return_value={'gateway_ip': gateway_ip,
-                                  'cidr': cidr,
-                                  'ip_version': ip_version,
-                                  'gateway_mac': 'aa:bb:cc:11:22:33'}),
-                mock.patch.object(self.agent.dvr_agent.plugin_rpc,
-                    'get_ports_on_host_by_subnet',
-                    return_value=[]),
-                mock.patch.object(self.agent.dvr_agent.int_br,
-                                  'get_vif_port_by_id',
-                                  return_value=self._port),
-                mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows')
-            ) as (get_subnet_fn, get_cphost_fn,
-                  get_vif_fn, add_flow_int_fn,
-                  add_flow_tun_fn, delete_flows_tun_fn):
-                self.agent.port_bound(
-                    self._port, self._net_uuid, 'vxlan',
-                    None, None, self._fixed_ips,
-                    n_const.DEVICE_OWNER_DVR_INTERFACE,
-                    False)
-                self.agent.port_bound(self._compute_port,
-                                      self._net_uuid, 'vxlan',
-                                      None, None,
-                                      self._compute_fixed_ips,
-                                      device_owner, False)
-                self.assertTrue(add_flow_tun_fn.called)
-                self.assertTrue(add_flow_int_fn.called)
-
+            mock.patch.object(
+                self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
+                return_value={'gateway_ip': gateway_ip,
+                              'cidr': cidr,
+                              'ip_version': ip_version,
+                              'gateway_mac': gateway_mac}),
+            mock.patch.object(self.agent.dvr_agent.plugin_rpc,
+                'get_ports_on_host_by_subnet',
+                return_value=[]),
+            mock.patch.object(self.agent.dvr_agent.int_br,
+                              'get_vif_port_by_id',
+                              return_value=self._port),
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+        ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _):
+            self.agent.port_bound(
+                self._port, self._net_uuid, 'vxlan',
+                None, None, self._fixed_ips,
+                n_const.DEVICE_OWNER_DVR_INTERFACE,
+                False)
+            lvid = self.agent.local_vlan_map[self._net_uuid].vlan
+            self.assertEqual(
+                self._expected_port_bound(self._port, lvid),
+                int_br.mock_calls)
+            expected_on_tun_br = [
+                mock.call.provision_local_vlan(
+                    network_type='vxlan',
+                    segmentation_id=None,
+                    lvid=lvid,
+                    distributed=True),
+            ] + self._expected_install_dvr_process(
+                port=self._port,
+                lvid=lvid,
+                ip_version=ip_version,
+                gateway_ip=gateway_ip,
+                gateway_mac=gateway_mac)
+            self.assertEqual(expected_on_tun_br, tun_br.mock_calls)
+            int_br.reset_mock()
+            tun_br.reset_mock()
+            self.agent.port_bound(self._compute_port,
+                                  self._net_uuid, 'vxlan',
+                                  None, None,
+                                  self._compute_fixed_ips,
+                                  device_owner, False)
+            self.assertEqual(
+                [
+                    mock.call.install_dvr_to_src_mac(
+                        network_type='vxlan',
+                        gateway_mac='aa:bb:cc:11:22:33',
+                        dst_mac=self._compute_port.vif_mac,
+                        dst_port=self._compute_port.ofport,
+                        vlan_tag=lvid,
+                    ),
+                ] + self._expected_port_bound(self._compute_port, lvid),
+                int_br.mock_calls)
+            self.assertEqual([], tun_br.mock_calls)
+
+        int_br.reset_mock()
+        tun_br.reset_mock()
         with contextlib.nested(
             mock.patch.object(self.agent, 'reclaim_local_vlan'),
             mock.patch.object(self.agent.plugin_rpc, 'update_device_down',
                               return_value=None),
-            mock.patch.object(self.agent.dvr_agent.int_br,
-                              'delete_flows')) as (reclaim_vlan_fn,
-                                                   update_dev_down_fn,
-                                                   delete_flows_int_fn):
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+        ) as (reclaim_vlan_fn, update_dev_down_fn, _, _, _, _):
                 self.agent.treat_devices_removed([self._compute_port.vif_id])
-                expected = [
-                    mock.call(
-                        table=constants.DVR_TO_SRC_MAC,
-                        dl_dst=self._compute_port.vif_mac,
-                        dl_vlan=(
-                            self.agent.local_vlan_map[self._net_uuid].vlan))]
-                self.assertEqual(expected, delete_flows_int_fn.call_args_list)
+                int_br.assert_has_calls([
+                    mock.call.delete_dvr_to_src_mac(
+                        network_type='vxlan',
+                        vlan_tag=lvid,
+                        dst_mac=self._compute_port.vif_mac,
+                    ),
+                ])
+                self.assertEqual([], tun_br.mock_calls)
 
     def test_treat_devices_removed_for_dvr_with_compute_ports(self):
         self._test_treat_devices_removed_for_dvr(
@@ -1828,90 +1763,115 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
 
     def test_treat_devices_removed_for_dvr_csnat_port(self, ofport=10):
         self._setup_for_dvr_test()
+        gateway_mac = 'aa:bb:cc:11:22:33'
+        int_br = mock.create_autospec(self.agent.int_br)
+        tun_br = mock.create_autospec(self.agent.tun_br)
+        int_br.set_db_attribute.return_value = True
+        int_br.db_get_val.return_value = {}
         with contextlib.nested(
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                        'set_db_attribute',
-                        return_value=True),
-                mock.patch('neutron.agent.common.ovs_lib.OVSBridge.'
-                           'db_get_val',
-                           return_value={})):
-            with contextlib.nested(
-                mock.patch.object(
-                    self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
-                    return_value={'gateway_ip': '1.1.1.1',
-                                  'cidr': '1.1.1.0/24',
-                                  'ip_version': 4,
-                                  'gateway_mac': 'aa:bb:cc:11:22:33'}),
-                mock.patch.object(self.agent.dvr_agent.plugin_rpc,
-                    'get_ports_on_host_by_subnet',
-                    return_value=[]),
-                mock.patch.object(self.agent.dvr_agent.int_br,
-                                  'get_vif_port_by_id',
-                                  return_value=self._port),
-                mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows')
-            ) as (get_subnet_fn, get_cphost_fn,
-                  get_vif_fn, add_flow_int_fn,
-                  add_flow_tun_fn, delete_flows_tun_fn):
-                self.agent.port_bound(
-                    self._port, self._net_uuid, 'vxlan',
-                    None, None, self._fixed_ips,
-                    n_const.DEVICE_OWNER_ROUTER_SNAT,
-                    False)
-                self.assertTrue(add_flow_int_fn.called)
+            mock.patch.object(
+                self.agent.dvr_agent.plugin_rpc, 'get_subnet_for_dvr',
+                return_value={'gateway_ip': '1.1.1.1',
+                              'cidr': '1.1.1.0/24',
+                              'ip_version': 4,
+                              'gateway_mac': gateway_mac}),
+            mock.patch.object(self.agent.dvr_agent.plugin_rpc,
+                'get_ports_on_host_by_subnet',
+                return_value=[]),
+            mock.patch.object(self.agent.dvr_agent.int_br,
+                              'get_vif_port_by_id',
+                              return_value=self._port),
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+        ) as (get_subnet_fn, get_cphost_fn, get_vif_fn, _, _, _, _):
+            self.agent.port_bound(
+                self._port, self._net_uuid, 'vxlan',
+                None, None, self._fixed_ips,
+                n_const.DEVICE_OWNER_ROUTER_SNAT,
+                False)
+            lvid = self.agent.local_vlan_map[self._net_uuid].vlan
+            expected_on_int_br = [
+                mock.call.install_dvr_to_src_mac(
+                    network_type='vxlan',
+                    gateway_mac=gateway_mac,
+                    dst_mac=self._port.vif_mac,
+                    dst_port=self._port.ofport,
+                    vlan_tag=lvid,
+                ),
+            ] + self._expected_port_bound(self._port, lvid)
+            self.assertEqual(expected_on_int_br, int_br.mock_calls)
+            expected_on_tun_br = [
+                mock.call.provision_local_vlan(
+                    network_type='vxlan',
+                    lvid=lvid,
+                    segmentation_id=None,
+                    distributed=True,
+                ),
+            ]
+            self.assertEqual(expected_on_tun_br, tun_br.mock_calls)
 
+        int_br.reset_mock()
+        tun_br.reset_mock()
         with contextlib.nested(
             mock.patch.object(self.agent, 'reclaim_local_vlan'),
             mock.patch.object(self.agent.plugin_rpc, 'update_device_down',
                               return_value=None),
-            mock.patch.object(self.agent.dvr_agent.int_br,
-                              'delete_flows')) as (reclaim_vlan_fn,
-                                                   update_dev_down_fn,
-                                                   delete_flows_int_fn):
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+        ) as (reclaim_vlan_fn, update_dev_down_fn, _, _, _, _):
                 self.agent.treat_devices_removed([self._port.vif_id])
-                self.assertTrue(delete_flows_int_fn.called)
+                expected_on_int_br = [
+                    mock.call.delete_dvr_to_src_mac(
+                        network_type='vxlan',
+                        dst_mac=self._port.vif_mac,
+                        vlan_tag=lvid,
+                    ),
+                ]
+                self.assertEqual(expected_on_int_br, int_br.mock_calls)
+                expected_on_tun_br = []
+                self.assertEqual(expected_on_tun_br, tun_br.mock_calls)
 
     def test_setup_dvr_flows_on_int_br(self):
         self._setup_for_dvr_test()
+        int_br = mock.create_autospec(self.agent.int_br)
+        tun_br = mock.create_autospec(self.agent.tun_br)
         with contextlib.nested(
-                mock.patch.object(self.agent.dvr_agent.int_br,
-                                  'remove_all_flows'),
-                mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'),
-                mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'),
-                mock.patch.object(
-                    self.agent.dvr_agent.plugin_rpc,
-                    'get_dvr_mac_address_list',
-                    return_value=[{'host': 'cn1',
-                                   'mac_address': 'aa:bb:cc:dd:ee:ff'},
-                                  {'host': 'cn2',
-                                   'mac_address': '11:22:33:44:55:66'}])) as \
-            (remove_flows_fn, add_int_flow_fn, add_tun_flow_fn,
-             get_mac_list_fn):
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+            mock.patch.object(
+                self.agent.dvr_agent.plugin_rpc,
+                'get_dvr_mac_address_list',
+                return_value=[{'host': 'cn1',
+                               'mac_address': 'aa:bb:cc:dd:ee:ff'},
+                              {'host': 'cn2',
+                               'mac_address': '11:22:33:44:55:66'}])
+        ) as (_, _, _, _, get_mac_list_fn):
             self.agent.dvr_agent.setup_dvr_flows_on_integ_br()
             self.assertTrue(self.agent.dvr_agent.in_distributed_mode())
             physical_networks = self.agent.dvr_agent.bridge_mappings.keys()
             ioport = self.agent.dvr_agent.int_ofports[physical_networks[0]]
-            expected = [
-                    mock.call(table=constants.CANARY_TABLE,
-                              priority=0,
-                              actions="drop"),
-                    mock.call(table=constants.DVR_TO_SRC_MAC,
-                             priority=1,
-                             actions="drop"),
-                    mock.call(table=constants.DVR_TO_SRC_MAC_VLAN,
-                             priority=1,
-                             actions="drop"),
-                    mock.call(table=constants.LOCAL_SWITCHING,
-                             priority=1,
-                             actions="normal"),
-                    mock.call(
-                        table=constants.LOCAL_SWITCHING, priority=2,
-                        actions="drop",
-                        in_port=ioport)]
-            self.assertTrue(remove_flows_fn.called)
-            self.assertEqual(expected, add_int_flow_fn.call_args_list)
-            self.assertEqual(add_int_flow_fn.call_count, 5)
+            expected_on_int_br = [
+                # setup_dvr_flows_on_integ_br
+                mock.call.delete_flows(),
+                mock.call.setup_canary_table(),
+                mock.call.install_drop(table_id=constants.DVR_TO_SRC_MAC,
+                                       priority=1),
+                mock.call.install_drop(table_id=constants.DVR_TO_SRC_MAC_VLAN,
+                                       priority=1),
+                mock.call.install_normal(table_id=constants.LOCAL_SWITCHING,
+                                         priority=1),
+                mock.call.install_drop(table_id=constants.LOCAL_SWITCHING,
+                                       priority=2,
+                                       in_port=ioport),
+            ]
+            self.assertEqual(expected_on_int_br, int_br.mock_calls)
+            self.assertEqual([], tun_br.mock_calls)
 
     def test_get_dvr_mac_address(self):
         self._setup_for_dvr_test()
@@ -1928,17 +1888,18 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
     def test_get_dvr_mac_address_exception(self):
         self._setup_for_dvr_test()
         self.agent.dvr_agent.dvr_mac_address = None
+        int_br = mock.create_autospec(self.agent.int_br)
         with contextlib.nested(
                 mock.patch.object(self.agent.dvr_agent.plugin_rpc,
                                'get_dvr_mac_address_by_host',
                                side_effect=oslo_messaging.RemoteError),
-                mock.patch.object(self.agent.dvr_agent.int_br,
-                                  'add_flow')) as (gd_mac, add_int_flow_fn):
-
+                mock.patch.object(self.agent, 'int_br', new=int_br),
+                mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+        ) as (gd_mac, _, _):
             self.agent.dvr_agent.get_dvr_mac_address()
             self.assertIsNone(self.agent.dvr_agent.dvr_mac_address)
             self.assertFalse(self.agent.dvr_agent.in_distributed_mode())
-            self.assertEqual(add_int_flow_fn.call_count, 1)
+            self.assertEqual([mock.call.install_normal()], int_br.mock_calls)
 
     def test_get_dvr_mac_address_retried(self):
         valid_entry = {'host': 'cn1', 'mac_address': 'aa:22:33:44:55:66'}
@@ -1962,12 +1923,15 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
         # Raise a timeout every time until we give up, currently 5 tries
         self._setup_for_dvr_test()
         self.agent.dvr_agent.dvr_mac_address = None
+        int_br = mock.create_autospec(self.agent.int_br)
         with contextlib.nested(
-                mock.patch.object(self.agent.dvr_agent.plugin_rpc,
-                                 'get_dvr_mac_address_by_host',
-                                 side_effect=raise_timeout),
-                mock.patch.object(utils, "execute"),
-        ) as (rpc_mock, execute_mock):
+            mock.patch.object(self.agent.dvr_agent.plugin_rpc,
+                             'get_dvr_mac_address_by_host',
+                             side_effect=raise_timeout),
+            mock.patch.object(utils, "execute"),
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+        ) as (rpc_mock, execute_mock, _, _):
             self.agent.dvr_agent.get_dvr_mac_address()
             self.assertIsNone(self.agent.dvr_agent.dvr_mac_address)
             self.assertFalse(self.agent.dvr_agent.in_distributed_mode())
@@ -1978,69 +1942,77 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
         self._setup_for_dvr_test()
         newhost = 'cn2'
         newmac = 'aa:bb:cc:dd:ee:ff'
-        int_ofport = self.agent.dvr_agent.int_ofports['physeth1']
-        patch_int_ofport = self.agent.dvr_agent.patch_int_ofport
-        patch_tun_ofport = self.agent.dvr_agent.patch_tun_ofport
+        int_br = mock.create_autospec(self.agent.int_br)
+        tun_br = mock.create_autospec(self.agent.tun_br)
+        phys_br = mock.create_autospec(self.br_phys_cls('br-phys'))
+        physical_network = 'physeth1'
         with contextlib.nested(
-            mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'),
-            mock.patch.object(self.agent.dvr_agent.tun_br, 'add_flow'),
-            mock.patch.object(self.agent.dvr_agent.int_br, 'delete_flows'),
-            mock.patch.object(self.agent.dvr_agent.phys_brs['physeth1'],
-                              'add_flow')
-        ) as (add_flow_fn, add_flow_tn_fn, del_flows_fn, add_flow_phys_fn):
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.dict(self.agent.phys_brs,
+                            {physical_network: phys_br}),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+            mock.patch.dict(self.agent.dvr_agent.phys_brs,
+                            {physical_network: phys_br}),
+        ):
             self.agent.dvr_agent.\
                 dvr_mac_address_update(
                     dvr_macs=[{'host': newhost,
                                'mac_address': newmac}])
-            expected = [
-                    mock.call(table=constants.LOCAL_SWITCHING,
-                              priority=4,
-                              in_port=int_ofport,
-                              dl_src=newmac,
-                              actions="resubmit(,%s)" %
-                              constants.DVR_TO_SRC_MAC_VLAN),
-                    mock.call(table=constants.LOCAL_SWITCHING,
-                              priority=2,
-                              in_port=patch_tun_ofport,
-                              dl_src=newmac,
-                              actions="resubmit(,%s)" %
-                              constants.DVR_TO_SRC_MAC)]
-            self.assertEqual(expected, add_flow_fn.call_args_list)
-            add_flow_phys_fn.assert_called_with(
-                    table=constants.DVR_NOT_LEARN_VLAN,
-                    priority=2,
-                    dl_src=newmac,
-                    actions="output:%s" %
-                    self.agent.dvr_agent.phys_ofports['physeth1'])
-            add_flow_tn_fn.assert_called_with(table=constants.DVR_NOT_LEARN,
-                                              priority=1,
-                                              dl_src=newmac,
-                                              actions="output:%s"
-                                              % patch_int_ofport)
-            self.assertFalse(del_flows_fn.called)
+            expected_on_int_br = [
+                mock.call.add_dvr_mac_vlan(
+                    mac=newmac,
+                    port=self.agent.int_ofports[physical_network]),
+                mock.call.add_dvr_mac_tun(
+                    mac=newmac,
+                    port=self.agent.patch_tun_ofport),
+            ]
+            expected_on_tun_br = [
+                mock.call.add_dvr_mac_tun(
+                    mac=newmac,
+                    port=self.agent.patch_int_ofport),
+            ]
+            expected_on_phys_br = [
+                mock.call.add_dvr_mac_vlan(
+                    mac=newmac,
+                    port=self.agent.phys_ofports[physical_network]),
+            ]
+            self.assertEqual(expected_on_int_br, int_br.mock_calls)
+            self.assertEqual(expected_on_tun_br, tun_br.mock_calls)
+            self.assertEqual(expected_on_phys_br, phys_br.mock_calls)
+        int_br.reset_mock()
+        tun_br.reset_mock()
+        phys_br.reset_mock()
         with contextlib.nested(
-            mock.patch.object(self.agent.dvr_agent.int_br, 'add_flow'),
-            mock.patch.object(self.agent.dvr_agent.tun_br, 'delete_flows'),
-            mock.patch.object(self.agent.dvr_agent.int_br, 'delete_flows'),
-            mock.patch.object(self.agent.dvr_agent.phys_brs['physeth1'],
-                              'delete_flows'),
-        ) as (add_flow_fn, del_flows_tn_fn, del_flows_fn, del_flows_phys_fn):
+            mock.patch.object(self.agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
+            mock.patch.dict(self.agent.phys_brs,
+                            {physical_network: phys_br}),
+            mock.patch.object(self.agent.dvr_agent, 'int_br', new=int_br),
+            mock.patch.object(self.agent.dvr_agent, 'tun_br', new=tun_br),
+            mock.patch.dict(self.agent.dvr_agent.phys_brs,
+                            {physical_network: phys_br}),
+        ):
             self.agent.dvr_agent.dvr_mac_address_update(dvr_macs=[])
-            ioport = self.agent.dvr_agent.int_ofports['physeth1']
-            expected = [
-                 mock.call(table=constants.LOCAL_SWITCHING,
-                           in_port=ioport,
-                           dl_src=newmac),
-                 mock.call(table=constants.LOCAL_SWITCHING,
-                           in_port=patch_tun_ofport,
-                           dl_src=newmac)]
-            self.assertEqual(expected, del_flows_fn.call_args_list)
-            del_flows_phys_fn.asert_called_with(
-                    table=constants.DVR_NOT_LEARN_VLAN,
-                    dl_src=newmac)
-            del_flows_tn_fn.assert_called_with(table=constants.DVR_NOT_LEARN,
-                                               dl_src=newmac)
-            self.assertFalse(add_flow_fn.called)
+            expected_on_int_br = [
+                mock.call.remove_dvr_mac_vlan(
+                    mac=newmac),
+                mock.call.remove_dvr_mac_tun(
+                    mac=newmac,
+                    port=self.agent.patch_tun_ofport),
+            ]
+            expected_on_tun_br = [
+                mock.call.remove_dvr_mac_tun(
+                    mac=newmac),
+            ]
+            expected_on_phys_br = [
+                mock.call.remove_dvr_mac_vlan(
+                    mac=newmac),
+            ]
+            self.assertEqual(expected_on_int_br, int_br.mock_calls)
+            self.assertEqual(expected_on_tun_br, tun_br.mock_calls)
+            self.assertEqual(expected_on_phys_br, phys_br.mock_calls)
 
     def test_ovs_restart(self):
         self._setup_for_dvr_test()
@@ -2050,11 +2022,13 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
             'setup_dvr_flows_on_phys_br', 'setup_dvr_mac_flows_on_all_brs')
         reset_mocks = [mock.patch.object(self.agent.dvr_agent, method).start()
                        for method in reset_methods]
+        tun_br = mock.create_autospec(self.agent.tun_br)
         with contextlib.nested(
             mock.patch.object(self.agent, 'check_ovs_status',
                               return_value=constants.OVS_RESTARTED),
             mock.patch.object(self.agent, '_agent_has_updates',
-                              side_effect=TypeError('loop exit'))
+                              side_effect=TypeError('loop exit')),
+            mock.patch.object(self.agent, 'tun_br', new=tun_br),
         ):
             # block RPC calls and bridge calls
             self.agent.setup_physical_bridges = mock.Mock()
@@ -2066,3 +2040,8 @@ class TestOvsDvrNeutronAgent(base.BaseTestCase):
             except TypeError:
                 pass
         self.assertTrue(all([x.called for x in reset_mocks]))
+
+
+class TestOvsDvrNeutronAgentOFCtl(TestOvsDvrNeutronAgent,
+                                  ovs_test_base.OVSOFCtlTestBase):
+    pass
index 7773d5a0ea73eb14cea099e166e18fcf3d69fdeb..a3f8600ed999bf1525ec499bca7f824192e43a69 100644 (file)
@@ -24,9 +24,8 @@ from oslo_log import log
 from neutron.agent.common import ovs_lib
 from neutron.agent.linux import ip_lib
 from neutron.plugins.common import constants as p_const
-from neutron.plugins.openvswitch.agent import ovs_neutron_agent
 from neutron.plugins.openvswitch.common import constants
-from neutron.tests import base
+from neutron.tests.unit.plugins.openvswitch.agent import ovs_test_base
 
 
 # Useful global dummy variables.
@@ -40,11 +39,6 @@ OFPORT_NUM = 1
 VIF_PORT = ovs_lib.VifPort('port', OFPORT_NUM,
                            VIF_ID, VIF_MAC, 'switch')
 VIF_PORTS = {VIF_ID: VIF_PORT}
-LVM = ovs_neutron_agent.LocalVLANMapping(LV_ID, 'gre', None, LS_ID, VIF_PORTS)
-LVM_FLAT = ovs_neutron_agent.LocalVLANMapping(
-    LV_ID, 'flat', 'net1', LS_ID, VIF_PORTS)
-LVM_VLAN = ovs_neutron_agent.LocalVLANMapping(
-    LV_ID, 'vlan', 'net1', LS_ID, VIF_PORTS)
 FIXED_IPS = [{'subnet_id': 'my-subnet-uuid',
               'ip_address': '1.1.1.1'}]
 VM_DEVICE_OWNER = "compute:None"
@@ -66,7 +60,7 @@ class DummyVlanBinding(object):
         self.vlan_id = vlan_id
 
 
-class TunnelTest(base.BaseTestCase):
+class TunnelTest(object):
     USE_VETH_INTERCONNECTION = False
     VETH_MTU = None
 
@@ -86,21 +80,41 @@ class TunnelTest(base.BaseTestCase):
         self.MAP_TUN_INT_OFPORT = 33333
         self.MAP_TUN_PHY_OFPORT = 44444
 
+        self.LVM = self.mod_agent.LocalVLANMapping(
+            LV_ID, 'gre', None, LS_ID, VIF_PORTS)
+        self.LVM_FLAT = self.mod_agent.LocalVLANMapping(
+            LV_ID, 'flat', 'net1', LS_ID, VIF_PORTS)
+        self.LVM_VLAN = self.mod_agent.LocalVLANMapping(
+            LV_ID, 'vlan', 'net1', LS_ID, VIF_PORTS)
+
         self.inta = mock.Mock()
         self.intb = mock.Mock()
 
-        self.ovs_bridges = {self.INT_BRIDGE: mock.Mock(),
-                            self.TUN_BRIDGE: mock.Mock(),
-                            self.MAP_TUN_BRIDGE: mock.Mock(),
-                            }
+        self.ovs_bridges = {
+            self.INT_BRIDGE: mock.create_autospec(
+                self.br_int_cls('br-int')),
+            self.TUN_BRIDGE: mock.create_autospec(
+                self.br_tun_cls('br-tun')),
+            self.MAP_TUN_BRIDGE: mock.create_autospec(
+                self.br_phys_cls('br-phys')),
+        }
         self.ovs_int_ofports = {
             'patch-tun': self.TUN_OFPORT,
             'int-%s' % self.MAP_TUN_BRIDGE: self.MAP_TUN_INT_OFPORT
         }
 
-        self.mock_bridge = mock.patch.object(ovs_lib, 'OVSBridge').start()
-        self.mock_bridge.side_effect = (lambda br_name:
-                                        self.ovs_bridges[br_name])
+        def lookup_br(br_name, *args, **kwargs):
+            return self.ovs_bridges[br_name]
+
+        self.mock_int_bridge_cls = mock.patch(self._BR_INT_CLASS,
+                                              autospec=True).start()
+        self.mock_int_bridge_cls.side_effect = lookup_br
+        self.mock_phys_bridge_cls = mock.patch(self._BR_PHYS_CLASS,
+                                               autospec=True).start()
+        self.mock_phys_bridge_cls.side_effect = lookup_br
+        self.mock_tun_bridge_cls = mock.patch(self._BR_TUN_CLASS,
+                                              autospec=True).start()
+        self.mock_tun_bridge_cls.side_effect = lookup_br
 
         self.mock_int_bridge = self.ovs_bridges[self.INT_BRIDGE]
         self.mock_int_bridge.add_port.return_value = self.MAP_TUN_INT_OFPORT
@@ -139,10 +153,14 @@ class TunnelTest(base.BaseTestCase):
 
         self._define_expected_calls()
 
-    def _define_expected_calls(self):
-        self.mock_bridge_expected = [
+    def _define_expected_calls(self, arp_responder=False):
+        self.mock_int_bridge_cls_expected = [
             mock.call(self.INT_BRIDGE),
+        ]
+        self.mock_phys_bridge_cls_expected = [
             mock.call(self.MAP_TUN_BRIDGE),
+        ]
+        self.mock_tun_bridge_cls_expected = [
             mock.call(self.TUN_BRIDGE),
         ]
 
@@ -150,20 +168,17 @@ class TunnelTest(base.BaseTestCase):
         self.mock_int_bridge_expected = [
             mock.call.create(),
             mock.call.set_secure_mode(),
+            mock.call.setup_controllers(mock.ANY),
             mock.call.delete_port('patch-tun'),
-            mock.call.remove_all_flows(),
-            mock.call.add_flow(priority=1, actions='normal'),
-            mock.call.add_flow(priority=0, table=constants.CANARY_TABLE,
-                               actions='drop'),
+            mock.call.setup_default_table(),
         ]
 
         self.mock_map_tun_bridge_expected = [
-            mock.call.remove_all_flows(),
-            mock.call.add_flow(priority=1, actions='normal'),
+            mock.call.setup_controllers(mock.ANY),
+            mock.call.setup_default_table(),
             mock.call.delete_port('phy-%s' % self.MAP_TUN_BRIDGE),
             mock.call.add_patch_port('phy-%s' % self.MAP_TUN_BRIDGE,
-                                     constants.NONEXISTENT_PEER),
-        ]
+                                     constants.NONEXISTENT_PEER), ]
         self.mock_int_bridge_expected += [
             mock.call.delete_port('int-%s' % self.MAP_TUN_BRIDGE),
             mock.call.add_patch_port('int-%s' % self.MAP_TUN_BRIDGE,
@@ -171,17 +186,13 @@ class TunnelTest(base.BaseTestCase):
         ]
 
         self.mock_int_bridge_expected += [
-            mock.call.add_flow(priority=2,
-                               in_port=self.MAP_TUN_INT_OFPORT,
-                               actions='drop'),
+            mock.call.drop_port(in_port=self.MAP_TUN_INT_OFPORT),
             mock.call.set_db_attribute(
                 'Interface', 'int-%s' % self.MAP_TUN_BRIDGE,
                 'options:peer', 'phy-%s' % self.MAP_TUN_BRIDGE),
         ]
         self.mock_map_tun_bridge_expected += [
-            mock.call.add_flow(priority=2,
-                               in_port=self.MAP_TUN_PHY_OFPORT,
-                               actions='drop'),
+            mock.call.drop_port(in_port=self.MAP_TUN_PHY_OFPORT),
             mock.call.set_db_attribute(
                 'Interface', 'phy-%s' % self.MAP_TUN_BRIDGE,
                 'options:peer', 'int-%s' % self.MAP_TUN_BRIDGE),
@@ -189,6 +200,7 @@ class TunnelTest(base.BaseTestCase):
 
         self.mock_tun_bridge_expected = [
             mock.call.reset_bridge(secure_mode=True),
+            mock.call.setup_controllers(mock.ANY),
             mock.call.add_patch_port('patch-int', 'patch-tun'),
         ]
         self.mock_int_bridge_expected += [
@@ -199,48 +211,8 @@ class TunnelTest(base.BaseTestCase):
         ]
 
         self.mock_tun_bridge_expected += [
-            mock.call.remove_all_flows(),
-            mock.call.add_flow(priority=1,
-                               actions="resubmit(,%s)" %
-                               constants.PATCH_LV_TO_TUN,
-                               in_port=self.INT_OFPORT),
-            mock.call.add_flow(priority=0, actions="drop"),
-            mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN,
-                               dl_dst=UCAST_MAC,
-                               actions="resubmit(,%s)" %
-                               constants.UCAST_TO_TUN),
-            mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN,
-                               dl_dst=BCAST_MAC,
-                               actions="resubmit(,%s)" %
-                               constants.FLOOD_TO_TUN),
-        ]
-        for tunnel_type in constants.TUNNEL_NETWORK_TYPES:
-            self.mock_tun_bridge_expected.append(
-                mock.call.add_flow(
-                    table=constants.TUN_TABLE[tunnel_type],
-                    priority=0,
-                    actions="drop"))
-        learned_flow = ("table=%s,"
-                        "priority=1,"
-                        "hard_timeout=300,"
-                        "NXM_OF_VLAN_TCI[0..11],"
-                        "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],"
-                        "load:0->NXM_OF_VLAN_TCI[],"
-                        "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],"
-                        "output:NXM_OF_IN_PORT[]" %
-                        constants.UCAST_TO_TUN)
-        self.mock_tun_bridge_expected += [
-            mock.call.add_flow(table=constants.LEARN_FROM_TUN,
-                               priority=1,
-                               actions="learn(%s),output:%s" %
-                               (learned_flow, self.INT_OFPORT)),
-            mock.call.add_flow(table=constants.UCAST_TO_TUN,
-                               priority=0,
-                               actions="resubmit(,%s)" %
-                               constants.FLOOD_TO_TUN),
-            mock.call.add_flow(table=constants.FLOOD_TO_TUN,
-                               priority=0,
-                               actions="drop")
+            mock.call.delete_flows(),
+            mock.call.setup_default_table(self.INT_OFPORT, arp_responder),
         ]
 
         self.device_exists_expected = []
@@ -255,6 +227,12 @@ class TunnelTest(base.BaseTestCase):
         self.execute_expected = []
 
     def _build_agent(self, **kwargs):
+        bridge_classes = {
+            'br_int': self.mock_int_bridge_cls,
+            'br_phys': self.mock_phys_bridge_cls,
+            'br_tun': self.mock_tun_bridge_cls,
+        }
+        kwargs.setdefault('bridge_classes', bridge_classes)
         kwargs.setdefault('integ_br', self.INT_BRIDGE)
         kwargs.setdefault('tun_br', self.TUN_BRIDGE)
         kwargs.setdefault('local_ip', '10.0.0.1')
@@ -264,14 +242,19 @@ class TunnelTest(base.BaseTestCase):
         kwargs.setdefault('veth_mtu', self.VETH_MTU)
         kwargs.setdefault('use_veth_interconnection',
                           self.USE_VETH_INTERCONNECTION)
-        return ovs_neutron_agent.OVSNeutronAgent(**kwargs)
+        return self.mod_agent.OVSNeutronAgent(**kwargs)
 
     def _verify_mock_call(self, mock_obj, expected):
         mock_obj.assert_has_calls(expected)
         self.assertEqual(len(mock_obj.mock_calls), len(expected))
 
     def _verify_mock_calls(self):
-        self._verify_mock_call(self.mock_bridge, self.mock_bridge_expected)
+        self._verify_mock_call(self.mock_int_bridge_cls,
+                               self.mock_int_bridge_cls_expected)
+        self._verify_mock_call(self.mock_tun_bridge_cls,
+                               self.mock_tun_bridge_cls_expected)
+        self._verify_mock_call(self.mock_phys_bridge_cls,
+                               self.mock_phys_bridge_cls_expected)
         self._verify_mock_call(self.mock_int_bridge,
                                self.mock_int_bridge_expected)
         self._verify_mock_call(self.mock_map_tun_bridge,
@@ -296,20 +279,7 @@ class TunnelTest(base.BaseTestCase):
     #                 The next two tests use l2_pop flag to test ARP responder
     def test_construct_with_arp_responder(self):
         self._build_agent(l2_population=True, arp_responder=True)
-        self.mock_tun_bridge_expected.insert(
-            5, mock.call.add_flow(table=constants.PATCH_LV_TO_TUN,
-                                  priority=1,
-                                  proto="arp",
-                                  dl_dst="ff:ff:ff:ff:ff:ff",
-                                  actions="resubmit(,%s)" %
-                                  constants.ARP_RESPONDER)
-        )
-        self.mock_tun_bridge_expected.insert(
-            12, mock.call.add_flow(table=constants.ARP_RESPONDER,
-                                   priority=0,
-                                   actions="resubmit(,%s)" %
-                                   constants.FLOOD_TO_TUN)
-        )
+        self._define_expected_calls(True)
         self._verify_mock_calls()
 
     def test_construct_without_arp_responder(self):
@@ -321,18 +291,13 @@ class TunnelTest(base.BaseTestCase):
         self._verify_mock_calls()
 
     def test_provision_local_vlan(self):
-        ofports = ','.join(TUN_OFPORTS[p_const.TYPE_GRE].values())
+        ofports = TUN_OFPORTS[p_const.TYPE_GRE].values()
         self.mock_tun_bridge_expected += [
-            mock.call.mod_flow(table=constants.FLOOD_TO_TUN,
-                               dl_vlan=LV_ID,
-                               actions="strip_vlan,"
-                               "set_tunnel:%s,output:%s" %
-                               (LS_ID, ofports)),
-            mock.call.add_flow(table=constants.TUN_TABLE['gre'],
-                               priority=1,
-                               tun_id=LS_ID,
-                               actions="mod_vlan_vid:%s,resubmit(,%s)" %
-                               (LV_ID, constants.LEARN_FROM_TUN)),
+            mock.call.install_flood_to_tun(LV_ID, LS_ID, ofports),
+            mock.call.provision_local_vlan(
+                network_type=p_const.TYPE_GRE,
+                lvid=LV_ID,
+                segmentation_id=LS_ID),
         ]
 
         a = self._build_agent()
@@ -342,15 +307,17 @@ class TunnelTest(base.BaseTestCase):
         self._verify_mock_calls()
 
     def test_provision_local_vlan_flat(self):
-        action_string = 'strip_vlan,normal'
         self.mock_map_tun_bridge_expected.append(
-            mock.call.add_flow(priority=4, in_port=self.MAP_TUN_PHY_OFPORT,
-                               dl_vlan=LV_ID, actions=action_string))
-
-        action_string = 'mod_vlan_vid:%s,normal' % LV_ID
+            mock.call.provision_local_vlan(
+                port=self.MAP_TUN_PHY_OFPORT,
+                lvid=LV_ID,
+                segmentation_id=None,
+                distributed=False))
         self.mock_int_bridge_expected.append(
-            mock.call.add_flow(priority=3, in_port=self.INT_OFPORT,
-                               dl_vlan=65535, actions=action_string))
+            mock.call.provision_local_vlan(
+                port=self.INT_OFPORT,
+                lvid=LV_ID,
+                segmentation_id=None))
 
         a = self._build_agent()
         a.available_local_vlans = set([LV_ID])
@@ -366,16 +333,17 @@ class TunnelTest(base.BaseTestCase):
         self._verify_mock_calls()
 
     def test_provision_local_vlan_vlan(self):
-        action_string = 'mod_vlan_vid:%s,normal' % LS_ID
         self.mock_map_tun_bridge_expected.append(
-            mock.call.add_flow(priority=4, in_port=self.MAP_TUN_PHY_OFPORT,
-                               dl_vlan=LV_ID, actions=action_string))
-
-        action_string = 'mod_vlan_vid:%s,normal' % LV_ID
+            mock.call.provision_local_vlan(
+                port=self.MAP_TUN_PHY_OFPORT,
+                lvid=LV_ID,
+                segmentation_id=LS_ID,
+                distributed=False))
         self.mock_int_bridge_expected.append(
-            mock.call.add_flow(priority=3, in_port=self.INT_OFPORT,
-                               dl_vlan=LS_ID, actions=action_string))
-
+            mock.call.provision_local_vlan(
+                port=self.INT_OFPORT,
+                lvid=LV_ID,
+                segmentation_id=LS_ID))
         a = self._build_agent()
         a.available_local_vlans = set([LV_ID])
         a.phys_brs['net1'] = self.mock_map_tun_bridge
@@ -391,54 +359,58 @@ class TunnelTest(base.BaseTestCase):
 
     def test_reclaim_local_vlan(self):
         self.mock_tun_bridge_expected += [
-            mock.call.delete_flows(
-                table=constants.TUN_TABLE['gre'], tun_id=LS_ID),
-            mock.call.delete_flows(dl_vlan=LVM.vlan)
+            mock.call.reclaim_local_vlan(network_type='gre',
+                                         segmentation_id=LS_ID),
+            mock.call.delete_flood_to_tun(LV_ID),
+            mock.call.delete_unicast_to_tun(LV_ID, None),
+            mock.call.delete_arp_responder(LV_ID, None),
         ]
 
         a = self._build_agent()
         a.available_local_vlans = set()
-        a.local_vlan_map[NET_UUID] = LVM
+        a.local_vlan_map[NET_UUID] = self.LVM
         a.reclaim_local_vlan(NET_UUID)
-        self.assertIn(LVM.vlan, a.available_local_vlans)
+        self.assertIn(self.LVM.vlan, a.available_local_vlans)
         self._verify_mock_calls()
 
     def test_reclaim_local_vlan_flat(self):
         self.mock_map_tun_bridge_expected.append(
-            mock.call.delete_flows(
-                in_port=self.MAP_TUN_PHY_OFPORT, dl_vlan=LVM_FLAT.vlan))
+            mock.call.reclaim_local_vlan(
+                port=self.MAP_TUN_PHY_OFPORT,
+                lvid=self.LVM_FLAT.vlan))
         self.mock_int_bridge_expected.append(
-            mock.call.delete_flows(
-                dl_vlan=65535, in_port=self.INT_OFPORT))
-
+            mock.call.reclaim_local_vlan(
+                port=self.INT_OFPORT,
+                segmentation_id=None))
         a = self._build_agent()
         a.phys_brs['net1'] = self.mock_map_tun_bridge
         a.phys_ofports['net1'] = self.MAP_TUN_PHY_OFPORT
         a.int_ofports['net1'] = self.INT_OFPORT
 
         a.available_local_vlans = set()
-        a.local_vlan_map[NET_UUID] = LVM_FLAT
+        a.local_vlan_map[NET_UUID] = self.LVM_FLAT
         a.reclaim_local_vlan(NET_UUID)
-        self.assertIn(LVM_FLAT.vlan, a.available_local_vlans)
+        self.assertIn(self.LVM_FLAT.vlan, a.available_local_vlans)
         self._verify_mock_calls()
 
     def test_reclaim_local_vlan_vlan(self):
         self.mock_map_tun_bridge_expected.append(
-            mock.call.delete_flows(
-                in_port=self.MAP_TUN_PHY_OFPORT, dl_vlan=LVM_VLAN.vlan))
+            mock.call.reclaim_local_vlan(
+                port=self.MAP_TUN_PHY_OFPORT,
+                lvid=self.LVM_VLAN.vlan))
         self.mock_int_bridge_expected.append(
-            mock.call.delete_flows(
-                dl_vlan=LS_ID, in_port=self.INT_OFPORT))
-
+            mock.call.reclaim_local_vlan(
+                port=self.INT_OFPORT,
+                segmentation_id=LS_ID))
         a = self._build_agent()
         a.phys_brs['net1'] = self.mock_map_tun_bridge
         a.phys_ofports['net1'] = self.MAP_TUN_PHY_OFPORT
         a.int_ofports['net1'] = self.INT_OFPORT
 
         a.available_local_vlans = set()
-        a.local_vlan_map[NET_UUID] = LVM_VLAN
+        a.local_vlan_map[NET_UUID] = self.LVM_VLAN
         a.reclaim_local_vlan(NET_UUID)
-        self.assertIn(LVM_VLAN.vlan, a.available_local_vlans)
+        self.assertIn(self.LVM_VLAN.vlan, a.available_local_vlans)
         self._verify_mock_calls()
 
     def test_port_bound(self):
@@ -453,17 +425,18 @@ class TunnelTest(base.BaseTestCase):
                                        vlan_mapping)]
 
         a = self._build_agent()
-        a.local_vlan_map[NET_UUID] = LVM
+        a.local_vlan_map[NET_UUID] = self.LVM
         a.local_dvr_map = {}
+        self.ovs_bridges[self.INT_BRIDGE].db_get_val.return_value = {}
         a.port_bound(VIF_PORT, NET_UUID, 'gre', None, LS_ID,
                      FIXED_IPS, VM_DEVICE_OWNER, False)
         self._verify_mock_calls()
 
     def test_port_unbound(self):
-        with mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+        with mock.patch.object(self.mod_agent.OVSNeutronAgent,
                                'reclaim_local_vlan') as reclaim_local_vlan:
             a = self._build_agent()
-            a.local_vlan_map[NET_UUID] = LVM
+            a.local_vlan_map[NET_UUID] = self.LVM
             a.port_unbound(VIF_ID, NET_UUID)
 
         reclaim_local_vlan.assert_called_once_with(NET_UUID)
@@ -474,14 +447,14 @@ class TunnelTest(base.BaseTestCase):
             mock.call.db_get_val('Port', VIF_PORT.port_name, 'tag'),
             mock.call.set_db_attribute(
                 'Port', VIF_PORT.port_name,
-                'tag', ovs_neutron_agent.DEAD_VLAN_TAG),
-            mock.call.add_flow(priority=2, in_port=VIF_PORT.ofport,
-                               actions='drop')
+                'tag', self.mod_agent.DEAD_VLAN_TAG),
+            mock.call.drop_port(in_port=VIF_PORT.ofport),
         ]
 
         a = self._build_agent()
         a.available_local_vlans = set([LV_ID])
-        a.local_vlan_map[NET_UUID] = LVM
+        a.local_vlan_map[NET_UUID] = self.LVM
+        self.ovs_bridges[self.INT_BRIDGE].db_get_val.return_value = mock.Mock()
         a.port_dead(VIF_PORT)
         self._verify_mock_calls()
 
@@ -491,8 +464,7 @@ class TunnelTest(base.BaseTestCase):
         self.mock_tun_bridge_expected += [
             mock.call.add_tunnel_port('gre-0a000a01', '10.0.10.1', '10.0.0.1',
                                       'gre', 4789, True),
-            mock.call.add_flow(priority=1, in_port=tunnel_port,
-                               actions='resubmit(,3)')
+            mock.call.setup_tunnel_port('gre', tunnel_port),
         ]
 
         a = self._build_agent()
@@ -517,20 +489,22 @@ class TunnelTest(base.BaseTestCase):
                   'removed': set(['tap0'])}
 
         self.mock_int_bridge_expected += [
-            mock.call.dump_flows_for_table(constants.CANARY_TABLE),
-            mock.call.dump_flows_for_table(constants.CANARY_TABLE)
+            mock.call.check_canary_table(),
+            mock.call.check_canary_table()
         ]
 
+        self.ovs_bridges[self.INT_BRIDGE].check_canary_table.return_value = \
+            constants.OVS_NORMAL
         with contextlib.nested(
             mock.patch.object(log.KeywordArgumentAdapter, 'exception'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'scan_ports'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'process_network_ports'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'tunnel_sync'),
             mock.patch.object(time, 'sleep'),
-            mock.patch.object(ovs_neutron_agent.OVSNeutronAgent,
+            mock.patch.object(self.mod_agent.OVSNeutronAgent,
                               'update_stale_ofport_rules')
         ) as (log_exception, scan_ports, process_network_ports,
               ts, time_sleep, update_stale):
@@ -569,29 +543,35 @@ class TunnelTest(base.BaseTestCase):
         self._verify_mock_calls()
 
 
+class TunnelTestOFCtl(TunnelTest, ovs_test_base.OVSOFCtlTestBase):
+    pass
+
+
 class TunnelTestUseVethInterco(TunnelTest):
     USE_VETH_INTERCONNECTION = True
 
-    def _define_expected_calls(self):
-        self.mock_bridge_expected = [
+    def _define_expected_calls(self, arp_responder=False):
+        self.mock_int_bridge_cls_expected = [
             mock.call(self.INT_BRIDGE),
+        ]
+        self.mock_phys_bridge_cls_expected = [
             mock.call(self.MAP_TUN_BRIDGE),
+        ]
+        self.mock_tun_bridge_cls_expected = [
             mock.call(self.TUN_BRIDGE),
         ]
 
         self.mock_int_bridge_expected = [
             mock.call.create(),
             mock.call.set_secure_mode(),
+            mock.call.setup_controllers(mock.ANY),
             mock.call.delete_port('patch-tun'),
-            mock.call.remove_all_flows(),
-            mock.call.add_flow(priority=1, actions='normal'),
-            mock.call.add_flow(table=constants.CANARY_TABLE, priority=0,
-                               actions="drop")
+            mock.call.setup_default_table(),
         ]
 
         self.mock_map_tun_bridge_expected = [
-            mock.call.remove_all_flows(),
-            mock.call.add_flow(priority=1, actions='normal'),
+            mock.call.setup_controllers(mock.ANY),
+            mock.call.setup_default_table(),
             mock.call.delete_port('phy-%s' % self.MAP_TUN_BRIDGE),
             mock.call.add_port(self.intb),
         ]
@@ -601,18 +581,15 @@ class TunnelTestUseVethInterco(TunnelTest):
         ]
 
         self.mock_int_bridge_expected += [
-            mock.call.add_flow(priority=2,
-                               in_port=self.MAP_TUN_INT_OFPORT,
-                               actions='drop')
+            mock.call.drop_port(in_port=self.MAP_TUN_INT_OFPORT),
         ]
         self.mock_map_tun_bridge_expected += [
-            mock.call.add_flow(priority=2,
-                               in_port=self.MAP_TUN_PHY_OFPORT,
-                               actions='drop')
+            mock.call.drop_port(in_port=self.MAP_TUN_PHY_OFPORT),
         ]
 
         self.mock_tun_bridge_expected = [
             mock.call.reset_bridge(secure_mode=True),
+            mock.call.setup_controllers(mock.ANY),
             mock.call.add_patch_port('patch-int', 'patch-tun'),
         ]
         self.mock_int_bridge_expected += [
@@ -622,50 +599,8 @@ class TunnelTestUseVethInterco(TunnelTest):
             mock.call.get_vif_ports(),
         ]
         self.mock_tun_bridge_expected += [
-            mock.call.remove_all_flows(),
-            mock.call.add_flow(priority=1,
-                               in_port=self.INT_OFPORT,
-                               actions="resubmit(,%s)" %
-                               constants.PATCH_LV_TO_TUN),
-            mock.call.add_flow(priority=0, actions='drop'),
-            mock.call.add_flow(priority=0,
-                               table=constants.PATCH_LV_TO_TUN,
-                               dl_dst=UCAST_MAC,
-                               actions="resubmit(,%s)" %
-                               constants.UCAST_TO_TUN),
-            mock.call.add_flow(priority=0,
-                               table=constants.PATCH_LV_TO_TUN,
-                               dl_dst=BCAST_MAC,
-                               actions="resubmit(,%s)" %
-                               constants.FLOOD_TO_TUN),
-        ]
-        for tunnel_type in constants.TUNNEL_NETWORK_TYPES:
-            self.mock_tun_bridge_expected.append(
-                mock.call.add_flow(
-                    table=constants.TUN_TABLE[tunnel_type],
-                    priority=0,
-                    actions="drop"))
-        learned_flow = ("table=%s,"
-                        "priority=1,"
-                        "hard_timeout=300,"
-                        "NXM_OF_VLAN_TCI[0..11],"
-                        "NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],"
-                        "load:0->NXM_OF_VLAN_TCI[],"
-                        "load:NXM_NX_TUN_ID[]->NXM_NX_TUN_ID[],"
-                        "output:NXM_OF_IN_PORT[]" %
-                        constants.UCAST_TO_TUN)
-        self.mock_tun_bridge_expected += [
-            mock.call.add_flow(table=constants.LEARN_FROM_TUN,
-                               priority=1,
-                               actions="learn(%s),output:%s" %
-                               (learned_flow, self.INT_OFPORT)),
-            mock.call.add_flow(table=constants.UCAST_TO_TUN,
-                               priority=0,
-                               actions="resubmit(,%s)" %
-                               constants.FLOOD_TO_TUN),
-            mock.call.add_flow(table=constants.FLOOD_TO_TUN,
-                               priority=0,
-                               actions="drop")
+            mock.call.delete_flows(),
+            mock.call.setup_default_table(self.INT_OFPORT, arp_responder),
         ]
 
         self.device_exists_expected = [
@@ -690,10 +625,20 @@ class TunnelTestUseVethInterco(TunnelTest):
                                             '--timeout=10'])]
 
 
+class TunnelTestUseVethIntercoOFCtl(TunnelTestUseVethInterco,
+                                    ovs_test_base.OVSOFCtlTestBase):
+    pass
+
+
 class TunnelTestWithMTU(TunnelTestUseVethInterco):
     VETH_MTU = 1500
 
-    def _define_expected_calls(self):
-        super(TunnelTestWithMTU, self)._define_expected_calls()
+    def _define_expected_calls(self, arp_responder=False):
+        super(TunnelTestWithMTU, self)._define_expected_calls(arp_responder)
         self.inta_expected.append(mock.call.link.set_mtu(self.VETH_MTU))
         self.intb_expected.append(mock.call.link.set_mtu(self.VETH_MTU))
+
+
+class TunnelTestWithMTUOFCtl(TunnelTestWithMTU,
+                             ovs_test_base.OVSOFCtlTestBase):
+    pass