]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
OVS-agent: Introduce Ryu based OpenFlow implementation
authorYAMAMOTO Takashi <yamamoto@valinux.co.jp>
Mon, 2 Mar 2015 07:40:11 +0000 (16:40 +0900)
committerIWAMOTO Toshihiro <iwamoto@valinux.co.jp>
Wed, 2 Sep 2015 05:05:18 +0000 (14:05 +0900)
Introduce an alternative OpenFlow implementation, "native",
implemented using Ryu ofproto python library from Ryu SDN Framework.
Make it selectable with of_driver=native agent option.
The aim is to replace the existing ovs-ofctl based implementation
eventually.

It introduces node-local OpenFlow controller embedded in
OVS agent.  Benefits include:
* Reduce the overhead of invoking ovs-ofctl command (and associated
  rootwrap)
* Make future uses of OpenFlow asynchronous messages (e.g. Packet-In,
  Port-Status, etc) easier
* Make XenAPI integration simpler

Highlights:
* Switch to OpenFlow 1.3.
* Make OVS-agent act as an OpenFlow controller
* Configure OVS on the node to connect to the controller

DocImpact

Implements: blueprint ovs-ofctl-to-python
Co-Authored-by: IWAMOTO Toshihiro <iwamoto@valinux.co.jp>
Change-Id: I02e65ea7c6083b2c0a686fed2ab04da4d92b21a3

29 files changed:
etc/neutron/plugins/ml2/openvswitch_agent.ini
etc/neutron/rootwrap.d/openvswitch-plugin.filters
neutron/agent/common/ovs_lib.py
neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py
neutron/plugins/ml2/drivers/openvswitch/agent/main.py
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/__init__.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_dvr_process.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_phys.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_tun.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/main.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ofswitch.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_ryuapp.py [new file with mode: 0644]
neutron/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/br_tun.py
neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py
neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/netwrap
neutron/tests/functional/agent/test_ovs_flows.py
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/fake_oflib.py [new file with mode: 0644]
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/__init__.py [new file with mode: 0644]
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py [new file with mode: 0644]
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py [new file with mode: 0644]
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py [new file with mode: 0644]
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py [new file with mode: 0644]
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/ovs_test_base.py
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py
neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py
requirements.txt

index 99cbaca5465a35efd4de17397e664b3078ac5a68..323ed3d740a5605e324aeb46e96597acdf864758 100644 (file)
 # ovsdb_connection = tcp:127.0.0.1:6640
 
 # (StrOpt) OpenFlow interface to use.
-# 'ovs-ofctl' is currently the only available choice.
+# 'ovs-ofctl' or 'native'.
 # of_interface = ovs-ofctl
+#
+# (IPOpt)
+# Address to listen on for OpenFlow connections.
+# Used only for 'native' driver.
+# of_listen_address = 127.0.0.1
+#
+# (IntOpt)
+# Port to listen on for OpenFlow connections.
+# Used only for 'native' driver.
+# of_listen_port = 6633
+#
+# (IntOpt)
+# Timeout in seconds to wait for the local switch connecting the controller.
+# Used only for 'native' driver.
+# of_connect_timeout=30
+#
+# (IntOpt)
+# Timeout in seconds to wait for a single OpenFlow request.
+# Used only for 'native' driver.
+# of_request_timeout=10
 
 # (StrOpt) ovs datapath to use.
 # 'system' is the default value and corresponds to the kernel datapath.
index ed7f1ce78c2cea432e4b316bc0f35e1f2feb7e7b..c738733bb42f75a4b7951c2d426de461e491abc5 100644 (file)
@@ -12,6 +12,7 @@
 # unclear whether both variants are necessary, but I'm transliterating
 # from the old mechanism
 ovs-vsctl: CommandFilter, ovs-vsctl, root
+# NOTE(yamamoto): of_interface=native doesn't use ovs-ofctl
 ovs-ofctl: CommandFilter, ovs-ofctl, root
 kill_ovsdb_client: KillFilter, root, /usr/bin/ovsdb-client, -9
 ovsdb-client: CommandFilter, ovsdb-client, root
index 930558212d0dcd22c755a37846ecd26d32a5827c..76f0b4478d9e98cdd66cee98b44d7c669d5afbac 100644 (file)
@@ -152,7 +152,7 @@ class OVSBridge(BaseOVS):
         super(OVSBridge, self).__init__()
         self.br_name = br_name
         self.datapath_type = datapath_type
-        self.agent_uuid_stamp = '0x0'
+        self.agent_uuid_stamp = 0
 
     def set_agent_uuid_stamp(self, val):
         self.agent_uuid_stamp = val
index 56e86f766427dfead52c0ba3ba97775e4cefc7f4..34685c53a2e8f2ed0172e6c7b6b975532831c03b 100644 (file)
@@ -45,12 +45,27 @@ 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'],
+    cfg.StrOpt('of_interface', default='ovs-ofctl',
+               choices=['ovs-ofctl', 'native'],
                help=_("OpenFlow interface to use.")),
     cfg.StrOpt('datapath_type', default=constants.OVS_DATAPATH_SYSTEM,
                choices=[constants.OVS_DATAPATH_SYSTEM,
                         constants.OVS_DATAPATH_NETDEV],
                help=_("OVS datapath to use.")),
