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:
YAMAMOTO Takashi 2015-03-02 16:40:11 +09:00 committed by IWAMOTO Toshihiro
parent 24fde6cae2
commit b3e7e21c32
29 changed files with 2583 additions and 38 deletions

View File

@ -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.

View File

@ -12,6 +12,7 @@
# unclear whether both variants are necessary, but I'm transliterating
# from the old mechanism
ovs-vsctl: CommandFilter, ovs-vsctl, root
# NOTE(yamamoto): of_interface=native doesn't use ovs-ofctl
ovs-ofctl: CommandFilter, ovs-ofctl, root
kill_ovsdb_client: KillFilter, root, /usr/bin/ovsdb-client, -9
ovsdb-client: CommandFilter, ovsdb-client, root

View File

@ -152,7 +152,7 @@ class OVSBridge(BaseOVS):
super(OVSBridge, self).__init__()
self.br_name = br_name
self.datapath_type = datapath_type
self.agent_uuid_stamp = '0x0'
self.agent_uuid_stamp = 0
def set_agent_uuid_stamp(self, val):
self.agent_uuid_stamp = val

View File

@ -45,12 +45,27 @@ ovs_opts = [
cfg.BoolOpt('use_veth_interconnection', default=False,
help=_("Use veths instead of patch ports to interconnect the "
"integration bridge to physical bridges.")),
cfg.StrOpt('of_interface', default='ovs-ofctl', choices=['ovs-ofctl'],
cfg.StrOpt('of_interface', default='ovs-ofctl',
choices=['ovs-ofctl', 'native'],
help=_("OpenFlow interface to use.")),
cfg.StrOpt('datapath_type', default=constants.OVS_DATAPATH_SYSTEM,
choices=[constants.OVS_DATAPATH_SYSTEM,
constants.OVS_DATAPATH_NETDEV],
help=_("OVS datapath to use.")),
cfg.IPOpt('of_listen_address', default='127.0.0.1',
help=_("Address to listen on for OpenFlow connections. "
"Used only for 'native' driver.")),
cfg.IntOpt('of_listen_port', default=6633,
help=_("Port to listen on for OpenFlow connections. "
"Used only for 'native' driver.")),
cfg.IntOpt('of_connect_timeout', default=30,
help=_("Timeout in seconds to wait for "
"the local switch connecting the controller. "
"Used only for 'native' driver.")),
cfg.IntOpt('of_request_timeout', default=10,
help=_("Timeout in seconds to wait for a single "
"OpenFlow request. "
"Used only for 'native' driver.")),
]
agent_opts = [

View File

@ -33,6 +33,8 @@ cfg.CONF.import_group('OVS', 'neutron.plugins.ml2.drivers.openvswitch.agent.'
_main_modules = {
'ovs-ofctl': 'neutron.plugins.ml2.drivers.openvswitch.agent.openflow.'
'ovs_ofctl.main',
'native': 'neutron.plugins.ml2.drivers.openvswitch.agent.openflow.'
'native.main',
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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',
])

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -62,7 +62,7 @@ class OVSTunnelBridge(ovs_bridge.OVSAgentBridge,
if arp_responder_enabled:
# ARP broadcast-ed request go to the local ARP_RESPONDER
# table to be locally resolved
# REVISIT(yamamoto): arp_op=arp.ARP_REQUEST
# REVISIT(yamamoto): add arp_op=arp.ARP_REQUEST matcher?
deferred_br.add_flow(table=constants.PATCH_LV_TO_TUN,
priority=1,
proto='arp',

View File

@ -351,7 +351,8 @@ class OVSNeutronAgent(sg_rpc.SecurityGroupAgentRpcCallbackMixin,
self.provision_local_vlan(local_vlan_map['net_uuid'],
local_vlan_map['network_type'],
local_vlan_map['physical_network'],
local_vlan_map['segmentation_id'],
int(local_vlan_map[
'segmentation_id']),
local_vlan)
def setup_rpc(self):

View File

@ -34,6 +34,7 @@ import XenAPIPlugin
ALLOWED_CMDS = [
'ip',
# NOTE(yamamoto): of_interface=native doesn't use ovs-ofctl
'ovs-ofctl',
'ovs-vsctl',
'ovsdb-client',

View File

@ -14,6 +14,7 @@
# under the License.
import eventlet
import fixtures
import mock
from oslo_config import cfg
@ -21,6 +22,7 @@ from oslo_utils import importutils
from neutron.agent.linux import ip_lib
from neutron.cmd.sanity import checks
from neutron.common import constants as n_const
from neutron.plugins.ml2.drivers.openvswitch.agent.common import constants
from neutron.plugins.ml2.drivers.openvswitch.agent \
import ovs_neutron_agent as ovsagt
@ -47,18 +49,34 @@ class _OVSAgentTestBase(test_ovs_lib.OVSBridgeTestBase,
self.br_int = None
self.init_done = False
self.init_done_ev = eventlet.event.Event()
self._main_thread = eventlet.spawn(self._kick_main)
self.addCleanup(self._kill_main)
retry_count = 3
while True:
cfg.CONF.set_override('of_listen_port',
net_helpers.get_free_namespace_port(
n_const.PROTO_NAME_TCP),
group='OVS')
self.of_interface_mod.init_config()
self._main_thread = eventlet.spawn(self._kick_main)
# Wait for _kick_main -> of_interface main -> _agent_main
# NOTE(yamamoto): This complexity came from how "native" of_interface
# runs its openflow controller. "native" of_interface's main routine
# blocks while running the embedded openflow controller. In that case,
# the agent rpc_loop runs in another thread. However, for FT we need
# to run setUp() and test_xxx() in the same thread. So I made this
# run of_interface's main in a separate thread instead.
while not self.init_done:
self.init_done_ev.wait()
# Wait for _kick_main -> of_interface main -> _agent_main
# NOTE(yamamoto): This complexity came from how "native"
# of_interface runs its openflow controller. "native"
# of_interface's main routine blocks while running the
# embedded openflow controller. In that case, the agent
# rpc_loop runs in another thread. However, for FT we
# need to run setUp() and test_xxx() in the same thread.
# So I made this run of_interface's main in a separate
# thread instead.
try:
while not self.init_done:
self.init_done_ev.wait()
break
except fixtures.TimeoutException:
self._kill_main()
retry_count -= 1
if retry_count < 0:
raise Exception('port allocation failed')
def _kick_main(self):
with mock.patch.object(ovsagt, 'main', self._agent_main):
@ -87,6 +105,11 @@ class _OVSAgentOFCtlTestBase(_OVSAgentTestBase):
'openflow.ovs_ofctl.main')
class _OVSAgentNativeTestBase(_OVSAgentTestBase):
_MAIN_MODULE = ('neutron.plugins.ml2.drivers.openvswitch.agent.'
'openflow.native.main')
class _ARPSpoofTestCase(object):
def setUp(self):
# NOTE(kevinbenton): it would be way cooler to use scapy for
@ -194,6 +217,10 @@ class ARPSpoofOFCtlTestCase(_ARPSpoofTestCase, _OVSAgentOFCtlTestBase):
pass
class ARPSpoofNativeTestCase(_ARPSpoofTestCase, _OVSAgentNativeTestBase):
pass
class _CanaryTableTestCase(object):
def test_canary_table(self):
self.br_int.delete_flows()
@ -206,3 +233,7 @@ class _CanaryTableTestCase(object):
class CanaryTableOFCtlTestCase(_CanaryTableTestCase, _OVSAgentOFCtlTestBase):
pass
class CanaryTableNativeTestCase(_CanaryTableTestCase, _OVSAgentNativeTestBase):
pass

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -55,7 +55,7 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
{'priority': 0, 'table': 4, 'actions': 'drop'},
{'priority': 0, 'table': 6, 'actions': 'drop'},
{'priority': 1, 'table': 10,
'actions': 'learn(cookie=0x0,table=20,priority=1,'
'actions': 'learn(cookie=0,table=20,priority=1,'
'hard_timeout=300,NXM_OF_VLAN_TCI[0..11],'
'NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],'
'load:0->NXM_OF_VLAN_TCI[],'
@ -90,7 +90,7 @@ class OVSTunnelBridgeTest(ovs_bridge_test_base.OVSBridgeTestBase,
{'priority': 0, 'table': 4, 'actions': 'drop'},
{'priority': 0, 'table': 6, 'actions': 'drop'},
{'priority': 1, 'table': 10,
'actions': 'learn(cookie=0x0,table=20,priority=1,'
'actions': 'learn(cookie=0,table=20,priority=1,'
'hard_timeout=300,NXM_OF_VLAN_TCI[0..11],'
'NXM_OF_ETH_DST[]=NXM_OF_ETH_SRC[],'
'load:0->NXM_OF_VLAN_TCI[],'

View File

@ -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)

View File

@ -1317,26 +1317,6 @@ class TestOvsNeutronAgent(object):
self.agent.state_rpc.client):
self.assertEqual(10, rpc_client.timeout)
def test_cleanup_stale_flows_iter_0(self):
with mock.patch.object(self.agent.int_br, 'agent_uuid_stamp',
new=1234),\
mock.patch.object(self.agent.int_br,
'dump_flows_all_tables') as dump_flows,\
mock.patch.object(self.agent.int_br,
'delete_flows') as del_flow:
dump_flows.return_value = [
'cookie=0x4d2, duration=50.156s, table=0,actions=drop',
'cookie=0x4321, duration=54.143s, table=2, priority=0',
'cookie=0x2345, duration=50.125s, table=2, priority=0',
'cookie=0x4d2, duration=52.112s, table=3, actions=drop',
]
self.agent.cleanup_stale_flows()
expected = [
mock.call(cookie='0x4321/-1', table='2'),
mock.call(cookie='0x2345/-1', table='2'),
]
self.assertEqual(expected, del_flow.mock_calls)
def test_set_rpc_timeout_no_value(self):
self.agent.quitting_rpc_timeout = None
with mock.patch.object(self.agent, 'set_rpc_timeout') as mock_set_rpc:
@ -1438,7 +1418,51 @@ class TestOvsNeutronAgent(object):
class TestOvsNeutronAgentOFCtl(TestOvsNeutronAgent,
ovs_test_base.OVSOFCtlTestBase):
pass
def test_cleanup_stale_flows_iter_0(self):
with mock.patch.object(self.agent.int_br, 'agent_uuid_stamp',
new=1234),\
mock.patch.object(self.agent.int_br,
'dump_flows_all_tables') as dump_flows,\
mock.patch.object(self.agent.int_br,
'delete_flows') as del_flow:
dump_flows.return_value = [
'cookie=0x4d2, duration=50.156s, table=0,actions=drop',
'cookie=0x4321, duration=54.143s, table=2, priority=0',
'cookie=0x2345, duration=50.125s, table=2, priority=0',
'cookie=0x4d2, duration=52.112s, table=3, actions=drop',
]
self.agent.cleanup_stale_flows()
expected = [
mock.call(cookie='0x4321/-1', table='2'),
mock.call(cookie='0x2345/-1', table='2'),
]
self.assertEqual(expected, del_flow.mock_calls)
class TestOvsNeutronAgentRyu(TestOvsNeutronAgent,
ovs_test_base.OVSRyuTestBase):
def test_cleanup_stale_flows_iter_0(self):
uint64_max = (1 << 64) - 1
with mock.patch.object(self.agent.int_br, 'agent_uuid_stamp',
new=1234),\
mock.patch.object(self.agent.int_br,
'dump_flows') as dump_flows,\
mock.patch.object(self.agent.int_br,
'delete_flows') as del_flow:
dump_flows.return_value = [
# mock ryu.ofproto.ofproto_v1_3_parser.OFPFlowStats
mock.Mock(cookie=1234, table_id=0),
mock.Mock(cookie=17185, table_id=2),
mock.Mock(cookie=9029, table_id=2),
mock.Mock(cookie=1234, table_id=3),
]
self.agent.cleanup_stale_flows()
expected = [mock.call(cookie=17185,
cookie_mask=uint64_max),
mock.call(cookie=9029,
cookie_mask=uint64_max)]
del_flow.assert_has_calls(expected, any_order=True)
self.assertEqual(len(expected), len(del_flow.mock_calls))
class AncillaryBridgesTest(object):
@ -1544,6 +1568,11 @@ class AncillaryBridgesTestOFCtl(AncillaryBridgesTest,
pass
class AncillaryBridgesTestRyu(AncillaryBridgesTest,
ovs_test_base.OVSRyuTestBase):
pass
class TestOvsDvrNeutronAgent(object):
def setUp(self):
@ -2427,6 +2456,11 @@ class TestOvsDvrNeutronAgentOFCtl(TestOvsDvrNeutronAgent,
pass
class TestOvsDvrNeutronAgentRyu(TestOvsDvrNeutronAgent,
ovs_test_base.OVSRyuTestBase):
pass
class TestValidateTunnelLocalIP(base.BaseTestCase):
def test_validate_local_ip_no_tunneling(self):
cfg.CONF.set_override('tunnel_types', [], group='AGENT')

View File

@ -575,6 +575,10 @@ class TunnelTestOFCtl(TunnelTest, ovs_test_base.OVSOFCtlTestBase):
pass
class TunnelTestRyu(TunnelTest, ovs_test_base.OVSRyuTestBase):
pass
class TunnelTestUseVethInterco(TunnelTest):
USE_VETH_INTERCONNECTION = True
@ -669,6 +673,11 @@ class TunnelTestUseVethIntercoOFCtl(TunnelTestUseVethInterco,
pass
class TunnelTestUseVethIntercoRyu(TunnelTestUseVethInterco,
ovs_test_base.OVSRyuTestBase):
pass
class TunnelTestWithMTU(TunnelTestUseVethInterco):
VETH_MTU = 1500
@ -681,3 +690,8 @@ class TunnelTestWithMTU(TunnelTestUseVethInterco):
class TunnelTestWithMTUOFCtl(TunnelTestWithMTU,
ovs_test_base.OVSOFCtlTestBase):
pass
class TunnelTestWithMTURyu(TunnelTestWithMTU,
ovs_test_base.OVSRyuTestBase):
pass

View File

@ -17,6 +17,7 @@ keystonemiddleware>=2.0.0
netaddr!=0.7.16,>=0.7.12
python-neutronclient<3,>=2.6.0
retrying!=1.3.0,>=1.2.3 # Apache-2.0
ryu>=3.23.2 # Apache-2.0
SQLAlchemy<1.1.0,>=0.9.7
WebOb>=1.2.3
python-keystoneclient>=1.6.0