# 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.
# 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
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
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 = [
_main_modules = {
'ovs-ofctl': 'neutron.plugins.ml2.drivers.openvswitch.agent.openflow.'
'ovs_ofctl.main',
+ 'native': 'neutron.plugins.ml2.drivers.openvswitch.agent.openflow.'
+ 'native.main',
}
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from ryu.lib.packet import ether_types
+from ryu.lib.packet import icmpv6
+from ryu.lib.packet import in_proto
+
+
+class OVSDVRProcessMixin(object):
+ """Common logic for br-tun and br-phys' DVR_PROCESS tables.
+
+ Inheriters should provide self.dvr_process_table_id and
+ self.dvr_process_next_table_id.
+ """
+
+ @staticmethod
+ def _dvr_process_ipv4_match(ofp, ofpp, vlan_tag, gateway_ip):
+ return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+ eth_type=ether_types.ETH_TYPE_ARP,
+ arp_tpa=gateway_ip)
+
+ def install_dvr_process_ipv4(self, vlan_tag, gateway_ip):
+ # block ARP
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._dvr_process_ipv4_match(ofp, ofpp,
+ vlan_tag=vlan_tag, gateway_ip=gateway_ip)
+ self.install_drop(table_id=self.dvr_process_table_id,
+ priority=3,
+ match=match)
+
+ def delete_dvr_process_ipv4(self, vlan_tag, gateway_ip):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._dvr_process_ipv4_match(ofp, ofpp,
+ vlan_tag=vlan_tag, gateway_ip=gateway_ip)
+ self.delete_flows(table_id=self.dvr_process_table_id, match=match)
+
+ @staticmethod
+ def _dvr_process_ipv6_match(ofp, ofpp, vlan_tag, gateway_mac):
+ return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+ eth_type=ether_types.ETH_TYPE_IPV6,
+ ip_proto=in_proto.IPPROTO_ICMPV6,
+ icmpv6_type=icmpv6.ND_ROUTER_ADVERT,
+ eth_src=gateway_mac)
+
+ def install_dvr_process_ipv6(self, vlan_tag, gateway_mac):
+ # block RA
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._dvr_process_ipv6_match(ofp, ofpp,
+ vlan_tag=vlan_tag, gateway_mac=gateway_mac)
+ self.install_drop(table_id=self.dvr_process_table_id, priority=3,
+ match=match)
+
+ def delete_dvr_process_ipv6(self, vlan_tag, gateway_mac):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._dvr_process_ipv6_match(ofp, ofpp,
+ vlan_tag=vlan_tag, gateway_mac=gateway_mac)
+ self.delete_flows(table_id=self.dvr_process_table_id, match=match)
+
+ @staticmethod
+ def _dvr_process_in_match(ofp, ofpp, vlan_tag, vif_mac):
+ return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+ eth_dst=vif_mac)
+
+ @staticmethod
+ def _dvr_process_out_match(ofp, ofpp, vlan_tag, vif_mac):
+ return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+ eth_src=vif_mac)
+
+ def install_dvr_process(self, vlan_tag, vif_mac, dvr_mac_address):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._dvr_process_in_match(ofp, ofpp,
+ vlan_tag=vlan_tag, vif_mac=vif_mac)
+ table_id = self.dvr_process_table_id
+ self.install_drop(table_id=table_id,
+ priority=2,
+ match=match)
+ match = self._dvr_process_out_match(ofp, ofpp,
+ vlan_tag=vlan_tag, vif_mac=vif_mac)
+ actions = [
+ ofpp.OFPActionSetField(eth_src=dvr_mac_address),
+ ]
+ instructions = [
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
+ ofpp.OFPInstructionGotoTable(
+ table_id=self.dvr_process_next_table_id),
+ ]
+ self.install_instructions(table_id=table_id,
+ priority=1,
+ match=match,
+ instructions=instructions)
+
+ def delete_dvr_process(self, vlan_tag, vif_mac):
+ (_dp, ofp, ofpp) = self._get_dp()
+ table_id = self.dvr_process_table_id
+ match = self._dvr_process_in_match(ofp, ofpp,
+ vlan_tag=vlan_tag, vif_mac=vif_mac)
+ self.delete_flows(table_id=table_id, match=match)
+ match = self._dvr_process_out_match(ofp, ofpp,
+ vlan_tag=vlan_tag, vif_mac=vif_mac)
+ self.delete_flows(table_id=table_id, match=match)
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+* references
+** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic
+"""
+
+from oslo_log import log as logging
+from ryu.lib.packet import ether_types
+
+from neutron.i18n import _LE
+from neutron.plugins.common import constants as p_const
+from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import ovs_bridge
+
+
+LOG = logging.getLogger(__name__)
+
+
+class OVSIntegrationBridge(ovs_bridge.OVSAgentBridge):
+ """openvswitch agent br-int specific logic."""
+
+ def setup_default_table(self):
+ self.install_normal()
+ self.setup_canary_table()
+ self.install_drop(table_id=constants.ARP_SPOOF_TABLE)
+
+ def setup_canary_table(self):
+ self.install_drop(constants.CANARY_TABLE)
+
+ def check_canary_table(self):
+ try:
+ flows = self.dump_flows(constants.CANARY_TABLE)
+ except RuntimeError:
+ LOG.exception(_LE("Failed to communicate with the switch"))
+ return constants.OVS_DEAD
+ if flows == []:
+ return constants.OVS_RESTARTED
+ return constants.OVS_NORMAL
+
+ @staticmethod
+ def _local_vlan_match(_ofp, ofpp, port, vlan_vid):
+ return ofpp.OFPMatch(in_port=port, vlan_vid=vlan_vid)
+
+ def provision_local_vlan(self, port, lvid, segmentation_id):
+ (_dp, ofp, ofpp) = self._get_dp()
+ if segmentation_id is None:
+ vlan_vid = ofp.OFPVID_NONE
+ actions = [ofpp.OFPActionPushVlan()]
+ else:
+ vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
+ actions = []
+ match = self._local_vlan_match(ofp, ofpp, port, vlan_vid)
+ actions += [
+ ofpp.OFPActionSetField(vlan_vid=lvid | ofp.OFPVID_PRESENT),
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+ ]
+ self.install_apply_actions(priority=3,
+ match=match,
+ actions=actions)
+
+ def reclaim_local_vlan(self, port, segmentation_id):
+ (_dp, ofp, ofpp) = self._get_dp()
+ if segmentation_id is None:
+ vlan_vid = ofp.OFPVID_NONE
+ else:
+ vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
+ match = self._local_vlan_match(ofp, ofpp, port, vlan_vid)
+ self.delete_flows(match=match)
+
+ @staticmethod
+ def _dvr_to_src_mac_match(ofp, ofpp, vlan_tag, dst_mac):
+ return ofpp.OFPMatch(vlan_vid=vlan_tag | ofp.OFPVID_PRESENT,
+ eth_dst=dst_mac)
+
+ @staticmethod
+ def _dvr_to_src_mac_table_id(network_type):
+ if network_type == p_const.TYPE_VLAN:
+ return constants.DVR_TO_SRC_MAC_VLAN
+ else:
+ return constants.DVR_TO_SRC_MAC
+
+ def install_dvr_to_src_mac(self, network_type,
+ vlan_tag, gateway_mac, dst_mac, dst_port):
+ table_id = self._dvr_to_src_mac_table_id(network_type)
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._dvr_to_src_mac_match(ofp, ofpp,
+ vlan_tag=vlan_tag, dst_mac=dst_mac)
+ actions = [
+ ofpp.OFPActionPopVlan(),
+ ofpp.OFPActionSetField(eth_src=gateway_mac),
+ ofpp.OFPActionOutput(dst_port, 0),
+ ]
+ self.install_apply_actions(table_id=table_id,
+ priority=4,
+ match=match,
+ actions=actions)
+
+ def delete_dvr_to_src_mac(self, network_type, vlan_tag, dst_mac):
+ table_id = self._dvr_to_src_mac_table_id(network_type)
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._dvr_to_src_mac_match(ofp, ofpp,
+ vlan_tag=vlan_tag, dst_mac=dst_mac)
+ self.delete_flows(table_id=table_id, match=match)
+
+ def add_dvr_mac_vlan(self, mac, port):
+ self.install_goto(table_id=constants.LOCAL_SWITCHING,
+ priority=4,
+ in_port=port,
+ eth_src=mac,
+ dest_table_id=constants.DVR_TO_SRC_MAC_VLAN)
+
+ def remove_dvr_mac_vlan(self, mac):
+ # REVISIT(yamamoto): match in_port as well?
+ self.delete_flows(table_id=constants.LOCAL_SWITCHING,
+ eth_src=mac)
+
+ def add_dvr_mac_tun(self, mac, port):
+ self.install_goto(table_id=constants.LOCAL_SWITCHING,
+ priority=2,
+ in_port=port,
+ eth_src=mac,
+ dest_table_id=constants.DVR_TO_SRC_MAC)
+
+ def remove_dvr_mac_tun(self, mac, port):
+ self.delete_flows(table_id=constants.LOCAL_SWITCHING,
+ in_port=port, eth_src=mac)
+
+ @staticmethod
+ def _arp_reply_match(ofp, ofpp, port):
+ return ofpp.OFPMatch(in_port=port,
+ eth_type=ether_types.ETH_TYPE_ARP)
+
+ def install_arp_spoofing_protection(self, port, ip_addresses):
+ # allow ARP replies as long as they match addresses that actually
+ # belong to the port.
+ for ip in ip_addresses:
+ masked_ip = self._cidr_to_ryu(ip)
+ self.install_normal(table_id=constants.ARP_SPOOF_TABLE,
+ priority=2,
+ eth_type=ether_types.ETH_TYPE_ARP,
+ arp_spa=masked_ip,
+ in_port=port)
+
+ # Now that the rules are ready, direct ARP traffic from the port into
+ # the anti-spoof table.
+ # This strategy fails gracefully because OVS versions that can't match
+ # on ARP headers will just process traffic normally.
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._arp_reply_match(ofp, ofpp, port=port)
+ self.install_goto(table_id=constants.LOCAL_SWITCHING,
+ priority=10,
+ match=match,
+ dest_table_id=constants.ARP_SPOOF_TABLE)
+
+ def delete_arp_spoofing_protection(self, port):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._arp_reply_match(ofp, ofpp, port=port)
+ self.delete_flows(table_id=constants.LOCAL_SWITCHING,
+ match=match)
+ self.delete_flows(table_id=constants.ARP_SPOOF_TABLE,
+ in_port=port)
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import br_dvr_process
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import ovs_bridge
+
+
+class OVSPhysicalBridge(ovs_bridge.OVSAgentBridge,
+ br_dvr_process.OVSDVRProcessMixin):
+ """openvswitch agent physical bridge specific logic."""
+
+ # Used by OVSDVRProcessMixin
+ dvr_process_table_id = constants.DVR_PROCESS_VLAN
+ dvr_process_next_table_id = constants.LOCAL_VLAN_TRANSLATION
+
+ def setup_default_table(self):
+ self.delete_flows()
+ self.install_normal()
+
+ @staticmethod
+ def _local_vlan_match(ofp, ofpp, port, lvid):
+ return ofpp.OFPMatch(in_port=port, vlan_vid=lvid | ofp.OFPVID_PRESENT)
+
+ def provision_local_vlan(self, port, lvid, segmentation_id, distributed):
+ table_id = constants.LOCAL_VLAN_TRANSLATION if distributed else 0
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._local_vlan_match(ofp, ofpp, port, lvid)
+ if segmentation_id is None:
+ actions = [ofpp.OFPActionPopVlan()]
+ else:
+ vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
+ actions = [ofpp.OFPActionSetField(vlan_vid=vlan_vid)]
+ actions += [ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0)]
+ self.install_apply_actions(table_id=table_id,
+ priority=4,
+ match=match,
+ actions=actions)
+
+ def reclaim_local_vlan(self, port, lvid):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._local_vlan_match(ofp, ofpp, port, lvid)
+ self.delete_flows(match=match)
+
+ def add_dvr_mac_vlan(self, mac, port):
+ self.install_output(table_id=constants.DVR_NOT_LEARN_VLAN,
+ priority=2, eth_src=mac, port=port)
+
+ def remove_dvr_mac_vlan(self, mac):
+ # REVISIT(yamamoto): match in_port as well?
+ self.delete_flows(table_id=constants.DVR_NOT_LEARN_VLAN,
+ eth_src=mac)
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+# Copyright 2011 VMware, Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from ryu.lib.packet import arp
+from ryu.lib.packet import ether_types
+
+from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import br_dvr_process
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import ovs_bridge
+
+
+class OVSTunnelBridge(ovs_bridge.OVSAgentBridge,
+ br_dvr_process.OVSDVRProcessMixin):
+ """openvswitch agent tunnel bridge specific logic."""
+
+ # Used by OVSDVRProcessMixin
+ dvr_process_table_id = constants.DVR_PROCESS
+ dvr_process_next_table_id = constants.PATCH_LV_TO_TUN
+
+ def setup_default_table(self, patch_int_ofport, arp_responder_enabled):
+ (dp, ofp, ofpp) = self._get_dp()
+
+ # Table 0 (default) will sort incoming traffic depending on in_port
+ self.install_goto(dest_table_id=constants.PATCH_LV_TO_TUN,
+ priority=1,
+ in_port=patch_int_ofport)
+ self.install_drop() # default drop
+
+ if arp_responder_enabled:
+ # ARP broadcast-ed request go to the local ARP_RESPONDER table to
+ # be locally resolved
+ # REVISIT(yamamoto): add arp_op=arp.ARP_REQUEST matcher?
+ self.install_goto(dest_table_id=constants.ARP_RESPONDER,
+ table_id=constants.PATCH_LV_TO_TUN,
+ priority=1,
+ eth_dst="ff:ff:ff:ff:ff:ff",
+ eth_type=ether_types.ETH_TYPE_ARP)
+
+ # PATCH_LV_TO_TUN table will handle packets coming from patch_int
+ # unicasts go to table UCAST_TO_TUN where remote addresses are learnt
+ self.install_goto(dest_table_id=constants.UCAST_TO_TUN,
+ table_id=constants.PATCH_LV_TO_TUN,
+ eth_dst=('00:00:00:00:00:00',
+ '01:00:00:00:00:00'))
+
+ # Broadcasts/multicasts go to table FLOOD_TO_TUN that handles flooding
+ self.install_goto(dest_table_id=constants.FLOOD_TO_TUN,
+ table_id=constants.PATCH_LV_TO_TUN,
+ eth_dst=('01:00:00:00:00:00',
+ '01:00:00:00:00:00'))
+
+ # Tables [tunnel_type]_TUN_TO_LV will set lvid depending on tun_id
+ # for each tunnel type, and resubmit to table LEARN_FROM_TUN where
+ # remote mac addresses will be learnt
+ for tunnel_type in constants.TUNNEL_NETWORK_TYPES:
+ self.install_drop(table_id=constants.TUN_TABLE[tunnel_type])
+
+ # LEARN_FROM_TUN table will have a single flow using a learn action to
+ # dynamically set-up flows in UCAST_TO_TUN corresponding to remote mac
+ # addresses (assumes that lvid has already been set by a previous flow)
+ # Once remote mac addresses are learnt, output packet to patch_int
+ flow_specs = [
+ ofpp.NXFlowSpecMatch(src=('vlan_vid', 0),
+ dst=('vlan_vid', 0),
+ n_bits=12),
+ ofpp.NXFlowSpecMatch(src=('eth_src', 0),
+ dst=('eth_dst', 0),
+ n_bits=48),
+ ofpp.NXFlowSpecLoad(src=0,
+ dst=('vlan_vid', 0),
+ n_bits=12),
+ ofpp.NXFlowSpecLoad(src=('tunnel_id', 0),
+ dst=('tunnel_id', 0),
+ n_bits=64),
+ ofpp.NXFlowSpecOutput(src=('in_port', 0),
+ dst='',
+ n_bits=32),
+ ]
+ actions = [
+ ofpp.NXActionLearn(table_id=constants.UCAST_TO_TUN,
+ cookie=self.agent_uuid_stamp,
+ priority=1,
+ hard_timeout=300,
+ specs=flow_specs),
+ ofpp.OFPActionOutput(patch_int_ofport, 0),
+ ]
+ self.install_apply_actions(table_id=constants.LEARN_FROM_TUN,
+ priority=1,
+ actions=actions)
+
+ # Egress unicast will be handled in table UCAST_TO_TUN, where remote
+ # mac addresses will be learned. For now, just add a default flow that
+ # will resubmit unknown unicasts to table FLOOD_TO_TUN to treat them
+ # as broadcasts/multicasts
+ self.install_goto(dest_table_id=constants.FLOOD_TO_TUN,
+ table_id=constants.UCAST_TO_TUN)
+
+ if arp_responder_enabled:
+ # If none of the ARP entries correspond to the requested IP, the
+ # broadcast-ed packet is resubmitted to the flooding table
+ self.install_goto(dest_table_id=constants.FLOOD_TO_TUN,
+ table_id=constants.ARP_RESPONDER)
+
+ # FLOOD_TO_TUN will handle flooding in tunnels based on lvid,
+ # for now, add a default drop action
+ self.install_drop(table_id=constants.FLOOD_TO_TUN)
+
+ @staticmethod
+ def _local_vlan_match(_ofp, ofpp, tun_id):
+ return ofpp.OFPMatch(tunnel_id=tun_id)
+
+ def provision_local_vlan(self, network_type, lvid, segmentation_id,
+ distributed=False):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._local_vlan_match(ofp, ofpp, segmentation_id)
+ table_id = constants.TUN_TABLE[network_type]
+ if distributed:
+ dest_table_id = constants.DVR_NOT_LEARN
+ else:
+ dest_table_id = constants.LEARN_FROM_TUN
+ actions = [
+ ofpp.OFPActionPushVlan(),
+ ofpp.OFPActionSetField(vlan_vid=lvid | ofp.OFPVID_PRESENT),
+ ]
+ instructions = [
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
+ ofpp.OFPInstructionGotoTable(table_id=dest_table_id)]
+ self.install_instructions(table_id=table_id,
+ priority=1,
+ match=match,
+ instructions=instructions)
+
+ def reclaim_local_vlan(self, network_type, segmentation_id):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._local_vlan_match(ofp, ofpp, segmentation_id)
+ table_id = constants.TUN_TABLE[network_type]
+ self.delete_flows(table_id=table_id, match=match)
+
+ @staticmethod
+ def _flood_to_tun_match(ofp, ofpp, vlan):
+ return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT)
+
+ def install_flood_to_tun(self, vlan, tun_id, ports):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._flood_to_tun_match(ofp, ofpp, vlan)
+ actions = [ofpp.OFPActionPopVlan(),
+ ofpp.OFPActionSetField(tunnel_id=tun_id)]
+ for port in ports:
+ actions.append(ofpp.OFPActionOutput(port, 0))
+ self.install_apply_actions(table_id=constants.FLOOD_TO_TUN,
+ priority=1,
+ match=match,
+ actions=actions)
+
+ def delete_flood_to_tun(self, vlan):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._flood_to_tun_match(ofp, ofpp, vlan)
+ self.delete_flows(table_id=constants.FLOOD_TO_TUN, match=match)
+
+ @staticmethod
+ def _unicast_to_tun_match(ofp, ofpp, vlan, mac):
+ return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT, eth_dst=mac)
+
+ def install_unicast_to_tun(self, vlan, tun_id, port, mac):
+ (_dp, ofp, ofpp) = self._get_dp()
+ match = self._unicast_to_tun_match(ofp, ofpp, vlan, mac)
+ actions = [ofpp.OFPActionPopVlan(),
+ ofpp.OFPActionSetField(tunnel_id=tun_id),
+ ofpp.OFPActionOutput(port, 0)]
+ self.install_apply_actions(table_id=constants.UCAST_TO_TUN,
+ priority=2,
+ match=match,
+ actions=actions)
+
+ def delete_unicast_to_tun(self, vlan, mac):
+ (_dp, ofp, ofpp) = self._get_dp()
+ if mac is None:
+ match = ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT)
+ else:
+ match = self._unicast_to_tun_match(ofp, ofpp, vlan, mac)
+ self.delete_flows(table_id=constants.UCAST_TO_TUN, match=match)
+
+ @staticmethod
+ def _arp_responder_match(ofp, ofpp, vlan, ip):
+ # REVISIT(yamamoto): add arp_op=arp.ARP_REQUEST matcher?
+ return ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT,
+ eth_type=ether_types.ETH_TYPE_ARP,
+ arp_tpa=ip)
+
+ def install_arp_responder(self, vlan, ip, mac):
+ (dp, ofp, ofpp) = self._get_dp()
+ match = self._arp_responder_match(ofp, ofpp, vlan, ip)
+ actions = [ofpp.OFPActionSetField(arp_op=arp.ARP_REPLY),
+ ofpp.NXActionRegMove(src_field='arp_sha',
+ dst_field='arp_tha',
+ n_bits=48),
+ ofpp.NXActionRegMove(src_field='arp_spa',
+ dst_field='arp_tpa',
+ n_bits=32),
+ ofpp.OFPActionSetField(arp_sha=mac),
+ ofpp.OFPActionSetField(arp_spa=ip),
+ ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0)]
+ self.install_apply_actions(table_id=constants.ARP_RESPONDER,
+ priority=1,
+ match=match,
+ actions=actions)
+
+ def delete_arp_responder(self, vlan, ip):
+ (_dp, ofp, ofpp) = self._get_dp()
+ if ip is None:
+ # REVISIT(yamamoto): add arp_op=arp.ARP_REQUEST matcher?
+ match = ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT,
+ eth_type=ether_types.ETH_TYPE_ARP)
+ else:
+ match = self._arp_responder_match(ofp, ofpp, vlan, ip)
+ self.delete_flows(table_id=constants.ARP_RESPONDER, match=match)
+
+ def setup_tunnel_port(self, network_type, port):
+ self.install_goto(dest_table_id=constants.TUN_TABLE[network_type],
+ priority=1,
+ in_port=port)
+
+ def cleanup_tunnel_port(self, port):
+ self.delete_flows(in_port=port)
+
+ def add_dvr_mac_tun(self, mac, port):
+ self.install_output(table_id=constants.DVR_NOT_LEARN,
+ priority=1,
+ eth_src=mac,
+ port=port)
+
+ def remove_dvr_mac_tun(self, mac):
+ # REVISIT(yamamoto): match in_port as well?
+ self.delete_flows(table_id=constants.DVR_NOT_LEARN,
+ eth_src=mac)
+
+ def deferred(self):
+ # REVISIT(yamamoto): This is for API compat with "ovs-ofctl"
+ # interface. Consider removing this mechanism when obsoleting
+ # "ovs-ofctl" interface.
+ # For "ovs-ofctl" interface, "deferred" mechanism would improve
+ # performance by batching flow-mods with a single ovs-ofctl command
+ # invocation.
+ # On the other hand, for this "native" interface, the overheads of
+ # each flow-mods are already minimum and batching doesn't make much
+ # sense. Thus this method is left as no-op.
+ # It might be possible to send multiple flow-mods with a single
+ # barrier. But it's unclear that level of performance optimization
+ # is desirable while it would certainly complicate error handling.
+ return self
+
+ def __enter__(self):
+ # REVISIT(yamamoto): See the comment on deferred().
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ # REVISIT(yamamoto): See the comment on deferred().
+ pass
--- /dev/null
+# Copyright (C) 2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_config import cfg
+from ryu.base import app_manager
+from ryu import cfg as ryu_cfg
+
+
+cfg.CONF.import_group(
+ 'OVS',
+ 'neutron.plugins.ml2.drivers.openvswitch.agent.common.config')
+
+
+def init_config():
+ ryu_cfg.CONF(project='ryu', args=[])
+ ryu_cfg.CONF.ofp_listen_host = cfg.CONF.OVS.of_listen_address
+ ryu_cfg.CONF.ofp_tcp_listen_port = cfg.CONF.OVS.of_listen_port
+
+
+def main():
+ app_manager.AppManager.run_apps([
+ 'neutron.plugins.ml2.drivers.openvswitch.agent.'
+ 'openflow.native.ovs_ryuapp',
+ ])
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import eventlet
+import netaddr
+from oslo_config import cfg
+from oslo_log import log as logging
+from oslo_utils import excutils
+from oslo_utils import timeutils
+import ryu.app.ofctl.api as ofctl_api
+import ryu.exception as ryu_exc
+
+from neutron.i18n import _LE, _LW
+
+LOG = logging.getLogger(__name__)
+
+
+class OpenFlowSwitchMixin(object):
+ """Mixin to provide common convenient routines for an openflow switch.
+
+ NOTE(yamamoto): super() points to ovs_lib.OVSBridge.
+ See ovs_bridge.py how this class is actually used.
+ """
+
+ @staticmethod
+ def _cidr_to_ryu(ip):
+ n = netaddr.IPNetwork(ip)
+ if n.hostmask:
+ return (str(n.ip), str(n.netmask))
+ return str(n.ip)
+
+ def __init__(self, *args, **kwargs):
+ self._app = kwargs.pop('ryu_app')
+ super(OpenFlowSwitchMixin, self).__init__(*args, **kwargs)
+
+ def _get_dp_by_dpid(self, dpid_int):
+ """Get Ryu datapath object for the switch."""
+ timeout_sec = cfg.CONF.OVS.of_connect_timeout
+ start_time = timeutils.now()
+ while True:
+ dp = ofctl_api.get_datapath(self._app, dpid_int)
+ if dp is not None:
+ break
+ # The switch has not established a connection to us.
+ # Wait for a little.
+ if timeutils.now() > start_time + timeout_sec:
+ m = _LE("Switch connection timeout")
+ LOG.error(m)
+ # NOTE(yamamoto): use RuntimeError for compat with ovs_lib
+ raise RuntimeError(m)
+ eventlet.sleep(1)
+ return dp
+
+ def _send_msg(self, msg, reply_cls=None, reply_multi=False):
+ timeout_sec = cfg.CONF.OVS.of_request_timeout
+ timeout = eventlet.timeout.Timeout(seconds=timeout_sec)
+ try:
+ result = ofctl_api.send_msg(self._app, msg, reply_cls, reply_multi)
+ except ryu_exc.RyuException as e:
+ m = _LE("ofctl request %(request)s error %(error)s") % {
+ "request": msg,
+ "error": e,
+ }
+ LOG.error(m)
+ # NOTE(yamamoto): use RuntimeError for compat with ovs_lib
+ raise RuntimeError(m)
+ except eventlet.timeout.Timeout as e:
+ with excutils.save_and_reraise_exception() as ctx:
+ if e is timeout:
+ ctx.reraise = False
+ m = _LE("ofctl request %(request)s timed out") % {
+ "request": msg,
+ }
+ LOG.error(m)
+ # NOTE(yamamoto): use RuntimeError for compat with ovs_lib
+ raise RuntimeError(m)
+ finally:
+ timeout.cancel()
+ LOG.debug("ofctl request %(request)s result %(result)s",
+ {"request": msg, "result": result})
+ return result
+
+ @staticmethod
+ def _match(_ofp, ofpp, match, **match_kwargs):
+ if match is not None:
+ return match
+ return ofpp.OFPMatch(**match_kwargs)
+
+ def delete_flows(self, table_id=None, strict=False, priority=0,
+ cookie=0, cookie_mask=0,
+ match=None, **match_kwargs):
+ (dp, ofp, ofpp) = self._get_dp()
+ if table_id is None:
+ table_id = ofp.OFPTT_ALL
+ match = self._match(ofp, ofpp, match, **match_kwargs)
+ if strict:
+ cmd = ofp.OFPFC_DELETE_STRICT
+ else:
+ cmd = ofp.OFPFC_DELETE
+ msg = ofpp.OFPFlowMod(dp,
+ command=cmd,
+ cookie=cookie,
+ cookie_mask=cookie_mask,
+ table_id=table_id,
+ match=match,
+ priority=priority,
+ out_group=ofp.OFPG_ANY,
+ out_port=ofp.OFPP_ANY)
+ self._send_msg(msg)
+
+ def dump_flows(self, table_id=None):
+ (dp, ofp, ofpp) = self._get_dp()
+ if table_id is None:
+ table_id = ofp.OFPTT_ALL
+ msg = ofpp.OFPFlowStatsRequest(dp, table_id=table_id)
+ replies = self._send_msg(msg,
+ reply_cls=ofpp.OFPFlowStatsReply,
+ reply_multi=True)
+ flows = []
+ for rep in replies:
+ flows += rep.body
+ return flows
+
+ def cleanup_flows(self):
+ cookies = set([f.cookie for f in self.dump_flows()])
+ for c in cookies:
+ if c == self.agent_uuid_stamp:
+ continue
+ LOG.warn(_LW("Deleting flow with cookie 0x%(cookie)x") % {
+ 'cookie': c})
+ self.delete_flows(cookie=c, cookie_mask=((1 << 64) - 1))
+
+ def install_goto_next(self, table_id):
+ self.install_goto(table_id=table_id, dest_table_id=table_id + 1)
+
+ def install_output(self, port, table_id=0, priority=0,
+ match=None, **match_kwargs):
+ (_dp, ofp, ofpp) = self._get_dp()
+ actions = [ofpp.OFPActionOutput(port, 0)]
+ instructions = [ofpp.OFPInstructionActions(
+ ofp.OFPIT_APPLY_ACTIONS, actions)]
+ self.install_instructions(table_id=table_id, priority=priority,
+ instructions=instructions,
+ match=match, **match_kwargs)
+
+ def install_normal(self, table_id=0, priority=0,
+ match=None, **match_kwargs):
+ (_dp, ofp, _ofpp) = self._get_dp()
+ self.install_output(port=ofp.OFPP_NORMAL,
+ table_id=table_id, priority=priority,
+ match=match, **match_kwargs)
+
+ def install_goto(self, dest_table_id, table_id=0, priority=0,
+ match=None, **match_kwargs):
+ (_dp, _ofp, ofpp) = self._get_dp()
+ instructions = [ofpp.OFPInstructionGotoTable(table_id=dest_table_id)]
+ self.install_instructions(table_id=table_id, priority=priority,
+ instructions=instructions,
+ match=match, **match_kwargs)
+
+ def install_drop(self, table_id=0, priority=0, match=None, **match_kwargs):
+ self.install_instructions(table_id=table_id, priority=priority,
+ instructions=[], match=match, **match_kwargs)
+
+ def install_instructions(self, instructions,
+ table_id=0, priority=0,
+ match=None, **match_kwargs):
+ (dp, ofp, ofpp) = self._get_dp()
+ match = self._match(ofp, ofpp, match, **match_kwargs)
+ msg = ofpp.OFPFlowMod(dp,
+ table_id=table_id,
+ cookie=self.agent_uuid_stamp,
+ match=match,
+ priority=priority,
+ instructions=instructions)
+ self._send_msg(msg)
+
+ def install_apply_actions(self, actions,
+ table_id=0, priority=0,
+ match=None, **match_kwargs):
+ (dp, ofp, ofpp) = self._get_dp()
+ instructions = [
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
+ ]
+ self.install_instructions(table_id=table_id,
+ priority=priority,
+ match=match,
+ instructions=instructions,
+ **match_kwargs)
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+from oslo_utils import excutils
+
+from neutron.agent.common import ovs_lib
+from neutron.i18n import _LI
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import ofswitch
+
+
+LOG = logging.getLogger(__name__)
+
+
+class OVSAgentBridge(ofswitch.OpenFlowSwitchMixin, ovs_lib.OVSBridge):
+ """Common code for bridges used by OVS agent"""
+
+ _cached_dpid = None
+
+ def _get_dp(self):
+ """Get (dp, ofp, ofpp) tuple for the switch.
+
+ A convenient method for openflow message composers.
+ """
+ while True:
+ dpid_int = self._cached_dpid
+ if dpid_int is None:
+ dpid_str = self.get_datapath_id()
+ LOG.info(_LI("Bridge %(br_name)s has datapath-ID %(dpid)s"),
+ {"br_name": self.br_name, "dpid": dpid_str})
+ dpid_int = int(dpid_str, 16)
+ try:
+ dp = self._get_dp_by_dpid(dpid_int)
+ except RuntimeError:
+ with excutils.save_and_reraise_exception() as ctx:
+ self._cached_dpid = None
+ # Retry if dpid has been changed.
+ # NOTE(yamamoto): Open vSwitch change its dpid on
+ # some events.
+ # REVISIT(yamamoto): Consider to set dpid statically.
+ new_dpid_str = self.get_datapath_id()
+ if new_dpid_str != dpid_str:
+ LOG.info(_LI("Bridge %(br_name)s changed its "
+ "datapath-ID from %(old)s to %(new)s"), {
+ "br_name": self.br_name,
+ "old": dpid_str,
+ "new": new_dpid_str,
+ })
+ ctx.reraise = False
+ else:
+ self._cached_dpid = dpid_int
+ return dp, dp.ofproto, dp.ofproto_parser
+
+ def setup_controllers(self, conf):
+ controllers = [
+ "tcp:%(address)s:%(port)s" % {
+ "address": conf.OVS.of_listen_address,
+ "port": conf.OVS.of_listen_port,
+ }
+ ]
+ self.set_protocols("OpenFlow13")
+ self.set_controller(controllers)
+
+ def drop_port(self, in_port):
+ self.install_drop(priority=2, in_port=in_port)
--- /dev/null
+# Copyright (C) 2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import functools
+
+import ryu.app.ofctl.api # noqa
+from ryu.base import app_manager
+from ryu.lib import hub
+from ryu.ofproto import ofproto_v1_3
+
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import br_int
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import br_phys
+from neutron.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import br_tun
+from neutron.plugins.ml2.drivers.openvswitch.agent \
+ import ovs_neutron_agent as ovs_agent
+
+
+class OVSNeutronAgentRyuApp(app_manager.RyuApp):
+ OFP_VERSIONS = [ofproto_v1_3.OFP_VERSION]
+
+ def start(self):
+ # Start Ryu event loop thread
+ super(OVSNeutronAgentRyuApp, self).start()
+
+ def _make_br_cls(br_cls):
+ return functools.partial(br_cls, ryu_app=self)
+
+ # Start agent main loop thread
+ bridge_classes = {
+ 'br_int': _make_br_cls(br_int.OVSIntegrationBridge),
+ 'br_phys': _make_br_cls(br_phys.OVSPhysicalBridge),
+ 'br_tun': _make_br_cls(br_tun.OVSTunnelBridge),
+ }
+ return hub.spawn(ovs_agent.main, bridge_classes)
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',
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):
ALLOWED_CMDS = [
'ip',
+ # NOTE(yamamoto): of_interface=native doesn't use ovs-ofctl
'ovs-ofctl',
'ovs-vsctl',
'ovsdb-client',
# under the License.
import eventlet
+import fixtures
import mock
from oslo_config import cfg
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
self.br_int = None
self.init_done = False
self.init_done_ev = eventlet.event.Event()
- self._main_thread = eventlet.spawn(self._kick_main)
self.addCleanup(self._kill_main)
-
- # Wait for _kick_main -> of_interface main -> _agent_main
- # NOTE(yamamoto): This complexity came from how "native" of_interface
- # runs its openflow controller. "native" of_interface's main routine
- # blocks while running the embedded openflow controller. In that case,
- # the agent rpc_loop runs in another thread. However, for FT we need
- # to run setUp() and test_xxx() in the same thread. So I made this
- # run of_interface's main in a separate thread instead.
- while not self.init_done:
- self.init_done_ev.wait()
+ retry_count = 3
+ while True:
+ cfg.CONF.set_override('of_listen_port',
+ net_helpers.get_free_namespace_port(
+ n_const.PROTO_NAME_TCP),
+ group='OVS')
+ self.of_interface_mod.init_config()
+ self._main_thread = eventlet.spawn(self._kick_main)
+
+ # Wait for _kick_main -> of_interface main -> _agent_main
+ # NOTE(yamamoto): This complexity came from how "native"
+ # of_interface runs its openflow controller. "native"
+ # of_interface's main routine blocks while running the
+ # embedded openflow controller. In that case, the agent
+ # rpc_loop runs in another thread. However, for FT we
+ # need to run setUp() and test_xxx() in the same thread.
+ # So I made this run of_interface's main in a separate
+ # thread instead.
+ try:
+ while not self.init_done:
+ self.init_done_ev.wait()
+ break
+ except fixtures.TimeoutException:
+ self._kill_main()
+ retry_count -= 1
+ if retry_count < 0:
+ raise Exception('port allocation failed')
def _kick_main(self):
with mock.patch.object(ovsagt, 'main', self._agent_main):
'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
pass
+class ARPSpoofNativeTestCase(_ARPSpoofTestCase, _OVSAgentNativeTestBase):
+ pass
+
+
class _CanaryTableTestCase(object):
def test_canary_table(self):
self.br_int.delete_flows()
class CanaryTableOFCtlTestCase(_CanaryTableTestCase, _OVSAgentOFCtlTestBase):
pass
+
+
+class CanaryTableNativeTestCase(_CanaryTableTestCase, _OVSAgentNativeTestBase):
+ pass
--- /dev/null
+# Copyright (C) 2014 VA Linux Systems Japan K.K.
+# Copyright (C) 2014 Fumihiko Kakuma <kakuma at valinux co jp>
+# Copyright (C) 2014 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+
+class _Eq(object):
+ def __eq__(self, other):
+ return repr(self) == repr(other)
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+
+class _Value(_Eq):
+ def __or__(self, b):
+ return _Op('|', self, b)
+
+ def __ror__(self, a):
+ return _Op('|', a, self)
+
+
+class _SimpleValue(_Value):
+ def __init__(self, name):
+ self.name = name
+
+ def __repr__(self):
+ return self.name
+
+
+class _Op(_Value):
+ def __init__(self, op, a, b):
+ self.op = op
+ self.a = a
+ self.b = b
+
+ def __repr__(self):
+ return '%s%s%s' % (self.a, self.op, self.b)
+
+
+def _mkcls(name):
+ class Cls(_Eq):
+ _name = name
+
+ def __init__(self, *args, **kwargs):
+ self._args = args
+ self._kwargs = kwargs
+ self._hist = []
+
+ def __getattr__(self, name):
+ return self._kwargs[name]
+
+ def __repr__(self):
+ args = list(map(repr, self._args))
+ kwargs = sorted(['%s=%s' % (x, y) for x, y in
+ self._kwargs.items()])
+ return '%s(%s)' % (self._name, ', '.join(args + kwargs))
+
+ return Cls
+
+
+class _Mod(object):
+ _cls_cache = {}
+
+ def __init__(self, name):
+ self._name = name
+
+ def __getattr__(self, name):
+ fullname = '%s.%s' % (self._name, name)
+ if '_' in name: # constants are named like OFPxxx_yyy_zzz
+ return _SimpleValue(fullname)
+ try:
+ return self._cls_cache[fullname]
+ except KeyError:
+ pass
+ cls = _mkcls(fullname)
+ self._cls_cache[fullname] = cls
+ return cls
+
+ def __repr__(self):
+ return 'Mod(%s)' % (self._name,)
+
+
+def patch_fake_oflib_of():
+ ryu_mod = mock.Mock()
+ ryu_base_mod = ryu_mod.base
+ ryu_exc_mod = ryu_mod.exception
+ ryu_ctrl_mod = ryu_mod.controller
+ handler = _Mod('ryu.controller.handler')
+ handler.set_ev_cls = mock.Mock()
+ ofp_event = _Mod('ryu.controller.ofp_event')
+ ryu_ctrl_mod.handler = handler
+ ryu_ctrl_mod.ofp_event = ofp_event
+ ryu_lib_mod = ryu_mod.lib
+ ryu_lib_hub = ryu_lib_mod.hub
+ ryu_packet_mod = ryu_lib_mod.packet
+ packet = _Mod('ryu.lib.packet.packet')
+ arp = _Mod('ryu.lib.packet.arp')
+ ethernet = _Mod('ryu.lib.packet.ethernet')
+ ether_types = _Mod('ryu.lib.packet.ether_types')
+ in_proto = _Mod('ryu.lib.packet.in_proto')
+ icmpv6 = _Mod('ryu.lib.packet.icmpv6')
+ vlan = _Mod('ryu.lib.packet.vlan')
+ ryu_packet_mod.packet = packet
+ packet.Packet = mock.Mock()
+ ryu_packet_mod.arp = arp
+ ryu_packet_mod.ethernet = ethernet
+ ryu_packet_mod.ether_types = ether_types
+ ryu_packet_mod.icmpv6 = icmpv6
+ ryu_packet_mod.in_proto = in_proto
+ ryu_packet_mod.vlan = vlan
+ ryu_ofproto_mod = ryu_mod.ofproto
+ ofp = _Mod('ryu.ofproto.ofproto_v1_3')
+ ofpp = _Mod('ryu.ofproto.ofproto_v1_3_parser')
+ ryu_ofproto_mod.ofproto_v1_3 = ofp
+ ryu_ofproto_mod.ofproto_v1_3_parser = ofpp
+ ryu_app_mod = ryu_mod.app
+ ryu_app_ofctl_mod = ryu_app_mod.ofctl
+ ryu_ofctl_api = ryu_app_ofctl_mod.api
+ modules = {'ryu': ryu_mod,
+ 'ryu.base': ryu_base_mod,
+ 'ryu.controller': ryu_ctrl_mod,
+ 'ryu.controller.handler': handler,
+ 'ryu.controller.handler.set_ev_cls': handler.set_ev_cls,
+ 'ryu.controller.ofp_event': ofp_event,
+ 'ryu.exception': ryu_exc_mod,
+ 'ryu.lib': ryu_lib_mod,
+ 'ryu.lib.hub': ryu_lib_hub,
+ 'ryu.lib.packet': ryu_packet_mod,
+ 'ryu.lib.packet.packet': packet,
+ 'ryu.lib.packet.packet.Packet': packet.Packet,
+ 'ryu.lib.packet.arp': arp,
+ 'ryu.lib.packet.ethernet': ethernet,
+ 'ryu.lib.packet.ether_types': ether_types,
+ 'ryu.lib.packet.icmpv6': icmpv6,
+ 'ryu.lib.packet.in_proto': in_proto,
+ 'ryu.lib.packet.vlan': vlan,
+ 'ryu.ofproto': ryu_ofproto_mod,
+ 'ryu.ofproto.ofproto_v1_3': ofp,
+ 'ryu.ofproto.ofproto_v1_3_parser': ofpp,
+ 'ryu.app': ryu_app_mod,
+ 'ryu.app.ofctl': ryu_app_ofctl_mod,
+ 'ryu.app.ofctl.api': ryu_ofctl_api}
+ return mock.patch.dict('sys.modules', modules)
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from oslo_utils import importutils
+
+from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent \
+ import ovs_test_base
+
+
+call = mock.call # short hand
+
+
+class OVSBridgeTestBase(ovs_test_base.OVSRyuTestBase):
+ _ARP_MODULE = 'ryu.lib.packet.arp'
+ _ETHER_TYPES_MODULE = 'ryu.lib.packet.ether_types'
+ _ICMPV6_MODULE = 'ryu.lib.packet.icmpv6'
+ _IN_PROTO_MODULE = 'ryu.lib.packet.in_proto'
+ _OFP_MODULE = 'ryu.ofproto.ofproto_v1_3'
+ _OFPP_MODULE = 'ryu.ofproto.ofproto_v1_3_parser'
+
+ def setup_bridge_mock(self, name, cls):
+ self.br = cls(name)
+ self.dp = mock.Mock()
+ self.ofp = importutils.import_module(self._OFP_MODULE)
+ self.ofpp = importutils.import_module(self._OFPP_MODULE)
+ self.arp = importutils.import_module(self._ARP_MODULE)
+ self.ether_types = importutils.import_module(self._ETHER_TYPES_MODULE)
+ self.icmpv6 = importutils.import_module(self._ICMPV6_MODULE)
+ self.in_proto = importutils.import_module(self._IN_PROTO_MODULE)
+ mock.patch.object(self.br, '_get_dp', autospec=True,
+ return_value=self._get_dp()).start()
+ mock__send_msg = mock.patch.object(self.br, '_send_msg').start()
+ mock_delete_flows = mock.patch.object(self.br, 'delete_flows').start()
+ self.mock = mock.Mock()
+ self.mock.attach_mock(mock__send_msg, '_send_msg')
+ self.mock.attach_mock(mock_delete_flows, 'delete_flows')
+
+ def _get_dp(self):
+ return self.dp, self.ofp, self.ofpp
+
+ def test_drop_port(self):
+ in_port = 2345
+ self.br.drop_port(in_port=in_port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(
+ ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(in_port=in_port),
+ priority=2,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_goto(self):
+ dest_table_id = 123
+ priority = 99
+ in_port = 666
+ self.br.install_goto(dest_table_id=dest_table_id,
+ priority=priority, in_port=in_port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(
+ ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionGotoTable(table_id=dest_table_id),
+ ],
+ match=ofpp.OFPMatch(in_port=in_port),
+ priority=priority,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_drop(self):
+ priority = 99
+ in_port = 666
+ self.br.install_drop(priority=priority, in_port=in_port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(
+ ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(in_port=in_port),
+ priority=priority,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_normal(self):
+ priority = 99
+ in_port = 666
+ self.br.install_normal(priority=priority, in_port=in_port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(
+ ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0)
+ ]),
+ ],
+ match=ofpp.OFPMatch(in_port=in_port),
+ priority=priority,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test__cidr_to_ryu(self):
+ f = self.br._cidr_to_ryu
+ self.assertEqual('192.168.0.1', f('192.168.0.1'))
+ self.assertEqual('192.168.0.1', f('192.168.0.1/32'))
+ self.assertEqual(('192.168.0.0', '255.255.255.0'), f('192.168.0.0/24'))
+
+
+class OVSDVRProcessTestMixin(object):
+ def test_install_dvr_process_ipv4(self):
+ vlan_tag = 999
+ gateway_ip = '192.0.2.1'
+ self.br.install_dvr_process_ipv4(vlan_tag=vlan_tag,
+ gateway_ip=gateway_ip)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(
+ eth_type=self.ether_types.ETH_TYPE_ARP,
+ arp_tpa=gateway_ip,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+ priority=3,
+ table_id=self.dvr_process_table_id)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_dvr_process_ipv4(self):
+ vlan_tag = 999
+ gateway_ip = '192.0.2.1'
+ self.br.delete_dvr_process_ipv4(vlan_tag=vlan_tag,
+ gateway_ip=gateway_ip)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(table_id=self.dvr_process_table_id,
+ match=ofpp.OFPMatch(
+ eth_type=self.ether_types.ETH_TYPE_ARP,
+ arp_tpa=gateway_ip,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_dvr_process_ipv6(self):
+ vlan_tag = 999
+ gateway_mac = '08:60:6e:7f:74:e7'
+ self.br.install_dvr_process_ipv6(vlan_tag=vlan_tag,
+ gateway_mac=gateway_mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(
+ eth_src=gateway_mac,
+ eth_type=self.ether_types.ETH_TYPE_IPV6,
+ icmpv6_type=self.icmpv6.ND_ROUTER_ADVERT,
+ ip_proto=self.in_proto.IPPROTO_ICMPV6,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+ priority=3,
+ table_id=self.dvr_process_table_id)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_dvr_process_ipv6(self):
+ vlan_tag = 999
+ gateway_mac = '08:60:6e:7f:74:e7'
+ self.br.delete_dvr_process_ipv6(vlan_tag=vlan_tag,
+ gateway_mac=gateway_mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(table_id=self.dvr_process_table_id,
+ match=ofpp.OFPMatch(
+ eth_src=gateway_mac,
+ eth_type=self.ether_types.ETH_TYPE_IPV6,
+ icmpv6_type=self.icmpv6.ND_ROUTER_ADVERT,
+ ip_proto=self.in_proto.IPPROTO_ICMPV6,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_dvr_process(self):
+ vlan_tag = 999
+ vif_mac = '00:0e:0c:5e:95:d0'
+ dvr_mac_address = 'f2:0b:a4:5b:b2:ab'
+ self.br.install_dvr_process(vlan_tag=vlan_tag,
+ vif_mac=vif_mac,
+ dvr_mac_address=dvr_mac_address)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(
+ eth_dst=vif_mac,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+ priority=2,
+ table_id=self.dvr_process_table_id)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionSetField(eth_src=dvr_mac_address),
+ ]),
+ ofpp.OFPInstructionGotoTable(
+ table_id=self.dvr_process_next_table_id),
+ ],
+ match=ofpp.OFPMatch(
+ eth_src=vif_mac,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+ priority=1,
+ table_id=self.dvr_process_table_id)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_dvr_process(self):
+ vlan_tag = 999
+ vif_mac = '00:0e:0c:5e:95:d0'
+ self.br.delete_dvr_process(vlan_tag=vlan_tag,
+ vif_mac=vif_mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(table_id=self.dvr_process_table_id,
+ match=ofpp.OFPMatch(
+ eth_dst=vif_mac,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+ call.delete_flows(table_id=self.dvr_process_table_id,
+ match=ofpp.OFPMatch(
+ eth_src=vif_mac,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import ovs_bridge_test_base
+
+
+call = mock.call # short hand
+
+
+class OVSIntegrationBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase):
+ def setUp(self):
+ super(OVSIntegrationBridgeTest, self).setUp()
+ self.setup_bridge_mock('br-int', self.br_int_cls)
+
+ def test_setup_default_table(self):
+ self.br.setup_default_table()
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(
+ ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0)
+ ]),
+ ],
+ match=ofpp.OFPMatch(),
+ priority=0,
+ table_id=0)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0,
+ table_id=23)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0,
+ table_id=24)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_provision_local_vlan(self):
+ port = 999
+ lvid = 888
+ segmentation_id = 777
+ self.br.provision_local_vlan(port=port, lvid=lvid,
+ segmentation_id=segmentation_id)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionSetField(
+ vlan_vid=lvid | ofp.OFPVID_PRESENT),
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0)
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ in_port=port,
+ vlan_vid=segmentation_id | ofp.OFPVID_PRESENT),
+ priority=3,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_provision_local_vlan_novlan(self):
+ port = 999
+ lvid = 888
+ segmentation_id = None
+ self.br.provision_local_vlan(port=port, lvid=lvid,
+ segmentation_id=segmentation_id)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionPushVlan(),
+ ofpp.OFPActionSetField(
+ vlan_vid=lvid | ofp.OFPVID_PRESENT),
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ in_port=port,
+ vlan_vid=ofp.OFPVID_NONE),
+ priority=3,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_reclaim_local_vlan(self):
+ port = 999
+ segmentation_id = 777
+ self.br.reclaim_local_vlan(port=port, segmentation_id=segmentation_id)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(
+ match=ofpp.OFPMatch(
+ in_port=port,
+ vlan_vid=segmentation_id | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_reclaim_local_vlan_novlan(self):
+ port = 999
+ segmentation_id = None
+ self.br.reclaim_local_vlan(port=port, segmentation_id=segmentation_id)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(
+ match=ofpp.OFPMatch(
+ in_port=port,
+ vlan_vid=ofp.OFPVID_NONE)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_dvr_to_src_mac(self):
+ network_type = 'vxlan'
+ vlan_tag = 1111
+ gateway_mac = '08:60:6e:7f:74:e7'
+ dst_mac = '00:02:b3:13:fe:3d'
+ dst_port = 6666
+ self.br.install_dvr_to_src_mac(network_type=network_type,
+ vlan_tag=vlan_tag,
+ gateway_mac=gateway_mac,
+ dst_mac=dst_mac,
+ dst_port=dst_port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionPopVlan(),
+ ofpp.OFPActionSetField(eth_src=gateway_mac),
+ ofpp.OFPActionOutput(6666, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ eth_dst=dst_mac,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+ priority=4,
+ table_id=1)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_dvr_to_src_mac(self):
+ network_type = 'vxlan'
+ vlan_tag = 1111
+ dst_mac = '00:02:b3:13:fe:3d'
+ self.br.delete_dvr_to_src_mac(network_type=network_type,
+ vlan_tag=vlan_tag,
+ dst_mac=dst_mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(table_id=1,
+ match=ofpp.OFPMatch(
+ eth_dst=dst_mac,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_dvr_to_src_mac_vlan(self):
+ network_type = 'vlan'
+ vlan_tag = 1111
+ gateway_mac = '08:60:6e:7f:74:e7'
+ dst_mac = '00:02:b3:13:fe:3d'
+ dst_port = 6666
+ self.br.install_dvr_to_src_mac(network_type=network_type,
+ vlan_tag=vlan_tag,
+ gateway_mac=gateway_mac,
+ dst_mac=dst_mac,
+ dst_port=dst_port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionPopVlan(),
+ ofpp.OFPActionSetField(eth_src=gateway_mac),
+ ofpp.OFPActionOutput(dst_port, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ eth_dst=dst_mac,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT),
+ priority=4,
+ table_id=2)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_dvr_to_src_mac_vlan(self):
+ network_type = 'vlan'
+ vlan_tag = 1111
+ dst_mac = '00:02:b3:13:fe:3d'
+ self.br.delete_dvr_to_src_mac(network_type=network_type,
+ vlan_tag=vlan_tag,
+ dst_mac=dst_mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(table_id=2,
+ match=ofpp.OFPMatch(
+ eth_dst=dst_mac,
+ vlan_vid=vlan_tag | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_add_dvr_mac_vlan(self):
+ mac = '00:02:b3:13:fe:3d'
+ port = 8888
+ self.br.add_dvr_mac_vlan(mac=mac, port=port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionGotoTable(table_id=2),
+ ],
+ match=ofpp.OFPMatch(
+ eth_src=mac,
+ in_port=port),
+ priority=4,
+ table_id=0))
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_remove_dvr_mac_vlan(self):
+ mac = '00:02:b3:13:fe:3d'
+ self.br.remove_dvr_mac_vlan(mac=mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(eth_src=mac, table_id=0),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_add_dvr_mac_tun(self):
+ mac = '00:02:b3:13:fe:3d'
+ port = 8888
+ self.br.add_dvr_mac_tun(mac=mac, port=port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionGotoTable(table_id=1),
+ ],
+ match=ofpp.OFPMatch(
+ eth_src=mac,
+ in_port=port),
+ priority=2,
+ table_id=0))
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_remove_dvr_mac_tun(self):
+ mac = '00:02:b3:13:fe:3d'
+ port = 8888
+ self.br.remove_dvr_mac_tun(mac=mac, port=port)
+ expected = [
+ call.delete_flows(eth_src=mac, in_port=port, table_id=0),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_arp_spoofing_protection(self):
+ port = 8888
+ ip_addresses = ['192.0.2.1', '192.0.2.2/32']
+ self.br.install_arp_spoofing_protection(port, ip_addresses)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ eth_type=self.ether_types.ETH_TYPE_ARP,
+ arp_spa='192.0.2.1',
+ in_port=8888,
+ ),
+ priority=2,
+ table_id=24)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ eth_type=self.ether_types.ETH_TYPE_ARP,
+ arp_spa='192.0.2.2',
+ in_port=8888
+ ),
+ priority=2,
+ table_id=24)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionGotoTable(table_id=24),
+ ],
+ match=ofpp.OFPMatch(
+ eth_type=self.ether_types.ETH_TYPE_ARP,
+ in_port=8888,
+ ),
+ priority=10,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_arp_spoofing_protection(self):
+ port = 8888
+ self.br.delete_arp_spoofing_protection(port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(table_id=0, match=ofpp.OFPMatch(
+ eth_type=self.ether_types.ETH_TYPE_ARP,
+ in_port=8888)),
+ call.delete_flows(table_id=24, in_port=port),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+import neutron.plugins.ml2.drivers.openvswitch.agent.common.constants \
+ as ovs_const
+from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import ovs_bridge_test_base
+
+
+call = mock.call # short hand
+
+
+class OVSPhysicalBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
+ ovs_bridge_test_base.OVSDVRProcessTestMixin):
+ dvr_process_table_id = ovs_const.DVR_PROCESS_VLAN
+ dvr_process_next_table_id = ovs_const.LOCAL_VLAN_TRANSLATION
+
+ def setUp(self):
+ super(OVSPhysicalBridgeTest, self).setUp()
+ self.setup_bridge_mock('br-phys', self.br_phys_cls)
+
+ def test_setup_default_table(self):
+ self.br.setup_default_table()
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(),
+ priority=0,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_provision_local_vlan(self):
+ port = 999
+ lvid = 888
+ segmentation_id = 777
+ distributed = False
+ self.br.provision_local_vlan(port=port, lvid=lvid,
+ segmentation_id=segmentation_id,
+ distributed=distributed)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionSetField(
+ vlan_vid=segmentation_id | ofp.OFPVID_PRESENT),
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ in_port=port,
+ vlan_vid=lvid | ofp.OFPVID_PRESENT),
+ priority=4,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_provision_local_vlan_novlan(self):
+ port = 999
+ lvid = 888
+ segmentation_id = None
+ distributed = False
+ self.br.provision_local_vlan(port=port, lvid=lvid,
+ segmentation_id=segmentation_id,
+ distributed=distributed)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionPopVlan(),
+ ofpp.OFPActionOutput(ofp.OFPP_NORMAL, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ in_port=port,
+ vlan_vid=lvid | ofp.OFPVID_PRESENT),
+ priority=4,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_reclaim_local_vlan(self):
+ port = 999
+ lvid = 888
+ self.br.reclaim_local_vlan(port=port, lvid=lvid)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(
+ match=ofpp.OFPMatch(
+ in_port=port,
+ vlan_vid=lvid | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_add_dvr_mac_vlan(self):
+ mac = '00:02:b3:13:fe:3d'
+ port = 8888
+ self.br.add_dvr_mac_vlan(mac=mac, port=port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionOutput(port, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(eth_src=mac),
+ priority=2,
+ table_id=3)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_remove_dvr_mac_vlan(self):
+ mac = '00:02:b3:13:fe:3d'
+ self.br.remove_dvr_mac_vlan(mac=mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(eth_src=mac, table_id=3),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
--- /dev/null
+# Copyright (C) 2014,2015 VA Linux Systems Japan K.K.
+# Copyright (C) 2014,2015 YAMAMOTO Takashi <yamamoto at valinux co jp>
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+import neutron.plugins.ml2.drivers.openvswitch.agent.common.constants \
+ as ovs_const
+from neutron.tests.unit.plugins.ml2.drivers.openvswitch.agent.openflow.native \
+ import ovs_bridge_test_base
+
+
+call = mock.call # short hand
+
+
+class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
+ ovs_bridge_test_base.OVSDVRProcessTestMixin):
+ dvr_process_table_id = ovs_const.DVR_PROCESS
+ dvr_process_next_table_id = ovs_const.PATCH_LV_TO_TUN
+
+ def setUp(self):
+ super(OVSTunnelBridgeTest, self).setUp()
+ self.setup_bridge_mock('br-tun', self.br_tun_cls)
+
+ def test_setup_default_table(self):
+ patch_int_ofport = 5555
+ arp_responder_enabled = False
+ self.br.setup_default_table(patch_int_ofport=patch_int_ofport,
+ arp_responder_enabled=arp_responder_enabled)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=2)],
+ match=ofpp.OFPMatch(in_port=patch_int_ofport),
+ priority=1, table_id=0)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0, table_id=0)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=20)],
+ match=ofpp.OFPMatch(
+ eth_dst=('00:00:00:00:00:00', '01:00:00:00:00:00')),
+ priority=0,
+ table_id=2)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+ match=ofpp.OFPMatch(
+ eth_dst=('01:00:00:00:00:00', '01:00:00:00:00:00')),
+ priority=0,
+ table_id=2)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0, table_id=3)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0, table_id=4)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0, table_id=6)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.NXActionLearn(
+ cookie=0,
+ hard_timeout=300,
+ priority=1,
+ specs=[
+ ofpp.NXFlowSpecMatch(
+ dst=('vlan_vid', 0),
+ n_bits=12,
+ src=('vlan_vid', 0)),
+ ofpp.NXFlowSpecMatch(
+ dst=('eth_dst', 0),
+ n_bits=48,
+ src=('eth_src', 0)),
+ ofpp.NXFlowSpecLoad(
+ dst=('vlan_vid', 0),
+ n_bits=12,
+ src=0),
+ ofpp.NXFlowSpecLoad(
+ dst=('tunnel_id', 0),
+ n_bits=64,
+ src=('tunnel_id', 0)),
+ ofpp.NXFlowSpecOutput(
+ dst='',
+ n_bits=32,
+ src=('in_port', 0)),
+ ],
+ table_id=20),
+ ofpp.OFPActionOutput(patch_int_ofport, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(),
+ priority=1,
+ table_id=10)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+ match=ofpp.OFPMatch(),
+ priority=0,
+ table_id=20)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0,
+ table_id=22))
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_setup_default_table_arp_responder_enabled(self):
+ patch_int_ofport = 5555
+ arp_responder_enabled = True
+ self.br.setup_default_table(patch_int_ofport=patch_int_ofport,
+ arp_responder_enabled=arp_responder_enabled)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=2)],
+ match=ofpp.OFPMatch(in_port=patch_int_ofport),
+ priority=1, table_id=0)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0, table_id=0)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=21)],
+ match=ofpp.OFPMatch(
+ eth_dst='ff:ff:ff:ff:ff:ff',
+ eth_type=self.ether_types.ETH_TYPE_ARP),
+ priority=1,
+ table_id=2)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=20)],
+ match=ofpp.OFPMatch(
+ eth_dst=('00:00:00:00:00:00', '01:00:00:00:00:00')),
+ priority=0,
+ table_id=2)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+ match=ofpp.OFPMatch(
+ eth_dst=('01:00:00:00:00:00', '01:00:00:00:00:00')),
+ priority=0,
+ table_id=2)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0, table_id=3)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0, table_id=4)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0, table_id=6)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.NXActionLearn(
+ cookie=0,
+ hard_timeout=300,
+ priority=1,
+ specs=[
+ ofpp.NXFlowSpecMatch(
+ dst=('vlan_vid', 0),
+ n_bits=12,
+ src=('vlan_vid', 0)),
+ ofpp.NXFlowSpecMatch(
+ dst=('eth_dst', 0),
+ n_bits=48,
+ src=('eth_src', 0)),
+ ofpp.NXFlowSpecLoad(
+ dst=('vlan_vid', 0),
+ n_bits=12,
+ src=0),
+ ofpp.NXFlowSpecLoad(
+ dst=('tunnel_id', 0),
+ n_bits=64,
+ src=('tunnel_id', 0)),
+ ofpp.NXFlowSpecOutput(
+ dst='',
+ n_bits=32,
+ src=('in_port', 0)),
+ ],
+ table_id=20),
+ ofpp.OFPActionOutput(patch_int_ofport, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(),
+ priority=1,
+ table_id=10)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+ match=ofpp.OFPMatch(),
+ priority=0,
+ table_id=20)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[ofpp.OFPInstructionGotoTable(table_id=22)],
+ match=ofpp.OFPMatch(),
+ priority=0,
+ table_id=21)),
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[],
+ match=ofpp.OFPMatch(),
+ priority=0,
+ table_id=22))
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_provision_local_vlan(self):
+ network_type = 'vxlan'
+ lvid = 888
+ segmentation_id = 777
+ distributed = False
+ self.br.provision_local_vlan(network_type=network_type, lvid=lvid,
+ segmentation_id=segmentation_id,
+ distributed=distributed)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionPushVlan(),
+ ofpp.OFPActionSetField(
+ vlan_vid=lvid | ofp.OFPVID_PRESENT)
+ ]),
+ ofpp.OFPInstructionGotoTable(table_id=10),
+ ],
+ match=ofpp.OFPMatch(tunnel_id=segmentation_id),
+ priority=1,
+ table_id=4)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_reclaim_local_vlan(self):
+ network_type = 'vxlan'
+ segmentation_id = 777
+ self.br.reclaim_local_vlan(network_type=network_type,
+ segmentation_id=segmentation_id)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(
+ table_id=4,
+ match=ofpp.OFPMatch(tunnel_id=segmentation_id)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_flood_to_tun(self):
+ vlan = 3333
+ tun_id = 2222
+ ports = [11, 44, 22, 33]
+ self.br.install_flood_to_tun(vlan=vlan,
+ tun_id=tun_id,
+ ports=ports)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionPopVlan(),
+ ofpp.OFPActionSetField(tunnel_id=tun_id),
+ ] + [ofpp.OFPActionOutput(p, 0) for p in ports]),
+ ],
+ match=ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT),
+ priority=1,
+ table_id=22)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_flood_to_tun(self):
+ vlan = 3333
+ self.br.delete_flood_to_tun(vlan=vlan)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(table_id=22,
+ match=ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_unicast_to_tun(self):
+ vlan = 3333
+ port = 55
+ mac = '08:60:6e:7f:74:e7'
+ tun_id = 2222
+ self.br.install_unicast_to_tun(vlan=vlan,
+ tun_id=tun_id,
+ port=port,
+ mac=mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionPopVlan(),
+ ofpp.OFPActionSetField(tunnel_id=tun_id),
+ ofpp.OFPActionOutput(port, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ eth_dst=mac, vlan_vid=vlan | ofp.OFPVID_PRESENT),
+ priority=2,
+ table_id=20)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_unicast_to_tun(self):
+ vlan = 3333
+ mac = '08:60:6e:7f:74:e7'
+ self.br.delete_unicast_to_tun(vlan=vlan, mac=mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(table_id=20,
+ match=ofpp.OFPMatch(
+ eth_dst=mac, vlan_vid=vlan | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_unicast_to_tun_without_mac(self):
+ vlan = 3333
+ mac = None
+ self.br.delete_unicast_to_tun(vlan=vlan, mac=mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(table_id=20,
+ match=ofpp.OFPMatch(vlan_vid=vlan | ofp.OFPVID_PRESENT)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_install_arp_responder(self):
+ vlan = 3333
+ ip = '192.0.2.1'
+ mac = '08:60:6e:7f:74:e7'
+ self.br.install_arp_responder(vlan=vlan, ip=ip, mac=mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionSetField(arp_op=self.arp.ARP_REPLY),
+ ofpp.NXActionRegMove(
+ dst_field='arp_tha',
+ n_bits=48,
+ src_field='arp_sha'),
+ ofpp.NXActionRegMove(
+ dst_field='arp_tpa',
+ n_bits=32,
+ src_field='arp_spa'),
+ ofpp.OFPActionSetField(arp_sha=mac),
+ ofpp.OFPActionSetField(arp_spa=ip),
+ ofpp.OFPActionOutput(ofp.OFPP_IN_PORT, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(
+ eth_type=self.ether_types.ETH_TYPE_ARP,
+ arp_tpa=ip,
+ vlan_vid=vlan | ofp.OFPVID_PRESENT),
+ priority=1,
+ table_id=21)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_arp_responder(self):
+ vlan = 3333
+ ip = '192.0.2.1'
+ self.br.delete_arp_responder(vlan=vlan, ip=ip)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(
+ match=ofpp.OFPMatch(
+ eth_type=self.ether_types.ETH_TYPE_ARP,
+ arp_tpa=ip,
+ vlan_vid=vlan | ofp.OFPVID_PRESENT),
+ table_id=21),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_delete_arp_responder_without_ip(self):
+ vlan = 3333
+ ip = None
+ self.br.delete_arp_responder(vlan=vlan, ip=ip)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(
+ match=ofpp.OFPMatch(
+ eth_type=self.ether_types.ETH_TYPE_ARP,
+ vlan_vid=vlan | ofp.OFPVID_PRESENT),
+ table_id=21),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_setup_tunnel_port(self):
+ network_type = 'vxlan'
+ port = 11111
+ self.br.setup_tunnel_port(network_type=network_type, port=port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionGotoTable(table_id=4),
+ ],
+ match=ofpp.OFPMatch(in_port=port),
+ priority=1,
+ table_id=0)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_cleanup_tunnel_port(self):
+ port = 11111
+ self.br.cleanup_tunnel_port(port=port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(in_port=port),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_add_dvr_mac_tun(self):
+ mac = '00:02:b3:13:fe:3d'
+ port = 8888
+ self.br.add_dvr_mac_tun(mac=mac, port=port)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call._send_msg(ofpp.OFPFlowMod(dp,
+ cookie=0,
+ instructions=[
+ ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
+ ofpp.OFPActionOutput(port, 0),
+ ]),
+ ],
+ match=ofpp.OFPMatch(eth_src=mac),
+ priority=1,
+ table_id=9)),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
+
+ def test_remove_dvr_mac_tun(self):
+ mac = '00:02:b3:13:fe:3d'
+ self.br.remove_dvr_mac_tun(mac=mac)
+ (dp, ofp, ofpp) = self._get_dp()
+ expected = [
+ call.delete_flows(eth_src=mac, table_id=9),
+ ]
+ self.assertEqual(expected, self.mock.mock_calls)
{'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[],'
{'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[],'
# 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'
_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)
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:
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):
pass
+class AncillaryBridgesTestRyu(AncillaryBridgesTest,
+ ovs_test_base.OVSRyuTestBase):
+ pass
+
+
class TestOvsDvrNeutronAgent(object):
def setUp(self):
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')
pass
+class TunnelTestRyu(TunnelTest, ovs_test_base.OVSRyuTestBase):
+ pass
+
+
class TunnelTestUseVethInterco(TunnelTest):
USE_VETH_INTERCONNECTION = True
pass
+class TunnelTestUseVethIntercoRyu(TunnelTestUseVethInterco,
+ ovs_test_base.OVSRyuTestBase):
+ pass
+
+
class TunnelTestWithMTU(TunnelTestUseVethInterco):
VETH_MTU = 1500
class TunnelTestWithMTUOFCtl(TunnelTestWithMTU,
ovs_test_base.OVSOFCtlTestBase):
pass
+
+
+class TunnelTestWithMTURyu(TunnelTestWithMTU,
+ ovs_test_base.OVSRyuTestBase):
+ pass
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