+    cfg.IPOpt('of_listen_address', default='127.0.0.1',
+              help=_("Address to listen on for OpenFlow connections. "
+                     "Used only for 'native' driver.")),
+    cfg.IntOpt('of_listen_port', default=6633,
+               help=_("Port to listen on for OpenFlow connections. "
+                      "Used only for 'native' driver.")),
+    cfg.IntOpt('of_connect_timeout', default=30,
+               help=_("Timeout in seconds to wait for "
+                      "the local switch connecting the controller. "
+                      "Used only for 'native' driver.")),
+    cfg.IntOpt('of_request_timeout', default=10,
+               help=_("Timeout in seconds to wait for a single "
+                      "OpenFlow request. "
+                      "Used only for 'native' driver.")),
 ]
 
 agent_opts = [
index b9bd5a96b5fd676ad90b1792b3e8c9f19b0d6532..2fd965274ecdde12ee8b7fcef00d01fee3e2c5ad 100644 (file)
@@ -33,6 +33,8 @@ cfg.CONF.import_group('OVS', 'neutron.plugins.ml2.drivers.openvswitch.agent.'
 _main_modules = {
     'ovs-ofctl': 'neutron.plugins.ml2.drivers.openvswitch.agent.openflow.'
                  'ovs_ofctl.main',
+    'native': 'neutron.plugins.ml2.drivers.openvswitch.agent.openflow.'
+                 'native.main',
 }
 
 
diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/__init__.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_dvr_process.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_dvr_process.py
new file mode 100644 (file)
index 0000000..a5551c1
--- /dev/null
@@ -0,0 +1,113 @@
+# 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 ryu.lib.packet import ether_types
+from ryu.lib.packet import icmpv6
+from ryu.lib.packet import in_proto
+
+
+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.
+    """
+
+    @staticmethod
+    def _dvr_process_ipv4_match(ofp, ofpp, vlan_tag, gateway_ip):
+        return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+                             eth_type=ether_types.ETH_TYPE_ARP,
+                             arp_tpa=gateway_ip)
+
+    def install_dvr_process_ipv4(self, vlan_tag, gateway_ip):
+        # block ARP
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._dvr_process_ipv4_match(ofp, ofpp,
+            vlan_tag=vlan_tag, gateway_ip=gateway_ip)
+        self.install_drop(table_id=self.dvr_process_table_id,
+                          priority=3,
+                          match=match)
+
+    def delete_dvr_process_ipv4(self, vlan_tag, gateway_ip):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._dvr_process_ipv4_match(ofp, ofpp,
+            vlan_tag=vlan_tag, gateway_ip=gateway_ip)
+        self.delete_flows(table_id=self.dvr_process_table_id, match=match)
+
+    @staticmethod
+    def _dvr_process_ipv6_match(ofp, ofpp, vlan_tag, gateway_mac):
+        return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+                             eth_type=ether_types.ETH_TYPE_IPV6,
+                             ip_proto=in_proto.IPPROTO_ICMPV6,
+                             icmpv6_type=icmpv6.ND_ROUTER_ADVERT,
+                             eth_src=gateway_mac)
+
+    def install_dvr_process_ipv6(self, vlan_tag, gateway_mac):
+        # block RA
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._dvr_process_ipv6_match(ofp, ofpp,
+            vlan_tag=vlan_tag, gateway_mac=gateway_mac)
+        self.install_drop(table_id=self.dvr_process_table_id, priority=3,
+                          match=match)
+
+    def delete_dvr_process_ipv6(self, vlan_tag, gateway_mac):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._dvr_process_ipv6_match(ofp, ofpp,
+            vlan_tag=vlan_tag, gateway_mac=gateway_mac)
+        self.delete_flows(table_id=self.dvr_process_table_id, match=match)
+
+    @staticmethod
+    def _dvr_process_in_match(ofp, ofpp, vlan_tag, vif_mac):
+        return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+                             eth_dst=vif_mac)
+
+    @staticmethod
+    def _dvr_process_out_match(ofp, ofpp, vlan_tag, vif_mac):
+        return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+                             eth_src=vif_mac)
+
+    def install_dvr_process(self, vlan_tag, vif_mac, dvr_mac_address):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._dvr_process_in_match(ofp, ofpp,
+                                           vlan_tag=vlan_tag, vif_mac=vif_mac)
+        table_id = self.dvr_process_table_id
+        self.install_drop(table_id=table_id,
+                          priority=2,
+                          match=match)
+        match = self._dvr_process_out_match(ofp, ofpp,
+                                            vlan_tag=vlan_tag, vif_mac=vif_mac)
+        actions = [
+            ofpp.OFPActionSetField(eth_src=dvr_mac_address),
+        ]
+        instructions = [
+            ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
+            ofpp.OFPInstructionGotoTable(
+                table_id=self.dvr_process_next_table_id),
+        ]
+        self.install_instructions(table_id=table_id,
+                                  priority=1,
+                                  match=match,
+                                  instructions=instructions)
+
+    def delete_dvr_process(self, vlan_tag, vif_mac):
+        (_dp, ofp, ofpp) = self._get_dp()
+        table_id = self.dvr_process_table_id
+        match = self._dvr_process_in_match(ofp, ofpp,
+                                           vlan_tag=vlan_tag, vif_mac=vif_mac)
+        self.delete_flows(table_id=table_id, match=match)
+        match = self._dvr_process_out_match(ofp, ofpp,
+                                            vlan_tag=vlan_tag, vif_mac=vif_mac)
+        self.delete_flows(table_id=table_id, match=match)
diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py
new file mode 100644 (file)
index 0000000..76eaf86
--- /dev/null
@@ -0,0 +1,177 @@
+# 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 oslo_log import log as logging
+from ryu.lib.packet import ether_types
+
+from neutron.i18n import _LE
+from neutron.plugins.common import constants as p_const
+from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    import ovs_bridge
+
+
+LOG = logging.getLogger(__name__)
+
+
+class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge):
+    """openvswitch agent br-int specific logic."""
+
+    def setup_default_table(self):
+        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):
+        try:
+            flows = self.dump_flows(constants.CANARY_TABLE)
+        except RuntimeError:
+            LOG.exception(_LE("Failed to communicate with the switch"))
+            return constants.OVS_DEAD
+        if flows == []:
+            return constants.OVS_RESTARTED
+        return constants.OVS_NORMAL
+
+    @staticmethod
+    def _local_vlan_match(_ofp, ofpp, port, vlan_vid):
+        return ofpp.OFPMatch(in_port=port, vlan_vid=vlan_vid)
+
+    def provision_local_vlan(self, port, lvid, segmentation_id):
+        (_dp, ofp, ofpp) = self._get_dp()
+        if segmentation_id is None:
+            vlan_vid = ofp.OFPVID_NONE
+            actions = [ofpp.OFPActionPushVlan()]
+        else:
+            vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
+            actions = []
+        match = self._local_vlan_match(ofp, ofpp, port, vlan_vid)
+        actions += [
+            ofpp.OFPActionSetField(vlan_vid=lvid | ofp.OFPVID_PRESENT),
+            ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+        ]
+        self.install_apply_actions(priority=3,
+                                   match=match,
+                                   actions=actions)
+
+    def reclaim_local_vlan(self, port, segmentation_id):
+        (_dp, ofp, ofpp) = self._get_dp()
+        if segmentation_id is None:
+            vlan_vid = ofp.OFPVID_NONE
+        else:
+            vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
+        match = self._local_vlan_match(ofp, ofpp, port, vlan_vid)
+        self.delete_flows(match=match)
+
+    @staticmethod
+    def _dvr_to_src_mac_match(ofp, ofpp, vlan_tag, dst_mac):
+        return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+                             eth_dst=dst_mac)
+
+    @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)
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._dvr_to_src_mac_match(ofp, ofpp,
+                                           vlan_tag=vlan_tag, dst_mac=dst_mac)
+        actions = [
+            ofpp.OFPActionPopVlan(),
+            ofpp.OFPActionSetField(eth_src=gateway_mac),
+            ofpp.OFPActionOutput(dst_port, 0),
+        ]
+        self.install_apply_actions(table_id=table_id,
+                                   priority=4,
+                                   match=match,
+                                   actions=actions)
+
+    def delete_dvr_to_src_mac(self, network_type, vlan_tag, dst_mac):
+        table_id = self._dvr_to_src_mac_table_id(network_type)
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._dvr_to_src_mac_match(ofp, ofpp,
+                                           vlan_tag=vlan_tag, dst_mac=dst_mac)
+        self.delete_flows(table_id=table_id, match=match)
+
+    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):
+        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)
+
+    @staticmethod
+    def _arp_reply_match(ofp, ofpp, port):
+        return ofpp.OFPMatch(in_port=port,
+                             eth_type=ether_types.ETH_TYPE_ARP)
+
+    def install_arp_spoofing_protection(self, port, ip_addresses):
+        # allow ARP replies as long as they match addresses that actually
+        # belong to the port.
+        for ip in ip_addresses:
+            masked_ip = self._cidr_to_ryu(ip)
+            self.install_normal(table_id=constants.ARP_SPOOF_TABLE,
+                                priority=2,
+                                eth_type=ether_types.ETH_TYPE_ARP,
+                                arp_spa=masked_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.
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._arp_reply_match(ofp, ofpp, port=port)
+        self.install_goto(table_id=constants.LOCAL_SWITCHING,
+                          priority=10,
+                          match=match,
+                          dest_table_id=constants.ARP_SPOOF_TABLE)
+
+    def delete_arp_spoofing_protection(self, port):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._arp_reply_match(ofp, ofpp, port=port)
+        self.delete_flows(table_id=constants.LOCAL_SWITCHING,
+                          match=match)
+        self.delete_flows(table_id=constants.ARP_SPOOF_TABLE,
+                          in_port=port)
diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_phys.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_phys.py
new file mode 100644 (file)
index 0000000..a3aad0f
--- /dev/null
@@ -0,0 +1,67 @@
+# 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.ml2.drivers.openvswitch.agent.common import constants
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    import br_dvr_process
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    import ovs_bridge
+
+
+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()
+
+    @staticmethod
+    def _local_vlan_match(ofp, ofpp, port, lvid):
+        return ofpp.OFPMatch(in_port=port, vlan_vid=lvid | ofp.OFPVID_PRESENT)
+
+    def provision_local_vlan(self, port, lvid, segmentation_id, distributed):
+        table_id = constants.LOCAL_VLAN_TRANSLATION if distributed else 0
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._local_vlan_match(ofp, ofpp, port, lvid)
+        if segmentation_id is None:
+            actions = [ofpp.OFPActionPopVlan()]
+        else:
+            vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
+            actions = [ofpp.OFPActionSetField(vlan_vid=vlan_vid)]
+        actions += [ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0)]
+        self.install_apply_actions(table_id=table_id,
+                                   priority=4,
+                                   match=match,
+                                   actions=actions)
+
+    def reclaim_local_vlan(self, port, lvid):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._local_vlan_match(ofp, ofpp, port, lvid)
+        self.delete_flows(match=match)
+
+    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/ml2/drivers/openvswitch/agent/openflow/native/br_tun.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_tun.py
new file mode 100644 (file)
index 0000000..6682eb1
--- /dev/null
@@ -0,0 +1,288 @@
+# 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.
+
+from ryu.lib.packet import arp
+from ryu.lib.packet import ether_types
+
+from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    import br_dvr_process
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    import ovs_bridge
+
+
+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):
+        (dp, ofp, ofpp) = self._get_dp()
+
+        # Table 0 (default) will sort incoming traffic depending on in_port
+        self.install_goto(dest_table_id=constants.PATCH_LV_TO_TUN,
+                          priority=1,
+                          in_port=patch_int_ofport)
+        self.install_drop()  # default drop
+
+        if arp_responder_enabled:
+            # ARP broadcast-ed request go to the local ARP_RESPONDER table to
+            # be locally resolved
+            # REVISIT(yamamoto): add arp_op=arp.ARP_REQUEST matcher?
+            self.install_goto(dest_table_id=constants.ARP_RESPONDER,
+                              table_id=constants.PATCH_LV_TO_TUN,
+                              priority=1,
+                              eth_dst="ff:ff:ff:ff:ff:ff",
+                              eth_type=ether_types.ETH_TYPE_ARP)
+
+        # 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.install_goto(dest_table_id=constants.UCAST_TO_TUN,
+                          table_id=constants.PATCH_LV_TO_TUN,
+                          eth_dst=('00:00:00:00:00:00',
+                                   '01:00:00:00:00:00'))
+
+        # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding
+        self.install_goto(dest_table_id=constants.FLOOD_TO_TUN,
+                          table_id=constants.PATCH_LV_TO_TUN,
+                          eth_dst=('01:00:00:00:00:00',
+                                   '01:00:00:00:00:00'))
+
+        # 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.install_drop(table_id=constants.TUN_TABLE[tunnel_type])
+
+        # 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)
+        # Once remote mac addresses are learnt, output packet to patch_int
+        flow_specs = [
+            ofpp.NXFlowSpecMatch(src=('vlan_vid', 0),
+                                 dst=('vlan_vid', 0),
+                                 n_bits=12),
+            ofpp.NXFlowSpecMatch(src=('eth_src', 0),
+                                 dst=('eth_dst', 0),
+                                 n_bits=48),
+            ofpp.NXFlowSpecLoad(src=0,
+                                dst=('vlan_vid', 0),
+                                n_bits=12),
+            ofpp.NXFlowSpecLoad(src=('tunnel_id', 0),
+                                dst=('tunnel_id', 0),
+                                n_bits=64),
+            ofpp.NXFlowSpecOutput(src=('in_port', 0),
+                                  dst='',
+                                  n_bits=32),
+        ]
+        actions = [
+            ofpp.NXActionLearn(table_id=constants.UCAST_TO_TUN,
+                               cookie=self.agent_uuid_stamp,
+                               priority=1,
+                               hard_timeout=300,
+                               specs=flow_specs),
+            ofpp.OFPActionOutput(patch_int_ofport, 0),
+        ]
+        self.install_apply_actions(table_id=constants.LEARN_FROM_TUN,
+                                   priority=1,
+                                   actions=actions)
+
+        # 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.install_goto(dest_table_id=constants.FLOOD_TO_TUN,
+                          table_id=constants.UCAST_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.install_goto(dest_table_id=constants.FLOOD_TO_TUN,
+                              table_id=constants.ARP_RESPONDER)
+
+        # 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)
+
+    @staticmethod
+    def _local_vlan_match(_ofp, ofpp, tun_id):
+        return ofpp.OFPMatch(tunnel_id=tun_id)
+
+    def provision_local_vlan(self, network_type, lvid, segmentation_id,
+                             distributed=False):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._local_vlan_match(ofp, ofpp, segmentation_id)
+        table_id = constants.TUN_TABLE[network_type]
+        if distributed:
+            dest_table_id = constants.DVR_NOT_LEARN
+        else:
+            dest_table_id = constants.LEARN_FROM_TUN
+        actions = [
+            ofpp.OFPActionPushVlan(),
+            ofpp.OFPActionSetField(vlan_vid=lvid | ofp.OFPVID_PRESENT),
+        ]
+        instructions = [
+            ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
+            ofpp.OFPInstructionGotoTable(table_id=dest_table_id)]
+        self.install_instructions(table_id=table_id,
+                                  priority=1,
+                                  match=match,
+                                  instructions=instructions)
+
+    def reclaim_local_vlan(self, network_type, segmentation_id):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._local_vlan_match(ofp, ofpp, segmentation_id)
+        table_id = constants.TUN_TABLE[network_type]
+        self.delete_flows(table_id=table_id, match=match)
+
+    @staticmethod
+    def _flood_to_tun_match(ofp, ofpp, vlan):
+        return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT)
+
+    def install_flood_to_tun(self, vlan, tun_id, ports):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._flood_to_tun_match(ofp, ofpp, vlan)
+        actions = [ofpp.OFPActionPopVlan(),
+                   ofpp.OFPActionSetField(tunnel_id=tun_id)]
+        for port in ports:
+            actions.append(ofpp.OFPActionOutput(port, 0))
+        self.install_apply_actions(table_id=constants.FLOOD_TO_TUN,
+                                   priority=1,
+                                   match=match,
+                                   actions=actions)
+
+    def delete_flood_to_tun(self, vlan):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._flood_to_tun_match(ofp, ofpp, vlan)
+        self.delete_flows(table_id=constants.FLOOD_TO_TUN, match=match)
+
+    @staticmethod
+    def _unicast_to_tun_match(ofp, ofpp, vlan, mac):
+        return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT, eth_dst=mac)
+
+    def install_unicast_to_tun(self, vlan, tun_id, port, mac):
+        (_dp, ofp, ofpp) = self._get_dp()
+        match = self._unicast_to_tun_match(ofp, ofpp, vlan, mac)
+        actions = [ofpp.OFPActionPopVlan(),
+                   ofpp.OFPActionSetField(tunnel_id=tun_id),
+                   ofpp.OFPActionOutput(port, 0)]
+        self.install_apply_actions(table_id=constants.UCAST_TO_TUN,
+                                   priority=2,
+                                   match=match,
+                                   actions=actions)
+
+    def delete_unicast_to_tun(self, vlan, mac):
+        (_dp, ofp, ofpp) = self._get_dp()
+        if mac is None:
+            match = ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT)
+        else:
+            match = self._unicast_to_tun_match(ofp, ofpp, vlan, mac)
+        self.delete_flows(table_id=constants.UCAST_TO_TUN, match=match)
+
+    @staticmethod
+    def _arp_responder_match(ofp, ofpp, vlan, ip):
+        # REVISIT(yamamoto): add arp_op=arp.ARP_REQUEST matcher?
+        return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT,
+                             eth_type=ether_types.ETH_TYPE_ARP,
+                             arp_tpa=ip)
+
+    def install_arp_responder(self, vlan, ip, mac):
+        (dp, ofp, ofpp) = self._get_dp()
+        match = self._arp_responder_match(ofp, ofpp, vlan, ip)
+        actions = [ofpp.OFPActionSetField(arp_op=arp.ARP_REPLY),
+                   ofpp.NXActionRegMove(src_field='arp_sha',
+                                        dst_field='arp_tha',
+                                        n_bits=48),
+                   ofpp.NXActionRegMove(src_field='arp_spa',
+                                        dst_field='arp_tpa',
+                                        n_bits=32),
+                   ofpp.OFPActionSetField(arp_sha=mac),
+                   ofpp.OFPActionSetField(arp_spa=ip),
+                   ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0)]
+        self.install_apply_actions(table_id=constants.ARP_RESPONDER,
+                                   priority=1,
+                                   match=match,
+                                   actions=actions)
+
+    def delete_arp_responder(self, vlan, ip):
+        (_dp, ofp, ofpp) = self._get_dp()
+        if ip is None:
+            # REVISIT(yamamoto): add arp_op=arp.ARP_REQUEST matcher?
+            match = ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT,
+                                  eth_type=ether_types.ETH_TYPE_ARP)
+        else:
+            match = self._arp_responder_match(ofp, ofpp, vlan, ip)
+        self.delete_flows(table_id=constants.ARP_RESPONDER, match=match)
+
+    def setup_tunnel_port(self, network_type, port):
+        self.install_goto(dest_table_id=constants.TUN_TABLE[network_type],
+                          priority=1,
+                          in_port=port)
+
+    def cleanup_tunnel_port(self, port):
+        self.delete_flows(in_port=port)
+
+    def add_dvr_mac_tun(self, mac, port):
+        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):
+        # REVISIT(yamamoto): This is for API compat with "ovs-ofctl"
+        # interface.  Consider removing this mechanism when obsoleting
+        # "ovs-ofctl" interface.
+        # For "ovs-ofctl" interface, "deferred" mechanism would improve
+        # performance by batching flow-mods with a single ovs-ofctl command
+        # invocation.
+        # On the other hand, for this "native" interface, the overheads of
+        # each flow-mods are already minimum and batching doesn't make much
+        # sense.  Thus this method is left as no-op.
+        # It might be possible to send multiple flow-mods with a single
+        # barrier.  But it's unclear that level of performance optimization
+        # is desirable while it would certainly complicate error handling.
+        return self
+
+    def __enter__(self):
+        # REVISIT(yamamoto): See the comment on deferred().
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        # REVISIT(yamamoto): See the comment on deferred().
+        pass
diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/main.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/main.py
new file mode 100644 (file)
index 0000000..6f3bd7b
--- /dev/null
@@ -0,0 +1,37 @@
+# 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 oslo_config import cfg
+from ryu.base import app_manager
+from ryu import cfg as ryu_cfg
+
+
+cfg.CONF.import_group(
+    'OVS',
+    'neutron.plugins.ml2.drivers.openvswitch.agent.common.config')
+
+
+def init_config():
+    ryu_cfg.CONF(project='ryu', args=[])
+    ryu_cfg.CONF.ofp_listen_host = cfg.CONF.OVS.of_listen_address
+    ryu_cfg.CONF.ofp_tcp_listen_port = cfg.CONF.OVS.of_listen_port
+
+
+def main():
+    app_manager.AppManager.run_apps([
+        'neutron.plugins.ml2.drivers.openvswitch.agent.'
+        'openflow.native.ovs_ryuapp',
+    ])
diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ofswitch.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ofswitch.py
new file mode 100644 (file)
index 0000000..c95c76e
--- /dev/null
@@ -0,0 +1,202 @@
+# 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 eventlet
+import netaddr
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_utils import excutils
+from oslo_utils import timeutils
+import ryu.app.ofctl.api as ofctl_api
+import ryu.exception as ryu_exc
+
+from neutron.i18n import _LE, _LW
+
+LOG = logging.getLogger(__name__)
+
+
+class OpenFlowSwitchMixin(object):
+    """Mixin to provide common convenient routines for an openflow switch.
+
+    NOTE(yamamoto): super() points to ovs_lib.OVSBridge.
+    See ovs_bridge.py how this class is actually used.
+    """
+
+    @staticmethod
+    def _cidr_to_ryu(ip):
+        n = netaddr.IPNetwork(ip)
+        if n.hostmask:
+            return (str(n.ip), str(n.netmask))
+        return str(n.ip)
+
+    def __init__(self, *args, **kwargs):
+        self._app = kwargs.pop('ryu_app')
+        super(OpenFlowSwitchMixin, self).__init__(*args, **kwargs)
+
+    def _get_dp_by_dpid(self, dpid_int):
+        """Get Ryu datapath object for the switch."""
+        timeout_sec = cfg.CONF.OVS.of_connect_timeout
+        start_time = timeutils.now()
+        while True:
+            dp = ofctl_api.get_datapath(self._app, dpid_int)
+            if dp is not None:
+                break
+            # The switch has not established a connection to us.
+            # Wait for a little.
+            if timeutils.now() > start_time + timeout_sec:
+                m = _LE("Switch connection timeout")
+                LOG.error(m)
+                # NOTE(yamamoto): use RuntimeError for compat with ovs_lib
+                raise RuntimeError(m)
+            eventlet.sleep(1)
+        return dp
+
+    def _send_msg(self, msg, reply_cls=None, reply_multi=False):
+        timeout_sec = cfg.CONF.OVS.of_request_timeout
+        timeout = eventlet.timeout.Timeout(seconds=timeout_sec)
+        try:
+            result = ofctl_api.send_msg(self._app, msg, reply_cls, reply_multi)
+        except ryu_exc.RyuException as e:
+            m = _LE("ofctl request %(request)s error %(error)s") % {
+                "request": msg,
+                "error": e,
+            }
+            LOG.error(m)
+            # NOTE(yamamoto): use RuntimeError for compat with ovs_lib
+            raise RuntimeError(m)
+        except eventlet.timeout.Timeout as e:
+            with excutils.save_and_reraise_exception() as ctx:
+                if e is timeout:
+                    ctx.reraise = False
+                    m = _LE("ofctl request %(request)s timed out") % {
+                        "request": msg,
+                    }
+                    LOG.error(m)
+                    # NOTE(yamamoto): use RuntimeError for compat with ovs_lib
+                    raise RuntimeError(m)
+        finally:
+            timeout.cancel()
+        LOG.debug("ofctl request %(request)s result %(result)s",
+                  {"request": msg, "result": result})
+        return result
+
+    @staticmethod
+    def _match(_ofp, ofpp, match, **match_kwargs):
+        if match is not None:
+            return match
+        return ofpp.OFPMatch(**match_kwargs)
+
+    def delete_flows(self, table_id=None, strict=False, priority=0,
+                     cookie=0, cookie_mask=0,
+                     match=None, **match_kwargs):
+        (dp, ofp, ofpp) = self._get_dp()
+        if table_id is None:
+            table_id = ofp.OFPTT_ALL
+        match = self._match(ofp, ofpp, match, **match_kwargs)
+        if strict:
+            cmd = ofp.OFPFC_DELETE_STRICT
+        else:
+            cmd = ofp.OFPFC_DELETE
+        msg = ofpp.OFPFlowMod(dp,
+                              command=cmd,
+                              cookie=cookie,
+                              cookie_mask=cookie_mask,
+                              table_id=table_id,
+                              match=match,
+                              priority=priority,
+                              out_group=ofp.OFPG_ANY,
+                              out_port=ofp.OFPP_ANY)
+        self._send_msg(msg)
+
+    def dump_flows(self, table_id=None):
+        (dp, ofp, ofpp) = self._get_dp()
+        if table_id is None:
+            table_id = ofp.OFPTT_ALL
+        msg = ofpp.OFPFlowStatsRequest(dp, table_id=table_id)
+        replies = self._send_msg(msg,
+                                 reply_cls=ofpp.OFPFlowStatsReply,
+                                 reply_multi=True)
+        flows = []
+        for rep in replies:
+            flows += rep.body
+        return flows
+
+    def cleanup_flows(self):
+        cookies = set([f.cookie for f in self.dump_flows()])
+        for c in cookies:
+            if c == self.agent_uuid_stamp:
+                continue
+            LOG.warn(_LW("Deleting flow with cookie 0x%(cookie)x") % {
+                'cookie': c})
+            self.delete_flows(cookie=c, cookie_mask=((1 << 64) - 1))
+
+    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,
+                       match=None, **match_kwargs):
+        (_dp, ofp, ofpp) = self._get_dp()
+        actions = [ofpp.OFPActionOutput(port, 0)]
+        instructions = [ofpp.OFPInstructionActions(
+                        ofp.OFPIT_APPLY_ACTIONS, actions)]
+        self.install_instructions(table_id=table_id, priority=priority,
+                                  instructions=instructions,
+                                  match=match, **match_kwargs)
+
+    def install_normal(self, table_id=0, priority=0,
+                       match=None, **match_kwargs):
+        (_dp, ofp, _ofpp) = self._get_dp()
+        self.install_output(port=ofp.OFPP_NORMAL,
+                            table_id=table_id, priority=priority,
+                            match=match, **match_kwargs)
+
+    def install_goto(self, dest_table_id, table_id=0, priority=0,
+                     match=None, **match_kwargs):
+        (_dp, _ofp, ofpp) = self._get_dp()
+        instructions = [ofpp.OFPInstructionGotoTable(table_id=dest_table_id)]
+        self.install_instructions(table_id=table_id, priority=priority,
+                                  instructions=instructions,
+                                  match=match, **match_kwargs)
+
+    def install_drop(self, table_id=0, priority=0, match=None, **match_kwargs):
+        self.install_instructions(table_id=table_id, priority=priority,
+                                  instructions=[], match=match, **match_kwargs)
+
+    def install_instructions(self, instructions,
+                             table_id=0, priority=0,
+                             match=None, **match_kwargs):
+        (dp, ofp, ofpp) = self._get_dp()
+        match = self._match(ofp, ofpp, match, **match_kwargs)
+        msg = ofpp.OFPFlowMod(dp,
+                              table_id=table_id,
+                              cookie=self.agent_uuid_stamp,
+                              match=match,
+                              priority=priority,
+                              instructions=instructions)
+        self._send_msg(msg)
+
+    def install_apply_actions(self, actions,
+                              table_id=0, priority=0,
+                              match=None, **match_kwargs):
+        (dp, ofp, ofpp) = self._get_dp()
+        instructions = [
+            ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
+        ]
+        self.install_instructions(table_id=table_id,
+                                  priority=priority,
+                                  match=match,
+                                  instructions=instructions,
+                                  **match_kwargs)
diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge.py
new file mode 100644 (file)
index 0000000..21e18fc
--- /dev/null
@@ -0,0 +1,79 @@
+# 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 oslo_log import log as logging
+from oslo_utils import excutils
+
+from neutron.agent.common import ovs_lib
+from neutron.i18n import _LI
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    import ofswitch
+
+
+LOG = logging.getLogger(__name__)
+
+
+class OVSAgentBridge(ofswitch.OpenFlowSwitchMixin, ovs_lib.OVSBridge):
+    """Common code for bridges used by OVS agent"""
+
+    _cached_dpid = None
+
+    def _get_dp(self):
+        """Get (dp, ofp, ofpp) tuple for the switch.
+
+        A convenient method for openflow message composers.
+        """
+        while True:
+            dpid_int = self._cached_dpid
+            if dpid_int is None:
+                dpid_str = self.get_datapath_id()
+                LOG.info(_LI("Bridge %(br_name)s has datapath-ID %(dpid)s"),
+                         {"br_name": self.br_name, "dpid": dpid_str})
+                dpid_int = int(dpid_str, 16)
+            try:
+                dp = self._get_dp_by_dpid(dpid_int)
+            except RuntimeError:
+                with excutils.save_and_reraise_exception() as ctx:
+                    self._cached_dpid = None
+                    # Retry if dpid has been changed.
+                    # NOTE(yamamoto): Open vSwitch change its dpid on
+                    # some events.
+                    # REVISIT(yamamoto): Consider to set dpid statically.
+                    new_dpid_str = self.get_datapath_id()
+                    if new_dpid_str != dpid_str:
+                        LOG.info(_LI("Bridge %(br_name)s changed its "
+                                     "datapath-ID from %(old)s to %(new)s"), {
+                            "br_name": self.br_name,
+                            "old": dpid_str,
+                            "new": new_dpid_str,
+                        })
+                        ctx.reraise = False
+            else:
+                self._cached_dpid = dpid_int
+                return dp, dp.ofproto, dp.ofproto_parser
+
+    def setup_controllers(self, conf):
+        controllers = [
+            "tcp:%(address)s:%(port)s" % {
+                "address": conf.OVS.of_listen_address,
+                "port": conf.OVS.of_listen_port,
+            }
+        ]
+        self.set_protocols("OpenFlow13")
+        self.set_controller(controllers)
+
+    def drop_port(self, in_port):
+        self.install_drop(priority=2, in_port=in_port)
diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_ryuapp.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_ryuapp.py
new file mode 100644 (file)
index 0000000..0409717
--- /dev/null
@@ -0,0 +1,50 @@
+# 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.
+
+import functools
+
+import ryu.app.ofctl.api  # noqa
+from ryu.base import app_manager
+from ryu.lib import hub
+from ryu.ofproto import ofproto_v1_3
+
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    import br_int
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    import br_phys
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    import br_tun
+from neutron.plugins.ml2.drivers.openvswitch.agent \
+    import ovs_neutron_agent as ovs_agent
+
+
+class OVSNeutronAgentRyuApp(app_manager.RyuApp):
+    OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
+
+    def start(self):
+        # Start Ryu event loop thread
+        super(OVSNeutronAgentRyuApp, self).start()
+
+        def _make_br_cls(br_cls):
+            return functools.partial(br_cls, ryu_app=self)
+
+        # Start agent main loop thread
+        bridge_classes = {
+            'br_int': _make_br_cls(br_int.OVSIntegrationBridge),
+            'br_phys': _make_br_cls(br_phys.OVSPhysicalBridge),
+            'br_tun': _make_br_cls(br_tun.OVSTunnelBridge),
+        }
+        return hub.spawn(ovs_agent.main, bridge_classes)
index fb2df032ff49cde3494b6b93a21ea1426637dad8..b7625760564ae8bd925392209dd634944a9f77ae 100644 (file)
@@ -62,7 +62,7 @@ class OVSTunnelBridge(ovs_bridge.OVSAgentBridge,
             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
+                # REVISIT(yamamoto): add arp_op=arp.ARP_REQUEST matcher?
                 deferred_br.add_flow(table=constants.PATCH_LV_TO_TUN,
                                      priority=1,
                                      proto='arp',
index dc707e85a4da942db961b56e2cb681e07841069a..e45a750c3f69fdc37ac2e98812452cd553f2b473 100644 (file)
@@ -351,7 +351,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
                 self.provision_local_vlan(local_vlan_map['net_uuid'],
                                           local_vlan_map['network_type'],
                                           local_vlan_map['physical_network'],
-                                          local_vlan_map['segmentation_id'],
+                                          int(local_vlan_map[
+                                              'segmentation_id']),
                                           local_vlan)
 
     def setup_rpc(self):
index 6ad7af26b79f5519d961b395b881d0b1d338e978..67dc111bd10f84fff6da15990bf612b058a63dae 100644 (file)
@@ -34,6 +34,7 @@ import XenAPIPlugin
 
 ALLOWED_CMDS = [
     'ip',
+    # NOTE(yamamoto): of_interface=native doesn't use ovs-ofctl
     'ovs-ofctl',
     'ovs-vsctl',
     'ovsdb-client',
index d9856a8f4bf4a30cbff8853004d9e19e70e34443..e2eca7649a3a6d3a86b2f9581f45f1e756cf2e75 100644 (file)
@@ -14,6 +14,7 @@
 #    under the License.
 
 import eventlet
+import fixtures
 import mock
 
 from oslo_config import cfg
@@ -21,6 +22,7 @@ from oslo_utils import importutils
 
 from neutron.agent.linux import ip_lib
 from neutron.cmd.sanity import checks
+from neutron.common import constants as n_const
 from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
 from neutron.plugins.ml2.drivers.openvswitch.agent \
     import ovs_neutron_agent as ovsagt
@@ -47,18 +49,34 @@ class _OVSAgentTestBase(test_ovs_lib.OVSBridgeTestBase,
         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()
+        retry_count = 3
+        while True:
+            cfg.CONF.set_override('of_listen_port',
+                                  net_helpers.get_free_namespace_port(
+                                      n_const.PROTO_NAME_TCP),
+                                  group='OVS')
+            self.of_interface_mod.init_config()
+            self._main_thread = eventlet.spawn(self._kick_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.
+            try:
+                while not self.init_done:
+                    self.init_done_ev.wait()
+                break
+            except fixtures.TimeoutException:
+                self._kill_main()
+            retry_count -= 1
+            if retry_count < 0:
+                raise Exception('port allocation failed')
 
     def _kick_main(self):
         with mock.patch.object(ovsagt, 'main', self._agent_main):
@@ -87,6 +105,11 @@ class _OVSAgentOFCtlTestBase(_OVSAgentTestBase):
                     'openflow.ovs_ofctl.main')
 
 
+class _OVSAgentNativeTestBase(_OVSAgentTestBase):
+    _MAIN_MODULE = ('neutron.plugins.ml2.drivers.openvswitch.agent.'
+                    'openflow.native.main')
+
+
 class _ARPSpoofTestCase(object):
     def setUp(self):
         # NOTE(kevinbenton): it would be way cooler to use scapy for
@@ -194,6 +217,10 @@ class ARPSpoofOFCtlTestCase(_ARPSpoofTestCase, _OVSAgentOFCtlTestBase):
     pass
 
 
+class ARPSpoofNativeTestCase(_ARPSpoofTestCase, _OVSAgentNativeTestBase):
+    pass
+
+
 class _CanaryTableTestCase(object):
     def test_canary_table(self):
         self.br_int.delete_flows()
@@ -206,3 +233,7 @@ class _CanaryTableTestCase(object):
 
 class CanaryTableOFCtlTestCase(_CanaryTableTestCase, _OVSAgentOFCtlTestBase):
     pass
+
+
+class CanaryTableNativeTestCase(_CanaryTableTestCase, _OVSAgentNativeTestBase):
+    pass
diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/fake_oflib.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/fake_oflib.py
new file mode 100644 (file)
index 0000000..ea36327
--- /dev/null
@@ -0,0 +1,158 @@
+# Copyright (C) 2014 VA Linux Systems Japan K.K.
+# Copyright (C) 2014 Fumihiko Kakuma <kakuma at valinux co jp>
+# Copyright (C) 2014 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
+
+
+class _Eq(object):
+    def __eq__(self, other):
+        return repr(self) == repr(other)
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+
+class _Value(_Eq):
+    def __or__(self, b):
+        return _Op('|', self, b)
+
+    def __ror__(self, a):
+        return _Op('|', a, self)
+
+
+class _SimpleValue(_Value):
+    def __init__(self, name):
+        self.name = name
+
+    def __repr__(self):
+        return self.name
+
+
+class _Op(_Value):
+    def __init__(self, op, a, b):
+        self.op = op
+        self.a = a
+        self.b = b
+
+    def __repr__(self):
+        return '%s%s%s' % (self.a, self.op, self.b)
+
+
+def _mkcls(name):
+    class Cls(_Eq):
+        _name = name
+
+        def __init__(self, *args, **kwargs):
+            self._args = args
+            self._kwargs = kwargs
+            self._hist = []
+
+        def __getattr__(self, name):
+            return self._kwargs[name]
+
+        def __repr__(self):
+            args = list(map(repr, self._args))
+            kwargs = sorted(['%s=%s' % (x, y) for x, y in
+                             self._kwargs.items()])
+            return '%s(%s)' % (self._name, ', '.join(args + kwargs))
+
+    return Cls
+
+
+class _Mod(object):
+    _cls_cache = {}
+
+    def __init__(self, name):
+        self._name = name
+
+    def __getattr__(self, name):
+        fullname = '%s.%s' % (self._name, name)
+        if '_' in name:  # constants are named like OFPxxx_yyy_zzz
+            return _SimpleValue(fullname)
+        try:
+            return self._cls_cache[fullname]
+        except KeyError:
+            pass
+        cls = _mkcls(fullname)
+        self._cls_cache[fullname] = cls
+        return cls
+
+    def __repr__(self):
+        return 'Mod(%s)' % (self._name,)
+
+
+def patch_fake_oflib_of():
+    ryu_mod = mock.Mock()
+    ryu_base_mod = ryu_mod.base
+    ryu_exc_mod = ryu_mod.exception
+    ryu_ctrl_mod = ryu_mod.controller
+    handler = _Mod('ryu.controller.handler')
+    handler.set_ev_cls = mock.Mock()
+    ofp_event = _Mod('ryu.controller.ofp_event')
+    ryu_ctrl_mod.handler = handler
+    ryu_ctrl_mod.ofp_event = ofp_event
+    ryu_lib_mod = ryu_mod.lib
+    ryu_lib_hub = ryu_lib_mod.hub
+    ryu_packet_mod = ryu_lib_mod.packet
+    packet = _Mod('ryu.lib.packet.packet')
+    arp = _Mod('ryu.lib.packet.arp')
+    ethernet = _Mod('ryu.lib.packet.ethernet')
+    ether_types = _Mod('ryu.lib.packet.ether_types')
+    in_proto = _Mod('ryu.lib.packet.in_proto')
+    icmpv6 = _Mod('ryu.lib.packet.icmpv6')
+    vlan = _Mod('ryu.lib.packet.vlan')
+    ryu_packet_mod.packet = packet
+    packet.Packet = mock.Mock()
+    ryu_packet_mod.arp = arp
+    ryu_packet_mod.ethernet = ethernet
+    ryu_packet_mod.ether_types = ether_types
+    ryu_packet_mod.icmpv6 = icmpv6
+    ryu_packet_mod.in_proto = in_proto
+    ryu_packet_mod.vlan = vlan
+    ryu_ofproto_mod = ryu_mod.ofproto
+    ofp = _Mod('ryu.ofproto.ofproto_v1_3')
+    ofpp = _Mod('ryu.ofproto.ofproto_v1_3_parser')
+    ryu_ofproto_mod.ofproto_v1_3 = ofp
+    ryu_ofproto_mod.ofproto_v1_3_parser = ofpp
+    ryu_app_mod = ryu_mod.app
+    ryu_app_ofctl_mod = ryu_app_mod.ofctl
+    ryu_ofctl_api = ryu_app_ofctl_mod.api
+    modules = {'ryu': ryu_mod,
+               'ryu.base': ryu_base_mod,
+               'ryu.controller': ryu_ctrl_mod,
+               'ryu.controller.handler': handler,
+               'ryu.controller.handler.set_ev_cls': handler.set_ev_cls,
+               'ryu.controller.ofp_event': ofp_event,
+               'ryu.exception': ryu_exc_mod,
+               'ryu.lib': ryu_lib_mod,
+               'ryu.lib.hub': ryu_lib_hub,
+               'ryu.lib.packet': ryu_packet_mod,
+               'ryu.lib.packet.packet': packet,
+               'ryu.lib.packet.packet.Packet': packet.Packet,
+               'ryu.lib.packet.arp': arp,
+               'ryu.lib.packet.ethernet': ethernet,
+               'ryu.lib.packet.ether_types': ether_types,
+               'ryu.lib.packet.icmpv6': icmpv6,
+               'ryu.lib.packet.in_proto': in_proto,
+               'ryu.lib.packet.vlan': vlan,
+               'ryu.ofproto': ryu_ofproto_mod,
+               'ryu.ofproto.ofproto_v1_3': ofp,
+               'ryu.ofproto.ofproto_v1_3_parser': ofpp,
+               'ryu.app': ryu_app_mod,
+               'ryu.app.ofctl': ryu_app_ofctl_mod,
+               'ryu.app.ofctl.api': ryu_ofctl_api}
+    return mock.patch.dict('sys.modules', modules)
diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/__init__.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py
new file mode 100644 (file)
index 0000000..1437e0c
--- /dev/null
@@ -0,0 +1,257 @@
+# 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 oslo_utils import importutils
+
+from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent \
+    import ovs_test_base
+
+
+call = mock.call  # short hand
+
+
+class OVSBridgeTestBase(ovs_test_base.OVSRyuTestBase):
+    _ARP_MODULE = 'ryu.lib.packet.arp'
+    _ETHER_TYPES_MODULE = 'ryu.lib.packet.ether_types'
+    _ICMPV6_MODULE = 'ryu.lib.packet.icmpv6'
+    _IN_PROTO_MODULE = 'ryu.lib.packet.in_proto'
+    _OFP_MODULE = 'ryu.ofproto.ofproto_v1_3'
+    _OFPP_MODULE = 'ryu.ofproto.ofproto_v1_3_parser'
+
+    def setup_bridge_mock(self, name, cls):
+        self.br = cls(name)
+        self.dp = mock.Mock()
+        self.ofp = importutils.import_module(self._OFP_MODULE)
+        self.ofpp = importutils.import_module(self._OFPP_MODULE)
+        self.arp = importutils.import_module(self._ARP_MODULE)
+        self.ether_types = importutils.import_module(self._ETHER_TYPES_MODULE)
+        self.icmpv6 = importutils.import_module(self._ICMPV6_MODULE)
+        self.in_proto = importutils.import_module(self._IN_PROTO_MODULE)
+        mock.patch.object(self.br, '_get_dp', autospec=True,
+                          return_value=self._get_dp()).start()
+        mock__send_msg = mock.patch.object(self.br, '_send_msg').start()
+        mock_delete_flows = mock.patch.object(self.br, 'delete_flows').start()
+        self.mock = mock.Mock()
+        self.mock.attach_mock(mock__send_msg, '_send_msg')
+        self.mock.attach_mock(mock_delete_flows, 'delete_flows')
+
+    def _get_dp(self):
+        return self.dp, self.ofp, self.ofpp
+
+    def test_drop_port(self):
+        in_port = 2345
+        self.br.drop_port(in_port=in_port)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(
+                ofpp.OFPFlowMod(dp,
+                    cookie=0,
+                    instructions=[],
+                    match=ofpp.OFPMatch(in_port=in_port),
+                    priority=2,
+                    table_id=0)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(
+                ofpp.OFPFlowMod(dp,
+                    cookie=0,
+                    instructions=[
+                        ofpp.OFPInstructionGotoTable(table_id=dest_table_id),
+                    ],
+                    match=ofpp.OFPMatch(in_port=in_port),
+                    priority=priority,
+                    table_id=0)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(
+                ofpp.OFPFlowMod(dp,
+                    cookie=0,
+                    instructions=[],
+                    match=ofpp.OFPMatch(in_port=in_port),
+                    priority=priority,
+                    table_id=0)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(
+                ofpp.OFPFlowMod(dp,
+                    cookie=0,
+                    instructions=[
+                        ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                            ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0)
+                        ]),
+                    ],
+                    match=ofpp.OFPMatch(in_port=in_port),
+                    priority=priority,
+                    table_id=0)),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test__cidr_to_ryu(self):
+        f = self.br._cidr_to_ryu
+        self.assertEqual('192.168.0.1', f('192.168.0.1'))
+        self.assertEqual('192.168.0.1', f('192.168.0.1/32'))
+        self.assertEqual(('192.168.0.0', '255.255.255.0'), f('192.168.0.0/24'))
+
+
+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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(
+                    eth_type=self.ether_types.ETH_TYPE_ARP,
+                    arp_tpa=gateway_ip,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+                priority=3,
+                table_id=self.dvr_process_table_id)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(table_id=self.dvr_process_table_id,
+                match=ofpp.OFPMatch(
+                    eth_type=self.ether_types.ETH_TYPE_ARP,
+                    arp_tpa=gateway_ip,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(
+                    eth_src=gateway_mac,
+                    eth_type=self.ether_types.ETH_TYPE_IPV6,
+                    icmpv6_type=self.icmpv6.ND_ROUTER_ADVERT,
+                    ip_proto=self.in_proto.IPPROTO_ICMPV6,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+                priority=3,
+                table_id=self.dvr_process_table_id)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(table_id=self.dvr_process_table_id,
+                match=ofpp.OFPMatch(
+                    eth_src=gateway_mac,
+                    eth_type=self.ether_types.ETH_TYPE_IPV6,
+                    icmpv6_type=self.icmpv6.ND_ROUTER_ADVERT,
+                    ip_proto=self.in_proto.IPPROTO_ICMPV6,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(
+                    eth_dst=vif_mac,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+                priority=2,
+                table_id=self.dvr_process_table_id)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionSetField(eth_src=dvr_mac_address),
+                    ]),
+                    ofpp.OFPInstructionGotoTable(
+                        table_id=self.dvr_process_next_table_id),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_src=vif_mac,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+                priority=1,
+                table_id=self.dvr_process_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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(table_id=self.dvr_process_table_id,
+                match=ofpp.OFPMatch(
+                    eth_dst=vif_mac,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+            call.delete_flows(table_id=self.dvr_process_table_id,
+                match=ofpp.OFPMatch(
+                    eth_src=vif_mac,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py
new file mode 100644 (file)
index 0000000..fab1f24
--- /dev/null
@@ -0,0 +1,344 @@
+# 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.ml2.drivers.openvswitch.agent.openflow.native \
+    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()
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(
+                        ofp.OFPIT_APPLY_ACTIONS, [
+                            ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0)
+                        ]),
+                ],
+                match=ofpp.OFPMatch(),
+                priority=0,
+                table_id=0)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0,
+                table_id=23)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0,
+                table_id=24)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionSetField(
+                            vlan_vid=lvid | ofp.OFPVID_PRESENT),
+                        ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0)
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    in_port=port,
+                    vlan_vid=segmentation_id | ofp.OFPVID_PRESENT),
+                priority=3,
+                table_id=0)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionPushVlan(),
+                        ofpp.OFPActionSetField(
+                            vlan_vid=lvid | ofp.OFPVID_PRESENT),
+                        ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    in_port=port,
+                    vlan_vid=ofp.OFPVID_NONE),
+                priority=3,
+                table_id=0)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(
+                match=ofpp.OFPMatch(
+                    in_port=port,
+                    vlan_vid=segmentation_id | ofp.OFPVID_PRESENT)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(
+                match=ofpp.OFPMatch(
+                    in_port=port,
+                    vlan_vid=ofp.OFPVID_NONE)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionPopVlan(),
+                        ofpp.OFPActionSetField(eth_src=gateway_mac),
+                        ofpp.OFPActionOutput(6666, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_dst=dst_mac,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+                priority=4,
+                table_id=1)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(table_id=1,
+                match=ofpp.OFPMatch(
+                    eth_dst=dst_mac,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionPopVlan(),
+                        ofpp.OFPActionSetField(eth_src=gateway_mac),
+                        ofpp.OFPActionOutput(dst_port, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_dst=dst_mac,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+                priority=4,
+                table_id=2)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(table_id=2,
+                match=ofpp.OFPMatch(
+                    eth_dst=dst_mac,
+                    vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionGotoTable(table_id=2),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_src=mac,
+                    in_port=port),
+                priority=4,
+                table_id=0))
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionGotoTable(table_id=1),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_src=mac,
+                    in_port=port),
+                priority=2,
+                table_id=0))
+        ]
+        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, in_port=port, table_id=0),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_type=self.ether_types.ETH_TYPE_ARP,
+                    arp_spa='192.0.2.1',
+                    in_port=8888,
+                ),
+                priority=2,
+                table_id=24)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_type=self.ether_types.ETH_TYPE_ARP,
+                    arp_spa='192.0.2.2',
+                    in_port=8888
+                ),
+                priority=2,
+                table_id=24)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionGotoTable(table_id=24),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_type=self.ether_types.ETH_TYPE_ARP,
+                    in_port=8888,
+                ),
+                priority=10,
+                table_id=0)),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_arp_spoofing_protection(self):
+        port = 8888
+        self.br.delete_arp_spoofing_protection(port)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(table_id=0, match=ofpp.OFPMatch(
+                eth_type=self.ether_types.ETH_TYPE_ARP,
+                in_port=8888)),
+            call.delete_flows(table_id=24, in_port=port),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py
new file mode 100644 (file)
index 0000000..a478f9a
--- /dev/null
@@ -0,0 +1,147 @@
+# 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.ml2.drivers.openvswitch.agent.common.constants \
+    as ovs_const
+from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    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()
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(),
+                priority=0,
+                table_id=0)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionSetField(
+                            vlan_vid=segmentation_id | ofp.OFPVID_PRESENT),
+                        ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    in_port=port,
+                    vlan_vid=lvid | ofp.OFPVID_PRESENT),
+                priority=4,
+                table_id=0)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionPopVlan(),
+                        ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    in_port=port,
+                    vlan_vid=lvid | ofp.OFPVID_PRESENT),
+                priority=4,
+                table_id=0)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(
+                match=ofpp.OFPMatch(
+                    in_port=port,
+                    vlan_vid=lvid | ofp.OFPVID_PRESENT)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionOutput(port, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(eth_src=mac),
+                priority=2,
+                table_id=3)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(eth_src=mac, table_id=3),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py
new file mode 100644 (file)
index 0000000..91b0af6
--- /dev/null
@@ -0,0 +1,484 @@
+# 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.ml2.drivers.openvswitch.agent.common.constants \
+    as ovs_const
+from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+    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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=2)],
+                match=ofpp.OFPMatch(in_port=patch_int_ofport),
+                priority=1, table_id=0)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0, table_id=0)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=20)],
+                match=ofpp.OFPMatch(
+                    eth_dst=('00:00:00:00:00:00', '01:00:00:00:00:00')),
+                priority=0,
+                table_id=2)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+                match=ofpp.OFPMatch(
+                    eth_dst=('01:00:00:00:00:00', '01:00:00:00:00:00')),
+                priority=0,
+                table_id=2)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0, table_id=3)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0, table_id=4)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0, table_id=6)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.NXActionLearn(
+                            cookie=0,
+                            hard_timeout=300,
+                            priority=1,
+                            specs=[
+                                ofpp.NXFlowSpecMatch(
+                                    dst=('vlan_vid', 0),
+                                    n_bits=12,
+                                    src=('vlan_vid', 0)),
+                                ofpp.NXFlowSpecMatch(
+                                    dst=('eth_dst', 0),
+                                    n_bits=48,
+                                    src=('eth_src', 0)),
+                                ofpp.NXFlowSpecLoad(
+                                    dst=('vlan_vid', 0),
+                                    n_bits=12,
+                                    src=0),
+                                ofpp.NXFlowSpecLoad(
+                                    dst=('tunnel_id', 0),
+                                    n_bits=64,
+                                    src=('tunnel_id', 0)),
+                                ofpp.NXFlowSpecOutput(
+                                    dst='',
+                                    n_bits=32,
+                                    src=('in_port', 0)),
+                            ],
+                            table_id=20),
+                        ofpp.OFPActionOutput(patch_int_ofport, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(),
+                priority=1,
+                table_id=10)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+                match=ofpp.OFPMatch(),
+                priority=0,
+                table_id=20)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0,
+                table_id=22))
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=2)],
+                match=ofpp.OFPMatch(in_port=patch_int_ofport),
+                priority=1, table_id=0)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0, table_id=0)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=21)],
+                match=ofpp.OFPMatch(
+                    eth_dst='ff:ff:ff:ff:ff:ff',
+                    eth_type=self.ether_types.ETH_TYPE_ARP),
+                priority=1,
+                table_id=2)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=20)],
+                match=ofpp.OFPMatch(
+                    eth_dst=('00:00:00:00:00:00', '01:00:00:00:00:00')),
+                priority=0,
+                table_id=2)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+                match=ofpp.OFPMatch(
+                    eth_dst=('01:00:00:00:00:00', '01:00:00:00:00:00')),
+                priority=0,
+                table_id=2)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0, table_id=3)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0, table_id=4)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0, table_id=6)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.NXActionLearn(
+                            cookie=0,
+                            hard_timeout=300,
+                            priority=1,
+                            specs=[
+                                ofpp.NXFlowSpecMatch(
+                                    dst=('vlan_vid', 0),
+                                    n_bits=12,
+                                    src=('vlan_vid', 0)),
+                                ofpp.NXFlowSpecMatch(
+                                    dst=('eth_dst', 0),
+                                    n_bits=48,
+                                    src=('eth_src', 0)),
+                                ofpp.NXFlowSpecLoad(
+                                    dst=('vlan_vid', 0),
+                                    n_bits=12,
+                                    src=0),
+                                ofpp.NXFlowSpecLoad(
+                                    dst=('tunnel_id', 0),
+                                    n_bits=64,
+                                    src=('tunnel_id', 0)),
+                                ofpp.NXFlowSpecOutput(
+                                    dst='',
+                                    n_bits=32,
+                                    src=('in_port', 0)),
+                            ],
+                            table_id=20),
+                        ofpp.OFPActionOutput(patch_int_ofport, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(),
+                priority=1,
+                table_id=10)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+                match=ofpp.OFPMatch(),
+                priority=0,
+                table_id=20)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+                match=ofpp.OFPMatch(),
+                priority=0,
+                table_id=21)),
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[],
+                match=ofpp.OFPMatch(),
+                priority=0,
+                table_id=22))
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionPushVlan(),
+                        ofpp.OFPActionSetField(
+                            vlan_vid=lvid | ofp.OFPVID_PRESENT)
+                    ]),
+                    ofpp.OFPInstructionGotoTable(table_id=10),
+                ],
+                match=ofpp.OFPMatch(tunnel_id=segmentation_id),
+                priority=1,
+                table_id=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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(
+                table_id=4,
+                match=ofpp.OFPMatch(tunnel_id=segmentation_id)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionPopVlan(),
+                        ofpp.OFPActionSetField(tunnel_id=tun_id),
+                    ] + [ofpp.OFPActionOutput(p, 0) for p in ports]),
+                ],
+                match=ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT),
+                priority=1,
+                table_id=22)),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_delete_flood_to_tun(self):
+        vlan = 3333
+        self.br.delete_flood_to_tun(vlan=vlan)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(table_id=22,
+                match=ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionPopVlan(),
+                        ofpp.OFPActionSetField(tunnel_id=tun_id),
+                        ofpp.OFPActionOutput(port, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_dst=mac, vlan_vid=vlan | ofp.OFPVID_PRESENT),
+                priority=2,
+                table_id=20)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(table_id=20,
+                match=ofpp.OFPMatch(
+                    eth_dst=mac, vlan_vid=vlan | ofp.OFPVID_PRESENT)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(table_id=20,
+                match=ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionSetField(arp_op=self.arp.ARP_REPLY),
+                        ofpp.NXActionRegMove(
+                            dst_field='arp_tha',
+                            n_bits=48,
+                            src_field='arp_sha'),
+                        ofpp.NXActionRegMove(
+                            dst_field='arp_tpa',
+                            n_bits=32,
+                            src_field='arp_spa'),
+                        ofpp.OFPActionSetField(arp_sha=mac),
+                        ofpp.OFPActionSetField(arp_spa=ip),
+                        ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(
+                    eth_type=self.ether_types.ETH_TYPE_ARP,
+                    arp_tpa=ip,
+                    vlan_vid=vlan | ofp.OFPVID_PRESENT),
+                priority=1,
+                table_id=21)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(
+                match=ofpp.OFPMatch(
+                    eth_type=self.ether_types.ETH_TYPE_ARP,
+                    arp_tpa=ip,
+                    vlan_vid=vlan | ofp.OFPVID_PRESENT),
+                table_id=21),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(
+                match=ofpp.OFPMatch(
+                    eth_type=self.ether_types.ETH_TYPE_ARP,
+                    vlan_vid=vlan | ofp.OFPVID_PRESENT),
+                table_id=21),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionGotoTable(table_id=4),
+                ],
+                match=ofpp.OFPMatch(in_port=port),
+                priority=1,
+                table_id=0)),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
+
+    def test_cleanup_tunnel_port(self):
+        port = 11111
+        self.br.cleanup_tunnel_port(port=port)
+        (dp, ofp, ofpp) = self._get_dp()
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call._send_msg(ofpp.OFPFlowMod(dp,
+                cookie=0,
+                instructions=[
+                    ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+                        ofpp.OFPActionOutput(port, 0),
+                    ]),
+                ],
+                match=ofpp.OFPMatch(eth_src=mac),
+                priority=1,
+                table_id=9)),
+        ]
+        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)
+        (dp, ofp, ofpp) = self._get_dp()
+        expected = [
+            call.delete_flows(eth_src=mac, table_id=9),
+        ]
+        self.assertEqual(expected, self.mock.mock_calls)
index 9f730246e3c0fa565e648e04b7cc9e114466ebd6..6c77d311519fb4c83cc93cb8377e3c5a341eb9ff 100644 (file)
@@ -55,7 +55,7 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
                      {'priority': 0, 'table': 4, 'actions': 'drop'},
                      {'priority': 0, 'table': 6, 'actions': 'drop'},
                      {'priority': 1, 'table': 10,
-                      'actions': 'learn(cookie=0x0,table=20,priority=1,'
+                      'actions': 'learn(cookie=0,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[],'
@@ -90,7 +90,7 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
                      {'priority': 0, 'table': 4, 'actions': 'drop'},
                      {'priority': 0, 'table': 6, 'actions': 'drop'},
                      {'priority': 1, 'table': 10,
-                      'actions': 'learn(cookie=0x0,table=20,priority=1,'
+                      'actions': 'learn(cookie=0,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[],'
index 0be0a33c021d41c20ba682fceabacb92190f8af3..264b922c52a82f1dfad3af78695a3c5af3e14e49 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import functools
+
+import mock
 from oslo_utils import importutils
 
 from neutron.tests import base
+from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent \
+    import fake_oflib
 
 
 _AGENT_PACKAGE = 'neutron.plugins.ml2.drivers.openvswitch.agent'
@@ -53,3 +58,20 @@ class OVSOFCtlTestBase(OVSAgentTestBase):
     _BR_INT_CLASS = _DRIVER_PACKAGE + '.br_int.OVSIntegrationBridge'
     _BR_TUN_CLASS = _DRIVER_PACKAGE + '.br_tun.OVSTunnelBridge'
     _BR_PHYS_CLASS = _DRIVER_PACKAGE + '.br_phys.OVSPhysicalBridge'
+
+
+class OVSRyuTestBase(OVSAgentTestBase):
+    _DRIVER_PACKAGE = _AGENT_PACKAGE + '.openflow.native'
+    _BR_INT_CLASS = _DRIVER_PACKAGE + '.br_int.OVSIntegrationBridge'
+    _BR_TUN_CLASS = _DRIVER_PACKAGE + '.br_tun.OVSTunnelBridge'
+    _BR_PHYS_CLASS = _DRIVER_PACKAGE + '.br_phys.OVSPhysicalBridge'
+
+    def setUp(self):
+        self.fake_oflib_of = fake_oflib.patch_fake_oflib_of()
+        self.fake_oflib_of.start()
+        self.addCleanup(self.fake_oflib_of.stop)
+        super(OVSRyuTestBase, self).setUp()
+        ryu_app = mock.Mock()
+        self.br_int_cls = functools.partial(self.br_int_cls, ryu_app=ryu_app)
+        self.br_phys_cls = functools.partial(self.br_phys_cls, ryu_app=ryu_app)
+        self.br_tun_cls = functools.partial(self.br_tun_cls, ryu_app=ryu_app)
index 3d851c479bf4517d5327dc076d8e49015ae67c8d..401deb756e8146c4570a8fa6488c0397ec33f5f8 100644 (file)
@@ -1317,26 +1317,6 @@ class TestOvsNeutronAgent(object):
                            self.agent.state_rpc.client):
             self.assertEqual(10, rpc_client.timeout)
 
-    def test_cleanup_stale_flows_iter_0(self):
-        with mock.patch.object(self.agent.int_br, 'agent_uuid_stamp',
-                               new=1234),\
-            mock.patch.object(self.agent.int_br,
-                              'dump_flows_all_tables') as dump_flows,\
-                mock.patch.object(self.agent.int_br,
-                                  'delete_flows') as del_flow:
-            dump_flows.return_value = [
-                'cookie=0x4d2, duration=50.156s, table=0,actions=drop',
-                'cookie=0x4321, duration=54.143s, table=2, priority=0',
-                'cookie=0x2345, duration=50.125s, table=2, priority=0',
-                'cookie=0x4d2, duration=52.112s, table=3, actions=drop',
-            ]
-            self.agent.cleanup_stale_flows()
-            expected = [
-                mock.call(cookie='0x4321/-1', table='2'),
-                mock.call(cookie='0x2345/-1', table='2'),
-            ]
-            self.assertEqual(expected, del_flow.mock_calls)
-
     def test_set_rpc_timeout_no_value(self):
         self.agent.quitting_rpc_timeout = None
         with mock.patch.object(self.agent, 'set_rpc_timeout') as mock_set_rpc:
@@ -1438,7 +1418,51 @@ class TestOvsNeutronAgent(object):
 
 class TestOvsNeutronAgentOFCtl(TestOvsNeutronAgent,
                                ovs_test_base.OVSOFCtlTestBase):
-    pass
+    def test_cleanup_stale_flows_iter_0(self):
+        with mock.patch.object(self.agent.int_br, 'agent_uuid_stamp',
+                               new=1234),\
+            mock.patch.object(self.agent.int_br,
+                              'dump_flows_all_tables') as dump_flows,\
+                mock.patch.object(self.agent.int_br,
+                                  'delete_flows') as del_flow:
+            dump_flows.return_value = [
+                'cookie=0x4d2, duration=50.156s, table=0,actions=drop',
+                'cookie=0x4321, duration=54.143s, table=2, priority=0',
+                'cookie=0x2345, duration=50.125s, table=2, priority=0',
+                'cookie=0x4d2, duration=52.112s, table=3, actions=drop',
+            ]
+            self.agent.cleanup_stale_flows()
+            expected = [
+                mock.call(cookie='0x4321/-1', table='2'),
+                mock.call(cookie='0x2345/-1', table='2'),
+            ]
+            self.assertEqual(expected, del_flow.mock_calls)
+
+
+class TestOvsNeutronAgentRyu(TestOvsNeutronAgent,
+                             ovs_test_base.OVSRyuTestBase):
+    def test_cleanup_stale_flows_iter_0(self):
+        uint64_max = (1 << 64) - 1
+        with mock.patch.object(self.agent.int_br, 'agent_uuid_stamp',
+                               new=1234),\
+            mock.patch.object(self.agent.int_br,
+                              'dump_flows') as dump_flows,\
+                mock.patch.object(self.agent.int_br,
+                                  'delete_flows') as del_flow:
+            dump_flows.return_value = [
+                # mock ryu.ofproto.ofproto_v1_3_parser.OFPFlowStats
+                mock.Mock(cookie=1234, table_id=0),
+                mock.Mock(cookie=17185, table_id=2),
+                mock.Mock(cookie=9029, table_id=2),
+                mock.Mock(cookie=1234, table_id=3),
+            ]
+            self.agent.cleanup_stale_flows()
+            expected = [mock.call(cookie=17185,
+                                  cookie_mask=uint64_max),
+                        mock.call(cookie=9029,
+                                  cookie_mask=uint64_max)]
+            del_flow.assert_has_calls(expected, any_order=True)
+            self.assertEqual(len(expected), len(del_flow.mock_calls))
 
 
 class AncillaryBridgesTest(object):
@@ -1544,6 +1568,11 @@ class AncillaryBridgesTestOFCtl(AncillaryBridgesTest,
     pass
 
 
+class AncillaryBridgesTestRyu(AncillaryBridgesTest,
+                              ovs_test_base.OVSRyuTestBase):
+    pass
+
+
 class TestOvsDvrNeutronAgent(object):
 
     def setUp(self):
@@ -2427,6 +2456,11 @@ class TestOvsDvrNeutronAgentOFCtl(TestOvsDvrNeutronAgent,
     pass
 
 
+class TestOvsDvrNeutronAgentRyu(TestOvsDvrNeutronAgent,
+                                ovs_test_base.OVSRyuTestBase):
+    pass
+
+
 class TestValidateTunnelLocalIP(base.BaseTestCase):
     def test_validate_local_ip_no_tunneling(self):
         cfg.CONF.set_override('tunnel_types', [], group='AGENT')
index 72cef8cfb16e530b799bdde7e2d4ba4995f4b7de..d2d1920a216db0d763a7930a3db76e2e56cc6366 100644 (file)
@@ -575,6 +575,10 @@ class TunnelTestOFCtl(TunnelTest, ovs_test_base.OVSOFCtlTestBase):
     pass
 
 
+class TunnelTestRyu(TunnelTest, ovs_test_base.OVSRyuTestBase):
+    pass
+
+
 class TunnelTestUseVethInterco(TunnelTest):
     USE_VETH_INTERCONNECTION = True
 
@@ -669,6 +673,11 @@ class TunnelTestUseVethIntercoOFCtl(TunnelTestUseVethInterco,
     pass
 
 
+class TunnelTestUseVethIntercoRyu(TunnelTestUseVethInterco,
+                                  ovs_test_base.OVSRyuTestBase):
+    pass
+
+
 class TunnelTestWithMTU(TunnelTestUseVethInterco):
     VETH_MTU = 1500
 
@@ -681,3 +690,8 @@ class TunnelTestWithMTU(TunnelTestUseVethInterco):
 class TunnelTestWithMTUOFCtl(TunnelTestWithMTU,
                              ovs_test_base.OVSOFCtlTestBase):
     pass
+
+
+class TunnelTestWithMTURyu(TunnelTestWithMTU,
+                           ovs_test_base.OVSRyuTestBase):
+    pass
index 1b2855451752fe9f62a2928f1541cadfa3d825c7..643e230c1d1a3c937ad117c7215cb29f14d7cf9f 100644 (file)
@@ -17,6 +17,7 @@ keystonemiddleware>=2.0.0
 netaddr!=0.7.16,>=0.7.12
 python-neutronclient<3,>=2.6.0
 retrying!=1.3.0,>=1.2.3 # Apache-2.0
+ryu>=3.23.2 # Apache-2.0
 SQLAlchemy<1.1.0,>=0.9.7
 WebOb>=1.2.3
 python-keystoneclient>=1.6.0