Merge "ofagent: Vendor code decomposition"
This commit is contained in:
commit
829f60bd8e
@ -411,7 +411,7 @@ The following chart captures the following aspects:
|
||||
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
||||
| networking-odl_ | ml2,l3,lb,fw | yes | no | [C] | Kilo |
|
||||
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
||||
| networking-ofagent_ | ml2 | yes | no | [B] | |
|
||||
| networking-ofagent_ | ml2 | yes | no | [C] | Kilo |
|
||||
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
||||
| networking-ovs-dpdk_ | | | | | |
|
||||
+-------------------------------+-----------------------+-----------+------------------+---------+--------------+
|
||||
|
@ -1,16 +0,0 @@
|
||||
# neutron-rootwrap command filters for nodes on which
|
||||
# neutron-ofagent-agent is expected to control network
|
||||
#
|
||||
# This file should be owned by (and only-writeable by) the root user
|
||||
|
||||
# format seems to be
|
||||
# cmd-name: filter-name, raw-command, user, args
|
||||
|
||||
[Filters]
|
||||
|
||||
# ovs_lib
|
||||
ovs-vsctl: CommandFilter, ovs-vsctl, root
|
||||
|
||||
# ip_lib
|
||||
ip: IpFilter, ip, root
|
||||
ip_exec: IpNetnsExecFilter, ip, root
|
@ -1,57 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# Copyright (C) 2014 Fumihiko Kakuma <kakuma at valinux co jp>
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Based on openvswitch mechanism driver.
|
||||
#
|
||||
# Copyright (c) 2013 OpenStack Foundation
|
||||
# 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.agent import securitygroups_rpc
|
||||
from neutron.common import constants
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.openstack.common import log
|
||||
from neutron.plugins.common import constants as p_constants
|
||||
from neutron.plugins.ml2.drivers import mech_agent
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class OfagentMechanismDriver(mech_agent.SimpleAgentMechanismDriverBase):
|
||||
"""Attach to networks using ofagent L2 agent.
|
||||
|
||||
The OfagentMechanismDriver integrates the ml2 plugin with the
|
||||
ofagent L2 agent. Port binding with this driver requires the
|
||||
ofagent agent to be running on the port's host, and that agent
|
||||
to have connectivity to at least one segment of the port's
|
||||
network.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
sg_enabled = securitygroups_rpc.is_firewall_enabled()
|
||||
vif_details = {portbindings.CAP_PORT_FILTER: sg_enabled,
|
||||
portbindings.OVS_HYBRID_PLUG: sg_enabled}
|
||||
super(OfagentMechanismDriver, self).__init__(
|
||||
constants.AGENT_TYPE_OFA,
|
||||
portbindings.VIF_TYPE_OVS,
|
||||
vif_details)
|
||||
|
||||
def get_allowed_network_types(self, agent):
|
||||
return (agent['configurations'].get('tunnel_types', []) +
|
||||
[p_constants.TYPE_LOCAL, p_constants.TYPE_FLAT,
|
||||
p_constants.TYPE_VLAN])
|
||||
|
||||
def get_mappings(self, agent):
|
||||
return dict(agent['configurations'].get('interface_mappings', {}))
|
@ -1,100 +0,0 @@
|
||||
This directory includes agent for OpenFlow Agent mechanism driver.
|
||||
|
||||
# -- Installation
|
||||
|
||||
For how to install/set up ML2 mechanism driver for OpenFlow Agent, please refer to
|
||||
https://github.com/osrg/ryu/wiki/OpenStack
|
||||
|
||||
# -- Notes for updating from Icehouce
|
||||
|
||||
OVS.bridge_mappings is deprecated for ofagent. It was removed in Kilo.
|
||||
Please use AGENT.physical_interface_mappings instead.
|
||||
To mimic an existing setup with bridge_mapping, you can create
|
||||
a veth pair, link one side of it to the bridge, and then specify
|
||||
the other side in physical_interface_mappings.
|
||||
For example, if you have the following:
|
||||
|
||||
[OVS]
|
||||
bridge_mappings=public:br-ex
|
||||
|
||||
You can do:
|
||||
|
||||
# ip link add int-public type veth peer name phy-public
|
||||
# ip link set int-public up
|
||||
# ip link set phy-public up
|
||||
# ovs-vsctl add-port br-ex phy-public
|
||||
|
||||
and then replace the bridge_mappings with:
|
||||
|
||||
[AGENT]
|
||||
physical_interface_mappings=public:int-public
|
||||
|
||||
After Icehouce, most of the functionality have been folded into
|
||||
a single bridge, the integration bridge. (aka. br-int)
|
||||
The integration bridge is the only bridge which would have an
|
||||
OpenFlow connection to the embedded controller in ofagent now.
|
||||
|
||||
- ofagent no longer uses a separate bridge for tunneling.
|
||||
Please remove br-tun if you have one.
|
||||
|
||||
# ovs-vsctl del-br br-tun
|
||||
|
||||
- ofagent no longer acts as an OpenFlow controller for physical bridges.
|
||||
Please remove set-controller configuration from your physical bridges.
|
||||
|
||||
# ovs-vsctl del-controller ${PHYSICAL_BRIDGE}
|
||||
|
||||
The support of ancillary bridges has been removed after Icehouce.
|
||||
While you can still use these bridges to provide connectivity,
|
||||
neutron-ofagent-agent no longer reports port state changes (up/down)
|
||||
for these bridges. If it is a problem for you, please consider
|
||||
tweaking your configuration to avoid using ancillary bridges.
|
||||
We recommend to use a provider network instead as the following:
|
||||
|
||||
- Make l3-agent external_network_bridge configuration empty.
|
||||
eg.
|
||||
[DEFAULT]
|
||||
external_network_bridge=
|
||||
|
||||
- (Re-)create a network (and subnet) for public connectivity with
|
||||
a flat provider network.
|
||||
eg.
|
||||
neutron net-create $PUBLIC_NETWORK -- \
|
||||
--router:external=True \
|
||||
--provider:network_type:flat \
|
||||
--provider:physical_network=$PUBLIC_PHYSICAL_NETWORK
|
||||
|
||||
- Associate your neutron router to the above network.
|
||||
eg.
|
||||
neutron router-gateway-clear $ROUTER_ID
|
||||
neutron router-gateway-set $ROUTER_ID $PUBLIC_NETWORK
|
||||
|
||||
- Add the corresponding entry to bridge_mappings.
|
||||
eg.
|
||||
[OVS]
|
||||
bridge_mappings=$PUBLIC_PHYSICAL_NETWORK:$PUBLIC_BRIDGE
|
||||
|
||||
The port naming scheme for ofagent has been changed after Icehouce.
|
||||
If you are using security groups, you should switch firewall_driver
|
||||
accordingly.
|
||||
From:
|
||||
[securitygroup]
|
||||
firewall_driver=neutron.agent.linux.iptables_firewall.OVSHybridIptablesFirewallDriver
|
||||
To:
|
||||
[securitygroup]
|
||||
firewall_driver=neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
|
||||
|
||||
# -- Ryu General
|
||||
|
||||
For general Ryu stuff, please refer to
|
||||
http://osrg.github.io/ryu/
|
||||
|
||||
Ryu is available at github
|
||||
git://github.com/osrg/ryu.git
|
||||
https://github.com/osrg/ryu
|
||||
|
||||
The mailing is at
|
||||
ryu-devel@lists.sourceforge.net
|
||||
https://lists.sourceforge.net/lists/listinfo/ryu-devel
|
||||
|
||||
Enjoy!
|
@ -1,187 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from ryu.app.ofctl import api as ryu_api
|
||||
from ryu.lib import dpid as dpid_lib
|
||||
from ryu.lib.packet import arp
|
||||
from ryu.lib.packet import ethernet
|
||||
from ryu.lib.packet import packet
|
||||
from ryu.lib.packet import vlan
|
||||
|
||||
from neutron.common import log
|
||||
from neutron.i18n import _LI
|
||||
from neutron.openstack.common import log as logging
|
||||
import neutron.plugins.ofagent.agent.metadata as meta
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ArpLib(object):
|
||||
|
||||
def __init__(self, ryuapp):
|
||||
"""Constructor.
|
||||
|
||||
Define the internal table mapped an ip and a mac in a network.
|
||||
self._arp_tbl:
|
||||
{network1: {ip_addr: mac, ...},
|
||||
network2: {ip_addr: mac, ...},
|
||||
...,
|
||||
}
|
||||
|
||||
:param ryuapp: object of the ryu app.
|
||||
"""
|
||||
self.ryuapp = ryuapp
|
||||
self._arp_tbl = {}
|
||||
self.br = None
|
||||
|
||||
def set_bridge(self, br):
|
||||
self.br = br
|
||||
|
||||
@log.log
|
||||
def _send_arp_reply(self, datapath, port, pkt):
|
||||
ofp = datapath.ofproto
|
||||
ofpp = datapath.ofproto_parser
|
||||
pkt.serialize()
|
||||
data = pkt.data
|
||||
actions = [ofpp.OFPActionOutput(port=port)]
|
||||
out = ofpp.OFPPacketOut(datapath=datapath,
|
||||
buffer_id=ofp.OFP_NO_BUFFER,
|
||||
in_port=ofp.OFPP_CONTROLLER,
|
||||
actions=actions,
|
||||
data=data)
|
||||
ryu_api.send_msg(self.ryuapp, out)
|
||||
|
||||
@log.log
|
||||
def _send_unknown_packet(self, msg, in_port, out_port):
|
||||
datapath = msg.datapath
|
||||
ofp = datapath.ofproto
|
||||
ofpp = datapath.ofproto_parser
|
||||
data = None
|
||||
if msg.buffer_id == ofp.OFP_NO_BUFFER:
|
||||
data = msg.data
|
||||
actions = [ofpp.OFPActionOutput(port=out_port)]
|
||||
out = ofpp.OFPPacketOut(datapath=datapath,
|
||||
buffer_id=msg.buffer_id,
|
||||
in_port=in_port,
|
||||
actions=actions,
|
||||
data=data)
|
||||
ryu_api.send_msg(self.ryuapp, out)
|
||||
|
||||
def _respond_arp(self, datapath, port, arptbl,
|
||||
pkt_ethernet, pkt_vlan, pkt_arp):
|
||||
if pkt_arp.opcode != arp.ARP_REQUEST:
|
||||
LOG.debug("unknown arp op %s", pkt_arp.opcode)
|
||||
return False
|
||||
ip_addr = pkt_arp.dst_ip
|
||||
hw_addr = arptbl.get(ip_addr)
|
||||
if hw_addr is None:
|
||||
LOG.debug("unknown arp request %s", ip_addr)
|
||||
return False
|
||||
LOG.debug("responding arp request %(ip_addr)s -> %(hw_addr)s",
|
||||
{'ip_addr': ip_addr, 'hw_addr': hw_addr})
|
||||
pkt = packet.Packet()
|
||||
pkt.add_protocol(ethernet.ethernet(ethertype=pkt_ethernet.ethertype,
|
||||
dst=pkt_ethernet.src,
|
||||
src=hw_addr))
|
||||
if pkt_vlan:
|
||||
pkt.add_protocol(vlan.vlan(cfi=pkt_vlan.cfi,
|
||||
ethertype=pkt_vlan.ethertype,
|
||||
pcp=pkt_vlan.pcp,
|
||||
vid=pkt_vlan.vid))
|
||||
pkt.add_protocol(arp.arp(opcode=arp.ARP_REPLY,
|
||||
src_mac=hw_addr,
|
||||
src_ip=ip_addr,
|
||||
dst_mac=pkt_arp.src_mac,
|
||||
dst_ip=pkt_arp.src_ip))
|
||||
self._send_arp_reply(datapath, port, pkt)
|
||||
return True
|
||||
|
||||
@log.log
|
||||
def add_arp_table_entry(self, network, ip, mac):
|
||||
if network in self._arp_tbl:
|
||||
self._arp_tbl[network][ip] = mac
|
||||
else:
|
||||
self._arp_tbl[network] = {ip: mac}
|
||||
|
||||
@log.log
|
||||
def del_arp_table_entry(self, network, ip):
|
||||
if network not in self._arp_tbl:
|
||||
LOG.debug("removal of unknown network %s", network)
|
||||
return
|
||||
if self._arp_tbl[network].pop(ip, None) is None:
|
||||
LOG.debug("removal of unknown ip %s", ip)
|
||||
return
|
||||
if not self._arp_tbl[network]:
|
||||
del self._arp_tbl[network]
|
||||
|
||||
def packet_in_handler(self, ev):
|
||||
"""Check a packet-in message.
|
||||
|
||||
Build and output an arp reply if a packet-in message is
|
||||
an arp packet.
|
||||
"""
|
||||
msg = ev.msg
|
||||
LOG.debug("packet-in msg %s", msg)
|
||||
datapath = msg.datapath
|
||||
if self.br is None:
|
||||
LOG.info(_LI("No bridge is set"))
|
||||
return
|
||||
if self.br.datapath.id != datapath.id:
|
||||
LOG.info(_LI("Unknown bridge %(dpid)s ours %(ours)s"),
|
||||
{"dpid": datapath.id, "ours": self.br.datapath.id})
|
||||
return
|
||||
ofp = datapath.ofproto
|
||||
port = msg.match['in_port']
|
||||
metadata = msg.match.get('metadata')
|
||||
# NOTE(yamamoto): Ryu packet library can raise various exceptions
|
||||
# on a corrupted packet.
|
||||
try:
|
||||
pkt = packet.Packet(msg.data)
|
||||
except Exception as e:
|
||||
LOG.debug("Unparsable packet: got exception %s", e)
|
||||
return
|
||||
LOG.debug("packet-in dpid %(dpid)s in_port %(port)s pkt %(pkt)s",
|
||||
{'dpid': dpid_lib.dpid_to_str(datapath.id),
|
||||
'port': port, 'pkt': pkt})
|
||||
|
||||
if metadata is None:
|
||||
LOG.info(_LI("drop non tenant packet"))
|
||||
return
|
||||
network = metadata & meta.NETWORK_MASK
|
||||
pkt_ethernet = pkt.get_protocol(ethernet.ethernet)
|
||||
if not pkt_ethernet:
|
||||
LOG.debug("drop non-ethernet packet")
|
||||
return
|
||||
pkt_vlan = pkt.get_protocol(vlan.vlan)
|
||||
pkt_arp = pkt.get_protocol(arp.arp)
|
||||
if not pkt_arp:
|
||||
LOG.debug("drop non-arp packet")
|
||||
return
|
||||
|
||||
arptbl = self._arp_tbl.get(network)
|
||||
if arptbl:
|
||||
if self._respond_arp(datapath, port, arptbl,
|
||||
pkt_ethernet, pkt_vlan, pkt_arp):
|
||||
return
|
||||
else:
|
||||
LOG.info(_LI("unknown network %s"), network)
|
||||
|
||||
# add a flow to skip a packet-in to a controller.
|
||||
self.br.arp_passthrough(network=network, tpa=pkt_arp.dst_ip)
|
||||
# send an unknown arp packet to the table.
|
||||
self._send_unknown_packet(msg, port, ofp.OFPP_TABLE)
|
@ -1,19 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# 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.
|
||||
|
||||
LOCAL_VLAN_MIN = 1
|
||||
LOCAL_VLAN_MAX = 0xfff
|
||||
LOCAL_VLAN_MASK = 0xfff
|
@ -1,407 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# 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.
|
||||
|
||||
"""
|
||||
OpenFlow1.3 flow table for OFAgent
|
||||
|
||||
* requirements
|
||||
** plain OpenFlow 1.3. no vendor extensions.
|
||||
|
||||
* legends
|
||||
xxx: network id (agent internal use)
|
||||
yyy: segment id (vlan id, gre key, ...)
|
||||
a,b,c: tunnel port (tun_ofports, map[net_id].tun_ofports)
|
||||
i,j,k: vm port (map[net_id].vif_ports[vif_id].ofport)
|
||||
x,y,z: physical port (int_ofports)
|
||||
N: tunnel type (0 for TYPE_GRE, 1 for TYPE_xxx, ...)
|
||||
iii: unknown ip address
|
||||
uuu: unicast l2 address
|
||||
|
||||
* tables (in order)
|
||||
CHECK_IN_PORT
|
||||
TUNNEL_IN+N
|
||||
PHYS_IN
|
||||
LOCAL_IN
|
||||
ARP_PASSTHROUGH
|
||||
ARP_RESPONDER
|
||||
TUNNEL_OUT
|
||||
LOCAL_OUT
|
||||
PHYS_OUT
|
||||
TUNNEL_FLOOD+N
|
||||
PHYS_FLOOD
|
||||
LOCAL_FLOOD
|
||||
|
||||
* CHECK_IN_PORT
|
||||
|
||||
for each vm ports:
|
||||
// check_in_port_add_local_port, check_in_port_delete_port
|
||||
in_port=i, write_metadata(LOCAL|xxx),goto(LOCAL_IN)
|
||||
TYPE_GRE
|
||||
for each tunnel ports:
|
||||
// check_in_port_add_tunnel_port, check_in_port_delete_port
|
||||
in_port=a, goto(TUNNEL_IN+N)
|
||||
TYPE_VLAN
|
||||
for each networks ports:
|
||||
// provision_tenant_physnet, reclaim_tenant_physnet
|
||||
in_port=x,vlan_vid=present|yyy, write_metadata(xxx),goto(PHYS_IN)
|
||||
TYPE_FLAT
|
||||
// provision_tenant_physnet, reclaim_tenant_physnet
|
||||
in_port=x, write_metadata(xxx),goto(PHYS_IN)
|
||||
default drop
|
||||
|
||||
* TUNNEL_IN+N (per tunnel types) tunnel -> network
|
||||
|
||||
for each networks:
|
||||
// provision_tenant_tunnel, reclaim_tenant_tunnel
|
||||
tun_id=yyy, write_metadata(xxx),goto(TUNNEL_OUT)
|
||||
|
||||
default drop
|
||||
|
||||
* PHYS_IN
|
||||
default goto(TUNNEL_OUT)
|
||||
|
||||
* LOCAL_IN
|
||||
default goto(next_table)
|
||||
|
||||
* ARP_PASSTHROUGH
|
||||
for each unknown tpa:
|
||||
// arp_passthrough
|
||||
arp,arp_op=request,metadata=xxx,tpa=iii, idle_timeout=5, goto(TUNNEL_OUT)
|
||||
default goto(next_table)
|
||||
|
||||
* ARP_RESPONDER
|
||||
arp,arp_op=request, output:controller
|
||||
default goto(next_table)
|
||||
|
||||
* TUNNEL_OUT
|
||||
TYPE_GRE
|
||||
// !FLOODING_ENTRY
|
||||
// install_tunnel_output, delete_tunnel_output
|
||||
metadata=LOCAL|xxx,eth_dst=uuu set_tunnel(yyy),output:a
|
||||
|
||||
default goto(next table)
|
||||
|
||||
* LOCAL_OUT
|
||||
for each known destinations:
|
||||
// local_out_add_port, local_out_delete_port
|
||||
metadata=xxx,eth_dst=uuu output:i
|
||||
default goto(next table)
|
||||
|
||||
* PHYS_OUT
|
||||
|
||||
NOTE(yamamoto): currently this table is always empty.
|
||||
|
||||
default goto(next table)
|
||||
|
||||
* TUNNEL_FLOOD+N. (per tunnel types)
|
||||
|
||||
network -> tunnel/vlan
|
||||
output to tunnel/physical ports
|
||||
"next table" might be LOCAL_OUT
|
||||
TYPE_GRE
|
||||
for each networks:
|
||||
// FLOODING_ENTRY
|
||||
// install_tunnel_output, delete_tunnel_output
|
||||
metadata=LOCAL|xxx, set_tunnel(yyy),output:a,b,c,goto(next table)
|
||||
|
||||
default goto(next table)
|
||||
|
||||
* PHYS_FLOOD
|
||||
|
||||
TYPE_VLAN
|
||||
for each networks:
|
||||
// provision_tenant_physnet, reclaim_tenant_physnet
|
||||
metadata=LOCAL|xxx, push_vlan:0x8100,set_field:present|yyy->vlan_vid,
|
||||
output:x,pop_vlan,goto(next table)
|
||||
TYPE_FLAT
|
||||
for each networks:
|
||||
// provision_tenant_physnet, reclaim_tenant_physnet
|
||||
metadata=LOCAL|xxx, output:x,goto(next table)
|
||||
|
||||
default goto(next table)
|
||||
|
||||
* LOCAL_FLOOD
|
||||
|
||||
for each networks:
|
||||
// local_flood_update, local_flood_delete
|
||||
metadata=xxx, output:i,j,k
|
||||
or
|
||||
metadata=xxx,eth_dst=broadcast, output:i,j,k
|
||||
|
||||
default drop
|
||||
|
||||
* references
|
||||
** OVS agent https://wiki.openstack.org/wiki/Ovs-flow-logic
|
||||
*** we use metadata instead of "internal" VLANs
|
||||
*** we don't want to use NX learn action
|
||||
"""
|
||||
|
||||
from ryu.lib.packet import arp
|
||||
from ryu.ofproto import ether
|
||||
|
||||
from neutron.plugins.common import constants as p_const
|
||||
import neutron.plugins.ofagent.agent.metadata as meta
|
||||
from neutron.plugins.ofagent.agent import ofswitch
|
||||
from neutron.plugins.ofagent.agent import tables
|
||||
|
||||
|
||||
class OFAgentIntegrationBridge(ofswitch.OpenFlowSwitch):
|
||||
"""ofagent br-int specific logic."""
|
||||
|
||||
def setup_default_table(self):
|
||||
self.delete_flows()
|
||||
|
||||
self.install_default_drop(tables.CHECK_IN_PORT)
|
||||
|
||||
for t in tables.TUNNEL_IN.values():
|
||||
self.install_default_drop(t)
|
||||
self.install_default_goto(tables.PHYS_IN, tables.TUNNEL_OUT)
|
||||
self.install_default_goto_next(tables.LOCAL_IN)
|
||||
self.install_default_goto_next(tables.ARP_PASSTHROUGH)
|
||||
self.install_arp_responder(tables.ARP_RESPONDER)
|
||||
|
||||
self.install_default_goto_next(tables.TUNNEL_OUT)
|
||||
self.install_default_goto_next(tables.LOCAL_OUT)
|
||||
self.install_default_goto_next(tables.PHYS_OUT)
|
||||
|
||||
for t in tables.TUNNEL_FLOOD.values():
|
||||
self.install_default_goto_next(t)
|
||||
self.install_default_goto_next(tables.PHYS_FLOOD)
|
||||
self.install_default_drop(tables.LOCAL_FLOOD)
|
||||
|
||||
def install_arp_responder(self, table_id):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
match = ofpp.OFPMatch(eth_type=ether.ETH_TYPE_ARP,
|
||||
arp_op=arp.ARP_REQUEST)
|
||||
actions = [ofpp.OFPActionOutput(ofp.OFPP_CONTROLLER)]
|
||||
instructions = [
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions)]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=table_id,
|
||||
priority=1,
|
||||
match=match,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
self.install_default_goto_next(table_id)
|
||||
|
||||
def install_tunnel_output(self, table_id,
|
||||
network, segmentation_id,
|
||||
ports, goto_next, **additional_matches):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
match = ofpp.OFPMatch(metadata=meta.mk_metadata(network, meta.LOCAL),
|
||||
**additional_matches)
|
||||
actions = [ofpp.OFPActionSetField(tunnel_id=segmentation_id)]
|
||||
actions += [ofpp.OFPActionOutput(port=p) for p in ports]
|
||||
instructions = [
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
|
||||
]
|
||||
if goto_next:
|
||||
instructions += [
|
||||
ofpp.OFPInstructionGotoTable(table_id=table_id + 1),
|
||||
]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=table_id,
|
||||
priority=1,
|
||||
match=match,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
|
||||
def delete_tunnel_output(self, table_id,
|
||||
network, **additional_matches):
|
||||
(dp, _ofp, ofpp) = self._get_dp()
|
||||
self.delete_flows(table_id=table_id,
|
||||
metadata=meta.mk_metadata(network, meta.LOCAL),
|
||||
**additional_matches)
|
||||
|
||||
def provision_tenant_tunnel(self, network_type, network, segmentation_id):
|
||||
(dp, _ofp, ofpp) = self._get_dp()
|
||||
match = ofpp.OFPMatch(tunnel_id=segmentation_id)
|
||||
metadata = meta.mk_metadata(network)
|
||||
instructions = [
|
||||
ofpp.OFPInstructionWriteMetadata(metadata=metadata[0],
|
||||
metadata_mask=metadata[1]),
|
||||
ofpp.OFPInstructionGotoTable(table_id=tables.TUNNEL_OUT),
|
||||
]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=tables.TUNNEL_IN[network_type],
|
||||
priority=1,
|
||||
match=match,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
|
||||
def reclaim_tenant_tunnel(self, network_type, network, segmentation_id):
|
||||
table_id = tables.TUNNEL_IN[network_type]
|
||||
self.delete_flows(table_id=table_id, tunnel_id=segmentation_id)
|
||||
|
||||
def provision_tenant_physnet(self, network_type, network,
|
||||
segmentation_id, phys_port):
|
||||
"""for vlan and flat."""
|
||||
assert(network_type in [p_const.TYPE_VLAN, p_const.TYPE_FLAT])
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
|
||||
# inbound
|
||||
metadata = meta.mk_metadata(network)
|
||||
instructions = [
|
||||
ofpp.OFPInstructionWriteMetadata(metadata=metadata[0],
|
||||
metadata_mask=metadata[1])
|
||||
]
|
||||
if network_type == p_const.TYPE_VLAN:
|
||||
vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
|
||||
match = ofpp.OFPMatch(in_port=phys_port, vlan_vid=vlan_vid)
|
||||
actions = [ofpp.OFPActionPopVlan()]
|
||||
instructions += [ofpp.OFPInstructionActions(
|
||||
ofp.OFPIT_APPLY_ACTIONS, actions)]
|
||||
else:
|
||||
match = ofpp.OFPMatch(in_port=phys_port)
|
||||
instructions += [ofpp.OFPInstructionGotoTable(table_id=tables.PHYS_IN)]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
priority=1,
|
||||
table_id=tables.CHECK_IN_PORT,
|
||||
match=match,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
|
||||
# outbound
|
||||
match = ofpp.OFPMatch(metadata=meta.mk_metadata(network, meta.LOCAL))
|
||||
if network_type == p_const.TYPE_VLAN:
|
||||
actions = [
|
||||
ofpp.OFPActionPushVlan(),
|
||||
ofpp.OFPActionSetField(vlan_vid=vlan_vid),
|
||||
]
|
||||
else:
|
||||
actions = []
|
||||
actions += [ofpp.OFPActionOutput(port=phys_port)]
|
||||
if network_type == p_const.TYPE_VLAN:
|
||||
actions += [ofpp.OFPActionPopVlan()]
|
||||
instructions = [
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
|
||||
ofpp.OFPInstructionGotoTable(table_id=tables.PHYS_FLOOD + 1),
|
||||
]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
priority=1,
|
||||
table_id=tables.PHYS_FLOOD,
|
||||
match=match,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
|
||||
def reclaim_tenant_physnet(self, network_type, network,
|
||||
segmentation_id, phys_port):
|
||||
(_dp, ofp, _ofpp) = self._get_dp()
|
||||
vlan_vid = segmentation_id | ofp.OFPVID_PRESENT
|
||||
if network_type == p_const.TYPE_VLAN:
|
||||
self.delete_flows(table_id=tables.CHECK_IN_PORT,
|
||||
in_port=phys_port, vlan_vid=vlan_vid)
|
||||
else:
|
||||
self.delete_flows(table_id=tables.CHECK_IN_PORT,
|
||||
in_port=phys_port)
|
||||
self.delete_flows(table_id=tables.PHYS_FLOOD,
|
||||
metadata=meta.mk_metadata(network))
|
||||
|
||||
def check_in_port_add_tunnel_port(self, network_type, port):
|
||||
(dp, _ofp, ofpp) = self._get_dp()
|
||||
match = ofpp.OFPMatch(in_port=port)
|
||||
instructions = [
|
||||
ofpp.OFPInstructionGotoTable(
|
||||
table_id=tables.TUNNEL_IN[network_type])
|
||||
]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=tables.CHECK_IN_PORT,
|
||||
priority=1,
|
||||
match=match,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
|
||||
def check_in_port_add_local_port(self, network, port):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
match = ofpp.OFPMatch(in_port=port)
|
||||
metadata = meta.mk_metadata(network, meta.LOCAL)
|
||||
instructions = [
|
||||
ofpp.OFPInstructionWriteMetadata(metadata=metadata[0],
|
||||
metadata_mask=metadata[1]),
|
||||
ofpp.OFPInstructionGotoTable(table_id=tables.LOCAL_IN),
|
||||
]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=tables.CHECK_IN_PORT,
|
||||
priority=1,
|
||||
match=match,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
|
||||
def check_in_port_delete_port(self, port):
|
||||
self.delete_flows(table_id=tables.CHECK_IN_PORT, in_port=port)
|
||||
|
||||
def local_flood_update(self, network, ports, flood_unicast):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
match_all = ofpp.OFPMatch(metadata=meta.mk_metadata(network))
|
||||
match_multicast = ofpp.OFPMatch(metadata=meta.mk_metadata(network),
|
||||
eth_dst=('01:00:00:00:00:00',
|
||||
'01:00:00:00:00:00'))
|
||||
if flood_unicast:
|
||||
match_add = match_all
|
||||
match_del = match_multicast
|
||||
else:
|
||||
match_add = match_multicast
|
||||
match_del = match_all
|
||||
actions = [ofpp.OFPActionOutput(port=p) for p in ports]
|
||||
instructions = [
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
|
||||
]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=tables.LOCAL_FLOOD,
|
||||
priority=1,
|
||||
match=match_add,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
self.delete_flows(table_id=tables.LOCAL_FLOOD, strict=True,
|
||||
priority=1, match=match_del)
|
||||
|
||||
def local_flood_delete(self, network):
|
||||
self.delete_flows(table_id=tables.LOCAL_FLOOD,
|
||||
metadata=meta.mk_metadata(network))
|
||||
|
||||
def local_out_add_port(self, network, port, mac):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
match = ofpp.OFPMatch(metadata=meta.mk_metadata(network), eth_dst=mac)
|
||||
actions = [ofpp.OFPActionOutput(port=port)]
|
||||
instructions = [
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, actions),
|
||||
]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=tables.LOCAL_OUT,
|
||||
priority=1,
|
||||
match=match,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
|
||||
def local_out_delete_port(self, network, mac):
|
||||
self.delete_flows(table_id=tables.LOCAL_OUT,
|
||||
metadata=meta.mk_metadata(network), eth_dst=mac)
|
||||
|
||||
def arp_passthrough(self, network, tpa):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
match = ofpp.OFPMatch(metadata=meta.mk_metadata(network),
|
||||
eth_type=ether.ETH_TYPE_ARP,
|
||||
arp_op=arp.ARP_REQUEST,
|
||||
arp_tpa=tpa)
|
||||
instructions = [
|
||||
ofpp.OFPInstructionGotoTable(table_id=tables.TUNNEL_OUT)]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=tables.ARP_PASSTHROUGH,
|
||||
priority=1,
|
||||
idle_timeout=5,
|
||||
match=match,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
@ -1,38 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from ryu.lib import hub
|
||||
hub.patch()
|
||||
|
||||
import sys
|
||||
|
||||
from oslo_config import cfg
|
||||
from ryu.base.app_manager import AppManager
|
||||
from ryu import cfg as ryu_cfg
|
||||
|
||||
from neutron.common import config as common_config
|
||||
|
||||
|
||||
def main():
|
||||
common_config.init(sys.argv[1:])
|
||||
# the following check is a transitional workaround to make this work
|
||||
# with different versions of ryu.
|
||||
# TODO(yamamoto) remove this later
|
||||
if ryu_cfg.CONF is not cfg.CONF:
|
||||
ryu_cfg.CONF(project='ryu', args=[])
|
||||
common_config.setup_logging()
|
||||
AppManager.run_apps(['neutron.plugins.ofagent.agent.ofa_neutron_agent'])
|
@ -1,26 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# 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.
|
||||
|
||||
from neutron.plugins.ofagent.agent import constants as const
|
||||
|
||||
|
||||
# metadata mask
|
||||
NETWORK_MASK = const.LOCAL_VLAN_MASK
|
||||
LOCAL = 0x10000 # the packet came from local vm ports
|
||||
|
||||
|
||||
def mk_metadata(network, flags=0):
|
||||
return (flags | network, flags | NETWORK_MASK)
|
@ -1,78 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# 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.
|
||||
|
||||
from ryu.app.ofctl import api as ofctl_api
|
||||
|
||||
|
||||
class OpenFlowSwitch(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(OpenFlowSwitch, self).__init__(*args, **kwargs)
|
||||
self._dp = None
|
||||
# logically app doesn't belong here. just for convenience.
|
||||
self._app = None
|
||||
|
||||
def set_dp(self, dp):
|
||||
self._dp = dp
|
||||
|
||||
def set_app(self, app):
|
||||
self._app = app
|
||||
|
||||
def _get_dp(self):
|
||||
"""a convenient method for openflow message composers"""
|
||||
dp = self._dp
|
||||
return (dp, dp.ofproto, dp.ofproto_parser)
|
||||
|
||||
def _send_msg(self, msg):
|
||||
return ofctl_api.send_msg(self._app, msg)
|
||||
|
||||
def delete_flows(self, table_id=None, strict=False, priority=0,
|
||||
match=None, **match_kwargs):
|
||||
(dp, ofp, ofpp) = self._get_dp()
|
||||
if table_id is None:
|
||||
table_id = ofp.OFPTT_ALL
|
||||
if match is None:
|
||||
match = ofpp.OFPMatch(**match_kwargs)
|
||||
if strict:
|
||||
cmd = ofp.OFPFC_DELETE_STRICT
|
||||
else:
|
||||
cmd = ofp.OFPFC_DELETE
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
command=cmd,
|
||||
table_id=table_id,
|
||||
match=match,
|
||||
priority=priority,
|
||||
out_group=ofp.OFPG_ANY,
|
||||
out_port=ofp.OFPP_ANY)
|
||||
self._send_msg(msg)
|
||||
|
||||
def install_default_drop(self, table_id):
|
||||
(dp, _ofp, ofpp) = self._get_dp()
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=table_id,
|
||||
priority=0)
|
||||
self._send_msg(msg)
|
||||
|
||||
def install_default_goto(self, table_id, dest_table_id):
|
||||
(dp, _ofp, ofpp) = self._get_dp()
|
||||
instructions = [ofpp.OFPInstructionGotoTable(table_id=dest_table_id)]
|
||||
msg = ofpp.OFPFlowMod(dp,
|
||||
table_id=table_id,
|
||||
priority=0,
|
||||
instructions=instructions)
|
||||
self._send_msg(msg)
|
||||
|
||||
def install_default_goto_next(self, table_id):
|
||||
self.install_default_goto(table_id, table_id + 1)
|
@ -1,90 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# 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.
|
||||
|
||||
from neutron.common import constants as n_const
|
||||
|
||||
|
||||
class OFPort(object):
|
||||
def __init__(self, port_name, ofport):
|
||||
self.port_name = port_name
|
||||
self.ofport = ofport
|
||||
|
||||
@classmethod
|
||||
def from_ofp_port(cls, ofp_port):
|
||||
"""Convert from ryu OFPPort."""
|
||||
return cls(port_name=ofp_port.name, ofport=ofp_port.port_no)
|
||||
|
||||
|
||||
PORT_NAME_LEN = 14
|
||||
PORT_NAME_PREFIXES = [
|
||||
n_const.TAP_DEVICE_PREFIX, # common cases, including ovs_use_veth=True
|
||||
"qvo", # nova hybrid interface driver
|
||||
"qr-", # l3-agent INTERNAL_DEV_PREFIX (ovs_use_veth=False)
|
||||
"qg-", # l3-agent EXTERNAL_DEV_PREFIX (ovs_use_veth=False)
|
||||
]
|
||||
|
||||
|
||||
def _is_neutron_port(name):
|
||||
"""Return True if the port name looks like a neutron port."""
|
||||
if len(name) != PORT_NAME_LEN:
|
||||
return False
|
||||
for pref in PORT_NAME_PREFIXES:
|
||||
if name.startswith(pref):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_normalized_port_name(interface_id):
|
||||
"""Convert from neutron device id (uuid) to "normalized" port name.
|
||||
|
||||
This needs to be synced with ML2 plugin's _device_to_port_id().
|
||||
|
||||
An assumption: The switch uses an OS's interface name as the
|
||||
corresponding OpenFlow port name.
|
||||
NOTE(yamamoto): While it's true for Open vSwitch, it isn't
|
||||
necessarily true everywhere. For example, LINC uses something
|
||||
like "LogicalSwitch0-Port2".
|
||||
|
||||
NOTE(yamamoto): The actual prefix might be different. For example,
|
||||
with the hybrid interface driver, it's "qvo". However, we always
|
||||
use "tap" prefix throughout the agent and plugin for simplicity.
|
||||
Some care should be taken when talking to the switch.
|
||||
"""
|
||||
return (n_const.TAP_DEVICE_PREFIX + interface_id)[0:PORT_NAME_LEN]
|
||||
|
||||
|
||||
def _normalize_port_name(name):
|
||||
"""Normalize port name.
|
||||
|
||||
See comments in _get_ofport_name.
|
||||
"""
|
||||
for pref in PORT_NAME_PREFIXES:
|
||||
if name.startswith(pref):
|
||||
return n_const.TAP_DEVICE_PREFIX + name[len(pref):]
|
||||
return name
|
||||
|
||||
|
||||
class Port(OFPort):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Port, self).__init__(*args, **kwargs)
|
||||
self.vif_mac = None
|
||||
|
||||
def is_neutron_port(self):
|
||||
"""Return True if the port looks like a neutron port."""
|
||||
return _is_neutron_port(self.port_name)
|
||||
|
||||
def normalized_port_name(self):
|
||||
return _normalize_port_name(self.port_name)
|
@ -1,62 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# 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.
|
||||
|
||||
from neutron.plugins.common import constants as p_const
|
||||
|
||||
|
||||
def _seq():
|
||||
"""Yield sequential numbers starting from 0."""
|
||||
i = 0
|
||||
while True:
|
||||
yield i
|
||||
i += 1
|
||||
|
||||
_table_id_gen = _seq()
|
||||
|
||||
|
||||
def _table_id():
|
||||
"""A simple table id allocator."""
|
||||
return _table_id_gen.next()
|
||||
|
||||
# Supported tunnel types.
|
||||
TUNNEL_TYPES = [
|
||||
p_const.TYPE_GRE,
|
||||
p_const.TYPE_VXLAN,
|
||||
]
|
||||
|
||||
# Reversed version of TUNNEL_TYPES.
|
||||
TUNNEL_TYPE_IDX = dict((t, TUNNEL_TYPES.index(t)) for t in TUNNEL_TYPES)
|
||||
|
||||
# We use sequential table ids starting from 0.
|
||||
# We don't hardcode values here to avoid manual reassignments eg. when adding
|
||||
# a new tunnel type.
|
||||
# See a big comment in flows.py for how each tables are used.
|
||||
CHECK_IN_PORT = _table_id()
|
||||
TUNNEL_IN = {}
|
||||
for t in TUNNEL_TYPES:
|
||||
TUNNEL_IN[t] = _table_id()
|
||||
PHYS_IN = _table_id()
|
||||
LOCAL_IN = _table_id()
|
||||
ARP_PASSTHROUGH = _table_id()
|
||||
ARP_RESPONDER = _table_id()
|
||||
TUNNEL_OUT = _table_id()
|
||||
LOCAL_OUT = _table_id()
|
||||
PHYS_OUT = _table_id()
|
||||
TUNNEL_FLOOD = {}
|
||||
for t in TUNNEL_TYPES:
|
||||
TUNNEL_FLOOD[t] = _table_id()
|
||||
PHYS_FLOOD = _table_id()
|
||||
LOCAL_FLOOD = _table_id()
|
@ -1,36 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# Copyright (C) 2014 Fumihiko Kakuma <kakuma 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 neutron.agent.common import config
|
||||
from neutron.plugins.openvswitch.common import config as ovs_config
|
||||
|
||||
|
||||
agent_opts = [
|
||||
cfg.IntOpt('get_datapath_retry_times', default=60,
|
||||
help=_("Number of seconds to retry acquiring "
|
||||
"an Open vSwitch datapath")),
|
||||
cfg.ListOpt('physical_interface_mappings',
|
||||
default=[],
|
||||
help=_("List of <physical_network>:<physical_interface>")),
|
||||
]
|
||||
|
||||
|
||||
cfg.CONF.register_opts(ovs_config.ovs_opts, 'OVS')
|
||||
cfg.CONF.register_opts(ovs_config.agent_opts, 'AGENT')
|
||||
cfg.CONF.register_opts(agent_opts, 'AGENT')
|
||||
config.register_agent_state_opts_helper(cfg.CONF)
|
@ -1,98 +0,0 @@
|
||||
# Copyright (c) 2014 OpenStack Foundation
|
||||
# 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 neutron.common import constants
|
||||
from neutron.extensions import portbindings
|
||||
from neutron.plugins.ml2.drivers import mech_ofagent
|
||||
from neutron.tests.unit.ml2 import _test_mech_agent as base
|
||||
|
||||
|
||||
class OfagentMechanismBaseTestCase(base.AgentMechanismBaseTestCase):
|
||||
VIF_TYPE = portbindings.VIF_TYPE_OVS
|
||||
VIF_DETAILS = {portbindings.CAP_PORT_FILTER: True,
|
||||
portbindings.OVS_HYBRID_PLUG: True}
|
||||
AGENT_TYPE = constants.AGENT_TYPE_OFA
|
||||
|
||||
GOOD_MAPPINGS = {'fake_physical_network': 'fake_interface'}
|
||||
GOOD_TUNNEL_TYPES = ['gre', 'vxlan']
|
||||
GOOD_CONFIGS = {'interface_mappings': GOOD_MAPPINGS,
|
||||
'tunnel_types': GOOD_TUNNEL_TYPES}
|
||||
|
||||
BAD_MAPPINGS = {'wrong_physical_network': 'wrong_interface'}
|
||||
BAD_TUNNEL_TYPES = ['bad_tunnel_type']
|
||||
BAD_CONFIGS = {'interface_mappings': BAD_MAPPINGS,
|
||||
'tunnel_types': BAD_TUNNEL_TYPES}
|
||||
|
||||
AGENTS = [{'alive': True,
|
||||
'configurations': GOOD_CONFIGS,
|
||||
'host': 'host'}]
|
||||
AGENTS_DEAD = [{'alive': False,
|
||||
'configurations': GOOD_CONFIGS,
|
||||
'host': 'dead_host'}]
|
||||
AGENTS_BAD = [{'alive': False,
|
||||
'configurations': GOOD_CONFIGS,
|
||||
'host': 'bad_host_1'},
|
||||
{'alive': True,
|
||||
'configurations': BAD_CONFIGS,
|
||||
'host': 'bad_host_2'}]
|
||||
|
||||
def setUp(self):
|
||||
super(OfagentMechanismBaseTestCase, self).setUp()
|
||||
self.driver = mech_ofagent.OfagentMechanismDriver()
|
||||
self.driver.initialize()
|
||||
|
||||
|
||||
class OfagentMechanismSGDisabledBaseTestCase(OfagentMechanismBaseTestCase):
|
||||
VIF_DETAILS = {portbindings.CAP_PORT_FILTER: False,
|
||||
portbindings.OVS_HYBRID_PLUG: False}
|
||||
|
||||
def setUp(self):
|
||||
cfg.CONF.set_override('enable_security_group',
|
||||
False,
|
||||
group='SECURITYGROUP')
|
||||
super(OfagentMechanismSGDisabledBaseTestCase, self).setUp()
|
||||
|
||||
|
||||
class OfagentMechanismGenericTestCase(OfagentMechanismBaseTestCase,
|
||||
base.AgentMechanismGenericTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class OfagentMechanismLocalTestCase(OfagentMechanismBaseTestCase,
|
||||
base.AgentMechanismLocalTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class OfagentMechanismFlatTestCase(OfagentMechanismBaseTestCase,
|
||||
base.AgentMechanismFlatTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class OfagentMechanismVlanTestCase(OfagentMechanismBaseTestCase,
|
||||
base.AgentMechanismVlanTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class OfagentMechanismGreTestCase(OfagentMechanismBaseTestCase,
|
||||
base.AgentMechanismGreTestCase):
|
||||
pass
|
||||
|
||||
|
||||
class OfagentMechanismSGDisabledLocalTestCase(
|
||||
OfagentMechanismSGDisabledBaseTestCase,
|
||||
base.AgentMechanismLocalTestCase):
|
||||
pass
|
@ -1,150 +0,0 @@
|
||||
# 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 = 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_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')
|
||||
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.vlan = vlan
|
||||
ryu_ofproto_mod = ryu_mod.ofproto
|
||||
ether = _Mod('ryu.ofproto.ether')
|
||||
ofp = _Mod('ryu.ofproto.ofproto_v1_3')
|
||||
ofpp = _Mod('ryu.ofproto.ofproto_v1_3_parser')
|
||||
ryu_ofproto_mod.ether = ether
|
||||
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.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.vlan': vlan,
|
||||
'ryu.ofproto': ryu_ofproto_mod,
|
||||
'ryu.ofproto.ether': ether,
|
||||
'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)
|
@ -1,71 +0,0 @@
|
||||
# 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
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
|
||||
from neutron.tests import base
|
||||
from neutron.tests.unit.ofagent import fake_oflib
|
||||
|
||||
|
||||
class OFATestBase(base.BaseTestCase):
|
||||
|
||||
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(OFATestBase, self).setUp()
|
||||
|
||||
def _mk_test_dp(self, name):
|
||||
ofp = importutils.import_module('ryu.ofproto.ofproto_v1_3')
|
||||
ofpp = importutils.import_module('ryu.ofproto.ofproto_v1_3_parser')
|
||||
dp = mock.Mock()
|
||||
dp.ofproto = ofp
|
||||
dp.ofproto_parser = ofpp
|
||||
dp.__repr__ = mock.Mock(return_value=name)
|
||||
return dp
|
||||
|
||||
def _mk_test_br(self, name):
|
||||
dp = self._mk_test_dp(name)
|
||||
br = mock.Mock()
|
||||
br.datapath = dp
|
||||
br.ofproto = dp.ofproto
|
||||
br.ofparser = dp.ofproto_parser
|
||||
return br
|
||||
|
||||
|
||||
class OFAAgentTestBase(OFATestBase):
|
||||
|
||||
_AGENT_NAME = 'neutron.plugins.ofagent.agent.ofa_neutron_agent'
|
||||
|
||||
def setUp(self):
|
||||
super(OFAAgentTestBase, self).setUp()
|
||||
self.mod_agent = importutils.import_module(self._AGENT_NAME)
|
||||
self.ryuapp = mock.Mock()
|
||||
|
||||
def setup_config(self):
|
||||
cfg.CONF.set_default('firewall_driver',
|
||||
'neutron.agent.firewall.NoopFirewallDriver',
|
||||
group='SECURITYGROUP')
|
||||
cfg.CONF.register_cli_opts([
|
||||
cfg.StrOpt('ofp-listen-host', default='',
|
||||
help='openflow listen host'),
|
||||
cfg.IntOpt('ofp-tcp-listen-port', default=6633,
|
||||
help='openflow tcp listen port')
|
||||
])
|
||||
super(OFATestBase, self).setup_config()
|
@ -1,334 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# Copyright (C) 2014 Fumihiko Kakuma <kakuma 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 collections
|
||||
import contextlib
|
||||
import copy
|
||||
|
||||
import mock
|
||||
from oslo_utils import importutils
|
||||
|
||||
import neutron.plugins.ofagent.agent.metadata as meta
|
||||
from neutron.tests.unit.ofagent import ofa_test_base
|
||||
|
||||
|
||||
_OFALIB_NAME = 'neutron.plugins.ofagent.agent.arp_lib'
|
||||
|
||||
|
||||
class OFAAgentTestCase(ofa_test_base.OFAAgentTestBase):
|
||||
|
||||
def setUp(self):
|
||||
super(OFAAgentTestCase, self).setUp()
|
||||
|
||||
Net = collections.namedtuple('Net', 'net, mac, ip')
|
||||
self.nets = [Net(net=10, mac='11:11:11:44:55:66', ip='10.1.2.20'),
|
||||
Net(net=10, mac='11:11:11:44:55:67', ip='10.1.2.21'),
|
||||
Net(net=20, mac='22:22:22:44:55:66', ip='10.2.2.20')]
|
||||
|
||||
self.packet_mod = mock.Mock()
|
||||
self.proto_ethernet_mod = mock.Mock()
|
||||
self.proto_vlan_mod = mock.Mock()
|
||||
self.proto_vlan_mod.vid = 999
|
||||
self.proto_arp_mod = mock.Mock()
|
||||
self.fake_get_protocol = mock.Mock(return_value=self.proto_vlan_mod)
|
||||
self.packet_mod.get_protocol = self.fake_get_protocol
|
||||
self.fake_add_protocol = mock.Mock()
|
||||
self.packet_mod.add_protocol = self.fake_add_protocol
|
||||
self.arp = importutils.import_module('ryu.lib.packet.arp')
|
||||
self.ethernet = importutils.import_module('ryu.lib.packet.ethernet')
|
||||
self.vlan = importutils.import_module('ryu.lib.packet.vlan')
|
||||
mock.patch('ryu.lib.packet.packet.Packet',
|
||||
return_value=self.packet_mod).start()
|
||||
|
||||
self.ryuapp = 'ryuapp'
|
||||
self.inport = '1'
|
||||
self.ev = mock.Mock()
|
||||
self.datapath = self._mk_test_dp('tun_br')
|
||||
self.ofproto = importutils.import_module('ryu.ofproto.ofproto_v1_3')
|
||||
self.ofpp = mock.Mock()
|
||||
self.datapath.ofproto = self.ofproto
|
||||
self.datapath.ofproto_parser = self.ofpp
|
||||
self.OFPActionOutput = mock.Mock()
|
||||
self.OFPActionOutput.return_value = 'OFPActionOutput'
|
||||
self.ofpp.OFPActionOutput = self.OFPActionOutput
|
||||
self.msg = mock.Mock()
|
||||
self.msg.datapath = self.datapath
|
||||
self.msg.buffer_id = self.ofproto.OFP_NO_BUFFER
|
||||
self.msg_data = 'test_message_data'
|
||||
self.msg.data = self.msg_data
|
||||
self.ev.msg = self.msg
|
||||
self.msg.match = {'in_port': self.inport,
|
||||
'metadata': meta.LOCAL | self.nets[0].net}
|
||||
|
||||
|
||||
class TestArpLib(OFAAgentTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestArpLib, self).setUp()
|
||||
|
||||
self.mod_arplib = importutils.import_module(_OFALIB_NAME)
|
||||
self.arplib = self.mod_arplib.ArpLib(self.ryuapp)
|
||||
self.packet_mod.get_protocol = self._fake_get_protocol
|
||||
self._fake_get_protocol_ethernet = True
|
||||
self._fake_get_protocol_vlan = True
|
||||
self._fake_get_protocol_arp = True
|
||||
self.br = mock.Mock(datapath=self.datapath)
|
||||
self.arplib.set_bridge(self.br)
|
||||
|
||||
def test__send_unknown_packet_no_buffer(self):
|
||||
in_port = 3
|
||||
out_port = self.ofproto.OFPP_TABLE
|
||||
self.msg.buffer_id = self.ofproto.OFP_NO_BUFFER
|
||||
self.arplib._send_unknown_packet(self.msg, in_port, out_port)
|
||||
actions = [self.ofpp.OFPActionOutput(self.ofproto.OFPP_TABLE, 0)]
|
||||
self.ofpp.OFPPacketOut.assert_called_once_with(
|
||||
datapath=self.datapath,
|
||||
buffer_id=self.msg.buffer_id,
|
||||
in_port=in_port,
|
||||
actions=actions,
|
||||
data=self.msg_data)
|
||||
|
||||
def test__send_unknown_packet_existence_buffer(self):
|
||||
in_port = 3
|
||||
out_port = self.ofproto.OFPP_TABLE
|
||||
self.msg.buffer_id = 256
|
||||
self.arplib._send_unknown_packet(self.msg, in_port, out_port)
|
||||
actions = [self.ofpp.OFPActionOutput(self.ofproto.OFPP_TABLE, 0)]
|
||||
self.ofpp.OFPPacketOut.assert_called_once_with(
|
||||
datapath=self.datapath,
|
||||
buffer_id=self.msg.buffer_id,
|
||||
in_port=in_port,
|
||||
actions=actions,
|
||||
data=None)
|
||||
|
||||
def test__respond_arp(self):
|
||||
self.arplib._arp_tbl = {
|
||||
self.nets[0].net: {self.nets[0].ip: self.nets[0].mac}}
|
||||
port = 3
|
||||
arptbl = self.arplib._arp_tbl[self.nets[0].net]
|
||||
pkt_ethernet = self.ethernet
|
||||
pkt_vlan = self.vlan
|
||||
pkt_arp = self.arp
|
||||
pkt_arp.opcode = self.arp.ARP_REQUEST
|
||||
pkt_arp.dst_ip = self.nets[0].ip
|
||||
with mock.patch.object(
|
||||
self.arplib, '_send_arp_reply'
|
||||
) as send_arp_rep_fn:
|
||||
self.assertTrue(
|
||||
self.arplib._respond_arp(self.datapath, port, arptbl,
|
||||
pkt_ethernet, pkt_vlan, pkt_arp))
|
||||
ethernet_ethernet = self.ethernet.ethernet(
|
||||
ethertype=pkt_ethernet.ethertype,
|
||||
dst=pkt_ethernet.src,
|
||||
src=self.nets[0].mac)
|
||||
vlan_vlan = self.vlan.vlan(cfi=pkt_vlan.cfi,
|
||||
ethertype=pkt_vlan.ethertype,
|
||||
pcp=pkt_vlan.pcp,
|
||||
vid=pkt_vlan.vid)
|
||||
arp_arp = self.arp.arp(opcode=self.arp.ARP_REPLY,
|
||||
src_mac=self.nets[0].mac,
|
||||
src_ip=pkt_arp.dst_ip,
|
||||
dst_mac=pkt_arp.src_mac,
|
||||
dst_ip=pkt_arp.src_ip)
|
||||
self.fake_add_protocol.assert_has_calls([mock.call(ethernet_ethernet),
|
||||
mock.call(vlan_vlan),
|
||||
mock.call(arp_arp)])
|
||||
send_arp_rep_fn.assert_called_once_with(
|
||||
self.datapath, port, self.packet_mod)
|
||||
|
||||
def _test__respond_arp(self, pkt_arp):
|
||||
self.arplib._arp_tbl = {
|
||||
self.nets[0].net: {self.nets[0].ip: self.nets[0].mac}}
|
||||
port = 3
|
||||
arptbl = self.arplib._arp_tbl[self.nets[0].net]
|
||||
pkt_ethernet = mock.Mock()
|
||||
pkt_vlan = mock.Mock()
|
||||
self.assertFalse(
|
||||
self.arplib._respond_arp(self.datapath, port, arptbl,
|
||||
pkt_ethernet, pkt_vlan, pkt_arp))
|
||||
|
||||
def test__respond_arp_non_arp_req(self):
|
||||
pkt_arp = mock.Mock()
|
||||
pkt_arp.opcode = self.arp.ARP_REPLY
|
||||
self._test__respond_arp(pkt_arp)
|
||||
|
||||
def test__respond_arp_ip_not_found_in_arptable(self):
|
||||
pkt_arp = mock.Mock()
|
||||
pkt_arp.opcode = self.arp.ARP_REQUEST
|
||||
pkt_arp.dst_ip = self.nets[1].ip
|
||||
self._test__respond_arp(pkt_arp)
|
||||
|
||||
def test_add_arp_table_entry(self):
|
||||
self.arplib.add_arp_table_entry(self.nets[0].net,
|
||||
self.nets[0].ip, self.nets[0].mac)
|
||||
self.assertEqual(
|
||||
self.arplib._arp_tbl,
|
||||
{self.nets[0].net: {self.nets[0].ip: self.nets[0].mac}})
|
||||
|
||||
def test_add_arp_table_entry_multiple_net(self):
|
||||
self.arplib.add_arp_table_entry(self.nets[0].net,
|
||||
self.nets[0].ip, self.nets[0].mac)
|
||||
self.arplib.add_arp_table_entry(self.nets[2].net,
|
||||
self.nets[2].ip, self.nets[2].mac)
|
||||
self.assertEqual(
|
||||
self.arplib._arp_tbl,
|
||||
{self.nets[0].net: {self.nets[0].ip: self.nets[0].mac},
|
||||
self.nets[2].net: {self.nets[2].ip: self.nets[2].mac}})
|
||||
|
||||
def test_add_arp_table_entry_multiple_ip(self):
|
||||
self.arplib.add_arp_table_entry(self.nets[0].net,
|
||||
self.nets[0].ip, self.nets[0].mac)
|
||||
self.arplib.add_arp_table_entry(self.nets[0].net,
|
||||
self.nets[1].ip, self.nets[1].mac)
|
||||
self.assertEqual(
|
||||
self.arplib._arp_tbl,
|
||||
{self.nets[0].net: {self.nets[0].ip: self.nets[0].mac,
|
||||
self.nets[1].ip: self.nets[1].mac}})
|
||||
|
||||
def test_del_arp_table_entry(self):
|
||||
self.arplib._arp_tbl = {
|
||||
self.nets[0].net: {self.nets[0].ip: self.nets[0].mac}}
|
||||
self.arplib.del_arp_table_entry(self.nets[0].net, self.nets[0].ip)
|
||||
self.assertEqual(self.arplib._arp_tbl, {})
|
||||
|
||||
def test_del_arp_table_entry_unknown_network(self):
|
||||
self.arplib._arp_tbl = {
|
||||
100: {"192.0.2.1": "fa:16:3e:e2:37:37"},
|
||||
}
|
||||
orig = copy.deepcopy(self.arplib._arp_tbl)
|
||||
self.arplib.del_arp_table_entry(200, "192.0.2.1")
|
||||
self.assertEqual(orig, self.arplib._arp_tbl)
|
||||
|
||||
def test_del_arp_table_entry_unknown_ip(self):
|
||||
self.arplib._arp_tbl = {
|
||||
100: {"192.0.2.1": "fa:16:3e:e2:37:37"},
|
||||
}
|
||||
orig = copy.deepcopy(self.arplib._arp_tbl)
|
||||
self.arplib.del_arp_table_entry(100, "192.0.2.9")
|
||||
self.assertEqual(orig, self.arplib._arp_tbl)
|
||||
|
||||
def test_del_arp_table_entry_multiple_net(self):
|
||||
self.arplib._arp_tbl = {
|
||||
self.nets[0].net: {self.nets[0].ip: self.nets[0].mac},
|
||||
self.nets[2].net: {self.nets[2].ip: self.nets[2].mac}}
|
||||
self.arplib.del_arp_table_entry(self.nets[0].net, self.nets[0].ip)
|
||||
self.assertEqual(
|
||||
self.arplib._arp_tbl,
|
||||
{self.nets[2].net: {self.nets[2].ip: self.nets[2].mac}})
|
||||
|
||||
def test_del_arp_table_entry_multiple_ip(self):
|
||||
self.arplib._arp_tbl = {
|
||||
self.nets[0].net: {self.nets[0].ip: self.nets[0].mac,
|
||||
self.nets[1].ip: self.nets[1].mac}}
|
||||
self.arplib.del_arp_table_entry(self.nets[0].net, self.nets[1].ip)
|
||||
self.assertEqual(
|
||||
self.arplib._arp_tbl,
|
||||
{self.nets[0].net: {self.nets[0].ip: self.nets[0].mac}})
|
||||
|
||||
def _fake_get_protocol(self, net_type):
|
||||
if net_type == self.ethernet.ethernet:
|
||||
if self._fake_get_protocol_ethernet:
|
||||
return self.proto_ethernet_mod
|
||||
else:
|
||||
return
|
||||
if net_type == self.vlan.vlan:
|
||||
if self._fake_get_protocol_vlan:
|
||||
return self.proto_vlan_mod
|
||||
else:
|
||||
return
|
||||
if net_type == self.arp.arp:
|
||||
if self._fake_get_protocol_arp:
|
||||
return self.proto_arp_mod
|
||||
else:
|
||||
return
|
||||
|
||||
def _test_packet_in_handler(self):
|
||||
self.arplib._arp_tbl = {
|
||||
self.nets[0].net: {self.nets[0].ip: self.nets[0].mac}}
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.arplib, '_respond_arp',
|
||||
return_value=True),
|
||||
mock.patch.object(self.br,
|
||||
'arp_passthrough'),
|
||||
mock.patch.object(self.arplib,
|
||||
'_send_unknown_packet'),
|
||||
) as (res_arp_fn, add_flow_fn, send_unknown_pk_fn):
|
||||
self.arplib.packet_in_handler(self.ev)
|
||||
self.assertFalse(add_flow_fn.call_count)
|
||||
self.assertFalse(send_unknown_pk_fn.call_count)
|
||||
res_arp_fn.assert_called_once_with(
|
||||
self.datapath, self.inport,
|
||||
self.arplib._arp_tbl[self.nets[0].net],
|
||||
self.proto_ethernet_mod,
|
||||
self.proto_vlan_mod if self._fake_get_protocol_vlan else None,
|
||||
self.proto_arp_mod)
|
||||
|
||||
def _test_packet_in_handler_drop(self):
|
||||
self.arplib._arp_tbl = {
|
||||
self.nets[0].net: {self.nets[0].ip: self.nets[0].mac}}
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.arplib, '_respond_arp',
|
||||
return_value=True),
|
||||
mock.patch.object(self.br, 'arp_passthrough'),
|
||||
mock.patch.object(self.arplib,
|
||||
'_send_unknown_packet'),
|
||||
) as (res_arp_fn, add_flow_fn, send_unknown_pk_fn):
|
||||
self.arplib.packet_in_handler(self.ev)
|
||||
self.assertFalse(add_flow_fn.call_count)
|
||||
self.assertFalse(send_unknown_pk_fn.call_count)
|
||||
self.assertFalse(res_arp_fn.call_count)
|
||||
|
||||
def test_packet_in_handler(self):
|
||||
self._test_packet_in_handler()
|
||||
|
||||
def test_packet_in_handler_non_ethernet(self):
|
||||
self._fake_get_protocol_ethernet = False
|
||||
self._test_packet_in_handler_drop()
|
||||
|
||||
def test_packet_in_handler_non_vlan(self):
|
||||
self._fake_get_protocol_vlan = False
|
||||
self._test_packet_in_handler()
|
||||
|
||||
def test_packet_in_handler_non_arp(self):
|
||||
self._fake_get_protocol_arp = False
|
||||
self._test_packet_in_handler_drop()
|
||||
|
||||
def test_packet_in_handler_corrupted(self):
|
||||
mock.patch('ryu.lib.packet.packet.Packet',
|
||||
side_effect=ValueError).start()
|
||||
self._test_packet_in_handler_drop()
|
||||
|
||||
def test_packet_in_handler_unknown_network(self):
|
||||
self.arplib._arp_tbl = {
|
||||
self.nets[0].net: {self.nets[0].ip: self.nets[0].mac}}
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.arplib, '_respond_arp',
|
||||
return_value=False),
|
||||
mock.patch.object(self.br, 'arp_passthrough'),
|
||||
mock.patch.object(self.arplib,
|
||||
'_send_unknown_packet'),
|
||||
) as (res_arp_fn, add_flow_fn, send_unknown_pk_fn):
|
||||
self.arplib.packet_in_handler(self.ev)
|
||||
add_flow_fn.assert_called_once_with(
|
||||
network=self.nets[0].net,
|
||||
tpa=self.proto_arp_mod.dst_ip)
|
||||
send_unknown_pk_fn.assert_called_once_with(
|
||||
self.ev.msg, self.msg.match['in_port'],
|
||||
self.datapath.ofproto.OFPP_TABLE)
|
||||
res_arp_fn.assert_called_once_with(
|
||||
self.datapath, self.inport,
|
||||
self.arplib._arp_tbl[self.nets[0].net],
|
||||
self.proto_ethernet_mod, self.proto_vlan_mod, self.proto_arp_mod)
|
@ -1,384 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# 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
|
||||
from oslo_utils import importutils
|
||||
|
||||
import neutron.plugins.ofagent.agent.metadata as meta
|
||||
from neutron.tests.unit.ofagent import ofa_test_base
|
||||
|
||||
|
||||
class TestOFAgentFlows(ofa_test_base.OFATestBase):
|
||||
|
||||
_MOD = 'neutron.plugins.ofagent.agent.flows'
|
||||
|
||||
def setUp(self):
|
||||
super(TestOFAgentFlows, self).setUp()
|
||||
self.mod = importutils.import_module(self._MOD)
|
||||
self.br = self.mod.OFAgentIntegrationBridge()
|
||||
self.br.set_dp(self._mk_test_dp("dp"))
|
||||
|
||||
def test_setup_default_table(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.setup_default_table()
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
arp = importutils.import_module('ryu.lib.packet.arp')
|
||||
ether = importutils.import_module('ryu.ofproto.ether')
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, command=ofp.OFPFC_DELETE,
|
||||
match=ofpp.OFPMatch(), out_group=ofp.OFPG_ANY,
|
||||
out_port=ofp.OFPP_ANY, priority=0, table_id=ofp.OFPTT_ALL)),
|
||||
call(ofpp.OFPFlowMod(dp, priority=0, table_id=0)),
|
||||
call(ofpp.OFPFlowMod(dp, priority=0, table_id=1)),
|
||||
call(ofpp.OFPFlowMod(dp, priority=0, table_id=2)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=7)],
|
||||
priority=0, table_id=3)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=5)],
|
||||
priority=0, table_id=4)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=6)],
|
||||
priority=0, table_id=5)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
|
||||
[ofpp.OFPActionOutput(ofp.OFPP_CONTROLLER)])],
|
||||
match=ofpp.OFPMatch(arp_op=arp.ARP_REQUEST,
|
||||
eth_type=ether.ETH_TYPE_ARP), priority=1, table_id=6)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=7)],
|
||||
priority=0, table_id=6)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=8)],
|
||||
priority=0, table_id=7)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=9)],
|
||||
priority=0, table_id=8)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=10)],
|
||||
priority=0, table_id=9)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=11)],
|
||||
priority=0, table_id=10)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=12)],
|
||||
priority=0, table_id=11)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=13)],
|
||||
priority=0, table_id=12)),
|
||||
call(ofpp.OFPFlowMod(dp, priority=0, table_id=13)),
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls, any_order=True)
|
||||
|
||||
def test_install_arp_responder(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.install_arp_responder(table_id=99)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
arp = importutils.import_module('ryu.lib.packet.arp')
|
||||
ether = importutils.import_module('ryu.ofproto.ether')
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
|
||||
[ofpp.OFPActionOutput(ofp.OFPP_CONTROLLER)])],
|
||||
match=ofpp.OFPMatch(arp_op=arp.ARP_REQUEST,
|
||||
eth_type=ether.ETH_TYPE_ARP), priority=1, table_id=99)),
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=100)],
|
||||
priority=0, table_id=99)),
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_install_tunnel_output(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.install_tunnel_output(table_id=110, network=111,
|
||||
segmentation_id=112, ports=[113, 114],
|
||||
goto_next=True)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(
|
||||
ofpp.OFPFlowMod(
|
||||
dp,
|
||||
instructions=[
|
||||
ofpp.OFPInstructionActions(
|
||||
ofp.OFPIT_APPLY_ACTIONS,
|
||||
[
|
||||
ofpp.OFPActionSetField(tunnel_id=112),
|
||||
ofpp.OFPActionOutput(port=113),
|
||||
ofpp.OFPActionOutput(port=114)
|
||||
]
|
||||
),
|
||||
ofpp.OFPInstructionGotoTable(table_id=111)
|
||||
],
|
||||
match=ofpp.OFPMatch(
|
||||
metadata=meta.mk_metadata(111, meta.LOCAL)
|
||||
),
|
||||
priority=1,
|
||||
table_id=110
|
||||
)
|
||||
)
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_delete_tunnel_output(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.delete_tunnel_output(table_id=110, network=111)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(
|
||||
ofpp.OFPFlowMod(
|
||||
dp,
|
||||
command=ofp.OFPFC_DELETE,
|
||||
match=ofpp.OFPMatch(
|
||||
metadata=meta.mk_metadata(111, meta.LOCAL)
|
||||
),
|
||||
out_group=ofp.OFPG_ANY,
|
||||
out_port=ofp.OFPP_ANY, priority=0, table_id=110
|
||||
)
|
||||
)
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_provision_tenant_tunnel(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.provision_tenant_tunnel(network_type="gre", network=150,
|
||||
segmentation_id=151)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionWriteMetadata(metadata=150,
|
||||
metadata_mask=meta.NETWORK_MASK),
|
||||
ofpp.OFPInstructionGotoTable(table_id=7)],
|
||||
match=ofpp.OFPMatch(tunnel_id=151), priority=1, table_id=1))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_reclaim_tenant_tunnel(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.reclaim_tenant_tunnel(network_type="gre", network=150,
|
||||
segmentation_id=151)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, command=ofp.OFPFC_DELETE,
|
||||
match=ofpp.OFPMatch(tunnel_id=151), out_group=ofp.OFPG_ANY,
|
||||
out_port=ofp.OFPP_ANY, priority=0, table_id=1))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_provision_tenant_physnet(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.provision_tenant_physnet(network_type="vlan", network=150,
|
||||
segmentation_id=151, phys_port=99)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionWriteMetadata(metadata=150,
|
||||
metadata_mask=meta.NETWORK_MASK),
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS, [
|
||||
ofpp.OFPActionPopVlan()]),
|
||||
ofpp.OFPInstructionGotoTable(table_id=3)],
|
||||
match=ofpp.OFPMatch(in_port=99,
|
||||
vlan_vid=151 | ofp.OFPVID_PRESENT),
|
||||
priority=1, table_id=0)),
|
||||
call(
|
||||
ofpp.OFPFlowMod(
|
||||
dp,
|
||||
instructions=[
|
||||
ofpp.OFPInstructionActions(
|
||||
ofp.OFPIT_APPLY_ACTIONS,
|
||||
[
|
||||
ofpp.OFPActionPushVlan(),
|
||||
ofpp.OFPActionSetField(
|
||||
vlan_vid=151 | ofp.OFPVID_PRESENT
|
||||
),
|
||||
ofpp.OFPActionOutput(port=99),
|
||||
ofpp.OFPActionPopVlan()
|
||||
]
|
||||
),
|
||||
ofpp.OFPInstructionGotoTable(table_id=13)
|
||||
],
|
||||
match=ofpp.OFPMatch(
|
||||
metadata=meta.mk_metadata(150, meta.LOCAL)
|
||||
),
|
||||
priority=1,
|
||||
table_id=12
|
||||
)
|
||||
)
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_reclaim_tenant_physnet(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.reclaim_tenant_physnet(network_type="vlan", network=150,
|
||||
segmentation_id=151, phys_port=99)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, command=ofp.OFPFC_DELETE,
|
||||
match=ofpp.OFPMatch(in_port=99,
|
||||
vlan_vid=151 | ofp.OFPVID_PRESENT),
|
||||
out_group=ofp.OFPG_ANY, out_port=ofp.OFPP_ANY, priority=0,
|
||||
table_id=0)),
|
||||
call(ofpp.OFPFlowMod(dp, command=ofp.OFPFC_DELETE,
|
||||
match=ofpp.OFPMatch(metadata=meta.mk_metadata(150)),
|
||||
out_group=ofp.OFPG_ANY, out_port=ofp.OFPP_ANY, priority=0,
|
||||
table_id=12))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_check_in_port_add_tunnel_port(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.check_in_port_add_tunnel_port(network_type="gre", port=99)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp,
|
||||
instructions=[ofpp.OFPInstructionGotoTable(table_id=1)],
|
||||
match=ofpp.OFPMatch(in_port=99), priority=1, table_id=0))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_check_in_port_add_local_port(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.check_in_port_add_local_port(network=123, port=99)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp,
|
||||
instructions=[
|
||||
ofpp.OFPInstructionWriteMetadata(
|
||||
metadata=meta.LOCAL | 123,
|
||||
metadata_mask=meta.LOCAL | meta.NETWORK_MASK),
|
||||
ofpp.OFPInstructionGotoTable(table_id=4)],
|
||||
match=ofpp.OFPMatch(in_port=99), priority=1, table_id=0))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_check_in_port_delete_port(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.check_in_port_delete_port(port=99)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, command=ofp.OFPFC_DELETE,
|
||||
match=ofpp.OFPMatch(in_port=99), out_group=ofp.OFPG_ANY,
|
||||
out_port=ofp.OFPP_ANY, priority=0, table_id=0))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_local_flood_update(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.local_flood_update(network=1234, ports=[1, 2, 3],
|
||||
flood_unicast=True)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp,
|
||||
instructions=[ofpp.OFPInstructionActions(
|
||||
ofp.OFPIT_APPLY_ACTIONS, [
|
||||
ofpp.OFPActionOutput(port=1),
|
||||
ofpp.OFPActionOutput(port=2),
|
||||
ofpp.OFPActionOutput(port=3)])],
|
||||
match=ofpp.OFPMatch(metadata=meta.mk_metadata(1234)),
|
||||
priority=1, table_id=13)),
|
||||
call(ofpp.OFPFlowMod(dp, command=ofp.OFPFC_DELETE_STRICT,
|
||||
match=ofpp.OFPMatch(
|
||||
eth_dst=('01:00:00:00:00:00', '01:00:00:00:00:00'),
|
||||
metadata=meta.mk_metadata(1234)),
|
||||
out_group=ofp.OFPG_ANY, out_port=ofp.OFPP_ANY, priority=1,
|
||||
table_id=13))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_local_flood_delete(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.local_flood_delete(network=1234)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, command=ofp.OFPFC_DELETE,
|
||||
match=ofpp.OFPMatch(metadata=meta.mk_metadata(1234)),
|
||||
out_group=ofp.OFPG_ANY, out_port=ofp.OFPP_ANY, priority=0,
|
||||
table_id=13))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_local_out_add_port(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.local_out_add_port(network=1234, port=7,
|
||||
mac='12:34:56:78:9a:bc')
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionActions(ofp.OFPIT_APPLY_ACTIONS,
|
||||
[ofpp.OFPActionOutput(port=7)])],
|
||||
match=ofpp.OFPMatch(eth_dst="12:34:56:78:9a:bc",
|
||||
metadata=meta.mk_metadata(1234)), priority=1, table_id=8))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_local_out_delete_port(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.local_out_delete_port(network=1234, mac='12:34:56:78:9a:bc')
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, command=ofp.OFPFC_DELETE,
|
||||
match=ofpp.OFPMatch(eth_dst="12:34:56:78:9a:bc",
|
||||
metadata=meta.mk_metadata(1234)), out_group=ofp.OFPG_ANY,
|
||||
out_port=ofp.OFPP_ANY, priority=0, table_id=8))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_arp_passthrough(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.arp_passthrough(network=1234, tpa='192.0.2.1')
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
arp = importutils.import_module('ryu.lib.packet.arp')
|
||||
ether = importutils.import_module('ryu.ofproto.ether')
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, idle_timeout=5,
|
||||
instructions=[ofpp.OFPInstructionGotoTable(table_id=7)],
|
||||
match=ofpp.OFPMatch(arp_op=arp.ARP_REQUEST,
|
||||
arp_tpa="192.0.2.1", eth_type=ether.ETH_TYPE_ARP,
|
||||
metadata=meta.mk_metadata(1234)), priority=1, table_id=5))
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
@ -1,798 +0,0 @@
|
||||
# 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.
|
||||
#
|
||||
# Based on test for openvswitch agent(test_ovs_neutron_agent.py).
|
||||
#
|
||||
# Copyright (c) 2012 OpenStack Foundation.
|
||||
#
|
||||
# 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 collections
|
||||
import contextlib
|
||||
import copy
|
||||
|
||||
import mock
|
||||
import netaddr
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import importutils
|
||||
import testtools
|
||||
|
||||
from neutron.agent.linux import ovs_lib
|
||||
from neutron.common import constants as n_const
|
||||
from neutron.plugins.common import constants as p_const
|
||||
from neutron.plugins.ml2.drivers.l2pop import rpc as l2pop_rpc
|
||||
from neutron.tests.unit.ofagent import ofa_test_base
|
||||
|
||||
|
||||
NOTIFIER = ('neutron.plugins.ml2.rpc.AgentNotifierApi')
|
||||
FLOODING_ENTRY = l2pop_rpc.PortInfo(*n_const.FLOODING_ENTRY)
|
||||
|
||||
|
||||
def _mock_port(is_neutron=True, normalized_name=None):
|
||||
p = mock.Mock()
|
||||
p.is_neutron_port.return_value = is_neutron
|
||||
if normalized_name:
|
||||
p.normalized_port_name.return_value = normalized_name
|
||||
return p
|
||||
|
||||
|
||||
class CreateAgentConfigMap(ofa_test_base.OFAAgentTestBase):
|
||||
|
||||
def test_create_agent_config_map_succeeds(self):
|
||||
self.assertTrue(self.mod_agent.create_agent_config_map(cfg.CONF))
|
||||
|
||||
def test_create_agent_config_map_fails_for_invalid_tunnel_config(self):
|
||||
# An ip address is required for tunneling but there is no default,
|
||||
# verify this for both gre and vxlan tunnels.
|
||||
cfg.CONF.set_override('tunnel_types', [p_const.TYPE_GRE],
|
||||
group='AGENT')
|
||||
with testtools.ExpectedException(ValueError):
|
||||
self.mod_agent.create_agent_config_map(cfg.CONF)
|
||||
cfg.CONF.set_override('tunnel_types', [p_const.TYPE_VXLAN],
|
||||
group='AGENT')
|
||||
with testtools.ExpectedException(ValueError):
|
||||
self.mod_agent.create_agent_config_map(cfg.CONF)
|
||||
|
||||
def test_create_agent_config_map_fails_no_local_ip(self):
|
||||
# An ip address is required for tunneling but there is no default
|
||||
cfg.CONF.set_override('tunnel_types', [p_const.TYPE_VXLAN],
|
||||
group='AGENT')
|
||||
with testtools.ExpectedException(ValueError):
|
||||
self.mod_agent.create_agent_config_map(cfg.CONF)
|
||||
|
||||
def test_create_agent_config_map_fails_for_invalid_tunnel_type(self):
|
||||
cfg.CONF.set_override('tunnel_types', ['foobar'], group='AGENT')
|
||||
with testtools.ExpectedException(ValueError):
|
||||
self.mod_agent.create_agent_config_map(cfg.CONF)
|
||||
|
||||
def test_create_agent_config_map_multiple_tunnel_types(self):
|
||||
cfg.CONF.set_override('local_ip', '10.10.10.10', group='OVS')
|
||||
cfg.CONF.set_override('tunnel_types', [p_const.TYPE_GRE,
|
||||
p_const.TYPE_VXLAN], group='AGENT')
|
||||
cfgmap = self.mod_agent.create_agent_config_map(cfg.CONF)
|
||||
self.assertEqual(cfgmap['tunnel_types'],
|
||||
[p_const.TYPE_GRE, p_const.TYPE_VXLAN])
|
||||
|
||||
|
||||
class TestOFANeutronAgentBridge(ofa_test_base.OFAAgentTestBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOFANeutronAgentBridge, self).setUp()
|
||||
self.br_name = 'bridge1'
|
||||
self.ovs = self.mod_agent.Bridge(
|
||||
self.br_name, self.ryuapp)
|
||||
|
||||
def test_find_datapath_id(self):
|
||||
with mock.patch.object(self.ovs, 'get_datapath_id',
|
||||
return_value='12345'):
|
||||
self.ovs.find_datapath_id()
|
||||
self.assertEqual(self.ovs.datapath_id, '12345')
|
||||
|
||||
def _fake_get_datapath(self, app, datapath_id):
|
||||
if self.ovs.retry_count >= 2:
|
||||
datapath = mock.Mock()
|
||||
datapath.ofproto_parser = mock.Mock()
|
||||
return datapath
|
||||
self.ovs.retry_count += 1
|
||||
return None
|
||||
|
||||
def test_get_datapath_normal(self):
|
||||
self.ovs.retry_count = 0
|
||||
with mock.patch.object(self.mod_agent.ryu_api, 'get_datapath',
|
||||
new=self._fake_get_datapath):
|
||||
self.ovs.datapath_id = '0x64'
|
||||
self.ovs.get_datapath(retry_max=4)
|
||||
self.assertEqual(self.ovs.retry_count, 2)
|
||||
|
||||
def test_get_datapath_retry_out_by_default_time(self):
|
||||
cfg.CONF.set_override('get_datapath_retry_times', 3, group='AGENT')
|
||||
with mock.patch.object(self.mod_agent.ryu_api, 'get_datapath',
|
||||
return_value=None) as mock_get_datapath:
|
||||
with testtools.ExpectedException(SystemExit):
|
||||
self.ovs.datapath_id = '0x64'
|
||||
self.ovs.get_datapath(retry_max=3)
|
||||
self.assertEqual(mock_get_datapath.call_count, 3)
|
||||
|
||||
def test_get_datapath_retry_out_by_specified_time(self):
|
||||
with mock.patch.object(self.mod_agent.ryu_api, 'get_datapath',
|
||||
return_value=None) as mock_get_datapath:
|
||||
with testtools.ExpectedException(SystemExit):
|
||||
self.ovs.datapath_id = '0x64'
|
||||
self.ovs.get_datapath(retry_max=2)
|
||||
self.assertEqual(mock_get_datapath.call_count, 2)
|
||||
|
||||
def test_setup_ofp_default_par(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.ovs, 'set_protocols'),
|
||||
mock.patch.object(self.ovs, 'set_controller'),
|
||||
mock.patch.object(self.ovs, 'find_datapath_id'),
|
||||
mock.patch.object(self.ovs, 'get_datapath'),
|
||||
) as (mock_set_protocols, mock_set_controller,
|
||||
mock_find_datapath_id, mock_get_datapath):
|
||||
self.ovs.setup_ofp()
|
||||
mock_set_protocols.assert_called_with('OpenFlow13')
|
||||
mock_set_controller.assert_called_with(['tcp:127.0.0.1:6633'])
|
||||
mock_get_datapath.assert_called_with(
|
||||
cfg.CONF.AGENT.get_datapath_retry_times)
|
||||
self.assertEqual(mock_find_datapath_id.call_count, 1)
|
||||
|
||||
def test_setup_ofp_specify_par(self):
|
||||
controller_names = ['tcp:192.168.10.10:1234', 'tcp:172.17.16.20:5555']
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.ovs, 'set_protocols'),
|
||||
mock.patch.object(self.ovs, 'set_controller'),
|
||||
mock.patch.object(self.ovs, 'find_datapath_id'),
|
||||
mock.patch.object(self.ovs, 'get_datapath'),
|
||||
) as (mock_set_protocols, mock_set_controller,
|
||||
mock_find_datapath_id, mock_get_datapath):
|
||||
self.ovs.setup_ofp(controller_names=controller_names,
|
||||
protocols='OpenFlow133',
|
||||
retry_max=11)
|
||||
mock_set_protocols.assert_called_with('OpenFlow133')
|
||||
mock_set_controller.assert_called_with(controller_names)
|
||||
mock_get_datapath.assert_called_with(11)
|
||||
self.assertEqual(mock_find_datapath_id.call_count, 1)
|
||||
|
||||
def test_setup_ofp_with_except(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.ovs, 'set_protocols',
|
||||
side_effect=RuntimeError),
|
||||
mock.patch.object(self.ovs, 'set_controller'),
|
||||
mock.patch.object(self.ovs, 'find_datapath_id'),
|
||||
mock.patch.object(self.ovs, 'get_datapath'),
|
||||
) as (mock_set_protocols, mock_set_controller,
|
||||
mock_find_datapath_id, mock_get_datapath):
|
||||
with testtools.ExpectedException(SystemExit):
|
||||
self.ovs.setup_ofp()
|
||||
|
||||
|
||||
class TestOFANeutronAgent(ofa_test_base.OFAAgentTestBase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestOFANeutronAgent, self).setUp()
|
||||
notifier_p = mock.patch(NOTIFIER)
|
||||
notifier_cls = notifier_p.start()
|
||||
self.notifier = mock.Mock()
|
||||
notifier_cls.return_value = self.notifier
|
||||
kwargs = self.mod_agent.create_agent_config_map(cfg.CONF)
|
||||
|
||||
class MockFixedIntervalLoopingCall(object):
|
||||
def __init__(self, f):
|
||||
self.f = f
|
||||
|
||||
def start(self, interval=0):
|
||||
self.f()
|
||||
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.mod_agent.OFANeutronAgent,
|
||||
'setup_integration_br',
|
||||
return_value=mock.Mock()),
|
||||
mock.patch.object(self.mod_agent.Bridge,
|
||||
'get_local_port_mac',
|
||||
return_value='00:00:00:00:00:01'),
|
||||
mock.patch('neutron.agent.linux.utils.get_interface_mac',
|
||||
return_value='00:00:00:00:00:01'),
|
||||
mock.patch('neutron.openstack.common.loopingcall.'
|
||||
'FixedIntervalLoopingCall',
|
||||
new=MockFixedIntervalLoopingCall)):
|
||||
self.agent = self.mod_agent.OFANeutronAgent(self.ryuapp, **kwargs)
|
||||
|
||||
self.agent.sg_agent = mock.Mock()
|
||||
self.int_dp = self._mk_test_dp('int_br')
|
||||
self.agent.int_br = self._mk_test_br('int_br')
|
||||
self.agent.int_br.set_dp(self.int_dp)
|
||||
self.agent.int_ofports['phys-net1'] = 666
|
||||
|
||||
def _create_tunnel_port_name(self, tunnel_ip, tunnel_type):
|
||||
tunnel_ip_hex = '%08x' % netaddr.IPAddress(tunnel_ip, version=4)
|
||||
return '%s-%s' % (tunnel_type, tunnel_ip_hex)
|
||||
|
||||
def mock_scan_ports(self, port_set=None, registered_ports=None,
|
||||
updated_ports=None, port_tags_dict=None):
|
||||
port_tags_dict = port_tags_dict or {}
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent, '_get_ofport_names',
|
||||
return_value=port_set),
|
||||
mock.patch.object(self.agent.int_br, 'get_port_tag_dict',
|
||||
return_value=port_tags_dict)
|
||||
):
|
||||
return self.agent.scan_ports(registered_ports, updated_ports)
|
||||
|
||||
def test_scan_ports_returns_current_only_for_unchanged_ports(self):
|
||||
vif_port_set = set([1, 3])
|
||||
registered_ports = set([1, 3])
|
||||
expected = {'current': vif_port_set}
|
||||
actual = self.mock_scan_ports(vif_port_set, registered_ports)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_scan_ports_returns_port_changes(self):
|
||||
vif_port_set = set([1, 3])
|
||||
registered_ports = set([1, 2])
|
||||
expected = dict(current=vif_port_set, added=set([3]), removed=set([2]))
|
||||
actual = self.mock_scan_ports(vif_port_set, registered_ports)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def _test_scan_ports_with_updated_ports(self, updated_ports):
|
||||
vif_port_set = set([1, 3, 4])
|
||||
registered_ports = set([1, 2, 4])
|
||||
expected = dict(current=vif_port_set, added=set([3]),
|
||||
removed=set([2]), updated=set([4]))
|
||||
actual = self.mock_scan_ports(vif_port_set, registered_ports,
|
||||
updated_ports)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_scan_ports_finds_known_updated_ports(self):
|
||||
self._test_scan_ports_with_updated_ports(set([4]))
|
||||
|
||||
def test_scan_ports_ignores_unknown_updated_ports(self):
|
||||
# the port '5' was not seen on current ports. Hence it has either
|
||||
# never been wired or already removed and should be ignored
|
||||
self._test_scan_ports_with_updated_ports(set([4, 5]))
|
||||
|
||||
def test_scan_ports_ignores_updated_port_if_removed(self):
|
||||
vif_port_set = set([1, 3])
|
||||
registered_ports = set([1, 2])
|
||||
updated_ports = set([1, 2])
|
||||
expected = dict(current=vif_port_set, added=set([3]),
|
||||
removed=set([2]), updated=set([1]))
|
||||
actual = self.mock_scan_ports(vif_port_set, registered_ports,
|
||||
updated_ports)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_scan_ports_no_vif_changes_returns_updated_port_only(self):
|
||||
vif_port_set = set([1, 2, 3])
|
||||
registered_ports = set([1, 2, 3])
|
||||
updated_ports = set([2])
|
||||
expected = dict(current=vif_port_set, updated=set([2]))
|
||||
actual = self.mock_scan_ports(vif_port_set, registered_ports,
|
||||
updated_ports)
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_treat_devices_added_returns_true_for_missing_device(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent.plugin_rpc, 'get_device_details',
|
||||
side_effect=Exception()),
|
||||
mock.patch.object(self.agent, '_get_ports',
|
||||
return_value=[_mock_port(True, 'xxx')])):
|
||||
self.assertTrue(self.agent.treat_devices_added_or_updated(['xxx']))
|
||||
|
||||
def _mock_treat_devices_added_updated(self, details, port, all_ports,
|
||||
func_name):
|
||||
"""Mock treat devices added or updated.
|
||||
|
||||
:param details: the details to return for the device
|
||||
:param port: port name to process
|
||||
:param all_ports: the port that _get_ports return
|
||||
:param func_name: the function that should be called
|
||||
:returns: whether the named function was called
|
||||
"""
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent.plugin_rpc, 'get_device_details',
|
||||
return_value=details),
|
||||
mock.patch.object(self.agent, '_get_ports',
|
||||
return_value=all_ports),
|
||||
mock.patch.object(self.agent.plugin_rpc, 'update_device_up'),
|
||||
mock.patch.object(self.agent.plugin_rpc, 'update_device_down'),
|
||||
mock.patch.object(self.agent, func_name)
|
||||
) as (get_dev_fn, _get_ports, upd_dev_up, upd_dev_down, func):
|
||||
self.assertFalse(self.agent.treat_devices_added_or_updated([port]))
|
||||
_get_ports.assert_called_once_with(self.agent.int_br)
|
||||
return func.called
|
||||
|
||||
def test_treat_devices_added_updated_ignores_invalid_ofport(self):
|
||||
port_name = 'hoge'
|
||||
p1 = _mock_port(True, port_name)
|
||||
p1.ofport = -1
|
||||
self.assertFalse(self._mock_treat_devices_added_updated(
|
||||
mock.MagicMock(), port_name, [p1], 'port_dead'))
|
||||
|
||||
def test_treat_devices_added_updated_marks_unknown_port_as_dead(self):
|
||||
port_name = 'hoge'
|
||||
p1 = _mock_port(True, port_name)
|
||||
p1.ofport = 1
|
||||
self.assertTrue(self._mock_treat_devices_added_updated(
|
||||
mock.MagicMock(), port_name, [p1], 'port_dead'))
|
||||
|
||||
def test_treat_devices_added_does_not_process_missing_port(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent.plugin_rpc, 'get_device_details'),
|
||||
mock.patch.object(self.agent.int_br, 'get_vif_port_by_id',
|
||||
return_value=None)
|
||||
) as (get_dev_fn, get_vif_func):
|
||||
self.assertFalse(get_dev_fn.called)
|
||||
|
||||
def test_treat_devices_added_updated_updates_known_port(self):
|
||||
port_name = 'tapd3315981-0b'
|
||||
p1 = _mock_port(False)
|
||||
p2 = _mock_port(True, port_name)
|
||||
ports = [p1, p2]
|
||||
details = mock.MagicMock()
|
||||
details.__contains__.side_effect = lambda x: True
|
||||
self.assertTrue(self._mock_treat_devices_added_updated(
|
||||
details, port_name, ports, 'treat_vif_port'))
|
||||
|
||||
def test_treat_devices_added_updated_put_port_down(self):
|
||||
fake_details_dict = {'admin_state_up': False,
|
||||
'port_id': 'xxx',
|
||||
'device': 'xxx',
|
||||
'network_id': 'yyy',
|
||||
'physical_network': 'foo',
|
||||
'segmentation_id': 'bar',
|
||||
'network_type': 'baz'}
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent.plugin_rpc, 'get_device_details',
|
||||
return_value=fake_details_dict),
|
||||
mock.patch.object(self.agent, '_get_ports',
|
||||
return_value=[_mock_port(True, 'xxx')]),
|
||||
mock.patch.object(self.agent.plugin_rpc, 'update_device_up'),
|
||||
mock.patch.object(self.agent.plugin_rpc, 'update_device_down'),
|
||||
mock.patch.object(self.agent, 'treat_vif_port')
|
||||
) as (get_dev_fn, _get_ports, upd_dev_up,
|
||||
upd_dev_down, treat_vif_port):
|
||||
self.assertFalse(self.agent.treat_devices_added_or_updated(
|
||||
['xxx']))
|
||||
self.assertTrue(treat_vif_port.called)
|
||||
self.assertTrue(upd_dev_down.called)
|
||||
_get_ports.assert_called_once_with(self.agent.int_br)
|
||||
|
||||
def test_treat_devices_removed_returns_true_for_missing_device(self):
|
||||
with mock.patch.object(self.agent.plugin_rpc, 'update_device_down',
|
||||
side_effect=Exception()):
|
||||
self.assertTrue(self.agent.treat_devices_removed([{}]))
|
||||
|
||||
def _mock_treat_devices_removed(self, port_exists):
|
||||
details = dict(exists=port_exists)
|
||||
with mock.patch.object(self.agent.plugin_rpc, 'update_device_down',
|
||||
return_value=details):
|
||||
with mock.patch.object(self.agent, 'port_unbound') as port_unbound:
|
||||
self.assertFalse(self.agent.treat_devices_removed([{}]))
|
||||
self.assertTrue(port_unbound.called)
|
||||
|
||||
def test_treat_devices_removed_unbinds_port(self):
|
||||
self._mock_treat_devices_removed(True)
|
||||
|
||||
def test_treat_devices_removed_ignores_missing_port(self):
|
||||
self._mock_treat_devices_removed(False)
|
||||
|
||||
def _test_process_network_ports(self, port_info):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent.sg_agent, "setup_port_filters"),
|
||||
mock.patch.object(self.agent, "treat_devices_added_or_updated",
|
||||
return_value=False),
|
||||
mock.patch.object(self.agent, "treat_devices_removed",
|
||||
return_value=False)
|
||||
) as (setup_port_filters, device_added_updated, device_removed):
|
||||
self.assertFalse(self.agent.process_network_ports(port_info))
|
||||
setup_port_filters.assert_called_once_with(
|
||||
port_info['added'], port_info.get('updated', set()))
|
||||
device_added_updated.assert_called_once_with(
|
||||
port_info['added'] | port_info.get('updated', set()))
|
||||
device_removed.assert_called_once_with(port_info['removed'])
|
||||
|
||||
def test_process_network_ports(self):
|
||||
self._test_process_network_ports(
|
||||
{'current': set(['tap0']),
|
||||
'removed': set(['eth0']),
|
||||
'added': set(['eth1'])})
|
||||
|
||||
def test_process_network_port_with_updated_ports(self):
|
||||
self._test_process_network_ports(
|
||||
{'current': set(['tap0', 'tap1']),
|
||||
'updated': set(['tap1', 'eth1']),
|
||||
'removed': set(['eth0']),
|
||||
'added': set(['eth1'])})
|
||||
|
||||
def test_report_state(self):
|
||||
with mock.patch.object(self.agent.state_rpc,
|
||||
"report_state") as report_st:
|
||||
self.agent.int_br_device_count = 5
|
||||
self.agent._report_state()
|
||||
report_st.assert_called_with(self.agent.context,
|
||||
self.agent.agent_state)
|
||||
self.assertNotIn("start_flag", self.agent.agent_state)
|
||||
self.assertEqual(
|
||||
self.agent.agent_state["configurations"]["devices"],
|
||||
self.agent.int_br_device_count
|
||||
)
|
||||
|
||||
def test_port_update(self):
|
||||
port = {"id": "b1981919-f516-11e3-a8f4-08606e7f74e7",
|
||||
"network_id": "124",
|
||||
"admin_state_up": False}
|
||||
self.agent.port_update("unused_context",
|
||||
port=port,
|
||||
network_type="vlan",
|
||||
segmentation_id="1",
|
||||
physical_network="physnet")
|
||||
self.assertEqual(set(['tapb1981919-f5']), self.agent.updated_ports)
|
||||
|
||||
def test_setup_physical_interfaces(self):
|
||||
with mock.patch.object(self.agent.int_br, "add_port") as add_port_fn:
|
||||
add_port_fn.return_value = "111"
|
||||
self.agent.setup_physical_interfaces({"physnet1": "eth1"})
|
||||
add_port_fn.assert_called_once_with("eth1")
|
||||
self.assertEqual(111, self.agent.int_ofports["physnet1"])
|
||||
|
||||
def test_port_unbound(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent, "reclaim_local_vlan"),
|
||||
mock.patch.object(self.agent, "get_net_uuid",
|
||||
return_value="netuid12345"),
|
||||
) as (reclvl_fn, _):
|
||||
self.agent.enable_tunneling = True
|
||||
lvm = mock.Mock()
|
||||
lvm.network_type = "gre"
|
||||
lvm.vif_ports = {"vif1": mock.Mock()}
|
||||
self.agent.local_vlan_map["netuid12345"] = lvm
|
||||
self.agent.port_unbound("vif1")
|
||||
self.assertTrue(reclvl_fn.called)
|
||||
|
||||
def _prepare_l2_pop_ofports(self, network_type=None):
|
||||
LVM = collections.namedtuple('LVM', 'net, vlan, segid, ip')
|
||||
self.lvms = [LVM(net='net1', vlan=11, segid=21, ip='1.1.1.1'),
|
||||
LVM(net='net2', vlan=12, segid=22, ip='2.2.2.2')]
|
||||
self.tunnel_type = 'gre'
|
||||
self.tun_name1 = self._create_tunnel_port_name(self.lvms[0].ip,
|
||||
self.tunnel_type)
|
||||
self.tun_name2 = self._create_tunnel_port_name(self.lvms[1].ip,
|
||||
self.tunnel_type)
|
||||
if network_type is None:
|
||||
network_type = self.tunnel_type
|
||||
lvm1 = mock.Mock()
|
||||
lvm1.network_type = network_type
|
||||
lvm1.vlan = self.lvms[0].vlan
|
||||
lvm1.segmentation_id = self.lvms[0].segid
|
||||
lvm1.tun_ofports = set([1])
|
||||
lvm2 = mock.Mock()
|
||||
lvm2.network_type = network_type
|
||||
lvm2.vlan = self.lvms[1].vlan
|
||||
lvm2.segmentation_id = self.lvms[1].segid
|
||||
lvm2.tun_ofports = set([1, 2])
|
||||
self.agent.tunnel_types = [self.tunnel_type]
|
||||
self.agent.local_vlan_map = {self.lvms[0].net: lvm1,
|
||||
self.lvms[1].net: lvm2}
|
||||
self.agent.tun_ofports = {self.tunnel_type:
|
||||
{self.lvms[0].ip: 1,
|
||||
self.lvms[1].ip: 2}}
|
||||
|
||||
def test_fdb_ignore_network(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
fdb_entry = {'net3': {}}
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent, '_setup_tunnel_port'),
|
||||
mock.patch.object(self.agent, 'cleanup_tunnel_port')
|
||||
) as (add_tun_fn, clean_tun_fn):
|
||||
self.agent.fdb_add(None, fdb_entry)
|
||||
self.assertFalse(add_tun_fn.called)
|
||||
self.agent.fdb_remove(None, fdb_entry)
|
||||
self.assertFalse(clean_tun_fn.called)
|
||||
|
||||
def test_fdb_ignore_self(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
self.agent.local_ip = 'agent_ip'
|
||||
fdb_entry = {self.lvms[1].net:
|
||||
{'network_type': self.tunnel_type,
|
||||
'segment_id': 'tun2',
|
||||
'ports':
|
||||
{'agent_ip':
|
||||
[l2pop_rpc.PortInfo('mac', 'ip'),
|
||||
FLOODING_ENTRY]}}}
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent.ryuapp, "add_arp_table_entry"),
|
||||
mock.patch.object(self.agent.ryuapp, "del_arp_table_entry"),
|
||||
) as (add_fn, del_fn):
|
||||
self.agent.fdb_add(None, copy.deepcopy(fdb_entry))
|
||||
add_fn.assert_called_once_with(12, 'ip', 'mac')
|
||||
self.assertFalse(del_fn.called)
|
||||
self.agent.fdb_remove(None, fdb_entry)
|
||||
add_fn.assert_called_once_with(12, 'ip', 'mac')
|
||||
del_fn.assert_called_once_with(12, 'ip')
|
||||
|
||||
def test_fdb_add_flows(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
fdb_entry = {self.lvms[0].net:
|
||||
{'network_type': self.tunnel_type,
|
||||
'segment_id': 'tun1',
|
||||
'ports':
|
||||
{self.lvms[1].ip:
|
||||
[l2pop_rpc.PortInfo('mac', 'ip'),
|
||||
FLOODING_ENTRY]}}}
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent, '_setup_tunnel_port'),
|
||||
mock.patch.object(self.agent.int_br, 'install_tunnel_output'),
|
||||
mock.patch.object(self.agent.int_br, 'delete_tunnel_output'),
|
||||
) as (add_tun_fn, install_fn, delete_fn):
|
||||
add_tun_fn.return_value = 2
|
||||
self.agent.fdb_add(None, fdb_entry)
|
||||
self.assertEqual(2, install_fn.call_count)
|
||||
expected_calls = [
|
||||
mock.call(7, 11, 21, set([2]), eth_dst='mac', goto_next=False),
|
||||
mock.call(10, 11, 21, set([1, 2]), goto_next=True)
|
||||
]
|
||||
install_fn.assert_has_calls(expected_calls)
|
||||
self.assertFalse(delete_fn.called)
|
||||
|
||||
def test_fdb_del_flows(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
fdb_entry = {self.lvms[1].net:
|
||||
{'network_type': self.tunnel_type,
|
||||
'segment_id': 'tun2',
|
||||
'ports':
|
||||
{self.lvms[1].ip:
|
||||
[l2pop_rpc.PortInfo('mac', 'ip'),
|
||||
FLOODING_ENTRY]}}}
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent.int_br, 'install_tunnel_output'),
|
||||
mock.patch.object(self.agent.int_br, 'delete_tunnel_output'),
|
||||
) as (install_fn, delete_fn):
|
||||
self.agent.fdb_remove(None, fdb_entry)
|
||||
install_fn.assert_called_once_with(10, 12, 22, set([1]),
|
||||
goto_next=True)
|
||||
delete_fn.assert_called_once_with(7, 12, eth_dst='mac')
|
||||
|
||||
def test_fdb_add_port(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
tunnel_ip = '10.10.10.10'
|
||||
tun_name = self._create_tunnel_port_name(tunnel_ip,
|
||||
self.tunnel_type)
|
||||
fdb_entry = {self.lvms[0].net:
|
||||
{'network_type': self.tunnel_type,
|
||||
'segment_id': 'tun1',
|
||||
'ports': {self.lvms[0].ip: [l2pop_rpc.PortInfo('mac',
|
||||
'ip')]}}}
|
||||
with mock.patch.object(self.agent, '_setup_tunnel_port') as add_tun_fn:
|
||||
self.agent.fdb_add(None, fdb_entry)
|
||||
self.assertFalse(add_tun_fn.called)
|
||||
fdb_entry[self.lvms[0].net]['ports'][tunnel_ip] = [
|
||||
l2pop_rpc.PortInfo('mac', 'ip')]
|
||||
self.agent.fdb_add(None, fdb_entry)
|
||||
add_tun_fn.assert_called_with(
|
||||
self.agent.int_br, tun_name, tunnel_ip, self.tunnel_type)
|
||||
|
||||
def test_fdb_del_port(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
fdb_entry = {self.lvms[1].net:
|
||||
{'network_type': self.tunnel_type,
|
||||
'segment_id': 'tun2',
|
||||
'ports': {self.lvms[1].ip: [FLOODING_ENTRY]}}}
|
||||
with mock.patch.object(self.agent.int_br,
|
||||
'delete_port') as del_port_fn:
|
||||
self.agent.fdb_remove(None, fdb_entry)
|
||||
del_port_fn.assert_called_once_with(self.tun_name2)
|
||||
|
||||
def test_add_arp_table_entry(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
fdb_entry = {self.lvms[0].net:
|
||||
{'network_type': self.tunnel_type,
|
||||
'segment_id': 'tun1',
|
||||
'ports': {self.lvms[0].ip: [
|
||||
FLOODING_ENTRY,
|
||||
l2pop_rpc.PortInfo('mac1', 'ip1')],
|
||||
self.lvms[1].ip: [
|
||||
l2pop_rpc.PortInfo('mac2', 'ip2')],
|
||||
'192.0.2.1': [
|
||||
FLOODING_ENTRY,
|
||||
l2pop_rpc.PortInfo('mac3', 'ip3')]}}}
|
||||
with mock.patch.object(self.agent,
|
||||
'setup_tunnel_port') as setup_tun_fn:
|
||||
self.agent.fdb_add(None, fdb_entry)
|
||||
calls = [
|
||||
mock.call(self.agent.local_vlan_map[self.lvms[0].net].vlan,
|
||||
'ip1', 'mac1'),
|
||||
mock.call(self.agent.local_vlan_map[self.lvms[0].net].vlan,
|
||||
'ip2', 'mac2')
|
||||
]
|
||||
self.ryuapp.add_arp_table_entry.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
setup_tun_fn.assert_called_once_with(self.agent.int_br,
|
||||
'192.0.2.1', 'gre')
|
||||
|
||||
def _test_add_arp_table_entry_non_tunnel(self, network_type):
|
||||
self._prepare_l2_pop_ofports(network_type=network_type)
|
||||
fdb_entry = {self.lvms[0].net:
|
||||
{'network_type': network_type,
|
||||
'segment_id': 'tun1',
|
||||
'ports': {self.lvms[0].ip: [
|
||||
FLOODING_ENTRY,
|
||||
l2pop_rpc.PortInfo('mac1', 'ip1')],
|
||||
self.lvms[1].ip: [
|
||||
l2pop_rpc.PortInfo('mac2', 'ip2')],
|
||||
'192.0.2.1': [
|
||||
FLOODING_ENTRY,
|
||||
l2pop_rpc.PortInfo('mac3', 'ip3')]}}}
|
||||
with mock.patch.object(self.agent,
|
||||
'setup_tunnel_port') as setup_tun_fn:
|
||||
self.agent.fdb_add(None, fdb_entry)
|
||||
calls = [
|
||||
mock.call(self.agent.local_vlan_map[self.lvms[0].net].vlan,
|
||||
'ip1', 'mac1'),
|
||||
mock.call(self.agent.local_vlan_map[self.lvms[0].net].vlan,
|
||||
'ip2', 'mac2')
|
||||
]
|
||||
self.ryuapp.add_arp_table_entry.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
self.assertFalse(setup_tun_fn.called)
|
||||
|
||||
def test_add_arp_table_entry_vlan(self):
|
||||
self._test_add_arp_table_entry_non_tunnel('vlan')
|
||||
|
||||
def test_add_arp_table_entry_flat(self):
|
||||
self._test_add_arp_table_entry_non_tunnel('flat')
|
||||
|
||||
def test_add_arp_table_entry_local(self):
|
||||
self._test_add_arp_table_entry_non_tunnel('local')
|
||||
|
||||
def test_del_arp_table_entry(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
fdb_entry = {self.lvms[0].net:
|
||||
{'network_type': self.tunnel_type,
|
||||
'segment_id': 'tun1',
|
||||
'ports': {self.lvms[0].ip: [
|
||||
FLOODING_ENTRY,
|
||||
l2pop_rpc.PortInfo('mac1', 'ip1')],
|
||||
self.lvms[1].ip: [
|
||||
l2pop_rpc.PortInfo('mac2', 'ip2')],
|
||||
'192.0.2.1': [
|
||||
FLOODING_ENTRY,
|
||||
l2pop_rpc.PortInfo('mac3', 'ip3')]}}}
|
||||
with mock.patch.object(self.agent,
|
||||
'cleanup_tunnel_port') as cleanup_tun_fn:
|
||||
self.agent.fdb_remove(None, fdb_entry)
|
||||
calls = [
|
||||
mock.call(self.agent.local_vlan_map[self.lvms[0].net].vlan,
|
||||
'ip1'),
|
||||
mock.call(self.agent.local_vlan_map[self.lvms[0].net].vlan,
|
||||
'ip2')
|
||||
]
|
||||
self.ryuapp.del_arp_table_entry.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
cleanup_tun_fn.assert_called_once_with(self.agent.int_br, 1, 'gre')
|
||||
|
||||
def _test_del_arp_table_entry_non_tunnel(self, network_type):
|
||||
self._prepare_l2_pop_ofports(network_type=network_type)
|
||||
fdb_entry = {self.lvms[0].net:
|
||||
{'network_type': network_type,
|
||||
'segment_id': 'tun1',
|
||||
'ports': {self.lvms[0].ip: [
|
||||
FLOODING_ENTRY,
|
||||
l2pop_rpc.PortInfo('mac1', 'ip1')],
|
||||
self.lvms[1].ip: [
|
||||
l2pop_rpc.PortInfo('mac2', 'ip2')],
|
||||
'192.0.2.1': [
|
||||
FLOODING_ENTRY,
|
||||
l2pop_rpc.PortInfo('mac3', 'ip3')]}}}
|
||||
with mock.patch.object(self.agent,
|
||||
'cleanup_tunnel_port') as cleanup_tun_fn:
|
||||
self.agent.fdb_remove(None, fdb_entry)
|
||||
calls = [
|
||||
mock.call(self.agent.local_vlan_map[self.lvms[0].net].vlan,
|
||||
'ip1'),
|
||||
mock.call(self.agent.local_vlan_map[self.lvms[0].net].vlan,
|
||||
'ip2')
|
||||
]
|
||||
self.ryuapp.del_arp_table_entry.assert_has_calls(calls,
|
||||
any_order=True)
|
||||
self.assertFalse(cleanup_tun_fn.called)
|
||||
|
||||
def test_del_arp_table_entry_vlan(self):
|
||||
self._test_del_arp_table_entry_non_tunnel('vlan')
|
||||
|
||||
def test_del_arp_table_entry_flat(self):
|
||||
self._test_del_arp_table_entry_non_tunnel('flat')
|
||||
|
||||
def test_del_arp_table_entry_local(self):
|
||||
self._test_del_arp_table_entry_non_tunnel('local')
|
||||
|
||||
def test_recl_lv_port_to_preserve(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
self.agent.enable_tunneling = True
|
||||
with mock.patch.object(
|
||||
self.agent.int_br, 'delete_port'
|
||||
) as del_port_fn:
|
||||
self.agent.reclaim_local_vlan(self.lvms[0].net)
|
||||
self.assertFalse(del_port_fn.called)
|
||||
|
||||
def test_recl_lv_port_to_remove(self):
|
||||
self._prepare_l2_pop_ofports()
|
||||
self.agent.enable_tunneling = True
|
||||
with mock.patch.object(self.agent.int_br,
|
||||
'delete_port') as del_port_fn:
|
||||
self.agent.reclaim_local_vlan(self.lvms[1].net)
|
||||
del_port_fn.assert_called_once_with(self.tun_name2)
|
||||
|
||||
def test__setup_tunnel_port_error_negative(self):
|
||||
with contextlib.nested(
|
||||
mock.patch.object(self.agent.int_br, 'add_tunnel_port',
|
||||
return_value=ovs_lib.INVALID_OFPORT),
|
||||
mock.patch.object(self.mod_agent.LOG, 'error')
|
||||
) as (add_tunnel_port_fn, log_error_fn):
|
||||
ofport = self.agent._setup_tunnel_port(
|
||||
self.agent.int_br, 'gre-1', 'remote_ip', p_const.TYPE_GRE)
|
||||
add_tunnel_port_fn.assert_called_once_with(
|
||||
'gre-1', 'remote_ip', self.agent.local_ip, p_const.TYPE_GRE,
|
||||
self.agent.vxlan_udp_port, self.agent.dont_fragment)
|
||||
log_error_fn.assert_called_once_with(
|
||||
_("Failed to set-up %(type)s tunnel port to %(ip)s"),
|
||||
{'type': p_const.TYPE_GRE, 'ip': 'remote_ip'})
|
||||
self.assertEqual(ofport, 0)
|
||||
|
||||
def test_setup_tunnel_port_returns_zero_for_failed_port_add(self):
|
||||
with mock.patch.object(self.agent.int_br, 'add_tunnel_port',
|
||||
return_value=ovs_lib.INVALID_OFPORT):
|
||||
result = self.agent._setup_tunnel_port(self.agent.int_br, 'gre-1',
|
||||
'remote_ip',
|
||||
p_const.TYPE_GRE)
|
||||
self.assertEqual(0, result)
|
||||
|
||||
def test_tunnel_sync(self):
|
||||
self.agent.local_ip = 'agent_ip'
|
||||
self.agent.context = 'fake_context'
|
||||
self.agent.tunnel_types = ['vxlan']
|
||||
self.agent.host = cfg.CONF.host
|
||||
with mock.patch.object(
|
||||
self.agent.plugin_rpc, 'tunnel_sync'
|
||||
) as tunnel_sync_rpc_fn:
|
||||
self.agent.tunnel_sync()
|
||||
tunnel_sync_rpc_fn.assert_called_once_with(
|
||||
self.agent.context,
|
||||
self.agent.local_ip,
|
||||
self.agent.tunnel_types[0],
|
||||
self.agent.host)
|
||||
|
||||
def test__get_ports(self):
|
||||
ofpp = importutils.import_module('ryu.ofproto.ofproto_v1_3_parser')
|
||||
reply = [ofpp.OFPPortDescStatsReply(body=[ofpp.OFPPort(name='hoge',
|
||||
port_no=8)])]
|
||||
sendmsg = mock.Mock(return_value=reply)
|
||||
self.mod_agent.ryu_api.send_msg = sendmsg
|
||||
result = self.agent._get_ports(self.agent.int_br)
|
||||
result = list(result) # convert generator to list.
|
||||
self.assertEqual(1, len(result))
|
||||
self.assertEqual('hoge', result[0].port_name)
|
||||
self.assertEqual(8, result[0].ofport)
|
||||
expected_msg = ofpp.OFPPortDescStatsRequest(
|
||||
datapath=self.agent.int_br.datapath)
|
||||
sendmsg.assert_has_calls([mock.call(app=self.agent.ryuapp,
|
||||
msg=expected_msg, reply_cls=ofpp.OFPPortDescStatsReply,
|
||||
reply_multi=True)])
|
||||
|
||||
def test__get_ofport_names(self):
|
||||
names = ['p111', 'p222', 'p333']
|
||||
ps = [_mock_port(True, x) for x in names]
|
||||
with mock.patch.object(self.agent, '_get_ports',
|
||||
return_value=ps) as _get_ports:
|
||||
result = self.agent._get_ofport_names('hoge')
|
||||
_get_ports.assert_called_once_with('hoge')
|
||||
self.assertEqual(set(names), result)
|
@ -1,54 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# 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
|
||||
|
||||
from neutron.common import constants as n_const
|
||||
from neutron.plugins.ofagent.agent import ports
|
||||
from neutron.tests import base
|
||||
|
||||
|
||||
class TestOFAgentPorts(base.BaseTestCase):
|
||||
def test_port(self):
|
||||
name = 'foo03b9a237-0b'
|
||||
p1 = ports.Port(port_name=name, ofport=999)
|
||||
ryu_ofp_port = mock.Mock(port_no=999)
|
||||
ryu_ofp_port.name = name
|
||||
p2 = ports.Port.from_ofp_port(ofp_port=ryu_ofp_port)
|
||||
self.assertEqual(p1.port_name, p2.port_name)
|
||||
self.assertEqual(p1.ofport, p2.ofport)
|
||||
self.assertFalse(p1.is_neutron_port())
|
||||
self.assertFalse(p2.is_neutron_port())
|
||||
|
||||
def test_neutron_port(self):
|
||||
for pref in ['qvo', 'qr-', 'qg-', n_const.TAP_DEVICE_PREFIX]:
|
||||
name = pref + '03b9a237-0b'
|
||||
p1 = ports.Port(port_name=name, ofport=999)
|
||||
ryu_ofp_port = mock.Mock(port_no=999)
|
||||
ryu_ofp_port.name = name
|
||||
p2 = ports.Port.from_ofp_port(ofp_port=ryu_ofp_port)
|
||||
self.assertEqual(p1.port_name, p2.port_name)
|
||||
self.assertEqual(p1.ofport, p2.ofport)
|
||||
self.assertTrue(p1.is_neutron_port())
|
||||
self.assertTrue(p2.is_neutron_port())
|
||||
self.assertTrue('tap03b9a237-0b', p1.normalized_port_name())
|
||||
self.assertTrue('tap03b9a237-0b', p2.normalized_port_name())
|
||||
|
||||
def test_get_normalized_port_name(self):
|
||||
self.assertEqual('tap03b9a237-0b',
|
||||
ports.get_normalized_port_name(
|
||||
'03b9a237-0b1b-11e4-b537-08606e7f74e7'))
|
@ -1,82 +0,0 @@
|
||||
# Copyright (C) 2014 VA Linux Systems Japan K.K.
|
||||
# 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
|
||||
from oslo_utils import importutils
|
||||
|
||||
from neutron.tests.unit.ofagent import ofa_test_base
|
||||
|
||||
|
||||
class TestOFAgentFlows(ofa_test_base.OFATestBase):
|
||||
|
||||
_MOD = 'neutron.plugins.ofagent.agent.ofswitch'
|
||||
|
||||
def setUp(self):
|
||||
super(TestOFAgentFlows, self).setUp()
|
||||
self.mod = importutils.import_module(self._MOD)
|
||||
self.br = self.mod.OpenFlowSwitch()
|
||||
self.br.set_dp(self._mk_test_dp("dp"))
|
||||
|
||||
def test_delete_flows(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.delete_flows()
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, command=ofp.OFPFC_DELETE,
|
||||
match=ofpp.OFPMatch(), out_group=ofp.OFPG_ANY,
|
||||
out_port=ofp.OFPP_ANY, priority=0, table_id=ofp.OFPTT_ALL)),
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_install_default_drop(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.install_default_drop(table_id=98)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, priority=0, table_id=98)),
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_install_default_goto(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.install_default_goto(table_id=98, dest_table_id=150)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=150)],
|
||||
priority=0, table_id=98)),
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
||||
|
||||
def test_install_default_goto_next(self):
|
||||
br = self.br
|
||||
with mock.patch.object(br, '_send_msg') as sendmsg:
|
||||
br.install_default_goto_next(table_id=100)
|
||||
(dp, ofp, ofpp) = br._get_dp()
|
||||
call = mock.call
|
||||
expected_calls = [
|
||||
call(ofpp.OFPFlowMod(dp, instructions=[
|
||||
ofpp.OFPInstructionGotoTable(table_id=101)],
|
||||
priority=0, table_id=100)),
|
||||
]
|
||||
sendmsg.assert_has_calls(expected_calls)
|
@ -38,7 +38,6 @@ data_files =
|
||||
etc/neutron/rootwrap.d/l3.filters
|
||||
etc/neutron/rootwrap.d/linuxbridge-plugin.filters
|
||||
etc/neutron/rootwrap.d/nec-plugin.filters
|
||||
etc/neutron/rootwrap.d/ofagent.filters
|
||||
etc/neutron/rootwrap.d/openvswitch-plugin.filters
|
||||
etc/init.d = etc/init.d/neutron-server
|
||||
etc/neutron/plugins/bigswitch =
|
||||
@ -110,7 +109,6 @@ console_scripts =
|
||||
neutron-rootwrap = oslo_rootwrap.cmd:main
|
||||
neutron-usage-audit = neutron.cmd.usage_audit:main
|
||||
neutron-metering-agent = neutron.cmd.eventlet.services.metering_agent:main
|
||||
neutron-ofagent-agent = neutron.plugins.ofagent.agent.main:main
|
||||
neutron-sriov-nic-agent = neutron.plugins.sriovnicagent.sriov_nic_agent:main
|
||||
neutron-sanity-check = neutron.cmd.sanity_check:main
|
||||
neutron.core_plugins =
|
||||
@ -174,7 +172,7 @@ neutron.ml2.mechanism_drivers =
|
||||
cisco_apic = neutron.plugins.ml2.drivers.cisco.apic.mechanism_apic:APICMechanismDriver
|
||||
l2population = neutron.plugins.ml2.drivers.l2pop.mech_driver:L2populationMechanismDriver
|
||||
bigswitch = neutron.plugins.ml2.drivers.mech_bigswitch.driver:BigSwitchMechanismDriver
|
||||
ofagent = neutron.plugins.ml2.drivers.mech_ofagent:OfagentMechanismDriver
|
||||
ofagent = networking_ofagent.plugins.ml2.drivers.mech_ofagent:OfagentMechanismDriver
|
||||
mlnx = neutron.plugins.ml2.drivers.mlnx.mech_mlnx:MlnxMechanismDriver
|
||||
brocade = neutron.plugins.ml2.drivers.brocade.mechanism_brocade:BrocadeMechanism
|
||||
fslsdn = neutron.plugins.ml2.drivers.freescale.mechanism_fslsdn:FslsdnMechanismDriver
|
||||
|
Loading…
Reference in New Issue
Block a user