From b3e7e21c32a251ba0b7123aa909edeaedd08152a Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Mon, 2 Mar 2015 16:40:11 +0900 Subject: [PATCH] OVS-agent: Introduce Ryu based OpenFlow implementation 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 Change-Id: I02e65ea7c6083b2c0a686fed2ab04da4d92b21a3 --- etc/neutron/plugins/ml2/openvswitch_agent.ini | 22 +- .../rootwrap.d/openvswitch-plugin.filters | 1 + neutron/agent/common/ovs_lib.py | 2 +- .../openvswitch/agent/common/config.py | 17 +- .../ml2/drivers/openvswitch/agent/main.py | 2 + .../agent/openflow/native/__init__.py | 0 .../agent/openflow/native/br_dvr_process.py | 113 ++++ .../agent/openflow/native/br_int.py | 177 +++++++ .../agent/openflow/native/br_phys.py | 67 +++ .../agent/openflow/native/br_tun.py | 288 +++++++++++ .../openvswitch/agent/openflow/native/main.py | 37 ++ .../agent/openflow/native/ofswitch.py | 202 ++++++++ .../agent/openflow/native/ovs_bridge.py | 79 +++ .../agent/openflow/native/ovs_ryuapp.py | 50 ++ .../agent/openflow/ovs_ofctl/br_tun.py | 2 +- .../openvswitch/agent/ovs_neutron_agent.py | 3 +- .../agent/xenapi/etc/xapi.d/plugins/netwrap | 1 + .../tests/functional/agent/test_ovs_flows.py | 51 +- .../drivers/openvswitch/agent/fake_oflib.py | 158 ++++++ .../agent/openflow/native/__init__.py | 0 .../openflow/native/ovs_bridge_test_base.py | 257 ++++++++++ .../agent/openflow/native/test_br_int.py | 344 +++++++++++++ .../agent/openflow/native/test_br_phys.py | 147 ++++++ .../agent/openflow/native/test_br_tun.py | 484 ++++++++++++++++++ .../agent/openflow/ovs_ofctl/test_br_tun.py | 4 +- .../openvswitch/agent/ovs_test_base.py | 22 + .../agent/test_ovs_neutron_agent.py | 76 ++- .../openvswitch/agent/test_ovs_tunnel.py | 14 + requirements.txt | 1 + 29 files changed, 2583 insertions(+), 38 deletions(-) create mode 100644 neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/__init__.py create mode 100644 neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_dvr_process.py create mode 100644 neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py create mode 100644 neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_phys.py create mode 100644 neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_tun.py create mode 100644 neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/main.py create mode 100644 neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ofswitch.py create mode 100644 neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge.py create mode 100644 neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_ryuapp.py create mode 100644 neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/fake_oflib.py create mode 100644 neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/__init__.py create mode 100644 neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py create mode 100644 neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py create mode 100644 neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py create mode 100644 neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py diff --git a/etc/neutron/plugins/ml2/openvswitch_agent.ini b/etc/neutron/plugins/ml2/openvswitch_agent.ini index 99cbaca5465..323ed3d740a 100644 --- a/etc/neutron/plugins/ml2/openvswitch_agent.ini +++ b/etc/neutron/plugins/ml2/openvswitch_agent.ini @@ -54,8 +54,28 @@ # 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. diff --git a/etc/neutron/rootwrap.d/openvswitch-plugin.filters b/etc/neutron/rootwrap.d/openvswitch-plugin.filters index ed7f1ce78c2..c738733bb42 100644 --- a/etc/neutron/rootwrap.d/openvswitch-plugin.filters +++ b/etc/neutron/rootwrap.d/openvswitch-plugin.filters @@ -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 diff --git a/neutron/agent/common/ovs_lib.py b/neutron/agent/common/ovs_lib.py index 930558212d0..76f0b4478d9 100644 --- a/neutron/agent/common/ovs_lib.py +++ b/neutron/agent/common/ovs_lib.py @@ -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 diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py b/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py index 56e86f76642..34685c53a2e 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/common/config.py @@ -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 = [ diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/main.py b/neutron/plugins/ml2/drivers/openvswitch/agent/main.py index b9bd5a96b5f..2fd965274ec 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/main.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/main.py @@ -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 index 00000000000..e69de29bb2d 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 index 00000000000..a5551c1e2e0 --- /dev/null +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_dvr_process.py @@ -0,0 +1,113 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 index 00000000000..76eaf86014b --- /dev/null +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_int.py @@ -0,0 +1,177 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 index 00000000000..a3aad0fc7ed --- /dev/null +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_phys.py @@ -0,0 +1,67 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 index 00000000000..6682eb12560 --- /dev/null +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/br_tun.py @@ -0,0 +1,288 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 index 00000000000..6f3bd7b28c7 --- /dev/null +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/main.py @@ -0,0 +1,37 @@ +# Copyright (C) 2015 VA Linux Systems Japan K.K. +# Copyright (C) 2015 YAMAMOTO Takashi +# 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 index 00000000000..c95c76e139c --- /dev/null +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ofswitch.py @@ -0,0 +1,202 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 index 00000000000..21e18fc86b5 --- /dev/null +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge.py @@ -0,0 +1,79 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 index 00000000000..0409717470c --- /dev/null +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_ryuapp.py @@ -0,0 +1,50 @@ +# Copyright (C) 2015 VA Linux Systems Japan K.K. +# Copyright (C) 2015 YAMAMOTO Takashi +# 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) diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/br_tun.py b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/br_tun.py index fb2df032ff4..b7625760564 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/br_tun.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/br_tun.py @@ -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', diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py index dc707e85a4d..e45a750c3f6 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py @@ -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): diff --git a/neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/netwrap b/neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/netwrap index 6ad7af26b79..67dc111bd10 100644 --- a/neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/netwrap +++ b/neutron/plugins/ml2/drivers/openvswitch/agent/xenapi/etc/xapi.d/plugins/netwrap @@ -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', diff --git a/neutron/tests/functional/agent/test_ovs_flows.py b/neutron/tests/functional/agent/test_ovs_flows.py index d9856a8f4bf..e2eca7649a3 100644 --- a/neutron/tests/functional/agent/test_ovs_flows.py +++ b/neutron/tests/functional/agent/test_ovs_flows.py @@ -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) + 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. - while not self.init_done: - self.init_done_ev.wait() + # 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 index 00000000000..ea363276af3 --- /dev/null +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/fake_oflib.py @@ -0,0 +1,158 @@ +# Copyright (C) 2014 VA Linux Systems Japan K.K. +# Copyright (C) 2014 Fumihiko Kakuma +# Copyright (C) 2014 YAMAMOTO Takashi +# 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 index 00000000000..e69de29bb2d 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 index 00000000000..1437e0cd420 --- /dev/null +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/ovs_bridge_test_base.py @@ -0,0 +1,257 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 index 00000000000..fab1f247e09 --- /dev/null +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_int.py @@ -0,0 +1,344 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 index 00000000000..a478f9a8b61 --- /dev/null +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_phys.py @@ -0,0 +1,147 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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 index 00000000000..91b0af60895 --- /dev/null +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/native/test_br_tun.py @@ -0,0 +1,484 @@ +# Copyright (C) 2014,2015 VA Linux Systems Japan K.K. +# Copyright (C) 2014,2015 YAMAMOTO Takashi +# 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) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py index 9f730246e3c..6c77d311519 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/openflow/ovs_ofctl/test_br_tun.py @@ -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[],' diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/ovs_test_base.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/ovs_test_base.py index 0be0a33c021..264b922c52a 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/ovs_test_base.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/ovs_test_base.py @@ -15,9 +15,14 @@ # 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) diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py index 3d851c479bf..401deb756e8 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_neutron_agent.py @@ -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') diff --git a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py index 72cef8cfb16..d2d1920a216 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py +++ b/neutron/tests/unit/plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py @@ -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 diff --git a/requirements.txt b/requirements.txt index 1b285545175..643e230c1d1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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