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